---
id: 773766eac9e6ff3c46bd686d78539aed
author: Lemon Mint
title: 'Het Git Monorepo heroverwogen: een CAS-native architectuur voor mensen en coding agents'
description: Hoe bouw je een zero-daemon, CAS-native monorepo op standaard Git met behulp van parentless trunk-snapshots, geïsoleerde workspace-referenties, in-memory merge-trees, hiërarchische OWNERS en O(delta) mirror-synchronisatie.
language: nl
date: 2026-10-30T01:17:15.292299Z
path: /blog/posts/rethinking-the-git-monorepo-a-cas-native-architecture-for-humans-and-coding-agents-zfd3d7931
category: lowlevel
---

Elke groeiende engineeringorganisatie kiest uiteindelijk tussen veel repositories en één. Begin met tientallen kleine repositories, en het leven voelt aanvankelijk eenvoudig aan. Elke bibliotheek heeft een eigen issuetracker, een eigen CI-pipeline en een `git clone` die in enkele seconden klaar is. Vervolgens wijzig je een struct in een kernnetwerkpakket en besteed je de rest van de middag aan het openen van version-bump pull requests in zes afhankelijke repositories, terwijl je wacht tot de CI-wachtrijen in topologische volgorde zijn leeggelopen.

Verplaats alles naar een traditionele Git-monorepo, en je ruilt coördinatie tussen repositories in voor de omvang van de repository. Naarmate de commitgeschiedenis groeit en binaire bestanden zich in eerdere revisies opstapelen, wordt `git fetch` trager. Werkmappen zwellen op tot gigabytes. Al snel stelt iemand voor om een aangepaste virtuele-bestandssysteemdaemon, een speciale code review-server of een vloot externe buildworkers op te zetten, alleen om de dagelijkse ontwikkeling draaglijk te houden.

Wanneer autonome codeeragents zich bij het team voegen, worden beide faalwijzen erger. Polyrepos ontnemen een agent het zicht op aanroepers in andere projecten en dwingen hem om credentials over repositories heen te beheren. Traditionele monorepo's verzwelgen bij elke taakinitialisatie de schijfruimte van de container van de agent en de netwerkbandbreedte, terwijl gelijktijdige agents die dezelfde branch gebruiken eindeloze rebase-races veroorzaken.

Geïnspireerd door de `google3`-architectuur hebben we single-trunk atomaire wijzigingen, subseconde sparse onboarding, hiërarchische `OWNERS`-governance en zelfstandige open-sourcespiegels gebouwd, met standaard Git-plumbing en goedkope objectopslag.

Het ontwerp heeft noch een aangepaste bestandssysteemdaemon nodig, noch een externe review-appliance van derden, omdat het Git behandelt als een Merkle-tree Content-Addressable Store (CAS).

## Het misverstand in het hart van trage Git-monorepo's

Wanneer ontwikkelaars klagen dat Git niet schaalt naar grote monorepo's, lopen ze zelden tegen een limiet van Git's objectdatabase aan. Binnen `.git/objects` slaat Git drie onveranderlijke primitieven op, gesleuteld op hun SHA-1 (of SHA-256) inhoudshash:

1. **`blob`**: De ruwe bytes van de inhoud van één bestand.
2. **`tree`**: Een directorylijst die bestandsnamen en rechten koppelt aan `blob`- of onderliggende `tree`-hashes.
3. **`commit`**: Een kleine tekstheader (doorgaans 200 tot 300 bytes) die verwijst naar één root-`tree`-hash, nul of meer parent-`commit`-hashes en auteursmetadata.

Als een monorepo 50.000 bestanden bevat en je wijzigt één regel in één bestand, dan hergebruikt de nieuwe commit de exacte `blob`- en `tree`-hashes voor de overige 49.999 bestanden.

Een traditionele Git-monorepo voelt zwaar aan omdat de conventie één enkele referentie, `refs/heads/main`, belast met drie conflicterende taken:

- Hij bevat de **volledige, verenigde directoryboom** van elk project.
- Hij sleept de **volledige, aan parents gekoppelde keten van historische commits** mee tot aan de eerste dag.
- Hij bevindt zich binnen `refs/heads/*`, de namespace die Git-servers standaard adverteren en waarover ze onderhandelen bij elke `git clone` en `git fetch`.

Telkens wanneer een ontwikkelaar of CI-runner `refs/heads/main` ophaalt, onderhandelen client en server over de bereikbaarheid in de commitgraaf over duizenden historische revisies. Als iemand drie jaar geleden per ongeluk een archief van 100 MB heeft gecommit en het de volgende dag heeft verwijderd, hangt de `blob` nog steeds aan een historische `tree` die bereikbaar is vanaf `refs/heads/main`.

## Het referentie-namespace scheiden

Git's wire protocol adverteert tijdens een standaard clone alleen `refs/heads/*` en `refs/tags/*`. Referenties onder aangepaste namespaces, zoals `refs/trunk/*` of `refs/ws/*`, staan in exact dezelfde objectopslag op de server, profiteren van dezelfde packfile-deltacompressie en kunnen expliciet worden opgehaald of gepusht, maar ze brengen geen overhead met zich mee bij standaard clones.

Het verdelen van deze taken over afzonderlijke referenties binnen één repository verwijdert het knelpunt:

```
refs/heads/main               Alleen control plane (~20 KB: regels, .gitconfig, synchronisatieconfiguratie, scripts)
refs/trunk/main               Parentloze snapshotcommit die verwijst naar de nieuwste verenigde root-tree
refs/trunk/history            Lineaire, aan parents gekoppelde commitketen die exact dezelfde root-tree-SHA-1 deelt
refs/ws/<actor>/<id>          Geïsoleerde werkruimte-snapshots (changelists) per ontwikkelaar of agent
refs/ws/cl/<change-id>/<tip>  Onveranderlijke patchsets: elke tip van een wijziging die ter review is ingediend
refs/heads/land/<id>          Tijdelijke merge-queue-trigger die direct na landing wordt verwijderd
```

### 1. Het control plane (`refs/heads/main`)

De standaardbranch bevat geen applicatiebroncode. Hij bevat alleen de bedrijfsregels van de repository (`AGENTS.md`, `README.md`), een gedeelde `.gitconfig` met workflow-aliassen, de Starlark-synchronisatieconfiguratie (`sync.star`) en server-side mergescripts. Het clonen van de standaardbranch transfereert ongeveer 20 KB en is in milliseconden klaar.

### 2. De parentloze trunk-snapshot (`refs/trunk/main`)

`refs/trunk/main` wijst naar de volledige, verenigde broncodeboom van de hele organisatie (`//web/*`, `//net/*`, `//ipc/*`, `//storage/*`, `//crypto/*`, `//ai/*`, `//devtools/*`, `//corp/*`, `//third_party/*`, `//experimental/*`), maar het commitobject wordt aangemaakt **zonder een `-p`-parentvlag**.

Omdat `refs/trunk/main` nul parents heeft (`parents = []`), vereist het ophalen ervan geen onderhandeling over de commitgraaf. De Git-server doorloopt de geschiedenis niet; hij stuurt de enkele head-commit en de huidige directory-`tree`-objecten. Historische binaire bestanden of bestanden die vorige maand zijn verwijderd, zijn niet bereikbaar vanaf `refs/trunk/main` en worden nooit overgedragen.

### 3. De lineaire auditgeschiedenis (`refs/trunk/history`)

Ontwikkelaars hebben nog steeds `git blame` en `git log` nodig bij het onderzoeken van regressies. Telkens wanneer een wijziging landt, maakt het mergescript twee commitobjecten aan die verwijzen naar exact dezelfde `$NEW_TREE`-SHA-1-hash:

```bash
# Parentloze snapshotcommit voor directe depth-1 werkruimte-checkouts
SNAP=$(git commit-tree "$NEW_TREE" -m "$RAW_MSG")

# Aan parent gekoppelde commit voor lineaire git log en git blame
HIST=$(git commit-tree "$NEW_TREE" -p "$OLD_HIST" -m "$RAW_MSG")

git push --atomic origin \
  --force-with-lease="refs/trunk/main:${OLD_MAIN}" \
  --force-with-lease="refs/trunk/history:${OLD_HIST}" \
  "${SNAP}:refs/trunk/main" \
  "${HIST}:refs/trunk/history"
```

Het bewaren van zowel `refs/trunk/main` als `refs/trunk/history` verdubbelt de opslagfootprint niet. Omdat beide commits naar dezelfde `$NEW_TREE`-hash verwijzen, dedupliceert Git's content-addressable store 100% van de `tree`- en `blob`-objecten. De extra opslagkosten van de parentloze snapshot bestaan uit slechts één commitheader van 250 bytes.

### 4. Geïsoleerde werkruimten (`refs/ws/<actor>/<id>`)

Werk-in-uitvoering-branches worden gepusht naar `refs/ws/<actor>/<id>` in plaats van naar `refs/heads/*`. Honderd codeeragents kunnen elke minuut checkpoints opslaan zonder de branch-dropdowns te vervuilen of `git fetch` voor menselijke ingenieurs te vertragen.

## Onboarding in vier stappen: hoe sparse + blobless checkout werkt

Met deze referentie-indeling installeert een nieuwe ontwikkelaar of een tijdelijke codeeragent-container geen aangepaste CLI of FUSE-driver. Onboarding bestaat uit vier stappen met standaard Git en de aliassen van de repository:

```bash
# 1. Clone het control plane van 20 KB met blobfiltering en sparse-modus ingeschakeld
git clone --filter=blob:none --sparse https://github.com/example-org/monorepo.git
cd monorepo

# 2. Laad repository-aliassen en koppel refs/trunk/*
git config include.path ../.gitconfig
git trunk-init

# 3. Geef de domeinmappen op die nodig zijn voor deze taak
git sparse-checkout set net/relay ipc/codec

# 4. Maak een geïsoleerde werkruimte aan op basis van de nieuwste trunk-snapshot
git ws-new alice/optimize-framing
```

Elke stap haalt alleen op wat nodig is:

1. `git clone --filter=blob:none --sparse` downloadt het control plane van 20 KB op `refs/heads/main`.
2. `git trunk-init` configureert `+refs/trunk/*:refs/remotes/origin/trunk/*` en voert een blobless fetch uit. Git downloadt de `tree`-metadata voor `refs/trunk/main` (doorgaans enkele tientallen kilobytes) en **nul bestands-blobs**.
3. `git sparse-checkout set net/relay ipc/codec` instrueert Git's cone-mode indexfilter om alleen de root-directory en die twee subbomen te materialiseren.
4. `git ws-new` schakelt over naar een nieuwe branch gebaseerd op `origin/trunk/main`. Git vraagt vervolgens alleen op verzoek de `blob`-objecten op binnen `net/relay` en `ipc/codec`. De overige 95% van de monorepo wordt nooit naar schijf gedownload.

### Toekomstige horizon: `refs/trunk/main` mounten als virtueel bestandssysteem (VFS)

Sparse checkout met blobless clones dekt de dagelijkse workflows zonder extra clienttooling. Omdat `refs/trunk/main` al een pure Merkle-tree in een Content-Addressable Store is, biedt de architectuur ook een directe basis voor een optioneel userspace virtueel bestandssysteem (VFS) via FUSE op Linux, FileProvider op macOS of ProjFS op Windows:

1. **Onmiddellijke mount van de volledige boom**: Een lichtgewicht lokale mount-daemon leest het root-`tree`-object van `refs/trunk/main` en projecteert de volledige monorepo-directoryhiërarchie binnen milliseconden in de werkmap, waardoor `git sparse-checkout set` niet meer nodig is.
2. **Blob-faulting op verzoek**: Bestandsinhoud blijft virtueel totdat een compiler, IDE of agent een `open(2)`- of `read(2)`-systeemaanroep uitvoert. De daemon haalt dan de overeenkomstige SHA-1 `blob` op bij het CAS-eindpunt en cachet die op lokale NVMe-opslag.
3. **Copy-on-write-overlay voor werkruimten**: Lokale bewerkingen worden naar een lichtgewicht bovenste overlaylaag geschreven. Het uitvoeren van `git ws-save` hasht alleen de gewijzigde paden in die overlay, schrijft de bijgewerkte `blob`- en `tree`-objecten en pusht de resulterende commit naar `refs/ws/<actor>/<id>` zonder ongewijzigde mappen te scannen.

## Het changelist-model op pure Git: landing in het geheugen aan de serverzijde

In een traditionele Git-workflow levert het samenvoegen van een featurebranch ofwel een merge-commit met twee parents op, waardoor de geschiedenis niet-lineair wordt, ofwel moeten ontwikkelaars handmatig rebasen en racen tegen gelijktijdige pushes naar `main`.

Geïnspireerd door het Changelist (CL)-model van `google3` vertegenwoordigt elke werkruimte in onze architectuur een **enkele atomaire wijziging** ten opzichte van de trunk-snapshot. Terwijl een ontwikkelaar of agent lokaal werkt in `ws/alice/optimize-framing`, kan hij tien incrementele commits maken (`wip`, `fix tests`, `lint`). Wanneer de wijziging klaar is om te landen, reduceert de server het werkruimte-delta tot één lineaire trunk-revisie, zonder ooit een werkmap uit te checken op de CI-runner.

Zo voert het landingsscript (`land.sh`) in het geheugen uit met Git 2.40+ plumbing:

```bash
# 1. Bepaal de root-snapshotcommit waarvan de werkruimte is afgetakt
BASE=$(git rev-list --max-parents=0 "$WORK_SHA")

BASE_TREE=$(git rev-parse "${BASE}^{tree}")
MAIN_TREE=$(git rev-parse "${OLD_MAIN}^{tree}")
WORK_TREE=$(git rev-parse "${WORK_SHA}^{tree}")

# 2. Bereken de samengevoegde root-tree in het geheugen zonder working-tree-checkout
if [ "$MAIN_TREE" = "$BASE_TREE" ]; then
  NEW_TREE="$WORK_TREE"
elif OUT=$(git merge-tree --write-tree --merge-base="$BASE_TREE" "$MAIN_TREE" "$WORK_TREE"); then
  NEW_TREE=$(head -n1 <<<"$OUT")
else
  echo "Merge conflict detected; run git ws-sync to rebase." >&2
  exit 1
fi
```

In stap 2:

- Ongeacht hoeveel lokale commits er op de werkruimte-branch zijn gemaakt, vergelijkt het script slechts drie tree-hashes: de oorspronkelijke basissnapshot (`$BASE_TREE`), de huidige trunk-snapshot (`$MAIN_TREE`) en de uiteindelijke werkruimte-tree (`$WORK_TREE`).
- Als `refs/trunk/main` in `//web/blog` is verder gegaan terwijl Alice `//net/relay` bewerkte, berekent `git merge-tree --write-tree` de gecombineerde root-tree `$NEW_TREE` direct binnen de Git-objectopslag, in milliseconden en zonder bestanden naar het bestandssysteem van de runner te schrijven.
- Nadat de onderstaande beleidscontroles zijn geslaagd, werkt één `git push --atomic` met `--force-with-lease` `refs/trunk/main` bij en voegt één lineaire commit toe aan `refs/trunk/history`, waarna de landingstrigger `refs/heads/land/<id>` wordt verwijderd.

## Architectuurgrenzen afdwingen: viervoudige zichtbaarheid en hiërarchische `OWNERS`

Het consolideren van code in één repository werkt alleen als de grenzen tussen domeinen mechanisch worden gehandhaafd voordat een commit kan landen. Direct nadat `git merge-tree --write-tree` `$NEW_TREE` heeft geproduceerd (en voordat `git push --atomic` de trunk bijwerkt), voert ons beleidsmechanisme twee O(delta)-controles uit op `git diff-tree "$MAIN_TREE" "$NEW_TREE"`.

### 1. Viervoudige domeinzichtbaarheid

De root-tree is verdeeld in vier zichtbaarheidsniveaus:

| Directory-prefix                                                                       | Niveau            | Toegestane imports                               | Verboden imports               | `OWNERS`-poort         |
| :------------------------------------------------------------------------------------- | :---------------- | :----------------------------------------------- | :----------------------------- | :--------------------- |
| `//web/*`, `//net/*`, `//ipc/*`, `//storage/*`, `//crypto/*`, `//ai/*`, `//devtools/*` | Public Production | Public Production, `//third_party/*`             | `//corp/*`, `//experimental/*` | Afgedwongen            |
| `//third_party/*`                                                                      | External Vendor   | Zelfstandig of `//third_party/*`                 | Alle domeinen die geen `third_party` zijn | Afgedwongen |
| `//corp/*`                                                                             | Internal Private  | Public Production, `//corp/*`, `//third_party/*` | `//experimental/*`             | Afgedwongen            |
| `//experimental/<actor>/*`                                                             | Sandbox           | Elk pakket dat geen `corp` is                    | `//corp/*`                     | Uitgezonderd (automatisch goedgekeurd) |

Als een publiek pakket in `//web/*` probeert een intern pakket uit `//corp/*` of een niet-afgestudeerd prototype uit `//experimental/*` te importeren, breekt de landingsjob af voordat `refs/trunk/main` wordt aangeraakt.

### 2. Hiërarchische `OWNERS`-verificatie zonder Gerrit-server

De verifier handhaaft directory-niveau `OWNERS`-bestanden met overervingsregels:

- Voor elk pad dat is gewijzigd tussen `$MAIN_TREE` en `$NEW_TREE`, loopt de verifier omhoog vanaf de directory van het bestand tot `//OWNERS`.
- Elk `OWNERS`-bestand kan `set noparent` declareren om geërfde goedkeuringen van bovenliggende directories te blokkeren, of `per-file *.sql=@dba-team` om goedkeuringen te beperken op bestandsnaamglob.
- **Anti-manipulatie-invariant**: Bestaande `OWNERS`-bestanden worden altijd gelezen uit `$MAIN_TREE` (de huidige trunk-tree), nooit uit `$NEW_TREE`. Een niet-goedgekeurde auteur kan zichzelf geen toestemming geven door `OWNERS` te bewerken in dezelfde werkruimte-commit.
- **Sandbox-uitzondering**: Elk pad onder `//experimental/<actor>/*` omzeilt de `OWNERS`-controles, zodat ingenieurs en agents vrijuit kunnen prototypen met `git ws-exp <actor> <project>` en het pakket later kunnen laten doorstromen naar een productiedomein met `git ws-graduate`.

## Code review zonder wrijving: tijdelijke PR's en snapshot-diffs

Omdat `refs/heads/main` alleen het control plane bevat en `refs/trunk/main` een parentloze commit is, zou het direct openen van een standaard GitHub Pull Request vanaf een werkruimte-branch naar `main` ongerelateerde geschiedenissen vergelijken. We hebben dit opgelost met drie reviewworkflows, afgestemd op verschillende wijzigingen:

1. **Directe two-dot diffs van werkruimten (`git ws-save` + Edge Diff Router)**
   Het pushen van een werkruimte met `git ws-save` maakt `refs/ws/<actor>/<id>` beschikbaar via een lichtgewicht edge-worker. Deze worker herleidt `refs/trunk/main` en de werkruimtereferentie naar hun commit-SHA's en stuurt door naar de two-dot compare-weergave van GitHub (`/compare/<trunk-sha>..<ws-sha>`). Bij kleine patches of door agents gegenereerde wijzigingen kunnen reviewers de diff inspecteren en direct in de webinterface op **Land** klikken.
2. **Tijdelijke GitHub Pull Requests (`git ws-pr`)**
   Voor regel-voor-regel-review op GitHub pusht `git ws-pr` de root-snapshotcommit (`$(git rev-list --max-parents=0 HEAD)`) naar `refs/heads/base/<id>` en de werkruimte-tip naar `refs/heads/review/<id>`. Vervolgens opent het een GitHub Pull Request die `review/<id>` vergelijkt met `base/<id>`. Omdat de basisbranch de snapshot is waarvan de werkruimte is afgetakt, toont het tabblad **Files changed** van GitHub alleen het werkruimte-delta. Wanneer de reviewer op **Merge pull request** klikt, promoveert een webhook de head-commit naar `refs/heads/land/<id>`, voert de in-memory landingspipeline uit en verwijdert automatisch zowel `refs/heads/base/<id>` als `refs/heads/review/<id>`.
3. **Pull Requests op externe spiegels**
   Open-sourcebijdragers hoeven nooit de monorepo-workflow te leren. Zij forken en openen pull requests op zelfstandige spiegelrepositories, zoals gebruikelijk.

### Wijzigingen, patchsets en behoud van goedkeuringen

Een branchnaam kan een wijziging niet identificeren. Een agent kan zijn branch hernoemen, de tip amenden of meerdere keren rebasen op een nieuwere trunk voordat hij landt. Zoals bij Gerrit identificeert de workflow een wijziging via een `Change-Id: I<40 hex>`-trailer in het commitbericht van de tip. `git ws-save`, `git ws-pr` en `git ws-land` voegen de trailer toe wanneer de tip er geen heeft en behouden deze anders, zodat de id amends, rebases en hernoemingen overleeft. De gelande trunk-commit behoudt de trailer en registreert het nummer van zijn review-pull request, zodat `git trunk-cl <number|Change-Id>` hem vindt in `refs/trunk/history`.

Elke tip die ter review wordt ingediend, wordt een patchset. `git ws-pr` pusht deze naar de onveranderlijke referentie `refs/ws/cl/<change-id>/<tip>`, naast de reviewbranches. Opnieuw `git ws-pr` uitvoeren na een amend of een `git ws-sync` voegt een patchset toe aan dezelfde pull request. `git ws-ps` toont de patchsets van de huidige wijziging, en `git ws-psdiff <a> <b>` drukt de interdiff tussen twee ervan af, zodat een reviewer alleen leest wat sinds de vorige ronde is veranderd.

GitHub koppelt een goedkeuring aan één enkele commit, dus elke rebase zou normaal gesproken een nieuwe review vereisen. De landingspipeline accepteert ook een goedkeuring die is gegeven aan een andere patchset van dezelfde wijziging, wanneer beide patchsets dezelfde patch maken op hun eigen basissnapshot. Hij vergelijkt `git patch-id --verbatim` over de volledige binaire diff. Een schone rebase op een nieuwere trunk landt zonder nieuwe review, terwijl elke inhoudswijziging, inclusief een witruimte- of bestandsmodus-wijziging, een nieuwe goedkeuring vereist.

## O(delta) bidirectionele synchronisatie en afzonderlijke productiebranches

Veel organisaties publiceren individuele pakketten als zelfstandige GitHub-repositories, zodat externe gebruikers `go get` of `cargo install` kunnen uitvoeren, of één project kunnen forken zonder de monorepo aan te raken. Sommige projecten, zoals statische blogs of webapps met releasepoorten, onderhouden ook een aparte `production`-branch naast `main`.

Onze Starlark-synchronisatiemachine verwerkt zowel uitgaande exports als inkomende bijdragen in **O(delta)**-tijd, evenredig aan de bestanden die in een commit zijn gewijzigd in plaats van aan de omvang van de monorepo:

- **Blobless index-splicing**: Bij het in- of uitsplitsen van een subboom van een spiegelrepository in of uit de root-tree van de monorepo, zet de machine `GIT_NO_LAZY_FETCH=1` en voert `git update-index --index-info` uit, gevolgd door `git write-tree --missing-ok`. Ongewijzigde subbomen en blobs blijven via SHA-1 gerefereerd en worden nooit naar de CI-runner gedownload.
- **Prefetching van alleen delta-blobs**: Voordat een geëxporteerde commit naar een zelfstandige spiegel wordt gepusht, voert het script `git diff-tree -r --diff-filter=AM "$ext_parent" "$export_tree"` uit en prefetcht het alleen de oude en nieuwe blobs van de gewijzigde bestanden via `git cat-file --batch`. Git kan dan een dunne deltapack berekenen en in enkele seconden naar de spiegelrepository pushen.
- **Behoud van workflow- en releasebranches**: Tijdens uitgaande export behoudt de synchronisatie-engine de eigen `.github/workflows/`-directory van elke zelfstandige repository, zodat publieke CI-runners de projecttests op de spiegel uitvoeren. Elke workflow in de Starlark-configuratie richt zich op een expliciete branch, zoals `main`, wat releasepoorten behoudt voor repositories die vanuit een aparte `production`-branch deployen. Landingen in de monorepo synchroniseren met de `main`-branch van de spiegel, en geautomatiseerde metadata- of vertaalcommits op `main` synchroniseren via webhook terug naar `refs/trunk/main`. Het promoveren van `main` naar `production` blijft een expliciete releasestap.

## Afsluitende gedachten

Deze architectuur behandelt de objectopslag van Git als een Content-Addressable Store, scheidt `refs/heads/main` van `refs/trunk/main` en `refs/trunk/history`, identificeert elke wijziging via een `Change-Id` met onveranderlijke patchsets, en voegt bomen samen in het geheugen met `git merge-tree --write-tree`. De onboardingkosten groeien met de directories die een taak nodig heeft, en de kosten van landen en spiegelsynchronisatie groeien met de omvang van een wijziging, niet met de omvang van de repository.
