TL;DR: you probably want unset GIT_DIR
at the front of your script (well, however Haskell spells this operation).
The issue here is that while /tmp/A
is a sensible Git repository, the actual repository itself is in the .git
sub-directory. That is, /tmp/A/
is the work-tree and /tmp/A/.git
is the repository proper.
In normal operation, we run git subcommand arguments ...
, e.g., git status
or git commit -m message
from our work-trees. The work-tree contains a .git
directory or, in some cases,1 a .git
file. The top-level git
command checks for .git
, finds it, and says aha, the repository is ./.git
. Or, it does not find .git
, so it looks one level up—e.g., from /tmp/A
it would climb one level to /tmp
itself, and check there for .git
. This repeats until some sort of stopping point.2
In all cases, this search process must either stop successfully—by finding the repository proper—or else git subcommand
dies with the fatal: Not a git repository ...
message. Now comes the critical, and hidden in plain sight, secret: At this point, the top-level git
command sets an environment variable named GIT_DIR
to contain the path name of the actual repository. It then runs the sub-command, which uses $GIT_DIR
to locate the repository. In our normal use-case, where we are in /tmp/A
and /tmp/A
contains a .git
directory, this sets $GIT_DIR
to /tmp/A/.git
and the sub-commands all work.
But in your case, $GIT_DIR
is already set. Specifially, it's set to .
. So now the top-level git
command stops searching. It just verifies that $GIT_DIR
is valid and names a Git repository, or dies with the fatal
error message. Since /tmp/A
isn't a repository—it's really /tmp/A/.git
—this causes the problem.
Unsetting the environment variable turns the search back on, fixing the problem. You can also use --git-dir
as an argument, or set a correct value in $GIT_DIR
. Note that the same rules apply with $GIT_WORK_TREE
: the --work-tree
argument sets $GIT_WORK_TREE
, and if you don't use that and $GIT_WORK_TREE
isn't set, the front-end git
command uses its climb up the file system tree to a .git
directory or file code to find the root of your work-tree. So if $GIT_DIR
or $GIT_WORK_TREE
are set when you run git subcommand
, Git obeys them unless you override them with --git-dir
and/or --work-tree
.
Hooks always have $GIT_DIR
set. They may or may not have $GIT_WORK_TREE
set as well. Most of them run in the top level of the work-tree, but, as the githooks documentation notes, pre-receive
, update
, post-receive
, post-update
, and push-to-checkout
all run in $GIT_DIR
.
1The .git
-as-a-file trick is used by both submodules and added work-trees, in modern Git. Git 1.7 and early 1.8 left submodules with embedded .git
directories, and does not support added work-trees.
2The obvious stopping point is upon reaching /
, when there is nowhere left to climb. However, Git is by default careful, at least on Unix and Unix-like systems, to avoid climbing through a mount point. Mount points allow you to graft different file system layers and/or storage pools into a single tree-structured hierarchy. Typically /tmp
itself might be a memory file system, for instance, and large systems might isolate "system storage" (/
and the like) from "user storage" (/home
). Docker uses mount points to restructure file systems in the docker image, and so on.