Tóm tắt: git fetch → git rebase origin/main → sửa commit cũ bằng git commit --fixup=<sha> + git rebase -i --autosquash → giải conflict (git add + git rebase --continue) → git push --force-with-lease. Chỉ rebase branch của riêng bạn, không rebase main hay branch dùng chung.
Luồng hoạt động
flowchart TD A[git fetch origin] --> B["git rebase origin/main"] B --> C{Conflict?} C -- có --> D["sửa file, git add"] D --> E[git rebase --continue] E --> C C -- không --> F["git commit --fixup / rebase -i --autosquash"] F --> G[chạy test] G --> H[git push --force-with-lease]
Lệnh
Cấu hình nên bật
git config --global pull.rebase true # git pull = fetch + rebase
git config --global rebase.autoSquash true # rebase -i tự xếp fixup!/squash!
git config --global rebase.autoStash true # tự stash/unstash khi working tree bẩn
git config --global rebase.updateRefs true # Git >= 2.38: cập nhật luôn các branch xếp chồng
git config --global rerere.enabled true # nhớ cách giải conflict, lần sau tự áp dụngCập nhật feature branch theo main
git switch feature/bgp-policy
git fetch origin
git rebase origin/main
# xong thì kiểm tra
git log --oneline origin/main..HEADChỉ muốn rebase lại các commit trong branch, giữ nguyên điểm gốc (không kéo main mới):
git rebase -i --keep-base origin/mainSửa một commit cũ bằng fixup + autosquash
git log --oneline origin/main..HEAD
# a1b2c3d add route-policy PASS-ALL
# e4f5a6b add BGP neighbor template
# sửa file, rồi gắn thay đổi vào commit e4f5a6b
git add -p
git commit --fixup=e4f5a6b # tạo commit "fixup! add BGP neighbor template"
git rebase -i --autosquash origin/main # todo list đã xếp sẵn, chỉ cần lưu và thoátKhông muốn mở editor (dùng trong script):
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash origin/mainCác lệnh trong todo list của rebase -i:
| Lệnh | Tác dụng |
|---|---|
pick |
giữ commit |
reword |
giữ commit, sửa message |
edit |
dừng lại để amend hoặc tách commit |
squash |
gộp vào commit trước, ghép cả message |
fixup |
gộp vào commit trước, bỏ message |
drop |
bỏ commit |
exec <cmd> |
chạy lệnh (vd test) sau commit trước đó |
Chạy test sau mỗi commit để đảm bảo commit nào cũng build được:
git rebase -i --exec "pytest -q" origin/mainGiải conflict
git status # file "both modified"
git diff # xem marker <<<<<<< ======= >>>>>>>
# sửa file, hoặc chọn hẳn một phía:
git restore --ours path/file # khi rebase: "ours" = base mới (main)
git restore --theirs path/file # khi rebase: "theirs" = commit của bạn đang được áp lại
git add path/file
git rebase --continue
# bỏ qua commit hiện tại / huỷ toàn bộ rebase
git rebase --skip
git rebase --abortKhi rebase, ours/theirs bị đảo so với khi merge. --ours là phía branch bạn đang rebase lên (main), --theirs là commit của bạn.
Push sau khi rebase
git push --force-with-lease # từ chối nếu remote có commit bạn chưa fetch
git push --force-with-lease --force-if-includes # Git >= 2.30, an toàn hơn khi IDE tự fetch nềnKhông dùng git push --force trần: nó ghi đè cả commit người khác vừa đẩy lên.
Stacked branch (PR xếp chồng)
# feat-b được tách ra từ feat-a; rebase feat-b và tự dời luôn feat-a
git switch feat-b
git rebase --update-refs origin/main
git push --force-with-lease origin feat-a feat-bLỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
! [rejected] ... (non-fast-forward) sau rebase |
Lịch sử đã viết lại | git push --force-with-lease |
! [rejected] ... (stale info) |
Remote có commit mới mà bạn chưa fetch | git fetch, xem git log HEAD..origin/<branch>, rebase lên rồi push lại |
| Conflict lặp lại ở nhiều commit | Cùng vùng code bị sửa nhiều lần | Bật rerere.enabled, hoặc squash trước rồi rebase |
fixup! không tự xếp |
Thiếu --autosquash và chưa bật rebase.autoSquash |
Thêm --autosquash |
cannot rebase: You have unstaged changes |
Working tree bẩn | git stash hoặc --autostash |
unknown option 'update-refs' |
Git < 2.38 | Nâng Git, hoặc rebase từng branch thủ công |
| Rebase xong mất commit | Chọn nhầm drop hoặc giải conflict sai |
git reflog → git reset --hard HEAD@{n} (xem troubleshooting) |