blob: 8ed0cebb5002960e0ee23e0b7037626b8e11041b [file] [log] [blame] [edit]
#!/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