| #!/bin/sh | 
 | # | 
 | # Copyright (c) 2018 Johannes E. Schindelin | 
 | # | 
 |  | 
 | test_description='git rebase -i --rebase-merges | 
 |  | 
 | This test runs git rebase "interactively", retaining the branch structure by | 
 | recreating merge commits. | 
 |  | 
 | Initial setup: | 
 |  | 
 |     -- B --                   (first) | 
 |    /       \ | 
 |  A - C - D - E - H            (main) | 
 |    \    \       / | 
 |     \    F - G                (second) | 
 |      \ | 
 |       Conflicting-G | 
 | ' | 
 | GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main | 
 | export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME | 
 |  | 
 | . ./test-lib.sh | 
 | . "$TEST_DIRECTORY"/lib-rebase.sh | 
 | . "$TEST_DIRECTORY"/lib-log-graph.sh | 
 |  | 
 | test_cmp_graph () { | 
 | 	cat >expect && | 
 | 	lib_test_cmp_graph --boundary --format=%s "$@" | 
 | } | 
 |  | 
 | test_expect_success 'setup' ' | 
 | 	write_script replace-editor.sh <<-\EOF && | 
 | 	mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)" | 
 | 	cp script-from-scratch "$1" | 
 | 	EOF | 
 |  | 
 | 	test_commit A && | 
 | 	git checkout -b first && | 
 | 	test_commit B && | 
 | 	b=$(git rev-parse --short HEAD) && | 
 | 	git checkout main && | 
 | 	test_commit C && | 
 | 	c=$(git rev-parse --short HEAD) && | 
 | 	test_commit D && | 
 | 	d=$(git rev-parse --short HEAD) && | 
 | 	git merge --no-commit B && | 
 | 	test_tick && | 
 | 	git commit -m E && | 
 | 	git tag -m E E && | 
 | 	e=$(git rev-parse --short HEAD) && | 
 | 	git checkout -b second C && | 
 | 	test_commit F && | 
 | 	f=$(git rev-parse --short HEAD) && | 
 | 	test_commit G && | 
 | 	g=$(git rev-parse --short HEAD) && | 
 | 	git checkout main && | 
 | 	git merge --no-commit G && | 
 | 	test_tick && | 
 | 	git commit -m H && | 
 | 	h=$(git rev-parse --short HEAD) && | 
 | 	git tag -m H H && | 
 | 	git checkout A && | 
 | 	test_commit conflicting-G G.t | 
 | ' | 
 |  | 
 | test_expect_success 'create completely different structure' ' | 
 | 	cat >script-from-scratch <<-\EOF && | 
 | 	label onto | 
 |  | 
 | 	# onebranch | 
 | 	pick G | 
 | 	pick D | 
 | 	label onebranch | 
 |  | 
 | 	# second | 
 | 	reset onto | 
 | 	pick B | 
 | 	label second | 
 |  | 
 | 	reset onto | 
 | 	merge -C H second | 
 | 	merge onebranch # Merge the topic branch '\''onebranch'\'' | 
 | 	EOF | 
 | 	test_config sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	test_tick && | 
 | 	git rebase -i -r A main && | 
 | 	test_cmp_graph <<-\EOF | 
 | 	*   Merge the topic branch '\''onebranch'\'' | 
 | 	|\ | 
 | 	| * D | 
 | 	| * G | 
 | 	* |   H | 
 | 	|\ \ | 
 | 	| |/ | 
 | 	|/| | 
 | 	| * B | 
 | 	|/ | 
 | 	* A | 
 | 	EOF | 
 | ' | 
 |  | 
 | test_expect_success 'generate correct todo list' ' | 
 | 	cat >expect <<-EOF && | 
 | 	label onto | 
 |  | 
 | 	reset onto | 
 | 	pick $b B | 
 | 	label E | 
 |  | 
 | 	reset onto | 
 | 	pick $c C | 
 | 	label branch-point | 
 | 	pick $f F | 
 | 	pick $g G | 
 | 	label H | 
 |  | 
 | 	reset branch-point # C | 
 | 	pick $d D | 
 | 	merge -C $e E # E | 
 | 	merge -C $h H # H | 
 |  | 
 | 	EOF | 
 |  | 
 | 	grep -v "^#" <.git/ORIGINAL-TODO >output && | 
 | 	test_cmp expect output | 
 | ' | 
 |  | 
 | test_expect_success '`reset` refuses to overwrite untracked files' ' | 
 | 	git checkout -b refuse-to-reset && | 
 | 	test_commit dont-overwrite-untracked && | 
 | 	git checkout @{-1} && | 
 | 	: >dont-overwrite-untracked.t && | 
 | 	echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch && | 
 | 	test_config sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	test_must_fail git rebase -ir HEAD && | 
 | 	git rebase --abort | 
 | ' | 
 |  | 
 | test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' ' | 
 | 	test_when_finished "test_might_fail git rebase --abort" && | 
 | 	git checkout -b conflicting-merge A && | 
 |  | 
 | 	: fail because of conflicting untracked file && | 
 | 	>G.t && | 
 | 	echo "merge -C H G" >script-from-scratch && | 
 | 	test_config sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	test_tick && | 
 | 	test_must_fail git rebase -ir HEAD && | 
 | 	grep "^merge -C .* G$" .git/rebase-merge/done && | 
 | 	grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && | 
 | 	test_path_is_file .git/rebase-merge/patch && | 
 |  | 
 | 	: fail because of merge conflict && | 
 | 	rm G.t .git/rebase-merge/patch && | 
 | 	git reset --hard conflicting-G && | 
 | 	test_must_fail git rebase --continue && | 
 | 	! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && | 
 | 	test_path_is_file .git/rebase-merge/patch | 
 | ' | 
 |  | 
 | test_expect_success 'failed `merge <branch>` does not crash' ' | 
 | 	test_when_finished "test_might_fail git rebase --abort" && | 
 | 	git checkout conflicting-G && | 
 |  | 
 | 	echo "merge G" >script-from-scratch && | 
 | 	test_config sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	test_tick && | 
 | 	test_must_fail git rebase -ir HEAD && | 
 | 	! grep "^merge G$" .git/rebase-merge/git-rebase-todo && | 
 | 	grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message | 
 | ' | 
 |  | 
 | test_expect_success 'merge -c commits before rewording and reloads todo-list' ' | 
 | 	cat >script-from-scratch <<-\EOF && | 
 | 	merge -c E B | 
 | 	merge -c H G | 
 | 	EOF | 
 |  | 
 | 	git checkout -b merge-c H && | 
 | 	( | 
 | 		set_reword_editor && | 
 | 		GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \ | 
 | 			git rebase -i -r D | 
 | 	) && | 
 | 	check_reworded_commits E H | 
 | ' | 
 |  | 
 | test_expect_success 'merge -c rewords when a strategy is given' ' | 
 | 	git checkout -b merge-c-with-strategy H && | 
 | 	write_script git-merge-override <<-\EOF && | 
 | 	echo overridden$1 >G.t | 
 | 	git add G.t | 
 | 	EOF | 
 |  | 
 | 	PATH="$PWD:$PATH" \ | 
 | 	GIT_SEQUENCE_EDITOR="echo merge -c H G >" \ | 
 | 	GIT_EDITOR="echo edited >>" \ | 
 | 		git rebase --no-ff -ir -s override -Xxopt E && | 
 | 	test_write_lines overridden--xopt >expect && | 
 | 	test_cmp expect G.t && | 
 | 	test_write_lines H "" edited "" >expect && | 
 | 	git log --format=%B -1 >actual && | 
 | 	test_cmp expect actual | 
 |  | 
 | ' | 
 | test_expect_success 'with a branch tip that was cherry-picked already' ' | 
 | 	git checkout -b already-upstream main && | 
 | 	base="$(git rev-parse --verify HEAD)" && | 
 |  | 
 | 	test_commit A1 && | 
 | 	test_commit A2 && | 
 | 	git reset --hard $base && | 
 | 	test_commit B1 && | 
 | 	test_tick && | 
 | 	git merge -m "Merge branch A" A2 && | 
 |  | 
 | 	git checkout -b upstream-with-a2 $base && | 
 | 	test_tick && | 
 | 	git cherry-pick A2 && | 
 |  | 
 | 	git checkout already-upstream && | 
 | 	test_tick && | 
 | 	git rebase -i -r upstream-with-a2 && | 
 | 	test_cmp_graph upstream-with-a2.. <<-\EOF | 
 | 	*   Merge branch A | 
 | 	|\ | 
 | 	| * A1 | 
 | 	* | B1 | 
 | 	|/ | 
 | 	o A2 | 
 | 	EOF | 
 | ' | 
 |  | 
 | test_expect_success 'do not rebase cousins unless asked for' ' | 
 | 	git checkout -b cousins main && | 
 | 	before="$(git rev-parse --verify HEAD)" && | 
 | 	test_tick && | 
 | 	git rebase -r HEAD^ && | 
 | 	test_cmp_rev HEAD $before && | 
 | 	test_tick && | 
 | 	git rebase --rebase-merges=rebase-cousins HEAD^ && | 
 | 	test_cmp_graph HEAD^.. <<-\EOF | 
 | 	*   Merge the topic branch '\''onebranch'\'' | 
 | 	|\ | 
 | 	| * D | 
 | 	| * G | 
 | 	|/ | 
 | 	o H | 
 | 	EOF | 
 | ' | 
 |  | 
 | test_expect_success 'refs/rewritten/* is worktree-local' ' | 
 | 	git worktree add wt && | 
 | 	cat >wt/script-from-scratch <<-\EOF && | 
 | 	label xyz | 
 | 	exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || : | 
 | 	exec git rev-parse --verify refs/rewritten/xyz >b | 
 | 	EOF | 
 |  | 
 | 	test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	git -C wt rebase -i HEAD && | 
 | 	test_must_be_empty wt/a && | 
 | 	test_cmp_rev HEAD "$(cat wt/b)" | 
 | ' | 
 |  | 
 | test_expect_success '--abort cleans up refs/rewritten' ' | 
 | 	git checkout -b abort-cleans-refs-rewritten H && | 
 | 	GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ && | 
 | 	git rev-parse --verify refs/rewritten/onto && | 
 | 	git rebase --abort && | 
 | 	test_must_fail git rev-parse --verify refs/rewritten/onto | 
 | ' | 
 |  | 
 | test_expect_success '--quit cleans up refs/rewritten' ' | 
 | 	git checkout -b quit-cleans-refs-rewritten H && | 
 | 	GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ && | 
 | 	git rev-parse --verify refs/rewritten/onto && | 
 | 	git rebase --quit && | 
 | 	test_must_fail git rev-parse --verify refs/rewritten/onto | 
 | ' | 
 |  | 
 | test_expect_success 'post-rewrite hook and fixups work for merges' ' | 
 | 	git checkout -b post-rewrite H && | 
 | 	test_commit same1 && | 
 | 	git reset --hard HEAD^ && | 
 | 	test_commit same2 && | 
 | 	git merge -m "to fix up" same1 && | 
 | 	echo same old same old >same2.t && | 
 | 	test_tick && | 
 | 	git commit --fixup HEAD same2.t && | 
 | 	fixup="$(git rev-parse HEAD)" && | 
 |  | 
 | 	mkdir -p .git/hooks && | 
 | 	test_when_finished "rm .git/hooks/post-rewrite" && | 
 | 	echo "cat >actual" | write_script .git/hooks/post-rewrite && | 
 |  | 
 | 	test_tick && | 
 | 	git rebase -i --autosquash -r HEAD^^^ && | 
 | 	printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \ | 
 | 		$fixup^^2 HEAD^2 \ | 
 | 		$fixup^^ HEAD^ \ | 
 | 		$fixup^ HEAD \ | 
 | 		$fixup HEAD) && | 
 | 	test_cmp expect actual | 
 | ' | 
 |  | 
 | test_expect_success 'refuse to merge ancestors of HEAD' ' | 
 | 	echo "merge HEAD^" >script-from-scratch && | 
 | 	test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	before="$(git rev-parse HEAD)" && | 
 | 	git rebase -i HEAD && | 
 | 	test_cmp_rev HEAD $before | 
 | ' | 
 |  | 
 | test_expect_success 'root commits' ' | 
 | 	git checkout --orphan unrelated && | 
 | 	(GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \ | 
 | 	 test_commit second-root) && | 
 | 	test_commit third-root && | 
 | 	cat >script-from-scratch <<-\EOF && | 
 | 	pick third-root | 
 | 	label first-branch | 
 | 	reset [new root] | 
 | 	pick second-root | 
 | 	merge first-branch # Merge the 3rd root | 
 | 	EOF | 
 | 	test_config sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	test_tick && | 
 | 	git rebase -i --force-rebase --root -r && | 
 | 	test "Parsnip" = "$(git show -s --format=%an HEAD^)" && | 
 | 	test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) && | 
 | 	test $(git rev-parse second-root:second-root.t) = \ | 
 | 		$(git rev-parse HEAD^:second-root.t) && | 
 | 	test_cmp_graph HEAD <<-\EOF && | 
 | 	*   Merge the 3rd root | 
 | 	|\ | 
 | 	| * third-root | 
 | 	* second-root | 
 | 	EOF | 
 |  | 
 | 	: fast forward if possible && | 
 | 	before="$(git rev-parse --verify HEAD)" && | 
 | 	test_might_fail git config --unset sequence.editor && | 
 | 	test_tick && | 
 | 	git rebase -i --root -r && | 
 | 	test_cmp_rev HEAD $before | 
 | ' | 
 |  | 
 | test_expect_success 'a "merge" into a root commit is a fast-forward' ' | 
 | 	head=$(git rev-parse HEAD) && | 
 | 	cat >script-from-scratch <<-EOF && | 
 | 	reset [new root] | 
 | 	merge $head | 
 | 	EOF | 
 | 	test_config sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	test_tick && | 
 | 	git rebase -i -r HEAD^ && | 
 | 	test_cmp_rev HEAD $head | 
 | ' | 
 |  | 
 | test_expect_success 'A root commit can be a cousin, treat it that way' ' | 
 | 	git checkout --orphan khnum && | 
 | 	test_commit yama && | 
 | 	git checkout -b asherah main && | 
 | 	test_commit shamkat && | 
 | 	git merge --allow-unrelated-histories khnum && | 
 | 	test_tick && | 
 | 	git rebase -f -r HEAD^ && | 
 | 	test_cmp_rev ! HEAD^2 khnum && | 
 | 	test_cmp_graph HEAD^.. <<-\EOF && | 
 | 	*   Merge branch '\''khnum'\'' into asherah | 
 | 	|\ | 
 | 	| * yama | 
 | 	o shamkat | 
 | 	EOF | 
 | 	test_tick && | 
 | 	git rebase --rebase-merges=rebase-cousins HEAD^ && | 
 | 	test_cmp_graph HEAD^.. <<-\EOF | 
 | 	*   Merge branch '\''khnum'\'' into asherah | 
 | 	|\ | 
 | 	| * yama | 
 | 	|/ | 
 | 	o shamkat | 
 | 	EOF | 
 | ' | 
 |  | 
 | test_expect_success 'labels that are object IDs are rewritten' ' | 
 | 	git checkout -b third B && | 
 | 	test_commit I && | 
 | 	third=$(git rev-parse HEAD) && | 
 | 	git checkout -b labels main && | 
 | 	git merge --no-commit third && | 
 | 	test_tick && | 
 | 	git commit -m "Merge commit '\''$third'\'' into labels" && | 
 | 	echo noop >script-from-scratch && | 
 | 	test_config sequence.editor \""$PWD"/replace-editor.sh\" && | 
 | 	test_tick && | 
 | 	git rebase -i -r A && | 
 | 	grep "^label $third-" .git/ORIGINAL-TODO && | 
 | 	! grep "^label $third$" .git/ORIGINAL-TODO | 
 | ' | 
 |  | 
 | test_expect_success 'octopus merges' ' | 
 | 	git checkout -b three && | 
 | 	test_commit before-octopus && | 
 | 	test_commit three && | 
 | 	git checkout -b two HEAD^ && | 
 | 	test_commit two && | 
 | 	git checkout -b one HEAD^ && | 
 | 	test_commit one && | 
 | 	test_tick && | 
 | 	(GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \ | 
 | 	 git merge -m "Tüntenfüsch" two three) && | 
 |  | 
 | 	: fast forward if possible && | 
 | 	before="$(git rev-parse --verify HEAD)" && | 
 | 	test_tick && | 
 | 	git rebase -i -r HEAD^^ && | 
 | 	test_cmp_rev HEAD $before && | 
 |  | 
 | 	test_tick && | 
 | 	git rebase -i --force-rebase -r HEAD^^ && | 
 | 	test "Hank" = "$(git show -s --format=%an HEAD)" && | 
 | 	test "$before" != $(git rev-parse HEAD) && | 
 | 	test_cmp_graph HEAD^^.. <<-\EOF | 
 | 	*-.   Tüntenfüsch | 
 | 	|\ \ | 
 | 	| | * three | 
 | 	| * | two | 
 | 	| |/ | 
 | 	* / one | 
 | 	|/ | 
 | 	o before-octopus | 
 | 	EOF | 
 | ' | 
 |  | 
 | test_expect_success 'with --autosquash and --exec' ' | 
 | 	git checkout -b with-exec H && | 
 | 	echo Booh >B.t && | 
 | 	test_tick && | 
 | 	git commit --fixup B B.t && | 
 | 	write_script show.sh <<-\EOF && | 
 | 	subject="$(git show -s --format=%s HEAD)" | 
 | 	content="$(git diff HEAD^ HEAD | tail -n 1)" | 
 | 	echo "$subject: $content" | 
 | 	EOF | 
 | 	test_tick && | 
 | 	git rebase -ir --autosquash --exec ./show.sh A >actual && | 
 | 	grep "B: +Booh" actual && | 
 | 	grep "E: +Booh" actual && | 
 | 	grep "G: +G" actual | 
 | ' | 
 |  | 
 | test_expect_success '--continue after resolving conflicts after a merge' ' | 
 | 	git checkout -b already-has-g E && | 
 | 	git cherry-pick E..G && | 
 | 	test_commit H2 && | 
 |  | 
 | 	git checkout -b conflicts-in-merge H && | 
 | 	test_commit H2 H2.t conflicts H2-conflict && | 
 | 	test_must_fail git rebase -r already-has-g && | 
 | 	grep conflicts H2.t && | 
 | 	echo resolved >H2.t && | 
 | 	git add -u && | 
 | 	git rebase --continue && | 
 | 	test_must_fail git rev-parse --verify HEAD^2 && | 
 | 	test_path_is_missing .git/MERGE_HEAD | 
 | ' | 
 |  | 
 | test_expect_success '--rebase-merges with strategies' ' | 
 | 	git checkout -b with-a-strategy F && | 
 | 	test_tick && | 
 | 	git merge -m "Merge conflicting-G" conflicting-G && | 
 |  | 
 | 	: first, test with a merge strategy option && | 
 | 	git rebase -ir -Xtheirs G && | 
 | 	echo conflicting-G >expect && | 
 | 	test_cmp expect G.t && | 
 |  | 
 | 	: now, try with a merge strategy other than recursive && | 
 | 	git reset --hard @{1} && | 
 | 	write_script git-merge-override <<-\EOF && | 
 | 	echo overridden$1 >>G.t | 
 | 	git add G.t | 
 | 	EOF | 
 | 	PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G && | 
 | 	test_write_lines G overridden--xopt >expect && | 
 | 	test_cmp expect G.t | 
 | ' | 
 |  | 
 | test_expect_success '--rebase-merges with commit that can generate bad characters for filename' ' | 
 | 	git checkout -b colon-in-label E && | 
 | 	git merge -m "colon: this should work" G && | 
 | 	git rebase --rebase-merges --force-rebase E | 
 | ' | 
 |  | 
 | test_expect_success '--rebase-merges with message matched with onto label' ' | 
 | 	git checkout -b onto-label E && | 
 | 	git merge -m onto G && | 
 | 	git rebase --rebase-merges --force-rebase E && | 
 | 	test_cmp_graph <<-\EOF | 
 | 	*   onto | 
 | 	|\ | 
 | 	| * G | 
 | 	| * F | 
 | 	* |   E | 
 | 	|\ \ | 
 | 	| * | B | 
 | 	* | | D | 
 | 	| |/ | 
 | 	|/| | 
 | 	* | C | 
 | 	|/ | 
 | 	* A | 
 | 	EOF | 
 | ' | 
 |  | 
 | test_done |