# Stacked PR：父 PR 经 squash merge 到 develop 后，子 PR 用 cherry-pick 而不是 git merge

**触发**：2026-05-16 PR #397 stacked on PR #395，#395 被 squash-merge 后 #397 的 rebase 一直冲突

## 现象

`feature/agent-phase1-gap-fill`（PR #397）的 base 设为 `feature/agent-phase1-quality-batch`（PR #395）。当 PR #395 通过 **squash merge** 合到 develop 后，develop 上是一个**新的 squash commit**（commit hash 与子分支链里原 commits 全部不同）。

子分支链：
```
gap-fill: 75fee93c → 31b9781b → 5a44463f → 9cfdec8d → 73592d0d → 9dd26950 → faadacc3 → 57e7cfbc
            └──── 这 4 个是 #395 内容 ────┘   └──── 这 4 个是 #397 独有 ────┘
```

develop 上：
```
develop: ... → 61e73096 (squash merge of #395 — 单个 commit 包含 75fee93c..9cfdec8d 全部 diff)
```

## 错误做法 1：直接 `git merge origin/develop`

会让 #397 分支同时持有「原 #395 的 4 个 commit」+「develop 上的 squash 版本」。最终 PR diff 显示**重复内容**（因为 git 看到的是两次相同 diff）+ merge commit。reviewer 看不清，CI 可能拒。

## 错误做法 2：`git rebase origin/develop`

```
Rebasing (1/8)
Auto-merging .learnings/2026-05-16-ffai-agent-ops-deferred.md
CONFLICT (add/add): Merge conflict in .learnings/2026-05-16-ffai-agent-ops-deferred.md
CONFLICT (content): Merge conflict in backend/src/modules/agent/services/messages.service.ts
... 5+ conflicts
```

Rebase 试图把每个旧 commit 重新 apply 到 develop 上。但 develop 已含 squash 后的相同改动，**每个 commit 都和 develop 的 squash 冲突**。手工解 5+ 个冲突浪费时间。

## 正确做法：reset + cherry-pick 独有 commits

```bash
git rebase --abort  # 放弃 rebase
git reset --hard origin/develop  # 重置到 develop tip（含 #395 的 squash）
# 只 cherry-pick 子分支独有的（不属于 #395 的）commits
for SHA in 73592d0d 9dd26950 faadacc3; do
  git cherry-pick $SHA
done
git push --force-with-lease origin HEAD:feature/agent-phase1-gap-fill
```

Cherry-pick 看的是**单个 commit 的 diff**，apply 到目标分支。由于这些 commit 改的文件在 develop（含 squash 版 #395）里语义上已经满足前置条件，apply 通常零冲突。

## 操作要点

1. **识别独有 commits**：用 `git log <父-PR-tip>..HEAD --oneline` 列子分支独有的，或人工识别 commit message 区分父子主题
2. **cherry-pick 跳过空 commit**：如果某 commit 内容已被 develop 吸收（如本次的 `57e7cfbc` 空 retrigger commit），cherry-pick 会 fail 提示 "nothing to commit"——用 `git cherry-pick --skip` 跳过
3. **force-with-lease 而不是 force**：避免覆盖远端他人 push（如 "Update branch" auto-merge）
4. **改 PR base 到 develop**：cherry-pick 完后通过 Gitea API PATCH `pulls/{id}` 把 `base` 从父分支改为 `develop`：
   ```bash
   curl -X PATCH -H "Authorization: token $GITEA_API_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"base":"develop"}' \
     http://43.130.59.228/api/v1/repos/FFAIWorkspace/workspace/pulls/<id>
   ```

## 何时 vs 不用 cherry-pick

| 父 PR merge style | 子 PR 处理 |
|---|---|
| **squash** | cherry-pick 独有 commits（本文） |
| **rebase**（保留每个 commit）| `git rebase origin/develop` 一般干净，因为 base 上的 SHA 还在 |
| **fast-forward-only** | 同 rebase，base 上的 SHA 还在 |
| **merge**（保留 merge commit）| 子分支 rebase 仍可能冲突，cherry-pick 更稳 |

## 复用条件

任何 stacked PR 场景：
- 父 PR squash-merged
- 子 PR 还 open

不需要重新做。
