GitLab Cherry-pick (main → release/<app>/<version>)
Skill này pair với gitlab-flow và gitlab-sync. Trong khi:
gitlab-flow lo feature → main
gitlab-sync lo main → builds/dev/<app> (deploy QA)
Skill này lo phần main → release/<app>/<version> — backport commit cụ thể để cut patch release.
Phạm vi: Chỉ tập trung main → release/<app>/<version>. Cut release branch mới (release/<app>/<v-mới>), deploy prod, tag release — out of scope, Maintainer làm tay.
Nguyên tắc cốt lõi
Code chỉ chảy 1 chiều từ main → release/<app>/<version>.
**Cherry-pick TRÊN nhánh trung gian cherry/, PR ngược về release/<app>/<version> — KHÔNG BAO GIỜ lôi code release/ vào main.**
Hierarchy của branch — Monorepo multi-app
┌──→ release/portal-web-admin/v1.2.0
├──→ release/portal-web-admin/v1.3.0
main ────────┼──→ release/gift-api/v0.5.3 ──→ deploy prod (Maintainer làm tay)
├──→ release/gift-api/v0.6.0
└──→ release/<app>/<other-version>
| Tier |
Branch |
Vai trò |
| Source of truth |
main |
Integration. Mọi feature/fix đều land ở đây trước |
| Release snapshot |
release/<app>/<version> |
1 nhánh cho mỗi app + mỗi version. Frozen sau khi cut, chỉ nhận cherry-pick patch |
| Ephemeral |
cherry/* |
Tạm thời, xoá ngay sau khi MR merged |
Key insight: cherry-pick không phải full sync (như gitlab-sync) — chỉ đưa subset commit từ main về release. Lý do: release đã frozen, chỉ nhận patch có chủ ý, không nhận hết mọi thay đổi của main.
Rule không bao giờ vi phạm
- 🚫 KHÔNG commit thẳng vào
release/<app>/<version> — kể cả "fix nhanh 1 dòng"
- 🚫 **KHÔNG PR
release/<app>/* → main** — chiều ngược, làm rác main
- 🚫 KHÔNG cherry-pick commit chưa land main — main là source of truth, cherry phải có gốc rõ ràng
- 🚫 KHÔNG dùng
git merge main để "đồng bộ" release — sẽ kéo HẾT main về, vỡ frozen state. Phải cherry-pick từng commit có chủ ý
- ✅ **Mọi cherry-pick → tạo MR
cherry/* → release/<app>/<version>** để team review trước khi vào release
Conventions
Source branch (nhánh tích hợp nguồn)
<source> = nhánh tích hợp mà team merge feature vào (source of truth để backport). Mặc định main. Một số project dùng dev/develop.
- Skill KHÔNG tự đoán
<source>. Quy tắc:
| Tình huống |
Hành động |
| User nói rõ trong prompt (vd "cherry-pick từ dev", "backport từ develop") |
Dùng luôn, không hỏi |
| Mọi trường hợp còn lại |
HỎI user chọn main hay dev (chạy git branch -r gợi ý tên thật) trước khi list commit |
- Một khi chốt
<source>, dùng nhất quán cho: list commit (git log origin/<source> ...), loại trừ commit đã có trên release (^origin/release/...), và đặt tên cherry branch.
- ⚠️ Đồng bộ với
gitlab-flow: nếu project tạo feature branch từ dev thì <source> cherry-pick cũng là dev. Đừng cứng nhắc main.
Cherry branch naming
Format: cherry/<source>-to-release-<app>-<version>[-<desc>] (vd cherry/main-to-release-... hoặc cherry/dev-to-release-...)
| Use case |
Tên nhánh ví dụ |
Cherry-pick 1-2 commit vào release/portal-web-admin/v1.2.0 |
cherry/main-to-release-portal-web-admin-v1.2.0 |
| Cherry-pick nhiều commit cho 1 fix cụ thể |
cherry/main-to-release-gift-api-v0.5.3-WRA-334-vat-fix |
| Cherry-pick 2 lần trong ngày trên cùng release |
thêm -2026-05-20-2 |
| Rule |
Detail |
| Prefix |
Luôn cherry/ — KHÔNG dùng feature/, fix/, sync/ |
| Format cơ bản |
cherry/<source>-to-release-<app>-<version> — đọc 1 phát biết chiều, app, version |
| Slash → dash |
release/<app>/<v> (slash) → trong cherry branch dùng dash: <app>-<v> |
| Optional suffix |
Thêm -<TASK-ID>-<short> khi cherry-pick có theme rõ (vd hotfix VAT) |
| Tuổi thọ |
Ephemeral — xoá ngay sau khi MR merged |
| Length |
Target ≤ 70 chars. Quá dài → bỏ suffix -<desc> |
Cherry-pick commit message
Mặc định dùng git cherry-pick -x <SHA> — git tự thêm dòng (cherry picked from commit <SHA>) vào cuối body. Giữ nguyên dòng này để trace về commit gốc.
| Element |
Rule |
| Subject |
Giữ NGUYÊN từ commit gốc (KHÔNG sửa) |
| Body |
Giữ nguyên + dòng (cherry picked from commit <SHA>) cuối |
| TASK-ID |
Giữ nguyên (<TASK-ID>) cuối subject |
| 🚫 KHÔNG thêm |
Co-Authored-By: Claude ... (kế thừa rule gitlab-flow) |
Ví dụ commit sau cherry-pick:
fix(billing): tính sai VAT cho đơn hàng có discount (WRA-334)
Tách hàm tính VAT khỏi total để tránh double-apply discount.
(cherry picked from commit def5678abc1234)
Target branch của MR
| Cherry branch |
MR target |
KHÔNG được set target = |
cherry/main-to-release-<app>-<v> |
release/<app>/<v> |
❌ main, ❌ release/<app>/<v-khác>, ❌ builds/dev/* |
⚠️ Self-check trước khi tạo MR:
1. Target bắt buộc là release/<app>/<version> — KHÔNG được PR vào main
2. Version trong cherry branch phải khớp version trong target (cherry/...-v1.2.0 → release/<app>/v1.2.0)
Triggers & Procedures
"list releases" / "show me các release branch" / "có release nào"
Liệt kê toàn bộ release/<app>/<version> hiện có — giúp user pick đúng target khi cherry-pick.
git fetch --all --prune
git ls-remote --heads origin "refs/heads/release/*" | awk '{print $2}' | sed 's|refs/heads/||' | sort
Output ví dụ:
release/gift-api/v0.5.3
release/gift-api/v0.6.0
release/portal-web-admin/v1.2.0
release/portal-web-admin/v1.3.0
Format report cho user (group theo app):
## Release branches
### portal-web-admin
- v1.2.0
- v1.3.0 (latest)
### gift-api
- v0.5.3
- v0.6.0 (latest)
Hỏi tiếp: "Bạn muốn cherry-pick vào release nào?" → trigger cherry-pick to release/<app>/<v>.
"list commit main last <N> days" / "liệt kê commit dev 5 ngày" / "show me commit gần đây"
Liệt kê commit trên nhánh tích hợp (<source>) trong N ngày qua — để user khảo sát trước khi quyết định pick.
Step 1 — Detect source + N từ input:
- Source: user gõ tên nhánh trong trigger (
list commit dev 5 ngày, commit main ...) → dùng nhánh đó. Không nói rõ → hỏi main hay dev (như mục Source branch).
- N (ngày):
| Input |
N (ngày) |
list commit dev last 5 days |
5 |
liệt kê commit main 7 ngày |
7 |
| Không có số |
STOP, hỏi: "Lùi bao nhiêu ngày? (default 7)" |
Step 2 — Fetch + list:
git fetch origin <source>
# --reverse BẮT BUỘC: git log mặc định in newest-first, ngược với bảng bên dưới
git log origin/<source> --since="<N> days ago" --reverse --pretty=format:"%h|%an|%ad|%s" --date=short
Step 3 — Format đẹp cho user:
## Commit trên <source> (5 ngày qua, mới nhất ở dưới)
| # | SHA | Author | Date | Subject |
|---|-----|--------|------|---------|
| 1 | abc1234 | nguyenvana | 2026-05-16 | feat(auth): restrict login domain (WRA-40) |
| 2 | def5678 | tranthib | 2026-05-17 | fix(billing): tính sai VAT (WRA-334) |
| 3 | 9876543 | lehvc | 2026-05-18 | refactor(order): tách handler (WRA-412) |
| 4 | 1234abc | nguyenvana | 2026-05-20 | feat(gift): báo cáo POD theo miền (HNCW-317) |
Trigger này độc lập, không yêu cầu release target. Dùng để khảo sát. Nếu user muốn pick → chạy cherry-pick to release/<app>/<v>.
"cherry-pick to release/<app>/<v>" / "cherry-pick main vào release/<app>/<v>" / "backport sang release/<app>/<v>"
Trigger chính. Interactive flow 2 tầng: chọn nhánh release → list commit → chọn commit → execute.
Luồng 2 tầng interactive: (0) chọn nhánh release target → (1-3) chọn commit. Luôn chốt nhánh trước khi đụng tới commit.
Step 0 — Xác định release target (CHỌN NHÁNH trước):
Parse phần text sau trigger:
| Input pattern |
Hành động |
Có đủ release/<app>/<version> (vd cherry-pick to release/gift-api/v0.5.3) |
Dùng luôn app + version đó, không hỏi → sang "verify" |
Có app nhưng thiếu version (vd cherry-pick to release gift-api) |
List các version của app đó → user pick theo số |
Không có app/version (vd cherry-pick, cherry-pick WRA-40, backport fix VAT) |
CHỌN NHÁNH interactive (bên dưới) — KHÔNG tự đoán release nào |
Chọn nhánh interactive (khi target chưa rõ): chạy logic list releases, in danh sách release đánh số rồi hỏi user pick:
## Chọn release branch để cherry-pick vào:
| # | Release branch |
|---|---|
| 1 | release/portal-web-admin/v1.2.0 |
| 2 | release/portal-web-admin/v1.3.0 (latest) |
| 3 | release/gift-api/v0.5.3 |
| 4 | release/gift-api/v0.6.0 (latest) |
Pick số (vd `4`):
- User pick số → đó là release target.
- User gõ thẳng
release/<app>/<v> → cũng chấp nhận.
- Repo không có release branch nào → STOP, báo "Chưa có release branch nào trên remote".
Verify release target đã chốt tồn tại trên remote:
git fetch --all --prune
git ls-remote --heads origin "refs/heads/release/<app>/<version>" | head -1
- Output rỗng → STOP, báo "Không tìm thấy
release/<app>/<version>. Chạy list releases xem các release có sẵn"
- Có output → tiếp tục Step 1
Step 1 — Xác định source branch + số ngày lùi:
- Source branch (
<source>) theo mục Source branch: trừ khi user nói rõ trong prompt, HỎI user chọn main hay dev (chạy git branch -r gợi ý). Đây là nhánh tích hợp để lấy commit backport. KHÔNG mặc định cứng main.
- Số ngày lùi (N): Hỏi "Lùi bao nhiêu ngày để list commit
<source>? (default 7)"
| User trả lời |
N |
5 / 5 ngày / 5d |
5 |
all / tất cả |
KHÔNG since filter (list từ phân kỳ với release) |
Enter / default |
7 |
Nếu user gõ trigger kèm last <N> days ngay từ đầu → skip hỏi N, dùng luôn. Source vẫn hỏi nếu chưa rõ.
Step 2 — List commit <source>, loại trừ commit có sẵn trên release:
# Commit có trên <source> nhưng KHÔNG có trên release target
# --reverse BẮT BUỘC: số thứ tự `#` user pick phải chạy cũ → mới, khớp thứ tự
# cherry-pick ở Step 5. Bỏ flag này = list newest-first = pick ngược chronological
git log origin/<source> ^origin/release/<app>/<version> \
--since="<N> days ago" --reverse \
--pretty=format:"%h|%an|%ad|%s" --date=short
| Output |
Hành động |
| Rỗng |
STOP, báo "Không có commit mới trên <source> trong <N> ngày qua mà chưa có trên release. Thử tăng N hoặc check release đã up-to-date" |
| Có list |
Format đẹp như Step 3 của trigger list commit main (kèm số # để user pick) |
Step 3 — Hỏi user pick commit:
Bắt buộc in kèm hướng dẫn cú pháp ngay dưới bảng commit (đừng chỉ hỏi trống "pick commit?"):
Pick commit cần cherry-pick (theo số # ở cột đầu):
• Nhiều commit rời: 2, 4
• Khoảng liên tục: 1-3
• Mix: 2, 4-6, 8
• Tất cả: all
• Theo SHA: f145c62, d336a84
• Huỷ: cancel
| Format user trả lời |
Diễn giải |
2, 4 |
Pick commit số 2 và 4 |
1-3 |
Pick commit số 1, 2, 3 |
2, 4-6, 8 |
Mix — pick 2, 4, 5, 6, 8 |
all |
Pick toàn bộ list |
abc1234, def5678 |
Pick bằng SHA (nếu user thích cách này) |
cancel / huỷ |
Abort, không cherry-pick |
Sau khi user pick, echo lại danh sách commit sẽ cherry-pick + hỏi xác nhận:
## Sẽ cherry-pick 3 commit sau vào release/gift-api/v0.5.3:
1. def5678 — fix(billing): tính sai VAT (WRA-334)
2. 9876543 — refactor(order): tách handler (WRA-412)
3. 1234abc — feat(gift): báo cáo POD theo miền (HNCW-317)
Xác nhận? (yes / no / edit)
| User trả lời |
Hành động |
yes / ok / xác nhận |
Sang Step 4 |
no / huỷ |
Abort |
edit / sửa pick |
Quay lại Step 3 |
Step 4 — Tạo cherry branch từ release target:
⚠️ Cherry branch phải tạo từ release/<app>/<version>, KHÔNG từ main. Vì mục tiêu là apply commit LÊN release state, không phải merge release vào main.
git fetch origin release/<app>/<version>
git checkout -b cherry/<source>-to-release-<app>-<version> origin/release/<app>/<version>
Vd cho release/gift-api/v0.5.3, source = main:
git checkout -b cherry/main-to-release-gift-api-v0.5.3 origin/release/gift-api/v0.5.3
(source = dev → nhánh là cherry/dev-to-release-gift-api-v0.5.3)
Step 5 — Cherry-pick từng commit theo thứ tự chronological (cũ → mới):
Quan trọng: cherry-pick theo thứ tự commit date ASC (cũ nhất trước). Lý do: commit sau có thể depend on commit trước; pick ngược thứ tự sẽ tăng nguy cơ conflict. Danh sách ở Step 2 đã in sẵn đúng thứ tự này (nhờ --reverse) → giữ nguyên thứ tự số # user pick, đừng sắp lại.
git cherry-pick -x <SHA1> <SHA2> <SHA3>
Flag -x: thêm dòng (cherry picked from commit <SHA>) vào body — bắt buộc để trace.
| Kết quả |
Hành động |
| All cherry-pick thành công |
Sang Step 7 |
CONFLICT (content): ... ở commit N |
Sang Step 6 (resolve), sau đó git cherry-pick --continue |
| User muốn skip 1 commit giữa flow |
git cherry-pick --skip (commit đó bị bỏ, tiếp commit sau) |
| User muốn abort toàn bộ |
git cherry-pick --abort → quay về state trước Step 5 |
Step 6 — Resolve conflict (nếu có):
git status # list file conflict
git diff --name-only --diff-filter=U # chỉ list file đang conflict
Quy tắc resolve:
| Tình huống |
Bên nên giữ |
Lý do |
| Code logic của feature đang pick |
phía cherry (incoming) |
Đó chính là thay đổi muốn backport |
| Code đã thay đổi trên release (bug fix release-specific) |
release (HEAD) |
Release có path riêng cho fix đó, không được ghi đè |
| File config version-specific (vd version string, changelog) |
release (HEAD) |
Version branch giữ version riêng |
| Resolution không rõ |
STOP, hỏi user |
Cherry-pick sai = release ship code lỗi |
Cách giữ phía cherry (incoming commit):
git checkout --theirs <file>
git add <file>
Cách giữ phía release (HEAD):
git checkout --ours <file>
git add <file>
Cách edit thủ công (default — nên dùng cho code logic):
- Mở file, xoá marker
<<<<<<<, =======, >>>>>>>
- Merge logic của 2 phía nếu cả hai cần thiết
git add <file>
Sau khi resolve hết:
git status # phải sạch, không còn file `U`
git cherry-pick --continue # tiếp tục cherry-pick các commit còn lại
Editor sẽ mở để confirm commit message — giữ nguyên message gốc + dòng (cherry picked from commit <SHA>). Đừng tự sửa.
Step 7 — Verify + push gate:
git log origin/release/<app>/<version>..HEAD --oneline # list commit vừa cherry-pick
- Báo user: list các commit mới + tóm tắt
- DỪNG, hỏi user: "Đã cherry-pick
<N> commit vào nhánh cherry/.... Push lên remote và tạo MR không?"
- Đợi xác nhận:
git push -u origin cherry/<source>-to-release-<app>-<version>
**Step 8 — Tạo MR cherry/* → release/<app>/<version>** (KHÔNG về main):
glab mr create \
--source-branch cherry/<source>-to-release-<app>-<version> \
--target-branch release/<app>/<version> \
--title "chore(cherry): backport <N> commit từ <source> → release/<app>/<version>" \
--description "$(cat <<'EOF'
## Summary
- Cherry-pick <N> commit từ <source> vào release/<app>/<version>
- Mục đích: <patch release / hotfix / backport feature>
## Commits backported
| Original SHA | Subject |
|---|---|
| def5678 | fix(billing): tính sai VAT (WRA-334) |
| 9876543 | refactor(order): tách handler (WRA-412) |
| 1234abc | feat(gift): báo cáo POD theo miền (HNCW-317) |
## Conflict resolution (nếu có)
- File X: giữ phía cherry (incoming)
- File Y: edit thủ công, merge logic 2 bên
## Test plan
- [ ] CI build pass trên cherry branch
- [ ] Smoke test golden path của app <app> trên release version <v>
- [ ] Regression test các module liên quan tới commit cherry-pick
EOF
)" \
--remove-source-branch
Self-check trước khi chạy lệnh trên:
Step 9 — Sau khi MR merged:
git checkout <source> # main hoặc dev — nhánh tích hợp
git fetch --prune
git branch -d cherry/<source>-to-release-<app>-<version> # local đã xoá
# Remote tự xoá bởi --remove-source-branch
Báo user: release <app>/<v> giờ có thể được Maintainer tag + deploy prod.
Quick decision: user nói gì → trigger gì
| User nói |
Trigger |
| "có release nào", "list release branches" |
list releases |
| "list commit main 5 ngày" |
list commit main last 5 days |
| "cherry-pick fix VAT vào release gift-api v0.5.3" |
cherry-pick to release/gift-api/v0.5.3 (sau đó pick commit theo số) |
| "backport WRA-40 sang release portal-web-admin v1.2.0" |
cherry-pick to release/portal-web-admin/v1.2.0 |
| "patch release portal-web-admin v1.2.0" |
cherry-pick to release/portal-web-admin/v1.2.0 |
Edge cases
Commit phụ thuộc lẫn nhau
Nếu commit B build trên commit A (vd A thêm helper, B dùng helper) — phải pick cả A và B, theo thứ tự A→B. Skill pick theo chronological ASC nên tự nhiên đúng thứ tự, nhưng nếu user pick chỉ B mà bỏ A → cherry-pick sẽ conflict hoặc fail compile.
Khuyến nghị: trước khi confirm pick (Step 3), nếu skill detect các commit pick KHÔNG liên tục trên main → warn user: "Đang skip commit <SHA-giữa> — có chắc commit pick không depend on nó không?"
Commit đã cherry-pick rồi (duplicate)
Nếu user vô tình pick lại commit đã có trên release (vd quên check list releases):
git cherry-pick -x <SHA>
# error: The previous cherry-pick is now empty
→ Báo user: "Commit <SHA> đã có sẵn trên release. Skip bằng git cherry-pick --skip hoặc abort."
Để phòng trường hợp này, Step 2 đã loại trừ commit có sẵn (^origin/release/<app>/<v>).
Revert commit trên main → có cần cherry-pick revert?
Nếu main đã revert commit X (vd commit Y = revert of X), nhưng X chưa từng land release → không cần pick cả 2, vì release chưa có X. Skill detect: nếu pick có cả X và Y (revert X) → warn user.
Release đã tag rồi, commit không vào kịp
Nếu release/<app>/<v> đã được tag và deploy prod, không nên cherry-pick thêm — phải cut release mới (v.<X.Y.Z+1>). Skill check trước khi pick:
git tag --points-at origin/release/<app>/<version>
Nếu có tag pointing tới HEAD của release → warn user: "Release đã tag <tag>. Cherry-pick thêm sẽ làm release version khớp với tag không nhất quán. Khuyến nghị cut release mới."
Out of scope (Maintainer làm tay)
| Flow |
Lý do skip |
Cut release branch mới (main → release/<app>/<v-mới>) |
Lần đầu cut từ main = FF được, Maintainer làm tay |
Tag release (git tag v1.2.0) + deploy prod |
Cần Maintainer judgement + permission |
Cherry-pick builds/dev/* → main |
Vi phạm rule 1-chiều — dùng gitlab-sync audit thay thế |
Cherry-pick release/<app>/* → main |
Vi phạm rule 1-chiều — fix gốc trên main, cherry ngược |
Merge main → release/<app>/<v> (full sync) |
Vỡ frozen state của release — chỉ accept cherry-pick có chủ ý |
Safety rules
- 🚫 **KHÔNG tạo MR
release/<app>/* → main** dưới bất kỳ hình thức nào. Nếu user yêu cầu, từ chối + giải thích rule 1-chiều
- 🚫 KHÔNG
git merge main vào release branch — sẽ kéo HẾT main về, vỡ frozen state
- 🚫 KHÔNG
git push --force vào main, release/, builds/ — kể cả --force-with-lease
- 🚫 KHÔNG cherry-pick commit chưa land main (vd commit từ branch feature chưa merge)
- 🚫 KHÔNG bỏ flag
-x — mất trace SHA gốc, sau audit không biết commit đến từ đâu
- ✅ Mọi cherry branch là ephemeral — đảm bảo
--remove-source-branch khi tạo MR
- ✅ Cherry-pick theo thứ tự chronological (cũ → mới) để giảm conflict
- ✅ Hỏi user trước khi resolve conflict nếu không chắc bên nào đúng — đặc biệt với file config, migration, route
- ✅ Verify target branch của MR trước khi
glab mr create — đọc lại --target-branch 2 lần
- ✅ Echo lại danh sách commit sẽ pick + chờ xác nhận trước khi execute (Step 3)
- 🚫 KHÔNG chèn AI attribution (Co-Authored-By Claude, 🤖, claude.com) vào: cherry-pick commit message (giữ nguyên message gốc), MR title/description, comment MR
Tools required
Interaction với gitlab-flow / gitlab-sync
| Tình huống |
Dùng skill |
| Feature mới từ Jira task → main |
gitlab-flow |
| Review/merge MR feature → main |
gitlab-review |
| Sau feature merged, deploy QA (sync main → builds/dev) |
gitlab-sync |
| Audit builds/dev có sạch không |
gitlab-sync (build hygiene) |
| Backport fix/feature từ main → release để cut patch |
gitlab-cherrypick (skill này) |
| Cut release branch mới từ main |
Out of scope — Maintainer làm tay |
| Tag + deploy prod |
Out of scope — Maintainer làm tay |
3 skill độc lập nhưng bổ sung nhau:
gitlab-flow lo "code đi vào main"
gitlab-sync lo "main → builds/dev/<app>" (QA deploy)
gitlab-cherrypick lo "main → release/<app>/<v>" (patch release)