@matheusbsbs / matheus-bagliano / commits / 190f53232d
Fork 0

rebase: learn to rebase root commit

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.) One possible 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> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Thomas Rast committed Jan 5, 2009 at 18:35 UTC 190f53232d1e92a57843df90e889bcfea39620d3
2 files changed +126 -16
git-rebase.sh
+40 -16
@@ -3,7 +3,7 @@
3 # Copyright (c) 2005 Junio C Hamano.
4 #
5
6 -USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
6 +USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]'
7 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
8 same name. When the --onto option is provided the new branch starts
9 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge
47 prec=4
48 verbose=
49 git_am_opt=
50 +rebase_root=
51
52 continue_merge () {
53 test -n "$prev_head" || die "prev_head must be defined"
@@ -297,6 +298,9 @@ do
298 -C*)
299 git_am_opt="$git_am_opt $1"
300 ;;
301 + --root)
302 + rebase_root=t
303 + ;;
304 -*)
305 usage
306 ;;
@@ -344,17 +348,29 @@ case "$diff" in
348 ;;
349 esac
350
347 -# The upstream head must be given. Make sure it is valid.
348 -upstream_name="$1"
349 -upstream=`git rev-parse --verify "${upstream_name}^0"` ||
350 - die "invalid upstream $upstream_name"
351 +if test -z "$rebase_root"
352 +then
353 + # The upstream head must be given. Make sure it is valid.
354 + upstream_name="$1"
355 + shift
356 + upstream=`git rev-parse --verify "${upstream_name}^0"` ||
357 + die "invalid upstream $upstream_name"
358 + unset root_flag
359 + upstream_arg="$upstream_name"
360 +else
361 + test -z "$newbase" && die "--root must be used with --onto"
362 + unset upstream_name
363 + unset upstream
364 + root_flag="--root"
365 + upstream_arg="$root_flag"
366 +fi
367
368 # Make sure the branch to rebase onto is valid.
369 onto_name=${newbase-"$upstream_name"}
370 onto=$(git rev-parse --verify "${onto_name}^0") || exit
371
372 # If a hook exists, give it a chance to interrupt
357 -run_pre_rebase_hook ${1+"$@"}
373 +run_pre_rebase_hook "$upstream_arg" "$@"
374
375 # If the branch to rebase is given, that is the branch we will rebase
376 # $branch_name -- branch being rebased, or HEAD (already detached)
@@ -362,16 +378,16 @@ run_pre_rebase_hook ${1+"$@"}
378 # $head_name -- refs/heads/<that-branch> or "detached HEAD"
379 switch_to=
380 case "$#" in
365 -2)
381 +1)
382 # Is it "rebase other $branchname" or "rebase other $commit"?
367 - branch_name="$2"
368 - switch_to="$2"
383 + branch_name="$1"
384 + switch_to="$1"
385
370 - if git show-ref --verify --quiet -- "refs/heads/$2" &&
371 - branch=$(git rev-parse -q --verify "refs/heads/$2")
386 + if git show-ref --verify --quiet -- "refs/heads/$1" &&
387 + branch=$(git rev-parse -q --verify "refs/heads/$1")
388 then
373 - head_name="refs/heads/$2"
374 - elif branch=$(git rev-parse -q --verify "$2")
389 + head_name="refs/heads/$1"
390 + elif branch=$(git rev-parse -q --verify "$1")
391 then
392 head_name="detached HEAD"
393 else
@@ -393,7 +409,8 @@ case "$#" in
409 esac
410 orig_head=$branch
411
396 -# Now we are rebasing commits $upstream..$branch on top of $onto
412 +# Now we are rebasing commits $upstream..$branch (or with --root,
413 +# everything leading up to $branch) on top of $onto
414
415 # Check if we are already based on $onto with linear history,
416 # but this should be done only when upstream and onto are the same.
@@ -429,10 +446,17 @@ then
446 exit 0
447 fi
448
449 +if test -n "$rebase_root"
450 +then
451 + revisions="$onto..$orig_head"
452 +else
453 + revisions="$upstream..$orig_head"
454 +fi
455 +
456 if test -z "$do_merge"
457 then
458 git format-patch -k --stdout --full-index --ignore-if-in-upstream \
435 - "$upstream..$orig_head" |
459 + $root_flag "$revisions" |
460 git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
461 move_to_original_branch
462 ret=$?
@@ -455,7 +479,7 @@ echo "$orig_head" > "$dotest/orig-head"
479 echo "$head_name" > "$dotest/head-name"
480
481 msgnum=0
458 -for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"`
482 +for cmt in `git rev-list --reverse --no-merges "$revisions"`
483 do
484 msgnum=$(($msgnum + 1))
485 echo "$cmt" > "$dotest/cmt.$msgnum"
t/t3412-rebase-root.sh new
+86
@@ -0,0 +1,86 @@
1 +#!/bin/sh
2 +
3 +test_description='git rebase --root
4 +
5 +Tests if git rebase --root --onto <newparent> can rebase the root commit.
6 +'
7 +. ./test-lib.sh
8 +
9 +test_expect_success 'prepare repository' '
10 + echo 1 > A &&
11 + git add A &&
12 + git commit -m 1 &&
13 + echo 2 > A &&
14 + git add A &&
15 + git commit -m 2 &&
16 + git symbolic-ref HEAD refs/heads/other &&
17 + rm .git/index &&
18 + echo 3 > B &&
19 + git add B &&
20 + git commit -m 3 &&
21 + echo 1 > A &&
22 + git add A &&
23 + git commit -m 1b &&
24 + echo 4 > B &&
25 + git add B &&
26 + git commit -m 4
27 +'
28 +
29 +test_expect_success 'rebase --root expects --onto' '
30 + test_must_fail git rebase --root
31 +'
32 +
33 +test_expect_success 'setup pre-rebase hook' '
34 + mkdir -p .git/hooks &&
35 + cat >.git/hooks/pre-rebase <<EOF &&
36 +#!$SHELL_PATH
37 +echo "\$1,\$2" >.git/PRE-REBASE-INPUT
38 +EOF
39 + chmod +x .git/hooks/pre-rebase
40 +'
41 +cat > expect <<EOF
42 +4
43 +3
44 +2
45 +1
46 +EOF
47 +
48 +test_expect_success 'rebase --root --onto <newbase>' '
49 + git checkout -b work &&
50 + git rebase --root --onto master &&
51 + git log --pretty=tformat:"%s" > rebased &&
52 + test_cmp expect rebased
53 +'
54 +
55 +test_expect_success 'pre-rebase got correct input (1)' '
56 + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
57 +'
58 +
59 +test_expect_success 'rebase --root --onto <newbase> <branch>' '
60 + git branch work2 other &&
61 + git rebase --root --onto master work2 &&
62 + git log --pretty=tformat:"%s" > rebased2 &&
63 + test_cmp expect rebased2
64 +'
65 +
66 +test_expect_success 'pre-rebase got correct input (2)' '
67 + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
68 +'
69 +
70 +test_expect_success 'setup pre-rebase hook that fails' '
71 + mkdir -p .git/hooks &&
72 + cat >.git/hooks/pre-rebase <<EOF &&
73 +#!$SHELL_PATH
74 +false
75 +EOF
76 + chmod +x .git/hooks/pre-rebase
77 +'
78 +
79 +test_expect_success 'pre-rebase hook stops rebase' '
80 + git checkout -b stops1 other &&
81 + GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
82 + test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
83 + test 0 = $(git rev-list other...stops1 | wc -l)
84 +'
85 +
86 +test_done