GoSuda

Meninjau Ulang Git Monorepo: Arsitektur CAS-Native untuk Manusia dan Coding Agents

By Lemon Mint views ...

Setiap organisasi teknik yang berkembang pada akhirnya menghadapi persimpangan yang sama. Pada awalnya, dengan puluhan repositori kecil, segalanya terasa sederhana. Setiap library memiliki issue tracker sendiri, pipeline CI sendiri, dan perintah git clone yang selesai sebelum Anda sempat menyeruput kopi. Kemudian beban itu datang. Anda mengubah sebuah struct dalam paket jaringan inti dan menghabiskan sisa sore hari dengan membuka pull request pembaruan versi di enam repositori hilir, sambil menunggu antrean CI selesai dalam urutan topologis.

Jika semuanya dipindahkan ke monorepo Git tradisional, Anda menukar masalah koordinasi dengan masalah fisik. Seiring bertambahnya riwayat commit dan terakumulasinya aset biner dalam revisi-revisi lama, git fetch menjadi lambat. Working tree membengkak hingga gigabyte. Tak lama kemudian, seseorang mengusulkan agar dibangun sebuah daemon virtual filesystem khusus, server code review tersendiri, atau sekumpulan remote build worker, semata-mata agar pengembangan harian tetap dapat ditangani.

Ketika agen pengodean otonom bergabung dengan tim, kedua mode kegagalan tersebut semakin memburuk. Polyrepo membuat agen tidak mampu melihat pemanggil lintas proyek dan memaksanya mengelola kredensial di berbagai repositori. Monorepo tradisional menguras disk container dan bandwidth jaringan agen pada setiap inisialisasi tugas, sementara agen-agen yang bekerja bersamaan pada branch yang sama menciptakan perlombaan rebase yang tidak berkesudahan.

Kami menginginkan opsi ketiga. Terinspirasi oleh arsitektur google3, kami mengajukan pertanyaan sederhana: Dapatkah kami memperoleh perubahan atomik pada single trunk, onboarding sparse dalam waktu di bawah satu detik, tata kelola OWNERS yang hierarkis, serta mirror open-source yang berdiri sendiri, hanya dengan menggunakan Git plumbing standar dan object storage komoditas?

Jawabannya ternyata ya, dan hal itu tidak memerlukan daemon filesystem khusus maupun appliance review pihak ketiga. Yang dibutuhkan hanyalah menggunakan Git untuk apa adanya di balik layar: sebuah Content-Addressable Store (CAS) berbasis Merkle-tree.

Kekeliruan Mendasar pada Monorepo Git yang Lambat

Ketika pengembang mengeluhkan bahwa Git tidak mampu melakukan skala pada monorepo besar, mereka jarang sekali menyentuh batas dalam object database Git. Di dalam .git/objects, Git menyimpan tiga primitif yang tidak dapat diubah, yang diidentifikasi berdasarkan hash konten SHA-1 (atau SHA-256):

  1. blob: Isi byte mentah dari satu file.
  2. tree: Daftar direktori yang memetakan nama file dan izin akses ke hash blob atau tree anak.
  3. commit: Header teks kecil (umumnya 200 hingga 300 byte) yang menunjuk ke satu hash tree akar, nol atau lebih hash commit induk, serta metadata author.

Sebagai content-addressable store, desain ini sangat efisien. Jika sebuah monorepo berisi 50.000 file dan Anda mengedit satu baris pada satu file, commit baru akan menggunakan kembali hash blob dan tree yang persis sama untuk 49.999 file lainnya.

Lalu mengapa monorepo Git tradisional terasa berat? Karena konvensi membebani satu referensi, refs/heads/main, dengan tiga tugas yang saling bertentangan:

  • Ia menyimpan seluruh directory tree terpadu dari setiap proyek.
  • Ia menyeret seluruh rantai commit historis yang terhubung melalui parent hingga hari pertama.
  • Ia berada di dalam refs/heads/*, namespace yang secara default diiklankan dan dinegosiasikan oleh server Git pada setiap git clone dan git fetch.

Setiap kali developer atau runner CI melakukan fetch refs/heads/main, klien dan server menegosiasikan keterjangkauan commit graph di ribuan revisi historis. Jika seseorang tidak sengaja melakukan commit arsip berukuran 100 MB tiga tahun lalu lalu menghapusnya keesokan harinya, blob-nya tetap tergantung pada tree historis yang dapat dijangkau dari refs/heads/main.

Memisahkan Namespace Referensi menjadi Empat Lapisan

Protokol wire Git hanya mengiklankan refs/heads/* dan refs/tags/* selama clone standar. Referensi di bawah namespace kustom seperti refs/trunk/* atau refs/ws/* berada di object store yang sama persis pada server, memperoleh manfaat dari kompresi delta packfile yang sama, dan dapat di-fetch atau di-push secara eksplisit, namun tidak menambah overhead apa pun pada clone default.

Dengan membagi tanggung jawab ke empat namespace referensi di dalam satu repositori, hambatan tersebut hilang:

1refs/heads/main          Hanya control plane (~20 KB: aturan, .gitconfig, konfigurasi sinkronisasi, skrip)
2refs/trunk/main          Commit snapshot tanpa induk yang menunjuk ke root tree terpadu terbaru
3refs/trunk/history       Rantai commit linear yang terhubung melalui parent dan berbagi SHA-1 root tree yang sama persis
4refs/ws/<actor>/<id>     Snapshot workspace terisolasi (changelist) per developer atau agen
5refs/heads/land/<id>     Pemicu merge-queue sementara yang langsung dihapus setelah landing

1. Control Plane (refs/heads/main)

Branch default tidak berisi kode sumber aplikasi. Ia hanya melacak aturan operasional repositori (AGENTS.md, README.md), .gitconfig bersama berisi alias alur kerja, konfigurasi sinkronisasi Starlark (sync.star), dan skrip merge sisi server. Mengclone branch default mentransfer sekitar 20 KB dan selesai dalam hitungan milidetik.

2. Snapshot Trunk Tanpa Induk (refs/trunk/main)

refs/trunk/main menunjuk ke seluruh source tree terpadu dari organisasi (//web/*, //net/*, //ipc/*, //devtools/*, //corp/*, //third_party/*, //experimental/*), tetapi object commit-nya dibuat tanpa flag parent -p.

Karena refs/trunk/main tidak memiliki parent (parents = []), proses fetch-nya tidak memerlukan negosiasi commit graph sama sekali. Server Git tidak menelusuri riwayat; ia hanya mengirimkan satu commit head dan object directory tree saat ini. Biner historis atau file yang dihapus bulan lalu tidak dapat dijangkau dari refs/trunk/main dan tidak pernah ditransfer.

3. Riwayat Audit Linear (refs/trunk/history)

Pengembang tetap membutuhkan git blame dan git log saat menyelidiki regresi. Setiap kali sebuah perubahan di-land, skrip merge membuat dua object commit yang menunjuk ke hash SHA-1 $NEW_TREE root yang persis sama:

 1# Commit snapshot tanpa induk untuk checkout workspace depth-1 secara instan
 2SNAP=$(git commit-tree "$NEW_TREE" -m "$RAW_MSG")
 3
 4# Commit yang terhubung melalui parent untuk git log dan git blame yang linear
 5HIST=$(git commit-tree "$NEW_TREE" -p "$OLD_HIST" -m "$RAW_MSG")
 6
 7git push --atomic origin \
 8  --force-with-lease="refs/trunk/main:${OLD_MAIN}" \
 9  --force-with-lease="refs/trunk/history:${OLD_HIST}" \
10  "${SNAP}:refs/trunk/main" \
11  "${HIST}:refs/trunk/history"

Apakah memelihara refs/trunk/main dan refs/trunk/history sekaligus menggandakan jejak penyimpanan repositori? Sama sekali tidak. Karena kedua commit mereferensikan hash $NEW_TREE yang identik, content-addressable store Git men-deduplikasi 100% object tree dan blob. Biaya penyimpanan tambahan dari snapshot tanpa induk hanyalah satu header commit berukuran 250 byte.

4. Workspace Terisolasi (refs/ws/<actor>/<id>)

Branch pekerjaan yang sedang berlangsung di-push ke refs/ws/<actor>/<id>, bukan refs/heads/*. Seratus agen pengodean dapat menyimpan checkpoint setiap menit tanpa mengotori daftar branch atau memperlambat git fetch bagi insinyur manusia.

Onboarding dalam Lima Perintah: Cara Kerja Sparse dan Blobless Checkout

Dengan tata letak referensi ini, developer baru atau container agen pengodean yang bersifat sementara tidak perlu memasang CLI kustom atau driver FUSE. Onboarding cukup dilakukan dengan lima perintah Git standar:

 1# 1. Clone control plane 20 KB dengan filter blob dan mode sparse diaktifkan
 2git clone --filter=blob:none --sparse https://github.com/example-org/monorepo.git
 3cd monorepo
 4
 5# 2. Muat alias repositori dan pasang refs/trunk/*
 6git config include.path ../.gitconfig
 7git trunk-init
 8
 9# 3. Deklarasikan direktori domain yang dibutuhkan untuk tugas ini
10git sparse-checkout set net/relay ipc/codec
11
12# 4. Buat workspace terisolasi yang berakar pada snapshot trunk terbaru
13git ws-new alice/optimize-framing

Di balik layar, setiap langkah hanya melakukan pekerjaan minimum yang diperlukan:

  1. git clone --filter=blob:none --sparse mengunduh control plane 20 KB pada refs/heads/main.
  2. git trunk-init mengonfigurasi +refs/trunk/*:refs/remotes/origin/trunk/* dan menjalankan blobless fetch. Git mengunduh metadata tree untuk refs/trunk/main (biasanya beberapa puluh kilobyte) dan nol blob file.
  3. git sparse-checkout set net/relay ipc/codec menginstruksikan cone-mode index filter milik Git untuk hanya memunculkan direktori root dan kedua subtree tersebut.
  4. git ws-new berpindah ke branch baru yang berakar pada origin/trunk/main. Pada saat itu, Git hanya meminta object blob di dalam net/relay dan ipc/codec sesuai kebutuhan. 95% sisa monorepo tidak pernah diunduh ke disk.

Cakrawala Masa Depan: Memasang refs/trunk/main sebagai Virtual Filesystem (VFS)

Sparse checkout dengan blobless clone mencakup alur kerja harian tanpa tambahan alat di sisi klien. Karena refs/trunk/main sudah berupa Merkle tree murni dalam Content-Addressable Store, arsitektur ini juga menyediakan fondasi langsung bagi Virtual Filesystem (VFS) opsional di ruang pengguna melalui FUSE pada Linux, FileProvider pada macOS, atau ProjFS pada Windows:

  1. Pemasangan Full-Tree Instan: Daemon mount lokal yang ringan membaca object tree akar dari refs/trunk/main dan memproyeksikan seluruh hierarki direktori monorepo ke dalam working directory dalam hitungan milidetik, sehingga tidak perlu menjalankan git sparse-checkout set.
  2. On-Demand Blob Faulting: Isi file tetap bersifat virtual hingga compiler, IDE, atau agen mengeluarkan panggilan sistem open(2) atau read(2). Daemon kemudian mengambil blob SHA-1 yang bersesuaian dari endpoint CAS dan menyimpannya dalam cache pada penyimpanan NVMe lokal.
  3. Copy-on-Write Overlay untuk Workspace: Perubahan lokal ditulis ke lapisan overlay atas yang ringan. Menjalankan git ws-save hanya melakukan hash pada path yang kotor di overlay tersebut, menulis object blob dan tree yang diperbarui, dan men-push commit yang dihasilkan ke refs/ws/<actor>/<id> tanpa memindai direktori yang tidak berubah.

Model Changelist di Atas Git Murni: Landing Sisi Server secara In-Memory

Dalam alur kerja Git tradisional, menggabungkan feature branch either membuat merge commit dengan dua parent (yang mengubah riwayat menjadi stasiun persilangan kereta yang kusut) atau mengharuskan developer melakukan rebase secara manual dan berlomba dengan insinyur lain yang melakukan push ke main.

Terinspirasi oleh model Changelist (CL) google3, setiap workspace dalam arsitektur kami merepresentasikan satu perubahan atomik terhadap snapshot trunk. Saat bekerja secara lokal di ws/alice/optimize-framing, seorang developer atau agen dapat membuat sepuluh commit inkremental (wip, fix tests, lint). Ketika perubahan siap di-land, server memadatkan delta workspace menjadi satu revisi trunk yang linear tanpa perlu melakukan checkout working directory pada runner CI.

Berikut cara skrip landing (land.sh) dieksekusi secara in-memory menggunakan plumbing Git 2.40+:

 1# 1. Identifikasi commit snapshot akar tempat workspace bercabang
 2BASE=$(git rev-list --max-parents=0 "$WORK_SHA")
 3
 4BASE_TREE=$(git rev-parse "${BASE}^{tree}")
 5MAIN_TREE=$(git rev-parse "${OLD_MAIN}^{tree}")
 6WORK_TREE=$(git rev-parse "${WORK_SHA}^{tree}")
 7
 8# 2. Hitung root tree hasil merge di memori tanpa checkout working tree
 9if [ "$MAIN_TREE" = "$BASE_TREE" ]; then
10  NEW_TREE="$WORK_TREE"
11elif OUT=$(git merge-tree --write-tree --merge-base="$BASE_TREE" "$MAIN_TREE" "$WORK_TREE"); then
12  NEW_TREE=$(head -n1 <<<"$OUT")
13else
14  echo "Merge conflict detected; run git ws-sync to rebase." >&2
15  exit 1
16fi

Perhatikan dengan saksama langkah 2:

  • Terlepas dari berapa banyak commit lokal yang dibuat pada branch workspace, skrip hanya membandingkan tiga hash tree: snapshot dasar asli ($BASE_TREE), snapshot trunk saat ini ($MAIN_TREE), dan tree workspace akhir ($WORK_TREE).
  • Jika refs/trunk/main maju di //web/blog sementara Alice mengedit //net/relay, git merge-tree --write-tree menghitung root tree gabungan $NEW_TREE langsung di dalam object store Git dalam hitungan milidetik. Tidak ada file yang ditulis ke filesystem runner.
  • Akhirnya, git push --atomic dengan --force-with-lease memperbarui refs/trunk/main, menambahkan satu commit linear ke refs/trunk/history, dan menghapus pemicu landing refs/heads/land/<id> dalam satu transaksi.

Menegakkan Batas Arsitektur: Visibilitas Empat Tingkat dan OWNERS Hierarkis

Mengonsolidasikan kode ke dalam satu repositori hanya berfungsi jika batas antardomain ditegakkan secara mekanis sebelum sebuah commit dapat di-land. Tepat setelah git merge-tree --write-tree menghasilkan $NEW_TREE (dan sebelum git push --atomic memperbarui trunk), policy engine kami menjalankan dua pemeriksaan O(delta) terhadap git diff-tree "$MAIN_TREE" "$NEW_TREE".

1. Visibilitas Domain Empat Tingkat

Root tree dibagi menjadi empat tingkat visibilitas:

Prefiks DirektoriTingkatImpor yang DiizinkanImpor yang DilarangGerbang OWNERS
//web/*, //net/*, //ipc/*, //storage/*, //crypto/*, //ai/*, //devtools/*Produksi PublikProduksi Publik, //third_party/*//corp/*, //experimental/*Diterapkan
//third_party/*Vendor EksternalMandiri atau //third_party/*Semua domain selain third_partyDiterapkan
//corp/*Privat InternalProduksi Publik, //corp/*, //third_party/*//experimental/*Diterapkan
//experimental/<actor>/*SandboxPaket apa pun selain corp//corp/*Dikecualikan (disetujui otomatis)

Jika paket publik di //web/* mencoba mengimpor paket internal dari //corp/* atau prototipe yang belum lulus dari //experimental/*, job landing dibatalkan sebelum menyentuh refs/trunk/main.

2. Verifikasi OWNERS Hierarkis Tanpa Server Gerrit

Anda tidak memerlukan instance Gerrit khusus untuk menegakkan file OWNERS tingkat direktori dengan aturan pewarisan:

  • Untuk setiap path yang dimodifikasi antara $MAIN_TREE dan $NEW_TREE, verifier menelusuri ke atas dari direktori file tersebut hingga //OWNERS.
  • Setiap file OWNERS dapat mendeklarasikan set noparent untuk memblokir persetujuan yang diwariskan dari direktori induk, atau per-file *.sql=@dba-team untuk membatasi persetujuan berdasarkan glob nama file.
  • Invarian Anti-Manipulasi: File OWNERS yang ada selalu dibaca dari $MAIN_TREE (tree trunk saat ini), bukan dari $NEW_TREE. Author yang tidak disetujui tidak dapat memberi izin kepada dirinya sendiri dengan mengedit OWNERS dalam commit workspace yang sama.
  • Pengecualian Sandbox: Setiap path di bawah //experimental/<actor>/* melewati pemeriksaan OWNERS sehingga insinyur dan agen dapat membuat prototipe dengan bebas menggunakan git ws-exp <actor> <project> dan kemudian meluluskan paket tersebut ke domain produksi dengan git ws-graduate.

Code Review Tanpa Hambatan: PR Sementara dan Snapshot Diff

Karena refs/heads/main hanya memegang control plane dan refs/trunk/main adalah commit tanpa parent, membuka Pull Request GitHub standar langsung dari branch workspace ke main akan mencoba membandingkan riwayat yang tidak berhubungan. Kami menyelesaikannya dengan tiga alur review yang disesuaikan untuk perubahan yang berbeda:

  1. Diff Workspace Two-Dot Instan (git ws-save + Edge Diff Router) Mendorong workspace dengan git ws-save mengekspos refs/ws/<actor>/<id> melalui edge worker yang ringan. Worker ini me-resolve refs/trunk/main dan referensi workspace menjadi SHA commit masing-masing, lalu mengalihkan ke tampilan compare two-dot milik GitHub (/compare/<trunk-sha>..<ws-sha>). Untuk patch kecil atau perubahan yang dihasilkan agen, reviewer dapat memeriksa diff dan langsung mengklik Land pada antarmuka web.
  2. Pull Request GitHub Sementara (git ws-pr) Untuk review baris demi baris di GitHub, git ws-pr mendorong commit snapshot akar ($(git rev-list --max-parents=0 HEAD)) ke refs/heads/base/<id> dan ujung workspace ke refs/heads/review/<id>. Setelah itu, perintah ini membuka Pull Request GitHub yang membandingkan review/<id> dengan base/<id>. Karena branch base adalah snapshot persis tempat workspace bercabang, tab Files changed milik GitHub hanya menampilkan baris yang benar-benar diubah dalam workspace. Ketika reviewer mengklik Merge pull request, sebuah webhook mempromosikan commit head ke refs/heads/land/<id>, menjalankan pipeline landing di memori, dan secara otomatis menghapus refs/heads/base/<id> dan refs/heads/review/<id>.
  3. Pull Request pada Mirror Eksternal Kontributor open-source tidak pernah perlu mempelajari alur kerja monorepo. Mereka cukup melakukan fork dan membuka pull request ke repositori mirror yang berdiri sendiri seperti biasa.

Sinkronisasi Dua Arah O(delta) dan Branch Produksi Terpisah

Banyak organisasi mempublikasikan paket individual sebagai repositori GitHub yang berdiri sendiri agar pengguna eksternal dapat menjalankan go get, cargo install, atau melakukan fork pada satu proyek tanpa menyentuh monorepo. Beberapa proyek, seperti blog statis atau aplikasi web dengan gerbang rilis, juga memelihara branch production terpisah di samping main.

Mesin sinkronisasi Starlark kami menangani ekspor keluar maupun kontribusi masuk dalam waktu O(delta), yaitu sebanding dengan jumlah file yang berubah dalam sebuah commit dan bukan dengan ukuran monorepo:

  • Splicing Index Tanpa Blob: Ketika menyisipkan subtree repositori mirror ke dalam atau keluar dari root tree monorepo, mesin menetapkan GIT_NO_LAZY_FETCH=1 dan menjalankan git update-index --index-info yang diikuti oleh git write-tree --missing-ok. Subtree dan blob yang tidak berubah direferensikan semata-mata melalui hash SHA-1 mereka tanpa pernah diunduh ke runner CI.
  • Prefetch Blob Khusus Delta: Sebelum mendorong commit hasil ekspor ke mirror yang berdiri sendiri, skrip menjalankan git diff-tree -r --diff-filter=AM "$ext_parent" "$export_tree" dan hanya melakukan prefetch pada blob lama dan baru dari file yang dimodifikasi melalui git cat-file --batch. Dengan demikian, Git dapat menghitung thin delta pack dan mendorongnya ke repositori mirror dalam hitungan detik.
  • Pelestarian Alur Kerja dan Branch Rilis: Selama ekspor keluar, synchronizer mempertahankan direktori .github/workflows/ milik setiap repositori yang berdiri sendiri sehingga runner CI publik menjalankan pengujian proyek pada mirror. Setiap workflow dalam konfigurasi Starlark menargetkan branch eksplisit seperti main, yang mempertahankan gerbang rilis bagi repositori yang melakukan deployment dari branch production terpisah. Landing monorepo disinkronkan dengan branch main pada mirror, dan commit metadata atau terjemahan otomatis pada main disinkronkan kembali ke refs/trunk/main melalui webhook. Mempromosikan main ke production tetap menjadi langkah rilis yang eksplisit.

Penutup

Membangun monorepo berkecepatan tinggi tidak mengharuskan Anda meninggalkan Git standar atau mengoperasikan armada server infrastruktur khusus yang kompleks. Arsitektur ini memperlakukan object store Git sebagai Content-Addressable Store, memisahkan refs/heads/main dari refs/trunk/main dan refs/trunk/history, serta menggabungkan tree di memori dengan git merge-tree --write-tree. Hasilnya, baik insinyur manusia maupun agen pengodean otonom dapat mengclone workspace dalam waktu kurang dari satu detik dan melakukan penskalaan secara bersih di seluruh ratusan proyek.