Apa sebenarnya yang dilakukan “rebase --preserve-merge” git (dan mengapa?)

355

Dokumentasirebase Git untuk perintah ini cukup singkat:

--preserve-merges
    Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).

Jadi apa yang sebenarnya terjadi ketika Anda menggunakannya --preserve-merges? Apa bedanya dengan perilaku default (tanpa tanda itu)? Apa artinya "menciptakan kembali" gabungan, dll.

Chris
sumber
20
Peringatan: dimulai dengan Git 2.18 (Q2 2018, 5 tahun kemudian), git --rebase-mergespada akhirnya akan menggantikan yang lama git --preserve-merges. Lihat jawaban saya di bawah ini
VonC

Jawaban:

464

Seperti halnya git rebase normal, git dengan --preserve-mergespertama mengidentifikasi daftar commit yang dibuat dalam satu bagian dari grafik commit, dan kemudian mengulang komit-komit tersebut di atas bagian lain. Perbedaan --preserve-mergesterkait dengan komit yang dipilih untuk diputar ulang dan bagaimana putar ulang bekerja untuk gabungan bergabung.

Untuk lebih eksplisit tentang perbedaan utama antara rebase normal dan penggabungan-mempertahankan:

  • Rebase pengawet gabung bersedia untuk memutar ulang (beberapa) komit gabungan, sedangkan rebase normal sepenuhnya mengabaikan komit gabungan.
  • Karena bersedia untuk mengulang komit gabungan, rebase pelestarian gabungan harus mendefinisikan apa artinya mengulang komit gabungan, dan menangani beberapa kerutan tambahan
    • Bagian yang paling menarik, secara konseptual, mungkin dalam memilih seperti apa seharusnya gabungan orangtua komit yang baru.
    • Memutar komit gabungan juga mengharuskan secara eksplisit memeriksa komit tertentu ( git checkout <desired first parent>), sedangkan rebase normal tidak perlu khawatir tentang itu.
  • Rebase pelestarian gabungan mempertimbangkan serangkaian komitmen yang lebih dangkal untuk diputar ulang:
    • Secara khusus, itu hanya akan mempertimbangkan komit replaying yang dibuat karena basis penggabungan terbaru - yaitu waktu terkini dua cabang berbeda -, sedangkan rebase normal mungkin mengulang komit akan kembali ke yang pertama kali kedua cabang menyimpang.
    • Untuk sementara dan tidak jelas, saya percaya ini pada akhirnya adalah cara untuk menyaring replay dari "komitmen lama" yang telah "dimasukkan ke dalam" komitmen gabungan.

Pertama saya akan mencoba untuk menggambarkan "cukup tepat" apa yang --preserve-mergesdilakukan rebase , dan kemudian akan ada beberapa contoh. Seseorang tentu saja bisa mulai dengan contoh-contoh, jika itu tampaknya lebih bermanfaat.

Algoritma dalam "Brief"

Jika Anda ingin benar-benar memasuki gulma, unduh sumber git dan jelajahi file tersebut git-rebase--interactive.sh. (Rebase bukan bagian dari inti C Git, melainkan ditulis dalam bash. Dan, di belakang layar, ia berbagi kode dengan "rebase interaktif".)

Tapi di sini saya akan membuat sketsa apa yang saya pikir adalah esensi dari itu. Untuk mengurangi jumlah hal yang perlu dipikirkan, saya telah mengambil beberapa kebebasan. (mis. Saya tidak mencoba menangkap dengan akurasi 100% urutan yang tepat di mana perhitungan dilakukan, dan mengabaikan beberapa topik yang tampak kurang sentral, misalnya apa yang harus dilakukan tentang komit yang telah dipilih dengan ceri di antara cabang).

Pertama, perhatikan bahwa rebase non-penggabungan agak sederhana. Lebih atau kurang:

Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A") 
Replay all those commits onto B one at a time in order.

Rebase --preserve-mergesrelatif rumit. Ini sesederhana yang saya bisa membuatnya tanpa kehilangan hal-hal yang tampaknya cukup penting:

Find the commits to replay:
  First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
    This (these) merge base(s) will serve as a root/boundary for the rebase.
    In particular, we'll take its (their) descendants and replay them on top of new parents
  Now we can define C, the set of commits to replay. In particular, it's those commits:
    1) reachable from B but not A (as in a normal rebase), and ALSO
    2) descendants of the merge base(s)
  If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
    git log A..B --not $(git merge-base --all A B)
Replay the commits:
  Create a branch B_new, on which to replay our commits.
  Switch to B_new (i.e. "git checkout B_new")
  Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
    If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
    Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
      To create a merge commit, its parents must exist and we must know what they are.
      So first, figure out which parents to use for c', by reference to the parents of c:
        For each parent p_i in parents_of(c):
          If p_i is one of the merge bases mentioned above:
            # p_i is one of the "boundary commits" that we no longer want to use as parents
            For the new commit's ith parent (p_i'), use the HEAD of B_new.
          Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
            # Note: Because we're moving parents-before-children, a rewritten version
            # of p_i must already exist. So reuse it:
            For the new commit's ith parent (p_i'), use the rewritten version of p_i.
          Otherwise:
            # p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
            For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
      Second, actually create the new commit c':
        Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
        Merge in the other parent(s):
          For a typical two-parent merge, it's just "git merge p_2'".
          For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
        Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
  Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

Rebase dengan --onto Cargumen harus sangat mirip. Hanya alih-alih mulai melakukan komit di HEAD of B, Anda mulai melakukan komit di HEAD of C. (Dan gunakan C_new bukan B_new.)

Contoh 1

Misalnya, ambil grafik komit

  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G

m adalah gabungan komitmen dengan orang tua E dan G.

Misalkan kita topik rebased (H) di atas master (C) menggunakan rebase normal, non-penggabungan melestarikan. (Sebagai contoh, checkout topic; rebase master .) Dalam hal ini, git akan memilih commit berikut untuk replay:

  • pilih D
  • pilih E
  • pilih F
  • pilih G
  • pilih H

dan kemudian perbarui grafik komit seperti:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic

(D 'adalah ekuivalen replayed dari D, dll.)

Perhatikan bahwa komit gabungan m tidak dipilih untuk diputar ulang.

Jika kita malah melakukan --preserve-mergesrebase H di atas C. (Sebagai contoh, topik checkout; rebase --preserve-merge master .) Dalam kasus baru ini, git akan memilih komit berikut untuk diputar ulang:

  • pilih D
  • pilih E
  • pilih F (ke D 'di cabang' subtopik ')
  • pilih G (ke F 'di cabang' subtopik ')
  • pilih Gabung cabang 'subtopik' ke dalam topik
  • pilih H

Sekarang m itu dipilih untuk replay. Juga perhatikan bahwa menggabungkan orang tua E dan G dipilih untuk dimasukkan sebelum menggabungkan komit m.

Berikut adalah grafik komit yang dihasilkan:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'

Sekali lagi, D 'adalah versi D. (sama diciptakan kembali) dari D. Sama untuk E', dll. Setiap komit tidak pada master telah diputar ulang. Baik E dan G (gabungan orang tua m) telah diciptakan kembali sebagai E 'dan G' untuk berfungsi sebagai orang tua m '(setelah rebase, sejarah pohon masih tetap sama).

Contoh 2

Tidak seperti rebase normal, rebase pelestarian gabungan dapat membuat banyak anak-anak dari kepala hulu.

Sebagai contoh, pertimbangkan:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 

Jika kita rebase H (topik) di atas C (master), maka komit yang dipilih untuk rebase adalah:

  • pilih D
  • pilih E
  • pilih F
  • pilih G
  • pilih m
  • pilih H

Dan hasilnya seperti ini:

  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/

Contoh 3

Dalam contoh di atas, komit gabungan dan kedua orang tuanya adalah komit yang diputar ulang, alih-alih dari orangtua asli yang dimiliki komit gabungan. Namun, dalam rebases lain, komit gabungan yang diputar dapat berakhir dengan orang tua yang sudah ada di grafik komit sebelum penggabungan.

Sebagai contoh, pertimbangkan:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic

Jika kita mengubah topik menjadi master (mempertahankan penggabungan), maka komitmen untuk memutar ulang akan menjadi

  • pilih gabung komit m
  • pilih F

Grafik komit yang ditulis ulang akan terlihat seperti ini:

                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic

Di sini, replayed merge commit m 'membuat orang tua yang sudah ada sebelumnya dalam grafik commit, yaitu D (HEAD of master) dan E (salah satu orang tua dari gabungan mate commit m).

Contoh 4

Rebase pengawetan gabungan dapat membingungkan dalam kasus "komit kosong" tertentu. Setidaknya ini benar hanya beberapa versi git yang lebih lama (mis. 1.7.8.)

Ambil grafik komit ini:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic

Perhatikan bahwa komit m1 dan m2 harus menggabungkan semua perubahan dari B dan F.

Jika kita mencoba melakukan git rebase --preserve-mergesH (topik) ke D (master), maka komit berikut dipilih untuk diputar ulang:

  • pilih m1
  • pilih H

Perhatikan bahwa perubahan (B, F) yang disatukan dalam m1 harus sudah dimasukkan ke dalam D. (Perubahan itu harus dimasukkan ke dalam m2, karena m2 menggabungkan anak-anak B dan F.) Oleh karena itu, secara konseptual, memutar ulang m1 di atas D mungkin harus berupa no-op atau membuat komit kosong (yaitu komis di mana perbedaan antara revisi berturut-turut kosong).

Sebaliknya, git dapat menolak upaya untuk memutar ulang m1 di atas D. Anda bisa mendapatkan kesalahan seperti:

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

Sepertinya orang lupa memberikan flag ke git, tetapi masalah mendasarnya adalah bahwa git tidak suka membuat commit kosong.

Chris
sumber
6
Saya perhatikan bahwa git rebase --preserve-mergesini jauh lebih lambat daripada rebasetanpanya --preserve-merges. Apakah itu efek samping dari menemukan komitmen yang tepat? Adakah yang bisa dilakukan seseorang untuk mempercepatnya? (Ngomong-ngomong ... terima kasih untuk jawaban yang sangat terperinci!)
David Alan Hjelle
7
Sepertinya Anda harus selalu menggunakan --preserve-merge. Kalau tidak, ada potensi untuk kehilangan riwayat yaitu penggabungan berkomitmen.
DarVar
19
@DarVar Anda selalu kehilangan riwayat dengan rebase, karena Anda mengklaim bahwa perubahan terjadi pada basis kode yang berbeda dari yang sebenarnya.
Kronik
5
Apakah ini masih "jawaban sementara"?
Andrew Grimm
5
@Chronial Tentu saja Anda benar, bahwa rebasing selalu memasukkan kehilangan sejarah, tapi mungkin DarVar menyinggung fakta, bahwa Anda tidak hanya kehilangan sejarah, tetapi juga perubahan pada basis kode. Resolusi konflik berisi informasi yang hilang dalam semua cara yang memungkinkan suatu rebase dapat dilakukan. Anda selalu harus mengulanginya. Apakah benar-benar tidak ada cara, untuk membiarkan git mengulang resolusi konflik Anda? Mengapa git-cher tidak bisa memilih komit gabungan?
Nils_M
94

Git 2.18 (Q2 2018) akan sangat meningkatkan --preserve-mergeopsi dengan menambahkan opsi baru.

" git rebase" belajar " --rebase-merges" untuk mentransplantasikan seluruh topologi grafik commit di tempat lain .

(Catatan: Git 2.22, Q2 2019, sebenarnya sudah usang --preserve-merge , dan Git 2.25, Q1 2020, berhenti mengiklankannya di git rebase --helpkeluaran " " )

Lihat komit 25cff9f , komit 7543f6f , komit 1131ec9 , komit 7ccdf65 , komit 537e7d6 , komit a9be29c , komit 8f6aed7 , komit 1644c73 , komit d1e8b01 , komit 4c68e7d , komit 9055e40 , komit cb5206e , komit a01c2a5 , komit 2f6b1d1 , komit bf5c057 (25 Apr 2018) oleh Johannes Schindelin ( dscho) .
Lihat commit f431d73 (25 Apr 2018) oleh Stefan Beller ( stefanbeller) .
Lihat komit 2429335 (25 Apr 2018) oleh Phillip Wood ( phillipwood) .
(Digabung oleh Junio ​​C Hamano - gitster- di komit 2c18e6a , 23 Mei 2018)

pull: terima --rebase-mergesuntuk membuat ulang topologi cabang

Mirip dengan preservemode hanya lewat --preserve-merges opsi ke rebaseperintah, mergesmode hanya melewati --rebase-mergesopsi.

Ini akan memungkinkan pengguna untuk dengan mudah rebase topologi komit non-sepele saat menarik komit baru, tanpa meratakannya.


git rebasehalaman buku panduan sekarang memiliki bagian lengkap yang didedikasikan untuk memunculkan kembali sejarah dengan penggabungan .

Ekstrak:

Ada alasan yang sah mengapa pengembang mungkin ingin membuat kembali komitmen gabungan: untuk menjaga struktur cabang (atau "melakukan topologi") ketika bekerja pada beberapa cabang yang saling terkait.

Dalam contoh berikut, pengembang bekerja pada cabang topik yang refactors cara tombol didefinisikan, dan pada cabang topik lain yang menggunakan refactoring itu untuk menerapkan tombol "Laporkan bug".
Output dari git log --graph --format=%s -5mungkin terlihat seperti ini:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

Pengembang mungkin ingin mengubah komitmennya ke yang lebih baru master sambil mempertahankan topologi cabang, misalnya ketika cabang topik pertama diharapkan akan diintegrasikan ke dalam yang masterjauh lebih awal daripada yang kedua, katakanlah, untuk menyelesaikan konflik gabungan dengan perubahan pada DownloadButtonkelas yang dibuat itu menjadi master.

Rebase ini dapat dilakukan menggunakan --rebase-mergesopsi.


Lihat komit 1644c73 untuk contoh kecil:

rebase-helper --make-script: memperkenalkan bendera untuk rebase penggabungan

Sequencer baru saja mempelajari perintah-perintah baru yang dimaksudkan untuk menciptakan kembali struktur cabang ( serupa dengan semangat --preserve-merges, tetapi dengan desain yang tidak terlalu rusak ).

Mari kita izinkan rebase--helperuntuk membuat daftar todo memanfaatkan perintah ini, dipicu oleh --rebase-mergesopsi baru .
Untuk topologi komit seperti ini (di mana HEAD menunjuk ke C):

- A - B - C (HEAD)
    \   /
      D

daftar todo yang dihasilkan akan terlihat seperti ini:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C

Apa bedanya dengan --preserve-merge?
Commit 8f6aed7 menjelaskan:

Sekali waktu, pengembang di sini berpikir: bukankah lebih baik jika, katakanlah, Git untuk tambalan Windows di atas core Git dapat direpresentasikan sebagai setumpuk cabang, dan di rebase di atas core Git untuk mempertahankan serangkaian patch seri cherry-pick'able?

Upaya asli untuk menjawab ini adalah: git rebase --preserve-merges.

Namun, percobaan itu tidak pernah dimaksudkan sebagai opsi interaktif, dan itu hanya didukung oleh piggy git rebase --interactivekarena implementasi perintah itu terlihat sudah sangat, sangat akrab: itu dirancang oleh orang yang sama yang mendesain --preserve-merges: Anda benar-benar.

Dan oleh "Anda benar-benar", penulis menyebut dirinya: Johannes Schindelin ( dscho) , yang merupakan alasan utama (dengan beberapa pahlawan lainnya - Hannes, Steffen, Sebastian, ...) bahwa kami memiliki Git Untuk Windows (meskipun kembali pada hari - 2009 - itu tidak mudah ).
Dia bekerja di Microsoft sejak September 2015 , yang masuk akal mengingat Microsoft sekarang banyak menggunakan Git dan membutuhkan layanannya.
Itu tren dimulai pada 2013 sebenarnya, dengan TFS . Sejak itu, Microsoft mengelola repositori Git terbesar di planet ini ! Dan, sejak Oktober 2018, Microsoft mengakuisisi GitHub .

Anda dapat melihat Johannes berbicara dalam video ini untuk Git Merge 2018 pada April 2018.

Beberapa waktu kemudian, beberapa pengembang lain (saya melihat Anda, Andreas! ;-)) memutuskan bahwa itu akan menjadi ide yang baik untuk memungkinkan --preserve-mergesuntuk digabungkan dengan --interactive(dengan peringatan!) Dan pengelola Git (baiklah, pengelola Git sementara selama Junio ​​tidak ada, itu disetujui, dan saat itulah kemewahan --preserve-mergesdesain mulai berantakan agak cepat dan tidak glamor.

Di sini Jonathan berbicara tentang Andreas Schwab dari Suse.
Anda dapat melihat beberapa diskusi mereka pada tahun 2012 .

Alasannya? Dalam --preserve-mergesmode, orang tua dari komit gabungan (atau dalam hal ini, komit apa pun ) tidak dinyatakan secara eksplisit, tetapi tersirat oleh nama komit yang diteruskan ke pickperintah .

Ini membuatnya tidak mungkin, misalnya, untuk menyusun ulang komitmen .
Belum lagi untuk memindahkan komit antar cabang atau, dewa melarang, untuk membagi cabang topik menjadi dua.

Sayangnya, kekurangan ini juga mencegah mode itu (yang tujuan awalnya adalah untuk melayani Git untuk kebutuhan Windows, dengan harapan tambahan bahwa mungkin bermanfaat bagi orang lain juga) dari melayani Git untuk kebutuhan Windows.

Lima tahun kemudian, ketika menjadi benar-benar tidak dapat dipertahankan untuk memiliki satu seri tambalan yang besar dan sulit dijangkau, sebagian terkait, sebagian tambalan yang tidak terkait di Git untuk Windows yang diubah kembali ke tag inti Git dari waktu ke waktu (mendapatkan kemarahan pengembang yang tidak selayaknya diterima) dari git-remote-hgseri naas yang pertama kali usang Git untuk pendekatan bersaing Windows, hanya untuk ditinggalkan tanpa pengelola kemudian) benar-benar tidak bisa dipertahankan, " Git garden shears " lahir : sebuah skrip, dukungan babi di atas rebase interaktif, yang pertama-tama akan menentukan topologi cabang dari tambalan-tambalan yang akan di-rebase, membuat daftar pseudo todo untuk pengeditan lebih lanjut, mengubah hasilnya menjadi daftar todo yang nyata (menggunakan banyakexec perintah untuk "mengimplementasikan" perintah daftar todo yang hilang) dan akhirnya menciptakan kembali seri tambalan di atas komit basis baru.

(Skrip gunting taman Git direferensikan dalam tambalan ini di commit 9055e40 )

Itu pada tahun 2013.
Dan butuh sekitar tiga minggu untuk membuat desain dan mengimplementasikannya sebagai skrip out-of-tree. Tak perlu dikatakan, implementasi membutuhkan beberapa tahun untuk stabil, sementara desain itu sendiri terbukti masuk akal.

Dengan tambalan ini, kebaikan gunting kebun Git muncul dengan git rebase -isendirinya .
Melewati --rebase-mergesopsi akan menghasilkan daftar todo yang dapat dipahami dengan mudah, dan di mana jelas bagaimana mengatur ulang komit .
Cabang baru dapat diperkenalkan dengan memasukkan labelperintah dan panggilan merge <label>.
Dan sekali mode ini akan menjadi stabil dan diterima secara universal, kita bisa mencela kesalahan desain itu--preserve-merges .


Git 2.19 (Q3 2018) meningkatkan --rebase-mergesopsi baru dengan membuatnya berfungsi --exec.

Opsi " --exec" untuk " git rebase --rebase-merges" menempatkan perintah exec di tempat yang salah, yang telah diperbaiki.

Lihat komit 1ace63b (09 Agustus 2018), dan komit f0880f7 (06 Agt 2018) oleh Johannes Schindelin ( dscho) .
(Digabung oleh Junio ​​C Hamano - gitster- dalam commit 750eb11 , 20 Agu 2018)

rebase --exec: membuatnya bekerja dengan --rebase-merges

Idenya --execadalah untuk menambahkan execpanggilan setelah masing-masing pick.

Sejak pengenalan fixup!/ s quash!komit, ide ini diperluas untuk diterapkan pada "pick, kemungkinan diikuti oleh fixup / squash chain", yaitu eksekutif tidak akan disisipkan antara a pickdan salah satu dari baris fixupatau korespondennya squash.

Implementasi saat menggunakan trik kotor untuk mencapai itu: mengasumsikan bahwa ada hanya memilih / fixup / squash perintah, dan kemudian memasukkan satu execbaris sebelum picktapi pertama, dan menambahkan satu akhir.

Dengan daftar todo yang dihasilkan oleh git rebase --rebase-merges, implementasi sederhana ini menunjukkan masalahnya: itu menghasilkan hal yang salah ketika ada label, resetdan mergeperintah.

Mari kita ubah implementasi untuk melakukan apa yang kita inginkan: cari pickbaris, lewati semua rantai fixup / squash, dan kemudian masukkan exec baris . Busa, bilas, ulangi.

Catatan: kami bersusah payah untuk menyisipkan sebelum baris komentar bila memungkinkan, karena komit kosong diwakili oleh garis pick yang dikomentari (dan kami ingin menyisipkan baris exec pick sebelumnya sebelum baris seperti itu, bukan sesudahnya).

Saat melakukannya, tambahkan juga execbaris setelah mergeperintah, karena mereka serupa semangatnya dengan pickperintah: mereka menambahkan komit baru.


Git 2.22 (Q2 2019) memperbaiki penggunaan referensi / ditulis ulang / hierarki untuk menyimpan status perantara rebase, yang secara inheren membuat hierarki per worktree.

Lihat komit b9317d5 , komit 90d31ff , komit 09e6564 (07 Mar 2019) oleh Nguyễn Thái Ngọc Duy ( pclouds) .
(Digabung oleh Junio ​​C Hamano - gitster- dalam komit 917f2cd , 09 Apr 2019)

Pastikan ref / ditulis ulang / adalah per-worktree

a9be29c (sequencer: make ref yang dihasilkan oleh labelperintah worktree-local, 2018-04-25, Git 2.19) ditambahkan refs/rewritten/sebagai ruang referensi per-worktree.
Sayangnya (salah saya) ada beberapa tempat yang perlu diperbarui untuk memastikan itu benar-benar per-worktree.

- add_per_worktree_entries_to_dir()diperbarui untuk memastikan tampilan daftar ref pada per-worktree refs/rewritten/bukan per-repo satu.

  • common_list[]diperbarui sehingga git_path()mengembalikan lokasi yang benar. Ini termasuk " rev-parse --git-path".

Kekacauan ini dibuat oleh saya.
Saya mulai mencoba memperbaikinya dengan perkenalan di refs/worktree,mana semua referensi akan per-kerja tanpa perawatan khusus.
Ref / ditulis ulang yang disayangkan datang sebelum ref / worktree sehingga hanya ini yang bisa kita lakukan.


Dengan Git 2.24 (Q4 2019), " git rebase --rebase-merges" belajar untuk mendorong berbagai strategi penggabungan dan memberikan opsi khusus strategi kepada mereka.

Lihat commit 476998d (04 Sep 2019) oleh Elijah Newren ( newren) .
Lihat komit e1fac53 , komit a63f990 , komit 5dcdd74 , komit e145d99 , komit 4e6023b , komit f67336d , komit a9c7107 , komit b8c6f24 , komit d51b771 , komit c248d32 , komit 8c1e240 , komit 5efed0e , komit 68b54f6 , komit 2e7bbac , komit 6180b20 , berkomitmen d5b581f (31 Jul 2019) olehJohannes Schindelin ( dscho) .
(Digabung oleh Junio ​​C Hamano - gitster- dalam komit 917a319 , 18 Sep 2019)


Dengan Git 2.25 (Q1 2020), logika yang digunakan untuk memberi tahu worktree local dan repositori global terpisah adalah tetap, untuk memfasilitasi penggabungan-penggabungan.

Lihat komit f45f88b , komit c72fc40 , komit 8a64881 , komit 7cb8c92 , komit e536b1f (21 Okt 2019) oleh SZEDER Gábor ( szeder) .
(Digabung oleh Junio ​​C Hamano - gitster- di commit db806d7 , 10 Nov 2019)

path.c: jangan panggil matchfungsi tanpa nilai dalamtrie_find()

Ditandatangani oleh: SZEDER Gábor

'log / ref' bukan jalur khusus pohon yang berfungsi, tetapi sejak commit b9317d55a3 (Pastikan ref / ditulis ulang / adalah per-worktree, 2019-03-07, v2.22.0-rc0) ' git rev-parse --git-path' telah mengembalikan jalur palsu jika ada trailing ' /':

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

Kami menggunakan a trie struktur data untuk memutuskan secara efisien apakah jalur milik dir umum atau bekerja khusus pohon.

Seperti yang terjadi, b9317d55a3 memicu bug yang setua trieimplementasi itu sendiri, ditambahkan di 4e09cf2acf (" path: optimalkan pemeriksaan direktori umum", 2015-08-31, Git v2.7.0-rc0 - gabung tercantum dalam batch # 2 ).

  • Menurut komentar yang menguraikan trie_find(), itu seharusnya hanya memanggil fungsi kecocokan yang diberikan 'fn' untuk awalan "/ -atau- \ 0 yang diakhiri dari kunci yang mengandung tiga nilai".
    Ini tidak benar: ada tiga tempat di mana trie_find () memanggil fungsi pencocokan, tetapi salah satunya tidak ada pemeriksaan untuk keberadaan nilai.

  • b9317d55a3 menambahkan dua kunci baru ke trie:

    • 'logs/refs/rewritten ', dan
    • ' logs/refs/worktree', di sebelah yang sudah ada ' logs/refs/bisect'.
      Ini menghasilkan triesimpul dengan jalur ' logs/refs/', yang tidak ada sebelumnya, dan yang tidak memiliki nilai terlampir.
      Kueri untuk ' logs/refs/' menemukan simpul ini dan kemudian mengenai satu situs panggilan matchfungsi yang tidak memeriksa keberadaan nilai, dan dengan demikian memanggil matchfungsi dengan NULLnilai.
  • Ketika matchfungsi check_common()dipanggil dengan NULLnilai, itu mengembalikan 0, yang menunjukkan bahwa jalur yang diminta bukan milik direktori umum, akhirnya menghasilkan jalur palsu yang ditunjukkan di atas.

Tambahkan kondisi yang hilang ke trie_find()sehingga tidak akan pernah memanggil fungsi pertandingan dengan nilai yang tidak ada.

check_common() maka tidak lagi harus memeriksa bahwa itu mendapat nilai non-NULL, jadi hapus kondisi itu.

Saya percaya bahwa tidak ada jalur lain yang dapat menyebabkan keluaran palsu yang serupa.

AFAICT satu-satunya kunci lain yang menghasilkan fungsi kecocokan yang dipanggil dengan NULLnilai adalah ' co' (karena tombol ' common' dan ' config').

Namun, karena mereka tidak berada dalam direktori yang termasuk dalam direktori umum, diharapkan dihasilkan jalur khusus pohon yang berfungsi.

VONC
sumber
3
Saya pikir ini harus menjadi jawaban teratas, --preserve-mergestidak benar-benar "mempertahankan" gabungan seperti yang Anda inginkan, itu sangat naif. Ini memungkinkan Anda untuk melestarikan gabungan komit & orang tua mereka melakukan hubungan sambil memberi Anda fleksibilitas rebase interaktif. Fitur baru ini luar biasa & jika bukan karena jawaban SO yang ditulis dengan baik ini, saya tidak akan tahu!
egucciar
@egucciar Terima kasih. Dan itu bukan satu-satunya fitur Git 2.18 ( stackoverflow.com/search?q=user%3A6309+%22git+2.18%22 ), dan Git 2.19 ( stackoverflow.com/search?q=user%3A6309+%22git+2.19% 22 )
VonC
1
Sangat membantu jika Anda mencoba untuk memindahkan bongkahan komit seperti di Q / A ini, stackoverflow.com/questions/45059039/…
okovko
1
Oh, itu benar-benar yang saya cari sejak beberapa saat! Saya punya solusi manual untuk kasus-kasus seperti itu di mana orang harus membuat komit fiktif bergabung dengan semua merger.
carnicer
Git yang khas. Berani Anda mengajukan pertanyaan sederhana dan Anda kemungkinan besar harus mempelajari sejarah Git, algoritma internal, semua detail implementasi yang berantakan ditambah Anda juga memerlukan jurusan teori grafik untuk memahami apa yang sedang terjadi.
Dimitris