Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/GitVersion.BuildAgents.Tests/Agents/GitHubActionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ public void GetCurrentBranchShouldHandlePullRequests()
result.ShouldBe("refs/pull/1/merge");
}

[TestCase("tag", "refs/tags/1.0.0", "refs/tags/1.0.0")]
[TestCase("TAG", "refs/tags/1.0.0", "refs/tags/1.0.0")]
[TestCase("branch", "refs/heads/main", null)]
[TestCase("branch", "refs/pull/1/merge", null)]
[TestCase(null, "refs/tags/1.0.0", null)]
[TestCase("tag", null, null)]
[TestCase("tag", "refs/heads/main", null)]
public void GetCurrentTagOnlyReturnsAnExplicitTagReference(string? refType, string? reference, string? expected)
{
this.environment.SetEnvironmentVariable("GITHUB_REF_TYPE", refType);
this.environment.SetEnvironmentVariable("GITHUB_REF", reference);

this.buildServer.GetCurrentTag().ShouldBe(expected);
}

[Test]
public void ShouldSetOutputVariables()
{
Expand Down
11 changes: 11 additions & 0 deletions src/GitVersion.BuildAgents/Agents/GitHubActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,15 @@ public override void WriteIntegration(Action<string?> writer, GitVersionVariable
}

public override bool PreventFetch() => true;

public override string? GetCurrentTag()
{
var refType = this.environment.GetEnvironmentVariable("GITHUB_REF_TYPE");
var reference = this.environment.GetEnvironmentVariable("GITHUB_REF");
return string.Equals(refType, "tag", StringComparison.OrdinalIgnoreCase)
&& reference != null
&& reference.StartsWith("refs/tags/", StringComparison.Ordinal)
? reference
: null;
Comment on lines +65 to +69

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense to use the GitVersion.Git.Tag value object for this parsing and validation?

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,170 @@ namespace GitVersion.Tests.IntegrationTests;
[TestFixture]
public class RemoteRepositoryScenarios : TestBase
{
[TestCase(false, false)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(true, true)]
[TestCase(false, false, "tree", false)]
[TestCase(true, false, "tree", false)]
[TestCase(false, false, "blob", false)]
[TestCase(true, false, "blob", false)]
[TestCase(false, false, "tree", true)]
[TestCase(true, false, "tree", true)]
[TestCase(false, false, "blob", true)]
[TestCase(true, false, "blob", true)]
public void TaggedHistoricalCommitNormalizesWithoutAccessingRemote(bool annotated, bool multipleTags, string? metadataTarget = null, bool packed = false)
{
using var fixture = new RemoteRepositoryFixture(path =>
{
Repository.Init(path);
var repository = new Repository(path);
repository.MakeACommit();
if (annotated)
{
repository.ApplyTag("1.2.3", repository.Head.Tip.Author, "Release");
}
else
{
repository.ApplyTag("1.2.3");
}
if (multipleTags)
{
repository.ApplyTag("release-alias");
}
repository.MakeACommit();
repository.CreateBranch("feature/normalized");
return repository;
});
var localRepository = fixture.LocalRepositoryFixture.Repository;
if (metadataTarget != null)
{
using var content = new MemoryStream([1, 2, 3]);
GitObject target = metadataTarget == "tree"
? localRepository.Head.Tip.Tree
: localRepository.ObjectDatabase.CreateBlob(content);
// Sort before the version tag so that the lookup must inspect this tag first.
if (annotated)
{
localRepository.ApplyTag("!metadata", target.Sha, localRepository.Head.Tip.Author, "Metadata");
}
else
{
localRepository.ApplyTag("!metadata", target.Sha);
}
}
if (packed)
{
GitTestExtensions.ExecuteGitCmd($"-C \"{fixture.LocalRepositoryFixture.RepositoryPath}\" pack-refs --all", ".");
}
var taggedCommit = (Commit)localRepository.Tags["1.2.3"].PeeledTarget;
localRepository.Branches["feature/normalized"].ShouldBeNull();
Commands.Checkout(localRepository, taggedCommit);
localRepository.Network.Remotes.Update("origin", remote =>
remote.Url = Path.Combine(fixture.LocalRepositoryFixture.RepositoryPath, "missing-remote"));

var options = Options.Create(new GitVersionOptions
{
WorkingDirectory = fixture.LocalRepositoryFixture.RepositoryPath,
Settings = { NoNormalize = false, NoFetch = true }
});
var environment = new TestEnvironment();
environment.SetEnvironmentVariable(GitHubActions.EnvironmentVariableName, "true");
environment.SetEnvironmentVariable("GITHUB_REF_TYPE", "tag");
environment.SetEnvironmentVariable("GITHUB_REF", "refs/tags/1.2.3");
var sp = ConfigureServices(services =>
{
services.AddSingleton(options);
services.AddSingleton<IEnvironment>(environment);
});
sp.DiscoverRepository();

sp.GetRequiredService<IGitPreparer>().Prepare();

localRepository.Head.Tip.Sha.ShouldBe(taggedCommit.Sha);
localRepository.Info.IsHeadDetached.ShouldBeTrue();
localRepository.Branches["feature/normalized"].Tip.Sha.ShouldBe(localRepository.Branches["origin/feature/normalized"].Tip.Sha);
fixture.AssertFullSemver("1.2.3", repository: localRepository);
}

[TestCase("refs/pull/42/merge", 0, "pull/42/merge", "1.2.4-PullRequest42.0")]
[TestCase(null, 1, "release/1.2.3", "1.3.0-beta.1+0")]
[TestCase(null, 2, "main", "1.2.3")]
public void TaggedCommitNormalizationPreservesBranchSelection(string? currentBranch, int localBranchCount, string expectedBranch, string expectedVersion)
{
using var fixture = new RemoteRepositoryFixture();
var repository = fixture.LocalRepositoryFixture.Repository;
var commit = repository.Head.Tip;
repository.ApplyTag("1.2.3");
repository.MakeACommit();
Commands.Checkout(repository, commit);
// Avoid updating main back to the tagged commit from its remote tracking ref.
repository.Refs.Remove("refs/remotes/origin/main");
if (localBranchCount > 0)
{
repository.CreateBranch("release/1.2.3", commit);
}
if (localBranchCount > 1)
{
repository.Refs.UpdateTarget(repository.Refs["refs/heads/main"], commit.Id);
}
repository.Network.Remotes.Update("origin", remote =>
remote.Url = Path.Combine(fixture.LocalRepositoryFixture.RepositoryPath, "missing-remote"));

PrepareOnGitHubActions(fixture.LocalRepositoryFixture.RepositoryPath, currentBranch);

repository.Head.Tip.Sha.ShouldBe(commit.Sha);
repository.Info.IsHeadDetached.ShouldBeFalse();
repository.Head.FriendlyName.ShouldBe(expectedBranch);
fixture.AssertFullSemver(expectedVersion, repository: repository);
}

[TestCase(null)]
[TestCase("refs/tags/missing")]
[TestCase("refs/tags/1.2.3")]
public void HistoricalCommitWithoutMatchingBuildTagStillDiscoversPullRequest(string? currentTag)
{
using var fixture = new RemoteRepositoryFixture(path =>
{
Repository.Init(path);
var repository = new Repository(path);
repository.MakeACommit();
repository.Refs.Add("refs/pull/42/merge", repository.Head.Tip.Id);
repository.MakeATaggedCommit("1.2.3");
return repository;
});
var localRepository = fixture.LocalRepositoryFixture.Repository;
var commit = localRepository.Head.Tip.Parents.Single();
Commands.Checkout(localRepository, commit);
localRepository.ApplyTag("local-only");

PrepareOnGitHubActions(fixture.LocalRepositoryFixture.RepositoryPath, null, currentTag);

localRepository.Head.Tip.Sha.ShouldBe(commit.Sha);
localRepository.Info.IsHeadDetached.ShouldBeFalse();
localRepository.Head.FriendlyName.ShouldBe("pull/42/merge");
}

private static void PrepareOnGitHubActions(string workingDirectory, string? currentBranch, string? currentTag = null)
{
var options = Options.Create(new GitVersionOptions
{
WorkingDirectory = workingDirectory,
Settings = { NoNormalize = false, NoFetch = true }
});
var environment = new TestEnvironment();
environment.SetEnvironmentVariable(GitHubActions.EnvironmentVariableName, "true");
environment.SetEnvironmentVariable("GITHUB_REF", currentTag ?? currentBranch);
environment.SetEnvironmentVariable("GITHUB_REF_TYPE", currentTag == null ? "branch" : "tag");
var sp = ConfigureServices(services =>
{
services.AddSingleton(options);
services.AddSingleton<IEnvironment>(environment);
});
sp.DiscoverRepository();
sp.GetRequiredService<IGitPreparer>().Prepare();
}

[Test]
public void GivenARemoteGitRepositoryWithCommitsThenClonedLocalShouldMatchRemoteVersion()
{
Expand Down
2 changes: 2 additions & 0 deletions src/GitVersion.Core/Agents/BuildAgentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ internal abstract class BuildAgentBase(IEnvironment environment, ILogger logger,

public virtual string? GetCurrentBranch(bool usingDynamicRepos) => null;

public virtual string? GetCurrentTag() => null;

public virtual bool IsDefault => false;
public virtual bool PreventFetch() => true;
public virtual bool ShouldCleanUpRemotes() => false;
Expand Down
3 changes: 3 additions & 0 deletions src/GitVersion.Core/Agents/IBuildAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public interface IBuildAgent
/// <summary>Returns the name of the current branch as reported by the build agent environment.</summary>
string? GetCurrentBranch(bool usingDynamicRepos);

/// <summary>Returns the canonical tag reference explicitly selected by the build agent environment, or null for other builds.</summary>
string? GetCurrentTag() => null;

/// <summary>Indicates whether fetching from the remote should be suppressed in this build agent environment.</summary>
bool PreventFetch();

Expand Down
10 changes: 10 additions & 0 deletions src/GitVersion.Core/Core/GitPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ private void EnsureHeadIsAttachedToBranch(string? currentBranchName, Authenticat
ChooseLocalBranchToAttach(headSha, localBranchesWhereCommitShaIsHead);
break;
case 0:
// Only the tag selected by the build identifies a tag checkout.
// Resolve that tag alone; unrelated local tags do not establish build context.
var currentTag = this.buildAgent.GetCurrentTag();
if (currentBranchName.IsNullOrEmpty() && currentTag != null
&& this.repository.Tags.FirstOrDefault(tag => tag.Name.Canonical == currentTag)?.Commit.Sha == headSha)
{
this.logger.LogInformation("HEAD points at the selected tag '{CurrentTag}' at '{HeadSha}'. Leaving HEAD detached.", currentTag, headSha);
break;
}
Comment thread
arturcic marked this conversation as resolved.

this.logger.LogInformation("No local branch pointing at the commit '{HeadSha}'. Fake branch needs to be created.", headSha);
this.retryAction.Execute(() => this.repository.CreateBranchForPullRequestBranch(authentication));
break;
Expand Down
1 change: 1 addition & 0 deletions src/GitVersion.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
GitVersion.Agents.IBuildAgent.GetCurrentTag() -> string?
GitVersion.ConfigurationMigrationInfo
GitVersion.ConfigurationMigrationInfo.ConfigurationMigrationInfo() -> void
GitVersion.ConfigurationMigrationInfo.Force.get -> bool
Expand Down
Loading