Reputation: 3906
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.
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):
Upvotes: 4
Views: 455
Reputation: 1323973
Torek mentions:
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.
That is now (Q4 2020, 3 years later) officially documented with With Git 2.29:
See commit 3100fd5 (27 Aug 2020) by Junio C Hamano (gitster
).
(Merged by Junio C Hamano -- gitster
-- in commit 2f1757e, 03 Sep 2020)
doc
: clarify how exit status ofpost-checkout
hook is used
Because the hook runs after the main checkout operation finishes, it cannot affect what branch will be the current branch, what paths are updated in the working tree, etc., which was described as "cannot affect the outcome of 'checkout'".
However, the
exit
status of the hook is used as theexit
status of the 'checkout
' command and is observable by anybody who spawned the 'checkout
', which was missing from the documentation.
Fix this.
githooks
now includes in its man page:
This hook cannot affect the outcome of
git switch
orgit checkout
, other than that the hook's exit status becomes the exit status of these two commands.
Upvotes: 1
Reputation: 488103
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
.)
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.
Upvotes: 1