Git, CI/CD & GitOpsII · Git ArchitectureArchitecture
The .git directory layout — what lives where under the repository
What you'll learn
- Map the .git directory layout and identify what each top-level entry stores
- Read objects/ and explain how a SHA is mapped to a disk path
- Distinguish loose refs from packed-refs and explain when Git uses each
- Identify the role of config, hooks, and the HEAD ref file
- Navigate the .git directory safely for forensics without corrupting the repository
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The .git directory is the repository. Everything Git knows about
your project — its history, its branches, its tags, its hooks, its
configuration — lives under this single directory. The working
tree is data; .git is the database. The first lesson of Git
Architecture was the three areas; this lesson is the on-disk
manifest of those areas, and the file you must be able to navigate
to debug a broken repository, recover a corrupted pack, or write
a script that acts on the repository without going through the
porcelain.
The layout at a glance
flowchart TB
subgraph dotgit[".git/"]
OBJ["objects/\nloose objects and packs"]
REFS["refs/\nheads, tags, remotes"]
HEAD["HEAD\n(current ref)"]
CFG["config\n(repo settings)"]
HOOKS["hooks/\n(workflow glue)"]
IDX["index\n(the staging file)"]
LOG["logs/\n(reflog)"]
PACKED["packed-refs\n(compressed refs file)"]
INFO["info/\n(exclude, grafts)"]
DESC["description\n(bare repo label)"]
end
OBJ --> OBJ1["objects/XX/YYYYYY...\nloose objects"]
OBJ --> OBJ2["objects/pack/*.pack\nobjects/pack/*.idx\nobjects/pack/*.rev"]
REFS --> REFS1["refs/heads/main\nrefs/tags/v3.0.0\nrefs/remotes/origin/main"]
HEAD --> HEADN["(contents: ref: refs/heads/main)"]
The entries that matter for an infrastructure engineer:
- objects/ — the content-addressed store.
- refs/ — the named pointers (branches, tags, remotes).
- HEAD — the current ref.
- config — the repository configuration.
- hooks/ — the workflow glue.
- index — the staging file.
- logs/ — the reflog.
- packed-refs — a compact form of refs/ for large repositories.
objects/ — the content-addressed store
Every blob, tree, commit, and tag is stored in objects/. The
directory layout is a function of the SHA-1 of the object: the
first two hex characters of the OID are the directory name, and
the remaining 38 characters are the file name:
ls .git/objects/05/
# 4a3b9c1d2e3f4g5h6i7j8k9l0m1n2o3p4q5r6s7t
Each file is a zlib-compressed object body. The format is
<type> <size>\0<contents> compressed with zlib. Reading a loose
object by OID is straightforward:
git cat-file -p 05a3b9c1d2e3f4g5h6i7j8k9l0m1n2o3p4q5r6s7t
For large repositories, objects are packed into .pack files
under objects/pack/. The .idx file is the index into the pack,
and the .rev file is the reverse index. Pack files are an
optimisation: many small loose objects are compressed into a
single large pack, with a delta encoding that stores only the
differences between similar objects.
ls .git/objects/pack/
# pack-abc123.pack
# pack-abc123.idx
# pack-abc123.rev
refs/ — the named pointers
A ref is a file whose contents are the OID of a commit (or a tag, which in turn OIDs a commit). The standard layout:
ls .git/refs/heads/
# main
# feature/add-cache
cat .git/refs/heads/main
# 8a3f9d2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e
Tag refs work the same way:
ls .git/refs/tags/
# v3.0.0
cat .git/refs/tags/v3.0.0
# f3a9c2b1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9
Remote-tracking refs live under refs/remotes/<remote>/:
ls .git/refs/remotes/origin/
# main
# HEAD
For repositories with many branches, Git stores refs in a single
file .git/packed-refs for efficiency. The format is one line per
ref: <oid> <refname>. When a ref exists both in packed-refs
and as a separate file, the separate file wins.
cat .git/packed-refs
# 8a3f9d2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e refs/tags/v2.0.0
# f3a9c2b1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9 refs/tags/v3.0.0
The on-disk format of refs is intentionally simple: a ref is a 40
hex character SHA followed by a newline. This is what makes
git update-ref safe and scriptable — atomic write of a 41-byte
file.
HEAD — the current ref
HEAD is a file whose contents are either ref: refs/heads/main
(symbolic HEAD, the normal case) or a 40-char OID (detached HEAD):
cat .git/HEAD
# ref: refs/heads/main
git checkout 8a3f9d2
cat .git/HEAD
# 8a3f9d2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e
The git switch and git checkout commands both update HEAD by
writing to this file. git symbolic-ref HEAD refs/heads/<branch>
is the lowest-level way to change it from a script.
config — the repository configuration
.git/config is an INI file with the repository’s settings. It
is the local-scope file in Git’s three-level config cascade
(system, global, local — covered in lesson II-05). Common
sections:
[core]
repositoryformatversion = 0
filemode = true
bare = false
[remote "origin"]
url = git@github.com:acme/infrastructure.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main
Edit this file with git config <key> <value> (the safe way) or
directly with an editor (only if you know the INI format). Git
will rewrite the file in place on every git config operation to
preserve formatting.
hooks/ — the workflow glue
.git/hooks/ contains executable scripts that Git runs at
specific points in the workflow. The shipped defaults are
non-executing .sample files (pre-commit.sample, post-commit.sample,
etc.) — copy and chmod +x the ones you want to use.
ls .git/hooks/
# applypatch-msg.sample
# commit-msg.sample
# pre-commit.sample
# pre-push.sample
# ...
The full hook list is in githooks(5). Client-side hooks (the
full list — pre-commit, commit-msg, pre-push, etc.) are bypassable
by anyone with shell access. Server-side hooks (pre-receive,
update, post-receive) are the only hooks that enforce policy on
push — they live on the receiving server, not in the client’s
.git/hooks/.
logs/ — the reflog
.git/logs/ records every movement of HEAD and every ref. This is
the recovery surface for git reset --hard mistakes, for commits
made in detached HEAD, and for branches that were deleted: the
commits are still in the object store, and the reflog still has
their OIDs.
git reflog
# 8a3f9d2 (HEAD -> main) HEAD@{0}: commit: add logging bucket
# f3a9c2b HEAD@{1}: reset: moving to main
# 5e7d8c9 HEAD@{2}: commit: (detached) debug cache layer
The reflog is the single most useful forensic tool in Git. It is the journal of every commit made to this clone, whether the commit is on a branch now or not.
Navigation safety
flowchart LR
A["Need to inspect\nan object"] --> B["git cat-file -p <oid>"]
C["Need to read\na ref"] --> D["git rev-parse <ref>"]
E["Need to recover\na commit"] --> F["git reflog"]
G["Need to edit\na ref"] --> H["git update-ref"]
I["Need to edit\nconfig"] --> J["git config <key> <value>"]
The rule is: prefer the plumbing commands, never edit
.git/objects/ directly, and treat .git/ as a database that
Git owns. For scripts that need to interact with the repository,
the plumbing commands from lesson II-03 are the right interface.
Production discipline
- Never hand-edit
.git/objects/. The SHA-1 in the filename is the OID of the contents. Any drift silently breaks the object. Usegit hash-objectandgit update-reffrom scripts. - Treat the reflog as a 30-day safety net. Reflog entries expire after roughly 30 days (90 for reachable commits). If a commit has been lost for more than a month and is not on any branch, treat it as gone.
- Back up
.git/, not the working tree. The working tree is a checkout of a commit in.git/. A disaster recovery procedure that backs up only the working tree gives you a snapshot without history; a backup that includes.git/gives you history, refs, the index, and the reflog.
Cross-course references
- Linux for Production Sysadmins - Part IX (Filesystem)
covers the inode/directory entry model, which is the closest
filesystem analogue to the
.git/objects/directory layout. - Ansible for Production Sysadmins - Part XXXVII (RepoArch)
describes the analogous layout for Ansible collections: the
roles/,playbooks/, andinventory/directories play roles similar toobjects/,refs/, andHEAD. - Terraform for Production Sysadmins - Part IX (State) describes the layout of a Terraform state file, which is a serialised form of the same content-addressed model.
Quiz
Knowledge check · 4 questions
Q1. A loose object is stored at `.git/objects/05/4a3b9c1d2e3f4g5h6i7j8k9l0m1n2o3p4q5r6s7t`. What is the full OID of this object?
Q2. Pack files in `.git/objects/pack/` are an alternative storage format that can be safely read and edited by humans.
Q3. Name the file inside `.git/` that records every move of HEAD and every ref, and explain what it is used for.
Q4. Diagnose a missing branch and recover the commits that were on it.
An engineer ran `git branch -D feature/add-cache` to delete a feature branch that had not been merged. The next day, the team decides the branch was needed and asks for the commits back. The branch is gone from `refs/heads/`, gone from `packed-refs`, and the engineer cannot remember the tip OID.
Passing score: 75%. Answers are checked in this browser.