Trong thế giới lập trình với Git, git merge
giống như một ngã tư đường, nơi các nhánh lịch sử giao nhau và tạo ra một điểm chung mới. Nó an toàn, dễ hiểu và ghi lại mọi thứ một cách trung thực. Nhưng đôi khi, sự trung thực đó lại tạo ra một lịch sử commit lộn xộn, phức tạp và khó theo dõi.
Thêm một phép so sánh nhỏ, nếu git merge
là một nhà sử học cần mẫn, thì git rebase
chính là một đạo diễn tài ba, cho phép bạn "cắt, ghép, sắp xếp" lại các cảnh phim (commit) để tạo ra một câu chuyện (lịch sử commit) liền mạch, sạch sẽ và dễ hiểu hơn.
Bài viết này sẽ giúp bạn làm chủ công cụ mạnh mẽ git rebase
.
1. Hiểu cốt lõi vấn đề: Git Rebase là gì? 🌿
Về cơ bản, Git Rebase là quá trình di chuyển hoặc kết hợp một chuỗi các commit sang một commit cơ sở (base) mới.
Hãy tưởng tượng bạn có một nhánh chính (main
) và bạn tạo ra một nhánh mới (feature
) để phát triển tính năng. Trong lúc bạn đang làm việc trên feature
, nhánh main
đã có thêm những commit mới từ các thành viên khác trong nhóm.
Lúc này, lịch sử của bạn trông như một cái cây rẽ nhánh. Bạn có hai lựa chọn để cập nhật nhánh feature
của mình với những thay đổi mới nhất từ main
:
git merge main
: Git sẽ tạo ra một "merge commit" mới trên nhánhfeature
. Commit này có hai "cha": commit cuối cùng củafeature
và commit cuối cùng củamain
. Nó giống như việc bạn buộc hai cành cây lại với nhau. Lịch sử sẽ ghi lại chính xác thời điểm hợp nhất này.git rebase main
: Đây là lúc phép màu xảy ra. Thay vì tạo merge commit, Rebase sẽ:- Tạm thời "nhấc" tất cả các commit mà bạn đã tạo trên nhánh
feature
ra. - Di chuyển con trỏ của nhánh
feature
đến commit mới nhất củamain
. - Áp dụng lại (replay) từng commit bạn đã "nhấc" ra lên trên đỉnh của
main
.
- Tạm thời "nhấc" tất cả các commit mà bạn đã tạo trên nhánh
Kết quả? Lịch sử commit trên nhánh feature
của bạn giờ đây trông như thể bạn vừa mới tạo ra nó từ commit mới nhất của main
. Nó trở thành một đường thẳng tắp, sạch sẽ và liền mạch.
Phép so sánh dễ hiểu: Hãy coi
main
là thân cây chính. Nhánhfeature
của bạn mọc ra từ điểm A trên thân cây. Khi thân cây mọc cao thêm đến điểm B,rebase
giống như việc bạn cẩn thận "bứng" cả nhánhfeature
của mình và "ghép" nó vào điểm B. Nhánh của bạn vẫn giữ nguyên các lá (commit) nhưng giờ đây nó mọc ra từ vị trí mới nhất của thân cây.
2. Tại sao phải dùng Git Rebase? 🚀
Việc giữ một lịch sử commit "thẳng và sạch" không chỉ là vấn đề thẩm mỹ. Nó mang lại những lợi ích vô cùng thực tế:
- Dễ đọc và dễ hiểu: Một lịch sử tuyến tính giúp bất kỳ ai (kể cả bạn trong tương lai) cũng có thể dễ dàng đọc và hiểu dòng chảy phát triển của dự án mà không bị rối bởi hàng tá merge commit vô nghĩa.
- Debug hiệu quả hơn: Khi cần tìm một bug, các công cụ như
git bisect
hoạt động hiệu quả hơn rất nhiều trên một lịch sử tuyến tính. - Code review dễ dàng hơn: Khi bạn tạo một Pull Request (Merge Request), người review sẽ thấy một chuỗi commit gọn gàng, mỗi commit thực hiện một thay đổi logic. Điều này giúp họ dễ dàng theo dõi và góp ý hơn.
- Loại bỏ "nhiễu": Rebase giúp loại bỏ các commit không cần thiết như "Merge branch 'main' into feature".
3. Cách sử dụng Git Rebase cơ bản
Kịch bản phổ biến nhất là cập nhật nhánh feature
của bạn với những thay đổi mới nhất từ nhánh main
.
Các bước thực hiện:
- Đầu tiên, đảm bảo nhánh
main
cục bộ của bạn được cập nhật:
git checkout main
git pull origin main
- Chuyển về nhánh
feature
của bạn:
git checkout feature
- Thực hiện rebase:
git rebase main
Xử lý xung đột (Conflict):
Trong quá trình rebase, Git sẽ áp dụng lại từng commit của bạn. Nếu một trong các commit đó thay đổi cùng một dòng code mà commit trên main
cũng đã thay đổi, xung đột sẽ xảy ra.
Đừng hoảng sợ! Git sẽ tạm dừng quá trình rebase và cho bạn biết file nào đang bị xung đột.
- Mở các file bị xung đột và sửa chúng. Bạn cần xóa các dấu
<<<<<<<
,=======
,>>>>>>>
và chọn phiên bản code cuối cùng mà bạn muốn giữ lại. - Sau khi sửa xong, hãy thêm các file đã sửa vào staging area:
git add <tên-file-đã-sửa>
- Tiếp tục quá trình rebase:
git rebase --continue
- Nếu bạn cảm thấy quá rối và muốn quay lại trạng thái trước khi rebase, hãy dùng lệnh:
git rebase --abort
4. Sức mạnh "tối thượng": Git Interactive Rebase 💡
Đây mới chính là "viên ngọc quý" của rebase. Rebase tương tác cho phép bạn toàn quyền chỉnh sửa chuỗi commit của mình trước khi áp dụng chúng.
Lệnh cơ bản: git rebase -i <commit-cơ-sở>
Ví dụ, bạn muốn dọn dẹp 5 commit gần nhất trên nhánh của mình. Bạn có thể dùng HEAD~5
:
git rebase -i HEAD~5
Một file văn bản sẽ mở ra trong editor của bạn, liệt kê 5 commit đó, mỗi commit bắt đầu bằng từ pick
.
pick a1b2c3d Thêm tính năng A - phần 1
pick f4e5d6c Sửa lỗi nhỏ
pick 9h8g7f6 Thêm test cho A
pick c1b2a3d Fix typo
pick e5f6g7h Hoàn thiện tính năng A
Bây giờ bạn có thể thay đổi pick
thành các lệnh khác để "viết lại lịch sử":
reword
(hoặcr
): Giữ nguyên commit nhưng sửa lại commit message.edit
(hoặce
): Dừng lại ở commit này để bạn có thể sửa code (ví dụ:git commit --amend
).squash
(hoặcs
): Gộp commit này vào commit phía trên nó. Git sẽ mở một editor mới để bạn viết lại commit message cho commit đã gộp. Đây là lệnh cực kỳ hữu ích để gộp các commit nhỏ, vụn vặt ("fix typo", "wip",...) thành một commit lớn có ý nghĩa.fixup
(hoặcf
): Tương tựsquash
, nhưng sẽ loại bỏ commit message của commit này và chỉ giữ lại message của commit phía trên.drop
(hoặcd
): Xóa hoàn toàn commit này.- Bạn cũng có thể thay đổi thứ tự các dòng để sắp xếp lại các commit.
Ví dụ dọn dẹp:
pick a1b2c3d Thêm tính năng A - phần 1
squash 9h8g7f6 Thêm test cho A # Gộp commit test vào commit "phần 1"
squash e5f6g7h Hoàn thiện tính năng A # Gộp nốt commit hoàn thiện vào
drop f4e5d6c Sửa lỗi nhỏ # Bỏ commit sửa lỗi không cần thiết
drop c1b2a3d Fix typo # Bỏ luôn commit sửa typo
Sau khi lưu và đóng file, Git sẽ thực hiện các hành động trên. Kết quả là từ 5 commit lộn xộn, bạn chỉ còn lại 1 commit duy nhất, sạch sẽ với một message hoàn chỉnh: "Thêm tính năng A".
5. Quy tắc vàng của Rebase ⚠️
Rebase là một công cụ thay đổi lịch sử. Điều này mang lại một quy tắc tối quan trọng, bất khả xâm phạm:
TUYỆT ĐỐI KHÔNG REBASE TRÊN CÁC NHÁNH ĐÃ ĐƯỢC CHIA SẺ (PUBLIC/SHARED BRANCHES).
Các nhánh như main
, develop
, hoặc bất kỳ nhánh nào mà các thành viên khác trong nhóm đang làm việc dựa trên đó đều là bất khả xâm phạm.
Tại sao? Khi bạn rebase, bạn đang tạo ra các commit hoàn toàn mới (dù nội dung giống hệt) và xóa đi các commit cũ. Nếu người khác đã clone hoặc pull nhánh cũ đó, lịch sử của họ và của bạn sẽ bị phân kỳ. Khi họ cố gắng pull code mới, Git sẽ bị "bối rối", gây ra xung đột cực lớn và có thể tạo ra các commit trùng lặp.
Quy tắc đơn giản: Chỉ rebase trên các nhánh cục bộ, của riêng bạn, mà bạn chưa đẩy lên remote hoặc chưa ai khác làm việc cùng.
Khi nào dùng Rebase, khi nào dùng Merge? 🏆
Đây là câu hỏi muôn thuở, và câu trả lời phụ thuộc vào quy trình làm việc của nhóm bạn. Tuy nhiên, một quy trình phổ biến và hiệu quả là:
-
Dùng Rebase khi:
- Cập nhật nhánh feature cá nhân của bạn với những thay đổi từ nhánh chính (
main
/develop
). - Dọn dẹp lịch sử commit cục bộ của bạn trước khi tạo Pull Request.
- Cập nhật nhánh feature cá nhân của bạn với những thay đổi từ nhánh chính (
-
Dùng Merge khi:
- Hợp nhất một nhánh feature đã hoàn thành và được review vào nhánh chính (
main
/develop
). Việc này tạo ra một merge commit, ghi lại dấu vết rõ ràng rằng "tính năng X đã được hợp nhất tại thời điểm Y".
- Hợp nhất một nhánh feature đã hoàn thành và được review vào nhánh chính (
Nói cách khác: Rebase để giữ lịch sử bên trong nhánh feature sạch sẽ, và Merge để hợp nhất kết quả cuối cùng của nhánh feature vào lịch sử chung.
Kết luận: Git Rebase không hề đáng sợ
Bạn thấy đó, git rebase
không hề đáng sợ như lời đồn! Nó là một kỹ năng nâng cao giúp bạn từ một người "biết dùng Git" trở thành một người "làm chủ Git". Bằng cách giữ cho lịch sử commit luôn sạch sẽ, tuyến tính và có ý nghĩa, bạn không chỉ giúp bản thân mà còn giúp cả nhóm làm việc hiệu quả hơn, ít lỗi hơn và dễ dàng bảo trì dự án trong dài hạn.
Hãy bắt đầu thực hành với các nhánh cá nhân của bạn, và bạn sẽ nhanh chóng nhận ra sức mạnh của việc "viết lại lịch sử" một cách có chủ đích.