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 asGIT_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.
githooks
manualWhat’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.)
-
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. ↩︎
-
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 thepre-commit
project, which is a package manager for client-side hooks. ↩︎