如何基于上一次成功运行的差异确定变更文件并触发对应测试?
问题背景与需求
我当前使用actions/checkout@v3配合tj-actions/changed-files@v37搭建GitHub Action,用来检测PR中的变更文件,以此决定需要运行的测试用例。由于所有测试都是必填项,无需运行的测试会被自动标记为“通过”(绿色对勾)。
目前遇到的问题是:tj-actions/changed-files@v37检测的是整个PR的全部变更文件,而非自上一次Action成功运行以来的变更文件。我希望基于上一次成功测试的记录来确定变更范围,只触发对应需要的测试动作,请问有没有可行的实现方式?
现有主Workflow示例
name: "Example tests" on: push: branches: - main - dev pull_request: types: [opened, reopened, synchronize] branches-ignore: [releases] jobs: changed-files: name: Determine changed files runs-on: [self-hosted, external] outputs: cpp: ${{ steps.changed-files.outputs.cpp_any_modified }} steps: - name: Checkout project uses: actions/checkout@v3 with: fetch-depth: 0 submodules: recursive token: ${{ secrets.GITHUBTOKEN }} - name: Check which files have changed id: changed-files uses: tj-actions/changed-files@v37 with: files_yaml: | cpp: - cpp/** cpp_test: needs: changed-files uses: My-Companys-Name/github_workflows/.github/workflows/test.yml@main with: branch_name: ${{ github.head_ref || github.ref_name }} test_file: cpp/test_suite.yml skip: ${{ needs.changed-files.outputs.cpp == 'false' }}
可复用工作流test.yml示例
name: "Trigger Tests to External Machine" on: workflow_call: inputs: branch_name: required: true type: string test_file: required: true type: string skip: required: false type : boolean default: false run-name: ${{ github.actor }} is running the ${{ inputs.test_file }} test jobs: testing: name: Tests runs-on: [self-hosted, external] steps: - name: Dispatch test to external machine if: ${{ !inputs.skip }} run: | # Send the test file to the external machine using http requests - name: Display test results if: ${{ !inputs.skip }} run: | # Display test results from the external machine - name: Notify supervisor of test cancellation - name: Download test result if: ${{ always() && !inputs.skip }} run: | # Download test results for artifact use - name: Upload test results if: ${{ always() && !inputs.skip }} uses: actions/upload-artifact@v3 with: name: test-results path: ${{ env.test_folder }} retention-days: 14 - name: Notify skipped if: ${{ inputs.skip }} run: echo 'Test ${{ inputs.test_file }} requested to be skipped'
解决方案
核心思路是记录上一次Action成功运行时的提交哈希,将该哈希与当前提交做对比,从而获取两次成功运行之间的变更文件。以下是两种可行实现方式:
方式一:用GitHub Actions缓存存储上次成功提交哈希
通过缓存持久化上次成功运行的提交哈希,仅在测试成功时更新缓存,确保对比基准的准确性。
修改后的主Workflow
name: "Example tests" on: push: branches: - main - dev pull_request: types: [opened, reopened, synchronize] branches-ignore: [releases] jobs: changed-files: name: Determine changed files runs-on: [self-hosted, external] outputs: cpp: ${{ steps.changed-files.outputs.cpp_any_modified }} current_sha: ${{ github.sha }} steps: - name: Checkout project uses: actions/checkout@v3 with: fetch-depth: 0 submodules: recursive token: ${{ secrets.GITHUBTOKEN }} - name: Get last successful run SHA id: get-last-sha uses: actions/cache@v3 with: path: last_success_sha.txt key: ${{ github.ref }}-last-success-sha - name: Set comparison SHA id: set-compare-sha run: | if [ -f last_success_sha.txt ]; then echo "compare_sha=$(cat last_success_sha.txt)" >> "$GITHUB_OUTPUT" else # 首次运行或无成功记录时,用基准分支作为对比起点 if [ "${{ github.event_name }}" = "pull_request" ]; then echo "compare_sha=${{ github.base_ref }}" >> "$GITHUB_OUTPUT" else echo "compare_sha=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" fi fi - name: Check changed files since last success id: changed-files uses: tj-actions/changed-files@v37 with: base_sha: ${{ steps.set-compare-sha.outputs.compare_sha }} files_yaml: | cpp: - cpp/** cpp_test: needs: changed-files uses: My-Companys-Name/github_workflows/.github/workflows/test.yml@main with: branch_name: ${{ github.head_ref || github.ref_name }} test_file: cpp/test_suite.yml skip: ${{ needs.changed-files.outputs.cpp == 'false' }} outputs: test_success: ${{ jobs.testing.outputs.success }} update-last-sha: needs: cpp_test if: ${{ needs.cpp_test.outputs.test_success == 'true' }} runs-on: [self-hosted, external] steps: - name: Save current SHA as last success run: echo "${{ needs.changed-files.outputs.current_sha }}" > last_success_sha.txt - name: Update cache with new SHA uses: actions/cache@v3 with: path: last_success_sha.txt key: ${{ github.ref }}-last-success-sha
修改后的test.yml(添加测试成功状态输出)
name: "Trigger Tests to External Machine" on: workflow_call: inputs: branch_name: required: true type: string test_file: required: true type: string skip: required: false type : boolean default: false outputs: success: description: "Whether the test succeeded" value: ${{ jobs.testing.outputs.success }} run-name: ${{ github.actor }} is running the ${{ inputs.test_file }} test jobs: testing: name: Tests runs-on: [self-hosted, external] outputs: success: ${{ steps.check-result.outputs.success }} steps: - name: Dispatch test to external machine if: ${{ !inputs.skip }} id: dispatch-test run: | # Send the test file to the external machine using http requests - name: Display test results if: ${{ !inputs.skip }} run: | # Display test results from the external machine - name: Download test result if: ${{ always() && !inputs.skip }} run: | # Download test results for artifact use - name: Upload test results if: ${{ always() && !inputs.skip }} uses: actions/upload-artifact@v3 with: name: test-results path: ${{ env.test_folder }} retention-days: 14 - name: Notify skipped if: ${{ inputs.skip }} run: | echo 'Test ${{ inputs.test_file }} requested to be skipped' echo "success=true" >> "$GITHUB_OUTPUT" - name: Check test result id: check-result if: ${{ !inputs.skip }} run: | if [ ${{ steps.dispatch-test.outcome }} == 'success' ]; then echo "success=true" >> "$GITHUB_OUTPUT" else echo "success=false" >> "$GITHUB_OUTPUT" fi
方式二:通过GitHub API查询上一次成功运行记录
直接调用GitHub API获取当前分支/PR的上一次成功Workflow运行记录,提取对应的提交哈希作为对比基准。
在changed-files job中添加以下步骤替换缓存逻辑:
- name: Fetch last successful run SHA via API id: get-last-sha run: | # 区分PR和普通分支场景 if [ "${{ github.event_name }}" = "pull_request" ]; then REF="pull/${{ github.event.pull_request.number }}/head" else REF="${{ github.ref }}" fi # 调用API获取最近一次成功运行的提交哈希 LAST_SUCCESS_SHA=$(curl -s -H "Authorization: token ${{ secrets.GITHUBTOKEN }}" \ "https://api.github.com/repos/${{ github.repository }}/actions/runs?branch=${REF#refs/heads/}&status=success" \ | jq -r '.workflow_runs[0].head_sha') # 无成功记录时回退到基准分支 if [ "$LAST_SUCCESS_SHA" = "null" ]; then if [ "${{ github.event_name }}" = "pull_request" ]; then LAST_SUCCESS_SHA="${{ github.base_ref }}" else LAST_SUCCESS_SHA="${{ github.ref_name }}" fi fi echo "compare_sha=$LAST_SUCCESS_SHA" >> "$GITHUB_OUTPUT"
之后在tj-actions/changed-files步骤中设置base_sha: ${{ steps.get-last-sha.outputs.compare_sha }}即可。
注意:此方式需要Runner环境安装jq工具,且GITHUBTOKEN需具备repo权限。
关键注意事项
- 缓存键需包含
github.ref,确保不同分支/PR的缓存相互独立,避免基准混乱 - 首次运行或无成功记录时,必须设置合理的默认基准分支(如PR的base分支)
- 仅在测试成功时更新上次成功提交哈希,避免失败提交被作为对比基准
- PR场景下需区分head分支和base分支的对比逻辑,确保变更检测准确
内容的提问来源于stack exchange,提问作者cwbusacker
相关产品推荐
相关产品推荐

