Chi tiết 'git merge' hoạt động như thế nào?


95

Tôi muốn biết một thuật toán chính xác (hoặc gần đó) đằng sau 'hợp nhất git'. Câu trả lời ít nhất cho những câu hỏi phụ này sẽ hữu ích:

  • Làm cách nào để git phát hiện bối cảnh của một thay đổi cụ thể không xung đột?
  • Làm thế nào git phát hiện ra rằng có sự xung đột trong những dòng chính xác này?
  • Những thứ nào git tự động kết hợp?
  • Làm thế nào để git hoạt động khi không có cơ sở chung để hợp nhất các nhánh?
  • Gt hoạt động như thế nào khi có nhiều cơ sở chung để hợp nhất các nhánh?
  • Điều gì xảy ra khi tôi hợp nhất nhiều nhánh cùng một lúc?
  • Sự khác biệt giữa các chiến lược hợp nhất là gì?

Nhưng mô tả của cả một thuật toán sẽ tốt hơn nhiều.


8
Tôi đoán bạn có thể điền vào một cuốn sách toàn bộ với những câu trả lời ...
Daniel Hilgarth

2
Hoặc bạn chỉ có thể đi và đọc mã, mà sẽ mất khoảng chừng "mô tả toàn bộ thuật toán"
Nevik Rehnel

3
@DanielHilgarth Tôi rất vui được biết nếu đã có cuốn sách như vậy ở đâu đó. Tài liệu tham khảo được hoan nghênh.
abyss. 7

5
@NevikRehnel Có, tôi có thể. Nhưng nó có thể dễ dàng hơn nhiều, nếu một số người đã biết lý thuyết đằng sau mã này.
abyss. 7

1. "Bối cảnh của một thay đổi cụ thể không xung đột" là gì? Điểm 2. và 3. giống nhau nhưng bị phủ định, hãy gộp hai câu hỏi đó lại?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Câu trả lời:


65

Tốt nhất bạn nên tìm kiếm mô tả về thuật toán hợp nhất 3 chiều. Mô tả cấp cao sẽ giống như sau:

  1. Tìm một cơ sở hợp nhất phù hợp B- một phiên bản của tệp là tổ tiên của cả hai phiên bản mới ( XY), và thường là cơ sở gần đây nhất như vậy (mặc dù có những trường hợp nó sẽ phải quay lại xa hơn, đó là một trong những các tính năng của hợp nhất gitmặc định của s recursive)
  2. Thực hiện sự khác biệt của Xvới BYvới B.
  3. Đi qua các khối thay đổi được xác định trong hai điểm khác biệt. Nếu cả hai bên đưa ra cùng một thay đổi tại cùng một điểm, hãy chấp nhận một trong hai; nếu một người giới thiệu một thay đổi và người kia rời khỏi khu vực đó một mình, hãy giới thiệu thay đổi trong trận chung kết; nếu cả hai đều đưa ra các thay đổi tại một vị trí, nhưng chúng không khớp, hãy đánh dấu xung đột để giải quyết theo cách thủ công.

Thuật toán đầy đủ giải quyết vấn đề này một cách chi tiết hơn và thậm chí còn có một số tài liệu ( https://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txt cho một thuật toán, cùng với các git help XXXtrang , nơi XXX là một trong những merge-base, merge-file, merge, merge-one-filevà có thể một vài người khác). Nếu điều đó không đủ sâu, luôn có mã nguồn ...


11

Gt hoạt động như thế nào khi có nhiều cơ sở chung để hợp nhất các nhánh?

Bài viết này rất hữu ích: http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html (đây là phần 2 ).

Đệ quy sử dụng đệ quy diff3 để tạo ra một nhánh ảo sẽ được sử dụng làm tổ tiên.

Ví dụ:

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

Sau đó:

git checkout E
git merge F

Có 2 tổ tiên chung tốt nhất (tổ tiên chung mà không phải tổ tiên của bất kỳ tổ tiên nào khác), CD. Git hợp nhất chúng thành một nhánh ảo mới Vvà sau đó sử dụng Vlàm cơ sở.

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

Tôi cho rằng Git sẽ chỉ tiếp tục với nếu có nhiều tổ tiên chung tốt nhất, hợp nhất Vvới tổ tiên tiếp theo.

Bài báo nói rằng nếu có xung đột hợp nhất trong khi tạo nhánh ảo Git chỉ để lại các điểm đánh dấu xung đột ở vị trí của chúng và tiếp tục.

Điều gì xảy ra khi tôi hợp nhất nhiều nhánh cùng một lúc?

Như @Nevik Rehnel đã giải thích, nó phụ thuộc vào chiến lược, nó được giải thích rõ ràng trong man git-merge MERGE STRATEGIESphần.

Chỉ octopusours/ theirshỗ trợ hợp nhất nhiều chi nhánh cùng một lúc, recursivechẳng hạn như không.

octopustừ chối hợp nhất nếu có xung đột và ourslà một hợp nhất tầm thường để không thể có xung đột.

Các lệnh đó tạo ra một cam kết mới sẽ có nhiều hơn 2 cha mẹ.

Tôi đã làm một điều merge -X octopustrên Git 1.8.5 mà không có xung đột để xem nó diễn ra như thế nào.

Trạng thái ban đầu:

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

Hoạt động:

git checkout B
git merge -Xoctopus C D

Trạng thái mới:

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

Đúng như dự đoán, Ecó 3 bố mẹ.

VIỆC CẦN LÀM: chính xác cách bạch tuộc hoạt động trên một sửa đổi tệp. Hợp nhất đệ quy hai nhân hai 3 chiều?

Làm thế nào để git hoạt động khi không có cơ sở chung để hợp nhất các nhánh?

@Torek đề cập rằng kể từ ngày 2.9, hợp nhất không thành công --allow-unrelated-histories.

Tôi đã thử thực nghiệm trên 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 chứa đựng:

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

Sau đó:

git checkout --conflict=diff3 -- .

a chứa đựng:

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

Diễn dịch:

  • cơ sở trống rỗng
  • khi cơ sở trống, không thể giải quyết bất kỳ sửa đổi nào trên một tệp duy nhất; chỉ những thứ như thêm tệp mới có thể được giải quyết. Xung đột ở trên sẽ được giải quyết trên hợp nhất 3 chiều với cơ sở a\nc\ndưới dạng bổ sung dòng đơn
  • Tôi nghĩ rằng hợp nhất 3 chiều không có tệp cơ sở được gọi là hợp nhất 2 chiều, chỉ là sự khác biệt

1
Có một liên kết SO mới cho câu hỏi này, vì vậy tôi đã quét qua câu trả lời này (khá tốt) và nhận thấy rằng một thay đổi Git gần đây đã lỗi thời phần cuối cùng một chút. Kể từ phiên bản Git 2.9 (cam kết e379fdf34fee96cd205be83ff4e71699bdc32b18), Git hiện từ chối hợp nhất nếu không có cơ sở hợp nhất trừ khi bạn thêm --allow-unrelated-histories.
torek

1
Đây là bài viết tiếp theo từ bài viết @Ciro đã đăng: blog.plasticscm.com/2012/01/…
adam0101

Trừ khi hành vi đã thay đổi kể từ lần cuối tôi thử: --allow-unrelated-historiescó thể được bỏ qua nếu không có đường dẫn tệp chung nào giữa các nhánh bạn đang hợp nhất.
Danh sách Jeremy

Điều chỉnh nhỏ: Có ourschiến lược hợp nhất, nhưng không có theirschiến lược hợp nhất. recursive+ theirschiến lược chỉ có thể giải quyết hai nhánh. git-scm.com/docs/git-merge#_merge_strategies
nekketsuuu

9

Tôi cũng quan tâm. Tôi không biết câu trả lời, nhưng ...

Một hệ thống phức tạp hoạt động luôn được phát hiện là đã phát triển từ một hệ thống đơn giản hoạt động

Tôi nghĩ việc hợp nhất git rất phức tạp và sẽ rất khó hiểu - nhưng một cách để tiếp cận điều này là từ các tiền thân của nó và tập trung vào trọng tâm mối quan tâm của bạn. Nghĩa là, với hai tệp không có chung tổ tiên, việc hợp nhất git sẽ làm cách nào để hợp nhất chúng và xung đột ở đâu?

Chúng ta hãy thử tìm một số tiền chất. Từ 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).

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

Liên kết cuối cùng đó là bản pdf của bài báo mô tả diff3chi tiết thuật toán. Đây là phiên bản google pdf-browser . Nó chỉ dài 12 trang và thuật toán chỉ dài một vài trang - nhưng là một phép toán học đầy đủ. Điều đó có vẻ hơi quá trang trọng, nhưng nếu bạn muốn hiểu hợp nhất của git, trước tiên bạn cần hiểu phiên bản đơn giản hơn. Tôi chưa kiểm tra, nhưng với một cái tên như vậy diff3, bạn có thể cũng cần hiểu khác biệt (sử dụng thuật toán dãy con chung dài nhất ). Tuy nhiên, có thể có một lời giải thích trực quan hơn diff3, nếu bạn có ...


Bây giờ, tôi vừa thực hiện một thử nghiệm so sánh diff3git merge-file. Họ đi cùng ba tập tin đầu vào version1 oldversion version2 và xung đột dấu cách giống nhau, với <<<<<<< version1, =======, >>>>>>> version2( diff3cũng có ||||||| oldversion), cho thấy di sản chung của họ.

Tôi đã sử dụng một tập tin rỗng cho oldversion , và các tập tin gần như giống hệt nhau cho version1version2 chỉ với một cú thêm dòng thêm vào version2 .

Kết quả: git merge-filexác định dòng thay đổi duy nhất là xung đột; nhưng diff3coi toàn bộ hai tệp là xung đột. Vì vậy, phức tạp như diff3, hợp nhất của git thậm chí còn phức tạp hơn, ngay cả đối với trường hợp đơn giản nhất này.

Đây là kết quả thực tế (tôi đã sử dụng câu trả lời của @ twalberg cho văn bản). Lưu ý các tùy chọn cần thiết (xem các trang tương ứng).

$ 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

Nếu bạn thực sự quan tâm đến điều này, đó là một lỗ thỏ. Đối với tôi, nó có vẻ sâu sắc như các biểu thức chính quy, thuật toán con chung dài nhất của diff, ngữ pháp tự do ngữ cảnh hoặc đại số quan hệ. Nếu bạn muốn đi đến tận cùng của nó, tôi nghĩ bạn có thể, nhưng sẽ cần một số nghiên cứu quyết tâm.



0

Làm cách nào để git phát hiện bối cảnh của một thay đổi cụ thể không xung đột?
Làm thế nào git phát hiện ra rằng có sự xung đột trong những dòng chính xác này?

Nếu cùng một dòng đã thay đổi ở cả hai phía của hợp nhất, đó là một xung đột; nếu họ không có, thay đổi từ một phía (nếu tồn tại) được chấp nhận.

Những thứ nào git tự động kết hợp?

Những thay đổi không xung đột (xem ở trên)

Gt hoạt động như thế nào khi có nhiều cơ sở chung để hợp nhất các nhánh?

Theo định nghĩa của một cơ sở hợp nhất Git , chỉ có một (tổ tiên chung mới nhất).

Điều gì xảy ra khi tôi hợp nhất nhiều nhánh cùng một lúc?

Điều đó phụ thuộc vào chiến lược hợp nhất (chỉ chiến lược octopusours/ theirshỗ trợ hợp nhất nhiều hơn hai nhánh).

Sự khác biệt giữa các chiến lược hợp nhất là gì?

Điều này được giải thích trong git mergetrang chủ .


2
'Dòng giống nhau' có nghĩa là gì? Nếu tôi chèn dòng không trống mới giữa hai dòng khác và hợp nhất - những dòng nào giống nhau? Nếu tôi xóa một số dòng trong một nhánh, thì những dòng nào giống nhau trong nhánh khác?
abyss. 7

1
Đó là một chút khó khăn để trả lời bằng văn bản. Git sử dụng [diffs] (en.wikipedia.org/wiki/Diff) để thể hiện sự khác biệt giữa hai tệp (hoặc hai bản sửa đổi của một tệp). Nó có thể phát hiện xem các dòng đã được thêm vào hoặc loại bỏ bằng cách so sánh ngữ cảnh (theo mặc định, ba dòng). Khi đó, "cùng một dòng" có nghĩa là theo ngữ cảnh, đồng thời lưu ý đến việc bổ sung và xóa.
Nevik Rehnel

1
Bạn đề nghị rằng thay đổi "cùng một dòng" sẽ cho thấy có xung đột. Động cơ tự động có thực sự dựa trên dòng không? Hay là nó dựa trên cơ thể? Có bao giờ chỉ có một tổ tiên chung không? Nếu vậy, tại sao lại git-merge-recursivetồn tại?
Edward Thomson

1
@EdwardThomson: Có, độ phân giải dựa trên dòng (các nhóm có thể được chia thành các nhóm nhỏ hơn cho đến khi chỉ còn lại một dòng). Chiến lược hợp nhất mặc định sử dụng tổ tiên chung mới nhất làm tham chiếu, nhưng có những chiến lược khác nếu bạn muốn sử dụng thứ khác. Và tôi không biết git-merge-recursivephải như thế nào (không có trang người đàn ông và google không thu được gì). Thông tin thêm về điều này có thể được tìm thấy trên các trang git mergegit merge-basengười đàn ông.
Nevik Rehnel

1
Các git-mergetrang man và các git-merge-basetrang người đàn ông mà bạn chỉ ra thảo luận nhiều tổ tiên chung và đệ quy merge. Tôi cảm thấy rằng câu trả lời của bạn là không đầy đủ nếu không có một cuộc thảo luận về điều đó.
Edward Thomson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.