Bỏ qua để đến nội dung
Knowledge Hub

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

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…

Stage cả thư mục không nhìn

Lệnh hay gõ

Terminal window
git add .
git commit -m "feat: thêm thanh toán Stripe"
git push
# .env (STRIPE_KEY=sk_live_...) vừa lên remote

Nên làm

Terminal window
echo ".env*" >> .gitignore && echo "!.env.example" >> .gitignore
git add -p # chọn từng hunk
git status --short # nhìn lại danh sách file
git diff --staged --stat # chỉ những gì sắp commit
git 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 .pem tải về để test.
  • Một lần push là secret đã lộ; phải rotate, không chỉ xoá file.
  • .gitignore chỉ có tác dụng với file chưa được track. File đã commit thì vẫn bị theo dõi — cần git rm --cached .env.

Message không nói gì

Commit message tồi

a1b2c3d fix
d4e5f6a fix review
9f8e7d6 update

Commit 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ân
vào cột bigint, làm job đối soát lệch 1đ.
Refs: PAY-482

Vì sao

  • Ba tháng sau, khi bisect chỉ ra commit fix, 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).

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.21
refactor(order): tách tính giá ra PricingService # test cũ pass
feat(order): hỗ trợ voucher theo phần trăm # chỉ logic mới

Vì sao

  • Voucher tính sai → git revert commit 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 -p trước khi commit.

Force push ghi đè commit đồng đội

Lệnh hay gõ

Terminal window
git rebase origin/main
git push --force
# + 9046029...3ab78af feature -> feature (forced update)
# commit D Bình vừa push lên feature biến mất khỏi remote

Nên gõ

Terminal window
git rebase origin/main
git push --force-with-lease --force-if-includes
# ! [rejected] feature -> feature (stale info)
# → git fetch, rebase lên commit của Bình, push lại

Vì sao

  • --force khô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-lease từ chối nếu remote đã khác origin/feature bạn thấy lần cuối.
  • IDE tự git fetch ngầm sẽ cập nhật origin/feature, làm lease mất tác dụng. --force-if-includes bắt được trường hợp này (chạy thử: rejected ... (remote ref updated since checkout)).

Rebase develop rồi force push

Hay gặp

Terminal window
# An, trên develop (Bình đã commit B dựa trên A)
git rebase main
git 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 M1

Nên làm

Terminal window
# Branch chung: tích hợp main bằng merge, không rewrite
git switch develop
git merge origin/main
git push
# Rebase chỉ dùng cho branch CỦA RIÊNG bạn
git switch feature/voucher
git rebase origin/main
git push --force-with-lease --force-if-includes

Vì sao

  • Rebase tạo commit mới (A') và bỏ commit cũ (A). Ai đã build trên A sẽ merge cả hai bản vào lịch sử.
  • Hậu quả: commit trùng, conflict lặp, bisect và revert khó 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).

Xoá commit lỗi bằng reset + force push

Hay gặp

Terminal window
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 main

Nên làm

Terminal window
git revert <sha-commit-lỗi> # tạo commit "Revert ..." đảo ngược
git 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.
  • revert là 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.

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ả 127
sh ./test.sh

Script nên viết

#!/bin/sh
[ -f ./test.sh ] || exit 125 # không test được → skip
npm ci --silent || exit 125 # không build được → skip
sh ./test.sh # chỉ test tái hiện bug quyết định good/bad

Vì sao

  • git bisect run coi 1–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ận c2 — commit đổi tên test.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ới bogus 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.

Merge lại sau khi revert merge

Hay gặp

Terminal window
git revert -m 1 <sha-merge> # gỡ feature X khỏi main
# ... sửa bug trên branch feature, thêm commit Y
git merge feature # chỉ Y vào main, X vẫn mất

Nên làm

Terminal window
git revert <sha-commit-revert> # "revert the revert" → X quay lại
git merge feature # rồi mới mang Y vào

Vì 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.

Nghĩ rằng commit xoá file là đủ

Hay gặp

Terminal window
git rm .env
echo ".env" >> .gitignore
git 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_abc

Nên làm

Terminal window
# 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 ignore
git 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 .env

Vì sao

  • Commit xoá chỉ gỡ file khỏi snapshot mới nhất. Commit cũ vẫn chứa secret; git log -p -S hay git 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).

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 flag

Vì 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.