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
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
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ước | Lệnh | Điều xảy ra |
|---|---|---|
| 1 | git rebase main | Rebase á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ũ. |
| 2 | git push --force origin feature | Bạ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 pull | Lị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ước | Lệnh | Điều xảy ra |
|---|---|---|
| 1 | git rebase main | feature 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. |
| 2 | git 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 pull | Họ 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ống | Nên dùng |
|---|---|
| Dọn nhánh tính năng cục bộ trước khi mở PR | rebase |
| Tích hợp nhánh đã public, nhiều người dùng | merge |
| 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ễ đọc | rebase 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
- Sau
git rebase main, các commit D và E củafeaturecó giữ nguyên hash không? - Vì sao không nên rebase commit đã push và được người khác dùng?
- Muốn lịch sử
mainthẳ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".