 bef805b7d8
			
		
	
	bef805b7d8
	
	
	
		
			
			Change tests that used a "mkdir -p .git/hooks && write_script" pattern to use the new "test_hook" helper instead. The new helper does not create the .git/hooks directory, rather we assume that the default template will do so for us. An upcoming series[1] will extend "test_hook" to operate in a "--template=" mode, but for now assuming that we have a .git/hooks already is a safe assumption. If that assumption becomes false in the future we'll only need to change 'test_hook", instead of all of these callsites. 1. https://lore.kernel.org/git/cover-00.13-00000000000-20211212T201308Z-avarab@gmail.com/ Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
		
			
				
	
	
		
			521 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			521 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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)" &&
 | |
| 
 | |
| 	test_hook post-rewrite <<-\EOF &&
 | |
| 	cat >actual
 | |
| 	EOF
 | |
| 
 | |
| 	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
 |