Liệu yêu cầu kéo có phá vỡ thuật toán hợp nhất của git không?


14

Tôi hiện đang làm việc cho một công ty sử dụng VSTS để quản lý mã git. Cách "hợp nhất" của Microsoft để hợp nhất một chi nhánh là thực hiện "hợp nhất squash", nghĩa là tất cả các cam kết cho chi nhánh đó sẽ bị ép thành một cam kết mới kết hợp tất cả các thay đổi.

Vấn đề là, điều gì sẽ xảy ra nếu tôi thực hiện một số thay đổi trong một chi nhánh cho một mục tồn đọng, sau đó ngay lập tức muốn bắt đầu thực hiện các thay đổi trong một chi nhánh khác cho một mục tồn đọng khác và những thay đổi đó phụ thuộc vào bộ thay đổi của chi nhánh đầu tiên?

Tôi có thể tạo một nhánh cho mục tồn đọng đó và đặt nó trên nhánh đầu tiên. Càng xa càng tốt. Tuy nhiên, khi đến lúc tạo một yêu cầu kéo cho nhánh thứ hai của tôi, nhánh đầu tiên đã được hợp nhất thành chủ và vì nó được thực hiện dưới dạng hợp nhất squash, git lên một loạt các xung đột. Điều này là do git không thấy các cam kết ban đầu mà nhánh thứ hai được dựa trên, nó chỉ nhìn thấy một hợp nhất bí đao lớn và vì vậy để hợp nhất nhánh thứ hai để làm chủ, nó cố gắng phát lại tất cả các cam kết của nhánh thứ nhất trong đầu hợp nhất bí đao, gây ra nhiều xung đột.

Vì vậy, câu hỏi của tôi là, có cách nào để khắc phục điều này (ngoài việc không bao giờ dựa vào một nhánh tính năng khác, điều này làm hạn chế quy trình làm việc của tôi) hoặc hợp nhất squash chỉ phá vỡ thuật toán hợp nhất của git?

Câu trả lời:


15

Với Git, cam kết

  • là bất biến,
  • và tạo thành một biểu đồ chu kỳ có hướng.

Squashing không kết hợp cam kết. Thay vào đó, nó ghi lại một cam kết mới với những thay đổi từ nhiều cam kết khác. Rebasing là tương tự, nhưng không kết hợp cam kết. Ghi lại một cam kết mới với các thay đổi tương tự như một cam kết hiện có được gọi là ghi lại lịch sử . Nhưng vì các cam kết hiện tại là bất biến, nên điều này được hiểu là khi viết một lịch sử thay thế.

Hợp nhất cố gắng kết hợp các thay đổi của lịch sử (các nhánh) của hai cam kết bắt đầu từ một cam kết tổ tiên chung.

Vì vậy, hãy nhìn vào lịch sử của bạn:

                                 F  feature2
                                /
               1---2---3---4---5    feature1 (old)
              /
-o---o---o---A---o---o---S          master

A là tổ tiên chung, 1 nhánh5 nhánh tính năng ban đầu, F nhánh tính năng mới và S cam kết bị nén có chứa các thay đổi tương tự như 1 trận5. Như bạn có thể thấy, tổ tiên chung của F và S là A. Theo như git, không có mối quan hệ nào giữa S và 1 Quay5. Vì vậy, việc hợp nhất chủ với S ở một bên và tính năng 2 với 1 bên5 sẽ xung đột. Giải quyết những xung đột này không khó, nhưng đó là công việc không cần thiết, tẻ nhạt.

Do những hạn chế này, có hai cách tiếp cận để xử lý sáp nhập / ép:

  • Hoặc bạn sử dụng viết lại lịch sử, trong trường hợp đó bạn sẽ nhận được nhiều cam kết đại diện cho cùng một thay đổi. Sau đó bạn sẽ rebase nhánh tính năng thứ hai vào đè bẹp cam kết:

                                     F  feature2 (old)
                                    /
                   1---2---3---4---5    feature1 (old)
                  /
    -o---o---o---A---o---o---S          master
                              \
                               F'       feature2
    
  • Hoặc bạn không sử dụng viết lại lịch sử, trong trường hợp đó bạn có thể nhận thêm các cam kết hợp nhất:

                                     F  feature2
                                    /
                   1---2---3---4---5    feature1 (old)
                  /                 \
    -o---o---o---A---o---o-----------M  master
    

    Khi tính năng 2 và chủ được hợp nhất, tổ tiên chung sẽ được cam kết 5.

Trong cả hai trường hợp, bạn sẽ có một số nỗ lực hợp nhất. Nỗ lực này không phụ thuộc rất nhiều vào chiến lược nào trong hai chiến lược trên mà bạn chọn. Nhưng hãy chắc chắn rằng

  • các nhánh tồn tại trong thời gian ngắn, để hạn chế khoảng cách chúng có thể trôi dạt từ nhánh chính và điều đó
  • bạn thường xuyên hợp nhất chủ vào nhánh tính năng của mình hoặc khởi động lại nhánh tính năng trên chủ để giữ cho các nhánh được đồng bộ hóa.

Khi làm việc trong một nhóm, rất hữu ích để phối hợp những người hiện đang làm việc về những gì. Điều này giúp duy trì số lượng tính năng đang được phát triển nhỏ và có thể giảm số lượng xung đột hợp nhất.


2
Câu trả lời của bạn dường như không giải quyết được điều gì xảy ra nếu bạn lần đầu tiên hợp nhất feature1vào chủ, sau đó muốn hợp nhất feature2sau. Trong trường hợp đó, cách tiếp cận đầu tiên sẽ dẫn đến xung đột khi git cố gắng áp dụng lại các feature1cam kết trên đỉnh của cam kết bị đè bẹp, trong khi cách thứ hai sẽ cho phép git xác định rằng các cam kết đó không cần phải được hợp nhất?
Jez

@Jez Đó chính xác là những gì xảy ra khi bạn đè bẹp PR. Gần đây tôi đã phải viết lại một cách thủ công PR trên một dự án OSS (bằng cách git clonenhập repo và sao chép các tập tin đã thay đổi của tôi!) Vì tôi đã phân nhánh từ một nhánh và sau đó người bảo trì đã đè bẹp nhánh đầu tiên. Trong công việc của tôi, họ cũng làm sáp nhập. Điều này có nghĩa là tôi không thể làm việc trên một tính năng bphụ thuộc vào tính năng acho đến khi tính năng ađược hợp nhất.
Vứt bỏ tài khoản

1
Và đó có phải là một sự phá vỡ thực sự khó chịu của một cái gì đó sẽ hoạt động, như git được thiết kế để? Hãy xem, tôi thấy các tổ chức khác nhau như Microsoft và Github thực sự khuyến nghị những sự hợp nhất bí đao này và họ dường như bị câm.
Jez

1
@Jez Trong kịch bản ban đầu, có, bạn sẽ gặp xung đột khi hợp nhất tính năng 2 thành chủ vì việc hợp nhất các cam kết 1 sẽ5 sẽ xung đột với các thay đổi tương tự trong S. Giải pháp là loại bỏ tính năng 2 (giải pháp 1) hoặc không sử dụng squash / rebasing tại tất cả (giải pháp 2).
amon

Việc sáp nhập squash có phù hợp với bạn hay không phụ thuộc vào những gì bạn muốn ghi lại trong lịch sử kiểm soát phiên bản. Nếu các nhánh tính năng có nhiều cam kết WIP, thì squash sẽ đặt một cam kết lớn duy nhất với tính năng hoàn chỉnh trên nhánh chính. Nếu bạn muốn duy trì tất cả các cam kết trung gian của nhánh tính năng, sau đó sử dụng việc khởi động lại hoặc hợp nhất.
amon

11

Hợp nhất squash phá vỡ thuật toán hợp nhất cho bất kỳ nhánh nào có chứa bất kỳ cam kết nào đã bị xóa bởi squash. Nói cách khác, cuộc nổi loạn là virus. Nếu bạn rebase một nhánh, bạn cần rebase bất kỳ nhánh nào khác phụ thuộc vào nhánh đó. Nếu bạn sử dụng rerere , mọi xung đột hợp nhất bạn đã giải quyết thủ công trong repo cục bộ của bạn sẽ không cần phải giải quyết lại một cách thủ công, nhưng điều đó không giúp giải quyết xung đột bởi những người khác.

Đó là lý do tại sao quy tắc bất thành văn của chúng tôi ở đây là ổn khi miễn là không ai khác phụ thuộc vào nhánh tính năng của bạn, đó là trường hợp có thể 90% thời gian. Nếu không, một sự hợp nhất thẳng giúp mọi người tránh các vấn đề.


Một cách để có cả một cam kết bị đè bẹp trong lịch sử tổng thể và một nhánh tính năng còn nguyên vẹn để tạo ra một nhánh chỉ riêng cho bóng quần. Giả sử bạn có một feature-xyzchi nhánh. Bạn có thể tạo một feature-xyz-squashednhánh bắt đầu tại cùng một cam kết với feature-xyznhánh đó, git cherry-pickcác cam kết từ feature-xyzđến feature-xyz-squashed, đè bẹp chúng ở đó và hợp nhất feature-xyz-squashedthành master. Bạn không nên hợp nhất feature-xyzsau đó. Đôi khi những điều trên có ý nghĩa (ví dụ: bạn không muốn bao gồm các cam kết với mật khẩu được nhập vào), nhưng đó là một cách giải quyết, hầu như không phải là cách thực hành tốt nhất.
9000
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.