mcpField
G
git-release-finish

git-release-finish

github.com/fudesign2008开发681 次安装1 天前更新
代码仓库

关于 git-release-finish

git-release-finish 是用于 Git 版本发布收尾的工作流技能,面向需要将发布分支合并到主开发分支的团队。它按阶段检查远程状态、标签、合并约束、冲突和残留文件,再执行合并并生成报告。

主要功能

  1. 01检查冲突标记等残留内容
  2. 02分析标签命名并验证远程提交
  3. 03识别主开发分支及合并限制
  4. 04创建 MR 或 PR 并检查冲突
  5. 05按情况清理分支额外文件
  6. 06合并并输出发布报告

适用场景

  1. 01完成发布分支到主分支的合并
  2. 02在多仓库中协调版本发布
  3. 03检查合并冲突及分支残留后再发布

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/fudesign2008/open-skills --skill git-release-finish

安装后在 Claude Code 中输入/git-release-finish确认已加载。

附带文件references/cross-release-sync.mdreferences/ff-cherry-pick.md

详细介绍

Git Release Finish

前置技能检查

加载时,验证 report-output-discipline 是否可用。如果缺失,则中止并打印:

npx skills add FuDesign2008/open-skills -g --skill '*' --yes

不要默默使用替代方案。

角色:迭代结束时的发布工作流——为发布分支打标签,向主分支创建 MR/PR,解决冲突,清理多余文件并合并。Git 操作使用原生命令表示;MR/PR 操作使用意图表示,由正在运行的 Agent 根据其环境选择平台 CLI(glab / gh / tea / 等效工具)。

配对技能:git-release-start(迭代开始时创建发布分支)↔ git-release-finish(本技能)。

依赖:第 6 阶段的冲突解决工作委托给 git-conflict-resolve 技能(语义分析、多轮聚合)。

远程优先原则:在执行任何本地操作前确认远程状态。打标签前,运行 git fetch 并验证远程提交 SHA。绝不要为未经验证的本地 HEAD 打标签——它可能落后于远程。若平台支持,优先通过远程 API 创建标签(避免本地状态漂移);否则,在已验证的远程 SHA 上本地创建标签。

多仓库规范

单仓库运行可以跳过本节。多仓库运行(≥2 个仓库)必须遵守以下三条规则——每条都源自真实的事后复盘。

M.1 — 明确隔离工作目录。 工具调用层不会持久保留仓库上下文。在仓库 A 中执行 cd 后,下一个不带 cd 的 Bash 调用仍会在 A 中运行。多仓库并行发布若未明确执行 cd,就可能在错误的仓库中创建 MR、对错误的分支执行 merge-tree,或合并错误的 MR。多仓库运行中的每个命令块都必须以 cd <REPO_PATH> && 开头,即使你确信当前已在该目录中。每个确认表(阶段 1/2/3/3.5 的输出)都必须包含 Repo 列。

M.2 — 跨仓库传播故障模式。 当某个仓库遇到环境限制(ff 合并模式、受保护分支规则、CI 流水线要求、CLI 身份验证失败)时,立即在所有尚未进入第 9 阶段的仓库中执行相同检查。这是补查,并非主流程的重复;阶段 3.5 已并行检查所有仓库。

仓库 A 发现的问题在仓库 B(及所有尚未合并的仓库)中执行
merge_method = ff阶段 3.5 指纹检查;预期采用 cherry-pick 策略
发布分支受保护(push=No one)检查 B 的发布分支保护规则;提前规划受保护分支的回退方案
CLI 身份验证失败重新运行平台的 auth status
流水线失败阻止合并检查 B 的 CI 配置和流水线状态

M.3 — 并行/串行边界。 与仓库无关的操作并行执行(打标签、创建 MR/PR、冲突试运行)。冲突解决(阶段 6)必须严格串行执行——同一时间只能有一个仓库的 worktree 处于冲突状态。

适用 / 不适用场景

适用于(单仓库或多仓库):标签命名不明确或发生过演变;主分支名称不确定(各仓库可能使用 master / main / develop);release → main 合并可能存在代码冲突;MR/PR 分支可能包含杂散文件(AI 工具配置、临时脚本);需要生成结构化的发布报告。

不适用于:非 Git 托管服务(SVN、Mercurial 等)。

必须端到端执行整个流程。无论仓库数量或发布看起来有多“简单”,每个阶段都必须执行。“这次看起来很简单”是跳过预检、将残留内容带入 main 的最常见原因。

前置条件:已安装并完成身份验证的平台 CLI(开始前验证身份验证状态);每个仓库的远程地址均可访问;已与用户确认版本号(例如 8.2.60)。

阶段图

阶段操作关键工具
0前置健康检查(残留标记扫描)git grep + git log -S
1分析标签命名约定git tag
2创建并推送标签(优先远程)git fetch / git tag / git push / 远程 API
3确定主要开发分支git remote show + git log
3.5环境指纹检查(merge_method、保护规则、ff 检测)平台 API + git merge-base --is-ancestor
4创建 MR/PR平台 CLI
5冲突演练git merge-tree
5.5验证 MR 是否可合并(merge-tree=0 后必须执行)平台 CLI/API
6解决冲突(如有)git-conflict-resolve 技能
7清理 MR/PR 分支中的额外文件(仅合并模式)git ls-tree + comm
8残留标记检查关卡(无条件执行)git grep
9合并 MR/PR平台 CLI
10输出报告—

阶段 0、3.5 和 8 均为无条件执行——每条路径都会运行。阶段 0 是前置保障,阶段 3.5 是合并约束关卡,阶段 8 是最终合并前关卡。无论阶段 5 是否检测到冲突,这些阶段都会执行。

执行路径
情况路径
merge-tree=0 且 MR 可合并0→1→2→3→3.5→4→5→5.5→8→9→10(跳过 6/7)
merge-tree=0,但 MR 不可合并0→1→2→3→3.5→4→5→5.5→6(rebase)→8→9→10(跳过 7)
冲突数 > 0,源分支 ≤ 3 个提交且 ≤ 1 个冲突文件0→1→2→3→3.5→4→5→6(rebase)→8→9→10(跳过 7)
冲突数 > 0,其他所有情况0→1→2→3→3.5→4→5→6(merge)→7→8→9→10
阶段 3.5 检测到 ff 模式 + 非快进0→1→2→3→3.5(cherry-pick)→8→9→10(跳过默认 MR 阶段 4 / 5 / 5.5 / 6 / 7;ff cherry-pick 会创建 sync-release MR——参见 references/ff-cherry-pick.md)

阶段 3.5 是每条路径上的必经节点。其判断结果决定后续流程采用默认的合并/变基分支(路径 1–4),还是 ff cherry-pick 分支(路径 5)。


阶段 0:发布前健康检查(残留标记扫描)

无条件执行。 每个仓库都必须在阶段 1 之前运行。如果工作树或近期提交中已包含冲突标记,请在发布前清理,否则这些残留会通过合并进入 main。

为何需要此步骤:阶段 5 的 git merge-tree 只能检测未来合并产生的新冲突;它无法发现已提交到分支历史中的冲突标记(这是 AI 自动解决失败后仍被推送时常见的遗留问题)。如果没有阶段 0,这些标记就会随合并进入 main。

精确的正则表达式(本技能后续的每次扫描都会重复使用):

^<{7,} |^={7,}$|^>{7,} |^\|{7,} 

可匹配标准的 7 字符标记、非标准的 8 个及以上字符标记,以及 diff3 的 ||||||| base 标记。git grep 会自动遵循 .gitignore,因此不会扫描未跟踪的构建产物。^={7,}$ 要求行尾之前只能是纯 =,因此不会匹配 /* ====== */ 这类 CSS 注释;^<{7,} 要求 7 个及以上 < 后跟一个空格,因此不会匹配普通的 < 运算符。

残留判定——先检查成对标记(权威规则;本技能和 git-conflict-resolve 中的每次扫描都采用相同语义):上述正则表达式用于匹配,而非判定。只有当同一文件还包含开头或结尾标记 ^<{7,} / ^>{7,} / ^\|{7,} 时,匹配才算真正的残留。单独出现的纯 = 行属于误报(例如用 =============== 作为下划线的第三方许可证标题 The MIT License,以及 Markdown 分隔线)。命中不自动意味着存在残留——判定依据是标记是否成对出现。

L4a——工作树扫描:

git grep -lE '^<{7,} |^={7,}$|^>{7,} |^\|{7,} ' 2>/dev/null

L4a 分流——区分真正的残留与纯 = 误报:

git grep -lE '^<{7,} |^={7,}$|^>{7,} |^\|{7,} ' 2>/dev/null | while read -r f; do
  if git grep -qE '^<{7,} |^>{7,} |^\|{7,} ' -- "$f" 2>/dev/null; then
    echo "TRUE RESIDUE: $f"
  else
    echo "false positive (pure '=' lines only): $f"
  fi
done

L4b——历史合并提交 pickaxe 扫描:

git log --all --merges --since="30 days ago" \
  -S'^<<<<<<< ' --pickaxe-regex \
  --format="COMMIT: %h %s" --name-only 2>/dev/null
结果操作
L4a 和 L4b 均为空状态正常;继续阶段 1
L4a 分流发现真正的残留,或 L4b 非空中止。清理工作树(解决冲突或检出干净副本)并重新运行
L4a 匹配项全是纯 = 行(没有 < / > / | 标记)误报。在扫描输出中记录;继续阶段 1

不要“先发布,之后再清理”。main 中的残留会传播到基于错误内容构建的下游 CI/CD 流程。


阶段 1:分析标签命名约定

切勿假定标签格式统一。请分别分析每个仓库。

git tag --sort=-version:refname | head -30
历史模式约定
8.2.52、8.2.51纯版本号:{VERSION}
v8.2.40、v8.2.20v 前缀:v{VERSION}
desktop-8.2.50、desktop-8.2.40产品前缀:desktop-{VERSION}
mobile-7.5.720产品前缀:mobile-{VERSION}

多产品仓库:检查完整标签列表(而不只是最新的 10 个),确认是否存在任何 product- 前缀。每条产品线都有自己的标签;本次发布只为当前产品线打标签。

标签约定可能会变化。同一仓库过去可能使用 v8.2.60,之后改为 8.2.63。遵循最新的标签;不要因为旧标签带有前缀,就假定必须使用前缀。如果历史记录内部不一致,请询问用户本次发布应采用哪种约定。

输出(确认表):

仓库发布分支标签名称最新历史示例
repo-Arelease/X.Y.ZX.Y.Z8.2.52
repo-Brelease/X.Y.ZvX.Y.Zv8.2.40

在进入阶段 2 之前,与用户确认标签名称。


阶段 2:创建并推送标签(先远程)

2.0 预检查(所有平台,必须执行)

针对每个仓库并行运行:

git fetch origin <RELEASE_BRANCH>
REMOTE_SHA=$(git rev-parse origin/<RELEASE_BRANCH>)
echo "remote $RELEASE_BRANCH HEAD: $REMOTE_SHA"

# Fork detection: is local behind remote?
LOCAL_SHA=$(git rev-parse HEAD)
BEHIND=$(git rev-list --count HEAD..origin/<RELEASE_BRANCH> 2>/dev/null || echo 0)
[ "$BEHIND" -gt 0 ] && echo "Local is $BEHIND behind remote; tag will anchor to remote SHA, not local HEAD"

# Tag-already-exists check
git ls-remote --tags origin | grep -q "refs/tags/<TAG_NAME>$" \
  && { echo "Tag <TAG_NAME> already exists on remote; aborting"; exit 1; }
2.1 创建标签

创建标签时,优先使用远程 API 创建(如果平台支持),以避免本地状态偏差;无论采用哪种方式,都要以已验证的 $REMOTE_SHA 为基准。

  • 远程 API 方式(可用时优先):调用平台的“创建标签”端点,传入 tag_name=<TAG_NAME>、ref=$REMOTE_SHA、message="Release <VERSION>"。无需执行 git push——标签会直接在远程创建。(如果 CLI 的 api 子命令会拆分参数,请参阅“常见错误”。)
  • 本地创建方式(其他情况):git tag <TAG_NAME> $REMOTE_SHA && git push origin <TAG_NAME>。标签对象以 $REMOTE_SHA 为基准,而不是本地 HEAD。
2.2 验证标签指向正确的提交
REMOTE_SHA=$(git rev-parse origin/<RELEASE_BRANCH>)
TAG_SHA=$(git ls-remote origin refs/tags/<TAG_NAME> | awk '{print $1}')
echo "tag <TAG_NAME> -> $TAG_SHA"
echo "expected        -> $REMOTE_SHA"
# Annotated tags return the tag-object SHA from ls-remote; dereference with:
# git ls-remote origin "refs/tags/<TAG_NAME>^{}"

如果 TAG_SHA ≠ REMOTE_SHA(对附注标签解引用后),说明标签指向了错误的提交——删除后重新创建(请参阅“常见错误”)。

输出:每个仓库一张表,列出标签名称、远程 SHA、已验证的标签 SHA 和状态。


阶段 3:确定主开发分支

主分支可能是 master、main 或 develop,且不同仓库可能各不相同。

四层证据,按可信度排序:

  1. 远程合并历史(最有力):
git log --oneline --merges -20 | grep -E "into '(master|main|develop)'"

历史记录中的“Merge branch 'release/X.Y.Z' into 'master'”可揭示目标分支。

  1. 远程 HEAD(git ls-remote --symref origin HEAD 是实时且权威的查询;git remote show origin 也是实时查询,但经常超时;git symbolic-ref refs/remotes/origin/HEAD 是本地缓存,可能已过时)。如果结果不一致,优先采用实时查询。

  2. 分支命名行为和已合并 MR 的分布:sync-release/*-to-<X> 同步分支会在分支名称中标明目标(git branch -a | grep sync-release/);如果所有版本发布 MR 都合并到了同一个目标分支,这种分布就是行为证据(通过平台 API 筛选目标分支为 target_branch 的已合并 MR)。

  3. 用户确认(备用方案):当来源相互冲突或没有历史记录时,列出候选项并询问。

候选分支可能是仍在使用的并行分支,而非已弃用的主干分支。 名为 master 的分支可能是仍在使用的并行 CI 分支——它有自己的 CI 配置,使用不同的镜像和安装步骤,由分支名称独立触发,并且有其他贡献者的近期提交,而主干分支则是 main。在将候选分支视为已弃用分支或清理它之前,先验证这一点:独立的 CI 配置或其他贡献者的近期提交都意味着它是仍在使用的并行分支。不要清理仍在使用的并行分支;在阶段 10 报告中记录其实际用途。

远程 HEAD 可能指向已弃用的分支。迁移或分支策略变更后,远程 HEAD 可能仍指向旧主分支。如果远程 HEAD 指向的分支比另一个候选分支落后数百个提交,就应视为远程 HEAD 已过时,并依据合并历史、向用户确认:

echo "HEAD candidate ahead of main: $(git rev-list --count origin/main..origin/<HEAD_BRANCH> 2>/dev/null)"
echo "main ahead of HEAD candidate: $(git rev-list --count origin/<HEAD_BRANCH>..origin/main 2>/dev/null)"

输出:每个仓库一张表,列出源分支、目标主分支和证据来源。进入阶段 4 前与用户确认一致。


第 3.5 阶段:环境指纹(无条件)

无条件执行。 创建任何 MR/PR 之前,都要收集每个代码库的合并限制。这道关卡可以避免经典陷阱:“merge-tree=0,can_be_merged=true,但 mr merge 返回 406”。

为什么需要这一步:第 5 阶段的 merge-tree 和第 5.5 阶段的 can_be_merged 检查,只能判断代码层面的可合并性,无法识别项目级合并限制。最常见的陷阱是 GitLab merge_method = ff(仅快进):release 不是 main 的后代(release 分支创建后 main 有了新提交)→ git merge-tree 报告 0 个冲突 → MR 的 can_be_merged=true(存在合并结果;但不会验证 merge_method 限制)→ mr merge 返回 406 "Branch cannot be merged",因为 ff 模式拒绝生成合并提交,而 release 无法快进到 main。第 5/5.5 阶段无法发现这个陷阱,直到第 9 阶段才会暴露。第 3.5 阶段将这道障碍提前处理。

检查项(每个代码库并行执行):

  1. merge_method / 合并提交许可 — 通过平台的 API 查询项目的 merge_method(GitLab),或 allow_merge_commit / allow_squash_merge / allow_rebase_merge(GitHub),以检测是否为仅 ff 模式。
  2. release 分支保护规则 — 查询 release/* 和 * 的 protected_branches;记录 push_access_levels 和 merge_access_levels,以检测 push=No one。当 git-branch-protect 可用时,也通过 glab 或 gh 运行其只读的 check-trunk。若发现不匹配,报告 "push denied, maintainer merge allowed"。不要运行 set-trunk。若该技能不可用,保留此指纹并继续。
  3. CI 门禁 — 查询 only_allow_merge_if_pipeline_succeeds(GitLab)或等效设置。
  4. 后代检查(ff 模式判定):
git merge-base --is-ancestor origin/<TARGET_BRANCH> origin/<RELEASE_BRANCH> \
  && echo "Descendant: yes (ff possible, default flow)" \
  || echo "Descendant: no (non-fast-forward; ff-mode repos must use cherry-pick)"

策略表:

merge_methodrelease 是 target 的后代?策略
merge / rebase_merge任意默认合并策略(第 6 阶段合并模式)
squash任意第 9 阶段移除 --squash=false(项目强制 squash)。release 上的 N 个提交会在 main 上变成 1 个 squash 提交;在第 10 阶段报告中记录这一点
ff✅ 是(可以快进)默认流程;mr merge 直接成功
ff❌ 否(不能快进)Cherry-pick 策略 — 参见 references/ff-cherry-pick.md

ff + 不能快进是最常见的陷阱。release 分支通常会与 main 分叉(两边都有新提交),因此 ff 模式的代码库默认采用 cherry-pick。不要等到第 9 阶段遇到 406。

GitHub 中的对应陷阱:GitHub 没有 merge_method=ff 开关,而是提供三个布尔值。当 allow_merge_commit=false && allow_squash_merge=false && allow_rebase_merge=true(仅 rebase)时,PR 合并会重写提交并改变其哈希值——效果等同于仅 ff。此配置下,将 release → main 的 PR 视同 ff 模式处理;跨 release 同步应遵循 references/cross-release-sync.md 中的 rebase/哈希变更路径。

输出:每个代码库的环境指纹表(merge_method、release 保护规则、CI 门禁、选定策略)。若 ff 模式或保护规则异常,应在进入第 4 阶段前与用户确认。

受保护分支 + 不能快进的组合:如果 release 受保护(push=No one),无论 merge_method 为何,第 6 阶段 rebase 模式的强制推送都会失败。将策略列标记为“受保护分支回退”,以便第 6 阶段采用回退路径。


阶段 4:创建 MR/PR

按仓库并行执行。通过平台的 CLI 创建从 <RELEASE_BRANCH> 到 <MAIN_BRANCH> 的合并请求(Agent 根据其环境选择 glab / gh / tea)。标题:Release <VERSION>。保留源分支(--remove-source-branch=false / --delete-branch=false)。记录每个 MR/PR 的 ID 和 URL。

如果阶段 3.5 选择了 ff cherry-pick 策略,则跳过此阶段,改为遵循 references/ff-cherry-pick.md——MR 的源分支将是 sync-release/<VERSION>-to-<TARGET>,而不是原始发布分支。


阶段 5:冲突检测(试运行,不修改文件)

按仓库并行执行:

BASE=$(git merge-base origin/<RELEASE_BRANCH> origin/<MAIN_BRANCH>)

# Content conflict count
git merge-tree $BASE origin/<RELEASE_BRANCH> origin/<MAIN_BRANCH> 2>&1 \
  | grep -c "^changed in both"

# Branch divergence
echo "release ahead: $(git rev-list --count origin/<MAIN_BRANCH>..origin/<RELEASE_BRANCH>)"
echo "main ahead:    $(git rev-list --count origin/<RELEASE_BRANCH>..origin/<MAIN_BRANCH>)"

git merge-tree 只统计文本内容冲突,不检测重命名/重命名冲突(这在构建产物哈希变更时很常见)。即使冲突数为 0,如果仓库中有构建产物,也要运行阶段 5.5。

结果分类:

内容冲突源提交数策略
0任意创建 MR 后,验证是否可合并(阶段 5.5);仅当可合并时才继续执行阶段 9
0(但 MR 实际上不可合并)任意Rebase 策略(阶段 6)
> 0≤ 3 且 ≤ 1 个冲突文件Rebase 可接受(逐提交解决冲突的成本较低)
> 0其他所有情况Merge 策略(阶段 6)

merge-tree=0 陷阱:文本合并干净,不代表 MR 可合并。结构性分歧仍可能导致平台拒绝合并。阶段 5.5 是强制步骤。

Rebase 陷阱:Rebase 会在每个有冲突的提交处停止。一个有 80 个提交的分支意味着要中断数十次。除非源分支很短(≤ 3 个提交)且冲突极少,否则优先选择 Merge。


阶段 5.5:验证 MR 是否可合并

只要 merge-tree 报告为 0,就必须执行。merge-tree=0 ≠ MR 可合并。

通过平台的 CLI/API 检查 MR 的 mergeable / can_be_merged 状态。然后从上到下查看表格——第一个匹配的行优先(各行互斥):

结果操作
can_be_merged / MERGEABLE,且阶段 3.5 未标记 ff 陷阱继续执行阶段 9
不可合并(conflict / UNMERGEABLE)切换到 Rebase 策略(阶段 6);不要继续执行阶段 9
can_be_merged,但阶段 3.5 标记了 ff 模式 + 非快进**阶段 9 的 mr merge 仍会返回 406。**切换到 references/ff-cherry-pick.md 中的 cherry-pick 策略;不要继续执行阶段 9
卡在 checking(见下方的解除卡住状态流程)平台仍在重新计算——运行解除卡住状态流程;不要将其判定为不可合并

can_be_merged 仅表示“代码没有冲突,并且存在合并结果”。它不会绕过 merge_method=ff 或其他项目级限制。完整机制和权威判断以阶段 3.5 为准。

解除卡在 checking 状态的情况

多仓库运行中最常遇到这种情况:前一个 MR 刚更新了目标引用,平台的异步重新检查队列因此排在其后。当 merge_status 连续 N 次轮询(默认 N=6,间隔 4 秒)都保持 checking,同时 has_conflicts=false 且 state=opened 时:

  1. 调用平台的可合并性重新计算端点(GitLab:GET /projects/:id/merge_requests/:iid/merge_ref),强制重新计算。
  2. 等待约 3 秒,然后再次检查 merge_status。
  3. 如果变为 can_be_merged → 按上表继续处理。
  4. 如果仍为 checking → 继续轮询;只有在多轮重新计算和等待后仍无变化,才将其视为平台异常并告知用户。

卡住的 checking 表示计算尚未完成,而不是合并判定——它绝不意味着“不可合并”。


阶段 6:冲突解决(委托给 git-conflict-resolve)

当阶段 5/5.5 检测到冲突时,委托给 git-conflict-resolve 技能——传入 source=<RELEASE_BRANCH>、target=<MAIN_BRANCH>、version=<VERSION>,以及 mode=merge(默认)或 mode=rebase(仅当阶段 5 将 source 判定为 ≤ 3 个提交且 ≤ 1 个冲突文件时)。

构建产物(dist/、resources/<bundle>/、哈希分块)会由 git-conflict-resolve 的构建产物自动快捷处理机制自动跳过,无需额外参数。

如果 git-conflict-resolve 无法完成(用户中止、冲突无法解决或 rebase 中断),停止。保持工作树现状以供人工处理;不要继续后续阶段。

git-conflict-resolve 完成并输出全局审查清单后,待用户确认,再根据模式分支:

Merge 模式——推送 merge 分支并替换 MR:

git push origin merge-release/<VERSION>
# Close the conflict-stalled MR via your platform CLI.
# Create a new MR: source=merge-release/<VERSION>, target=<MAIN_BRANCH>.

Rebase 模式——将 rebase 结果推回 release 分支(原 MR 会自动更新):

git push origin rebase-release/<VERSION>:<RELEASE_BRANCH> --force-with-lease

如果远程分支已更新,--force-with-lease 会拒绝推送,以防意外覆盖他人的工作。强制推送前,请向用户确认:“是否有其他人正在基于此分支工作?请确认强制推送。”

受保护分支回退方案(强制推送因 ! [remote rejected] 或 pre-receive hook declined 被拒绝时):

  1. 将 rebase 结果推送到新分支(普通推送,不强制):git push origin rebase-release/<VERSION>
  2. 关闭原 MR
  3. 创建新的 MR,源分支为 rebase-release/<VERSION>,目标分支为 <MAIN_BRANCH>

原 release 分支仍停留在 rebase 前的提交上。之后任何 release → main 同步都必须考虑哈希分叉(参见 references/cross-release-sync.md)。

Rebase 跳过审计——如果 rebase 输出了 warning: skipped previously applied commit <SHA>:

git show <SKIPPED_SHA> --stat --oneline
git log origin/<MAIN_BRANCH> --oneline --grep="<keyword>" | head -5
diff <(git show <SKIPPED_SHA> --format=) <(git show <TARGET_SHA> --format=)
diff 结果操作
相同可安全跳过;无需操作
不同被跳过的提交并未完全包含;手动 cherry-pick <SKIPPED_SHA>

阶段 7:清理 MR/PR 中的多余文件(仅 merge 模式)

Rebase 模式下跳过——rebase 不会带入目标分支的多余文件。仅适用于 merge 模式,此时阶段 6 生成了 merge-release/<VERSION>。

存在于 merge-release 中、但 release 和 target 中均不存在的文件,是意外暂存的本地文件(通常是在解决冲突时运行 git add -A 所致):

comm -23 \
  <(git ls-tree -r HEAD --name-only | sort) \
  <(sort \
      <(git ls-tree -r origin/<RELEASE_BRANCH> --name-only) \
      <(git ls-tree -r origin/<TARGET_BRANCH> --name-only) \
    | uniq)

常见问题文件:AI 工具配置(.claude/、.omc/、.codemap/、opencode.json)、临时调试文件(ci-build-error.txt、ci-fix-log.txt)。存在于 release 或 target 任一分支中的文件都是预期文件——不要删除。

删除并 amend:

git rm --cached -rf <EXTRA_PATH_1> <EXTRA_PATH_2>     # if "pathspec not found": git add -A first, then retry
git commit --amend --no-edit --no-verify
git push origin merge-release/<VERSION> --force-with-lease

重新运行 comm -23 检查;计数必须为 0。


阶段 8:残留标记门禁(无条件执行)

无条件门禁。 无论阶段 5/6/7 执行了什么,都必须在阶段 9 之前运行。这是最后一道防线——即使 git-conflict-resolve 之前的所有防护都失效,阶段 8 也能捕获带有冲突标记的提交。

8.1 残留标记扫描(使用阶段 0 的正则表达式)

范围为 git diff --name-only(不是整个仓库)——只有合并更改的文件可能带有新的残留标记。扫描整个仓库会被 CSS ========= 注释、ASCII 艺术字、许可证文件和 Markdown 分隔线产生的大量误报淹没。判定优先检查标记组合(阶段 0):没有 <、> 或 | 标记的纯 = 行是误报,不是残留标记。

BASE=$(git merge-base origin/<MAIN_BRANCH> HEAD)
git diff --name-only $BASE..HEAD | while read f; do
  if git grep -qE '^<{7,} |^>{7,} |^\|{7,} ' -- "$f" 2>/dev/null; then
    git grep -nE '^<{7,} |^={7,}$|^>{7,} |^\|{7,} ' -- "$f" 2>/dev/null
    echo "Residue in $f; merge forbidden"; exit 1
  elif git grep -qE '^={7,}$' -- "$f" 2>/dev/null; then
    echo "pure '=' lines in $f (no <, >, | markers) — pair-first false positive, not residue"
  fi
done

构建产物冲突通常会由 git-conflict-resolve 的构建产物自动快捷处理机制自动跳过(采用 release 一侧,不会留下残留标记)。如果该机制意外将标记带入构建产物,阶段 8 会作为故障保护。

遇到 exit 1 时:不要继续阶段 9。 返回阶段 6,修复后重新运行阶段 8。

8.2 条件性增强(仅在运行 Phase 6 时)
  • 冲突文件差异审查 — 对 Phase 5 记录为冲突的每个文件执行:git diff origin/<MAIN_BRANCH>..HEAD -- <conflict_file>。检查:双方的导入/require 均已保留;双方新增的函数/类均已保留;没有重复的符号定义;没有整段逻辑被注释掉的代码块(用 // 或 /* */ 包裹);没有意外删除。任何一项不通过 → ❌。
  • 变基跳过等价性 — 如果 Phase 6 在变基时跳过了提交,重新确认差异等价性(使用 Phase 6 中的命令)。差异非空 → 被跳过的提交未被完全包含;需要手动 cherry-pick。
8.3 判定
判定条件后续操作
通过8.1 无残留,且 8.2(若运行)全部通过继续执行 Phase 9
未通过8.1 检测到残留,或任意 8.2 检查为 ❌禁止合并。 返回 Phase 6;修复后重新运行 Phase 8

输出:

[Residual Scan Report]
- 8.1 marker scan: 42 files in merge scope, no residue
- 8.2 diff review (if run):
  - .gitignore: 8.2.70 .omc/.sisyphus changes layered on master
  - src/bridge/api/MainProcessAPI.ts: both sides' new functions retained
- 8.2 rebase skip (if run):
  - skipped 7437368df: diff-equal to master 7421ca91b
Verdict: Pass — proceed to Phase 9

Phase 9:合并 MR/PR

获得用户确认后,按仓库并行执行:

  1. CI 门禁检查:查询 MR 的合并状态;如果状态不是 mergeable / can_be_merged,则在继续之前提示“CI 可能尚未通过 — 确认合并吗?”。
  2. 合并:调用平台的合并命令。对于使用 merge_method=merge 的 GitLab,传入 --squash=false --remove-source-branch=false。对于 merge_method=squash,省略 --squash=false(项目强制使用 squash)。对于 GitHub,使用 --merge(如果项目要求,则使用 --squash)。保留源分支。
  3. 验证:确认合并成功输出(✓ Merged! / ✓ Pull request ... was merged)。然后获取并检查目标分支:
git fetch origin <MAIN_BRANCH>
git log origin/<MAIN_BRANCH> --oneline -5

确认最新提交包含此次发布合并。

  1. 冻结检查 — 本阶段成功合并且 Phase 2 中的标签已存在后,如果 git-branch-protect 可用,则读取此发布分支是否已冻结。如果已冻结,记录报告。如果未冻结,则显示当前规则,并询问用户是否接受 freeze;只有在用户接受后才运行。不要调用 rollback。如果用户拒绝,或报告显示分支未冻结,则保留标签和主干合并,并将该报告复制到 Phase 10。如果技能不可用,则跳过此步骤。

Phase 10:输出报告

写入 $(git rev-parse --show-toplevel)/release-<VERSION>-report.md。注册表行:report-output-discipline/reference.md 中的 发布报告(human + 接入)。写入 Markdown 报告后,遵循 report-output-discipline 在同一目录生成 release-<VERSION>-report.html,并报告两个文件的路径。

报告包含:

  1. 标签摘要:仓库 / 标签名称 / 提交 SHA / 远程验证状态
  2. MR/PR 摘要:仓库 / 源 → 目标 / ID 和 URL / 合并状态
  3. 冲突详情(如有):分支分歧情况(各仓库超前的提交数)、仅存在于目标分支的提交(冲突来源)、冲突文件及解决方式 — 直接引用 git-conflict-resolve 技能的全局审查清单,而不是重新生成该清单;已清理的额外文件列表
  4. 手动后续事项:剩余风险、待处理的 cherry-pick 等
  5. 跨发布同步(如有):源发布 / 目标发布 / 策略 / 冲突摘要(参见 references/cross-release-sync.md)

常见错误

错误后果修正方法
因为“看起来很干净”而跳过 Phase 0失败的 AI 自动解决所遗留的历史问题会随合并进入 main对每个仓库都无条件运行 Phase 0
未验证远程状态就为本地 HEAD 打标签本地落后了;标签会落在过时的提交上始终获取并为 $REMOTE_SHA 打标签,绝不要为本地 HEAD 打标签
在 Phase 3.5 指纹检查前创建 MR在 ff 模式仓库的 Phase 9 遇到 406;回滚代价高所有路径都必须执行 Phase 3.5
将 can_be_merged=true 当作“合并一定会成功”ff 模式仓库仍会返回 406交叉核对 Phase 3.5 的判定结果;Phase 5.5 中以首个匹配行的结果为准
未经确认就强制推送共享的发布分支覆盖队友的工作使用 --force-with-lease 前先向用户确认
在包含 50 个提交的分支上执行 rebase每个冲突提交都会中断 rebase;可能连续数天被打断除非提交数 ≤ 3 且冲突文件数 ≤ 1,否则使用合并策略
执行多仓库命令时未添加 cd <REPO> && 前缀在错误的仓库中创建 MR,合并了错误的 MRM.1:多仓库操作中的每个命令块都必须以 cd <REPO_PATH> && 开头
遇到第一个仓库 Phase 9 失败就停止其他仓库也会遇到相同问题M.2:立即将失败模式应用到所有尚未合并的仓库
标签落在了错误的提交上发布指向错误的 SHA执行 git push origin --delete <TAG> + git tag -d <TAG>,然后基于 $REMOTE_SHA 重新执行 Phase 2;平台 API 也可能支持远程删除标签
平台 api 报告 Accepts 1 arg(s), received 2Shell 拆分了参数为每个字段加引号:-f "key=value";如果该 CLI 版本不接受,则使用 --raw-field "key=value"。优先升级 CLI
Cherry-pick 产生空提交(“previous cherry-pick is now empty”)目标分支已有等效变更执行 git cherry-pick --skip;仅在必须保留记录时才使用 --allow-empty
git rm --cached 报告 “pathspec not found”文件在工作树中,但不在索引中执行 git add -A 将工作树同步到索引,然后重试
发布过程中 CLI 认证过期命令因认证错误而失败重新运行平台 auth login;从失败的阶段重新开始

附录:pre-commit hook(可选的 L5 增强)

一个 Git 原生 hook,可对每项已暂存内容运行 Phase 0/8 标记正则表达式。安装一次后即可使用,且独立于任何技能而持续生效。

cat > .git/hooks/pre-commit << 'HOOK'
#!/bin/bash
# Reject staged content with git conflict markers (Phase 0/8 pair-first verdict)
# Pure '=' lines without <, >, or | markers are false positives (license underlines,
# Markdown separators) — they do not block.
# Scan the staged blobs directly: git diff output prefixes every line (+/-/space),
# which defeats the ^ anchors of the marker regex.
git diff --cached --name-only -z | while IFS= read -r -d '' f; do
  if git grep --cached -qE '^<{7,} |^>{7,} |^\|{7,} ' -- "$f" 2>/dev/null; then
    echo "pre-commit: staged content contains conflict markers ($f); commit blocked"
    exit 1
  fi
done || exit 1
exit 0
HOOK
chmod +x .git/hooks/pre-commit
  • 可通过 git commit --no-verify 绕过 — L5 是增强功能,并非门禁
  • 如果项目已有 pre-commit hook,请将标记检查追加到现有 hook 中
  • 使用 Phase 0 正则表达式,并采用相同的“配对优先”判定规则

参考资料

  • references/ff-cherry-pick.md — 当 Phase 3.5 检测到 ff 模式 + 非快进时的完整 cherry-pick 流程。还涵盖 ff 项目中的哈希一致性细节。
  • references/cross-release-sync.md — 将变更从 release/A(已合并到 main)同步到 release/B 的决策树和命令,包括 rebase/哈希变更陷阱。

评论

支持 Markdown