Chapter 9-6 — .git Directory Deep Structure | DevInHyderabad☕ ☕ 20 min read

.git Directory Deep Structure

Andar ka structure samjho, toh Git ka engine samajh aa gaya.

01The .git Directory IS the Repository

The most important thing to understand: .git/ IS the repository. Your working directory files are just a checkout.

If you copy .git/ to another folder and run git checkout, you get all your files back with full history.

If you delete .git/, your folder becomes a normal directory with no Git history. That is it. Game over.

git init does exactly one thing: creates the .git/ directory with a basic structure. No files are tracked until you git add and git commit.

Understanding this directory structure is the key to understanding how Git works internally.

# What git init actually creates
mkdir demo && cd demo
git init

# The ENTIRE repository is in this one directory
ls -la .git/
# total 24
# drwxr-xr-x   9 user  group  288 Jan 15 10:00 .
# drwxr-xr-x   3 user  group   96 Jan 15 10:00 ..
# -rw-r--r--   1 user  group   23 Jan 15 10:00 HEAD
# -rw-r--r--   1 user  group  137 Jan 15 10:00 config
# drwxr-xr-x   2 user  group   64 Jan 15 10:00 hooks/
# drwxr-xr-x   3 user  group  128 Jan 15 10:00 objects/
# -rw-r--r--   1 user  group    0 Jan 15 10:00 objects/pack/.gitkeep
# drwxr-xr-x   2 user  group   64 Jan 15 10:00 info/
# drwxr-xr-x   4 user  group  128 Jan 15 10:00 refs/

# No commits yet — objects and refs are empty
# But the structure is ready to store everything
02HEAD: The You-Are-Here Pointer

.git/HEAD is a text file that tells Git where you are right now.

Normally it contains: ref: refs/heads/main — a symbolic reference to a branch.

In detached HEAD state, it contains a raw commit hash.

Git reads this file for EVERY operation to know which branch or commit you are on.

When you git checkout, Git updates this file. When you git commit, Git follows the reference in this file to update the branch.

# Normal HEAD — points to a branch
cat .git/HEAD
# ref: refs/heads/main

# When you switch branches:
git checkout feature
cat .git/HEAD
# ref: refs/heads/feature

# Detached HEAD — points to a commit
git checkout abc1234
cat .git/HEAD
# abc1234def5678... (raw hash)

# HEAD is the FIRST thing Git reads for any command
# git status, git commit, git log — all start by reading HEAD

# Fun fact: you can manually edit .git/HEAD
echo "ref: refs/heads/main" > .git/HEAD
# This is equivalent to: git checkout main

# But don't put an invalid value!
echo "invalid" > .git/HEAD
git status
# fatal: ambiguous argument 'HEAD'
HEAD is the single most important file in .git. It is the "you are here" marker. Every single Git command starts by reading .git/HEAD to know which branch or commit you are currently on. Understanding HEAD is understanding how Git knows where you are in the history graph.
03refs/: Where Branches and Tags Live

.git/refs/ contains branch and tag references — files that store commit hashes.

  • .git/refs/heads/ — local branches. One file per branch, containing the commit hash.
  • .git/refs/tags/ — tags. One file per lightweight tag, containing the commit hash.
  • .git/refs/remotes/ — remote-tracking branches. Organized by remote name (e.g., origin/main).

Creating a branch = writing a 41-byte file (40 hex chars + newline). Deleting a branch = deleting that file.

In large repositories, refs may be "packed" into .git/packed-refs for efficiency.

# Branch references
cat .git/refs/heads/main
# a1b2c3d4e5f6... (40-char SHA-1)

# Creating a branch = creating a file
git branch feature
cat .git/refs/heads/feature
# a1b2c3d4e5f6... (same hash as main — they start at the same point)

# After committing on feature:
git checkout feature
git commit --allow-empty -m "new work"
cat .git/refs/heads/feature
# d4e5f6g7h8i9... (different hash — branch moved forward)
cat .git/refs/heads/main
# a1b2c3d4e5f6... (main hasn't moved)

# Tag references
git tag v1.0
cat .git/refs/tags/v1.0
# a1b2c3d4e5f6... (lightweight tag = just a hash)

# Remote-tracking references
cat .git/refs/remotes/origin/main
# f6g7h8i9j0k1... (what origin/main points to)

# Packed refs (in large repos)
cat .git/packed-refs
# Multiple refs packed into one file for efficiency
04objects/: The Content-Addressable Store

.git/objects/ is where ALL Git objects live — blobs, trees, commits, and tags.

Objects are stored as files: .git/objects/XX/YYYYYY... where XX is the first 2 chars of the SHA-1 hash and YYYYYY... is the remaining 38 chars.

The content is compressed with zlib. You cannot read it directly with cat — use git cat-file.

Loose objects: stored as individual files. Packfiles: stored in .git/objects/pack/ for efficiency.

.git/objects/info/ — metadata about the object store.

Every git add creates blob objects. Every git commit creates tree and commit objects.

# Create a file and stage it
echo "hello" > file.txt
git add file.txt

# A new object appeared!
find .git/objects -type f
# .git/objects/ce/013625030ba8dba906f756967f9e9ca394464a

# Try to read it directly (compressed)
cat .git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
# (binary compressed data — not readable)

# Read it the Git way
git cat-file -p ce0136
# hello

# Commit creates more objects
git commit -m "first commit"
# Created commit object + tree object

# After many commits, objects get packed
git gc
ls .git/objects/pack/
# pack-abc123.pack  pack-abc123.idx
05index: The Staging Area Binary File

.git/index is a BINARY file that represents the staging area (the state of your next commit).

It is NOT a Git object — it is a separate data structure that maps file paths to blob object hashes.

git add updates the index. git commit reads the index to create the tree object.

git status compares: working directory vs index (unstaged changes) AND index vs HEAD commit (staged changes).

git diff compares working dir vs index. git diff --staged compares index vs HEAD.

The index is why Git has a two-step commit process: add (update index) then commit (snapshot index).

# The index is a binary file
file .git/index
# .git/index: Git index, version 2, 1 entry

# You can't cat it (it's binary)
cat .git/index
# (gibberish)

# Inspect the index
git ls-files -s
# 100644 ce013625030ba8dba906f756967f9e9ca394464a 0 file.txt
# mode    object hash                                     stage  filename

# What the index contains:
# - File mode (100644 = regular file)
# - Blob object hash (the content)
# - Stage number (0 = normal, 1-3 = during merge conflict)
# - Filename

# The index is rebuilt from a commit during checkout
git checkout main
# Git reads main's tree, builds the index, and checks out files

# Reset the index (unstage everything)
git reset
# Removes entries from the index, making it match HEAD again
💡 The .git/index file is the bridge between your working directory and the object store. When you git add, you are updating the index to point to new blob objects. When you git commit, Git reads the index to build the tree and commit objects. Understanding the index explains why git add is a separate step from git commit.

Lo kar liya — Key Points:

  • ✅ The .git directory IS the repository — your working directory is just a checked-out snapshot
  • ✅ .git/HEAD is a text file pointing to the current branch (or a commit hash in detached HEAD)
  • ✅ .git/refs/heads/ contains local branches — each is a file with a 40-character commit hash
  • ✅ .git/refs/tags/ contains tags, and .git/refs/remotes/ contains remote-tracking branches
  • ✅ .git/objects/ stores all Git objects (blobs, trees, commits) in compressed files keyed by SHA-1 hash
  • ✅ .git/index is a binary file representing the staging area — it bridges working directory and object store
  • ✅ Creating a branch is just writing a 41-byte file — this is why Git branching is so fast
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase