{"thread":{"id":"8718","subject":"[PATCH 3/3] Teach rebase -i about --preserve-merges","startedAt":"2007-06-25T17:59:43Z","lastAt":"2007-06-26T02:14:26Z","messageCount":3,"participants":["Johannes Schindelin","Jakub Narebski"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"45783","messageId":"Pine.LNX.4.64.0706251859270.4059@racer.site","threadId":"8718","inReplyTo":null,"subject":"[PATCH 3/3] Teach rebase -i about --preserve-merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-06-25T17:59:43Z","receivedAt":"2007-06-25T17:59:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nThe option \"-p\" (or long \"--preserve-merges\") makes it possible to\nrebase side branches including merges, without straightening the\nhistory.\n\nExample:\n\n           X\n            \\\n         A---M---B\n        /\n---o---O---P---Q\n\nWhen the current HEAD is \"B\", \"git rebase -i -p --onto Q O\" will yield\n\n               X\n                 \\\n---o---O---P---Q---A'---M'---B'\n\nNote that this will\n\n- _not_ touch X [*1*], it does\n\n- _not_ work without the --interactive flag [*2*], it does\n\n- _not_ guess the type of the merge, but blindly uses recursive or\n  whatever strategy you provided with \"-s <strategy>\" for all merges it\n  has to redo, and it does\n\n- _not_ make use of the original merge commit via git-rerere.\n\n*1*: only commits which reach a merge base between <upstream> and HEAD\n     are reapplied. The others are kept as-are.\n\n*2*: git-rebase without --interactive is inherently patch based (at\n     least at the moment), and therefore merges cannot be preserved.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tThis (especially the part about passing the message to git merge,\n\tyou know, correctly quoted and stuff) could use a couple of\n\teyeball pairs.\n\n Documentation/git-rebase.txt  |   23 ++++++++-\n git-rebase--interactive.sh    |  110 +++++++++++++++++++++++++++++++++++++++-\n t/t3404-rebase-interactive.sh |   22 ++++++++\n 3 files changed, 151 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 2e3363a..96907d4 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -9,7 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git-rebase' [-i | --interactive] [-v | --verbose] [--merge] [-C<n>]\n-\t[--onto <newbase>] <upstream> [<branch>]\n+\t[-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>]\n 'git-rebase' --continue | --skip | --abort\n \n DESCRIPTION\n@@ -213,6 +213,10 @@ OPTIONS\n \tMake a list of the commits which are about to be rebased.  Let the\n \tuser edit that list before rebasing.\n \n+-p, \\--preserve-merges::\n+\tInstead of ignoring merges, try to recreate them.  This option\n+\tonly works in interactive mode.\n+\n include::merge-strategies.txt[]\n \n NOTES\n@@ -304,6 +308,23 @@ $ git rebase -i HEAD~5\n \n And move the first patch to the end of the list.\n \n+You might want to preserve merges, if you have a history like this:\n+\n+------------------\n+           X\n+            \\\n+         A---M---B\n+        /\n+---o---O---P---Q\n+------------------\n+\n+Suppose you want to rebase the side branch starting at \"A\" to \"Q\". Make\n+sure that the current HEAD is \"B\", and call\n+\n+-----------------------------\n+$ git rebase -i -p --onto Q O\n+-----------------------------\n+\n Authors\n ------\n Written by Junio C Hamano <junkio@cox.net> and\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex fb93e13..73cbcde 100755\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -10,7 +10,8 @@\n # The original idea comes from Eric W. Biederman, in\n # http://article.gmane.org/gmane.comp.version-control.git/22407\n \n-USAGE='(--continue | --abort | --skip | [--onto <branch>] <upstream> [<branch>])'\n+USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]\n+\t[--onto <branch>] <upstream> [<branch>])'\n \n . git-sh-setup\n require_work_tree\n@@ -18,6 +19,8 @@ require_work_tree\n DOTEST=\"$GIT_DIR/.dotest-merge\"\n TODO=\"$DOTEST\"/todo\n DONE=\"$DOTEST\"/done\n+REWRITTEN=\"$DOTEST\"/rewritten\n+PRESERVE_MERGES=\n STRATEGY=\n VERBOSE=\n \n@@ -68,6 +71,8 @@ die_abort () {\n pick_one () {\n \tcase \"$1\" in -n) sha1=$2 ;; *) sha1=$1 ;; esac\n \tgit rev-parse --verify $sha1 || die \"Invalid commit name: $sha1\"\n+\ttest -d \"$REWRITTEN\" &&\n+\t\tpick_one_preserving_merges \"$@\" && return\n \tparent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)\n \tcurrent_sha1=$(git rev-parse --verify HEAD)\n \tif [ $current_sha1 = $parent_sha1 ]; then\n@@ -79,6 +84,75 @@ pick_one () {\n \tfi\n }\n \n+pick_one_preserving_merges () {\n+\tcase \"$1\" in -n) sha1=$2 ;; *) sha1=$1 ;; esac\n+\tsha1=$(git rev-parse $sha1)\n+\n+\tif [ -f \"$DOTEST\"/current-commit ]\n+\tthen\n+\t\tcurrent_commit=$(cat \"$DOTEST\"/current-commit) &&\n+\t\tgit rev-parse HEAD > \"$REWRITTEN\"/$current_commit &&\n+\t\trm \"$DOTEST\"/current-commit ||\n+\t\tdie \"Cannot write current commit's replacement sha1\"\n+\tfi\n+\n+\t# rewrite parents; if none were rewritten, we can fast-forward.\n+\tfast_forward=t\n+\tpreserve=t\n+\tnew_parents=\n+\tfor p in $(git rev-list --parents -1 $sha1 | cut -d\\  -f2-)\n+\tdo\n+\t\tif [ -f \"$REWRITTEN\"/$p ]\n+\t\tthen\n+\t\t\tpreserve=f\n+\t\t\tnew_p=$(cat \"$REWRITTEN\"/$p)\n+\t\t\ttest $p != $new_p && fast_forward=f\n+\t\t\tcase \"$new_parents\" in\n+\t\t\t*$new_p*)\n+\t\t\t\t;; # do nothing; that parent is already there\n+\t\t\t*)\n+\t\t\t\tnew_parents=\"$new_parents $new_p\"\n+\t\t\tesac\n+\t\tfi\n+\tdone\n+\tcase $fast_forward in\n+\tt)\n+\t\techo \"Fast forward to $sha1\"\n+\t\ttest $preserve=f && echo $sha1 > \"$REWRITTEN\"/$sha1\n+\t\t;;\n+\tf)\n+\t\ttest \"a$1\" = a-n && die \"Refusing to squash a merge: $sha1\"\n+\n+\t\tfirst_parent=$(expr \"$new_parents\" : \" \\([^ ]*\\)\")\n+\t\t# detach HEAD to current parent\n+\t\tgit checkout $first_parent 2> /dev/null ||\n+\t\t\tdie \"Cannot move HEAD to $first_parent\"\n+\n+\t\techo $sha1 > \"$DOTEST\"/current-commit\n+\t\tcase \"$new_parents\" in\n+\t\t\\ *\\ *)\n+\t\t\t# redo merge\n+\t\t\tauthor_script=$(get_author_ident_from_commit $sha1)\n+\t\t\teval \"$author_script\"\n+\t\t\tmsg=\"$(git cat-file commit $sha1 | \\\n+\t\t\t\tsed -e '1,/^$/d' -e \"s/[\\\"\\\\]/\\\\\\\\&/g\")\"\n+\t\t\t# NEEDSWORK: give rerere a chance\n+\t\t\tif ! git merge $STRATEGY -m \"$msg\" $new_parents\n+\t\t\tthen\n+\t\t\t\techo \"$msg\" > \"$GIT_DIR\"/MERGE_MSG\n+\t\t\t\twarn Error redoing merge $sha1\n+\t\t\t\twarn\n+\t\t\t\twarn After fixup, please use\n+\t\t\t\tdie \"$author_script git commit\"\n+\t\t\tfi\n+\t\t\t;;\n+\t\t*)\n+\t\t\tgit cherry-pick $STRATEGY \"$@\" ||\n+\t\t\t\tdie_with_patch $sha1 \"Could not pick $sha1\"\n+\t\tesac\n+\tesac\n+}\n+\n do_next () {\n \tread command sha1 rest < \"$TODO\"\n \tcase \"$command\" in\n@@ -155,7 +229,15 @@ do_next () {\n \tHEADNAME=$(cat \"$DOTEST\"/head-name) &&\n \tOLDHEAD=$(cat \"$DOTEST\"/head) &&\n \tSHORTONTO=$(git rev-parse --short $(cat \"$DOTEST\"/onto)) &&\n-\tNEWHEAD=$(git rev-parse HEAD) &&\n+\tif [ -d \"$REWRITTEN\" ]\n+\tthen\n+\t\ttest -f \"$DOTEST\"/current-commit &&\n+\t\t\tcurrent_commit=$(cat \"$DOTEST\"/current-commit) &&\n+\t\t\tgit rev-parse HEAD > \"$REWRITTEN\"/$current_commit\n+\t\tNEWHEAD=$(cat \"$REWRITTEN\"/$OLDHEAD)\n+\telse\n+\t\tNEWHEAD=$(git rev-parse HEAD)\n+\tfi &&\n \tmessage=\"$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)\" &&\n \tgit update-ref -m \"$message\" $HEADNAME $NEWHEAD $OLDHEAD &&\n \tgit symbolic-ref HEAD $HEADNAME &&\n@@ -226,6 +308,9 @@ do\n \t-v|--verbose)\n \t\tVERBOSE=t\n \t\t;;\n+\t-p|--preserve-merges)\n+\t\tPRESERVE_MERGES=t\n+\t\t;;\n \t-i|--interactive)\n \t\t# yeah, we know\n \t\t;;\n@@ -274,6 +359,25 @@ do\n \t\techo $UPSTREAM > \"$DOTEST\"/upstream\n \t\techo $ONTO > \"$DOTEST\"/onto\n \t\ttest t = \"$VERBOSE\" && : > \"$DOTEST\"/verbose\n+\t\tif [ t = \"$PRESERVE_MERGES\" ]\n+\t\tthen\n+\t\t\t# $REWRITTEN contains files for each commit that is\n+\t\t\t# reachable by at least one merge base of $HEAD and\n+\t\t\t# $UPSTREAM. They are not necessarily rewritten, but\n+\t\t\t# their children might be.\n+\t\t\t# This ensures that commits on merged, but otherwise\n+\t\t\t# unrelated side branches are left alone. (Think \"X\"\n+\t\t\t# in the man page's example.)\n+\t\t\tmkdir \"$REWRITTEN\" &&\n+\t\t\tfor c in $(git merge-base --all $HEAD $UPSTREAM)\n+\t\t\tdo\n+\t\t\t\techo $ONTO > \"$REWRITTEN\"/$c ||\n+\t\t\t\t\tdie \"Could not init rewritten commits\"\n+\t\t\tdone\n+\t\t\tMERGES_OPTION=\n+\t\telse\n+\t\t\tMERGES_OPTION=--no-merges\n+\t\tfi\n \n \t\tSHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)\n \t\tSHORTHEAD=$(git rev-parse --short $HEAD)\n@@ -286,7 +390,7 @@ do\n #  edit = use commit, but stop for amending\n #  squash = use commit, but meld into previous commit\n EOF\n-\t\tgit rev-list --no-merges --pretty=oneline --abbrev-commit \\\n+\t\tgit rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \\\n \t\t\t--abbrev=7 --reverse $UPSTREAM..$HEAD | \\\n \t\t\tsed \"s/^/pick /\" >> \"$TODO\"\n \ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 9f12bb9..883cf29 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -166,4 +166,26 @@ test_expect_success 'retain authorship when squashing' '\n \tgit show HEAD | grep \"^Author: Nitfol\"\n '\n \n+test_expect_success 'preserve merges with -p' '\n+\tgit checkout -b to-be-preserved master^ &&\n+\t: > unrelated-file &&\n+\tgit add unrelated-file &&\n+\ttest_tick &&\n+\tgit commit -m \"unrelated\" &&\n+\tgit checkout -b to-be-rebased master &&\n+\techo B > file1 &&\n+\ttest_tick &&\n+\tgit commit -m J file1 &&\n+\ttest_tick &&\n+\tgit merge to-be-preserved &&\n+\techo C > file1 &&\n+\ttest_tick &&\n+\tgit commit -m K file1 &&\n+\tgit rebase -i -p --onto branch1 master &&\n+\ttest $(git rev-parse HEAD^^2) = $(git rev-parse to-be-preserved) &&\n+\ttest $(git rev-parse HEAD~3) = $(git rev-parse branch1) &&\n+\ttest $(git show HEAD:file1) = C &&\n+\ttest $(git show HEAD~2:file1) = B\n+'\n+\n test_done\n-- \n1.5.2.2.3172.ge55a1-dirty\n"},{"id":"45811","messageId":"f5plgb$uvl$1@sea.gmane.org","threadId":"8718","inReplyTo":"Pine.LNX.4.64.0706251859270.4059@racer.site","subject":"Re: [PATCH 3/3] Teach rebase -i about --preserve-merges","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2007-06-26T00:15:48Z","receivedAt":"2007-06-26T00:15:48Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Johannes Schindelin wrote:\n\n> *2*: git-rebase without --interactive is inherently patch based (at\n>      least at the moment), and therefore merges cannot be preserved.\n\nWhat about \"git rebase --merge\"?\n-- \nJakub Narebski\nWarsaw, Poland\nShadeHawk on #git\n"},{"id":"45812","messageId":"Pine.LNX.4.64.0706260310230.4059@racer.site","threadId":"8718","inReplyTo":"f5plgb$uvl$1@sea.gmane.org","subject":"Re: [PATCH 3/3] Teach rebase -i about --preserve-merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-06-26T02:14:26Z","receivedAt":"2007-06-26T02:14:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\n[Jakub, it would be really nice if you got into the habit again of Cc'ing, \nif not To'ing, the original poster you are replying to. Thank you.]\n\nOn Tue, 26 Jun 2007, Jakub Narebski wrote:\n\n> Johannes Schindelin wrote:\n> \n> > *2*: git-rebase without --interactive is inherently patch based (at\n> >      least at the moment), and therefore merges cannot be preserved.\n> \n> What about \"git rebase --merge\"?\n\nWell, be my guest.\n\nCiao,\nDscho\n"}]}