threads / patch / 30911

v2, 2 partsrebase -i: support --root without --onto

Subject: [PATCH v2 1/2] rebase -i: support --root without --onto

## tl;dr

3 messages between Jun 26, 2012 and Jun 26, 2012. Diffs are folded; open one to read it.

replies: 2people: 2as markdown or json

Chris Webb· Jun 26, 2012, 21:55 UTC · lore

Allow --root to be specified to rebase -i without --onto, making it possible to edit and re-order all commits right back to the root(s).

If there is a conflict to be resolved when applying the first change, the user will expect a sane index and working tree to get sensible behaviour from git-diff and friends, so create a sentinel commit with an empty tree to rebase onto. Automatically squash the sentinel with any commits rebased directly onto it, so they end up as root commits in their own right and retain their authorship and commit message.

Implicitly use rebase -i for non-interactive rebase of --root without an --onto argument now that rebase -i can correctly do this.

Signed-off-by: Chris Webb <chris@arachsys.com>
---
 Documentation/git-rebase.txt |    9 +++++----
 git-rebase--interactive.sh   |   32 ++++++++++++++++++++++++++------
 git-rebase.sh                |   14 ++++++++++++--
 3 files changed, 43 insertions(+), 12 deletions(-)
Show changes to 3 files +43 −12

Documentation/git-rebase.txt, git-rebase--interactive.sh, git-rebase.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 147fa1a..85b5e44 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git rebase' [-i | --interactive] [options] [--onto <newbase>]
 	[<upstream>] [<branch>]
-'git rebase' [-i | --interactive] [options] --onto <newbase>
+'git rebase' [-i | --interactive] [options] [--onto <newbase>]
 	--root [<branch>]
 'git rebase' --continue | --skip | --abort
 
@@ -348,10 +348,11 @@ idea unless you know what you are doing (see BUGS below).
 --root::
 	Rebase all commits reachable from <branch>, instead of
 	limiting them with an <upstream>.  This allows you to rebase
-	the root commit(s) on a branch.  Must be used with --onto, and
+	the root commit(s) on a branch.  When used with --onto, it
 	will skip changes already contained in <newbase> (instead of
-	<upstream>).  When used together with --preserve-merges, 'all'
-	root commits will be rewritten to have <newbase> as parent
+	<upstream>) whereas without --onto it will operate on every change.
+	When used together with both --onto and --preserve-merges,
+	'all' root commits will be rewritten to have <newbase> as parent
 	instead.
 
 --autosquash::
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 0c19b7c..fcb5f61 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -417,6 +417,29 @@ record_in_rewritten() {
 	esac
 }
 
+do_pick () {
+	if test "$(git rev-parse HEAD)" = "$squash_onto"
+	then
+		# Set the correct commit message and author info on the
+		# sentinel root before cherry-picking the original changes
+		# without committing (-n).  Finally, update the sentinel again
+		# to include these changes.  If the cherry-pick results in a
+		# conflict, this means our behaviour is similar to a standard
+		# failed cherry-pick during rebase, with a dirty index to
+		# resolve before manually running git commit --amend then git
+		# rebase --continue.
+		git commit --allow-empty --allow-empty-message --amend \
+			   --no-post-rewrite -n -q -C $1 &&
+			pick_one -n $1 &&
+			git commit --allow-empty --allow-empty-message \
+				   --amend --no-post-rewrite -n -q -C $1 ||
+			die_with_patch $1 "Could not apply $1... $2"
+	else
+		pick_one $1 ||
+			die_with_patch $1 "Could not apply $1... $2"
+	fi
+}
+
 do_next () {
 	rm -f "$msg" "$author_script" "$amend" || exit
 	read -r command sha1 rest < "$todo"
@@ -428,16 +451,14 @@ do_next () {
 		comment_for_reflog pick
 
 		mark_action_done
-		pick_one $sha1 ||
-			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		do_pick $sha1 "$rest"
 		record_in_rewritten $sha1
 		;;
 	reword|r)
 		comment_for_reflog reword
 
 		mark_action_done
-		pick_one $sha1 ||
-			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		do_pick $sha1 "$rest"
 		git commit --amend --no-post-rewrite || {
 			warn "Could not amend commit after successfully picking $sha1... $rest"
 			warn "This is most likely due to an empty commit message, or the pre-commit hook"
@@ -451,8 +472,7 @@ do_next () {
 		comment_for_reflog edit
 
 		mark_action_done
-		pick_one $sha1 ||
-			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		do_pick $sha1 "$rest"
 		warn "Stopped at $sha1... $rest"
 		exit_with_patch $sha1 0
 		;;
diff --git a/git-rebase.sh b/git-rebase.sh
index e616737..bde2be8 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -31,7 +31,7 @@ SUBDIRECTORY_OK=Yes
 OPTIONS_KEEPDASHDASH=
 OPTIONS_SPEC="\
 git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>]
-git rebase [-i] [options] --onto <newbase> --root [<branch>]
+git rebase [-i] [options] [--onto <newbase>] --root [<branch>]
 git-rebase [-i] --continue | --abort | --skip
 --
  Available options are
@@ -364,6 +364,11 @@ and run me again.  I am stopping in case you still have something
 valuable there.'
 fi
 
+if test -n "$rebase_root" && test -z "$onto"
+then
+	test -z "$interactive_rebase" && interactive_rebase=implied
+fi
+
 if test -n "$interactive_rebase"
 then
 	type=interactive
@@ -397,7 +402,12 @@ then
 	die "invalid upstream $upstream_name"
 	upstream_arg="$upstream_name"
 else
-	test -z "$onto" && die "You must specify --onto when using --root"
+	if test -z "$onto"
+	then
+		empty_tree=`git hash-object -t tree /dev/null`
+		onto=`git commit-tree $empty_tree </dev/null`
+		squash_onto="$onto"
+	fi
 	unset upstream_name
 	unset upstream
 	upstream_arg=--root
-- 
1.7.10
Chris Webb· Jun 26, 2012, 21:55 UTC · re: Chris Webb · lore

[PATCH v2 2/2] Add tests for rebase -i --root without --onto

Test for likely breakages in t3404, including successful reordering of non-conflicting changes with a new root, correct preservation of commit message and author in a root commit when it is squashed with the sentinel, and presence of the sentinel following a conflicting cherry-pick of a new root.

Remove test_must_fail for git rebase --root without --onto from t3412 as this case will now be successfully handled by an implicit git rebase -i.

Signed-off-by: Chris Webb <chris@arachsys.com>
---
 t/t3404-rebase-interactive.sh |   27 +++++++++++++++++++++++++++
 t/t3412-rebase-root.sh        |    4 ----
 2 files changed, 27 insertions(+), 4 deletions(-)
Show changes to 2 files +27 −4

t/t3404-rebase-interactive.sh, t/t3412-rebase-root.sh

diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 025c1c6..6ffc9c2 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -755,4 +755,31 @@ test_expect_success 'rebase-i history with funny messages' '
 	test_cmp expect actual
 '
 
+test_expect_success 'rebase -i --root re-order and drop commits' '
+	git checkout E &&
+	FAKE_LINES="3 1 2 5" git rebase -i --root &&
+	test E = $(git cat-file commit HEAD | sed -ne \$p) &&
+	test B = $(git cat-file commit HEAD^ | sed -ne \$p) &&
+	test A = $(git cat-file commit HEAD^^ | sed -ne \$p) &&
+	test C = $(git cat-file commit HEAD^^^ | sed -ne \$p) &&
+	test 0 = $(git cat-file commit HEAD^^^ | grep -c ^parent\ )
+'
+
+test_expect_success 'rebase -i --root retain root commit author and message' '
+	git checkout A &&
+	echo B >file7 &&
+	git add file7 &&
+	GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
+	FAKE_LINES="2" git rebase -i --root &&
+	git cat-file commit HEAD | grep -q "^author Twerp Snog" &&
+	git cat-file commit HEAD | grep -q "^different author$"
+'
+
+test_expect_success 'rebase -i --root temporary sentinel commit' '
+	git checkout B &&
+	FAKE_LINES="2" test_must_fail git rebase -i --root &&
+	git cat-file commit HEAD | grep "^tree 4b825dc642cb" &&
+	git rebase --abort
+'
+
 test_done
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
index 086c91c..e4f9da8 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -22,10 +22,6 @@ test_expect_success 'prepare repository' '
 	test_commit 4 B
 '
 
-test_expect_success 'rebase --root expects --onto' '
-	test_must_fail git rebase --root
-'
-
 test_expect_success 'setup pre-rebase hook' '
 	mkdir -p .git/hooks &&
 	cat >.git/hooks/pre-rebase <<EOF &&
-- 
1.7.10
Junio C Hamano· Jun 26, 2012, 22:09 UTC · re: Chris Webb · lore

Re: [PATCH v2 1/2] rebase -i: support --root without --onto

Chris Webb <chris@arachsys.com> writes:
Show 8 quoted lines
> +		# Set the correct commit message and author info on the
> +		# sentinel root before cherry-picking the original changes
> +		# without committing (-n).  Finally, update the sentinel again
> +		# to include these changes.  If the cherry-pick results in a
> +		# conflict, this means our behaviour is similar to a standard
> +		# failed cherry-pick during rebase, with a dirty index to
> +		# resolve before manually running git commit --amend then git
> +		# rebase --continue.
Thanks; very readable and understandable.

← back to recent threads