rebase: implement --merge via the interactive machinery

As part of an ongoing effort to make rebase have more uniform behavior, modify the merge backend to behave like the interactive one, by re-implementing it on top of the latter. Interactive rebases are implemented in terms of cherry-pick rather than the merge-recursive builtin, but cherry-pick also calls into the recursive merge machinery by default and can accept special merge strategies and/or special strategy options. As such, there really is not any need for having both git-rebase--merge and git-rebase--interactive anymore. Delete git-rebase--merge.sh and instead implement it in builtin/rebase.c. This results in a few deliberate but small user-visible changes: * The progress output is modified (see t3406 and t3420 for examples) * A few known test failures are now fixed (see t3421) * bash-prompt during a rebase --merge is now REBASE-i instead of REBASE-m. Reason: The prompt is a reflection of the backend in use; this allows users to report an issue to the git mailing list with the appropriate backend information, and allows advanced users to know where to search for relevant control files. (see t9903) testcase modification notes: t3406: --interactive and --merge had slightly different progress output while running; adjust a test to match the new expectation t3420: these test precise output while running, but rebase--am, rebase--merge, and rebase--interactive all were built on very different commands (am, merge-recursive, cherry-pick), so the tests expected different output for each type. Now we expect --merge and --interactive to have the same output. t3421: --interactive fixes some bugs in --merge! Wahoo! t9903: --merge uses the interactive backend so the prompt expected is now REBASE-i. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Dec 11, 2018 at 08:11 UTC 68aa495b590d417e88562ab1e5da7d84d0531f21
10 files changed +43 -297
.gitignore
-1
@@ -124,7 +124,6 @@
124 /git-rebase--am
125 /git-rebase--common
126 /git-rebase--interactive
127 -/git-rebase--merge
127 /git-rebase--preserve-merges
128 /git-receive-pack
129 /git-reflog
Documentation/git-rebase.txt
+3 -14
@@ -504,15 +504,7 @@ See also INCOMPATIBLE OPTIONS below.
504 INCOMPATIBLE OPTIONS
505 --------------------
506
507 -git-rebase has many flags that are incompatible with each other,
508 -predominantly due to the fact that it has three different underlying
509 -implementations:
510 -
511 - * one based on linkgit:git-am[1] (the default)
512 - * one based on git-merge-recursive (merge backend)
513 - * one based on linkgit:git-cherry-pick[1] (interactive backend)
514 -
515 -Flags only understood by the am backend:
507 +The following options:
508
509 * --committer-date-is-author-date
510 * --ignore-date
@@ -520,15 +512,12 @@ Flags only understood by the am backend:
512 * --ignore-whitespace
513 * -C
514
523 -Flags understood by both merge and interactive backends:
515 +are incompatible with the following options:
516
517 * --merge
518 * --strategy
519 * --strategy-option
520 * --allow-empty-message
529 -
530 -Flags only understood by the interactive backend:
531 -
521 * --[no-]autosquash
522 * --rebase-merges
523 * --preserve-merges
@@ -539,7 +528,7 @@ Flags only understood by the interactive backend:
528 * --edit-todo
529 * --root when used in combination with --onto
530
542 -Other incompatible flag pairs:
531 +In addition, the following pairs of options are incompatible:
532
533 * --preserve-merges and --interactive
534 * --preserve-merges and --signoff
Makefile
-1
@@ -628,7 +628,6 @@ SCRIPT_LIB += git-parse-remote
628 SCRIPT_LIB += git-rebase--am
629 SCRIPT_LIB += git-rebase--common
630 SCRIPT_LIB += git-rebase--preserve-merges
631 -SCRIPT_LIB += git-rebase--merge
631 SCRIPT_LIB += git-sh-setup
632 SCRIPT_LIB += git-sh-i18n
633
builtin/rebase.c
+6 -9
@@ -122,7 +122,7 @@ static void imply_interactive(struct rebase_options *opts, const char *option)
122 case REBASE_PRESERVE_MERGES:
123 break;
124 case REBASE_MERGE:
125 - /* we silently *upgrade* --merge to --interactive if needed */
125 + /* we now implement --merge via --interactive */
126 default:
127 opts->type = REBASE_INTERACTIVE; /* implied */
128 break;
@@ -481,10 +481,6 @@ static int run_specific_rebase(struct rebase_options *opts)
481 backend = "git-rebase--am";
482 backend_func = "git_rebase__am";
483 break;
484 - case REBASE_MERGE:
485 - backend = "git-rebase--merge";
486 - backend_func = "git_rebase__merge";
487 - break;
484 case REBASE_PRESERVE_MERGES:
485 backend = "git-rebase--preserve-merges";
486 backend_func = "git_rebase__preserve_merges";
@@ -1191,6 +1187,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1187 }
1188 }
1189
1190 + if (options.type == REBASE_MERGE)
1191 + imply_interactive(&options, "--merge");
1192 +
1193 if (options.root && !options.onto_name)
1194 imply_interactive(&options, "--root without --onto");
1195
@@ -1220,10 +1219,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1219 break;
1220
1221 if (is_interactive(&options) && i >= 0)
1223 - die(_("cannot combine am options "
1224 - "with interactive options"));
1225 - if (options.type == REBASE_MERGE && i >= 0)
1226 - die(_("cannot combine am options with merge options "));
1222 + die(_("cannot combine am options with either "
1223 + "interactive or merge options"));
1224 }
1225
1226 if (options.signoff) {
git-legacy-rebase.sh
+21 -22
@@ -218,6 +218,7 @@ then
218 state_dir="$apply_dir"
219 elif test -d "$merge_dir"
220 then
221 + type=interactive
222 if test -d "$merge_dir"/rewritten
223 then
224 type=preserve-merges
@@ -225,10 +226,7 @@ then
226 preserve_merges=t
227 elif test -f "$merge_dir"/interactive
228 then
228 - type=interactive
229 interactive_rebase=explicit
230 - else
231 - type=merge
230 fi
231 state_dir="$merge_dir"
232 fi
@@ -477,6 +475,7 @@ then
475 test -z "$interactive_rebase" && interactive_rebase=implied
476 fi
477
478 +actually_interactive=
479 if test -n "$interactive_rebase"
480 then
481 if test -z "$preserve_merges"
@@ -485,11 +484,12 @@ then
484 else
485 type=preserve-merges
486 fi
488 -
487 + actually_interactive=t
488 state_dir="$merge_dir"
489 elif test -n "$do_merge"
490 then
492 - type=merge
491 + interactive_rebase=implied
492 + type=interactive
493 state_dir="$merge_dir"
494 else
495 type=am
@@ -505,13 +505,9 @@ incompatible_opts=$(echo " $git_am_opt " | \
505 sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/')
506 if test -n "$incompatible_opts"
507 then
508 - if test -n "$interactive_rebase"
509 - then
510 - die "$(gettext "fatal: cannot combine am options with interactive options")"
511 - fi
512 - if test -n "$do_merge"
508 + if test -n "$actually_interactive" || test "$do_merge"
509 then
514 - die "$(gettext "fatal: cannot combine am options with merge options")"
510 + die "$(gettext "fatal: cannot combine am options with either interactive or merge options")"
511 fi
512 fi
513
@@ -676,7 +672,7 @@ require_clean_work_tree "rebase" "$(gettext "Please commit or stash them.")"
672 # but this should be done only when upstream and onto are the same
673 # and if this is not an interactive rebase.
674 mb=$(git merge-base "$onto" "$orig_head")
679 -if test -z "$interactive_rebase" && test "$upstream" = "$onto" &&
675 +if test -z "$actually_interactive" && test "$upstream" = "$onto" &&
676 test "$mb" = "$onto" && test -z "$restrict_revision" &&
677 # linear history?
678 ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
@@ -726,6 +722,19 @@ then
722 GIT_PAGER='' git diff --stat --summary "$mb_tree" "$onto"
723 fi
724
725 +if test -z "$actually_interactive" && test "$mb" = "$orig_head"
726 +then
727 + say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
728 + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \
729 + git checkout -q "$onto^0" || die "could not detach HEAD"
730 + # If the $onto is a proper descendant of the tip of the branch, then
731 + # we just fast-forwarded.
732 + git update-ref ORIG_HEAD $orig_head
733 + move_to_original_branch
734 + finish_rebase
735 + exit 0
736 +fi
737 +
738 test -n "$interactive_rebase" && run_specific_rebase
739
740 # Detach HEAD and reset the tree
@@ -735,16 +744,6 @@ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \
744 git checkout -q "$onto^0" || die "could not detach HEAD"
745 git update-ref ORIG_HEAD $orig_head
746
738 -# If the $onto is a proper descendant of the tip of the branch, then
739 -# we just fast-forwarded.
740 -if test "$mb" = "$orig_head"
741 -then
742 - say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
743 - move_to_original_branch
744 - finish_rebase
745 - exit 0
746 -fi
747 -
747 if test -n "$rebase_root"
748 then
749 revisions="$onto..$orig_head"
git-rebase--merge.sh deleted
-166
@@ -1,166 +0,0 @@
1 -# This shell script fragment is sourced by git-rebase to implement
2 -# its merge-based non-interactive mode that copes well with renamed
3 -# files.
4 -#
5 -# Copyright (c) 2010 Junio C Hamano.
6 -#
7 -
8 -prec=4
9 -
10 -read_state () {
11 - onto_name=$(cat "$state_dir"/onto_name) &&
12 - end=$(cat "$state_dir"/end) &&
13 - msgnum=$(cat "$state_dir"/msgnum)
14 -}
15 -
16 -continue_merge () {
17 - test -d "$state_dir" || die "$state_dir directory does not exist"
18 -
19 - unmerged=$(git ls-files -u)
20 - if test -n "$unmerged"
21 - then
22 - echo "You still have unmerged paths in your index"
23 - echo "did you forget to use git add?"
24 - die "$resolvemsg"
25 - fi
26 -
27 - cmt=$(cat "$state_dir/current")
28 - if ! git diff-index --quiet --ignore-submodules HEAD --
29 - then
30 - if ! git commit ${gpg_sign_opt:+"$gpg_sign_opt"} $signoff $allow_empty_message \
31 - --no-verify -C "$cmt"
32 - then
33 - echo "Commit failed, please do not call \"git commit\""
34 - echo "directly, but instead do one of the following: "
35 - die "$resolvemsg"
36 - fi
37 - if test -z "$GIT_QUIET"
38 - then
39 - printf "Committed: %0${prec}d " $msgnum
40 - fi
41 - echo "$cmt $(git rev-parse HEAD^0)" >> "$state_dir/rewritten"
42 - else
43 - if test -z "$GIT_QUIET"
44 - then
45 - printf "Already applied: %0${prec}d " $msgnum
46 - fi
47 - fi
48 - test -z "$GIT_QUIET" &&
49 - GIT_PAGER='' git log --format=%s -1 "$cmt"
50 -
51 - # onto the next patch:
52 - msgnum=$(($msgnum + 1))
53 - echo "$msgnum" >"$state_dir/msgnum"
54 -}
55 -
56 -call_merge () {
57 - msgnum="$1"
58 - echo "$msgnum" >"$state_dir/msgnum"
59 - cmt="$(cat "$state_dir/cmt.$msgnum")"
60 - echo "$cmt" > "$state_dir/current"
61 - git update-ref REBASE_HEAD "$cmt"
62 - hd=$(git rev-parse --verify HEAD)
63 - cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD)
64 - eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
65 - eval GITHEAD_$hd='$onto_name'
66 - export GITHEAD_$cmt GITHEAD_$hd
67 - if test -n "$GIT_QUIET"
68 - then
69 - GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY
70 - fi
71 - test -z "$strategy" && strategy=recursive
72 - # If cmt doesn't have a parent, don't include it as a base
73 - base=$(git rev-parse --verify --quiet $cmt^)
74 - eval 'git merge-$strategy' $strategy_opts $base ' -- "$hd" "$cmt"'
75 - rv=$?
76 - case "$rv" in
77 - 0)
78 - unset GITHEAD_$cmt GITHEAD_$hd
79 - return
80 - ;;
81 - 1)
82 - git rerere $allow_rerere_autoupdate
83 - die "$resolvemsg"
84 - ;;
85 - 2)
86 - echo "Strategy: $strategy failed, try another" 1>&2
87 - die "$resolvemsg"
88 - ;;
89 - *)
90 - die "Unknown exit code ($rv) from command:" \
91 - "git merge-$strategy $cmt^ -- HEAD $cmt"
92 - ;;
93 - esac
94 -}
95 -
96 -finish_rb_merge () {
97 - move_to_original_branch
98 - if test -s "$state_dir"/rewritten
99 - then
100 - git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
101 - hook="$(git rev-parse --git-path hooks/post-rewrite)"
102 - test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
103 - fi
104 - say All done.
105 -}
106 -
107 -git_rebase__merge () {
108 -
109 -case "$action" in
110 -continue)
111 - read_state
112 - continue_merge
113 - while test "$msgnum" -le "$end"
114 - do
115 - call_merge "$msgnum"
116 - continue_merge
117 - done
118 - finish_rb_merge
119 - return
120 - ;;
121 -skip)
122 - read_state
123 - git rerere clear
124 - cmt="$(cat "$state_dir/cmt.$msgnum")"
125 - echo "$cmt $(git rev-parse HEAD^0)" >> "$state_dir/rewritten"
126 - msgnum=$(($msgnum + 1))
127 - while test "$msgnum" -le "$end"
128 - do
129 - call_merge "$msgnum"
130 - continue_merge
131 - done
132 - finish_rb_merge
133 - return
134 - ;;
135 -show-current-patch)
136 - exec git show REBASE_HEAD --
137 - ;;
138 -esac
139 -
140 -mkdir -p "$state_dir"
141 -echo "$onto_name" > "$state_dir/onto_name"
142 -write_basic_state
143 -rm -f "$(git rev-parse --git-path REBASE_HEAD)"
144 -
145 -msgnum=0
146 -for cmt in $(git rev-list --topo-order --reverse --no-merges "$revisions")
147 -do
148 - msgnum=$(($msgnum + 1))
149 - echo "$cmt" > "$state_dir/cmt.$msgnum"
150 -done
151 -
152 -echo 1 >"$state_dir/msgnum"
153 -echo $msgnum >"$state_dir/end"
154 -
155 -end=$msgnum
156 -msgnum=1
157 -
158 -while test "$msgnum" -le "$end"
159 -do
160 - call_merge "$msgnum"
161 - continue_merge
162 -done
163 -
164 -finish_rb_merge
165 -
166 -}
t/t3406-rebase-message.sh
+1 -6
@@ -17,14 +17,9 @@ test_expect_success 'setup' '
17 git tag start
18 '
19
20 -cat >expect <<\EOF
21 -Already applied: 0001 A
22 -Already applied: 0002 B
23 -Committed: 0003 Z
24 -EOF
25 -
20 test_expect_success 'rebase -m' '
21 git rebase -m master >report &&
22 + >expect &&
23 sed -n -e "/^Already applied: /p" \
24 -e "/^Committed: /p" report >actual &&
25 test_cmp expect actual
t/t3420-rebase-autostash.sh
+6 -72
@@ -53,41 +53,6 @@ create_expected_success_interactive () {
53 EOF
54 }
55
56 -create_expected_success_merge () {
57 - cat >expected <<-EOF
58 - $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
59 - HEAD is now at $(git rev-parse --short feature-branch) third commit
60 - First, rewinding head to replay your work on top of it...
61 - Merging unrelated-onto-branch with HEAD~1
62 - Merging:
63 - $(git rev-parse --short unrelated-onto-branch) unrelated commit
64 - $(git rev-parse --short feature-branch^) second commit
65 - found 1 common ancestor:
66 - $(git rev-parse --short feature-branch~2) initial commit
67 - [detached HEAD $(git rev-parse --short rebased-feature-branch~1)] second commit
68 - Author: A U Thor <author@example.com>
69 - Date: Thu Apr 7 15:14:13 2005 -0700
70 - 2 files changed, 2 insertions(+)
71 - create mode 100644 file1
72 - create mode 100644 file2
73 - Committed: 0001 second commit
74 - Merging unrelated-onto-branch with HEAD~0
75 - Merging:
76 - $(git rev-parse --short rebased-feature-branch~1) second commit
77 - $(git rev-parse --short feature-branch) third commit
78 - found 1 common ancestor:
79 - $(git rev-parse --short feature-branch~1) second commit
80 - [detached HEAD $(git rev-parse --short rebased-feature-branch)] third commit
81 - Author: A U Thor <author@example.com>
82 - Date: Thu Apr 7 15:15:13 2005 -0700
83 - 1 file changed, 1 insertion(+)
84 - create mode 100644 file3
85 - Committed: 0002 third commit
86 - All done.
87 - Applied autostash.
88 - EOF
89 -}
90 -
56 create_expected_failure_am () {
57 cat >expected <<-EOF
58 $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
@@ -112,43 +77,6 @@ create_expected_failure_interactive () {
77 EOF
78 }
79
115 -create_expected_failure_merge () {
116 - cat >expected <<-EOF
117 - $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
118 - HEAD is now at $(git rev-parse --short feature-branch) third commit
119 - First, rewinding head to replay your work on top of it...
120 - Merging unrelated-onto-branch with HEAD~1
121 - Merging:
122 - $(git rev-parse --short unrelated-onto-branch) unrelated commit
123 - $(git rev-parse --short feature-branch^) second commit
124 - found 1 common ancestor:
125 - $(git rev-parse --short feature-branch~2) initial commit
126 - [detached HEAD $(git rev-parse --short rebased-feature-branch~1)] second commit
127 - Author: A U Thor <author@example.com>
128 - Date: Thu Apr 7 15:14:13 2005 -0700
129 - 2 files changed, 2 insertions(+)
130 - create mode 100644 file1
131 - create mode 100644 file2
132 - Committed: 0001 second commit
133 - Merging unrelated-onto-branch with HEAD~0
134 - Merging:
135 - $(git rev-parse --short rebased-feature-branch~1) second commit
136 - $(git rev-parse --short feature-branch) third commit
137 - found 1 common ancestor:
138 - $(git rev-parse --short feature-branch~1) second commit
139 - [detached HEAD $(git rev-parse --short rebased-feature-branch)] third commit
140 - Author: A U Thor <author@example.com>
141 - Date: Thu Apr 7 15:15:13 2005 -0700
142 - 1 file changed, 1 insertion(+)
143 - create mode 100644 file3
144 - Committed: 0002 third commit
145 - All done.
146 - Applying autostash resulted in conflicts.
147 - Your changes are safe in the stash.
148 - You can run "git stash pop" or "git stash drop" at any time.
149 - EOF
150 -}
151 -
80 testrebase () {
81 type=$1
82 dotest=$2
@@ -177,6 +105,9 @@ testrebase () {
105 test_expect_success "rebase$type --autostash: check output" '
106 test_when_finished git branch -D rebased-feature-branch &&
107 suffix=${type#\ --} && suffix=${suffix:-am} &&
108 + if test ${suffix} = "merge"; then
109 + suffix=interactive
110 + fi &&
111 create_expected_success_$suffix &&
112 test_i18ncmp expected actual
113 '
@@ -274,6 +205,9 @@ testrebase () {
205 test_expect_success "rebase$type: check output with conflicting stash" '
206 test_when_finished git branch -D rebased-feature-branch &&
207 suffix=${type#\ --} && suffix=${suffix:-am} &&
208 + if test ${suffix} = "merge"; then
209 + suffix=interactive
210 + fi &&
211 create_expected_failure_$suffix &&
212 test_i18ncmp expected actual
213 '
t/t3421-rebase-topology-linear.sh
+5 -5
@@ -111,7 +111,7 @@ test_run_rebase () {
111 "
112 }
113 test_run_rebase success ''
114 -test_run_rebase failure -m
114 +test_run_rebase success -m
115 test_run_rebase success -i
116 test_have_prereq !REBASE_P || test_run_rebase success -p
117
@@ -126,7 +126,7 @@ test_run_rebase () {
126 "
127 }
128 test_run_rebase success ''
129 -test_run_rebase failure -m
129 +test_run_rebase success -m
130 test_run_rebase success -i
131 test_have_prereq !REBASE_P || test_run_rebase success -p
132
@@ -141,7 +141,7 @@ test_run_rebase () {
141 "
142 }
143 test_run_rebase success ''
144 -test_run_rebase failure -m
144 +test_run_rebase success -m
145 test_run_rebase success -i
146 test_have_prereq !REBASE_P || test_run_rebase success -p
147
@@ -284,7 +284,7 @@ test_run_rebase () {
284 "
285 }
286 test_run_rebase success ''
287 -test_run_rebase failure -m
287 +test_run_rebase success -m
288 test_run_rebase success -i
289 test_have_prereq !REBASE_P || test_run_rebase success -p
290
@@ -315,7 +315,7 @@ test_run_rebase () {
315 "
316 }
317 test_run_rebase success ''
318 -test_run_rebase failure -m
318 +test_run_rebase success -m
319 test_run_rebase success -i
320 test_have_prereq !REBASE_P || test_run_rebase failure -p
321
t/t9903-bash-prompt.sh
+1 -1
@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '
180 '
181
182 test_expect_success 'prompt - rebase merge' '
183 - printf " (b2|REBASE-m 1/3)" >expected &&
183 + printf " (b2|REBASE-i 1/3)" >expected &&
184 git checkout b2 &&
185 test_when_finished "git checkout master" &&
186 test_must_fail git rebase --merge b1 b2 &&