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 <firstname.lastname@example.org> 1314178977 +0100 committer Alex Blewitt <email@example.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 <firstname.lastname@example.org> $TIMENOW +0100\n→ committer Alex Blewitt <email@example.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.