diff --git a/.github/workflows/ubuntu-latest.yml b/.github/workflows/ubuntu-latest.yml
index 7aaa4355a..dfd3e733f 100644
--- a/.github/workflows/ubuntu-latest.yml
+++ b/.github/workflows/ubuntu-latest.yml
@@ -34,10 +34,6 @@ jobs:
with:
submodules: recursive
fetch-depth: 0
- # Check out the source branch (not the merge SHA) so HEAD stays
- # attached. GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest
- # requires repository.Branch to be non-null. NOTE: this file is
- # auto-generated by Fallout; next regen will overwrite. Track in #110.
ref: ${{ github.head_ref }}
- name: 'Cache: .fallout/temp, ~/.nuget/packages'
uses: actions/cache@v4
diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs
index a27ebb397..67ee7df96 100644
--- a/build/Build.CI.GitHubActions.cs
+++ b/build/Build.CI.GitHubActions.cs
@@ -27,11 +27,16 @@
PublishArtifacts = false)]
// pull_request only — same-repo branches would otherwise fire both push and
// pull_request events on every push, double-running the validation.
+//
+// CheckoutRef = github.head_ref pins checkout to the PR source branch instead of the merge SHA,
+// keeping HEAD attached so GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest (which reads
+// .git/HEAD via GitRepository.FromLocalDirectory) resolves a non-null branch.
[GitHubActions(
"ubuntu-latest",
GitHubActionsImage.UbuntuLatest,
FetchDepth = 0,
Submodules = GitHubActionsSubmodules.Recursive,
+ CheckoutRef = "${{ github.head_ref }}",
OnPullRequestBranches = new[] { MainBranch },
OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" },
InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) },
diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs
index 67d95dfb1..78cea71ff 100644
--- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs
+++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs
@@ -17,11 +17,21 @@ public class GitHubActionsCheckoutStep : GitHubActionsStep
public bool? Progress { get; set; }
public string Filter { get; set; }
+ ///
+ /// The git ref to check out. When unset, actions/checkout picks the default for the event
+ /// (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.
+ ///
+ public string Ref { get; set; }
+
public override void Write(CustomFileWriter writer)
{
writer.WriteLine("- uses: actions/checkout@v6");
- if (Submodules.HasValue || Lfs.HasValue || FetchDepth.HasValue || Progress.HasValue || !Filter.IsNullOrWhiteSpace())
+ if (Submodules.HasValue || Lfs.HasValue || FetchDepth.HasValue || Progress.HasValue ||
+ !Filter.IsNullOrWhiteSpace() || !Ref.IsNullOrWhiteSpace())
{
using (writer.Indent())
{
@@ -38,6 +48,8 @@ public override void Write(CustomFileWriter writer)
writer.WriteLine($"progress: {Progress.ToString().ToLowerInvariant()}");
if (!Filter.IsNullOrWhiteSpace())
writer.WriteLine($"filter: {Filter}");
+ if (!Ref.IsNullOrWhiteSpace())
+ writer.WriteLine($"ref: {Ref}");
}
}
}
diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
index aac79378f..195ed005a 100644
--- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
+++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
@@ -29,6 +29,7 @@ public class GitHubActionsAttribute : ConfigurationAttributeBase
private uint? _fetchDepth;
private bool? _progress;
private string _filter;
+ private string _ref;
public GitHubActionsAttribute(
string name,
@@ -117,6 +118,17 @@ public string Filter
get => throw new NotSupportedException();
}
+ ///
+ /// 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).
+ ///
+ public string CheckoutRef
+ {
+ set => _ref = value;
+ get => throw new NotSupportedException();
+ }
+
public override CustomFileWriter CreateWriter(StreamWriter streamWriter)
{
return new CustomFileWriter(streamWriter, indentationFactor: 2, commentPrefix: "#");
@@ -167,7 +179,8 @@ private IEnumerable GetSteps(GitHubActionsImage image, IReadO
Lfs = _lfs,
FetchDepth = _fetchDepth,
Progress = _progress,
- Filter = _filter
+ Filter = _filter,
+ Ref = _ref
};
if (CacheKeyFiles.Any())