@matheusbsbs / matheus-bagliano / commits / d911d1465d
Fork 0

rebase -i: learn to rebase root commit

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

Thomas Rast committed Jan 2, 2009 at 23:28 UTC d911d1465d134cbd126e531a544f33b13f4e855a
2 files changed +179 -31
git-rebase--interactive.sh
+78 -31
@@ -27,6 +27,7 @@ continue continue rebasing process
27 abort abort rebasing process and restore original branch
28 skip skip current patch and continue rebasing process
29 no-verify override pre-rebase hook from stopping the operation
30 +root rebase all reachable commmits up to the root(s)
31 "
32
33 . git-sh-setup
@@ -44,6 +45,7 @@ STRATEGY=
45 ONTO=
46 VERBOSE=
47 OK_TO_SKIP_PRE_REBASE=
48 +REBASE_ROOT=
49
50 GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
51 mark the corrected paths with 'git add <paths>', and
@@ -154,6 +156,11 @@ pick_one () {
156 output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
157 test -d "$REWRITTEN" &&
158 pick_one_preserving_merges "$@" && return
159 + if test ! -z "$REBASE_ROOT"
160 + then
161 + output git cherry-pick "$@"
162 + return
163 + fi
164 parent_sha1=$(git rev-parse --verify $sha1^) ||
165 die "Could not get the parent of $sha1"
166 current_sha1=$(git rev-parse --verify HEAD)
@@ -197,7 +204,11 @@ pick_one_preserving_merges () {
204
205 # rewrite parents; if none were rewritten, we can fast-forward.
206 new_parents=
200 - pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)"
207 + pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)"
208 + if test "$pend" = " "
209 + then
210 + pend=" root"
211 + fi
212 while [ "$pend" != "" ]
213 do
214 p=$(expr "$pend" : ' \([^ ]*\)')
@@ -227,7 +238,9 @@ pick_one_preserving_merges () {
238 if test -f "$DROPPED"/$p
239 then
240 fast_forward=f
230 - pend=" $(cat "$DROPPED"/$p)$pend"
241 + replacement="$(cat "$DROPPED"/$p)"
242 + test -z "$replacement" && replacement=root
243 + pend=" $replacement$pend"
244 else
245 new_parents="$new_parents $p"
246 fi
@@ -443,6 +456,7 @@ get_saved_options () {
456 test -d "$REWRITTEN" && PRESERVE_MERGES=t
457 test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
458 test -f "$DOTEST"/verbose && VERBOSE=t
459 + test ! -s "$DOTEST"/upstream && REBASE_ROOT=t
460 }
461
462 while test $# != 0
@@ -547,6 +561,9 @@ first and then run 'git rebase --continue' again."
561 -i)
562 # yeah, we know
563 ;;
564 + --root)
565 + REBASE_ROOT=t
566 + ;;
567 --onto)
568 shift
569 ONTO=$(git rev-parse --verify "$1") ||
@@ -554,28 +571,36 @@ first and then run 'git rebase --continue' again."
571 ;;
572 --)
573 shift
557 - test $# -eq 1 -o $# -eq 2 || usage
574 + test ! -z "$REBASE_ROOT" -o $# -eq 1 -o $# -eq 2 || usage
575 test -d "$DOTEST" &&
576 die "Interactive rebase already started"
577
578 git var GIT_COMMITTER_IDENT >/dev/null ||
579 die "You need to set your committer info first"
580
564 - UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
565 - run_pre_rebase_hook ${1+"$@"}
581 + if test -z "$REBASE_ROOT"
582 + then
583 + UPSTREAM_ARG="$1"
584 + UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
585 + test -z "$ONTO" && ONTO=$UPSTREAM
586 + shift
587 + else
588 + UPSTREAM_ARG=--root
589 + test -z "$ONTO" &&
590 + die "You must specify --onto when using --root"
591 + fi
592 + run_pre_rebase_hook "$UPSTREAM_ARG" "$@"
593
594 comment_for_reflog start
595
596 require_clean_work_tree
597
571 - test -z "$ONTO" && ONTO=$UPSTREAM
572 -
573 - if test ! -z "$2"
598 + if test ! -z "$1"
599 then
575 - output git show-ref --verify --quiet "refs/heads/$2" ||
576 - die "Invalid branchname: $2"
577 - output git checkout "$2" ||
578 - die "Could not checkout $2"
600 + output git show-ref --verify --quiet "refs/heads/$1" ||
601 + die "Invalid branchname: $1"
602 + output git checkout "$1" ||
603 + die "Could not checkout $1"
604 fi
605
606 HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
@@ -599,12 +624,19 @@ first and then run 'git rebase --continue' again."
624 # This ensures that commits on merged, but otherwise
625 # unrelated side branches are left alone. (Think "X"
626 # in the man page's example.)
602 - mkdir "$REWRITTEN" &&
603 - for c in $(git merge-base --all $HEAD $UPSTREAM)
604 - do
605 - echo $ONTO > "$REWRITTEN"/$c ||
627 + if test -z "$REBASE_ROOT"
628 + then
629 + mkdir "$REWRITTEN" &&
630 + for c in $(git merge-base --all $HEAD $UPSTREAM)
631 + do
632 + echo $ONTO > "$REWRITTEN"/$c ||
633 + die "Could not init rewritten commits"
634 + done
635 + else
636 + mkdir "$REWRITTEN" &&
637 + echo $ONTO > "$REWRITTEN"/root ||
638 die "Could not init rewritten commits"
607 - done
639 + fi
640 # No cherry-pick because our first pass is to determine
641 # parents to rewrite and skipping dropped commits would
642 # prematurely end our probe
@@ -614,12 +646,21 @@ first and then run 'git rebase --continue' again."
646 MERGES_OPTION="--no-merges --cherry-pick"
647 fi
648
617 - SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
649 SHORTHEAD=$(git rev-parse --short $HEAD)
650 SHORTONTO=$(git rev-parse --short $ONTO)
651 + if test -z "$REBASE_ROOT"
652 + # this is now equivalent to ! -z "$UPSTREAM"
653 + then
654 + SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
655 + REVISIONS=$UPSTREAM...$HEAD
656 + SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD
657 + else
658 + REVISIONS=$ONTO...$HEAD
659 + SHORTREVISIONS=$SHORTHEAD
660 + fi
661 git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
662 --abbrev=7 --reverse --left-right --topo-order \
622 - $UPSTREAM...$HEAD | \
663 + $REVISIONS | \
664 sed -n "s/^>//p" | while read shortsha1 rest
665 do
666 if test t != "$PRESERVE_MERGES"
@@ -627,14 +668,19 @@ first and then run 'git rebase --continue' again."
668 echo "pick $shortsha1 $rest" >> "$TODO"
669 else
670 sha1=$(git rev-parse $shortsha1)
630 - preserve=t
631 - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
632 - do
633 - if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
634 - then
635 - preserve=f
636 - fi
637 - done
671 + if test -z "$REBASE_ROOT"
672 + then
673 + preserve=t
674 + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
675 + do
676 + if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
677 + then
678 + preserve=f
679 + fi
680 + done
681 + else
682 + preserve=f
683 + fi
684 if test f = "$preserve"
685 then
686 touch "$REWRITTEN"/$sha1
@@ -648,11 +694,11 @@ first and then run 'git rebase --continue' again."
694 then
695 mkdir "$DROPPED"
696 # Save all non-cherry-picked changes
651 - git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \
697 + git rev-list $REVISIONS --left-right --cherry-pick | \
698 sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks
699 # Now all commits and note which ones are missing in
700 # not-cherry-picks and hence being dropped
655 - git rev-list $UPSTREAM..$HEAD |
701 + git rev-list $REVISIONS |
702 while read rev
703 do
704 if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
@@ -661,17 +707,18 @@ first and then run 'git rebase --continue' again."
707 # not worthwhile, we don't want to track its multiple heads,
708 # just the history of its first-parent for others that will
709 # be rebasing on top of it
664 - git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev
710 + git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev
711 short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
712 grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
713 rm "$REWRITTEN"/$rev
714 fi
715 done
716 fi
717 +
718 test -s "$TODO" || echo noop >> "$TODO"
719 cat >> "$TODO" << EOF
720
674 -# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
721 +# Rebase $SHORTREVISIONS onto $SHORTONTO
722 #
723 # Commands:
724 # p, pick = use commit
t/t3412-rebase-root.sh
+101
@@ -67,6 +67,100 @@ 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 'rebase -i --root --onto <newbase>' '
71 + git checkout -b work3 other &&
72 + GIT_EDITOR=: git rebase -i --root --onto master &&
73 + git log --pretty=tformat:"%s" > rebased3 &&
74 + test_cmp expect rebased3
75 +'
76 +
77 +test_expect_success 'pre-rebase got correct input (3)' '
78 + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
79 +'
80 +
81 +test_expect_success 'rebase -i --root --onto <newbase> <branch>' '
82 + git branch work4 other &&
83 + GIT_EDITOR=: git rebase -i --root --onto master work4 &&
84 + git log --pretty=tformat:"%s" > rebased4 &&
85 + test_cmp expect rebased4
86 +'
87 +
88 +test_expect_success 'pre-rebase got correct input (4)' '
89 + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4
90 +'
91 +
92 +test_expect_success 'rebase -i -p with linear history' '
93 + git checkout -b work5 other &&
94 + GIT_EDITOR=: git rebase -i -p --root --onto master &&
95 + git log --pretty=tformat:"%s" > rebased5 &&
96 + test_cmp expect rebased5
97 +'
98 +
99 +test_expect_success 'pre-rebase got correct input (5)' '
100 + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
101 +'
102 +
103 +test_expect_success 'set up merge history' '
104 + git checkout other^ &&
105 + git checkout -b side &&
106 + echo 5 > C &&
107 + git add C &&
108 + git commit -m 5 &&
109 + git checkout other &&
110 + git merge side
111 +'
112 +
113 +sed 's/#/ /g' > expect-side <<'EOF'
114 +* Merge branch 'side' into other
115 +|\##
116 +| * 5
117 +* | 4
118 +|/##
119 +* 3
120 +* 2
121 +* 1
122 +EOF
123 +
124 +test_expect_success 'rebase -i -p with merge' '
125 + git checkout -b work6 other &&
126 + GIT_EDITOR=: git rebase -i -p --root --onto master &&
127 + git log --graph --topo-order --pretty=tformat:"%s" > rebased6 &&
128 + test_cmp expect-side rebased6
129 +'
130 +
131 +test_expect_success 'set up second root and merge' '
132 + git symbolic-ref HEAD refs/heads/third &&
133 + rm .git/index &&
134 + rm A B C &&
135 + echo 6 > D &&
136 + git add D &&
137 + git commit -m 6 &&
138 + git checkout other &&
139 + git merge third
140 +'
141 +
142 +sed 's/#/ /g' > expect-third <<'EOF'
143 +* Merge branch 'third' into other
144 +|\##
145 +| * 6
146 +* | Merge branch 'side' into other
147 +|\ \##
148 +| * | 5
149 +* | | 4
150 +|/ /##
151 +* | 3
152 +|/##
153 +* 2
154 +* 1
155 +EOF
156 +
157 +test_expect_success 'rebase -i -p with two roots' '
158 + git checkout -b work7 other &&
159 + GIT_EDITOR=: git rebase -i -p --root --onto master &&
160 + git log --graph --topo-order --pretty=tformat:"%s" > rebased7 &&
161 + test_cmp expect-third rebased7
162 +'
163 +
164 test_expect_success 'setup pre-rebase hook that fails' '
165 mkdir -p .git/hooks &&
166 cat >.git/hooks/pre-rebase <<EOF &&
@@ -83,4 +177,11 @@ test_expect_success 'pre-rebase hook stops rebase' '
177 test 0 = $(git rev-list other...stops1 | wc -l)
178 '
179
180 +test_expect_success 'pre-rebase hook stops rebase -i' '
181 + git checkout -b stops2 other &&
182 + GIT_EDITOR=: test_must_fail git rebase --root --onto master &&
183 + test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
184 + test 0 = $(git rev-list other...stops2 | wc -l)
185 +'
186 +
187 test_done