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ứ?
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ứ?
Câu trả lời:
Vậy khi nào bạn sử dụng một trong hai?
init
một repo mới, add
tệ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 master
và merge 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.
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 master
chi nhánh. Bạn chỉ có thể chuyển sang master
và hợp nhất cool-feature
chi 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.
but this way a new dummy commit is added, if you want to avoid spaghetti-history
- thế nào là xấu?
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 đó. "
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í.
Để 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 Y
công việc của chi nhánh B
mà 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 B
có 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 đó B
có 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 B
lê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 B
thô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ỏ
chúng tôi nhận được bằng cách nổi loạn:
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-feature
lên master
nếu bạn muốn duy trì các cam kết được thực hiện trongnew-feature
chi nhánh).
Vì thế:
master
.Nếu bạn có bất kỳ nghi ngờ, sử dụng hợp nhất.
Sự khác biệt duy nhất giữa rebase và hợp nhất là:
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ó 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.
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.
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ì.
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.
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.
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 --rebase
cho tất cả các lần kéo).
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.
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.
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.
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.
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.
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ủ:
Ngược lại, hợp nhất một nhánh chủ đề thành chủ:
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.
git merge
hỗ trợ --no-ff
tùy chọn buộc nó thực hiện một cam kết hợp nhất.
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:
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.
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ử.
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.
Để 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.
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.
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.
Chúng tôi sẽ làm điều này trong ba bước
git rebase branch-A // Synchronises the history with branch-A
git checkout branch-A // Change the current branch to branch-A
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".
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.
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.
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 rebase
cũng là một đọc tốt trong cùng một bài.
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 .
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 đó .
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 danh là git 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 merge
và git merge --no-ff
đầu tiên (bạn thường muốn sử dụng git merge --no-ff
vì 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 rebase
là không bao giờ sử dụng nó trên các chi nhánh công cộng .
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 merge
vs chỉ git rebase
áp dụng cho các nhánh tính năng (trong các ví dụ sau, --no-ff
luô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 rebase
vì nó tạo ra một cây lịch sử đẹp hơn :)
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
develop
một nhánh tính nănggit 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
git cherry-pick
Khi bạn chỉ cần một cam kết cụ thể, git cherry-pick
là một giải pháp tốt ( -x
tù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 --rebase
thay 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.
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.
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.
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
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 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 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ó).
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 merge
hầ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.