From bd16d241fbbdb54a81e98f728b350da293576fe2 Mon Sep 17 00:00:00 2001 From: Jake Ginnivan Date: Sun, 21 May 2017 12:56:41 +0800 Subject: [PATCH 1/5] Introduced MergeMessage class This class parses the merge message and exposes all the useful information, like is it a pull request, what was the source and target branch etc. --- src/GitVersionCore/GitVersionCore.csproj | 1 + src/GitVersionCore/MergeMessage.cs | 89 +++++++++++++++++++ .../MergeMessageBaseVersionStrategy.cs | 28 ++---- 3 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 src/GitVersionCore/MergeMessage.cs diff --git a/src/GitVersionCore/GitVersionCore.csproj b/src/GitVersionCore/GitVersionCore.csproj index 906bd1abef..b00463ec2d 100644 --- a/src/GitVersionCore/GitVersionCore.csproj +++ b/src/GitVersionCore/GitVersionCore.csproj @@ -128,6 +128,7 @@ + diff --git a/src/GitVersionCore/MergeMessage.cs b/src/GitVersionCore/MergeMessage.cs new file mode 100644 index 0000000000..d9bd26c959 --- /dev/null +++ b/src/GitVersionCore/MergeMessage.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace GitVersion +{ + class MergeMessage + { + static Regex parseMergeMessage = new Regex( + @"^Merge (branch|tag) '(?[^']*)'", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + static Regex parseGitHubPullMergeMessage = new Regex( + @"^Merge pull request #(?\d*) (from|in) (?.*)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + static Regex smartGitMergeMessage = new Regex( + @"^Finish (?.*)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + private string mergeMessage; + private Config config; + + public MergeMessage(string mergeMessage, Config config) + { + this.mergeMessage = mergeMessage; + this.config = config; + + var lastIndexOf = mergeMessage.LastIndexOf("into", StringComparison.OrdinalIgnoreCase); + if (lastIndexOf != -1) + { + // If we have into in the merge message the rest should be the target branch + TargetBranch = mergeMessage.Substring(lastIndexOf + 5); + } + + MergedBranch = ParseBranch(); + + // Remove remotes and branch prefixes like release/ feature/ hotfix/ etc + var toMatch = Regex.Replace(MergedBranch, @"^(\w+[-/])*", "", RegexOptions.IgnoreCase); + toMatch = Regex.Replace(toMatch, $"^{config.TagPrefix}", ""); + // We don't match if the version is likely an ip (i.e starts with http://) + var versionMatch = new Regex(@"^(? @@ -21,7 +19,7 @@ public override IEnumerable GetVersions(GitVersionContext context) .SelectMany(c => { SemanticVersion semanticVersion; - if (TryParse(c, context.Configuration, out semanticVersion)) + if (TryParse(c, context, out semanticVersion)) { var shouldIncrement = !context.Configuration.PreventIncrementForMergedBranchVersion; return new[] @@ -34,35 +32,21 @@ public override IEnumerable GetVersions(GitVersionContext context) return baseVersions; } - static bool TryParse(Commit mergeCommit, EffectiveConfiguration configuration, out SemanticVersion semanticVersion) + static bool TryParse(Commit mergeCommit, GitVersionContext context, out SemanticVersion semanticVersion) { - semanticVersion = Inner(mergeCommit, configuration); + semanticVersion = Inner(mergeCommit, context); return semanticVersion != null; } - static SemanticVersion Inner(Commit mergeCommit, EffectiveConfiguration configuration) + static SemanticVersion Inner(Commit mergeCommit, GitVersionContext context) { if (mergeCommit.Parents.Count() < 2) { return null; } - var commitMessage = mergeCommit.Message; - var lastIndexOf = commitMessage.LastIndexOf("into", StringComparison.OrdinalIgnoreCase); - if (lastIndexOf != -1) - commitMessage = commitMessage.Substring(0, lastIndexOf); - - //TODO: Make the version prefixes customizable - var possibleVersions = Regex.Matches(commitMessage, @"^.*?(([rR]elease|[hH]otfix|[aA]lpha)-|-v|/|/v|'|Finish )(?(?() - .Select(m => m.Groups["PossibleVersions"].Value); - - return possibleVersions - .Select(part => - { - SemanticVersion v; - return SemanticVersion.TryParse(part, configuration.GitTagPrefix, out v) ? v : null; - }).FirstOrDefault(v => v != null); + var mergeMessage = new MergeMessage(mergeCommit.Message, context.FullConfiguration); + return mergeMessage.Version; } } } \ No newline at end of file From e5f1c1d23d0b318c854a3ce0de5610c21fb19afa Mon Sep 17 00:00:00 2001 From: Jake Ginnivan Date: Sun, 21 May 2017 12:57:14 +0800 Subject: [PATCH 2/5] Extracted Mainline mode logic into it's own class --- src/GitVersionCore/GitVersionCore.csproj | 1 + .../MainlineVersionCalculator.cs | 214 ++++++++++++++++++ .../NextVersionCalculator.cs | 193 +--------------- 3 files changed, 219 insertions(+), 189 deletions(-) create mode 100644 src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs diff --git a/src/GitVersionCore/GitVersionCore.csproj b/src/GitVersionCore/GitVersionCore.csproj index b00463ec2d..fea4f65af9 100644 --- a/src/GitVersionCore/GitVersionCore.csproj +++ b/src/GitVersionCore/GitVersionCore.csproj @@ -133,6 +133,7 @@ + diff --git a/src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs b/src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs new file mode 100644 index 0000000000..75ceb76605 --- /dev/null +++ b/src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs @@ -0,0 +1,214 @@ +using GitVersion.VersionCalculation.BaseVersionCalculators; +using LibGit2Sharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace GitVersion.VersionCalculation +{ + class MainlineVersionCalculator + { + IMetaDataCalculator metaDataCalculator; + + public MainlineVersionCalculator(IMetaDataCalculator metaDataCalculator) + { + this.metaDataCalculator = metaDataCalculator; + } + + public 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; + + // Forward merge / PR + // * feature/foo + // / | + // master * * + // + + var mainlineTip = GetMainlineTip(context); + var commitsNotOnMainline = context.Repository.Commits.QueryBy(new CommitFilter + { + IncludeReachableFrom = context.CurrentBranch, + ExcludeReachableFrom = mainlineTip, + SortBy = CommitSortStrategies.Reverse, + FirstParentOnly = true + }).Where(c => c.Sha != baseVersion.BaseVersionSource.Sha && c.Parents.Count() == 1).ToList(); + var commitLog = context.Repository.Commits.QueryBy(new CommitFilter + { + IncludeReachableFrom = context.CurrentBranch, + ExcludeReachableFrom = baseVersion.BaseVersionSource, + SortBy = CommitSortStrategies.Reverse, + FirstParentOnly = true + }) + .Where(c => c.Sha != baseVersion.BaseVersionSource.Sha) + .Except(commitsNotOnMainline) + .ToList(); + + var directCommits = new List(); + + // Scans commit log in reverse, aggregating merge commits + foreach (var commit in commitLog) + { + directCommits.Add(commit); + if (commit.Parents.Count() > 1) + { + mainlineVersion = AggregateMergeCommitIncrement(context, commit, directCommits, mainlineVersion); + } + } + + if (context.CurrentBranch.FriendlyName != "master") + { + var mergedHead = context.CurrentCommit; + 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); + // This will increment for any direct commits on master + mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion); + mainlineVersion.BuildMetaData = metaDataCalculator.Create(findMergeBase, context); + // Don't increment if the merge commit is a merge into mainline + // this ensures PR's and forward merges end up correct. + if (mergedHead.Parents.Count() == 1 || mergedHead.Parents.First() != mainlineTip) + { + 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); + mainlineVersion.BuildMetaData = metaDataCalculator.Create(baseVersion.BaseVersionSource, context); + } + + return mainlineVersion; + } + } + + SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit commit, List 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(); + 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; + }) + .Select(b => new + { + MergeBase = context.Repository.ObjectDatabase.FindMergeBase(b.Tip, context.CurrentCommit), + Branch = b + }) + .Where(a => a.MergeBase != null) + .GroupBy(b => b.MergeBase.Sha, b => b.Branch) + .ToDictionary(b => b.Key, b => b.ToList()); + + var allMainlines = mainlineBranches.Values.SelectMany(branches => branches.Select(b => b.FriendlyName)); + Logger.WriteInfo("Found possible mainline branches: " + string.Join(", ", allMainlines)); + + // Find closest mainline branch + var firstMatchingCommit = context.CurrentBranch.Commits.First(c => mainlineBranches.ContainsKey(c.Sha)); + var possibleMainlineBranches = mainlineBranches[firstMatchingCommit.Sha]; + + if (possibleMainlineBranches.Count == 1) + { + var mainlineBranch = possibleMainlineBranches[0]; + Logger.WriteInfo("Mainline for current branch is " + mainlineBranch.FriendlyName); + return mainlineBranch.Tip; + } + + var chosenMainline = possibleMainlineBranches[0]; + Logger.WriteInfo(string.Format( + "Multiple mainlines ({0}) have the same merge base for the current branch, choosing {1} because we found that branch first...", + string.Join(", ", possibleMainlineBranches.Select(b => b.FriendlyName)), + chosenMainline.FriendlyName)); + return chosenMainline.Tip; + } + + 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 = mergeCommit == null ? + context.Repository.Commits.QueryBy(filter).ToList() : + new[] { mergeCommit }.Union(context.Repository.Commits.QueryBy(filter)).ToList(); + commitLog.RemoveAll(c => commits.Any(c1 => c1.Sha == c.Sha)); + return IncrementStrategyFinder.GetIncrementForCommits(context, commits) + ?? TryFindIncrementFromMergeMessage(mergeCommit); + } + + private static VersionField TryFindIncrementFromMergeMessage(Commit mergeCommit) + { + + + // Fallback to patch + return 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(); + } + } +} diff --git a/src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs b/src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs index 771a7e4743..3cb2a5e145 100644 --- a/src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs +++ b/src/GitVersionCore/VersionCalculation/NextVersionCalculator.cs @@ -1,11 +1,8 @@ namespace GitVersion.VersionCalculation { - using System; - using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using BaseVersionCalculators; - using LibGit2Sharp; public class NextVersionCalculator { @@ -45,7 +42,10 @@ public SemanticVersion FindVersion(GitVersionContext context) var baseVersion = baseVersionFinder.GetBaseVersion(context); SemanticVersion semver; if (context.Configuration.VersioningMode == VersioningMode.Mainline) - semver = FindMainlineModeVersion(baseVersion, context); + { + var mainlineMode = new MainlineVersionCalculator(metaDataCalculator); + semver = mainlineMode.FindMainlineModeVersion(baseVersion, context); + } else { semver = PerformIncrement(context, baseVersion); @@ -82,191 +82,6 @@ private static SemanticVersion PerformIncrement(GitVersionContext context, BaseV return semver; } - 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; - - // Forward merge / PR - // * feature/foo - // / | - // master * * - // - - var mainlineTip = GetMainlineTip(context); - var commitsNotOnMainline = context.Repository.Commits.QueryBy(new CommitFilter - { - IncludeReachableFrom = context.CurrentBranch, - ExcludeReachableFrom = mainlineTip, - SortBy = CommitSortStrategies.Reverse, - FirstParentOnly = true - }).Where(c => c.Sha != baseVersion.BaseVersionSource.Sha && c.Parents.Count() == 1).ToList(); - var commitLog = context.Repository.Commits.QueryBy(new CommitFilter - { - IncludeReachableFrom = context.CurrentBranch, - ExcludeReachableFrom = baseVersion.BaseVersionSource, - SortBy = CommitSortStrategies.Reverse, - FirstParentOnly = true - }) - .Where(c => c.Sha != baseVersion.BaseVersionSource.Sha) - .Except(commitsNotOnMainline) - .ToList(); - - var directCommits = new List(); - - // Scans commit log in reverse, aggregating merge commits - foreach (var commit in commitLog) - { - directCommits.Add(commit); - if (commit.Parents.Count() > 1) - { - mainlineVersion = AggregateMergeCommitIncrement(context, commit, directCommits, mainlineVersion); - } - } - - if (context.CurrentBranch.FriendlyName != "master") - { - var mergedHead = context.CurrentCommit; - 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); - // This will increment for any direct commits on master - mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion); - mainlineVersion.BuildMetaData = metaDataCalculator.Create(findMergeBase, context); - // Don't increment if the merge commit is a merge into mainline - // this ensures PR's and forward merges end up correct. - if (mergedHead.Parents.Count() == 1 || mergedHead.Parents.First() != mainlineTip) - { - 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); - mainlineVersion.BuildMetaData = metaDataCalculator.Create(baseVersion.BaseVersionSource, context); - } - - return mainlineVersion; - } - } - - SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit commit, List 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(); - 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; - }) - .Select(b => new - { - MergeBase = context.Repository.ObjectDatabase.FindMergeBase(b.Tip, context.CurrentCommit), - Branch = b - }) - .Where(a => a.MergeBase != null) - .GroupBy(b => b.MergeBase.Sha, b => b.Branch) - .ToDictionary(b => b.Key, b => b.ToList()); - - var allMainlines = mainlineBranches.Values.SelectMany(branches => branches.Select(b => b.FriendlyName)); - Logger.WriteInfo("Found possible mainline branches: " + string.Join(", ", allMainlines)); - - // Find closest mainline branch - var firstMatchingCommit = context.CurrentBranch.Commits.First(c => mainlineBranches.ContainsKey(c.Sha)); - var possibleMainlineBranches = mainlineBranches[firstMatchingCommit.Sha]; - - if (possibleMainlineBranches.Count == 1) - { - var mainlineBranch = possibleMainlineBranches[0]; - Logger.WriteInfo("Mainline for current branch is " + mainlineBranch.FriendlyName); - return mainlineBranch.Tip; - } - - var chosenMainline = possibleMainlineBranches[0]; - Logger.WriteInfo(string.Format( - "Multiple mainlines ({0}) have the same merge base for the current branch, choosing {1} because we found that branch first...", - string.Join(", ", possibleMainlineBranches.Select(b => b.FriendlyName)), - chosenMainline.FriendlyName)); - return chosenMainline.Tip; - } - - 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 = mergeCommit == null ? - context.Repository.Commits.QueryBy(filter).ToList() : - 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); From 286ec6901e6d5225c9f299d3c42b2e0c1be4418b Mon Sep 17 00:00:00 2001 From: Jake Ginnivan Date: Sun, 21 May 2017 12:59:44 +0800 Subject: [PATCH 3/5] Remove a merge message false positive test This example is super hard to support, the way we supported it was by hard coding branch prefixes. Instead scenarios like this should use the ignore-sha feature to remove the merge message from version calculations. --- .../Strategies/MergeMessageBaseVersionStrategyTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs b/src/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs index 8bab66b5c1..6890b1080f 100644 --- a/src/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs +++ b/src/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs @@ -82,14 +82,12 @@ Another commit message Commit message including a IP-number https://10.50.1.1 A commit message")] [TestCase(@"Merge branch 'release/Sprint_2.0_Holdings_Computed_Balances'")] - [TestCase(@"Merge branch 'feature/fix-for-08.14-push'")] [TestCase(@"Merge branch 'develop' of http://10.0.6.3/gitblit/r/... into develop")] [TestCase(@"Merge branch 'master' of http://172.16.3.10:8082/r/asu_tk/p_sd")] [TestCase(@"Merge branch 'master' of http://212.248.89.56:8082/r/asu_tk/p_sd")] [TestCase(@"Merge branch 'DEMO' of http://10.10.10.121/gitlab/mtolland/orcid into DEMO")] public void MergeMessagesThatIsNotRelatedToGitVersion(string commitMessage) { - var parents = GetParents(true); AssertMergeMessage(commitMessage, null, parents); From 047de5ca96828ef63ef14e28f3c90ada11a11bb4 Mon Sep 17 00:00:00 2001 From: Jake Ginnivan Date: Sun, 21 May 2017 13:29:45 +0800 Subject: [PATCH 4/5] Simplified getting branch configuration --- .../BranchConfigurationCalculator.cs | 51 ++++--------------- src/GitVersionCore/Configuration/Config.cs | 29 +++++++++++ src/GitVersionCore/MergeMessage.cs | 2 +- 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/GitVersionCore/BranchConfigurationCalculator.cs b/src/GitVersionCore/BranchConfigurationCalculator.cs index 97154e88f1..c7ac864c1d 100644 --- a/src/GitVersionCore/BranchConfigurationCalculator.cs +++ b/src/GitVersionCore/BranchConfigurationCalculator.cs @@ -13,50 +13,21 @@ public class BranchConfigurationCalculator /// public static BranchConfig GetBranchConfiguration(GitVersionContext context, Branch targetBranch, IList excludedInheritBranches = null) { - var matchingBranches = LookupBranchConfiguration(context.FullConfiguration, targetBranch).ToArray(); - - BranchConfig branchConfiguration; - if (matchingBranches.Length > 0) - { - branchConfiguration = matchingBranches[0]; - - if (matchingBranches.Length > 1) - { - Logger.WriteWarning(string.Format( - "Multiple branch configurations match the current branch branchName of '{0}'. Using the first matching configuration, '{1}'. Matching configurations include: '{2}'", - targetBranch.FriendlyName, - branchConfiguration.Name, - string.Join("', '", matchingBranches.Select(b => b.Name)))); - } - } - else + var matchingBranches = context.FullConfiguration.GetConfigForBranch(targetBranch.FriendlyName); + + if (matchingBranches == null) { Logger.WriteInfo(string.Format( "No branch configuration found for branch {0}, falling back to default configuration", targetBranch.FriendlyName)); - branchConfiguration = new BranchConfig { Name = string.Empty }; - ConfigurationProvider.ApplyBranchDefaults(context.FullConfiguration, branchConfiguration, ""); - } - - return branchConfiguration.Increment == IncrementStrategy.Inherit ? - InheritBranchConfiguration(context, targetBranch, branchConfiguration, excludedInheritBranches) : - branchConfiguration; - } - - static IEnumerable LookupBranchConfiguration(Config config, Branch currentBranch) - { - if (config == null) - { - throw new ArgumentNullException(nameof(config)); - } - - if (currentBranch == null) - { - throw new ArgumentNullException(nameof(currentBranch)); + matchingBranches = new BranchConfig { Name = string.Empty }; + ConfigurationProvider.ApplyBranchDefaults(context.FullConfiguration, matchingBranches, ""); } - return config.Branches.Where(b => Regex.IsMatch(currentBranch.FriendlyName, "^" + b.Value.Regex, RegexOptions.IgnoreCase)).Select(kvp => kvp.Value); + return matchingBranches.Increment == IncrementStrategy.Inherit ? + InheritBranchConfiguration(context, targetBranch, matchingBranches, excludedInheritBranches) : + matchingBranches; } static BranchConfig InheritBranchConfiguration(GitVersionContext context, Branch targetBranch, BranchConfig branchConfiguration, IList excludedInheritBranches) @@ -77,11 +48,9 @@ static BranchConfig InheritBranchConfiguration(GitVersionContext context, Branch { excludedInheritBranches = repository.Branches.Where(b => { - var branchConfig = LookupBranchConfiguration(config, b).ToArray(); + var branchConfig = config.GetConfigForBranch(b.FriendlyName); - // NOTE: if length is 0 we couldn't find the configuration for the branch e.g. "origin/master" - // NOTE: if the length is greater than 1 we cannot decide which merge strategy to pick - return (branchConfig.Length != 1) || (branchConfig.Length == 1 && branchConfig[0].Increment == IncrementStrategy.Inherit); + return branchConfig != null && branchConfig.Increment == IncrementStrategy.Inherit; }).ToList(); } // Add new excluded branches. diff --git a/src/GitVersionCore/Configuration/Config.cs b/src/GitVersionCore/Configuration/Config.cs index 8cd94e083c..a5d6dcecf8 100644 --- a/src/GitVersionCore/Configuration/Config.cs +++ b/src/GitVersionCore/Configuration/Config.cs @@ -1,8 +1,10 @@ namespace GitVersion { + using System; using System.Collections.Generic; using System.Globalization; using System.Linq; + using System.Text.RegularExpressions; using YamlDotNet.Serialization; public class Config @@ -89,6 +91,33 @@ public Dictionary Branches } } + public BranchConfig GetConfigForBranch(string branchName) + { + if (branchName == null) throw new ArgumentNullException(nameof(branchName)); + var matches = Branches + .Where(b => Regex.IsMatch(branchName, "^" + b.Value.Regex, RegexOptions.IgnoreCase)); + + try + { + return matches + .Select(kvp => kvp.Value) + .SingleOrDefault(); + } + catch (InvalidOperationException) + { + var matchingConfigs = string.Join("\n - ", matches.Select(m => m.Key)); + var picked = matches + .Select(kvp => kvp.Value) + .First(); + + Logger.WriteWarning( + $"Multiple branch configurations match the current branch branchName of '{branchName}'. " + + $"Using the first matching configuration, '{picked}'. Matching configurations include: '{matchingConfigs}'"); + + return picked; + } + } + T MergeObjects(T target, T source) { typeof(T).GetProperties() diff --git a/src/GitVersionCore/MergeMessage.cs b/src/GitVersionCore/MergeMessage.cs index d9bd26c959..1dd5ecc3ac 100644 --- a/src/GitVersionCore/MergeMessage.cs +++ b/src/GitVersionCore/MergeMessage.cs @@ -31,7 +31,7 @@ public MergeMessage(string mergeMessage, Config config) } MergedBranch = ParseBranch(); - + // Remove remotes and branch prefixes like release/ feature/ hotfix/ etc var toMatch = Regex.Replace(MergedBranch, @"^(\w+[-/])*", "", RegexOptions.IgnoreCase); toMatch = Regex.Replace(toMatch, $"^{config.TagPrefix}", ""); From 83e150ffad34dad49419ac52d79a30e23d0d6b2f Mon Sep 17 00:00:00 2001 From: Jake Ginnivan Date: Sun, 21 May 2017 13:30:30 +0800 Subject: [PATCH 5/5] When a branch is merged in mainline, try to get the increment configuration for the merged branch --- .../MainlineDevelopmentMode.cs | 38 ++++++++++++++++++- .../MainlineVersionCalculator.cs | 17 +++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs b/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs index 203085f3d0..8e06d19e9a 100644 --- a/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs +++ b/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs @@ -6,6 +6,7 @@ using GitVersionCore.Tests; using LibGit2Sharp; using NUnit.Framework; +using System.Collections.Generic; public class MainlineDevelopmentMode { @@ -206,7 +207,7 @@ public void VerifyMergingMasterToFeatureDoesNotCauseBranchCommitsToIncrementVers fixture.BranchTo("feature/foo", "foo"); fixture.MakeACommit("first in foo"); - + fixture.Checkout("master"); fixture.MakeACommit("second in master"); @@ -303,6 +304,41 @@ public void VerifyMergingMasterIntoAFeatureBranchWorksWithMultipleBranches() fixture.AssertFullSemver(config, "1.0.2"); } } + + [Test] + public void MergingFeatureBranchThatIncrementsMinorNumberIncrementsMinorVersionOfMaster() + { + var currentConfig = new Config + { + VersioningMode = VersioningMode.Mainline, + Branches = new Dictionary + { + { + "feature", new BranchConfig + { + VersioningMode = VersioningMode.ContinuousDeployment, + Increment = IncrementStrategy.Minor + } + } + } + }; + + using (var fixture = new EmptyRepositoryFixture()) + { + fixture.MakeACommit("first in master"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.AssertFullSemver(currentConfig, "1.0.0"); + + fixture.BranchTo("feature/foo", "foo"); + fixture.MakeACommit("first in foo"); + fixture.MakeACommit("second in foo"); + fixture.AssertFullSemver(currentConfig, "1.1.0-foo.2"); + + fixture.Checkout("master"); + fixture.MergeNoFF("feature/foo"); + fixture.AssertFullSemver(currentConfig, "1.1.0"); + } + } } static class CommitExtensions diff --git a/src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs b/src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs index 75ceb76605..4d27c77d79 100644 --- a/src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs +++ b/src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs @@ -192,12 +192,23 @@ private static VersionField FindMessageIncrement( new[] { mergeCommit }.Union(context.Repository.Commits.QueryBy(filter)).ToList(); commitLog.RemoveAll(c => commits.Any(c1 => c1.Sha == c.Sha)); return IncrementStrategyFinder.GetIncrementForCommits(context, commits) - ?? TryFindIncrementFromMergeMessage(mergeCommit); + ?? TryFindIncrementFromMergeMessage(mergeCommit, context); } - private static VersionField TryFindIncrementFromMergeMessage(Commit mergeCommit) + private static VersionField TryFindIncrementFromMergeMessage(Commit mergeCommit, GitVersionContext context) { - + if (mergeCommit != null) + { + var mergeMessage = new MergeMessage(mergeCommit.Message, context.FullConfiguration); + if (mergeMessage.MergedBranch != null) + { + var config = context.FullConfiguration.GetConfigForBranch(mergeMessage.MergedBranch); + if (config != null && config.Increment.HasValue && config.Increment != IncrementStrategy.Inherit) + { + return config.Increment.Value.ToVersionField(); + } + } + } // Fallback to patch return VersionField.Patch;