Ở Bài 4, merge tạo ra một commit hợp nhất với 2 cha, giữ lại hình
thoi phân nhánh trong lịch sử. git rebase đi theo hướng hoàn toàn khác: nó
viết lại lịch sử để trông như mọi commit luôn nằm trên 1 đường thẳng — nhưng cái giá
phải trả là những commit "cũ" không còn tồn tại theo đúng nghĩa nữa.
1. Rebase vs Merge: Viết Lại Lịch Sử Thay Vì Hợp Nhất
Merge giữ nguyên lịch sử cũ của cả 2 nhánh và thêm 1 commit hợp nhất ở cuối. Rebase làm ngược lại: nó lấy các commit riêng của nhánh bạn đang đứng, rồi phát lại (replay) từng commit đó — theo đúng thứ tự — lên trên đỉnh mới nhất của nhánh khác. Kết quả cuối cùng có cùng nội dung, nhưng đồ thị commit trông thẳng hàng, không còn hình thoi phân nhánh.
2. Cơ Chế: Replay Từng Commit, Không Di Chuyển Commit Cũ
Đây là điểm hay bị hiểu lầm nhất: git rebase không hề "di chuyển" các
commit cũ. Nhớ lại từ Bài 1, mọi object trong Git đều bất biến. Rebase
tính toán diff mà mỗi commit cũ giới thiệu, rồi tạo ra một
commit hoàn toàn mới áp đúng diff đó lên nền mới — commit mới có cha khác, nên
hash cũng khác. Các commit gốc vẫn còn nguyên trong .git/objects, nhưng
branch giờ trỏ sang chuỗi mới — commit cũ trở thành mồ côi, giống hệt cơ chế đã thấy
ở Bài 3.
parent <hash-khác>) — buộc ra 1 hash hoàn toàn mới. Đây không phải lỗi hay giới
hạn, mà là hệ quả tất yếu của content-addressable storage.
3. git rebase --onto: Chuyển Cả Đoạn Lịch Sử Sang Gốc Khác
Đôi khi bạn không muốn rebase lên đỉnh MỚI NHẤT của 1 nhánh, mà muốn di chuyển một
đoạn commit cụ thể sang hẳn 1 gốc khác — ví dụ nhánh topic đang dựa trên
feature (đã bị huỷ), bạn muốn chuyển thẳng các commit riêng của topic sang
main:
# Lấy các commit CHỈ CÓ trên topic (không có trên feature),
# replay chúng lên trên main thay vì lên trên feature
$ git rebase --onto main feature topic
4. Interactive Rebase: Sửa Lại Lịch Sử Trước Khi Chia Sẻ
git rebase -i <base> mở ra 1 "kế hoạch" liệt kê từng commit sắp được replay, cho
phép bạn quyết định số phận từng commit trước khi thực thi:
- pick — giữ nguyên, replay bình thường.
- squash — gộp commit này vào commit ngay phía trước trong kế hoạch, kết hợp thành 1 commit duy nhất.
- drop — bỏ hẳn commit này, coi như chưa từng tồn tại.
Interactive rebase cực hữu ích để "dọn dẹp" một chuỗi commit lộn xộn (kiểu "fix typo", "fix typo again", "oops") thành vài commit có ý nghĩa trước khi mở Pull Request.
5. Golden Rule: Không Bao Giờ Rebase Nhánh Công Khai
Vì rebase tạo hash mới cho mọi commit bị viết lại, nếu ai đó đã
git pull nhánh của bạn về máy họ TRƯỚC khi bạn rebase, lịch sử của họ giờ chứa các commit
cũ (đã mồ côi với bạn) trong khi bạn có bản mới — 2 lịch sử phân kỳ dù nội dung logic giống hệt nhau.
Việc đồng bộ lại đòi hỏi force-push và có thể gây mất commit của người khác nếu không cẩn thận (xem
thêm ở Bài 9).
6. So Sánh Merge vs Rebase
| Tiêu chí | Merge | Rebase |
|---|---|---|
| Hình dạng lịch sử | Phân nhánh (hình thoi) | Thẳng hàng (linear) |
| Commit cũ có thay đổi? | Không — giữ nguyên | Có — bị thay bằng commit mới, hash khác |
| An toàn cho nhánh public? | Luôn an toàn | Chỉ an toàn cho nhánh CHƯA chia sẻ |
| Commit hợp nhất? | Có (2 cha) | Không có commit hợp nhất nào |
Sân chơi tương tác: Rebase Simulator
Tiếp nối engine từ các bài trước, giờ hỗ trợ git rebase, git rebase -i (kế
hoạch pick/squash/drop), và git rebase --onto. Tạo nhánh, commit vài lần, rồi thử rebase
để xem commit cũ mồ côi và lịch sử thẳng hàng xuất hiện.
Terminal giả lập
git branch/checkout <tên>git commit -m "..."git rebase <tên>git rebase -i <tên>git rebase --onto <new> <old>git log
Nhật ký
Trắc nghiệm ôn tập
Câu 1: Sau khi rebase, các commit "cũ" của nhánh vẫn còn nguyên nội dung/thay đổi — vậy tại sao chúng lại trở thành mồ côi?
Trắc nghiệm ôn tập
Câu 2: Trong interactive rebase, đánh dấu 1 commit là "squash" sẽ làm gì?
Trắc nghiệm ôn tập
Câu 3: Vì sao "Golden Rule" khuyên không rebase 1 nhánh công khai đã có người khác pull về?