Ayrıntılı olarak 'git birleştirme' nasıl çalışır?


95

'Git merge'nin arkasında kesin bir algoritma (veya buna yakın) bilmek istiyorum. En azından bu alt sorulara verilecek cevaplar yardımcı olacaktır:

  • Git, çakışmayan belirli bir değişikliğin içeriğini nasıl algılar?
  • Git tam olarak bu satırlarda bir çelişki olduğunu nasıl anlar?
  • Git hangi şeyleri otomatik olarak birleştirir?
  • Dalları birleştirmek için ortak bir temel olmadığında git nasıl çalışır?
  • Dalları birleştirmek için birden fazla ortak temel varken git nasıl bir performans gösterir?
  • Aynı anda birden fazla dalı birleştirdiğimde ne olur?
  • Birleştirme stratejileri arasındaki fark nedir?

Ancak bütün bir algoritmanın tanımı çok daha iyi olacaktır.


8
Sanırım tüm kitabı bu cevaplarla doldurabilirsin ...
Daniel Hilgarth

2
Ya da sadece "tüm algoritmayı açıklamak" kadar uzun süren kodu okuyabilirsin
Nevik Rehnel

3
@DanielHilgarth Bir yerlerde böyle bir kitap olup olmadığını öğrenmekten memnuniyet duyarım. Referanslara açığız.
abyss. 7

5
@NevikRehnel Evet yapabilirim. Ancak birileri bu kodun arkasındaki teoriyi zaten biliyorsa, çok daha kolay olabilir.
abyss. 7

1. "Çelişkili olmayan belirli bir değişikliğin bağlamı" nedir? 2. ve 3. noktalar aynı, ancak olumsuz, bu iki soruyu birleştirelim mi?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Yanıtlar:


65

3 yollu birleştirme algoritmasının açıklamasını aramanız en iyisi olabilir. Üst düzey bir açıklama şuna benzer:

  1. Uygun bir birleştirme tabanı bulun B- her iki yeni sürümün ( Xve Y) atası olan dosyanın bir sürümü ve genellikle bu türden en yeni taban (daha ileri gitmesi gereken durumlar olsa da, bunlardan biridir. gitvarsayılan recursivebirleştirmenin özellikleri )
  2. Arasında diffs gerçekleştirin Xile Bve Yile B.
  3. İki farkta tanımlanan değişiklik bloklarını inceleyin. Her iki taraf da aynı noktada aynı değişikliği yaparsa, ikisinden birini kabul edin; biri bir değişiklik getirir ve diğeri o bölgeyi yalnız bırakırsa, değişikliği finalde tanıtın; her ikisi de bir noktada değişiklik getiriyor ancak bunlar eşleşmiyorsa, manuel olarak çözülecek bir çatışmayı işaretleyin.

Tam algoritma bununla çok daha ayrıntılı olarak ilgilenir ve hatta bazı belgelere sahiptir ( biri için https://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txt , git help XXXsayfalarla birlikte) XXX biri olduğu merge-base, merge-file, merge, merge-one-fileve muhtemelen birkaç diğerleri). Bu yeterince derin değilse, her zaman kaynak kodu vardır ...


11

Dalları birleştirmek için birden fazla ortak temel varken git nasıl bir performans gösterir?

Bu makale çok yardımcı oldu: http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html (burada bölüm 2 ).

Özyinelemeli, ata olarak kullanılacak sanal bir dal oluşturmak için özyinelemeli diff3 kullanır.

Örneğin:

(A)----(B)----(C)-----(F)
        |      |       |
        |      |   +---+
        |      |   |
        |      +-------+
        |          |   |
        |      +---+   |
        |      |       |
        +-----(D)-----(E)

Sonra:

git checkout E
git merge F

En iyi 2 ortak ata vardır (diğerlerinin atası olmayan ortak atalar) Cve D. Git bunları yeni bir sanal dalda birleştirir Vve ardından Vtemel olarak kullanır .

(A)----(B)----(C)--------(F)
        |      |          |
        |      |      +---+
        |      |      |
        |      +----------+
        |      |      |   |
        |      +--(V) |   |
        |          |  |   |
        |      +---+  |   |
        |      |      |   |
        |      +------+   |
        |      |          |
        +-----(D)--------(E)

Sanırım Git, daha çok ortak atalar olsaydı, bir sonrakiyle birleşerek devam ederdi V.

Makale, sanal dal oluştururken bir birleştirme çakışması olursa Git'in çakışma işaretlerini bulundukları yerde bırakıp devam ettiğini söylüyor.

Aynı anda birden fazla dalı birleştirdiğimde ne olur?

@Nevik Rehnel'in açıkladığı gibi, stratejiye bağlı, man git-merge MERGE STRATEGIESbölümde iyi anlatılmış .

Sadece octopusve ours/ theirskerede birden fazla şubesi birleştirme desteği, recursiveörneğin yapmaz.

octopusÇatışmalar olursa birleşmeyi reddeder ve oursönemsiz bir birleşmedir, bu yüzden çatışma olmaz.

Bu komutlar yeni bir kayıt oluşturur, 2'den fazla üst öğeye sahip olacaktır.

merge -X octopusNasıl gittiğini görmek için Git 1.8.5'te çakışma olmadan bir tane yaptım .

Başlangıç ​​hali:

   +--B
   |
A--+--C
   |
   +--D

Aksiyon:

git checkout B
git merge -Xoctopus C D

Yeni durum:

   +--B--+
   |     |
A--+--C--+--E
   |     |
   +--D--+

Beklendiği gibi E3 ebeveyni var.

YAPILACAKLAR: ahtapotun tek bir dosya modifikasyonu üzerinde tam olarak nasıl çalıştığı. Yinelemeli ikiye iki 3 yollu birleştirme

Dalları birleştirmek için ortak bir temel olmadığında git nasıl çalışır?

@Torek, 2.9'dan beri birleştirmenin olmadan başarısız olduğunu belirtiyor --allow-unrelated-histories.

Git 1.8.5'te deneysel olarak denedim:

git init
printf 'a\nc\n' > a
git add .
git commit -m a

git checkout --orphan b
printf 'a\nb\nc\n' > a
git add .
git commit -m b
git merge master

a içerir:

a
<<<<<<< ours
b
=======
>>>>>>> theirs
c

Sonra:

git checkout --conflict=diff3 -- .

a içerir:

<<<<<<< ours
a
b
c
||||||| base
=======
a
c
>>>>>>> theirs

Yorumlama:

  • üs boş
  • baz boş olduğunda, herhangi bir değişikliği tek bir dosyada çözmek mümkün değildir; yalnızca yeni dosya ekleme gibi şeyler çözülebilir. Yukarıdaki çatışma, a\nc\ntek satırlı bir ekleme olarak tabanla 3 yollu bir birleştirmeyle çözülecektir.
  • Ben düşünüyorum bir taban dosyası olmadan bir 3 yollu birleştirme sadece bir fark olduğu, bir 2 yönlü birleştirme denir

1
Bu soruya yeni bir SO bağlantısı var, bu yüzden bu cevabı taradım (ki bu oldukça iyi) ve son Git değişikliğinin son bölümü biraz geçerliliğini yitirdiğini fark ettim. Git sürüm 2.9'dan (commit e379fdf34fee96cd205be83ff4e71699bdc32b18) bu yana Git , siz eklemedikçe bir birleştirme tabanı yoksa artık birleştirmeyi reddediyor --allow-unrelated-histories.
torek

1
İşte @Ciro'nun gönderdiği bir sonraki makale: blog.plasticscm.com/2012/01/…
adam0101

Davranış son denememden bu yana değişmedikçe: --allow-unrelated-historiesbirleştirmekte olduğunuz dallar arasında ortak dosya yolları yoksa atlanabilir.
Jeremy Liste

Küçük düzeltme: oursBirleştirme stratejisi var, ancak theirsbirleştirme stratejisi yok . recursive+ theirsstrateji yalnızca iki dalı çözebilir. git-scm.com/docs/git-merge#_merge_strategies
nekketsuuu

9

Ben de ilgileniyorum. Cevabı bilmiyorum ama ...

Çalışan karmaşık bir sistemin her zaman çalışan basit bir sistemden evrimleştiği görülmüştür.

Bence git'in birleşmesi oldukça karmaşık ve anlaşılması çok zor - ancak buna yaklaşmanın bir yolu öncülerinden ve endişenizin özüne odaklanmaktır. Yani, ortak bir atası olmayan iki dosya verildiğinde, git merge bunları nasıl birleştireceğini ve çatışmaların nerede olduğunu nasıl çözer?

Bazı öncüler bulmaya çalışalım. Kimden git help merge-file:

git merge-file is designed to be a minimal clone of RCS merge; that is,
       it implements all of RCS merge's functionality which is needed by
       git(1).

Wikipedia'dan: http://en.wikipedia.org/wiki/Git_%28software%29 -> http://en.wikipedia.org/wiki/Three-way_merge#Three-way_merge -> http: //en.wikipedia .org / wiki / Diff3 -> http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf

Bu son bağlantı, diff3algoritmayı ayrıntılı olarak açıklayan bir makalenin pdfidir . İşte bir google pdf görüntüleyici sürümü . Sadece 12 sayfa uzunluğunda ve algoritma sadece birkaç sayfadan ibaret - ancak tam bir matematiksel işlem. Bu biraz fazla resmi görünebilir, ancak git'in birleştirmesini anlamak istiyorsanız, önce daha basit sürümü anlamanız gerekir. Henüz kontrol etmedim, ancak gibi bir adla diff3muhtemelen diff ( en uzun ortak alt sıra algoritmasını kullanan) anlamanız gerekecek . Bununla birlikte, diff3bir google'ınız varsa , orada daha sezgisel bir açıklama olabilir ...


Şimdi, diff3ve karşılaştırarak bir deney yaptım git merge-file. Bunlar aynı üç girdi dosyaları almak sürüm1 oldversion sürüm2 ile ve işareti çatışmaları yolu aynı <<<<<<< version1, =======, >>>>>>> version2( diff3aynı zamanda sahip ||||||| oldversion), ortak mirasını gösteren.

Ben boş bir dosya kullanılan OldVersion ve için yakın özdeş dosyalar tipinde1 ve sürüm2 sadece bir fazladan satır ilave ile Version2 .

Sonuç: git merge-filetek değiştirilen satırı çakışma olarak belirledi; ancak diff3iki dosyayı da bir çatışma olarak değerlendirdi. Bu nedenle, diff3 kadar karmaşık olan git'in birleştirme işlemi, bu en basit durumlar için bile daha karmaşıktır.

İşte gerçek sonuçlar (metin için @ twalberg'in cevabını kullandım). Gerekli seçeneklere dikkat edin (ilgili yönetim sayfalarına bakın).

$ git merge-file -p fun1.txt fun0.txt fun2.txt

You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
<<<<<<< fun1.txt
=======
THIS IS A BIT DIFFERENT
>>>>>>> fun2.txt

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...

$ diff3 -m fun1.txt fun0.txt fun2.txt

<<<<<<< fun1.txt
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
||||||| fun0.txt
=======
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
THIS IS A BIT DIFFERENT

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
>>>>>>> fun2.txt

Bununla gerçekten ilgileniyorsanız, biraz tavşan deliği. Bana göre, normal ifadeler kadar derin , diff'in en uzun ortak altdizisi algoritması, bağlamdan bağımsız gramerler veya ilişkisel cebir gibi görünüyor. Altına inmek istiyorsan, bence yapabilirsin, ama biraz kararlı çalışma gerektirecek.



0

Git, çakışmayan belirli bir değişikliğin içeriğini nasıl algılar?
Git tam olarak bu satırlarda bir çelişki olduğunu nasıl anlar?

Birleştirmenin her iki tarafında da aynı satır değiştiyse, bu bir çelişkidir; yoksa, bir taraftan (varsa) değişiklik kabul edilir.

Git hangi şeyleri otomatik olarak birleştirir?

Çelişmeyen değişiklikler (yukarıya bakın)

Dalları birleştirmek için birden fazla ortak temel varken git nasıl bir performans gösterir?

Git birleştirme tabanının tanımına göre , yalnızca bir tane vardır (en son ortak ata).

Aynı anda birden fazla dalı birleştirdiğimde ne olur?

Bu, birleştirme stratejisine bağlıdır (yalnızca octopusve ours/ theirsstratejileri ikiden fazla şubenin birleştirilmesini destekler).

Birleştirme stratejileri arasındaki fark nedir?

Bu git mergekılavuz sayfasında açıklanmıştır .


2
'Aynı çizgi' ne anlama geliyor? Diğer ikisi arasına boş olmayan yeni bir satır ekler ve birleştirirsem - hangi satırlar aynıdır? Bir daldaki bazı satırları silersem, hangileri başka bir dalda 'aynı' olur?
abyss. 7

1
Metinde yanıtlamak biraz zor. Git, iki dosya (veya bir dosyanın iki revizyonu) arasındaki farkı ifade etmek için [diffs] (en.wikipedia.org/wiki/Diff) kullanır. İçeriği karşılaştırarak (varsayılan olarak, üç satır) satırların eklenip eklenmediğini algılayabilir. "Aynı satır", eklemeler ve silmeler göz önünde bulundurularak bağlamla ifade edilir.
Nevik Rehnel

1
"Aynı çizgi" değişikliğinin bir çatışmayı göstereceğini öne sürüyorsunuz. Otomatikleştirme motoru gerçekten hat tabanlı mı? Yoksa hunk temelli mi? Sadece bir ortak ata var mı? Öyleyse neden git-merge-recursivevar?
Edward Thomson

1
@EdwardThomson: Evet, çözünürlük satır tabanlıdır (yalnızca bir satır kalana kadar hunks küçük parçalara bölünebilir). Varsayılan birleştirme stratejisi, referans olarak en son ortak atayı kullanır, ancak başka bir şey kullanmak istiyorsanız başkaları da vardır. Ve ne git-merge-recursiveolması gerektiğini bilmiyorum (man sayfası yok ve google hiçbir şey vermiyor). Bununla ilgili daha fazla bilgi git mergeve git merge-baseman sayfalarında bulunabilir.
Nevik Rehnel

1
git-mergeMan ve git-merge-basesen işaret birden ortak ataları ve özyinelemeli birleştirme tartışmak o adam sayfaları. Böyle bir tartışma olmadan cevabınızın eksik olduğunu hissediyorum.
Edward Thomson
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.