paths-filter:基于 GitHub Actions 的路径变更过滤工具

Conditionally run actions based on files modified by PR, feature branch or pushed commits

分支6Tags39
文件最后提交记录最后更新时间
1 个月前
6 年前
28 天前
28 天前
28 天前
5 年前
6 年前
4 年前
5 年前
6 年前
6 年前
6 年前
1 个月前
6 年前
1 个月前
1 个月前
6 年前
5 个月前
5 个月前
5 年前

路径变更过滤器

GitHub Action,可根据拉取请求、功能分支或最近推送的提交所修改的文件,有条件地执行工作流步骤和作业。

仅对已更改的组件运行集成测试或部署等耗时任务。这样可以节省时间和资源,在单体仓库设置中尤为如此。GitHub 工作流内置的路径过滤器不支持此功能,因为它们无法在单个作业或步骤级别运行。

实际使用示例:

支持的工作流

  • 拉取请求:
  • 功能分支:
    • push 或任何其他 event 触发的工作流
    • base 输入参数不得与触发工作流的分支相同
    • 针对与已配置基准分支或默认分支的合并基准检测变更
    • 使用 git 命令检测变更 - 仓库必须已 检出
  • 合并队列
    • merge_group 事件触发的工作流
    • baseref 输入参数默认为事件中的提交哈希,除非明确指定
    • 使用 git 命令检测变更 - 仓库必须已 检出
  • 主分支、发布分支或其他长期存在的分支:
    • base 输入参数与触发工作流的分支相同时,由 push 事件触发的工作流:
      • 针对推送前同一分支上的最近一次提交检测变更
    • base 输入参数为提交 SHA 时,由任何其他 event 触发的工作流:
      • 针对提供的 base 提交检测变更
    • base 输入参数与触发工作流的分支相同时,由任何其他 event 触发的工作流:
      • 从上次提交中检测变更
    • 使用 git 命令检测变更 - 仓库必须已 检出
  • 本地变更
    • base 输入参数设置为 HEAD 时,由任何事件触发的工作流
    • 针对当前 HEAD 检测变更
    • 忽略未跟踪文件

示例

- uses: dorny/paths-filter@v4
  id: changes
  with:
    filters: |
      src:
        - 'src/**'

  # run only if some file in 'src' folder was changed
- if: steps.changes.outputs.src == 'true'
  run: ...

有关更多场景,请参见示例部分。

注意事项

  • 路径表达式使用picomatch库进行计算。路径表达式格式的文档可在该项目的GitHub页面上找到。
  • Picomatch的dot选项已设置为true。 glob模式匹配也会匹配文件名或文件夹名以点开头的路径。
  • 建议使用'"引用您的路径表达式。否则,如果表达式以*开头,将会报错。
  • 使用act进行本地执行时,仅适用于替代的运行器镜像。默认运行器没有git二进制文件。
    • 使用:act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04
  • 容器作业中的Git“可疑所有权”错误会自动处理 - 该操作会使用包含safe.directory条目的临时HOME重试,这与actions/checkout使用的技术相同。 只有当获取操作依赖于存储在HOME相关文件(例如~/.git-credentials~/.netrc)中的凭据时,才需要在该操作之前的步骤中自行将仓库标记为安全:git config --global --add safe.directory "$GITHUB_WORKSPACE"

新增功能

  • predicate-quantifier输入参数添加了some-with-excludes
  • 自动解决容器作业中的Git“可疑所有权”错误
  • 更新到Node 24后发布新的主要版本v4【重大变更】
  • 添加ref输入参数
  • 添加list-files: csv格式
  • 使用changes输出配置矩阵作业,使其针对每个有变更的文件夹运行
  • 改进了list-files: shelllist-files: escape选项的匹配文件列表
  • 路径表达式现在使用picomatch库进行计算

有关更多信息,请参见CHANGELOG

使用方法

- uses: dorny/paths-filter@v4
  with:
    # Defines filters applied to detected changed files.
    # Each filter has a name and a list of rules.
    # Rule is a glob expression - paths of all changed
    # files are matched against it.
    # Rule can optionally specify if the file
    # should be added, modified, or deleted.
    # For each filter, there will be a corresponding output variable to
    # indicate if there's a changed file matching any of the rules.
    # Optionally, there can be a second output variable
    # set to list of all files matching the filter.
    # Filters can be provided inline as a string (containing valid YAML document),
    # or as a relative path to a file (e.g.: .github/filters.yaml).
    # Filters syntax is documented by example - see examples section.
    filters: ''

    # Branch, tag, or commit SHA against which the changes will be detected.
    # If it references the same branch it was pushed to,
    # changes are detected against the most recent commit before the push.
    # If it is empty and action is triggered by merge_group event,
    # the base commit in the event will be used.
    # Otherwise, it uses git merge-base to find the best common ancestor between
    # current branch (HEAD) and base.
    # When merge-base is found, it's used for change detection - only changes
    # introduced by the current branch are considered.
    # All files are considered as added if there is no common ancestor with
    # base branch or no previous commit.
    # This option is ignored if action is triggered by pull_request event,
    # unless 'token' is set to an empty string (see the 'token' input below).
    # Default: repository default branch (e.g. master)
    base: ''

    # Git reference (e.g. branch name) from which the changes will be detected.
    # Useful when workflow can be triggered only on the default branch (e.g. repository_dispatch event)
    # but you want to get changes on a different branch.
    # If this is empty and action is triggered by merge_group event,
    # the head commit in the event will be used.
    # This option is ignored if action is triggered by pull_request event.
    # default: ${{ github.ref }}
    ref:

    # How many commits are initially fetched from the base branch.
    # If needed, each subsequent fetch doubles the
    # previously requested number of commits until the merge-base
    # is found, or there are no more commits in the history.
    # This option takes effect only when changes are detected
    # using git against base branch (feature branch workflow).
    # Default: 100
    initial-fetch-depth: ''

    # Enables listing of files matching the filter:
    #   'none'  - Disables listing of matching files (default).
    #   'csv'   - Coma separated list of filenames.
    #             If needed, it uses double quotes to wrap filename with unsafe characters.
    #   'json'  - File paths are formatted as JSON array.
    #   'shell' - Space delimited list usable as command-line argument list in Linux shell.
    #             If needed, it uses single or double quotes to wrap filename with unsafe characters.
    #   'escape'- Space delimited list usable as command-line argument list in Linux shell.
    #             Backslash escapes every potentially unsafe character.
    # Default: none
    list-files: ''

    # Relative path under $GITHUB_WORKSPACE where the repository was checked out.
    working-directory: ''

    # Personal access token used to fetch a list of changed files
    # from GitHub REST API.
    # It's only used if action is triggered by a pull request event.
    # GitHub token from workflow context is used as default value.
    # If an empty string is provided, the action falls back to detect
    # changes using git commands. In that case, on pull request events
    # the 'base' input overrides the pull request base - e.g. set
    # base: ${{ github.event.before }} to detect changes since the last push.
    # Default: ${{ github.token }}
    token: ''

    # Optional parameter to override the default behavior of file matching algorithm.
    # Supported values:
    #   'some'               - File is included if it matches at least one pattern (default).
    #   'every'              - File is included only if it matches all of the patterns.
    #   'some-with-excludes' - File is included if it matches at least one pattern
    #                          and no negated pattern (the ones prefixed with '!').
    #
    # An example scenario where this is useful if you would like to match all
    # .ts files in a sub-directory but not .md files.
    # The filters below will match markdown files despite the exclusion syntax UNLESS
    # you specify 'every' or 'some-with-excludes' as the predicate-quantifier parameter.
    # When you do that, it will only match the .ts files in the subdirectory as expected.
    #
    # backend:
    #  - 'pkg/a/b/c/**'
    #  - '!**/*.jpeg'
    #  - '!**/*.md'
    predicate-quantifier: 'some'

输出

  • 每个过滤器都会设置一个以过滤器命名的输出变量,其文本值取决于 predicate-quantifier 设置:
    • predicate-quantifier: 'some'(默认值)时:
      • 'true' - 若任何已更改文件匹配过滤器的至少一条规则
      • 'false' - 若没有已更改文件匹配过滤器的至少一条规则
    • predicate-quantifier: 'every' 时:
      • 'true' - 若任何已更改文件匹配过滤器的所有规则
      • 'false' - 若没有已更改文件匹配过滤器的所有规则
    • predicate-quantifier: 'some-with-excludes' 时:
      • 'true' - 若任何已更改文件匹配过滤器的至少一条规则且不匹配任何其否定规则
      • 'false' - 若没有已更改文件匹配过滤器的至少一条规则且不匹配任何其否定规则
  • 每个过滤器都会设置一个名为 ${FILTER_NAME}_count 的输出变量,用于表示匹配文件的数量。
  • 若启用,每个过滤器会设置一个名为 ${FILTER_NAME}_files 的输出变量,其中包含所有匹配该过滤器的文件列表。
  • changes - 包含所有与已更改文件匹配的过滤器名称的 JSON 数组。

示例

条件执行

仅当子文件夹中的某些文件发生更改时,才执行工作流作业中的步骤
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v6
    - uses: dorny/paths-filter@v4
      id: filter
      with:
        filters: |
          backend:
            - 'backend/**'
          frontend:
            - 'frontend/**'

    # run only if 'backend' files were changed
    - name: backend tests
      if: steps.filter.outputs.backend == 'true'
      run: ...

    # run only if 'frontend' files were changed
    - name: frontend tests
      if: steps.filter.outputs.frontend == 'true'
      run: ...

    # run if 'backend' or 'frontend' files were changed
    - name: e2e tests
      if: steps.filter.outputs.backend == 'true' || steps.filter.outputs.frontend == 'true'
      run: ...
仅当子文件夹中的某些文件发生更改时,才在工作流中执行 job
jobs:
  # JOB to run change detection
  changes:
    runs-on: ubuntu-latest
    # Required permissions
    permissions:
      pull-requests: read
    # Set job outputs to values from filter step
    outputs:
      backend: ${{ steps.filter.outputs.backend }}
      frontend: ${{ steps.filter.outputs.frontend }}
    steps:
    # For pull requests it's not necessary to checkout the code
    - uses: dorny/paths-filter@v4
      id: filter
      with:
        filters: |
          backend:
            - 'backend/**'
          frontend:
            - 'frontend/**'

  # JOB to build and test backend code
  backend:
    needs: changes
    if: ${{ needs.changes.outputs.backend == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - ...

  # JOB to build and test frontend code
  frontend:
    needs: changes
    if: ${{ needs.changes.outputs.frontend == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - ...
使用变更检测配置矩阵任务
jobs:
  # JOB to run change detection
  changes:
    runs-on: ubuntu-latest
    # Required permissions
    permissions:
      pull-requests: read
    outputs:
      # Expose matched filters as job 'packages' output variable
      packages: ${{ steps.filter.outputs.changes }}
    steps:
    # For pull requests it's not necessary to checkout the code
    - uses: dorny/paths-filter@v4
      id: filter
      with:
        filters: |
          package1: src/package1
          package2: src/package2

  # JOB to build and test each of modified packages
  build:
    needs: changes
    strategy:
      matrix:
        # Parse JSON array containing names of all filters matching any of changed files
        # e.g. ['package1', 'package2'] if both package folders contains changes
        package: ${{ fromJSON(needs.changes.outputs.packages) }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - ...

变更检测工作流

拉取请求: 检测相对于 PR 基准分支的变更
on:
  pull_request:
    branches: # PRs to the following branches will trigger the workflow
      - master
      - develop
  # Optionally you can use the action in the merge queue
  # if your repository enables the feature.
  merge_group:
    branches:
      - master
      - develop
jobs:
  build:
    runs-on: ubuntu-latest
    # Required permissions
    permissions:
      contents: read      # required by actions/checkout
      pull-requests: read # required by dorny/paths-filter
    steps:
    - uses: actions/checkout@v6
    - uses: dorny/paths-filter@v4
      id: filter
      with:
        filters: ... # Configure your filters
功能分支:根据已配置的基准分支检测变更
on:
  push:
    branches: # Push to following branches will trigger the workflow
      - feature/**
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v6
      with:
        # This may save additional git fetch roundtrip if
        # merge-base is found within latest 20 commits
        fetch-depth: 20
    - uses: dorny/paths-filter@v4
      id: filter
      with:
        base: develop # Change detection against merge-base with this branch
        filters: ... # Configure your filters
长期分支: 针对推送前同一分支上的最新提交检测变更
on:
  push:
    branches: # Push to the following branches will trigger the workflow
      - master
      - develop
      - release/**
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v6
    - uses: dorny/paths-filter@v4
      id: filter
      with:
        # Use context to get the branch where commits were pushed.
        # If there is only one long-lived branch (e.g. master),
        # you can specify it directly.
        # If it's not configured, the repository default branch is used.
        base: ${{ github.ref }}
        filters: ... # Configure your filters
本地变更: 检测暂存和未暂存的本地变更
on:
  push:
    branches: # Push to following branches will trigger the workflow
      - master
      - develop
      - release/**
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v6

      # Some action that modifies files tracked by git (e.g. code linter)
    - uses: johndoe/some-action@v1

      # Filter to detect which files were modified
      # Changes could be, for example, automatically committed
    - uses: dorny/paths-filter@v4
      id: filter
      with:
        base: HEAD
        filters: ... # Configure your filters

高级选项

在独立文件中定义过滤规则
- uses: dorny/paths-filter@v4
      id: filter
      with:
        # Path to file where filters are defined
        filters: .github/filters.yaml
使用 YAML 锚点在另一个规则中重用路径表达式
- uses: dorny/paths-filter@v4
      id: filter
      with:
        # &shared is YAML anchor,
        # *shared references previously defined anchor
        # src filter will match any path under common, config and src folders
        filters: |
          shared: &shared
            - common/**
            - config/**
          src:
            - *shared
            - src/**
考虑文件是新增、修改还是删除
- uses: dorny/paths-filter@v4
      id: filter
      with:
        # Changed file can be 'added', 'modified', or 'deleted'.
        # By default, the type of change is not considered.
        # Optionally, it's possible to specify it using nested
        # dictionary, where the type of change composes the key.
        # Multiple change types can be specified using `|` as the delimiter.
        filters: |
          shared: &shared
            - common/**
            - config/**
          addedOrModified:
            - added|modified: '**'
          allChanges:
            - added|deleted|modified: '**'
          addedOrModifiedAnchors:
            - added|modified: *shared
仅针对部分文件扩展名检测文件夹中的更改
- uses: dorny/paths-filter@v4
      id: filter
      with:
        # This makes it so that all the patterns have to match a file for it to be
        # considered changed. Because we have the exclusions for .jpeg and .md files
        # the end result is that if those files are changed they will be ignored
        # because they don't match the respective rules excluding them.
        #
        # This can be leveraged to ensure that you only build & test software changes
        # that have real impact on the behavior of the code, e.g. you can set up your
        # build to run when Typescript/Rust/etc. files are changed but markdown
        # changes in the diff will be ignored and you consume less resources to build.
        predicate-quantifier: 'every'
        filters: |
          backend:
            - 'pkg/a/b/c/**'
            - '!**/*.jpeg'
            - '!**/*.md'
检测多个不相关路径中的变更并排除部分文件扩展名
- uses: dorny/paths-filter@v4
  id: filter
  with:
    # With 'some-with-excludes' a file is matched when it matches at least one pattern
    # and none of the negated ones. The filter below therefore matches all the files
    # in the 'mobile' folder and the workflow file, but never a markdown file or
    # anything in 'mobile/.config'.
    #
    # An exclusion is final - a file excluded by one pattern can't be included back
    # by another one. Consequently, a filter consisting of negated patterns only
    # never matches anything.
    predicate-quantifier: 'some-with-excludes'
    filters: |
      mobile:
        - 'mobile/**'
        - '!mobile/**/*.md'
        - '!mobile/.config/**'
        - '.github/workflows/test_mobile.yml'

变更文件的自定义处理

在 Linux shell 中将已修改文件列表作为命令行参数传递
- uses: dorny/paths-filter@v4
  id: filter
  with:
    # Enable listing of files matching each filter.
    # Paths to files will be available in `${FILTER_NAME}_files` output variable.
    # Paths will be escaped and space-delimited.
    # Output is usable as command-line argument list in Linux shell
    list-files: shell

    # In this example changed files will be checked by linter.
    # It doesn't make sense to lint deleted files.
    # Therefore we specify we are only interested in added or modified files.
    filters: |
      markdown:
        - added|modified: '*.md'
- name: Lint Markdown
  if: ${{ steps.filter.outputs.markdown == 'true' }}
  run: npx textlint ${{ steps.filter.outputs.markdown_files }}
将修改文件列表以 JSON 数组形式传递给另一个 action
- uses: dorny/paths-filter@v4
  id: filter
  with:
    # Enable listing of files matching each filter.
    # Paths to files will be available in `${FILTER_NAME}_files` output variable.
    # Paths will be formatted as JSON array
    list-files: json

    # In this example all changed files are passed to the following action to do
    # some custom processing.
    filters: |
      changed:
        - '**'
- name: Lint Markdown
  uses: johndoe/some-action@v1
  with:
    files: ${{ steps.filter.outputs.changed_files }}

另请参阅

  • test-reporter - 直接在 GitHub 中显示来自主流测试框架的测试结果

许可证

本项目中的脚本和文档均根据 MIT 许可证 发布

项目介绍

根据PR修改的文件、功能分支或推送的提交记录,有条件地执行操作。【此简介由AI生成】

定制我的领域
113.31 K387访问 GitHub