reduce .git size

reduce .git size

Basic stuff

Check how big your repo actually is before doing anything:

du -sh .git
git count-objects -v

count-objects -v breaks down loose vs packed objects and their sizes — useful for confirming whether cleanup actually helped afterward.

git gc / prune

Standard housekeeping — clears reflog entries and repacks/prunes unreachable objects:

git reflog expire --expire=now --all
git gc --prune=now --aggressive

Note: this won’t remove large files that are referenced by some commit, it only cleans up objects that are already dangling. For actual history rewriting, you need filter-repo (steps 4–5).

Identifying big files

Find the largest blobs across all of history, not just the current tree:

git rev-list --objects --all |
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
  sed -n 's/^blob //p' |
  sort --numeric-sort --key=2 --reverse |
  head -20 |
  cut -c 1-12,41- |
  numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

git-filter-repo for history manipulation

Install git-filter-repo first.

filter-repo might fail to run, since it expects to run on a fresh clone by default (safety check, since it’s destructive). Use --force to run in-place on an existing clone.

And git filter-repo removes your origin, since it edits the whole history, and you have to force push your local repo.

Using git-filter-repo on a directory

git filter-repo --path dir/to/remove/ --invert-paths

Removes the whole directory from every commit in history.

Using git-filter-repo on a file (or multiple files)

git filter-repo --invert-paths \
  --path path/to/file-one.png \
  --path path/to/file-two.woff2

Repeat --path per file. Stack as many as needed in one pass rather than running filter-repo multiple times — every run rewrites all commit hashes again.

Post-filter cleanup and push

filter-repo strips the origin remote automatically (another safety measure) — re-add it:

git remote add origin <your-repo-url>

Then force-push, since history has been rewritten:

git push origin --force --all
git push origin --force --tags

Gotchas

  • All downstream commit hashes change. Anyone with an existing clone needs to re-clone or hard-reset onto the new history — pulling/merging normally will conflict badly.
  • Add removed paths to .gitignore if you don’t want them accidentally re-committed later.
  • If you’re rewriting in stages (multiple filter-repo runs) and already pushed in between, collaborators need to re-sync after each push. It’s better to cleanup all at once and then push, making it easier for everyone.
  • For ongoing large-file needs (not just cleanup) — binary assets you actually want to keep versioned — consider Git LFS instead of committing them raw.