Chapter 9-5 — gc & Reflog Expiry | DevInHyderabad☕ ☕ 18 min read

gc & Reflog Expiry

Ran gc --prune=now without understanding reflog expiry? Your data just left the chat. Permanently.

01What is Git Garbage Collection (gc)?

Git Garbage Collector (git gc) is the cleanup crew for your repository. It removes unreachable objects and compresses files into efficient packfiles.

Every time you git commit --amend, git rebase, or git reset, the old commit objects are left behind in .git/objects/. They are unreachable — no branch or tag points to them — but they still take up space on disk.

git gc does two important things: packs loose objects into packfiles (efficient compression) and deletes objects that are no longer reachable AND have expired their reflog grace period.

Git runs auto gc automatically after certain commands (like git commit, git merge) when the number of loose objects exceeds a threshold (default ~700). You rarely need to run git gc manually unless you are doing repository maintenance or trying to save disk space.

# Check repository size and object count
git count-objects -v
# count: 48            ← loose objects
# size: 24             ← size in KiB
# in-pack: 120         ← objects in packfiles
# pack-size: 56        ← packfile size in KiB

# Run garbage collection manually
git gc
# Enumerating objects: 168, done.
# Compressing objects: 100% (120/120), done.
# Total 168 (delta 45), reused 168 (delta 45)

# After gc, check again
git count-objects -v
# count: 0             ← loose objects packed!
# size: 0
# in-pack: 168
# pack-size: 48        ← compressed!

# Aggressive gc (slower but more optimization)
git gc --aggressive
# Use sparingly — re-deltas everything
02Reflog Expiry: The 90-Day Safety Net

The reflog is a log of where HEAD and branch pointers have been. It is what makes recovering from git reset and git rebase possible.

But the reflog does not keep entries forever. It expires.

Default expiry: reachable objects are kept for 90 days (gc.reflogExpire). Unreachable objects are kept for 30 days (gc.reflogExpireUnreachable).

After a reflog entry expires, the next git gc will PERMANENTLY DELETE the unreachable object it pointed to.

This means: if you reset --hard and wait 30+ days without recovering, the commits might be gone forever.

git reflog shows entries with dates. You can check how much time you have left before they expire.

# View reflog with dates
git reflog --date=iso
# abc1234 HEAD@{0} {2024-01-15 10:30}: commit: add feature
# def5678 HEAD@{1} {2024-01-14 09:15}: reset: moving to HEAD~1
# ghi9012 HEAD@{2} {2024-01-10 14:00}: commit: lost work

# Check default expiry settings
git config --get gc.reflogExpire
# 90 days (default for reachable)

git config --get gc.reflogExpireUnreachable
# 30 days (default for unreachable)

# After 30 days, ghi9012 entry expires
# After running git gc, the commit ghi9012 is PERMANENTLY DELETED

# Never expire (dangerous but prevents data loss)
git config --global gc.reflogExpire never
git config --global gc.reflogExpireUnreachable never
# Warning: your .git directory will grow forever!
The reflog is your safety net, but it has a time limit. If you accidentally reset or rebase and lose commits, recover them SOON — do not wait weeks. After the reflog expiry period (30 days for unreachable objects by default), git gc will permanently delete those commits. Set gc.reflogExpireUnreachable to a longer period if you want more time, but be aware your repository will grow larger.
03Prune: Deleting Unreachable Objects

git prune is the underlying command that actually deletes unreachable objects. git gc calls prune internally.

git prune --expire=now deletes ALL unreachable objects immediately, ignoring the 30-day grace period.

git gc --prune=now runs gc and prunes everything immediately. DANGEROUS if you have uncommitted or reflog-recoverable work.

git reflog expire --expire=0 --all expires ALL reflog entries immediately. Combined with prune, this is the nuclear option.

Only use --prune=now if you are absolutely sure you do not need any lost commits, or on a fresh clone where there is nothing to recover.

# See what would be deleted (safe, does not delete anything)
git prune --dry-run
# abc1234 commit
# def5678 blob
# (these objects are unreachable and would be deleted)

# Normal prune (respects expiry period)
git prune
# Only deletes objects older than 2 weeks

# NUCLEAR: Delete everything unreachable NOW
git reflog expire --expire=0 --all  # expire all reflog entries
git gc --prune=now                  # delete all unreachable objects

# After this, there is NO recovery for lost commits
# The objects are GONE from .git/objects/

# When is this appropriate?
# 1. Fresh clone where you have not done any local work
# 2. Repository maintenance on a server
# 3. Removing sensitive data (after git filter-repo)
# NEVER run this casually on your working repository!
04Packfiles: How Git Stores Efficiently

Initially, Git stores each object as a separate file in .git/objects/XX/. This is loose storage.

With many objects, this is inefficient — thousands of tiny files waste disk space and are slow to access.

Git solves this with PACKFILES: it combines many objects into one .pack file and creates a .idx index file.

Inside a packfile, Git uses delta compression: it stores only the DIFFERENCE between similar objects, not the full content. This dramatically reduces size for repositories with many similar versions of files.

git gc and git repack create packfiles. git verify-pack lets you inspect them.

# Before packing: loose objects
find .git/objects -type f | wc -l
# 48 loose objects

# Pack everything
git repack -a -d
# -a = pack ALL objects (not just reachable)
# -d = delete redundant packs after packing

# Now check
find .git/objects -type f | wc -l
# 3 files (pack file, index, and pack checksum)

ls .git/objects/pack/
# pack-abc123.pack  ← compressed objects
# pack-abc123.idx   ← index for fast lookup

# Inspect pack contents
git verify-pack -v .git/objects/pack/pack-abc123.idx
# Shows all objects in the pack with sizes
# non delta: 15 objects
# delta: 33 objects  ← stored as differences!

# The packfile is much smaller than loose objects
# because delta compression only stores changes
05Repository Maintenance Checklist

Most of the time, Git's automatic gc is enough. But for large repos or after heavy rewriting, manual maintenance helps.

  • git gc — standard cleanup (pack loose objects, prune expired)
  • git gc --aggressive — thorough optimization (re-delta everything, slower but smaller)
  • git repack -a -d — repack all objects into a single pack
  • git prune --expire=now — delete unreachable objects immediately (caution!)
  • git reflog expire --expire=0 --all — clear all reflog entries (caution!)
  • git fsck — verify repository integrity before and after maintenance
# Standard maintenance (safe, run anytime)
git gc

# Heavy maintenance (after large rebase or filter-repo)
git reflog expire --expire=1.week.ago --all  # expire old entries
git gc --prune=1.week.ago                    # delete objects older than 1 week

# Full repack (consolidate all packfiles)
git repack -a -d

# Verify integrity
git fsck --full
# If fsck reports errors, STOP and investigate
# Do not run gc on a corrupted repository

# Check repository size before and after
du -sh .git/
# 48M .git/
git gc
du -sh .git/
# 31M .git/

# Schedule regular maintenance (optional)
git maintenance start
# Git 2.31+ runs maintenance automatically in background
Pro tip: If your repository feels slow or is taking too much disk space, run git gc first. If that is not enough, try git repack -a -d. Only use git gc --aggressive for severe cases — it is much slower and the benefits are usually minimal for normal use. And NEVER run git gc --prune=now without checking git fsck and git reflog first.

Lo kar liya — Key Points:

  • ✅ git gc cleans up the repository by packing loose objects and deleting unreachable expired objects
  • ✅ The reflog has an expiry period — 90 days for reachable, 30 days for unreachable objects by default
  • ✅ After reflog entries expire, git gc permanently deletes the unreachable objects they protected
  • ✅ git prune is the underlying command that deletes objects; git gc calls it internally
  • ✅ Never run git gc --prune=now casually — it deletes ALL unreachable objects immediately, even recent ones
  • ✅ Packfiles combine many loose objects into one compressed file using delta compression for efficiency
  • ✅ Git runs auto gc automatically when loose objects exceed ~700, so manual gc is rarely needed
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