Fundamentals 14 min read

Unveiling Git’s Hidden Mechanics: Inside the .git Directory and Object Model

This article walks readers through Git’s internal architecture, explaining the .git directory layout, object storage, staging area, and how low‑level commands like git hash-object, write‑tree, and commit‑tree build commits and branches.

Goodme Frontend Team
Goodme Frontend Team
Goodme Frontend Team
Unveiling Git’s Hidden Mechanics: Inside the .git Directory and Object Model

This article targets readers with basic Git usage knowledge and explains Git’s internal mechanisms by introducing low‑level commands.

Basic Knowledge

Git Directory Structure

Running git init creates a .git folder that stores all repository data. Its key sub‑directories are:

info : metadata such as the exclude file (similar to .gitignore).

config : repository‑specific configuration like user name, email, and remote URLs.

objects : the core object database that holds all Git objects.

refs : pointers to commit objects.

HEAD : the currently checked‑out branch.

Other entries like description and hooks exist but are omitted here.

Storage Method

Git’s core is a key‑value store: any content can be inserted to obtain a unique hash, which is then used to retrieve the content. The data is stored in the objects directory. Adding a file with git add demonstrates this.

$ git init test
$ cd test
$ ls .git/objects
info pack
$ echo "hello world" >> a.txt && git add .
$ ls -R .git/objects
3b info pack
.git/objects/3b:
18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/info:
.git/objects/pack:

The new object appears in a folder named after the first two hash characters (e.g., 3b), with the remaining 38 characters as the filename. Its content is the compressed binary representation of the added file.

Git provides the cat-file command to read an object by its hash, e.g., git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad prints hello world .

git add

The previous git add example stored the file in the object database. Below we explore the low‑level commands that underpin git add.

Saving Content

After initializing a new repository, git hash-object inserts data into the object database. Adding the -w flag writes the object to disk.

$ git init dismantle
$ cd dismantle
$ echo "hello world" >> a.txt && git hash-object -w ./a.txt
$ ls -R .git/objects
3b info pack
.git/objects/3b:
18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/info:
.git/objects/pack:
The hash-object command without -w only returns the hash; -w also stores the object.

Staging Area

Both repositories have objects, but only the first one has the file staged. git status shows the staged state in test and the untracked state in dismantle. Using git update-index --add --cacheinfo 100644 <hash> a.txt adds the file to the index.

$ cd test && git status -s
A  a.txt
$ cd dismantle && git status -s
?? a.txt
$ git update-index --add --cacheinfo 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad a.txt
$ git status -s
A  a.txt
For a deeper understanding of the staging area and commit process, refer to the official Git documentation.

git commit

Stored Objects

Git has four object types: blob (data), tree, commit, and tag (the article covers the first three). After a git commit, new objects appear in .git/objects.

$ git commit -m 'feat: 2.5'
$ ls -R .git/objects
3b eb f4 info pack
.git/objects/3b:
18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/eb:
aa691b5554f29ac9d4f37811a1da6f24d376a1
.git/objects/f4:
100ba8b3119f593a2b89c7284cf66d4be739b3
.git/objects/info:
.git/objects/pack:

The add or hash-object commands create blob objects that store file contents. Tree objects map filenames to blobs (or sub‑trees), enabling reconstruction of a project snapshot. Commit objects link a tree to parent commits, author information, and a message.

$ git cat-file -p ebaa691b5554f29ac9d4f37811a1da6f24d376a1
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad a.txt
$ git cat-file -p f4100ba8b3119f593a2b89c7284cf66d4be739b3
tree ebaa691b5554f29ac9d4f37811a1da6f24d376a1
author gugu <[email protected]> 1697353561 +0800
committer gugu <[email protected]> 1697353561 +0800
feat: 2.5
The cat-file -t option shows an object’s type.

Generating Tree and Commit Objects

Low‑level commands can create a tree with git write-tree and a commit with git commit-tree.

$ cd dismantle && git write-tree
ebaa691b5554f29ac9d4f37811a1da6f24d376a1
$ git cat-file -p ebaa691b5554f29ac9d4f37811a1da6f24d376a1
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad a.txt
$ git commit-tree ebaa691b -m 'feat: 2.5'
f1ded58d3f850515daa3636efce0598bbe9a1180
$ git cat-file -p f1ded58d3f850515daa3636efce0598bbe9a1180
tree ebaa691b5554f29ac9d4f37811a1da6f24d376a1
author gugu <[email protected]> 1697354831 +0800
committer gugu <[email protected]> 1697354831 +0800

feat: 2.5

The commit hash differs because of timestamps, but the tree and blob parts are identical.

Branches

Branches are references stored under refs/heads. Updating a branch simply writes a new commit hash to the corresponding file.

$ git update-ref refs/heads/master f1ded58d3f850515daa3636efce0598bbe9a1180
$ ls .git/refs/heads
master
$ cat .git/refs/heads/master
f1ded58d3f850515daa3636efce0598bbe9a1180

At this point a complete commit cycle is finished.

Supplement

Object File Generation Rules

Git forms an object’s header as <type> <size>\0, concatenates it with the content, computes a SHA‑1 checksum, and stores the compressed result (via zlib) under .git/objects/xx/yyyy…. The following Node.js snippet reproduces this process.

const { deflateSync } = require('zlib');
const crypto = require('crypto');
const fs = require('fs');
const addFile = (content) => {
  const headers = `blob ${content.length}\0`;
  const shasum = crypto.createHash('sha1');
  shasum.update(headers + content);
  const hash = shasum.digest('hex');
  console.log('hash: %s', hash);
  const deflatedContent = deflateSync(headers + content);
  const dirname = hash.slice(0, 2);
  const fileName = hash.slice(2);
  if (!fs.existsSync(`.git/objects/${dirname}`)) {
    fs.mkdirSync(`.git/objects/${dirname}`, { recursive: true });
  }
  fs.writeFileSync(`.git/objects/${dirname}/${fileName}`, deflatedContent, { encoding: 'hex' });
};

addFile("this is a demo");
$ echo -n "this is a demo" | git hash-object --stdin
e0501eec17daa40898f8340ca52af1949852025e
git cat-file -p e0501eec17daa40898f8340ca52af1949852025e
this is a demo

Summary

Below is a diagram from the Git book that visualizes the entire object database structure.

GitVersion Controlcommitobject modelgit internals
Goodme Frontend Team
Written by

Goodme Frontend Team

Regularly sharing the team's insights and expertise in the frontend field

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.