diff --git a/docs/configuration.md b/docs/configuration.md
index 12cdd451a9..6f8afcf066 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -24,7 +24,7 @@ The global configuration options are:
- **`assembly-informational-format:`** Set this to any of the available [variables](/more-info/variables) to change the value of the `AssemblyInformationalVersion` attribute. Default set to `{InformationalVersion}`. It also supports string interpolation (`{MajorMinorPatch}+{Branch}`)
- - **`mode:`** Sets the mode of how GitVersion should create a new version. Can be set to either `ContinuousDelivery` or `ContinuousDeployment`. Read more about [ContinuousDelivery](/reference/continuous-delivery/) or [ContinuousDeployment](/reference/continuous-deployment/).
+ - **`mode:`** Sets the mode of how GitVersion should create a new version. Read more at [versioning mode](./versioning-mode.md)
- **`continuous-delivery-fallback-tag:`** When using `mode: ContinuousDeployment`, the value specified will be used as the pre-release tag for branches which do not have one specified. Default set to `ci`.
@@ -36,6 +36,8 @@ The global configuration options are:
- **`patch-version-bump-message:`** The regex to match commit messages with to perform a patch version increment. Default set to `'\+semver:\s?(fix|patch)'`, which will match occurrences of `+semver: fix` and `+semver: patch` in a commit message.
+ - **`no-bump-message:`** Used to tell GitVersion not to increment when in Mainline development mode. Default `\+semver:\s?(none|skip)`, which will match occurrences of `+semver: none` and `+semver: skip`
+
- **`legacy-semver-padding:`** The number of characters to pad `LegacySemVer` to in the `LegacySemVerPadded` [variable](/more-info/variables). Is default set to `4`, which will pad the `LegacySemVer` value of `3.0.0-beta1` to `3.0.0-beta0001`.
- **`build-metadata-padding:`** The number of characters to pad `BuildMetaData` to in the `BuildMetaDataPadded` [variable](/more-info/variables). Is default set to `4`, which will pad the `BuildMetaData` value of `1` to `0001`.
diff --git a/docs/git-branching-strategies/img/39f9d8b8b007c82f1f80_major-release.png b/docs/git-branching-strategies/img/39f9d8b8b007c82f1f80_major-release.png
index 74a1b2f976..34c37d7502 100644
Binary files a/docs/git-branching-strategies/img/39f9d8b8b007c82f1f80_major-release.png and b/docs/git-branching-strategies/img/39f9d8b8b007c82f1f80_major-release.png differ
diff --git a/docs/img/mainline-mode.png b/docs/img/mainline-mode.png
new file mode 100644
index 0000000000..52c15df9cc
Binary files /dev/null and b/docs/img/mainline-mode.png differ
diff --git a/docs/versioning-mode.md b/docs/versioning-mode.md
new file mode 100644
index 0000000000..41eae53e4f
--- /dev/null
+++ b/docs/versioning-mode.md
@@ -0,0 +1,42 @@
+# Versioning modes
+GitVersion has multiple modes to fit different different ways of working.
+
+
+## Continuous Delivery
+This is the default mode, GitVersion calculates the next version and will use that until that is released. For instance:
+
+ - 1.1.0+5
+ - 1.1.0+6
+ - 1.1.0+7 <-- This is the artifact we release, tag the commit which created this version
+ - 1.1.1+0
+
+Tags are required in this mode to communicate when the release is done as it's an external manual process.
+
+## Continuous deployment
+Sometimes you just want the version to keep changing and continuously deploy. A good case for this is when using Octopus deploy, as you cannot publish the same version of a package into the same feed.
+
+For this mode we followed the logic in this blog post by Xavier Decoster on the issues of incrementing automatically - http://www.xavierdecoster.com/semantic-versioning-auto-incremented-nuget-package-versions
+
+As such we force a pre-release tag on all branches, this is fine for applications but can cause problems for libraries. As such this mode may or may not work for you, which leads us into a new mode in v4. Mainline development.
+
+## Mainline development
+Mainline development works more like the Continuous Delivery mode, except that it tells GitVersion to *infer* releases from merges and commits to `master`.
+
+This mode is great if you do not want to tag each release because you simply deploy every commit to master. The behaviour of this mode is as follows:
+
+1. Calclate a base version (likely a tag in this mode)
+1. Walk all commits from the base version commit
+1. When a merge commit is found:
+ - Calculate increments for each direct commit on master
+ - Calculate the increment for the branch
+1. Calculate increments for each remaining direct commit
+1. For feature branches then calculate increment for the commits so far on your feature branch.
+
+If you *do not want* GitVersion to treat a commit or a pull request as a release and increment the version you can use `+semver: none` or `+semver: skip` in a commit message to skip incrementing for that commit.
+
+Here is an example of what mainline development looks like:
+
+
+
+
+**WARNING:** This approach can slow down over time, we recommend to tag intermitently (maybe for minor or major releases) because then GitVersion will start the version calculation from that point. Much like a snapshot in an event sourced system. We will probably add in warnings to tag when things are slowing down.
\ No newline at end of file
diff --git a/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt b/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt
index 9fcb8beca8..4dd013a10e 100644
--- a/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt
+++ b/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt
@@ -5,6 +5,7 @@ continuous-delivery-fallback-tag: ci
major-version-bump-message: '\+semver:\s?(breaking|major)'
minor-version-bump-message: '\+semver:\s?(feature|minor)'
patch-version-bump-message: '\+semver:\s?(fix|patch)'
+no-bump-message: '\+semver:\s?(none|skip)'
legacy-semver-padding: 4
build-metadata-padding: 4
commits-since-version-source-padding: 4
diff --git a/src/GitVersionCore.Tests/GitVersionCore.Tests.csproj b/src/GitVersionCore.Tests/GitVersionCore.Tests.csproj
index dc33d577f8..d6c3ee9ec9 100644
--- a/src/GitVersionCore.Tests/GitVersionCore.Tests.csproj
+++ b/src/GitVersionCore.Tests/GitVersionCore.Tests.csproj
@@ -113,6 +113,7 @@
+
diff --git a/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs b/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs
new file mode 100644
index 0000000000..6650600401
--- /dev/null
+++ b/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Reflection;
+using System.Text;
+using GitTools.Testing;
+using GitVersion;
+using GitVersionCore.Tests;
+using LibGit2Sharp;
+using NUnit.Framework;
+
+public class MainlineDevelopmentMode
+{
+ private Config config = new Config
+ {
+ VersioningMode = VersioningMode.Mainline
+ };
+
+ [Test]
+ public void MergedFeatureBranchesToMasterImpliesRelease()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.Repository.MakeACommit("1");
+ fixture.MakeATaggedCommit("1.0.0");
+
+ fixture.BranchTo("feature/foo", "foo");
+ fixture.MakeACommit("2");
+ fixture.AssertFullSemver(config, "1.0.1-foo.1+1");
+ fixture.Checkout("master");
+ fixture.MergeNoFF("feature/foo");
+
+ fixture.AssertFullSemver(config, "1.0.1+2");
+
+ fixture.BranchTo("feature/foo2", "foo2");
+ fixture.MakeACommit("3 +semver: minor");
+ fixture.AssertFullSemver(config, "1.1.0-foo2.1+3");
+ fixture.Checkout("master");
+ fixture.MergeNoFF("feature/foo2");
+ fixture.AssertFullSemver(config, "1.1.0+4");
+
+ fixture.BranchTo("feature/foo3", "foo3");
+ fixture.MakeACommit("4");
+ fixture.Checkout("master");
+ fixture.MergeNoFF("feature/foo3");
+ fixture.SequenceDiagram.NoteOver("Merge message contains '+semver: minor'", "master");
+ var commit = fixture.Repository.Head.Tip;
+ // Put semver increment in merge message
+ fixture.Repository.Commit(commit.Message + " +semver: minor", commit.Author, commit.Committer, new CommitOptions
+ {
+ AmendPreviousCommit = true
+ });
+ fixture.AssertFullSemver(config, "1.2.0+6");
+
+ fixture.BranchTo("feature/foo4", "foo4");
+ fixture.MakeACommit("5 +semver: major");
+ fixture.AssertFullSemver(config, "2.0.0-foo4.1+7");
+ fixture.Checkout("master");
+ fixture.MergeNoFF("feature/foo4");
+ fixture.AssertFullSemver(config, "2.0.0+8");
+
+ // We should evaluate any commits not included in merge commit calculations for direct commit/push or squash to merge commits
+ fixture.MakeACommit("6 +semver: major");
+ fixture.AssertFullSemver(config, "3.0.0+9");
+ fixture.MakeACommit("7 +semver: minor");
+ fixture.AssertFullSemver(config, "3.1.0+10");
+ fixture.MakeACommit("8");
+ fixture.AssertFullSemver(config, "3.1.1+11");
+
+ // Finally verify that the merge commits still function properly
+ fixture.BranchTo("feature/foo5", "foo5");
+ fixture.MakeACommit("9 +semver: minor");
+ fixture.AssertFullSemver(config, "3.2.0-foo5.1+12");
+ fixture.Checkout("master");
+ fixture.MergeNoFF("feature/foo5");
+ fixture.AssertFullSemver(config, "3.2.0+13");
+
+ // One more direct commit for good measure
+ fixture.MakeACommit("10 +semver: minor");
+ fixture.AssertFullSemver(config, "3.3.0+14");
+ // And we can commit without bumping semver
+ fixture.MakeACommit("11 +semver: none");
+ fixture.AssertFullSemver(config, "3.3.0+15");
+ Console.WriteLine(fixture.SequenceDiagram.GetDiagram());
+ }
+ }
+ // Write test which has a forward merge into a feature branch
+}
+
+static class CommitExtensions
+{
+ public static void MakeACommit(this RepositoryFixtureBase fixture, string commitMsg)
+ {
+ fixture.Repository.MakeACommit(commitMsg);
+ var diagramBuilder = (StringBuilder)typeof(SequenceDiagram)
+ .GetField("_diagramBuilder", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(fixture.SequenceDiagram);
+ Func getParticipant = participant => (string)typeof(SequenceDiagram)
+ .GetMethod("GetParticipant", BindingFlags.Instance | BindingFlags.NonPublic)
+ .Invoke(fixture.SequenceDiagram, new object[]
+ {
+ participant
+ });
+ diagramBuilder.AppendLineFormat("{0} -> {0}: Commit '{1}'", getParticipant(fixture.Repository.Head.FriendlyName),
+ commitMsg);
+ }
+}
\ No newline at end of file
diff --git a/src/GitVersionCore.Tests/TestEffectiveConfiguration.cs b/src/GitVersionCore.Tests/TestEffectiveConfiguration.cs
index d4412cca0d..d8ea075bc7 100644
--- a/src/GitVersionCore.Tests/TestEffectiveConfiguration.cs
+++ b/src/GitVersionCore.Tests/TestEffectiveConfiguration.cs
@@ -22,6 +22,7 @@ public TestEffectiveConfiguration(
string majorMessage = null,
string minorMessage = null,
string patchMessage = null,
+ string noBumpMessage = null,
CommitMessageIncrementMode commitMessageMode = CommitMessageIncrementMode.Enabled,
int legacySemVerPadding = 4,
int buildMetaDataPadding = 4,
@@ -32,7 +33,7 @@ public TestEffectiveConfiguration(
base(assemblyVersioningScheme, assemblyInformationalFormat, versioningMode, gitTagPrefix, tag, nextVersion, IncrementStrategy.Patch,
branchPrefixToTrim, preventIncrementForMergedBranchVersion, tagNumberPattern, continuousDeploymentFallbackTag,
trackMergeTarget,
- majorMessage, minorMessage, patchMessage,
+ majorMessage, minorMessage, patchMessage, noBumpMessage,
commitMessageMode, legacySemVerPadding, buildMetaDataPadding, commitsSinceVersionSourcePadding,
versionFilters ?? Enumerable.Empty(),
isDevelop, isRelease)
diff --git a/src/GitVersionCore/Configuration/Config.cs b/src/GitVersionCore/Configuration/Config.cs
index 4c492f77a0..dac4786940 100644
--- a/src/GitVersionCore/Configuration/Config.cs
+++ b/src/GitVersionCore/Configuration/Config.cs
@@ -52,6 +52,9 @@ public string NextVersion
[YamlMember(Alias = "patch-version-bump-message")]
public string PatchVersionBumpMessage { get; set; }
+ [YamlMember(Alias = "no-bump-message")]
+ public string NoBumpMessage { get; set; }
+
[YamlMember(Alias = "legacy-semver-padding")]
public int? LegacySemVerPadding { get; set; }
diff --git a/src/GitVersionCore/Configuration/ConfigurationProvider.cs b/src/GitVersionCore/Configuration/ConfigurationProvider.cs
index 4e799db8e9..9da592bae5 100644
--- a/src/GitVersionCore/Configuration/ConfigurationProvider.cs
+++ b/src/GitVersionCore/Configuration/ConfigurationProvider.cs
@@ -62,6 +62,7 @@ public static void ApplyDefaultsTo(Config config)
config.MajorVersionBumpMessage = config.MajorVersionBumpMessage ?? IncrementStrategyFinder.DefaultMajorPattern;
config.MinorVersionBumpMessage = config.MinorVersionBumpMessage ?? IncrementStrategyFinder.DefaultMinorPattern;
config.PatchVersionBumpMessage = config.PatchVersionBumpMessage ?? IncrementStrategyFinder.DefaultPatchPattern;
+ config.NoBumpMessage = config.NoBumpMessage ?? IncrementStrategyFinder.DefaultNoBumpPattern;
config.CommitMessageIncrementing = config.CommitMessageIncrementing ?? CommitMessageIncrementMode.Enabled;
config.LegacySemVerPadding = config.LegacySemVerPadding ?? 4;
config.BuildMetaDataPadding = config.BuildMetaDataPadding ?? 4;
diff --git a/src/GitVersionCore/EffectiveConfiguration.cs b/src/GitVersionCore/EffectiveConfiguration.cs
index 6341cb49d2..522e98b4e0 100644
--- a/src/GitVersionCore/EffectiveConfiguration.cs
+++ b/src/GitVersionCore/EffectiveConfiguration.cs
@@ -21,6 +21,7 @@ public EffectiveConfiguration(
string majorVersionBumpMessage,
string minorVersionBumpMessage,
string patchVersionBumpMessage,
+ string noBumpMessage,
CommitMessageIncrementMode commitMessageIncrementing,
int legacySemVerPaddding,
int buildMetaDataPadding,
@@ -44,6 +45,7 @@ public EffectiveConfiguration(
MajorVersionBumpMessage = majorVersionBumpMessage;
MinorVersionBumpMessage = minorVersionBumpMessage;
PatchVersionBumpMessage = patchVersionBumpMessage;
+ NoBumpMessage = noBumpMessage;
CommitMessageIncrementing = commitMessageIncrementing;
LegacySemVerPadding = legacySemVerPaddding;
BuildMetaDataPadding = buildMetaDataPadding;
@@ -91,6 +93,7 @@ public EffectiveConfiguration(
public string PatchVersionBumpMessage { get; private set; }
+ public string NoBumpMessage { get; private set; }
public int LegacySemVerPadding { get; private set; }
public int BuildMetaDataPadding { get; private set; }
diff --git a/src/GitVersionCore/GitVersionContext.cs b/src/GitVersionCore/GitVersionContext.cs
index b600769b80..72ec9b2650 100644
--- a/src/GitVersionCore/GitVersionContext.cs
+++ b/src/GitVersionCore/GitVersionContext.cs
@@ -123,6 +123,7 @@ void CalculateEffectiveConfiguration()
var majorMessage = FullConfiguration.MajorVersionBumpMessage;
var minorMessage = FullConfiguration.MinorVersionBumpMessage;
var patchMessage = FullConfiguration.PatchVersionBumpMessage;
+ var noBumpMessage = FullConfiguration.NoBumpMessage;
var commitMessageVersionBump = currentBranchConfig.Value.CommitMessageIncrementing ?? FullConfiguration.CommitMessageIncrementing.Value;
@@ -132,7 +133,7 @@ void CalculateEffectiveConfiguration()
preventIncrementForMergedBranchVersion,
tagNumberPattern, FullConfiguration.ContinuousDeploymentFallbackTag,
trackMergeTarget,
- majorMessage, minorMessage, patchMessage,
+ majorMessage, minorMessage, patchMessage, noBumpMessage,
commitMessageVersionBump,
FullConfiguration.LegacySemVerPadding.Value,
FullConfiguration.BuildMetaDataPadding.Value,
diff --git a/src/GitVersionCore/IncrementStrategyFinder.cs b/src/GitVersionCore/IncrementStrategyFinder.cs
index 6c5140d9f7..82fbb38b75 100644
--- a/src/GitVersionCore/IncrementStrategyFinder.cs
+++ b/src/GitVersionCore/IncrementStrategyFinder.cs
@@ -19,6 +19,7 @@ public static class IncrementStrategyFinder
public const string DefaultMajorPattern = @"\+semver:\s?(breaking|major)";
public const string DefaultMinorPattern = @"\+semver:\s?(feature|minor)";
public const string DefaultPatchPattern = @"\+semver:\s?(fix|patch)";
+ public const string DefaultNoBumpPattern = @"\+semver:\s?(none|skip)";
public static VersionField? DetermineIncrementedField(GitVersionContext context, BaseVersion baseVersion)
{
@@ -61,12 +62,18 @@ public static class IncrementStrategyFinder
commits = commits.Where(c => c.Parents.Count() > 1);
}
+ return GetIncrementForCommits(context, commits);
+ }
+
+ public static VersionField? GetIncrementForCommits(GitVersionContext context, IEnumerable commits)
+ {
var majorRegex = CreateRegex(context.Configuration.MajorVersionBumpMessage ?? DefaultMajorPattern);
var minorRegex = CreateRegex(context.Configuration.MinorVersionBumpMessage ?? DefaultMinorPattern);
var patchRegex = CreateRegex(context.Configuration.PatchVersionBumpMessage ?? DefaultPatchPattern);
+ var none = CreateRegex(context.Configuration.NoBumpMessage ?? DefaultNoBumpPattern);
var increments = commits
- .Select(c => FindIncrementFromMessage(c.Message, majorRegex, minorRegex, patchRegex))
+ .Select(c => FindIncrementFromMessage(c.Message, majorRegex, minorRegex, patchRegex, none))
.Where(v => v != null)
.Select(v => v.Value)
.ToList();
@@ -78,7 +85,7 @@ public static class IncrementStrategyFinder
return null;
}
-
+
private static IEnumerable GetIntermediateCommits(IRepository repo, Commit baseCommit, Commit headCommit)
{
if (baseCommit == null) yield break;
@@ -105,11 +112,12 @@ private static IEnumerable GetIntermediateCommits(IRepository repo, Comm
}
}
- private static VersionField? FindIncrementFromMessage(string message, Regex major, Regex minor, Regex patch)
+ private static VersionField? FindIncrementFromMessage(string message, Regex major, Regex minor, Regex patch, Regex none)
{
if (major.IsMatch(message)) return VersionField.Major;
if (minor.IsMatch(message)) return VersionField.Minor;
if (patch.IsMatch(message)) return VersionField.Patch;
+ if (none.IsMatch(message)) return VersionField.None;
return null;
}
diff --git a/src/GitVersionCore/LibGitExtensions.cs b/src/GitVersionCore/LibGitExtensions.cs
index 8ca8d666f5..15bcfaf192 100644
--- a/src/GitVersionCore/LibGitExtensions.cs
+++ b/src/GitVersionCore/LibGitExtensions.cs
@@ -85,15 +85,16 @@ public static Commit FindMergeBase(this Branch branch, Branch otherBranch, IRepo
{
// Otherbranch tip is a forward merge
var commitToFindCommonBase = otherBranch.Tip;
- if (otherBranch.Tip.Parents.Contains(branch.Tip))
+ var commit = branch.Tip;
+ if (otherBranch.Tip.Parents.Contains(commit))
{
commitToFindCommonBase = otherBranch.Tip.Parents.First();
}
- var findMergeBase = repository.ObjectDatabase.FindMergeBase(branch.Tip, commitToFindCommonBase);
+ var findMergeBase = repository.ObjectDatabase.FindMergeBase(commit, commitToFindCommonBase);
if (findMergeBase != null)
{
- Logger.WriteInfo(string.Format("Found merge base of {0} against {1}", findMergeBase.Sha, otherBranch.FriendlyName));
+ Logger.WriteInfo(string.Format("Found merge base of {0}", findMergeBase.Sha));
// We do not want to include merge base commits which got forward merged into the other branch
bool mergeBaseWasFowardMerge;
do
@@ -106,7 +107,7 @@ public static Commit FindMergeBase(this Branch branch, Branch otherBranch, IRepo
if (mergeBaseWasFowardMerge)
{
var second = commitToFindCommonBase.Parents.First();
- var mergeBase = repository.ObjectDatabase.FindMergeBase(branch.Tip, second);
+ var mergeBase = repository.ObjectDatabase.FindMergeBase(commit, second);
if (mergeBase == findMergeBase)
{
break;
diff --git a/src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs b/src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs
index 930f76a600..2498e173de 100644
--- a/src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs
+++ b/src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs
@@ -1,8 +1,12 @@
namespace GitVersion.VersionCalculation
{
+ using System;
+ using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using BaseVersionCalculators;
+ using GitTools;
+ using LibGit2Sharp;
public class NextVersionCalculator
{
@@ -40,13 +44,9 @@ public SemanticVersion FindVersion(GitVersionContext context)
}
var baseVersion = baseVersionFinder.GetBaseVersion(context);
- var semver = baseVersion.SemanticVersion;
- var increment = IncrementStrategyFinder.DetermineIncrementedField(context, baseVersion);
- if (increment != null)
- {
- semver = semver.IncrementVersion(increment.Value);
- }
- else Logger.WriteInfo("Skipping version increment");
+ var semver = context.Configuration.VersioningMode == VersioningMode.Mainline ?
+ FindMainlineModeVersion(baseVersion, context) :
+ PerformIncrement(context, baseVersion);
if (!semver.PreReleaseTag.HasTag() && !string.IsNullOrEmpty(context.Configuration.Tag))
{
@@ -64,6 +64,117 @@ public SemanticVersion FindVersion(GitVersionContext context)
return taggedSemanticVersion ?? semver;
}
+ private static SemanticVersion PerformIncrement(GitVersionContext context, BaseVersion baseVersion)
+ {
+ var semver = baseVersion.SemanticVersion;
+ var increment = IncrementStrategyFinder.DetermineIncrementedField(context, baseVersion);
+ if (increment != null)
+ {
+ semver = semver.IncrementVersion(increment.Value);
+ }
+ else Logger.WriteInfo("Skipping version increment");
+ return semver;
+ }
+
+ private SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersionContext context)
+ {
+ if (baseVersion.SemanticVersion.PreReleaseTag.HasTag())
+ {
+ throw new NotSupportedException("Mainline development mode doesn't yet support pre-release tags on master");
+ }
+
+ using (Logger.IndentLog("Using mainline development mode to calculate current version"))
+ {
+ var mainlineVersion = baseVersion.SemanticVersion;
+
+ var commitLog = context.Repository.Commits.QueryBy(new CommitFilter
+ {
+ IncludeReachableFrom = context.CurrentBranch,
+ ExcludeReachableFrom = baseVersion.BaseVersionSource,
+ SortBy = CommitSortStrategies.Reverse
+ }).Where(c => c.Sha != baseVersion.BaseVersionSource.Sha).ToList();
+ var directCommits = new List();
+
+ 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));
+ }
+ }
+
+ if (context.CurrentBranch.FriendlyName != "master")
+ {
+ var mergedHead = context.CurrentCommit;
+ var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, context.Repository.FindBranch("master").Tip);
+ Logger.WriteInfo(string.Format("Current branch ({0}) was branch from {1}", context.CurrentBranch.FriendlyName, findMergeBase));
+
+ var branchIncrement = FindMessageIncrement(context, findMergeBase, mergedHead, findMergeBase, directCommits);
+ mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
+ Logger.WriteInfo(string.Format("Performing {0} increment for current branch ", branchIncrement));
+ mainlineVersion = mainlineVersion.IncrementVersion(branchIncrement);
+ }
+ else
+ {
+ // If we are on master, make sure no commits get left behind
+ mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
+ }
+
+ return mainlineVersion;
+ }
+ }
+
+ private static SemanticVersion IncrementForEachCommit(GitVersionContext context, List directCommits, SemanticVersion mainlineVersion)
+ {
+ foreach (var directCommit in directCommits)
+ {
+ var directCommitIncrement = IncrementStrategyFinder.GetIncrementForCommits(context, new[]
+ {
+ directCommit
+ }) ?? VersionField.Patch;
+ mainlineVersion = mainlineVersion.IncrementVersion(directCommitIncrement);
+ Logger.WriteInfo(string.Format("Direct commit on master {0} incremented base versions {1}, now {2}",
+ directCommit.Sha, directCommitIncrement, mainlineVersion));
+ }
+ return mainlineVersion;
+ }
+
+ private static VersionField FindMessageIncrement(
+ GitVersionContext context, Commit mergeCommit, Commit mergedHead, Commit findMergeBase, List commitLog)
+ {
+ var filter = new CommitFilter
+ {
+ IncludeReachableFrom = mergedHead,
+ ExcludeReachableFrom = findMergeBase
+ };
+ var commits = new[] { mergeCommit }.Union(context.Repository.Commits.QueryBy(filter)).ToList();
+ commitLog.RemoveAll(c => commits.Any(c1 => c1.Sha == c.Sha));
+ return IncrementStrategyFinder.GetIncrementForCommits(context, commits) ?? VersionField.Patch;
+ }
+
+ private Commit GetMergedHead(Commit 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");
+ return parents.Single();
+ }
+
void UpdatePreReleaseTag(GitVersionContext context, SemanticVersion semanticVersion, string branchNameOverride)
{
var tagToUse = GetBranchSpecificTag(context.Configuration, context.CurrentBranch.FriendlyName, branchNameOverride);
@@ -78,7 +189,7 @@ void UpdatePreReleaseTag(GitVersionContext context, SemanticVersion semanticVers
number = int.Parse(numberGroup.Value);
}
}
-
+
var lastTag = context.CurrentBranch
.GetVersionTagsOnBranch(context.Repository, context.Configuration.GitTagPrefix)
.FirstOrDefault(v => v.PreReleaseTag.Name == tagToUse);
diff --git a/src/GitVersionCore/VersioningModes/VersioningMode.cs b/src/GitVersionCore/VersioningModes/VersioningMode.cs
index d3d97c0632..45acb68ccd 100644
--- a/src/GitVersionCore/VersioningModes/VersioningMode.cs
+++ b/src/GitVersionCore/VersioningModes/VersioningMode.cs
@@ -3,6 +3,7 @@
public enum VersioningMode
{
ContinuousDelivery,
- ContinuousDeployment
+ ContinuousDeployment,
+ Mainline
}
}