2 giờ sáng. Code review xong, chuẩn bị tạo PR thì reviewer ping lại: “Lịch sử commit của mày trông như rác. Fix xong rồi commit thêm 5 cái ‘fix typo’, ‘fix again’, ‘ok done now’… dọn đi đi.”
Cảm giác quen không? Mình đã ở tình huống đó nhiều lần. Cái workflow thông thường là mở git rebase -i HEAD~10, ngồi squash thủ công từng dòng, rất dễ nhầm thứ tự. Đặc biệt lúc 2 giờ sáng, não không còn hoạt động tốt nữa.
May mắn là Git có sẵn một cặp tính năng ít người biết: --fixup và --autosquash. Kết hợp lại, chúng tự động làm cái việc mà bạn đang ngồi squash tay đó — đúng commit, đúng thứ tự, không cần nhìn vào hash.
Vấn đề: Lịch sử commit như mớ bòng bong
Giả sử bạn đang làm feature “Add user authentication”. Sau vài tiếng code và vài lần sửa lỗi nhỏ, git log trông thế này:
$ git log --oneline
a3f9c12 ok done
b7e2d45 fix typo in login form
c1a8b33 forgot to add validation
d4f7e22 Add user authentication
e9b1c55 Update README
Ba commit đầu thực chất là sửa lỗi nhỏ của d4f7e22. Về mặt logic chúng thuộc về nhau, nhưng lịch sử thì bị phân tán. Reviewer nhìn vào thấy rối, git bisect sau này cũng khó hơn, và git blame trả về commit “ok done” thay vì commit có nghĩa.
Giải pháp thủ công: git rebase -i HEAD~4, đổi pick thành squash cho từng dòng. Tốn thời gian, dễ nhầm, và lúc mệt thì sai nhiều hơn đúng.
Khái niệm cốt lõi
git commit –fixup là gì?
git commit --fixup <commit-hash> tạo một commit mới với message được đặt tự động theo format fixup! <original message>. Đây là “nhãn dán” để Git biết commit này cần được gộp vào commit gốc khi rebase.
Ví dụ, muốn đánh dấu một sửa lỗi thuộc về commit d4f7e22 (message: “Add user authentication”):
$ git add auth/login.py
$ git commit --fixup d4f7e22
# Git tự tạo commit với message: "fixup! Add user authentication"
Bạn không cần nghĩ message, không cần nhớ tên commit gốc sau này — cái prefix fixup! đã giữ đủ thông tin.
git rebase –autosquash làm gì?
git rebase -i --autosquash tự động sắp xếp lại các commit có prefix fixup! vào ngay sau commit gốc trong danh sách rebase, đồng thời đổi action thành fixup. Bạn mở editor ra đã thấy mọi thứ được xếp đúng chỗ — chỉ cần save và thoát.
Thực hành chi tiết
Bước 1: Commit bình thường
Mình đang làm feature payment, bắt đầu với hai commit:
$ git add payment/checkout.py
$ git commit -m "Add checkout flow"
# Hash: abc1234
$ git add payment/cart.py
$ git commit -m "Add cart calculation"
# Hash: def5678
Bước 2: Phát hiện lỗi — dùng –fixup thay vì commit bừa
Đang test thì phát hiện checkout.py thiếu validation. Sửa xong:
# Thay vì: git commit -m "fix validation in checkout" ← tạo thêm rác
# Dùng:
$ git add payment/checkout.py
$ git commit --fixup abc1234
# git log --oneline:
# f9e3a12 fixup! Add checkout flow ← tự động đặt tên
# def5678 Add cart calculation
# abc1234 Add checkout flow
Tiếp tục phát hiện lỗi trong cart, làm tương tự:
$ git add payment/cart.py
$ git commit --fixup def5678
# git log --oneline:
# 7b2c891 fixup! Add cart calculation
# f9e3a12 fixup! Add checkout flow
# def5678 Add cart calculation
# abc1234 Add checkout flow
Bước 3: Dọn dẹp trước khi push
$ git rebase -i --autosquash HEAD~4
Git tự động mở editor với danh sách đã được xếp lại — không cần chỉnh gì:
pick abc1234 Add checkout flow
fixup f9e3a12 fixup! Add checkout flow
pick def5678 Add cart calculation
fixup 7b2c891 fixup! Add cart calculation
Save, thoát editor. Git gộp tự động. Kết quả:
$ git log --oneline
a8d3f21 Add cart calculation
c4b7e92 Add checkout flow
Lịch sử sạch, 2 commit gọn gàng thay vì 4 commit lộn xộn. Reviewer vui, mình ngủ được.
Cấu hình mặc định — làm một lần dùng mãi
Thay vì gõ --autosquash mỗi lần, bật nó vào global config:
$ git config --global rebase.autoSquash true
Sau đó chỉ cần git rebase -i HEAD~N là autosquash tự kích hoạt.
–squash vs –fixup: chọn cái nào?
--fixup gộp commit và bỏ message của commit con, chỉ giữ message gốc. --squash gộp nhưng giữ cả hai message để bạn chỉnh trong editor khi rebase.
# Dùng --squash khi muốn merge nội dung message vào commit gốc
$ git commit --squash abc1234
# Tạo: "squash! Add checkout flow" — Git mở editor cho bạn edit message khi rebase
Với 95% trường hợp “fix nhỏ sau khi commit”, --fixup là đủ. --squash chỉ cần khi bạn muốn bổ sung thêm thông tin vào commit message gốc.
Fixup commit nằm sâu trong lịch sử
Cái hay là --autosquash tìm đúng commit gốc dù nó nằm ở đâu trong lịch sử. Bạn đã làm 10 commit, muốn fixup commit số 3 — cứ làm bình thường, sau đó rebase -i HEAD~11. Git tự xếp fixup! vào đúng vị trí ngay sau commit gốc, không cần bạn đếm.
Áp dụng thực tế trong team
Trong team 8 người mình từng làm, mình đề xuất flow này cho quy trình review: tất cả member dùng --fixup cho các sửa lỗi sau review comment, sau đó autosquash trước khi merge vào main. Số lượng merge conflict giảm hẳn vì history rõ ràng hơn, và reviewer dễ track được thay đổi — thay vì phải đọc qua 7 commit “fix” để hiểu feature làm gì.
Quy tắc team mình dùng:
- Trong quá trình code: thoải mái commit, dùng
--fixupđể đánh dấu fix - Trước khi push lên remote: chạy
rebase --autosquashđể dọn local - Không bao giờ rebase commit đã có trên shared branch (tạo diverge cho người khác)
Kiểm tra trước khi rebase
Nếu chưa chắc commit nào đã push lên remote:
# Xem commit nào chưa có trên remote
$ git log origin/feature-branch..HEAD --oneline
# Chỉ rebase những commit này — không đụng vào cái đã push
Kết luận
git commit --fixup + git rebase -i --autosquash là combo tiết kiệm thời gian nhất mình biết cho việc giữ lịch sử commit sạch. Thay vì ngồi squash tay trong interactive rebase lúc mệt và dễ nhầm, bạn chỉ cần đánh dấu fix ngay lúc commit, rồi một lệnh rebase trước khi push là xong.
Thêm rebase.autoSquash true vào global config một lần. Từ lần sau PR của bạn sẽ không còn lịch sử “fix typo”, “ok done now”, “actually fixed now” nữa — và reviewer sẽ không ping bạn lúc 2 giờ sáng.

