optional githook behaving as non-optional

I am attempting to make use of this gist in my workflow as post-merge and post-checkout git hooks.

#!/usr/bin/env bash
# MIT © Sindre Sorhus - sindresorhus.com

# git hook to run a command after `git pull` if a specified file was changed
# Run `chmod +x post-merge` to make it executable then put it into `.git/hooks/`.

changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"

check_run() {
    echo "$changed_files" | grep --quiet "$1" && eval "$2"
}

# Example usage
# In this example it's used to run `npm install` if package.json changed
check_run package.json "npm install"

This claims to only run npm install if the package.json file is changed.

  • Git: Additional changes during merge without conflicts
  • Building PhantomJS-2 from source on Windows
  • how to get project path in hook script post-commit?(git)
  • Recreating branch history in git from imported branches
  • Use Windows git with Git Kraken
  • race conditions if two clients git svn rebase and git push to the same git repo?
  • However on all the machines I have tried this on. The npm install command runs regardless of whether package.json has been changed or not.

    To test this I have been creating a new branch at my current commit and then checking it out, thus triggering the post-checkout git hook. I would not expect npm install to run because the package.json is unchanged.

    Visual Proof (note the npm warning text):

    enter image description here

  • Sprockets::FileNotFound: couldn't find file '../../fonts/bootstrap/glyphicons-halflings-regular.eot'
  • Limiting file size in git repository
  • git - how to handle a branch “test”
  • How to best share code with SmartGit
  • Git-publishing an Azure website with lots of NuGet dependencies
  • git, ignore files with a pattern in Laravel
  • 2 Solutions collect form web for “optional githook behaving as non-optional”

    TL;DR

    Use a different post-checkout hook, that uses $1 instead of ORIG_HEAD. (Or, check the number of arguments to decide whether you are being invoked as the post-checkout or post-merge hook, to get the same effect. Or, if you know that reflogs are always enabled, use HEAD@{1} to get the previous value of HEAD.)

    Discussion

    Using ORIG_HEAD in a post-merge hook makes sense, because git merge sets ORIG_HEAD to the commit that was current before the merge. (If the merge was a true merge, rather than a fast-forward, the commit identified by MERGE_HEAD and the commit identified by HEAD^1 are necessarily identical. If the merge was a fast-forward, however, only MERGE_HEAD and the reflog will be able to locate the previous commit hash that was stored in HEAD before the merge.)

    Using ORIG_HEAD in a post-checkout hook, however, is blatantly wrong, because git checkout does not set ORIG_HEAD. This means that if ORIG_HEAD even exists at all, it effectively points to some random commit. (Of course, it actually resolves to whatever commit was left in it by whatever command last updated it: git merge, git rebase, or any other command that writes to ORIG_HEAD. But the point here is that it does not have any relationship to the commit that was current before the checkout.) A post-checkout hook:

    is given three parameters: the ref of the
    previous HEAD, the ref of the new HEAD (which may or may not have
    changed), and a flag indicating whether the checkout was a branch
    checkout (changing branches, flag=1) or a file checkout (retrieving a
    file from the index, flag=0). This hook cannot affect the outcome of
    git checkout.

    (That last sentence is not quite right. Although the post-checkout hook cannot stop checkout from having updated the index and work-tree, it can overwrite various work-tree or index contents, and if it produces a failure exit status, it causes git checkout itself to also produce a failure exit status.)

    What this all means is that you need to take a different action in a post-checkout hook: use $1, the first parameter, to get the hash ID of the previous HEAD. Note that in exotic cases,1 the post-checkout hook is run on the initial git clone, so $1 can be the null-ref. (I’m now curious as to what it is when you use git checkout --orphan and then don’t create the new branch, as well. It seems likely that $1 will be the null-ref here too.)


    1The only way to get a post-checkout hook to run on git clone is to have git clone install the post-checkout hook. This is normally impossible, but can be done by pointing your Git to your own template directories that have actual hooks instead of just sample hooks.

    ORIG_HEAD should be replaced with HEAD@{1} as noted in this question ORIG_HEAD is an older, less reliable way to supposedly get the previous state of HEAD. In my case it was not being set.

    Git Baby is a git and github fan, let's start git clone.