Bài 02

Làm việc team: merge, rebase, conflict, PR, GitFlow vs Trunk-based

06 tháng 5, 2026 · 45 phút đọc

Bài 02 — Làm việc team: merge, rebase, conflict, PR, GitFlow vs Trunk-based

Bài 01 cho bạn workflow cá nhân. Bài này cho khi 5–10 người cùng push vào 1 repo. Lúc đó merge, rebase, conflict, PR review xuất hiện. Sai workflow = mất commit, ghi đè đồng đội, history rối loạn.


Merge vs Rebase — chốt 1 lần để không bao giờ phải tranh cãi

Trước:
main:    A───B───C
              \
feat:          D───E

MERGE (git merge main tfeat):
feat:    A───B───C───M
              \     /
               D───E
            (M = merge commit, gihistory)

REBASE (git rebase main tfeat):
feat:    A───B───C───D'───E'
            (D', E' = commit mi SHA khác, linear sch)

Quy tắc vàng — nhớ duy nhất cái này:

Branch riêng của bạn → rebase OK.
Branch shared với người khác → merge thôi, KHÔNG rebase.

Lý do: rebase = rewrite history → SHA đổi → đồng đội pull → loạn / mất commit.


Workflow chuẩn với main

# 1. Bắt đầu task — sync main mới nhất
git switch main
git pull
git switch -c feat/checkout

# 2. Làm việc, commit
# ...

# 3. Trong lúc làm, main có update từ team — sync bằng REBASE (branch riêng OK)
git fetch origin
git rebase origin/main
# Resolve conflict nếu có (bên dưới)

# 4. Push
git push -u origin feat/checkout

# 5. Sau khi rebase, push tiếp cần force (vì SHA đã đổi)
git push --force-with-lease

⚠️ --force-with-lease chứ không --force thuần. Khác biệt:

  • --force: ghi đè blindly, ai push lên remote trong lúc bạn rebase đều mất.
  • --force-with-lease: check remote vẫn như local nhớ. Nếu ai push thêm → từ chối → bạn pull lại trước.

Mất 1 ký tự, an toàn hơn 100x.


Conflict — không panic

Conflict = Git nói "tao không biết chọn, mày tự quyết". Không phải lỗi.

Khi gặp:

CONFLICT (content): Merge conflict in src/api.js

Mở file:

<<<<<<< HEAD
console.log("Hi");        // phía bạn
=======
console.log("Hello");     // phía bên kia
>>>>>>> feat/other

Bạn quyết định: giữ cái nào, hoặc kết hợp. Xoá hết markers sau khi sửa.

# Sau khi sửa file
git add src/api.js

# Đang merge:
git commit

# Đang rebase:
git rebase --continue

Lỡ rối → abort, làm lại:

git merge --abort
git rebase --abort

An toàn 100% — quay về trạng thái trước khi merge/rebase.

File lock (package-lock.json, yarn.lock): đừng resolve thủ công 5000 dòng. Xoá rồi rebuild:

rm package-lock.json
npm install
git add package-lock.json

Tránh conflict nhiều lần khi rebase: bật rerere (Git ghi nhớ resolve, lần sau auto):

git config --global rerere.enabled true

3 lệnh nhỏ — cứu nhiều case

Stash — cất tạm khi cần switch gấp

Đang sửa dở dang, sếp bảo fix gấp branch khác:

git stash push -u -m "WIP: login form"   # -u: include file mới chưa add
git switch hotfix/critical
# ... fix, commit, push ...
git switch feat/login
git stash pop          # khôi phục thay đổi

Xem các stash:

git stash list

⚠️ Quên -u thì file mới (chưa git add) không được stash → mất khi switch branch. Habit: luôn -u.

Cherry-pick — copy commit qua branch khác

Hotfix vào release branch:

git switch release/v1
git cherry-pick <SHA-of-fix-from-main>
# Tạo commit mới với cùng nội dung, SHA khác

Revert — đảo ngược commit đã push

Deploy commit X lên production, phát hiện bug → rollback:

git revert <SHA-of-X>
git push
# Tạo commit Y với thay đổi ngược lại X

Quan trọng: branch shared (main, develop) luôn dùng git revert, KHÔNG bao giờ git reset. Reset xoá history → đồng đội loạn.

Revert merge commit cần thêm flag:

git revert -m 1 <merge-SHA>

Pull Request workflow

1. Branch feat/x từ main
2. Commit, push, mở PR trên GitHub/GitLab
3. CI chạy (lint, test, build)
4. Đồng đội review
5. Merge vào main qua UI

PR merge có 3 lựa chọn (chọn ở UI):

Strategy Khi nào
Squash and merge Branch nhiều commit "wip/fix" → gộp thành 1 commit clean trên main
Rebase and merge Branch ít commit (1–3) đã clean, muốn linear
Merge commit Hiếm dùng cho feature, có ích cho release branch

Default cho 90% PR: Squash and merge. Linear history, dễ revert/bisect (Bài 03).

Branch protection — config 1 lần trên main

GitHub Settings → Branches → Add rule cho main:

  • ✅ Require pull request before merging
  • ✅ Require status checks to pass (CI)
  • ✅ Require approvals (≥ 1)
  • ✅ Do not allow force pushes
  • ✅ Do not allow deletions

Không có protection = một developer typo git push --force origin main → toàn team đau.

PR size

PR > 400 dòng = reviewer không review nổi → rubber stamp ("LGTM" mà không đọc). Chia PR nhỏ. Feature lớn split thành 3-5 PR.


GitFlow vs Trunk-based — chọn cái nào?

2 model phổ biến. Đừng tranh cãi — chọn theo project.

Trunk-based (default cho team modern)

main (trunk)
  ↑↑↑
  feat-1, feat-2, feat-3 (sống 1-3 ngày, merge nhanh)
  • 1 branch chính: main.
  • Feature branch sống ngắn.
  • Merge sớm, deploy thường xuyên (CI/CD).
  • Hotfix = branch từ main, fix, merge ngay.

Phù hợp: SaaS, microservice, CI/CD mạnh, deploy nhiều lần/ngày.

GitFlow (cổ điển, release-based)

main (production, chỉ chứa code đã release)
  ↑
  release/v1.2  (chuẩn bị release, fix bug nhỏ)
  ↑
  develop  (integration của tất cả feature)
  ↑↑↑
  feat-x, feat-y (tách từ develop)

hotfix/xxx  (từ main, merge cả vào main và develop)
  • 2 branch chính: main (production) + develop (integration).
  • Release branch để chuẩn bị release.
  • Hotfix branch trực tiếp từ main.

Phù hợp: Sản phẩm có version rõ (mobile app, desktop, on-prem), release theo lịch (mỗi 2 tuần / 1 tháng).

Đừng dùng GitFlow cho: SaaS deploy hàng ngày, microservice. Quá phức tạp, chậm team.

Tag và versioning

Sau release, tag commit:

git tag -a v1.2.0 -m "Release v1.2.0"
git push origin v1.2.0

Semver (semantic versioning): MAJOR.MINOR.PATCH

  • MAJOR: breaking change.
  • MINOR: feature mới, backward compatible.
  • PATCH: bug fix.

Tool semantic-release đọc Conventional Commits → tự bump version → tự tag → tự changelog. Setup 1 lần, chạy mãi.


Câu chuyện thực tế

Tháng 7/2024, team microservice 8 dev, trunk-based. Bạn A đang làm feat/checkout 3 ngày. Sáng thứ 5, bạn B "giúp" A push thêm 1 commit fix vào branch đó (đồng nghiệp bảo nhau OK).

30 phút sau, A pull main → rebase → git push --force (force thuần, không --force-with-lease). Commit của B biến mất.

Cứu được vì B vẫn có local commit. Nếu B đã git reset --hard thì mất.

Sau vụ đó team thống nhất:

  1. Branch cá nhân = 1 người. Cần 2 người làm chung → tạo branch chung, không rebase.
  2. --force-with-lease mặc định. Ban --force thuần ở mọi guideline nội bộ.
  3. CI cảnh báo khi PR có force push.

Bài học: rebase + force = công cụ mạnh + rủi ro cao. --force-with-lease cứu được case này — B đã push thì lease invalid → A force fail → A buộc phải pull merge B trước.


Lab

mkdir /tmp/team-lab && cd /tmp/team-lab
git init
echo "v1" > app.js && git add . && git commit -m "init"

# Branch feat
git switch -c feat/a
echo "// feat A" >> app.js
git commit -am "feat: A"

# Quay main, làm thay đổi
git switch main
echo "// main update" >> app.js
git commit -am "chore: main"

# Sync feat với main bằng rebase
git switch feat/a
git rebase main
# Conflict — mở app.js, resolve, xoá markers
git add app.js
git rebase --continue

git log --oneline --graph
# Linear history

# Stash demo
echo "WIP edit" >> app.js
git stash push -u -m "WIP"
git status      # clean
git switch main
# ... làm gì đó ...
git switch feat/a
git stash pop

Test conflict abort:

git switch main
echo "main change 2" >> app.js
git commit -am "main 2"

git switch feat/a
git merge main      # conflict!
git merge --abort   # quay về trước, an toàn
git status

Pitfalls

  1. Rebase branch shared → đồng đội mất commit. Branch chỉ rebase khi 100% riêng bạn.
  2. git push --force thuần → ghi đè work. Luôn --force-with-lease.
  3. Quên xoá conflict markers trước commit → push code chứa <<<<<<< → CI fail. Grep <<<<<<< trước commit.
  4. Branch sống 2 tuần không sync main → mega conflict. Sync mỗi ngày.
  5. Reset thay revert trên main → đồng đội loạn. Branch shared = revert, luôn luôn.
  6. PR ngàn dòng → reviewer rubber stamp. Chia nhỏ < 400 dòng.
  7. Stash quên -u → file mới mất khi switch branch.

Tóm tắt

  • Quy tắc vàng: branch riêng → rebase OK, branch shared → merge.
  • --force-with-lease thay --force.
  • Conflict không phải lỗi — đọc markers, quyết, xoá markers, continue.
  • Stash / cherry-pick / revert = 3 tool cứu nhiều tình huống.
  • PR squash and merge mặc định, branch protection trên main.
  • Trunk-based cho SaaS modern, GitFlow cho release-based product.

Bài tiếp: 03 — Khi rối: reflog, reset, bisect, hotfix production.

← Bài trước
Bài 01
Bài tiếp theo →
Bài 03
Zalo tư vấn