Pertanyaan Git alur kerja dan rebase vs menggabungkan pertanyaan


Saya telah menggunakan Git sekarang selama beberapa bulan pada proyek dengan satu pengembang lainnya. Saya memiliki beberapa tahun pengalaman dengan SVN, jadi saya rasa saya membawa banyak bagasi ke hubungan.

Saya telah mendengar bahwa Git sangat baik untuk bercabang dan bergabung, dan sejauh ini, saya tidak melihatnya. Tentu, percabangan mati sederhana, tetapi ketika saya mencoba untuk menggabungkan, semuanya berjalan sampai ke neraka. Sekarang, saya sudah terbiasa dengan yang dari SVN, tetapi tampaknya bagi saya bahwa saya hanya diperdagangkan satu sistem versi sub-par untuk yang lain.

Mitra saya mengatakan kepada saya bahwa masalah saya berasal dari keinginan saya untuk bergabung dengan tidak mau, dan bahwa saya harus menggunakan rebase daripada bergabung dalam banyak situasi. Misalnya, inilah alur kerja yang dia berikan:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

Pada dasarnya, buat cabang fitur, SELALU rebase dari master ke cabang, dan bergabung dari cabang kembali ke master. Penting untuk dicatat adalah bahwa cabang selalu tetap lokal.

Berikut adalah alur kerja yang saya mulai dengan

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

Ada dua perbedaan penting (saya pikir): Saya menggunakan penggabungan selalu, bukan rebase, dan saya mendorong cabang fitur saya (dan cabang fitur saya melakukan) ke repositori jarak jauh.

Alasan saya untuk cabang terpencil adalah bahwa saya ingin pekerjaan saya didukung saat saya sedang bekerja. Repositori kami dicadangkan secara otomatis dan dapat dipulihkan jika terjadi kesalahan. Laptop saya tidak, atau tidak sepenuhnya. Oleh karena itu, saya tidak suka memiliki kode di laptop saya yang tidak dicerminkan di tempat lain.

Alasan saya untuk menggabungkan bukannya rebase adalah bahwa menggabungkan tampaknya standar dan rebase tampaknya menjadi fitur lanjutan. Firasat saya adalah bahwa apa yang saya coba lakukan bukanlah pengaturan lanjutan, jadi rebase seharusnya tidak diperlukan. Saya bahkan membaca buku Pragmatic Programming baru di Git, dan mereka mencakup penggabungan secara luas dan hampir tidak menyebutkan rebase.

Bagaimanapun, saya mengikuti alur kerja saya di cabang baru-baru ini, dan ketika saya mencoba untuk menggabungkan kembali untuk menguasai, semuanya pergi ke neraka. Ada banyak konflik dengan hal-hal yang seharusnya tidak penting. Konflik itu tidak masuk akal bagiku. Saya butuh satu hari untuk menyelesaikan semuanya, dan akhirnya memuncak dengan dorongan paksa kepada guru jarak jauh, karena tuan lokal saya telah menyelesaikan semua konflik, tetapi yang terpencil masih tidak senang.

Apa alur kerja yang "benar" untuk sesuatu seperti ini? Git seharusnya membuat percabangan dan menggabungkan super-mudah, dan aku tidak melihatnya.

Perbarui 2011-04-15

Ini sepertinya pertanyaan yang sangat populer, jadi saya pikir saya akan memperbarui dengan pengalaman dua tahun saya sejak pertama kali saya bertanya.

Ternyata alur kerja asli benar, setidaknya dalam kasus kami. Dengan kata lain, inilah yang kami lakukan dan berhasil:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

Bahkan, alur kerja kami sedikit berbeda, seperti yang cenderung kami lakukan gabungan labu bukannya gabungan mentah. (Catatan: Ini kontroversial, lihat di bawah.) Ini memungkinkan kami untuk mengubah seluruh cabang fitur kami menjadi satu komit pada master. Lalu kami menghapus cabang fitur kami. Ini memungkinkan kita untuk secara logis menyusun komitmen kita pada master, bahkan jika mereka sedikit berantakan di cabang-cabang kita. Jadi, inilah yang kami lakukan:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

Kontroversi Penggabungan Squash - Seperti yang telah ditunjukkan oleh beberapa komentator, gabungan squash akan membuang semua riwayat di cabang fitur Anda. Seperti namanya, itu membuat komit semua komit menjadi satu. Untuk fitur kecil, ini masuk akal karena mengembunkannya ke dalam satu paket. Untuk fitur yang lebih besar, itu mungkin bukan ide bagus, terutama jika komitmen individu Anda sudah menjadi atom. Itu benar-benar turun ke preferensi pribadi.

Github dan Bitbucket (lainnya?) Tarik Permintaan - Jika Anda bertanya-tanya bagaimana menggabungkan / rebase berhubungan dengan Permintaan Tarik, saya sarankan mengikuti semua langkah di atas sampai Anda siap untuk menggabungkan kembali ke master. Alih-alih menggabungkan secara manual dengan git, Anda cukup menerima PR. Secara khusus, ia berfungsi seperti ini:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

Saya telah mencintai Git dan tidak pernah ingin kembali ke SVN. Jika Anda sedang berjuang, tetaplah melakukannya dan akhirnya Anda akan melihat cahaya di ujung terowongan.


895
2018-01-19 15:16


asal


Jawaban:


"Konflik" berarti "evolusi paralel dari konten yang sama". Jadi jika itu berjalan "semua ke neraka" selama penggabungan, itu berarti Anda memiliki evolusi besar pada kumpulan file yang sama.

Alasan mengapa rebase kemudian lebih baik daripada gabungan adalah bahwa:

  • Anda menulis ulang riwayat komitmen lokal Anda dengan salah satu master (dan kemudian menerapkan kembali pekerjaan Anda, menyelesaikan konflik apa pun kemudian)
  • penggabungan akhir pasti akan menjadi "maju cepat", karena akan memiliki semua sejarah komitmen dari master, ditambah hanya perubahan Anda untuk mengajukan permohonan kembali.

Saya mengkonfirmasi bahwa alur kerja yang benar dalam kasus itu (evolusi pada kumpulan file yang umum) adalah rebase dulu, lalu gabung.

Namun, itu berarti bahwa, jika Anda menekan cabang lokal Anda (untuk alasan cadangan), cabang itu tidak boleh ditarik (atau setidaknya digunakan) oleh orang lain (karena sejarah komitmen akan ditulis ulang oleh rebase berturut-turut).


Pada topik itu (rebase lalu gabungkan alur kerja), barraponto sebutan di komentar dua posting menarik, keduanya dari randyfay.com:

Dengan menggunakan teknik ini, pekerjaan Anda selalu berada di atas cabang publik seperti patch yang mutakhir dengan arus HEAD.

(teknik yang serupa ada untuk bazaar)


342
2018-01-19 15:32



TL; DR

Alur kerja git rebase tidak melindungi Anda dari orang-orang yang buruk dalam resolusi konflik atau orang yang terbiasa dengan alur kerja SVN, seperti disarankan di Menghindari Git Disasters: A Gory Story. Itu hanya membuat resolusi konflik lebih membosankan bagi mereka dan mempersulit pemulihan dari resolusi konflik yang buruk. Sebaliknya, gunakan diff3 sehingga tidak begitu sulit di tempat pertama.


Alur kerja Rebase tidak lebih baik untuk resolusi konflik!

Saya sangat pro-rebase untuk membersihkan sejarah. Namun jika Saya pernah memukul konflik, saya segera membatalkan rebase dan melakukan penggabungan sebagai gantinya!Ini benar-benar membunuh saya bahwa orang-orang merekomendasikan alur kerja rebase sebagai alternatif yang lebih baik untuk menggabungkan alur kerja untuk resolusi konflik (yang persis seperti apa pertanyaan ini).

Jika itu pergi "semua ke neraka" selama penggabungan, itu akan pergi "semua ke neraka" selama rebase, dan berpotensi lebih banyak neraka juga! Inilah alasannya:

Alasan # 1: Selesaikan konflik sekali, daripada sekali untuk setiap commit

Saat Anda melakukan rebase alih-alih bergabung, Anda harus melakukan resolusi konflik hingga sebanyak yang Anda lakukan untuk melakukan rebase, untuk konflik yang sama!

Skenario nyata

Saya cabang dari master untuk refactor metode rumit di cabang. Pekerjaan refactoring saya terdiri dari 15 komit total ketika saya bekerja untuk refactor dan mendapatkan tinjauan kode. Bagian refactoring saya melibatkan memperbaiki tab campuran dan ruang yang ada di master sebelumnya. Ini perlu, tetapi sayangnya itu akan bertentangan dengan perubahan apa pun yang dibuat sesudahnya ke metode ini di master. Benar saja, ketika saya sedang mengerjakan metode ini, seseorang membuat perubahan yang sederhana dan sah ke metode yang sama di cabang master yang harus digabungkan dengan perubahan saya.

Ketika saatnya untuk menggabungkan kembali cabang saya dengan master, saya memiliki dua opsi:

git bergabung:  Saya mendapat konflik. Saya melihat perubahan yang mereka buat untuk menguasai dan menggabungkannya dengan (produk akhir dari) cabang saya. Selesai

git rebase:  Saya mendapat konflik dengan saya pertama melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya kedua melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya ketiga melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya keempat melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya kelima melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya keenam melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya ketujuh melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya kedelapan melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya kesembilan melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya kesepuluh melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya kesebelas melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya keduabelas melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya ketigabelas melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya keempatbelas melakukan. Saya menyelesaikan konflik dan melanjutkan rebase. Saya mendapat konflik dengan saya kelimabelas melakukan. Saya menyelesaikan konflik dan melanjutkan rebase.

Anda pasti bercanda jika saya ini adalah alur kerja pilihan Anda. Yang diperlukan hanyalah perbaikan spasi yang konflik dengan satu perubahan yang dibuat pada master, dan setiap commit akan konflik dan harus diselesaikan. Dan ini adalah sebuah sederhana skenario hanya dengan konflik whitespace. Surga melarang Anda memiliki konflik nyata yang melibatkan perubahan kode besar di seluruh file dan harus diselesaikan bahwa beberapa kali.

Dengan semua resolusi konflik ekstra yang perlu Anda lakukan, itu hanya meningkatkan kemungkinan itu kamu akan membuat kesalahan. Tapi kesalahan baik-baik saja di git karena Anda bisa membatalkannya, kan? Kecuali tentu saja ...

Alasan # 2: Dengan rebase, tidak ada undo!

Saya pikir kita semua bisa sepakat bahwa resolusi konflik bisa sulit, dan juga bahwa beberapa orang sangat buruk dalam hal itu. Itu bisa sangat rentan terhadap kesalahan, yang mengapa begitu hebat sehingga git memudahkan untuk membatalkan!

Ketika Anda bergabung cabang, git membuat gabungan commit yang dapat dibuang atau diubah jika resolusi konflik berjalan buruk. Bahkan jika Anda telah mendorong komitmen penggabungan yang buruk ke repo publik / otoritatif, Anda dapat menggunakannya git revertuntuk membatalkan perubahan yang diperkenalkan oleh gabungan dan mengulang penggabungan dengan benar dalam komit gabungan baru.

Ketika Anda melakukan rebase cabang, dalam hal yang mungkin bahwa resolusi konflik dilakukan salah, Anda kacau. Setiap commit sekarang mengandung penggabungan yang buruk, dan Anda tidak bisa hanya mengulang rebase *. Paling baik, Anda harus kembali dan mengubah setiap commit yang terpengaruh. Tidak menyenangkan.

Setelah rebase, tidak mungkin untuk menentukan apa yang semula merupakan bagian dari komitmen dan apa yang diperkenalkan sebagai akibat dari resolusi konflik yang buruk.

* Anda dapat membatalkan rebase jika Anda dapat menggali ref lama dari log internal git, atau jika Anda membuat cabang ketiga yang mengarah ke commit terakhir sebelum melakukan rebasing.

Keluarkan dari resolusi konflik: gunakan diff3

Ambillah konflik ini misalnya:

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Melihat konflik, tidak mungkin untuk mengatakan apa yang setiap cabang berubah atau apa maksudnya. Ini adalah alasan terbesar menurut saya mengapa resolusi konflik membingungkan dan sulit.

diff3 untuk menyelamatkan!

git config --global merge.conflictstyle diff3

Ketika Anda menggunakan diff3, setiap konflik baru akan memiliki bagian ke-3, leluhur bersama yang digabung.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Pertama periksa nenek moyang yang digabungkan. Kemudian bandingkan masing-masing pihak untuk menentukan maksud setiap cabang. Anda dapat melihat bahwa HEAD mengubah EmailMessage ke TextMessage. Tujuannya adalah untuk mengubah kelas yang digunakan untuk TextMessage, melewati parameter yang sama. Anda juga dapat melihat bahwa maksud dari fitur-cabang adalah untuk mengirimkan false, bukan true untuk: include_timestamp option. Untuk menggabungkan perubahan ini, gabungkan maksud keduanya:

TextMessage.send(:include_timestamp => false)

Secara umum:

  1. Bandingkan leluhur yang sama dengan masing-masing cabang, dan tentukan cabang mana yang memiliki perubahan paling sederhana
  2. Terapkan perubahan sederhana itu ke versi kode cabang lain, sehingga berisi perubahan yang lebih sederhana dan lebih rumit
  3. Hapus semua bagian dari kode konflik selain dari yang baru saja Anda gabungkan perubahan menjadi satu

Alternatif: Pecahkan dengan secara manual menerapkan perubahan cabang

Akhirnya, beberapa konflik sangat sulit dipahami bahkan dengan diff3. Hal ini terjadi terutama ketika diff menemukan garis yang sama yang tidak semantik yang umum (misalnya kedua cabang kebetulan memiliki garis kosong di tempat yang sama!). Misalnya, satu cabang mengubah indentasi badan kelas atau mengatur ulang metode yang serupa. Dalam kasus ini, strategi resolusi yang lebih baik dapat memeriksa perubahan dari kedua sisi penggabungan dan secara manual menerapkan diff ke file lain.

Mari kita lihat bagaimana kita bisa menyelesaikan konflik dalam skenario saat penggabungan origin/feature1 dimana lib/message.rb konflik.

  1. Tentukan apakah cabang kami yang saat ini diperiksa (HEAD, atau --ours) atau cabang yang kami gabungkan (origin/feature1, atau --theirs) adalah perubahan yang lebih sederhana untuk diterapkan. Menggunakan diff dengan triple dot (git diff a...b) menunjukkan perubahan yang terjadi pada b sejak divergensi terakhirnya dari a, atau dengan kata lain, bandingkan leluhur bersama a dan b dengan b.

    git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
    git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. Periksa versi file yang lebih rumit. Ini akan menghapus semua penanda konflik dan menggunakan sisi yang Anda pilih.

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
    
  3. Dengan perubahan rumit yang diperiksa, tarik diff dari perubahan yang lebih sederhana (lihat langkah 1). Terapkan setiap perubahan dari diff ini ke file yang bentrok.


358
2018-06-27 04:25



Dalam alur kerja saya, saya rebase sebanyak mungkin (dan saya mencoba untuk sering melakukannya. Tidak membiarkan perbedaan tersebut secara drastis mengurangi jumlah dan tingkat tabrakan antar cabang).

Namun, bahkan dalam alur kerja yang sebagian besar berbasis rebase, ada tempat untuk menggabungkan.

Ingat bahwa penggabungan sebenarnya membuat simpul yang memiliki dua orang tua. Sekarang perhatikan situasi berikut: Saya memiliki dua fitur independen brances A dan B, dan sekarang ingin mengembangkan hal-hal pada cabang fitur C yang bergantung pada A dan B, sementara A dan B sedang ditinjau.

Apa yang saya lakukan, adalah sebagai berikut:

  1. Buat (dan checkout) cabang C di atas A.
  2. Gabungkan dengan B

Sekarang cabang C mencakup perubahan dari A dan B, dan saya dapat terus mengembangkannya. Jika saya melakukan perubahan ke A, maka saya merekonstruksi grafik cabang dengan cara berikut:

  1. buat cabang T di bagian atas A yang baru
  2. gabungkan T dengan B
  3. rebase C ke T
  4. hapus cabang T

Dengan cara ini saya benar-benar dapat mempertahankan grafik cabang yang acak, tetapi melakukan sesuatu yang lebih rumit daripada situasi yang dijelaskan di atas sudah terlalu rumit, mengingat tidak ada alat otomatis untuk melakukan rebasing ketika induknya berubah.


29
2018-05-12 20:54



JANGAN gunakan git push origin --mirror UNDER ALMOST ANY CIRCUMSTANCE.

Itu tidak menanyakan apakah Anda yakin ingin melakukan ini, dan sebaiknya Anda memastikannya, karena ini akan menghapus semua cabang jarak jauh Anda yang tidak ada di kotak lokal Anda.

http://twitter.com/dysinger/status/1273652486


20
2018-04-10 01:06



Saya punya satu pertanyaan setelah membaca penjelasan Anda: Mungkinkah Anda tidak pernah melakukan

git checkout master
git pull origin
git checkout my_new_feature

sebelum melakukan 'git rebase / penggabungan master' di cabang fitur Anda?

Karena anda cabang utama tidak akan diperbarui secara otomatis dari repositori teman Anda. Anda harus melakukannya dengan git pull origin. Yaitu. mungkin Anda akan selalu rebase dari cabang master lokal yang tidak pernah berubah? Dan kemudian datang waktu push, Anda mendorong dalam repositori yang memiliki (lokal) melakukan Anda tidak pernah melihat dan dengan demikian push gagal.


13
2018-05-25 20:52



Dalam situasi Anda, saya pikir pasangan Anda benar. Apa yang baik tentang rebasing adalah bahwa bagi orang luar, perubahan Anda terlihat seperti semua terjadi dalam urutan yang bersih dengan sendirinya. Ini berarti

  • perubahan Anda sangat mudah ditinjau
  • Anda dapat terus membuat komitmen yang baik dan kecil namun Anda dapat membuat kelompok-kelompok yang berkomitmen secara publik (dengan bergabung menjadi master) sekaligus
  • ketika Anda melihat cabang master publik, Anda akan melihat serangkaian komit yang berbeda untuk fitur yang berbeda oleh pengembang yang berbeda tetapi tidak semuanya akan tercampur

Anda masih dapat terus mendorong cabang pengembangan pribadi Anda ke repositori jarak jauh demi cadangan, tetapi yang lain tidak boleh menganggapnya sebagai cabang "publik" karena Anda akan melakukan rebase. BTW, perintah mudah untuk melakukan ini git push --mirror origin .

Artikel Perangkat lunak pengemasan menggunakan Git melakukan pekerjaan yang cukup bagus menjelaskan trade off dalam merger versus rebasing. Ini adalah konteks yang sedikit berbeda tetapi prinsipnya sama - pada dasarnya berujung pada apakah cabang Anda bersifat publik atau pribadi dan bagaimana Anda merencanakan untuk mengintegrasikannya ke dalam garis utama.


12
2018-01-19 16:15



Bagaimanapun, saya mengikuti alur kerja saya di cabang baru-baru ini, dan ketika saya mencoba untuk menggabungkan kembali untuk menguasai, semuanya pergi ke neraka. Ada banyak konflik dengan hal-hal yang seharusnya tidak penting. Konflik itu tidak masuk akal bagiku. Saya butuh satu hari untuk menyelesaikan semuanya, dan akhirnya memuncak dengan dorongan paksa kepada guru jarak jauh, karena tuan lokal saya telah menyelesaikan semua konflik, tetapi yang terpencil masih tidak senang.

Baik dalam pasangan Anda maupun alur kerja yang Anda sarankan seharusnya Anda mengalami konflik yang tidak masuk akal. Bahkan jika Anda memiliki, jika Anda mengikuti alur kerja yang disarankan, maka setelah resolusi, dorongan 'paksa' tidak diperlukan. Ini menunjukkan bahwa Anda belum benar-benar menggabungkan cabang yang Anda dorong, tetapi harus mendorong cabang yang bukan merupakan keturunan dari ujung terpencil.

Saya pikir Anda perlu melihat dengan hati-hati apa yang terjadi. Mungkinkah orang lain (sengaja atau tidak) memutar kembali cabang master jarak jauh antara kreasi cabang lokal Anda dan titik di mana Anda mencoba untuk menggabungkannya kembali ke cabang lokal?

Dibandingkan dengan banyak sistem kontrol versi lain, saya telah menemukan bahwa menggunakan Git melibatkan lebih sedikit perkelahian alat dan memungkinkan Anda untuk bekerja pada masalah yang mendasar bagi aliran sumber Anda. Git tidak melakukan sihir, sehingga perubahan yang saling bertentangan menyebabkan konflik, tetapi hal itu seharusnya memudahkan untuk melakukan hal yang ditulis oleh pelacakan dari turunannya.


11
2018-01-19 19:30