I have a git problem, I need to run the "git --no-pager diff --name-only {oldrev} {newrev}"
command in my pre-receive, but when oldrev is "000000000000000000000000000000000000000000" I have an bad object error, how to compare the difference between two commit, if the previous commit is "000000000000000000000000000000000000000000"?
-
d a t a l o s s – bahrep Jan 31 '18 at 14:15
-
2You don't have a previous commit 000000000000000000000000000000000000000000. If you want to ask about the situation where you don't have a previous commit at all, ask that. – Jan 31 '18 at 14:15
-
How did you get a commit with that hash? O.o – cmaster - reinstate monica Jan 31 '18 at 14:16
-
cmaster, create a local branch and push with --set-upstream. Git send oldrev in stdin to pre-receive hook: 00000000000000000000000000000000000000000 – ArthNRick Jan 31 '18 at 14:32
-
1hvd, git send "00000000..." in stdin, not me. – ArthNRick Jan 31 '18 at 14:36
-
@ArthNRick [The docs](https://git-scm.com/docs/githooks) say "When creating a new ref, `
` is 40 `0`." Like I said, you don't have a previous commit. – Jan 31 '18 at 14:38 -
In that case, you don't have any commit at all. Not even an empty one. Your diff is the current state of `{newrev}`. I believe, there is a well-known hash of the empty tree, but I can't find it right now. – cmaster - reinstate monica Jan 31 '18 at 14:39
-
@hvd, I understand what you said, "000 .." that means there is no commit, so I want to know how to find the difference in these cases. – ArthNRick Jan 31 '18 at 14:40
-
The difference between some current state and the empty state, is always the current state. – cmaster - reinstate monica Jan 31 '18 at 14:41
-
@cmaster Good idea. `4b825dc642cb6eb9a060e54bf8d69288fbee4904` is the one you're looking for. That should work for the OP as an answer, and if you post it as such I'll be happy to upvote it. – Jan 31 '18 at 14:43
-
but how do I compare? I can have 500 commits between state "000." and {newrev}, git diff {newrev} will return all modifications, regardless of how many commits there are? – ArthNRick Jan 31 '18 at 14:45
-
@hvd Done, and thanks for unearthing that hash :-) – cmaster - reinstate monica Jan 31 '18 at 15:20
2 Answers
git
reports the commit as 000[…]000
because it doesn't exist. That's the state of a branch/repository that's just been created with no existing history whatsoever. As such, git diff 000[…]000..master
gives you an error, since the "commit" that you want to compare against does not exist.
In some cases, it might be helpful to compare against the empty tree instead:
git diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904..master
will give you a diff that contains exactly the current state of master
. Here, the cryptic number 4b825dc642cb6eb9a060e54bf8d69288fbee4904
is just the SHA-1 of an empty tree, which is a truly global constant by definition (thanks to @hvd for unearthing that hash). git
knows about that empty tree out of the box, so you don't need to commit anything to git
for it to correctly resolve that hash.

- 38,891
- 9
- 62
- 106
-
it will not be a problem if the master has been modified since the branch was created? – ArthNRick Jan 31 '18 at 15:44
-
-
I say this because the last version of the master may be different from when the branch was created, comparing the last commit of the branch with the current master may not be exactly the version of the master that the branch was created, and we do not want to do any merge or something the type at the moment (sorry my bad English) – ArthNRick Jan 31 '18 at 16:03
cmaster's answer shows you how to diff against the empty tree, which may be what you want to do here. It's worth backing up a bit, though, and considering what the all-zeros hash in your pre-receive hook means. This same "null hash ID" will show up in update, post-receive, and post-update hooks. What you should do with these is up to you. There's no single correct answer for dealing with null-hash-IDs in hooks.
A Git reference—branch name, tag name, or any other name that can be sent through git fetch
or git push
—always identifies one (1) object (usually a commit object, except for tags which often point instead to an annotated tag object that then points to the commit object). Any additional commits that you might like to think of as being "on a branch", if the reference is a branch name, are more properly termed reachable from the branch name. (For much more on this, see Think Like (a) Git.) Another way to think about this is that commits are contained within branches: often, one particular commit is contained in many branches.
When your Git is the target of git push
, what the other Git—there is another Git that is the source of the git push
—has done is to call up your Git, typically over an SSH or HTTPS connection, and have a little conversation with it. They offer your Git some set of objects (commits, annotated tags, trees, and/or blobs) and your Git saves those away in a temporary location.1 Then they send your Git some requests (non-forced push updates) or commands (force-push updates) of the form: Please set your refs/heads/master
so that it points to commit 1234567...
and Please set your refs/tags/v1.2
so that it points to annotated tag fedcba9...
.
Your Git takes these requests-or-commands and invokes your pre-receive hook. Your Git, because this is your repository, knows whether the names in question—here, refs/heads/master
and refs/tags/v1.2
—already exist in your repository. If they do exist in your repository, they identify one particular Git object. The other Git is proposing to change them so that they identify some other Git object. So your Git gives your hook three pieces of information:
- the name;
- the object that the name currently identifies; and
- the object that the other Git is proposing (requesting or commanding) that your Git should identify instead.
(The other Git is also either using --force
or a +
, or not, but for no particularly good or bad reason, your Git doesn't tell your hook.)
But it's possible—for tags, likely—that the name their Git sent does not currently identify any object in your own repository. In this case, your Git gives your hook the all-zeros hash ID, on the assumption that you will know what to do with this.
This means that when you write any of these hooks, you must be prepared to accept an all-zeros hash and treat it as a request meaning: create this new name pointing to the specified object. That is, in fact, what it means.
If the new name is a tag name, well, we generally think of tags as pointing to one specific commit (perhaps through a series of annotated tag objects), and "create new tag" is what we generally expect: tags are not supposed to "move"; the tag name v1.2
should not identify commit 1234567...
today and commit 89abcde...
tomorrow. (Git automatically enforces this, since Git 1.8.4 anyway, by rejecting the tag update even before running your hook, unless the other Git sets the force flag.)
For branch names, however, create new branch name at commit Y is an expected operation, as is move existing branch name from commit X to commit Y. If the motion, from hash X to hash Y, only adds new commits to the set contained in the branch, that's a normal (or fast forward) operation. If it removes some commits, it must by definition be a force-push since Git would have rejected it earlier otherwise.
(For names that are neither branch names nor tag names, Git has no built-in rules about forced vs non-forced updates—unless perhaps it uses the branch rules for refs/notes/
name-space updates; I have not checked this for this answer.)
Note that there's one more kind of reference update, namely the delete request-or-command. In this case, the other Git asks or commands your Git to delete some reference. Your hook will receive the name of the reference, its current object, and an all-zeros hash ID as the new ID to indicate that the other Git is requesting-or-commanding a delete operation.
In a pre-receive or update hook, you may allow the operation (by exiting with a zero status) or forbid it (by exiting with a nonzero status). A pre-receive hook gets, on its standard input, the entire proposal—all the creations, deletions, and updates—in one fell swoop. An update hook gets, as arguments, one proposed change at a time. Hence the pre-receive hook either permits all updates (which can then be selectively rejected, one at a time, in an update hook) or forbids all of them (making whoever initiated the git push
try again, perhaps, with a smaller set of updates).
1In modern Git, the incoming objects are stored in a "quarantine area" and then migrated into the regular repository database only if the request is accepted. In older Gits the incoming objects go right into the repository database immediately; Git uses git gc
to discard them later if necessary.

- 448,244
- 59
- 642
- 775