16

I have a Git pre-commit hook that prevents me from committing to master unless overridden, in order to encourage developing on branch.

However I would like to automatically allow merge commits to master. Is there a way to identify a merge commit from my pre-commit hook script? The script looks like this:

#!/bin/bash

BRANCH=`git branch --color=never| grep '^*'|cut -c3-`

if [ "${BRANCH}D" == "masterD" -a "${GIT_COMMIT_TO_MASTER}D" != "trueD" ]
then
  echo "Commit directly to master is discouraged."
  echo "If you want to do this, please set GIT_COMMIT_TO_MASTER=true and then commit."
  exit 1
fi

SOLVED: For anyone looking for a cut-and-paste, the working version of this hook script is:

#!/bin/bash

BRANCH=$(git rev-parse --abbrev-ref HEAD)

if [ "${BRANCH}" == "master" -a "${GIT_COMMIT_TO_MASTER}" != "true" ]
then
  if [ -e "${GIT_DIR}/MERGE_MODE" ]
  then
    echo "Merge to master is allowed."
    exit 0
  else
    echo "Commit directly to master is discouraged."
    echo "If you want to do this, please set GIT_COMMIT_TO_MASTER=true and then commit."
    exit 1
  fi
fi
Richard Neish
  • 8,414
  • 4
  • 39
  • 69
  • 1
    Suggestion: a more robust way to get the current branch is `branch=$(git rev-parse --abbrev-ref HEAD)`. – jub0bs Mar 24 '15 at 09:54
  • 1
    @Jubobs: generally it's better to use `git symbolic-ref HEAD`. One can add `-q` if needed—it would be in this case and then you'd want to exit 0, so in this *particular* case, using `git rev-parse` might well be superior anyway: for a detached head you'll just get `HEAD` back, which won't match `master`. – torek Mar 24 '15 at 10:09
  • Not related to the question, but your `D` suffices are entirely redundant. You would only need them if you didn't use quotes, and they reduce legibility, so just drop them. – Adam Spiers Mar 24 '15 at 10:10
  • @torek Interesting. Thanks. Taking it in... – jub0bs Mar 24 '15 at 10:12
  • 1
    It's also worth noting that the '--no-verify' flag will bypass the pre-commit hook. I suggest removing the 'GIT_COMMIT_TO_MASTER' section in favor of overriding on a case-by-case basis, since it's easy to forget to set 'GIT_COMMIT_TO_MASTER' back to false after overriding. – thislooksfun May 09 '17 at 15:16

1 Answers1

8

I put a few comments in, but the important one here is that in a pre-commit hook, the commit you're about to test does not yet exist, so you can't count its parents.

Here's what you do get:

  • If you're using git commit --amend to amend a merge commit, the pre-commit hook is run as usual, but it can't really detect that this is happening. The new commit will be a merge, but you can't tell.

  • If you're using regular old git commit to create a non-merge commit, the file MERGE_HEAD will not exist in the git directory, and you can tell that this is not going to create a merge commit.

  • If you're using git commit to finish off a conflicted merge, the file MERGE_HEAD will exist, and you can tell that this is going to create a merge commit.

  • If you're running git merge and it succeeds on its own, it makes a new commit without using the pre-commit hook, so you don't even get invoked here.

Hence, if you're willing to allow git commit --amend on merges to misfire, you can get close to what you want: just test for the existence of $GIT_DIR/MERGE_HEAD to see if this is a git commit that is finishing off a conflicted merge. (The use of $GIT_DIR is a trick to make this work even if the commands are run outside the git tree. Git sets $GIT_DIR so that in-hook git commands will work right.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • That looks promising. I am happy for `git commit --amend` to require an override, so checking for `${GIT_DIR/MERGE_HEAD}` is exactly what I'm looking for. I will test and if I don't hit any issues, I will accept this answer. – Richard Neish Mar 24 '15 at 10:33