Di Packfiles kita melihat git gc mengemas objek — tetapi gc punya pekerjaan kedua yang lebih menakutkan: menghapus objek. Database Git tidak pernah menimpa apa pun, jadi setiap amend, reset, dan rebase meninggalkan objek lama yang kehilangan penunjuk. Git menahannya untuk sementara sebagai cadangan, lalu secara berkala membuangnya lewat mekanisme garbage collection. Chapter ini membedah siklus hidup objek dari lahir sampai terhapus: siapa yang menjaganya tetap hidup, berapa lama masa tenggangnya, kapan pembersihan otomatis berjalan — dan mengapa gc justru harus ditahan saat kita sedang panik kehilangan commit.

Objek Hidup Selama Masih Terjangkau #

Aturan hidup-matinya satu objek ditentukan oleh reachability. Sebuah objek reachable (terjangkau) bila bisa ditelusuri dari titik awal tertentu: branch, tag, HEAD, refs remote, hingga isi staging index. Mulai dari titik-titik itu, Git berjalan menyusuri rantai — commit menunjuk tree, tree menunjuk blob dan tree anak — dan semua yang tersentuh adalah objek hidup. Objek unreachable (tak terjangkau) adalah yang kehilangan seluruh jalur menuju dirinya; objek seperti inilah yang dilaporkan git fsck sebagai dangling dan dibedah tuntas di Dangling Objects .

Ada satu pengecualian penting: reflog juga dihitung sebagai penjaga. git reflog mencatat setiap posisi lama HEAD dan branch, sehingga commit yang baru saja “dibuang” masih terjangkau lewat catatan perjalanan itu. Kita buktikan dengan amend klasik:

bash
mkdir latihan-siklus && cd latihan-siklus
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'laporan v1\n' > laporan.txt
git add laporan.txt
git commit -m "Tulis laporan v1"
printf 'laporan v2\n' > laporan.txt
git add laporan.txt
git commit -m "Perbarui isi laporan"
printf 'lampiran A\n' > lampiran.txt
git add lampiran.txt
git commit --amend -m "Tambah lampiran dan rapikan judul"
git reflog

Output yang Dihasilkan:

text
72550bd HEAD@{0}: commit (amend): Tambah lampiran dan rapikan judul
f1e81f9 HEAD@{1}: commit: Tambah lampiran
fc827f2 HEAD@{2}: commit: Perbarui isi laporan
12143f7 HEAD@{3}: commit (initial): Tulis laporan v1

Amend membuat commit baru 72550bd menggantikan f1e81f9 — branch kini menunjuk yang baru, dan commit lama kehilangan semua refs. Namun HEAD@{1} masih mencatatnya. Dua versi fsck memperlihatkan peran jangkar itu:

bash
git fsck
git fsck --no-reflogs

Perintah pertama tidak mencetak apa pun. Keluaran berikut berasal dari perintah kedua:

Output yang Dihasilkan:

text
dangling commit f1e81f90f4d62d779e010be8dff81c6287c797aa

git fsck bawaan tidak melaporkan apa pun — bagi fsck, objek itu masih hidup karena reflog menjaganya. Tambahkan --no-reflogs dan kebenaran mentahnya muncul: f1e81f9 adalah commit dangling. Hash di mesin pembaca akan berbeda, tetapi polanya sama.

Tiga Pekerjaan gc: Reflog Expire, Prune, Repack #

git gc sebenarnya menjalankan tiga pekerjaan berurutan. Pertama, reflog expire: entri reflog yang lebih tua dari masa tenggang dihapus (inilah yang mencabut jangkar). Kedua, prune: objek tak terjangkau yang telah melewati masa tenggang dihapus. Ketiga, repack: semua objek hidup dikemas menjadi pack seperti chapter sebelumnya. Masa tenggangnya diatur tiga konfigurasi berikut:

PengaturanNilai bawaanEfek
gc.pruneExpire2 mingguobjek tak terjangkau yang lebih muda dari dua minggu tidak boleh dihapus
gc.reflogExpireUnreachable30 harientri reflog yang menunjuk objek tak terjangkau dihapus setelah sebulan
gc.reflogExpire90 harientri reflog HEAD dan branch lokal dihapus setelah tiga bulan

Tabel ini adalah jendela penyelamatan: commit yang “hilang” hari ini dijamin Git menahannya minimal dua minggu ke depan, apa pun yang terjadi. Jalankan gc biasa — di repository sekecil ini git gc tidak mencetak apa pun — lalu periksa ketiga buktinya:

bash
git gc
git count-objects -v

Output yang Dihasilkan:

text
count: 0
size: 0
in-pack: 10
packs: 1
size-pack: 2
prune-packable: 0
garbage: 0
size-garbage: 0
bash
git fsck --no-reflogs

Output yang Dihasilkan:

text
dangling commit f1e81f90f4d62d779e010be8dff81c6287c797aa
bash
git cat-file -t f1e81f90f4d62d779e010be8dff81c6287c797aa

Output yang Dihasilkan:

text
commit

git gc mengemas 10 objek dan semuanya lolos: yang dangling masih tercatat di reflog dan reflog kita masih segar, jadi fsck tetap melaporkannya dan cat-file masih bisa membacanya. Objek muda tidak disentuh. Git modern bahkan tidak meledakkan objek tak terjangkau yang sudah lepas dari pack: objek semacam itu dikemas ke cruft pack terpisah yang ditemani berkas .mtimes — catatan umur tiap objek, yang kelak menjadi dasar hitungan masa tenggang.

Masa tenggang bisa kita habiskan paksa dengan dua langkah yang sama seperti di Dangling Objects — keduanya senyap tanpa keluaran:

bash
git reflog expire --expire-unreachable=now --all
git gc --prune=now
git count-objects -v

Output yang Dihasilkan:

text
count: 0
size: 0
in-pack: 9
packs: 1
size-pack: 2
prune-packable: 0
garbage: 0
size-garbage: 0

Jangkar reflog dicabut, --prune=now meniadakan tenggang dua minggu, dan paket menyusut dari 10 objek menjadi 9 — commit f1e81f9 resmi terhapus, ditandai git fsck --no-reflogs yang kini bersih tanpa keluaran. Setelah saat itu cat-file -t f1e81f9... akan gagal dengan fatal: Not a valid object name.

gc Otomatis #

Sebagian perintah porcelain — git commit, git merge, git rebase, git pull — menutup pekerjaannya dengan pemeriksaan pembersihan otomatis. Versi Git terbaru meneruskan pemeriksaan itu ke git maintenance run --auto (senyap, diatur maintenance.auto), yang pada akhirnya tetap menjalankan git gc --auto. Ambang picunya: objek lepas melebihi gc.auto (bawaan 6700) atau jumlah pack melebihi gc.autoPackLimit (bawaan 64). Karena Git menghitung ambang lewat sampel statistik — menghitung isi satu subdirektori objects/17 lalu dikalikan 256 — angka di kehidupan nyata jarang tercapai; pesan gc otomatis justru biasa muncul setelah operasi besar seperti merge panjang atau menulis ulang riwayat.

Sandbox kita menurunkan ambang agar pemicunya terlihat:

bash
git config gc.auto 2
git config gc.autoDetach false

Konfigurasi gc.autoDetach false membuat gc berjalan di depan; nilai bawaannya true akan melepas gc ke proses latar belakang. Siapkan 700 objek lepas lalu picu:

bash
mkdir -p src
for i in $(seq 1 700); do printf "modul %d\nbaris data %d\n" "$i" "$i" > "src/modul-$i.txt"; done
git add .
git gc --auto
git count-objects -v

Output yang Dihasilkan:

text
Auto packing the repository for optimum performance.
See "git help gc" for manual housekeeping.
text
count: 0
size: 0
in-pack: 700
packs: 1
size-pack: 41
prune-packable: 0
garbage: 0
size-garbage: 0

Dua baris pesan itulah yang di dunia nyata muncul sebagai “Auto packing the repository in background for optimum performance.” — gc otomatis jarang mengganggu karena dikerjakan di latar belakang. Lanjutkan dengan commit biasa:

bash
git commit -m "Impor 700 modul"

Output yang Dihasilkan:

text
[main (root-commit) 54b8d8b] Impor 700 modul
 700 files changed, 1400 insertions(+)
 create mode 100644 src/modul-1.txt
 create mode 100644 src/modul-10.txt
 create mode 100644 src/modul-100.txt

Keluaran itu dipotong — aslinya berlanjut dengan 698 baris create mode lainnya. Commit tersebut juga menjalankan pemeriksaan otomatis, dan kali ini senyap tanpa pesan: jumlah objek lepas yang ditinggalkannya (3 objek commit dan tree) jauh di bawah ambang, jadi gc tidak perlu bekerja. Barulah saat kita menimbuni objects/ dengan 700 objek lepas tak terjangkau, gc otomatis menolak diam:

bash
for i in $(seq 1 700); do printf "draf %d\n" "$i" | git hash-object -w --stdin; done
git gc --auto

Output yang Dihasilkan:

text
Auto packing the repository for optimum performance.
See "git help gc" for manual housekeeping.
warning: There are too many unreachable loose objects; run 'git prune' to remove them.

Peringatan itu menegaskan prinsipnya: gc otomatis mengemas objek hidup, tetapi tidak pernah menghapus objek tak terjangkau yang masih muda — ia menyarankan git prune, bukan menjalankannya sendiri. Konfigurasi gc.aggressiveWindow satu paket tema yang sama: nilai jendela pencarian basis delta ketika gc berjalan agresif (bawaan 250) — makin besar jendelanya, makin rapat delta yang ditemukan, makin lama pula gc bekerja. Pembahasan lengkapnya menyusul di git gc .

Siklus hidup, masa tenggang, dan pemicunya kini jelas. Yang belum: bagaimana menjalankan gc secara manual dengan sadar — kapan hasilnya terasa, apa efek samping berkasnya, dan opsi apa yang aman dipakai — semuanya dibedah di chapter berikutnya: git gc . Rujukan lengkap pengaturan waktu ada di dokumentasi git-gc .