跳过重复操作
skip-duplicate-actions
提供以下功能来优化 GitHub Actions:
- 跳过重复的工作流运行,适用于合并、拉取请求或类似情况。
- 跳过并发或并行的工作流运行,用于不希望重复运行的情况。
- 跳过被忽略的路径,以加快文档更改等类似情况的处理。
- 如果路径未变更则跳过,用于目录特定测试等情况。
- 取消过时的工作流运行,适用于分支推送后的情况。
所有这些功能都有助于节省时间和成本,特别是对于长时间运行的工作流。 您可以选择使用其中的任意功能组合。
跳过重复的工作流运行
如果您使用功能分支,可能会看到许多重复的工作流运行。
例如,当在功能分支上执行工作流运行后,合并该功能分支时可能会再次重复执行工作流运行。
skip-duplicate-actions
可以防止这种情况发生。
- 完全可追溯: 在干净的合并后,您将看到类似
跳过执行,因为完全相同的文件已在 <previous_run_URL> 中成功检查
的消息。 - 完全可配置: 默认情况下,手动触发和定时任务永远不会被跳过。
- 灵活的 Git 使用:
skip-duplicate-actions
不关心您使用快进合并、变基合并还是压缩合并。 但是,如果合并产生的结果与源分支不同,那么结果工作流运行将不会被跳过。 这通常发生在合并"过时分支"的情况下。
跳过并发的工作流运行
有时,即使触发了两次,您也不希望同时运行某些工作流。
因此,skip-duplicate-actions
提供以下选项,在同一工作流已经运行时跳过工作流运行:
- 始终跳过: 适用于您永远不希望同时运行两次的工作流。
- 仅跳过相同内容: 例如,当工作流同时具有
push
和pull_request
触发器时,或在推送提交后立即推送标签时,这可能很有用。 (已弃用,请使用same_content_newer
代替) - 仅跳过具有相同内容的较新运行: 如果同一工作流正在运行完全相同的内容,则跳过较新的运行。
same_content_newer
确保至少有一个这样的工作流会运行,而same_content
可能会跳过所有运行。 - 仅跳过过时的运行: 例如,这对于不在作业开始时进行的跳过检查很有用。
- 永不跳过: 这会禁用并发跳过功能,但仍允许您使用所有其他选项,如重复跳过。
跳过被忽略的路径
在许多项目中,对于仅文档更改的情况,运行所有测试是不必要的。
因此,GitHub 本身提供了 paths-ignore 功能。
然而,GitHub 的 paths-ignore
有一些限制:
- GitHub 的
paths-ignore
无法查看先前的提交。这意味着结果取决于您推送更改的频率。 - 因此,GitHub 的
paths-ignore
对于必需检查不起作用。 如果您对必需检查使用 path-ignore,那么拉取请求将永远无法合并。
为了克服这些限制,skip-duplicate-actions
提供了一个更灵活的 paths_ignore
功能,使用高效的回溯算法。
paths_ignore
不会简单地查看当前提交,而是在您的提交历史中寻找成功的检查。
如果您需要在单个工作流中定义多个 paths_ignore
模式,可以使用 paths_filter
选项。
如果路径未变更则跳过
在某些项目中,只有在特定子目录发生变更时才应执行某些任务。
因此,GitHub 本身提供了 paths 功能。
然而,GitHub 的 paths
有一些限制:
- GitHub 的
paths
无法跳过工作流中的单个步骤。 - GitHub 的
paths
不适用于您真正希望成功通过的必需检查。
为了克服这些限制,skip-duplicate-actions
提供了一个更复杂的 paths
功能。
回溯算法不会盲目地跳过检查,而是只有在您的提交历史中找到合适的检查时才会跳过。
如果您需要在单个工作流中定义多个 paths
模式,可以使用 paths_filter
选项。
取消过时的工作流运行
通常,工作流应该只针对最新的提交运行。
因此,当您向分支推送更改时,可以配置 skip-duplicate-actions
以取消针对过时提交运行的任何先前工作流运行。
- 完全可追溯: 如果工作流运行被取消,您将看到类似
已取消 <previous_run_URL>
的消息。 - 保证执行: 取消算法保证无论如何都会完成一组完整的检查。
输入
paths_ignore
包含被忽略路径模式的 JSON 数组。 有关路径模式示例,请参阅备忘单。 有关支持的路径模式的详细信息,请参阅 micromatch。
示例: '["**/README.md", "**/docs/**"]'
默认值: '[]'
paths
包含路径模式的 JSON 数组。
如果非空,skip-duplicate-actions
将尝试跳过未更改这些路径的提交。
它使用与 paths_ignore
相同的语法。
示例: '["platform-specific/**"]'
默认值: '[]'
paths_filter
包含命名的 paths_ignore
/ paths
模式的 YAML 字符串。
示例:
frontend:
paths_ignore:
- 'frontend/docs/**'
paths:
- 'frontend/**'
backend:
paths:
- 'backend/**'
### 在这里您可以选择控制/限制回溯
# 布尔值或数字(默认值:true)
# 'false' 表示完全禁用回溯
# '5' 表示在回溯 5 个提交后停止
backtracking: 5
当您在一个工作流中有多个作业,并希望根据不同的 paths_ignore
/ paths
模式跳过它们时很有用。
请参阅相应的 paths_result
输出和示例配置。
cancel_others
如果为 true,则会取消来自过时提交的工作流运行。
默认值: 'false'
skip_after_successful_duplicate
如果为 true,在找到已完成的重复运行时跳过。
默认值: 'true'
do_not_skip
包含不应跳过的触发器的 JSON 数组。
可能的值有 pull_request
、push
、workflow_dispatch
、schedule
、release
、merge_group
。
默认值: '["workflow_dispatch", "schedule", "merge_group"]'
concurrent_skipping
如果同一工作流已在运行,则跳过工作流运行。
可选值:never
、same_content
、same_content_newer
、outdated_runs
、always
。
默认值: 'never'
输出
should_skip
如果根据您配置的规则应跳过当前运行,则返回 'true'
。这应该用于评估单个步骤或整个作业。
reason
当前运行被认为可跳过或不可跳过的原因。大致对应于输入选项。
示例: skip_after_successful_duplicate
skipped_by
返回导致当前运行被跳过的工作流运行的相关信息。
示例:
{
"id": 1709469369,
"runNumber": 737,
"event": "pull_request",
"treeHash": "e3434bb7aeb3047d7df948f09419ac96cf03d73e",
"commitHash": "4a0432e823468ecff81a978165cb35586544c795",
"status": "completed",
"conclusion": "success",
"htmlUrl": "https://github.com/fkirc/skip-duplicate-actions/actions/runs/1709469369",
"branch": "master",
"repo": "fkirc/skip-duplicate-actions",
"workflowId": 2640563,
"createdAt": "2022-01-17T18:56:06Z"
}
- 仅当当前运行被认为是可跳过时才返回信息,否则返回空对象(
{}
)。
paths_result
返回 paths_filter
中每个配置的过滤器的信息。
示例:
{
"frontend": {
"should_skip": true,
"backtrack_count": 1,
"skipped_by": {
// 工作流运行的相关信息
},
"backend": {
"should_skip": false,
"backtrack_count": 1,
"matched_files": ["backend/file.txt"]
},
"global": {
"should_skip": false,
"backtrack_count": 0
}
}
global
键对应"全局"paths_ignore
和paths
选项。- 如果有匹配的文件,会在
matched_files
中返回匹配文件列表。 skipped_by
返回值的行为与"全局"skipped_by
输出相同。backtrack_count
显示在找到适当的提交之前回溯(跳过)了多少个提交。- 如果
skip-duplicate-actions
在执行路径检查之前终止(例如,当找到成功的重复运行时),则返回空对象({}
)。
changed_files
一个二维数组,包含每个被追溯的提交的已更改文件列表。
示例: [["some/example/file.txt", "another/example/file.txt"], ["frontend/file.txt"]]
- 使用二维列表使处理更加灵活。例如,可以将列表展平(并去重)以获取所有被追溯的提交中的已更改文件。或者可以使用
changed_files[0]
获取最新提交的已更改文件。也可以使用paths_result
中的backtrack_count
输出来处理已更改文件的列表。 - 仅当设置了
paths_ignore
、paths
或paths_filter
选项之一时才返回信息。 - 如果
skip-duplicate-actions
在执行路径检查之前终止(例如,当找到成功的重复运行时),则返回空数组([]
)。
使用示例
你可以使用 skip-duplicate-actions
来跳过单个步骤或整个作业。
为了最小化对现有作业的更改,通常更容易跳过整个作业。
注意
示例 1:跳过整个作业
要跳过整个作业,你应该添加一个 pre_job
作为 main_job
的前置条件。
虽然这个示例看起来代码很多,但在你的特定项目的 main_job
中只有两行额外的代码(needs
子句和 if
子句):
jobs:
pre_job:
# continue-on-error: true # 完成集成后取消注释
runs-on: ubuntu-latest
# 将步骤输出映射到作业输出
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# 所有这些选项都是可选的,如果你对默认值满意,可以删除它们
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
paths_ignore: '["**/README.md", "**/docs/**"]'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
main_job:
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
runs-on: ubuntu-latest
steps:
- run: echo "运行耗时测试..." && sleep 30
示例 2:跳过单个步骤
以下示例演示了如何使用 if
子句和 id
跳过单个步骤。
在这个示例中,如果 src/
或 dist/
中没有文件被更改,则该步骤将被跳过:
jobs:
skip_individual_steps_job:
runs-on: ubuntu-latest
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
cancel_others: 'false'
paths: '["src/**", "dist/**"]'
- if: steps.skip_check.outputs.should_skip != 'true'
run: |
echo "仅在 src/ 或 dist/ 发生更改时运行..." && sleep 30
echo "执行其他操作..."
示例 3:使用 paths_filter
跳过
[!警告]
如果 paths_filter 选项无法正常工作,你可以根据需要多次复制"示例 1"(详见 https://github.com/fkirc/skip-duplicate-actions/issues/326)。
如果工作流中有多个作业,并且想根据不同的 paths_ignore
/ paths
模式跳过它们,可以使用 paths_filter
选项。定义这些过滤器时,操作会在 paths_result
输出中返回相应的信息。
例如,在一个单体仓库中,你可能希望仅在相应的 "frontend/" 文件夹中的某些文件发生更改时运行与"前端"相关的作业,对"后端"也是如此。可以通过以下配置实现:
jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
paths_result: ${{ steps.skip_check.outputs.paths_result }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
paths_filter: |
frontend:
paths_ignore:
- 'frontend/docs/**'
paths:
- 'frontend/**'
backend:
paths:
- 'backend/**'
# 可以与"全局" 'paths_ignore' / 'paths' 选项混合使用,例如:
# paths_ignore: '["**/README.md"]'
前端: 需要:pre_job # 如果'skip-duplicate-actions'在执行路径检查之前终止(例如,找到成功的重复运行时),'paths_result'会输出一个空对象('{}')。 # 可以在作业的if条件中通过首先检查"全局"'should_skip'输出的结果来轻松拦截这种情况。 如果:needs.pre_job.outputs.should_skip != 'true' && !fromJSON(needs.pre_job.outputs.paths_result).frontend.should_skip # ...
后端: # ...
## 它是如何工作的?
`skip-duplicate-actions`使用[工作流运行API](https://docs.github.com/en/rest/reference/actions#workflow-runs)来查询工作流运行。
`skip-duplicate-actions`只会查看属于当前工作流运行的同一工作流的工作流运行。
查询这些工作流运行后,它会将它们与当前工作流运行进行如下比较:
- 如果存在具有相同树哈希的工作流运行,那么我们就找到了一个重复的工作流运行。
- 如果存在正在进行的工作流运行,那么我们可以根据您的配置取消或跳过它。
## 路径跳过是如何工作的?
如上所述,`skip-duplicate-actions`提供了一个路径跳过功能,这与GitHub原生的`paths`和`paths_ignore`功能有些相似。
然而,路径跳过并不完全简单,因为存在多种执行路径跳过的选项:
### 选项1:只查看"当前"提交
这是GitHub目前正在做的事情,我们认为它是不够的,因为它对"必需"检查不起作用。
另一个问题是,结果可能高度依赖于提交的推送顺序。
### 选项2:查看拉取请求差异
PR差异易于理解,但它们只在打开PR后才起作用,而不是在推送功能分支后立即起作用。
### 选项3:查找先前提交的成功检查
这个选项由`skip-duplicate-actions`实现。
一个优点是,无论您是使用PR还是原始功能分支,它都能工作,当然它也适用于"必需"检查。
在内部,`skip-duplicate-actions`使用[Repos Commit API](https://docs.github.com/en/rest/reference/repos#get-a-commit)来执行高效的回溯算法进行路径跳过检测。
这是该算法大致的工作方式:
```mermaid
stateDiagram-v2
检查提交: 检查提交
[*] --> 检查提交: 当前提交
state 路径被忽略 <<choice>>
检查提交 --> 路径被忽略: 所有更改的文件是否都匹配"paths_ignore"?
忽略_是: 是
忽略_否: 否
路径被忽略 --> 忽略_是
路径被忽略 --> 忽略_否
state 路径被跳过 <<choice>>
忽略_否 --> 路径被跳过: 是否没有更改的文件匹配"paths"?
跳过_是: 是
跳过_否: 否
路径被跳过 --> 跳过_是: 无匹配
路径被跳过 --> 跳过_否: 有匹配
父提交: 获取父提交
忽略_是 --> 父提交
跳过_是 --> 父提交
state 成功运行 <<choice>>
父提交 --> 成功运行: 是否有此提交的成功运行?
运行_是: 是
运行_否: 否
成功运行 --> 运行_是
成功运行 --> 运行_否
运行_否 --> 检查提交: 父提交
跳过: 跳过!
运行_是 --> 跳过: (因为自此运行以来的所有更改都在被忽略或跳过的路径中)
不跳过: 不跳过!
跳过_否 --> 不跳过: (因为更改的文件需要"测试")
常见问题
如何对必需的矩阵作业使用跳过检查?
在https://github.com/fkirc/skip-duplicate-actions/issues/44中讨论。
如果您有注册为必需状态检查的矩阵作业,并且矩阵根据跳过检查条件运行,您可能会遇到拉取请求永远处于无法合并状态的问题,因为作业根本没有执行,因此没有报告为已跳过(Expected - Waiting for status to be reported
)。
有几种方法可以解决这个问题:
-
在矩阵作业的每个步骤中定义条件(
if
),而不是在作业级别定义单一条件:https://github.com/fkirc/skip-duplicate-actions/issues/44 -
如果您希望只有在矩阵中的所有作业都成功时才将检查视为成功,可以添加一个后续作业,其唯一任务是报告矩阵的最终状态。然后,您可以将此最终作业注册为必需状态检查:
result: name: 结果 if: needs.pre_job.outputs.should_skip != 'true' && always() runs-on: ubuntu-latest needs: - pre_job - example-matrix-job steps: - name: 标记结果为失败 if: needs.example-matrix-job.result != 'success' run: exit 1
-
定义一个相反的工作流,如GitHub官方建议:处理跳过但必需的检查