Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Git Tip of the Week: Commits

2011, git, gtotw

This week's Git Tip of the Week is about git commit storage. You can subscribe to the feed if you want to receive new instalments automatically.


Last week we looked at the way trees are stored in Git (and the week before how objects are stored in Git). We're now going to see how those are hooked up to commits, which are the basis of branches, tags and the like. Here's an example commit:


(master) $ git cat-file -p HEAD
tree 2b61e34a91ca9780ea2f943e72f1a4a022cdd206
parent f44c95384463187acd83ff418ddd9c48659db8dd
author Alex Blewitt <alex.blewitt@gmail.com> 1314178977 +0100
committer Alex Blewitt <alex.blewitt@gmail.com> 1314178977 +0100

Another empty
(master) $ git rev-parse HEAD
ca5fc4f022595972639331adcab40d810b9882a0

It's not going to come as a surprise that a commit is a hashed object, stored in exactly the same mechanisms as blobs and trees are. A commit is a hash of the commit message, with an identifying type and length (as for blobs and trees). In this case, the commit message is 236 bytes long, so we write out commit 236\0 followed by the content, and show the hash:


(master) $ (echo -en "commit 236\0"; git cat-file -p HEAD) | shasum
ca5fc4f022595972639331adcab40d810b9882a0  -
(master) $ # Or, we can use this to find the size automatically:
(master) $ (echo -en "commit $((`git cat-file -p HEAD | wc -c`))\0"; →
 git cat-file -p HEAD) | shasum
ca5fc4f022595972639331adcab40d810b9882a0  -

So, given this knowledge, we can create a new commit all of our own. All we need to do is to refer to a tree (such as d2d6bbd1c25c154fcbb045d66e8a6f9b83587a68 from last time), refer to the HEAD as the parent, and add in some timestamp information.


(master) $ # TIMENOW=`date +%s`
(master) $ TIMENOW=1314385772
(master) $ echo -en "tree d2d6bbd1c25c154fcbb045d66e8a6f9b83587a68\n→
parent ca5fc4f022595972639331adcab40d810b9882a0\n→
author Alex Blewitt <alex.blewitt@gmail.com> $TIMENOW +0100\n→
committer Alex Blewitt <alex.blewitt@gmail.com> $TIMENOW +0100\n→
\n→
Manually generated commit" | git hash-object -w --stdin -t commit
195751d8f0822325eb3f234de9c0e720ae53d8ff

We've created our first (manually generated) commit, and it points to the tree from last time. Since all is now well, we should be able to check this commit out:


(master) $ git checkout 195751d8f0822325eb3f234de9c0e720ae53d8ff
Note: checking out '195751d8f0822325eb3f234de9c0e720ae53d8ff'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 195751d... Manually generated commit
((195751d...)) $ ls
anotherEmpty	empty		void
((195751d...)) apple[bar] $ git diff HEAD^
diff --git a/void b/void
new file mode 100644
index 0000000..e69de29

That represents the committed tree which we wrote last time. We can even do diffs between the previous version to find out that the new file is indeed the void that we added previously.

Now we've got the ability to create our own commits, we can take a deeper look into Git's storage structure next time.


Come back next week for another instalment in the Git Tip of the Week series.