/til/

2024 0827 post-commit hook GIT_DIR

For secret reasons, instead of using a Git forge like Github for one of my projects, I am using a regular Linux virtual server running in a major hosting provider. I push to a bare Git repository1, which has a post-commit hook2 that checks out the commit I just pushed, builds the project and runs a few sanity checks, and then deploys the result.

This was the first time I had set such a system up from scratch myself, and I fell into a common pitfall: GIT_DIR.

Here’s the punchline: When checking out a repository from a Git hook, make sure to unset Git environment variables like GIT_DIR, or the actions will fail with strange errors.

What that looks like in practice is pushing to the Git repository and seeing an error like this:

remote: fatal: Not a git repository: '.'

The Git documentation actually calls this out, but I didn’t see it until I ran into problems.

Environment variables, such as GIT_DIR, GIT_WORK_TREE, etc., are exported so that Git commands run by the hook can correctly locate the repository. If your hook needs to invoke Git commands in a foreign repository or in a different working tree of the same repository, then it should clear these environment variables so they do not interfere with Git operations at the foreign location.
The githooks manual

What’s happening is that the post-commit hook sets variables like GIT_DIR, which has special value to subsequent git commands. GIT_DIR in particular is set to ., which is where the error message Not a git repository: '.' comes from.

You can use unset $(git rev-parse --local-env-vars) in a hook to unset all such variables.

Implementation details

My build script was doing something like this (heavily paraphrased):

#!/bin/sh
set -eux
# $newrev is the revision that was just pushed
# $PWD is the bare git repo path
local_git_url="file://$PWD"
while read oldrev newrev refname; do
    # We can't clone a repo and check out a specific commit in a single command.
    git clone --no-checkout "$local_git_url" /tmp/checkout
    cd /tmp/checkout
    git checkout "$newrev"
done

The git clone would succeed – I guess a bad value for GIT_DIR doesn’t break that command – but git checkout would fail.

Of course, running these commands in an interactive SSH session worked just fine, because GIT_DIR hadn’t been set. This had me tearing my hair out for a bit until I found the exact error message on StackOverflow. After that, I discovered that the githooks manual addresses this problem as quoted above, notes that there are other variables aside from GIT_DIR that may cause similar problems, and includes the unset $(git rev-parse --local-env-vars) example to handle all of them. (I added an answer to the SO post that covers this.)


  1. As described in Pro Git chapter 4.2, a bare repository is a repository that doesn’t contain a working directory. You can’t interact with the files in the repo directly. In the past, pushing to non-bare repositories was actively discouraged; now, Git makes allowances for doing that in some use cases. Regardless, a bare repository is the best plan for my specific use case. ↩︎

  2. Git hooks are scripts that run automatically at certain stages, like on the client before pushing a commit, or on a server after receiving a commit. See the githooks manual for official documentation, and https://githooks.com/ for unofficial but very nice documentation. Developers may be familiar with the pre-commit project, which is a package manager for client-side hooks. ↩︎

Responses

Webmentions

Hosted on remote sites, and collected here via Webmention.io (thanks!).

Comments

Comments are hosted on this site and powered by Remark42 (thanks!).