Bagaimana cara memulihkan / mensinkronisasi ulang setelah seseorang melakukan rebase atau reset ke cabang yang diterbitkan?

88

Kita semua telah mendengar bahwa seseorang harus tidak pernah rebase menerbitkan karya, bahwa itu berbahaya, dll Namun, saya belum melihat resep yang dipasang untuk bagaimana menghadapi situasi dalam kasus rebase yang diterbitkan.

Sekarang, perhatikan bahwa ini hanya benar-benar layak jika repositori hanya diklon oleh sekelompok orang yang dikenal (dan sebaiknya kecil), sehingga siapa pun yang mendorong rebase atau reset dapat memberi tahu semua orang bahwa mereka perlu memperhatikan lain kali mereka mengambil(!).

Satu solusi jelas yang saya lihat akan berfungsi jika Anda tidak memiliki komitmen lokal foodan itu didasarkan pada:

git fetch
git checkout foo
git reset --hard origin/foo

Ini hanya akan membuang status lokal yang foomendukung riwayatnya sesuai dengan repositori jarak jauh.

Tetapi bagaimana seseorang menghadapi situasi tersebut jika seseorang telah melakukan perubahan lokal yang substansial pada cabang itu?

Aristoteles Pagaltzis
sumber
1 untuk resep kasus sederhana. Ini ideal untuk sinkronisasi pribadi antar mesin, terutama jika mereka memiliki OS yang berbeda. Itu adalah sesuatu yang harus disebutkan di manual.
Philip Oakley
Resep ideal untuk sinkronisasi pribadi adalah git pull --rebase && git push. Jika Anda bekerja masterhanya, maka ini hampir tidak pernah gagal akan melakukan hal yang benar untuk Anda, bahkan jika Anda telah melakukan rebased dan mendorong di ujung yang lain.
Aristoteles Pagaltzis
Karena saya menyinkronkan dan mengembangkan antara PC dan mesin Linux, saya menemukan bahwa menggunakan cabang baru untuk setiap rebase / pembaruan berfungsi dengan baik. Saya juga menggunakan varian git reset --hard @{upstream}sekarang setelah saya tahu mantra refspec ajaib untuk "lupakan apa yang saya miliki / miliki, gunakan apa yang saya ambil dari remote" Lihat komentar terakhir saya ke stackoverflow.com/a/15284176/717355
Philip Oakley
Anda akan dapat, dengan Git2.0, untuk menemukan asal lama cabang Anda (sebelum cabang hulu ditulis ulang dengan a push -f): lihat jawaban saya di bawah
VonC

Jawaban:

75

Kembali selaras setelah push rebase sebenarnya tidak terlalu rumit dalam banyak kasus.

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

Yaitu. pertama-tama Anda menyiapkan bookmark di mana cabang jarak jauh awalnya berada, lalu Anda menggunakannya untuk memutar ulang komitmen lokal Anda sejak saat itu dan seterusnya ke cabang jarak jauh yang direbasis.

Rebasing seperti kekerasan: jika tidak menyelesaikan masalah, Anda hanya membutuhkan lebih banyak. ☺

Anda tentu saja dapat melakukan ini tanpa bookmark, jika Anda mencari origin/fooID commit pra-rebase , dan menggunakannya.

Ini juga bagaimana Anda menghadapi situasi di mana Anda lupa membuat bookmark sebelum mengambilnya. Tidak ada yang hilang - Anda hanya perlu memeriksa reflog untuk cabang jarak jauh:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

Ini akan mencetak ID komit yang origin/foomengarah ke sebelum pengambilan terbaru yang mengubah riwayatnya.

Anda kemudian bisa dengan sederhana

git rebase --onto origin/foo $commit foo
Aristoteles Pagaltzis
sumber
11
Catatan singkat: Saya rasa ini cukup intuitif, tetapi jika Anda tidak terlalu paham ... bahwa satu-liner hanya melihat-lihat keluaran dari git reflog show origin/foobaris pertama yang mengatakan "fetch: forced-update"; itulah yang dicatat oleh git saat pengambilan menyebabkan cabang jarak jauh melakukan apa pun kecuali maju cepat. (Anda bisa melakukannya dengan tangan juga - pembaruan paksa mungkin adalah hal yang paling baru.)
Cascabel
2
Ini tidak seperti kekerasan. Kekerasan terkadang menyenangkan
Iolo
5
@iolo Benar, rebasing selalu menyenangkan.
Dan Bechard
1
Seperti halnya kekerasan, hampir selalu menghindari rebasing. Tapi punya petunjuk bagaimana caranya.
Bob Stein
2
Nah, hindari mendorong rebase di mana orang lain akan terpengaruh.
Aristoteles Pagaltzis
11

Saya akan mengatakan pemulihan dari bagian rebase hulu halaman manual git-rebase mencakup hampir semua ini.

Ini benar-benar tidak ada bedanya dengan memulihkan dari rebase Anda sendiri - Anda memindahkan satu cabang, dan mendasarkan kembali semua cabang yang ada dalam riwayat mereka ke posisi baru.

Bertingkat
sumber
4
Ah, memang begitu. Tetapi meskipun sekarang saya mengerti apa yang dikatakannya, saya tidak akan melakukannya sebelumnya, sebelum memikirkannya sendiri. Dan tidak ada resep buku masak (mungkin memang benar dalam dokumentasi semacam itu). Saya juga akan mengemukakan bahwa menyebut "kasus sulit" itu sulit adalah FUD. Saya sampaikan bahwa sejarah yang ditulis ulang dapat dikelola dengan mudah pada skala pengembangan internal yang paling. Cara takhayul yang selalu memperlakukan subjek ini mengganggu saya.
Aristoteles Pagaltzis
4
@Aristotle: Anda benar bahwa ini sangat mudah dikelola, mengingat semua pengembang tahu cara menggunakan git, dan bahwa Anda dapat berkomunikasi secara efektif dengan semua pengembang. Di dunia yang sempurna, itulah akhir cerita. Tetapi banyak proyek di luar sana yang cukup besar sehingga rebase upstream benar-benar hal yang menakutkan. (Dan kemudian ada tempat-tempat seperti tempat kerja saya, di mana sebagian besar pengembang belum pernah mendengar tentang rebase.) Menurut saya "takhayul" hanyalah cara untuk memberikan saran yang paling aman dan umum. Tidak ada yang mau menjadi orang yang menyebabkan bencana di repo orang lain.
Kaskabel
2
Ya, saya mengerti motifnya. Dan saya setuju sepenuhnya. Tetapi ada perbedaan dunia antara "jangan coba ini jika Anda tidak memahami konsekuensinya" dan "Anda tidak boleh melakukan itu karena itu jahat", dan ini saja yang saya persoalkan. Itu selalu lebih baik untuk memberi instruksi daripada menanamkan rasa takut.
Aristoteles Pagaltzis
@Aristoteles: Setuju. Saya mencoba untuk cenderung ke arah akhir "pastikan Anda tahu apa yang Anda lakukan", tetapi terutama secara online, saya mencoba memberikan bobot yang cukup sehingga pengunjung biasa dari google akan memperhatikan. Anda benar, banyak dari itu mungkin harus dilunakkan.
Cascabel
11

Dimulai dengan git 1,9 / 2,0 Q1 2014, Anda tidak perlu menandai asal cabang Anda sebelumnya sebelum rebasing pada cabang hulu ditulis ulang, seperti yang dijelaskan dalam Aristoteles Pagaltzis 's jawabannya :
Lihat komit 07d406b dan berkomitmen d96855f :

Setelah mengerjakan topiccabang yang dibuat dengan git checkout -b topic origin/master, riwayat cabang pelacak jarak jauh origin/mastermungkin telah diputar ulang dan dibangun kembali, yang mengarah ke riwayat bentuk ini:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

di mana origin/masterdigunakan untuk titik di komit B3, B2, B1dan sekarang menunjuk B, dan Anda topiccabang dimulai di atas itu kembali ketika origin/masterberada di B3.

Mode ini menggunakan reflog dari origin/masterto find B3sebagai titik percabangan, sehingga topicdapat di-rebased di atas yang diperbaruiorigin/master oleh:

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

Itulah mengapa git merge-baseperintah tersebut memiliki opsi baru:

--fork-point::

Temukan titik di mana cabang (atau sejarah yang mengarah ke <commit>) bercabang dari cabang lain (atau referensi apa pun) <ref>.
Ini tidak hanya mencari nenek moyang yang sama dari dua commit, tetapi juga memperhitungkan reflog <ref>untuk melihat apakah sejarah yang mengarah ke <commit>bercabang dari inkarnasi cabang sebelumnya<ref> .


Perintah " git pull --rebase" menghitung titik percabangan dari cabang yang di-rebased menggunakan entri reflog dari basecabang " " (biasanya cabang pelacak jarak jauh) yang menjadi dasar pekerjaan cabang, untuk mengatasi kasus di mana "basis" cabang telah diputar ulang dan dibangun kembali.

Misalnya, jika sejarah tampak seperti di mana:

  • ujung saat ini dari basecabang " " ada di B, tetapi pengambilan sebelumnya mengamati bahwa ujungnya dulu B3dan kemudian B2dan kemudian B1 sebelum sampai ke komit saat ini, dan
  • cabang yang di-rebased di atas "base" terbaru didasarkan pada commit B3,

mencoba untuk menemukan B3dengan pergi melalui output dari " git rev-list --reflog base" (yaitu B, B1, B2, B3) sampai menemukan komit yang merupakan nenek moyang dari ujung saat ini " Derived (topic)".

Secara internal, kami memiliki get_merge_bases_many()yang dapat menghitung ini dengan sekali jalan.
Kami menginginkan basis penggabungan antara Deriveddan komitmen penggabungan fiktif yang akan dihasilkan dengan menggabungkan semua kiat historis dari " base (origin/master)".
Jika komit seperti itu ada, kita harus mendapatkan satu hasil, yang sama persis dengan salah satu entri reflog " base".


Git 2.1 (Q3 2014) akan menambahkan untuk membuat fitur ini lebih kuat untuk ini: lihat komit 1e0dacd oleh John Keeping ( johnkeeping)

menangani dengan benar skenario di mana kita memiliki topologi berikut ini:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

dimana:

  • B'adalah versi tetap Byang tidak identik dengan patch B;
  • C*dan D*yang patch-identik dengan Cdan Dmasing-masing dan konflik tekstual jika diterapkan dalam urutan yang salah;
  • Etergantung secara tekstual D.

Hasil yang benar dari git rebase master devyaitu yang Bdiidentifikasi sebagai garpu-titik devdan master, sehingga C, D, Eadalah commit yang perlu diputar ke master; tetapi Cdan Didentik dengan patch C*dan D*dan dan karenanya dapat dihilangkan, sehingga hasil akhirnya adalah:

o --- B' --- C* --- D* --- E  <- dev

Jika titik percabangan tidak teridentifikasi, maka memilih Bke cabang yang berisi B'hasil dalam konflik dan jika komit yang identik dengan tambalan tidak diidentifikasi dengan benar, maka memilih Cke cabang yang berisi D(atau setara D*) menghasilkan konflik.


" --fork-point" Mode " git rebase" mengalami kemunduran saat perintah ditulis ulang di C kembali di era 2.20, yang telah diperbaiki dengan Git 2.27 (Q2 2020).

Lihat commit f08132f (09 Des 2019) oleh Junio ​​C Hamano ( gitster) .
(Digabung oleh Junio ​​C Hamano - gitster- di commit fb4175b , 27 Mar 2020)

rebase: --fork-pointperbaikan regresi

Ditandatangani oleh: Alex Torok
[jc: mengubah perbaikan dan menggunakan tes Alex]
Ditandatangani oleh: Junio ​​C Hamano

" git rebase --fork-point master" dulu bekerja dengan baik, karena secara internal disebut " git merge-base --fork-point" yang tahu bagaimana menangani refname singkat dan mengubahnya menjadi nama ref lengkap sebelum memanggil get_fork_point()fungsi yang mendasarinya .

Hal ini tidak berlaku lagi setelah perintah ditulis ulang dalam C, karena panggilan internal yang dibuat langsung ke get_fork_point()tidak hanya berupa ref singkat.

Pindahkan logika "dwim the refname to the full refname" yang digunakan dalam "git merge-base" ke get_fork_point()fungsi yang mendasarinya , sehingga pemanggil lain dari fungsi tersebut dalam implementasi "git rebase" berperilaku sama untuk memperbaikinya regresi ini.

VonC
sumber
1
Perhatikan bahwa git push --force sekarang dapat (git 1.8.5) dilakukan dengan lebih hati-hati: stackoverflow.com/a/18505634/6309
VonC