From d42d3d61bcdcccb7c885c54012bd405ad92adf76 Mon Sep 17 00:00:00 2001 From: Frode Date: Fri, 30 Oct 2020 11:08:48 +0100 Subject: [PATCH] Added strategy to handle versioning from merge of release branch to master, where version is picked up from branch name. --- .../VersionInMergedBranchNameScenarios.cs | 68 ++++++++++++++-- ...nInMergedReleaseBranchNameStrategyTests.cs | 79 +++++++++++++++++++ ...ersionInMergedReleaseBranchNameStrategy.cs | 76 ++++++++++++++++++ 3 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 src/GitVersionCore.Tests/VersionCalculation/Strategies/VersionInMergedReleaseBranchNameStrategyTests.cs create mode 100644 src/GitVersionCore/VersionCalculation/BaseVersionCalculators/VersionInMergedReleaseBranchNameStrategy.cs diff --git a/src/GitVersionCore.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs b/src/GitVersionCore.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs index 18a1416101..5abc0d23f8 100644 --- a/src/GitVersionCore.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs +++ b/src/GitVersionCore.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs @@ -1,9 +1,14 @@ using System.Collections.Generic; using System.Linq; + using GitTools.Testing; + using GitVersion.Model.Configuration; + using GitVersionCore.Tests.Helpers; + using LibGit2Sharp; + using NUnit.Framework; namespace GitVersionCore.Tests.IntegrationTests @@ -17,7 +22,16 @@ public void TakesVersionFromNameOfReleaseBranch() using var fixture = new BaseGitFlowRepositoryFixture("1.0.0"); fixture.CreateAndMergeBranchIntoDevelop("release/2.0.0"); - fixture.AssertFullSemver("2.1.0-alpha.2"); + fixture.AssertFullSemver("2.1.0-alpha.2"); // TODO : shouldn't commit count be 0 ? + } + + [Test] + public void TakesVersionFromNameOfReleaseBranchWhenMergedIntoMaster() + { + using var fixture = new BaseGitFlowRepositoryFixture("1.0.0"); + fixture.CreateAndMergeBranchIntoMaster("release/2.0.0"); + + fixture.AssertFullSemver("2.0.0+0"); } [Test] @@ -41,7 +55,26 @@ public void TakesVersionFromNameOfBranchThatIsReleaseByConfig() using var fixture = new BaseGitFlowRepositoryFixture("1.0.0"); fixture.CreateAndMergeBranchIntoDevelop("support/2.0.0"); - fixture.AssertFullSemver("2.1.0-alpha.2", config); + fixture.AssertFullSemver("2.1.0-alpha.2", config); // TODO : shouldn't commit count be 0 ? + } + + [Test] + public void TakesVersionFromNameOfBranchThatIsReleaseByConfigWhenMergedIntoMaster() + { + var config = new Config + { + Branches = new Dictionary { + { "hotfix", new BranchConfig { IsReleaseBranch = true } }, + // This will normally work because of increment rule in master, reconfigure for this test as we + // are testing versioning based on merged branch name. + { "master", new BranchConfig { Increment = GitVersion.IncrementStrategy.None } } + } + }; + + using var fixture = new BaseGitFlowRepositoryFixture("1.0.0"); + fixture.CreateAndMergeBranchIntoMaster("hotfix/1.0.1"); + + fixture.AssertFullSemver("1.0.1+0", config); } [Test] @@ -51,8 +84,9 @@ public void TakesVersionFromNameOfRemoteReleaseBranchInOrigin() fixture.BranchTo("release/2.0.0"); fixture.MakeACommit(); Commands.Fetch((Repository)fixture.LocalRepositoryFixture.Repository, fixture.LocalRepositoryFixture.Repository.Network.Remotes.First().Name, new string[0], new FetchOptions(), null); - - fixture.LocalRepositoryFixture.MergeNoFF("origin/release/2.0.0"); + // fixture.LocalRepositoryFixture.MergeNoFF("origin/release/2.0.0"); + // merge without default merge message, as that will invalidate these tests + fixture.LocalRepositoryFixture.Repository.MergeNoFF("origin/release/2.0.0", "a merge message"); fixture.LocalRepositoryFixture.AssertFullSemver("2.0.0+0"); } @@ -66,7 +100,9 @@ public void DoesNotTakeVersionFromNameOfRemoteReleaseBranchInCustomRemote() fixture.MakeACommit(); Commands.Fetch((Repository)fixture.LocalRepositoryFixture.Repository, fixture.LocalRepositoryFixture.Repository.Network.Remotes.First().Name, new string[0], new FetchOptions(), null); - fixture.LocalRepositoryFixture.MergeNoFF("upstream/release/2.0.0"); + // fixture.LocalRepositoryFixture.MergeNoFF("upstream/release/2.0.0"); + // merge without default merge message, as that will invalidate these tests + fixture.LocalRepositoryFixture.Repository.MergeNoFF("upstream/release/2.0.0", "a merge message"); fixture.LocalRepositoryFixture.AssertFullSemver("0.1.0+6"); } @@ -75,11 +111,27 @@ public void DoesNotTakeVersionFromNameOfRemoteReleaseBranchInCustomRemote() internal static class BaseGitFlowRepositoryFixtureExtensions { public static void CreateAndMergeBranchIntoDevelop(this BaseGitFlowRepositoryFixture fixture, string branchName) + => CreateAndMergeBranch(fixture, branchName, "develop"); + + public static void CreateAndMergeBranchIntoMaster(this BaseGitFlowRepositoryFixture fixture, string branchName) + => CreateAndMergeBranch(fixture, branchName, "master"); + + public static void CreateAndMergeBranch(this BaseGitFlowRepositoryFixture fixture, string sourceBranchName, string targetBranchName) { - fixture.BranchTo(branchName); + fixture.BranchTo(sourceBranchName); fixture.MakeACommit(); - fixture.Checkout("develop"); - fixture.MergeNoFF(branchName); + fixture.Checkout(targetBranchName); + // merge without default merge message, as that will invalidate these tests + fixture.Repository.MergeNoFF(sourceBranchName, "a merge message"); + } + } + + internal static class IRepositoryExtensions + { + public static void MergeNoFF(this IRepository repository, string branchName, string message) + { + repository.Merge(branchName, Generate.SignatureNow(), new MergeOptions { CommitOnSuccess = false, FastForwardStrategy = FastForwardStrategy.NoFastForward }); + repository.Commit(message, Generate.SignatureNow(), Generate.SignatureNow()); } } } diff --git a/src/GitVersionCore.Tests/VersionCalculation/Strategies/VersionInMergedReleaseBranchNameStrategyTests.cs b/src/GitVersionCore.Tests/VersionCalculation/Strategies/VersionInMergedReleaseBranchNameStrategyTests.cs new file mode 100644 index 0000000000..da0e1cc0d8 --- /dev/null +++ b/src/GitVersionCore.Tests/VersionCalculation/Strategies/VersionInMergedReleaseBranchNameStrategyTests.cs @@ -0,0 +1,79 @@ +using System.Linq; + +using GitTools.Testing; + +using GitVersion.Configuration; +using GitVersion.Extensions; +using GitVersion.Model.Configuration; +using GitVersion.VersionCalculation; + +using GitVersionCore.Tests.Helpers; + +using LibGit2Sharp; + +using NUnit.Framework; + +using Shouldly; + +namespace GitVersionCore.Tests.VersionCalculation.Strategies +{ + [TestFixture] + public class VersionInMergedReleaseBranchNameStrategyTests : TestBase + { + [TestCase("release-2.0.0", "2.0.0")] + [TestCase("release/3.0.0", "3.0.0")] + public void CanTakeVersionFromNameOfReleaseBranch(string branchName, string expectedBaseVersion) + { + string mergedToBranchName = "master"; + using var fixture = new EmptyRepositoryFixture(); + fixture.MakeACommit(); + fixture.BranchTo(branchName); + fixture.MakeACommit(); + fixture.Checkout(mergedToBranchName); + fixture.Repository.MergeNoFF(branchName, "a merge message"); + + var strategy = GetVersionStrategy(fixture.RepositoryPath, fixture.Repository, mergedToBranchName); + var baseVersion = strategy.GetVersions().Single(); + baseVersion.ShouldNotBeNull(); + baseVersion.SemanticVersion.ToString().ShouldBe(expectedBaseVersion); + } + + [TestCase("hotfix/2.0.1", "2.0.1")] + [TestCase("hotfix-3.0.1", "3.0.1")] + public void CanTakeVersionFromNameOfConfiguredReleaseBranch(string branchName, string expectedBaseVersion) + { + string mergedToBranchName = "master"; + using var fixture = new EmptyRepositoryFixture(); + fixture.MakeACommit(); + fixture.BranchTo(branchName); + fixture.MakeACommit(); + fixture.Checkout(mergedToBranchName); + fixture.Repository.MergeNoFF(branchName, "a merge message"); + + var config = new ConfigurationBuilder() + .Add(new Config { Branches = { { "hotfix", new BranchConfig { IsReleaseBranch = true } } } }) + .Build(); + + var strategy = GetVersionStrategy(fixture.RepositoryPath, fixture.Repository, mergedToBranchName, config); + + var baseVersion = strategy.GetVersions().Single(); + + baseVersion.SemanticVersion.ToString().ShouldBe(expectedBaseVersion); + } + + private static IVersionStrategy GetVersionStrategy(string workingDirectory, IRepository repository, string branch, Config config = null) + { + var sp = BuildServiceProvider(workingDirectory, repository, branch, config); + return sp.GetServiceForType(); + } + } + + internal static class IRepositoryExtensions + { + public static void MergeNoFF(this IRepository repository, string branchName, string message) + { + repository.Merge(branchName, Generate.SignatureNow(), new MergeOptions { CommitOnSuccess = false, FastForwardStrategy = FastForwardStrategy.NoFastForward }); + repository.Commit(message, Generate.SignatureNow(), Generate.SignatureNow()); + } + } +} diff --git a/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/VersionInMergedReleaseBranchNameStrategy.cs b/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/VersionInMergedReleaseBranchNameStrategy.cs new file mode 100644 index 0000000000..d39464ae1e --- /dev/null +++ b/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/VersionInMergedReleaseBranchNameStrategy.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +using GitVersion.Common; +using GitVersion.Configuration; +using GitVersion.Extensions; +using GitVersion.Model.Configuration; + +namespace GitVersion.VersionCalculation +{ + /// + /// Version is extracted from the name of merged 'is release branch' when merged to master. + /// BaseVersionSource is the current commit. + /// Does not increment. + /// + public class VersionInMergedReleaseBranchNameStrategy : VersionStrategyBase + { + private readonly IRepositoryMetadataProvider repositoryMetadataProvider; + + public VersionInMergedReleaseBranchNameStrategy(IRepositoryMetadataProvider repositoryMetadataProvider, Lazy versionContext) : base(versionContext) + { + this.repositoryMetadataProvider = repositoryMetadataProvider ?? throw new ArgumentNullException(nameof(repositoryMetadataProvider)); + } + + public override IEnumerable GetVersions() + { + var tagPrefixRegex = Context.Configuration.GitTagPrefix; + + var commit = Context.CurrentCommit; + var branchName = Context.CurrentBranch.FriendlyName; + var configuration = Context.FullConfiguration; + + var masterBranchRegex = configuration.Branches[Config.MasterBranchKey].Regex; + + // TODO : Should we check master + tracks release branches, should tracks release branches be set on master ? + if (!Regex.IsMatch(branchName, masterBranchRegex, RegexOptions.IgnoreCase)) + { + yield break; + } + + // TODO : Is this the optimal way to find branches merged into current commit ? + var parentBranch = commit.Parents.SelectMany(p => + repositoryMetadataProvider.GetBranchesContainingCommit(p).Where(b => + configuration.IsReleaseBranch(b.FriendlyName))).Distinct().SingleOrDefault(); + if (parentBranch == null) + { + yield break; + } + + branchName = parentBranch.FriendlyName; + var versionInBranch = GetVersionInBranch(branchName, tagPrefixRegex); + if (versionInBranch != null) + { + // TODO : Not certain what this branch name override is about ... + var branchNameOverride = branchName.RegexReplace("[-/]" + versionInBranch.Item1, string.Empty); + yield return new BaseVersion("Version in parent branch name", false, versionInBranch.Item2, commit, branchNameOverride); + } + } + + private Tuple GetVersionInBranch(string branchName, string tagPrefixRegex) + { + var branchParts = branchName.Split('/', '-'); + foreach (var part in branchParts) + { + if (SemanticVersion.TryParse(part, tagPrefixRegex, out var semanticVersion)) + { + return Tuple.Create(part, semanticVersion); + } + } + + return null; + } + } +}