Bài git-06 giới thiệu rebase để phát lại commit lên nhánh khác. git rebase -i (interactive) mạnh hơn hẳn: nó cho bạn viết lại lịch sử N commit gần nhất theo ý muốn — gộp nhiều commit thành một, sửa message, xóa commit thừa, đổi thứ tự. Đây là công cụ để dọn nhánh tính năng cho sạch trước khi mở PR: thay vì một chuỗi "wip", "fix typo", "sua nua", reviewer thấy một (hoặc vài) commit gọn gàng có nghĩa. Đo thật bằng git 2.39, tự động hóa để không cần mở editor.

Todo list của rebase tương tác

git rebase -i HEAD~N mở một "todo list" — mỗi commit một dòng, bắt đầu bằng một lệnh bạn có thể đổi:

pick    giữ commit như cũ
reword  giữ thay đổi, SỬA message
squash  gộp vào commit phía trên, GỘP cả message (mở editor)
fixup   gộp vào commit phía trên, BỎ message của nó (gọn hơn squash)
edit    dừng lại để sửa nội dung commit đó
drop    xóa hẳn commit;  đổi thứ tự = đổi thứ tự các dòng

Lưu lại và đóng editor, Git thực thi từng dòng. Để tự động hóa (không mở editor tay), đặt GIT_SEQUENCE_EDITOR để sửa todo bằng script:

GIT_SEQUENCE_EDITOR='sed -i -e "2,\$s/^pick/fixup/"' git rebase -i HEAD~4
git rebase --autosquash   # gộp tự động các commit đánh dấu fixup!/squash!

Ảnh chụp đoạn mã nền tối giải thích rebase tương tác dọn lịch sử nhánh cho sạch trước khi mở PR, vấn đề nhánh đầy commit fix typo wip sua nua làm một tính năng thường đẻ ra 4-5 commit lộn xộn mở PR với lịch sử đó reviewer khó đọc khó revert gọn git rebase -i HEAD~N mở todo list cho bạn sửa N commit gần nhất, các lệnh trong todo list pick giữ commit như cũ reword giữ thay đổi sửa message squash gộp vào commit trên gộp cả message mở editor fixup gộp vào commit trên bỏ message của nó gọn hơn squash edit dừng lại để sửa nội dung commit đó drop xóa hẳn commit đổi thứ tự bằng đổi thứ tự các dòng, tự động hóa không mở editor cộng quy tắc vàng GIT_SEQUENCE_EDITOR sed đổi pick thành fixup tự động git rebase --autosquash gộp tự động các commit fixup squash quy tắc vàng chỉ rebase commit chưa push chung viết lại SHA nhánh chia sẻ sẽ phân kỳ như bài git-06

Hình 1: git rebase -i HEAD~N mở todo list; đổi pick thành reword/squash/fixup/edit/drop hay đổi thứ tự dòng để viết lại lịch sử. Tự động hóa bằng GIT_SEQUENCE_EDITOR/--autosquash. Quy tắc vàng: chỉ rebase commit chưa push chung.

Đo thật: 5 commit lộn xộn gộp còn 2

Ảnh chụp bảng kết quả đo thật nền tối chạy git 2.39, phần một trước 5 commit lộn xộn 85c3452 hoan thien X 96adf62 fix typo nua 28652ca fix typo 789a622 them tinh nang X 1c20dbc base, phần hai rebase -i HEAD4 fixup 3 commit fix vào commit đầu GIT_SEQUENCE_EDITOR sed 2 đô la s pick thành fixup git rebase -i HEAD4 sau aac45b5 them tinh nang X 1c20dbc base 5 commit thành 2 commit sạch, phần ba nội dung file không mất chỉ gộp lịch sử cat f.txt ra base a b c d đủ mọi thay đổi, phần bốn reword đổi message nội dung giữ nguyên reword ra f693dd9 feat hoan thien tinh nang X

Hình 2: Trước có 5 commit (base + 4 cho tính năng X). rebase -i HEAD~4 với fixup gộp 3 commit "fix" vào commit đầu → còn 2 commit (aac45b5 + base). File vẫn đủ base a b c d — chỉ lịch sử gọn lại. reword đổi message thành feat: hoan thien tinh nang X.

Kết quả rõ ràng:

  • Trước: 5 commit — base rồi them tinh nang X, fix typo, fix typo nua, hoan thien X. Lịch sử điển hình của một buổi code: một commit chính + mấy commit sửa vặt.
  • fixup gộp lại: GIT_SEQUENCE_EDITOR đổi 3 dòng pick cuối thành fixup, rebase gộp chúng vào commit đầu. Kết quả còn 2 commit (aac45b5 them tinh nang X + base). Dùng fixup (không squash) nên không cần sửa message — message của các commit fix bị bỏ, giữ message commit đầu.
  • Nội dung nguyên vẹn: cat f.txt vẫn cho base a b c d — mọi thay đổi còn đủ, chỉ lịch sử gọn lại. Rebase viết lại cách các commit được nhóm, không mất công việc.
  • reword: đổi message commit mới nhất thành feat: hoan thien tinh nang X (SHA đổi thành f693dd9 vì message là một phần của commit — như bài git-01) mà nội dung không đổi.

Đánh đổi và lưu ý

Quy tắc vàng (nhắc lại từ git-06): chỉ rebase commit chưa push chung. Rebase tương tác viết lại SHA của mọi commit nó chạm tới. Nếu bạn rebase các commit đã push và người khác đã pull, lịch sử phân kỳ — lần push sau phải --force (nên dùng --force-with-lease) và đồng đội gặp rối. Chỉ dọn lịch sử nhánh riêng của mình trước khi mở PR, đừng đụng commit đã lên nhánh chung.

--autosquash + commit --fixup là quy trình chuyên nghiệp. Khi đang code, thay vì "fix typo", hãy git commit --fixup=<sha commit cần sửa> — Git tạo commit đánh dấu fixup!. Sau đó git rebase -i --autosquash tự sắp và gộp chúng vào đúng commit gốc. Sạch, ít thao tác tay, ít nhầm.

Gặp xung đột thì giải rồi --continue. Rebase phát lại từng commit; nếu một commit xung đột với thay đổi mới, rebase dừng — giải xung đột, git add, rồi git rebase --continue (hoặc --abort để hủy về trạng thái cũ). Với chuỗi dài, xung đột có thể lặp lại; git rerere (bài sau nếu có) giúp nhớ cách giải.

Đừng rebase quá tay thành "che giấu". Lịch sử sạch tốt, nhưng đừng gộp mọi thứ thành một commit khổng lồ — mất khả năng bisect/revert từng phần. Nguyên tắc: mỗi commit là một thay đổi logic hoàn chỉnh, tự biên dịch được. Dọn cho rõ ý, không phải cho ít commit nhất.

Ba ý mang về

  1. git rebase -i viết lại N commit gần nhất qua todo list: pick/reword/squash/fixup/edit/drop/đổi thứ tự — đo thật, 5 commit lộn xộn gộp fixup còn 2 commit sạch, nội dung file (base a b c d) nguyên vẹn.
  2. Tự động hóa bằng GIT_SEQUENCE_EDITOR/--autosquash: dùng git commit --fixup=<sha> khi code rồi rebase -i --autosquash để gộp đúng chỗ; fixup gọn hơn squash (bỏ message thừa).
  3. Quy tắc vàng: chỉ rebase commit chưa push chung (viết lại SHA → nhánh chia sẻ phân kỳ); giải xung đột rồi --continue/--abort; và dọn cho rõ ý — mỗi commit một thay đổi logic hoàn chỉnh, đừng gộp thành một khối khổng lồ.

Nguồn

Phần sau — bài cuối sê-ri Git — ta nhìn vào cách Git tự bảo trì kho: git gc, git prune, git fsck — dọn object mồ côi, nén packfile, và tìm lại thứ tưởng đã mất.