---
id: 773766eac9e6ff3c46bd686d78539aed
author: Lemon Mint
title: 'Przemyślenie monorepo Git: architektura natywna dla CAS dla ludzi i agentów kodujących'
description: Jak zbudować monorepozytorium bez demona, natywnie oparte na CAS, na standardowym Gicie, z wykorzystaniem bezrodzicielskich migawek gałęzi głównej (parentless trunk snapshots), izolowanych referencji przestrzeni roboczych, drzew scaleń w pamięci (in-memory merge trees), hierarchicznych plików OWNERS oraz synchronizacji lustrzanych o złożoności O(delta)
language: pl
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
---

Każda rozwijająca się organizacja inżynierska prędzej czy później staje przed wyborem między wieloma repozytoriami a jednym. Na początku, gdy istnieje kilkadziesiąt małych repozytoriów, życie wydaje się proste. Każda biblioteka ma własny tracker zgłoszeń, własny potok CI oraz polecenie `git clone`, które kończy się w ciągu kilku sekund. Potem zmieniasz strukturę w kluczowym pakiecie sieciowym i spędzasz resztę popołudnia na otwieraniu pull requestów z podbiciem wersji w sześciu zależnych repozytoriach, czekając, aż kolejki CI opróżnią się w kolejności topologicznej.

Jeśli przeniesiesz wszystko do tradycyjnego monorepozytorium Git, wymienisz koordynację między repozytoriami na rozmiar repozytorium. Wraz z pogłębianiem się historii commitów i gromadzeniem się binarnych zasobów w poprzednich rewizjach `git fetch` zaczyna zwalniać. Drzewa robocze puchną do gigabajtów. Wkrótce ktoś zaproponuje uruchomienie niestandardowego demona wirtualnego systemu plików, dedykowanego serwera do przeglądu kodu lub floty zdalnych workerów budowania, wyłącznie po to, by codzienna praca pozostała znośna.

Gdy do zespołu dołączają autonomiczne agenty programistyczne, oba tryby awarii się nasilają. Polirepozytoria pozbawiają agenta wiedzy o wywołaniach z innych projektów i zmuszają go do jongllowania poświadczeniami między repozytoriami. Tradycyjne monorepozytoria z kolei pochłaniają dysk kontenera agenta i przepustowość sieci przy każdej inicjalizacji zadania, a współbieżni agenci pracujący na tej samej gałęzi tworzą niekończące się wyścigi przy rebase.

Inspirując się architekturą `google3`, zbudowaliśmy atomowe zmiany na jednym pniu, wdrażanie sparse trwające poniżej sekundy, hierarchiczne zarządzanie `OWNERS` oraz samodzielne otwarte lustra oparte na standardowej infrastrukturze Git i tanim magazynie obiektów.

Projekt nie wymaga ani niestandardowego demona systemu plików, ani zewnętrznego urządzenia do przeglądu kodu, ponieważ traktuje Git jako Content-Addressable Store (CAS) oparty na drzewie Merkle.

## Błędne przekonanie u podstaw powolnych monorepozytoriów Git

Gdy programiści narzekają, że Git nie skaluje się do dużych monorepozytoriów, rzadko trafiają na ograniczenie bazy obiektów Git. W katalogu `.git/objects` Git przechowuje trzy niezmienne prymitywy indeksowane skrótem SHA-1 (lub SHA-256) zawartości:

1. **`blob`**: surowa zawartość bajtowa pojedynczego pliku.
2. **`tree`**: listing katalogu, który odwzorowuje nazwy plików i uprawnienia na skróty `blob` lub podrzędnych `tree`.
3. **`commit`**: niewielki tekstowy nagłówek (zazwyczaj 200 do 300 bajtów) wskazujący na jeden korzeniowy `tree`, zero lub więcej rodzicielskich `commit` oraz metadane autora.

Jeśli monorepozytorium zawiera 50 000 plików, a ty edytujesz jedną linię w jednym pliku, nowy commit ponownie wykorzystuje dokładne skróty `blob` i `tree` pozostałych 49 999 plików.

Tradycyjne monorepozytorium Git wydaje się ciężkie, ponieważ konwencja przeciąża pojedyncze odniesienie, `refs/heads/main`, trzema sprzecznymi zadaniami:

- Przechowuje **całe zunifikowane drzewo katalogów** wszystkich projektów.
- Ciągnie za sobą **cały łańcuch historycznych commitów powiązanych przez rodziców** sięgający pierwszego dnia.
- Znajduje się w `refs/heads/*`, przestrzeni nazw, którą serwery Git domyślnie ogłaszają i negocjują przy każdym `git clone` oraz `git fetch`.

Za każdym razem, gdy programista lub runner CI pobiera `refs/heads/main`, klient i serwer negocjują osiągalność w grafie commitów przez tysiące historycznych rewizji. Jeśli ktoś przypadkowo zacommitował archiwum o rozmiarze 100 MB trzy lata temu i usunął je następnego dnia, jego `blob` nadal wisi na historycznym `tree`, osiągalnym z `refs/heads/main`.

## Rozdzielenie przestrzeni nazw referencji

Protokół sieciowy Git ogłasza podczas standardowego klonowania wyłącznie `refs/heads/*` i `refs/tags/*`. Referencje w niestandardowych przestrzeniach nazw, takich jak `refs/trunk/*` czy `refs/ws/*`, znajdują się w tej samej bazie obiektów na serwerze, korzystają z tej samej kompresji delta w packfile i mogą być pobierane lub wysyłane jawnie, a jednocześnie nie generują żadnego narzutu przy domyślnych klonowaniach.

Rozdzielenie tych zadań między osobne referencje w jednym repozytorium usuwa wąskie gardło:

```
refs/heads/main               Tylko płaszczyzna sterowania (~20 KB: reguły, .gitconfig, konfiguracja synchronizacji, skrypty)
refs/trunk/main               Bezrodzicielski commit migawki wskazujący na najnowsze zunifikowane korzeniowe drzewo
refs/trunk/history            Liniowy łańcuch commitów powiązanych przez rodziców, współdzielący dokładnie ten sam SHA-1 korzeniowego drzewa
refs/ws/<actor>/<id>          Izolowane migawki przestrzeni roboczych (listy zmian) dla każdego programisty lub agenta
refs/ws/cl/<change-id>/<tip>  Niezmienne zestawy poprawek: każdy wierzchołek zmiany przesłany do przeglądu
refs/heads/land/<id>          Efemeryczny wyzwalacz kolejki scalania, usuwany natychmiast po wylądowaniu
```

### 1. Płaszczyzna sterowania (`refs/heads/main`)

Domyślna gałąź nie zawiera kodu źródłowego aplikacji. Śledzi wyłącznie zasady działania repozytorium (`AGENTS.md`, `README.md`), wspólny `.gitconfig` z aliasami przepływu pracy, konfigurację synchronizacji w Starlark (`sync.star`) oraz skrypty scalania po stronie serwera. Klonowanie domyślnej gałęzi przesyła około 20 KB i kończy się w ciągu milisekund.

### 2. Bezrodzicielska migawka pnia (`refs/trunk/main`)

`refs/trunk/main` wskazuje na kompletne, zunifikowane drzewo źródeł całej organizacji (`//web/*`, `//net/*`, `//ipc/*`, `//storage/*`, `//crypto/*`, `//ai/*`, `//devtools/*`, `//corp/*`, `//third_party/*`, `//experimental/*`), jednak jego obiekt commit jest tworzony **bez flagi rodzica `-p`**.

Ponieważ `refs/trunk/main` nie ma rodziców (`parents = []`), jego pobranie nie wymaga negocjacji grafu commitów. Serwer Git nie przechodzi po historii; wysyła pojedynczy commit wierzchołkowy i jego bieżące obiekty `tree` katalogów. Historyczne binaria lub pliki usunięte w zeszłym miesiącu nie są osiągalne z `refs/trunk/main` i nigdy nie są przesyłane.

### 3. Liniowa historia audytu (`refs/trunk/history`)

Programiści nadal potrzebują `git blame` i `git log` podczas badania regresji. Za każdym razem, gdy zmiana zostaje wylądowana, skrypt scalający tworzy dwa obiekty commit wskazujące na **dokładnie ten sam** skrót SHA-1 korzeniowego drzewa `$NEW_TREE`:

```bash
# Bezrodzicielski commit migawki dla natychmiastowych checkoutów przestrzeni roboczej o głębokości 1
SNAP=$(git commit-tree "$NEW_TREE" -m "$RAW_MSG")

# Commit powiązany z rodzicem dla liniowego git log i 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"
```

Utrzymywanie zarówno `refs/trunk/main`, jak i `refs/trunk/history` nie podwaja zajmowanej pamięci. Ponieważ oba commity odwołują się do identycznego skrótu `$NEW_TREE`, magazyn obiektów Git deduplikuje w 100% obiekty `tree` i `blob`. Dodatkowym kosztem bezrodzicielskiej migawki jest jedynie nagłówek commitu o rozmiarze 250 bajtów.

### 4. Izolowane przestrzenie robocze (`refs/ws/<actor>/<id>`)

Gałęzie prac w toku są wysyłane do `refs/ws/<actor>/<id>`, a nie do `refs/heads/*`. Setka agentów programistycznych może zapisywać punkty kontrolne co minutę, nie zaśmiecając list gałęzi i nie spowalniając `git fetch` dla inżynierów.

## Wdrożenie w czterech krokach: jak działa sparse i blobless checkout

Przy takim układzie referencji nowy programista lub efemeryczny kontener agenta programistycznego nie instaluje żadnego niestandardowego CLI ani sterownika FUSE. Wdrożenie składa się z czterech kroków wykonywanych standardowym Gitem i aliasami repozytorium:

```bash
# 1. Sklonuj 20-kilobajtową płaszczyznę sterowania z filtrowaniem blobów i włączonym trybem sparse
git clone --filter=blob:none --sparse https://github.com/example-org/monorepo.git
cd monorepo

# 2. Załaduj aliasy repozytorium i podłącz refs/trunk/*
git config include.path ../.gitconfig
git trunk-init

# 3. Zadeklaruj katalogi domenowe potrzebne do tego zadania
git sparse-checkout set net/relay ipc/codec

# 4. Utwórz izolowaną przestrzeń roboczą opartą na najnowszej migawce pnia
git ws-new alice/optimize-framing
```

Każdy krok pobiera wyłącznie to, czego potrzebuje:

1. `git clone --filter=blob:none --sparse` pobiera 20-kilobajtową płaszczyznę sterowania z `refs/heads/main`.
2. `git trunk-init` konfiguruje `+refs/trunk/*:refs/remotes/origin/trunk/*` i wykonuje pobranie bez blobów. Git ściąga metadane `tree` dla `refs/trunk/main` (zazwyczaj kilkadziesiąt kilobajtów) i **zero blobów plików**.
3. `git sparse-checkout set net/relay ipc/codec` nakazuje indeksowi Git w trybie cone zmaterializować wyłącznie katalog główny oraz te dwa poddrzewa.
4. `git ws-new` przełącza się na nową gałąź opartą na `origin/trunk/main`. Następnie Git żąda na żądanie wyłącznie obiektów `blob` znajdujących się w `net/relay` i `ipc/codec`. Pozostałe 95% monorepozytorium nigdy nie jest pobierane na dysk.

### Perspektywa na przyszłość: montowanie `refs/trunk/main` jako wirtualnego systemu plików (VFS)

Sparse checkout z klonowaniem bez blobów obejmuje codzienne przepływy pracy bez dodatkowych narzędzi klienckich. Ponieważ `refs/trunk/main` jest już czystym drzewem Merkle w Content-Addressable Store, architektura stanowi również bezpośrednią podstawę dla opcjonalnego wirtualnego systemu plików (VFS) w przestrzeni użytkownika, realizowanego przez FUSE w systemie Linux, FileProvider w systemie macOS lub ProjFS w systemie Windows:

1. **Natychmiastowe montowanie pełnego drzewa**: lekki lokalny demon montowania odczytuje korzeniowy obiekt `tree` z `refs/trunk/main` i w ciągu milisekund rzutuje całą hierarchię katalogów monorepozytorium do katalogu roboczego, eliminując potrzebę uruchamiania `git sparse-checkout set`.
2. **Pobieranie blobów na żądanie**: zawartość plików pozostaje wirtualna, dopóki kompilator, IDE lub agent nie wywoła wywołania systemowego `open(2)` lub `read(2)`. Demon pobiera wtedy odpowiedni blob o skrócie SHA-1 z punktu końcowego CAS i zapisuje go w lokalnej pamięci NVMe.
3. **Nakładka copy-on-write dla przestrzeni roboczych**: lokalne edycje zapisywane są w lekkiej górnej warstwie nakładki. Uruchomienie `git ws-save` haszuje wyłącznie zmienione ścieżki w tej nakładce, zapisuje zaktualizowane obiekty `blob` i `tree` oraz wysyła powstały commit do `refs/ws/<actor>/<id>`, bez skanowania niezmodyfikowanych katalogów.

## Model listy zmian na czystym Gitcie: scalanie po stronie serwera w pamięci

W tradycyjnym przepływie pracy Git scalenie gałęzi funkcjonalnej albo tworzy merge commit z dwoma rodzicami, co czyni historię nieliniową, albo wymaga od programistów ręcznego rebase i wyścigu z równoległymi wysyłkami do `main`.

Inspirując się modelem listy zmian (Changelist, CL) z `google3`, każda przestrzeń robocza w naszej architekturze reprezentuje **pojedynczą atomową zmianę** względem migawki pnia. Podczas pracy lokalnej w `ws/alice/optimize-framing` programista lub agent może wykonać dziesięć przyrostowych commitów (`wip`, `fix tests`, `lint`). Gdy zmiana jest gotowa do wylądowania, serwer zwija deltę przestrzeni roboczej do jednej liniowej rewizji pnia, nigdy nie wykonując checkoutu katalogu roboczego na runnerze CI.

Oto sposób, w jaki skrypt lądowania (`land.sh`) wykonuje się w pamięci, przy użyciu plumbingu Git 2.40+:

```bash
# 1. Zidentyfikuj korzeniowy commit migawki, od którego odgałęziła się przestrzeń robocza
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. Oblicz scalone korzeniowe drzewo w pamięci, bez checkoutu drzewa roboczego
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
```

W kroku 2:

- Niezależnie od liczby lokalnych commitów wykonanych na gałęzi przestrzeni roboczej, skrypt porównuje tylko trzy skróty drzew: pierwotną migawkę bazową (`$BASE_TREE`), bieżącą migawkę pnia (`$MAIN_TREE`) oraz końcowe drzewo przestrzeni roboczej (`$WORK_TREE`).
- Jeśli `refs/trunk/main` zaktualizowało się w `//web/blog`, podczas gdy Alice edytowała `//net/relay`, `git merge-tree --write-tree` oblicza połączone korzeniowe drzewo `$NEW_TREE` bezpośrednio w magazynie obiektów Git w ciągu milisekund, bez zapisywania plików w systemie plików runnera.
- Po pomyślnym przejściu poniższych kontroli polityki jedno polecenie `git push --atomic` z `--force-with-lease` aktualizuje `refs/trunk/main` i dopisuje jeden liniowy commit do `refs/trunk/history`, a wyzwalacz lądowania `refs/heads/land/<id>` zostaje usunięty.

## Egzekwowanie granic architektury: czterostopniowa widoczność i hierarchiczne `OWNERS`

Konsolidacja kodu w jednym repozytorium działa wyłącznie wtedy, gdy granice między domenami są egzekwowane mechanicznie, zanim commit zostanie wylądowany. Zaraz po tym, jak `git merge-tree --write-tree` wygeneruje `$NEW_TREE` (a przed aktualizacją pnia przez `git push --atomic`), nasz silnik polityk uruchamia dwa sprawdzenia o złożoności O(delta) nad wynikiem `git diff-tree "$MAIN_TREE" "$NEW_TREE"`.

### 1. Czterostopniowa widoczność domen

Korzeniowe drzewo jest podzielone na cztery poziomy widoczności:

| Prefiks katalogu                                                                       | Poziom            | Dozwolone importy                                | Zabronione importy             | Bramka `OWNERS`        |
| :------------------------------------------------------------------------------------- | :---------------- | :----------------------------------------------- | :----------------------------- | :--------------------- |
| `//web/*`, `//net/*`, `//ipc/*`, `//storage/*`, `//crypto/*`, `//ai/*`, `//devtools/*` | Publiczna produkcja | Publiczna produkcja, `//third_party/*`         | `//corp/*`, `//experimental/*` | Egzekwowana            |
| `//third_party/*`                                                                      | Zewnętrzny dostawca | Samodzielne lub `//third_party/*`              | Wszystkie domeny inne niż `third_party`        | Egzekwowana            |
| `//corp/*`                                                                             | Wewnętrzna prywatna | Publiczna produkcja, `//corp/*`, `//third_party/*` | `//experimental/*`             | Egzekwowana            |
| `//experimental/<actor>/*`                                                             | Piaskownica       | Dowolny pakiet niebędący `corp`                  | `//corp/*`                     | Zwolniona (automatycznie zatwierdzona) |

Jeśli publiczny pakiet w `//web/*` próbuje zaimportować wewnętrzny pakiet z `//corp/*` lub niedojrzały prototyp z `//experimental/*`, zadanie lądowania zostaje przerwane, zanim dotknie `refs/trunk/main`.

### 2. Hierarchiczna weryfikacja `OWNERS` bez serwera Gerrit

Weryfikator egzekwuje pliki `OWNERS` na poziomie katalogów z regułami dziedziczenia:

- Dla każdej ścieżki zmodyfikowanej między `$MAIN_TREE` a `$NEW_TREE` weryfikator przechodzi w górę od katalogu pliku do `//OWNERS`.
- Dowolny plik `OWNERS` może zadeklarować `set noparent`, aby zablokować dziedziczone zatwierdzenia z katalogów nadrzędnych, lub `per-file *.sql=@dba-team`, aby zawęzić zatwierdzenia do wzorca nazwy pliku.
- **Niezmiennik przeciwko manipulacji**: istniejące pliki `OWNERS` są zawsze odczytywane z `$MAIN_TREE` (bieżącego drzewa pnia), nigdy z `$NEW_TREE`. Nieautoryzowany autor nie może przyznać sobie uprawnień, edytując `OWNERS` w tym samym commicie przestrzeni roboczej.
- **Wyłączenie dla piaskownicy**: każda ścieżka w `//experimental/<actor>/*` omija kontrole `OWNERS`, dzięki czemu inżynierowie i agenty mogą swobodnie prototypować za pomocą `git ws-exp <actor> <project>`, a następnie przenieść pakiet do domeny produkcyjnej poleceniem `git ws-graduate`.

## Przegląd kodu bez tarcia: efemeryczne PR-y i różnice migawek

Ponieważ `refs/heads/main` zawiera wyłącznie płaszczyznę sterowania, a `refs/trunk/main` jest bezrodzicielskim commitem, otwarcie standardowego pull requesta w GitHub bezpośrednio z gałęzi przestrzeni roboczej do `main` próbowałoby porównać niepowiązane historie. Rozwiązaliśmy to, wprowadzając trzy przepływy przeglądu dostosowane do różnych rodzajów zmian:

1. **Natychmiastowe dwukropkowe różnice przestrzeni roboczej (`git ws-save` + Edge Diff Router)**
   Wypchnięcie przestrzeni roboczej poleceniem `git ws-save` udostępnia `refs/ws/<actor>/<id>` przez lekkiego workera brzegowego. Worker ten rozwiązuje `refs/trunk/main` i referencję przestrzeni roboczej do ich SHA commitów, a następnie przekierowuje do widoku porównania z dwiema kropkami w GitHub (`/compare/<trunk-sha>..<ws-sha>`). Przy małych poprawkach lub zmianach wygenerowanych przez agenta recenzenci mogą sprawdzić różnice i kliknąć **Land** bezpośrednio w interfejsie webowym.
2. **Efemeryczne pull requesty w GitHub (`git ws-pr`)**
   Do przeglądu linia po linii w GitHub polecenie `git ws-pr` wysyła commit migawki bazowej (`$(git rev-list --max-parents=0 HEAD)`) do `refs/heads/base/<id>`, a wierzchołek przestrzeni roboczej do `refs/heads/review/<id>`. Następnie otwiera pull request w GitHub, porównujący `review/<id>` z `base/<id>`. Ponieważ gałąź bazowa jest migawką, z której odgałęziła się przestrzeń robocza, zakładka **Files changed** w GitHub pokazuje wyłącznie deltę przestrzeni roboczej. Gdy recenzent kliknie **Merge pull request**, webhook awansuje commit wierzchołkowy do `refs/heads/land/<id>`, uruchamia potok lądowania w pamięci i automatycznie usuwa zarówno `refs/heads/base/<id>`, jak i `refs/heads/review/<id>`.
3. **Pull requesty do zewnętrznych luster**
   Współtwórcy open source nigdy nie muszą poznawać przepływu pracy monorepozytorium. Wykonują fork i otwierają pull requesty do samodzielnych repozytoriów-luster w zwykły sposób.

### Zmiany, zestawy poprawek i przenoszenie zatwierdzeń

Nazwa gałęzi nie może identyfikować zmiany. Agent może kilkakrotnie zmienić nazwę gałęzi, poprawić wierzchołek lub wykonać rebase na nowszy pień przed lądowaniem. Podobnie jak w Gerrit, przepływ identyfikuje zmianę za pomocą nagłówka `Change-Id: I<40 znaków szesnastkowych>` w wiadomości wierzchołkowego commitu. Polecenia `git ws-save`, `git ws-pr` i `git ws-land` dodają ten nagłówek, gdy wierzchołek go nie ma, i zachowują go, gdy już istnieje, dzięki czemu identyfikator przetrwa poprawki, rebase i zmiany nazw. Wylądowany commit pnia zachowuje nagłówek i zapisuje numer swojego pull requesta do przeglądu, dzięki czemu `git trunk-cl <number|Change-Id>` odnajduje go w `refs/trunk/history`.

Każdy wierzchołek przesłany do przeglądu staje się zestawem poprawek. Polecenie `git ws-pr` wysyła go do niezmiennej referencji `refs/ws/cl/<change-id>/<tip>` obok gałęzi przeglądu. Ponowne uruchomienie `git ws-pr` po poprawce lub `git ws-sync` dodaje zestaw poprawek do tego samego pull requesta. Polecenie `git ws-ps` wypisuje zestawy poprawek bieżącej zmiany, a `git ws-psdiff <a> <b>` wyświetla interdiff między dwoma z nich, tak aby recenzent czytał wyłącznie to, co zmieniło się od ostatniej rundy.

GitHub wiąże zatwierdzenie z pojedynczym commitem, więc każdy rebase normalnie wymagałby nowego przeglądu. Potok lądowania akceptuje również zatwierdzenie udzielone innemu zestawowi poprawek tej samej zmiany, jeśli oba zestawy wprowadzają tę samą poprawkę względem własnej migawki bazowej. Porównuje on wynik `git patch-id --verbatim` dla pełnego binarnego diffu. Czysty rebase na nowszy pień zostaje wylądowany bez ponownego przeglądu, natomiast każda edycja treści, także zmiana białych znaków lub trybu pliku, wymaga nowego zatwierdzenia.

## Dwukierunkowa synchronizacja O(delta) i oddzielne gałęzie produkcyjne

Wiele organizacji publikuje poszczególne pakiety jako samodzielne repozytoria GitHub, aby zewnętrzni użytkownicy mogli wykonać `go get`, `cargo install` lub sforkować pojedynczy projekt bez dotykania monorepozytorium. Niektóre projekty, takie jak statyczne blogi lub aplikacje webowe z bramkami wydań, utrzymują również osobną gałąź `production` obok `main`.

Nasz silnik synchronizacji w Starlark obsługuje zarówno eksport wychodzący, jak i wkłady przychodzące w czasie **O(delta)**, proporcjonalnym do liczby plików zmienionych w commicie, a nie do rozmiaru monorepozytorium:

- **Wstawianie indeksu bez blobów**: podczas wstawiania poddrzewa repozytorium-lustra do korzeniowego drzewa monorepozytorium lub wyjmowania go z niego silnik ustawia `GIT_NO_LAZY_FETCH=1` i uruchamia `git update-index --index-info`, a następnie `git write-tree --missing-ok`. Niezmodyfikowane poddrzewa i bloby pozostają referencjonowane przez SHA-1 i nigdy nie są pobierane na runner CI.
- **Wstępne pobieranie blobów wyłącznie dla delty**: przed wypchnięciem wyeksportowanego commitu do samodzielnego lustra skrypt uruchamia `git diff-tree -r --diff-filter=AM "$ext_parent" "$export_tree"` i pobiera wyłącznie stare oraz nowe bloby zmodyfikowanych plików poleceniem `git cat-file --batch`. Dzięki temu Git może obliczyć cienki pakiet delta i wypchnąć go do repozytorium-lustra w ciągu kilku sekund.
- **Zachowanie gałęzi przepływu pracy i wydań**: podczas eksportu wychodzącego synchronizator zachowuje katalog `.github/workflows/` każdego samodzielnego repozytorium, dzięki czemu publiczne runnery CI wykonują testy projektu na lustrze. Każdy workflow w konfiguracji Starlark odwołuje się do jawnej gałęzi, na przykład `main`, co zachowuje bramki wydań dla repozytoriów wdrażanych z osobnej gałęzi `production`. Lądowania w monorepozytorium synchronizują się z gałęzią `main` lustra, a zautomatyzowane commity metadanych lub tłumaczeń na `main` synchronizują się z powrotem do `refs/trunk/main` przez webhook. Awansowanie `main` do `production` pozostaje jawnym krokiem wydania.

## Uwagi końcowe

Ta architektura traktuje magazyn obiektów Git jako Content-Addressable Store, oddziela `refs/heads/main` od `refs/trunk/main` i `refs/trunk/history`, identyfikuje każdą zmianę za pomocą `Change-Id` z niezmiennymi zestawami poprawek i scala drzewa w pamięci za pomocą `git merge-tree --write-tree`. Koszt wdrożenia rośnie wraz z katalogami, których potrzebuje zadanie, a koszt lądowania i synchronizacji lustra rośnie wraz z rozmiarem zmiany, a nie z rozmiarem repozytorium.
