Khi nào bạn sử dụng Git rebase thay vì Git merge?


1549

Khi nào nên sử dụng Git rebase so với Git merge?

Tôi vẫn cần hợp nhất sau khi rebase thành công chứ?




6
Một vấn đề với những người thích sử dụng rebase là nó ngăn cản họ đẩy mã của họ thường xuyên. Vì vậy, muốn lịch sử sạch sẽ ngăn họ chia sẻ mã của họ, điều mà tôi nghĩ là quan trọng hơn.
static_rtti

9
@static_rtti: Điều đó không đúng. Bạn đang sử dụng một luồng dựa trên rebase sai nếu nó ngăn bạn đẩy các thay đổi của bạn thường xuyên.
juzzlin

5
Thật xấu hổ khi câu trả lời của Andrew Arnott và câu trả lời của Pace không được đăng trước đó, vì họ trả lời câu hỏi này một cách toàn diện hơn câu trả lời trước đó đã tích lũy được nhiều phiếu.
Đánh dấu gian hàng

Câu trả lời:


1136

Phiên bản ngắn

  • Hợp nhất nhận tất cả các thay đổi trong một nhánh và hợp nhất chúng vào một nhánh khác trong một cam kết.
  • Rebase nói rằng tôi muốn điểm mà tôi phân nhánh để chuyển đến điểm bắt đầu mới

Vậy khi nào bạn sử dụng một trong hai?

Hợp nhất

  • Giả sử bạn đã tạo một chi nhánh cho mục đích phát triển một tính năng duy nhất. Khi bạn muốn đưa những thay đổi đó trở lại thành chủ, bạn có thể muốn hợp nhất (bạn không quan tâm đến việc duy trì tất cả các cam kết tạm thời).

Nổi loạn

  • Một kịch bản thứ hai sẽ là nếu bạn bắt đầu thực hiện một số phát triển và sau đó một nhà phát triển khác đã thực hiện một thay đổi không liên quan. Bạn có thể muốn kéo và sau đó rebase để căn cứ các thay đổi của bạn từ phiên bản hiện tại từ kho lưu trữ.

105
@Rob đã đề cập đến việc duy trì các cam kết tạm thời khi sáp nhập. Tôi tin rằng theo mặc định sáp nhập nhánh B (một nhánh tính năng mà bạn đang làm việc) vào nhánh M (nhánh chính) sẽ tạo một cam kết trong M cho mỗi cam kết được thực hiện trong B kể từ khi hai chuyển hướng. Nhưng nếu bạn hợp nhất bằng tùy chọn --squash, tất cả các cam kết được thực hiện trên nhánh B sẽ được "gộp lại với nhau" và được hợp nhất thành một cam kết duy nhất trên nhánh M, giữ cho nhật ký trên nhánh chính của bạn luôn sạch sẽ và sạch sẽ. Squashing có lẽ là những gì bạn muốn nếu bạn có nhiều nhà phát triển làm việc độc lập và hợp nhất trở lại thành chủ.
spaaarky21

19
Tôi tin rằng giả định của @ spaaarky21 về việc sáp nhập là không chính xác. Nếu bạn hợp nhất một nhánh B vào M chính, sẽ chỉ có một cam kết duy nhất trên M (ngay cả khi B có nhiều cam kết), bất kể bạn sử dụng hợp nhất đơn giản hay --squash. Những gì --squash sẽ làm là loại bỏ tham chiếu đến B với tư cách là cha mẹ. Một hình ảnh trực quan tốt có ở đây: syevo.com/smartgithg/howtos.html?page=workflows.merge
jpeskin

14
@jpeskin Đó không phải là những gì tôi đang thấy. Tôi vừa làm một bài kiểm tra nhanh để xác minh. Tạo một thư mục với một tệp văn bản, initmột repo mới, addtệp và commit. Kiểm tra một nhánh tính năng mới ( checkout -b feature.) Thay đổi tệp văn bản, cam kết và lặp lại để có hai cam kết mới trên nhánh tính năng. Sau đó checkout mastermerge feature. Trong log, tôi thấy cam kết ban đầu của tôi về chủ, tiếp theo là hai cam kết được hợp nhất từ ​​tính năng. Nếu bạn merge --squash feature, tính năng được hợp nhất thành chủ nhưng không được cam kết, do đó, cam kết mới duy nhất về chủ sẽ là do bạn tự tạo.
spaaarky21

21
@ spaaarky21 Có vẻ như cả hai chúng ta đều đúng một nửa. Khi có thể hợp nhất chuyển tiếp nhanh (như trong ví dụ của bạn), git sẽ mặc định bao gồm tất cả các cam kết trong nhánh tính năng B (hoặc như bạn đề xuất, bạn có thể sử dụng --squash để kết hợp thành một cam kết duy nhất). Nhưng trong trường hợp có hai nhánh M và B khác nhau mà bạn hợp nhất, git sẽ không bao gồm tất cả các cam kết riêng lẻ từ nhánh B nếu được sáp nhập vào M (cho dù bạn có sử dụng --squash hay không).
jpeskin

6
Tại sao câu "(bạn không quan tâm đến việc duy trì tất cả các cam kết tạm thời)" vẫn còn trong câu trả lời này? Nó không có ý nghĩa gì trong '09 và bây giờ nó không có ý nghĩa gì. Ngoài ra, chắc chắn bạn sẽ chỉ muốn phản ứng lại nếu nhà phát triển khác thực hiện các thay đổi liên quan mà bạn cần - nếu họ thực hiện các thay đổi không liên quan, nhánh tính năng của bạn sẽ dễ dàng hợp nhất mà không có xung đột và lịch sử của bạn sẽ được duy trì.
Đánh dấu gian hàng

372

Thật đơn giản. Với rebase bạn nói hãy sử dụng một nhánh khác làm cơ sở mới cho công việc của bạn.

Ví dụ, nếu bạn có một nhánh master, bạn tạo một nhánh để triển khai một tính năng mới và nói rằng bạn đặt tên cho nó cool-feature, tất nhiên nhánh chính là cơ sở cho tính năng mới của bạn.

Bây giờ tại một thời điểm nhất định bạn muốn thêm tính năng mới mà bạn đã triển khai trong masterchi nhánh. Bạn chỉ có thể chuyển sang mastervà hợp nhất cool-featurechi nhánh:

$ git checkout master
$ git merge cool-feature

Nhưng theo cách này, một cam kết giả mới được thêm vào. Nếu bạn muốn tránh lịch sử spaghetti, bạn có thể rebase :

$ git checkout cool-feature
$ git rebase master

Và sau đó hợp nhất nó vào master:

$ git checkout master
$ git merge cool-feature

Lần này, vì nhánh chủ đề có cùng các cam kết của chủ cộng với các cam kết với tính năng mới, việc hợp nhất sẽ chỉ là một chuyển tiếp nhanh.


31
but this way a new dummy commit is added, if you want to avoid spaghetti-history- thế nào là xấu?
ア レ ッ

6
Ngoài ra, cờ --no-ff hợp nhất là rất rất hữu ích.
Aldo 'xoen' Giambelluca

3
@ ア レ khi người dùng Sean Schofieldđưa nó vào một bình luận: "Rebase cũng tốt bởi vì một khi bạn cuối cùng hợp nhất công cụ của bạn trở lại thành chủ (điều này không quan trọng như đã mô tả), bạn có nó ngồi ở" đỉnh "của lịch sử cam kết của bạn. các dự án nơi các tính năng có thể được viết nhưng được hợp nhất vài tuần sau đó, bạn không muốn hợp nhất chúng thành chủ vì chúng bị "nhồi" vào đường dẫn chính trong lịch sử. Cá nhân tôi thích có thể thực hiện nhật ký git và xem tính năng gần đây ngay ở "trên cùng". Lưu ý ngày cam kết được giữ nguyên - rebase không thay đổi thông tin đó. "
Adrien Be

4
Tôi nghĩ rằng nó nhắc lại ở đây - hãy nhớ rằng tất cả những điều khoản này ( merge, rebase, fast-forward, vv) được đề cập đến các thao tác cụ thể của một đồ thị acyclic đạo. Họ trở nên dễ dàng hơn để lý luận về mô hình tinh thần đó trong tâm trí.
Roy Tinker

10
@Aldo Không có gì "sạch" hay "gọn gàng" về lịch sử bị đánh bại. Nói chung là bẩn thỉu và IMHO khủng khiếp vì bạn không biết chuyện gì đang thực sự xảy ra. Lịch sử Git "sạch nhất" là lịch sử đã thực sự xảy ra. :)
Marnen Laibow-Koser

269

Để bổ sung cho câu trả lời của riêng tôi được đề cập bởi TSamper ,

  • một rebase thường là một ý tưởng tốt để thực hiện trước khi hợp nhất, bởi vì ý tưởng là bạn tích hợp trong chi nhánh của bạn Ycông việc của chi nhánh Bmà bạn sẽ hợp nhất.
    Nhưng một lần nữa, trước khi hợp nhất, bạn giải quyết bất kỳ xung đột nào trong chi nhánh của mình (ví dụ: "rebase", như trong "phát lại công việc của tôi trong chi nhánh của tôi bắt đầu từ một điểm gần đây từ chi nhánh B).
    Nếu được thực hiện chính xác, việc hợp nhất tiếp theo từ chi nhánh của bạn thành chi nhánh Bcó thể được chuyển tiếp nhanh.

  • một sự hợp nhất tác động trực tiếp đến nhánh đích B, điều đó có nghĩa là sự hợp nhất tốt hơn là tầm thường, nếu không thì nhánh đó Bcó thể tồn tại lâu để trở lại trạng thái ổn định (thời gian để bạn giải quyết tất cả các xung đột)


điểm hợp nhất sau một cuộc nổi loạn?

Trong trường hợp mà tôi mô tả, tôi nổi loạn Blên chi nhánh của mình, chỉ để có cơ hội phát lại công việc của tôi từ một điểm gần đây hơn B, nhưng trong khi ở lại chi nhánh của tôi.
Trong trường hợp này, một sự hợp nhất vẫn là cần thiết để đưa công việc "phát lại" của tôi lên B.

Kịch bản khác ( được mô tả trong Git Ready chẳng hạn), là đưa công việc của bạn trực tiếp Bthông qua một cuộc nổi loạn (bảo tồn tất cả các cam kết tốt đẹp của bạn, hoặc thậm chí cho bạn cơ hội để đặt hàng lại chúng thông qua một cuộc nổi loạn tương tác).
Trong trường hợp đó (khi bạn rebase trong khi ở nhánh B), bạn đã đúng: không cần hợp nhất thêm nữa:

Cây Git theo mặc định khi chúng ta không hợp nhất cũng không bị hủy bỏ

rebase1

chúng tôi nhận được bằng cách nổi loạn:

rebase3

Kịch bản thứ hai đó là tất cả về: làm thế nào để tôi lấy lại tính năng mới thành chủ.

Quan điểm của tôi, bằng cách mô tả kịch bản rebase đầu tiên, là để nhắc nhở mọi người rằng một rebase cũng có thể được sử dụng như một bước sơ bộ cho điều đó (đó là "lấy lại tính năng mới thành chủ").
Bạn có thể sử dụng rebase để đưa master "vào" nhánh tính năng mới: rebase sẽ phát lại các cam kết tính năng mới từ HEAD master, nhưng vẫn trong nhánh tính năng mới, di chuyển điểm bắt đầu chi nhánh của bạn từ một cam kết chính cũ sang HEAD-master.
Điều đó cho phép bạn giải quyết bất kỳ xung đột nào trong chi nhánh của mình (nghĩa là cô lập, đồng thời cho phép chủ nhân tiếp tục phát triển song song nếu giai đoạn giải quyết xung đột của bạn mất quá nhiều thời gian).
Sau đó, bạn có thể chuyển sang chủ và hợp nhất new-feature(hoặc rebase new-featurelên masternếu bạn muốn duy trì các cam kết được thực hiện trongnew-feature chi nhánh).

Vì thế:

  • "Rebase vs. merge" có thể được xem là hai cách để nhập tác phẩm vào, giả sử , master.
  • Nhưng "rebase then merge" có thể là một quy trình công việc hợp lệ để giải quyết xung đột một cách cô lập, sau đó mang lại công việc của bạn.

17
hợp nhất sau khi rebase là một chuyển tiếp nhanh tầm thường mà không phải giải quyết xung đột.
obecalp

4
@obelcap: Thật vậy, đây là một ý tưởng: bạn xử lý tất cả các xung đột vấn đề trong môi trường của mình (rebase master trong nhánh tính năng mới của bạn), sau đó đồng chủ, hợp nhất tính năng mới: 1 pico-giây (nhanh- chuyển tiếp) nếu chủ nhân không có diễn biến
VonC

27
Rebase cũng tốt bởi vì một khi cuối cùng bạn hợp nhất công cụ của mình trở lại thành chủ (điều này không quan trọng như đã mô tả), bạn có nó ngồi ở "đỉnh" của lịch sử cam kết của bạn. Trong các dự án lớn hơn, nơi các tính năng có thể được viết nhưng được hợp nhất vài tuần sau đó, bạn không muốn hợp nhất chúng thành chủ vì chúng bị "nhồi" vào đường dẫn chính trong lịch sử. Cá nhân tôi thích có thể thực hiện git log và xem tính năng gần đây ngay trên "đỉnh". Lưu ý ngày cam kết được giữ nguyên - rebase không thay đổi thông tin đó.
Sean Schofield

3
@Joe: về mặt tinh thần, bạn đang nói "phát lại bất kỳ thay đổi nào của tôi (được thực hiện một cách cô lập trong nhánh riêng của tôi) trên đầu nhánh khác, nhưng hãy để tôi ở nhánh riêng của tôi sau khi quá trình rebase được thực hiện". Đó là một cơ hội tốt để làm sạch lịch sử địa phương, tránh "cam kết điểm kiểm tra", chia đôi lỗi và kết quả đổ lỗi không chính xác. Xem "Git workflow": sandofsky.com/blog/git-workflow.html
VonC

4
@scoarescoare chìa khóa là để xem các thay đổi cục bộ của bạn tương thích như thế nào trên đầu nhánh mới nhất. Nếu một trong những cam kết của bạn giới thiệu một cuộc xung đột, bạn sẽ thấy nó ngay lập tức. Hợp nhất chỉ giới thiệu một cam kết (đã hợp nhất), có thể gây ra nhiều xung đột mà không có cách dễ dàng để xem một cam kết nào, trong số các cam kết tại địa phương của bạn, đã thêm xung đột. Vì vậy, ngoài lịch sử sạch hơn, bạn có được cái nhìn chính xác hơn về các thay đổi bạn giới thiệu, cam kết bằng cam kết (được phát lại bởi rebase), trái ngược với tất cả các thay đổi được giới thiệu bởi nhánh ngược dòng (đổ vào một hợp nhất).
VonC

229

TL; DR

Nếu bạn có bất kỳ nghi ngờ, sử dụng hợp nhất.

Câu trả lời ngắn

Sự khác biệt duy nhất giữa rebase và hợp nhất là:

  • Cấu trúc cây kết quả của lịch sử (thường chỉ đáng chú ý khi nhìn vào biểu đồ cam kết) là khác nhau (một cái sẽ có các nhánh, cái kia sẽ không).
  • Hợp nhất thường sẽ tạo ra một cam kết bổ sung (ví dụ: nút trong cây).
  • Hợp nhất và rebase sẽ xử lý các xung đột khác nhau. Rebase sẽ đưa ra xung đột một cam kết tại một thời điểm trong đó hợp nhất sẽ trình bày tất cả chúng cùng một lúc.

Vì vậy, câu trả lời ngắn gọn là chọn rebase hoặc hợp nhất dựa trên những gì bạn muốn lịch sử của bạn trông như thế nào .

Câu trả lời dài

Có một vài yếu tố bạn nên xem xét khi lựa chọn sử dụng thao tác nào.

Là chi nhánh bạn đang nhận được các thay đổi từ được chia sẻ với các nhà phát triển khác ngoài nhóm của bạn (ví dụ: nguồn mở, công khai)?

Nếu vậy, đừng nổi loạn. Rebase phá hủy chi nhánh và những nhà phát triển đó sẽ có các kho bị hỏng / không nhất quán trừ khi họ sử dụng git pull --rebase. Đây là một cách tốt để nhanh chóng làm phiền các nhà phát triển khác.

Đội ngũ phát triển của bạn có kỹ năng như thế nào?

Rebase là một hoạt động phá hoại. Điều đó có nghĩa là, nếu bạn không áp dụng nó một cách chính xác, bạn có thể mất công việc đã cam kết và / hoặc phá vỡ tính nhất quán của các kho lưu trữ của nhà phát triển khác.

Tôi đã làm việc trong các nhóm mà tất cả các nhà phát triển đều xuất phát từ thời điểm các công ty có thể đủ khả năng cho nhân viên chuyên trách để giải quyết việc phân nhánh và sáp nhập. Những nhà phát triển đó không biết nhiều về Git và không muốn biết nhiều. Trong các đội này, tôi sẽ không mạo hiểm đề nghị nổi loạn vì bất kỳ lý do gì.

Chi nhánh có đại diện cho thông tin hữu ích không

Một số nhóm sử dụng mô hình chi nhánh cho mỗi tính năng trong đó mỗi chi nhánh đại diện cho một tính năng (hoặc lỗi, hoặc tính năng phụ, v.v.) Trong mô hình này, chi nhánh giúp xác định các bộ xác nhận liên quan. Ví dụ, người ta có thể nhanh chóng hoàn nguyên một tính năng bằng cách hoàn nguyên việc hợp nhất của nhánh đó (công bằng mà nói, đây là một hoạt động hiếm gặp). Hoặc khác một tính năng bằng cách so sánh hai nhánh (phổ biến hơn). Rebase sẽ phá hủy chi nhánh và điều này sẽ không đơn giản.

Tôi cũng đã làm việc trên các nhóm sử dụng mô hình chi nhánh cho mỗi nhà phát triển (tất cả chúng tôi đã ở đó). Trong trường hợp này, chi nhánh không truyền đạt bất kỳ thông tin bổ sung nào (cam kết đã có tác giả). Sẽ không có hại trong việc nổi loạn.

Bạn có thể muốn hoàn nguyên hợp nhất cho bất kỳ lý do?

Hoàn nguyên (như hoàn tác) một rebase là rất khó khăn và / hoặc không thể (nếu rebase có xung đột) so với hoàn nguyên hợp nhất. Nếu bạn nghĩ rằng có một cơ hội bạn sẽ muốn hoàn nguyên thì hãy sử dụng hợp nhất.

Bạn có làm việc trong một nhóm không? Nếu vậy, bạn có sẵn sàng thực hiện một cách tiếp cận tất cả hoặc không có gì trên chi nhánh này?

Hoạt động Rebase cần phải được kéo với một tương ứng git pull --rebase. Nếu bạn đang làm việc một mình, bạn có thể nhớ những gì bạn nên sử dụng vào thời điểm thích hợp. Nếu bạn đang làm việc trong một nhóm, điều này sẽ rất khó phối hợp. Đây là lý do tại sao hầu hết các quy trình công việc rebase khuyên bạn nên sử dụng rebase cho tất cả các kết hợp (và git pull --rebasecho tất cả các lần kéo).

Thần thoại thông thường

Hợp nhất phá hủy lịch sử (cam kết squash)

Giả sử bạn có hợp nhất sau:

    B -- C
   /      \
  A--------D

Một số người sẽ nói rằng việc hợp nhất "phá hủy" lịch sử cam kết bởi vì nếu bạn chỉ nhìn vào nhật ký của nhánh chính (A - D), bạn sẽ bỏ lỡ các thông điệp cam kết quan trọng có trong B và C.

Nếu điều này là đúng, chúng tôi sẽ không có câu hỏi như thế này . Về cơ bản, bạn sẽ thấy B và C trừ khi bạn yêu cầu không nhìn thấy chúng (sử dụng --first-Parent). Điều này rất dễ dàng để thử cho chính mình.

Rebase cho phép hợp nhất an toàn hơn / đơn giản hơn

Hai cách tiếp cận hợp nhất khác nhau, nhưng không rõ ràng cái nào luôn tốt hơn cái kia và nó có thể phụ thuộc vào quy trình làm việc của nhà phát triển. Ví dụ: nếu nhà phát triển có xu hướng cam kết thường xuyên (ví dụ có thể họ cam kết hai lần một ngày khi họ chuyển từ công việc về nhà) thì có thể có rất nhiều cam kết cho một chi nhánh nhất định. Nhiều trong số các cam kết đó có thể trông không giống bất kỳ sản phẩm cuối cùng nào (tôi có xu hướng cấu trúc lại cách tiếp cận của mình một hoặc hai lần cho mỗi tính năng). Nếu ai đó đang làm việc trên một lĩnh vực mã liên quan và họ cố gắng chống lại những thay đổi của tôi thì đó có thể là một hoạt động khá tẻ nhạt.

Rebase là mát mẻ / quyến rũ hơn / chuyên nghiệp hơn

Nếu bạn muốn bí danh rmđể rm -rf"tiết kiệm thời gian" thì có lẽ rebase là dành cho bạn.

Theo quan điểm của tôi

Tôi luôn nghĩ rằng một ngày nào đó tôi sẽ bắt gặp một kịch bản trong đó Git rebase là công cụ tuyệt vời giải quyết vấn đề. Giống như tôi nghĩ rằng tôi sẽ bắt gặp một kịch bản trong đó Git reflog là một công cụ tuyệt vời giải quyết vấn đề của tôi. Tôi đã làm việc với Git hơn năm năm nay. Nó đã không xảy ra.

Lịch sử lộn xộn chưa bao giờ thực sự là một vấn đề đối với tôi. Tôi không bao giờ chỉ đọc lịch sử cam kết của mình như một cuốn tiểu thuyết thú vị. Phần lớn thời gian tôi cần một lịch sử tôi sẽ sử dụng Git blame hoặc Git bisect anyway. Trong trường hợp đó, có cam kết hợp nhất thực sự hữu ích với tôi, bởi vì nếu hợp nhất giới thiệu vấn đề, đó là thông tin có ý nghĩa với tôi.

Cập nhật (4/2017)

Tôi cảm thấy bắt buộc phải đề cập rằng cá nhân tôi đã mềm lòng khi sử dụng rebase mặc dù lời khuyên chung của tôi vẫn đứng vững. Gần đây tôi đã tương tác rất nhiều với dự án Angular 2 Material . Họ đã sử dụng rebase để giữ một lịch sử cam kết rất sạch sẽ. Điều này đã cho phép tôi rất dễ dàng nhìn thấy những gì cam kết đã sửa chữa một khiếm khuyết nhất định và liệu cam kết đó có được đưa vào một bản phát hành hay không. Nó phục vụ như một ví dụ tuyệt vời về việc sử dụng rebase một cách chính xác.


5
Nên là câu trả lời xác thực.
Mik378

Đây chắc chắn là câu trả lời tốt nhất. Đặc biệt với nhận xét được làm rõ tại bản cập nhật mới nhất. Nó giúp giữ cho lịch sử git sạch sẽ và rõ ràng, nhưng sử dụng nó một cách an toàn.
zquintana

5
Tôi chủ yếu thích câu trả lời này. Nhưng: Rebase không tạo ra một lịch sử "sạch". Nó tạo ra một lịch sử tuyến tính hơn, nhưng đó không phải là điều tương tự, vì ai biết bây giờ nhiều "bụi bẩn" mà mỗi cam kết đang che giấu? Lịch sử Git sạch nhất, rõ ràng nhất là lịch sử giữ chi nhánh và cam kết toàn vẹn.
Marnen Laibow-Koser

3
"Huyền thoại thông thường, bạn thấy cam kết B và C": Không nhất thiết !! Bạn thực sự chỉ nhìn thấy B và C nếu hợp nhất là hợp nhất chuyển tiếp nhanh và điều đó chỉ có thể, nếu không có xung đột. Nếu có xung đột, bạn nhận được một cam kết duy nhất! Tuy nhiên: Trước tiên, bạn có thể hợp nhất chủ vào tính năng và giải quyết xung đột ở đó và nếu sau đó bạn hợp nhất tính năng thành chủ, bạn sẽ nhận được cam kết B và C, và một cam kết X từ (đầu tiên) hợp nhất từ ​​chủ vào tính năng trong lịch sử của bạn.
Jeremy Benks

giải thích rất tốt! Nó phải nâng cao hơn nữa!
dimmits

185

Rất nhiều câu trả lời ở đây nói rằng việc hợp nhất biến tất cả các cam kết của bạn thành một, và do đó đề nghị sử dụng rebase để duy trì các cam kết của bạn. Điều này là không chính xác. Và một ý tưởng tồi nếu bạn đã đẩy mạnh cam kết của mình .

Hợp nhất không xóa sạch cam kết của bạn. Hợp nhất bảo tồn lịch sử! (chỉ cần nhìn vào gitk) Rebase viết lại lịch sử, đó là một điều xấu sau khi bạn đã đẩy nó.

Sử dụng hợp nhất - không rebase bất cứ khi nào bạn đã đẩy.

Đây là Linus '(tác giả của Git) đảm nhận nó (hiện được lưu trữ trên blog của riêng tôi, khi được phục hồi bởi Wayback Machine ). Đó là một bài đọc thực sự tốt.

Hoặc bạn có thể đọc phiên bản của riêng tôi cùng ý tưởng dưới đây.

Nổi loạn một chi nhánh trên chủ:

  • cung cấp một ý tưởng không chính xác về cách tạo ra các cam kết
  • chủ nhân gây ô nhiễm với một loạt các cam kết trung gian có thể chưa được kiểm tra tốt
  • thực sự có thể giới thiệu các bản dựng xây dựng trên các cam kết trung gian này vì những thay đổi được thực hiện để làm chủ giữa khi nhánh chủ đề ban đầu được tạo và khi nó được khởi động lại.
  • làm cho việc tìm kiếm những nơi tốt trong tổng thể để kiểm tra khó khăn.
  • Làm cho các dấu thời gian trên cam kết không phù hợp với thứ tự thời gian của chúng trong cây. Vì vậy, bạn sẽ thấy rằng cam kết A đi trước cam kết B trong chủ, nhưng cam kết B được ủy quyền trước. (Gì?!)
  • Tạo ra nhiều xung đột hơn, bởi vì các cam kết riêng lẻ trong nhánh chủ đề có thể liên quan đến các xung đột hợp nhất phải được giải quyết riêng lẻ (nằm trong lịch sử về những gì đã xảy ra trong mỗi cam kết).
  • là một bản viết lại của lịch sử. Nếu chi nhánh bị phản đối đã bị đẩy đi bất cứ đâu (chia sẻ với bất kỳ ai khác ngoài chính bạn) thì bạn đã làm hỏng mọi người khác có chi nhánh đó kể từ khi bạn viết lại lịch sử.

Ngược lại, hợp nhất một nhánh chủ đề thành chủ:

  • lưu giữ lịch sử nơi các nhánh chủ đề được tạo, bao gồm mọi sự hợp nhất từ ​​chủ đến nhánh chủ đề để giúp giữ cho nó hiện tại. Bạn thực sự có được một ý tưởng chính xác về mã mà nhà phát triển đã làm việc với khi họ đang xây dựng.
  • master là một nhánh được tạo thành chủ yếu từ các sự hợp nhất và mỗi cam kết hợp nhất đó thường là 'điểm tốt' trong lịch sử an toàn để kiểm tra, bởi vì đó là nơi nhánh chủ đề đã sẵn sàng để được tích hợp.
  • tất cả các cam kết riêng lẻ của nhánh chủ đề đều được giữ nguyên, bao gồm cả thực tế là chúng nằm trong nhánh chủ đề, do đó, cách ly những thay đổi đó là tự nhiên và bạn có thể khoan vào nơi cần thiết.
  • xung đột hợp nhất chỉ phải được giải quyết một lần (tại thời điểm hợp nhất), vì vậy các thay đổi cam kết trung gian được thực hiện trong nhánh chủ đề không phải được giải quyết độc lập.
  • có thể được thực hiện nhiều lần trơn tru. Nếu bạn tích hợp nhánh chủ đề của mình để làm chủ định kỳ, mọi người có thể tiếp tục xây dựng trên nhánh chủ đề và nó có thể tiếp tục được hợp nhất độc lập.

3
Ngoài ra, git merge có tùy chọn "--no-ff" (không chuyển tiếp nhanh) cho phép bạn hoàn nguyên tất cả các thay đổi được giới thiệu bởi một hợp nhất nhất định thực sự dễ dàng.
Tiago

3
Chỉ cần nói rõ hơn: Bạn đề cập đến tình huống 'bất cứ khi nào bạn đã đẩy' - điều này nên được in đậm. Bài viết Liên kết đến Linus là tuyệt vời, btw., Làm rõ nó.
honzajde 14/2/2015

2
nhưng không phải là cách tốt nhất để "cập nhật" từ chủ vào nhánh chủ đề của bạn, trước khi bạn hợp nhất nhánh chủ đề thành chủ thông qua PR (để giải quyết xung đột trong chi nhánh của bạn chứ không phải chủ)? Chúng tôi đang làm như vậy nên hầu hết các nhánh chủ đề đều có cam kết cuối cùng là "hợp nhất nhánh chủ vào chủ đề -..." nhưng ở đây, đây được liệt kê là "tính năng" của việc nổi loạn và không ai đề cập đến việc hợp nhất ...?
Vấn

2
@AndrewArnott "Hầu hết các nhánh chủ đề sẽ có thể hợp nhất mà không có xung đột vào các nhánh mục tiêu của họ" Làm thế nào có thể xảy ra khi 20 nhà phát triển đang làm việc trên 30 chi nhánh? Sẽ có sự hợp nhất trong khi bạn đang làm việc với bạn - vì vậy tất nhiên bạn phải cập nhật nhánh chủ đề của mình từ mục tiêu trước khi tạo PR ... không?
Vấn

3
Không thường xuyên, @Sumit. Git có thể hợp nhất một trong hai hướng tốt mặc dù các thay đổi đã được thực hiện cho một hoặc cả hai nhánh. Chỉ khi cùng một dòng mã (hoặc rất gần) được sửa đổi trên hai nhánh, bạn mới có xung đột. Nếu điều đó xảy ra thường xuyên trên bất kỳ nhóm nào, nhóm nên suy nghĩ lại về cách họ phân phối công việc vì giải quyết xung đột là thuế và làm chậm chúng.
Andrew Arnott

76

TLDR: Nó phụ thuộc vào điều quan trọng nhất - lịch sử gọn gàng hoặc đại diện thực sự của chuỗi phát triển

Nếu một lịch sử gọn gàng là quan trọng nhất, thì bạn sẽ khởi động lại trước và sau đó hợp nhất các thay đổi của bạn, vì vậy rõ ràng chính xác mã mới là gì. Nếu bạn đã đẩy chi nhánh của mình, đừng nổi loạn trừ khi bạn có thể giải quyết hậu quả.

Nếu đại diện thực sự của chuỗi là quan trọng nhất, bạn sẽ hợp nhất mà không cần khởi động lại.

Hợp nhất có nghĩa là: Tạo một cam kết mới duy nhất hợp nhất các thay đổi của tôi vào đích. Lưu ý: Cam kết mới này sẽ có hai cha mẹ - cam kết mới nhất từ ​​chuỗi cam kết của bạn và cam kết mới nhất của chi nhánh khác mà bạn đang hợp nhất.

Rebase có nghĩa là: Tạo một loạt các cam kết mới, sử dụng bộ cam kết hiện tại của tôi làm gợi ý. Nói cách khác, hãy tính toán những thay đổi của tôi sẽ như thế nào nếu tôi bắt đầu thực hiện chúng từ thời điểm tôi đang bắt đầu. Do đó, sau khi rebase, bạn có thể cần kiểm tra lại các thay đổi của mình và trong quá trình rebase, bạn có thể sẽ có một vài xung đột.

Đưa ra điều này, tại sao bạn sẽ nổi loạn? Chỉ để giữ cho lịch sử phát triển rõ ràng. Giả sử bạn đang làm việc trên tính năng X và khi bạn hoàn thành, bạn hợp nhất các thay đổi của mình. Điểm đến bây giờ sẽ có một cam kết duy nhất có thể nói điều gì đó dọc theo dòng "Đã thêm tính năng X". Bây giờ, thay vì hợp nhất, nếu bạn khởi động lại và sau đó hợp nhất, lịch sử phát triển đích sẽ chứa tất cả các cam kết riêng lẻ trong một tiến trình logic duy nhất. Điều này làm cho việc xem xét các thay đổi sau này dễ dàng hơn nhiều. Hãy tưởng tượng bạn sẽ thấy khó khăn như thế nào khi xem lại lịch sử phát triển nếu 50 nhà phát triển luôn hợp nhất các tính năng khác nhau.

Điều đó nói rằng, nếu bạn đã đẩy chi nhánh bạn đang làm việc ngược dòng, bạn không nên khởi động lại mà thay vào đó là hợp nhất. Đối với các nhánh chưa được đẩy ngược dòng, rebase, kiểm tra và hợp nhất.

Một lần khác bạn có thể muốn rebase là khi bạn muốn thoát khỏi các cam kết từ chi nhánh của mình trước khi đẩy ngược dòng. Ví dụ: Các cam kết giới thiệu một số mã gỡ lỗi sớm và các cam kết khác về việc làm sạch mã đó. Cách duy nhất để làm điều này là bằng cách thực hiện một rebase tương tác:git rebase -i <branch/commit/tag>

CẬP NHẬT: Bạn cũng muốn sử dụng rebase khi bạn đang sử dụng Git để giao diện với hệ thống kiểm soát phiên bản không hỗ trợ lịch sử phi tuyến tính ( ví dụ Subversion ). Khi sử dụng cầu git-svn, điều rất quan trọng là những thay đổi bạn hợp nhất trở lại Subversion là một danh sách tuần tự các thay đổi trên đầu trang của những thay đổi gần đây nhất trong thân cây. Chỉ có hai cách để làm điều đó: (1) Tạo lại thủ công các thay đổi và (2) Sử dụng lệnh rebase, nhanh hơn rất nhiều.

CẬP NHẬT 2: Một cách khác để nghĩ về rebase là nó cho phép một loại ánh xạ từ kiểu phát triển của bạn sang kiểu được chấp nhận trong kho lưu trữ mà bạn cam kết. Giả sử bạn muốn cam kết trong những khối nhỏ, nhỏ. Bạn có một cam kết sửa lỗi chính tả, một cam kết loại bỏ mã không sử dụng, v.v. Khi bạn hoàn thành những gì bạn cần làm, bạn có một loạt các cam kết dài. Bây giờ, giả sử kho lưu trữ mà bạn cam kết khuyến khích các cam kết lớn, vì vậy đối với công việc bạn đang thực hiện, người ta sẽ mong đợi một hoặc có thể hai cam kết. Làm thế nào để bạn lấy chuỗi cam kết của mình và nén chúng theo những gì được mong đợi? Bạn sẽ sử dụng một rebase tương tác và nén các cam kết nhỏ của bạn thành các phần nhỏ hơn. Điều tương tự cũng đúng nếu điều ngược lại là cần thiết - nếu phong cách của bạn là một vài cam kết lớn, nhưng kho lưu trữ yêu cầu chuỗi dài các cam kết nhỏ. Bạn sẽ sử dụng một rebase để làm điều đó là tốt. Nếu bạn đã hợp nhất thay vào đó, bây giờ bạn đã ghép kiểu cam kết của mình vào kho lưu trữ chính. Nếu có nhiều nhà phát triển, bạn có thể tưởng tượng việc theo dõi lịch sử với một số kiểu cam kết khác nhau sẽ khó như thế nào sau một thời gian.

CẬP NHẬT 3: Does one still need to merge after a successful rebase?Có, bạn làm. Lý do là một cuộc nổi loạn về cơ bản liên quan đến việc "thay đổi" các cam kết. Như tôi đã nói ở trên, những cam kết này đã được tính toán, nhưng nếu bạn có 14 lần xác nhận từ thời điểm phân nhánh, thì giả sử không có gì sai với cuộc nổi loạn của bạn, bạn sẽ có 14 lần cam kết trước (về điểm bạn sẽ phản đối) sau cuộc nổi loạn đã xong. Bạn đã có một chi nhánh trước khi nổi loạn. Bạn sẽ có một nhánh có cùng chiều dài sau. Bạn vẫn cần hợp nhất trước khi xuất bản các thay đổi của mình. Nói cách khác, rebase bao nhiêu lần bạn muốn (một lần nữa, chỉ khi bạn không đẩy những thay đổi của mình lên trên dòng). Hợp nhất chỉ sau khi bạn rebase.


1
Hợp nhất với chủ có thể dẫn đến một chuyển tiếp nhanh. Trong một nhánh tính năng có thể có một số cam kết, có lỗi nhỏ hoặc thậm chí không biên dịch. Nếu bạn chỉ kiểm tra đơn vị trong một nhánh tính năng, một số lỗi trong tích hợp sẽ xảy ra. Trước khi hợp nhất với chủ, các thử nghiệm tích hợp là bắt buộc và có thể hiển thị một số lỗi. Nếu những cái này được sửa, tính năng có thể được tích hợp. Vì bạn không muốn cam kết mã lỗi thành chủ, một cuộc nổi loạn dường như không cần thiết để ngăn chặn tất cả các cam kết chuyển tiếp nhanh.
mbx

1
@mbx git mergehỗ trợ --no-fftùy chọn buộc nó thực hiện một cam kết hợp nhất.
Gavin S. Yancey

63

Mặc dù hợp nhất chắc chắn là cách dễ nhất và phổ biến nhất để tích hợp các thay đổi, nhưng đó không phải là cách duy nhất: Rebase là một phương tiện tích hợp thay thế.

Hiểu rõ hơn một chút

Khi Git thực hiện hợp nhất, nó sẽ tìm ba cam kết:

  • (1) Cam kết tổ tiên chung. Nếu bạn theo dõi lịch sử của hai nhánh trong một dự án, chúng luôn có ít nhất một cam kết chung: tại thời điểm này, cả hai nhánh có cùng một nội dung và sau đó phát triển khác nhau.
  • (2) + (3) Điểm cuối của mỗi nhánh. Mục tiêu của hội nhập là kết hợp các trạng thái hiện tại của hai nhánh. Do đó, phiên bản mới nhất tương ứng của họ được quan tâm đặc biệt. Kết hợp ba cam kết này sẽ dẫn đến sự tích hợp mà chúng tôi hướng tới.

Cam kết chuyển tiếp nhanh hoặc hợp nhất

Trong các trường hợp rất đơn giản, một trong hai nhánh không có bất kỳ cam kết mới nào kể từ khi phân nhánh xảy ra - cam kết mới nhất của nó vẫn là tổ tiên chung.

Nhập mô tả hình ảnh ở đây

Trong trường hợp này, việc thực hiện tích hợp rất đơn giản: Git chỉ có thể thêm tất cả các cam kết của nhánh khác lên trên cam kết chung của tổ tiên chung. Trong Git, hình thức tích hợp đơn giản nhất này được gọi là hợp nhất "chuyển tiếp nhanh". Cả hai chi nhánh sau đó chia sẻ cùng một lịch sử.

Nhập mô tả hình ảnh ở đây

Tuy nhiên, trong rất nhiều trường hợp, cả hai chi nhánh đều di chuyển về phía trước.

Nhập mô tả hình ảnh ở đây

Để thực hiện tích hợp, Git sẽ phải tạo một cam kết mới có chứa sự khác biệt giữa chúng - cam kết hợp nhất.

Nhập mô tả hình ảnh ở đây

Cam kết của con người & Hợp nhất

Thông thường, một cam kết được tạo ra cẩn thận bởi một con người. Đó là một đơn vị có ý nghĩa chỉ bao bọc các thay đổi liên quan và chú thích chúng với một nhận xét.

Một cam kết hợp nhất có một chút khác biệt: thay vì được tạo bởi nhà phát triển, nó được tạo tự động bởi Git. Và thay vì gói một tập hợp các thay đổi liên quan, mục đích của nó là kết nối hai nhánh, giống như một nút thắt. Nếu bạn muốn hiểu một hoạt động hợp nhất sau này, bạn cần xem lịch sử của cả hai nhánh và biểu đồ cam kết tương ứng.

Tích hợp với Rebase

Một số người thích đi mà không có cam kết hợp nhất tự động như vậy. Thay vào đó, họ muốn lịch sử của dự án trông như thể nó đã phát triển theo một đường thẳng duy nhất. Không có dấu hiệu nào cho thấy nó đã được chia thành nhiều nhánh tại một số điểm.

Nhập mô tả hình ảnh ở đây

Chúng ta hãy đi qua từng bước hoạt động rebase. Kịch bản giống như trong các ví dụ trước: chúng tôi muốn tích hợp các thay đổi từ nhánh B vào nhánh A, nhưng bây giờ bằng cách sử dụng rebase.

Nhập mô tả hình ảnh ở đây

Chúng tôi sẽ làm điều này trong ba bước

  1. git rebase branch-A // Synchronises the history with branch-A
  2. git checkout branch-A // Change the current branch to branch-A
  3. git merge branch-B // Merge/take the changes from branch-B to branch-A

Đầu tiên, Git sẽ "hoàn tác" tất cả các cam kết trên nhánh A xảy ra sau khi các dòng bắt đầu phân nhánh (sau khi cam kết tổ tiên chung). Tuy nhiên, tất nhiên, nó sẽ không loại bỏ chúng: thay vào đó bạn có thể nghĩ rằng những cam kết đó là "được lưu tạm thời".

Nhập mô tả hình ảnh ở đây

Tiếp theo, nó áp dụng các cam kết từ nhánh B mà chúng tôi muốn tích hợp. Tại thời điểm này, cả hai nhánh trông giống hệt nhau.

Nhập mô tả hình ảnh ở đây

Trong bước cuối cùng, các cam kết mới trên nhánh A hiện được áp dụng lại - nhưng ở một vị trí mới, trên đầu các cam kết tích hợp từ nhánh B (chúng được dựa trên lại).

Kết quả có vẻ như sự phát triển đã xảy ra theo một đường thẳng. Thay vì một cam kết hợp nhất có chứa tất cả các thay đổi kết hợp, cấu trúc cam kết ban đầu được giữ nguyên.

Nhập mô tả hình ảnh ở đây

Cuối cùng, bạn nhận được một nhánh nhánh-A sạch mà không có các cam kết không mong muốn và được tạo tự động.

Lưu ý: Lấy từ bài viết tuyệt vời của git-tower. Những nhược điểm của rebasecũng là một đọc tốt trong cùng một bài.


+1 cho sơ đồ rất tuyệt. Tôi luôn muốn có thể minh họa một ví dụ về dòng chảy git theo cách tương tự mà không gặp may mắn.
Mikayil Abdullayev

60

Trước khi hợp nhất / rebase:

A <- B <- C    [master]
^
 \
  D <- E       [branch]

Sau git merge master:

A <- B <- C
^         ^
 \         \
  D <- E <- F

Sau git rebase master:

A <- B <- C <- D' <- E'

(A, B, C, D, E và F là các cam kết)

Ví dụ này và nhiều thông tin minh họa rõ hơn về Git có thể được tìm thấy trong Hướng dẫn cơ bản của Git .


30

Câu này được nó:

Nói chung, cách để có được điều tốt nhất của cả hai thế giới là phản ứng lại những thay đổi cục bộ bạn đã thực hiện, nhưng chưa chia sẻ, trước khi bạn thúc đẩy chúng để dọn dẹp câu chuyện của bạn, nhưng không bao giờ phản bác bất cứ điều gì bạn đã đẩy ở đâu đó .

Nguồn: 3.6 Phân nhánh Git - Rebasing, Rebase vs. Merge


25

Câu trả lời này được định hướng rộng rãi xung quanh Git Flow . Các bảng đã được tạo bằng Trình tạo bảng ASCII đẹp và các cây lịch sử với lệnh tuyệt vời này (có biệt danhgit lg):

git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

Các bảng theo thứ tự thời gian đảo ngược để phù hợp hơn với các cây lịch sử. Xem thêm sự khác biệt giữa git mergegit merge --no-ffđầu tiên (bạn thường muốn sử dụng git merge --no-ffvì nó làm cho lịch sử của bạn gần với thực tế hơn):

git merge

Các lệnh:

Time          Branch "develop"             Branch "features/foo"
------- ------------------------------ -------------------------------
15:04   git merge features/foo
15:03                                  git commit -m "Third commit"
15:02                                  git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Kết quả:

* 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo)
|           Third commit - Christophe
* 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago)
|           Second commit - Christophe
* 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge --no-ff

Các lệnh:

Time           Branch "develop"              Branch "features/foo"
------- -------------------------------- -------------------------------
15:04   git merge --no-ff features/foo
15:03                                    git commit -m "Third commit"
15:02                                    git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Kết quả:

*   1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/foo' - Christophe
| * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago)
|/            Second commit - Christophe
* c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge đấu với git rebase

Điểm đầu tiên: luôn hợp nhất các tính năng để phát triển, không bao giờ rebase phát triển từ các tính năng . Đây là kết quả của Nguyên tắc Vàng nổi loạn :

Nguyên tắc vàng git rebaselà không bao giờ sử dụng nó trên các chi nhánh công cộng .

Nói cách khác :

Không bao giờ phản đối bất cứ điều gì bạn đã đẩy ở đâu đó.

Cá nhân tôi sẽ thêm: trừ khi đó là một nhánh tính năng VÀ bạn và nhóm của bạn nhận thức được hậu quả .

Vì vậy, câu hỏi git mergevs chỉ git rebaseáp dụng cho các nhánh tính năng (trong các ví dụ sau, --no-ffluôn được sử dụng khi hợp nhất). Lưu ý rằng vì tôi không chắc có một giải pháp tốt hơn ( một cuộc tranh luận tồn tại ), tôi sẽ chỉ cung cấp cách cả hai lệnh hoạt động. Trong trường hợp của tôi, tôi thích sử dụng git rebasevì nó tạo ra một cây lịch sử đẹp hơn :)

Giữa các nhánh tính năng

git merge

Các lệnh:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- --------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Kết quả:

*   c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago)
| |\            Merge branch 'features/foo' into features/bar - Christophe
| * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | |           Fifth commit - Christophe
| * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | |           Fourth commit - Christophe
* | |   98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \ \            Merge branch 'features/foo' - Christophe
| |/ /
|/| /
| |/
| * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

Các lệnh:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git rebase features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Kết quả:

*   7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
| * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

Từ developmột nhánh tính năng

git merge

Các lệnh:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git merge --no-ff develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Kết quả:

*   9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago)
| |\            Merge branch 'develop' into features/bar - Christophe
| |/
|/|
* |   5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | |           Third commit - Christophe
| * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ /            Second commit - Christophe
| * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
* 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

Các lệnh:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git rebase develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Kết quả:

*   b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
*   856433e - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\            Merge branch 'features/foo' - Christophe
| * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

Ghi chú bên

git cherry-pick

Khi bạn chỉ cần một cam kết cụ thể, git cherry-picklà một giải pháp tốt ( -xtùy chọn sẽ thêm một dòng có nội dung " (cherry được chọn từ cam kết ...) " vào thân thông điệp cam kết ban đầu, vì vậy thường nên sử dụng nó - git log <commit_sha1>để xem nó):

Các lệnh:

Time           Branch "develop"              Branch "features/foo"                Branch "features/bar"
------- -------------------------------- ------------------------------- -----------------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git cherry-pick -x <second_commit_sha1>
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Kết quả:

*   50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago)
| |           Second commit - Christophe
| * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
|/|
| * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git pull --rebase

Tôi không chắc mình có thể giải thích điều đó tốt hơn Derek Gourlay ... Về cơ bản, hãy sử dụng git pull --rebasethay vì git pull:) Tuy nhiên, điều còn thiếu trong bài viết là bạn có thể bật nó theo mặc định :

git config --global pull.rebase true

git rerere

Một lần nữa, giải thích độc đáo ở đây . Nhưng nói một cách đơn giản, nếu bạn kích hoạt nó, bạn sẽ không phải giải quyết xung đột tương tự nhiều lần nữa.


15

Các Git Pro cuốn sách có một lời giải thích thực sự tốt trên trang rebasing .

Về cơ bản hợp nhất sẽ có hai cam kết và kết hợp chúng.

Một cuộc nổi loạn sẽ đi đến tổ tiên chung trên cả hai và tăng dần áp dụng các thay đổi lên nhau. Điều này làm cho một lịch sử 'sạch hơn' và tuyến tính hơn.

Nhưng khi bạn rebase, bạn từ bỏ các cam kết trước đó và tạo các cam kết mới. Vì vậy, bạn không bao giờ nên rebase một kho lưu trữ công khai. Những người khác làm việc trên kho sẽ ghét bạn.

Vì lý do đó một mình tôi gần như độc quyền hợp nhất. 99% thời gian các chi nhánh của tôi không khác nhau nhiều, vì vậy nếu có xung đột thì chỉ ở một hoặc hai nơi.


1
Sáp nhập không kết hợp các cam kết - đó sẽ là viết lại lịch sử. Rebase làm điều đó.
kellyfj

4

Git rebase được sử dụng để làm cho các đường dẫn phân nhánh trong lịch sử sạch hơn và cấu trúc kho lưu trữ tuyến tính.

Nó cũng được sử dụng để giữ cho các nhánh do bạn tạo riêng tư, vì sau khi khởi động lại và đẩy các thay đổi đến máy chủ, nếu bạn xóa chi nhánh của mình, sẽ không có bằng chứng nào về chi nhánh bạn đã làm việc. Vì vậy, chi nhánh của bạn bây giờ là mối quan tâm địa phương của bạn.

Sau khi thực hiện rebase, chúng tôi cũng loại bỏ một cam kết bổ sung mà chúng tôi đã sử dụng để xem liệu chúng tôi có hợp nhất bình thường không.

Và đúng vậy, người ta vẫn cần phải hợp nhất sau khi rebase thành công vì lệnh rebase chỉ đặt công việc của bạn lên trên nhánh bạn đã đề cập trong khi rebase, nói chủ và thực hiện cam kết đầu tiên của chi nhánh của bạn như là hậu duệ trực tiếp của nhánh chính . Điều này có nghĩa là bây giờ chúng ta có thể thực hiện hợp nhất chuyển tiếp nhanh để mang các thay đổi từ nhánh này sang nhánh chính.


4

Nếu bạn chỉ là một nhà phát triển, bạn có thể sử dụng rebase thay vì hợp nhất để có một lịch sử rõ ràng

nhập mô tả hình ảnh ở đây


3

Một số ví dụ thực tế, phần nào được kết nối với phát triển quy mô lớn, nơi Gerrit được sử dụng để xem xét và tích hợp phân phối:

Tôi hợp nhất khi tôi nâng cấp tính năng của mình lên một chủ từ xa mới. Điều này mang lại công việc nâng cao tối thiểu và thật dễ dàng để theo dõi lịch sử phát triển tính năng, ví dụ như gitk .

git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature

Tôi hợp nhất khi tôi chuẩn bị một cam kết giao hàng.

git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master

Tôi phản đối khi cam kết phân phối của tôi không tích hợp được vì bất kỳ lý do gì và tôi cần cập nhật nó theo hướng chủ mới.

git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master

3

Nó đã được giải thích nhiều lần rebase là gì và hợp nhất là gì, nhưng khi nào bạn nên sử dụng cái gì?

Khi nào bạn nên sử dụng rebase?

  • khi bạn chưa đẩy chi nhánh / không ai khác đang làm việc trên nó
  • bạn muốn toàn bộ lịch sử
  • bạn muốn tránh tất cả các thông báo cam kết "đã hợp nhất .." được tạo tự động

Khi Git rebase thay đổi lịch sử. Do đó, bạn không nên sử dụng nó khi người khác đang làm việc trên cùng một chi nhánh / nếu bạn đã đẩy nó. Nhưng nếu bạn có một nhánh cục bộ, bạn có thể thực hiện một master rebase hợp nhất trước khi hợp nhất lại nhánh của bạn thành master để giữ một lịch sử sạch hơn. Làm điều này, sau khi sáp nhập vào nhánh chính, bạn sẽ không thấy rằng bạn đã sử dụng một nhánh trong nhánh chính - lịch sử là "sạch hơn" vì bạn không tự động tạo ra "sáp nhập ..", nhưng vẫn có lịch sử đầy đủ trong nhánh chính của bạn mà không có các cam kết "được hợp nhất .." tự động.

Mặc dù vậy, hãy chắc chắn rằng bạn sử dụng git merge feature-branch --ff-onlyđể đảm bảo không có xung đột khi tạo một cam kết duy nhất khi bạn hợp nhất tính năng của bạn trở lại chính. Điều này thật thú vị nếu bạn đang sử dụng các nhánh tính năng cho mọi tác vụ bạn làm khi bạn nhận được lịch sử của nhánh tính năng, nhưng không phải là một cam kết "hợp nhất .."

Một kịch bản thứ hai sẽ là, nếu bạn phân nhánh từ một nhánh và muốn biết những gì đã thay đổi trong nhánh chính. Rebase cung cấp cho bạn thông tin vì nó bao gồm mọi cam kết.

Khi nào bạn nên sử dụng hợp nhất?

  • khi bạn đã đẩy chi nhánh / những người khác cũng đang làm việc trên nó
  • bạn không cần lịch sử đầy đủ
  • chỉ đơn giản là hợp nhất là đủ tốt cho bạn

Khi bạn không cần hoặc muốn có tất cả lịch sử của một nhánh tính năng trong nhánh chính của mình hoặc nếu những người khác đang làm việc trên cùng một nhánh / bạn đã đẩy nó. Nếu bạn vẫn muốn có lịch sử, chỉ cần hợp nhất chủ vào nhánh tính năng trước khi hợp nhất nhánh tính năng thành chủ. Điều này sẽ dẫn đến việc hợp nhất chuyển tiếp nhanh trong đó bạn có lịch sử của nhánh tính năng trong chủ của mình (bao gồm cả cam kết hợp nhất nằm trong nhánh tính năng của bạn vì bạn đã hợp nhất chủ vào nó).


-4

Khi nào tôi sử dụng git rebase? Hầu như không bao giờ, bởi vì nó viết lại lịch sử. git mergehầu như luôn luôn là lựa chọn tốt hơn, bởi vì nó tôn trọng những gì thực sự xảy ra trong dự án của bạn.


1
@benjaminhull Cảm ơn! Mối quan hệ Tôi hy vọng câu trả lời của tôi là dựa trên thực tế. Ý kiến ​​của IMHO có rất ít vị trí trong vấn đề này: thực tế là việc mất lịch sử thực tế của bạn khiến cuộc sống sau này trở nên khó khăn hơn.
Marnen Laibow-Koser

1
Đồng ý. Merge sẽ không bao giờ dẫn đến lịch sử hỏng vv (khi bạn rebase cam kết đẩy của bạn)
Surfrider
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.