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.

🔬 Đào sâu: Vì sao rebase "trông giống" nhưng lại là commit khác?
Hash commit được tính từ (trong số nhiều thứ khác) hash của commit cha (xem lại công thức băm ở Bài 1). Khi rebase đặt 1 commit lên trên 1 cha khác, dù nội dung tree/message giữ nguyên y hệt, chuỗi văn bản được băm để tạo commit đã đổi (vì có dòng 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:

terminal
# 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).

🕳️ Cạm bẫy thường gặp: Rebase xong rồi mới nhớ ra đã push
Quy tắc thực dụng: chỉ rebase các commit CHƯA từng push lên remote dùng chung (hoặc nhánh cá nhân không ai khác pull). Nếu lỡ rebase một nhánh đã có người khác kéo về, cách an toàn nhất là trao đổi trực tiếp với họ trước khi force-push — đừng âm thầm ghi đè lịch sử chung.

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.

📏 Sân chơi tương tác: Rebase Simulator

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ý

📋 Kế hoạch Interactive Rebase — chọn hành động rồi bấm "Thực thi"

git-rebase-live.js

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ề?

📖 Tài liệu tham khảo / References

Bài viết liên quan trong series

Bài 6: Cherry-pick — Chọn Lọc Commit Cụ Thể Bài 4: Merge & Conflict Quay lại Lộ trình Series Git