Why we built for polyrepos
Monorepos solve a tooling problem by creating a platform problem. We solved the tooling problem instead.
The monorepo trend has a seductive pitch: put everything in one repo and you get atomic changes across services, a single CI pipeline, shared tooling, and no dependency versioning. Google does it. Meta does it. It must be the right approach.
It is not. Or rather — it is the right approach if you are Google or Meta, with dedicated teams building custom VCS tooling, custom build systems, and custom CI infrastructure. For everyone else, a monorepo trades one set of problems for a worse set.
The monorepo tax
A monorepo with 20 services needs a build system that understands which services are affected by a change. That means Bazel, Nx, Turborepo, or a custom solution. These tools are complex. They have configuration files, caching strategies, dependency graphs, and their own failure modes. The team that was supposed to be shipping product is now debugging why Bazel's remote cache returned a stale artifact.
Git itself struggles. Clone times grow. Checkout is slow. Blame takes seconds instead of milliseconds. Partial clone and sparse checkout help, but they are workarounds for a problem that does not exist with smaller repos. You are fighting the version control system instead of using it.
CI becomes a bottleneck. A change to a shared library triggers builds for every consumer. If the build system's change detection is wrong — and it will be, eventually — you either rebuild everything (slow) or miss an affected service (broken). A flaky test in one team's directory blocks every other team from merging. The monorepo that was supposed to simplify CI has made it everyone's problem.
The platform engineering perspective
If you work in platform engineering, you see the monorepo from the other side. You are the team that has to make it work. You maintain the build system config. You debug the CI pipeline when it takes 45 minutes. You handle the merge queue when 15 teams are trying to land changes on the same branch. You write the custom tooling that makes a 500-service monorepo usable.
This is a full-time job. Not a side project, not a part-time responsibility — a dedicated platform team maintaining the monorepo infrastructure. That is the hidden cost. The teams using the monorepo see simplicity. The platform team sees a system that requires constant maintenance to keep from collapsing under its own weight.
Most companies do not have a dedicated monorepo platform team. They have a few engineers who "also handle CI." The result is a monorepo that works adequately until it does not, and then nobody knows how to fix it because the tooling is too complex for part-time maintenance.
What polyrepos actually need
The legitimate problems that monorepos solve are:
- ‣ Cross-repo dependency testing — when a shared library changes, consumers should be tested.
- ‣ Visibility into dependencies — you need to know what depends on what.
- ‣ Consistent CI across services — teams should not reinvent pipeline config for every repo.
- ‣ Shared policies — security scanning, license checks, and image signing should apply everywhere.
These are real problems. But they are tooling problems, not repository structure problems. You do not need to put all code in one repo to solve them. You need a platform that understands relationships between repos.
How gittan solves them
The dependency graph is visible in the UI. Declare dependencies between repos, and you can see which repos depend on which and understand the blast radius of a change before you push. This is the same information a monorepo's build graph provides, but across separate repos — without automatic triggering that breaks ownership.
Team templates provide consistent CI without copy-pasting config. A team defines their pipeline steps once, and every repo in the team inherits them. Changes to the template apply to all repos immediately.
Org policies inject security scanning, license compliance, and image signing into every pipeline in the organization. No repo can opt out. The platform team controls the policies centrally.
The benefits you keep
With polyrepos you get things that monorepos make hard:
- ✓ Clear ownership — each repo belongs to one team. No directory-level ACLs, no "who owns this folder."
- ✓ Independent deployment — a team deploys their service without coordinating with every other team in the repo.
- ✓ Fast git operations — small repos clone, checkout, and blame in milliseconds. No sparse checkout hacks.
- ✓ Isolated CI — a flaky test in one repo does not block another team. A broken build does not create a merge queue.
- ✓ No specialized tooling — standard git, standard containers, no Bazel configs to maintain.
You can still use a monorepo
Nothing in gittan prevents you from putting all your code in one repo. The pipeline runs, the gated push works, the policies apply. If your team wants a monorepo, it will work.
But gittan is not optimized for it. There is no built-in change detection to figure out which service in the repo was affected. There is no Bazel integration. The dependency graph, the team-per-repo ownership model, the per-repo team metrics — all of these assume that one repo is one deployable thing owned by one team.
We built for the model we believe in. If you share that belief, gittan will feel natural. If you are committed to a monorepo, you will be underusing the platform.
The right abstraction
A monorepo puts the abstraction boundary at the repo level: everything inside is one unit. This works at Google because Google built the tooling to make it work. For everyone else, it means fighting git, fighting CI, and fighting the build system to maintain an abstraction that the tooling does not natively support.
Polyrepos put the abstraction boundary at the service level: each service is its own unit, with its own repo, its own pipeline, and its own team. The relationships between services are explicit and visible in the dependency graph. No custom build system required.
Monorepos are a workaround for platforms that do not understand cross-repo relationships. gittan understands them. That makes the workaround unnecessary.