git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] rebase --root
@ 2008-12-29 16:45 Thomas Rast
       [not found] ` <cover.1230569041.git.trast@student.ethz.ch>
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Rast @ 2008-12-29 16:45 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Inspired by f95ebf7 (Allow cherry-picking root commits, 2008-07-04),
this teaches git rebase to rebase everything up to the root commit(s).

The main use-case for this is if you hack on new history, then later
notice that you want to unroll the commits "on" a git-svn branch to
dcommit them separately.  (If you just want to squash them, a merge
will do fine.)

Note that --root (in both modes) requires --onto; you cannot rebase
"onto nothing" to edit the root commit.  Such an option --onto-nothing
might be a worthwile feature however, since it would also allow
"detaching" a set of commits from their parent history.

BTW, while hacking this I noticed that there are two different tests
t3409:

  t3409-rebase-hook.sh
  t3409-rebase-preserve-merges.sh

Is this okay, or should one of them be renamed?


Thomas Rast (3):
  rebase: learn to rebase root commit
  rebase -i: learn to rebase root commit
  rebase: update documentation for --root

 Documentation/git-rebase.txt |   17 +++++++---
 git-rebase--interactive.sh   |   53 +++++++++++++++++++++++++--------
 git-rebase.sh                |   51 ++++++++++++++++++++++----------
 t/t3412-rebase-root.sh       |   66 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 153 insertions(+), 34 deletions(-)
 create mode 100755 t/t3412-rebase-root.sh

^ permalink raw reply	[flat|nested] 27+ messages in thread

* rebase: learn to rebase root commit
       [not found] ` <cover.1230569041.git.trast@student.ethz.ch>
@ 2008-12-29 16:45   ` Thomas Rast
  2008-12-29 16:45   ` rebase -i: " Thomas Rast
  2008-12-29 16:45   ` rebase: update documentation for --root Thomas Rast
  2 siblings, 0 replies; 27+ messages in thread
From: Thomas Rast @ 2008-12-29 16:45 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Teach git-rebase a new option --root, which instructs it to rebase the
entire history leading up to <branch>.

The main use-case is with git-svn: suppose you start hacking (perhaps
offline) on a new project, but later notice you want to commit this
work to SVN.  You will have to rebase the entire history, including
the root commit, on a (possibly empty) commit coming from git-svn, to
establish a history connection.  This previously had to be done by
cherry-picking the root commit manually.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-rebase.sh          |   51 ++++++++++++++++++++++++++++++++--------------
 t/t3412-rebase-root.sh |   52 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 87 insertions(+), 16 deletions(-)
 create mode 100755 t/t3412-rebase-root.sh

diff --git a/git-rebase.sh b/git-rebase.sh
index ebd4df3..89de3c4 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
 git_am_opt=
+rebase_root=
 
 continue_merge () {
 	test -n "$prev_head" || die "prev_head must be defined"
@@ -297,6 +298,9 @@ do
 	-C*)
 		git_am_opt="$git_am_opt $1"
 		;;
+	--root)
+		rebase_root=t
+		;;
 	-*)
 		usage
 		;;
@@ -344,17 +348,23 @@ case "$diff" in
 	;;
 esac
 
+if test -z "$rebase_root"; then
 # The upstream head must be given.  Make sure it is valid.
-upstream_name="$1"
-upstream=`git rev-parse --verify "${upstream_name}^0"` ||
-    die "invalid upstream $upstream_name"
+	upstream_name="$1"
+	shift
+	upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+	die "invalid upstream $upstream_name"
+fi
+
+test ! -z "$rebase_root" -a -z "$newbase" &&
+	die "--root must be used with --onto"
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
 onto=$(git rev-parse --verify "${onto_name}^0") || exit
 
 # If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook ${1+"$@"}
+run_pre_rebase_hook ${upstream_name+"$upstream_name"} "$@"
 
 # If the branch to rebase is given, that is the branch we will rebase
 # $branch_name -- branch being rebased, or HEAD (already detached)
@@ -362,16 +372,16 @@ run_pre_rebase_hook ${1+"$@"}
 # $head_name -- refs/heads/<that-branch> or "detached HEAD"
 switch_to=
 case "$#" in
-2)
+1)
 	# Is it "rebase other $branchname" or "rebase other $commit"?
-	branch_name="$2"
-	switch_to="$2"
+	branch_name="$1"
+	switch_to="$1"
 
-	if git show-ref --verify --quiet -- "refs/heads/$2" &&
-	   branch=$(git rev-parse -q --verify "refs/heads/$2")
+	if git show-ref --verify --quiet -- "refs/heads/$1" &&
+	   branch=$(git rev-parse -q --verify "refs/heads/$1")
 	then
-		head_name="refs/heads/$2"
-	elif branch=$(git rev-parse -q --verify "$2")
+		head_name="refs/heads/$1"
+	elif branch=$(git rev-parse -q --verify "$1")
 	then
 		head_name="detached HEAD"
 	else
@@ -393,7 +403,8 @@ case "$#" in
 esac
 orig_head=$branch
 
-# Now we are rebasing commits $upstream..$branch on top of $onto
+# Now we are rebasing commits $upstream..$branch (or simply $branch
+# with --root) on top of $onto
 
 # Check if we are already based on $onto with linear history,
 # but this should be done only when upstream and onto are the same.
@@ -429,10 +440,18 @@ then
 	exit 0
 fi
 
+if test ! -z "$rebase_root"; then
+	revisions="$orig_head"
+	fp_flag="--root"
+else
+	revisions="$upstream..$orig_head"
+	fp_flag="--ignore-if-in-upstream"
+fi
+
 if test -z "$do_merge"
 then
-	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-		"$upstream..$orig_head" |
+	git format-patch -k --stdout --full-index "$fp_flag" \
+		"$revisions" |
 	git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
 	move_to_original_branch
 	ret=$?
@@ -455,7 +474,7 @@ echo "$orig_head" > "$dotest/orig-head"
 echo "$head_name" > "$dotest/head-name"
 
 msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"`
+for cmt in `git rev-list --reverse --no-merges "$revisions"`
 do
 	msgnum=$(($msgnum + 1))
 	echo "$cmt" > "$dotest/cmt.$msgnum"
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
new file mode 100755
index 0000000..63ec5e6
--- /dev/null
+++ b/t/t3412-rebase-root.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git rebase --root
+
+Tests if git rebase --root --onto <newparent> can rebase the root commit.
+'
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' '
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1 &&
+	echo 2 > A &&
+	git add A &&
+	git commit -m 2 &&
+	git symbolic-ref HEAD refs/heads/other &&
+	rm .git/index &&
+	rm A &&
+	echo 3 > B &&
+	git add B &&
+	git commit -m 3 &&
+	echo 4 > B &&
+	git add B &&
+	git commit -m 4
+'
+
+test_expect_success 'rebase --root expects --onto' '
+	test_must_fail git rebase --root
+'
+
+cat > expect <<EOF
+4
+3
+2
+1
+EOF
+
+test_expect_success 'rebase --root --onto <newbase>' '
+	git checkout -b work &&
+	git rebase --root --onto master &&
+	git log --pretty=tformat:"%s" > rebased &&
+	test_cmp expect rebased
+'
+
+test_expect_success 'rebase --root --onto <newbase> <branch>' '
+	git branch work2 other &&
+	git rebase --root --onto master work2 &&
+	git log --pretty=tformat:"%s" > rebased2 &&
+	test_cmp expect rebased2
+'
+
+test_done
-- 
1.6.1.1.g4c1d9.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* rebase -i: learn to rebase root commit
       [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   ` Thomas Rast
  2008-12-29 21:49     ` Thomas Rast
  2008-12-29 16:45   ` rebase: update documentation for --root Thomas Rast
  2 siblings, 1 reply; 27+ messages in thread
From: Thomas Rast @ 2008-12-29 16:45 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

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).

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-rebase--interactive.sh |   53 +++++++++++++++++++++++++++++++++----------
 t/t3412-rebase-root.sh     |   14 +++++++++++
 2 files changed, 54 insertions(+), 13 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index c8b0861..f29b5ee 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)
@@ -443,6 +450,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 +555,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 +566,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 +577,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?"
@@ -613,12 +631,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"
@@ -647,11 +674,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)" = ""
@@ -670,7 +697,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..bb44a52 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -49,4 +49,18 @@ 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_done
-- 
1.6.1.1.g4c1d9.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* rebase: update documentation for --root
       [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 16:45   ` Thomas Rast
  2 siblings, 0 replies; 27+ messages in thread
From: Thomas Rast @ 2008-12-29 16:45 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Since the new option depends on --onto and omission of <upstream>, use
a separate invocation style, and omit most options to save space.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-rebase.txt |   17 ++++++++++++-----
 1 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index c8ad86a..3268cd2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -8,10 +8,11 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
-	[-s <strategy> | --strategy=<strategy>] [--no-verify]
-	[-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
-	[--onto <newbase>] <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] [--onto <newbase>]
+	<upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] --onto <newbase>
+	--root [<branch>]
+
 'git rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -22,7 +23,8 @@ it remains on the current branch.
 
 All changes made by commits in the current branch but that are not
 in <upstream> are saved to a temporary area.  This is the same set
-of commits that would be shown by `git log <upstream>..HEAD`.
+of commits that would be shown by `git log <upstream>..HEAD` (or
+`git log HEAD`, if --root is specified).
 
 The current branch is reset to <upstream>, or <newbase> if the
 --onto option was supplied.  This has the exact same effect as
@@ -255,6 +257,11 @@ OPTIONS
 --preserve-merges::
 	Instead of ignoring merges, try to recreate them.
 
+--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.
+
 include::merge-strategies.txt[]
 
 NOTES
-- 
1.6.1.1.g4c1d9.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: rebase -i: learn to rebase root commit
  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  8:22       ` rebase -i: " Junio C Hamano
  0 siblings, 2 replies; 27+ messages in thread
From: Thomas Rast @ 2008-12-29 21:49 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

[-- Attachment #1: Type: text/plain, Size: 710 bytes --]

Thomas Rast wrote:
> 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).

Actually, I forgot the "rebase -i -p" code path, which dies if --root
is used with -p.  Apologies.

So for now, consider this broken and RFC: is there any sensible
use/interpretation of -p --root that I'm missing?  Or should it just
disallow this combination?


[I also seem to manage to shoot myself with format-patch & topgit
every time, no matter how trivial the issue.]

-- 
Thomas Rast
trast@{inf,student}.ethz.ch


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: rebase -i: learn to rebase root commit
  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  8:22       ` rebase -i: " Junio C Hamano
  1 sibling, 1 reply; 27+ messages in thread
From: Boyd Stephen Smith Jr. @ 2008-12-29 22:21 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Junio C Hamano

[-- Attachment #1: Type: text/plain, Size: 1456 bytes --]

On Monday 2008 December 29 15:49:42 Thomas Rast wrote:
> Thomas Rast wrote:
> > 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).
>
> Actually, I forgot the "rebase -i -p" code path, which dies if --root
> is used with -p.  Apologies.
>
> So for now, consider this broken and RFC: is there any sensible
> use/interpretation of -p --root that I'm missing?  Or should it just
> disallow this combination?

Here's the interpretation that *I* come up with for -p --root used together:
The commit with no parents (OLD_ROOT) is rebased as if -p were not given, call 
the resulting commit NEW_ROOT.  Then, the rebase continues as if "--onto 
NEW_ROOT OLD_ROOT <branch>" was specified instead of "--onto=NEW_ROOT^ --root 
<branch>".

Basically, --root only changes how the first commit is handled, which I think 
is consistent with other uses of --root.  It's also similar to cherry-picking 
the first commit, follwed by a non-root rebase, which I think is also 
consistent with the intention of --root.
-- 
Boyd Stephen Smith Jr.                     ,= ,-_-. =. 
bss@iguanasuicide.net                     ((_/)o o(\_))
ICQ: 514984 YM/AIM: DaTwinkDaddy           `-'(. .)`-' 
http://iguanasuicide.net/                      \_/     

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: rebase -i: learn to rebase root commit
  2008-12-29 21:49     ` Thomas Rast
  2008-12-29 22:21       ` Boyd Stephen Smith Jr.
@ 2008-12-30  8:22       ` Junio C Hamano
  1 sibling, 0 replies; 27+ messages in thread
From: Junio C Hamano @ 2008-12-30  8:22 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git

Thomas Rast <trast@student.ethz.ch> writes:

> ... is there any sensible
> use/interpretation of -p --root that I'm missing?  Or should it just
> disallow this combination?

If --root is about replaying all the history, wouldn't people want to use
it together with -p to reconstruct the whole history as a substitute for
filter-branch?  IOW, I have a suspicion that they most likely should go
together.

I never felt a need to rebase deep down to --root myself anyway, so I am a
bad judge for people's needs on this topic, though.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: rebase -i: learn to rebase root commit
  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
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Rast @ 2008-12-30 12:23 UTC (permalink / raw)
  To: git; +Cc: Boyd Stephen Smith Jr.

[-- Attachment #1: Type: text/plain, Size: 2042 bytes --]

Boyd Stephen Smith Jr. wrote:
> Here's the interpretation that *I* come up with for -p --root used together:
> The commit with no parents (OLD_ROOT) is rebased as if -p were not given, call 
> the resulting commit NEW_ROOT.  Then, the rebase continues as if "--onto 
> NEW_ROOT OLD_ROOT <branch>" was specified instead of "--onto=NEW_ROOT^ --root 
> <branch>".

I like this logic, but it feels inconsistent as soon as there are
several root commits.  (This may be somewhat academic, since any repo
with several roots should also be able to cope with a merge...)

Some digging into the -p code shows that it knows which commits were
rewritten, and which were untouched.  It rewrites such that _all_
commits in $(git merge-base --all $branch $upstream) are rewritten to
look like $onto instead, i.e., all their occurrences in parent lists
of commits are rewritten to $onto.  All other commits are only
rewritten if they have a parent that was rewritten.

So I think one sane way is to define a virtual parent 'root', and
think of parentless commits as having the (sole) parent 'root'.  Then
we can rewrite such that 'root' becomes $onto, i.e., all occurrences
of 'root' in parent lists become $onto, consistent with the normal
operation.  (For the other commits, the same rule as above is
applied.)

Of course this just boils down to saying that _all_ root commits
reachable from $branch are rewritten to have $onto as their parent.
Subsequently, all other commits will also be rewritten because they
all must have at least one rewritten parent.

> Basically, --root only changes how the first commit is handled, which I think 
> is consistent with other uses of --root.  It's also similar to cherry-picking 
> the first commit, follwed by a non-root rebase, which I think is also 
> consistent with the intention of --root.

I believe this remark still holds if there is only a single root
commit on $branch.

I will reroll with an updated 2/3 shortly.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v2 1/3] rebase: learn to rebase root commit
  2008-12-30 12:23         ` Thomas Rast
@ 2008-12-30 12:29           ` Thomas Rast
  2008-12-30 12:29             ` [PATCH v2 2/3] rebase -i: " Thomas Rast
  2009-01-01 21:00             ` [PATCH v2 1/3] rebase: learn to rebase root commit Junio C Hamano
  0 siblings, 2 replies; 27+ messages in thread
From: Thomas Rast @ 2008-12-30 12:29 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Teach git-rebase a new option --root, which instructs it to rebase the
entire history leading up to <branch>.

The main use-case is with git-svn: suppose you start hacking (perhaps
offline) on a new project, but later notice you want to commit this
work to SVN.  You will have to rebase the entire history, including
the root commit, on a (possibly empty) commit coming from git-svn, to
establish a history connection.  This previously had to be done by
cherry-picking the root commit manually.

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

No changes since v1.

 git-rebase.sh          |   51 ++++++++++++++++++++++++++++++++--------------
 t/t3412-rebase-root.sh |   52 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 87 insertions(+), 16 deletions(-)
 create mode 100755 t/t3412-rebase-root.sh

diff --git a/git-rebase.sh b/git-rebase.sh
index ebd4df3..89de3c4 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
 git_am_opt=
+rebase_root=
 
 continue_merge () {
 	test -n "$prev_head" || die "prev_head must be defined"
@@ -297,6 +298,9 @@ do
 	-C*)
 		git_am_opt="$git_am_opt $1"
 		;;
+	--root)
+		rebase_root=t
+		;;
 	-*)
 		usage
 		;;
@@ -344,17 +348,23 @@ case "$diff" in
 	;;
 esac
 
+if test -z "$rebase_root"; then
 # The upstream head must be given.  Make sure it is valid.
-upstream_name="$1"
-upstream=`git rev-parse --verify "${upstream_name}^0"` ||
-    die "invalid upstream $upstream_name"
+	upstream_name="$1"
+	shift
+	upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+	die "invalid upstream $upstream_name"
+fi
+
+test ! -z "$rebase_root" -a -z "$newbase" &&
+	die "--root must be used with --onto"
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
 onto=$(git rev-parse --verify "${onto_name}^0") || exit
 
 # If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook ${1+"$@"}
+run_pre_rebase_hook ${upstream_name+"$upstream_name"} "$@"
 
 # If the branch to rebase is given, that is the branch we will rebase
 # $branch_name -- branch being rebased, or HEAD (already detached)
@@ -362,16 +372,16 @@ run_pre_rebase_hook ${1+"$@"}
 # $head_name -- refs/heads/<that-branch> or "detached HEAD"
 switch_to=
 case "$#" in
-2)
+1)
 	# Is it "rebase other $branchname" or "rebase other $commit"?
-	branch_name="$2"
-	switch_to="$2"
+	branch_name="$1"
+	switch_to="$1"
 
-	if git show-ref --verify --quiet -- "refs/heads/$2" &&
-	   branch=$(git rev-parse -q --verify "refs/heads/$2")
+	if git show-ref --verify --quiet -- "refs/heads/$1" &&
+	   branch=$(git rev-parse -q --verify "refs/heads/$1")
 	then
-		head_name="refs/heads/$2"
-	elif branch=$(git rev-parse -q --verify "$2")
+		head_name="refs/heads/$1"
+	elif branch=$(git rev-parse -q --verify "$1")
 	then
 		head_name="detached HEAD"
 	else
@@ -393,7 +403,8 @@ case "$#" in
 esac
 orig_head=$branch
 
-# Now we are rebasing commits $upstream..$branch on top of $onto
+# Now we are rebasing commits $upstream..$branch (or simply $branch
+# with --root) on top of $onto
 
 # Check if we are already based on $onto with linear history,
 # but this should be done only when upstream and onto are the same.
@@ -429,10 +440,18 @@ then
 	exit 0
 fi
 
+if test ! -z "$rebase_root"; then
+	revisions="$orig_head"
+	fp_flag="--root"
+else
+	revisions="$upstream..$orig_head"
+	fp_flag="--ignore-if-in-upstream"
+fi
+
 if test -z "$do_merge"
 then
-	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-		"$upstream..$orig_head" |
+	git format-patch -k --stdout --full-index "$fp_flag" \
+		"$revisions" |
 	git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
 	move_to_original_branch
 	ret=$?
@@ -455,7 +474,7 @@ echo "$orig_head" > "$dotest/orig-head"
 echo "$head_name" > "$dotest/head-name"
 
 msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"`
+for cmt in `git rev-list --reverse --no-merges "$revisions"`
 do
 	msgnum=$(($msgnum + 1))
 	echo "$cmt" > "$dotest/cmt.$msgnum"
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
new file mode 100755
index 0000000..63ec5e6
--- /dev/null
+++ b/t/t3412-rebase-root.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git rebase --root
+
+Tests if git rebase --root --onto <newparent> can rebase the root commit.
+'
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' '
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1 &&
+	echo 2 > A &&
+	git add A &&
+	git commit -m 2 &&
+	git symbolic-ref HEAD refs/heads/other &&
+	rm .git/index &&
+	rm A &&
+	echo 3 > B &&
+	git add B &&
+	git commit -m 3 &&
+	echo 4 > B &&
+	git add B &&
+	git commit -m 4
+'
+
+test_expect_success 'rebase --root expects --onto' '
+	test_must_fail git rebase --root
+'
+
+cat > expect <<EOF
+4
+3
+2
+1
+EOF
+
+test_expect_success 'rebase --root --onto <newbase>' '
+	git checkout -b work &&
+	git rebase --root --onto master &&
+	git log --pretty=tformat:"%s" > rebased &&
+	test_cmp expect rebased
+'
+
+test_expect_success 'rebase --root --onto <newbase> <branch>' '
+	git branch work2 other &&
+	git rebase --root --onto master work2 &&
+	git log --pretty=tformat:"%s" > rebased2 &&
+	test_cmp expect rebased2
+'
+
+test_done
-- 
1.6.1.20.g7f5c5.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 2/3] rebase -i: learn to rebase root commit
  2008-12-30 12:29           ` [PATCH v2 1/3] rebase: " Thomas Rast
@ 2008-12-30 12:29             ` Thomas Rast
  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
  1 sibling, 1 reply; 27+ messages in thread
From: Thomas Rast @ 2008-12-30 12:29 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

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

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 3/3] rebase: update documentation for --root
  2008-12-30 12:29             ` [PATCH v2 2/3] rebase -i: " Thomas Rast
@ 2008-12-30 12:29               ` Thomas Rast
  0 siblings, 0 replies; 27+ messages in thread
From: Thomas Rast @ 2008-12-30 12:29 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Since the new option depends on --onto and omission of <upstream>, use
a separate invocation style, and omit most options to save space.

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

Changed since v1: remark about the effect of --root --preserve-merges.

 Documentation/git-rebase.txt |   19 ++++++++++++++-----
 1 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index c8ad86a..e3b4b83 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -8,10 +8,11 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
-	[-s <strategy> | --strategy=<strategy>] [--no-verify]
-	[-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
-	[--onto <newbase>] <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] [--onto <newbase>]
+	<upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] --onto <newbase>
+	--root [<branch>]
+
 'git rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -22,7 +23,8 @@ it remains on the current branch.
 
 All changes made by commits in the current branch but that are not
 in <upstream> are saved to a temporary area.  This is the same set
-of commits that would be shown by `git log <upstream>..HEAD`.
+of commits that would be shown by `git log <upstream>..HEAD` (or
+`git log HEAD`, if --root is specified).
 
 The current branch is reset to <upstream>, or <newbase> if the
 --onto option was supplied.  This has the exact same effect as
@@ -255,6 +257,13 @@ OPTIONS
 --preserve-merges::
 	Instead of ignoring merges, try to recreate them.
 
+--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.
+	When used together with --preserve-merges, 'all' root commits
+	will be rewritten to have <newbase> as parent instead.
+
 include::merge-strategies.txt[]
 
 NOTES
-- 
1.6.1.20.g7f5c5.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/3] rebase: learn to rebase root commit
  2008-12-30 12:29           ` [PATCH v2 1/3] rebase: " Thomas Rast
  2008-12-30 12:29             ` [PATCH v2 2/3] rebase -i: " Thomas Rast
@ 2009-01-01 21:00             ` Junio C Hamano
  2009-01-02 18:58               ` Johannes Schindelin
  2009-01-02 22:20               ` Thomas Rast
  1 sibling, 2 replies; 27+ messages in thread
From: Junio C Hamano @ 2009-01-01 21:00 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git

Thomas Rast <trast@student.ethz.ch> writes:

> Teach git-rebase a new option --root, which instructs it to rebase the
> entire history leading up to <branch>.
>
> The main use-case is with git-svn: suppose you start hacking (perhaps
> offline) on a new project, but later notice you want to commit this
> work to SVN.  You will have to rebase the entire history, including
> the root commit, on a (possibly empty) commit coming from git-svn, to
> establish a history connection.  This previously had to be done by
> cherry-picking the root commit manually.

I like what this series tries to do.  Using the --root option is probably
a more natural way to do what people often do with the "add graft and
filter-branch the whole history once" procedure.

But it somewhat feels sad if the "main" use-case for this is to start your
project in git and then migrate away by feeding your history to subversion
;-).

> @@ -344,17 +348,23 @@ case "$diff" in
>  	;;
>  esac
>  
> +if test -z "$rebase_root"; then
>  # The upstream head must be given.  Make sure it is valid.
> -upstream_name="$1"
> -upstream=`git rev-parse --verify "${upstream_name}^0"` ||
> -    die "invalid upstream $upstream_name"
> +	upstream_name="$1"
> +	shift
> +	upstream=`git rev-parse --verify "${upstream_name}^0"` ||
> +	die "invalid upstream $upstream_name"
> +fi
> +
> +test ! -z "$rebase_root" -a -z "$newbase" &&
> +	die "--root must be used with --onto"

This is much easier to read if it were:

	if test -z "$rebase_root"
        then
        	... do the upstream_name/upstream thing, such as
                upstream_name="$1"
                ...
	else
        	... do the rebase_root thing, including
                unset upstream
                unset upstream_name
                test -z "$newbase" && die "--root without --onto"
                ...
	fi

(Mental note.  You shifted positional parameters and the remainders need
to be adjusted, which you seem to have done).

>  # Make sure the branch to rebase onto is valid.
>  onto_name=${newbase-"$upstream_name"}
>  onto=$(git rev-parse --verify "${onto_name}^0") || exit
>  
>  # If a hook exists, give it a chance to interrupt
> -run_pre_rebase_hook ${1+"$@"}
> +run_pre_rebase_hook ${upstream_name+"$upstream_name"} "$@"

I do not think ${upstream_name+"$upstream_name"} is a good check to begin
with, because presense of it does not necessarily mean the command was
invoked without --root; it could have come from the environment of the
invoker (hence the suggestion to unset the variable explicitly).

And I do not think this is a good way to extend the calling convention of
the hook, either.  pre-rebase-hook used to always take upstream and
optionally the explicit branch name.  When --root is given, your code will
give the hook a single parameter which is the explicit branch name
(i.e. "we will switch to this branch and rebase it, are you Ok with it?"),
but the hook will mistakenly think you are feeding the fork-point commit.

Because an old pre-rebase-hook cannot verify --root case correctly anyway
and needs to be updated, how about doing 'run_pre_rebase_hook --root "$@"'
when --root was given?

> @@ -393,7 +403,8 @@ case "$#" in
>  esac
>  orig_head=$branch
>  
> -# Now we are rebasing commits $upstream..$branch on top of $onto
> +# Now we are rebasing commits $upstream..$branch (or simply $branch
> +# with --root) on top of $onto

"or simply everything leading to $branch if --root is given"?

>  # Check if we are already based on $onto with linear history,
>  # but this should be done only when upstream and onto are the same.
> @@ -429,10 +440,18 @@ then
>  	exit 0
>  fi
>  
> +if test ! -z "$rebase_root"; then
> +	revisions="$orig_head"
> +	fp_flag="--root"
> +else
> +	revisions="$upstream..$orig_head"
> +	fp_flag="--ignore-if-in-upstream"
> +fi

Hmph, the reason why --ignore-if-in-upstream is irrelevant when --root is
given is because...?

> diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
> new file mode 100755
> index 0000000..63ec5e6
> --- /dev/null
> +++ b/t/t3412-rebase-root.sh
> @@ -0,0 +1,52 @@
> +#!/bin/sh
> +
> +test_description='git rebase --root
> ...
> +test_done

Including tests for the pre-rebase-hook would be nice.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/3] rebase: learn to rebase root commit
  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
  1 sibling, 0 replies; 27+ messages in thread
From: Johannes Schindelin @ 2009-01-02 18:58 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Thomas Rast, git

Hi,

On Thu, 1 Jan 2009, Junio C Hamano wrote:

> Thomas Rast <trast@student.ethz.ch> writes:
> 
> > Teach git-rebase a new option --root, which instructs it to rebase the
> > entire history leading up to <branch>.
> >
> > The main use-case is with git-svn: suppose you start hacking (perhaps
> > offline) on a new project, but later notice you want to commit this
> > work to SVN.  You will have to rebase the entire history, including
> > the root commit, on a (possibly empty) commit coming from git-svn, to
> > establish a history connection.  This previously had to be done by
> > cherry-picking the root commit manually.
> 
> I like what this series tries to do.  Using the --root option is probably
> a more natural way to do what people often do with the "add graft and
> filter-branch the whole history once" procedure.
> 
> But it somewhat feels sad if the "main" use-case for this is to start your
> project in git and then migrate away by feeding your history to subversion
> ;-).

FWIW I had a single case where I could have used something like this 
myself, in my whole life.  It was when I started to write 
git-edit-patch-series.sh in its own repository, only to realize at the end 
that I should have started it in a topic branch in my git.git tree.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/3] rebase: learn to rebase root commit
  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:49                 ` [PATCH v2 1/3] " Junio C Hamano
  1 sibling, 2 replies; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 22:20 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 3524 bytes --]

Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> > The main use-case is with git-svn: [...]
> 
> I like what this series tries to do.  Using the --root option is probably
> a more natural way to do what people often do with the "add graft and
> filter-branch the whole history once" procedure.

Their uses are somewhat disjoint, as grafting only works if the
grafted parent has an empty tree.  Otherwise the grafted child will
appear to revert the entire history leading to it.  Rebase OTOH
changes the committers.

> But it somewhat feels sad if the "main" use-case for this is to start your
> project in git and then migrate away by feeding your history to subversion
> ;-).

You can remove that paragraph if you don't want to "support" SVN in
git.git ;-)

> >  # Make sure the branch to rebase onto is valid.
> >  onto_name=${newbase-"$upstream_name"}
> >  onto=$(git rev-parse --verify "${onto_name}^0") || exit
> >  
> >  # If a hook exists, give it a chance to interrupt
> > -run_pre_rebase_hook ${1+"$@"}
> > +run_pre_rebase_hook ${upstream_name+"$upstream_name"} "$@"
> 
> I do not think ${upstream_name+"$upstream_name"} is a good check to begin
> with, because presense of it does not necessarily mean the command was
> invoked without --root; it could have come from the environment of the
> invoker (hence the suggestion to unset the variable explicitly).

Good catch, thanks.

I'm still not sure what ${1+"$@"} was about by the way.  The most
authoritative reference I can find is

  http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_05_02

which says

  If there are no positional parameters, the expansion of '@' shall
  generate zero fields, even when '@' is double-quoted.

('man bash' agrees.)

> And I do not think this is a good way to extend the calling convention of
> the hook, either.  pre-rebase-hook used to always take upstream and
> optionally the explicit branch name.  When --root is given, your code will
> give the hook a single parameter which is the explicit branch name
> (i.e. "we will switch to this branch and rebase it, are you Ok with it?"),
> but the hook will mistakenly think you are feeding the fork-point commit.
> 
> Because an old pre-rebase-hook cannot verify --root case correctly anyway
> and needs to be updated, how about doing 'run_pre_rebase_hook --root "$@"'
> when --root was given?

True.  I noticed that I had to fix the positionals, but forgot about
the hook afterwards.  v3 implements this as you suggested.

> > +if test ! -z "$rebase_root"; then
> > +	revisions="$orig_head"
> > +	fp_flag="--root"
> > +else
> > +	revisions="$upstream..$orig_head"
> > +	fp_flag="--ignore-if-in-upstream"
> > +fi
> 
> Hmph, the reason why --ignore-if-in-upstream is irrelevant when --root is
> given is because...?

Well, originally because format-patch didn't like the argument.
Thanks for prodding however, $onto..$head makes sort of makes sense
here and I discovered that even $onto...$head works, which is used in
'rebase -i'.

However, it's still a change of semantics: With --root we now ignore
patches that are already contained in $onto, as opposed to patches
that were already in $upstream in normal operation.  It seems sensible
to do it this way, and perhaps even normal rebase should do it the
same way... if only format-patch would support it.

Thanks for the review! v3 upcoming.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 1/4] rebase -i: execute hook only after argument checking
  2009-01-02 22:20               ` Thomas Rast
@ 2009-01-02 22:28                 ` Thomas Rast
  2009-01-02 22:28                   ` [PATCH v3 2/4] rebase: learn to rebase root commit Thomas Rast
  2009-01-02 22:49                 ` [PATCH v2 1/3] " Junio C Hamano
  1 sibling, 1 reply; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 22:28 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, bss

Previously, the pre-rebase-hook would be launched before we knew if
the <upstream> [<branch>] arguments were supplied.

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

This is a quick fix and not really part of the series.  2/4 textually
depends on it, however, so I'm sending it along.

 git-rebase--interactive.sh |    5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index c8b0861..2c668cd 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -554,7 +554,6 @@ first and then run 'git rebase --continue' again."
 		;;
 	--)
 		shift
-		run_pre_rebase_hook ${1+"$@"}
 		test $# -eq 1 -o $# -eq 2 || usage
 		test -d "$DOTEST" &&
 			die "Interactive rebase already started"
@@ -562,11 +561,13 @@ first and then run 'git rebase --continue' again."
 		git var GIT_COMMITTER_IDENT >/dev/null ||
 			die "You need to set your committer info first"
 
+		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
+		run_pre_rebase_hook ${1+"$@"}
+
 		comment_for_reflog start
 
 		require_clean_work_tree
 
-		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
 		test -z "$ONTO" && ONTO=$UPSTREAM
 
 		if test ! -z "$2"
-- 
1.6.1.71.gaaa47.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 2/4] rebase: learn to rebase root commit
  2009-01-02 22:28                 ` [PATCH v3 1/4] rebase -i: execute hook only after argument checking Thomas Rast
@ 2009-01-02 22:28                   ` Thomas Rast
  2009-01-02 22:28                     ` [PATCH v3 3/4] rebase -i: " Thomas Rast
                                       ` (2 more replies)
  0 siblings, 3 replies; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 22:28 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, bss

Teach git-rebase a new option --root, which instructs it to rebase the
entire history leading up to <branch>.  This option must be used with
--onto <newbase>, and causes commits that already exist in <newbase>
to be skipped.  (Normal operation skips commits that already exist in
<upstream> instead.)

The main use-case is with git-svn: suppose you start hacking (perhaps
offline) on a new project, but later notice you want to commit this
work to SVN.  You will have to rebase the entire history, including
the root commit, on a (possibly empty) commit coming from git-svn, to
establish a history connection.  This previously had to be done by
cherry-picking the root commit manually.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-rebase.sh          |   50 +++++++++++++++++++--------
 t/t3412-rebase-root.sh |   86 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+), 15 deletions(-)
 create mode 100755 t/t3412-rebase-root.sh

diff --git a/git-rebase.sh b/git-rebase.sh
index ebd4df3..9437e51 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
 git_am_opt=
+rebase_root=
 
 continue_merge () {
 	test -n "$prev_head" || die "prev_head must be defined"
@@ -297,6 +298,9 @@ do
 	-C*)
 		git_am_opt="$git_am_opt $1"
 		;;
+	--root)
+		rebase_root=t
+		;;
 	-*)
 		usage
 		;;
@@ -344,17 +348,26 @@ case "$diff" in
 	;;
 esac
 
+if test -z "$rebase_root"; then
 # The upstream head must be given.  Make sure it is valid.
-upstream_name="$1"
-upstream=`git rev-parse --verify "${upstream_name}^0"` ||
-    die "invalid upstream $upstream_name"
+	upstream_name="$1"
+	shift
+	upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+	die "invalid upstream $upstream_name"
+	unset root_flag
+else
+	test -z "$newbase" && die "--root must be used with --onto"
+	unset upstream_name
+	unset upstream
+	root_flag="--root"
+fi
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
 onto=$(git rev-parse --verify "${onto_name}^0") || exit
 
 # If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook ${1+"$@"}
+run_pre_rebase_hook $root_flag $upstream_name "$@"
 
 # If the branch to rebase is given, that is the branch we will rebase
 # $branch_name -- branch being rebased, or HEAD (already detached)
@@ -362,16 +375,16 @@ run_pre_rebase_hook ${1+"$@"}
 # $head_name -- refs/heads/<that-branch> or "detached HEAD"
 switch_to=
 case "$#" in
-2)
+1)
 	# Is it "rebase other $branchname" or "rebase other $commit"?
-	branch_name="$2"
-	switch_to="$2"
+	branch_name="$1"
+	switch_to="$1"
 
-	if git show-ref --verify --quiet -- "refs/heads/$2" &&
-	   branch=$(git rev-parse -q --verify "refs/heads/$2")
+	if git show-ref --verify --quiet -- "refs/heads/$1" &&
+	   branch=$(git rev-parse -q --verify "refs/heads/$1")
 	then
-		head_name="refs/heads/$2"
-	elif branch=$(git rev-parse -q --verify "$2")
+		head_name="refs/heads/$1"
+	elif branch=$(git rev-parse -q --verify "$1")
 	then
 		head_name="detached HEAD"
 	else
@@ -393,7 +406,8 @@ case "$#" in
 esac
 orig_head=$branch
 
-# Now we are rebasing commits $upstream..$branch on top of $onto
+# Now we are rebasing commits $upstream..$branch (or with --root,
+# everything leading up to $branch) on top of $onto
 
 # Check if we are already based on $onto with linear history,
 # but this should be done only when upstream and onto are the same.
@@ -429,10 +443,16 @@ then
 	exit 0
 fi
 
+if test ! -z "$rebase_root"; then
+	revisions="$onto..$orig_head"
+else
+	revisions="$upstream..$orig_head"
+fi
+
 if test -z "$do_merge"
 then
 	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-		"$upstream..$orig_head" |
+		$root_flag "$revisions" |
 	git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
 	move_to_original_branch
 	ret=$?
@@ -455,7 +475,7 @@ echo "$orig_head" > "$dotest/orig-head"
 echo "$head_name" > "$dotest/head-name"
 
 msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"`
+for cmt in `git rev-list --reverse --no-merges "$revisions"`
 do
 	msgnum=$(($msgnum + 1))
 	echo "$cmt" > "$dotest/cmt.$msgnum"
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
new file mode 100755
index 0000000..1978512
--- /dev/null
+++ b/t/t3412-rebase-root.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='git rebase --root
+
+Tests if git rebase --root --onto <newparent> can rebase the root commit.
+'
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' '
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1 &&
+	echo 2 > A &&
+	git add A &&
+	git commit -m 2 &&
+	git symbolic-ref HEAD refs/heads/other &&
+	rm .git/index &&
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1b &&
+	echo 3 > B &&
+	git add B &&
+	git commit -m 3 &&
+	echo 4 > B &&
+	git add B &&
+	git commit -m 4
+'
+
+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 &&
+#!$SHELL_PATH
+echo "\$1,\$2" >.git/PRE-REBASE-INPUT
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
+cat > expect <<EOF
+4
+3
+2
+1
+EOF
+
+test_expect_success 'rebase --root --onto <newbase>' '
+	git checkout -b work &&
+	git rebase --root --onto master &&
+	git log --pretty=tformat:"%s" > rebased &&
+	test_cmp expect rebased
+'
+
+test_expect_success 'pre-rebase got correct input (1)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase --root --onto <newbase> <branch>' '
+	git branch work2 other &&
+	git rebase --root --onto master work2 &&
+	git log --pretty=tformat:"%s" > rebased2 &&
+	test_cmp expect rebased2
+'
+
+test_expect_success 'pre-rebase got correct input (2)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
+'
+
+test_expect_success 'setup pre-rebase hook that fails' '
+	mkdir -p .git/hooks &&
+	cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+false
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
+
+test_expect_success 'pre-rebase hook stops rebase' '
+	git checkout -b stops1 other &&
+	GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
+	test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+	test 0 = $(git rev-list other...stops1 | wc -l)
+'
+
+test_done
-- 
1.6.1.71.gaaa47.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 3/4] rebase -i: learn to rebase root commit
  2009-01-02 22:28                   ` [PATCH v3 2/4] rebase: learn to rebase root commit Thomas Rast
@ 2009-01-02 22:28                     ` 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
  2 siblings, 2 replies; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 22:28 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, bss

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 <newbase>).  Commits that already
exist in <newbase> are skipped.

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>
---
 git-rebase--interactive.sh |  109 +++++++++++++++++++++++++++++++------------
 t/t3412-rebase-root.sh     |  101 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 179 insertions(+), 31 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 2c668cd..14d3e38 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" : ' \([^ ]*\)')
@@ -227,7 +238,9 @@ pick_one_preserving_merges () {
 			if test -f "$DROPPED"/$p
 			then
 				fast_forward=f
-				pend=" $(cat "$DROPPED"/$p)$pend"
+				replacement="$(cat "$DROPPED"/$p)"
+				test -z "$replacement" && replacement=root
+				pend=" $replacement$pend"
 			else
 				new_parents="$new_parents $p"
 			fi
@@ -443,6 +456,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 +561,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") ||
@@ -554,28 +571,36 @@ first and then run 'git rebase --continue' again."
 		;;
 	--)
 		shift
-		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"
 
 		git var GIT_COMMITTER_IDENT >/dev/null ||
 			die "You need to set your committer info first"
 
-		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
-		run_pre_rebase_hook ${1+"$@"}
+		if test -z "$REBASE_ROOT"
+		then
+			UPSTREAM_ARG="$1"
+			UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
+			test -z "$ONTO" && ONTO=$UPSTREAM
+			shift
+		else
+			UPSTREAM_ARG=--root
+			test -z "$ONTO" &&
+				die "You must specify --onto when using --root"
+		fi
+		run_pre_rebase_hook "$UPSTREAM_ARG" "$@"
 
 		comment_for_reflog start
 
 		require_clean_work_tree
 
-		test -z "$ONTO" && ONTO=$UPSTREAM
-
-		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?"
@@ -599,12 +624,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
@@ -614,12 +646,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=$ONTO...$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"
@@ -627,14 +668,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
@@ -648,11 +694,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)" = ""
@@ -661,17 +707,18 @@ 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
 				fi
 			done
 		fi
+
 		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 1978512..cbf3414 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -67,6 +67,100 @@ test_expect_success 'pre-rebase got correct input (2)' '
 	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
 '
 
+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 'pre-rebase got correct input (3)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+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 'pre-rebase got correct input (4)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4
+'
+
+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 'pre-rebase got correct input (5)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+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_expect_success 'setup pre-rebase hook that fails' '
 	mkdir -p .git/hooks &&
 	cat >.git/hooks/pre-rebase <<EOF &&
@@ -83,4 +177,11 @@ test_expect_success 'pre-rebase hook stops rebase' '
 	test 0 = $(git rev-list other...stops1 | wc -l)
 '
 
+test_expect_success 'pre-rebase hook stops rebase -i' '
+	git checkout -b stops2 other &&
+	GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
+	test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
+	test 0 = $(git rev-list other...stops2 | wc -l)
+'
+
 test_done
-- 
1.6.1.71.gaaa47.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 4/4] rebase: update documentation for --root
  2009-01-02 22:28                     ` [PATCH v3 3/4] rebase -i: " Thomas Rast
@ 2009-01-02 22:28                       ` Thomas Rast
  2009-01-02 22:41                       ` [INTERDIFF v3 3/4] rebase -i: learn to rebase root commit Thomas Rast
  1 sibling, 0 replies; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 22:28 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, bss

Since the new option depends on --onto and omission of <upstream>, use
a separate invocation style, and omit most options to save space.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-rebase.txt |   21 ++++++++++++++++-----
 1 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index c8ad86a..3d6d429 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -8,10 +8,11 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
-	[-s <strategy> | --strategy=<strategy>] [--no-verify]
-	[-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
-	[--onto <newbase>] <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] [--onto <newbase>]
+	<upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] --onto <newbase>
+	--root [<branch>]
+
 'git rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -22,7 +23,8 @@ it remains on the current branch.
 
 All changes made by commits in the current branch but that are not
 in <upstream> are saved to a temporary area.  This is the same set
-of commits that would be shown by `git log <upstream>..HEAD`.
+of commits that would be shown by `git log <upstream>..HEAD` (or
+`git log HEAD`, if --root is specified).
 
 The current branch is reset to <upstream>, or <newbase> if the
 --onto option was supplied.  This has the exact same effect as
@@ -255,6 +257,15 @@ OPTIONS
 --preserve-merges::
 	Instead of ignoring merges, try to recreate them.
 
+--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
+	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
+	instead.
+
 include::merge-strategies.txt[]
 
 NOTES
-- 
1.6.1.71.gaaa47.dirty

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [INTERDIFF v3 2/4] rebase: learn to rebase root commit
  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:41                     ` Thomas Rast
  2009-01-02 23:06                     ` [PATCH " Junio C Hamano
  2 siblings, 0 replies; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 22:41 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, bss

The interdiff to v2.

diff --git a/git-rebase.sh b/git-rebase.sh
index 89de3c4..9437e51 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -354,17 +354,20 @@ if test -z "$rebase_root"; then
 	shift
 	upstream=`git rev-parse --verify "${upstream_name}^0"` ||
 	die "invalid upstream $upstream_name"
+	unset root_flag
+else
+	test -z "$newbase" && die "--root must be used with --onto"
+	unset upstream_name
+	unset upstream
+	root_flag="--root"
 fi
 
-test ! -z "$rebase_root" -a -z "$newbase" &&
-	die "--root must be used with --onto"
-
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
 onto=$(git rev-parse --verify "${onto_name}^0") || exit
 
 # If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook ${upstream_name+"$upstream_name"} "$@"
+run_pre_rebase_hook $root_flag $upstream_name "$@"
 
 # If the branch to rebase is given, that is the branch we will rebase
 # $branch_name -- branch being rebased, or HEAD (already detached)
@@ -403,8 +406,8 @@ case "$#" in
 esac
 orig_head=$branch
 
-# Now we are rebasing commits $upstream..$branch (or simply $branch
-# with --root) on top of $onto
+# Now we are rebasing commits $upstream..$branch (or with --root,
+# everything leading up to $branch) on top of $onto
 
 # Check if we are already based on $onto with linear history,
 # but this should be done only when upstream and onto are the same.
@@ -441,17 +444,15 @@ then
 fi
 
 if test ! -z "$rebase_root"; then
-	revisions="$orig_head"
-	fp_flag="--root"
+	revisions="$onto..$orig_head"
 else
 	revisions="$upstream..$orig_head"
-	fp_flag="--ignore-if-in-upstream"
 fi
 
 if test -z "$do_merge"
 then
-	git format-patch -k --stdout --full-index "$fp_flag" \
-		"$revisions" |
+	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+		$root_flag "$revisions" |
 	git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
 	move_to_original_branch
 	ret=$?
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
index 63ec5e6..1978512 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -15,7 +15,9 @@ test_expect_success 'prepare repository' '
 	git commit -m 2 &&
 	git symbolic-ref HEAD refs/heads/other &&
 	rm .git/index &&
-	rm A &&
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1b &&
 	echo 3 > B &&
 	git add B &&
 	git commit -m 3 &&
@@ -28,6 +30,14 @@ 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 &&
+#!$SHELL_PATH
+echo "\$1,\$2" >.git/PRE-REBASE-INPUT
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
 cat > expect <<EOF
 4
 3
@@ -42,6 +52,10 @@ test_expect_success 'rebase --root --onto <newbase>' '
 	test_cmp expect rebased
 '
 
+test_expect_success 'pre-rebase got correct input (1)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
 test_expect_success 'rebase --root --onto <newbase> <branch>' '
 	git branch work2 other &&
 	git rebase --root --onto master work2 &&
@@ -49,4 +63,24 @@ test_expect_success 'rebase --root --onto <newbase> <branch>' '
 	test_cmp expect rebased2
 '
 
+test_expect_success 'pre-rebase got correct input (2)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
+'
+
+test_expect_success 'setup pre-rebase hook that fails' '
+	mkdir -p .git/hooks &&
+	cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+false
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
+
+test_expect_success 'pre-rebase hook stops rebase' '
+	git checkout -b stops1 other &&
+	GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
+	test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+	test 0 = $(git rev-list other...stops1 | wc -l)
+'
+
 test_done

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [INTERDIFF v3 3/4] rebase -i: learn to rebase root commit
  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                       ` Thomas Rast
  1 sibling, 0 replies; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 22:41 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, bss

This is the interdiff to v2.  Some of the changes are noise from 1/4.

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index d6f54eb..14d3e38 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -238,7 +238,9 @@ pick_one_preserving_merges () {
 			if test -f "$DROPPED"/$p
 			then
 				fast_forward=f
-				pend=" $(cat "$DROPPED"/$p)$pend"
+				replacement="$(cat "$DROPPED"/$p)"
+				test -z "$replacement" && replacement=root
+				pend=" $replacement$pend"
 			else
 				new_parents="$new_parents $p"
 			fi
@@ -569,7 +571,6 @@ first and then run 'git rebase --continue' again."
 		;;
 	--)
 		shift
-		run_pre_rebase_hook ${1+"$@"}
 		test ! -z "$REBASE_ROOT" -o $# -eq 1 -o $# -eq 2 || usage
 		test -d "$DOTEST" &&
 			die "Interactive rebase already started"
@@ -577,19 +578,22 @@ first and then run 'git rebase --continue' again."
 		git var GIT_COMMITTER_IDENT >/dev/null ||
 			die "You need to set your committer info first"
 
-		comment_for_reflog start
-
-		require_clean_work_tree
-
 		if test -z "$REBASE_ROOT"
 		then
+			UPSTREAM_ARG="$1"
 			UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
 			test -z "$ONTO" && ONTO=$UPSTREAM
 			shift
 		else
+			UPSTREAM_ARG=--root
 			test -z "$ONTO" &&
 				die "You must specify --onto when using --root"
 		fi
+		run_pre_rebase_hook "$UPSTREAM_ARG" "$@"
+
+		comment_for_reflog start
+
+		require_clean_work_tree
 
 		if test ! -z "$1"
 		then
@@ -651,7 +655,7 @@ first and then run 'git rebase --continue' again."
 			REVISIONS=$UPSTREAM...$HEAD
 			SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD
 		else
-			REVISIONS=$HEAD
+			REVISIONS=$ONTO...$HEAD
 			SHORTREVISIONS=$SHORTHEAD
 		fi
 		git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
@@ -710,6 +714,7 @@ first and then run 'git rebase --continue' again."
 				fi
 			done
 		fi
+
 		test -s "$TODO" || echo noop >> "$TODO"
 		cat >> "$TODO" << EOF
 
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
index c845dfc..cbf3414 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -15,7 +15,9 @@ test_expect_success 'prepare repository' '
 	git commit -m 2 &&
 	git symbolic-ref HEAD refs/heads/other &&
 	rm .git/index &&
-	rm A &&
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1b &&
 	echo 3 > B &&
 	git add B &&
 	git commit -m 3 &&
@@ -28,6 +30,14 @@ 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 &&
+#!$SHELL_PATH
+echo "\$1,\$2" >.git/PRE-REBASE-INPUT
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
 cat > expect <<EOF
 4
 3
@@ -42,6 +52,10 @@ test_expect_success 'rebase --root --onto <newbase>' '
 	test_cmp expect rebased
 '
 
+test_expect_success 'pre-rebase got correct input (1)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
 test_expect_success 'rebase --root --onto <newbase> <branch>' '
 	git branch work2 other &&
 	git rebase --root --onto master work2 &&
@@ -49,6 +63,10 @@ test_expect_success 'rebase --root --onto <newbase> <branch>' '
 	test_cmp expect rebased2
 '
 
+test_expect_success 'pre-rebase got correct input (2)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
+'
+
 test_expect_success 'rebase -i --root --onto <newbase>' '
 	git checkout -b work3 other &&
 	GIT_EDITOR=: git rebase -i --root --onto master &&
@@ -56,6 +74,10 @@ test_expect_success 'rebase -i --root --onto <newbase>' '
 	test_cmp expect rebased3
 '
 
+test_expect_success 'pre-rebase got correct input (3)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
 test_expect_success 'rebase -i --root --onto <newbase> <branch>' '
 	git branch work4 other &&
 	GIT_EDITOR=: git rebase -i --root --onto master work4 &&
@@ -63,6 +85,10 @@ test_expect_success 'rebase -i --root --onto <newbase> <branch>' '
 	test_cmp expect rebased4
 '
 
+test_expect_success 'pre-rebase got correct input (4)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4
+'
+
 test_expect_success 'rebase -i -p with linear history' '
 	git checkout -b work5 other &&
 	GIT_EDITOR=: git rebase -i -p --root --onto master &&
@@ -70,6 +96,10 @@ test_expect_success 'rebase -i -p with linear history' '
 	test_cmp expect rebased5
 '
 
+test_expect_success 'pre-rebase got correct input (5)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
 test_expect_success 'set up merge history' '
 	git checkout other^ &&
 	git checkout -b side &&
@@ -131,4 +161,27 @@ test_expect_success 'rebase -i -p with two roots' '
 	test_cmp expect-third rebased7
 '
 
+test_expect_success 'setup pre-rebase hook that fails' '
+	mkdir -p .git/hooks &&
+	cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+false
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
+
+test_expect_success 'pre-rebase hook stops rebase' '
+	git checkout -b stops1 other &&
+	GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
+	test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+	test 0 = $(git rev-list other...stops1 | wc -l)
+'
+
+test_expect_success 'pre-rebase hook stops rebase -i' '
+	git checkout -b stops2 other &&
+	GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
+	test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
+	test 0 = $(git rev-list other...stops2 | wc -l)
+'
+
 test_done

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/3] rebase: learn to rebase root commit
  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:49                 ` Junio C Hamano
  2009-01-02 22:54                   ` Thomas Rast
  1 sibling, 1 reply; 27+ messages in thread
From: Junio C Hamano @ 2009-01-02 22:49 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git

Thomas Rast <trast@student.ethz.ch> writes:

> I'm still not sure what ${1+"$@"} was about by the way.

It is one of the many old-timer's portability idioms that let us work
around bugs in some ancient shell implementations.

${1+"$@"} should be equivalent to "$@" in modern Bourne shell variants
that are POSIX compliant.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/3] rebase: learn to rebase root commit
  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
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 22:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 477 bytes --]

Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> 
> > I'm still not sure what ${1+"$@"} was about by the way.
> 
> It is one of the many old-timer's portability idioms that let us work
> around bugs in some ancient shell implementations.
> 
> ${1+"$@"} should be equivalent to "$@" in modern Bourne shell variants
> that are POSIX compliant.

So do you want me to change it back to ${1+"$@"}?

-- 
Thomas Rast
trast@{inf,student}.ethz.ch


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 2/4] rebase: learn to rebase root commit
  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:41                     ` [INTERDIFF v3 2/4] rebase: " Thomas Rast
@ 2009-01-02 23:06                     ` Junio C Hamano
  2009-01-02 23:45                       ` [PATCH v3.1] " Thomas Rast
  2 siblings, 1 reply; 27+ messages in thread
From: Junio C Hamano @ 2009-01-02 23:06 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, bss


>  # If a hook exists, give it a chance to interrupt
> -run_pre_rebase_hook ${1+"$@"}
> +run_pre_rebase_hook $root_flag $upstream_name "$@"

You'd have to quote the $upstream_name properly here, because the original
command line could well have been:

	$ git rebase 'master@{3 days ago}'

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 1/3] rebase: learn to rebase root commit
  2009-01-02 22:54                   ` Thomas Rast
@ 2009-01-02 23:07                     ` Junio C Hamano
  0 siblings, 0 replies; 27+ messages in thread
From: Junio C Hamano @ 2009-01-02 23:07 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git

Thomas Rast <trast@student.ethz.ch> writes:

>> ${1+"$@"} should be equivalent to "$@" in modern Bourne shell variants
>> that are POSIX compliant.
>
> So do you want me to change it back to ${1+"$@"}?

No.  I was merely answering your question.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3.1] rebase: learn to rebase root commit
  2009-01-02 23:06                     ` [PATCH " Junio C Hamano
@ 2009-01-02 23:45                       ` Thomas Rast
  2009-01-05 17:35                         ` [PATCH v3.2] " Thomas Rast
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Rast @ 2009-01-02 23:45 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Teach git-rebase a new option --root, which instructs it to rebase the
entire history leading up to <branch>.  This option must be used with
--onto <newbase>, and causes commits that already exist in <newbase>
to be skipped.  (Normal operation skips commits that already exist in
<upstream> instead.)

The main use-case is with git-svn: suppose you start hacking (perhaps
offline) on a new project, but later notice you want to commit this
work to SVN.  You will have to rebase the entire history, including
the root commit, on a (possibly empty) commit coming from git-svn, to
establish a history connection.  This previously had to be done by
cherry-picking the root commit manually.

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

---

Junio C Hamano wrote:
> 
> >  # If a hook exists, give it a chance to interrupt
> > -run_pre_rebase_hook ${1+"$@"}
> > +run_pre_rebase_hook $root_flag $upstream_name "$@"
> 
> You'd have to quote the $upstream_name properly here, because the original
> command line could well have been:
> 
> 	$ git rebase 'master@{3 days ago}'

Right, thanks.  Let's fix it like this, so I won't have to look up
what the various special parameter expansion characters mean.
($root_flag is still used further below.)

>> --- a/git-rebase.sh
>> +++ b/git-rebase.sh
>> @@ -355,11 +355,13 @@ if test -z "$rebase_root"; then
>>         upstream=`git rev-parse --verify "${upstream_name}^0"` ||
>>         die "invalid upstream $upstream_name"
>>         unset root_flag
>> +       upstream_arg="$upstream_name"
>>  else
>>         test -z "$newbase" && die "--root must be used with --onto"
>>         unset upstream_name
>>         unset upstream
>>         root_flag="--root"
>> +       upstream_arg="$root_flag"
>>  fi
>>  
>>  # Make sure the branch to rebase onto is valid.
>> @@ -367,7 +369,7 @@ onto_name=${newbase-"$upstream_name"}
>>  onto=$(git rev-parse --verify "${onto_name}^0") || exit
>>  
>>  # If a hook exists, give it a chance to interrupt
>> -run_pre_rebase_hook $root_flag $upstream_name "$@"
>> +run_pre_rebase_hook "$upstream_arg" "$@"
>>  
>>  # If the branch to rebase is given, that is the branch we will rebase
>>  # $branch_name -- branch being rebased, or HEAD (already detached)

 git-rebase.sh          |   52 ++++++++++++++++++++--------
 t/t3412-rebase-root.sh |   86 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 123 insertions(+), 15 deletions(-)

diff --git a/git-rebase.sh b/git-rebase.sh
index ebd4df3..d1083f1 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
 git_am_opt=
+rebase_root=
 
 continue_merge () {
 	test -n "$prev_head" || die "prev_head must be defined"
@@ -297,6 +298,9 @@ do
 	-C*)
 		git_am_opt="$git_am_opt $1"
 		;;
+	--root)
+		rebase_root=t
+		;;
 	-*)
 		usage
 		;;
@@ -344,17 +348,28 @@ case "$diff" in
 	;;
 esac
 
+if test -z "$rebase_root"; then
 # The upstream head must be given.  Make sure it is valid.
-upstream_name="$1"
-upstream=`git rev-parse --verify "${upstream_name}^0"` ||
-    die "invalid upstream $upstream_name"
+	upstream_name="$1"
+	shift
+	upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+	die "invalid upstream $upstream_name"
+	unset root_flag
+	upstream_arg="$upstream_name"
+else
+	test -z "$newbase" && die "--root must be used with --onto"
+	unset upstream_name
+	unset upstream
+	root_flag="--root"
+	upstream_arg="$root_flag"
+fi
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
 onto=$(git rev-parse --verify "${onto_name}^0") || exit
 
 # If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook ${1+"$@"}
+run_pre_rebase_hook "$upstream_arg" "$@"
 
 # If the branch to rebase is given, that is the branch we will rebase
 # $branch_name -- branch being rebased, or HEAD (already detached)
@@ -362,16 +377,16 @@ run_pre_rebase_hook ${1+"$@"}
 # $head_name -- refs/heads/<that-branch> or "detached HEAD"
 switch_to=
 case "$#" in
-2)
+1)
 	# Is it "rebase other $branchname" or "rebase other $commit"?
-	branch_name="$2"
-	switch_to="$2"
+	branch_name="$1"
+	switch_to="$1"
 
-	if git show-ref --verify --quiet -- "refs/heads/$2" &&
-	   branch=$(git rev-parse -q --verify "refs/heads/$2")
+	if git show-ref --verify --quiet -- "refs/heads/$1" &&
+	   branch=$(git rev-parse -q --verify "refs/heads/$1")
 	then
-		head_name="refs/heads/$2"
-	elif branch=$(git rev-parse -q --verify "$2")
+		head_name="refs/heads/$1"
+	elif branch=$(git rev-parse -q --verify "$1")
 	then
 		head_name="detached HEAD"
 	else
@@ -393,7 +408,8 @@ case "$#" in
 esac
 orig_head=$branch
 
-# Now we are rebasing commits $upstream..$branch on top of $onto
+# Now we are rebasing commits $upstream..$branch (or with --root,
+# everything leading up to $branch) on top of $onto
 
 # Check if we are already based on $onto with linear history,
 # but this should be done only when upstream and onto are the same.
@@ -429,10 +445,16 @@ then
 	exit 0
 fi
 
+if test ! -z "$rebase_root"; then
+	revisions="$onto..$orig_head"
+else
+	revisions="$upstream..$orig_head"
+fi
+
 if test -z "$do_merge"
 then
 	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-		"$upstream..$orig_head" |
+		$root_flag "$revisions" |
 	git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
 	move_to_original_branch
 	ret=$?
@@ -455,7 +477,7 @@ echo "$orig_head" > "$dotest/orig-head"
 echo "$head_name" > "$dotest/head-name"
 
 msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"`
+for cmt in `git rev-list --reverse --no-merges "$revisions"`
 do
 	msgnum=$(($msgnum + 1))
 	echo "$cmt" > "$dotest/cmt.$msgnum"
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
new file mode 100755
index 0000000..1978512
--- /dev/null
+++ b/t/t3412-rebase-root.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='git rebase --root
+
+Tests if git rebase --root --onto <newparent> can rebase the root commit.
+'
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' '
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1 &&
+	echo 2 > A &&
+	git add A &&
+	git commit -m 2 &&
+	git symbolic-ref HEAD refs/heads/other &&
+	rm .git/index &&
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1b &&
+	echo 3 > B &&
+	git add B &&
+	git commit -m 3 &&
+	echo 4 > B &&
+	git add B &&
+	git commit -m 4
+'
+
+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 &&
+#!$SHELL_PATH
+echo "\$1,\$2" >.git/PRE-REBASE-INPUT
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
+cat > expect <<EOF
+4
+3
+2
+1
+EOF
+
+test_expect_success 'rebase --root --onto <newbase>' '
+	git checkout -b work &&
+	git rebase --root --onto master &&
+	git log --pretty=tformat:"%s" > rebased &&
+	test_cmp expect rebased
+'
+
+test_expect_success 'pre-rebase got correct input (1)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase --root --onto <newbase> <branch>' '
+	git branch work2 other &&
+	git rebase --root --onto master work2 &&
+	git log --pretty=tformat:"%s" > rebased2 &&
+	test_cmp expect rebased2
+'
+
+test_expect_success 'pre-rebase got correct input (2)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
+'
+
+test_expect_success 'setup pre-rebase hook that fails' '
+	mkdir -p .git/hooks &&
+	cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+false
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
+
+test_expect_success 'pre-rebase hook stops rebase' '
+	git checkout -b stops1 other &&
+	GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
+	test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+	test 0 = $(git rev-list other...stops1 | wc -l)
+'
+
+test_done
-- 
tg: (cb706e1..) t/rr-normal (depends on: origin/master t/rebase-hook-order)

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3.2] rebase: learn to rebase root commit
  2009-01-02 23:45                       ` [PATCH v3.1] " Thomas Rast
@ 2009-01-05 17:35                         ` Thomas Rast
  2009-01-06  8:19                           ` Junio C Hamano
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Rast @ 2009-01-05 17:35 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Teach git-rebase a new option --root, which instructs it to rebase the
entire history leading up to <branch>.  This option must be used with
--onto <newbase>, and causes commits that already exist in <newbase>
to be skipped.  (Normal operation skips commits that already exist in
<upstream> instead.)

The main use-case is with git-svn: suppose you start hacking (perhaps
offline) on a new project, but later notice you want to commit this
work to SVN.  You will have to rebase the entire history, including
the root commit, on a (possibly empty) commit coming from git-svn, to
establish a history connection.  This previously had to be done by
cherry-picking the root commit manually.

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

---

Since this is still in 'pu', can you replace it again?  The test case
I designed for v3, to check that it also skips identical commits,
actually makes the _root_ commit a duplicate.  I could sleep better at
night if the root commit was actually part of the rebase :-)

3/4 is not affected patch-wise, though it of course inherits the same
test history.

Thanks!


Interdiff:

>> diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
>> index 1978512..dd91910 100755
>> --- a/t/t3412-rebase-root.sh
>> +++ b/t/t3412-rebase-root.sh
>> @@ -15,12 +15,12 @@ test_expect_success 'prepare repository' '
>>         git commit -m 2 &&
>>         git symbolic-ref HEAD refs/heads/other &&
>>         rm .git/index &&
>> -       echo 1 > A &&
>> -       git add A &&
>> -       git commit -m 1b &&
>>         echo 3 > B &&
>>         git add B &&
>>         git commit -m 3 &&
>> +       echo 1 > A &&
>> +       git add A &&
>> +       git commit -m 1b &&
>>         echo 4 > B &&
>>         git add B &&
>>         git commit -m 4


 git-rebase.sh          |   52 ++++++++++++++++++++--------
 t/t3412-rebase-root.sh |   86 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 123 insertions(+), 15 deletions(-)

diff --git a/git-rebase.sh b/git-rebase.sh
index ebd4df3..d1083f1 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
 git_am_opt=
+rebase_root=
 
 continue_merge () {
 	test -n "$prev_head" || die "prev_head must be defined"
@@ -297,6 +298,9 @@ do
 	-C*)
 		git_am_opt="$git_am_opt $1"
 		;;
+	--root)
+		rebase_root=t
+		;;
 	-*)
 		usage
 		;;
@@ -344,17 +348,28 @@ case "$diff" in
 	;;
 esac
 
+if test -z "$rebase_root"; then
 # The upstream head must be given.  Make sure it is valid.
-upstream_name="$1"
-upstream=`git rev-parse --verify "${upstream_name}^0"` ||
-    die "invalid upstream $upstream_name"
+	upstream_name="$1"
+	shift
+	upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+	die "invalid upstream $upstream_name"
+	unset root_flag
+	upstream_arg="$upstream_name"
+else
+	test -z "$newbase" && die "--root must be used with --onto"
+	unset upstream_name
+	unset upstream
+	root_flag="--root"
+	upstream_arg="$root_flag"
+fi
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
 onto=$(git rev-parse --verify "${onto_name}^0") || exit
 
 # If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook ${1+"$@"}
+run_pre_rebase_hook "$upstream_arg" "$@"
 
 # If the branch to rebase is given, that is the branch we will rebase
 # $branch_name -- branch being rebased, or HEAD (already detached)
@@ -362,16 +377,16 @@ run_pre_rebase_hook ${1+"$@"}
 # $head_name -- refs/heads/<that-branch> or "detached HEAD"
 switch_to=
 case "$#" in
-2)
+1)
 	# Is it "rebase other $branchname" or "rebase other $commit"?
-	branch_name="$2"
-	switch_to="$2"
+	branch_name="$1"
+	switch_to="$1"
 
-	if git show-ref --verify --quiet -- "refs/heads/$2" &&
-	   branch=$(git rev-parse -q --verify "refs/heads/$2")
+	if git show-ref --verify --quiet -- "refs/heads/$1" &&
+	   branch=$(git rev-parse -q --verify "refs/heads/$1")
 	then
-		head_name="refs/heads/$2"
-	elif branch=$(git rev-parse -q --verify "$2")
+		head_name="refs/heads/$1"
+	elif branch=$(git rev-parse -q --verify "$1")
 	then
 		head_name="detached HEAD"
 	else
@@ -393,7 +408,8 @@ case "$#" in
 esac
 orig_head=$branch
 
-# Now we are rebasing commits $upstream..$branch on top of $onto
+# Now we are rebasing commits $upstream..$branch (or with --root,
+# everything leading up to $branch) on top of $onto
 
 # Check if we are already based on $onto with linear history,
 # but this should be done only when upstream and onto are the same.
@@ -429,10 +445,16 @@ then
 	exit 0
 fi
 
+if test ! -z "$rebase_root"; then
+	revisions="$onto..$orig_head"
+else
+	revisions="$upstream..$orig_head"
+fi
+
 if test -z "$do_merge"
 then
 	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-		"$upstream..$orig_head" |
+		$root_flag "$revisions" |
 	git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
 	move_to_original_branch
 	ret=$?
@@ -455,7 +477,7 @@ echo "$orig_head" > "$dotest/orig-head"
 echo "$head_name" > "$dotest/head-name"
 
 msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"`
+for cmt in `git rev-list --reverse --no-merges "$revisions"`
 do
 	msgnum=$(($msgnum + 1))
 	echo "$cmt" > "$dotest/cmt.$msgnum"
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
new file mode 100755
index 0000000..dd91910
--- /dev/null
+++ b/t/t3412-rebase-root.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='git rebase --root
+
+Tests if git rebase --root --onto <newparent> can rebase the root commit.
+'
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' '
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1 &&
+	echo 2 > A &&
+	git add A &&
+	git commit -m 2 &&
+	git symbolic-ref HEAD refs/heads/other &&
+	rm .git/index &&
+	echo 3 > B &&
+	git add B &&
+	git commit -m 3 &&
+	echo 1 > A &&
+	git add A &&
+	git commit -m 1b &&
+	echo 4 > B &&
+	git add B &&
+	git commit -m 4
+'
+
+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 &&
+#!$SHELL_PATH
+echo "\$1,\$2" >.git/PRE-REBASE-INPUT
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
+cat > expect <<EOF
+4
+3
+2
+1
+EOF
+
+test_expect_success 'rebase --root --onto <newbase>' '
+	git checkout -b work &&
+	git rebase --root --onto master &&
+	git log --pretty=tformat:"%s" > rebased &&
+	test_cmp expect rebased
+'
+
+test_expect_success 'pre-rebase got correct input (1)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase --root --onto <newbase> <branch>' '
+	git branch work2 other &&
+	git rebase --root --onto master work2 &&
+	git log --pretty=tformat:"%s" > rebased2 &&
+	test_cmp expect rebased2
+'
+
+test_expect_success 'pre-rebase got correct input (2)' '
+	test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
+'
+
+test_expect_success 'setup pre-rebase hook that fails' '
+	mkdir -p .git/hooks &&
+	cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+false
+EOF
+	chmod +x .git/hooks/pre-rebase
+'
+
+test_expect_success 'pre-rebase hook stops rebase' '
+	git checkout -b stops1 other &&
+	GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
+	test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+	test 0 = $(git rev-list other...stops1 | wc -l)
+'
+
+test_done
-- 
tg: (f62b77f..) t/rr-normal (depends on: origin/master t/rebase-hook-order)

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v3.2] rebase: learn to rebase root commit
  2009-01-05 17:35                         ` [PATCH v3.2] " Thomas Rast
@ 2009-01-06  8:19                           ` Junio C Hamano
  0 siblings, 0 replies; 27+ messages in thread
From: Junio C Hamano @ 2009-01-06  8:19 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git

Thomas Rast <trast@student.ethz.ch> writes:

> Since this is still in 'pu', can you replace it again?

Thanks, done.

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2009-01-06  8:21 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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             ` [PATCH v2 2/3] rebase -i: " Thomas Rast
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

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).