Git’s empty tree

It’s late. You’ve been coding up a greenfield project and it needs to be done by tomorrow. Yes, the team could have gone with a similar tool that has some of the necessary features, but damn, that thing written in PHP! This is your chance to write a totally new project and to show the company that python/ruby/go is the future. I mean, PHP, really? No, didn’t think so.

Ok, ready for reviewboard.
diff --full-index --oh-crap-you-forgot-to-make-an-initial-commit

Oops. This is dumb. You can’t believe you forgot to do an initial commit, and your first commit was only after 2 hours of work- totally useless to your coworkers… Damn it, why didn’t you create an alias for git init?

Here’s my stream-of-consciousness from solving this one:

Hmm- if only there was a way to diff against a totally empty commit, a magic empty git repository…

Well, how about we check out the first commit in the internals of git:

> cat .git/logs/refs/heads/master
0000000000000000000000000000000000000000 a7726d5201b0e56bf6e15e9ed72ea42192013d09 Colin P. Schimmelfing <theboss@colinschimmelfing.com> 1369722239 -0700 commit (initial): adds money-printing functionality to our app. biz-dev should be happy

those zeros look good as some sort of magic original empty commit… lets try that:

> git diff 0000000000000000000000000000000000000000
fatal: bad object 0000000000000000000000000000000000000000

Nope, no go.

What if we init a new repo and see what a blank repo is like:
> git init test2
Initialized empty Git repository in /Users/cschimmelfing/code/blog/empty_git/test2/.git/
> cd test2
> git show
fatal: bad default revision 'HEAD'
> git log
fatal: bad default revision 'HEAD'
> cat .git/logs/refs/heads/master
cat: .git/logs/refs/heads/master: No such file or directory

Damn, no commits means normal ways I’d look at the repo are pretty useless. Looking elsewhere in the .git directory gives just as little insight.

At this point, you hit up stackoverflow, and when I ran into this problem I was able to find a few items mentioning the magic commit I was looking for. (From this thread or this Stack Overflow post)

drum rolllllllll:

4b825dc642cb6eb9a060e54bf8d69288fbee4904

Huh. Well that’s random.

> git show 4b825dc642cb6eb9a060e54bf8d69288fbee4904
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
(END)

Well, let’s try it out:
diff --git a/test1.txt b/test1.txt
new file mode 100644
index 0000000..95f29d0
--- /dev/null
+++ b/test1.txt
@@ -0,0 +1 @@
+hi HN
(END)

So now you are thinking: “Colin, that looks good, but where does this magic hash come from?”

Well, we can see that it’s a tree, so let’s try:

> git init test2
Initialized empty Git repository in /tmp/test3/.git/
> cd test3
> git write-tree
4b825dc642cb6eb9a060e54bf8d69288fbee4904

Aha! there it is, the hash is simply the value git creates when you ask for the hash of an empty directory. For more on this, check out this breakdown on the internals of git .

In both of the links that mentioned the special hash, it looks like there is another way to find the magic value, a little faster:

> git hash-object -t tree --stdin < /dev/null
4b825dc642cb6eb9a060e54bf8d69288fbee4904

So there we go! A little window into the internals of git, and a useful trick. If you don’t use review board (or always remember to touch an empty README, etc), you may also find it useful in other contexts, for instance creating a patch that can recreate the whole repo. Yes, you could tar the whole repo up, but maybe there are some embarrassing commits you’d like your colleague not to see, or a FUBAR-ed history that you might want to totally nuke before starting to collaborate.

Of course, this is git, so there are probably three other ways to do the same thing. Please comment, internet points will be awarded to the best answer!

Leave a Reply

Your email address will not be published.