Reputation: 4032
I'm using a git
pre-commit
hook to automatically format all of my staged files. I'm determining the staged files via git diff --name-only --cached
and then call a script on those files.
This all works well in the standard use case, however it doesn't work when I commit via
git commit -a ..
because the files are not yet staged.
Is there some way to:
Run the -a
effect (add files to staging area) before the pre-commit
hook?
Find out that the pre-commit runs in a -a
commit?
There must be a way to handle this.
Upvotes: 6
Views: 7495
Reputation: 488183
because the files are not yet staged
This actually isn't quite true. But it's not quite false either.
Here is a (silly, meant for illustration only) pre-commit hook to demonstrate the problem:
$ cat .git/hooks/pre-commit
#! /bin/sh
echo \$GIT_INDEX_FILE = $GIT_INDEX_FILE
git diff-index --cached --name-only HEAD
exit 1
This hook uses the correct (from a reliability perspective anyway) command, git diff-index --cached HEAD
, to find the names of staged files. First, though, it prints the name of the index that is being used to commit the files. (Last, it prevents the commit, since I don't really want to commit any of this.)
I made this executable (in a Git repository for Git itself), and modified a few files without git add
ing them:
$ git status --short
M Makefile
M wt-status.c
(note that the M
s are in the second column). Then:
$ git commit
$GIT_INDEX_FILE = .git/index
$ git commit -a
$GIT_INDEX_FILE = [redacted]/.git/index.lock
Makefile
wt-status.c
The first hook invocation's echo
tells us that we're using the real (main) index, and its git diff-index
produces no output.
The second invocation tells us that we're using an alternate index file, named .git/index.lock
(I trimmed away my source path). It shows two staged files.
Let's go on to do one more thing: I'll git add
the modified Makefile
and make a second change to Makefile
. Now we have:
$ git status --short
MM Makefile
M wt-status.c
The first line shows us that HEAD:Makefile
(in the commit, frozen) differs from :Makefile
(in the index, staged) which differs from Makefile
(in the work-tree, unstaged), and indeed, we can see the three files are different:
$ git show HEAD:Makefile | head -2
# The default target of this Makefile is...
all::
$ git show :Makefile | head -2
#
# The default target of this Makefile is...
$ head -2 Makefile
# different
# The default target of this Makefile is...
Running git commit
vs git commit -a
now produces:
$ git commit
$GIT_INDEX_FILE = .git/index
Makefile
$ git commit -a
$GIT_INDEX_FILE = [redacted]/.git/index.lock
Makefile
wt-status.c
If I had not prevented the non--a
version of git commit
, what would have been committed by git commit
is the version of Makefile
in the (main / real / .git/index
) index, not the version in the work-tree. Hence, if you want to inspect files that would be committed, you should look in the index. You can use git checkout-index
to extract files from the index, but be careful not to clobber work-tree versions, which may differ.
What would have been committed by git commit -a
is the version of the Makefile in the work-tree, which Git already added to the (non-standard, temporary) .git/index.lock
index. Once the git commit -a
finished, that non-standard temporary index would become the real index, destroying my intermediate, special staged copy of Makefile
. Again, to inspect files that would be committed, look in the index—using the redirected index, as Git will automatically, for both git diff-index
and git checkout-index
.
(Since I don't know quite what your script needs, I can't make particular recommendations for exactly how to use git checkout-index
to extract the files of interest. Consider using --work-tree=
and a temporary directory, though.)
(See also my answer to Skip past (full) staging area and commit file or patch directly?, which discusses just what -a
, --only
, and --include
really do internally.)
Upvotes: 12