Skip to content

Git Object Model


What Will We Learn?

Practically with example, Git’s underlying architecture with four main Git Object Model — blobs, trees, commits and tags where tag is specific to projects. Some projects widely use it for releases and some don’t even need it.

Git Object Model

Git objects can be catagorized into four main types which all are identified by a unique SHA-1 hash stored inside .git/objects/. First let’s know some theory, an example is there to make things more clear.

Visually:

    flowchart TD
Commit
Tree
ParentCommit
Blob1
BlobN
AnotherTree
Commit -- points to --> Tree
Commit -- points to --> ParentCommit
Tree -- contains --> Blob1
Tree -- contains --> BlobN
Tree -- points to --> AnotherTree
  

Blob Objects

Blob objects are just naive contents of files. Naive in a sense that they just store contents identified by a unique SHA-1 hash and don’t know anything about which filename the content belongs to.

Tree Objects

Tree objects are what that makes blob objects useful because tree allows you to store a group of files together. You can think of tree objects as UNIX directory which can contain multiple trees and blobs. Tree makes it possible by pointing to specific blobs and even other trees which allows Git to keep track of entire directory structure.

Commit Objects

Trees have make Git’s life much easier by tracking entire directory hierarchy but. Ah, yes, there is a but. Git still has problems, Git problems means our problems:

  • Tree objects grow as number of directories in your project grows which means there will be tons of SHA-1 hashes Git may need to track instead of just one
  • If only tree exists, Git would have no way to know by whom, when and why the tree objects or snapshots were saved which makes our life even harder as a developer especially in collaborative development environment

We should consider ourselves lucky, Git has solved this for us by introducing commits. Commit object specifies top-level tree of the project and the parent commits alongwith the author information (user.name and user.email configuration). This way we and Git both have just one hash (commit hash) to remember and we can dig more from that hash if we want to. The example later will make this even more obvious.

Tag Objects

Tags are not always necessary but if you are working in a project that needs to be released like some application with version details. You can annote the version of the application with tags which then creates tag object. Generally, annotated tags (git tag -a) creates separate tag objects which points to the specific commit.

The visual diagram we defined here changes a bit when tags are introduced:

    flowchart TD
Tag
Commit
Tree
ParentCommit
Blob1
BlobN
AnotherTree
subgraph TagsIntroduced
Tag
end
Tag ---> Commit
subgraph General
Commit -- points to --> Tree
Commit -- points to --> ParentCommit
Tree -- contains --> Blob1
Tree -- contains --> BlobN
Tree -- contains --> AnotherTree
end
  

Example: Git Objects

Examining Git Objects

Fire up any terminal with git installed and run these commands:

mkdir git-object-model-test
cd git-object-model-test
git init
mkdir hello
echo HelloDear > hello/file
git add hello/file
git commit -m 'first commit'
git tag -a 'v1.0' -m 'first version tag'

It creates git-object-model-test directory for our example and creates all the Git objects.

As told earlier, Git stores objects at .git/objects/ directory. See them:

tree .git/objects

You should see the output similar to the following.

├───09
│       e897639cf7fa7e012dd54227e72ccf257de20b
├───6d
│       cf3d551a48a75b4750680be565e53f5fde93a8
├───b8
│       f70c63ab0b9b4c07b6e2c22a7a37003f6f245f
├───cc
│       1209f98bc270f3d7ba2618dd7c484d4858bd22
├───f0
│       44e7d28dc9b9c5988e1d43005e4a92e8dfd89e
├───info
└───pack

Hashes won’t match as they are time specific. And ignore info and pack, just focus on hashes.

SHA-1 is 40 character hash where first 2 character is named as directory and the remaining 38 as filename. So full hash is directory+file.

You can see the type of hash with the following command:

git cat-file -t <hash>

And to see the content of hash with the following command:

git cat-file -p <hash>

Examining the first hash, we get:

git cat-file -t 09e89
# blob

See what’s inside the blob:

git cat-file -p 09e89
# HelloDear

We see the content but we don’t know which filename it belongs to as this is just a blob object. Examining the second hash we get:

git cat-file -t 6dcf3d
# tree
git cat-file -p 6dcf3d
# 100644 blob 09e897639cf7fa7e012dd54227e72ccf257de20b    file

This verifies, the second hash is a tree that contains a blob named file.

Let’s see the third hash:

git cat-file -t b8f70
# commit

Okay this is a commit, and what’s inside this (git cat-file -p b8f70):

tree f044e7d28dc9b9c5988e1d43005e4a92e8dfd89e
author sonu-nigam <[email protected]> 1783759462 +0545
committer sonu-nigam <[email protected]> 1783759462 +0545

first commit

As we have discussed, commit keeps tree, parent commit and author info. But the tree hash is f044e7 which is yet for us to examine. The second hash 6dcf3d was a tree but it was not a top-level tree so it’s not stored inside the commit, instead f044e7, the last hash is stored.

Note

Since this is the only commit it doesn’t have pointer to parent commit yet.

So, let’s first examine this last hash (f044e7) before fourth one:

git cat-file -t f044e7
# tree
git cat-file -p f044e7
# 040000 tree 6dcf3d551a48a75b4750680be565e53f5fde93a8    hello

And yes, this is a tree which contains another tree object named hello. If you remember, this was the exact top-level directory we created for this example using mkdir hello.

Everything Makes Sense Now

Everything makes sense now. Because the commit hash b8f70 has top-level tree f044e7 which points to another tree 6dcf3d named hello which actually has the blob 09e89 named file and the blob has the content (HelloDear). And this is exactly what our diagram has shown above. Generally speaking, Git can retrieve any content from any file in this way.

Last updated on