#!/bin/sh test_description='tests for git-history split subcommand' . ./test-lib.sh . "$TEST_DIRECTORY/lib-log-graph.sh" # The fake editor takes multiple arguments, each of which represents a commit # message. Subsequent invocations of the editor will then yield those messages # in order. # set_fake_editor () { printf "%s\n" "$@" >fake-input && write_script fake-editor.sh <<-\EOF && head -n1 fake-input >"$1" sed 1d fake-input >fake-input.trimmed && mv fake-input.trimmed fake-input EOF test_set_editor "$(pwd)"/fake-editor.sh } expect_graph () { cat >expect && lib_test_cmp_graph --graph --format=%s "$@" } expect_log () { git log --format="%s" >actual && cat >expect && test_cmp expect actual } expect_tree_entries () { git ls-tree --name-only "$1" >actual && cat >expect && test_cmp expect actual } test_expect_success 'refuses to work with merge commits' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit base && git branch branch && test_commit ours && git switch branch && test_commit theirs && git switch - && git merge theirs && test_must_fail git history split HEAD 2>err && test_grep "cannot split up merge commit" err && test_must_fail git history split HEAD~ 2>err && test_grep "replaying merge commits is not supported yet" err ) ' test_expect_success 'errors on missing commit argument' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit initial && test_must_fail git history split 2>err && test_grep "command expects a committish" err ) ' test_expect_success 'errors on unknown revision' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit initial && test_must_fail git history split does-not-exist 2>err && test_grep "commit cannot be found" err ) ' test_expect_success '--dry-run does not modify any refs' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit base && touch bar foo && git add . && git commit -m split-me && git refs list --include-root-refs >before && set_fake_editor "first" "second" && git history split --dry-run HEAD <<-EOF && y n EOF git refs list --include-root-refs >after && test_cmp before after ) ' test_expect_success 'can split up tip commit' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit initial && touch bar foo && git add . && git commit -m split-me && git symbolic-ref HEAD >expect && set_fake_editor "first" "second" && git history split HEAD <<-EOF && y n EOF git symbolic-ref HEAD >actual && test_cmp expect actual && expect_log <<-EOF && second first initial EOF expect_tree_entries HEAD~ <<-EOF && bar initial.t EOF expect_tree_entries HEAD <<-EOF && bar foo initial.t EOF git reflog >reflog && test_grep "split: updating HEAD" reflog ) ' test_expect_success 'can split up root commit' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && touch bar foo && git add . && git commit -m root && test_commit tip && set_fake_editor "first" "second" && git history split HEAD~ <<-EOF && y n EOF expect_log <<-EOF && tip second first EOF expect_tree_entries HEAD~2 <<-EOF && bar EOF expect_tree_entries HEAD~ <<-EOF && bar foo EOF expect_tree_entries HEAD <<-EOF bar foo tip.t EOF ) ' test_expect_success 'can split up in-between commit' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit initial && touch bar foo && git add . && git commit -m split-me && test_commit tip && set_fake_editor "first" "second" && git history split HEAD~ <<-EOF && y n EOF expect_log <<-EOF && tip second first initial EOF expect_tree_entries HEAD~2 <<-EOF && bar initial.t EOF expect_tree_entries HEAD~ <<-EOF && bar foo initial.t EOF expect_tree_entries HEAD <<-EOF bar foo initial.t tip.t EOF ) ' test_expect_success 'can split HEAD only' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit base && touch a b && git add . && git commit -m split-me && git branch unrelated && set_fake_editor "ours-a" "ours-b" && git history split --update-refs=head HEAD <<-EOF && y n EOF expect_graph --branches <<-EOF * ours-b * ours-a | * split-me |/ * base EOF ) ' test_expect_success 'can split detached HEAD' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit initial && touch bar foo && git add . && git commit -m split-me && git checkout --detach HEAD && set_fake_editor "first" "second" && git history split --update-refs=head HEAD <<-EOF && y n EOF # HEAD should be detached and updated. test_must_fail git symbolic-ref HEAD && expect_log <<-EOF second first initial EOF ) ' test_expect_success 'can split commit in unrelated branch' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit base && git branch ours && git switch --create theirs && touch theirs-a theirs-b && git add . && git commit -m theirs && git switch ours && test_commit ours && # With --update-refs=head it is not possible to split up a # commit that is unrelated to HEAD. test_must_fail git history split --update-refs=head theirs 2>err && test_grep "rewritten commit must be an ancestor of HEAD" err && set_fake_editor "theirs-rewritten-a" "theirs-rewritten-b" && git history split theirs <<-EOF && y n EOF expect_graph --branches <<-EOF && * ours | * theirs-rewritten-b | * theirs-rewritten-a |/ * base EOF expect_tree_entries theirs~ <<-EOF && base.t theirs-a EOF expect_tree_entries theirs <<-EOF base.t theirs-a theirs-b EOF ) ' test_expect_success 'updates multiple descendant branches' ' test_when_finished "rm -rf repo" && git init repo --initial-branch=main && ( cd repo && test_commit base && touch file-a file-b && git add . && git commit -m split-me && git branch branch && test_commit on-main && git switch branch && test_commit on-branch && git switch main && set_fake_editor "split-a" "split-b" && git history split HEAD~ <<-EOF && y n EOF # Both branches should now descend from the split commits. expect_graph --branches <<-EOF * on-branch | * on-main |/ * split-b * split-a * base EOF ) ' test_expect_success 'can pick multiple hunks' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && touch bar baz foo qux && git add . && git commit -m split-me && set_fake_editor "first" "second" && git history split HEAD <<-EOF && y n y n EOF expect_tree_entries HEAD~ <<-EOF && bar foo EOF expect_tree_entries HEAD <<-EOF bar baz foo qux EOF ) ' test_expect_success 'can use only last hunk' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && touch bar foo && git add . && git commit -m split-me && set_fake_editor "first" "second" && git history split HEAD <<-EOF && n y EOF expect_log <<-EOF && second first EOF expect_tree_entries HEAD~ <<-EOF && foo EOF expect_tree_entries HEAD <<-EOF bar foo EOF ) ' test_expect_success 'can split commit with file deletions' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && echo a >a && echo b >b && echo c >c && git add . && git commit -m base && git rm a b && git commit -m delete-both && set_fake_editor "delete-a" "delete-b" && git history split HEAD <<-EOF && y n EOF expect_log <<-EOF && delete-b delete-a base EOF expect_tree_entries HEAD~ <<-EOF && b c EOF expect_tree_entries HEAD <<-EOF c EOF ) ' test_expect_success 'preserves original authorship' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit initial && touch bar foo && git add . && GIT_AUTHOR_NAME="Other Author" \ GIT_AUTHOR_EMAIL="other@example.com" \ git commit -m split-me && set_fake_editor "first" "second" && git history split HEAD <<-EOF && y n EOF git log -1 --format="%an <%ae>" HEAD~ >actual && echo "Other Author " >expect && test_cmp expect actual && git log -1 --format="%an <%ae>" HEAD >actual && test_cmp expect actual ) ' test_expect_success 'aborts with empty commit message' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && touch bar foo && git add . && git commit -m split-me && set_fake_editor "" && test_must_fail git history split HEAD <<-EOF 2>err && y n EOF test_grep "Aborting commit due to empty commit message." err ) ' test_expect_success 'commit message editor sees split-out changes' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && touch bar foo && git add . && git commit -m split-me && write_script fake-editor.sh <<-\EOF && cat "$1" >>MESSAGES && echo "some commit message" >"$1" EOF test_set_editor "$(pwd)"/fake-editor.sh && git history split HEAD <<-EOF && y n EOF # Note that we expect to see the messages twice, once for each # of the commits. The committed files are different though. cat >expect <<-EOF && split-me # Please enter the commit message for the split-out changes. Lines starting # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit. # Changes to be committed: # new file: bar # split-me # Please enter the commit message for the split-out changes. Lines starting # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit. # Changes to be committed: # new file: foo # EOF test_cmp expect MESSAGES && expect_log <<-EOF some commit message some commit message EOF ) ' test_expect_success 'can use pathspec to limit what gets split' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && touch bar foo && git add . && git commit -m split-me && set_fake_editor "first" "second" && git history split HEAD -- foo <<-EOF && y EOF expect_tree_entries HEAD~ <<-EOF && foo EOF expect_tree_entries HEAD <<-EOF bar foo EOF ) ' test_expect_success 'pathspec matching no files produces empty split error' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit initial && touch bar foo && git add . && git commit -m split-me && set_fake_editor "first" "second" && test_must_fail git history split HEAD -- nonexistent 2>err && test_grep "split commit is empty" err ) ' test_expect_success 'split with multiple pathspecs' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit initial && touch a b c d && git add . && git commit -m split-me && # Only a and c should be offered for splitting. set_fake_editor "split-ac" "remainder" && git history split HEAD -- a c <<-EOF && y y EOF expect_tree_entries HEAD~ <<-EOF && a c initial.t EOF expect_tree_entries HEAD <<-EOF a b c d initial.t EOF ) ' test_expect_success 'split with file mode change' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && echo content >script && git add . && git commit -m base && test_chmod +x script && echo change >script && git commit -a -m "mode and content change" && set_fake_editor "mode-change" "content-change" && git history split HEAD <<-EOF && y n EOF expect_log <<-EOF content-change mode-change base EOF ) ' test_expect_success 'refuses to create empty split-out commit' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && test_commit base && touch bar foo && git add . && git commit -m split-me && test_must_fail git history split HEAD 2>err <<-EOF && n n EOF test_grep "split commit is empty" err ) ' test_expect_success 'hooks are not executed for rewritten commits' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && touch bar foo && git add . && git commit -m split-me && old_head=$(git rev-parse HEAD) && ORIG_PATH="$(pwd)" && export ORIG_PATH && for hook in prepare-commit-msg pre-commit post-commit post-rewrite commit-msg do write_script .git/hooks/$hook <<-\EOF || exit 1 touch "$ORIG_PATH"/hooks.log EOF done && set_fake_editor "first" "second" && git history split HEAD <<-EOF && y n EOF expect_log <<-EOF && second first EOF test_path_is_missing hooks.log ) ' test_expect_success 'refuses to create empty original commit' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && touch bar foo && git add . && git commit -m split-me && test_must_fail git history split HEAD 2>err <<-EOF && y y EOF test_grep "split commit tree matches original commit" err ) ' test_expect_success 'retains changes in the worktree and index' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && echo a >a && echo b >b && git add . && git commit -m "initial commit" && echo a-modified >a && echo b-modified >b && git add b && set_fake_editor "a-only" "remainder" && git history split HEAD <<-EOF && y n EOF expect_tree_entries HEAD~ <<-EOF && a EOF expect_tree_entries HEAD <<-EOF && a b EOF cat >expect <<-\EOF && M a M b ?? actual ?? expect ?? fake-editor.sh ?? fake-input EOF git status --porcelain >actual && test_cmp expect actual ) ' test_done