Saya tidak bisa mengerti perilaku git rebase --onto

169

Saya perhatikan bahwa dua blok dari perintah git berikut memiliki perilaku yang berbeda dan saya tidak mengerti mengapa.

Saya memiliki Adan Bcabang yang berbeda dengan satucommit

---COMMIT--- (A)
\
 --- (B)

Saya ingin rebase Bcabang pada yang terbaru A(dan memiliki komitmen pada Bcabang)

---COMMIT--- (A)
         \
          --- (B)

Tidak masalah jika saya lakukan:

checkout B
rebase A

Tetapi jika saya lakukan:

checkout B
rebase --onto B A

Tidak berfungsi sama sekali, tidak ada yang terjadi. Saya tidak mengerti mengapa kedua perilaku itu berbeda.

Phpstorm git client menggunakan sintaks kedua, dan sepertinya bagi saya benar-benar rusak, itu sebabnya saya meminta masalah sintaks ini.

Xmanoux
sumber

Jawaban:

412

tl; dr

Sintaks yang benar untuk rebase Bdi atas Amenggunakan git rebase --ontodalam kasus Anda adalah:

git checkout B
git rebase --onto A B^

atau rebase Bdi atas Amulai dari komit yang merupakan induk dariB direferensikan dengan B^atau B~1.

Jika Anda tertarik pada perbedaan antara git rebase <branch>dan git rebase --onto <branch>baca terus.

Quick: git rebase

git rebase <branch>akan rebase cabang saat ini Anda telah memeriksa, direferensikan oleh HEAD, di atas terbaru komit yang dicapai dari <branch>namun tidak dari HEAD.
Ini adalah kasus rebasing yang paling umum dan bisa dibilang salah satu yang membutuhkan perencanaan kurang di muka.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                         \
              D---E (HEAD)                              D---E (HEAD)

Dalam contoh ini, Fdan Gadalah komitmen yang dapat dijangkau branchtetapi tidak dari HEAD. Mengatakan git rebase branchakan mengambil D, itu adalah komit pertama setelah titik percabangan, dan rebase itu (yaitu mengubah induknya ) di atas komit terbaru yang dapat dicapai dari branchtetapi tidak dari HEAD, yaitu G.

Precise: git rebase --onto dengan 2 argumen

git rebase --ontomemungkinkan Anda melakukan rebase mulai dari komit tertentu . Ini memberi Anda kontrol tepat atas apa yang sedang direstrukturisasi dan di mana. Ini untuk skenario di mana Anda harus tepat.

Misalnya, mari kita bayangkan bahwa kita perlu rebase HEADtepat di atas Fmulai dari E. Kami hanya tertarik untuk membawa Fke cabang kerja kami sementara, pada saat yang sama, kami tidak ingin menyimpannya Dkarena mengandung beberapa perubahan yang tidak kompatibel.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                     \
              D---E---H---I (HEAD)                  E---H---I (HEAD)

Dalam hal ini, kami akan mengatakan git rebase --onto F D. Ini berarti:

Rebase komit yang dapat dijangkau dari HEADyang induknya ada Ddi atas F.

Dengan kata lain, ubah induk dari Edari Dmenjadi F. Sintaksnya git rebase --ontoadalah git rebase --onto <newparent> <oldparent>.

Skenario lain yang sangat berguna adalah ketika Anda ingin dengan cepat menghapus beberapa komit dari cabang saat ini tanpa harus melakukan rebase interaktif :

          Before                       After
    A---B---C---E---F (HEAD)        A---B---F (HEAD)

Dalam contoh ini, untuk menghapus Cdan Edari urutan yang Anda katakan git rebase --onto B E, atau rebase HEADdi atas di Bmana orang tua yang lama berada E.

The Surgeon: git rebase --onto dengan 3 argumen

git rebase --ontobisa melangkah lebih jauh dalam hal presisi. Bahkan, ini memungkinkan Anda untuk rebase kisaran komit sewenang - wenang di atas yang lain.

Ini sebuah contoh:

          Before                                     After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD)                          E---H (HEAD)

Dalam hal ini, kami ingin mengubah rentang yang tepat E---Hdi atas F, mengabaikan ke mana HEADsaat ini menunjuk. Kita dapat melakukannya dengan mengatakan git rebase --onto F D H, yang berarti:

Rebase kisaran komit yang induknya adalah Dsampai dengan Hdi atas F.

Sintaksnya git rebase --ontodengan rentang komit kemudian menjadi git rebase --onto <newparent> <oldparent> <until>. Kuncinya di sini adalah mengingat bahwa komit direferensikan oleh <until>yang termasuk dalam jangkauan dan akan menjadi baru HEADsetelah rebase selesai.

Enrico Campidoglio
sumber
1
Jawaban bagus. Hanya tambahan kecil untuk kasus umum: <oldparent>Nama rusak jika dua bagian rentang berada di cabang yang berbeda. Secara umum: "Sertakan setiap komit yang dapat dijangkau <until>tetapi kecualikan setiap komit yang dapat dijangkau <oldparent>."
musiKk
50
git rebase --onto <newparent> <oldparent>adalah penjelasan terbaik tentang --tingkah laku yang pernah kulihat!
ronkot
4
Terima kasih! Saya agak kesulitan dengan --ontoopsi ini, tetapi ini membuatnya sangat jelas! Saya bahkan tidak mengerti bagaimana saya tidak bisa memahaminya sebelumnya: D Terima kasih untuk "tutorial" yang sangat baik :-)
grongor
3
Meskipun jawaban ini sangat bagus, saya merasa tidak mencakup semua kasus yang mungkin. Bentuk sintaks terakhir juga dapat digunakan untuk mengekspresikan tipe rebase yang lebih halus. Contoh di Pro Git (2nd Ed.), D tidak harus menjadi leluhur H. Sebaliknya, D dan H juga bisa dilakukan dengan leluhur yang sama - dalam hal ini, Git akan mengetahui leluhur mereka yang sama dan ulangan dari leluhur itu ke H ke F.
Pastafarian
1
Ini sangat membantu. Halaman manual tidak menjelaskan argumen sama sekali.
a544jh
61

Ini semua yang perlu Anda ketahui untuk mengerti --onto:

git rebase --onto <newparent> <oldparent>

Anda mengalihkan orang tua pada komit, tetapi Anda tidak menyediakan sha dari komit, hanya sha dari orang tua (lama) itu.

memutuskan
sumber
4
Singkat dan mudah. Sebenarnya, menyadari bahwa saya harus memberikan komitmen kepada orang tua dari komitmen yang ingin saya rebase alih-alih komitmen itu, membutuhkan waktu paling lama.
Antoniossss
1
Perincian penting adalah bahwa Anda memilih anak dari orang tua dari cabang saat ini karena satu komit dapat menjadi orangtua untuk banyak komit, tetapi ketika Anda membatasi diri pada cabang saat ini maka komit dapat menjadi orangtua hanya untuk satu komit. Dengan kata lain kapal induk hubungan adalah unik di cabang tetapi tidak harus jika Anda tidak menentukan cabang.
Trismegistos
2
Untuk diketahui: Anda harus berada di cabang, atau menambahkan nama cabang sebagai parameter ke-3 git rebase --untuk <newparent> <oldparent> <feature-branch>
Jason Portnoy
1
Jawaban ini luar biasa, langsung ke titik sebagaimana diperlukan di utas ini
John Culviner
13

Masukan singkat, diberikan:

      Before rebase                             After rebase
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                         \   \
          D---E---H---I (HEAD)                      \   E'---H' (HEAD)
                                                     \
                                                      D---E---H---I

git rebase --onto F D H

Yang sama dengan (karena --onto mengambil satu argumen):

git rebase D H --onto F

Berarti rebase komit dalam kisaran (D, H] di atas F. Perhatikan rentang tersebut adalah eksklusif sisi kiri. Ini eksklusif karena lebih mudah untuk menentukan komit pertama dengan mengetik misalnya branchuntuk membiarkangit menemukan komit 1 yang berbeda dari branchmis. DYang mengarah ke H.

Kasus OP

    o---o (A)
     \
      o (B)(HEAD)

git checkout B
git rebase --onto B A

Dapat diubah menjadi perintah tunggal:

git rebase --onto B A B

Apa yang tampak seperti kesalahan di sini adalah penempatan Byang berarti "pindahkan beberapa komit yang mengarah ke cabang Bdi atas B". Pertanyaannya adalah apa itu "beberapa komitmen". Jika Anda menambahkan -iflag, Anda akan melihatnya komit tunggal yang ditunjuk oleh HEAD. Komit dilompati karena sudah diterapkan ke --ontotarget Bsehingga tidak ada yang terjadi.

Perintah tidak masuk akal dalam hal apapun di mana nama cabang diulang seperti itu. Ini karena rentang komit akan menjadi beberapa komit yang sudah ada di cabang itu dan selama rebase semuanya akan dilewati.

Penjelasan lebih lanjut dan penggunaan yang berlaku dari git rebase <upstream> <branch> --onto <newbase>.

git rebase default.

git rebase master

Perluas salah satu:

git rebase --onto master master HEAD
git rebase --onto master master current_branch

Checkout otomatis setelah rebase.

Saat digunakan dengan cara standar, seperti:

git checkout branch
git rebase master

Anda tidak akan melihat bahwa setelah rebase gitpindah branchke komit yang baru saja dirubah dan lakukan git checkout branch(lihat git reflogriwayat). Apa yang menarik ketika argumen ke-2 adalah hash bukan nama cabang rebase masih berfungsi tetapi tidak ada cabang untuk dipindahkan sehingga Anda berakhir di "HEAD terpisah" bukannya diperiksa keluar untuk pindah cabang.

Abaikan komitmen berbeda utama.

The masterdi --ontodiambil dari 1 git rebaseargumen.

                   git rebase master
                              /    \
         git rebase --onto master master

Begitu praktis, bisa berupa komit atau cabang lainnya. Dengan cara ini Anda dapat membatasi jumlah komit rebase dengan mengambil komit terbaru dan meninggalkan komit divergen utama.

git rebase --onto master HEAD~
git rebase --onto master HEAD~ HEAD  # Expanded.

Akan rebase tunggal komit ditunjuk oleh HEADkemaster dan berakhir di "KEPALA terpisah".

Hindari checkout eksplisit.

Default HEADatau current_branchargumen diambil secara kontekstual dari tempat Anda berada. Inilah sebabnya mengapa kebanyakan orang melakukan checkout ke cabang yang ingin mereka rebase. Tetapi ketika argumen rebase ke-2 diberikan secara eksplisit, Anda tidak perlu checkout sebelum rebase untuk memberikannya secara implisit.

(branch) $ git rebase master
(branch) $ git rebase master branch  # Expanded.
(branch) $ git rebase master $(git rev-parse --abbrev-ref HEAD)  # Kind of what git does.

Ini artinya Anda dapat rebase komit dan cabang dari mana saja . Jadi bersama dengan checkout otomatis setelah rebase. Anda tidak harus secara terpisah checkout cabang yang direbahkan sebelum atau setelah rebase.

(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.
WloHu
sumber
8

Sederhananya, git rebase --onto pilih serangkaian commit dan rebases mereka pada komit yang diberikan sebagai parameter.

Baca halaman manual untuk git rebase, cari "ke". Contohnya sangat bagus:

example of --onto option is to rebase part of a branch. If we have the following situation:

                                   H---I---J topicB
                                  /
                         E---F---G  topicA
                        /
           A---B---C---D  master

   then the command

       git rebase --onto master topicA topicB

   would result in:

                        H'--I'--J'  topicB
                       /
                       | E---F---G  topicA
                       |/
           A---B---C---D  master

Dalam hal ini Anda memberi tahu git untuk mengubah komit dari topicAmenjadi topicBdi atas master.

Gauthier
sumber
8

Untuk lebih memahami perbedaan antara git rebasedan git rebase --ontoitu baik untuk mengetahui apa perilaku yang mungkin untuk kedua perintah. git rebasememungkinkan kita untuk memindahkan komit kita di atas cabang yang dipilih. Seperti di sini:

git rebase master

dan hasilnya adalah:

Before                              After
A---B---C---F---G (master)          A---B---C---F---G (master)
         \                                           \
          D---E (HEAD next-feature)                   D'---E' (HEAD next-feature)

git rebase --ontolebih tepat. Hal ini memungkinkan kita untuk memilih komit tertentu di mana kita ingin memulai dan juga di mana kita ingin menyelesaikan. Seperti di sini:

git rebase --onto F D

dan hasilnya adalah:

Before                                    After
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                             \
          D---E---H---I (HEAD my-branch)                E'---H'---I' (HEAD my-branch)

Untuk mendapatkan rincian lebih lanjut, saya sarankan Anda untuk membaca artikel saya sendiri tentang git rebase --tinjauan umum

womanonrails
sumber
@Makyen Tentu, saya akan mengingatnya di masa depan :)
womanonrails
Jadi, kita dapat membaca git rebase --onto F Dsebagai set anak dari orangtua D sebagai F , bukan?
Prihex
2

Untuk ontoAnda memerlukan dua cabang tambahan. Dengan perintah itu Anda dapat menerapkan komit dari branchByang didasarkan pada branchAke cabang lain misalnya master. Dalam contoh di bawah branchBini didasarkan pada branchAdan Anda ingin menerapkan perubahan branchBpada mastertanpa menerapkan perubahan branchA.

o---o (master)
     \
      o---o---o---o (branchA)
                   \
                    o---o (branchB)

dengan menggunakan perintah:

checkout branchB
rebase --onto master branchA 

Anda akan mengikuti hierarki komit.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)
Michael Mairegger
sumber
1
Bisakah Anda jelaskan lebih banyak lagi, jika kami ingin rebase ke master, kenapa bisa menjadi cabang saat ini? Jika Anda melakukan itu rebase --onto branchA branchBakan menempatkan seluruh cabang master di kepala branchA?
Polymerase
8
bukankah ini seharusnya checkout branchB: rebase --onto master branchA?
goldenratio
4
Mengapa ini dibalik? ini tidak melakukan apa yang dikatakannya.
De Novo
Saya mengedit dan memperbaiki jawabannya, jadi orang-orang tidak perlu merusak cabang repo mereka dan setelah itu datang dan membaca komentar ... 🙄
Kamafeather
0

Ada kasus lain di mana git rebase --ontosulit untuk dipahami: ketika Anda rebase ke komit yang dihasilkan dari pemilih perbedaan simetris (tiga titik '... ')

Git 2.24 (Q4 2019) melakukan pekerjaan yang lebih baik dalam mengelola kasus itu:

Lihat komit 414d924 , komit 4effc5b , komit c0efb4c , komit 2b318aa (27 Agustus 2019), dan komit 793ac7e , komit 359eceb (25 Agustus 2019) oleh Denton Liu ( Denton-L) .
Dibantu-oleh: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) , dan Johannes Schindelin ( dscho) .
Lihat komit 6330209 , komit c9efc21 (27 Agustus 2019), dan komit 4336d36 (25 Agustus 2019) oleh Ævar Arnfjörð Bjarmason ( Dibantu-oleh: Eric Sunshine ( ,avar ).
sunshineco) Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) , dan Johannes Schindelin ( dscho) .
(Digabung oleh Junio ​​C Hamano - gitster- dalam komit 640f9cd , 30 Sep 2019)

rebase: maju cepat --onto dalam lebih banyak kasus

Sebelumnya, ketika kami memiliki grafik berikut,

A---B---C (master)
     \
      D (side)

berlari ' git rebase --onto master... master side' akan berakibat Dselalu dilahirkan kembali, tidak peduli apa.

Pada titik ini, baca " Apa perbedaan antara titik ganda ' ..' dan titik tiga" ..."dalam rentang komit Git? "

https://sphinx.mythic-beasts.com/~mark/git-diff-help.png

Di sini: " master..." mengacu pada master...HEAD, yaitu B: KEPALA adalah sisi KEPALA (saat ini sedang diperiksa): Anda sedang menuju B.
Apa yang kamu rebasing? Setiap komit yang tidak dikuasai, dan dapat dijangkau dari sidecabang: hanya ada satu komit yang cocok dengan deskripsi tersebut: D... yang sudah ada di atas B!

Sekali lagi, sebelum Git 2.24, hal seperti itu rebase --ontoakan berakibat Dselalu diubah, apa pun yang terjadi.

Namun, perilaku yang diinginkan adalah bahwa rebase harus memperhatikan bahwa ini adalah fast-forwardable dan melakukan itu sebagai gantinya.

Itu mirip dengan rebase --onto B AOP, yang tidak melakukan apa pun.

Tambahkan deteksi can_fast_forwardsehingga kasus ini dapat dideteksi dan fast-forward akan dilakukan.
Pertama-tama, tulis ulang fungsi untuk menggunakan gotos yang menyederhanakan logika.
Selanjutnya, sejak

options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)

kondisi telah dihapus dalam cmd_rebase, kami memperkenalkan kembali pengganti di can_fast_forward.
Secara khusus, memeriksa basis gabungan upstreamdan headmemperbaiki kasus gagal di t3416.

Grafik disingkat untuk t3416 adalah sebagai berikut:

        F---G topic
       /
  A---B---C---D---E master

dan perintah yang gagal adalah

git rebase --onto master...topic F topic

Sebelumnya, Git akan melihat bahwa ada satu basis penggabungan ( C, hasil dari master...topic), dan gabungan dan ke adalah sama sehingga akan salah mengembalikan 1, yang menunjukkan bahwa kita dapat mempercepat-maju. Ini akan menyebabkan grafik yang dirangkum menjadi ' ABCFG' ketika kami mengharapkan ' ABCG'.

A rebase --onto C F topicberarti setiap komit setelah F , dapat dijangkau oleh topicKEPALA: itu Ghanya, bukan Fdirinya sendiri.
Penerusan cepat dalam kasus ini akan termasuk Fdalam cabang rebased, yang salah.

Dengan logika tambahan, kami mendeteksi bahwa hulu dan basis gabungan kepala adalah F. Karena ke tidak F, itu berarti kami tidak membatalkan set penuh komitmen dari master..topic.
Karena kami mengecualikan beberapa komitmen, fast-forward tidak bisa dapat dilakukan dan kami mengembalikan 0 dengan benar.

Tambahkan ' -f' untuk menguji kasus-kasus yang gagal sebagai akibat dari perubahan ini karena mereka tidak mengharapkan fast-forward sehingga rebase terpaksa.

VONC
sumber