Lỗi Git hay gặp: force push, commit secret, rebase branch chung Trung cấp
Thời gian đọc ước tính: 9 phút
Cần đọc trước
Trang này gom các lỗi Git mà team backend hay mắc, mỗi lỗi đặt cạnh cách làm đúng. Output trong các ví dụ lấy từ chạy thử trên git 2.54.0 trong repo tạm.
Đang tải diagram…
Commit
Phần tiêu đề “Commit”git add . kéo theo .env
Phần tiêu đề “git add . kéo theo .env”Stage cả thư mục không nhìn
Lệnh hay gõ
git add .git commit -m "feat: thêm thanh toán Stripe"git push# .env (STRIPE_KEY=sk_live_...) vừa lên remoteNên làm
echo ".env*" >> .gitignore && echo "!.env.example" >> .gitignoregit add -p # chọn từng hunkgit status --short # nhìn lại danh sách filegit diff --staged --stat # chỉ những gì sắp commitgit commit -m "feat(payment): thêm thanh toán Stripe"Vì sao
git add .stage mọi file chưa bị ignore:.env, dump DB, file.pemtải về để test.- Một lần push là secret đã lộ; phải rotate, không chỉ xoá file.
.gitignorechỉ có tác dụng với file chưa được track. File đã commit thì vẫn bị theo dõi — cầngit rm --cached .env.
Commit message “fix”
Phần tiêu đề “Commit message “fix””Message không nói gì
Commit message tồi
a1b2c3d fixd4e5f6a fix review9f8e7d6 updateCommit message tốt
fix(order): làm tròn tổng tiền sau giảm giá theo VND
Giảm 10% cho đơn 199.999đ trả 179.999,1 và lưu số thập phânvào cột bigint, làm job đối soát lệch 1đ.
Refs: PAY-482Vì sao
- Ba tháng sau, khi
bisectchỉ ra commitfix, không ai biết nó sửa gì, có được revert không. - Không theo Conventional Commits thì tool sinh changelog và SemVer bỏ qua commit đó.
- Commit “fix review” nên là
git commit --fixup=<sha>rồi autosquash (xem Branch & merge).
Gộp refactor và tính năng trong một commit
Phần tiêu đề “Gộp refactor và tính năng trong một commit”Một commit làm ba việc
Lịch sử hay gặp
feat(order): hỗ trợ voucher theo phần trăm src/order/pricing.ts | 180 +++++---- (đổi tên, tách hàm, sửa logic) src/order/order.module.ts | 12 +- (đổi DI) package.json | 2 +- (nâng lodash)Lịch sử nên có
chore(deps): nâng lodash 4.17.20 → 4.17.21refactor(order): tách tính giá ra PricingService # test cũ passfeat(order): hỗ trợ voucher theo phần trăm # chỉ logic mớiVì sao
- Voucher tính sai →
git revertcommit gộp kéo theo mất refactor và bản nâng lodash, có thể làm vỡ code khác đã dựa vào chúng. - Reviewer không phân biệt được dòng nào đổi hành vi, dòng nào chỉ đổi tên.
- Tách bằng
git add -ptrước khi commit.
Push và rebase
Phần tiêu đề “Push và rebase”git push --force sau khi rebase
Phần tiêu đề “git push --force sau khi rebase”Force push ghi đè commit đồng đội
Lệnh hay gõ
git rebase origin/maingit push --force# + 9046029...3ab78af feature -> feature (forced update)# commit D Bình vừa push lên feature biến mất khỏi remoteNên gõ
git rebase origin/maingit push --force-with-lease --force-if-includes# ! [rejected] feature -> feature (stale info)# → git fetch, rebase lên commit của Bình, push lạiVì sao
--forcekhông kiểm tra gì: commit đồng đội vừa push bị xoá khỏi remote, không ai được báo. Họ chỉ phát hiện khi PR thiếu code.--force-with-leasetừ chối nếu remote đã khácorigin/featurebạn thấy lần cuối.- IDE tự
git fetchngầm sẽ cập nhậtorigin/feature, làm lease mất tác dụng.--force-if-includesbắt được trường hợp này (chạy thử:rejected ... (remote ref updated since checkout)).
Rebase branch nhiều người cùng dùng
Phần tiêu đề “Rebase branch nhiều người cùng dùng”Rebase develop rồi force push
Hay gặp
# An, trên develop (Bình đã commit B dựa trên A)git rebase maingit push --force-with-lease
# Bình: git pull (merge) → lịch sử có HAI bản "feat: A"* 538f95a Merge branch 'develop' ...|\| * e06f472 feat: A ← A' (hash mới)| * 7c86048 M2* | 2f2e839 feat: B* | e75f376 feat: A ← A cũ|/* 04427b4 M1Nên làm
# Branch chung: tích hợp main bằng merge, không rewritegit switch developgit merge origin/maingit push
# Rebase chỉ dùng cho branch CỦA RIÊNG bạngit switch feature/vouchergit rebase origin/maingit push --force-with-lease --force-if-includesVì sao
- Rebase tạo commit mới (
A') và bỏ commit cũ (A). Ai đã build trênAsẽ merge cả hai bản vào lịch sử. - Hậu quả: commit trùng, conflict lặp,
bisectvàrevertkhó dùng. Pro Git: “Do not rebase commits that exist outside your repository and that people may have based work on.” - Nếu lỡ rồi, người bị ảnh hưởng dùng
git pull --rebase: git bỏ bản trùng (chạy thử:dropping ... feat: A -- patch contents already upstream).
Cứu sự cố
Phần tiêu đề “Cứu sự cố”reset --hard commit đã push lên main
Phần tiêu đề “reset --hard commit đã push lên main”Xoá commit lỗi bằng reset + force push
Hay gặp
git reset --hard HEAD~1 # bỏ "feat: bug"git push # ! [rejected] main -> main (non-fast-forward)git push --force # "được rồi"
# Đồng nghiệp đã pull "feat: bug" trước đó, commit thêm rồi push:# 53e7633..e93d9cd main -> main# → "feat: bug" quay lại mainNên làm
git revert <sha-commit-lỗi> # tạo commit "Revert ..." đảo ngượcgit push # fast-forward bình thường# revert merge commit: git revert -m 1 <sha-merge>Vì sao
reset+ force push rewrite lịch sử mà mọi clone khác vẫn giữ. Chỉ cần một người push từ clone cũ là commit lỗi quay lại.revertlà commit mới, ai pull cũng nhận được; lịch sử ghi rõ đã gỡ gì và vì sao.- Trên branch chung, không có ngoại lệ. Branch protection nên chặn force push lên
main.
Script bisect trả exit code sai
Phần tiêu đề “Script bisect trả exit code sai”Bisect tính 'command not found' là bad
Script hay viết
#!/bin/sh# test.sh bị đổi tên ở vài commit giữa → sh trả 127sh ./test.shScript nên viết
#!/bin/sh[ -f ./test.sh ] || exit 125 # không test được → skipnpm ci --silent || exit 125 # không build được → skipsh ./test.sh # chỉ test tái hiện bug quyết định good/badVì sao
git bisect runcoi1–127(trừ125) là bad. Thiếu file hay lệnh trả127, nên commit đầu tiên thiếu nó bị báo là “first bad commit”.- Chạy thử: bug thật ở commit
c7, nhưng bisect kết luậnc2— commit đổi têntest.sh. - Git 2.54 có kiểm tra lại khi gặp
126/127: nếu commit good cũng trả mã đó, bisect dừng vớibogus exit code 127 for good revision. Nhưng khi chỉ vài commit thiếu lệnh, kết quả vẫn sai im lặng.
Revert merge rồi merge lại branch đó
Phần tiêu đề “Revert merge rồi merge lại branch đó”Merge lại sau khi revert merge
Hay gặp
git revert -m 1 <sha-merge> # gỡ feature X khỏi main# ... sửa bug trên branch feature, thêm commit Ygit merge feature # chỉ Y vào main, X vẫn mấtNên làm
git revert <sha-commit-revert> # "revert the revert" → X quay lạigit merge feature # rồi mới mang Y vàoVì sao
- Docs
git revert: revert một merge nghĩa là bạn tuyên bố sẽ không bao giờ muốn các thay đổi cây do merge đó mang vào. - Merge lại sau đó chỉ mang các commit mới (Y). Chạy thử: file của X không quay lại
main, không có conflict hay cảnh báo nào. - Muốn đưa lại tính năng, phải revert chính commit revert.
Secret và PR
Phần tiêu đề “Secret và PR”Xoá secret bằng một commit mới
Phần tiêu đề “Xoá secret bằng một commit mới”Nghĩ rằng commit xoá file là đủ
Hay gặp
git rm .envecho ".env" >> .gitignoregit commit -m "chore: xoá .env"git push
# Bất kỳ ai clone repo:git log -p -S sk_live --oneline# c9ad1c2 feat: init# +STRIPE_KEY=sk_live_abcNên làm
# 1. Rotate/revoke key trên Stripe NGAY# 2. Kiểm tra log truy cập trong khoảng thời gian lộ# 3. Gỡ khỏi tracking, thêm ignoregit rm --cached .env && echo ".env" >> .gitignore# 4. Chỉ khi cần (không rotate được): rewrite lịch sửgit filter-repo --sensitive-data-removal --invert-paths --path .envVì sao
- Commit xoá chỉ gỡ file khỏi snapshot mới nhất. Commit cũ vẫn chứa secret;
git log -p -Shaygit show <sha>:.envđọc lại được. - Kẻ tấn công quét repo liên tục; khoảng thời gian từ push tới bị khai thác có thể rất ngắn. Vì vậy rotate trước.
- Rewrite không xoá được trong clone, fork, và commit tham chiếu bởi PR trên GitHub (xem Secret & PR).
PR khổng lồ
Phần tiêu đề “PR khổng lồ”Một PR gom mọi thứ
PR hay gặp
PR #812: hoàn tiền một phần + refactor module + nâng Prisma 48 files changed, 1.900 insertions(+), 640 deletions(-) Mô tả: "như title"PR nên chia
PR #810: chore(deps): nâng Prisma (merge trước)PR #811: refactor(payment): đổi tên module (test cũ pass)PR #812: feat(refund): hoàn tiền một phần (~300 dòng, sau feature flag) Mô tả: vấn đề, cách giải, cách test, rủi ro, rollback = tắt flagVì sao
- PR 2.000 dòng nhận “LGTM” vì reviewer bỏ cuộc, không phải vì code đúng.
- Squash merge gộp tất cả thành một commit: tính năng lỗi thì không revert riêng được, revert là đụng luôn migration của bản nâng cấp.
- Mục tiêu ~400 dòng thay đổi thật là kinh nghiệm, không phải chuẩn; điều quan trọng là reviewer hiểu toàn bộ PR.
Tiếp theo: quay về Tổng quan cụm Git hoặc đọc lại Checklist code review cho Git.