Git, CI/CD & GitOpsI · Version Control FoundationsFoundations
Snapshots and the history model — what a commit really records
What you'll learn
- Describe what a Git commit stores at the byte level, not the conceptual level
- Explain content-addressed storage and why the SHA hash is a tamper-evident handle
- Distinguish snapshot-based history from diff-based history and the operational consequences of each
- Trace a commit hash to its tree, parent, and author metadata
- Recognise why the snapshot model is what makes collaboration, reproducibility, and auditability possible
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
A Git commit is not a patch. It is a snapshot of the entire repository at a moment in time, identified by a SHA computed from its contents. This is the foundational claim of the rest of the course: every property that matters for an infrastructure team — collaboration, reproducibility, auditability — is a consequence of the fact that a commit is a content-addressed, immutable snapshot, not a list of changes.
What a commit actually stores
When you run git commit, Git does not record a diff. It serialises the
current state of the index into a tree object, wraps that tree with
metadata (parent commit, author, committer, message), hashes the result
with SHA-256 (or SHA-1 in older Git), and stores the resulting object in
the object database under that hash. The hash is the commit’s identity;
nothing else names it.
flowchart LR
A["Working tree"] -->|git add| B["Index"]
B -->|git commit| C["Tree object"]
C --> D["Commit object"]
D --> E["SHA-256 hash"]
E --> F["Object database"]
The tree object is a manifest of every file in the repository at the moment of the commit, with the SHA of each file’s blob. Files that did not change between commits are referenced by the same blob — the storage is deduplicated, even though the logical view is that every commit is a full snapshot. You can reconstruct the working tree of any commit by following the tree from the commit object down to the blobs.
# Inspect the object graph from a commit down to its files
git cat-file -p "$HASH"
git ls-tree -r "$HASH"
Snapshot versus diff
Older version-control systems (early CVS, RCS) stored the history as a sequence of diffs: the first commit held a full file, and every subsequent commit was a delta against its parent. To reconstruct a commit, the system had to replay the diffs from the beginning of history. Git inverts this: every commit is a full snapshot, and the diff is computed on demand by walking the tree of two commits.
The operational consequences of the snapshot model are non-obvious but decisive:
- Constant-time checkout. Checking out a commit is reading its tree object and materialising the files. There is no replay of history.
- Trivial fork-join. Two branches can diverge at any commit and rejoin later, because each commit is a complete state. Diffs cannot be cleanly fork-joined without re-playing the intervening history.
- Cheap diffs, expensive history. Storing the full snapshot per commit costs more disk than storing diffs; the cost is bounded by deduplication. The benefit is that any operation that reads history is local and fast.
sequenceDiagram
participant W as Working tree
participant I as Index
participant O as Object DB
W->>I: git add
I->>O: write blob
W->>I: git commit
I->>O: write tree
O->>O: write commit
O-->>W: hash returned
Production discipline
The snapshot model has two implications that recur in incident response and audit work:
- A commit is a reproducible input. If you give a CI pipeline the hash of a commit and the hash of the build container, you can reproduce the same artifact six months later. If you give it a branch name or a tag, you cannot — the branch has moved, and the tag may have been deleted or moved. The hash is the only unit of reproducibility that survives time.
- History rewriting breaks every external reference. When a rebase or a force-push changes commit hashes, every external system that recorded the old hash (an artifact registry, a deployment log, a compliance ticket) is now reading a dangling reference. Protected branches and signed commits are the discipline that prevents this.
Cross-course references
- Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) discusses byte-identical reproducibility of package builds; the same property applies to Git snapshots.
- Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers why Ansible roles are pinned to specific commit hashes in CI.
- Terraform for Production Sysadmins - Part VI (Lockfile) explains how the dependency lockfile gives Terraform a snapshot-equivalent identity for the provider graph.
Quiz
Knowledge check · 4 questions
Q1. What does Git actually store when you run `git commit`?
Q2. Two commits that share an identical file will share the same blob object in the object database.
Q3. Give the operational reason an artifact registry should record a commit hash rather than a branch name when storing a build.
Q4. An engineer rewrites three months of Git history with an interactive rebase to clean up old commit messages. The CI pipeline was already publishing artifacts tagged with the original commit hashes. Diagnose the breakage and recommend a remediation.
A rebase rewrites 412 commits. The old commit hashes are invalidated. The CI pipeline was publishing artifacts tagged with `git rev-parse HEAD` at build time. Three months of artifact-registry entries now reference commit hashes that no longer exist in the repository. A deploy record that pointed at artifact `sha-abc123` cannot find the artifact in the registry because the registry still holds the bytes (content-addressed) but the registry's tag index points at a commit that the repository no longer contains.
Passing score: 75%. Answers are checked in this browser.