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 HEADKeluarannya 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 frontmatterAda empat hal penting di situ:
tree— penunjuk ke struktur berkas lengkap pada saat itu.parent— penunjuk ke commit sebelumnya.authordancommitter— 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 diffdihitung 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───ESetelah 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 refloge4a9c21 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 mainKenapa hash commit berubah
Alasannya sudah terlihat. Bagian ini merangkumnya karena konsep tersebut menjadi dasar aturan tentang rebase.
Hash sebuah commit dihitung dari:
| Bagian | Ikut dihitung? |
|---|---|
Isi berkas (tree) | Ya |
Commit induk (parent) | Ya |
| Nama dan waktu author | Ya |
| Nama dan waktu committer | Ya |
| Pesan commit | Ya |
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 --forceKalau memang harus memaksa, pakai bentuk yang lebih aman:
git push --force-with-leasePerintah 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 -54c88ae9 typo
9d31b02 perbaiki lagi
f7a2c11 wip
2b1a0f8 coba pendekatan lain
8e7d6c5 Tambah validasi frontmatterTidak ada orang yang perlu membaca wip dan typo enam bulan dari sekarang.
Rapikan dengan:
git rebase -i HEAD~5Editor 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 typoPerintah yang paling sering dipakai hanya empat:
pick— pakai commit ini apa adanya.reword— pakai commit ini, tapi ubah pesannya.squash— gabungkan ke commit di atasnya, gabungkan juga pesannya.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.