`git diff` does not work when run from a git pre-commit hook
I have a git pre-commit hook that does some style checking on any modified files before committing.
The implementation is irrelevant, but it starts by calling
git diff. Here's what i have in
#!/bin/sh echo "=== Running script..." git diff echo "=== Done running script..." # Other stuf # .... # Always exit with 1 so pre-commit hook always fails. # Useful for testing exit 1
When I actually try committing something, the
pre-commit hook correctly fires, but the
git diff command doesn't output anything (there are definitely modified files)
> git commit --all -m "foo" === Running script... === Done running script...
However if I run the
pre-commit hook script directly/manually, it does work
> ./.git/hooks/pre-commit === Running script... (... outputs git diff ...) === Done running script...
What's different about git calling the hook versus me manually calling it? It runs as the same user either way (my username)
I've also tried suggestions from this thread, but
work-tree= didn't fix anything.
You need to use
git diff --cachedbecause the changes are already staged.
As ishegg said, you will need
git diff --cachedhere, but that's not necessarily the whole story.
There are two traps to be wary of here. The first is what
git diffdoes: it compares two trees (or tree-like things). The second has to do with the very phrase the index.
The index, and selecting trees for
Quite often, the two trees you diff are the trees associated with two specific commits:
git diff <hash1> <hash2>
(or the same with
<hash1>..<hash2>, which—unlike most Git commands—doesn't treat the two hashes the way the two-dot
..operation is described in the SPECIFYING RANGES section of the gitrevisions documentation).
It's also pretty common to have one of the two trees be your work-tree, which is what happens when you run:
git diff HEAD
for instance: this compares the commit named by
HEAD—i.e., the current branch-tip commit—to the current work-tree.
git diff --cached
tells Git to compare the
HEADcommit to the tree represented by your index. Git's index, also called the staging area or the cache, is where Git builds up the next commit to make. It's why you must run
git addbefore committing: the
git addcommand copies files from the work-tree to the index.
with no arguments at all chooses the index as the first tree, and the work-tree as the second tree. Once everything has been copied from the work-tree into the index, this particular diff will be empty, which is what you are seeing here.
The index vs an index
The second trap lies in the fact that you are using
git commit --all.
While Git talks about the index, and there is in fact one particular, distinguished index to go with each work-tree, Git actually allows for more than one index, using exactly one at a time.
When you use
git commit --allor pass file names to
git commit, Git makes a temporary index that it uses during the pre-committing process. To indicate that there is a temporary index in play, Git sets the environment variable
GIT_INDEX_FILE. This temporary index gets used instead of the ordinary index, up until either the commit is allowed and committed, or until the commit is rejected.
If the commit is rejected, Git just removes the temporary index, and everything goes back to the way it was before. If things were not
git add-ed, they are still not
If the commit is accepted, the temporary index becomes the "real" or "main" index, more or less. Here things can get complicated, because you can stage some items in the real index, run
git commit --allor
git commit --include <paths>or
git commit --only <paths>, and if the commit succeeds, the difference between the initial (main or real) index and the temporary index matters.
If you plan to do special tricks with files in the work-tree and/or their copies in the index, you may want to simply reject any attempt to work with a temporary index, so as to avoid these complications. However, that will preclude the use of