| name | rpm-patch-applier |
| description | Use when user wants to apply a git patch to an RPM package directory. Handles source extraction via rpmbuild -bp, AI-assisted conflict resolution (including filename mismatch scenarios), spec file updates (Patch declaration, %patch directive, Release bump, changelog), and verification. Triggers on phrases like "apply patch to RPM", "把补丁应用到 RPM 包", "rpm 补丁", "应用补丁", "backport", or any request involving patching an RPM spec/tarball. Works with both CVE and non-CVE patches. Supports patches from refactored codebases applied to pre-refactored versions by intelligently matching code patterns across different filenames while preserving original commit metadata. |
RPM 补丁应用器
将 git format-patch 格式的补丁应用到 RPM 包项目中。使用多 subagent 架构,每个步骤有干净独立的上下文,主 Agent 负责串联。
架构
Main Agent(编排者 — 永不退出,持续工作直到完成或遇到不可恢复的错误)
│
├── Sub 1: 源码准备 ← 展开 tarball + git init
├── Sub 2: 补丁应用+冲突解决 ← git am / AI 手动解决冲突 / 重新生成补丁
├── Sub 3: Spec 文件更新 ← Patch 声明 / %patch 指令 / Release / changelog
└── Sub 4: 验证 ← rpmbuild -bp 确认补丁可正确应用
数据流:
用户输入
│
▼
Sub 1 ──→ source_dir, spec_file, packager_info
│
▼
Sub 2 ──→ final_patch_path (RPM 目录中的补丁路径)
│
▼
Sub 3 ──→ new_patch_num, new_release
│
▼
Sub 4 ──→ pass / fail
每个 subagent 有独立的 Prompt 模板,详见 references/ 目录:
references/sub1-source-prepare.md — Sub 1 源码准备
references/sub2-patch-apply.md — Sub 2 补丁应用与冲突解决
references/sub3-spec-update.md — Sub 3 Spec 文件更新
references/sub4-verify.md — Sub 4 验证
主 Agent:流程编排
你是主编排者。你本身不做文件操作,只负责串联 subagent、传递数据、处理错误决策。
核心生存原则(最高优先级)
- 永不退出:除非流程完整走完,或者遇到你确实无法解决的错误,否则你必须持续工作。不要因为"看起来差不多了"就停下来。
- 同步等待 subagent:所有 subagent 调用必须使用
run_in_background=false(同步模式)。这意味着你会被阻塞等待 subagent 完成——这正是你要的,确保你不会在等待期间退出。
- 失败不等于结束:subagent 失败时,你的工作是决策——重试、换方案、还是上报用户。不要因为一个 subagent 失败就直接放弃。
- 传递完整状态:每个 subagent 从干净上下文启动,你必须把前序 subagent 的所有必要输出写入 prompt 中。
- 模型一致性:所有 subagent 必须使用与主 agent 相同的模型。在
task() 调用时,所有 subagent 统一使用 category="deep",确保模型一致。各 reference 文件中的推荐调用方式已统一为此 category。
编排流程
阶段 0:解析输入
rpm_dir ← 用户提供的 RPM 包目录
patch_file ← 用户提供的补丁文件
source_dir ← 可选,已展开的源码目录(跳过 Sub 1 的展开步骤)
name / email ← 可选,packager 信息
cve_id ← 可选,如果是 CVE 补丁
补充 packager 信息(优先级:参数 > 环境变量 RPM_PACKAGER_NAME/EMAIL > git config)。
阶段 1 → 调用 Sub 1(源码准备)
读取 references/sub1-source-prepare.md 获取完整 Prompt 模板,填入变量后调用。
- 成功:记录
source_dir, spec_file, packager_name, packager_email → 进入阶段 2
- 失败(rpmbuild -bp 失败):决策
- 已有补丁冲突 1-2 个 → 方案 A(修复问题补丁)或 B(临时从 spec 注释掉)
- 已有补丁冲突 3+ 个 → 方案 C(跳过 rpmbuild -bp,手动解压 tarball,等同于
--source-dir)
- 始终告知用户失败原因和方案,让用户确认后执行
阶段 2 → 调用 Sub 2(补丁应用 + 冲突解决)
读取 references/sub2-patch-apply.md 获取完整 Prompt 模板,填入变量后调用。
- 成功:记录
final_patch_path → 进入阶段 3
- 冲突:Sub 2 内部自行分析和解决(这是 Sub 2 的职责)
- 彻底失败:告知用户具体原因
阶段 3 → 调用 Sub 3(Spec 文件更新)
读取 references/sub3-spec-update.md 获取完整 Prompt 模板,填入变量后调用。
必须传递的参数:除模板要求的 spec_file、rpm_dir、patch_filename 外,还必须传递 packager_name 和 packager_email(来源:阶段 0 解析的用户参数 > Sub 1 的输出)。
- 成功:记录
new_patch_num, new_release → 进入阶段 4
- 失败:分析原因,如果可以自行修复则修复后重试,否则上报
阶段 4 → 调用 Sub 4(验证)
读取 references/sub4-verify.md 获取完整 Prompt 模板,填入变量后调用。
重要参数:
参数
| 参数 | 必需 | 描述 |
|---|
rpm_dir | 是 | RPM 包目录路径 |
patch_file | 是 | 补丁文件路径(git format-patch 格式) |
final_patch_filename | 否 | 最终补丁文件名(Sub 3 生成的,如 backport-CVE-2026-32777.patch),用于 Sub 4 验证 |
--source-dir | 否 | 已展开的源码目录路径(跳过 rpmbuild -bp) |
--name | 否 | Packager 名称(优先级最高) |
--email | 否 | Packager 邮箱(优先级最高) |
--skip-patch-range | 否 | 源码准备阶段要跳过的补丁编号范围,格式:"9000-9999"。指定后,匹配该范围的 Patch 声明和 %patch 指令会在 rpmbuild -bp 前被临时注释掉 |
--reattach-skipped | 否 | 在成功应用新补丁后,重新验证并重新适配被跳过的补丁。默认:false。仅在设置了 --skip-patch-range 时生效 |
--continue | 否 | 继续模式:跳过 Sub 1 源码准备步骤,直接在已有的 source_dir 中工作。用于顺序多补丁应用场景 |
source_dir | 否 | 已展开的源码目录(与 --continue 配合使用)。如未指定,从 workdir 自动检测 |
环境变量
| 变量 | 描述 |
|---|
RPM_PACKAGER_NAME | Packager 名称(次优先级) |
RPM_PACKAGER_EMAIL | Packager 邮箱(次优先级) |
关键规则
- 补丁格式:必须是
git format-patch 生成的格式
- 补丁编号:使用
Patch6xxx 四位数格式,按顺序插入:如有 Patch6xxx 则在最后一个之后,否则若有 Patch7xxx或者其他大于6xxx的编号 则在其之前,否则在其他 Patch 之后,无任何 Patch 时在 Source0 之后
- 冲突处理:必须由 AI 分析和解决,不能跳过。支持以下冲突类型:
- 文件冲突:相同文件路径中的代码冲突,通过分析 .rej 文件解决
- 文件名不匹配:补丁中的文件路径在当前代码中不存在,通过搜索代码模式找到对应文件并手动应用修改
- 非关键文件跳过:当冲突文件属于非关键文件(文档、changelog、测试代码、构建脚本等)且适配复杂度高时,可以跳过该文件的适配——不 git add、不纳入最终补丁。详见 Sub 2 模板中的分类流程
- CVE 补丁命名:如果是 CVE 相关补丁,最终补丁文件命名为
backport-{cve-id}.patch
- 不得修改其他补丁:只处理用户指定的新补丁
- 格式保持:更新 Patch 声明和 Release 时保持原有空格对齐格式
- Changelog 空行:新条目前必须有空行分隔
- 日志记录:冲突解决后必须调用
record_conflict_resolution 记录到日志
- 防重复:添加 Patch 声明前检查 spec 中是否已存在同名补丁
- 补丁头保留:文件名不匹配场景下,最终补丁的提交信息(header 部分)必须与原始补丁完全一致。不得使用
git format-patch 重新生成 header(会改变格式),必须从原始补丁提取 header 并与新的 diff 内容拼接。如果原始补丁没有 header(纯 diff 格式),则生成的补丁也不应有 header
- ⚠️ %autosetup 与 %patch 互斥:如果 spec 使用
%autosetup(不带 -N),绝不能额外添加 %patch -P<N> 指令。%autosetup 展开后会调用 %autopatch,后者自动遍历所有声明的 Patch 并逐一应用。额外写 %patch 会导致补丁被应用两次,第二次因上下文已变而失败。唯一的例外是 %autosetup -N(-N 禁用自动补丁应用)。
- ⚠️ 绝不主动替换 %autosetup:即使验证失败,也不应在未经用户确认的情况下将
%autosetup 替换为 %setup + 显式 %patch。%autosetup 除了补丁应用外还处理 Source 解压、自动检测压缩格式等功能,直接替换为 %setup 可能破坏构建流程。如果确因 %autopatch bug 需要替换,必须先告知用户并获得确认,且只能替换为 %autosetup -N(保留 %autosetup 的其他功能),然后在 %prep 段中按数字升序手动添加显式 %patch 命令。
失败处理决策
场景 1: rpmbuild -bp 失败(Sub 1 阶段,已有补丁冲突)
| 方案 | 操作 | 适用场景 |
|---|
| A. 修复问题补丁 | 更新失败补丁使其兼容 | 1-2 个补丁冲突 |
| B. 临时移除问题补丁 | 从 spec 中注释掉失败补丁 | 1-2 个补丁冲突,且补丁功能不重要 |
| C. 跳过已有补丁 | 手动解压 tarball,等同于 --source-dir 参数 | 3+ 个补丁冲突 |
决策流程:分析失败输出 → 确定冲突补丁数量 → 向用户提出方案 → 用户确认后执行
场景 2: 补丁冲突无法解决(Sub 2 阶段)
Sub 2 自动检测两种冲突类型并分别处理:
前置步骤:冲突文件分类
- 对所有涉及冲突的文件按类型分为「关键文件」和「非关键文件」
- 非关键文件:文档(.md/.rst/.txt)、CHANGELOG/NEWS/README、测试代码(test/ 目录下)、示例代码等
- 关键文件:源码(.c/.cpp/.h/.py/.rs 等)、Makefile、配置文件等
- 非关键文件如果冲突复杂(需 3+ 处修改或上下文差异大)→ 跳过,不纳入最终补丁
- 非关键文件如果冲突简单(1-2 处明确修改)→ 仍然正常解决
类型 A:文件冲突(有 .rej 文件)
- 分析 .rej 文件中的冲突内容
- 读取对应源码文件,理解当前代码状态
- 使用 Edit 工具手动合并补丁更改(仅关键文件)
- 保留补丁核心逻辑,适应当前代码上下文
类型 B:文件名不匹配(无 .rej 文件,但 git apply 失败)
- 解析原始补丁,提取 From/Date/Subject 等 commit 信息
- 分析补丁中的 diff 块,理解修改意图(仅关键文件的 diff 块)
- 使用 Grep 工具在代码库中搜索关键代码模式
- 找到匹配的源文件(可能文件名不同但逻辑相同)
- 手动应用修改,并使用原始 commit 信息重新提交
- 生成包含正确元数据的新补丁(不包含被跳过的非关键文件)
失败处理:
- 代码差异太大、上下文完全不同 → 告知用户,附上冲突详情
- 无法找到匹配文件或代码模式 → 告知用户,可能需要手动定位
- Sub 2 可以尝试一次重试(重新分析),但仍失败则上报
场景 3: 验证失败 — %autosetup/%autopatch 问题(Sub 4 阶段)
当 Sub 4 失败且报告 autopatch_suspected_bug: true 时,说明 %autopatch(%autosetup 内部调用的宏)可能存在 bug,未能正确应用所有声明的 Patch。典型表现为:hunk 在错误行号失败、补丁上下文不匹配、部分 Patch 声明未被应用。
处理原则(按优先级):
a. 首选方案 — 保持 %autosetup 不变,告知用户:
%autopatch bug 是 RPM 本身的问题,不是补丁的问题
- 告知用户具体现象和 RPM 版本
- 建议用户升级 RPM 或手动处理
- 绝不主动替换
%autosetup
b. 如果用户明确要求修复,才执行以下方案:
- 将
%autosetup -p1 替换为 %autosetup -N -p1(-N 禁用自动补丁应用)
- 然后在
%prep 段中添加显式 %patch 命令
- 关键规则:显式
%patch 命令必须按数字升序排列(Patch01 → Patch02 → ... → Patch56 → Patch6000 → Patch6001)
- 格式为
%patch0N -p1 或 %patchNNNN -p1
c. 绝对禁止:
- 不经用户确认就替换
%autosetup
- 使用
%setup -q 完全替代 %autosetup(%autosetup 除了补丁应用外还有其他功能,如自动解压 Source)
- 只替换部分
%patch 命令(要么全替换,要么不替换)
场景 4: 验证失败 — 其他原因(Sub 4 阶段)
- 验证失败 → 直接告知用户失败原因,不重试,建议手动检查
日志记录
日志位置:/tmp/gitcode/{pkg_name}/apply_proc/apply.log
冲突解决后必须记录(Sub 2 负责):
source /root/.config/opencode/skills/rpm-patch-applier/scripts/rpm-patch-apply.sh
record_conflict_resolution "{pkg_name}" "一句话概括遇到的问题和解决方式"
多补丁顺序应用(Multi-Patch Application)
当需要向同一个 RPM 包依次应用多个补丁时,可以使用 --continue 模式避免重复的源码展开。
典型工作流
# 第1个补丁:完整流程(Sub 1 → Sub 2 → Sub 3 → Sub 4)
apply_patch(rpm_dir="/path/to/pkg", patch_file="/path/to/patch1.patch")
# 第2个补丁起:使用 --continue 模式跳过源码展开
apply_patch(rpm_dir="/path/to/pkg", patch_file="/path/to/patch2.patch",
continue_mode=true, source_dir="/tmp/rpmbuild.BUILD/pkg-1.0")
跳过问题补丁(--skip-patch-range)
某些 RPM 包中存在与当前源码不兼容的历史补丁(如 Patch9xxx 系列),会导致 rpmbuild -bp 失败。使用 --skip-patch-range 可以临时注释掉这些补丁:
apply_patch(rpm_dir="/path/to/pkg", patch_file="/path/to/fix.patch",
skip_patch_range="9000-9999")
skip_patch_range 行为:
- Sub 1 在
rpmbuild -bp 前,将 spec 中匹配范围的 Patch 声明和 %patch 指令注释掉
- 将被跳过的补丁信息记录到
skipped_patches.json(保存在 rpm_dir 中)
- 正常执行 Sub 2/Sub 3/Sub 4
重新适配跳过的补丁(--reattach-skipped)
当 --reattach-skipped 为 true 时,Sub 2 在成功应用新补丁后,会尝试重新验证每个被跳过的补丁:
- 恢复 spec 中被注释的 Patch 声明和 %patch 指令
- 对每个被跳过的补丁执行
git apply --check 验证
- 验证通过的补丁无需处理
- 验证失败的补丁由 AI 分析冲突、重新适配并写回补丁文件
- 结果记录到
reattach_results.json
继续模式(--continue)
当 --continue 为 true 时:
- 跳过整个 Sub 1(源码准备 / rpmbuild -bp / git init)
- 直接使用提供的
source_dir(如未指定则从 workdir 自动检测)
- 在 source_dir 中执行
git add -A && git commit 确保干净状态
- 直接进入 Sub 2(补丁应用)
状态文件
| 文件 | 位置 | 用途 |
|---|
skipped_patches.json | rpm_dir | 记录被跳过的补丁信息(skip_patch_range 模式) |
reattach_results.json | rpm_dir | 记录重新适配的结果(reattach_skipped 模式) |
skipped_patches.json 格式:
{
"skip_range": "9000-9999",
"skipped": [
{"num": 9001, "file": "backport-old-fix.patch", "declaration": "Patch9001:", "directive": "%patch9001 -p1"},
{"num": 9002, "file": "another-fix.patch", "declaration": "Patch9002:", "directive": "%patch9002 -p1"}
]
}
reattach_results.json 格式:
{
"results": [
{"num": 9001, "file": "backport-old-fix.patch", "status": "clean", "action": "none"},
{"num": 9002, "file": "another-fix.patch", "status": "conflict", "action": "re-adapted"}
]
}
编排流程变更
当使用新参数时,主 Agent 的编排流程如下变更:
skip_patch_range:
- 阶段 0:额外解析
skip_patch_range 参数
- 阶段 1(Sub 1):Sub 1 在 rpmbuild -bp 前注释匹配的补丁,记录
skipped_patches.json
- 阶段 2(Sub 2):如果
reattach_skipped 为 true,Sub 2 在应用新补丁后执行重新适配流程
- 阶段 3(Sub 3):正常执行 spec 更新
- 阶段 4(Sub 4):验证时恢复所有注释的补丁后执行 rpmbuild -bp
continue_mode:
- 跳过阶段 1(Sub 1)
- 直接从阶段 2(Sub 2)开始,使用已有的 source_dir
辅助脚本
scripts/rpm-patch-apply.sh 仅提供日志辅助函数,可被 subagent source 后使用:
record_conflict_resolution <pkg_name> <description> — 记录冲突解决方式
init_log <rpm_dir> <patch_file> — 初始化日志文件
write_log <step> <detail> — 写入日志条目
comment_out_patches <spec_file> <range_start> <range_end> <rpm_dir> — 注释掉 spec 中指定范围的 Patch 声明和 %patch 指令,生成 skipped_patches.json
restore_commented_patches <spec_file> — 恢复被注释的 Patch 声明和 %patch 指令
check_patch_applies <source_dir> <patch_file> — 检查补丁是否可以干净地应用(返回 0=可应用,1=冲突)