git pull sore hari membawa perubahan Siti: file konfigurasi baru, direktori yang dipindah, dan — tanpa terlihat — satu baris baru di package.json beserta dependency yang belum terpasang di mesin kita. Esok paginya aplikasi tidak jalan, dan satu jam pertama habis untuk mengejar penyebabnya. Perubahan yang datang dari luar memang sering butuh tindak lanjut lokal: instal dependensi, jalankan migrasi, hasilkan berkas. Hook post-merge adalah titik otomatisinya — berjalan setelah setiap merge (termasuk merge di dalam git pull), tepat saat repository berada di keadaan paling segar.
Momen Berjalannya dan Batasnya #
post-merge adalah hook pasca: tidak bisa membatalkan apa pun karena merge sudah selesai saat hook dipanggil. Hook menerima satu argumen $1 — flag bernilai 1 bila merge dilakukan dengan --squash. Dua fakta penting lainnya dari perilaku Git di sini:
- Hook juga terpanggil oleh
git pull— karena pull pada dasarnyagit fetchlalugit merge. - Fast-forward juga memicunya — padahal tidak ada commit baru yang digabung, hanya penunjuk yang maju. Tindak lanjut berat seperti
npm installsebaiknya tidak berjalan untuk kasus ini, dan pembeda keduanya bisa diperiksa.
Deteksi Merge Sungguhan: MERGE_HEAD #
Saat merge sungguhan berjalan, Git menulis ref sementara MERGE_HEAD yang menunjuk branch yang digabung — dan ref itu masih berupa saat hook post-merge dieksekusi; Git baru membersihkannya setelah hook selesai. Pada fast-forward, MERGE_HEAD memang tidak pernah diciptakan. Maka git rev-parse --verify MERGE_HEAD di dalam hook menjadi pembeda yang andal: gagal berarti fast-forward, berhasil berarti merge commit sungguhan.
Berbekal itu plus git diff-tree yang membandingkan keadaan sebelum dan sesudah merge, hook pengawas dependensi lengkapnya begini:
mkdir latihan-postmerge && cd latihan-postmerge
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
cat > .git/hooks/post-merge <<'HOOK'
#!/bin/sh
# post-merge — sinkron dependensi setelah merge.
# Merge sungguhan (merge commit) meninggalkan MERGE_HEAD;
# fast-forward tidak — berarti tidak ada commit baru yang
# butuh sinkronisasi ulang.
if ! git rev-parse --verify MERGE_HEAD >/dev/null 2>&1; then
exit 0
fi
# Berkas apa saja yang dibawa merge?
if git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD \
| grep -q '^package\.json$'; then
echo "post-merge: package.json berubah — dependensi diperbarui"
echo "post-merge: jalankan 'npm install' sebelum melanjutkan"
fi
HOOK
chmod +x .git/hooks/post-mergeBedah skripnya baris per baris:
git rev-parse --verify MERGE_HEADmenguji keberadaan ref merge; keluar non-zero bila tidak ada, dan blokif ! ...membaliknya jadi kondisi “bukan merge sungguhan → keluar diam-diam” (exit 0).git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEADmendaftar berkas yang berbeda antara keadaan sebelum merge (ORIG_HEAD— posisi HEAD lama yang dicatat Git sebelum merge) dan hasil merge (HEAD). Opsi-rmenurun ke subdirektori,--name-onlyhanya nama berkas,--no-commit-idmembuang baris hash.grep -q '^package\.json$'memeriksa kecocokan persis satu nama berkas — pola yang mudah diperluas kerequirements.txt,poetry.lock, ataucomposer.json.
Uji Nyata: Merge yang Menyentuh Dependensi #
Panggungnya meniru proyek sungguhan: package.json di main, lalu branch fitur menambah satu dependency. Supaya merge-nya sungguhan (bukan fast-forward), main juga bergerak sedikit setelah branch dibuat:
printf '{\n "name": "toko",\n "dependencies": {}\n}\n' > package.json
printf 'beranda toko\n' > beranda.md
git add package.json beranda.md
GIT_AUTHOR_DATE="2026-11-09T09:00:00+07:00" GIT_COMMITTER_DATE="2026-11-09T09:00:00+07:00" \
git commit -m "chore: paket awal"
git switch -c fitur-dep
printf '{\n "name": "toko",\n "dependencies": {\n "lodash": "^4.17.21"\n }\n}\n' > package.json
git add package.json
GIT_AUTHOR_DATE="2026-11-09T09:10:00+07:00" GIT_COMMITTER_DATE="2026-11-09T09:10:00+07:00" \
git commit -m "build: tambah lodash"
git switch main
printf 'tentang toko\n' > tentang.md
git add tentang.md
GIT_AUTHOR_DATE="2026-11-09T09:15:00+07:00" GIT_COMMITTER_DATE="2026-11-09T09:15:00+07:00" \
git commit -m "docs: halaman tentang"
GIT_AUTHOR_DATE="2026-11-09T09:20:00+07:00" GIT_COMMITTER_DATE="2026-11-09T09:20:00+07:00" \
GIT_MERGE_AUTOEDIT=no git merge fitur-depGIT_MERGE_AUTOEDIT=no memakai pesan merge bawaan tanpa membuka editor — dan menandai tanggal commit merge agar deterministik. Output yang Dihasilkan (perintah terakhir):
Merge made by the 'ort' strategy.
package.json | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
post-merge: package.json berubah — dependensi diperbarui
post-merge: jalankan 'npm install' sebelum melanjutkanDua baris terakhir adalah suara hook: merge selesai, package.json terdeteksi di daftar perubahan, dan pesan pengingat muncul di terminal — pada proyek nyata, npm install bisa langsung dijalankan hook itu, atau tim memilih post-merge hanya mengingatkan agar kegagalan instalasi tetap terbaca jelas.
Dua Uji Negatif: Sunyi Itu Kebenaran #
Merge yang tidak menyentuh package.json harus lewat senyap:
git switch -c fitur-galeri
printf 'galeri v1\n' > galeri.md
git add galeri.md
GIT_AUTHOR_DATE="2026-11-09T09:25:00+07:00" GIT_COMMITTER_DATE="2026-11-09T09:25:00+07:00" \
git commit -m "feat: halaman galeri"
git switch main
printf 'kontak toko\n' > kontak.md
git add kontak.md
GIT_AUTHOR_DATE="2026-11-09T09:30:00+07:00" GIT_COMMITTER_DATE="2026-11-09T09:30:00+07:00" \
git commit -m "docs: halaman kontak"
GIT_AUTHOR_DATE="2026-11-09T09:35:00+07:00" GIT_COMMITTER_DATE="2026-11-09T09:35:00+07:00" \
GIT_MERGE_AUTOEDIT=no git merge fitur-galeriOutput yang Dihasilkan (perintah terakhir):
Merge made by the 'ort' strategy.
galeri.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 galeri.mdTidak ada baris post-merge: — grep tidak menemukan package.json di daftar perubahan. Uji terakhir adalah fast-forward: branch baru berbasis ujung main, lalu di-merge tanpa commit penghalang:
git switch -c fitur-cicd
printf 'pipeline\n' > ci.txt
git add ci.txt
GIT_AUTHOR_DATE="2026-11-09T09:40:00+07:00" GIT_COMMITTER_DATE="2026-11-09T09:40:00+07:00" \
git commit -m "ci: pipeline awal"
git switch main
GIT_MERGE_AUTOEDIT=no git merge fitur-cicdOutput yang Dihasilkan (perintah terakhir):
Updating 81abadc..929aa01
Fast-forward
ci.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 ci.txtSenyap lagi — kali ini karena MERGE_HEAD tidak ada, pemeriksaan baris pertama hook langsung menutup skrip. Hash 81abadc dan 929aa01 berbeda di mesin pembaca.
Tugas Lain dan Saudaranya: post-checkout #
Pola “mendeteksi berkas tertentu, lalu bertindak” membentang jauh melebihi dependensi: migrasi basis data (bila db/migrate/ berubah), pembuatan ulang aset hasil generate (bila docs/api.yaml berubah), invalidasi cache build (bila config/ berubah). Semuanya menempatkan perintah nyata di posisi echo kita. Saudara terdekatnya adalah post-checkout — berjalan setiap ganti branch, masukannya tiga argumen yang sudah dibedah di Client-Side Hooks
— pas untuk me-render ulang aset atau memuat ulang environment setelah berpindah konteks. Keduanya berbagi satu batas yang sama penting:
Dengan empat hook kini teruji — pemblokir commit, validasi pesan, penolak push, pengawas merge — satu masalah besar tersisa, dan ia struktural: keempatnya hidup di .git/hooks/ yang tidak pernah ikut saat repository di-clone. Bagaimana tim memastikan setiap anggota memasang hook yang sama? Itulah pekerjaan chapter berikutnya.