git-merge: honor pre-merge-commit hook

git-merge does not honor the pre-commit hook when doing automatic merge
commits, and for compatibility reasons this is going to stay.

Introduce a pre-merge-commit hook which is called for an automatic merge
commit just like pre-commit is called for a non-automatic merge commit
(or any other commit).

[js: * renamed hook from "pre-merge" to "pre-merge-commit"
     * only discard the index if the hook is actually present
     * expanded githooks documentation entry
     * clarified that hook should write messages to stderr
     * squashed test changes from the original series' patch 4/4
     * modified tests to follow new pattern from this series' patch 1/4
     * added a test case for non-executable merge hooks
     * added a test case for failed merges
     * when testing that the merge hook did not run, make sure we
       actually have a merge to perform (by resetting the "side" branch
       to its original state).
     * reworded commit message
]

Improved-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Michael J Gruber <git@grubix.eu>
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Josh Steadmon <steadmon@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Michael J Gruber 2019-08-07 11:57:07 -07:00 committed by Junio C Hamano
parent a1f3dd7eb3
commit 6098817fd7
4 changed files with 129 additions and 1 deletions

View File

@ -103,6 +103,27 @@ The default 'pre-commit' hook, when enabled--and with the
`hooks.allownonascii` config option unset or set to false--prevents
the use of non-ASCII filenames.
pre-merge-commit
~~~~~~~~~~~~~~~~
This hook is invoked by linkgit:git-merge[1]. It takes no parameters, and is
invoked after the merge has been carried out successfully and before
obtaining the proposed commit log message to
make a commit. Exiting with a non-zero status from this script
causes the `git merge` command to abort before creating a commit.
The default 'pre-merge-commit' hook, when enabled, runs the
'pre-commit' hook, if the latter is enabled.
This hook is invoked with the environment variable
`GIT_EDITOR=:` if the command will not bring up an editor
to modify the commit message.
If the merge cannot be carried out automatically, the conflicts
need to be resolved and the result committed separately (see
linkgit:git-merge[1]). At that point, this hook will not be executed,
but the 'pre-commit' hook will, if it is enabled.
prepare-commit-msg
~~~~~~~~~~~~~~~~~~

View File

@ -816,6 +816,18 @@ static void write_merge_heads(struct commit_list *);
static void prepare_to_commit(struct commit_list *remoteheads)
{
struct strbuf msg = STRBUF_INIT;
const char *index_file = get_index_file();
if (run_commit_hook(0 < option_edit, index_file, "pre-merge-commit", NULL))
abort_commit(remoteheads, NULL);
/*
* Re-read the index as pre-merge-commit hook could have updated it,
* and write it out as a tree. We must do this before we invoke
* the editor and after we invoke run_status above.
*/
if (find_hook("pre-merge-commit"))
discard_cache();
read_cache_from(index_file);
strbuf_addbuf(&msg, &merge_msg);
if (squash)
BUG("the control must not reach here under --squash");

View File

@ -1,11 +1,12 @@
#!/bin/sh
test_description='pre-commit hook'
test_description='pre-commit and pre-merge-commit hooks'
. ./test-lib.sh
HOOKDIR="$(git rev-parse --git-dir)/hooks"
PRECOMMIT="$HOOKDIR/pre-commit"
PREMERGE="$HOOKDIR/pre-merge-commit"
# Prepare sample scripts that write their $0 to actual_hooks
test_expect_success 'sample script setup' '
@ -34,6 +35,30 @@ test_expect_success 'sample script setup' '
EOF
'
test_expect_success 'root commit' '
echo "root" >file &&
git add file &&
git commit -m "zeroth" &&
git checkout -b side &&
echo "foo" >foo &&
git add foo &&
git commit -m "make it non-ff" &&
git branch side-orig side &&
git checkout master
'
test_expect_success 'setup conflicting branches' '
test_when_finished "git checkout master" &&
git checkout -b conflicting-a master &&
echo a >conflicting &&
git add conflicting &&
git commit -m conflicting-a &&
git checkout -b conflicting-b master &&
echo b >conflicting &&
git add conflicting &&
git commit -m conflicting-b
'
test_expect_success 'with no hook' '
test_when_finished "rm -f actual_hooks" &&
echo "foo" >file &&
@ -42,6 +67,15 @@ test_expect_success 'with no hook' '
test_path_is_missing actual_hooks
'
test_expect_success 'with no hook (merge)' '
test_when_finished "rm -f actual_hooks" &&
git branch -f side side-orig &&
git checkout side &&
git merge -m "merge master" master &&
git checkout master &&
test_path_is_missing actual_hooks
'
test_expect_success '--no-verify with no hook' '
test_when_finished "rm -f actual_hooks" &&
echo "bar" >file &&
@ -60,6 +94,34 @@ test_expect_success 'with succeeding hook' '
test_cmp expected_hooks actual_hooks
'
test_expect_success 'with succeeding hook (merge)' '
test_when_finished "rm -f \"$PREMERGE\" expected_hooks actual_hooks" &&
cp "$HOOKDIR/success.sample" "$PREMERGE" &&
echo "$PREMERGE" >expected_hooks &&
git checkout side &&
git merge -m "merge master" master &&
git checkout master &&
test_cmp expected_hooks actual_hooks
'
test_expect_success 'automatic merge fails; both hooks are available' '
test_when_finished "rm -f \"$PREMERGE\" \"$PRECOMMIT\"" &&
test_when_finished "rm -f expected_hooks actual_hooks" &&
test_when_finished "git checkout master" &&
cp "$HOOKDIR/success.sample" "$PREMERGE" &&
cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
git checkout conflicting-a &&
test_must_fail git merge -m "merge conflicting-b" conflicting-b &&
test_path_is_missing actual_hooks &&
echo "$PRECOMMIT" >expected_hooks &&
echo a+b >conflicting &&
git add conflicting &&
git commit -m "resolve conflict" &&
test_cmp expected_hooks actual_hooks
'
test_expect_success '--no-verify with succeeding hook' '
test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
@ -88,6 +150,16 @@ test_expect_success '--no-verify with failing hook' '
test_path_is_missing actual_hooks
'
test_expect_success 'with failing hook (merge)' '
test_when_finished "rm -f \"$PREMERGE\" expected_hooks actual_hooks" &&
cp "$HOOKDIR/fail.sample" "$PREMERGE" &&
echo "$PREMERGE" >expected_hooks &&
git checkout side &&
test_must_fail git merge -m "merge master" master &&
git checkout master &&
test_cmp expected_hooks actual_hooks
'
test_expect_success POSIXPERM 'with non-executable hook' '
test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
cp "$HOOKDIR/non-exec.sample" "$PRECOMMIT" &&
@ -106,6 +178,16 @@ test_expect_success POSIXPERM '--no-verify with non-executable hook' '
test_path_is_missing actual_hooks
'
test_expect_success POSIXPERM 'with non-executable hook (merge)' '
test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
cp "$HOOKDIR/non-exec.sample" "$PREMERGE" &&
git branch -f side side-orig &&
git checkout side &&
git merge -m "merge master" master &&
git checkout master &&
test_path_is_missing actual_hooks
'
test_expect_success 'with hook requiring GIT_PREFIX' '
test_when_finished "rm -rf \"$PRECOMMIT\" expected_hooks actual_hooks success" &&
cp "$HOOKDIR/require-prefix.sample" "$PRECOMMIT" &&

View File

@ -0,0 +1,13 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
: