ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Renovate Pull Request 机制详解:PR 发现、关闭即忽略与 Immortal PR 的来龙去脉

2026/9/13 9:32:10 拓冰建站 浏览量
Renovate Pull Request 机制详解:PR 发现、关闭即忽略与 Immortal PR 的来龙去脉 Renovate Pull Request 机制详解PR 发现、关闭即忽略与 Immortal PR 的来龙去脉【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovateRenovate 的 Pull RequestPR是依赖自动化更新的核心交付物。本篇指南以官方文档 pull-requests.md 为主体深入讲解 Renovate 如何在不维护任何数据库的前提下发现既有 PR、为何关闭 PR 即表示忽略该更新、以及令人困扰的 Immortal PR永生 PR背后的缓存键设计逻辑与修复方案。读完本文你将理解 Renovate PR 的标题与分支命名约定掌握recreateWhen等关键配置项并能从源码层面解释为什么某些 PR 关闭后会反复出现。Renovate 如何发现既有 PRRenovate 采用无状态设计它不需要维护任何关于打开或关闭 PR 的数据库/状态记录。这一点与许多自建依赖机器人不同——Renovate 不会在本地持久化我历史上创建过哪些 PR而是每次运行都直接调用代码平台GitHub、GitLab、Gitea、Azure DevOps 等的 API去搜索、查找这些 PR。具体来说Renovate 通过同时匹配两个条件来定位一个既有 PR无论是打开的还是关闭的分支名branch name例如renovate/lodash-4.xPR 标题PR title例如Update lodash to v4.17.21。在大多数场景下分支名 PR 标题的组合是唯一的因此只会有一个既有 PR 与之匹配。但如果你使用了分组group配置把多个更新聚合到一个 PR并使用类似 All non-major updates 的通用标题那么历史上有多个PR 都可能匹配这个标题——这正是后续 Immortal PR 问题的伏笔。源码印证findPr 的双重匹配逻辑以 GitHub 平台实现为例lib/modules/platform/github/index.ts 中的findPr函数精确地实现了上述匹配逻辑export async function findPr({ branchName, prTitle, state all, includeOtherAuthors, }: FindPRConfig): PromiseGhPr | null { logger.debug(findPr(${branchName}, ${prTitle}, ${state})); if (includeOtherAuthors) { // PR 可能由任何人创建因此不走缓存的 Renovate PR 列表直接查平台 API const { body: prList } await githubApi.getJsonUncheckedGhRestPr[]( repos/${repo}/pulls?head${org}:${branchName}stateopen, { cacheProvider: repoCacheProvider }, ); ... } const prList await getPrList(); const pr prList.find((p) { if (p.sourceBranch ! branchName) { return false; } if (prTitle prTitle.toUpperCase() ! p.title.toUpperCase()) { return false; } if (!matchesState(p.state, state)) { return false; } ... return true; }); ... }从源码可以看到三个关键细节分支名是强匹配p.sourceBranch ! branchName直接判不等即排除标题匹配不区分大小写通过toUpperCase()比较所以Update lodash to v4.17.21与update lodash to v4.17.21会被视为同一个 PRstate 参数决定搜索范围默认state all即同时覆盖打开与关闭的 PR。关闭的 PR 之所以能被记住并忽略正是因为搜索范围覆盖了已关闭状态。findPr是各平台通用的接口契约在 lib/modules/platform/types.ts 中定义GitHub、GitLab、Bitbucket、Gitea、Forgejo 等所有平台适配器都实现了该函数。这印证了文档所述它使用代码平台的 API 来搜索和查找 PR——每个平台各有对应的实现但语义一致。普通 PR关闭即忽略如上文所述普通 Renovate PR 的标题中通常带有与升级版本相关的唯一性信息例如标题里的目标版本号。当这些唯一 PR 被关闭时Renovate 会假设你不想再看到该更新从而在未来跳过它。以lodash为例文档给出了完整的行为链条你通过关闭 Renovate 的 PR忽略了lodash4.17.21Renovate 据此假设你不需要4.17.21的任何更新当分支名 标题唯一性重新出现时例如lodash4.17.22发布Renovate 会创建新 PR——因为这是一个新的版本、新的唯一性。对于major大版本更新行为类似但范围更大你通过关闭 PR忽略了 Lodash 的 major 更新PR 标题形如 Update lodash to v4Renovate 假设你不需要v4的任何更新即使v4后续发布了更新版本Renovate 也不会为v4创建任何更新 PR。这就是关闭 PR 忽略该更新的完整语义标题中的版本信息越具体忽略的范围就越精确反之标题越笼统忽略的范围就越宽越容易误伤后续更新。Immortal PR为什么关闭后还会反复出现有时你会看到某些 Renovate PR 的描述里带有这样一段特殊声明Immortal:This PR will be recreated if closed unmerged. Get config help if thats undesired.这段声明意味着这是一个 Immortal永生PR——你关闭它之后它会在下一次 Renovate 运行时再次出现。本文档明确澄清这并不是 Renovate 出于这个更新对你有益别忽略它之类的哲学考量而是因为在现有机制下Renovate 没有好的办法在 PR 关闭后安全地忽略它们。根因分支名和 PR 标题是缓存键Renovate 把分支名 PR 标题当作类似**缓存键cache key**的机制来使用如果同一个缓存键存在且对应的 PR 已被关闭Renovate 就忽略这个 PR如果缓存键不存在例如版本升级到了新的目标版本标题随之变化Renovate 就会创建一个新 PR。这套机制对普通 PR工作得很好因为普通 PR 的标题带有版本唯一性如 to v16.1.2关闭后可以被安全地忽略但一旦标题失去唯一性缓存键机制就会出问题。缓存键引发的两个典型问题问题一过度宽泛的标题会永久屏蔽整个更新类别。假设你配置了一个 All non-major updates 的分组 PR。如果关闭这个 PR而 Renovate 基于 PR 标题将其忽略那么你将永远不再收到任何 non-major 更新——因为标题里没有任何版本信息可以区分这次的 non-major 更新和下一次的 non-major 更新。问题二只有唯一版本 PR 才能被安全忽略。Renovate 只有在标题包含唯一版本如 to v16.1.2 或 to v16时才能安全地忽略被关闭的 PR。一旦标题退化成了没有具体版本的形式见下文的分组场景关闭它就不再是一个可靠的忽略信号。分组更新且各依赖版本不同Immortal 的典型来源问题的高发场景是分组更新grouped updates中包含版本各不相同的多个依赖。此时如果组内各依赖升级到同一个版本标题可以是 Update react to v16仍具有唯一性可被安全忽略如果组内各依赖的目标版本各不相同标题就会退化为 Update react (major) 这种不可安全忽略的形式而不是 Update react to v16。换句话说Immortal PR 本质上是无法用标题表达唯一版本的分组 PR。你越倾向于把版本进度不同的依赖强行分组就越容易制造 Immortal PR。源码印证Immortal 标记的生成条件PR 描述中的 Immortal 声明并非随机出现而是由 lib/workers/repository/update/pr/body/config-description.ts 依据recreateClosed标志渲染的if (config.recreateClosed) { prBody emojify( :ghost: **Immortal**: This PR will be recreated if closed unmerged. Get config help.help}) if thats undesired.\n\n, ); } else { prBody emojify( :no_bell: **Ignore**: Close this PR and you wont be reminded about ${ config.upgrades.length 1 ? this update : these updates } again.\n\n, ); }这段代码揭示了两种截然相反的 PR 行为声明recreateClosed true渲染 Immortal声明提示关闭后会被重建recreateClosed false渲染 Ignore声明提示关闭后不会再提醒你。那么recreateClosed何时为true答案在分支生成逻辑 lib/workers/repository/updates/generate.ts 中普通升级upg.recreateClosed upg.recreateWhen always——只有显式配置recreateWhen: always才会变成 Immortallockfile 维护lockFileMaintenanceupg.recreateClosed upg.recreateWhen ! never——只要没配置never默认auto即视为 Immortal分组后目标版本不唯一toVersions.length 1 toValues.size 1 newValue.length 1upgrade.recreateClosed upgrade.recreateWhen ! never——这正是本文档描述的分组更新不同版本导致不可忽略场景源码中对应地将其判定为 Immortal除非显式配置never。可见文档中关于分组后标题退化为 Update react (major) 因而不可安全忽略的论述与源码中多版本分组 → 强制 recreateClosed的判定逻辑完全一致。recreateWhen配置项详解控制 Immortal 行为的关键配置是recreateWhen定义于 lib/config/options/index.ts属性值名称recreateWhen描述Recreate PRs even if same ones were closed previously.即使之前关闭过相同 PR 也重新创建类型string默认值auto允许值auto、always、never三个取值的行为差异auto默认智能判定。普通唯一版本 PR 被关闭后正常忽略但对于 lockfile 维护、目标版本不唯一的分组等无法安全忽略的场景自动表现为 Immortalalways无论何种情况关闭后一律重建 PR所有升级都会变成 Immortalnever彻底关闭 Immortal 行为关闭后的 PR 不再重建。顺带说明旧版配置recreateClosed已被迁移为recreateWhen迁移逻辑见 lib/config/migrations/custom/recreate-closed-migration.ts。未来规划文档中提到的未来方向是在 PR 中嵌入元数据精确标识该 PR 包含的具体文件、包与版本。这样一来Renovate 就可以安全地允许此类 PR 被关闭/忽略同时只要其中的文件、包或版本有任何更新的可能就缓存失效并创建新 PR——从基于标题猜测升级为基于内容精确判断。此外文档给出了一条实用建议如果你经常需要关闭 Immortal PR这本身就是一个信号说明你的分组可能过于宽泛。频繁关闭 Immortal PR 不是长久之计真正要做的是调整分组策略。如何修复 Immortal PR针对 Immortal PR文档给出了三条可落地的修复建议1. 避免分组版本不同步的依赖。不要把版本各不相同、或者你很可能想要忽略的依赖强行分到一组。分组只应面向升级节奏一致、版本可同步的依赖集合。正如 lib/workers/repository/updates/generate.ts 所展示的一旦组内出现多个目标版本标题就失去唯一性PR 就会被标记为 Immortal。2. 对确实希望永久保持关闭的 Immortal PR设置recreateWhen: never。这是一个全局或按 packageRules 作用域配置{ recreateWhen: never, }设置后关闭的 PR 将不再被重建对应源码中upgrade.recreateClosed upgrade.recreateWhen ! never分支的判定结果。3. Major 更新走 Dependency Dashboard 审批。除非 major 升级的对象是相互关联的依赖否则应避免把多个 major 升级分组在一起。更稳妥的做法是让 major 更新进入依赖仪表盘Dependency Dashboard人工审批{ packageRules: [ { matchUpdateTypes: [major], dependencyDashboardApproval: true, } ], }通过dependencyDashboardApproval: truemajor 更新不会直接创建 PR而是先出现在 Dependency Dashboard 上等待你批准从而让你对何时创建 major PR拥有完全的控制权从根源上避免产生需要反复关闭的 major 分组 PR。关于 Dashboard 的完整用法可参考 dashboard.md。忽略 PR 的正确姿势关闭一个 Renovate PR就是忽略它。这是 Renovate 最基础也最重要的交互约定对于普通 PR标题含唯一版本关闭后该版本的更新不会再出现对于 Immortal PR关闭后它会在下次 Renovate 运行时再次出现因此关闭无法真正忽略 Immortal PR。要真正忽略 Immortal PR请回到上文 如何修复 Immortal PR 一节要么调整分组避免产生 Immortal PR要么显式设置recreateWhen: never要么为 major 分组开启dependencyDashboardApproval人工把关。正确理解关闭即忽略的边界是驾驭 Renovate PR 工作流的关键。【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考