Mesin yang menolak commit kotor bekerja lebih konsisten daripada ingatan manusia — itu janji pre-commit yang sudah kita buktikan mekanismenya di dua chapter sebelumnya. Sekarang kita bangun versi produksinya: satu skrip yang menjalankan tiga pemeriksaan nyata, masing-masing menjawab kelalaian yang berbeda — jejak console.log dan print() yang lupa dihapus, whitespace error yang mengotori diff, dan berkas besar yang tidak seharusnya masuk repository. Setiap pemeriksaan diuji sungguhan: commit kotor ditolak, commit bersih lolos.

Tiga Pemeriksaan, Satu Gerbang #

Semua pemeriksaan berbagi satu sumber kebenaran: staging area. git diff --cached --name-only mencantumkan berkas yang benar-benar akan masuk commit berikutnya — bukan seluruh working directory, karena berkas yang belum di-git add bukan urusan commit ini. Skrip lengkapnya:

bash
mkdir latihan-precommit && cd latihan-precommit
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
cat > .git/hooks/pre-commit <<'HOOK'
#!/bin/sh
# pre-commit — gerbang sebelum pesan commit ditulis.

# 1) Berkas yang benar-benar masuk commit berikutnya.
berkas="$(git diff --cached --name-only)"

# 2) Jejak debug pada berkas yang di-staging.
for f in $berkas; do
  [ -f "$f" ] || continue
  if grep -nE 'console\.log|print\(' "$f"; then
    echo "pre-commit: jejak debug ditemukan di $f — bersihkan dulu" >&2
    exit 1
  fi
done

# 3) Whitespace error pada baris baru.
if ! git diff --cached --check; then
  echo "pre-commit: whitespace error — lihat daftar di atas" >&2
  exit 1
fi

# 4) Berkas lebih besar dari 1 MiB.
for f in $berkas; do
  [ -f "$f" ] || continue
  ukuran="$(stat -c %s "$f")"
  if [ "$ukuran" -gt 1048576 ]; then
    echo "pre-commit: $f ($ukuran byte) melebihi 1 MiB" >&2
    exit 1
  fi
done
HOOK
chmod +x .git/hooks/pre-commit

Baris-baris kuncinya bekerja begini:

  1. berkas="$(git diff --cached --name-only)" mengambil daftar berkas staged — baris pertama seluruh pemeriksaan mengacu ke sini.
  2. [ -f "$f" ] || continue melewati berkas yang sudah tidak ada di working directory — misalnya berkas yang justru dihapus pada commit ini; grep pada berkas tak ada akan memicu kesalahan palsu.
  3. grep -nE 'console\.log|print\(' mencari dua pola debug sekaligus; -n menyertakan nomor baris, -E mengaktifkan regex. Bila grep menemukan kecocokan, grep mencetak barisnya dan keluar 0 — itulah kondisi gagal kita.
  4. git diff --cached --check adalah pemeriksaan bawaan Git untuk whitespace error; ia sekaligus mencetak lokasinya, jadi skrip tinggal melaporkan gagal.
  5. stat -c %s membaca ukuran berkas dalam byte; ambang 1048576 adalah 1 MiB — tim yang memakai Git LFS menaikkannya.
  6. Setiap cabang gagal menutup dengan exit 1 — bahasa tunggal yang Git pahami sebagai “batalkan commit”.

Uji Pertama: Jejak Debug Ditolak #

Berkas uji pertama memuat persis kesalahan yang paling sering lolos: console.log yang dipakai saat menulis fungsi lalu terlupakan:

bash
cat > app.js <<'EOF'
function sapa() {
  console.log("halo dari sapa()");
  return "siap";
}
EOF
git add app.js
git commit -m "feat: fungsi sapa"

Output yang Dihasilkan (perintah terakhir):

text
2:  console.log("halo dari sapa()");
pre-commit: jejak debug ditemukan di app.js — bersihkan dulu

Baris pertama adalah keluaran grep — nomor baris dan isinya, sehingga penulisnya tahu persis ke mana melihat. Commit batal, staging area utuh. Setelah baris debug dihapus, commit yang sama lolos tanpa drama:

bash
cat > app.js <<'EOF'
function sapa() {
  return "siap";
}
EOF
git add app.js
git commit -m "feat: fungsi sapa"

Output yang Dihasilkan (perintah terakhir):

text
[main (root-commit) e70c3ec] feat: fungsi sapa
 1 file changed, 3 insertions(+)
 create mode 100644 app.js

Hash e70c3ec berbeda di mesin pembaca.

Uji Kedua dan Ketiga: Whitespace dan Berkas Besar #

Pemeriksaan kedua menangkap spasi yang menggantung di ujung baris. Berkas catatan.md dibuat dengan dua spasi tersembunyi di ujung:

bash
printf 'catatan  \n' > catatan.md
git add catatan.md
git commit -m "docs: tambah catatan"

Output yang Dihasilkan (perintah terakhir):

text
catatan.md:1: trailing whitespace.
+catatan  
pre-commit: whitespace error — lihat daftar di atas

git diff --cached --check menyebut berkas, nomor baris, jenis masalahnya, lalu menampilkan baris bermasalah dengan prefiks + — spasi di ujung baris juga ikut terlihat di keluaran asli di atas. Untuk pemeriksaan ketiga, catatan diperbaiki lalu sebuah berkas besar 2,1 MB ikut di-staging:

bash
printf 'catatan\n' > catatan.md
git add catatan.md
python3 -c "open('logo.bin','wb').write(b'\x89PNG' + bytes(2_100_000))"
git add logo.bin
git commit -m "chore: tambah aset"

Output yang Dihasilkan (perintah terakhir):

text
pre-commit: logo.bin (2100004 byte) melebihi 1 MiB

Hook melaporkan nama, ukuran persisnya, dan ambang batas. Berkas besar ditarik keluar dari staging area — git reset logo.bin — dan commit catatan yang bersih kemudian lolos:

bash
git reset logo.bin
rm logo.bin
git commit -m "docs: tambah catatan"

Output yang Dihasilkan (perintah terakhir):

text
[main 44ba0f0] docs: tambah catatan
 1 file changed, 1 insertion(+)
 create mode 100644 catatan.md

Performa dan Jalan Darurat #

pre-commit berjalan pada setiap commit — hook yang lambat menghukum frekuensi tertinggi dalam alur kerja Git. Ukurannya saat ini:

bash
printf 'data\n' > data.md
git add data.md
time git commit -m "chore: tambah data"

Output yang Dihasilkan (bagian time, di mesin Anda angkanya berbeda):

text
real	0m0.008s

0,008 detik — tiga pemeriksaan di atas memang murah. Aturannya praktis: pemeriksaan yang lebih berat (test suite, type checker) sebaiknya tidak menunggu di hook, melainkan berjalan terpisah atau dipindah ke hook pre-push yang frekuensinya lebih rendah. Git juga menyediakan pintu darurat yang jujur:

bash
printf 'draf cepat\n' > draf.md
git add draf.md
git commit --no-verify -m "wip: draf sementara"

Output yang Dihasilkan (perintah terakhir):

text
[main 163f1f5] wip: draf sementara
 1 file changed, 1 insertion(+)
 create mode 100644 draf.md

Hook ini kini menjaga commit kita — tapi hanya di mesin ini, karena .git/hooks/ tidak pernah ikut di-clone. Sebelum masalah itu dibedah, ada satu hook tetangga yang sama penting untuk kualitas riwayat: pemeriksa pesan commit.