Skip to content

Strategy to handle versioning from merge of release branch to master, version from branch name. #2442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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]
Expand All @@ -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<string, BranchConfig> {
{ "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]
Expand All @@ -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");
}
Expand All @@ -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");
}
Expand All @@ -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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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<IVersionStrategy, VersionInMergedReleaseBranchNameStrategy>();
}
}

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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Version is extracted from the name of merged 'is release branch' when merged to master.
/// BaseVersionSource is the current commit.
/// Does not increment.
/// </summary>
public class VersionInMergedReleaseBranchNameStrategy : VersionStrategyBase
{
private readonly IRepositoryMetadataProvider repositoryMetadataProvider;

public VersionInMergedReleaseBranchNameStrategy(IRepositoryMetadataProvider repositoryMetadataProvider, Lazy<GitVersionContext> versionContext) : base(versionContext)
{
this.repositoryMetadataProvider = repositoryMetadataProvider ?? throw new ArgumentNullException(nameof(repositoryMetadataProvider));
}

public override IEnumerable<BaseVersion> 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<string, SemanticVersion> 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;
}
}
}