Skip to content

Repository Limits and Health at Scale

Lesson 10 of 10Intermediate15 min readGit at Scale & Enterprise Engineering · Large RepositoriesVerified: GitHub repository limits documentation, September 2026

Repositories rarely fail suddenly. They degrade, and then one day something that used to work does not.

The limits below are the boundaries where degradation becomes refusal. Knowing them turns “the repository is getting big” into a measurable position relative to a known ceiling, which is the difference between a concern and a plan.

Repository size. A repository’s on-disk size — the compressed .git directory — is limited to 10 GB. Approaching it means clone times and push times that are already painful.

Individual objects. A single file is enforced at 100 MB, with 1 MB given as the recommended maximum. Anything above 100 MB cannot be pushed at all. This is the hard boundary that pushes teams toward Git LFS or an artifact store.

Push size. A single push is enforced at 2 GB. Relevant when importing history or pushing a long-lived branch for the first time.

Directory shape. A directory may contain at most 3,000 entries, and the tree may be at most 50 levels deep. Both are reached by generated content rather than by hand-written code.

Branches. A repository may have at most 5,000 branches. Reached by automation that creates branches and never deletes them — dependency bots, per-build branches, per-environment branches.

Request rates. GitHub gives recommended maximums of 15 Git read operations per second and 6 pushes per minute per repository. These are recommendations rather than hard refusals, but a repository whose CI generates far more than this is a repository whose users will experience slowness.

Separate limits, hit by a different kind of problem.

Open pull requests targeting the same branch: 1,000. Hit by automation opening pull requests faster than they merge.

Merge rate: roughly one merged pull request per minute is the recommended maximum for a single repository.

Diff size: a pull request diff is limited to 20,000 lines or 1 MB of raw content in total. A single file’s diff is limited to 20,000 lines or 500 KB, with automatic loading up to 400 lines or 20 KB.

Files per diff: 300, of which 25 are rendered as rich diffs.

Commits shown: compare and pull request views show 250 commits; the commits tab shows up to 10,000. Rebase-and-merge is limited to 100 commits.

Why these matter organisationally: they define what a reviewable pull request is. A change touching 800 files is not reviewable on GitHub regardless of policy, and a team producing them is producing changes nobody can actually review. That is a workflow problem the limits merely expose.

An organisation cannot exceed 100,000 repositories, and GitHub displays a warning banner at 50,000.

This is a real ceiling for organisations that create repositories programmatically — one per customer, one per service, one per experiment. It is also the point at which the tooling around a repository fleet stops being optional; see repository fleet management.

Size is not one number. A repository can be perfectly healthy on the metric people quote and unusable on one they never check.

DimensionMeasureSymptom when large
History size.git directory sizeSlow clone, slow fetch
Working tree sizeFile count, checkout sizeSlow checkout, slow status
Commit countgit rev-list --count HEADSlow history traversal without a commit-graph
Ref countgit for-each-ref | wc -lSlow anything touching refs
Largest objectBlob sizePush refusal at 100 MB
Tree depth and widthDirectory structureHard limits at 50 and 3,000
Pack count.pack file countEvery object lookup multiplied
Churn rateCommits per dayFetch size, CI load

Diagnose the right dimension. A repository with 12 GB of history and 4,000 files needs partial clone. One with 400 MB of history and 600,000 files needs sparse checkout. The two are unrelated problems and the remedies do not overlap.

Terminal window
git count-objects -vH
git rev-list --count HEAD
git for-each-ref | wc -l
ls .git/objects/pack/*.pack 2>/dev/null | wc -l
git ls-files | wc -l

git count-objects -vH gives object counts and pack size in human units. The size-pack line is the number closest to what GitHub counts.

git-sizer is the purpose-built tool. It reports every dimension above, including tree depth and width and the largest objects, and flags values that are unusual. It is worth installing specifically for this.

The largest-blob listing is the fastest route to a cause:

Terminal window
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '$1=="blob" {print $3, $4}' \
| sort -rn | head -20

Run this before anything else. The answer is usually one directory, and knowing which one determines everything that follows.

What GitHub’s reported size does and does not mean

Section titled “What GitHub’s reported size does and does not mean”

A recurring source of confusion when comparing numbers.

The size GitHub reports is the compressed size on its side, in kilobytes, and it is what the API returns. It is not the same as your local .git size, and it is not the same as the working-tree size.

It can differ from your local number substantially. GitHub maintains the server-side repository with its own repacking schedule, so its packing is usually better than a developer’s clone that has not run maintenance. A repository reporting 2 GB from the API might be 3.5 GB in a poorly-maintained local clone.

It excludes some things people expect it to include. Git LFS objects are stored separately and billed separately; they are not part of the repository size in the sense the limit refers to. Release assets and packages are likewise separate.

Which cuts both ways. A repository under the size limit can still be miserable to work with because of a terabyte of LFS content, and the size metric will not show it. Check LFS storage separately in the billing view.

And the working tree is invisible to it entirely. A repository with a small history and half a million files reports a modest size and takes ten minutes to check out.

The practical rule: use the API size for fleet-wide ranking, because it is available without cloning and is comparable across repositories. Use local measurement for diagnosis, because it tells you which dimension is actually the problem.

One repository is a command. Four hundred is an API problem.

Repository size is available from the API. The repository object includes a size field, which lets you rank every repository in an organisation without cloning anything.

Rank and threshold. You do not need precision — you need to know which twenty repositories are worth looking at. A single API sweep produces that list.

Then clone only the outliers and run the detailed measurements on those.

Track it over time. A repository at 3 GB is not interesting; a repository that was 800 MB last quarter and is 3 GB now is very interesting, because the trend predicts when it becomes a problem.

Watch branch counts too. The 5,000-branch limit is reached silently by automation, and the first symptom is a bot failing to create a branch.

In the order that produces the most benefit for the least disruption.

  1. Find the cause. The largest-blob listing, the file count, the ref count. Do not remediate before you know which dimension is the problem.

  2. Stop the growth. A .gitignore rule, a fixed build step, a push ruleset blocking large files, a bot that deletes its branches. This is cheap, immediate, and it is what makes everything else worth doing.

  3. Apply client-side mitigation. Partial clone, sparse checkout and maintenance make a large repository workable without changing it. For many repositories this is the whole answer.

  4. Clean up refs. Deleting thousands of stale branches is safe, reversible for a while, and immediately improves every ref-touching operation.

  5. Consider splitting. If the repository is large because it contains unrelated things, extraction is a structural fix that no amount of Git tuning replaces.

  6. Rewrite history only if you must. It shrinks the repository and it invalidates every clone, every open pull request and every recorded SHA.

Branch count deserves separate treatment because it is the limit most often reached by accident and the one with the least obvious symptoms.

5,000 branches is the enforced ceiling. But the performance degradation starts long before that, and it affects operations people run constantly.

What creates them: dependency update bots opening a branch per update, CI systems creating a branch per build, per-environment deployment branches, release automation, and — most commonly — branches from merged pull requests that nobody deletes.

The symptoms: slow git fetch (every ref is negotiated), slow branch pickers in the interface, slow git branch -a, and slow clones. On the client, loose ref files accumulate until pack-refs runs.

Tags are the same problem with a longer memory. A repository tagging every build accumulates tags that are never deleted because deleting a tag feels destructive. Ten years of per-build tags is tens of thousands of refs fetched on every clone.

The remedies, in order:

  • Enable automatic branch deletion on merge. One setting, applied at the repository or organisation level, and it addresses the largest single source.
  • Make bots clean up after themselves. Most dependency tools close and delete their branches; verify yours does.
  • Audit stale branches periodically. A branch with no commits in a year and no open pull request is a candidate.
  • Reconsider per-build tags. A tag per release is useful; a tag per build is a ref-count problem with no consumer.
  • Use --no-tags in CI where tags are not read.
Terminal window
# Branches with no commits in the last year
git for-each-ref --sort=committerdate refs/remotes/origin \
--format='%(committerdate:short) %(refname:short)' | head -50

Deleting a branch is recoverable for a while — the commits remain reachable from the reflog and from any open pull request, and GitHub can restore a recently deleted branch. That makes bulk cleanup much less frightening than it feels, though it is still worth announcing before doing it at scale.

Read forward rather than as failure conditions, the limits tell you what GitHub expects a repository to be.

Under 10 GB, under 100 MB per file. A repository is source code and small assets, not an artifact store.

Under 5,000 branches. Branches are transient. Automation that creates them must delete them.

Under 3,000 entries per directory, under 50 deep. A repository holds a human-organised tree, not a generated one.

Reviewable diffs under 300 files. Changes are human-scale.

Under 100,000 repositories per organisation. Repository creation is a governed act, not an unbounded one.

A repository fighting these limits is usually being used for something else — as a build artifact store, as a database, as a queue. That is worth naming, because the remediation is then obvious.

Turn the limits into policy people can act on before the ceiling.

ThresholdAction
1 GBNote it. Enable maintenance for its developers.
2 GBInvestigate the cause. Recommend partial clone.
5 GBActive remediation. Assign an owner.
8 GBEscalate. Plan a structural fix.
2,500 branchesAudit branch-creating automation.
Any object over 50 MBReview before it reaches the hard limit.

The numbers are yours to choose, and choosing them is the point. A threshold nobody set is a threshold nobody notices crossing.

Automate the check. A scheduled job that reports repositories crossing a threshold turns this from an annual audit into a continuous one.

Give the report an owner. A dashboard nobody reads produces the same outcome as no dashboard.

Preventing problems rather than measuring them

Section titled “Preventing problems rather than measuring them”

Monitoring tells you a repository is unhealthy. Prevention stops it becoming so, and the controls are mostly things you can set once.

A push ruleset restricting file size. GitHub’s push rulesets can block pushes containing files above a threshold. Setting it well below the 100 MB hard limit — 25 MB, say — means a developer gets an immediate, actionable rejection rather than discovering the problem months later when it is in history. See enterprise rulesets.

Path restrictions. The same ruleset family can restrict which file paths may be pushed, which blocks the dist/ directory problem at source.

Automatic branch deletion on merge, set at the organisation level so new repositories inherit it.

A .gitignore in the repository template covering the build output conventions of your ecosystems. Most accidental commits are of files a template would have excluded.

A pre-commit hook or a CI check for file size, so the feedback comes before the push rather than at it.

These are cheap and they apply to repositories that do not exist yet, which is the property that makes prevention worth more than remediation. A control added to the template today applies to every repository created from tomorrow onward, forever, with no further effort. Remediation applies to one repository, once, at considerable cost.

The measurement is the easy half. Getting somebody to do something is the hard half, and most repository-health programmes die here.

Report to owners, not to a central team. A central list of unhealthy repositories becomes a central backlog nobody owns. A message to the team that owns a repository, naming their repository and the specific cause, gets acted on.

Name the cause, not the metric. “Your repository is 6 GB” prompts a shrug. “assets/renders/ contains 4.2 GB across 340 committed video files, added between March and August” prompts a fix.

Give the remediation. Most teams do not know that partial clone exists or that deleting files does not shrink history. A three-line recommendation converts a report into a change.

Include the trend. “Growing 400 MB per month, will reach the 10 GB limit in September” is a deadline, and deadlines get scheduled.

Escalate on a schedule, not on severity alone. A repository that has been over threshold for six months with no action is a different conversation from one that crossed last week, and the escalation path should reflect that.

Track remediation, not just measurement. The number that says whether the programme works is how many repositories improved, not how many were measured.

Treating size as one number. History size and working-tree size are different problems.

Remediating before diagnosing. Rewriting history when the problem was file count wastes an enormous amount of goodwill.

Rewriting history before stopping the growth. You will do it twice.

Ignoring branch counts. The 5,000 limit is reached by automation, silently.

Assuming limits are static. They change; verify before relying on a number.

Only tracking the largest repositories. The fastest-growing ones are where new problems are.

Force-pushing a rewrite without a coordination plan. Every clone, every open pull request, every recorded SHA.

Not setting thresholds. Without them there is no moment at which anybody acts.

Client-side mitigation has a ceiling. Past it, the question is structural.

The signal is not size — it is independence. A repository that is large because it holds one genuinely large system is a monorepo, and the techniques in this cluster are how you operate it. A repository that is large because it accumulated several unrelated systems is a filing accident, and no tuning fixes that.

The questions worth asking:

  • Do the parts share a build, or merely a directory?
  • Do they release together, or independently?
  • Would a change to one ever require a change to another?
  • Does anybody work across the boundary in practice?

If the answers are all “no”, extraction is straightforward — the parts are already independent and only the repository disagrees.

If the answers are mixed, extraction is expensive and frequently regretted. Splitting a repository whose parts genuinely depend on each other replaces a size problem with a coordination problem, and coordination problems are worse. Monorepo versus polyrepo covers that trade in full.

Extraction preserves history if you want it to, at the cost of a filtering operation over the whole history — which is a rewrite, with all the coordination that implies. Extracting without history is a single commit and no coordination, and for many cases it is the better trade. Decide deliberately rather than defaulting to “preserve everything”.

Do not split as a performance fix alone. If the parts belong together, you will pay for the split every day and the size problem will reappear in the largest fragment.

A repository has several sizes, and each has its own limit, its own symptom, and its own remedy. Health monitoring means measuring all of them, setting thresholds well below the ceilings, and acting on growth rate rather than absolute size. Client-side mitigation makes a large repository workable; only structural change makes it small.

  • GitHub enforces 10 GB per repository, 100 MB per object, 2 GB per push, 5,000 branches, 3,000 entries per directory and 50 levels of depth
  • Pull request diffs are limited to 20,000 lines or 1 MB, 300 files, and 250 commits in compare views
  • An organisation cannot exceed 100,000 repositories, with a warning at 50,000
  • Repository size is several independent dimensions with different symptoms and different remedies
  • git count-objects -vH, git-sizer and a largest-blob listing diagnose a single repository
  • The API’s repository size field lets you rank a whole fleet without cloning
  • Growth rate predicts trouble better than absolute size
  • Stop growth first, mitigate client-side second, restructure third, rewrite history last
  • History rewriting is a coordinated organisational operation, not a command
  • Setting thresholds below the ceilings is what creates a moment at which somebody acts

Use repositories you have access to.

  1. Pick your largest repository. Run git count-objects -vH, git rev-list --count HEAD, git for-each-ref | wc -l, and count pack files.

  2. Run the largest-blob listing. Identify the top five and classify each: source, input, output, or accident.

  3. Against each limit in this article, state where the repository sits as a percentage.

  4. Which dimension is closest to its limit? Predict: which of partial clone, sparse checkout, maintenance or restructuring would help most?

  5. Find the repository’s branch count. Identify how many were created by automation and how many of those are stale.

  6. Using the API, list every repository in one organisation ranked by size. Predict: did you know the top five?

  7. Propose thresholds for your organisation, and name who would receive the alert.

  8. For the largest repository, write a one-paragraph remediation plan in the order given above.

Engineering Team Onboarding SystemA 30-day Git and GitHub programme with standards templates, assessments and governance checklists.