Pesan commit sudah dijaga, tetapi gerbang kedua masih kosong: isi staging area lolos begitu saja. Bagian ini mengisi gerbang pre-commit dengan tiga pemeriksaan yang masing-masing menjawab kelalaian berbeda — whitespace error yang mengotori diff, jejak console.log/print( yang tertinggal dari sesi debugging, dan berkas besar yang tidak seharusnya masuk repository. Ketiganya berbagi satu sumber kebenaran: staging area, daftar berkas yang benar-benar akan masuk commit berikutnya. Bagian ini ditutup dengan satu pembenahan struktur: validasi pesan yang saat ini tertanam di hooks/commit-msg dipisahkan menjadi skrip inti scripts/validate-commit-message.sh, sehingga dua gerbang kita tinggal menjadi pembungkus tipis di atas kumpulan pemeriksaan yang bisa dipakai ulang.
Tiga Pemeriksaan, Satu Gerbang pre-commit #
Tulis hooks/pre-commit yang riil, lalu pasang salinannya ke .git/hooks/ seperti pada Bagian 2:
cat > hooks/pre-commit <<'HOOK'
#!/bin/sh
# pre-commit — pemeriksaan staging area untuk web-toko.
# Tiga pemeriksaan: whitespace error, jejak debug, berkas besar.
status=0
berkas="$(git diff --cached --name-only)"
# 1) Whitespace error pada diff yang di-staging.
if ! git diff --cached --check; then
echo "pre-commit: whitespace error — lihat daftar di atas" >&2
status=1
fi
# 2) Jejak debug pada berkas kode yang di-staging.
# Berkas perkakas (hooks/, scripts/) dilewati agar hook tidak
# menolak dirinya sendiri — pola print( tertulis di skrip ini.
for f in $berkas; do
case "$f" in
hooks/*|scripts/*) continue ;;
esac
[ -f "$f" ] || continue
if grep -n "console\.log\|print(" "$f"; then
echo "pre-commit: jejak debug di $f — hapus sebelum commit" >&2
status=1
fi
done
# 3) Berkas lebih besar dari 1 MiB (1.048.576 byte).
for f in $berkas; do
[ -f "$f" ] || continue
ukuran="$(wc -c < "$f")"
if [ "$ukuran" -gt 1048576 ]; then
echo "pre-commit: $f ($ukuran byte) melebihi 1 MiB" >&2
status=1
fi
done
exit $status
HOOK
chmod +x hooks/pre-commit
cp hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commitBaris-baris kuncinya bekerja begini:
berkas="$(git diff --cached --name-only)"mengambil daftar berkas staged — baris pertama seluruh pemeriksaan mengacu ke sini. Flag--cachedberarti staging area, bukan seluruh working directory; berkas yang belum di-git addbukan urusan commit ini.status=0…exit $statusmengumpulkan hasil ketiga pemeriksaan: satu variabel ditandai1setiap ada pelanggaran, dan hook keluar dengan nilai akhirnya sekali saja. Pola ini membuat satu percobaan commit melaporkan semua masalah sekaligus, bukan satu per satu.git diff --cached --checkadalah pemeriksaan whitespace bawaan Git; perintah ini sekaligus mencetak lokasi pelanggarannya, jadi skrip tinggal melaporkan gagal — kelakuan lengkap opsi ini dibahas di Whitespace dan Opsi Diff .grep -n "console\.log\|print(" "$f"mencari dua pola debug sekaligus dengan regex BRE:\-nmenyertakan nomor baris, dan\|adalah alternasi. Bilagrepmenemukan kecocokan,grepmencetak barisnya dan keluar 0 — itulah kondisi gagal kita.case "$f" in hooks/*|scripts/*) continue ;; esacmelewati berkas perkakas. Alasannya akan terbukti nyata di bagian bawah halaman: polaprint(tertulis di dalam skrip hook ini sendiri, dan tanpa pengecualian itu hook akan menolak commit yang memasang dirinya.[ -f "$f" ] || continuemelewati berkas yang sudah tidak ada di working directory — misalnya berkas yang justru dihapus pada commit ini;greppada berkas tak ada akan memicu kesalahan palsu.ukuran="$(wc -c < "$f")"membaca ukuran berkas dalam byte, dibandingkan dengan1048576— satu MiB. Tim yang memakai Git LFS biasanya menaikkan ambang ini.
Uji Whitespace: Spasi Menggantung Ditangkap #
Berkas uji pertama adalah halaman promo dengan dua spasi tersembunyi di ujung barisnya — lihat dua spasi sebelum \n pada perintah printf:
printf 'Diskon akhir tahun untuk semua produk. \nBerlaku 1-31 Desember. \n' > src/promo.txt
git add src/promo.txt
git diff --cached --checkOutput yang Dihasilkan (perintah terakhir):
src/promo.txt:1: trailing whitespace.
+Diskon akhir tahun untuk semua produk.
src/promo.txt:2: trailing whitespace.
+Berlaku 1-31 Desember. Setiap pelanggaran dilaporkan dengan format berkas:baris: jenis lalu barisnya dikutip dengan prefiks + — spasi menggantung di ujung kedua baris ikut terlihat pada keluaran asli di atas. Sekarang biarkan hook yang bicara:
git commit -m "feat: tambah halaman promo"Output yang Dihasilkan:
src/promo.txt:1: trailing whitespace.
+Diskon akhir tahun untuk semua produk.
src/promo.txt:2: trailing whitespace.
+Berlaku 1-31 Desember.
pre-commit: whitespace error — lihat daftar di atasCommit batal, dan daftar di atasnya sudah menjelaskan ke mana melihat. Tulis ulang berkasnya bersih, lalu commit:
printf 'Diskon akhir tahun untuk semua produk.\nBerlaku 1-31 Desember.\n' > src/promo.txt
git add src/promo.txt
git commit -m "feat: tambah halaman promo"Output yang Dihasilkan (perintah terakhir):
[main 0f3e0f1] feat: tambah halaman promo
1 file changed, 2 insertions(+)
create mode 100644 src/promo.txtUji Jejak Debug: console.log Tertinggal #
Kelalaian kedua paling sering lolos review: console.log yang dipakai saat menulis fungsi lalu terlupakan. Modul kupon berikut memuat persis itu:
printf 'function hitungDiskon(total) {\n console.log("debug: total belanja =", total);\n return total * 0.9;\n}\n' > src/kupon.js
git add src/kupon.js
git diff --cached --name-only | xargs grep -n "console\.log\|print("Output yang Dihasilkan (perintah terakhir):
2: console.log("debug: total belanja =", total);Pipeline itu idenya: git diff --cached --name-only mendaftar berkas staged, xargs grep -n mencari pola debug di dalamnya — cocok ketemu, grep mencetak 2: karena kecocokan ada di baris kedua. Namun hook tidak memakai pipeline mentah itu: grep keluar 0 saat menemukan dan 1 saat tidak — kebalikan dari logika “ditolak bila ketemu” — dan bila staging kosong, xargs tetap menjalankan grep tanpa operan sehingga grep membaca stdin. Karena itu hook membalik logikanya lewat if grep ...; then dan mengiterasi daftar berkas dalam for:
git commit -m "feat: tambah kupon diskon"Output yang Dihasilkan:
2: console.log("debug: total belanja =", total);
pre-commit: jejak debug di src/kupon.js — hapus sebelum commitBaris pertama adalah keluaran grep dari dalam hook — nomor baris dan isinya, sehingga penulisnya tahu persis ke mana melihat. Bersihkan jejaknya, lalu ulangi commit:
printf 'function hitungDiskon(total) {\n return total * 0.9;\n}\n' > src/kupon.js
git add src/kupon.js
git commit -m "feat: tambah kupon diskon"Output yang Dihasilkan (perintah terakhir):
[main 3e08c4d] feat: tambah kupon diskon
1 file changed, 3 insertions(+)
create mode 100644 src/kupon.jsUji Berkas Besar: Batas 1 MiB #
Pemeriksaan ketiga menjaga repository dari aset raksasa — logo hero berukuran 1.200.004 byte, cukup realistis untuk tergelincir masuk:
mkdir aset
python3 -c "open('aset/hero.bin','wb').write(b'\x89PNG' + bytes(1200000))"
git add aset/hero.bin
git commit -m "chore: tambah hero beranda"Output yang Dihasilkan (perintah terakhir):
pre-commit: aset/hero.bin (1200004 byte) melebihi 1 MiBHook melaporkan nama berkas, ukuran persisnya, dan ambang batasnya — angka besar menunggu di Git LFS atau CDN, bukan di repository. Tarik berkas itu keluar dari staging area dan hapus berkasnya:
git reset aset/hero.bin
rm -rf asetHook yang Tidak Menolak Dirinya Sendiri #
Saatnya meng-commit hook pre-commit itu sendiri. Sebelum itu, bukti nyata kenapa skrip memuat pengecualian case hooks/*|scripts/* — jalankan pemeriksaan debug pada berkas hooknya:
grep -n "console\.log\|print(" hooks/pre-commitOutput yang Dihasilkan:
16:# menolak dirinya sendiri — pola print( tertulis di skrip ini.
22: if grep -n "console\.log\|print(" "$f"; thenDua kecocokan: baris komentar yang menyebut print( dan baris pola grep itu sendiri. Tanpa pengecualian hooks/*|scripts/*, pemeriksaan debug akan menolak commit yang justru memasang pemeriksaan itu — hook menggigit ekornya sendiri. Dengan pengecualian, commit berjalan normal:
git add hooks/pre-commit
git commit -m "chore: pasang pemeriksaan pre-commit"Output yang Dihasilkan (perintah terakhir):
[main 6f7aed7] chore: pasang pemeriksaan pre-commit
1 file changed, 38 insertions(+)
create mode 100755 hooks/pre-commitMemisahkan Validasi ke Skrip Inti #
Dua gerbang kini hidup, tapi satu bau kode mulai terasa: aturan validasi pesan tertanam di dalam hooks/commit-msg, padahal aturan itu juga berguna bagi CI atau pemeriksa pesan di server. Pembenahannya adalah memindahkan inti validasi ke scripts/validate-commit-message.sh dan menjadikan hooks/commit-msg pembungkus tipis yang hanya meneruskan $1:
mkdir scripts
cat > scripts/validate-commit-message.sh <<'SKRIP'
#!/bin/sh
# validate-commit-message.sh — inti validasi pesan commit web-toko.
# Dipanggil oleh hooks/commit-msg dengan $1 = berkas pesan sementara
# (biasanya .git/COMMIT_EDITMSG). Aturan: Conventional Commits.
berkas_pesan="$1"
subjek="$(grep -v '^#' "$berkas_pesan" | sed '/^[[:space:]]*$/d' | head -n 1)"
pola='^(feat|fix|docs|chore|refactor|test)(\(.+\))?: .+'
if [ -z "$subjek" ]; then
echo "commit-msg: pesan kosong — tulis subjek dengan format type(scope): deskripsi" >&2
exit 1
fi
if ! printf '%s\n' "$subjek" | grep -qE "$pola"; then
{
echo "commit-msg: subjek ditolak: '$subjek'"
echo " format: type(scope): deskripsi — scope opsional"
echo " type: feat | fix | docs | chore | refactor | test"
echo " contoh: 'feat: tambah keranjang' atau 'fix(ui): benahi tombol bayar'"
} >&2
exit 1
fi
if [ "${#subjek}" -gt 72 ]; then
echo "commit-msg: subjek ${#subjek} karakter — maksimal 72: '$subjek'" >&2
exit 1
fi
SKRIP
chmod +x scripts/validate-commit-message.sh
cat > hooks/commit-msg <<'HOOK'
#!/bin/sh
# commit-msg — pintu masuk validasi pesan commit untuk web-toko.
# Git mengoper jalur berkas pesan sebagai $1; pemeriksaan intinya
# didelegasikan ke scripts/validate-commit-message.sh agar bisa
# dipakai ulang oleh alat lain (CI, commitlint, dan sebagainya).
exec "$(git rev-parse --show-toplevel)/scripts/validate-commit-message.sh" "$@"
HOOK
chmod +x hooks/commit-msg
cp hooks/commit-msg .git/hooks/commit-msg
chmod +x .git/hooks/commit-msgIsi hooks/commit-msg kini hanya komentar dan satu baris kerja: exec menggantikan proses hook dengan skrip inti dan mengoper seluruh argumen ("$@"); jalur skrip inti dihitung dari git rev-parse --show-toplevel supaya tetap benar di clone mana pun. Struktur ini ringan dibaca: hook adalah pintu, pemeriksaan adalah bangunan.
Refaktor yang tidak diuji adalah refaktor yang bohong. Pasang kedua berkasnya ke staging area, coba dulu pesan yang salah, baru commit dengan pesan yang benar:
git add hooks/commit-msg scripts/validate-commit-message.sh
git commit -m "update"
git commit -m "refactor: pisahkan validasi pesan ke skrip inti"Output yang Dihasilkan (perintah git commit -m "update"):
commit-msg: subjek ditolak: 'update'
format: type(scope): deskripsi — scope opsional
type: feat | fix | docs | chore | refactor | test
contoh: 'feat: tambah keranjang' atau 'fix(ui): benahi tombol bayar'Output yang Dihasilkan (perintah git commit -m "refactor: ..."):
[main 1a1337c] refactor: pisahkan validasi pesan ke skrip inti
2 files changed, 34 insertions(+), 27 deletions(-)
create mode 100755 scripts/validate-commit-message.shPenolakan pertama membuktikan delegasi bekerja — pembungkus memanggil skrip inti, skrip inti menolak. Baris 2 files changed, 34 insertions(+), 27 deletions(-) menunjukkan pemindahan itu: 29 baris lama di hooks/commit-msg menyusut jadi 8, sementara scripts/validate-commit-message.sh lahir dengan 33 baris. Riwayat web-toko kini tujuh commit:
git log --onelineOutput yang Dihasilkan:
1a1337c refactor: pisahkan validasi pesan ke skrip inti
6f7aed7 chore: pasang pemeriksaan pre-commit
3e08c4d feat: tambah kupon diskon
0f3e0f1 feat: tambah halaman promo
668d6c2 chore: pasang validasi pesan commit
9643466 feat: tambah keranjang
e9d7cc7 feat: rintis web-tokoKedua gerbang berdiri dan saling menopang. Yang tersisa bukan lagi membangun, melainkan membuktikan: gerbang sekuat apa pun tetap hanya hidup di mesin yang memasangnya. Bagian penutup menguji semuanya secara sistematis, lalu menyebarluaskan ke mesin rekan tim: Bagian 4: Pengujian dan Pemasangan .
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
scripts/validate-commit-message.sh
Unduh berkas#!/bin/sh
# validate-commit-message.sh — inti validasi pesan commit web-toko.
# Dipanggil oleh hooks/commit-msg dengan $1 = berkas pesan sementara
# (biasanya .git/COMMIT_EDITMSG). Aturan: Conventional Commits.
berkas_pesan="$1"
subjek="$(grep -v '^#' "$berkas_pesan" | sed '/^[[:space:]]*$/d' | head -n 1)"
pola='^(feat|fix|docs|chore|refactor|test)(\(.+\))?: .+'
if [ -z "$subjek" ]; then
echo "commit-msg: pesan kosong — tulis subjek dengan format type(scope): deskripsi" >&2
exit 1
fi
if ! printf '%s\n' "$subjek" | grep -qE "$pola"; then
{
echo "commit-msg: subjek ditolak: '$subjek'"
echo " format: type(scope): deskripsi — scope opsional"
echo " type: feat | fix | docs | chore | refactor | test"
echo " contoh: 'feat: tambah keranjang' atau 'fix(ui): benahi tombol bayar'"
} >&2
exit 1
fi
if [ "${#subjek}" -gt 72 ]; then
echo "commit-msg: subjek ${#subjek} karakter — maksimal 72: '$subjek'" >&2
exit 1
fi