Seluruh peraturan kolaborasi sejauh ini — workflow, review, konvensi pesan — berdiri di atas satu asumsi rapuh: semua anggota patuh. Server Git sendiri hanya menjaga satu pagar bawaan: penolakan push yang bukan maju cepat, seperti yang kita alami di Push Rejected . Dua celah besar tetap terbuka: siapa pun bisa mendorong commit langsung ke main tanpa satu pun review, dan siapa pun bisa menimpa riwayat bersama dengan git push --force. Protected branches menutup kedua celah itu — bukan dengan perintah Git baru, melainkan dengan kebijakan yang ditegakkan di sisi server, tempat semua push berakhir.

Push Langsung ke main Masih Bisa Terjadi #

Bukti pertamanya mudah dibuat. Budi menyiapkan panggung yang kini sudah akrab — repo kerja, bare pusat, dua meja:

bash
mkdir budi && cd budi
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'beranda\n' > situs.md
git add situs.md
GIT_AUTHOR_DATE="2026-10-27T09:00:00+07:00" GIT_COMMITTER_DATE="2026-10-27T09:00:00+07:00" \
  git commit -m "Buat kerangka toko"
git init --bare ../toko-proteksi.git
git remote add origin ../toko-proteksi.git
git push -u origin main

Output yang Dihasilkan (perintah terakhir):

text
To ../toko-proteksi.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

Lalu Budi menambahkan satu commit dan langsung mengirimkannya ke main di server:

bash
printf 'kontak\n' > kontak.md
git add kontak.md
GIT_AUTHOR_DATE="2026-10-27T10:00:00+07:00" GIT_COMMITTER_DATE="2026-10-27T10:00:00+07:00" \
  git commit -m "Tambah halaman kontak"
git push origin main

Output yang Dihasilkan (perintah terakhir):

text
To ../toko-proteksi.git
   926f104..6e340f1  main -> main

Tidak ada yang salah secara Git — push ini maju cepat, rapi, sah. Yang tidak ada di sini adalah seluruh perangkat tim yang kita bangun sepanjang section ini: tidak ada pull request, tidak ada review, tidak ada status check. Commit itu sampai ke main bersama hanya karena pengetiknya mengetiknya. Pada bare repo polos, itulah satu-satunya syarat.

Tanpa Proteksi, Riwayat Bisa Ditimpa #

bash
cd ..
git clone toko-proteksi.git siti
cd siti
git config user.name "Siti Rahma"
git config user.email "[email protected]"
printf 'kontak\npeta\n' > kontak.md
git add kontak.md
GIT_AUTHOR_DATE="2026-10-27T11:00:00+07:00" GIT_COMMITTER_DATE="2026-10-27T11:00:00+07:00" \
  git commit --amend -m "Tambah halaman kontak dan peta"
git push --force origin main

Output yang Dihasilkan (perintah terakhir):

text
To ../toko-proteksi.git
 + 6e340f1...2f6daa0 main -> main (forced update)

--force menggeser main di server mundur ke kerangka lalu maju ke salinan baru — dan tanda + beserta (forced update) adalah pengakuan resmi bahwa penulisan ulang terjadi. Kembali di meja Budi, dunia berubah tanpa ia berbuat apa pun:

bash
cd ../budi
git fetch origin
git status

Output yang Dihasilkan (dua perintah):

text
From ../toko-proteksi
 + 6e340f1...2f6daa0 main       -> origin/main  (forced update)
text
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" if you want to integrate the remote branch with yours)

nothing to commit, working tree clean

Commit 6e340f1 — karya Budi — tergusur dari riwayat bersama; ia masih hidup di repo lokal Budi, tetapi versi “resmi” kini salinan Siti. Inilah bencana persis yang dilarang aturan emas di Aturan Aman Mengubah Riwayat — dan pada bare polos, tidak ada yang mencegahnya terjadi.

Gerbang Server-Side: Hook pre-receive #

Solusi self-hosted-nya hidup di tempat kejadian: hook di server. Hook pre-receive berjalan di dalam repo bare sebelum satu pun penunjuk diperbarui — git mengirimkannya baris demi baris berisi old new ref, dan keluar dengan kode satu berarti seluruh push ditolak. Karena bare repo kita adalah server mini, kita bisa memasangnya langsung:

bash
cd ../toko-proteksi.git
cat > hooks/pre-receive <<'HOOK'
#!/bin/sh
# Tolak penulisan ulang riwayat pada branch utama.
while read old new ref; do
  case "$ref" in
    refs/heads/main)
      if [ "$old" != "0000000000000000000000000000000000000000" ] \
         && ! git merge-base --is-ancestor "$old" "$new"; then
        echo "pre-receive: penulisan ulang 'main' ditolak (non-fast-forward)" >&2
        exit 1
      fi
      ;;
  esac
done
HOOK
chmod +x hooks/pre-receive

Skripnya membaca tiga nilai per branch yang didorong: hash lama, hash baru, dan nama penunjuk. Untuk refs/heads/main, syaratnya satu — hash lama harus tetap merupakan leluhur hash baru (git merge-base --is-ancestor); bila tidak, push adalah penulisan ulang dan ditolak. Baris $old penuh nol menandai branch baru yang wajar diloloskan. Ragam hook server lainnya dibedah di chapter Server-Side Hooks .

Siti mencoba aksi yang sama sekali lagi — menggandakan amend-nya dengan “jam buka”:

bash
cd ../siti
printf 'kontak\npeta\njam buka\n' > kontak.md
git add kontak.md
GIT_AUTHOR_DATE="2026-10-27T12:00:00+07:00" GIT_COMMITTER_DATE="2026-10-27T12:00:00+07:00" \
  git commit --amend -m "Tambah halaman kontak, peta, dan jam buka"
git push --force origin main

Output yang Dihasilkan (perintah terakhir):

text
remote: pre-receive: penulisan ulang 'main' ditolak (non-fast-forward)        
To ../toko-proteksi.git
 ! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to '../toko-proteksi.git'

Push ditolak di gerbang — sebelum penunjuk tersentuh, sebelum riwayat Budi terancam lagi. Siti kini menempuh jalan yang benar: kembali ke titik resmi, lalu maju dengan commit baru:

bash
git reset --hard origin/main
printf 'buka setiap hari 09-17\n' > jam.md
git add jam.md
GIT_AUTHOR_DATE="2026-10-27T13:00:00+07:00" GIT_COMMITTER_DATE="2026-10-27T13:00:00+07:00" \
  git commit -m "Tambah jam buka"
git push origin main

Output yang Dihasilkan (perintah terakhir):

text
To ../toko-proteksi.git
   2f6daa0..0cfb35d  main -> main

Hook kita memang hanya menolak penulisan ulang — push langsung yang maju saja masih lolos, karena membedakan “commit baru hasil review” dari “commit baru hasil ketikan malam” butuh kebijakan yang lebih kaya daripada leluhur-majemuk. Itulah pekerjaan platform.

Proteksi di Platform: GitHub dan GitLab #

Hosting seperti GitHub dan GitLab menjalankan logika proteksi sebagai fitur server — tidak ada perintah git protect; yang terlihat kontributor hanyalah push yang ditolak atau pull request yang menolak digabung sebelum syarat terpenuhi. Aturannya dikonfigurasi per branch:

Aturan proteksiEfek yang terasa
Larangan push langsung ke maingit push origin main ditolak server; satu-satunya jalan adalah pull request
Wajib pull request dengan persetujuan minimumtombol merge terkunci sampai reviewer yang ditunjuk menyetujui
Status check wajib hijauPR tak bisa digabung sebelum CI lulus
Riwayat linear — squash atau rebase-onlymerge commit ditolak; commit PR dirapikan menjadi urutan linear
Larangan force push termasuk administratorgit push --force ke branch terlindungi selalu gagal — padanan hook kita, tanpa pengecualian
Review kode pemilik via CODEOWNERSperubahan pada berkas milik pemilik menuntut persetujuan pemiliknya

Dua gerbang bekerja berurutan: hook menjaga bentuk riwayat, aturan platform menjaga prosesnya. Rincian fitur GitHub dirangkum di dokumentasi protected branches , padanannya di GitLab di dokumentasi protected branches — keduanya juga bisa dipadukan dengan hook self-hosted bila tim menjalankan server Git sendiri.

Dengan pintu belakang terkunci, satu urusan kolaborasi besar masih tersisa: mengatur saat seluruh pekerjaan itu berbuah menjadi rilis. Kapan fitur berhenti masuk, bagaimana perbaikan menyusul ke branch rilis, dan siapa yang menarik kendali — itulah panggung chapter penutup section ini: koordinasi release.