Introduction
A large .git directory slows clones, fetches, and CI jobs. Growth usually comes from unreachable objects, many loose objects, or oversized historical files that were committed in the past. You can reclaim space safely if you measure first, then apply cleanup commands in the right order.
Measure What Is Taking Space
Before changing anything, inspect repository storage details.
This reports loose object count, packed size, and garbage metrics. Next, inspect pack files:
If one or two pack files are very large, history likely includes big binaries. Also check overall repository size:
Measure first, clean second, then measure again to confirm real improvement.
Find the Largest Historical Blobs
When size is unexpectedly high, identify which objects dominate storage. This helps decide whether normal garbage collection is enough or history rewriting is required.
git verify-pack -v .git/objects/pack/*.idx \
| sort -k3 -n \ | tail -20 ``` Then map blob IDs to file paths: ```bash git rev-list --objects --all | grep '<blob-id>' ``` If results point to archives, media files, or generated artifacts, history cleanup will likely produce significant size reduction. ## Repack and Prune Unreachable Objects For repositories with many loose objects, repacking is often the fastest win. ```bash git gc --aggressive --prune=now ``` This command repacks objects and removes unreachable data immediately. If you want finer control, run the underlying steps yourself. ```bash git reflog expire --expire=now --expire-unreachable=now --all git repack -Ad git prune --expire=now ``` Explanation of the sequence: * `reflog expire` removes old references that keep objects alive. * `repack -Ad` creates compact packs and drops redundant ones. * `prune` deletes objects that are no longer reachable. For shared repositories, avoid aggressive pruning during active collaboration windows. Expired reflogs can reduce recovery options for recently rewritten commits. ## Remove Large Historical Blobs When Needed If history includes accidental archives, media, or build outputs, normal garbage collection will not help because those blobs are still reachable. You need history rewriting. `git filter-repo` is the recommended modern tool for this task. ```bash # Example: remove all .zip files from history git filter-repo --path-glob '*.zip' --invert-paths ``` Or remove one large directory from all commits: ```bash git filter-repo --path data/raw-dumps --invert-paths ``` After rewriting, run cleanup: ```bash git reflog expire --expire=now --all git gc --prune=now --aggressive ``` Then force-push rewritten branches and coordinate with your team, because commit IDs will change. ## Reduce Future Growth Shrinking once is not enough if large artifacts keep entering history. Add preventive controls. ```bash # .gitignore build/ node_modules/ *.zip *.tar *.mp4 ``` For files that must be versioned but are large, use Git LFS. ```bash git lfs track "*.psd" git add .gitattributes git add design/mockup.psd git commit -m "Track design assets with LFS" ``` You can also enforce repository policy in CI by rejecting commits that add files above a size threshold. ## Common Pitfalls A common mistake is running `git gc` and expecting dramatic size reduction when large blobs are still referenced by commits. Garbage collection only removes unreachable data. Another issue is rewriting history on a shared branch without coordination. Team members may continue from old commit lines, causing confusing merge and push failures. Developers also forget to update mirrors, forks, and CI caches after rewrite. If old references remain in external systems, space can grow again or deleted content can reappear. ## Summary * Measure `.git` size and object distribution before cleanup. * Identify top blobs so you can choose the right cleanup strategy. * Use `git gc`, `repack`, and reflog expiration for unreachable cleanup wins. * Use `git filter-repo` to remove large files from history when necessary. * Prevent regrowth with `.gitignore`, Git LFS, and CI file-size checks.