Merge và rebase

Xem thử

Vấn đề cần giải quyết

Nhánh feature của bạn đã tách khỏi main hai tuần, và main đã đi tiếp. Trước khi mở Pull Request, bạn phải đưa thay đổi của main vào feature. Có hai cách: merge và rebase. Chúng cho cùng một nội dung cuối, nhưng để lại hai hình dạng lịch sử rất khác nhau, và một trong hai có thể làm hỏng công việc của đồng nghiệp nếu dùng sai chỗ.

Sau bài này, bạn có thể

  • Mô tả lịch sử sau khi merge và sau khi rebase từ cùng một tình huống.
  • Phát biểu quy tắc vàng của rebase và giải thích vì sao nó tồn tại.
  • Chọn merge hay rebase theo tình huống cụ thể.

Tiên quyết: các bài git merge và git rebase ở chương trước.

Merge: thêm một commit hợp nhất

bash
git switch feature
git merge main

Merge tạo một commit hợp nhất nối hai nhánh. Ưu điểm: không viết lại lịch sử, nên an toàn tuyệt đối. Nhược điểm: lịch sử có nhiều commit hợp nhất, và đồ thị trở nên rối khi main cập nhật liên tục.

Cách đọc hình. Hai dòng cùng tách từ B: dòng có C là main, dòng có D và E là feature. Sau git merge main, Git tạo M có hai cha là E và C. Lịch sử vẫn hiện rõ chỗ rẽ nhánh và chỗ gộp, và D, E giữ nguyên hash. Merge không viết lại gì, nó chỉ thêm một commit.

Rebase: lịch sử thẳng một đường

bash
git switch feature
git rebase main

Rebase dời commit của feature lên đỉnh của main, cho lịch sử phẳng, sạch. Đổi lại, rebase tạo lại các commit với hash mới.

Cách đọc hình. Cùng tình huống, nhưng chạy git rebase main. Git áp lại D và E lên trên C, tạo D' và E' với hash mới. Lịch sử thành một đường thẳng và không có commit hợp nhất. Dễ nhầm: D' và E' có cùng thay đổi với D và E nhưng là những commit khác, còn D và E cũ không còn thuộc feature.

Quy tắc vàng của rebase

Đừng rebase commit đã được chia sẻ công khai. Nếu người khác đã dựa trên các commit đó, rebase sẽ khiến lịch sử của họ và của bạn không còn khớp nhau.

Thử và quan sát

Hãy dự đoán: bạn rebase nhánh feature đã push rồi force push, trong khi đồng nghiệp đã làm thêm một commit trên D cũ. Lần pull tiếp theo của họ sẽ ra sao? Chạy mô phỏng, rồi bấm biến thể rebase nhánh chưa push.

Đang tải Animation…

Bảng bước (bản đọc tĩnh của mô phỏng)

BướcLệnhĐiều xảy ra
1git rebase mainRebase áp lại D lên trên C thành D' với hash mới. Trên máy bạn mọi thứ trông gọn, nhưng remote vẫn giữ D cũ.
2git push --force origin featureBạn force push để thay D bằng D' trên remote. Công việc của đồng nghiệp vẫn nằm trên D cũ, không còn chỗ nào trong lịch sử chung.
3(đồng nghiệp) git pullLịch sử của họ có D và F, remote có D phía trên C. Hai bên có hai phiên bản của cùng một công việc, nên pull sinh xung đột hoặc commit trùng lặp.

Biến thể: Rebase nhánh chưa push

BướcLệnhĐiều xảy ra
1git rebase mainfeature chỉ nằm trên máy bạn nên viết lại thoải mái. D thành D' trên đỉnh main.
2git push -u origin featureĐẩy lần đầu nên không cần force. Remote chỉ nhận D' và không có D cũ nào cần thay.
3(đồng nghiệp) git pullHọ nhận D' sạch sẽ, không có gì để xung đột.

Giải đáp. Đồng nghiệp vẫn giữ D cũ cộng commit F của họ, trong khi remote giờ có D'. Hai bên có hai lịch sử cho cùng một công việc, nên pull gây xung đột hoặc commit trùng lặp. Ở biến thể, nhánh chưa ai khác dựa vào nên rebase an toàn và push lần đầu không cần force.

Chọn cái nào

Tình huốngNên dùng
Dọn nhánh tính năng cục bộ trước khi mở PRrebase
Tích hợp nhánh đã public, nhiều người dùngmerge
Muốn giữ dấu vết "tính năng này được gộp lúc nào"merge --no-ff
Muốn lịch sử main phẳng, dễ đọcrebase rồi merge fast-forward

Không có cách nào đúng cho mọi trường hợp. Nhiều đội theo quy ước: rebase nhánh riêng của bạn, merge khi đưa vào nhánh chung.

Hiểu lầm thường gặp

  • "Rebase tốt hơn merge vì lịch sử đẹp hơn." Đẹp hơn nhưng có giá: viết lại lịch sử và rủi ro khi đã chia sẻ.
  • "Merge làm lịch sử bẩn nên không nên dùng." Commit hợp nhất ghi lại sự thật về thời điểm gộp, và an toàn với nhánh chung.
  • "Force push sau rebase là bình thường." Chỉ chấp nhận trên nhánh riêng; trên nhánh chung nó làm mất công việc người khác.

Tự kiểm tra

  1. Sau git rebase main, các commit D và E của feature có giữ nguyên hash không?
  2. Vì sao không nên rebase commit đã push và được người khác dùng?
  3. Muốn lịch sử main thẳng nhưng nhánh tính năng đã được review, bạn làm thế nào?

Đáp án gợi ý. (1) Không: chúng thành D' và E' với hash mới. (2) Người khác vẫn giữ commit cũ nên hai lịch sử rẽ nhau. (3) Rebase nhánh lên main rồi merge fast-forward.

Điều cần nhớ

  • Merge thêm một commit hợp nhất và không viết lại lịch sử; rebase tạo commit mới với hash mới.
  • Không rebase commit đã chia sẻ.
  • Chọn theo tình huống: rebase nhánh riêng, merge khi đưa vào nhánh chung.
  • Bài sau: reset, checkout và revert.

Nguồn tham khảo

Pro Git, chương 3.6 "Rebasing" (git-scm.com/book); tài liệu Atlassian "Merging vs. Rebasing".