Skip to content

Tiga Commit Merah yang Tidak Boleh Ditulis Ulang

Adityo Guni Waluyo

Checker gate baru menandai tiga commit index lama karena tak punya trailer Task. Menulis ulang sejarah bukan jawabannya; grandfather by record adalah jalan ketiga.

Ringkasan

Commit lama kena FAIL gara-gara aturan baru minta trailer Task:, padahal commitnya lahir sebelum aturan itu ada. Rewrite sejarah bukan solusi, karena ubah satu commit bikin semua hash setelahnya ikut berubah dan merusak CI, cache, dll. Jadi aturannya grandfather by record: tiga hash itu dicatat sebagai pengecualian lengkap dengan alasannya, dan trailer ditegakkan cuma buat commit ke depan.

Checker script dijalankan pertama kali setelah aturan trailer Task: aktif. Hasilnya: tiga commit merah, 6f8c549, d45dbf3, 771704f, semuanya FAIL. Bukan karena rusak, tapi karena tidak punya trailer yang diminta aturan baru.

Ketiganya commit regenerasi. Isinya cuma satu file: COMMITS.md, index mesin yang menggambarkan ulang seluruh riwayat commit KotaPortal. Aturan barunya sederhana: commit yang menutup index wajib membawa trailer Task: dari closing card, sebagai alasan kenapa commit itu ada. Masalahnya, tiga commit itu lahir berminggu-minggu sebelum aturan itu dipikirkan.

Ini bukan bug di kode. Ini tabrakan antara gate yang baru dan sejarah yang sudah keburu ada.

Salah tebak: tulis ulang saja

Reaksi pertama saya klasik: rebase interaktif, edit tiga message itu, tambahkan trailer, force-push. Sepuluh menit kerja. Tapi opsi ini tidak pernah menyentuh akar masalahnya, dan justru menciptakan masalah baru.

Commit hash di Git mencakup message, author, committer, tree, dan parent. Ubah satu saja dari itu, hash-nya berubah. Dan karena setiap commit menyimpan hash parent-nya, perubahan di satu commit lama akan menghasilkan hash baru untuk semua commit sampai yang paling baru. Menurut dokumentasi git-filter-repo, ini "fundamental to Git and there is nothing you can do to change it" [3]. Satu baris trailer di tiga commit lama menghukum seluruh branch.

GitHub mendokumentasikan konsekuensinya dengan lugas: tooling dan automation yang bergantung pada hash commit yang tidak berubah akan rusak [4]. Di repo KotaPortal, yang bergantung pada hash itu banyak: CI cache, tautan review lama, referensi di COMMITS.md itu sendiri. Menulis ulang sejarah untuk memuaskan gate justru memecah lebih banyak hal daripada yang diperbaiki.

Kenapa gate tidak bisa menegakkan masa lalu

Ada alasan struktural kenapa tiga commit itu lolos dulu. commit-msg hook hanya menerima file pesan yang diajukan dan bisa menolak commit setelah membacanya [1]. Hook bekerja saat commit dibuat. Ia tidak punya ingatan ke belakang, tidak pernah melihat commit yang sudah masuk sebelum ia dipasang.

Dan hook sendiri bukan benteng. pre-commit bisa dilewati dengan --no-verify; keluaran nonzero memang menggagalkan commit sebelum dibuat [1], tapi hanya kalau orang tidak menambahkan flag itu. Jadi menegakkan aturan ke commit masa lalu lewat hook itu mustahil, dan menegakannya ke commit masa depan lewat hook saja tidak cukup.

Retrospective enforcement lewat jalur yang dirancang untuk prospective enforcement selalu berakhir di dua jalan: rewrite destruktif, atau gate yang terus merah selamanya. Ada jalan ketiga.

Grandfather by record

Keputusannya: catat pengecualian, jangan hapus sejarah. Di dalam checker script, sebuah dict bernama GRANDFATHER menyimpan tiga hash itu sebagai key, dengan alasan tertulis sebagai value: commit regenerasi index yang lahir sebelum aturan trailer berlaku. Saat gate menemukan hash di dict itu, hasilnya bukan FAIL, tapi "lolos dengan alasan tercatat".

Yang membedakan ini dari sekadar skip diam-diam adalah audit trail-nya. Setiap entry punya alasan sendiri, bukan satu pengecualian massal. Daftarnya terlihat saat code review, jadi pengecualian baru tidak bisa masuk tanpa terbaca. Dan keputusannya masuk ke baris DECISIONS.md, bukan cuma di kepala orang yang menulis commit-nya.

Istilahnya memang "grandfather": yang lama dibiarkan berlaku, aturan diperketat ke depan. Pengecualian ini tidak punya tanggal kedaluwarsa, dan itu disengaja. Commit di branch itu tidak akan pernah berubah lagi, jadi alasan pengecualiannya tidak akan pernah basi.

Ke depannya, aturan trailer ditegakkan tanpa kompromi. Formatnya mengikuti spesifikasi trailer Git: pasangan key-value dengan pemisah titik dua, diletakkan setelah satu baris kosong di akhir bagian free-form pesan commit [2]. Commit yang menutup regenerasi index wajib membawa Task: dari closing card-nya. Kalau tidak ada, gate menolak, dan orang yang commit-nya ditolak tinggal membuka kartunya dan menyalin alasan kerja itu ke trailer.

Satu catatan praktis: --no-verify tetap bisa melewati hook, jadi checker tetap dijalankan sebagai gate terpisah, bukan hanya sebagai hook. Hook untuk kenyamanan, gate untuk kebenaran. Pemeriksaannya mudah: jalankan git interpret-trailers pada pesan commit yang diajukan; kalau baris Task: tidak muncul di situ, commit itu belum memenuhi aturan.

Yang tersisa sekarang: tiga hash di dalam GRANDFATHER, satu baris di DECISIONS.md, dan gate yang untuk pertama kalinya berjalan hijau penuh tanpa menyentuh riwayat.

Sumber

Artikel terkait