'git merge' มีรายละเอียดอย่างไร


95

ฉันต้องการทราบอัลกอริทึมที่แน่นอน (หรือใกล้เคียง) ที่อยู่เบื้องหลัง 'git merge' อย่างน้อยคำตอบสำหรับคำถามย่อยเหล่านี้จะเป็นประโยชน์:

  • git ตรวจจับบริบทของการเปลี่ยนแปลงที่ไม่ขัดแย้งโดยเฉพาะได้อย่างไร
  • คอมไพล์พบได้อย่างไรว่ามีความขัดแย้งในบรรทัดที่แน่นอนเหล่านี้?
  • คอมไพล์อัตโนมัติผสานสิ่งใดบ้าง
  • git ทำงานอย่างไรเมื่อไม่มีฐานร่วมสำหรับการรวมสาขา
  • git ทำงานอย่างไรเมื่อมีหลายฐานสำหรับการรวมสาขา
  • จะเกิดอะไรขึ้นเมื่อฉันรวมหลายสาขาพร้อมกัน
  • อะไรคือความแตกต่างระหว่างกลยุทธ์การผสาน?

แต่คำอธิบายของอัลกอริทึมทั้งหมดจะดีกว่ามาก


8
ฉันเดาว่าคุณสามารถตอบทั้งเล่มได้ด้วยคำตอบเหล่านี้ ...
Daniel Hilgarth

2
หรือคุณอาจจะไปอ่านโค้ดก็ได้ซึ่งจะใช้เวลานานพอ ๆ กับ "อธิบายอัลกอริทึมทั้งหมด"
Nevik Rehnel

3
@DanielHilgarth ฉันดีใจที่ได้พบว่ามีหนังสือเล่มนี้อยู่ที่ไหนสักแห่ง ยินดีต้อนรับการอ้างอิง
เหวอ 7

5
@NevikRehnel ใช่ฉันทำได้ แต่จะง่ายกว่ามากถ้ามีใครรู้ทฤษฎีที่อยู่เบื้องหลังรหัสนี้อยู่แล้ว
เหว 7

1. อะไรคือ "บริบทของการเปลี่ยนแปลงที่ไม่ขัดแย้งโดยเฉพาะ"? คะแนน 2. และ 3. เหมือนกัน แต่ถูกลบลองรวมสองคำถามนี้ไหม
Ciro Santilli 郝海东冠状病六四事件法轮功

คำตอบ:


65

คุณอาจต้องมองหาคำอธิบายของอัลกอริธึมการผสาน 3 ทาง คำอธิบายระดับสูงจะเป็นดังนี้:

  1. ค้นหาฐานการผสานที่เหมาะสมB- เวอร์ชันของไฟล์ที่เป็นบรรพบุรุษของทั้งสองเวอร์ชันใหม่ ( XและY) และโดยปกติจะเป็นฐานล่าสุดดังกล่าว (แม้ว่าจะมีบางกรณีที่จะต้องย้อนกลับไปมากกว่านี้ซึ่งเป็นหนึ่งใน คุณสมบัติของการผสานgitเริ่มต้นของs recursive)
  2. ดำเนินการ diffs ของXด้วยBและมีYB
  3. เดินผ่านบล็อกการเปลี่ยนแปลงที่ระบุในความแตกต่างทั้งสอง หากทั้งสองฝ่ายแนะนำการเปลี่ยนแปลงเดียวกันในจุดเดียวกันให้ยอมรับข้อใดข้อหนึ่ง หากมีคนแนะนำการเปลี่ยนแปลงและอีกคนออกจากภูมิภาคนั้นเพียงอย่างเดียวให้แนะนำการเปลี่ยนแปลงในขั้นสุดท้าย หากทั้งคู่แนะนำการเปลี่ยนแปลงตรงจุด แต่ไม่ตรงกันให้ทำเครื่องหมายข้อขัดแย้งที่จะแก้ไขด้วยตนเอง

อัลกอริทึมแบบเต็มเกี่ยวข้องกับสิ่งนี้ในรายละเอียดมากขึ้นและยังมีเอกสารประกอบ ( https://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txtสำหรับหนึ่งพร้อมกับgit help XXXหน้า ซึ่ง XXX เป็นหนึ่งmerge-base, merge-file, merge, merge-one-fileและอาจจะไม่กี่อื่น ๆ ) หากไม่ลึกพอก็มีซอร์สโค้ดเสมอ ...


11

git ทำงานอย่างไรเมื่อมีหลายฐานสำหรับการรวมสาขา

บทความนี้มีประโยชน์มาก: http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html (นี่คือตอนที่ 2 )

Recursive ใช้ diff3 แบบวนซ้ำเพื่อสร้างสาขาเสมือนซึ่งจะใช้เป็นบรรพบุรุษ

เช่น:

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

จากนั้น:

git checkout E
git merge F

มี 2 ที่ดีที่สุดบรรพบุรุษร่วมกันคือ (บรรพบุรุษทั่วไปที่ไม่ได้เป็นบรรพบุรุษของคนอื่น ๆ ) และC DGit รวมเข้าด้วยกันเป็นสาขาเสมือนใหม่Vจากนั้นใช้Vเป็นฐาน

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

ฉันคิดว่า Git จะดำเนินต่อไปหากมีบรรพบุรุษร่วมกันที่ดีที่สุดรวมVกับคนต่อไป

บทความกล่าวว่าหากมีความขัดแย้งในการผสานในขณะที่สร้างสาขาเสมือน Git เพียงแค่ทิ้งเครื่องหมายความขัดแย้งที่พวกเขาอยู่และดำเนินต่อไป

จะเกิดอะไรขึ้นเมื่อฉันรวมหลายสาขาพร้อมกัน

ตามที่ @Nevik Rehnel อธิบายมันขึ้นอยู่กับกลยุทธ์มันอธิบายได้ดีในman git-merge MERGE STRATEGIESส่วน

เท่านั้นoctopusและours/ theirsสนับสนุนการรวมหลายสาขาในครั้งเดียวrecursiveเช่นไม่

octopusปฏิเสธที่จะรวมเข้าด้วยกันหากจะมีความขัดแย้งและoursเป็นการรวมที่ไม่สำคัญดังนั้นจึงไม่มีข้อขัดแย้ง

คำสั่งเหล่านั้นสร้างคอมมิตใหม่จะมีผู้ปกครองมากกว่า 2 คน

ฉันทำหนึ่งmerge -X octopusใน Git 1.8.5 โดยไม่มีข้อขัดแย้งเพื่อดูว่ามันเป็นอย่างไร

สถานะเริ่มต้น:

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

หนังบู๊:

git checkout B
git merge -Xoctopus C D

สถานะใหม่:

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

ตามคาดEมีพ่อแม่ 3 คน

สิ่งที่ต้องทำ: ปลาหมึกยักษ์ทำงานอย่างไรในการแก้ไขไฟล์เดียว การผสาน 3 ทางแบบวนซ้ำสองต่อสอง?

git ทำงานอย่างไรเมื่อไม่มีฐานร่วมสำหรับการรวมสาขา

@Torek กล่าวว่าตั้งแต่ 2.9 --allow-unrelated-historiesผสานล้มเหลวโดยไม่ต้อง

ฉันลองใช้งานเชิงประจักษ์บน Git 1.8.5:

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 ประกอบด้วย:

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

จากนั้น:

git checkout --conflict=diff3 -- .

a ประกอบด้วย:

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

การตีความ:

  • ฐานว่างเปล่า
  • เมื่อฐานว่างเปล่าจะไม่สามารถแก้ไขการแก้ไขใด ๆ ในไฟล์เดียวได้ เฉพาะบางอย่างเช่นการเพิ่มไฟล์ใหม่เท่านั้นที่สามารถแก้ไขได้ ความขัดแย้งข้างต้นจะได้รับการแก้ไขด้วยการผสาน 3 ทางกับฐานa\nc\nเป็นการเพิ่มบรรทัดเดียว
  • ฉันคิดว่าการผสาน 3 ทางโดยไม่มีไฟล์ฐานเรียกว่าการผสาน 2 ทางซึ่งเป็นเพียงความแตกต่าง

1
มีลิงก์ SO ใหม่สำหรับคำถามนี้ดังนั้นฉันจึงอ่านคำตอบนี้ (ซึ่งค่อนข้างดี) และสังเกตเห็นว่าการเปลี่ยนแปลง Git ล่าสุดได้ล้าสมัยในส่วนสุดท้ายเล็กน้อย ตั้งแต่ Git เวอร์ชัน 2.9 (การกระทำe379fdf34fee96cd205be83ff4e71699bdc32b18) Git --allow-unrelated-historiesตอนนี้ปฏิเสธที่จะผสานถ้าไม่มีฐานผสานจนกว่าคุณจะเพิ่ม
torek

1
ต่อไปนี้เป็นบทความติดตามจาก @Ciro โพสต์: blog.plasticscm.com/2012/01/…
adam0101

เว้นแต่ว่าพฤติกรรมจะเปลี่ยนไปตั้งแต่ฉันพยายามครั้งล่าสุด: --allow-unrelated-historiesสามารถละเว้นได้หากไม่มีเส้นทางไฟล์ทั่วไประหว่างสาขาที่คุณกำลังรวมเข้าด้วยกัน
Jeremy List

การแก้ไขoursเล็กน้อย: มีกลยุทธ์การผสาน แต่ไม่มีtheirsกลยุทธ์การผสาน recursive+ theirsกลยุทธ์สามารถแก้ไขได้เพียงสองสาขา git-scm.com/docs/git-merge#_merge_strategies
nekketsuuu

9

ฉันก็สนใจเหมือนกัน ฉันไม่รู้คำตอบ แต่ ...

ระบบที่ซับซ้อนที่ใช้งานได้มักจะพบว่ามีวิวัฒนาการมาจากระบบง่ายๆที่ใช้งานได้

ฉันคิดว่าการรวมกันของ git มีความซับซ้อนสูงและจะเข้าใจยากมาก - แต่วิธีหนึ่งในการเข้าถึงสิ่งนี้คือจากสารตั้งต้นและมุ่งเน้นไปที่หัวใจของความกังวลของคุณ นั่นคือได้รับสองไฟล์ที่ไม่มีบรรพบุรุษร่วมกัน git merge จะหาวิธีผสานเข้าด้วยกันอย่างไรและความขัดแย้งอยู่ที่ใด

มาลองหาสารตั้งต้นกันบ้าง จาก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).

จากวิกิพีเดีย: 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

ลิงก์สุดท้ายนั้นเป็น pdf ของเอกสารที่อธิบายdiff3อัลกอริทึมโดยละเอียด นี่เป็นรุ่น Google รูปแบบไฟล์ PDF ของผู้ชม มีความยาวเพียง 12 หน้าและอัลกอริทึมมีเพียงไม่กี่หน้า - แต่เป็นการจัดการทางคณิตศาสตร์แบบเต็มรูปแบบ อาจดูเป็นทางการเกินไป แต่ถ้าคุณต้องการเข้าใจการผสานของ git คุณจะต้องเข้าใจเวอร์ชันที่ง่ายกว่าก่อน ฉันยังไม่ได้ตรวจสอบ แต่ด้วยชื่อที่ชอบdiff3คุณอาจต้องเข้าใจความแตกต่างด้วย (ซึ่งใช้อัลกอริทึมลำดับต่อมาที่ยาวที่สุด ) อย่างไรก็ตามอาจมีคำอธิบายที่เข้าใจง่ายกว่าdiff3นั้นหากคุณมี Google ...


ตอนนี้ผมก็ไม่ได้การทดลองเปรียบเทียบและdiff3 git merge-fileพวกเขาใช้เวลาเดียวกันแฟ้มใส่สามversion1 oldversion version2และเครื่องหมายความขัดแย้งทางเดียวกันกับ<<<<<<< version1, =======, >>>>>>> version2( diff3ยังมี||||||| oldversion) แสดงให้เห็นมรดกร่วมกันของพวกเขา

ผมใช้ไฟล์ที่ว่างเปล่าสำหรับoldversionและไฟล์ที่อยู่ใกล้เหมือนกันสำหรับversion1และversion2ที่มีเพียงหนึ่งสายพิเศษที่เพิ่มการversion2

ผลลัพธ์: git merge-fileระบุว่าบรรทัดเดียวที่เปลี่ยนแปลงเป็นความขัดแย้ง แต่diff3ถือว่าทั้งสองไฟล์เป็นข้อขัดแย้ง ดังนั้นความซับซ้อนเช่น diff3 คือการผสานของ git จึงซับซ้อนยิ่งขึ้นแม้ในกรณีที่ง่ายที่สุดนี้

นี่คือผลลัพธ์ที่แท้จริง (ฉันใช้คำตอบของ @ twalberg สำหรับข้อความ) สังเกตตัวเลือกที่จำเป็น (ดูการจัดการตามลำดับ)

$ 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

หากคุณสนใจเรื่องนี้อย่างแท้จริงมันเป็นโพรงกระต่าย สำหรับฉันแล้วดูเหมือนว่าจะลึกพอ ๆ กับนิพจน์ทั่วไปอัลกอริธึมลำดับต่อมาที่ยาวที่สุดของความแตกต่างไวยากรณ์อิสระตามบริบทหรือพีชคณิตเชิงสัมพันธ์ ถ้าคุณต้องการที่จะไปถึงจุดสูงสุดฉันคิดว่าคุณทำได้ แต่จะต้องใช้การศึกษาอย่างตั้งใจ


2

นี่คือการใช้งานดั้งเดิม

http://git.kaarsemaker.net/git/blob/857f26d2f41e16170e48076758d974820af685ff/git-merge-recursive.py

โดยพื้นฐานแล้วคุณจะสร้างรายการบรรพบุรุษร่วมกันสำหรับการคอมมิตสองรายการแล้วรวมซ้ำทั้งการส่งต่ออย่างรวดเร็วหรือการสร้างคอมมิตเสมือนที่ใช้เป็นพื้นฐานของการผสานสามทางบนไฟล์


ลิงค์ไม่ทำงาน
Chujun Song

0

git ตรวจจับบริบทของการเปลี่ยนแปลงที่ไม่ขัดแย้งโดยเฉพาะได้อย่างไร
คอมไพล์พบได้อย่างไรว่ามีความขัดแย้งในบรรทัดที่แน่นอนเหล่านี้?

หากบรรทัดเดียวกันมีการเปลี่ยนแปลงทั้งสองด้านของการผสานแสดงว่ามีความขัดแย้ง หากไม่เป็นเช่นนั้นจะยอมรับการเปลี่ยนแปลงจากด้านหนึ่ง (หากมีอยู่)

คอมไพล์อัตโนมัติผสานสิ่งใดบ้าง

การเปลี่ยนแปลงที่ไม่ขัดแย้งกัน (ดูด้านบน)

git ทำงานอย่างไรเมื่อมีหลายฐานสำหรับการรวมสาขา

ตามคำจำกัดความของGit merge-baseมีเพียงหนึ่งเดียวเท่านั้น (บรรพบุรุษร่วมล่าสุด)

จะเกิดอะไรขึ้นเมื่อฉันรวมหลายสาขาพร้อมกัน

ขึ้นอยู่กับกลยุทธ์การผสาน (เฉพาะกลยุทธ์octopusและours/ เท่านั้นที่theirsสนับสนุนการรวมสาขามากกว่าสองสาขา)

อะไรคือความแตกต่างระหว่างกลยุทธ์การผสาน?

นี่คือคำอธิบายในmanpagegit merge


2
'บรรทัดเดียวกัน' หมายถึงอะไร? ถ้าฉันแทรกบรรทัดใหม่ที่ไม่ว่างระหว่างอีกสองบรรทัดและผสานบรรทัดใดที่เหมือนกัน ถ้าฉันลบบางบรรทัดในสาขาหนึ่งบรรทัดใดที่ 'เหมือนกัน' ในอีกสาขา?
เหวอ 7

1
การตอบเป็นข้อความค่อนข้างยาก Git ใช้ [diffs] (en.wikipedia.org/wiki/Diff) เพื่อแสดงความแตกต่างระหว่างสองไฟล์ (หรือการแก้ไขไฟล์สองไฟล์) สามารถตรวจจับได้ว่ามีการเพิ่มหรือลบบรรทัดโดยการเปรียบเทียบบริบท (โดยค่าเริ่มต้นคือสามบรรทัด) "บรรทัดเดียวกัน" แล้วหมายถึงตามบริบทในขณะที่คำนึงถึงการเพิ่มเติมและการลบ
Nevik Rehnel

1
คุณแนะนำว่าการเปลี่ยน "บรรทัดเดียวกัน" จะบ่งบอกถึงความขัดแย้ง เครื่องยนต์ Automerge เป็นไปตามบรรทัดจริงหรือไม่? หรือมันเป็นก้อนใหญ่? มีบรรพบุรุษร่วมกันเพียงคนเดียวหรือไม่? ถ้าเป็นเช่นนั้นทำไมถึงgit-merge-recursiveมีอยู่?
Edward Thomson

1
@EdwardThomson: ใช่ความละเอียดเป็นแบบเส้น (hunks สามารถแบ่งย่อยเป็น hunks ขนาดเล็กได้จนกว่าจะเหลือเพียงบรรทัดเดียว) กลยุทธ์การผสานเริ่มต้นใช้บรรพบุรุษร่วมล่าสุดเป็นข้อมูลอ้างอิง แต่ยังมีอื่น ๆ อีกหากคุณต้องการใช้อย่างอื่น และฉันไม่รู้ว่าgit-merge-recursiveควรจะเป็นอย่างไร (ไม่มี man page และ google ไม่ให้อะไรเลย) ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้สามารถพบได้ในgit mergeและgit merge-baseหน้า man
Nevik Rehnel

1
git-mergeหน้าคนและgit-merge-baseหน้าคนที่คุณชี้หารือบรรพบุรุษร่วมหลายและผสาน recursive ฉันรู้สึกว่าคำตอบของคุณไม่สมบูรณ์หากไม่มีการอภิปราย
Edward Thomson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.