Di dalam folder .git, Git menyimpan semuanya sebagai object, dan tiap object dinamai dengan hash dari isinya. Hash itu seperti sidik jari: isi yang sama selalu menghasilkan sidik jari yang sama, dan beda satu huruf saja sidik jarinya berubah total. Karena namanya diambil dari isi, Git gak pernah menyimpan isi yang sama dua kali.
🧪 Tebakan cepat: kamu bikin 2 commit. Di commit kedua cuma
app.jsyang berubah,README.mdtetap. Ada berapa salinan isiREADME.mdyang disimpan Git? Tebak dulu, jawabannya ada di bagian "Commit menyimpan snapshot, bukan diff".
Hash: nama dari isi
Repository latihan ini berisi README.md (isinya # Object Model) dan app.js.
$ git hash-object README.md
e6962494240ce890906939be986e7c9a196f20a0
- Hash Git (SHA-1) panjangnya 40 karakter heksadesimal, yaitu angka 0 sampai 9 dan huruf a sampai f. Biasanya cukup ditulis 7 karakter pertamanya:
e696249. - Hash dihitung dari isi file (plus jenis dan ukurannya), bukan dari nama file. Dua file berbeda nama dengan isi yang persis sama punya hash yang sama.
README.mdberisi# Object Modeldi komputer siapa pun hash-nyae696249. git hash-objectcuma menghitung, gak menyimpan apa-apa.
Empat jenis object
| Object | Isinya | Di repo ini |
|---|---|---|
| blob | isi satu file, tanpa nama | isi README.md, isi app.js |
| tree | daftar nama file dan folder, masing-masing dengan hash blob atau tree-nya | isi folder proyek |
| commit | hash tree, hash parent, author, waktu, dan pesan | initial, update |
| tag | penunjuk ke satu commit, plus nama pembuat dan pesan (annotated tag) | dibahas di pelajaran Git Tag |
git cat-file -p menampilkan isi object apa pun. Ini repo latihan setelah dua commit: initial, lalu echo added >> app.js dan commit update.
$ git cat-file -p HEAD
tree f42ae588e355b1868af494346ae690c501821376
parent 24f0fbab243182bc80947f22cd64795997a2342d
author Budi <[email protected]> 1767225660 +0700
committer Budi <[email protected]> 1767225660 +0700
update
$ git cat-file -p f42ae58
100644 blob e6962494240ce890906939be986e7c9a196f20a0 README.md
100644 blob 954567f8f8a8fd5349f30d8d5d685f6eba6703af app.js
$ git cat-file -p 954567f
console.log(1)
added
- Commit gak menyimpan file secara langsung. Dia menunjuk satu tree, dan tree itu menunjuk blob-blob.
parentmenunjuk commit sebelumnya. Commit pertama gak punya parent.100644artinya file biasa. Nama file disimpan di tree, bukan di blob.- Waktunya ditulis sebagai jumlah detik sejak 1 Januari 1970 (
1767225660), plus zona waktu+0700. Karena nama dan waktu ikut dihitung, hash commit di komputermu akan beda, walau isi filenya sama.
Commit menyimpan snapshot, bukan diff
Tree kedua commit itu:
Commit initial (24f0fba) |
Commit update (b599099) |
|
|---|---|---|
| tree | 66f43bf |
f42ae58 |
README.md |
e696249 |
e696249 |
app.js |
e14c4f2 |
954567f |
Tiap commit menyimpan snapshot lengkap: tree yang menyebut semua file. Tapi isi README.md gak berubah, jadi hash-nya sama, dan kedua tree menunjuk blob yang sama. Isinya cuma disimpan sekali. Itu jawaban tebakannya: satu. Total object-nya 7: 3 blob, 2 tree, 2 commit.
git diff dan git show menghitung perubahan saat itu juga, dengan membandingkan dua snapshot. Supaya hemat tempat, belakangan Git memadatkan object ke packfile, tapi model datanya tetap snapshot.
Branch dan HEAD cuma penunjuk
$ cat .git/refs/heads/main
b599099245eff68a5dfc714169941a4885e7242c
$ cat .git/HEAD
ref: refs/heads/main
- Branch adalah file kecil berisi hash satu commit: penunjuk (pointer) ke commit, bukan salinan file. Itu sebabnya bikin branch di Git murah dan cepat.
.git/HEADmenyimpan ref ke branch yang sedang aktif:ref: refs/heads/main.- Waktu kamu commit, Git membuat object commit baru yang parent-nya commit lama, lalu menulis hash baru itu ke
refs/heads/main. HEAD ikut maju karena dia menunjukmain. - Kalau kamu pindah langsung ke satu commit (
git switch --detach 24f0fba, atau cara lamagit checkout 24f0fba), isiHEADjadi hash itu sendiri. Keadaan ini disebut detached HEAD.
🤔 Kenapa riwayatnya disebut DAG?
Tiap commit menunjuk parent-nya, gak pernah anaknya. Commit hasil merge punya dua parent. Panahnya selalu ke masa lalu, jadi gak mungkin berputar balik: graf berarah tanpa siklus, Directed Acyclic Graph (DAG). Hash menjaminnya: hash commit dihitung dari hash parent-nya, jadi sebuah commit gak bisa menunjuk commit yang dibuat sesudahnya.
⚠️ Jebakan yang sering ketemu
- Mengira branch itu salinan folder. Branch cuma file 41 byte berisi hash. Isi file-filenya tetap di object yang sama.
- Mengedit isi
.gitdengan tangan. Object dinamai dari isinya, jadi mengubah isinya merusak repo. Biarkan perintah Git yang menulis ke.git. cat .git/refs/heads/mainbilang file-nya gak ada. Git kadang memadatkan ref ke file.git/packed-refs. Cara yang selalu jalan:git rev-parse main.- Commit kedua tanpa perubahan. Git asli menolak commit yang gak mengubah apa pun (
nothing to commit, working tree clean). Di latihan, ubahapp.jsdulu denganecho added >> app.js, barugit add .dan commitupdate.
TL;DR: Git menyimpan semuanya sebagai object yang dinamai hash SHA-1 dari isinya (40 karakter, biasanya ditulis 7). Ada 4 jenis: blob (isi file), tree (daftar nama file dengan hash-nya), commit (tree, parent, author, waktu, pesan), dan tag (annotated tag). Commit menyimpan snapshot lengkap, tapi isi yang sama cuma disimpan sekali. Branch adalah pointer ke commit, file kecil di .git/refs/heads/ berisi satu hash, dan .git/HEAD berisi ref ke branch aktif, ref: refs/heads/main. git cat-file -p menampilkan isi object apa pun.