diff --git a/.github/workflows/ubuntu-latest.yml b/.github/workflows/ubuntu-latest.yml index 2e25f0563..73c0e46c4 100644 --- a/.github/workflows/ubuntu-latest.yml +++ b/.github/workflows/ubuntu-latest.yml @@ -34,6 +34,7 @@ jobs: with: submodules: recursive fetch-depth: 0 + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.head_ref }} - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index cc806f1b7..313e41e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - **Retired `GitVersion.Tool`** (#81) — version is now sourced exclusively from `Nerdbank.GitVersioning` via `version.json`. `MajorMinorPatchVersion` and friends in `Build.cs` read NB.GV's `$(Version)` MSBuild output. - **Trimmed unused / replaceable dependencies** (#75, #78, #79, #80, #82): dropped `JetBrains.ReSharper.GlobalTools`, the matkoch Spectre.Console fork (swapped for upstream `Spectre.Console`), `Microsoft.ApplicationInsights`, `Codecov.Tool`, and `xunit.runner.console`. See `docs/dependencies.md` for the current graph. - **Bumped Scriban 7.1.0 → 7.2.0** (#84) to clear NU1903. +- **Fixed `[GitHubActions]` `CheckoutRef` breaking cross-repo / fork PRs**. The generator emitted `ref: ${{ github.head_ref }}` but didn't set `repository:`, so the default `${{ github.repository }}` was used. For PRs from a fork, the source branch only exists on the fork; checkout tried to resolve it on origin and failed with "branch or tag could not be found" (regressed in #175). Generator now also emits `repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}` whenever `CheckoutRef` is set — works for fork PRs, same-repo PRs, AND push events (the `||` fallback). Regenerate via any `dotnet fallout ` to pick up the fix. - **Fixed `fallout-migrate` producing unrestorable `_build.csproj`** (closes #217). The Nuke → Fallout namespace rewrite carried the original NUKE `Version="10.1.0"` pins onto `Fallout.*` packages — but `Fallout.*` was never published at `10.1.0`, so NuGet hit NU1603 ("not found, falling back to next-higher") and `WarningsAsErrors` in the migrated project escalated to fatal. Migrate now bumps inline `Version="..."` attributes on Nuke → Fallout PackageReferences to the running migrate tool's own version in the same regex pass, and strips the stale `System.Security.Cryptography.Xml` `` (NUKE-era projects often pinned an older major that conflicts with `Fallout.Common`'s transitive ≥ 10.0.6 — NU1605 downgrade). CPM-managed references (no inline Version) keep namespace-only rewrite — version stays in Directory.Packages.props. ## [10.2.0] / 2026-05-21 diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs index 78cea71ff..c40f737a8 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs @@ -22,7 +22,10 @@ public class GitHubActionsCheckoutStep : GitHubActionsStep /// (the merge SHA on pull_request triggers, which leaves HEAD detached). Set to /// ${{ github.head_ref }} on PR workflows that read .git/HEAD directly /// (e.g. ) so the branch - /// resolves correctly. + /// resolves correctly. When set, the generator also emits a repository: line that + /// resolves to the PR head's repo for fork PRs and falls back to the current repo for push + /// events — without it, fork PRs fail with "branch or tag could not be found" because the + /// branch only exists on the fork, not on origin. /// public string Ref { get; set; } @@ -49,7 +52,16 @@ public override void Write(CustomFileWriter writer) if (!Filter.IsNullOrWhiteSpace()) writer.WriteLine($"filter: {Filter}"); if (!Ref.IsNullOrWhiteSpace()) + { + // Pin checkout to the source repo of the PR head (or the current repo on + // push events). Required when `ref:` is set to `${{ github.head_ref }}` — + // for cross-repo PRs, that branch only exists on the fork, not on origin, + // and checkout's default `repository: ${{ github.repository }}` would fail + // to resolve it. The `||` fallback handles push events where there's no + // pull_request context. + writer.WriteLine("repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}"); writer.WriteLine($"ref: {Ref}"); + } } } } diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs index 25bc1341d..f8e57a623 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs @@ -122,6 +122,11 @@ public string Filter /// Forwarded to actions/checkout's ref input. Set to /// ${{ github.head_ref }} on PR-triggered workflows that need .git/HEAD /// to stay attached (e.g. ones that read the current branch via GitRepository). + /// + /// When set, the generator also emits repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + /// so cross-repo PRs (from forks) resolve the branch name on the fork instead of failing + /// on origin. The fallback to github.repository keeps push-triggered workflows + /// (where there's no PR context) working unchanged. /// public string CheckoutRef { diff --git a/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-ref_attribute=GitHubActionsAttribute.verified.txt b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-ref_attribute=GitHubActionsAttribute.verified.txt new file mode 100644 index 000000000..dc14198ea --- /dev/null +++ b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-ref_attribute=GitHubActionsAttribute.verified.txt @@ -0,0 +1,62 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [TestGitHubActions (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# fallout --generate-configuration GitHubActions_test --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: test + +on: + pull_request: + branches: + - main + +jobs: + ubuntu-latest: + name: ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + ref: ${{ github.head_ref }} + - name: 'Cache: .fallout/temp, ~/.nuget/packages' + uses: actions/cache@v4 + with: + path: | + .fallout/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - name: 'Setup: .NET SDK' + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + - name: 'Restore: dotnet tools' + run: dotnet tool restore + - name: 'Run: Test' + run: dotnet fallout Test + - name: 'Publish: src' + uses: actions/upload-artifact@v5 + with: + name: src + path: src + - name: 'Publish: test-results' + uses: actions/upload-artifact@v5 + with: + name: test-results + path: output/test-results + - name: 'Publish: coverage-report.zip' + uses: actions/upload-artifact@v5 + with: + name: coverage-report.zip + path: output/coverage-report.zip diff --git a/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.cs b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.cs index 20d59afc4..b4658e6f2 100644 --- a/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.cs +++ b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.cs @@ -168,6 +168,22 @@ public class TestBuild : FalloutBuild } ); + // Regression guard: when CheckoutRef is set, the generator must also emit a + // repository: line that resolves to the PR's head repo (for cross-repo / fork PRs) + // with a fallback to the current repo (for push events). Without this, fork PRs + // fail at the checkout step because the branch only exists on the fork, not on + // origin. + yield return + ( + "checkout-ref", + new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) + { + InvokedTargets = new[] { nameof(Test) }, + OnPullRequestBranches = new[] { "main" }, + CheckoutRef = "${{ github.head_ref }}" + } + ); + yield return ( null,