Trong git, sự khác biệt giữa merge --squash và rebase là gì?


363

Tôi mới sử dụng git và tôi đang cố gắng tìm hiểu sự khác biệt giữa squash và rebase. Theo tôi hiểu, bạn thực hiện một squash khi thực hiện rebase.

Câu trả lời:


360

Cả hai git merge --squashgit rebase --interactivecó thể tạo ra một cam kết "đè bẹp".
Nhưng họ phục vụ các mục đích khác nhau.

sẽ tạo ra một cam kết bị đè bẹp trên nhánh đích, mà không đánh dấu bất kỳ mối quan hệ hợp nhất nào.
(Lưu ý: nó không tạo ra một cam kết ngay lập tức: bạn cần một bổ sung git commit -m "squash branch")
Điều này rất hữu ích nếu bạn muốn loại bỏ hoàn toàn nhánh nguồn, đi từ (lược đồ lấy từ câu hỏi SO ):

 git checkout stable

      X                   stable
     /                   
a---b---c---d---e---f---g tmp

đến:

git merge --squash tmp
git commit -m "squash tmp"

      X-------------------G stable
     /                   
a---b---c---d---e---f---g tmp

và sau đó xóa tmpchi nhánh.


Lưu ý: git mergecó một --committùy chọn , nhưng nó không thể được sử dụng với --squash. Nó không bao giờ có thể sử dụng --commit--squashcùng nhau.
Kể từ Git 2.22.1 (quý 3 năm 2019), tính không tương thích này được thể hiện rõ ràng:

Xem cam kết 1d14d0c (24 tháng 5 năm 2019) của Vishal Verma ( reloadbrain) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 33f2790 , ngày 25 tháng 7 năm 2019)

merge: từ chối --commitvới--squash

Trước đây, khi --squashđược cung cấp, ' option_commit' đã âm thầm giảm xuống. Điều này có thể gây ngạc nhiên cho người dùng đã cố gắng ghi đè hành vi không cam kết của squash bằng cách sử dụng --commitrõ ràng.

git/git builtin/merge.c#cmd_merge() bây giờ bao gồm:

if (option_commit > 0)
    die(_("You cannot combine --squash with --commit."));

phát lại một số hoặc tất cả các cam kết của bạn trên một cơ sở mới, cho phép bạn xóa sổ (hoặc gần đây là "sửa lỗi", xem câu hỏi SO này ), trực tiếp đến:

git checkout tmp
git rebase -i stable

      stable
      X-------------------G tmp
     /                     
a---b

Nếu bạn chọn đè bẹp tất cả các cam kết của tmp(nhưng ngược lại merge --squash, bạn có thể chọn phát lại một số và xóa các số khác).

Vì vậy, sự khác biệt là:

  • squashkhông chạm vào nhánh nguồn của bạn ( tmpở đây) và tạo một cam kết duy nhất ở nơi bạn muốn.
  • rebasecho phép bạn tiếp tục trên cùng một nhánh nguồn (tĩnh tmp) với:
    • một căn cứ mới
    • một lịch sử sạch hơn

11
Gđược c--d--e--f--gép lại với nhau?
Wayne Conrad

8
@Wayne: có, G trong các ví dụ đó đại diện cho các tmpcam kết bị đè bẹp với nhau.
VonC

3
@ Th4wn: Vì lý do Git với ảnh chụp nhanh của tất cả các dự án, Gsẽ không đại diện cho cùng một nội dung hơn g, vì những thay đổi được giới thiệu bởi X.
VonC

1
@VonC: không chắc chắn về nhận xét cuối cùng đó. Nếu bạn có một git merge --no-ff tempthay thế git merge --squash temp, sau đó bạn có được một lịch sử lộn xộn, nhưng bạn cũng có thể làm những việc như thế git revert e, dễ dàng hơn nhiều. Đó là một lịch sử lộn xộn, nhưng trung thực và thực dụng, và chi nhánh chính vẫn còn khá sạch sẽ.
ness101

2
@ naught101 Tôi đồng ý. Như đã giải thích trong stackoverflow.com/a/7425751/6309 , nó cũng không bị phá vỡ git bisecthoặc git blamekhi được sử dụng quá thường xuyên (như tronggit pull --no-ff : stackoverflow.com/questions/12798767/ .) Dù sao cũng không có một cách tiếp cận nào, đó là lý do tại sao bài viết này mô tả ba ( stackoverflow.com/questions/9107861/iêu )
VonC

183

Hợp nhất cam kết: giữ lại tất cả các cam kết trong chi nhánh của bạn và xen kẽ chúng với các cam kết trên nhánh cơ sởnhập mô tả hình ảnh ở đây

Hợp nhất Squash: giữ lại các thay đổi nhưng bỏ qua các xác nhận riêng lẻ từ lịch sử nhập mô tả hình ảnh ở đây

Rebase: Điều này di chuyển toàn bộ nhánh tính năng để bắt đầu trên đỉnh của nhánh chính, kết hợp hiệu quả tất cả các cam kết mới trong master

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

Thêm vào đây


81

Hợp nhất squash hợp nhất một cây (một chuỗi các xác nhận) thành một cam kết duy nhất. Đó là, nó đè bẹp tất cả những thay đổi được thực hiện trong n cam kết thành một cam kết duy nhất.

Rebasing là căn cứ lại, nghĩa là chọn một cơ sở mới (cam kết cha mẹ) cho một cây. Có lẽ thuật ngữ đồng bóng cho điều này rõ ràng hơn: họ gọi nó là cấy ghép bởi vì nó chỉ là: chọn một mặt bằng mới (cam kết gốc, gốc) cho một cây.

Khi thực hiện một rebase tương tác, bạn được cung cấp tùy chọn để squash, chọn, chỉnh sửa hoặc bỏ qua các cam kết mà bạn sẽ bắt đầu.

Hy vọng rằng đã rõ ràng!


7
Khi nào tôi nên rebase và khi nào tôi nên squash?
Martin Thoma

31

Hãy bắt đầu bằng ví dụ sau:

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

Bây giờ chúng tôi có 3 tùy chọn để hợp nhất các thay đổi của nhánh tính năng thành nhánh chính :

  1. Hợp nhất cam kết
    Sẽ giữ tất cả các cam kết lịch sử của nhánh tính năng và di chuyển chúng vào nhánh chính
    Sẽ thêm cam kết giả.

  2. Rebase và merge
    Sẽ nối thêm tất cả các cam kết lịch sử của nhánh tính năng ở phía trước của nhánh chính
    sẽ KHÔNG thêm cam kết giả.

  3. Bóp và hợp nhất
    Sẽ nhóm tất cả các cam kết nhánh tính năng thành một cam kết sau đó nối nó ở phía trước nhánh chính
    Sẽ thêm cam kết giả.

Bạn có thể tìm thấy bên dưới cách nhánh chính sẽ chăm sóc từng người trong số họ.

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

Trong mọi trường hợp:
Chúng ta có thể XÓA một cách an toàn nhánh tính năng .


1
Bạn có thể giải thích cam kết giả trong hình ảnh thứ 2 là gì không ?? Tôi là người mới bắt đầu trong git.
Yusuf

1
@Yusuf, đó chỉ là một cam kết bổ sung có chứa cả hai bản cập nhật chi nhánh, đó là thông báo cam kết mặc định = "Megre chi nhánh XYZ thành chủ"
ahmednabil88
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.