# Gitlab Cherrypick

> Use khi user nói "cherry-pick to release/<app>/<v>", "cherry-pick main vào release", "backport WRA-40 sang release", "đưa commit vào release", "list releases", "list commit main 5 ngày", "patch release", hoặc khi cần đưa fix/feature đã land `main`/`dev` sang nhánh `release/<app>/<version>` đã cut. Áp dụng cho repo monorepo multi-app có release branch riêng theo app + version.

- Skill: `nguyenvanchiens/gitlab-cherrypick` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nguyenvanchiens/gitlab-cherrypick`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nguyenvanchiens/gitlab-cherrypick/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools, Productivity
- Author: nguyenvanchiens (https://skillmd.com/u/nguyenvanchiens)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/nguyenvanchiens/gitlab-cherrypick

---


# 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

1. 🚫 **KHÔNG commit thẳng vào `release/<app>/<version>`** — kể cả "fix nhanh 1 dòng"
2. 🚫 **KHÔNG PR `release/<app>/* → main`** — chiều ngược, làm rác main
3. 🚫 **KHÔNG cherry-pick commit chưa land main** — main là source of truth, cherry phải có gốc rõ ràng
4. 🚫 **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ủ ý
5. ✅ **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.

```bash
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**:

```bash
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:

```bash
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**:

1. **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`.**
2. **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**:

```bash
# 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.

```bash
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`:
```bash
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.

```bash
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ó)**:

```bash
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)**:
```bash
git checkout --theirs <file>
git add <file>
```

**Cách giữ phía release (HEAD)**:
```bash
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:
```bash
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**:

```bash
git log origin/release/<app>/<version>..HEAD --oneline    # list commit vừa cherry-pick
```

1. Báo user: list các commit mới + tóm tắt
2. **DỪNG, hỏi user**: "Đã cherry-pick `<N>` commit vào nhánh `cherry/...`. Push lên remote và tạo MR không?"
3. Đợi xác nhận:

```bash
git push -u origin cherry/<source>-to-release-<app>-<version>
```

**Step 8 — Tạo MR `cherry/* → release/<app>/<version>`** (KHÔNG về main):

```bash
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**:
- [ ] `--source-branch` bắt đầu bằng `cherry/`
- [ ] `--target-branch` là `release/<app>/<version>` (KHÔNG phải `main`, KHÔNG phải `builds/dev/*`)
- [ ] App + version trong source khớp target (`cherry/...-gift-api-v0.5.3` → `release/gift-api/v0.5.3`)
- [ ] Title có prefix `chore(cherry):`
- [ ] `--remove-source-branch` có mặt (cherry branch là ephemeral)
- [ ] Description **KHÔNG** chứa `Claude`, `🤖`, `Generated with`, `claude.com`, `Co-Authored-By:`

**Step 9 — Sau khi MR merged**:

```bash
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`):

```bash
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:

```bash
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

- `git` (luôn có)
- `glab` (GitLab CLI) — cần để tạo MR cherry-pick. Tham khảo install: https://gitlab.com/gitlab-org/cli

## 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)

