Zed 发布补丁实践:script/cherry-pick 脚本、cherry_pick 工作流与 preview/stable 分支的手动 Cherry-Pick 流程
【免费下载链接】zedCode at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.项目地址: https://gitcode.com/GitHub_Trending/ze/zed
Zed 的main分支上合并的修复,最终需要落到preview与stable两条长期维护的发布分支上。本篇以仓库中的 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-pick 与cherry_pick工作流里,"如果任何地方看起来不对劲,先读脚本——你的本地步骤必须产出与它相同的分支名、PR 标题和 PR 正文"。脚本签名:
script/cherry-pick <branch-name> <commit-sha> <channel><branch-name>是发布分支(如v1.4.x),不是通道名;<channel>是preview或stable,仅用于 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)的工作流,接收四个必填字符串输入:commit、branch、channel、pr_number。其执行链为(见 cherry_pick.yml):
steps::authenticate_as_zippy—— 通过actions/create-github-app-token为机器人zed-zippy[bot]生成 token,授予 contents/workflows/pull-requests 三项写权限;steps::checkout_repo—— 使用上述 token 检出仓库;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失败日志还会顺带确认工作流实际使用的BRANCH与COMMIT——存在歧义时这是可靠依据(对应上文工作流中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 规定完成后必须向用户交代四件事:
- 新 PR 的 URL("When Finished"一节强调:最后一步永远是给出已开 PR 的链接);
- 冲突及解决方式的一句话总结;
- 运行了哪些验证(命令 + 结果);
- 本地分支当前停在
cherry-pick-<branch-name>-<short-sha>上,以便用户需要时切回。
6. 常见陷阱(Gotchas)
runbook 单独列出四条,全部有明确的工程原因:
--no-pager与GIT_EDITOR=true:本环境中非交互 git 的硬性要求;cherry-pick --continue漏掉GIT_EDITOR=true会挂起终端。- worktree 的索引锁:若前一条 git 命令被中断,可能遇到
index.lock错误;worktree 场景下锁位于<gitdir>/index.lock,<gitdir>是cat .git所指向的路径。仅确认没有 git 进程在运行时才可删除。 - 不要扩大 cherry-pick 的范围:解冲突时绝不因为无关改动恰好位于冲突区旁边就从
main把它们拉进来。PR 应当是"在发布分支上复现原始提交意图的最小 diff"。 - 通道分支不叫
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 |
适用前提说明:上述流程依赖仓库具备可用的ghCLI 与对origin的写权限,且目标仓库启用 GitHub App(zed-zippy[bot])自动化;对于仅本地查看的镜像仓库,本文档的价值在于理解发布分支的补丁规范与分支/PR 命名约定,所有命令均可照原文复制执行。
【免费下载链接】zedCode at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.项目地址: https://gitcode.com/GitHub_Trending/ze/zed
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考