---
id: 773766eac9e6ff3c46bd686d78539aed
author: Lemon Mint
title: 'Merancang Ulang Monorepo Git: Arsitektur CAS-Native untuk Manusia dan Agen Pengodean'
description: Cara membangun monorepo zero-daemon, CAS-native, di atas Git standar dengan menggunakan parentless trunk snapshots, isolated workspace references, in-memory merge trees, hierarchical OWNERS, dan O(delta) mirror synchronization.
language: id
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
---

Setiap organisasi engineering yang terus berkembang pada akhirnya harus memilih antara banyak repositori dan satu repositori. Dimulai dengan puluhan repositori kecil, hidup terasa sederhana pada awalnya. Setiap pustaka memiliki issue tracker sendiri, pipeline CI sendiri, dan `git clone` yang selesai dalam hitungan detik. Kemudian Anda mengubah sebuah struct dalam paket jaringan inti dan menghabiskan sisa sore untuk membuka pull request kenaikan versi di enam repositori hilir, sambil menunggu antrean CI habis secara berurutan topologis.

Memindahkan semuanya ke monorepo Git tradisional berarti Anda menukar koordinasi lintas repositori dengan ukuran repositori. Seiring riwayat commit bertambah panjang dan aset biner menumpuk di revisi-revisi sebelumnya, `git fetch` melambat. Working tree membengkak hingga gigabyte. Tak lama kemudian, seseorang mengusulkan untuk membangun daemon virtual filesystem khusus, server code review tersendiri, atau sekumpulan remote build worker hanya agar pengembangan harian tetap dapat ditoleransi.

Ketika agen pengodean otonom bergabung dengan tim, kedua mode kegagalan tersebut menjadi semakin buruk. Polyrepo membutakan agen terhadap pemanggil lintas proyek dan memaksanya mengelola kredensial di berbagai repositori. Monorepo tradisional menghabiskan disk container dan bandwidth jaringan agen pada setiap inisialisasi tugas, sementara agen-agen yang bekerja bersamaan pada cabang yang sama menciptakan perlombaan rebase tanpa henti.

Terinspirasi oleh arsitektur `google3`, kami membangun perubahan atomik dengan satu trunk, onboarding sparse dalam waktu kurang dari satu detik, tata kelola `OWNERS` yang hierarkis, serta mirror open-source mandiri dengan Git plumbing standar dan object storage komoditas.

Desain ini tidak memerlukan daemon filesystem khusus maupun appliance review pihak ketiga, karena memperlakukan Git sebagai Merkle-tree Content-Addressable Store (CAS).

## Kesalahpahaman Mendasar di Balik Lambatnya Monorepo Git

Ketika pengembang mengeluh bahwa Git tidak skalabel untuk monorepo besar, mereka jarang menyentuh batas pada object database Git. Di dalam `.git/objects`, Git menyimpan tiga primitif yang tidak dapat diubah, diindeks berdasarkan hash konten SHA-1 (atau SHA-256):

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

Jika sebuah monorepo berisi 50.000 file dan Anda mengedit satu baris pada satu file, commit baru menggunakan kembali hash `blob` dan `tree` yang sama persis untuk 49.999 file lainnya.

Monorepo Git tradisional terasa berat karena konvensi membebani satu referensi, `refs/heads/main`, dengan tiga tugas yang saling bertentangan:

- Ia menyimpan **seluruh pohon direktori terpadu** dari setiap proyek.
- Ia menyeret **seluruh rantai commit historis yang terhubung induk** hingga hari pertama.
- Ia berada di dalam `refs/heads/*`, namespace yang diiklankan dan dinegosiasikan oleh server Git secara default pada setiap `git clone` dan `git fetch`.

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

## Memisahkan Namespace Referensi

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 kompresi delta packfile yang sama, dan dapat di-fetch atau di-push secara eksplisit, namun tidak menimbulkan overhead apa pun pada clone default.

Memisahkan tugas-tugas ini ke referensi yang berbeda dalam satu repositori menghilangkan bottleneck:

```
refs/heads/main               Bidang kontrol saja (~20 KB: aturan, .gitconfig, konfigurasi sinkronisasi, skrip)
refs/trunk/main               Commit snapshot tanpa induk yang menunjuk ke root tree terpadu terbaru
refs/trunk/history            Rantai commit linear yang terhubung induk dan berbagi SHA-1 root tree yang sama persis
refs/ws/<actor>/<id>          Snapshot workspace terisolasi (changelist) per pengembang atau agen
refs/ws/cl/<change-id>/<tip>  Patchset tidak dapat diubah: setiap tip dari sebuah change yang dikirim untuk review
refs/heads/land/<id>          Pemicu merge-queue sementara yang langsung dihapus setelah landing
```

### 1. Bidang Kontrol (`refs/heads/main`)

Cabang default tidak berisi kode sumber aplikasi. Cabang ini hanya memuat aturan operasional repositori (`AGENTS.md`, `README.md`), `.gitconfig` bersama berisi alias alur kerja, konfigurasi sinkronisasi Starlark (`sync.star`), serta skrip merge di sisi server. Meng-clone cabang default mentransfer sekitar 20 KB dan selesai dalam hitungan milidetik.

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

`refs/trunk/main` menunjuk ke source tree terpadu lengkap dari seluruh organisasi (`//web/*`, `//net/*`, `//ipc/*`, `//storage/*`, `//crypto/*`, `//ai/*`, `//devtools/*`, `//corp/*`, `//third_party/*`, `//experimental/*`), tetapi objek commit-nya dibuat **tanpa flag induk `-p`**.

Karena `refs/trunk/main` tidak memiliki induk (`parents = []`), fetch-nya tidak memerlukan negosiasi commit-graph. Server Git tidak menelusuri riwayat; ia hanya mengirim commit head tunggal beserta objek `tree` direktori saat ini. Binary 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 memerlukan `git blame` dan `git log` ketika menyelidiki regresi. Setiap kali sebuah perubahan di-land, skrip merge membuat dua objek commit yang menunjuk ke hash SHA-1 `$NEW_TREE` root yang **persis sama**:

```bash
# Snapshot commit tanpa induk untuk checkout workspace depth-1 secara instan
SNAP=$(git commit-tree "$NEW_TREE" -m "$RAW_MSG")

# Commit berinduk untuk git log dan git blame yang linear
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"
```

Menyimpan `refs/trunk/main` dan `refs/trunk/history` sekaligus tidak menggandakan jejak penyimpanan. Karena kedua commit mereferensikan hash `$NEW_TREE` yang identik, content-addressable store Git mendeduplikasi 100% objek `tree` dan `blob`. Biaya penyimpanan tambahan dari snapshot tanpa induk hanyalah satu header commit berukuran 250 byte.

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

Cabang yang sedang dikerjakan didorong ke `refs/ws/<actor>/<id>`, bukan `refs/heads/*`. Seratus agen pengodean dapat menyimpan checkpoint setiap menit tanpa mengotori daftar cabang atau memperlambat `git fetch` bagi insinyur manusia.

## Onboarding dalam Empat Langkah: Cara Kerja Checkout Sparse + Blobless

Dengan tata letak referensi ini, pengembang baru atau container agen pengodean yang bersifat sementara tidak perlu memasang CLI kustom atau driver FUSE. Onboarding membutuhkan empat langkah dengan Git standar dan alias repositori:

```bash
# 1. Clone control plane 20 KB dengan penyaringan blob dan mode sparse diaktifkan
git clone --filter=blob:none --sparse https://github.com/example-org/monorepo.git
cd monorepo

# 2. Muat alias repositori dan pasang refs/trunk/*
git config include.path ../.gitconfig
git trunk-init

# 3. Deklarasikan direktori domain yang dibutuhkan untuk tugas ini
git sparse-checkout set net/relay ipc/codec

# 4. Buat workspace terisolasi yang berakar pada snapshot trunk terbaru
git ws-new alice/optimize-framing
```

Setiap langkah hanya mengambil apa yang dibutuhkannya:

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 fetch blobless. Git mengunduh metadata `tree` untuk `refs/trunk/main` (umumnya beberapa puluh kilobyte) dan **nol file blob**.
3. `git sparse-checkout set net/relay ipc/codec` menginstruksikan index filter mode cone milik Git untuk hanya memunculkan direktori root dan kedua subtree tersebut.
4. `git ws-new` berpindah ke cabang baru yang berakar pada `origin/trunk/main`. Git kemudian meminta secara on-demand hanya objek `blob` di dalam `net/relay` dan `ipc/codec`. 95% monorepo lainnya tidak pernah diunduh ke disk.

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

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

1. **Instant Full-Tree Mount**: Daemon mount lokal yang ringan membaca objek `tree` root dari `refs/trunk/main` dan memproyeksikan seluruh hierarki direktori monorepo ke working directory dalam hitungan milidetik, sehingga tidak perlu menjalankan `git sparse-checkout set`.
2. **On-Demand Blob Faulting**: Isi file tetap virtual hingga compiler, IDE, atau agen mengeluarkan system call `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 for Workspaces**: Perubahan lokal ditulis ke lapisan overlay atas yang ringan. Menjalankan `git ws-save` hanya men-hash path yang kotor di overlay tersebut, menulis objek `blob` dan `tree` yang diperbarui, lalu mendorong commit yang dihasilkan ke `refs/ws/<actor>/<id>` tanpa memindai direktori yang tidak berubah.

## Model Changelist pada Git Murni: Landing Sisi Server di Memori

Dalam alur kerja Git tradisional, menggabungkan cabang fitur menghasilkan merge commit dengan dua induk, yang membuat riwayat menjadi non-linear, atau mengharuskan pengembang melakukan rebase secara manual dan berlomba dengan push konkuren 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 pengembang atau agen mungkin membuat sepuluh commit inkremental (`wip`, `fix tests`, `lint`). Ketika perubahan siap di-land, server memampatkan delta workspace menjadi satu revisi trunk linear tanpa pernah melakukan checkout working directory pada runner CI.

Berikut cara skrip landing (`land.sh`) dieksekusi di memori menggunakan plumbing Git 2.40+:

```bash
# 1. Identifikasi commit snapshot root tempat workspace bercabang
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. Hitung root tree hasil merge di memori tanpa checkout working tree
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
```

Pada langkah 2:

- Terlepas dari berapa banyak commit lokal yang dibuat pada cabang 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, tanpa menulis file ke filesystem runner.
- Setelah pemeriksaan kebijakan di bawah ini lolos, satu `git push --atomic` dengan `--force-with-lease` memperbarui `refs/trunk/main` dan menambahkan satu commit linear ke `refs/trunk/history`, lalu pemicu landing `refs/heads/land/<id>` dihapus.

## Menegakkan Batas Arsitektur: Visibilitas Empat Tingkat dan `OWNERS` Hierarkis

Mengonsolidasikan kode ke dalam satu repositori hanya berhasil 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), mesin kebijakan kami menjalankan dua pemeriksaan O(delta) atas `git diff-tree "$MAIN_TREE" "$NEW_TREE"`.

### 1. Visibilitas Domain Empat Tingkat

Root tree dibagi menjadi empat tingkat visibilitas:

| Prefiks Direktori                                                                      | Tingkat           | Impor yang Diizinkan                             | Impor yang Dilarang              | Gerbang `OWNERS`        |
| :------------------------------------------------------------------------------------- | :---------------- | :----------------------------------------------- | :------------------------------- | :---------------------- |
| `//web/*`, `//net/*`, `//ipc/*`, `//storage/*`, `//crypto/*`, `//ai/*`, `//devtools/*` | Public Production | Public Production, `//third_party/*`             | `//corp/*`, `//experimental/*`   | Diberlakukan            |
| `//third_party/*`                                                                      | External Vendor   | Mandiri atau `//third_party/*`                   | Semua domain selain `third_party` | Diberlakukan            |
| `//corp/*`                                                                             | Internal Private  | Public Production, `//corp/*`, `//third_party/*` | `//experimental/*`               | Diberlakukan            |
| `//experimental/<actor>/*`                                                             | Sandbox           | Paket non-`corp` mana pun                        | `//corp/*`                       | Dikecualikan (disetujui otomatis) |

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

### 2. Verifikasi `OWNERS` Hierarkis Tanpa Server Gerrit

Verifier menegakkan file `OWNERS` tingkat direktori dengan aturan pewarisan:

- Untuk setiap path yang diubah antara `$MAIN_TREE` dan `$NEW_TREE`, verifier menelusuri ke atas dari direktori file hingga `//OWNERS`.
- File `OWNERS` apa pun 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`. Penulis yang belum disetujui tidak dapat memberikan 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 Diff Snapshot

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

1. **Instant Two-Dot Workspace Diffs (`git ws-save` + Edge Diff Router)**
   Mendorong workspace dengan `git ws-save` mengekspos `refs/ws/<actor>/<id>` melalui edge worker ringan. Worker ini menyelesaikan `refs/trunk/main` dan referensi workspace menjadi SHA commit masing-masing, lalu mengalihkan ke tampilan compare two-dot GitHub (`/compare/<trunk-sha>..<ws-sha>`). Untuk patch kecil atau perubahan yang dihasilkan agen, reviewer dapat memeriksa diff dan mengklik **Land** langsung di antarmuka web.
2. **Ephemeral GitHub Pull Requests (`git ws-pr`)**
   Untuk review baris per baris di GitHub, `git ws-pr` mendorong commit snapshot root (`$(git rev-list --max-parents=0 HEAD)`) ke `refs/heads/base/<id>` dan ujung workspace ke `refs/heads/review/<id>`. Kemudian membuka Pull Request GitHub yang membandingkan `review/<id>` dengan `base/<id>`. Karena cabang dasar adalah snapshot tempat workspace bercabang, tab **Files changed** di GitHub hanya menampilkan delta workspace. Ketika reviewer mengklik **Merge pull request**, 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. **External Mirror Pull Requests**
   Kontributor open-source tidak perlu mempelajari alur kerja monorepo. Mereka melakukan fork dan membuka pull request ke repositori mirror mandiri seperti biasa.

### Perubahan, Patchset, dan Pembawaan Persetujuan

Nama cabang tidak dapat mengidentifikasi sebuah perubahan. Seorang agen mungkin mengganti nama cabangnya, melakukan amend pada tip, atau melakukan rebase ke trunk yang lebih baru beberapa kali sebelum landing. Seperti pada Gerrit, alur kerja ini mengidentifikasi perubahan melalui trailer `Change-Id: I<40 hex>` pada pesan commit tip. `git ws-save`, `git ws-pr`, dan `git ws-land` menambahkan trailer ini jika tip belum memilikinya dan mempertahankannya jika sudah ada, sehingga id tetap bertahan melalui amend, rebase, dan perubahan nama. Commit trunk yang di-land tetap menyimpan trailer tersebut dan mencatat nomor pull request review-nya, sehingga `git trunk-cl <number|Change-Id>` menemukannya di `refs/trunk/history`.

Setiap tip yang dikirim untuk review menjadi sebuah patchset. `git ws-pr` mendorongnya ke referensi tidak dapat diubah `refs/ws/cl/<change-id>/<tip>` di samping cabang review. Menjalankan `git ws-pr` lagi setelah amend atau `git ws-sync` menambahkan patchset ke pull request yang sama. `git ws-ps` menampilkan daftar patchset dari perubahan saat ini, dan `git ws-psdiff <a> <b>` mencetak interdiff antara dua patchset, sehingga reviewer hanya membaca apa yang berubah sejak putaran terakhir.

GitHub mengikat persetujuan pada satu commit, sehingga setiap rebase biasanya memerlukan review baru. Pipeline landing juga menerima persetujuan yang diberikan pada patchset lain dari perubahan yang sama, asalkan kedua patchset membuat patch yang sama terhadap snapshot dasar masing-masing. Pipeline membandingkan `git patch-id --verbatim` atas diff biner lengkap. Rebase bersih ke trunk yang lebih baru dapat di-land tanpa review ulang, sedangkan setiap perubahan konten, termasuk perubahan spasi atau mode file, memerlukan persetujuan baru.

## Sinkronisasi Dua Arah O(delta) dan Cabang Produksi Terpisah

Banyak organisasi mempublikasikan paket individual sebagai repositori GitHub mandiri 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 cabang `production` terpisah di samping `main`.

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

- **Blobless Index Splicing**: Saat menyisipkan subtree repositori mirror ke atau dari root tree monorepo, mesin menetapkan `GIT_NO_LAZY_FETCH=1` dan menjalankan `git update-index --index-info` diikuti `git write-tree --missing-ok`. Subtree dan blob yang tidak berubah tetap dirujuk melalui SHA-1 dan tidak pernah diunduh ke runner CI.
- **Delta-Only Blob Prefetching**: Sebelum mendorong commit hasil ekspor ke mirror mandiri, skrip menjalankan `git diff-tree -r --diff-filter=AM "$ext_parent" "$export_tree"` dan melakukan prefetch hanya pada blob lama dan baru dari file yang diubah melalui `git cat-file --batch`. Dengan demikian Git dapat menghitung thin delta pack dan mendorongnya ke repositori mirror dalam hitungan detik.
- **Workflow and Release Branch Preservation**: Selama ekspor keluar, synchronizer mempertahankan direktori `.github/workflows/` milik setiap repositori mandiri sehingga runner CI publik menjalankan pengujian proyek pada mirror. Setiap workflow dalam konfigurasi Starlark menargetkan cabang eksplisit seperti `main`, yang mempertahankan gerbang rilis bagi repositori yang melakukan deployment dari cabang `production` terpisah. Landing monorepo disinkronkan dengan cabang `main` pada mirror, dan commit metadata atau terjemahan otomatis pada `main` disinkronkan kembali ke `refs/trunk/main` melalui webhook. Mempromosikan `main` ke `production` tetap merupakan langkah rilis yang eksplisit.

## Penutup

Arsitektur ini memperlakukan object store Git sebagai Content-Addressable Store, memisahkan `refs/heads/main` dari `refs/trunk/main` dan `refs/trunk/history`, mengidentifikasi setiap perubahan melalui `Change-Id` dengan patchset yang tidak dapat diubah, serta menggabungkan tree di memori dengan `git merge-tree --write-tree`. Biaya onboarding bertumbuh mengikuti direktori yang dibutuhkan suatu tugas, sedangkan biaya landing dan sinkronisasi mirror bertumbuh mengikuti ukuran perubahan, bukan ukuran repositori.
