Branching strategy, merge/rebase/squash và force push an toàn Trung cấp
Thời gian đọc ước tính: 5 phút
Trang này trả lời: team nên dùng mô hình branch nào, tích hợp PR bằng merge, rebase hay squash, và force push thế nào cho không mất commit của đồng đội.
Chọn branching strategy
Phần tiêu đề “Chọn branching strategy”| Trunk-based (branch ngắn) | GitHub flow | GitFlow | |
|---|---|---|---|
| Branch lâu dài | Chỉ main (trunk) |
Chỉ main |
main + develop, thêm release/*, hotfix/* |
| Branch tính năng | Sống rất ngắn, merge vào trunk ít nhất mỗi ngày | Branch → PR → review → merge → xoá branch | feature/* từ develop, có thể sống lâu |
| Tính năng dở dang | Feature flag, branch by abstraction | Feature flag hoặc giữ PR mở | Giữ trên feature branch |
| Release | Từ trunk, hoặc cắt release branch “just in time” | Deploy từ main |
Qua release/*, merge về main và develop |
| Hợp với | Web service, continuous delivery | Team nhỏ-vừa, deploy liên tục | Phần mềm có nhiều version song song (app cài đặt, SDK, firmware) |
Cơ sở cho lựa chọn:
- trunkbaseddevelopment.com: dev cộng tác trên một nhánh “trunk”, tránh branch dài hạn. Branch tính năng phải ngắn, chỉ để review và chạy CI; mỗi dev tích hợp vào trunk ít nhất mỗi 24 giờ.
- Tác giả GitFlow, Vincent Driessen, thêm “note of reflection” (5/3/2020): web app thường continuous delivery, không phải hỗ trợ nhiều version. Với team như vậy ông khuyên workflow đơn giản hơn như GitHub flow.
Mặc định cho backend service: trunk-based / GitHub flow, PR nhỏ, feature flag cho việc dài. Chỉ dùng GitFlow khi thật sự phải vá nhiều version đang chạy ngoài kia.
Merge, rebase hay squash
Phần tiêu đề “Merge, rebase hay squash”Branch feature có 3 commit (A, B, C), tách từ main tại M1; trong lúc đó main có thêm M2.
Trước khi tích hợp: main: M1 ── M2 \ feature: A ── B ── C
1) Merge commit (git merge --no-ff / "Create a merge commit") main: M1 ── M2 ─────────── X X có 2 parent: M2 và C \ / A ──── B ──── C ─ giữ nguyên A, B, C và hash cũ
2) Rebase rồi fast-forward (git rebase main / "Rebase and merge") main: M1 ── M2 ── A' ── B' ── C' A', B', C' là commit MỚI (hash mới)
3) Squash merge ("Squash and merge") main: M1 ── M2 ── S S = A+B+C gộp làm một commit| Merge commit | Rebase + fast-forward | Squash merge | |
|---|---|---|---|
Lịch sử main |
Có nhánh, đầy đủ | Thẳng, giữ từng commit | Thẳng, 1 commit/PR |
| Hash commit gốc | Giữ | Đổi | Mất (commit gốc chỉ còn trên branch) |
| Revert cả PR | git revert -m 1 X |
Revert từng commit | git revert S |
| Hợp khi | Commit trong PR đều có chủ đích, cần giữ | Commit đã được dọn sạch, atomic | PR nhỏ, commit trung gian là “wip” |
GitHub docs lưu ý: squash merge làm mất SHA gốc. Nếu tiếp tục mở PR mới từ cùng branch đã squash, commit cũ có thể xuất hiện lại và gây conflict lặp. Sau squash merge: xoá branch, tạo branch mới từ main.
Golden rule của rebase
Phần tiêu đề “Golden rule của rebase”Pro Git: “Do not rebase commits that exist outside your repository and that people may have based work on.”
Rebase không “di chuyển” commit. Nó tạo commit mới (A') và bỏ commit cũ (A). Ai đã build trên A phải merge lại, và lịch sử có hai bản A (xem demo trong Lỗi hay gặp).
| Branch | Rebase lên origin/main? |
|---|---|
| Branch của riêng bạn, kể cả đã push để mở PR | Được — miễn chưa ai base công việc lên nó |
main, develop, branch nhiều người cùng push |
Không bao giờ — dùng git merge origin/main |
--force-with-lease thay cho --force
Phần tiêu đề “--force-with-lease thay cho --force”Rebase branch đã push thì bắt buộc force push. Khác biệt:
| Lệnh | Hành vi |
|---|---|
git push --force |
Tắt mọi kiểm tra; ghi đè remote dù trên đó có commit bạn chưa thấy. Docs: “It can cause the remote repository to lose commits” |
git push --force-with-lease |
Chỉ ghi đè nếu ref trên remote vẫn bằng remote-tracking ref của bạn (origin/feature) |
git push --force-with-lease --force-if-includes |
Thêm kiểm tra: tip của remote-tracking ref phải reachable từ một entry trong reflog của branch local — chống trường hợp IDE/cron tự git fetch ngầm làm lease mất tác dụng |
Đang tải diagram…
Alias nên có trong ~/.gitconfig:
git config --global alias.pushf "push --force-with-lease --force-if-includes"Sửa commit cũ trong PR: --fixup + --autosquash
Phần tiêu đề “Sửa commit cũ trong PR: --fixup + --autosquash”Reviewer chỉ ra lỗi ở commit refactor (không phải commit cuối). Đừng tạo commit “fix review” lẻ loi.
git add src/order/pricing.tsgit commit --fixup=3f2a9c1 # tạo commit "fixup! refactor(order): tách tính giá..."git rebase -i --autosquash origin/main# todo list tự đặt "fixup 7b1e..." ngay sau 3f2a9c1 — lưu và thoátgit push --force-with-leaseTheo docs:
--autosquashnhận diện message bắt đầu bằngfixup!,squash!,amend!và chuyển commit đó về ngay sau commit đích.--fixup=amend:<commit>thay luôn message;--fixup=reword:<commit>chỉ đổi message.- Bật mặc định:
git config --global rebase.autoSquash true.
Mẹo: muốn reviewer xem riêng phần sửa, push commit fixup! trước, đợi approve rồi mới autosquash.
Tiếp theo: Cứu sự cố.