首页
/ Motrix 的 Git 工作流与发布安全实践:Conventional Commits、分支策略与标签驱动的签名发布

Motrix 的 Git 工作流与发布安全实践:Conventional Commits、分支策略与标签驱动的签名发布

2026-09-06 18:14:55作者:宣聪麟

Motrix(一个基于 Electron 的全功能下载管理器,仓库包名为 motrix-turbo,当前版本 2.0.0-beta.28)在其 Agent 规则目录中沉淀了一套完整的 Git 工作流规范:提交信息遵循英文 Conventional Commits、分支与 PR 有严格命名和合并策略、发布则由受保护标签触发签名流水线完成。本文基于仓库内的 git-workflow.md 展开,并对照仓库中实际存在的 release.ymlrelease-metadata.mjssnap-promote.yml 等文件,说明每条规则背后的实现机制与落地方式。

Motrix 下载仪表盘界面

提交规范:英文 Conventional Commits

格式与允许的 type

git-workflow.md 要求所有提交使用英文 Conventional Commits:

<type>(<optional-scope>): <imperative summary>

允许的 type 共九种:featfixrefactorperftestdocschorecistyle。对摘要行的约束是:

  • 小写开头,使用祈使语气(imperative summary);
  • 结尾不加句号;
  • 长度控制在 72 个字符以内;
  • 非显而易见的改动动机写进 body;
  • 涉及破坏性变更时附加 BREAKING CHANGE: ... footer。

关于 AI 署名的约束

规则中特别指出:不要自动附加 AI 署名或 co-author trailer;但由贡献者显式提供、或受法律政策要求保留的 trailer 必须原样保留。这一条明确了"自动化工具不得污染提交历史"与"尊重显式署名"之间的边界。

提交前置门禁

文档同时要求:创建提交前必须先遵循 commit-and-quality.md。该规则文件规定了每次提交前必须执行并通过的三条命令:

pnpm run check:boundaries
pnpm run lint
pnpm exec tsc --noEmit

其中 pnpm run lint 实际执行 biome check .(由 biome.json 限定检查范围),与 CI 运行的是同一条命令,不允许用更窄的路径列表替代,也不允许以管道方式丢弃其退出码。提交前还要 git diff --staged 检查暂存内容、git diff --cached --check 检查空白问题,并且不允许因为某个 CI job 是 non-blocking 就掩盖其失败。

针对改动类型还有配套检查(同样出自 commit-and-quality.md):行为/逻辑变更用 pnpm exec vitest run <test-path> 跑聚焦测试;浏览器/Electron 用户流程用 pnpm test:e2e;locale 资源变更跑 pnpm run check:i18n;插件清单契约变更跑 pnpm run check:schema-parity;依赖或许可证元数据变更跑 pnpm run check:third-party-noticespackages/native-host 的 Rust 代码则要求 cargo fmt --checkcargo clippy -- -D warningscargo test 全部通过。这些命令均可在 package.jsonscripts 字段中一一对应找到(如 check:boundariescheck:i18ncheck:schema-parity 等),说明规则与仓库脚本是严格对齐的。

分支与 Pull Request 策略

分支模型:main 受保护,master 冻结

规则文件给出的核心事实是:

  • main 是受保护的活跃分支;master 是冻结的历史遗留分支。绝不允许把"Turbo 工作"(即 Motrix 2.0 重写线)target 或 merge 进 master
  • 所有新分支必须从当前 main 切出,命名格式为 <type>/<snake_case_topic>_<YYYYMMDD>,可在 topic 前放置 issue 编号。type 与提交 type 保持一致(featfix 等),例如 feat/media_dash_parser_20260906fix/123_tray_icon_20260906
  • 禁止直接 push 或 force-push 到 main,一切变更走 PR。

Rebase 与 force push 边界

  • 在评审前,把自己的私有 feature 分支 rebase 到 main 上;
  • 绝不允许 rebase main 本身或任何与其他贡献者共享的分支;
  • rebase 之后只允许用 git push --force-with-lease 更新自己的 feature 分支,永远不要使用无条件的 git push -f--force-with-lease 会在远端分支被他人更新过时拒绝推送,这是共享协作下的安全底线。

PR 与合并方式

  • PR 保持聚焦(单一目的);标题遵循 Conventional Commits;描述说明改了什么(what)、为什么改(why)、以及如何验证(how);
  • 功能类 PR 默认使用 squash merge;只有 release、hotfix,或需要保留 commit 级历史的刻意结构化重构,才使用普通 merge;
  • 合并完成后删除已合并的分支。

发布安全:受保护标签驱动的签名发布流水线

git-workflow.md 的 Release Safety 一节确立了总原则:检入的 workflows 和 scripts 是发布的唯一权威(release authority),不得用本地发布步骤替代它们。 逐条拆解并与仓库实现对照如下。

版本号:严格 SemVer + 受保护标签

发布要求的操作顺序是:

  1. package.json 中的 version 设为严格 SemVer(当前仓库为 2.0.0-beta.28,属于 beta 预发布通道);
  2. 位于 main 上的提交创建受保护的 v<package-version> 标签,例如 v2.0.0-beta.28
  3. 标签版本与 package 版本必须一致;只支持 stablebeta 两个通道。

这一约束在 CI 中有精确的落地实现。release.yml 的 preflight job 调用 release-metadata.mjs

node scripts/release-metadata.mjs --package-json package.json --github-output "$GITHUB_OUTPUT"

release-metadata.mjs 的源码可以看到它用一个严格的正则 STRICT_SEMVER_PATTERN 解析版本,拒绝任何非严格 SemVer 形式,并输出 versionprereleasechannel 三个元数据;channel 取预发布标识的第一段(如 beta.28beta),无预发布则为 stableresolveReleaseMetadata第 24-61 行)进一步保证:

  • 标签必须以 v 开头,剥掉 v 后与 package.json 版本做逐字符相等比较,不一致直接报错 Release tag version ... does not match package.json version ...
  • 标签必须由 ruleset 保护(refProtected 为 true),否则报 Release tag ... is not protected by a ruleset
  • 通道校验 assertSupportedReleaseChannel 只放行 stablebeta,其余一律报 Release prerelease channel must be beta
  • 还有一个容易被忽略的细节 assertMacUpdaterSafeVersion第 71-79 行):版本串中不允许出现小写 arm64 子串,因为 electron-updater 会在完整的 macOS 产物 URL 中做子串匹配,导致 x64 与 arm64 更新资产产生歧义。

preflight job 还会单独验证"标签提交确实在 main 上":

git fetch --no-tags origin refs/heads/main:refs/remotes/origin/main
git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main

并校验发布说明文件 docs/release-notes/<VERSION>.md 必须存在(仓库中已能看到 docs/release-notes 下成对的英文/中文 release notes,如 2.0.0-beta.28.md)。

触发器:tag push 与 manual dispatch 的权限差异

release.yml 的触发条件只有两个:

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:

对应文档中的描述:标签 push 触发正式发布流水线;而 workflow_dispatch(手动触发)只用于验证构建路径,不签名、不发布。流水线内部也用 github.event_name == 'workflow_dispatch' 作为多处门控条件(例如签名 job 的跳过条件),从代码层面落实了"手动触发不发布"的承诺。

门控路径:构建 → 签名 → 校验 → 装配 → 发布

release.yml 的 job 编排看,发布是一条完整门控链:preflight(校验源码与版本)→ build(按平台矩阵构建)→ sign(隔离的签名/最终化 job,macOS 分支会校验 Developer ID Application: 身份完成签名与公证)→ assemble(产物装配,随后运行 pnpm run check:update-artifacts 校验更新产物,对应 verify-update-artifacts.mjs)→ publish。之后还有一条容器镜像发布链:plan-container-publicationbuild-container-platform(amd64/arm64 矩阵)→ publish-containerverify-container-runtimepromote-container-aliasespublish-feed,其中容器元数据由 container-release-metadata.mjs 解析,并区分不可变标签与浮动标签(immutable tags / floating tags)。

签名策略的差异也写入规则:macOS 发布必须完成签名和公证(notarization);Windows 发布在两个 Authenticode secrets 都缺失时允许显式以未签名方式最终化,但必须在公开 release notes 中披露未签名的 Windows 产物。这解释了为什么 beta 阶段的 release notes 会包含此类说明。

禁止事项

规则明确禁止:手工上传 release 文件、复用未经验证的产物、绕过受保护环境、削弱签名输入的隔离性、覆盖已存在的不可变容器标签。容器侧的"不可变"语义在 preflight 输出中以 container_immutable_tags 形式显式计算出来,与这条禁令直接对应。

Snap 通道晋级:promotion 而非 rebuild

最后一条规则涉及 Snap 发布:受保护的 stable 与 beta 标签各发布一套经过验证的 amd64/arm64 Snap 构建集到 latest/edge;晋级(promotion)到 candidate/stable 只能由 snap-promote.yml 从受保护的 main 完成,且永远不允许在晋级时重新构建;stable 通道拒绝预发布版本。

snap-promote.yml 的 preflight job 把这条规则固化成了可执行检查:

  • 强制 RELEASE_REF == refs/heads/mainRELEASE_REF_PROTECTED == true,否则直接失败;
  • 要求提供 expected_versionsource_run_idsource_run_attempttarget 四个必填输入,确保晋级的是"某个受保护标签 run 中已经存在的精确 revision",而不是现造一个;
  • 复用 release-metadata.mjs 中的 parseStrictSemVer 校验版本,且当目标是 stable 时抛出 Prerelease versions cannot enter stable
  • 通道映射为 candidate ← latest/edgestable ← latest/candidate,目标通道为 latest/<target>

文件头注释也直白地复述了策略:"Promote, never rebuild, a verified two-architecture Snap build set."

发布门禁失败后的正确姿势

规则对失败场景只给了一条路:如果任一发布门禁失败,修复源码或 workflow 后重新走完整的门控路径,绝不允许发布"部分平台集合"(partial platform set)。这与 release.ymlconcurrency: group: release-${{ github.ref }} + cancel-in-progress: false 的配置一致——同一次发布不会被新触发取代,必须完整跑完。

小结

Motrix 的 Git 工作流可以归纳为三层约束:提交层(Conventional Commits + 提交前三检门禁)、协作层(受保护 main<type>/<topic>_<date> 分支、rebase 纪律、squash 优先)、发布层(严格 SemVer 的受保护 v* 标签驱动签名流水线,容器标签不可变,Snap 只做晋级不做重建)。所有发布规则都不是纸面约定——它们分别对应 release.ymlrelease-metadata.mjssnap-promote.ymlpackage.json 中可运行、可测试的脚本和 CI 门禁(如 tests/scripts/release-version-contract.test.tstests/scripts/verify-update-artifacts.test.ts 对版本契约的单元测试),形成"规则文件描述策略、源码实现策略、测试守护策略"的闭环。

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