Search code examples
node.jsgithuskygit-husky

Why does `git` commands fail when run as part of a git hook in a subfolder?


We recently moved a Node project into a subfolder of a mono-repo, and after that our commit hooks (installed using Husky) started failing due to being executed in the wrong directory:

{ Error: Command failed: git rev-parse HEAD
fatal: not a git repository: '.git'

    at ChildProcess.exithandler (child_process.js:297:12)
    at ChildProcess.emit (events.js:197:13)
    at maybeClose (internal/child_process.js:978:16)
    at Socket.stream.socket.on (internal/child_process.js:395:11)
    at Socket.emit (events.js:197:13)
    at Pipe._handle.close (net.js:611:12)
  killed: false,
  code: 128,
  signal: null,
  cmd: 'git rev-parse HEAD' }

I didn't have problems fixing the issue (basically cd-ing to the root directory before executing any git commands in the hooks), but what I don't get is why does the (unfixed) scripts work on the command line - regardless of which folder I execute them from?.

The failing parts of the script is basically:

console.log('PWD', cp.execSync('pwd'));
console.log(cp.execSync('git rev-parse HEAD'));

Project layout

.git/
frontend/
  package.json
backend/

Example command that will trigger the error above:

git checkout master

Apart from the error, it prints the working directory: PWD /tmp/foo/myproj/frontend

If I manually trigger the script, everything works fine:

npm run postcheckout
PWD /tmp/foo/myproj/frontend

82dc6d2089a397f0889addb562ea84ba8d846215

This works no matter if the root folder or a sub-folder, and I cannot really see the difference. Husky is supposed to find the .git folder, and this seems to prove it, but obviously something changes when it is run as a hook script. The relevant parts of the auto-generated Husky script is:

cd "frontend"
...
...
npm run postcheckout

Solution

  • TL;DR: unset $GIT_DIR (in sh script, unset GIT_DIR).

    There is a hint in the githooks documentation:

    Before Git invokes a hook, it changes its working directory to either $GIT_DIR in a bare repository or the root of the working tree in a non-bare repository. An exception are hooks triggered during a push (pre-receive, update, post-receive, post-update, push-to-checkout) which are always executed in $GIT_DIR.

    plus another few in the top level Git manual page:

    --git-dir=<path>
    Set the path to the repository. This can also be controlled by setting the GIT_DIR environment variable. It can be an absolute path or relative path to current working directory.

    GIT_DIR
    If the GIT_DIR environment variable is set then it specifies a path to use instead of the default .git for the base of the repository. The --git-dir command-line option also sets this value.

    What happens here is that the top level Git program sets $GIT_DIR to the path by which the .git directory is to be found. Since the hook has changed its working directory to the work-tree, the Git program has set $GIT_DIR to .git. If you chdir elsewhere, you must adjust $GIT_DIR appropriately—and if you chdir completely away from the main repository and wish to have Git discover $GIT_DIR again, you must unset $GIT_DIR.