diff --git a/docs/input/docs/reference/configuration.md b/docs/input/docs/reference/configuration.md index f586f821a1..a409df22e7 100644 --- a/docs/input/docs/reference/configuration.md +++ b/docs/input/docs/reference/configuration.md @@ -552,6 +552,11 @@ Example of invalid `Strict`, but valid `Loose` 1.2.3.4 ``` +### handle-detached-branch + +Avoid to strickly request to refer to a branch. If detached, the `no-branch` +reference will be used. + [1145]: https://github.com/GitTools/GitVersion/issues/1145 [1366]: https://github.com/GitTools/GitVersion/issues/1366 [2506]: https://github.com/GitTools/GitVersion/pull/2506#issuecomment-754754037 diff --git a/src/GitVersion.Core.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt b/src/GitVersion.Core.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt index f141ccc585..7d9e1cf636 100644 --- a/src/GitVersion.Core.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt +++ b/src/GitVersion.Core.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt @@ -109,3 +109,4 @@ increment: Inherit commit-date-format: yyyy-MM-dd merge-message-formats: {} update-build-number: true +handle-detached-branch: false diff --git a/src/GitVersion.Core.Tests/Helpers/GitFlowConfigurationBuilder.cs b/src/GitVersion.Core.Tests/Helpers/GitFlowConfigurationBuilder.cs index 09028e451b..b9ec8d9784 100644 --- a/src/GitVersion.Core.Tests/Helpers/GitFlowConfigurationBuilder.cs +++ b/src/GitVersion.Core.Tests/Helpers/GitFlowConfigurationBuilder.cs @@ -24,6 +24,7 @@ private GitFlowConfigurationBuilder() CommitDateFormat = "yyyy-MM-dd", UpdateBuildNumber = true, SemanticVersionFormat = SemanticVersionFormat.Strict, + HandleDetachedBranch = false, TagPreReleaseWeight = 60000, Increment = IncrementStrategy.Inherit }); diff --git a/src/GitVersion.Core.Tests/Helpers/GitHubFlowConfigurationBuilder.cs b/src/GitVersion.Core.Tests/Helpers/GitHubFlowConfigurationBuilder.cs index 49de2d5e6c..36f8e08487 100644 --- a/src/GitVersion.Core.Tests/Helpers/GitHubFlowConfigurationBuilder.cs +++ b/src/GitVersion.Core.Tests/Helpers/GitHubFlowConfigurationBuilder.cs @@ -24,6 +24,7 @@ private GitHubFlowConfigurationBuilder() CommitDateFormat = "yyyy-MM-dd", UpdateBuildNumber = true, SemanticVersionFormat = SemanticVersionFormat.Strict, + HandleDetachedBranch = false, TagPreReleaseWeight = 60000, Increment = IncrementStrategy.Inherit }); diff --git a/src/GitVersion.Core.Tests/Helpers/TestConfigurationBuilderBase.cs b/src/GitVersion.Core.Tests/Helpers/TestConfigurationBuilderBase.cs index 76ed28b0e2..0bf0776324 100644 --- a/src/GitVersion.Core.Tests/Helpers/TestConfigurationBuilderBase.cs +++ b/src/GitVersion.Core.Tests/Helpers/TestConfigurationBuilderBase.cs @@ -27,6 +27,7 @@ internal abstract class TestConfigurationBuilderBase private string? commitDateFormat; private bool? updateBuildNumber; private SemanticVersionFormat semanticVersionFormat = SemanticVersionFormat.Strict; + private bool? handleDetachedBranch; private Dictionary? mergeMessageFormats; protected readonly Dictionary branchConfigurationBuilders = new(); @@ -164,6 +165,12 @@ public virtual TConfigurationBuilder WithSemanticVersionFormat(SemanticVersionFo return (TConfigurationBuilder)this; } + public virtual TConfigurationBuilder WithHandleDetachedBranch(bool? value) + { + this.handleDetachedBranch = value; + return (TConfigurationBuilder)this; + } + public virtual TConfigurationBuilder WithMergeMessageFormats(Dictionary value) { this.mergeMessageFormats = value; @@ -214,6 +221,7 @@ public virtual TConfigurationBuilder WithConfiguration(GitVersionConfiguration v WithCommitDateFormat(value.CommitDateFormat); WithUpdateBuildNumber(value.UpdateBuildNumber); WithSemanticVersionFormat(value.SemanticVersionFormat); + WithHandleDetachedBranch(value.HandleDetachedBranch); WithMergeMessageFormats(value.MergeMessageFormats); foreach (var (name, branchConfiguration) in value.Branches) { @@ -246,6 +254,7 @@ public virtual GitVersionConfiguration Build() CommitDateFormat = this.commitDateFormat, UpdateBuildNumber = this.updateBuildNumber, SemanticVersionFormat = this.semanticVersionFormat, + HandleDetachedBranch = this.handleDetachedBranch, MergeMessageFormats = this.mergeMessageFormats ?? new() }; Dictionary branches = new(); diff --git a/src/GitVersion.Core.Tests/IntegrationTests/RemoteRepositoryScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/RemoteRepositoryScenarios.cs index 43e4a6ab77..6d4f7e0bd5 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/RemoteRepositoryScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/RemoteRepositoryScenarios.cs @@ -1,5 +1,6 @@ using GitTools.Testing; using GitVersion.BuildAgents; +using GitVersion.Configuration; using GitVersion.Core.Tests.Helpers; using LibGit2Sharp; using Microsoft.Extensions.DependencyInjection; @@ -98,6 +99,22 @@ public void GivenARemoteGitRepositoryWhenCheckingOutDetachedHeadUsingExistingImp $"It looks like the branch being examined is a detached Head pointing to commit '{fixture.LocalRepositoryFixture.Repository.Head.Tip.Id.ToString(7)}'. Without a proper branch name GitVersion cannot determine the build version."); } + [Test] + [TestCase(true)] + [TestCase(false)] + public void GivenARemoteGitRepositoryWhenCheckingOutDetachedHeadUsingExistingImplementationHandleDetachedBranch(bool handleDetachedBranch) + { + using var fixture = new RemoteRepositoryFixture(); + fixture.LocalRepositoryFixture.Checkout(fixture.LocalRepositoryFixture.Repository.Head.Tip.Sha); + + var configuration = new GitVersionConfiguration { HandleDetachedBranch = handleDetachedBranch }; + if (handleDetachedBranch) + fixture.AssertFullSemver("0.0.1--no-branch-.1+5", configuration: configuration, repository: fixture.LocalRepositoryFixture.Repository, onlyTrackedBranches: false); + else + Should.Throw(() => fixture.AssertFullSemver("0.1.0+4", repository: fixture.LocalRepositoryFixture.Repository, onlyTrackedBranches: false), + $"It looks like the branch being examined is a detached Head pointing to commit '{fixture.LocalRepositoryFixture.Repository.Head.Tip.Id.ToString(7)}'. Without a proper branch name GitVersion cannot determine the build version."); + } + [Test] [Ignore("Needs more investigations.")] public void GivenARemoteGitRepositoryWhenCheckingOutDetachedHeadUsingTrackingBranchOnlyBehaviourShouldReturnVersion014Plus5() diff --git a/src/GitVersion.Core/Configuration/ConfigurationBuilder.cs b/src/GitVersion.Core/Configuration/ConfigurationBuilder.cs index 333b570c9d..14e49c4421 100644 --- a/src/GitVersion.Core/Configuration/ConfigurationBuilder.cs +++ b/src/GitVersion.Core/Configuration/ConfigurationBuilder.cs @@ -54,6 +54,7 @@ private static void ApplyOverrides(GitVersionConfiguration targetConfig, GitVers targetConfig.MergeMessageFormats = overrideConfiguration.MergeMessageFormats.Any() ? overrideConfiguration.MergeMessageFormats : targetConfig.MergeMessageFormats; targetConfig.UpdateBuildNumber = overrideConfiguration.UpdateBuildNumber ?? targetConfig.UpdateBuildNumber; targetConfig.SemanticVersionFormat = overrideConfiguration.SemanticVersionFormat; + targetConfig.HandleDetachedBranch = overrideConfiguration.HandleDetachedBranch ?? targetConfig.HandleDetachedBranch; if (overrideConfiguration.Ignore is { IsEmpty: false }) { @@ -187,6 +188,7 @@ private static GitVersionConfiguration CreateDefaultConfiguration() CommitDateFormat = "yyyy-MM-dd", UpdateBuildNumber = true, SemanticVersionFormat = SemanticVersionFormat.Strict, + HandleDetachedBranch = false, TagPreReleaseWeight = DefaultTagPreReleaseWeight, Increment = IncrementStrategy.Inherit }; diff --git a/src/GitVersion.Core/Configuration/GitVersionConfiguration.cs b/src/GitVersion.Core/Configuration/GitVersionConfiguration.cs index 497a366258..0e99b04f99 100644 --- a/src/GitVersion.Core/Configuration/GitVersionConfiguration.cs +++ b/src/GitVersion.Core/Configuration/GitVersionConfiguration.cs @@ -88,6 +88,9 @@ public string? NextVersion [YamlMember(Alias = "semver-format")] public SemanticVersionFormat SemanticVersionFormat { get; set; } = SemanticVersionFormat.Strict; + [YamlMember(Alias = "handle-detached-branch")] + public bool? HandleDetachedBranch { get; set; } + public override string ToString() { var stringBuilder = new StringBuilder(); diff --git a/src/GitVersion.Core/Core/Abstractions/IRepositoryStore.cs b/src/GitVersion.Core/Core/Abstractions/IRepositoryStore.cs index 6f524517b2..f721f96b18 100644 --- a/src/GitVersion.Core/Core/Abstractions/IRepositoryStore.cs +++ b/src/GitVersion.Core/Core/Abstractions/IRepositoryStore.cs @@ -38,7 +38,7 @@ public interface IRepositoryStore IEnumerable GetSourceBranches(IBranch branch, GitVersionConfiguration configuration, IEnumerable excludedBranches); - SemanticVersion? GetCurrentCommitTaggedVersion(ICommit? commit, string? tagPrefix); + SemanticVersion? GetCurrentCommitTaggedVersion(ICommit? commit, GitVersionConfiguration configuration); IEnumerable GetVersionTagsOnBranch(IBranch branch, string? tagPrefixRegex); IEnumerable<(ITag Tag, SemanticVersion Semver, ICommit Commit)> GetValidVersionTags(string? tagPrefixRegex, DateTimeOffset? olderThan = null); diff --git a/src/GitVersion.Core/Core/GitVersionContextFactory.cs b/src/GitVersion.Core/Core/GitVersionContextFactory.cs index 56b717b9ac..069f3ed23e 100644 --- a/src/GitVersion.Core/Core/GitVersionContextFactory.cs +++ b/src/GitVersion.Core/Core/GitVersionContextFactory.cs @@ -33,7 +33,7 @@ public GitVersionContext Create(GitVersionOptions gitVersionOptions) currentBranch = branchForCommit ?? currentBranch; } - var currentCommitTaggedVersion = this.repositoryStore.GetCurrentCommitTaggedVersion(currentCommit, configuration.TagPrefix); + var currentCommitTaggedVersion = this.repositoryStore.GetCurrentCommitTaggedVersion(currentCommit, configuration); var numberOfUncommittedChanges = this.repositoryStore.GetNumberOfUncommittedChanges(); return new GitVersionContext(currentBranch, currentCommit, configuration, currentCommitTaggedVersion, numberOfUncommittedChanges); diff --git a/src/GitVersion.Core/Core/RepositoryStore.cs b/src/GitVersion.Core/Core/RepositoryStore.cs index b8899a5308..cafd0b4042 100644 --- a/src/GitVersion.Core/Core/RepositoryStore.cs +++ b/src/GitVersion.Core/Core/RepositoryStore.cs @@ -146,17 +146,16 @@ public IEnumerable GetSourceBranches(IBranch branch, GitVersionConfigur var referenceLookup = this.repository.Refs.ToLookup(r => r.TargetIdentifier); var returnedBranches = new HashSet(); - if (referenceLookup.Any()) + if (!referenceLookup.Any()) + yield break; + foreach (var branchCommit in FindCommitBranchesWasBranchedFrom(branch, configuration, excludedBranches)) { - foreach (var branchCommit in FindCommitBranchesWasBranchedFrom(branch, configuration, excludedBranches)) + foreach (var _ in referenceLookup[branchCommit.Commit.Sha] + .Where(r => r.Name.Friendly == branchCommit.Branch.Name.Friendly)) { - foreach (var _ in referenceLookup[branchCommit.Commit.Sha] - .Where(r => r.Name.Friendly == branchCommit.Branch.Name.Friendly)) + if (returnedBranches.Add(branchCommit.Branch)) { - if (returnedBranches.Add(branchCommit.Branch)) - { - yield return branchCommit.Branch; - } + yield return branchCommit.Branch; } } } @@ -219,9 +218,9 @@ public IEnumerable FindCommitBranchesWasBranchedFrom(IBranch branc } } - public SemanticVersion? GetCurrentCommitTaggedVersion(ICommit? commit, string? tagPrefix) + public SemanticVersion? GetCurrentCommitTaggedVersion(ICommit? commit, GitVersionConfiguration configuration) => this.repository.Tags - .SelectMany(t => GetCurrentCommitSemanticVersions(commit, tagPrefix, t)) + .SelectMany(t => GetCurrentCommitSemanticVersions(commit, configuration.TagPrefix, t, configuration.HandleDetachedBranch ?? false)) .Max(); public IEnumerable GetVersionTagsOnBranch(IBranch branch, string? tagPrefixRegex) @@ -290,12 +289,19 @@ public bool IsCommitOnBranch(ICommit? baseVersionSource, IBranch branch, ICommit private static bool IsReleaseBranch(INamedReference branch, IEnumerable> releaseBranchConfig) => releaseBranchConfig.Any(c => c.Value?.Regex != null && Regex.IsMatch(branch.Name.Friendly, c.Value.Regex)); - private static IEnumerable GetCurrentCommitSemanticVersions(ICommit? commit, string? tagPrefix, ITag tag) + private IEnumerable GetCurrentCommitSemanticVersions(ICommit? commit, string? tagPrefix, ITag tag, bool handleDetachedBranch) { + if (commit == null) + return Array.Empty(); + var targetCommit = tag.PeeledTargetCommit(); + if (targetCommit == null) + return Array.Empty(); + + var commitToCompare = handleDetachedBranch ? FindMergeBase(commit, targetCommit) : commit; var tagName = tag.Name.Friendly; - return targetCommit != null && Equals(targetCommit, commit) && SemanticVersion.TryParse(tagName, tagPrefix, out var version) + return Equals(targetCommit, commitToCompare) && SemanticVersion.TryParse(tagName, tagPrefix, out var version) ? new[] { version } : Array.Empty(); } diff --git a/src/GitVersion.Core/VersionCalculation/NextVersionCalculator.cs b/src/GitVersion.Core/VersionCalculation/NextVersionCalculator.cs index ba126d2e1c..6394fe111b 100644 --- a/src/GitVersion.Core/VersionCalculation/NextVersionCalculator.cs +++ b/src/GitVersion.Core/VersionCalculation/NextVersionCalculator.cs @@ -133,7 +133,7 @@ private void UpdatePreReleaseTag(EffectiveBranchConfiguration configuration, Sem private static void EnsureHeadIsNotDetached(GitVersionContext context) { - if (context.CurrentBranch.IsDetachedHead != true) + if (context.CurrentBranch.IsDetachedHead != true || (context.Configuration.HandleDetachedBranch ?? false)) { return; }