Rebase mengubah susunan riwayat commit. Risikonya muncul ketika riwayat itu sudah dipakai orang lain. Untuk memahami alasannya, perhatikan data apa yang membentuk sebuah commit dan apa yang dibuat ulang selama rebase.

Apa yang disimpan sebuah commit

Sebelum membahas rebase, perhatikan satu hal: commit menunjuk ke sebuah tree yang mewakili potret isi proyek pada satu titik waktu, beserta parent dan metadata lainnya. Commit tidak menyimpan diff sebagai identitas utamanya.

Periksa sembarang commit untuk melihat isinya:

git cat-file -p HEAD

Keluarannya kira-kira seperti ini:

tree a3c2f1d9e8b7c6a5f4e3d2c1b0a9f8e7d6c5b4a3
parent 7f3b2a1c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a
author Bagus Aji <info@kodepas.com> 1753488000 +0700
committer Bagus Aji <info@kodepas.com> 1753488000 +0700

Tambah validasi frontmatter

Ada empat hal penting di situ:

  • tree — penunjuk ke struktur berkas lengkap pada saat itu.
  • parent — penunjuk ke commit sebelumnya.
  • author dan committer — siapa dan kapan.
  • Pesan commit.

Nama commit itu sendiri — hash seperti 7f3b2a1... — bukan nomor urut. Ia adalah hasil hash dari seluruh isi di atas. Ganti satu huruf di pesan commit, dan hash-nya berubah total.

Commit tidak menyimpan “apa yang berubah”. Perbedaan yang terlihat di git diff dihitung ulang setiap kali, dengan membandingkan dua potret.

Poin terakhir itulah yang membuat sisa tulisan ini masuk akal.

Rebase tidak memindahkan commit

Kalimat “rebase memindahkan commit ke ujung branch lain” melewatkan operasi penting yang dilakukan Git.

Karena parent ikut dihitung ke dalam hash, mengubah induk sebuah commit berarti mengubah identitasnya. Rebase membuat commit baru dengan perubahan yang diterapkan di atas parent berbeda, lalu memperbarui branch agar menunjuk ke rangkaian baru tersebut.

Bayangkan riwayat seperti ini:

main      A───B───C
                   \
fitur               D───E

Setelah git rebase main dari branch fitur:

main      A───B───C
                   \
fitur               D'──E'

D' dan E' punya isi perubahan yang identik dengan D dan E, tapi hash yang berbeda. Commit D dan E yang asli tidak hilang seketika — mereka masih ada di dalam repositori sampai dibersihkan, dan dapat ditemukan melalui:

git reflog
e4a9c21 HEAD@{0}: rebase (finish): returning to refs/heads/fitur
e4a9c21 HEAD@{1}: rebase (pick): Tambah komponen kartu
b8f7d30 HEAD@{2}: rebase (pick): Perbaiki penataan tombol
1c2d3e4 HEAD@{3}: checkout: moving from fitur to main

Kenapa hash commit berubah

Alasannya sudah terlihat. Bagian ini merangkumnya karena konsep tersebut menjadi dasar aturan tentang rebase.

Hash sebuah commit dihitung dari:

BagianIkut dihitung?
Isi berkas (tree)Ya
Commit induk (parent)Ya
Nama dan waktu authorYa
Nama dan waktu committerYa
Pesan commitYa

Rebase mengganti parent. Karena parent ikut dihitung, hash pasti berubah. Dan karena hash commit anak ikut menghitung hash induknya, satu perubahan di bawah akan mengubah semua commit di atasnya.

Itu sebabnya rebase pada commit tertua di sebuah branch akan menulis ulang seluruh branch tersebut, bukan hanya satu commit.

Aturan emas: jangan rebase yang sudah dibagikan

Sekarang aturan yang tadi terasa sewenang-wenang jadi punya alasan.

Jika commit D dan E sudah di-push lalu ditarik anggota tim lain, salinan mereka tetap menunjuk ke D dan E. Ketika rangkaian hasil rebase D' dan E' di-push, Git di sisi mereka melihat dua rangkaian commit berbeda untuk pekerjaan yang sama.

Hasilnya bukan kesalahan Git. Hasilnya adalah dua versi sejarah yang keduanya sah, dan seseorang harus menyatukannya dengan tangan.

# Aman: branch lokal yang belum pernah di-push
git rebase main

# Berisiko: branch yang sudah dipakai orang lain
git push --force

Kalau memang harus memaksa, pakai bentuk yang lebih aman:

git push --force-with-lease

Perintah itu memeriksa apakah nilai remote masih sesuai dengan nilai yang diharapkan. Pemeriksaan ini lebih aman daripada --force, tetapi tetap menulis ulang riwayat remote ketika syaratnya terpenuhi.

Rebase interaktif untuk merapikan pekerjaan sendiri

Rebase interaktif berguna untuk merapikan pekerjaan sebelum membuka pull request. Riwayat commit sementara mungkin terlihat seperti ini:

git log --oneline -5
4c88ae9 typo
9d31b02 perbaiki lagi
f7a2c11 wip
2b1a0f8 coba pendekatan lain
8e7d6c5 Tambah validasi frontmatter

Tidak ada orang yang perlu membaca wip dan typo enam bulan dari sekarang. Rapikan dengan:

git rebase -i HEAD~5

Editor akan terbuka berisi daftar seperti ini:

pick   8e7d6c5 Tambah validasi frontmatter
squash 2b1a0f8 coba pendekatan lain
squash f7a2c11 wip
squash 9d31b02 perbaiki lagi
squash 4c88ae9 typo

Perintah yang paling sering dipakai hanya empat:

  1. pick — pakai commit ini apa adanya.
  2. reword — pakai commit ini, tapi ubah pesannya.
  3. squash — gabungkan ke commit di atasnya, gabungkan juga pesannya.
  4. drop — buang commit ini sepenuhnya.

Hasil akhirnya satu commit dengan pesan yang bisa dibaca orang lain. Karena branch ini belum pernah di-push, perubahan tersebut tidak menulis ulang riwayat yang sudah dipakai orang lain.

Kapan sebaiknya tidak rebase

Rebase bukan pilihan yang selalu lebih baik daripada merge. Keduanya menyimpan bentuk riwayat yang berbeda.

Tetap pakai merge kalau:

  • Branch sudah dibagikan ke orang lain.
  • Riwayat perlu mencatat bahwa dua jalur pekerjaan memang pernah berjalan paralel.
  • Konflik yang muncul saat rebase harus diselesaikan berulang kali di tiap commit — kadang satu penyelesaian konflik di merge jauh lebih murah.

Pilihan antara keduanya bergantung pada bentuk riwayat yang perlu dipertahankan.

Ringkasnya

Rebase terasa menakutkan karena dijelaskan sebagai “memindahkan commit”. Ketika commit dipahami sebagai objek yang identitasnya ikut menghitung induknya, semuanya jadi konsisten:

  • Mengubah induk berarti membuat commit baru.
  • Commit baru berarti hash baru.
  • Hash baru pada sesuatu yang sudah dibagikan berarti masalah untuk orang lain.
  • Pada branch lokal yang belum dibagikan, penulisan ulang tidak mengubah riwayat yang dipakai orang lain.

Untuk memilih strategi pada pekerjaan sehari-hari, lanjutkan ke Merge atau Rebase. Artikel lain tersedia pada topik Git.

Rujukan: dokumentasi git rebase, git push, dan git reflog.