Repository health, LFS and recovery

Keep a repository fast with fsck, gc and repack, move large assets into Git LFS, purge a file from history, and recover from a damaged object store.

Health checks and housekeeping

git count-objects -vH          # loose objects, packs and total size
git fsck --full                # verify every object and link
git fsck --unreachable         # objects nothing points at

git gc                         # pack loose objects, prune what is safe
git gc --aggressive            # slower and better compressed, rarely worth it
git prune --expire=now         # remove unreachable objects immediately
git repack -ad                 # rebuild packs without redundant ones
git reflog expire --expire=now --all
CommandDoesRisk
git count-objects -vHReports size and object countsNone
git fsck --fullVerifies object integrity and linksNone
git gcPacks objects and prunes unreachable ones past the grace periodLow
git prune --expire=nowDeletes unreachable objects immediatelyRemoves anything you might still recover
git repack -adRebuilds all packs from scratchInterrupting it leaves the repository in an awkward state
git reflog expire --expire=now --allDrops reflog entriesDestroys your local recovery path

Git runs git gc automatically when the number of loose objects grows, so manual runs are mostly for recovery work, size audits, or a repository that has just been through a history rewrite.

Git LFS for large assets

# once per machine
git lfs install

# track a file type: the blob goes elsewhere, a small pointer goes in Git
git lfs track "*.psd"
git add .gitattributes
git add design/mockup.psd
git commit -m "Add design source (LFS)"

git lfs ls-files
git lfs status
git lfs pull                                              # fetch missing objects
git lfs migrate import --include="*.zip" --everything     # move existing history to LFS
  • LFS stores the bytes on a separate server and commits a text pointer, so a clone without LFS installed gives you pointers instead of files.
  • The .gitattributes entry must be committed, or the next clone cannot tell which paths are LFS-tracked.
  • Storage and bandwidth quotas apply on most hosts; a repository full of video becomes an ongoing bill rather than a one-off cost.
  • Converting existing history requires a rewrite (git lfs migrate), so do it early, before many people have cloned.
⚠️
Rewriting history to move files into LFS changes every affected commit id. Treat it like any other rewrite: freeze pushes, announce it, and expect everyone to delete and re-clone rather than pull — an old clone will happily push the old blobs back.

Removing a leaked file and recovering damage

# a file was committed by mistake: it lives in history, not only in the tip
git log --all --oneline -- path/to/secret.env
git filter-repo --path path/to/secret.env --invert-paths
# git filter-branch is the legacy equivalent; prefer filter-repo

# find the largest objects in your packs
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n -r | head -5

# recover from a damaged or partially fetched repository
git fsck --full                     # identify what is missing or corrupt
git remote -v
git fetch --all                     # re-download objects from a known-good remote
git fetch --refetch                 # force a fresh download of the packfiles
git reflog                          # a local safety net while it still exists
  • Once a secret has been pushed, treat it as compromised: rotate the credential first, then clean the history. Cleaning alone does not un-leak anything.
  • After any history rewrite, every clone must be replaced — old clones re-push the deleted objects on their next push.
  • A corrupt object usually means a bad disk or an interrupted transfer. Re-fetch from a known-good remote before attempting anything more creative.
  • Back up important repositories by mirroring them (git clone --mirror), not by copying a working directory, which holds no history.

FAQ

How big is too big for a Git repository?
There is no hard limit, but clone time and everyday commands degrade past roughly a gigabyte of packfile, and hosts escalate warnings after that. Keep binaries in LFS or outside Git entirely.
Does git gc delete work I still need?
Not while something still points at it: gc only prunes unreachable objects older than the grace period, two weeks for loose objects by default. --expire=now bypasses that protection, so use it only when you are certain.

Worktrees, submodules and large repositories Hooks, aliases and automation

Last refreshed 2026-09-18.