Kita sudah membuka isi database objek, refs, dan HEAD — tetapi satu berkas masih bekerja diam-diam setiap kali kita menjalankan git add dan git status: .git/index. Inilah mesin di balik staging area yang sejak section Melacak Perubahan kita pakai sebagai konsep. Chapter ini membedahnya secara mekanis — membaca entrinya lewat ls-files -s, membuktikan staging adalah satu berkas biner (bukan objek), menjalankan alur add lewat plumbing, lalu menyaksikan konflik merge membelah index menjadi tiga panggung.

Index: Snapshot Staging dalam Satu Berkas #

Repository latihan kita mulai sederhana — satu berkas, satu commit:

bash
mkdir latihan-index && cd latihan-index
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"

Daftar isi index terbaca dengan plumbing git ls-files -s (staged):

bash
git ls-files -s

Output yang Dihasilkan:

text
100644 d433484e4c05ef2efa746269a28947b95c83b103 0	menu.txt

Satu entri dengan empat kolom: mode berkas, hash blob, nomor stage (angka 0 — arti pentingnya segera kita lihat), lalu nama berkas (dipisahkan tab). Inilah seluruh isi staging area: daftar “path → blob” yang akan menjadi isi commit berikutnya. Wujud fisiknya:

bash
ls -l .git/index

Output yang Dihasilkan:

text
-rw-rw-r-- 1 z z 137 Oct 13 15:16 .git/index
bash
file .git/index

Output yang Dihasilkan:

text
.git/index: Git index, version 2, 1 entries

Index bukan objek di objects/ — tidak dialamatkan hash, tidak immutable, tidak masuk riwayat. Index hanyalah satu berkas biner biasa yang ditulis ulang setiap kali staging berubah. Saat commit dibuat, Git merangkai tree dari daftar ini — snapshot commit memang potret index saat itu, buktinya menyusul di bawah.

Alur add: Hitung Hash, Simpan Blob, Perbarui Index #

Mari kita bedah apa yang sebenarnya dikerjakan git add. Sunting harga tanpa mendaftarkannya:

bash
printf 'espresso 20000\n' > menu.txt
git ls-files -s

Output yang Dihasilkan:

text
100644 d433484e4c05ef2efa746269a28947b95c83b103 0	menu.txt
bash
git status --short

Output yang Dihasilkan:

text
 M menu.txt

Berkas di disk sudah berubah, tetapi index masih mencatat blob lama d433484... — itulah mengapa status melaporkan M. Hash isi yang baru bisa dihitung tanpa menyentuh staging:

bash
git hash-object menu.txt

Output yang Dihasilkan:

text
e29201c2a49faafed39ff5bbacba04e5f19a7e94
bash
git count-objects

Output yang Dihasilkan:

text
3 objects, 12 kilobytes

Perhatikan: hash e29201c... sudah diketahui, tapi database masih berisi 3 objek (commit, tree, blob lama) — hash-object tadi hanya menghitung, belum menyimpan. Sekarang daftarkan:

bash
git add menu.txt
git ls-files -s

Output yang Dihasilkan:

text
100644 e29201c2a49faafed39ff5bbacba04e5f19a7e94 0	menu.txt
bash
git count-objects

Output yang Dihasilkan:

text
4 objects, 16 kilobytes

Tiga hal terjadi dalam satu git add: blob baru disimpan (3 → 4 objek), entri menu.txt di index digeser ke hash baru, dan selesai — tidak ada commit, tidak ada tree baru. Staging hanyalah pembaruan satu baris daftar.

update-index –add: Staging Tanpa git add #

Kalau add cuma itu pekerjaannya, kita seharusnya bisa mengerjakannya manual dengan plumbing. Benar — git update-index --add mendaftarkan berkas ke index tanpa git add:

bash
printf 'Pelanggan:\n- Siti\n' > pelanggan.txt
git update-index --add pelanggan.txt
git status --short

Output yang Dihasilkan:

text
M  menu.txt
A  pelanggan.txt

Perintah update-index --add sunyi, tapi statusnya nyata: A (added) pada pelanggan.txt — berkas baru itu kini staged tanpa pernah disentuh git add. Blob-nya juga tersimpan, seperti terlihat dari jumlah objek:

bash
git count-objects

Output yang Dihasilkan:

text
5 objects, 20 kilobytes
bash
git ls-files -s

Output yang Dihasilkan:

text
100644 e29201c2a49faafed39ff5bbacba04e5f19a7e94 0	menu.txt
100644 70c8d3b5dae8af7e68ba3bb4c440ac52e353aefa 0	pelanggan.txt

Porcelain git add, plumbing update-index --add — hasil akhirnya identik. Bedah penuh daftar perintah plumbing dan porcelain dibuka pada chapter Object Model .

write-tree: Index Menjadi Snapshot #

git write-tree merangkai tree dari isi index saat ini — langkah yang oleh git commit dikerjakan diam-diam:

bash
git write-tree

Output yang Dihasilkan:

text
0a36a11b5daf460d0167e64b18ae61d1304e540c

Tree itu tersimpan di database — dan commit yang kita buat sesudahnya memakai tree yang sama persis:

bash
git commit -m "Naikkan harga espresso dan daftar pelanggan"
git cat-file -p HEAD

Output yang Dihasilkan:

text
tree 0a36a11b5daf460d0167e64b18ae61d1304e540c
parent 303205ab0bf31e63ac6e7691752eee2ce476a3c0
author Budi Santoso <[email protected]> 1794021300 +0700
committer Budi Santoso <[email protected]> 1794021300 +0700

Naikkan harga espresso dan daftar pelanggan

Baris tree 0a36a11... pada commit identik dengan keluaran write-tree — commit memotret index, bukan working directory. Inilah alasan staging area disebut snapshot yang akan di-commit: yang masuk commit hanya yang terdaftar di index saat itu.

Index sebagai Cache: ctime dan mtime #

Nama lain index — cache — berasal dari perannya mempercepat status. Tiap entri index menyimpan statistik berkas:

bash
git ls-files --debug

Output yang Dihasilkan:

text
menu.txt
  ctime: 1791904612:407060083
  mtime: 1791904612:407060083
  dev: 44	ino: 152653
  uid: 1001	gid: 1001
  size: 15	flags: 0

Selain hash blob, index menyimpan ctime (waktu metadata berubah), mtime (waktu isi berubah), nomor perangkat dan inode, ukuran, serta pemiliknya — nilai-nilai ini mengikuti berkas di mesin masing-masing. Fungsinya: git status membandingkan statistik itu dengan keadaan berkas di disk. Bila mtime dan size tidak berubah sejak terakhir dicatat, Git langsung menyimpulkan berkas tidak berubah tanpa membaca isi dan meng-hash-nya ulang; hanya berkas yang statistiknya bergeser yang diperiksa isinya. Di repository berisi puluhan ribu berkas, cache inilah yang membuat git status selesai dalam hitungan milidetik.

Konflik di Index: Stage 1, 2, 3 #

Angka 0 pada kolom ketiga ls-files -s akhirnya menemui maknanya. Kita ciptakan konflik: dua branch menyunting baris yang sama.

bash
git switch -c fitur-harga
printf 'espresso 25000\n' > menu.txt
git add menu.txt
git commit -m "Naikkan harga jadi 25000"
git switch main
printf 'espresso 22000\n' > menu.txt
git add menu.txt
git commit -m "Naikkan harga jadi 22000"
git merge fitur-harga

Output yang Dihasilkan:

text
Auto-merging menu.txt
CONFLICT (content): Merge conflict in menu.txt
Automatic merge failed; fix conflicts and then commit the result.

Merge berhenti di tengah jalan — dan keadaan berhentinya terbaca langsung dari index:

bash
git ls-files -s

Output yang Dihasilkan:

text
100644 e29201c2a49faafed39ff5bbacba04e5f19a7e94 1	menu.txt
100644 5144563a92a93ffbaf49fed013acf85f56457fbd 2	menu.txt
100644 0c0927312991510e4fab103de1a0456e5d87c3ea 3	menu.txt
100644 70c8d3b5dae8af7e68ba3bb4c440ac52e353aefa 0	pelanggan.txt

menu.txt kini menempati tiga panggung sekaligus: stage 1 versi bersama terakhir (e29201c... — harga 20000 sebelum kedua cabang menyimpang), stage 2 versi kita di main (5144563... — 22000), stage 3 versi fitur-harga (0c09273... — 25000). Tiga blob itulah sumber tanda <<<<<<</>>>>>>> di berkas konflik; pelanggan.txt yang tak terlibat tetap tenang di stage 0. Status singkatnya terkenal:

bash
git status --short

Output yang Dihasilkan:

text
UU menu.txt

UU berarti kedua sisi menyunting — penanda yang dibahas penuh di Menyelesaikan Merge Conflicts . Penyelesaiannya pun berujung di index: pilih isi akhir, lalu git add menu.txt menutup tiga panggung menjadi satu:

bash
printf 'espresso 22000\nkopi susu 25000\n' > menu.txt
git add menu.txt
git ls-files -s

Output yang Dihasilkan:

text
100644 d707de7d592117c735e5ecac91632800503fc6f2 0	menu.txt
100644 70c8d3b5dae8af7e68ba3bb4c440ac52e353aefa 0	pelanggan.txt

Blob baru d707de7... (hasil penggabungan) duduk di stage 0 — index kembali normal, dan commit merge tinggal menulis tree dari daftar itu.

Rename: Kesimpulan Index, Bukan Data yang Disimpan #

Penutup yang menghubungkan index dengan satu fakta lama: Git tidak menyimpan kata “rename” di objeknya. Uji cepat:

bash
git commit -m "Selesaikan konflik harga menu"
git mv menu.txt daftar-menu.txt
git status --short

Output yang Dihasilkan:

text
R  menu.txt -> daftar-menu.txt
bash
git diff --cached -M --summary

Output yang Dihasilkan:

text
 rename menu.txt => daftar-menu.txt (100%)

git mv mengerjakan dua operasi yang kini kita kenali: memindahkan berkas di disk dan memperbarui index (buang entri menu.txt, daftarkan daftar-menu.txt menunjuk blob yang sama). Label R dan baris rename ... (100%) adalah kesimpulan yang dihitung ulang saat ditampilkan — index kehilangan satu path dan mendapat path lain dengan blob identik — bukan data yang tersimpan. Parameter -M mengaktifkan deteksinya; mekanisme lengkapnya dibedah di git mv .

Empat mekanisme Git kini utuh kita pegang: objek menyimpan isi, refs memberi nama, HEAD mencatat posisi, index menjembatani disk dengan commit. Satu pertanyaan dari awal section masih menggantung: mengapa repository tidak membengkak meski menyimpan setiap versi tiap berkas sebagai objek lepas? Jawabannya di teknik pengemasan di .git/objects/pack — packfile dan kompresi delta-nya, pembuka part berikutnya: Packfiles .