ShrinkRay All Articles
Developer Productivity

How Your Git Repo Got Fat: The Accidental Bloat Nobody Notices Until It's Too Late

By ShrinkRay Developer Productivity
How Your Git Repo Got Fat: The Accidental Bloat Nobody Notices Until It's Too Late

Somebody on your team committed a video file six months ago. Nobody caught it. Then someone else added compiled binaries to the repo because it was "easier." A few sprints later, your once-nimble codebase takes eleven minutes to clone fresh, your CI pipeline is timing out, and the new engineer who joined last Monday is asking why her laptop fan sounds like a jet engine every time she pulls main.

This is the quiet crisis that lives in git history — invisible until it's catastrophic.

How Repositories Get Heavy Without Anyone Noticing

Git is brilliantly designed for tracking text. Line diffs, merges, conflict resolution — it handles all of that elegantly. What it was never built for is storing large binary files. The problem is that nobody told the developers.

Here's how the bloat typically accumulates:

Committed build artifacts. Someone runs a build locally, forgets to add dist/ or build/ to .gitignore, and pushes. The artifacts get removed in the next commit, but they're still sitting in history, permanently inflating the pack file. Every clone pulls that dead weight forever.

Design assets in the wrong place. PSD files, high-res PNGs, exported Figma assets — these belong in cloud storage, not version control. A single Photoshop source file can be 200MB. Commit it, then update it three times, and you've just quietly added 800MB to your repo that nobody can see from git status.

Dependency snapshots. Some teams — particularly in enterprise environments — commit their vendor/ folders or even node_modules as a misguided attempt at reproducible builds. This is the nuclear option of bad ideas. A modest Node project's node_modules can run 500MB or more.

Log files and database dumps. It happens more than you'd think. A developer is debugging a production issue, generates a log dump or a database export, and commits it alongside a hotfix at 11pm. Nobody reviews the diff closely. It's in the repo now.

Real Teams, Real Pain

One engineering team at a mid-sized SaaS company discovered their mobile app repository had ballooned to 14GB after two years of development. The culprit? A contractor who had committed compiled iOS and Android binaries directly to the repo as part of a release workflow that nobody had properly reviewed. By the time they found it, the repo had been cloned hundreds of times by CI runners alone. The cleanup took a full sprint and required coordinating a forced push across a team of 23 developers.

Another team running a content platform found 6GB of compressed video test files buried in a branch from a proof-of-concept that shipped 18 months prior. The branch had been merged. The files lived on.

These aren't edge cases. They're Tuesday.

Finding the Bloat

Before you can fix anything, you need to know what you're dealing with. Here are the tools worth reaching for:

git-sizer — Released by GitHub, this tool gives you a comprehensive breakdown of your repository's object sizes, including the largest blobs, deepest trees, and fattest commits. Run it, brace yourself, then scroll to the bottom.

git rev-list with --objects — A built-in approach for finding large objects in history. The command git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | sort -k3 -n -r | head -20 will surface your twenty largest stored objects. Not pretty, but effective.

BFG Repo Cleaner — Once you know what needs to go, BFG is dramatically faster than git filter-branch for actually removing it. It's purpose-built for this exact problem.

git-filter-repo — The officially recommended replacement for git filter-branch. More flexible than BFG for complex rewrites, and maintained actively.

Cleaning Up Without Breaking Your Team

Removing objects from git history requires rewriting that history, which means every team member needs to re-clone or reset their local copy. This is non-trivial on a large team, so communication matters.

Here's a reasonable cleanup playbook:

  1. Freeze the branch. Announce that no new commits will be accepted to main during the cleanup window.
  2. Run BFG or git-filter-repo to strip the offending files from history.
  3. Force-push the cleaned history to your remote.
  4. Have every team member delete their local clone and re-clone fresh. Yes, all of them. This is not optional.
  5. Update your .gitignore immediately and add a pre-commit hook that rejects files over a configurable size threshold (100KB is a reasonable starting point for most projects).

For ongoing prevention, consider integrating Git LFS (Large File Storage) for any genuinely large assets your project needs to version — design files, audio, video, binary assets. LFS stores the actual content on a remote server and only keeps lightweight pointer files in the repository itself.

Building the Habits That Prevent This

The best cleanup is the one you never have to do. A few practices that keep repositories lean from the start:

Your git history is supposed to be a record of decisions, not a landfill. Treat it accordingly, and your team — plus every CI runner that ever clones your repo — will thank you.