Tại sao nhiều dự án lại thích ăn vặt git rebase trên hơn git hợp nhất?


68

Một trong những lợi thế của việc sử dụng DVCS là quy trình công việc chỉnh sửa-cam kết hợp nhất (trên chỉnh sửa-hợp nhất-cam kết thường được thi hành bởi CVCS). Cho phép mỗi thay đổi duy nhất được ghi lại trong kho lưu trữ độc lập với các phép hợp nhất đảm bảo DAG phản ánh chính xác phả hệ thực sự của dự án.

Tại sao nhiều trang web nói về việc muốn "tránh các cam kết hợp nhất"? Không hợp nhất trước khi cam kết hoặc khởi động lại sau hợp nhất làm cho việc cô lập hồi quy, hoàn nguyên các thay đổi trong quá khứ, v.v.

Điểm cần làm rõ: Hành vi mặc định cho DVCS tạo các cam kết hợp nhất. Tại sao nhiều nơi nói về mong muốn nhìn thấy một lịch sử phát triển tuyến tính che giấu các cam kết hợp nhất này?


18
Công cụ không phải lúc nào cũng đúng. Và khi họ hiểu sai, oh chàng trai, họ có làm sai không.
Oded

2
@Oded bạn đang đề cập đến các cam kết hợp nhất tự động (chẳng hạn như các cam kết được tạo bởi git pull)? Tôi có thể thấy lý do tại sao điều đó có thể có vấn đề, nhưng câu hỏi của tôi là về tất cả các cam kết hợp nhất.
Jace Browning

có thể trùng lặp các xung đột thường xuyên phức tạp có phải là dấu hiệu của vấn đề không? "Sáp nhập và nổi loạn sẽ gây ra những xung đột chính xác như nhau, đối với những xung đột vốn có mà một con người phải giải quyết (tức là hai nhà phát triển thay đổi cùng một dòng mã) ..."
gnat

Một suy nghĩ mà tôi có sau khi đăng câu trả lời của mình, rằng IMO không hoàn toàn phù hợp như một câu trả lời: git pull --rebase == svn up(lỏng lẻo, không nghiêm ngặt bằng)
Izkata

trong một cửa hàng tập trung như SourceSafe, các thay đổi sẽ tiếp tục được kiểm tra trong một nỗ lực đang diễn ra, nhưng một khi đạt đến một cao nguyên ổn định, chúng tôi sẽ Dán nhãn tất cả các tệp vào một ngày nhất định. Sau đó, ít nhất bạn có thể tương quan các lỗi với trước hoặc sau đó và một phiên bản như vậy, hoặc rebase -line.
Andyz Smith

Câu trả lời:


64

Mọi người muốn tránh các cam kết hợp nhất vì nó làm cho nhật ký đẹp hơn. Nghiêm túc. Có vẻ như các bản ghi tập trung mà họ đã lớn lên và tại địa phương họ có thể thực hiện tất cả sự phát triển của mình trong một nhánh duy nhất. Không có lợi ích nào ngoài tính thẩm mỹ đó, và một số nhược điểm ngoài những điều bạn đã đề cập, như khiến nó dễ bị xung đột kéo trực tiếp từ đồng nghiệp mà không thông qua máy chủ "trung tâm".


5
Bạn là người duy nhất đã hiểu chính xác câu hỏi của tôi. Làm thế nào tôi có thể làm cho nó rõ ràng hơn?
Jace Browning

12
Có thể viết lại thành "lợi ích của lịch sử phát triển tuyến tính là gì?"
Karl Bielefeldt

11
"Thông thường, bạn sẽ làm điều này để đảm bảo các cam kết của bạn được áp dụng sạch trên một chi nhánh ở xa - có thể trong một dự án mà bạn đang cố gắng đóng góp nhưng bạn không duy trì. Trong trường hợp này, bạn sẽ làm việc của mình trong một chi nhánh và sau đó khởi động lại công việc của bạn thành nguồn gốc / chủ khi bạn sẵn sàng gửi các bản vá của mình cho dự án chính. Bằng cách đó, người bảo trì không phải thực hiện bất kỳ công việc tích hợp nào - chỉ cần áp dụng nhanh hoặc đơn giản. " git-scm.com/book/en/Git-Branching-Rebasing
Andyz Smith

12
Bạn làm cho nó nghe có vẻ như chỉ là mỹ phẩm, nhưng nó thực sự thiết thực: dễ hiểu hơn những gì đang diễn ra trong lịch sử tuyến tính hơn là từ một DAG phức tạp với các đường chéo và hợp nhất.
Daniel Hershcovich

20
"Không có lợi ích nào ngoài những thẩm mỹ đó" - Tôi không biết về điều đó. Thật khó để hiểu những gì đang diễn ra trong lịch sử của bạn khi lịch sử của bạn trông giống như một bảng mạch. (Giả sử nhiều thành viên trong nhóm, mỗi thành viên có chi nhánh và quy trình làm việc riêng.)
Archagon 29/11/13

46

Trong hai từ: git bisect

Lịch sử tuyến tính cho phép bạn xác định chính xác nguồn gốc của lỗi.


Một ví dụ. Đây là mã ban đầu của chúng tôi:

def bar(arg=None):
    pass

def foo():
    bar()

Chi nhánh 1 thực hiện một số cấu trúc lại argkhông còn hiệu lực:

def bar():
    pass

def foo():
    bar()

Chi nhánh 2 có một tính năng mới cần sử dụng arg:

def bar(arg=None):
    pass

def foo():
    bar(arg=1)

Sẽ không có xung đột hợp nhất, tuy nhiên một lỗi đã được đưa ra. May mắn thay, điều đặc biệt này sẽ được bắt gặp trong bước biên dịch, nhưng không phải lúc nào chúng ta cũng may mắn như vậy. Nếu lỗi biểu hiện là hành vi không mong muốn, thay vì lỗi biên dịch, chúng tôi có thể không tìm thấy lỗi trong một hoặc hai tuần. Tại thời điểm đó, git bisectđể giải cứu!

Chết tiệt. Đây là những gì nó thấy:

(master: green)
|             \_________________________
|                \                      \
(master: green)  (branch 1: green)     (branch 2: green)
|                 |                     |
|                 |                     |
(master/merge commit: green)            |
|                         ______________/
|                        /
(master/merge commit: red)
|
...days pass...
|
(master: red)

Vì vậy, khi chúng tôi gửi đi git bisectđể tìm cam kết phá vỡ bản dựng, nó sẽ xác định chính xác một cam kết hợp nhất. Chà, điều đó có ích một chút, nhưng về cơ bản, nó chỉ vào một gói cam kết chứ không phải một gói. Tất cả tổ tiên đều có màu xanh. Mặt khác, với việc nổi loạn, bạn có được một lịch sử tuyến tính:

(master: green)
|
(master: green)
|
(all branch 1 commits: green)
|
(some branch 2 commits: green)
|
(branch 2 commit: red)
|
(remaining branch 2 commits: red)
|
...days pass...
|
(master: still red)

Bây giờ, git bisectsẽ chỉ vào cam kết tính năng chính xác đã phá vỡ bản dựng. Lý tưởng nhất, thông báo cam kết sẽ giải thích những gì đã được dự định đủ tốt để thực hiện một công cụ tái cấu trúc khác và sửa lỗi ngay lập tức.

Hiệu ứng chỉ được gộp trong các dự án lớn khi các nhà bảo trì không viết tất cả mã, vì vậy họ không nhất thiết phải nhớ tại sao bất kỳ cam kết cụ thể nào được thực hiện / mỗi chi nhánh được thực hiện. Vì vậy, xác định chính xác cam kết (và sau đó có thể kiểm tra các cam kết xung quanh nó) là một trợ giúp tuyệt vời.


Điều đó nói rằng, tôi (hiện tại) vẫn thích hợp nhất. Việc khởi động lại vào một nhánh phát hành sẽ cung cấp cho bạn lịch sử tuyến tính của bạn để sử dụng git bisect, trong khi vẫn giữ lại lịch sử thực sự cho công việc hàng ngày.


2
Cảm ơn lời giải thích này. Bạn có thể mở rộng lý do tại sao bạn vẫn thích hợp nhất? Tôi biết rất nhiều người thích hợp nhất để nổi loạn trong hầu hết các tình huống, nhưng tôi chưa bao giờ có thể hiểu tại sao.
Philip

@Philip Tôi hơi do dự khi làm như vậy, vì tôi chỉ sử dụng git trong một vài tháng cho các dự án cá nhân nhỏ, không bao giờ trong nhóm. Nhưng khi theo dõi những gì tôi đã làm gần đây, việc có thể thấy dòng cam kết giữa các chi nhánh gitk --alllà vô cùng hữu ích (đặc biệt là vì tôi thường có các chi nhánh 3 giờ vào bất kỳ thời điểm nào và chuyển đổi giữa chúng mỗi ngày tùy thuộc vào tâm trạng của tôi thời gian).
Izkata

16
Nhưng sự hợp nhất là vấn đề, do đó bạn muốn có một cam kết hợp nhất bisect. Thật dễ dàng để tìm thấy các cam kết riêng lẻ với một blamehoặc khác bisectnếu bạn muốn tìm hiểu sâu hơn. Với một lịch sử tuyến tính, việc hợp nhất được thực hiện ngoài cuốn sách, do đó bạn không còn có thể nói một sự hợp nhất xấu là vấn đề. Nó trông giống như một thằng ngốc sử dụng một đối số không tồn tại, đặc biệt là nếu đối số đã bị xóa một số lần xác nhận trước đó. Cố gắng tìm ra lý do tại sao anh ta sẽ làm điều đó khó khăn hơn nhiều khi bạn phá hủy lịch sử.
Karl Bielefeldt

1
Thực sự không đồng ý với câu trả lời này. Đôi khi, phá hủy phá hủy tiện ích của bisect. Ngoại trừ các cam kết của một rebase, các cam kết từ một rebase có thể không thể chạy được. Ví dụ: Bạn đã phân nhánh tại A và tạo B, C, D, ai đó đã đẩy E. Bạn là B và C và làm việc nhưng bị từ chối trên E, bạn là B và C không thể truy cập được hoặc không thể chạy được và không hoạt động. Trường hợp tốt nhất là bạn từ bỏ, trường hợp xấu nhất bạn nghĩ B hoặc C có vấn đề khi thực tế đó là D.
Lan

4
@Izkata Tại sao bạn sử dụng bisect? Bởi vì lỗi / sai lầm xảy ra. Đó là một thực tế khiêm tốn. Có thể "kéo chuột sang trái" không phải là một phần của danh sách kiểm tra tự động / thủ công trước đây và bây giờ chúng tôi nhận thấy tính năng đó bị hỏng nhưng chúng tôi biết nó đã từng hoạt động.
Lan

17

Nói tóm lại, vì sáp nhập thường là một nơi khác để xảy ra sự cố và nó chỉ cần sai một lần để khiến mọi người rất sợ phải đối phó với nó một lần nữa (nếu bạn bị cắn hai lần ngại ngùng, nếu bạn muốn).

Vì vậy, giả sử chúng tôi đang làm việc trên Màn hình quản lý tài khoản mới và hóa ra có một lỗi được phát hiện trong quy trình làm việc của Tài khoản mới. OK, chúng tôi có hai đường dẫn riêng biệt - bạn hoàn thành Quản lý tài khoản và tôi sửa lỗi với Tài khoản mới. Vì cả hai chúng tôi đều đang xử lý các tài khoản, chúng tôi đã làm việc với mã rất giống nhau - có lẽ chúng tôi thậm chí phải điều chỉnh cùng một đoạn mã.

Bây giờ, tại thời điểm này, chúng tôi có hai phiên bản phần mềm khác nhau nhưng hoạt động đầy đủ. Cả hai chúng tôi đều thực hiện một cam kết về các thay đổi của mình, chúng tôi đều đã kiểm tra mã của mình một cách cẩn thận và độc lập, chúng tôi rất tự tin rằng chúng tôi đã thực hiện một công việc tuyệt vời. Giờ thì sao?

Chà, đã đến lúc hợp nhất, nhưng ... tào lao, chuyện gì xảy ra bây giờ? Chúng tôi rất có thể chuyển từ hai bộ phần mềm đang hoạt động thành một, phần mềm bị lỗi thống nhất, bị hỏng khủng khiếp khi Quản lý tài khoản của bạn không hoạt động và Tài khoản mới bị hỏng và tôi thậm chí không biết liệu lỗi cũ có còn không .

Có lẽ phần mềm rất thông minh và nó nói có mâu thuẫn và khăng khăng chúng tôi cho nó hướng dẫn. Chà, tào lao - Tôi ngồi xuống để làm điều đó và thấy bạn đã thêm một số mã phức tạp mà tôi không hiểu ngay lập tức. Tôi nghĩ rằng nó mâu thuẫn với những thay đổi tôi đã thực hiện ... Tôi hỏi bạn, và khi bạn nhận được một phút, bạn kiểm tra và bạn thấy mã của tôi mà bạn không hiểu. Một hoặc cả hai chúng tôi phải dành thời gian để ngồi xuống, băm ra một sự hợp nhất thích hợp và có thể kiểm tra lại toàn bộ điều nguy hiểm để đảm bảo rằng chúng tôi đã không phá vỡ nó.

Trong khi đó, 8 người khác đều đang thực hiện mã giống như những kẻ tàn bạo, tôi đã thực hiện một vài sửa lỗi nhỏ và gửi chúng trước khi tôi biết rằng chúng tôi đã có một cuộc xung đột hợp nhất, và có vẻ như đây là thời điểm tốt để nghỉ ngơi, và có lẽ bạn được nghỉ cho buổi chiều hoặc bị mắc kẹt trong một cuộc họp hoặc bất cứ điều gì. Có lẽ tôi chỉ nên đi nghỉ. Hoặc thay đổi nghề nghiệp.

Và vì vậy, để thoát khỏi cơn ác mộng này, một số người đã trở nên rất sợ cam kết (còn gì mới, đáng kinh ngạc không?). Tự nhiên chúng ta có nguy cơ ác cảm trong các tình huống như thế này - trừ khi chúng ta nghĩ rằng chúng ta bị hút và sẽ làm hỏng nó bằng mọi cách, trong trường hợp đó mọi người bắt đầu hành động với sự từ bỏ liều lĩnh. thở dài

Vì vậy, có bạn đi. Vâng, các hệ thống hiện đại được thiết kế để giảm bớt nỗi đau này, và nó được cho là có thể dễ dàng rút lui và phản ứng lại và gỡ lỗi và cơ sở tự do và hanglide và tất cả những thứ đó.

Nhưng tất cả còn hơn thế nữa và chúng tôi chỉ muốn nhấn nút trên lò vi sóng và dùng bữa 4 món trước khi có thời gian để tìm một cái nĩa, và tất cả đều cảm thấy rất không thỏa mãn - mã là công việc, nó hiệu quả, nó hiệu quả có ý nghĩa, nhưng duyên dáng xử lý một sự hợp nhất chỉ không tính.

Các lập trình viên, theo quy luật, phải phát triển một trí nhớ làm việc tuyệt vời, và sau đó có xu hướng ngay lập tức quên đi tất cả các tên rác và tên biến và phạm vi ngay khi họ kết thúc vấn đề và xử lý xung đột hợp nhất (hoặc tệ hơn là xử lý sai hợp nhất) là một lời mời để được nhắc nhở về tỷ lệ tử vong của bạn.


5
Thật tuyệt vời, thưa ngài!
Southpaw Hare

10
Câu trả lời này tại sao mọi người sợ sáp nhập, chứ không phải tại sao các cam kết hợp nhất được coi là xấu bởi rất nhiều.
Jay

23
Vấn đề với câu trả lời này là điều này vẫn đúng với một cuộc nổi loạn . Rốt cuộc, một rebase vẫn đang thực hiện hợp nhất trong đó bạn đã thay đổi một dòng mã đã được thay đổi. Tôi nghĩ rằng đây là một vấn đề với cách câu hỏi đã được nêu.
deworde

1
Tôi đánh giá thấp câu trả lời này bởi vì nó vẫn còn hùng hồn, nó vượt quá quan điểm, imho.
Eugene

5
Điều này dường như không ảnh hưởng đến rebase vs merge-merge.
Andyz Smith

5

Rebasing cung cấp một điểm nhánh di chuyển giúp đơn giản hóa quá trình đẩy các thay đổi trở lại đường cơ sở. Điều này cho phép bạn coi một nhánh chạy dài như thể đó là một thay đổi cục bộ. Không khởi động lại, các nhánh tích lũy các thay đổi từ đường cơ sở sẽ được bao gồm trong các thay đổi được hợp nhất trở lại đường cơ sở.

Hợp nhất lá đường cơ sở của bạn tại điểm nhánh ban đầu. Nếu bạn hợp nhất một vài tuần thay đổi từ dòng bạn đã phân nhánh, thì bây giờ bạn có rất nhiều thay đổi từ điểm chi nhánh của bạn, nhiều trong số đó sẽ nằm trong đường cơ sở của bạn. Điều này gây khó khăn cho việc xác định các thay đổi của bạn trong chi nhánh. Đẩy các thay đổi trở lại đường cơ sở có thể tạo ra xung đột không liên quan đến các thay đổi của bạn. Trong trường hợp có xung đột, có thể đẩy những thay đổi không nhất quán. Việc sáp nhập đang diễn ra cần nỗ lực để quản lý và việc thay đổi tương đối dễ dàng.

Rebase di chuyển điểm chi nhánh của bạn đến phiên bản mới nhất trên đường cơ sở của bạn. Bất kỳ xung đột nào bạn gặp phải sẽ chỉ dành cho (các) thay đổi của bạn. Đẩy các thay đổi đơn giản hơn nhiều. Xung đột được giải quyết trong chi nhánh địa phương bằng cách thực hiện một cuộc nổi loạn bổ sung. Trong trường hợp đẩy xung đột, người cuối cùng đẩy thay đổi của họ sẽ cần giải quyết vấn đề với thay đổi của họ.


1
Vì vậy, tất cả những gì nó làm là hạ cấp những thay đổi mới đến một mức độ mà chúng bị coi là "điều sai trái" bởi vì chúng là thay đổi duy nhất . Nếu tất cả các thay đổi cũ được bao gồm trong hợp nhất, thì tất cả các thay đổi đều ở mức độ, liên quan đến việc liệu chúng có phải là 'điều đúng đắn' ngày hôm nay hay không.
Andyz Smith

@AndyzSmith Tôi sẽ không coi những thay đổi mới là sai . Khi cố gắng xác định những gì đang được thay đổi và nên được đẩy, chúng là điều đúng đắn . Khi bạn đã khởi động lại, bạn có thể bỏ qua những thay đổi cũ vì chúng là một phần của đường cơ sở của bạn.
BillThor

đúng, vì vậy Nếu bạn không chắc chắn đường cơ sở của mình là gì, nghĩa là, liệu những gì bạn đã thay đổi đã sẵn sàng để được hợp nhất hay chưa là 'điều đúng đắn' thì đừng phản đối.
Andyz Smith

Và, nếu bạn nhận được sự thay đổi từ ai đó và nó phá vỡ bản dựng vì nguyên mẫu chức năng của chúng không khớp với nguyên mẫu hiện có, bạn biết ngay lập tức, vì sự nổi loạn, rằng anh chàng mới đã sai. bạn không cần phải đoán rằng có lẽ anh chàng mới có nguyên mẫu phù hợp và những thay đổi thay đổi trước đó, chưa được áp dụng, không được áp dụng là những thay đổi với nguyên mẫu sai.
Andyz Smith

4

Các công cụ tự động đang trở nên tốt hơn trong việc đảm bảo rằng mã hợp nhất sẽ biên dịch và chạy, do đó tránh được xung đột cú pháp , nhưng chúng không thể đảm bảo không có xung đột logic có thể được đưa vào bằng cách hợp nhất. Vì vậy, một sự hợp nhất 'thành công' mang lại cho bạn cảm giác tự tin sai lầm, trong thực tế, nó không đảm bảo gì cả, và bạn phải làm lại tất cả các thử nghiệm của mình.

Vấn đề thực sự với việc phân nhánh và hợp nhất, như tôi thấy, là nó đá câu tục ngữ xuống đường. Nó cho phép bạn nói "Tôi sẽ chỉ làm việc trong thế giới nhỏ bé của riêng tôi" trong một tuần và giải quyết mọi vấn đề phát sinh sau này. Nhưng sửa lỗi luôn nhanh hơn / rẻ hơn khi chúng còn mới. Vào thời điểm tất cả các nhánh mã bắt đầu được hợp nhất, bạn có thể đã quên một số sắc thái của những việc đã được thực hiện.

Kết hợp hai vấn đề đã nói ở trên và bạn có thể thấy mình trong tình huống đơn giản và dễ dàng hơn khi mọi người làm việc trong cùng một thân cây và liên tục giải quyết xung đột khi chúng xuất hiện, ngay cả khi chúng phát triển tích cực chậm hơn một chút.


4
Câu trả lời này tại sao mọi người sợ sáp nhập, chứ không phải tại sao các cam kết hợp nhất được coi là xấu bởi rất nhiều.
Jay

Vì vậy, thân cây này bạn đề cập đến, có liên quan đến rebase? Xin vui lòng.
Andyz Smith

@AndyzSmith "thân cây" là thuật ngữ của svn tương đương với chi nhánh "chính chủ" của git
Izkata

@Izkata vì vậy câu trả lời này ủng hộ không sử dụng hoặc hợp nhất hoặc rebase sau đó?
Andyz Smith

@AndyzSmith Vâng, đó cũng là cách tôi đọc nó. Và tôi không đồng ý với nó; Sau khi làm việc với một nhóm với 7 nhà phát triển khác theo cách được đề xuất, tôi không đề xuất điều đó. Chúng ta nên sử dụng các nhánh tính năng nhiều hơn chúng ta đã làm, nhưng hầu hết chúng đều sợ hợp nhất (như BrianDHall nhận được trong câu trả lời của mình).
Izkata

0

Một điểm liên quan khác là: Với việc khởi động lại, tôi có thể dễ dàng chọn hoặc hoàn nguyên một tính năng trong nhánh phát hành của mình.


1
Điều này công phu về điều này?
Akavall

Chắc chắn :-) Trong luồng git, chúng tôi thường xuyên tạo các nhánh phát hành mới. Nếu sau khi tạo, một lỗi được sửa trong nhánh phát triển, chúng tôi sử dụng git cherry-pick -x <commit>để áp dụng nó cho nhánh phát hành. Hoặc, chúng tôi có thể muốn hoàn tác một cái gì đó để phát hành, với git revert <commit>. Nếu <commit>là hợp nhất, nó có lông. Nó tốt như xa như tôi biết, nhưng không dễ dàng để làm. Khi sử dụng rebase, mọi 'hợp nhất' là một cam kết khổng lồ nhưng thường xuyên, dễ dàng chọn lựa và có thể hoàn nguyên.
Bruno Schäpper

0

Ba điều chưa được nói trong bất kỳ câu trả lời nào:

  • Khác biệt bên trong một chi nhánh:

    • Sự khác nhau giữa một cặp xác nhận tùy ý trong một nhánh trở nên vô cùng khó khăn khi có các xác nhận hợp nhất.
  • Hợp nhất một mặt hàng tại một thời điểm:

    • Khi bạn giải quyết sự khác biệt giữa hai nhánh, một sự hợp nhất thường xảy ra cùng một lúc và bất kỳ xung đột hợp nhất nào bạn phải làm mà không có bối cảnh cam kết cụ thể nào chịu trách nhiệm cho xung đột. Nếu bạn rebase, rebase sẽ dừng tại thời điểm xảy ra xung đột và cho phép bạn giải quyết nó trong bối cảnh đó.
  • Dọn dẹp trước khi đẩy:

    • Nếu bạn đã phạm một lỗi cam kết mà sau này bạn cần sửa, nếu bạn chưa đẩy rebase tương tác sẽ cho phép bạn kết hợp / chia / thay đổi / di chuyển các cam kết. Mặc dù bạn vẫn có thể làm điều đó nếu bạn đã hợp nhất, sẽ rất khó nếu bạn muốn kết hợp / tách / thay đổi / di chuyển qua một ranh giới hợp nhất.
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.