| #!/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 <other@example.com>" >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 |