首页
/ Moby 补丁发布流程解析:release 分支、Backport 规则与 cherry-pick 标签体系

Moby 补丁发布流程解析:release 分支、Backport 规则与 cherry-pick 标签体系

2026-09-06 15:59:56作者:冯梦姬Eddie

Moby(Docker Engine 的上游项目)采用「master 直接出包 + 按需建立 release 分支」的补丁(patch/bugfix)发布策略,补丁必须满足"非新功能、不破坏现有功能、属于 bug 修复或小改进"三条标准,并通过 process/cherry-pick/<分支名> 标签驱动 backport 流程。本文以官方文档 PATCH-RELEASES.md 为核心,结合仓库中的分支规范、标签约定与 releases/scripts 下的同步脚本,完整梳理补丁从 master 到发布分支的落地路径。

何时可以直接从 master 切补丁版本

官方规则的第一条(见 PATCH-RELEASES.md)非常简洁:

如果 master 分支上没有"不适合打进补丁版本"的变更(即不会促使发布一个非补丁版本的新版本),补丁版本可以直接从 master 分支切出。

这意味着 Moby 并没有强制要求每个补丁版本都走 release 分支。只要 master 上的在研变更与当前补丁目标不冲突,维护者直接基于 master 打 tag 即可,无需任何 backport 操作。这也是 RELEASE-PROCESS.md 中 "When possible, releases are cut directly from master" 思想的延续。

何时需要创建 release 分支

一旦 master 上存在不适合进入补丁版本的变更(例如未定型的大特性、需要更长评审周期的重构),就需要为当前维护的版本线创建 release 分支,补丁版本改为从该分支切出。分支模型在 BRANCHES-AND-TAGS.md 中有完整定义:

  • 分支命名格式:Docker Engine 的 release 分支使用 docker-MAJOR.x 格式,例如 docker-29.x 承载整个 29 大版本及其所有小版本的发布周期。历史上的 26.026.1 分支则是按"单个小版本"建分支的旧做法,具体采用哪种粒度由该分支的赞助维护者(sponsoring maintainers)决定。
  • 维护者责任:release 分支的赞助维护者是首要联系人,负责指导向该分支的变更贡献。
  • 当前维护状态:从源码中的分支状态表看,docker-29.x 由 Moby 项目维护者(MAINTAINERS)维护,支持到 docker-30.x 之后;25.0 分支由社区维护者维护,已知到期日为 2026-12-04;docker-28.x27.x26.126.024.023.0 及更早分支均已不再维护。
  • 贡献状态三档(对贡献者预期具有信息性意义):
    • Maintained:维护者积极开发,接受贡献与 backport,在安全公告覆盖范围内;
    • Maintained (security):不再积极开发,仅可能接受关键安全问题的 backport,仍在安全公告范围内;
    • Unmaintained:不再开发,不接受贡献,超出安全公告范围。

版本基线本身也记录在 releases/versions.yaml 中,例如当前 Docker 最近发布为 29.7.2、下一计划版本为 29.8.0,可作为"当前补丁线"的参考锚点。

Backport(回合)规则与补丁准入标准

当 release 分支存在时(因为 master 上有不适合补丁版本的变更),所有 bug 修复与补丁必须 backport 到该 release 分支;如果补丁直接来自 master,则不需要 backport。这是文档中"Backporting changes"一节的核心逻辑。

一个补丁要进入 release 分支,必须同时满足以下三条标准(PATCH-RELEASES.md):

  1. 不能是重大功能或新功能(Not be a major/new feature);
  2. 不能破坏现有功能(Not break existing functionality);
  3. 必须是一个 bug 修复或小改进(Be a bugfix or a small improvement)。

这实际上把 release 分支定位为"稳定性优先"的通道:任何会改变对外行为面的变更都被排除在外,只有回归性质的修复才能流入。

cherry-pick 标签:标记 PR 进入下一个补丁版本

流程的最后一步落在 Pull Request 的标签操作上。对于提交到 master 分支、需要进入下一个补丁版本的 PR,作者或维护者应为其打上 process/cherry-pick/<BRANCH_NAME> 标签,其中 <BRANCH_NAME> 对应目标 release 分支名。

REVIEWING.md 的 Process labels 表可以看到这套标签在评审工作流中的完整配套定义(Process labels 专用于辅助补丁发布准备,且只应用于 PR):

标签 用途
process/cherry-pick 应在 bump/release 分支上被 cherry-pick 的 PR,且必须同时分配 milestone
process/cherry-picked 已完成 cherry-pick 的 PR,便于查找已进入 RC 的 PR 并更新 changelog
process/docs-cherry-pick 应在 docs 分支 cherry-pick 的文档类 PR(仅针对当前发布周期的文档修改、通用 Markdown/拼写修复)
process/docs-cherry-picked 已在 docs 分支完成 cherry-pick
process/merge-to-master 直接开在 bump/release 分支上、但还需要回合回 master 的 PR
process/merged-to-master 已回合回 master 的 PR

注意 process/merge-to-masterprocess/merged-to-master 构成反向闭环:当修复只能先落在 release 分支(典型场景是线上问题先于 master 修复)时,必须显式标记"待回 master / 已回 master",避免 release 分支上的修复在 master 上丢失。从这套标签结构看,Moby 的 backport 是双向的:master → release 分支走 cherry-pick,release 分支 → master 走 merge-to-master。

配套脚本:sync-branch 与 unmerged-tags

仓库 releases/scripts 目录下有两个与补丁同步直接相关的 shell 脚本,是上述流程的落地工具。

unmerged-tags:列出 release 分支缺少的发布 tag

unmerged-tags 的用法为 unmerged-tags <RELEASE_BRANCH> <TAG>,逻辑上做了三层校验:

  1. 目标 tag 必须存在,且必须是 master(优先 upstream/master,其次 origin/master)的祖先;
  2. 如果 tag 已包含在 release 分支中,直接返回;
  3. 否则按版本排序(通过 versionsort.suffix=-alpha./-beta./-rc. 保证预发布版本正确排序)列出所有"已并入 master 但未并入 release 分支"的 docker-v* tag,输出到指定 tag 为止:
git -c versionsort.suffix=-alpha. \
    -c versionsort.suffix=-beta. \
    -c versionsort.suffix=-rc. \
    tag --merged "$master" --no-merged "$release_branch" \
    --sort=v:refname \
    --format='%(refname:short)' \
    'docker-v*' \
    | awk -v tag="$tag" '{print} $0==tag{exit}'

(见 unmerged-tags)。这相当于为 release 维护者生成一份"待同步补丁清单",恰好对应 PATCH-RELEASES 文档中 backport 一节的操作场景。

sync-branch:把发布 tag 合入 release 分支

sync-branch 接收一个或多个 tag,逐个执行 git merge --no-ff;若合并产生冲突,则以 tag 的内容为准(git checkout <tag> -- '.')完成合并——这在 tag 之间内容单调递增的前提下是合理策略。最后用 git merge-base --is-ancestor 逐一校验每个 tag 确实已包含在当前分支中,防止"合了但没真正并入"的静默失败:

for tag_to_sync in "${tags_to_sync[@]}"; do
    if ! git merge-base --is-ancestor "$tag_to_sync" HEAD; then
        echo "tag $tag_to_sync is not contained in $(git rev-parse --abbrev-ref HEAD)" >&2
        exit 1
    fi
done

(见 sync-branch)。

两个脚本配合的典型工作流是:先跑 unmerged-tags 得到待同步的 tag 列表,再用 sync-branch 一次性把它们合入 release 分支并做祖先校验。

标签(Tag)与多组件版本规范

补丁最终落地为 tag。按照 BRANCHES-AND-TAGS.md 的约定,仓库遵循语义化版本,tag 通用格式为 vX.Y.Z[-suffix[N]](X/Y/Z 必须齐全;1.8.0 的第一个候选版为 v1.8.0-rc1,产品的第二个 alpha 为 v1.0.0-alpha1)。由于仓库包含多个独立版本化的组件,tag 前缀也按组件区分:

组件 Tag 格式 示例
Moby(核心库,当前处于 v2.x beta) vX.Y.Z v2.0.0-beta.5
Docker Engine docker-vX.Y.Z docker-v29.1.2
API(api/ 独立 Go module) api/vX.Y.Z api/v1.52.0
Client(client/ 独立 Go module) client/vX.Y.Z client/v1.52.0

其中 unmerged-tags 脚本过滤 docker-v* 的行为,正对应上表中 Docker Engine 组件的 tag 前缀。API 与 Client 子模块的发布配置分别存放在 api/releases/v1.52.0-beta.tomlclient/releases/v0.1.0-beta.toml,其中的 sub_pathpreviouspre_release 字段用于生成子模块的发布说明(例如 API 说明中写明"该发布延续 1.x API 兼容性线的第 52 个 minor 版本")。需要说明:Moby 项目只提供源码(tag)发布,二进制发行版来自各贡献方,已知发行渠道可在 PACKAGERS.md 中查阅。

与九周发布周期的关系

补丁发布不是孤立流程。RELEASE-PROCESS.md 描述了完整的项目发布节奏:九周为一个发布周期,前六周为开发评审期(新特性与 bug 修复均有资格进入下一版本),第六周起代码冻结并产生 Release Candidate(RC),冻结期内 master 继续正常开发但 RC 不再接受新特性——在 master 上修复的 bug,由 release owner 选择性地 cherry-pick 进 RC,随着 RC 变化再发布新的 RC 供社区测试,第九周统一发布。

对照本文主题可以这样理解两者的分工:

  • 冻结期内的 RC 维护:cherry-pick 的对象是 RC(本质是 master 的快照分支),目的是让即将发布的版本收敛;
  • 补丁版本维护:cherry-pick 的对象是长期存在的 release 分支(如 docker-29.x),目的是让已发布的版本线持续获得 bug 修复。

两者共用同一套 process/cherry-pick* 标签体系与"补丁准入三标准",区别只在于目标分支的生命周期长短。

实践要点小结

  1. 优先从 master 出补丁:仅当 master 上的在研变更与补丁目标冲突时才建 release 分支(docker-MAJOR.x 格式);
  2. 补丁准入三标准:非新功能、不破坏现有功能、属 bug 修复或小改进,三者缺一不可;
  3. 用标签驱动 backport:给 master 上的 PR 打 process/cherry-pick/<release 分支名>,完成后打 process/cherry-picked 以便更新 changelog;
  4. 反向闭环:先落在 release 分支的修复要用 process/merge-to-master / process/merged-to-master 保证回灌 master;
  5. 用脚本做同步与校验unmerged-tags <分支> <tag> 生成待同步清单,sync-branch TAG... 执行合并并以祖先关系校验结果;
  6. 按组件打 tag:Docker Engine 用 docker-vX.Y.Z,API/Client 子模块用 api/vX.Y.Zclient/vX.Y.Z,版本基线以 releases/versions.yaml 为准。
登录后查看全文
热门项目推荐
相关项目推荐