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
5 changes: 4 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,7 @@ branch. For example `develop` → `release/1.0.0` → merge into `master` and ta
Indicates this branch config represents develop in GitFlow.

### is-release-branch
Indicates this branch config represents a release branch in GitFlow.
Indicates this branch config represents a release branch in GitFlow.

### is-mainline
When using Mainline mode, this indicates that this branch is a mainline. By default support/ and master are mainlines.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ branches:
track-merge-target: false
is-develop: false
is-release-branch: false
is-mainline: true
releases?[/-]:
mode: ContinuousDelivery
tag: beta
Expand All @@ -27,6 +28,7 @@ branches:
track-merge-target: false
is-develop: false
is-release-branch: true
is-mainline: false
features?[/-]:
mode: ContinuousDelivery
tag: useBranchName
Expand All @@ -35,6 +37,7 @@ branches:
track-merge-target: false
is-develop: false
is-release-branch: false
is-mainline: false
(pull|pull\-requests|pr)[/-]:
mode: ContinuousDelivery
tag: PullRequest
Expand All @@ -44,6 +47,7 @@ branches:
track-merge-target: false
is-develop: false
is-release-branch: false
is-mainline: false
hotfix(es)?[/-]:
mode: ContinuousDelivery
tag: beta
Expand All @@ -52,6 +56,7 @@ branches:
track-merge-target: false
is-develop: false
is-release-branch: false
is-mainline: false
support[/-]:
mode: ContinuousDelivery
tag: ''
Expand All @@ -60,6 +65,7 @@ branches:
track-merge-target: false
is-develop: false
is-release-branch: false
is-mainline: true
dev(elop)?(ment)?$:
mode: ContinuousDeployment
tag: alpha
Expand All @@ -68,5 +74,6 @@ branches:
track-merge-target: true
is-develop: true
is-release-branch: false
is-mainline: false
ignore:
sha: []
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using GitVersionCore.Tests;
using LibGit2Sharp;
using NUnit.Framework;
using LibGitExtensions = GitTools.LibGitExtensions;

public class MainlineDevelopmentMode
{
Expand Down Expand Up @@ -105,7 +104,41 @@ public void VerifyPullRequestsActLikeContinuousDelivery()
}

[Test]
[Ignore("Not passing yet")]
public void SupportBranches()
{
using (var fixture = new EmptyRepositoryFixture())
{
fixture.Repository.MakeACommit("1");
fixture.MakeATaggedCommit("1.0.0");
fixture.MakeACommit(); // 1.0.1
fixture.MakeACommit(); // 1.0.2
fixture.AssertFullSemver(config, "1.0.2");

fixture.BranchTo("support/1.0", "support10");
fixture.AssertFullSemver(config, "1.0.3");

// Move master on
fixture.Checkout("master");
fixture.MakeACommit("+semver: major"); // 2.0.0 (on master)
fixture.AssertFullSemver(config, "2.0.0");

// Continue on support/1.0
fixture.Checkout("support/1.0");
fixture.MakeACommit(); // 1.0.4
fixture.MakeACommit(); // 1.0.5
fixture.AssertFullSemver(config, "1.0.5");
fixture.BranchTo("feature/foo", "foo");
fixture.AssertFullSemver(config, "1.0.5-foo.0");
fixture.MakeACommit();
fixture.AssertFullSemver(config, "1.0.5-foo.1");
fixture.MakeACommit();
fixture.AssertFullSemver(config, "1.0.5-foo.2");
fixture.Repository.CreatePullRequestRef("feature/foo", "support/1.0", normalise: true, prNumber: 7);
fixture.AssertFullSemver(config, "1.0.5-PullRequest0007.3");
}
}

[Test]
public void VerifyForwardMerge()
{
using (var fixture = new EmptyRepositoryFixture())
Expand All @@ -124,7 +157,7 @@ public void VerifyForwardMerge()
fixture.AssertFullSemver(config, "1.0.2");
fixture.Checkout("feature/foo");
fixture.MergeNoFF("master");
fixture.AssertFullSemver(config, "1.0.3-foo.3");
fixture.AssertFullSemver(config, "1.0.4-foo.3");
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/GitVersionCore/Configuration/BranchConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public BranchConfig(BranchConfig branchConfiguration)
CommitMessageIncrementing = branchConfiguration.CommitMessageIncrementing;
IsDevelop = branchConfiguration.IsDevelop;
IsReleaseBranch = branchConfiguration.IsReleaseBranch;
IsMainline = branchConfiguration.IsMainline;
}

[YamlMember(Alias = "mode")]
Expand Down Expand Up @@ -50,5 +51,8 @@ public BranchConfig(BranchConfig branchConfiguration)

[YamlMember(Alias = "is-release-branch")]
public bool? IsReleaseBranch { get; set; }

[YamlMember(Alias = "is-mainline")]
public bool? IsMainline { get; set; }
}
}
30 changes: 27 additions & 3 deletions src/GitVersionCore/Configuration/ConfigurationProvider.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace GitVersion
{
using System;
using GitVersion.Configuration.Init.Wizard;
using GitVersion.Helpers;
using System.ComponentModel;
Expand Down Expand Up @@ -43,13 +44,28 @@ public static string SelectConfigFilePath(GitPreparer gitPreparer, IFileSystem f
public static Config Provide(string workingDirectory, IFileSystem fileSystem, bool applyDefaults = true, Config overrideConfig = null)
{
var readConfig = ReadConfig(workingDirectory, fileSystem);
VerifyConfiguration(readConfig);

if (applyDefaults)
ApplyDefaultsTo(readConfig);
if (null != overrideConfig)
ApplyOverridesTo(readConfig, overrideConfig);
return readConfig;
}

static void VerifyConfiguration(Config readConfig)
{
// Verify no branches are set to mainline mode
if (readConfig.Branches.Any(b => b.Value.VersioningMode == VersioningMode.Mainline))
{
throw new Exception(@"Mainline mode only works at the repository level, a single branch cannot be put into mainline mode

This is because mainline mode treats your entire git repository as an event source with each merge into the 'mainline' incrementing the version.

If the docs do not help you decide on the mode open an issue to discuss what you are trying to do.");
}
}

public static void ApplyDefaultsTo(Config config)
{
MigrateBranches(config);
Expand All @@ -70,15 +86,21 @@ public static void ApplyDefaultsTo(Config config)

var configBranches = config.Branches.ToList();

ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "master"), defaultTag: string.Empty, defaultPreventIncrement: true);
ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "master"),
defaultTag: string.Empty,
defaultPreventIncrement: true,
isMainline: true);
ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "releases?[/-]"), defaultTag: "beta", defaultPreventIncrement: true, isReleaseBranch: true);
ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "features?[/-]"), defaultIncrementStrategy: IncrementStrategy.Inherit);
ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, @"(pull|pull\-requests|pr)[/-]"),
defaultTag: "PullRequest",
defaultTagNumberPattern: @"[/-](?<number>\d+)[-/]",
defaultIncrementStrategy: IncrementStrategy.Inherit);
ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "hotfix(es)?[/-]"), defaultTag: "beta");
ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "support[/-]"), defaultTag: string.Empty, defaultPreventIncrement: true);
ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "support[/-]"),
defaultTag: string.Empty,
defaultPreventIncrement: true,
isMainline: true);
ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "dev(elop)?(ment)?$"),
defaultTag: "alpha",
defaultIncrementStrategy: IncrementStrategy.Minor,
Expand Down Expand Up @@ -144,7 +166,8 @@ public static void ApplyBranchDefaults(Config config,
bool defaultTrackMergeTarget = false,
string defaultTagNumberPattern = null,
bool isDevelop = false,
bool isReleaseBranch = false)
bool isReleaseBranch = false,
bool isMainline = false)
{
branchConfig.Tag = branchConfig.Tag ?? defaultTag;
branchConfig.TagNumberPattern = branchConfig.TagNumberPattern ?? defaultTagNumberPattern;
Expand All @@ -154,6 +177,7 @@ public static void ApplyBranchDefaults(Config config,
branchConfig.VersioningMode = branchConfig.VersioningMode ?? defaultVersioningMode ?? config.VersioningMode;
branchConfig.IsDevelop = branchConfig.IsDevelop ?? isDevelop;
branchConfig.IsReleaseBranch = branchConfig.IsReleaseBranch ?? isReleaseBranch;
branchConfig.IsMainline = branchConfig.IsMainline ?? isMainline;
}

static Config ReadConfig(string workingDirectory, IFileSystem fileSystem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ protected override StepResult HandleResult(string result, Queue<ConfigInitWizard
config.VersioningMode = VersioningMode.ContinuousDeployment;
steps.Enqueue(returnToStep);
return StepResult.Ok();
case "0":
case "3":
config.VersioningMode = VersioningMode.Mainline;
steps.Enqueue(returnToStep);
return StepResult.Ok();
case "0":
case "4":
steps.Enqueue(returnToStep);
return StepResult.Ok();
}
Expand All @@ -43,14 +47,15 @@ protected override string GetPrompt(Config config, string workingDirectory)
{0}
1) Follow SemVer and only increment when a release has been tagged (continuous delivery mode)
2) Increment based on branch config every commit (continuous deployment mode)
3) Each merged branch against master will increment the version (mainline mode)
{1}",
!isPartOfWizard ? "0) Go Back" : string.Empty,
isPartOfWizard ? "3) Skip" : string.Empty);
isPartOfWizard ? "4) Skip" : string.Empty);
}

protected override string DefaultResult
{
get { return "3"; }
get { return "4"; }
}
}
}
74 changes: 56 additions & 18 deletions src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Linq;
using System.Text.RegularExpressions;
using BaseVersionCalculators;
using GitTools;
using LibGit2Sharp;

public class NextVersionCalculator
Expand Down Expand Up @@ -91,6 +90,11 @@ SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersionConte
{
var mainlineVersion = baseVersion.SemanticVersion;

// Forward merge / PR
// * feature/foo
// / |
// master * *
//
var commitLog = context.Repository.Commits.QueryBy(new CommitFilter
{
IncludeReachableFrom = context.CurrentBranch,
Expand All @@ -99,34 +103,21 @@ SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersionConte
}).Where(c => c.Sha != baseVersion.BaseVersionSource.Sha).ToList();
var directCommits = new List<Commit>();

// Scans commit log in reverse, aggregating merge commits
foreach (var commit in commitLog)
{
directCommits.Add(commit);
if (commit.Parents.Count() > 1)
{
// Merge commit, process all merged commits as a batch
var mergeCommit = commit;
var mergedHead = GetMergedHead(mergeCommit);
var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(mergeCommit.Parents.First(), mergedHead);
var findMessageIncrement = FindMessageIncrement(context, mergeCommit, mergedHead, findMergeBase, directCommits);

// If this collection is not empty there has been some direct commits against master
// Treat each commit as it's own 'release', we need to do this before we increment the branch
mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
directCommits.Clear();

// Finally increment for the branch
mainlineVersion = mainlineVersion.IncrementVersion(findMessageIncrement);
Logger.WriteInfo(string.Format("Merge commit {0} incremented base versions {1}, now {2}",
mergeCommit.Sha, findMessageIncrement, mainlineVersion));
mainlineVersion = AggregateMergeCommitIncrement(context, commit, directCommits, mainlineVersion);
}
}

if (context.CurrentBranch.FriendlyName != "master")
{
var mergedHead = context.CurrentCommit;
var second = context.Repository.FindBranch("master").Tip;
var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, second);
var mainlineTip = GetMainlineTip(context);
var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainlineTip);
Logger.WriteInfo(string.Format("Current branch ({0}) was branch from {1}", context.CurrentBranch.FriendlyName, findMergeBase));

var branchIncrement = FindMessageIncrement(context, null, mergedHead, findMergeBase, directCommits);
Expand All @@ -151,6 +142,53 @@ SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersionConte
}
}

SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit commit, List<Commit> directCommits, SemanticVersion mainlineVersion)
{
// Merge commit, process all merged commits as a batch
var mergeCommit = commit;
var mergedHead = GetMergedHead(mergeCommit);
var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(mergeCommit.Parents.First(), mergedHead);
var findMessageIncrement = FindMessageIncrement(context, mergeCommit, mergedHead, findMergeBase, directCommits);

// If this collection is not empty there has been some direct commits against master
// Treat each commit as it's own 'release', we need to do this before we increment the branch
mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
directCommits.Clear();

// Finally increment for the branch
mainlineVersion = mainlineVersion.IncrementVersion(findMessageIncrement);
Logger.WriteInfo(string.Format("Merge commit {0} incremented base versions {1}, now {2}",
mergeCommit.Sha, findMessageIncrement, mainlineVersion));
return mainlineVersion;
}

static Commit GetMainlineTip(GitVersionContext context)
{
var mainlineBranchConfigs = context.FullConfiguration.Branches.Where(b => b.Value.IsMainline == true).ToList();
var seenMainlineTips = new List<string>();
var mainlineBranches = context.Repository.Branches.Where(b =>
{
return mainlineBranchConfigs.Any(c => Regex.IsMatch(b.FriendlyName, c.Key));
}).Where(b =>
{
if (seenMainlineTips.Contains(b.Tip.Sha))
{
Logger.WriteInfo("Multiple possible mainlines pointing at the same commit, dropping " + b.FriendlyName);
return false;
}
seenMainlineTips.Add(b.Tip.Sha);
return true;
}).ToDictionary(b => context.Repository.ObjectDatabase.FindMergeBase(b.Tip, context.CurrentCommit).Sha, b => b);
Logger.WriteInfo("Found possible mainline branches: " + string.Join(", ", mainlineBranches.Values.Select(b => b.FriendlyName)));

// Find closest mainline branch
var firstMatchingCommit = context.CurrentBranch.Commits.First(c => mainlineBranches.ContainsKey(c.Sha));
var mainlineBranch = mainlineBranches[firstMatchingCommit.Sha];
Logger.WriteInfo("Mainline for current branch is " + mainlineBranch.FriendlyName);

return mainlineBranch.Tip;
}

private static SemanticVersion IncrementForEachCommit(GitVersionContext context, List<Commit> directCommits, SemanticVersion mainlineVersion)
{
foreach (var directCommit in directCommits)
Expand Down