Objek-objek pada dua chapter sebelumnya saling menunjuk dengan alamat 40 karakter. Dari mana alamat itu berasal? Database biasa memberi nomor urut, tetapi Git memilih cara yang lebih radikal: alamat dihitung dari isi. Isi yang sama selalu menghasilkan alamat yang sama di komputer mana pun, dan isi yang berbeda sedikit pun menghasilkan alamat yang sama sekali berbeda. Desain ini — content-addressable storage — bukan kuriositas internal; dialah yang memberi Git integritas data gratis, deduplikasi otomatis, dan clone yang cepat sekaligus bisa dipercaya.
Identitas Dihitung dari Isi #
Kita buat repository latihan baru berisi dua berkas yang isinya identik:
mkdir latihan-cas && cd latihan-cas
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'espresso 18000\n' > menu-awal.txt
printf 'espresso 18000\n' > menu-lama.txt
git hash-object menu-awal.txt menu-lama.txtOutput yang Dihasilkan:
d433484e4c05ef2efa746269a28947b95c83b103
d433484e4c05ef2efa746269a28947b95c83b103Nama berkas berbeda, isinya sama — dan git hash-object mengembalikan alamat yang sama persis untuk keduanya. Perhatikan pula bahwa hash ini identik dengan blob src/menu.txt dari repository latihan-model pada chapter sebelumnya: berkas yang sama, di repository yang berbeda, tetap jatuh ke alamat yang sama, karena alamat memang tidak menghitung nama, path, ataupun waktu — hanya isi.
Algoritmanya bisa kita buktikan sendiri. Git menghitung SHA-1 dari header kecil plus isi berkas — header berupa blob, spasi, panjang isi dalam byte, lalu satu karakter NUL:
printf 'blob 15\0espresso 18000\n' | sha1sumOutput yang Dihasilkan:
d433484e4c05ef2efa746269a28947b95c83b103 -Isi espresso 18000\n panjangnya 15 byte, jadi blok yang di-hash adalah blob 15 + NUL + isi — dan SHA-1-nya persis alamat dari git hash-object. Jenis objek ikut dihitung; nama berkas tidak. Karena SHA-1 bersifat deterministik, dua orang di dua negara yang menulis isi byte-identik akan menghasilkan objek yang identik tanpa perlu saling berkoordinasi.
Avalanche: Satu Byte Mengubah Segalanya #
Sisi lainnya: ubah satu angka saja, dan alamatnya berubah total:
printf 'espresso 19000\n' > menu-awal.txt
git hash-object menu-awal.txt menu-lama.txtOutput yang Dihasilkan:
851fe460158c2fea9e0f9b32581ddc45edc03f6d
d433484e4c05ef2efa746269a28947b95c83b103Perbedaan satu digit — 8 menjadi 9 — membuat hash berubah total dari awal sampai akhir, bukan satu kolom saja. Perilaku avalanche ini khas fungsi hash kriptografi: perubahan sekecil apa pun menyebar ke seluruh alamat. Konsekuensinya: hash sebuah commit merangkum seluruh isi pohonnya, sehingga mustahil mengubah isi di tingkat mana pun tanpa mengubah hash commit di atasnya — inilah alasan menulis ulang riwayat selalu mengubah semua hash setelahnya, sebagaimana kita alami di Aturan Aman Mengubah Riwayat
.
Integritas Gratis: Korupsi Terdeteksi #
Karena alamat adalah sekaligus checksum isinya, kejujuran setiap objek bisa diperiksa ulang kapan pun: hitung isi, bandingkan dengan nama berkasnya. Perintah git fsck melakukan persis itu untuk seluruh database. Kita uji pada repository sehat berisi satu commit:
cd .. && mkdir latihan-cacat && cd latihan-cacat
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'espresso 18000\n' > menu.txt
git add menu.txt
git commit -m "Buat daftar menu"
git fsckKeluarannya kosong — git fsck hanya bersuara saat menemukan masalah, dan repository sehat tidak punya keluhan. Sekarang kita rusakkan satu objek secara sengaja: timpa isi berkas blob menu.txt dengan teks sampah. Berkas objek berstatus hanya-baca, jadi izinnya kita longgarkan dulu:
BLOB=$(git rev-parse HEAD:menu.txt)
chmod u+w ".git/objects/${BLOB:0:2}/${BLOB:2}"
printf 'bocor' > ".git/objects/${BLOB:0:2}/${BLOB:2}"
git fsckOutput yang Dihasilkan:
error: inflate: data stream error (incorrect header check)
error: unable to unpack header of .git/objects/d4/33484e4c05ef2efa746269a28947b95c83b103
error: d433484e4c05ef2efa746269a28947b95c83b103: object corrupt or missing: .git/objects/d4/33484e4c05ef2efa746269a28947b95c83b103
missing blob d433484e4c05ef2efa746269a28947b95c83b103git fsck menemukan blob yang isinya tidak lagi cocok dengan alamatnya dan melaporkannya rusak — tanpa checksum terpisah, karena alamatnya sendiri sudah menjadi checksum. Kalimat pertama keluaran bisa berbeda tergantung di byte mana kerusakan jatuh; baris object corrupt or missing adalah petanya. Objek sehat tapi tak terjangkau riwayat juga dilaporkan sebagai dangling — dibahas di Dangling Objects
.
Konsekuensi berikutnya terjadi otomatis setiap kali repository berpindah: git clone memverifikasi checksum objek yang dikirim kepadanya. Kita coba meng-clone repository yang barusan kita rusak — lewat prefiks file:// agar Git memakai jalur protokol yang sama dengan clone dari server sungguhan:
cd ..
git clone "file://$(pwd)/latihan-cacat" uji-klon-cacatOutput yang Dihasilkan:
Cloning into 'uji-klon-cacat'...
remote: error: inflate: data stream error (incorrect header check)
remote: error: unable to unpack d433484e4c05ef2efa746269a28947b95c83b103 header
remote: error: inflate: data stream error (incorrect header check)
remote: error: unable to unpack d433484e4c05ef2efa746269a28947b95c83b103 header
remote: fatal: loose object d433484e4c05ef2efa746269a28947b95c83b103 (stored in ./objects/d4/33484e4c05ef2efa746269a28947b95c83b103) is corrupt
error: git upload-pack: git-pack-objects died with error.
fatal: git upload-pack: aborting due to possible repository corruption on the remote side.
remote: aborting due to possible repository corruption on the remote side.
fatal: early EOF
fatal: fetch-pack: invalid index-pack outputClone ditolak total: sumber mengemas objek, checksum gagal, transfer dibatalkan — copy tidak pernah setengah benar; itulah makna “clone adalah verifikasi”. Keluaran di atas menggabungkan stdout dan stderr, dan urutan baris di antara keduanya bisa berselang-seling di tiap jalankan — kumpulan barisnya tetap sama.
Deduplikasi Otomatis: Disimpan Sekali #
Sisi hematnya: karena alamat ditentukan isi, isi yang sama otomatis hanya menempati satu objek. Kembali ke repository latihan-cas, pulihkan berkas yang kita ubah tadi lalu daftarkan keduanya:
cd latihan-cas
printf 'espresso 18000\n' > menu-awal.txt
git count-objects
git add menu-awal.txt menu-lama.txt
git count-objectsOutput yang Dihasilkan:
0 objects, 0 kilobytesOutput yang Dihasilkan:
1 objects, 4 kilobytesSebelum git add, database kosong; setelah mendaftarkan dua berkas, bertambah tepat satu objek. Daftar index memperlihatkan dua path berbagi alamat yang sama:
git ls-files -sOutput yang Dihasilkan:
100644 d433484e4c05ef2efa746269a28947b95c83b103 0 menu-awal.txt
100644 d433484e4c05ef2efa746269a28947b95c83b103 0 menu-lama.txtDeduplikasi ini bekerja lintas berkas, lintas commit, lintas branch, bahkan lintas repository: berkas yang tidak berubah antar dua commit tidak pernah disimpan ulang — commit lama dan baru menunjuk blob yang sama. Kontrasnya dengan model “setiap versi = salinan baru” sangatlah jauh.
SHA-256: Format Objek Generasi Berikutnya #
Kabar buruknya: SHA-1 sudah lama dianggap lemah terhadap serangan tabrakan buatan, dan Git sejak lama menyiapkan penggantinya — repository berformat SHA-256 sudah didukung dan bisa kita coba langsung:
cd ..
git init -b main --object-format=sha256 latihan-sha256
cd latihan-sha256
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'espresso 18000\n' > menu.txt
git hash-object menu.txtOutput yang Dihasilkan:
988c1e8b2549253f26e46eba77c48f5293299be74049c7fc5f8be736f291d2d3Alamatnya kini 64 karakter heksadesimal — SHA-256 dari header plus isi dengan formula yang sama, sehingga isi yang identik tetap menghasilkan alamat identik. Format aktifnya tercatat sebagai konfigurasi repository:
git config extensions.objectformatOutput yang Dihasilkan:
sha256git add menu.txt
git commit -m "Buat daftar menu"
git rev-parse HEADOutput yang Dihasilkan:
e1a21d52b0d5e8afabc7fde6760167842196f5a27bb2dc2cc95c11b2854d87b0Berkas .git/config menandainya lewat repositoryformatversion = 1 dan extensions.objectformat = sha256. Seluruh mekanik section ini tidak berubah — masih empat jenis objek, masih content-addressable; hanya panjang alamatnya yang berbeda.
Miskonsepsi: Git Bukan Database Delta #
Pemahaman ini meluruskan miskonsepsi yang paling tersebar: “Git menyimpan perubahan (delta) antar versi, kan?” Tidak. Setiap commit menyimpan snapshot utuh — tree lengkap yang menunjuk blob-blob isi berkas. Yang membuatnya murah bukan delta, melainkan pemakaian ulang objek: isi yang tidak berubah tetap menunjuk blob lama, sehingga commit baru hanya “membeli” objek yang benar-benar baru. Delta memang ada di Git — tetapi hanya sebagai teknik kompresi di dalam packfile, dibedah di chapter Packfiles . Ringkasan keuntungan alamat-isi:
| Sifat | Dampak praktis |
|---|---|
| Isi sama → alamat sama | Deduplikasi otomatis lintas berkas, commit, branch, dan repository |
| Isi berubah → alamat berubah total | Perubahan apa pun terdeteksi; hash commit merangkum seluruh pohon |
| Alamat = checksum | Korupsi ketahuan oleh git fsck dan setiap clone tanpa biaya tambahan |
| Dihitung lokal tanpa koordinasi | Objek identik lahir identik di mesin siapa pun |
Objek-objek ini kokoh dan hemat — tetapi satu masalah praktis tersisa: manusia tidak bisa menghafal d433484e.... Database berisi alamat-alamat anonim, sedangkan kita berpikir dengan nama: main, v1.0.0, origin/main. Lapisan yang menerjemahkan nama menjadi alamat itulah yang kita bedah berikutnya di References dan Refs
.