Hook pre-commit yang menjaga kualitas commit tim hanya bermanfaat bila terpasang di semua mesin — dan di situlah letak masalah strukturalnya: .git/hooks/ hidup di dalam .git, folder yang tidak pernah dikirim saat git clone, git push, maupun git fetch. Kontributor baru menerima repository lengkap dengan kode dan riwayat, tapi tanpa satu pun hook. Bab ini membandingkan empat cara menutup celah itu, semuanya diuji nyata: skrip pemasang, konfigurasi core.hooksPath, framework pre-commit, dan template directory.

Solusi A: Folder hooks/ dalam Repository plus install.sh #

Cara paling klasik: hook diperlakukan sebagai berkas proyek biasa di folder hooks/ sehingga ikut ter-version-control, plus satu skrip pemasang yang menautkannya ke .git/hooks/ clone:

bash
mkdir -p latihan-distribusi/proyek-tim/hooks && cd latihan-distribusi/proyek-tim
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
cat > hooks/pre-commit <<'EOF'
#!/bin/sh
# Larang jejak debug pada berkas yang di-staging.
for f in $(git diff --cached --name-only); do
  [ -f "$f" ] || continue
  if grep -qE 'console\.log' "$f"; then
    echo "pre-commit: $f memuat console.log — hapus dulu" >&2
    exit 1
  fi
done
EOF
chmod +x hooks/pre-commit
cat > install.sh <<'EOF'
#!/bin/sh
# install.sh — pasang hook tim ke .git/hooks milik clone ini.
set -e
cd "$(git rev-parse --show-toplevel)"
for hook in hooks/*; do
  nama="$(basename "$hook")"
  ln -sf "../../$hook" ".git/hooks/$nama"
  echo "terpasang: $nama"
done
EOF
chmod +x install.sh
printf 'beranda\n' > README.md
git add README.md hooks/pre-commit install.sh
GIT_AUTHOR_DATE="2026-11-10T10:00:00+07:00" GIT_COMMITTER_DATE="2026-11-10T10:00:00+07:00" \
  git commit -m "chore: sertakan hook tim"
./install.sh

Output yang Dihasilkan (perintah terakhir):

text
terpasang: pre-commit

install.sh membuat symlink, bukan menyalin: .git/hooks/pre-commit menunjuk ke ../../hooks/pre-commit — jalur relatif terhadap .git/hooks/, jadi tautan tetap benar di clone mana pun. Keuntungan besarnya: saat tim memperbarui hooks/pre-commit lalu kita git pull, hook yang aktif ikut termutakhirkan tanpa memasang ulang. Ujinya nyata — berkas debug ditolak:

bash
printf 'function sapa() {\n  console.log("tes");\n}\n' > app.js
git add app.js
git commit -m "feat: fungsi sapa"

Output yang Dihasilkan (perintah terakhir):

text
pre-commit: app.js memuat console.log — hapus dulu

Di clone baru, ./install.sh dijalankan sekali seumur clone — biasanya didokumentasikan di README sebagai langkah setup, sama seperti npm install.

Solusi B: core.hooksPath — Folder Versioned Langsung #

Sejak Git 2.9, folder hooks tidak harus berada di .git/hooks/: konfigurasi core.hooksPath mengarahkan Git ke folder mana pun — termasuk folder dalam repository yang ikut di-clone:

bash
cd ../.. && mkdir -p latihan-distribusi/proyek-hookspath/.githooks
cd latihan-distribusi/proyek-hookspath
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
cp ../proyek-tim/hooks/pre-commit .githooks/pre-commit
printf 'beranda\n' > README.md
git add README.md .githooks/pre-commit
GIT_AUTHOR_DATE="2026-11-10T11:00:00+07:00" GIT_COMMITTER_DATE="2026-11-10T11:00:00+07:00" \
  git commit -m "chore: sertakan .githooks"
git config core.hooksPath .githooks

Konfigurasi ini lokal per clone — tidak ikut repository — jadi tetap ada satu langkah setup, tapi tanpa skrip dan tanpa symlink: satu baris git config core.hooksPath .githooks. Efeknya bisa dibuktikan tanpa satu pun berkas di .git/hooks/:

bash
printf 'function sapa() {\n  console.log("tes");\n}\n' > app.js
git add app.js
git commit -m "feat: fungsi sapa"

Output yang Dihasilkan (perintah terakhir):

text
pre-commit: app.js memuat console.log — hapus dulu

Berkas .git/hooks/pre-commit memang tidak ada — ls .git/hooks/pre-commit keluar dengan “No such file or directory” — sementara git config core.hooksPath mengembalikan .githooks. Di clone baru, commit polos akan lolos sampai konfigurasinya dipasang; setelah git config core.hooksPath .githooks, gerbang aktif — urutan itu sudah diuji nyata di sandbox.

Solusi C: Framework pre-commit #

Skrip buatan sendiri tumbuh rumit cepat — filter jenis berkas, lingkungan bahasa, cache. Framework pre-commit mengambil alih bagian itu: hook dideklarasikan di .pre-commit-config.yaml, framework yang memasang dan menjalankannya. Dipasang lewat pip install pre-commit atau uvx pre-commit:

bash
pip install pre-commit
mkdir -p ../../latihan-fw/scripts && cd ../../latihan-fw
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
cat > scripts/larang-debug.sh <<'EOF'
#!/bin/sh
# larang-debug — gagal bila ada jejak debug.
status=0
for f in "$@"; do
  if grep -nE 'console\.log' "$f"; then
    echo "larang-debug: $f memuat console.log — hapus dulu" >&2
    status=1
  fi
done
exit $status
EOF
chmod +x scripts/larang-debug.sh
cat > .pre-commit-config.yaml <<'EOF'
repos:
  - repo: local
    hooks:
      - id: larang-debug
        name: larang console.log
        language: system
        entry: scripts/larang-debug.sh
        files: \.js$
EOF
git add .pre-commit-config.yaml scripts/larang-debug.sh
pre-commit install
GIT_AUTHOR_DATE="2026-11-10T09:00:00+07:00" GIT_COMMITTER_DATE="2026-11-10T09:00:00+07:00" \
  git commit -m "chore: sertakan konfigurasi pre-commit"

Output yang Dihasilkan (dua perintah terakhir):

text
pre-commit installed at .git/hooks/pre-commit
larang console.log...................................(no files to check)Skipped
[main (root-commit) 80bf430] chore: sertakan konfigurasi pre-commit
 2 files changed, 18 insertions(+)
 create mode 100644 .pre-commit-config.yaml
 create mode 100755 scripts/larang-debug.sh

Framework menulis shim ke .git/hooks/pre-commit — skrip kecil yang meneruskan panggilan ke framework. Konfigurasi contoh sengaja memakai repo: local dengan language: system — hook menjalankan skrip di repository kita sendiri, tanpa unduhan. Pemakaian paling umum justru mendeklarasikan hook dari repositori publik (eslint, black, prettier), yang framework unduh otomatis pada pemakaian pertama. Ujinya nyata:

bash
printf 'function sapa() {\n  console.log("tes");\n}\n' > app.js
git add app.js
pre-commit run

Output yang Dihasilkan (perintah terakhir):

text
larang console.log.......................................................Failed
- hook id: larang-debug
- exit code: 1

2:  console.log("tes");
larang-debug: app.js memuat console.log — hapus dulu

Framework hanya menjalankan hook pada berkas staged yang cocok dengan pola files: \.js$, lalu menyalin keluaran skrip dengan rapi. Setelah berkasnya dibereskan, git commit -m "feat: fungsi sapa" berjalan normal — shim memanggil framework, hook lolos, commit lahir sebagai 9578357. Satu langkah setup tetap ada (pre-commit install), dan framework menawarkan pre-commit install --install-hooks plus pre-commit run --all-files untuk pemeriksaan menyeluruh.

Solusi D: Template Directory #

Ketiga solusi di atas menyasar repository yang sudah ada. Template directory menyasar repository yang baru lahir: git init menyalin isi folder template ke .git yang fresh — termasuk folder hooks/-nya. Diuji nyata dengan flag --template:

bash
mkdir -p ../../latihan-distribusi/templat-tim/hooks
cd ../../latihan-distribusi
cat > templat-tim/hooks/pre-commit <<'EOF'
#!/bin/sh
echo "pre-commit bawaan templat-tim aktif"
exit 1
EOF
chmod +x templat-tim/hooks/pre-commit
mkdir proyek-templat && cd proyek-templat
git init --template=../templat-tim -b main
ls .git/hooks

Output yang Dihasilkan (perintah terakhir):

text
pre-commit

Folder hooks repo baru berisi hanya berkas salinan itu — tanpa berkas .sample. Konfigurasi init.templateDir memasang templat sebagai bawaan semua git init di mesin itu, jadi setiap proyek baru lahir dengan hook standar perusahaan. Batasnya jelas: template hanya bekerja pada init, bukan clone — template directory menyelesaikan masalah proyek baru, bukan masalah rekan baru.

Memilih yang Pas #

SolusiSatu langkah setup per cloneTer-update lewat git pullCocok untuk
hooks/ + install.sh./install.shya (symlink)tim kecil, tanpa dependensi bahasa
core.hooksPathgit config core.hooksPath .githooksya (folder versioned)tim tanpa framework, Git ≥ 2.9
framework pre-commitpre-commit installya (konfigurasi versioned)tim berbahasa campuran, butuh hook siap pakai
template directorytanpa (hanya git init)tidak (salinan sekali)standar per-proyek baru di satu mesin

Hook kini terdistribusi — masalah berikutnya bukan lagi “bagaimana hook sampai ke semua mesin”, melainkan “apa lagi yang bisa otomatis”. Jawabannya tidak harus hook sama sekali: Git bisa diperluas langsung lewat alias berfungsi dan perintah buatan sendiri, dua jalan yang kita bangun di chapter berikutnya.