git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Thomas Rast <trast@student.ethz.ch>
To: git@vger.kernel.org
Cc: Junio C Hamano <junio@pobox.com>
Subject: [PATCH v2 2/3] rebase -i: learn to rebase root commit
Date: Tue, 30 Dec 2008 13:29:47 +0100	[thread overview]
Message-ID: <3c7d0d7e84b6841041d23e16bf04d65ebea503a4.1230639970.git.trast@student.ethz.ch> (raw)
In-Reply-To: <7b2902d36a4790670f20f786d4ea2e26052a6e71.1230639970.git.trast@student.ethz.ch>
In-Reply-To: <7b2902d36a4790670f20f786d4ea2e26052a6e71.1230639970.git.trast@student.ethz.ch>

Teach git-rebase -i a new option --root, which instructs it to rebase
the entire history leading up to <branch>.  This is mainly for
symmetry with ordinary git-rebase; it cannot be used to edit the root
commit in-place (it requires --onto).

In the normal mode of operation, this is fairly straightforward.  We
run cherry-pick in a loop, and cherry-pick has supported picking the
root commit since f95ebf7 (Allow cherry-picking root commits,
2008-07-04).

In --preserve-merges mode, we track the mapping from old to rewritten
commits and use it to update the parent list of each commit.  In this
case, we define 'rebase -i -p --root --onto $onto $branch' to rewrite
the parent list of all root commit(s) on $branch to contain $onto
instead.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v1: also supports -p --root, with tests.

 git-rebase--interactive.sh |   99 +++++++++++++++++++++++++++++++------------
 t/t3412-rebase-root.sh     |   82 ++++++++++++++++++++++++++++++++++++
 2 files changed, 153 insertions(+), 28 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index c8b0861..d6f54eb 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -27,6 +27,7 @@ continue           continue rebasing process
 abort              abort rebasing process and restore original branch
 skip               skip current patch and continue rebasing process
 no-verify          override pre-rebase hook from stopping the operation
+root               rebase all reachable commmits up to the root(s)
 "
 
 . git-sh-setup
@@ -44,6 +45,7 @@ STRATEGY=
 ONTO=
 VERBOSE=
 OK_TO_SKIP_PRE_REBASE=
+REBASE_ROOT=
 
 GIT_CHERRY_PICK_HELP="  After resolving the conflicts,
 mark the corrected paths with 'git add <paths>', and
@@ -154,6 +156,11 @@ pick_one () {
 	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
 	test -d "$REWRITTEN" &&
 		pick_one_preserving_merges "$@" && return
+	if test ! -z "$REBASE_ROOT"
+	then
+		output git cherry-pick "$@"
+		return
+	fi
 	parent_sha1=$(git rev-parse --verify $sha1^) ||
 		die "Could not get the parent of $sha1"
 	current_sha1=$(git rev-parse --verify HEAD)
@@ -197,7 +204,11 @@ pick_one_preserving_merges () {
 
 	# rewrite parents; if none were rewritten, we can fast-forward.
 	new_parents=
-	pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)"
+	pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)"
+	if test "$pend" = " "
+	then
+		pend=" root"
+	fi
 	while [ "$pend" != "" ]
 	do
 		p=$(expr "$pend" : ' \([^ ]*\)')
@@ -443,6 +454,7 @@ get_saved_options () {
 	test -d "$REWRITTEN" && PRESERVE_MERGES=t
 	test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
 	test -f "$DOTEST"/verbose && VERBOSE=t
+	test ! -s "$DOTEST"/upstream && REBASE_ROOT=t
 }
 
 while test $# != 0
@@ -547,6 +559,9 @@ first and then run 'git rebase --continue' again."
 	-i)
 		# yeah, we know
 		;;
+	--root)
+		REBASE_ROOT=t
+		;;
 	--onto)
 		shift
 		ONTO=$(git rev-parse --verify "$1") ||
@@ -555,7 +570,7 @@ first and then run 'git rebase --continue' again."
 	--)
 		shift
 		run_pre_rebase_hook ${1+"$@"}
-		test $# -eq 1 -o $# -eq 2 || usage
+		test ! -z "$REBASE_ROOT" -o $# -eq 1 -o $# -eq 2 || usage
 		test -d "$DOTEST" &&
 			die "Interactive rebase already started"
 
@@ -566,15 +581,22 @@ first and then run 'git rebase --continue' again."
 
 		require_clean_work_tree
 
-		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
-		test -z "$ONTO" && ONTO=$UPSTREAM
+		if test -z "$REBASE_ROOT"
+		then
+			UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
+			test -z "$ONTO" && ONTO=$UPSTREAM
+			shift
+		else
+			test -z "$ONTO" &&
+				die "You must specify --onto when using --root"
+		fi
 
-		if test ! -z "$2"
+		if test ! -z "$1"
 		then
-			output git show-ref --verify --quiet "refs/heads/$2" ||
-				die "Invalid branchname: $2"
-			output git checkout "$2" ||
-				die "Could not checkout $2"
+			output git show-ref --verify --quiet "refs/heads/$1" ||
+				die "Invalid branchname: $1"
+			output git checkout "$1" ||
+				die "Could not checkout $1"
 		fi
 
 		HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
@@ -598,12 +620,19 @@ first and then run 'git rebase --continue' again."
 			# This ensures that commits on merged, but otherwise
 			# unrelated side branches are left alone. (Think "X"
 			# in the man page's example.)
-			mkdir "$REWRITTEN" &&
-			for c in $(git merge-base --all $HEAD $UPSTREAM)
-			do
-				echo $ONTO > "$REWRITTEN"/$c ||
+			if test -z "$REBASE_ROOT"
+			then
+				mkdir "$REWRITTEN" &&
+				for c in $(git merge-base --all $HEAD $UPSTREAM)
+				do
+					echo $ONTO > "$REWRITTEN"/$c ||
+						die "Could not init rewritten commits"
+				done
+			else
+				mkdir "$REWRITTEN" &&
+				echo $ONTO > "$REWRITTEN"/root ||
 					die "Could not init rewritten commits"
-			done
+			fi
 			# No cherry-pick because our first pass is to determine
 			# parents to rewrite and skipping dropped commits would
 			# prematurely end our probe
@@ -613,12 +642,21 @@ first and then run 'git rebase --continue' again."
 			MERGES_OPTION="--no-merges --cherry-pick"
 		fi
 
-		SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
 		SHORTHEAD=$(git rev-parse --short $HEAD)
 		SHORTONTO=$(git rev-parse --short $ONTO)
+		if test -z "$REBASE_ROOT"
+			# this is now equivalent to ! -z "$UPSTREAM"
+		then
+			SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
+			REVISIONS=$UPSTREAM...$HEAD
+			SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD
+		else
+			REVISIONS=$HEAD
+			SHORTREVISIONS=$SHORTHEAD
+		fi
 		git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
 			--abbrev=7 --reverse --left-right --topo-order \
-			$UPSTREAM...$HEAD | \
+			$REVISIONS | \
 			sed -n "s/^>//p" | while read shortsha1 rest
 		do
 			if test t != "$PRESERVE_MERGES"
@@ -626,14 +664,19 @@ first and then run 'git rebase --continue' again."
 				echo "pick $shortsha1 $rest" >> "$TODO"
 			else
 				sha1=$(git rev-parse $shortsha1)
-				preserve=t
-				for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
-				do
-					if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
-					then
-						preserve=f
-					fi
-				done
+				if test -z "$REBASE_ROOT"
+				then
+					preserve=t
+					for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
+					do
+						if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
+						then
+							preserve=f
+						fi
+					done
+				else
+					preserve=f
+				fi
 				if test f = "$preserve"
 				then
 					touch "$REWRITTEN"/$sha1
@@ -647,11 +690,11 @@ first and then run 'git rebase --continue' again."
 		then
 			mkdir "$DROPPED"
 			# Save all non-cherry-picked changes
-			git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \
+			git rev-list $REVISIONS --left-right --cherry-pick | \
 				sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks
 			# Now all commits and note which ones are missing in
 			# not-cherry-picks and hence being dropped
-			git rev-list $UPSTREAM..$HEAD |
+			git rev-list $REVISIONS |
 			while read rev
 			do
 				if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
@@ -660,7 +703,7 @@ first and then run 'git rebase --continue' again."
 					# not worthwhile, we don't want to track its multiple heads,
 					# just the history of its first-parent for others that will
 					# be rebasing on top of it
-					git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev
+					git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev
 					short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
 					grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
 					rm "$REWRITTEN"/$rev
@@ -670,7 +713,7 @@ first and then run 'git rebase --continue' again."
 		test -s "$TODO" || echo noop >> "$TODO"
 		cat >> "$TODO" << EOF
 
-# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
+# Rebase $SHORTREVISIONS onto $SHORTONTO
 #
 # Commands:
 #  p, pick = use commit
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
index 63ec5e6..c845dfc 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -49,4 +49,86 @@ test_expect_success 'rebase --root --onto <newbase> <branch>' '
 	test_cmp expect rebased2
 '
 
+test_expect_success 'rebase -i --root --onto <newbase>' '
+	git checkout -b work3 other &&
+	GIT_EDITOR=: git rebase -i --root --onto master &&
+	git log --pretty=tformat:"%s" > rebased3 &&
+	test_cmp expect rebased3
+'
+
+test_expect_success 'rebase -i --root --onto <newbase> <branch>' '
+	git branch work4 other &&
+	GIT_EDITOR=: git rebase -i --root --onto master work4 &&
+	git log --pretty=tformat:"%s" > rebased4 &&
+	test_cmp expect rebased4
+'
+
+test_expect_success 'rebase -i -p with linear history' '
+	git checkout -b work5 other &&
+	GIT_EDITOR=: git rebase -i -p --root --onto master &&
+	git log --pretty=tformat:"%s" > rebased5 &&
+	test_cmp expect rebased5
+'
+
+test_expect_success 'set up merge history' '
+	git checkout other^ &&
+	git checkout -b side &&
+	echo 5 > C &&
+	git add C &&
+	git commit -m 5 &&
+	git checkout other &&
+	git merge side
+'
+
+sed 's/#/ /g' > expect-side <<'EOF'
+*   Merge branch 'side' into other
+|\##
+| * 5
+* | 4
+|/##
+* 3
+* 2
+* 1
+EOF
+
+test_expect_success 'rebase -i -p with merge' '
+	git checkout -b work6 other &&
+	GIT_EDITOR=: git rebase -i -p --root --onto master &&
+	git log --graph --topo-order --pretty=tformat:"%s" > rebased6 &&
+	test_cmp expect-side rebased6
+'
+
+test_expect_success 'set up second root and merge' '
+	git symbolic-ref HEAD refs/heads/third &&
+	rm .git/index &&
+	rm A B C &&
+	echo 6 > D &&
+	git add D &&
+	git commit -m 6 &&
+	git checkout other &&
+	git merge third
+'
+
+sed 's/#/ /g' > expect-third <<'EOF'
+*   Merge branch 'third' into other
+|\##
+| * 6
+* |   Merge branch 'side' into other
+|\ \##
+| * | 5
+* | | 4
+|/ /##
+* | 3
+|/##
+* 2
+* 1
+EOF
+
+test_expect_success 'rebase -i -p with two roots' '
+	git checkout -b work7 other &&
+	GIT_EDITOR=: git rebase -i -p --root --onto master &&
+	git log --graph --topo-order --pretty=tformat:"%s" > rebased7 &&
+	test_cmp expect-third rebased7
+'
+
 test_done
-- 
1.6.1.20.g7f5c5.dirty

  reply	other threads:[~2008-12-30 12:31 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-12-29 16:45 [PATCH 0/3] rebase --root Thomas Rast
     [not found] ` <cover.1230569041.git.trast@student.ethz.ch>
2008-12-29 16:45   ` rebase: learn to rebase root commit Thomas Rast
2008-12-29 16:45   ` rebase -i: " Thomas Rast
2008-12-29 21:49     ` Thomas Rast
2008-12-29 22:21       ` Boyd Stephen Smith Jr.
2008-12-30 12:23         ` Thomas Rast
2008-12-30 12:29           ` [PATCH v2 1/3] rebase: " Thomas Rast
2008-12-30 12:29             ` Thomas Rast [this message]
2008-12-30 12:29               ` [PATCH v2 3/3] rebase: update documentation for --root Thomas Rast
2009-01-01 21:00             ` [PATCH v2 1/3] rebase: learn to rebase root commit Junio C Hamano
2009-01-02 18:58               ` Johannes Schindelin
2009-01-02 22:20               ` Thomas Rast
2009-01-02 22:28                 ` [PATCH v3 1/4] rebase -i: execute hook only after argument checking Thomas Rast
2009-01-02 22:28                   ` [PATCH v3 2/4] rebase: learn to rebase root commit Thomas Rast
2009-01-02 22:28                     ` [PATCH v3 3/4] rebase -i: " Thomas Rast
2009-01-02 22:28                       ` [PATCH v3 4/4] rebase: update documentation for --root Thomas Rast
2009-01-02 22:41                       ` [INTERDIFF v3 3/4] rebase -i: learn to rebase root commit Thomas Rast
2009-01-02 22:41                     ` [INTERDIFF v3 2/4] rebase: " Thomas Rast
2009-01-02 23:06                     ` [PATCH " Junio C Hamano
2009-01-02 23:45                       ` [PATCH v3.1] " Thomas Rast
2009-01-05 17:35                         ` [PATCH v3.2] " Thomas Rast
2009-01-06  8:19                           ` Junio C Hamano
2009-01-02 22:49                 ` [PATCH v2 1/3] " Junio C Hamano
2009-01-02 22:54                   ` Thomas Rast
2009-01-02 23:07                     ` Junio C Hamano
2008-12-30  8:22       ` rebase -i: " Junio C Hamano
2008-12-29 16:45   ` rebase: update documentation for --root Thomas Rast

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3c7d0d7e84b6841041d23e16bf04d65ebea503a4.1230639970.git.trast@student.ethz.ch \
    --to=trast@student.ethz.ch \
    --cc=git@vger.kernel.org \
    --cc=junio@pobox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).