How would I write a pre-merge hook in Git?
Asked Answered
C

3

64

The question says it all. Is there a way to perform an action before a merge? I'm guessing there's a way to make use of a pre-commit hook, but I'm not quite sure.

Cyclohexane answered 30/9, 2013 at 19:59 Comment(2)
Even if this question is more than 5 years old -- meanwhile any solution for this? For instance, I want people to be able to commit into their own, private branches whatever they want, but not merge code with terrible metrics into the develop or master branch...Keel
Note: Git 2.24 (Q4 2019) actually does include a pre-merge-commit hook. See my answer belowCollagen
F
28

You can try using the prepare-commit-msg hook. The second argument will be merge "if the commit is a merge or a .git/MERGE_MSG file exists". A non-zero exit status will abort the commit.

I don't think this will work with a fast-forward merge, since there won't be a commit message.

More info on hooks: https://www.kernel.org/pub/software/scm/git/docs/githooks.html#_prepare_commit_msg

Flowing answered 18/10, 2013 at 23:1 Comment(6)
This doesn't seem to be triggered when you do a git merge. To see this: put exit 1 at the top of prepare-commit-msg and perform a merge commit.Pitchstone
Was it a fast-forward merge? If the merge doesn't trigger a commit message, it won't help.Flowing
Even for an actual merge with a commit (message) this is not triggered. I've tried with the test described above... Very happy to be proved wrong with an example, but please give this a test.Pitchstone
Is this possible even now? I have been trying to trigger a hook that would fire up before someone does a merge. I don't think its possible, but just wanted to confirm.Mongolian
Works for me on non-fast-forward merges with git 2.18. printf -- '%s\n%s\n' '#!/bin/sh' 'echo nope && exit 1' > prepare-commit-msgRafflesia
Works for me in a number of tests (in 2022, pre-merge-commit is supported in Git but not by all Git GUI clients, so this is still relevant)Campagna
C
29

With Git 2.24 (Q4 2019), no need for script wrapper, or prepare-message hook.

A new "pre-merge-commit" hook has been introduced.

See commit bc40ce4, commit 6098817, commit a1f3dd7 (07 Aug 2019) by Michael J Gruber (mjg).
See commit f78f6c7 (07 Aug 2019) by Josh Steadmon (steadmon).
(Merged by Junio C Hamano -- gitster -- in commit f76bd8c, 18 Sep 2019)

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).

The documentation now includes:

pre-merge-commit

This hook is invoked by git-merge.
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 git-merge).
At that point, this hook will not be executed, but the 'pre-commit' hook will, if it is enabled.


With Git 2.36 (Q2 2022), the callers of run_commit_hook() learn if it got "success" because the hook succeeded or because there wasn't any hook.

See commit a8cc594, commit 9f6e63b (07 Mar 2022) by Ævar Arnfjörð Bjarmason (avar).
(Merged by Junio C Hamano -- gitster -- in commit 7431379, 16 Mar 2022)

merge: don't run post-hook logic on --no-verify

Signed-off-by: Ævar Arnfjörð Bjarmason

Fix a minor bug introduced in bc40ce4 ("merge: --no-verify to bypass pre-merge-commit hook", 2019-08-07, Git v2.24.0-rc0 -- merge listed in batch #3), when that change made the --no-verify option bypass the pre-merge-commit hook it didn't update the corresponding find_hook() (later hook_exists()) condition.

As can be seen in the preceding commit in 6098817 ("git-merge: honor pre-merge-commit hook", 2019-08-07, Git v2.24.0-rc0 -- merge listed in batch #3) the two should go hand in hand.
There's no point in invoking discard_cache() here if the hook couldn't have possibly updated the index.

It's buggy that we use "hook_exist()" here, and as discussed in the subsequent commit it's subject to obscure race conditions that we're about to fix, but for now this change is a strict improvement that retains any caveats to do with the use of "hooks_exist()" as-is.


Warning: Devin Rhode reports (Nov. 2022) in the comments:

I think I actually found a bug with the pre-merge-commit hook not running.
See PR "pre-merge-commit hook is not running"

But Pascal Jufer adds:

I came to the conclusion that the pre-merge-commit hook is actually working as intended, which means this is not a bug.
The documentation states the following:

[...] is invoked after the merge has been carried out successfully and before obtaining the proposed commit log message to make a commit [...]

and

[...] If the merge cannot be carried out automatically, the conflicts need to be resolved and the result committed separately (see git-merge1).
At that point, this hook will not be executed, but the pre-commit hook will, if it is enabled.

That means:

  • If there's a conflict like provoked in your reproduction (merge cannot be carried out automatically), the pre-merge-commit hook won't be called at all.
    Understandably, after resolving the conflict and committing the changes, Git treats it like a normal commit and only calls the pre-commit hook:

    $ git merge remotes/origin/feature/make-eggs
    $ git add readme.md
    $ git commit
    pre-commit hook
    [main 26262e1] Merge remote-tracking branch 'remotes/origin/feature/make-eggs'
    
  • When executing a "conflict-free" merge, there's no commit log message to obtain (fast-forward updates do not create a merge commit) which means the pre-merge-commit hook is skipped here as well.

  • The pre-merge-commit is getting called (also with custom hooksPath) as intended when a merge commit is generated, for example using git merge --no-ff: $ git merge test --no-ff pre-merge-commit hook Merge made by the 'ort' strategy.

Collagen answered 18/9, 2019 at 21:44 Comment(6)
Nice!! Way better than my hacky wrapper.Keratosis
Hey @Collagen I think I actually found a bug with the pre-merge-commit hook not running: github.com/devinrhode2/git-pre-merge-commit-test/pull/1Killingsworth
@DevinRhode Thank you for this feedback. I have included your PR in the answer for more visibility, and will follow it.Collagen
Sounds like this hook is working correctly. You could remove call out in your answer, and just let people read comments. It's only called when you are prompted for a merge commit message. When you have to resolve conflicts, the normal pre-commit hook is called instead. See awesome write up from paescuj: github.com/typicode/husky/issues/1210#issuecomment-1371726293Killingsworth
@DevinRhode I have edited the answer accordingly (an answer is more readable than comments). Let me know if I got everything.Collagen
Splitting hairs, but I think Pascals explanation works well in the context of that github thread, and I think something shorter and simpler would be more suitable for stackoverflow. Github links hopefully keep working for next 100 years, so I think summarizing + linking to his full comment is most sensibleKillingsworth
F
28

You can try using the prepare-commit-msg hook. The second argument will be merge "if the commit is a merge or a .git/MERGE_MSG file exists". A non-zero exit status will abort the commit.

I don't think this will work with a fast-forward merge, since there won't be a commit message.

More info on hooks: https://www.kernel.org/pub/software/scm/git/docs/githooks.html#_prepare_commit_msg

Flowing answered 18/10, 2013 at 23:1 Comment(6)
This doesn't seem to be triggered when you do a git merge. To see this: put exit 1 at the top of prepare-commit-msg and perform a merge commit.Pitchstone
Was it a fast-forward merge? If the merge doesn't trigger a commit message, it won't help.Flowing
Even for an actual merge with a commit (message) this is not triggered. I've tried with the test described above... Very happy to be proved wrong with an example, but please give this a test.Pitchstone
Is this possible even now? I have been trying to trigger a hook that would fire up before someone does a merge. I don't think its possible, but just wanted to confirm.Mongolian
Works for me on non-fast-forward merges with git 2.18. printf -- '%s\n%s\n' '#!/bin/sh' 'echo nope && exit 1' > prepare-commit-msgRafflesia
Works for me in a number of tests (in 2022, pre-merge-commit is supported in Git but not by all Git GUI clients, so this is still relevant)Campagna
K
1

Another nice workaround would be to add a shell script, call it like you want, then add these lines to the script:

git() {
    if [ "$1" == "merge" ]; then
        echo "seems to work like a charme"
    fi
    command git "$@"
}

git "$@"

Then make an

alias git="./my-pre-merge-script.sh"

Then you are good to go. You just added your own pre-merge hook. I know, that you do not have access to whatever arguments git would pass to a real pre-merge hook, but you can prepare files or whatever you want to prepare for merge now; I personally am very happy with this approach: I spent 2 or 3 whole days to find something for pre-merge, then I had to go with the pre-commit-msg which I did not find accurate enough for my needs. This solves all my problems. Hope this helps anybody in the future.

Keratosis answered 6/2, 2018 at 20:27 Comment(5)
-1. Not only is this ugly, it's not robust at all. merge isn't always the first argument to git: consider something like git -c foo=bar merge foobar.Tyrus
It is an ugly hack indeed. However, thanks for the hint that merge does not have to be $1, however, you can still parse "merge" from the arguments list nevertheless! Would have a better solution for pre-merge hooks?Keratosis
That might be possible, but it's definitely pretty complicated. What if someone has a branch named merge? What if someone merges with git pull instead of git merge, or even with a custom git script? Your script needs to understand git to determine when the user is actually merging.Tyrus
I am actually currently working on a huge script, involving git hooks. I need my own pre-git hook, which actually cares about git pull like you just said. Also, I need to add special case handling for stuff like git merge --abort or git commit --amend... and there are quite endless possibilities of what users can do... But sometimes, people might just be happy with a solution I provided above, for private/hobby projects, NOT for production projects in a company. Thus, I think it is a bit harsh to downvote it instant, especially when you cannot provide a better alternative.Keratosis
Also, whoever comes to the idea of calling a branch "merge" should find a new hobby imho.Keratosis

© 2022 - 2024 — McMap. All rights reserved.