diff --git a/src/GitVersion.Core.Tests/IntegrationTests/OtherScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/OtherScenarios.cs index 306ae3def6..6ccb0c86b2 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/OtherScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/OtherScenarios.cs @@ -1218,4 +1218,87 @@ public void EnsurePreventIncrementWhenCurrentCommitTaggedOnReleaseBranchAndIncre fixture.AssertFullSemver(semVersion, configuration); } + + [Test] + public void EnsureVersionAfterMainIsMergedBackToDevelopIsCorrect() + { + using EmptyRepositoryFixture fixture = new("main"); + + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit("A"); + + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-alpha.1"); + + fixture.Checkout("main"); + fixture.MakeACommit("B"); + fixture.BranchTo("hotfix/just-a-hotfix"); + fixture.MakeACommit("C +semver: major"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.Checkout("develop"); + fixture.MakeACommit("D"); + fixture.Checkout("main"); + fixture.MakeACommit("E"); + fixture.ApplyTag("1.0.1"); + fixture.Checkout("develop"); + fixture.MergeNoFF("main"); + + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-alpha.7"); + } + + [TestCase(false, "2.0.0-alpha.3")] + [TestCase(true, "2.0.0-alpha.2")] + public void EnsureVersionAfterMainIsMergedBackToDevelopIsCorrectForTrunkBased(bool applyTag, string semanticVersion) + { + var configuration = GitFlowConfigurationBuilder.New + .WithVersionStrategy(VersionStrategies.TrunkBased) + .Build(); + + using EmptyRepositoryFixture fixture = new("main"); + + fixture.MakeACommit("A"); + fixture.ApplyTag("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit("B +semver: major"); + + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.0-alpha.1", configuration); + + fixture.Checkout("main"); + fixture.MakeACommit("C"); + if (applyTag) fixture.ApplyTag("1.0.1"); + fixture.Checkout("develop"); + fixture.MergeNoFF("main"); + + // ✅ succeeds as expected + fixture.AssertFullSemver(semanticVersion, configuration); + } + + [TestCase(false, "2.0.0-alpha.3")] + [TestCase(true, "2.0.0-alpha.3")] + public void EnsureVersionAfterMainIsMergedBackToDevelopIsCorrectForGitFlow(bool applyTag, string semanticVersion) + { + var configuration = GitFlowConfigurationBuilder.New.Build(); + + using EmptyRepositoryFixture fixture = new("main"); + + fixture.MakeACommit("A"); + fixture.ApplyTag("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit("B +semver: major"); + + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.0-alpha.1", configuration); + + fixture.Checkout("main"); + fixture.MakeACommit("C"); + if (applyTag) fixture.ApplyTag("1.0.1"); + fixture.Checkout("develop"); + fixture.MergeNoFF("main"); + + // ✅ succeeds as expected + fixture.AssertFullSemver(semanticVersion, configuration); + } } diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index e3224c1785..b0e70de7ee 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -6,7 +6,7 @@ namespace GitVersion.VersionCalculation; -internal class IncrementStrategyFinder : IIncrementStrategyFinder +internal sealed class IncrementStrategyFinder : IIncrementStrategyFinder { public const string DefaultMajorPattern = @"\+semver:\s?(breaking|major)"; public const string DefaultMinorPattern = @"\+semver:\s?(feature|minor)"; @@ -28,7 +28,6 @@ internal class IncrementStrategyFinder : IIncrementStrategyFinder public IncrementStrategyFinder(IGitRepository repository, ITaggedSemanticVersionRepository taggedSemanticVersionRepository) { - this.repository = repository.NotNull(); this.repository = repository.NotNull(); this.taggedSemanticVersionRepository = taggedSemanticVersionRepository; } @@ -81,26 +80,21 @@ public VersionField DetermineIncrementedField( } private VersionField? FindCommitMessageIncrement( - EffectiveConfiguration configuration, ICommit? baseCommit, ICommit? currentCommit, string? label) + EffectiveConfiguration configuration, ICommit? baseVersionSource, ICommit currentCommit, string? label) { if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.Disabled) { return null; } - //get tags with valid version - depends on configuration (see #3757) - var targetShas = new Lazy>(() => - this.taggedSemanticVersionRepository.GetTaggedSemanticVersions(configuration.TagPrefix, configuration.SemanticVersionFormat) - .SelectMany(_ => _).Where(_ => _.Value.IsMatchForBranchSpecificLabel(label)).Select(_ => _.Tag.TargetSha).ToHashSet() + IEnumerable commits = GetCommitHistory( + tagPrefix: configuration.TagPrefix, + semanticVersionFormat: configuration.SemanticVersionFormat, + baseVersionSource: baseVersionSource, + currentCommit: currentCommit, + label: label ); - var commits = GetIntermediateCommits(baseCommit, currentCommit); - // consider commit messages since latest tag only (see #3071) - commits = commits - .Reverse() - .TakeWhile(x => !targetShas.Value.Contains(x.Sha)) - .Reverse(); - if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.MergeMessageOnly) { commits = commits.Where(c => c.Parents.Count() > 1); @@ -120,6 +114,41 @@ private static Regex TryGetRegexOrDefault(string? messageRegex, Regex defaultReg ? defaultRegex : CompiledRegexCache.GetOrAdd(messageRegex, pattern => new(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase)); + public IReadOnlyCollection GetCommitHistory( + string? tagPrefix, SemanticVersionFormat semanticVersionFormat, ICommit? baseVersionSource, ICommit currentCommit, string? label) + { + var targetShas = new Lazy>(() => + this.taggedSemanticVersionRepository.GetTaggedSemanticVersions(tagPrefix, semanticVersionFormat) + .SelectMany(_ => _).Where(_ => _.Value.IsMatchForBranchSpecificLabel(label)).Select(_ => _.Tag.TargetSha).ToHashSet() + ); + + var intermediateCommits = GetIntermediateCommits(baseVersionSource, currentCommit).ToArray(); + + var commitLog = intermediateCommits.ToDictionary(element => element.Id.Sha); + + foreach (var item in intermediateCommits.Reverse()) + { + if (!commitLog.ContainsKey(item.Sha)) continue; + + if (targetShas.Value.Contains(item.Sha)) + { + void RemoveCommitFromHistory(ICommit commit) + { + if (!commitLog.ContainsKey(commit.Sha)) return; + + commitLog.Remove(commit.Sha); + foreach (var item in commit.Parents) + { + RemoveCommitFromHistory(item); + } + } + RemoveCommitFromHistory(item); + } + } + + return commitLog.Values; + } + /// /// Get the sequence of commits in a repository between a (exclusive) /// and a particular (inclusive) @@ -155,7 +184,7 @@ private Dictionary GetHeadCommitsMap(ICommit? headCommit) => /// private ICommit[] GetHeadCommits(ICommit? headCommit) => this.headCommitsCache.GetOrAdd(headCommit?.Sha ?? "NULL", () => - GetCommitsReacheableFromHead(repository, headCommit).ToArray()); + GetCommitsReacheableFromHead(headCommit).ToArray()); private VersionField? GetIncrementFromCommit(ICommit commit, Regex majorRegex, Regex minorRegex, Regex patchRegex, Regex none) => this.commitIncrementCache.GetOrAdd(commit.Sha, () => @@ -170,11 +199,7 @@ private ICommit[] GetHeadCommits(ICommit? headCommit) => return null; } - /// - /// Query a for the sequence of commits from the beginning to a particular - /// (inclusive) - /// - private static IEnumerable GetCommitsReacheableFromHead(IGitRepository repo, ICommit? headCommit) + private IEnumerable GetCommitsReacheableFromHead(ICommit? headCommit) { var filter = new CommitFilter { @@ -182,7 +207,7 @@ private static IEnumerable GetCommitsReacheableFromHead(IGitRepository SortBy = CommitSortStrategies.Topological | CommitSortStrategies.Reverse }; - return repo.Commits.QueryBy(filter); + return repository.Commits.QueryBy(filter); } public IEnumerable GetMergedCommits(ICommit mergeCommit, int index) @@ -208,7 +233,7 @@ private static ICommit GetMergedHead(ICommit mergeCommit) { var parents = mergeCommit.Parents.Skip(1).ToList(); if (parents.Count > 1) - throw new NotSupportedException("Mainline development does not support more than one merge source in a single commit yet"); + throw new NotSupportedException("GitVersion does not support more than one merge source in a single commit yet"); return parents.Single(); }