首页
/ Zed 发布补丁实践:script/cherry-pick 脚本、cherry_pick 工作流与 preview/stable 分支的手动 Cherry-Pick 流程

Zed 发布补丁实践:script/cherry-pick 脚本、cherry_pick 工作流与 preview/stable 分支的手动 Cherry-Pick 流程

2026-09-03 15:28:07作者:伍希望

Zed 的 main 分支上合并的修复,最终需要落到 previewstable 两条长期维护的发布分支上。本篇以仓库中的 runbook 文档 .agents/skills/zed-cherry-pick/SKILL.md 为主体,结合 cherry-pick 脚本cherry_pick 工作流 的源码级实现,完整讲解如何在自动化的 GitHub Actions 工作流失败(几乎总是合并冲突)时,在本地手工完成 cherry-pick 并开出一个与自动化产物不可区分的 PR——读完你能掌握通道到分支的映射查询、冲突判定与解决准则、验证与收尾的全部操作。

1. 发布模型:两条长期分支与"绝不硬编码"的通道映射

Zed 从位于 origin 的两条长期发布分支出货(runbook 原文表述):

  • preview 通道 → 形如 v1.4.x 的分支
  • stable 通道 → 形如 v1.3.x 的分支

版本号随每次发布变化,因此绝不能硬编码通道与分支的映射,必须按当前仓库状态动态发现(见第 3 节)。

这一"分支名即 vX.Y.x"的约定可以从版本提升流程得到印证:bump_zed_version 工作流 中明确计算 preview_branch="v${major}.${minor}.x"(从当前 main 切出新 preview 分支),并通过 script/get-released-version preview 反查已发布的 preview 版本来推导 stable_branch="v${stable_major}.${stable_minor}.x"。同时 release_channel 定义 中通道取值为 stable | dev | nightly | preview 四种,可见 preview/stable 是通道(channel)概念,而非分支名——这正是后文"陷阱"一节强调 git fetch origin preview 会失败的原因。

2. 自动化基线:读懂 script/cherry-pick 脚本

runbook 明确指出:规范流程住在 script/cherry-pickcherry_pick 工作流里,"如果任何地方看起来不对劲,先读脚本——你的本地步骤必须产出与它相同的分支名、PR 标题和 PR 正文"。脚本签名:

script/cherry-pick <branch-name> <commit-sha> <channel>
  • <branch-name> 是发布分支(如 v1.4.x),不是通道名;
  • <channel>previewstable,仅用于 PR 标题/正文中的展示文本。

对照脚本源码(script/cherry-pick,全文仅 34 行),其完整行为如下:

步骤 源码行为 说明
参数校验 set -euxo pipefail + 参数数检查(L2-L7) 任一命令失败立即退出,且逐行回显命令,便于定位失败点
构造分支名 SHORT_SHA="${COMMIT_SHA:0:8}"NEW_BRANCH="cherry-pick-${BRANCH_NAME}-${SHORT_SHA}"(L13-L14) 短 SHA 取提交前 8 个字符;分支名必须严格遵循 cherry-pick-<branch-name>-<short-sha> 约定
拉取 git fetch --depth 2 origin +${COMMIT_SHA} ${BRANCH_NAME}(L15) 从源码结构看,--depth 2 浅拉取提交及其父提交,为 cherry-pick 提供完整 diff 上下文
建本地分支 git checkout --force "origin/$BRANCH_NAME" -B "$NEW_BRANCH"(L16) 以发布分支远端引用为基线强制重建本地分支
拣选 git cherry-pick "$COMMIT_SHA"(L18) 冲突时在此中止
推送 git push origin -f "$NEW_BRANCH"(L20) 强制推送
生成 PR 文本 提取 %s/%b,正则匹配标题尾部 (#数字)(L21-L30) 见下文标题/正文格式
开 PR gh pr create --base ... --head ... --title ... --body ...(L33) 使用 GitHub App token

PR 标题(脚本 L33 与 runbook 一致):

<original commit subject> (cherry-pick to <channel>)

原始提交的主题通常已以 (#<original_pr_number>) 结尾(如 squash merge 的提交),这一后缀必须保留。

PR 正文:runbook 描述的是正常情况(原始提交标题以 (#<N>) 结尾):

Cherry-pick of #<original_pr_number> to <channel>

----
<original commit body, verbatim>

脚本源码还揭示了 runbook 未展开的兜底分支(script/cherry-pick):若标题(#N) 结尾(如直接拣选裸提交),正文首行退化为 Cherry-pick of <commit-sha> to <channel>,其余格式不变。

3. cherry_pick 工作流与通道-分支映射的现查现用

3.1 工作流长什么样

cherry_pick.yml 是手动触发(workflow_dispatch)的工作流,接收四个必填字符串输入:commitbranchchannelpr_number。其执行链为(见 cherry_pick.yml):

  1. steps::authenticate_as_zippy —— 通过 actions/create-github-app-token 为机器人 zed-zippy[bot] 生成 token,授予 contents/workflows/pull-requests 三项写权限;
  2. steps::checkout_repo —— 使用上述 token 检出仓库;
  3. cherry_pick::run_cherry_pick::cherry_pick —— 执行 ./script/cherry-pick "$BRANCH" "$COMMIT" "$CHANNEL",其中 BRANCH/COMMIT/CHANNEL 三个环境变量直接来自工作流输入(L46-L51),提交者身份被固定为 zed-zippy[bot](L52-L55)。

该文件头两行注明 Generated from xtask::workflows::cherry_pick,即它是代码生成的产物,生成器位于 tooling/xtask/src/tasks/workflows/cherry_pick.rs,可用 cargo xtask workflows 重建。pr_number 输入只用于 run-name 展示(cherry_pick to ${{ inputs.channel }} #${{ inputs.pr_number }}),并不参与脚本执行。

3.2 现查通道→分支的当前映射

runbook 给出的标准做法是检查最近的 cherry_pick 工作流运行记录:

gh run list --workflow=cherry_pick.yml --limit 30 --json displayTitle,databaseId
# pick a recent run for the channel you want, then:
gh run view <id> --log 2>&1 | grep -E "BRANCH:|CHANNEL:"

一次成功的运行会在日志中打印 BRANCH:CHANNEL: 两个环境变量;这就是当前的通道→分支映射。

4. 手动完成 Cherry-Pick 的标准流程

4.1 第一步:收集上下文

需要三要素:merge 提交 SHA目标分支通道名

  • 用户给出多个 PR/提交时,先收集齐全部元数据,再按它们落到 main 的先后顺序(旧到新)依次拣选:PR 按 mergedAt 排序,裸提交按其在 main 上的顺序(不可得时按提交日期)。runbook 的解释是:后续改动可能依赖前序改动,按序拣选可减少不必要的冲突,但当发布分支已分叉时并不保证无冲突。
gh pr view <PR_NUMBER> --json title,number,mergeCommit,mergedAt,url
  • 用户提到"工作流失败了"时,拉取失败日志,看清究竟哪条命令失败、哪个文件冲突:
gh run list --workflow=cherry_pick.yml --limit 10 --json databaseId,displayTitle,status,conclusion
gh run view <failed_run_id> --log-failed

失败日志还会顺带确认工作流实际使用的 BRANCHCOMMIT——存在歧义时这是可靠依据(对应上文工作流中 BRANCH/COMMIT/CHANNEL 环境变量的回显)。

4.2 第二步:本地复现脚本的准备工作

仓库目录可能是 git worktree(检查 .git:若它是一个文件,说明当前是 worktree,指向共享的 gitdir)。这没有问题,照常操作即可。

git --no-pager fetch origin <branch-name> <commit-sha>
git checkout --force origin/<branch-name> -B cherry-pick-<branch-name>-<short-sha>
git cherry-pick <commit-sha>

分支名必须与 cherry-pick-<branch-name>-<short-sha> 完全一致(脚本约定;评审人与工具链都依赖它)。这三条命令与 script/cherry-pick 中的 fetch/checkout/cherry-pick 一一对应,差异仅在于本地场景不需要 --depth 2

4.3 第三步:先查"缺失的前置 cherry-pick",不要急着手工解冲突

runbook 在此设置了一个关键闸门:cherry-pick 若冲突,不要立即手工解决

先判断冲突是否很可能由"main 上已存在、但发布分支缺失"的其他 PR/提交引起。若是,向用户指出这些候选前置 PR/提交(附 PR 链接),并给出两个选项:手工解决冲突,或先让 GitHub cherry-pick 工作流把这些前置提交拣过去。若用户选择先跑工作流补齐前置,到此停止——这往往能让后续 cherry-pick 保持干净、并有资格获得自动批准(automated approval)。

只有满足以下其一,才进入手工解决:

  • 未发现可能存在缺失的前置;或
  • 用户明确选择手工解决而非先拣选前置。

4.4 第四步:手工解决冲突

仅在完成前置检查后进行。runbook 的准则:

  • grep -n '<<<<<<<\|>>>>>>>\|=======' <path> 定位每个冲突文件中的标记;
  • 冲突通常是 diff3 风格,含三段:HEAD(发布分支侧)、||||||| parent of <sha>(在 main 上的合并基)、以及传入的改动;
  • 先读原始提交git --no-pager show <commit-sha> -- <path>)理解作者意图,然后选择一种能"在发布分支上产出等价终态"的解法;
  • 不要顺手把 main 上恰好位于冲突区旁边的无关改动一并带进来——保持 cherry-pick 最小化。

4.5 第五步:验证

在继续 cherry-pick 之前,必须构建(在合理时并测试)受影响的 crate:

cargo check -p <affected_crate>
cargo test  -p <affected_crate>

验证失败就修解决方案,绝不让构建处于破损状态继续。如果无法达到干净状态,用 git cherry-pick --abort 中止并向用户回报。

4.6 第六步:完成 cherry-pick

git cherry-pick --continue 默认会打开编辑器,非交互环境下必须屏蔽:

git add <resolved_files>
GIT_EDITOR=true git cherry-pick --continue

这样做会逐字保留原始提交信息——与脚本的行为一致。

4.7 第七步:推送并创建 PR

git push origin -f cherry-pick-<branch-name>-<short-sha>

然后用 gh pr create 创建 PR,标题与正文格式必须与 script/cherry-pick 的产物完全一致,使手动 PR 与自动化 PR 不可区分:

  • 标题<commit subject> (cherry-pick to <channel>)(原始主题的 (#<N>) 后缀保留);
  • 正文(原始提交标题以 (#<N>) 结尾的正常情形):
Cherry-pick of #<original_pr_number> to <channel>

----
<original commit body, verbatim>

runbook 建议把正文写入临时文件以保持格式:

git --no-pager log -1 --pretty=format:"%b" > /tmp/cp-body-tail.md
printf 'Cherry-pick of #%s to %s\n\n----\n' <PR_NUMBER> <channel> | cat - /tmp/cp-body-tail.md > /tmp/cp-body.md
gh pr create --base <branch-name> --head cherry-pick-<branch-name>-<short-sha> \
  --title "<commit subject> (cherry-pick to <channel>)" \
  --body-file /tmp/cp-body.md

两条明确的"不要做":

  • 不要添加 Release Notes: 段——原始提交正文里已经有一个(或已写 N/A),重复添加会造成冗余;
  • 标题不匹配 (#N) 时,正文首行使用 Cherry-pick of <commit-sha> to <channel>(脚本 L28-L30 的兜底行为)。

5. 收尾:给用户的最终报告

runbook 规定完成后必须向用户交代四件事:

  1. 新 PR 的 URL("When Finished"一节强调:最后一步永远是给出已开 PR 的链接);
  2. 冲突及解决方式的一句话总结;
  3. 运行了哪些验证(命令 + 结果);
  4. 本地分支当前停在 cherry-pick-<branch-name>-<short-sha> 上,以便用户需要时切回。

6. 常见陷阱(Gotchas)

runbook 单独列出四条,全部有明确的工程原因:

  1. --no-pagerGIT_EDITOR=true:本环境中非交互 git 的硬性要求;cherry-pick --continue 漏掉 GIT_EDITOR=true 会挂起终端。
  2. worktree 的索引锁:若前一条 git 命令被中断,可能遇到 index.lock 错误;worktree 场景下锁位于 <gitdir>/index.lock<gitdir>cat .git 所指向的路径。仅确认没有 git 进程在运行时才可删除。
  3. 不要扩大 cherry-pick 的范围:解冲突时绝不因为无关改动恰好位于冲突区旁边就从 main 把它们拉进来。PR 应当是"在发布分支上复现原始提交意图的最小 diff"。
  4. 通道分支不叫 preview/stable:不要尝试 git fetch origin preview,先查出真实的 vX.Y.x 分支名再操作。

7. 相关文件速查

文件 作用
.agents/skills/zed-cherry-pick/SKILL.md 本 runbook 的原始文档(何时使用、七步流程、陷阱清单)
script/cherry-pick 规范脚本:分支命名、拣选、强推、PR 标题/正文生成
.github/workflows/cherry_pick.yml 自动化工作流:四个输入、zippy 鉴权、脚本调用与环境变量
tooling/xtask/src/tasks/workflows/cherry_pick.rs 工作流的 xtask 生成器(cargo xtask workflows 重建)
.github/workflows/bump_zed_version.yml 版本提升流程,展示 vX.Y.x 分支名的派生规则
crates/release_channel/src/lib.rs 通道枚举定义:stable / dev / nightly / preview

适用前提说明:上述流程依赖仓库具备可用的 gh CLI 与对 origin 的写权限,且目标仓库启用 GitHub App(zed-zippy[bot])自动化;对于仅本地查看的镜像仓库,本文档的价值在于理解发布分支的补丁规范与分支/PR 命名约定,所有命令均可照原文复制执行。

登录后查看全文
热门项目推荐
相关项目推荐