Jika saya memiliki komit di masa lalu yang menunjuk ke satu orangtua, tapi saya ingin mengubah orangtua yang ditunjuknya, bagaimana saya bisa melakukan itu?
Menggunakan git rebase
. Ini adalah generik "take commit (s) dan plop / mereka di atas perintah parent (base)" yang berbeda di Git.
Beberapa hal yang perlu diketahui:
Karena SHA komit melibatkan orang tua mereka, ketika Anda mengubah induk dari komit yang diberikan, SHA-nya akan berubah - seperti halnya SHA dari semua komit yang datang setelahnya (lebih baru daripada itu) di jalur pengembangan.
Jika Anda bekerja dengan orang lain, dan Anda telah mendorong komit yang dipertanyakan ke tempat mereka menariknya, memodifikasi komit mungkin adalah Ide Buruk ™. Ini karena # 1, dan dengan demikian kebingungan yang dihasilkan repositori pengguna lain akan menghadapi ketika mencoba untuk mencari tahu apa yang terjadi karena SHA Anda tidak lagi cocok dengan mereka untuk komitmen "sama". (Lihat bagian "MEMULIHKAN DARI REBASE UPSTREAM" dari halaman manual yang terhubung untuk detail.)
Yang mengatakan, jika Anda saat ini di cabang dengan beberapa komitmen yang ingin Anda pindahkan ke orangtua baru, itu akan terlihat seperti ini:
git rebase --onto <new-parent> <old-parent>
Itu akan memindahkan segalanya setelah <old-parent>
di cabang saat ini untuk duduk di atas <new-parent>
sebagai gantinya.
--onto
digunakan untuk apa! Dokumentasinya selalu sama sekali tidak jelas bagi saya, bahkan setelah membaca jawaban ini!git rebase --onto <new-parent> <old-parent>
Memberi tahu saya segala sesuatu tentang bagaimana menggunakanrebase --onto
setiap pertanyaan dan dokumen yang saya baca sejauh ini gagal.rebase this commit on that one
ataugit rebase --on <new-parent> <commit>
. Menggunakan orangtua lama di sini tidak masuk akal bagi saya.git rebase <new-parent>
.Jika ternyata Anda perlu menghindari rebasing komit berikutnya (misalnya karena penulisan ulang sejarah tidak akan bisa dipertahankan), maka Anda dapat menggunakan ganti git (tersedia di Git 1.6.5 dan yang lebih baru).
Dengan penggantian di atas ditetapkan, setiap permintaan untuk objek B benar-benar akan mengembalikan objek C. Isi C persis sama dengan isi B kecuali untuk orang tua pertama (orang tua yang sama (kecuali untuk yang pertama), pohon yang sama, pesan komit yang sama).
Penggantian aktif secara default, tetapi dapat diaktifkan dengan menggunakan
--no-replace-objects
opsi untuk git (sebelum nama perintah) atau dengan mengaturGIT_NO_REPLACE_OBJECTS
variabel lingkungan. Penggantian dapat dibagi dengan mendorongrefs/replace/*
(selain yang normalrefs/heads/*
).Jika Anda tidak menyukai komit-munging (dilakukan dengan sed di atas), maka Anda dapat membuat komit pengganti Anda menggunakan perintah tingkat yang lebih tinggi:
Perbedaan besar adalah bahwa urutan ini tidak menyebarkan orang tua tambahan jika B adalah gabungan komit.
sumber
refs/replace/
hierarki referensi. Jika Anda hanya perlu melakukannya sekali, maka Anda dapat melakukannyagit push yourremote 'refs/replace/*'
di repositori sumber, dangit fetch yourremote 'refs/replace/*:refs/replace/*'
di repositori tujuan. Jika Anda perlu melakukannya berkali-kali, Anda bisa menambahkan refspec tersebut keremote.yourremote.push
variabel config (dalam repositori sumber) danremote.yourremote.fetch
variabel config (dalam repositori tujuan).git replace --grafts
. Tidak yakin kapan ini ditambahkan, tetapi seluruh tujuannya adalah untuk membuat pengganti yang sama dengan komit B tetapi dengan orang tua yang ditentukan.Perhatikan bahwa mengubah komit di Git mengharuskan semua komit yang mengikutinya juga harus diubah. Ini tidak disarankan jika Anda telah menerbitkan bagian sejarah ini, dan seseorang mungkin telah membangun karya mereka berdasarkan sejarah sebelum perubahan.
Solusi alternatif untuk
git rebase
disebutkan dalam respons Amber adalah dengan menggunakan mekanisme cangkok (lihat definisi Git cangkok di Git Glosarium dan dokumentasi.git/info/grafts
file dalam dokumentasi Tata Letak Repositori Git ) untuk mengubah induk dari komit, periksa apakah ia melakukan hal yang benar dengan beberapa penampil riwayat (gitk
,git log --graph
, dll.) dan kemudian menggunakangit filter-branch
(seperti yang dijelaskan dalam bagian "Contoh" dari halaman manualnya) untuk membuatnya permanen (dan kemudian menghapus graft, dan secara opsional menghapus referensi asli yang didukung olehgit filter-branch
, atau memilih repositori):CATATAN !!! Solusi ini berbeda dari solusi rebase dalam hal itu
git rebase
akan rebase / transplantasi perubahan , sedangkan solusi berbasis cangkok hanya akan reparent berkomitmen seperti apa adanya , tidak memperhitungkan perbedaan akun antara orangtua lama dan orangtua baru!sumber
git replace
telah menggantikan git cangkokan (dengan asumsi Anda memiliki git 1.6.5 atau lebih baru).git-replace
+git-filter-branch
? Dalam pengujian saya,filter-branch
tampaknya menghormati penggantian, rebasing seluruh pohon.git filter-branch
menulis ulang sejarah, menghormati cangkok dan penggantian (tetapi dalam sejarah penulisan ulang penggantian akan diperdebatkan); push akan menghasilkan perubahan non fasf-forward.git replace
menciptakan pengganti yang dapat ditransfer di tempat; push akan maju cepat, tetapi Anda harus mendorongrefs/replace
untuk mentransfer pengganti agar riwayatnya terkoreksi. HTHUntuk memperjelas jawaban di atas dan pasang tanpa malu skrip saya sendiri:
Itu tergantung pada apakah Anda ingin "rebase" atau "reparent". Sebuah rebase , seperti yang disarankan oleh Amber , bergerak di sekitar diffs . Seorang reparen , seperti yang disarankan oleh Jakub dan Chris , bergerak di sekitar foto seluruh pohon. Jika Anda ingin reparen, saya sarankan menggunakan
git reparent
daripada melakukan pekerjaan secara manual.Perbandingan
Misalkan Anda memiliki gambar di sebelah kiri dan Anda ingin terlihat seperti gambar di sebelah kanan:
Baik rebasing dan reparenting akan menghasilkan gambaran yang sama, tetapi definisi
C'
berbeda. Dengangit rebase --onto A B
,C'
tidak akan berisi perubahan apa pun yang diperkenalkan olehB
. Dengangit reparent -p A
,C'
akan identik denganC
(kecuali yangB
tidak akan ada dalam sejarah).sumber
reparent
skrip; itu bekerja dengan indah; sangat dianjurkan . Saya juga merekomendasikan membuatbranch
label baru , dan kecheckout
cabang itu sebelum memanggil (karena label cabang akan pindah).Jawaban @ Jakub pasti membantu saya beberapa jam yang lalu ketika saya mencoba hal yang sama persis seperti OP.
Namun,
git replace --graft
sekarang solusi yang lebih mudah mengenai cangkok. Juga, masalah utama dengan solusi itu adalah bahwa cabang-filter membuat saya kehilangan setiap cabang yang tidak digabungkan ke dalam cabang KEPALA. Kemudian,git filter-repo
lakukan pekerjaan itu dengan sempurna dan tanpa cacat.Untuk info lebih lanjut: checkout bagian "Menyambung ulang riwayat" di dokumen
sumber