git rebase vs git merge: Khi nào nên dùng để Git Log sạch và lịch sử an toàn?
So sánh git rebase và git merge bằng sơ đồ, quy trình thực hành và quy tắc an toàn: khi nào giữ merge commit, khi nào rebase branch cá nhân và cách tạo Git Log dễ đọc.
ShanverseUpdated 10 min read— viewsLoading views
Git
Developer Tools
Một Git Log “sạch” không nhất thiết là log không có merge commit. Lịch sử tốt phải giúp team trả lời ba câu hỏi: thay đổi nào đã xảy ra, vì sao nó xảy ra và có thể hoàn tác an toàn hay không.
Bài này giúp bạn chọn giữa rebase và merge dựa trên bối cảnh, không chỉ dựa trên sở thích về biểu đồ commit.
Ví dụ ban đầu
Giả sử bạn tách feature/login từ main, sau đó cả hai branch đều có commit mới:
Mục tiêu là cập nhật feature branch với thay đổi mới từ main, hoặc tích hợp feature vào main
Comments
.
git merge làm gì?
Khi đứng trên feature/login và merge main:
Git có thể tạo merge commit M:
Merge commit có hai parent và ghi lại rằng hai dòng lịch sử đã được kết hợp.
Nếu merge feature vào main:
Kết quả có thể là:
Đặc điểm của merge
Không viết lại commit hiện có.
Giữ nguyên topology và thời điểm branch tách/gộp.
Phù hợp với branch đã chia sẻ.
Có thể tạo merge commit.
Dễ revert toàn bộ một feature qua merge commit nếu lịch sử được tổ chức tốt.
git rebase làm gì?
Khi đứng trên feature/login và rebase lên main:
Git lấy các commit riêng của feature (C, D) và phát lại chúng trên commit mới nhất của main:
C' và D' có nội dung tương ứng với C và D, nhưng là commit mới với hash mới. Commit cũ không còn nằm trên branch sau rebase.
Sau đó, merge vào main có thể fast-forward:
Kết quả:
Đặc điểm của rebase
Viết lại commit được phát lại.
Tạo lịch sử tuyến tính, dễ đọc theo thứ tự.
Có thể khiến conflict xuất hiện nhiều lần theo từng commit.
Phù hợp để làm sạch branch cá nhân trước khi chia sẻ hoặc merge.
Nguy hiểm nếu rewrite lịch sử mà người khác đang dựa vào.
So sánh nhanh
Quy tắc vàng
Không rebase các commit public mà người khác có thể đã checkout hoặc phát triển tiếp, trừ khi team đã thống nhất rõ việc rewrite lịch sử.
Vì sao? Sau rebase, bạn push lịch sử mới. Người khác vẫn có commit cũ và có thể phải xử lý divergence, duplicate commit hoặc conflict khó hiểu.
Khi nào nên dùng git merge?
1. Branch đã được chia sẻ
Nếu nhiều người cùng làm trên branch:
Merge giữ commit hash và không bắt mọi người đồng bộ lại lịch sử đã rewrite.
2. Muốn giữ bối cảnh feature
Một merge commit có thể thể hiện feature được tích hợp như một đơn vị:
--no-ff yêu cầu merge commit ngay cả khi fast-forward có thể thực hiện.
Điều này hữu ích khi:
Feature gồm nhiều commit liên quan.
Muốn revert cả feature dễ hơn.
Muốn log cho thấy ranh giới Pull Request.
3. Branch release hoặc integration
Các branch dài hạn như release, develop hoặc branch phối hợp nhiều team thường ưu tiên merge để không rewrite lịch sử chung.
4. Conflict phức tạp cần giải quyết một lần
Merge so sánh trạng thái cuối của hai branch và thường tạo một lượt conflict resolution. Rebase có thể lặp conflict khi nhiều commit chạm cùng vùng.
Khi nào nên dùng git rebase?
1. Cập nhật feature branch cá nhân
Bạn đang làm một mình trên branch và muốn đặt commit lên main mới nhất:
Kết quả giúp Pull Request dễ đọc hơn và tránh merge commit kiểu “Merge main into feature” lặp lại.
2. Làm sạch commit trước Pull Request
Interactive rebase cho phép sửa message, gộp, sắp xếp hoặc bỏ commit:
Danh sách có thể như:
Các action thường dùng:
pick: giữ commit.
reword: sửa message.
squash: gộp và chỉnh message.
fixup: gộp, bỏ message phụ.
drop: loại commit.
edit: dừng để sửa commit.
Chỉ làm việc này trên commit chưa được team dùng chung hoặc khi đã thống nhất rewrite.
3. Giữ lịch sử tuyến tính theo quy ước team
Một số team yêu cầu feature branch rebase lên main, sau đó fast-forward hoặc squash merge. Quy ước này giúp log ngắn, nhưng cần CI và review tốt để không mất bối cảnh.
Rebase branch đã push có được không?
Có thể, nếu branch thuộc riêng bạn và team biết lịch sử sẽ bị rewrite.
Sau rebase, push thường bị từ chối vì non-fast-forward. Dùng:
Không ưu tiên:
--force-with-lease kiểm tra remote chưa thay đổi ngoài điều bạn biết, giảm nguy cơ ghi đè commit mới của người khác.
Quy trình rebase an toàn
Quy trình merge an toàn
Conflict trong rebase khác merge thế nào?
Trong merge, bạn giải quyết conflict giữa hai đầu branch một lần cho merge commit.
Trong rebase, Git phát lại từng commit:
Sau khi sửa:
Conflict có thể xuất hiện lại ở commit tiếp theo.
Cẩn thận với ours và theirs khi rebase
Trong merge thông thường:
ours thường là branch hiện tại.
theirs là branch được merge.
Trong rebase, Git đang phát lại commit của bạn lên base mới, nên cách gọi có thể trái với trực giác. Đừng chọn toàn bộ một phía trước khi xem nội dung:
Fast-forward, no-fast-forward và squash merge
Fast-forward
Nếu main không phát triển thêm sau khi tách branch:
Git chỉ di chuyển con trỏ, không tạo merge commit.
No-fast-forward
Luôn tạo merge commit để giữ ranh giới feature.
Squash merge
Đưa toàn bộ thay đổi thành một commit trên branch đích. Lịch sử gọn nhưng không giữ quan hệ merge topology và các commit feature không trở thành ancestor của main.
Chiến lược phổ biến cho team
Chiến lược A: Rebase feature, merge fast-forward
Ưu điểm:
Lịch sử tuyến tính.
Dễ đọc git log --oneline.
Nhược điểm:
Cần rewrite branch feature.
Conflict có thể lặp.
Ít thể hiện ranh giới feature.
Chiến lược B: Merge commit cho mỗi Pull Request
Ưu điểm:
Giữ bối cảnh và topology.
Dễ thấy commit nào thuộc Pull Request.
Không rewrite commit feature.
Nhược điểm:
Graph nhiều nhánh hơn.
Có thể có nhiều merge commit nếu branch cập nhật liên tục bằng merge.
Chiến lược C: Squash merge Pull Request
Ưu điểm:
main gọn, một commit cho một thay đổi.
Commit WIP trên feature không làm rối lịch sử chính.
Nhược điểm:
Mất chi tiết commit feature trên main.
Khó dùng topology để theo dõi merge.
Đây là lựa chọn cân bằng phổ biến nếu team ưu tiên main dễ đọc hơn việc giữ từng commit phát triển.
“Git Log sạch” nên được định nghĩa thế nào?
Một lịch sử tốt thường có:
Commit message mô tả mục đích.
Mỗi commit build/test được hoặc ít nhất có ý nghĩa.
Không có hàng loạt commit “fix”, “update”, “final”.
Pull Request có phạm vi nhỏ.
Quy tắc merge nhất quán.
Có thể revert thay đổi mà không kéo theo phần không liên quan.
Không rewrite lịch sử chung ngoài quy ước.
Lịch sử tuyến tính nhưng toàn commit mơ hồ vẫn không sạch. Lịch sử có merge commit nhưng mỗi PR rõ ràng vẫn rất hữu ích.
Những sai lầm phổ biến
Rebase main lên feature branch
Bạn thường muốn rebase feature lên main:
Không đứng trên main rồi rebase lên feature nếu không chủ đích rewrite main.
Rebase branch dùng chung mà không thông báo
Điều này khiến branch của đồng đội diverge. Với lịch sử public, ưu tiên merge hoặc phối hợp rõ ràng.
Dùng git pull mà không biết strategy
Cấu hình rõ hành vi:
hoặc:
Hoặc yêu cầu chỉ fast-forward:
Đừng sao chép cấu hình nếu chưa hiểu workflow của team.
Force push sau rebase bằng --force
Ưu tiên:
Và kiểm tra:
Rebase chỉ để graph đẹp
Nếu lịch sử branch chứa quyết định cộng tác quan trọng hoặc branch đã dùng chung, việc giữ merge commit có thể giá trị hơn biểu đồ tuyến tính.
Ma trận quyết định
Khuyến nghị thực tế
Với phần lớn team nhỏ:
Mỗi thay đổi dùng một feature branch ngắn hạn.
Có thể rebase feature branch cá nhân lên origin/main trước khi merge.
Không rebase main, release branch hoặc branch chung.
Dùng Pull Request và CI.
Chọn một chính sách nhất quán:
Squash merge nếu muốn một commit/PR.
Merge commit nếu muốn giữ bối cảnh branch.
Rebase + fast-forward nếu team thực sự ưu tiên lịch sử tuyến tính.
Dùng --force-with-lease, không force push mù quáng.
Tổng kết
Merge giữ lịch sử thật, an toàn cho cộng tác và phù hợp với branch public.
Rebase viết lại commit để lịch sử tuyến tính, phù hợp với branch cá nhân.
Squash merge tạo một commit gọn trên main, phù hợp khi commit feature còn lộn xộn.
“Log sạch” đến từ commit tốt và quy ước nhất quán, không chỉ từ việc loại bỏ merge commit.
1# 1. Đảm bảo working tree sạch2gitstatus34# 2. Cập nhật remote refs5gitfetchorigin67# 3. Tạo backup branch nếu cần8gitbranchbackup/feature-login910# 4. Rebase11gitrebaseorigin/main1213# 5. Nếu conflict: sửa, stage, tiếp tục14gitstatus15gitaddpath/to/resolved-file16gitrebase--continue1718# 6. Nếu muốn hủy19gitrebase--abort2021# 7. Kiểm tra lịch sử và test22gitlog--oneline--graph--decorate-1523npmtest2425# 8. Nếu branch đã push26gitpush--force-with-lease
Codebash
1# 1. Cập nhật remote refs2gitfetchorigin34# 2. Chuyển tới branch nhận thay đổi5gitswitchmain6gitpull--ff-only78# 3. Merge feature9gitmerge--no-fffeature/login1011# 4. Nếu conflict: sửa, stage, hoàn tất12gitstatus13gitaddpath/to/resolved-file14gitmerge--continue1516# 5. Nếu muốn hủy17gitmerge--abort1819# 6. Kiểm tra và push20npmtest21gitlog--oneline--graph--decorate-1522gitpush
Hướng dẫn Git Aliases và .gitconfig thực chiến: alias an toàn, log đẹp, cấu hình pull/push/rebase, includeIf cho tài khoản work/personal, bảo mật và chia sẻ dotfiles.
Phân tích Gitflow vs GitHub Flow vs Trunk-Based Development: cấu trúc branch, ưu nhược điểm, điều kiện áp dụng, ma trận quyết định và lộ trình chuyển đổi cho từng loại dự án.
Hướng dẫn git worktree từ cơ bản đến thực tế: tạo nhiều working tree, làm feature và hotfix song song, review PR, quản lý list/remove/prune/lock/repair và tránh lỗi phổ biến.