Chapter 03Lesson 05~120 minutes

Checkpoint Lab — Git Object Model: Blobs, Trees, Commits, Tags, and the Object Database

Checkpoint the Git object model by creating three snapshots, proving object reuse, walking every commit/tree/blob edge, inspecting an annotated tag, and validating an object-format-aware provenance graph.

CheckpointObject graphProvenanceVerification

Learning objectives

  • Create a reproducible three-commit history with controlled changed and unchanged content.
  • Capture and type-check commit, tree, blob, and annotated-tag object IDs.
  • Prove blob reuse and commit parent ancestry with Git commands.
  • Draw and verify every reference/object edge in the repository graph.
  • Produce an object-format-aware source provenance report and clean up safely.

1. Checkpoint scenario — prove the graph rather than memorizing it

You will create three commits containing two files. stable.txt remains unchanged throughout; changing.txt changes in commits 2 and 3. You will then walk from HEAD to each commit, tree, and blob, create an annotated tag, and verify every edge in a final graph.

2. Predictions before mutation

Write your answers before running the lab:

  1. Will changing only changing.txt create a new blob for stable.txt?
  2. Will commit 2 have the same OID as commit 1 if most files are unchanged?
  3. When an annotated tag is created at commit 3, will the tag ref point directly to the commit or to a tag object?
  4. If the branch advances to a fourth commit later, should the existing annotated tag automatically move?

3. Setup and preflight

Disposable repository: the entire directory is created for this checkpoint and removed at the end. No remote account is required.

Git Bash, Bash, or zsh

mkdir git-object-checkpoint
cd git-object-checkpoint
git init -b trunk
git config user.name "Object Checkpoint"
git config user.email "checkpoint@example.invalid"
git status --short --branch
git rev-parse --show-object-format=storage

PowerShell

New-Item -ItemType Directory git-object-checkpoint | Out-Null
Set-Location git-object-checkpoint
git init -b trunk
git config user.name "Object Checkpoint"
git config user.email "checkpoint@example.invalid"
git status --short --branch
git rev-parse --show-object-format=storage

4. Commit 1 — establish the first tree

Git Bash, Bash, or zsh

printf "never changes\n" > stable.txt
printf "one\n" > changing.txt
git status --short
git add stable.txt changing.txt
git diff --staged
git commit -m "Snapshot one"

C1=$(git rev-parse HEAD)
T1=$(git rev-parse HEAD^{tree})
S1=$(git rev-parse HEAD:stable.txt)
X1=$(git rev-parse HEAD:changing.txt)

printf "C1=%s\nT1=%s\nS1=%s\nX1=%s\n" "$C1" "$T1" "$S1" "$X1"

PowerShell

Set-Content stable.txt 'never changes'
Set-Content changing.txt 'one'
git status --short
git add stable.txt changing.txt
git diff --staged
git commit -m "Snapshot one"

$C1 = git rev-parse HEAD
$T1 = git rev-parse HEAD^{tree}
$S1 = git rev-parse HEAD:stable.txt
$X1 = git rev-parse HEAD:changing.txt
$C1; $T1; $S1; $X1

5. Commit 2 — change only one blob

Git Bash, Bash, or zsh

printf "two\n" > changing.txt
git status --short
git diff
git add changing.txt
git commit -m "Snapshot two"

C2=$(git rev-parse HEAD)
T2=$(git rev-parse HEAD^{tree})
S2=$(git rev-parse HEAD:stable.txt)
X2=$(git rev-parse HEAD:changing.txt)

PowerShell

Set-Content changing.txt 'two'
git status --short
git diff
git add changing.txt
git commit -m "Snapshot two"

$C2 = git rev-parse HEAD
$T2 = git rev-parse HEAD^{tree}
$S2 = git rev-parse HEAD:stable.txt
$X2 = git rev-parse HEAD:changing.txt

Now verify prediction 1: S1 and S2 should match; X1 and X2 should differ.

6. Commit 3 — repeat the pattern

Git Bash, Bash, or zsh

printf "three\n" > changing.txt
git add changing.txt
git commit -m "Snapshot three"

C3=$(git rev-parse HEAD)
T3=$(git rev-parse HEAD^{tree})
S3=$(git rev-parse HEAD:stable.txt)
X3=$(git rev-parse HEAD:changing.txt)

test "$S1" = "$S2" && test "$S2" = "$S3"
test "$X1" != "$X2" && test "$X2" != "$X3"

PowerShell

Set-Content changing.txt 'three'
git add changing.txt
git commit -m "Snapshot three"

$C3 = git rev-parse HEAD
$T3 = git rev-parse HEAD^{tree}
$S3 = git rev-parse HEAD:stable.txt
$X3 = git rev-parse HEAD:changing.txt

$S1 -eq $S2 -and $S2 -eq $S3
$X1 -ne $X2 -and $X2 -ne $X3

The equality checks should report success/true for the stable blob, while every changed-content blob should have a different OID.

7. Verify commit ancestry, not timestamp guesses

git log --graph --decorate --oneline --all
git rev-parse HEAD
git rev-parse HEAD^
git rev-parse HEAD~2
git cat-file -p HEAD
git cat-file -p HEAD^

The parent line of commit 3 should name commit 2; the parent line of commit 2 should name commit 1. This proves the ancestry edges directly.

8. Verify every tree edge

git ls-tree HEAD~2
git ls-tree HEAD~1
git ls-tree HEAD

Each tree should list the same blob OID for stable.txt and a different blob OID for changing.txt. The root tree OID itself changes because at least one entry changed.

9. Add an annotated tag to commit 3

git status --short --branch
git tag -a checkpoint-v1 -m "Object checkpoint release"

TAGOID=$(git rev-parse checkpoint-v1^{tag})
# PowerShell: $TAGOID = git rev-parse checkpoint-v1^{tag}

git cat-file -t checkpoint-v1
git cat-file -p checkpoint-v1
git rev-parse checkpoint-v1^{commit}
git rev-parse HEAD

The tag type should be tag; the peeled commit should equal HEAD. This is different from a lightweight tag, where the tag ref directly names its target without a separate annotated tag object.

10. Draw the graph you actually verified

Checkpoint reference and object graph
flowchart TD
H[HEAD] --> B[refs/heads/trunk]
B --> C3[commit C3]
C3 -->|parent| C2[commit C2]
C2 -->|parent| C1[commit C1]
C3 -->|tree| T3[tree T3]
C2 -->|tree| T2[tree T2]
C1 -->|tree| T1[tree T1]
T1 -->|stable.txt| S[blob stable]
T2 -->|stable.txt| S
T3 -->|stable.txt| S
T1 -->|changing.txt| X1[blob one]
T2 -->|changing.txt| X2[blob two]
T3 -->|changing.txt| X3[blob three]
R[refs/tags/checkpoint-v1] --> A[annotated tag]
A -->|object| C3

Verify each arrow using a Git command:

  • git symbolic-ref HEAD for HEAD → branch ref;
  • git rev-parse trunk for branch ref → C3;
  • git cat-file -p C3 for C3 → T3 and C2;
  • git ls-tree C3 for tree → blob edges;
  • git rev-parse checkpoint-v1^{tag} and git cat-file -p checkpoint-v1 for tag ref → tag object → C3.

11. Type-check every captured object

Git Bash, Bash, or zsh

for oid in "$C1" "$C2" "$C3" "$T1" "$T2" "$T3" "$S1" "$X1" "$X2" "$X3" "$TAGOID"; do
  printf "%s  " "$oid"
  git cat-file -t "$oid"
done

PowerShell

@($C1,$C2,$C3,$T1,$T2,$T3,$S1,$X1,$X2,$X3,$TAGOID) | ForEach-Object {
    "$_  $(git cat-file -t $_)"
}

You should see three commits, three trees, four distinct blobs (one stable plus three changing versions), and one tag object.

12. Capture an object-format-aware provenance report

git rev-parse --show-object-format=storage
git rev-parse --verify HEAD
git rev-parse --short=12 HEAD
git show --no-patch --format=fuller HEAD
git show-ref --heads --tags

This report stores the full commit OID for machine provenance while also showing a Git-validated abbreviation suitable for humans.

13. Verification checklist

  • Exactly three ordinary commits exist on trunk.
  • Commit 3 points to commit 2; commit 2 points to commit 1.
  • The root-tree OIDs differ as changing.txt changes.
  • The stable.txt blob OID is identical in all three commits.
  • The changing.txt blob OID is different in all three commits.
  • checkpoint-v1 is an annotated tag object.
  • The tag object's target commit equals commit 3.
  • The repository's object format was discovered from Git rather than inferred from OID length.
  • No direct file editing occurred inside .git/objects.

14. Cleanup

Preflight: confirm the directory is the disposable checkpoint before recursive deletion.

Git Bash, Bash, or zsh

cd ..
pwd
rm -rf git-object-checkpoint

PowerShell

Set-Location ..
Get-Location
Remove-Item -Recurse -Force git-object-checkpoint

15. Knowledge check

Question 1. Why did all three root tree OIDs change even though stable.txt did not?

Question 2. What proves that commit 3 is a descendant of commit 2?

Question 3. Why is the annotated tag object not the same object as commit 3?

Question 4. If the trunk branch moves later, does checkpoint-v1 automatically move?

Question 5. A teammate stores only the first 7 characters of a commit ID in a deployment database. What is the design problem?

16. What Chapter 03 adds to a production Git operating model

You can now distinguish logical source identity from filenames, branches, and physical storage. You can trace a release/source ref to immutable objects, verify ancestry, reason about reuse and retention, and avoid destructive “repairs” based on false assumptions about .git/objects.

17. Chapter checkpoint summary

The object database is the foundation under everything that follows: Chapter 04's index/working-tree lifecycle, branch movement, merge ancestry, recovery, tags/releases, fetch/push transfer, repository maintenance, and provenance automation all manipulate or navigate this same object/reference graph.

Next chapter

Repositories, Working Tree, Index, HEAD, and the File Lifecycle

Chapter 04 connects the immutable object database to the mutable working tree and index so you can predict exactly what status, add, diff, restore, rm, and related commands change.

Authoritative references

 git-cat-file
 git-ls-tree
 git-rev-parse
 git-tag
 gitrepository-layout

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.