git/t/t7505-prepare-commit-msg-hook.sh
Phillip Wood 450efe2d53 rebase -i: always update HEAD before rewording
If the user runs git log while rewording a commit it is confusing if
sometimes we're amending the commit that's being reworded and at other
times we're creating a new commit depending on whether we could
fast-forward or not[1]. Fix this inconsistency by always committing the
picked commit and then running 'git commit --amend' to do the reword.

The first commit is performed by the sequencer without forking git
commit and does not impact on the speed of rebase. In a test rewording
100 commits with

    GIT_EDITOR=true GIT_SEQUENCE_EDITOR='sed -i s/pick/reword/' \
	../bin-wrappers/git rebase -i --root

and taking the best of three runs the current master took
957ms and with this patch it took 961ms.

This change fixes rewording the new root commit when rearranging commits
with --root.

Note that the new code no longer updates CHERRY_PICK_HEAD after creating
a root commit - I'm not sure why the old code was that creating that ref
after a successful commit, everywhere else it is removed after a
successful commit.

[1] https://public-inbox.org/git/xmqqlfvu4be3.fsf@gitster-ct.c.googlers.com/T/#m133009cb91cf0917bcf667300f061178be56680a

Reported-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-19 15:27:09 -07:00

315 lines
6.9 KiB
Bash
Executable File

#!/bin/sh
test_description='prepare-commit-msg hook'
. ./test-lib.sh
test_expect_success 'set up commits for rebasing' '
test_commit root &&
test_commit a a a &&
test_commit b b b &&
git checkout -b rebase-me root &&
test_commit rebase-a a aa &&
test_commit rebase-b b bb &&
for i in $(test_seq 1 13)
do
test_commit rebase-$i c $i
done &&
git checkout master &&
cat >rebase-todo <<-EOF
pick $(git rev-parse rebase-a)
pick $(git rev-parse rebase-b)
fixup $(git rev-parse rebase-1)
fixup $(git rev-parse rebase-2)
pick $(git rev-parse rebase-3)
fixup $(git rev-parse rebase-4)
squash $(git rev-parse rebase-5)
reword $(git rev-parse rebase-6)
squash $(git rev-parse rebase-7)
fixup $(git rev-parse rebase-8)
fixup $(git rev-parse rebase-9)
edit $(git rev-parse rebase-10)
squash $(git rev-parse rebase-11)
squash $(git rev-parse rebase-12)
edit $(git rev-parse rebase-13)
EOF
'
test_expect_success 'with no hook' '
echo "foo" > file &&
git add file &&
git commit -m "first"
'
# set up fake editor for interactive editing
cat > fake-editor <<'EOF'
#!/bin/sh
exit 0
EOF
chmod +x fake-editor
## Not using test_set_editor here so we can easily ensure the editor variable
## is only set for the editor tests
FAKE_EDITOR="$(pwd)/fake-editor"
export FAKE_EDITOR
# now install hook that always succeeds and adds a message
HOOKDIR="$(git rev-parse --git-dir)/hooks"
HOOK="$HOOKDIR/prepare-commit-msg"
mkdir -p "$HOOKDIR"
echo "#!$SHELL_PATH" > "$HOOK"
cat >> "$HOOK" <<'EOF'
GIT_DIR=$(git rev-parse --git-dir)
if test -d "$GIT_DIR/rebase-merge"
then
rebasing=1
else
rebasing=0
fi
get_last_cmd () {
tail -n1 "$GIT_DIR/rebase-merge/done" | {
read cmd id _
git log --pretty="[$cmd %s]" -n1 $id
}
}
if test "$2" = commit
then
if test $rebasing = 1
then
source="$3"
else
source=$(git rev-parse "$3")
fi
else
source=${2-default}
fi
test "$GIT_EDITOR" = : && source="$source (no editor)"
if test $rebasing = 1
then
echo "$source $(get_last_cmd)" >"$1"
else
sed -e "1s/.*/$source/" "$1" >msg.tmp
mv msg.tmp "$1"
fi
exit 0
EOF
chmod +x "$HOOK"
echo dummy template > "$(git rev-parse --git-dir)/template"
test_expect_success 'with hook (-m)' '
echo "more" >> file &&
git add file &&
git commit -m "more" &&
test "$(git log -1 --pretty=format:%s)" = "message (no editor)"
'
test_expect_success 'with hook (-m editor)' '
echo "more" >> file &&
git add file &&
GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit -e -m "more more" &&
test "$(git log -1 --pretty=format:%s)" = message
'
test_expect_success 'with hook (-t)' '
echo "more" >> file &&
git add file &&
git commit -t "$(git rev-parse --git-dir)/template" &&
test "$(git log -1 --pretty=format:%s)" = template
'
test_expect_success 'with hook (-F)' '
echo "more" >> file &&
git add file &&
(echo more | git commit -F -) &&
test "$(git log -1 --pretty=format:%s)" = "message (no editor)"
'
test_expect_success 'with hook (-F editor)' '
echo "more" >> file &&
git add file &&
(echo more more | GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit -e -F -) &&
test "$(git log -1 --pretty=format:%s)" = message
'
test_expect_success 'with hook (-C)' '
head=$(git rev-parse HEAD) &&
echo "more" >> file &&
git add file &&
git commit -C $head &&
test "$(git log -1 --pretty=format:%s)" = "$head (no editor)"
'
test_expect_success 'with hook (editor)' '
echo "more more" >> file &&
git add file &&
GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit &&
test "$(git log -1 --pretty=format:%s)" = default
'
test_expect_success 'with hook (--amend)' '
head=$(git rev-parse HEAD) &&
echo "more" >> file &&
git add file &&
GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit --amend &&
test "$(git log -1 --pretty=format:%s)" = "$head"
'
test_expect_success 'with hook (-c)' '
head=$(git rev-parse HEAD) &&
echo "more" >> file &&
git add file &&
GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit -c $head &&
test "$(git log -1 --pretty=format:%s)" = "$head"
'
test_expect_success 'with hook (merge)' '
test_when_finished "git checkout -f master" &&
git checkout -B other HEAD@{1} &&
echo "more" >>file &&
git add file &&
git commit -m other &&
git checkout - &&
git merge --no-ff other &&
test "$(git log -1 --pretty=format:%s)" = "merge (no editor)"
'
test_expect_success 'with hook and editor (merge)' '
test_when_finished "git checkout -f master" &&
git checkout -B other HEAD@{1} &&
echo "more" >>file &&
git add file &&
git commit -m other &&
git checkout - &&
env GIT_EDITOR="\"\$FAKE_EDITOR\"" git merge --no-ff -e other &&
test "$(git log -1 --pretty=format:%s)" = "merge"
'
test_rebase () {
expect=$1 &&
mode=$2 &&
test_expect_$expect C_LOCALE_OUTPUT "with hook (rebase ${mode:--i})" '
test_when_finished "\
git rebase --abort
git checkout -f master
git branch -D tmp" &&
git checkout -b tmp rebase-me &&
GIT_SEQUENCE_EDITOR="cp rebase-todo" &&
GIT_EDITOR="\"$FAKE_EDITOR\"" &&
(
export GIT_SEQUENCE_EDITOR GIT_EDITOR &&
test_must_fail git rebase -i $mode b &&
echo x >a &&
git add a &&
test_must_fail git rebase --continue &&
echo x >b &&
git add b &&
git commit &&
git rebase --continue &&
echo y >a &&
git add a &&
git commit &&
git rebase --continue &&
echo y >b &&
git add b &&
git rebase --continue
) &&
git log --pretty=%s -g -n18 HEAD@{1} >actual &&
test_cmp "$TEST_DIRECTORY/t7505/expected-rebase${mode:--i}" actual
'
}
test_rebase success
test_have_prereq !REBASE_P || test_rebase success -p
test_expect_success 'with hook (cherry-pick)' '
test_when_finished "git checkout -f master" &&
git checkout -B other b &&
git cherry-pick rebase-1 &&
test "$(git log -1 --pretty=format:%s)" = "message (no editor)"
'
test_expect_success 'with hook and editor (cherry-pick)' '
test_when_finished "git checkout -f master" &&
git checkout -B other b &&
git cherry-pick -e rebase-1 &&
test "$(git log -1 --pretty=format:%s)" = merge
'
cat > "$HOOK" <<'EOF'
#!/bin/sh
exit 1
EOF
test_expect_success 'with failing hook' '
test_when_finished "git checkout -f master" &&
head=$(git rev-parse HEAD) &&
echo "more" >> file &&
git add file &&
test_must_fail env GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit -c $head
'
test_expect_success 'with failing hook (--no-verify)' '
test_when_finished "git checkout -f master" &&
head=$(git rev-parse HEAD) &&
echo "more" >> file &&
git add file &&
test_must_fail env GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit --no-verify -c $head
'
test_expect_success 'with failing hook (merge)' '
test_when_finished "git checkout -f master" &&
git checkout -B other HEAD@{1} &&
echo "more" >> file &&
git add file &&
rm -f "$HOOK" &&
git commit -m other &&
write_script "$HOOK" <<-EOF &&
exit 1
EOF
git checkout - &&
test_must_fail git merge --no-ff other
'
test_expect_success C_LOCALE_OUTPUT 'with failing hook (cherry-pick)' '
test_when_finished "git checkout -f master" &&
git checkout -B other b &&
test_must_fail git cherry-pick rebase-1 2>actual &&
test $(grep -c prepare-commit-msg actual) = 1
'
test_done