Chapter sebelumnya memetakan keluarga refs: branch, tag, remote — semuanya berkas berisi hash. Satu penunjuk sengaja kita tunda: HEAD, penunjuk paling istimewa karena menjadi jawaban Git atas pertanyaan “kita sedang berada di mana”. Kebanyakan waktu HEAD bekerja diam-diam lewat nama branch, tetapi sesekali Git menampilkan status yang membuat semua panik: HEAD detached at 1a2b3c4. Chapter ini membuka kedua wajah HEAD langsung dari berkasnya, membuktikan kapan HEAD menjadi penunjuk tak langsung dan kapan menjadi hash telanjang, lalu menyaksikan sendiri mengapa commit yang dibuat saat detached berada dalam posisi terancam — dan bagaimana penunjuk @{-1} membawa kita kembali.

HEAD Attached: Penunjuk Tak Langsung #

Kita bangun repository latihan tiga commit dengan satu tag di commit pertama:

bash
mkdir latihan-head && cd latihan-head
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'Keranjang:\n- kopi\n' > keranjang.md
git add keranjang.md
git commit -m "Buat keranjang belanja"
git tag -a v1.0.0 -m "Rilis pertama"
printf 'Keranjang:\n- kopi\n- gula\n' > keranjang.md
git add keranjang.md
git commit -m "Tambah gula"
printf 'Keranjang:\n- kopi\n- gula\n- teh\n' > keranjang.md
git add keranjang.md
git commit -m "Tambah teh"

Dalam keadaan normal — Git menyebutnya attached — isi berkas HEAD bukan hash, melainkan nama ref:

bash
cat .git/HEAD

Output yang Dihasilkan:

text
ref: refs/heads/main

Bentuk ref: refs/heads/main berarti HEAD menunjuk ke branch, dan branch itulah yang menunjuk commit — rantai dua lompatan. Perintah plumbing git symbolic-ref membaca penunjuk tak langsung ini secara eksplisit:

bash
git symbolic-ref HEAD

Output yang Dihasilkan:

text
refs/heads/main
bash
git symbolic-ref --short HEAD

Output yang Dihasilkan:

text
main

Inilah mekanisme di balik nama branch pada prompt terminal dan git status: keduanya membaca HEAD, mengikuti penunjuknya ke refs/heads/main, lalu melaporkan main. Ketika kita membuat commit, alurnya pun jelas: commit baru ditulis, berkas refs/heads/main digeser ke commit baru, dan HEAD tidak berubah sama sekali. Peta lengkap posisi kita:

Wajah Kedua: HEAD Detached #

Berpindah ke sebuah commit — bukan branch — memutus rantai dua lompatan itu. Perintah git switch --detach sengaja diminta saat kita memotret rilis lama:

bash
git switch --detach v1.0.0

Output yang Dihasilkan:

text
HEAD is now at 4fecd2f Buat keranjang belanja

Mari kita baca isi berkas HEAD sekarang:

bash
cat .git/HEAD

Output yang Dihasilkan:

text
4fecd2f22a947d3c45053e9ffb5a93222888ee7a

Hash telanjang — HEAD tidak lagi menunjuk branch mana pun; keadaan ini disebut detached HEAD. Status memaparkannya dengan jelas:

bash
git status

Output yang Dihasilkan:

text
HEAD detached at v1.0.0
nothing to commit, working tree clean

Git 2.47 bahkan mengingat nama yang kita pakai saat melepaskan diri: detached at v1.0.0. Konsekuensi mekanisnya: commit baru tidak lagi punya branch yang otomatis digeser, karena tidak ada ref yang dipegang HEAD. git symbolic-ref menolak keras:

bash
git symbolic-ref HEAD

Output yang Dihasilkan:

text
fatal: ref HEAD is not a symbolic ref

Perintah itu keluar dengan kode galat 128 — HEAD memang bukan symbolic ref saat detached. Kedua wajah ini punya deteksi resminya: git symbolic-ref berhasil berarti attached, gagal berarti detached. Kondisi detached sendiri bukan kesalahan — justru berguna untuk memotret keadaan rilis lama, membandingkan dua commit, atau eksperimen singkat yang tidak ingin meninggalkan branch; git switch memperkenalkannya sebagai mode kerja sah.

Commit Saat Detached: Pekerjaan di Udara Tipis #

Yang berbahaya adalah membuat commit saat detached. Kita coba — suntingan kecil untuk rilis yang sedang kita tinjau:

bash
printf 'catatan: sedang uji coba di v1.0.0\n' > catatan.txt
git add catatan.txt
git commit -m "Catatan uji coba rilis"

Commit berhasil dibuat; git log --oneline -3 memperlihatkannya di puncak:

bash
git log --oneline -3

Output yang Dihasilkan:

text
3f5fb6d Catatan uji coba rilis
4fecd2f Buat keranjang belanja

Status kini bergeser dari detached at menjadi detached from:

bash
git status

Output yang Dihasilkan:

text
HEAD detached from v1.0.0
nothing to commit, working tree clean

Bedanya tajam: at berarti kita masih berdiri di titik pelepasan, from berarti kita sudah melangkah menjauh darinya. Sekarang saatnya kembali ke main — dan di sinilah Git memberi peringatan yang terkenal itu:

bash
git switch main

Output yang Dihasilkan:

text
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  3f5fb6d Catatan uji coba rilis

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> 3f5fb6d

Switched to branch 'main'

Commit 3f5fb6d tertinggal tanpa branch yang menunjuknya — hanya HEAD (yang kini sudah pindah) dan reflog yang masih mengenalnya. Git sendiri menyarankan obatnya: tempelkan branch baru pada hash itu dengan git branch <nama> 3f5fb6d. Prosedur penyelamatan lengkapnya — termasuk mengapa objek tak ditunjuk tetap hidup dan kapan git gc benar-benar menghapusnya — sudah dibahas tuntas pada Menangani Detached HEAD ; cukup diingat di sini: commit detached yang ditinggalkan tidak langsung hilang, tapi posisinya memang rawan.

Kembali dengan switch - dan Notasi @{-1} #

Bagaimana pulang ke posisi sebelumnya? Notasi @{-1} berarti “posisi checkout sebelumnya” (@{-2} dua langkah sebelumnya, dst.), dan git switch - adalah singkatannya. Kita coba:

bash
git switch -

Output yang Dihasilkan:

text
fatal: a branch is expected, got commit '3f5fb6de1ccbfb0d20aafa6e77018ddd772d4c7c'
hint: If you want to detach HEAD at the commit, try again with the --detach option.

git switch - menolak — @{-1} kita memang berupa commit lepas, bukan branch, dan switch hanya mau menerima branch tanpa --detach. Petunjuknya sendiri jelas: ulangi dengan --detach. Tapi @{-1} tetap menyelesaikan alamatnya dengan benar:

bash
git rev-parse @{-1}

Output yang Dihasilkan:

text
3f5fb6de1ccbfb0d20aafa6e77018ddd772d4c7c

Commit yang tadi tertinggal — persis. Untuk melihat switch - bekerja mulus, pindah dulu antar branch:

bash
git switch -c uji

Output yang Dihasilkan:

text
Switched to a new branch 'uji'
bash
git switch -

Output yang Dihasilkan:

text
Switched to branch 'main'

Dari uji kembali ke main dalam satu ketik — karena @{-1} kini berupa branch. Riwayat perpindahan lengkapnya terbaca dari reflog:

bash
git reflog

Output yang Dihasilkan:

text
d13ed34 HEAD@{0}: checkout: moving from uji to main
d13ed34 HEAD@{1}: checkout: moving from main to uji
d13ed34 HEAD@{2}: checkout: moving from 3f5fb6de1ccbfb0d20aafa6e77018ddd772d4c7c to main
3f5fb6d HEAD@{3}: commit: Catatan uji coba rilis
4fecd2f HEAD@{4}: checkout: moving from main to v1.0.0
d13ed34 HEAD@{5}: commit: Tambah teh
bf3dcfd HEAD@{6}: commit: Tambah gula
4fecd2f HEAD@{7}: commit (initial): Buat keranjang belanja

Delapan baris memuat seluruh perjalanan kita: tiga commit awal, pelepasan ke v1.0.0, commit saat detached, kepulangan ke main, lalu bolak-balik uji. Notasi @{-1}, @{-2} membaca daftar ini dari atas; reflog dan notasi HEAD@{N} dibedah penuh di Git Reflog . Detail perilaku symbolic-ref tersedia di dokumentasinya: git symbolic-ref .

Tiga pilar kini terkuak: objek (isi riwayat), refs (penunjuk bernama), dan HEAD (posisi kita). Tersisa satu mekanisme yang setiap hari kita pakai tanpa sadari namanya: index — berkas misterius .git/index yang menjadi jantung staging area. Perintah git add menulisnya, git status membacanya, dan konflik merge membelahnya menjadi tiga panggung. Chapter penutup bagian ini, Index dan Cache , membedahnya sampai ke format berkasnya.