etcd Cherry-Pick 回移实战:从 k8s-infra-cherrypick-robot 自动化流程到 scripts/cherrypick.sh 实现剖析
本文围绕 etcd 贡献者指南中的 cherry-pick 回移文档 展开,讲解如何把已合入 main 分支的 PR 安全回移到 release-x.y 稳定分支:先掌握推荐的 /cherry-pick 机器人自动化流程,再深入剖析兜底脚本 scripts/cherrypick.sh 的分支命名、git am -3 补丁应用、冲突处理与自动建 PR 的完整实现,读完即可独立完成一次 etcd 补丁回移(backport)。
为什么 etcd 需要专门的 cherry-pick 流程
etcd 采用滚动发布模型(rolling release model),新特性先合入 main 开发分支,之后 main 在发版时被标记为新稳定分支;所有以 release- 为前缀的分支都是稳定分支,团队对最近两个受支持的小版本持续提供向后兼容的 bug 修复,补丁发布大约每两周一次。详见 分支管理指南。
在这个模型下,cherry-pick 承担了核心职责:回移文档 开篇即定义——"Cherry-picking applies changes from a PR to a release branch",即把某个 PR 的变更应用到发布分支上。发布指南 进一步给出了回移的规范要求:
- 回移 PR 必须指向正确的发布分支(
base:release-<major>-<minor>); - 提交的 commits 不应包含 merge commit,且内容应仅限于 bug 修复和安全补丁;
- 补丁发布经理(patch release manager)负责评审回移 PR,并从最旧的 commit 开始依次 cherry-pick 进稳定分支。
另外,安全发布流程 中也依赖同样的机制:PSC(私有安全委员会)会把安全补丁 cherry-pick 到 main 分支及所有相关的 release 分支。由于每个小版本有独立的变更日志(如 CHANGELOG-3.6.md),且补丁版本的 changelog 只记录相对上一个补丁版本的增量(规则见 CHANGELOG/README.md),所以回移操作直接决定了各 release 分支上补丁版本的变更记录。
etcd 提供了两条执行路径:首选自动化机器人,机器人失效时再落到手动脚本。
推荐流程:k8s-infra-cherrypick-robot 自动化回移
回移主要由 k8s-infra-cherrypick-robot 承担。用法非常简单:在已合入的 PR 下评论:
/cherry-pick release-3.6
机器人会自动创建一个指向目标 release 分支的 cherry-pick PR。
这个命令背后的约定与 发布指南 中的 "k8s infra cherry pick robot /cherrypick <branch> PR chatops command" 描述一致:<branch> 参数填发布分支名(如 release-3.6),机器人以该分支为 base 生成回移 PR。对贡献者来说,选择这条路径意味着:
- 无需本地准备 fork/remote,也无需处理冲突分支的推送细节;
- 回移 PR 的标题、描述由机器人自动生成,便于补丁经理统一评审;
- 冲突仍需回移发起者(或评审者)在机器人生成的 PR 中解决——这与手动脚本的冲突处理本质相同。
适用前提:目标 PR 已经合入上游 main,且你有权在对应 PR 下评论。若机器人无响应、目标分支特殊或需要一次性回移多个 PR 合并成单个 PR,则使用下一节的脚本。
兜底方案:手动 cherry-pick 脚本的完整准备
回移文档 将 scripts/cherrypick.sh 定位为 "automation 不生效时的手动兜底"。脚本头部的注释说明它派生自 Kubernetes 社区的 cherry_pick_pull.sh(见脚本第 4 行注释),工作流是:检出目标远端分支,逐个应用 PR 补丁,然后为你留下创建 PR 的一切材料。
环境变量准备
文档要求的三步准备:
export UPSTREAM_REMOTE=upstream
export FORK_REMOTE=origin
export GITHUB_USER=${github-username}
UPSTREAM_REMOTE:上游 etcd 仓库在你本地的 git remote 名,对应github.com/etcd-io/etcd;FORK_REMOTE:你自己 fork 的 etcd 仓库对应的 remote 名(github.com/<你的用户名>/etcd)。如果还没有 fork,需要先在 GitHub 上 fork,再用git remote add ...在本地注册;- 用
git remote -v查看实际的 remote 名。
此外还需安装 hub 命令行工具(脚本用它创建 PR)。这些要求都能在源码中得到印证——脚本在入口处做硬性校验:
GITHUB_USER未设置时直接退出并提示Please export GITHUB_USER=<your-user>(cherrypick.sh);- PATH 中找不到
hub时退出并提示安装(cherrypick.sh); UPSTREAM_REMOTE和FORK_REMOTE若未设置,默认值分别为upstream和origin(cherrypick.sh),所以文档中的export更多是显式声明,方便本地 remote 命名与默认值不一致时覆盖。
基本用法
把 PR 12345 回移到 release-3.6 并自动以 PR 形式提出:
./scripts/cherrypick.sh ${UPSTREAM_REMOTE}/release-3.6 12345
把 12345 和 56789 两个 PR 依次回移并合并为一个 PR 提出:
./scripts/cherrypick.sh ${UPSTREAM_REMOTE}/release-3.6 12345 56789
脚本自身的帮助信息(参数不足 2 个时打印,cherrypick.sh)还额外说明了两个文档未展开的环境变量,值得了解:
| 环境变量 | 作用 |
|---|---|
DRY_RUN |
跳过 git push 和 PR 创建,只在你本地保留一个包含已回移 commits 的分支。适合"只想给 release 分支做补丁、不打算开 PR"的场景 |
REGENERATE_DOCS |
在回移 commit 后为目标分支重新生成文档,适用于回移了涉及 API 文档变更的 commit 的情况 |
UPSTREAM_REMOTE / FORK_REMOTE |
覆盖默认的 upstream / origin remote 名 |
脚本实现剖析:从补丁下载到 PR 创建
下面按执行顺序拆解 cherrypick.sh 的关键机制,帮助你在脚本行为不符合预期时快速定位原因。
前置检查:拒绝在脏树上工作
脚本开头启用了 set -o errexit / nounset / pipefail,并强制切换到 etcd 仓库根目录(cherrypick.sh)。随后做两项安全检查:
- 工作树必须干净:
git status --porcelain --untracked=no非空即报!!! Dirty tree. Clean up and try again.(cherrypick.sh); - 不允许有进行中的 rebase/am:检查
.git/rebase-apply目录是否存在,存在则提示先清理(cherrypick.sh、L60-L63)。
脚本同时记录你执行前所在的分支 STARTINGBRANCH(git symbolic-ref --short HEAD),这是后面"善后返回"的落点。
分支命名规则
目标分支即命令行第一个参数(如 upstream/release-3.6),其余参数全部视为 PR 编号(cherrypick.sh)。脚本据此构造回移分支名:
automated-cherry-pick-of-#12345-#56789-upstream-release-3.6-<unix时间戳>
由三段拼接而成(cherrypick.sh):
automated-cherry-pick-of-#12345-#56789——固定前缀加#号连接的 PR 列表,注释里说明这是给工具识别的 "Required" 部分;- 目标分支名中的
/替换为-(upstream-release-3.6); date +%s时间戳,保证重复执行不冲突。
#12345 #56789 形式的 PULLSUBJ 则会用于最终 PR 的标题与描述。
补丁应用:curl 下载 + git am -3
对每个 PR 编号,脚本执行以下循环(cherrypick.sh):
- 下载 patch:
curl强制 HTTPS 下载该 PR 的.patch文件到/tmp/<pr>.patch,并明确提示"万一需要重来,这是你再次使用的材料"; - 应用 patch:
git am -3 /tmp/<pr>.patch,即带三方合并能力的git am,这是脚本"cherry-pick"的实际执行手段(并非git cherry-pick命令); - 冲突处理循环:若
git am失败,脚本检查git status --porcelain | grep ^U中是否有未合并文件,有则打印冲突文件清单,提示你在另一个窗口解决冲突并执行git add/git am --continue,然后交互式询问是否继续(非y即中止,cherrypick.sh)。同时它也给出手动重放提示:git am -3 /tmp/<pr>.patch; - 提取 commit 主题:从 patch 文件的
Subject:行解析出 PR 标题,累积进SUBJECTS数组(cherrypick.sh),最终写入 PR 描述。
注意一个边界细节:如果 git am 失败但没有未合并文件,脚本会判定为"可能有未完成的 git am 或 git rebase"并直接退出(cherrypick.sh)——这对应前置检查之外运行时产生的残留状态。
PR 描述模板与 hub 建 PR
所有 PR 应用完成后,make-a-pr 函数(cherrypick.sh)生成如下描述的 PR 文本并调用 hub pull-request 创建 PR:
Automated cherry pick of #12345 #56789
Cherry pick of #12345 #56789 on release-3.6.
#12345: <PR 1 的标题>
#56789: <PR 2 的标题>
这里有两个从源码可见的细节需要留意:
- PR 的 head 是
<GITHUB_USER>:<回移分支>,base 拼写为coreos:<release分支名>(cherrypick.sh)。coreos是 etcd 旧组织名的历史遗留前缀,若你的远端组织名已不是coreos,自动建 PR 这一步可能需要人工修正 base; - 描述文本先写入
mktemp临时文件再交给hub,注释解释这是为了规避hub直接读 heredoc stdin 时的崩溃问题(cherrypick.sh)。
推送前先做一次防御:如果 FORK_REMOTE 恰好配置成了上游 etcd/etcd.git 本身(cherrypick.sh),脚本认为"这不正常",改为打印手工 git push REMOTE <回移分支> 指令让你自行推送。正常路径则是打印将要执行的 git push <FORK_REMOTE> <带时间戳分支>:<规范回移分支>,交互式确认后执行,再调用 make-a-pr(cherrypick.sh)。
DRY_RUN 与善后清理
设置 DRY_RUN 后,脚本完成全部 cherry-pick 后停在回移分支上,打印两条收尾指令:git checkout <起始分支> 返回、git branch -D <回移分支> 删除分支(cherrypick.sh)。
未设置 DRY_RUN 时,脚本注册了 trap return_to_kansas EXIT(cherrypick.sh):无论正常结束还是中途失败,都会自动 git am --abort(若 am 未完成)、强制切回起始分支、删除临时回移分支并清理 PR 描述临时文件。这意味着手动解决冲突期间脚本进程保持挂起,你解决完冲突执行 git am --continue 后脚本会继续循环检查,直到没有未合并文件为止。
实践要点与常见问题
结合文档与源码,实际操作时建议关注以下几点:
- 回移顺序:一次脚本调用中多个 PR 按命令行给出的先后顺序依次
git am,与 发布指南 "从最旧的 commit 开始 cherry-pick" 的要求一致——请自行保证参数顺序即新旧顺序; - patch 留档:每个 PR 的 patch 先落在
/tmp/<pr>.patch供重试(cherrypick.sh),循环末尾才清理;如果中途退出,可手动git am -3重新应用; - 文档再生成:
REGENERATE_DOCS分支会调用hack/generate-docs.sh(cherrypick.sh);从当前仓库结构看,hack/目录下并没有该文件,因此该功能在当前版本更多是面向旧流程的保留项,回移普通 bug 修复时无需设置; - 内容红线:无论走机器人还是手动脚本,回移内容都应限于 bug 修复与安全补丁、不含 merge commit,并在回移 PR 中等待补丁经理评审——这是 发布指南 对 patch release 的硬性要求,每个补丁版本都应"严格优于"它的上一个版本;
- 分支命名勿改:
automated-cherry-pick-of-#...前缀被标注为 "Required portion for tools"(cherrypick.sh),自动化工具依赖该命名做识别,手动操作时不要擅自简写。
小结
etcd 的 cherry-pick 机制是"文档流程 + 可审计脚本"的组合:日常回移优先用 k8s-infra-cherrypick-robot 的 /cherry-pick release-x.y 评论一键完成;遇到机器人不可用、多 PR 合并回移或需要本地 DRY_RUN 演练时,scripts/cherrypick.sh 提供了从补丁下载、git am -3 冲突处理、规范分支命名到自动推送建 PR 的完整兜底能力。理解其分支命名约定、patch 留档路径与 trap 清理机制后,你就能在任意 release 分支上安全、可重复地完成一次补丁回移。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00