aboutsummaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/meson.build1
-rwxr-xr-xt/t3452-history-split.sh757
2 files changed, 758 insertions, 0 deletions
diff --git a/t/meson.build b/t/meson.build
index 6d91470ebc..2d578ef58b 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -392,6 +392,7 @@ integration_tests = [
't3438-rebase-broken-files.sh',
't3450-history.sh',
't3451-history-reword.sh',
+ 't3452-history-split.sh',
't3500-cherry.sh',
't3501-revert-cherry-pick.sh',
't3502-cherry-pick-merge.sh',
diff --git a/t/t3452-history-split.sh b/t/t3452-history-split.sh
new file mode 100755
index 0000000000..8ed0cebb50
--- /dev/null
+++ b/t/t3452-history-split.sh
@@ -0,0 +1,757 @@
+#!/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