The issue is a difference in how Git commands behave in the environment created for hook scripts versus your normal environment.
First, hook scripts run with their current working directory set to the Git directory itself (i.e. the .git/
directory of a non-bare repository). Second, hook scripts run with the GIT_DIR environment variable set and pointing to the Git repository (again, the .git/
directory of a non-bare repository).
Normally, if you try to run git reset --hard
from the .git/
directory, it will die with the following message:
fatal: This operation must be run in a work tree
But when GIT_DIR is set, Git commands assume that the current directory is the working tree. Since the current directory when the hook runs is the .git/
directory, your git reset --hard
is actually “checking out” your working tree files directly into .git/
instead of its parent directory (i.e. you now have a copy of your versioned content in your .git/
directory).
Hopefully none of versioned content in your repository has pathnames that coincide with pathnames that Git uses in Git repositories themselves. If they do coincide, then your git reset --hard
will have overwritten some bit of the internal structure of your repository and you will probably want to re-clone it from some other repository. If you are confident that none of the versioned content conflicts with Git’s internal pathnames, then you may be able to clean it up with this:
# make a backup of your repository first!
(cd .git && GIT_DIR=$PWD git ls-files -cz | xargs -0 rm)
This will only remove currently tracked files (it will leave behind files that have since been removed, but were once tracked in tip commits pushed while the broken hook was active).
One solution is to change the current working directory to the normal working tree and unset GIT_DIR and GIT_WORK_TREE before calling Git commands.
⋮
test "${PWD%/.git}" != "$PWD" && cd ..
unset GIT_DIR GIT_WORK_TREE
# you can now safely use Git commands
⋮
Another solution is to explicitly reset GIT_DIR, set GIT_WORK_TREE and chdir there. The Git FAQ “Why won't I see changes in the remote repo after "git push"?” recommends a post-update script that does just this. The linked script is also much safer since it makes a stash if the index or working tree is dirty before doing the hard reset.