C.W.K.
Stream
Lesson 03 of 04 · published

Packfiles, GC, and Repository Maintenance

~18 min · gc, maintenance

Level 0Untracked Rookie
0 XP0/47 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Packfiles, gc, and routine maintenance

Loose objects — one file per blob, tree, commit, tag — are simple but expensive at scale. A repo with 100k commits and millions of blobs would have millions of files in .git/objects/. Filesystem performance, listing speed, and remote-transfer overhead all collapse. Git's answer is packfiles: many objects compressed and concatenated into one .pack file with a companion .idx index. Operations on packed objects are fast; transfer is dramatically smaller.

git gc ("garbage collect") packs loose objects, removes unreachable ones, and rebuilds indexes. Modern Git runs gc --auto automatically when thresholds are crossed (200 loose objects, 7000 too-many-packs, etc.). For most repos that is enough. For long-lived shared repos, periodic git gc --aggressive reorganizes packs more thoroughly at the cost of more CPU; do this on a server-side mirror or off-hours, not in the middle of your workday.

"Unreachable" is the key concept. Any object reachable from at least one ref (HEAD, branch, tag, reflog entry, stash) survives. Objects no other ref can reach become candidates for GC. The reflog acts as a 90-day safety net — even orphaned commits remain reachable via reflog entries until their TTL expires. git gc --prune=now bypasses the safety net and immediately removes unreachable objects, useful when you really want sensitive content gone (paired with git filter-repo for history rewrite).

Routine maintenance commands. git fsck verifies object integrity. git count-objects -v shows loose vs packed counts and on-disk size. git maintenance start (Git 2.30+) registers background tasks (gc, pack-refs, commit-graph, prefetch) so the repo stays optimized without explicit cron. Modern Git also writes a commit-graph file that accelerates ancestry queries (git log --graph, git merge-base); enable with git config core.commitGraph true and refresh with git commit-graph write.

Code

Inspect repo health and pack state·bash
# Loose vs packed object counts and total size:
git count-objects -vH

# Verify integrity (slow but thorough):
git fsck --full

# Manual gc when something feels off:
git gc

# More aggressive repacking — server-side or off-hours:
git gc --aggressive --prune=now

# Modern background maintenance (Git 2.30+):
git maintenance start            # registers gc, pack-refs, commit-graph
git maintenance run              # one-shot run
git maintenance unregister       # turn off
Pack and commit-graph specifics·bash
# Manually repack everything:
git repack -ad

# Show packfile details:
ls -lh .git/objects/pack/
git verify-pack -v .git/objects/pack/*.idx | tail -20

# Commit-graph speeds up history queries:
git config core.commitGraph true
git config gc.writeCommitGraph true

# Build/refresh the commit-graph:
git commit-graph write --reachable

External links

Exercise

On a real repo, run git count-objects -vH and read the loose vs packed counts. Run git gc and check the counts again. Enable commit-graph with git config core.commitGraph true and git commit-graph write --reachable; time a git log --graph --all | head -5000 before and after on a large-ish repo. Note in writing whether the difference matched your expectations.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.