758 lines
13 KiB
Bash
Executable File
758 lines
13 KiB
Bash
Executable File
#!/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
|