git rebase
Xem thửVấn đề cần giải quyết
Nhánh feature của bạn tách từ main cách đây một tuần. Từ đó main đã có thêm commit mới. Nếu merge, lịch sử có thêm một commit hợp nhất và các đường rẽ nhánh. Nhiều đội muốn một lịch sử thẳng, như thể bạn bắt đầu làm việc từ commit mới nhất của main. git rebase làm được điều đó, đổi lại bạn phải hiểu nó tạo commit mới.
Sau bài này, bạn có thể
- Giải thích rebase tạo bản sao mới của commit trên nền mới và vì sao hash đổi.
- So sánh kết quả của merge và rebase.
- Dùng
git rebase -iđể gộp, đổi tên và bỏ commit, và xử lý xung đột khi rebase.
Tiên quyết: bài Tổng quan viết lại lịch sử và khái niệm nhánh, merge.
Merge và rebase cho hai hình dạng khác nhau
Cách đọc hình. Cả hai panel xuất phát từ cùng một tình huống: feature tách từ B, còn main đã đi tiếp tới C. Panel trên là merge: Git thêm commit hợp nhất M có hai cha là C và E, lịch sử giữ hình dạng rẽ nhánh và D, E giữ nguyên hash. Panel dưới là rebase: Git tạo D' và E' là bản sao mới của D và E nằm sau C, nên lịch sử là một đường thẳng, nhưng D' và E' có hash khác D và E.
Thử và quan sát: rebase từng bước
Hãy dự đoán: sau git rebase main trên feature, commit D và E có giữ nguyên hash không? Chạy mô phỏng và quan sát từng commit được áp lại, rồi thử biến thể gặp xung đột.
Đ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 | Git áp lại D lên đỉnh main thành D' với hash mới. HEAD tạm tách ra trong lúc rebase. |
| 2 | (tiếp tục áp lại) | Tiếp theo E được áp lại thành E' trên D'. |
| 3 | (hoàn tất) | Nhánh feature chuyển sang E'. D và E cũ không còn nhánh nào trỏ tới (nét đứt). main không đổi. |
| 4 | git switch main && git merge feature | Vì lịch sử đã thẳng, merge chỉ là fast-forward: main trượt tới E, không có commit hợp nhất. |
Biến thể: Rebase gặp xung đột rồi hủy
| Bước | Lệnh | Điều xảy ra |
|---|---|---|
| 1 | git rebase main | D sửa cùng vùng với C trên main, nên Git không áp tự động được và dừng lại giữa chừng. |
| 2 | git status | Git cho biết đang ở giữa rebase và gợi ý ba lối đi: sửa rồi tiếp tục, bỏ commit này, hoặc hủy cả rebase. |
| 3 | git rebase --abort | Hủy toàn bộ: feature trở về đúng trạng thái trước khi rebase, không có gì bị mất. |
Giải đáp. Không. Rebase không di chuyển commit: nó áp lại thay đổi của D và E lên đỉnh main để tạo D' và E' với hash mới, rồi cho feature trỏ tới E'. D và E cũ không còn nhánh nào trỏ tới. Vì lịch sử đã thẳng, merge feature vào main chỉ là fast-forward.
git switch feature
git rebase main
Khi gặp xung đột
Nếu một commit không áp sạch, rebase dừng giữa chừng. Bạn có ba lối đi.
Đang tải sơ đồ…
Cách đọc hình. Rebase bắt đầu ở trạng thái Rebasing (lệnh git rebase main) và áp từng commit. Nếu một commit không áp sạch, Git dừng ở Conflict. Từ đó bạn sửa tệp, git add rồi git rebase --continue để quay lại Rebasing, hoặc git rebase --abort để trở về trạng thái ban đầu (Original). Khi mọi commit áp xong, rebase kết thúc ở Done.
git add <tệp>
git rebase --continue
git rebase --abort
--abort luôn là lối thoát an toàn khi bạn bối rối.
Rebase tương tác
git rebase -i HEAD~4
Trình soạn thảo mở ra bốn commit gần nhất, mỗi dòng một lệnh:
| Lệnh | Tác dụng |
|---|---|
pick | Giữ nguyên commit |
reword | Giữ nội dung, đổi thông điệp |
squash | Gộp vào commit đứng trước, hợp nhất thông điệp |
fixup | Gộp vào commit đứng trước, bỏ thông điệp |
edit | Dừng lại để bạn sửa commit |
drop | Bỏ hẳn commit |
Hãy dự đoán: bạn có bốn commit, trong đó một commit sửa chính tả và một commit thử nghiệm. Làm sao gộp, đổi thông điệp và bỏ chúng bằng một lần rebase?
Đ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 -i HEAD~4 | Git mở trình soạn thảo với bốn commit gần nhất, mỗi dòng mặc định là pick (giữ nguyên). |
| 2 | (sửa dòng 2: squash) | squash gộp commit này vào commit đứng trước nó, nên b4c5 sẽ nhập vào a1b2. |
| 3 | (sửa dòng 3: reword) | reword giữ nguyên nội dung nhưng cho bạn đổi thông điệp. |
| 4 | (sửa dòng 4: drop) | drop bỏ hẳn commit thử nghiệm. |
| 5 | (lưu và đóng trình soạn thảo) | Git áp lại theo danh sách: a1b2 và b4c5 thành A', c7d8 thành C' với thông điệp mới, d0e1 bị bỏ. Bốn commit thành hai, tất cả có hash mới. |
Biến thể: Dùng fixup tự động
| Bước | Lệnh | Điều xảy ra |
|---|---|---|
| 1 | git commit --fixup=a1b2 | Bạn nhận ra lỗi nhỏ thuộc về commit a1b2 và tạo một commit fixup trỏ tới nó. |
| 2 | git rebase -i --autosquash HEAD~5 | Cờ --autosquash tự sắp xếp commit fixup đứng ngay sau commit mục tiêu và đặt sẵn lệnh fixup. |
| 3 | (lưu và đóng trình soạn thảo) | fixup gộp sửa lỗi vào a1b2 mà không giữ thông điệp riêng. Kết quả sạch hơn squash vì không phải viết lại thông điệp. |
Giải đáp. Đổi dòng thứ hai thành squash, dòng thứ ba thành reword, dòng thứ tư thành drop, rồi lưu. Bốn commit thành hai, và cả hai đều là commit mới với hash mới. Biến thể cho thấy --fixup cùng --autosquash để sửa lỗi thuộc về một commit cũ mà không phải viết lại thông điệp.
Quy tắc vàng của rebase
Đừng bao giờ rebase một nhánh đã được chia sẻ công khai. Rebase viết lại hash và làm lệch bản sao của người khác. Chỉ rebase các commit chưa rời khỏi máy bạn.
Hiểu lầm thường gặp
- "Rebase di chuyển commit." Rebase tạo commit mới; commit cũ chỉ còn trong reflog.
- "Rebase luôn tốt hơn merge." Rebase cho lịch sử sạch nhưng viết lại hash; merge giữ nguyên lịch sử thật. Mỗi đội có quy ước riêng.
- "Gặp xung đột khi rebase là hỏng rồi." Bạn sửa xung đột rồi
--continue, hoặc--abortđể quay lại, không mất gì.
Tự kiểm tra
- Vì sao hash của D và D' khác nhau dù nội dung thay đổi giống nhau?
- Lệnh nào hủy một rebase đang dở?
- Đồng đội đã lấy nhánh feature của bạn. Có nên rebase nó không, vì sao?
Đáp án gợi ý. (1) D' có cha khác (đỉnh main mới), và hash tính từ cả cha nên khác. (2) git rebase --abort. (3) Không, vì rebase đổi hash và bản của đồng đội sẽ lệch với bản của bạn.
Điều cần nhớ
- Rebase tạo bản sao mới trên nền mới, cho lịch sử thẳng.
rebase -idùng pick, reword, squash, fixup, edit, drop.- Xung đột: sửa, add,
--continue; hoặc--abort. - Không rebase nhánh đã chia sẻ.
- Bài sau: git reflog, lưới an toàn khi viết lại lịch sử.
Nguồn tham khảo
Pro Git, chương 3.6 "Rebasing" và 7.6 "Rewriting History" (git-scm.com/book); tài liệu git-rebase (git-scm.com/docs/git-rebase).