Mekanisme exit code sudah di tangan; sekarang hook yang benar-benar berpikir. Masalah yang dijawab bagian ini klasik: tim web-toko sepakat memakai Conventional Commits — pesan berpola type(scope): deskripsi — tetapi Git tidak peduli; "update", "perbaiki dikit", dan "wip" tetap sah sebagai pesan commit. Hook commit-msg menutup celah itu: hook ini berjalan tepat setelah pesan ditulis dan sebelum commit lahir, membaca pesannya dari sebuah berkas, lalu menolak pesan yang melanggar pola dengan alasan yang jelas. Spesifikasi lengkap formatnya bisa dibaca di situs Conventional Commits — tersedia juga dalam terjemahan Indonesia.

Ada satu keputusan desain sebelum menulis skrip: hook ditempatkan di folder hooks/ di dalam repository, bukan langsung di .git/hooks/. Alasannya sederhana — berkas di .git/ tidak pernah ter-version-control dan tidak ikut saat di-clone, sedangkan hooks/commit-msg yang hidup di working tree bisa di-commit, di-review, dan dibagikan. Konsekuensinya ada satu langkah pemasangan: menyalin (copy) berkas itu ke .git/hooks/ yang benar-benar dicari Git — cara kedua yang jauh lebih rapi menyusul di Bagian 4 .

Skrip Validasi di Dalam Repository #

Tulis hooks/commit-msg lengkap, lalu pasang salinannya:

bash
mkdir hooks
cat > hooks/commit-msg <<'HOOK'
#!/bin/sh
# commit-msg — validasi pesan commit untuk web-toko.
# Git mengoper jalur berkas pesan sementara sebagai $1
# (biasanya .git/COMMIT_EDITMSG).

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
HOOK
chmod +x hooks/commit-msg
cp hooks/commit-msg .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg

Perintah cp itulah pemasangannya: Git mencari hook bernama commit-msg di .git/hooks/, jadi salinan harus sampai ke sana dan tetap executable. Verifikasi keduanya:

bash
stat -c '%A %n' .git/hooks/commit-msg

Output yang Dihasilkan:

text
-rwxrwxr-x .git/hooks/commit-msg

Membaca Pesan dari Berkas $1 #

Mekanisme masukannya: Git menyimpan draf pesan ke berkas sementara — biasanya .git/COMMIT_EDITMSG — lalu mengoper jalurnya sebagai argumen pertama, jadi $1 di dalam skrip. Bedah baris-per-baris inti skripnya:

  1. berkas_pesan="$1" menyimpan jalur berkas pesan — satu-satunya argumen yang Git kirim ke hook commit-msg.
  2. grep -v '^#' "$berkas_pesan" | sed '/^[[:space:]]*$/d' | head -n 1 mengambil subjek: baris pertama yang bukan komentar. Saat commit ditulis lewat editor, berkas pesan masih memuat baris petunjuk berawalan # yang baru dibuang Git setelah hook selesai — perilaku yang dibedah tuntas di Commit-Msg Hook ; sed tambahan melewati baris kosong.
  3. pola='^(feat|fix|docs|chore|refactor|test)(\(.+\))?: .+' adalah pola Conventional Commits versi web-toko: salah satu dari enam type, scope opsional dalam kurung, titik dua, spasi, lalu deskripsi. Pilihan enam type ini sengaja lebih ramping daripada daftar lengkap spesifikasinya — tim kecil cukup memulai dari yang paling sering terpakai.
  4. if [ -z "$subjek" ] menolak pesan kosong lebih dulu, dengan pesan yang mengajari formatnya.
  5. printf '%s\n' "$subjek" | grep -qE "$pola" menguji subjek terhadap pola; printf menambah baris baru karena grep menguji per baris, dan -q membuatnya bisu karena kita hanya butuh exit code-nya.
  6. Blok { ... } >&2 mencetak penolakan beserta petunjuk perbaikan ke stderr — pesan kesalahan yang hanya berkata “salah” tanpa berkata “bagaimana benarnya” hanya melahirkan frustrasi.
  7. [ "${#subjek}" -gt 72 ] menjaga subjek tetap nyaman dibaca di git log --oneline — angka yang sama dipakai alat pemeriksa pesan di CI.

Uji: “update” Ditolak, “feat: tambah keranjang” Lolos #

Fitur keranjang masih menunggu di staging area sejak Bagian 1 — bahan uji yang sempurna. Coba pesan favorit semua orang yang sedang buru-buru:

bash
git commit -m "update"

Output yang Dihasilkan:

text
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'

Commit batal — dan penulisnya tinggal membaca empat baris itu untuk tahu persis cara memperbaikinya. Staging area tetap utuh memuat keranjang. Sekarang ketik pesannya benar:

bash
git commit -m "feat: tambah keranjang"

Output yang Dihasilkan:

text
[main 9643466] feat: tambah keranjang
 1 file changed, 4 insertions(+)

Fitur keranjang akhirnya lahir setelah menunggu dua bagian — dan sejak commit ini, semua pesan web-toko melewati pengadilan yang sama. Terakhir, hooknya sendiri layak masuk repository:

bash
git add hooks/commit-msg
git commit -m "chore: pasang validasi pesan commit"

Output yang Dihasilkan (perintah terakhir):

text
[main 668d6c2] chore: pasang validasi pesan commit
 1 file changed, 29 insertions(+)
 create mode 100755 hooks/commit-msg
bash
git log --oneline

Output yang Dihasilkan:

text
668d6c2 chore: pasang validasi pesan commit
9643466 feat: tambah keranjang
e9d7cc7 feat: rintis web-toko

Ada satu kejutan manis di commit terakhir: create mode 100755 — Git menyimpan bit executable bersama isi berkas, jadi salinan hooks/commit-msg di clone mana pun nanti tetap siap dieksekusi. Dan perhatikan juga bahwa commit yang memasang hook itu sendiri divalidasi oleh hook yang baru dipasangnya: pesan chore: pasang validasi pesan commit lolos pola yang berlaku mulai saat itu.

Pesan commit kini dijaga, tetapi isi berkasnya masih bebas — spasi menggantung di ujung baris, jejak debug, dan berkas raksasa bisa masuk dengan pesan yang rapi. Bagian berikutnya memasang pemeriksa kedua di gerbang yang sama: Bagian 3: Otomasi Pemeriksaan .

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

scripts/validate-commit-message.sh

Unduh berkas
bash
#!/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