From 2568836a816a0ccf58608c80c2ffc28bec6e3e47 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 11 Sep 2025 15:07:27 +0200 Subject: [PATCH 01/10] ci(build): use Unix line endings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Git simply has so strong an opinion about line endings that it is not worth fighting. Git will passive-aggressively color the Carriage Return in the diff output until you give up, and that's what I'm doing here. Der Klügere gibt nach. Technically, this is far from the only file that has this issue. But I do not want to interfere with other ongoing work in the CI area, so I'll refrain from touching files other than the one I am about to modify. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 324 +++++++++++++++++------------------ 1 file changed, 162 insertions(+), 162 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 12bdf560d..7f1172aa7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,162 +1,162 @@ -name: VFS for Git - -on: - pull_request: - branches: [ master, releases/shipped ] - push: - branches: [ master, releases/shipped ] - workflow_dispatch: - inputs: - git_version: - description: 'Microsoft Git version tag to include in the build (leave empty for default)' - required: false - type: string - -env: - GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.50.1.vfs.0.1' }} - -jobs: - validate: - runs-on: windows-2025 - name: Validation - steps: - - name: Checkout source - uses: actions/checkout@v5 - - - name: Validate Microsoft Git version - shell: pwsh - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - & "$env:GITHUB_WORKSPACE\.github\workflows\scripts\validate_release.ps1" ` - -Repository microsoft/git ` - -Tag $env:GIT_VERSION && ` - Write-Host ::notice title=Validation::Using microsoft/git version $env:GIT_VERSION - - build: - runs-on: windows-2025 - name: Build and Unit Test - needs: validate - - strategy: - matrix: - configuration: [ Debug, Release ] - - steps: - - name: Checkout source - uses: actions/checkout@v5 - with: - path: src - - - name: Install .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 8.0.413 - - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v2.0.0 - - - name: Build VFS for Git - shell: cmd - run: src\scripts\Build.bat ${{ matrix.configuration }} - - - name: Run unit tests - shell: cmd - run: src\scripts\RunUnitTests.bat ${{ matrix.configuration }} - - - name: Create build artifacts - shell: cmd - run: src\scripts\CreateBuildArtifacts.bat ${{ matrix.configuration }} artifacts - - - name: Download microsoft/git installers - shell: cmd - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release download %GIT_VERSION% --repo microsoft/git --pattern "Git*.exe" --dir artifacts\GVFS.Installers - - - name: Upload functional tests drop - uses: actions/upload-artifact@v4 - with: - name: FunctionalTests_${{ matrix.configuration }} - path: artifacts\GVFS.FunctionalTests - - - name: Upload FastFetch drop - uses: actions/upload-artifact@v4 - with: - name: FastFetch_${{ matrix.configuration }} - path: artifacts\FastFetch - - - name: Upload installers - uses: actions/upload-artifact@v4 - with: - name: Installers_${{ matrix.configuration }} - path: artifacts\GVFS.Installers - - functional_test: - runs-on: ${{ matrix.architecture == 'arm64' && 'windows-11-arm' || 'windows-2025' }} - name: Functional Tests - needs: build - - strategy: - matrix: - configuration: [ Debug, Release ] - architecture: [ x86_64, arm64 ] - - steps: - - name: Download installers - uses: actions/download-artifact@v5 - with: - name: Installers_${{ matrix.configuration }} - path: install - - - name: Download functional tests drop - uses: actions/download-artifact@v5 - with: - name: FunctionalTests_${{ matrix.configuration }} - path: ft - - - name: ProjFS details (pre-install) - shell: cmd - run: install\info.bat - - - name: Install product - shell: cmd - run: install\install.bat - - - name: ProjFS details (post-install) - shell: cmd - run: install\info.bat - - - name: Upload installation logs - if: always() - uses: actions/upload-artifact@v4 - with: - name: InstallationLogs_${{ matrix.configuration }}_${{ matrix.architecture }} - path: install\logs - - - name: Run functional tests - shell: cmd - run: | - SET PATH=C:\Program Files\VFS for Git;%PATH% - SET GIT_TRACE2_PERF=C:\temp\git-trace2.log - ft\GVFS.FunctionalTests.exe /result:TestResult.xml --ci - - - name: Upload functional test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: FunctionalTests_Results_${{ matrix.configuration }}_${{ matrix.architecture }} - path: TestResult.xml - - - name: Upload Git trace2 output - if: always() - uses: actions/upload-artifact@v4 - with: - name: GitTrace2_${{ matrix.configuration }}_${{ matrix.architecture }} - path: C:\temp\git-trace2.log - - - name: ProjFS details (post-test) - if: always() - shell: cmd - run: install\info.bat +name: VFS for Git + +on: + pull_request: + branches: [ master, releases/shipped ] + push: + branches: [ master, releases/shipped ] + workflow_dispatch: + inputs: + git_version: + description: 'Microsoft Git version tag to include in the build (leave empty for default)' + required: false + type: string + +env: + GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.50.1.vfs.0.1' }} + +jobs: + validate: + runs-on: windows-2025 + name: Validation + steps: + - name: Checkout source + uses: actions/checkout@v5 + + - name: Validate Microsoft Git version + shell: pwsh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + & "$env:GITHUB_WORKSPACE\.github\workflows\scripts\validate_release.ps1" ` + -Repository microsoft/git ` + -Tag $env:GIT_VERSION && ` + Write-Host ::notice title=Validation::Using microsoft/git version $env:GIT_VERSION + + build: + runs-on: windows-2025 + name: Build and Unit Test + needs: validate + + strategy: + matrix: + configuration: [ Debug, Release ] + + steps: + - name: Checkout source + uses: actions/checkout@v5 + with: + path: src + + - name: Install .NET SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 8.0.413 + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v2.0.0 + + - name: Build VFS for Git + shell: cmd + run: src\scripts\Build.bat ${{ matrix.configuration }} + + - name: Run unit tests + shell: cmd + run: src\scripts\RunUnitTests.bat ${{ matrix.configuration }} + + - name: Create build artifacts + shell: cmd + run: src\scripts\CreateBuildArtifacts.bat ${{ matrix.configuration }} artifacts + + - name: Download microsoft/git installers + shell: cmd + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release download %GIT_VERSION% --repo microsoft/git --pattern "Git*.exe" --dir artifacts\GVFS.Installers + + - name: Upload functional tests drop + uses: actions/upload-artifact@v4 + with: + name: FunctionalTests_${{ matrix.configuration }} + path: artifacts\GVFS.FunctionalTests + + - name: Upload FastFetch drop + uses: actions/upload-artifact@v4 + with: + name: FastFetch_${{ matrix.configuration }} + path: artifacts\FastFetch + + - name: Upload installers + uses: actions/upload-artifact@v4 + with: + name: Installers_${{ matrix.configuration }} + path: artifacts\GVFS.Installers + + functional_test: + runs-on: ${{ matrix.architecture == 'arm64' && 'windows-11-arm' || 'windows-2025' }} + name: Functional Tests + needs: build + + strategy: + matrix: + configuration: [ Debug, Release ] + architecture: [ x86_64, arm64 ] + + steps: + - name: Download installers + uses: actions/download-artifact@v5 + with: + name: Installers_${{ matrix.configuration }} + path: install + + - name: Download functional tests drop + uses: actions/download-artifact@v5 + with: + name: FunctionalTests_${{ matrix.configuration }} + path: ft + + - name: ProjFS details (pre-install) + shell: cmd + run: install\info.bat + + - name: Install product + shell: cmd + run: install\install.bat + + - name: ProjFS details (post-install) + shell: cmd + run: install\info.bat + + - name: Upload installation logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: InstallationLogs_${{ matrix.configuration }}_${{ matrix.architecture }} + path: install\logs + + - name: Run functional tests + shell: cmd + run: | + SET PATH=C:\Program Files\VFS for Git;%PATH% + SET GIT_TRACE2_PERF=C:\temp\git-trace2.log + ft\GVFS.FunctionalTests.exe /result:TestResult.xml --ci + + - name: Upload functional test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: FunctionalTests_Results_${{ matrix.configuration }}_${{ matrix.architecture }} + path: TestResult.xml + + - name: Upload Git trace2 output + if: always() + uses: actions/upload-artifact@v4 + with: + name: GitTrace2_${{ matrix.configuration }}_${{ matrix.architecture }} + path: C:\temp\git-trace2.log + + - name: ProjFS details (post-test) + if: always() + shell: cmd + run: install\info.bat From c5f1ca3cc7cafc51b88924d47da9c162a516e289 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 12 Sep 2025 14:17:04 +0200 Subject: [PATCH 02/10] ci: explicitly restrict `GITHUB_TOKEN` permissions Yes, yes, CodeQL. It is totally _possible_ that someone with admin privileges will change the default back to the unsafe `write` permission. Even if unlikely, let's make it explicit that the `build` workflow only requires `read` permission. Even in a PR that is only modifying that workflow, and whose purpose is a totally different one. Let's conflate separations of concerns. Whatever. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7f1172aa7..9067c0b6f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,6 +12,9 @@ on: required: false type: string +permissions: + contents: read + env: GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.50.1.vfs.0.1' }} From 5ca1b9972e051f9408da3d6c2744493c671735a9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 11 Sep 2025 11:32:50 +0200 Subject: [PATCH 03/10] ci: avoid failing fast in the matrix jobs Most of the failures we encounter in VFSforGit's CI runs are flakes. In those instances, it is not helpful but rather annoying when long-running jobs are canceled after more than half an hour when they could have run to completion and succeeded instead. Because when those runs fail, the first thing I do is to hit "Re-run" to see whether it _is_ a flake. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9067c0b6f..f9e26ed89 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -44,6 +44,7 @@ jobs: strategy: matrix: configuration: [ Debug, Release ] + fail-fast: false steps: - name: Checkout source @@ -105,6 +106,7 @@ jobs: matrix: configuration: [ Debug, Release ] architecture: [ x86_64, arm64 ] + fail-fast: false # most failures are flaky tests, no need to stop the other jobs from succeeding steps: - name: Download installers From a08ef4c43901dbb18f97d20ac9665a451bf8b5fb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 12 Sep 2025 12:28:28 +0200 Subject: [PATCH 04/10] ci: skip running if the same commit has already seen a successful run GitHub Actions sadly lacks the feature where you can reuse prior runs that were successful on the same `head_sha`. Similar to what I did in git/git@99fe06cbfd6 (ci: avoid building from the same commit in parallel, 2023-08-23), let's simply skip the CI run if the same commit has been tested successfully before. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 46 +++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f9e26ed89..90863bbaf 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,11 +22,53 @@ jobs: validate: runs-on: windows-2025 name: Validation + outputs: + skip: ${{ steps.check.outputs.result }} + steps: + - name: Look for prior successful runs + id: check + uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + /* + * Look for any successful run for the same commit. No need to run it again, right? + */ + try { + // Figure out workflow ID, commit and tree + const { data: run } = await github.rest.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + }); + const workflow_id = run.workflow_id; + const head_sha = run.head_sha; + const tree_id = run.head_commit.tree_id; + + // See whether there is a successful run for that commit + const { data: runs } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 500, + workflow_id, + status: 'success', + }); + for (const run of runs.workflow_runs) { + if (head_sha === run.head_sha) return true + } + return false + } catch (e) { + core.error(e) + core.warning(e) + } + - name: Checkout source + if: steps.check.outputs.result != 'true' uses: actions/checkout@v5 - name: Validate Microsoft Git version + if: steps.check.outputs.result != 'true' shell: pwsh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -42,6 +84,7 @@ jobs: needs: validate strategy: + if: needs.validate.outputs.skip != 'true' matrix: configuration: [ Debug, Release ] fail-fast: false @@ -100,9 +143,10 @@ jobs: functional_test: runs-on: ${{ matrix.architecture == 'arm64' && 'windows-11-arm' || 'windows-2025' }} name: Functional Tests - needs: build + needs: [validate, build] strategy: + if: needs.validate.outputs.skip != 'true' matrix: configuration: [ Debug, Release ] architecture: [ x86_64, arm64 ] From 2edc6c182001d3c01180000b5126303357c4c1be Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 12 Sep 2025 12:33:11 +0200 Subject: [PATCH 05/10] ci: skip also for successful runs for _tree-same_ commits As far as VFSforGit's CI is concerned, there is no difference between commits when their _files_ have not changed (it's not like the tip commit's SHA is persisted in some version number or some such). Therefore, if there is already a successful run for another commit whose top-level tree is identical to the one that is about to be tested, skip the current run in favor of that one. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 90863bbaf..36e4ead5a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -34,6 +34,8 @@ jobs: script: | /* * Look for any successful run for the same commit. No need to run it again, right? + * Also allow for _tree-same_ commits, i.e. commits whose SHA is different, but whose + * top-level trees' SHA isn't. */ try { // Figure out workflow ID, commit and tree @@ -46,7 +48,7 @@ jobs: const head_sha = run.head_sha; const tree_id = run.head_commit.tree_id; - // See whether there is a successful run for that commit + // See whether there is a successful run for that commit or tree const { data: runs } = await github.rest.actions.listWorkflowRuns({ owner: context.repo.owner, repo: context.repo.repo, @@ -56,6 +58,7 @@ jobs: }); for (const run of runs.workflow_runs) { if (head_sha === run.head_sha) return true + else if (tree_id === run.head_commit?.tree_id) return true } return false } catch (e) { From 4ee8c1c9a176d98ca9b3fcfe0013b3dd93d20bbb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 12 Sep 2025 12:41:57 +0200 Subject: [PATCH 06/10] ci: when skipping runs, do not skip the required jobs To increase confidence in Pull Requests, a couple of Checks are required, for example the PR build must pass. However, due to one of GitHub Actions' limitations that seem to stay with us forever, it is impossible to require a specific _workflow_ to succeed for a PR before it can be merged. You have to specify workflow _jobs_ that are required. Therefore, the changes I just made to skip runs when there already exists a successful run for the same commit (or at least a tree-same one), would eternally prevent affected PRs from being merged. While the _workflow_ succeeds in such instances, the (required) workflow _jobs_ would be skipped (and therefore the required checks would not be fulfilled). Let's bite the bullet and patch through the information whether to skip re-running the tests _to the individual jobs_, so that they do run, and the do get marked as successful, and the Pull Requests can proceed. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 60 ++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 36e4ead5a..74bda369a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -33,9 +33,19 @@ jobs: github-token: ${{secrets.GITHUB_TOKEN}} script: | /* - * Look for any successful run for the same commit. No need to run it again, right? - * Also allow for _tree-same_ commits, i.e. commits whose SHA is different, but whose - * top-level trees' SHA isn't. + * It would be nice if GitHub Actions offered a convenient way to avoid running + * successful workflow runs _again_ for the respective commit (or for a tree-same one): + * We would expect the same outcome in those cases, right? + * + * Let's check for such a scenario: Look for previous runs that have been successful + * and that correspond to the same commit, or at least a tree-same one. If there is + * one, skip running the build and tests _again_. + * + * There are challenges, though: We need to require those _jobs_ to succeed before PRs + * can be merged. You can mark workflow _jobs_ as required on GitHub, but not + * _workflows_. So if those jobs are now simply skipped, the requirement isn't met and + * the PR cannot be merged. We can't just skip the job. Instead, we need to run the job + * _but skip every single step_ so that the job can "succeed". */ try { // Figure out workflow ID, commit and tree @@ -87,38 +97,53 @@ jobs: needs: validate strategy: - if: needs.validate.outputs.skip != 'true' matrix: configuration: [ Debug, Release ] fail-fast: false steps: + - name: Skip this job if there is a previous successful run + if: needs.validate.outputs.skip == 'true' + id: skip + uses: actions/github-script@v7 + with: + script: | + core.info(`Skipping: There already is a successful run`) + return true + - name: Checkout source + if: steps.skip.outputs.result != 'true' uses: actions/checkout@v5 with: path: src - name: Install .NET SDK + if: steps.skip.outputs.result != 'true' uses: actions/setup-dotnet@v5 with: dotnet-version: 8.0.413 - name: Add MSBuild to PATH + if: steps.skip.outputs.result != 'true' uses: microsoft/setup-msbuild@v2.0.0 - name: Build VFS for Git + if: steps.skip.outputs.result != 'true' shell: cmd run: src\scripts\Build.bat ${{ matrix.configuration }} - name: Run unit tests + if: steps.skip.outputs.result != 'true' shell: cmd run: src\scripts\RunUnitTests.bat ${{ matrix.configuration }} - name: Create build artifacts + if: steps.skip.outputs.result != 'true' shell: cmd run: src\scripts\CreateBuildArtifacts.bat ${{ matrix.configuration }} artifacts - name: Download microsoft/git installers + if: steps.skip.outputs.result != 'true' shell: cmd env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -126,18 +151,21 @@ jobs: gh release download %GIT_VERSION% --repo microsoft/git --pattern "Git*.exe" --dir artifacts\GVFS.Installers - name: Upload functional tests drop + if: steps.skip.outputs.result != 'true' uses: actions/upload-artifact@v4 with: name: FunctionalTests_${{ matrix.configuration }} path: artifacts\GVFS.FunctionalTests - name: Upload FastFetch drop + if: steps.skip.outputs.result != 'true' uses: actions/upload-artifact@v4 with: name: FastFetch_${{ matrix.configuration }} path: artifacts\FastFetch - name: Upload installers + if: steps.skip.outputs.result != 'true' uses: actions/upload-artifact@v4 with: name: Installers_${{ matrix.configuration }} @@ -149,45 +177,59 @@ jobs: needs: [validate, build] strategy: - if: needs.validate.outputs.skip != 'true' matrix: configuration: [ Debug, Release ] architecture: [ x86_64, arm64 ] fail-fast: false # most failures are flaky tests, no need to stop the other jobs from succeeding steps: + - name: Skip this job if there is a previous successful run + if: needs.validate.outputs.skip == 'true' + id: skip + uses: actions/github-script@v7 + with: + script: | + core.info(`Skipping: There already is a successful run`) + return true + - name: Download installers + if: steps.skip.outputs.result != 'true' uses: actions/download-artifact@v5 with: name: Installers_${{ matrix.configuration }} path: install - name: Download functional tests drop + if: steps.skip.outputs.result != 'true' uses: actions/download-artifact@v5 with: name: FunctionalTests_${{ matrix.configuration }} path: ft - name: ProjFS details (pre-install) + if: steps.skip.outputs.result != 'true' shell: cmd run: install\info.bat - name: Install product + if: steps.skip.outputs.result != 'true' shell: cmd run: install\install.bat - name: ProjFS details (post-install) + if: steps.skip.outputs.result != 'true' shell: cmd run: install\info.bat - name: Upload installation logs - if: always() + if: always() && steps.skip.outputs.result != 'true' uses: actions/upload-artifact@v4 with: name: InstallationLogs_${{ matrix.configuration }}_${{ matrix.architecture }} path: install\logs - name: Run functional tests + if: steps.skip.outputs.result != 'true' shell: cmd run: | SET PATH=C:\Program Files\VFS for Git;%PATH% @@ -195,20 +237,20 @@ jobs: ft\GVFS.FunctionalTests.exe /result:TestResult.xml --ci - name: Upload functional test results - if: always() + if: always() && steps.skip.outputs.result != 'true' uses: actions/upload-artifact@v4 with: name: FunctionalTests_Results_${{ matrix.configuration }}_${{ matrix.architecture }} path: TestResult.xml - name: Upload Git trace2 output - if: always() + if: always() && steps.skip.outputs.result != 'true' uses: actions/upload-artifact@v4 with: name: GitTrace2_${{ matrix.configuration }}_${{ matrix.architecture }} path: C:\temp\git-trace2.log - name: ProjFS details (post-test) - if: always() + if: always() && steps.skip.outputs.result != 'true' shell: cmd run: install\info.bat From 5099500912f2026915cbc54e7439f2cd64ddf262 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 12 Sep 2025 13:01:38 +0200 Subject: [PATCH 07/10] ci: when skipping runs, report the previous successful one It is always nice when one is given a good reason for something. For example, when a CI run is skipped, it is nice to not only to know that there _has_ been a previous successful run, but also _which one_. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 74bda369a..ee0c17bb1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -31,6 +31,7 @@ jobs: uses: actions/github-script@v7 with: github-token: ${{secrets.GITHUB_TOKEN}} + result-encoding: string script: | /* * It would be nice if GitHub Actions offered a convenient way to avoid running @@ -67,21 +68,21 @@ jobs: status: 'success', }); for (const run of runs.workflow_runs) { - if (head_sha === run.head_sha) return true - else if (tree_id === run.head_commit?.tree_id) return true + if (head_sha === run.head_sha) return run.html_url + else if (tree_id === run.head_commit?.tree_id) return run.html_url } - return false + return '' } catch (e) { core.error(e) core.warning(e) } - name: Checkout source - if: steps.check.outputs.result != 'true' + if: steps.check.outputs.result == '' uses: actions/checkout@v5 - name: Validate Microsoft Git version - if: steps.check.outputs.result != 'true' + if: steps.check.outputs.result == '' shell: pwsh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -103,12 +104,12 @@ jobs: steps: - name: Skip this job if there is a previous successful run - if: needs.validate.outputs.skip == 'true' + if: needs.validate.outputs.skip != '' id: skip uses: actions/github-script@v7 with: script: | - core.info(`Skipping: There already is a successful run`) + core.info(`Skipping: There already is a successful run: ${{ needs.validate.outputs.skip }}`) return true - name: Checkout source @@ -184,12 +185,12 @@ jobs: steps: - name: Skip this job if there is a previous successful run - if: needs.validate.outputs.skip == 'true' + if: needs.validate.outputs.skip != '' id: skip uses: actions/github-script@v7 with: script: | - core.info(`Skipping: There already is a successful run`) + core.info(`Skipping: There already is a successful run: ${{ needs.validate.outputs.skip }}`) return true - name: Download installers From a9fc9a57c696a9b4514ec8f324a370dc6f70508f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 12 Sep 2025 13:40:26 +0200 Subject: [PATCH 08/10] ci: when looking for successful workflow runs, wait for in-progress ones The Functional Test jobs takes long enough (35 minutes for x86_64, 1 hour 15 minutes for ARM64) that it is worth waiting for them to finish before kicking off a matrix build that tests the exact same code. So let's do that, wait for any in-progress run that might result in some saved time. Granted, this spends cycles of GitHub runners waiting idly, but GitHub does not provide for a better way. Note: The `concurrency` attribute _might_ look as helpful in this context at first sight. But is comically limited: It can only let a _single_ job wait in a queue, subsequent jobs get canceled (and if you have notifications turned on, in a loud manner!). And even letting the job wait would not help here because we also want to handle _tree-same_ commits, not only identical commits, and GitHub does not provide for a way to identify them in GitHub workflow expressions, either. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ee0c17bb1..ea1579d61 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -65,11 +65,30 @@ jobs: repo: context.repo.repo, per_page: 500, workflow_id, - status: 'success', }); for (const run of runs.workflow_runs) { - if (head_sha === run.head_sha) return run.html_url - else if (tree_id === run.head_commit?.tree_id) return run.html_url + if (head_sha !== run.head_sha && tree_id !== run.head_commit?.tree_id) continue + if (context.runId === run.id) continue // do not wait for the current run to finish ;-) + + if (run.status === 'in_progress') { + // poll until the run is done + const pollIntervalInSeconds = 30 + let seconds = 0 + for (;;) { + console.log(`Found existing, in-progress run at ${run.html_url}; Waiting for it to finish (waited ${seconds} seconds so far)...`) + await new Promise((resolve) => { setTimeout(resolve, pollIntervalInSeconds * 1000) }) + seconds += pollIntervalInSeconds + + const { data: polledRun } = await github.rest.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: run.id + }) + if (polledRun.status !== 'in_progress') break + } + } + + if (run.status === 'success') return run.html_url } return '' } catch (e) { From 65755726a33be3c829d864a3a9209d66559fb35b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 12 Sep 2025 13:40:26 +0200 Subject: [PATCH 09/10] ci: when looking for successful workflow jobs, use a smart order There might be runs for the very same commit, those should be looked at first. Then there might be runs that have not yet finished; Let's first look at the finished ones. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ea1579d61..92a850693 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -66,7 +66,10 @@ jobs: per_page: 500, workflow_id, }); - for (const run of runs.workflow_runs) { + // first look at commit-same runs, then at finished ones, then at in-progress ones + const rank = (a) => (a.status === 'in_progress' ? 0 : (head_sha === a.head_sha ? 2 : 1)) + const demoteInProgressToEnd = (a, b) => (rank(b) - rank(a)) + for (const run of runs.workflow_runs.sort(demoteInProgressToEnd)) { if (head_sha !== run.head_sha && tree_id !== run.head_commit?.tree_id) continue if (context.runId === run.id) continue // do not wait for the current run to finish ;-) From 45580086fb34570b48ef2f4b8d8c990aacf47450 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 15 Sep 2025 20:19:34 +0200 Subject: [PATCH 10/10] ci: exclude `workflow_dispatch` runs from the "skip same run" logic In `workflow_dispatch`-triggered runs, the user can specify a Git version to use for testing, which means that something different is actually tested than in regular, non-`workflow_dispatch` runs. So let's neither skip the build/test when triggered thusly, nor reuse any results from thusly-triggered runs. Signed-off-by: Johannes Schindelin --- .github/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 92a850693..74c6f8e47 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,6 +28,7 @@ jobs: steps: - name: Look for prior successful runs id: check + if: github.event.inputs.git_version == '' uses: actions/github-script@v7 with: github-token: ${{secrets.GITHUB_TOKEN}} @@ -72,6 +73,7 @@ jobs: for (const run of runs.workflow_runs.sort(demoteInProgressToEnd)) { if (head_sha !== run.head_sha && tree_id !== run.head_commit?.tree_id) continue if (context.runId === run.id) continue // do not wait for the current run to finish ;-) + if (run.event === 'workflow_dispatch') continue // skip runs that were started manually: they can override the Git version if (run.status === 'in_progress') { // poll until the run is done