diff --git a/docs/configuration.md b/docs/configuration.md index 806cf71499..7a4fc23714 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -48,6 +48,7 @@ commit-date-format: 'yyyy-MM-dd' ignore: sha: [] commits-before: yyyy-MM-ddTHH:mm:ss +merge-message-formats: {} ``` And the description of the available options are: @@ -179,6 +180,24 @@ Date and time in the format `yyyy-MM-ddTHH:mm:ss` (eg `commits-before: 2015-10-23T12:23:15`) to setup an exclusion range. Effectively any commit before `commits-before` will be ignored. +### merge-message-formats +Custom merge message formats to enable identification of merge messages that do not +follow the built-in conventions. Entries should be added as key-value pairs where +the value is a regular expression. +e.g. + +``` +merge-message-formats: + tfs: ^Merged (?:PR (?\d+)): Merge (?.+) to (?.+) +``` + +The regular expression should contain the following capture groups: ++ SourceBranch - Identifies the source branch of the merge ++ TargetBranch - Identifies the target of the merge ++ PullRequestNumber - Captures the pull-request number + +Custom merge message formats are evalauted _before_ any built in formats. + ## Branch configuration Then we have branch specific configuration, which looks something like this: diff --git a/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt b/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt index a2918846a6..0c3764505e 100644 --- a/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt +++ b/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt @@ -122,3 +122,4 @@ branches: ignore: sha: [] commit-date-format: yyyy-MM-dd +merge-message-formats: {} diff --git a/src/GitVersionCore.Tests/Init/InitScenarios.CanSetNextVersion.approved.txt b/src/GitVersionCore.Tests/Init/InitScenarios.CanSetNextVersion.approved.txt index 4d1763fcf8..0368e755e7 100644 --- a/src/GitVersionCore.Tests/Init/InitScenarios.CanSetNextVersion.approved.txt +++ b/src/GitVersionCore.Tests/Init/InitScenarios.CanSetNextVersion.approved.txt @@ -2,3 +2,4 @@ next-version: 2.0.0 branches: {} ignore: sha: [] +merge-message-formats: {} diff --git a/src/GitVersionCore.Tests/MergeMessageTests.cs b/src/GitVersionCore.Tests/MergeMessageTests.cs index 4defaec033..14b0a18514 100644 --- a/src/GitVersionCore.Tests/MergeMessageTests.cs +++ b/src/GitVersionCore.Tests/MergeMessageTests.cs @@ -2,6 +2,7 @@ using NUnit.Framework; using Shouldly; using System; +using System.Collections.Generic; namespace GitVersionCore.Tests { @@ -54,13 +55,13 @@ public void EmptyTagPrefix(string prefix) private static readonly object[] MergeMessages = { - new object[] { "Merge branch 'feature/one'", "feature/one", null, null }, - new object[] { "Merge branch 'origin/feature/one'", "origin/feature/one", null, null }, - new object[] { "Merge tag 'v4.0.0' into master", "v4.0.0", "master", new SemanticVersion(4) }, - new object[] { "Merge tag 'V4.0.0' into master", "V4.0.0", "master", new SemanticVersion(4) }, - new object[] { "Merge branch 'feature/4.1/one'", "feature/4.1/one", null, new SemanticVersion(4, 1) }, - new object[] { "Merge branch 'origin/4.1/feature/one'", "origin/4.1/feature/one", null, new SemanticVersion(4, 1) }, - new object[] { "Merge tag 'v://10.10.10.10' into master", "v://10.10.10.10", "master", null } + new object[] { "Merge branch 'feature/one'", "feature/one", null, null }, + new object[] { "Merge branch 'origin/feature/one'", "origin/feature/one", null, null }, + new object[] { "Merge tag 'v4.0.0' into master", "v4.0.0", "master", new SemanticVersion(4) }, + new object[] { "Merge tag 'V4.0.0' into master", "V4.0.0", "master", new SemanticVersion(4) }, + new object[] { "Merge branch 'feature/4.1/one'", "feature/4.1/one", null, new SemanticVersion(4, 1) }, + new object[] { "Merge branch 'origin/4.1/feature/one'", "origin/4.1/feature/one", null, new SemanticVersion(4, 1) }, + new object[] { "Merge tag 'v://10.10.10.10' into master", "v://10.10.10.10", "master", null } }; [TestCaseSource(nameof(MergeMessages))] @@ -74,7 +75,7 @@ public void ParsesMergeMessage( var sut = new MergeMessage(message, _config); // Assert - sut.MatchDefinition.ShouldBe("Default"); + sut.FormatName.ShouldBe("Default"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeFalse(); @@ -84,13 +85,13 @@ public void ParsesMergeMessage( private static readonly object[] GitHubPullPullMergeMessages = { - new object[] { "Merge pull request #1234 from feature/one", "feature/one", null, null, 1234 }, - new object[] { "Merge pull request #1234 in feature/one", "feature/one", null, null, 1234 }, - new object[] { "Merge pull request #1234 in v4.0.0", "v4.0.0", null, new SemanticVersion(4), 1234 }, - new object[] { "Merge pull request #1234 from origin/feature/one", "origin/feature/one", null, null, 1234 }, - new object[] { "Merge pull request #1234 in feature/4.1/one", "feature/4.1/one", null, new SemanticVersion(4,1), 1234 }, - new object[] { "Merge pull request #1234 in V://10.10.10.10", "V://10.10.10.10", null, null, 1234 }, - new object[] { "Merge pull request #1234 from feature/one into dev", "feature/one", "dev", null, 1234 } + new object[] { "Merge pull request #1234 from feature/one", "feature/one", null, null, 1234 }, + new object[] { "Merge pull request #1234 in feature/one", "feature/one", null, null, 1234 }, + new object[] { "Merge pull request #1234 in v4.0.0", "v4.0.0", null, new SemanticVersion(4), 1234 }, + new object[] { "Merge pull request #1234 from origin/feature/one", "origin/feature/one", null, null, 1234 }, + new object[] { "Merge pull request #1234 in feature/4.1/one", "feature/4.1/one", null, new SemanticVersion(4,1), 1234 }, + new object[] { "Merge pull request #1234 in V://10.10.10.10", "V://10.10.10.10", null, null, 1234 }, + new object[] { "Merge pull request #1234 from feature/one into dev", "feature/one", "dev", null, 1234 } }; [TestCaseSource(nameof(GitHubPullPullMergeMessages))] @@ -105,7 +106,7 @@ public void ParsesGitHubPullMergeMessage( var sut = new MergeMessage(message, _config); // Assert - sut.MatchDefinition.ShouldBe("GitHubPull"); + sut.FormatName.ShouldBe("GitHubPull"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeTrue(); @@ -115,16 +116,16 @@ public void ParsesGitHubPullMergeMessage( private static readonly object[] BitBucketPullMergeMessages = { - new object[] { "Merge pull request #1234 from feature/one from feature/two to dev", "feature/two", "dev", null, 1234 }, - new object[] { "Merge pull request #1234 in feature/one from feature/two to dev", "feature/two", "dev", null, 1234 }, - new object[] { "Merge pull request #1234 in v4.0.0 from v4.1.0 to dev", "v4.1.0", "dev", new SemanticVersion(4,1), 1234 }, - new object[] { "Merge pull request #1234 from origin/feature/one from origin/feature/4.2/two to dev", "origin/feature/4.2/two", "dev", new SemanticVersion(4,2), 1234 }, - new object[] { "Merge pull request #1234 in feature/4.1/one from feature/4.2/two to dev", "feature/4.2/two", "dev", new SemanticVersion(4,2), 1234 }, - new object[] { "Merge pull request #1234 from feature/one from feature/two to master" , "feature/two", "master", null, 1234 }, - new object[] { "Merge pull request #1234 in V4.1.0 from V://10.10.10.10 to dev", "V://10.10.10.10", "dev", null, 1234 }, - //TODO: Investigate successful bitbucket merge messages that may be invalid - // Regex has double 'from/in from' section. Is that correct? - new object[] { "Merge pull request #1234 from feature/one from v4.0.0 to master", "v4.0.0", "master", new SemanticVersion(4), 1234 } + new object[] { "Merge pull request #1234 from feature/one from feature/two to dev", "feature/two", "dev", null, 1234 }, + new object[] { "Merge pull request #1234 in feature/one from feature/two to dev", "feature/two", "dev", null, 1234 }, + new object[] { "Merge pull request #1234 in v4.0.0 from v4.1.0 to dev", "v4.1.0", "dev", new SemanticVersion(4,1), 1234 }, + new object[] { "Merge pull request #1234 from origin/feature/one from origin/feature/4.2/two to dev", "origin/feature/4.2/two", "dev", new SemanticVersion(4,2), 1234 }, + new object[] { "Merge pull request #1234 in feature/4.1/one from feature/4.2/two to dev", "feature/4.2/two", "dev", new SemanticVersion(4,2), 1234 }, + new object[] { "Merge pull request #1234 from feature/one from feature/two to master" , "feature/two", "master", null, 1234 }, + new object[] { "Merge pull request #1234 in V4.1.0 from V://10.10.10.10 to dev", "V://10.10.10.10", "dev", null, 1234 }, + //TODO: Investigate successful bitbucket merge messages that may be invalid + // Regex has double 'from/in from' section. Is that correct? + new object[] { "Merge pull request #1234 from feature/one from v4.0.0 to master", "v4.0.0", "master", new SemanticVersion(4), 1234 } }; [TestCaseSource(nameof(BitBucketPullMergeMessages))] @@ -139,7 +140,7 @@ public void ParsesBitBucketPullMergeMessage( var sut = new MergeMessage(message, _config); // Assert - sut.MatchDefinition.ShouldBe("BitBucketPull"); + sut.FormatName.ShouldBe("BitBucketPull"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeTrue(); @@ -150,27 +151,27 @@ public void ParsesBitBucketPullMergeMessage( private static readonly object[] SmartGitMergeMessages = { - new object[] { "Finish feature/one", "feature/one", null, null }, - new object[] { "Finish origin/feature/one", "origin/feature/one", null, null }, - new object[] { "Finish v4.0.0", "v4.0.0", null, new SemanticVersion(4) }, - new object[] { "Finish feature/4.1/one", "feature/4.1/one", null, new SemanticVersion(4, 1) }, - new object[] { "Finish origin/4.1/feature/one", "origin/4.1/feature/one", null, new SemanticVersion(4, 1) }, - new object[] { "Finish V://10.10.10.10", "V://10.10.10.10", null, null }, - new object[] { "Finish V4.0.0 into master", "V4.0.0", "master", new SemanticVersion(4) } + new object[] { "Finish feature/one", "feature/one", null, null }, + new object[] { "Finish origin/feature/one", "origin/feature/one", null, null }, + new object[] { "Finish v4.0.0", "v4.0.0", null, new SemanticVersion(4) }, + new object[] { "Finish feature/4.1/one", "feature/4.1/one", null, new SemanticVersion(4, 1) }, + new object[] { "Finish origin/4.1/feature/one", "origin/4.1/feature/one", null, new SemanticVersion(4, 1) }, + new object[] { "Finish V://10.10.10.10", "V://10.10.10.10", null, null }, + new object[] { "Finish V4.0.0 into master", "V4.0.0", "master", new SemanticVersion(4) } }; [TestCaseSource(nameof(SmartGitMergeMessages))] public void ParsesSmartGitMergeMessage( - string message, - string expectedMergedBranch, - string expectedTargetBranch, - SemanticVersion expectedVersion) + string message, + string expectedMergedBranch, + string expectedTargetBranch, + SemanticVersion expectedVersion) { // Act var sut = new MergeMessage(message, _config); // Assert - sut.MatchDefinition.ShouldBe("SmartGit"); + sut.FormatName.ShouldBe("SmartGit"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeFalse(); @@ -180,27 +181,27 @@ public void ParsesSmartGitMergeMessage( private static readonly object[] RemoteTrackingMergeMessages = { - new object[] { "Merge remote-tracking branch 'feature/one' into master", "feature/one", "master", null }, - new object[] { "Merge remote-tracking branch 'origin/feature/one' into dev", "origin/feature/one", "dev", null }, - new object[] { "Merge remote-tracking branch 'v4.0.0' into master", "v4.0.0", "master", new SemanticVersion(4) }, - new object[] { "Merge remote-tracking branch 'V4.0.0' into master", "V4.0.0", "master", new SemanticVersion(4) }, - new object[] { "Merge remote-tracking branch 'feature/4.1/one' into dev", "feature/4.1/one", "dev", new SemanticVersion(4, 1) }, - new object[] { "Merge remote-tracking branch 'origin/4.1/feature/one' into master", "origin/4.1/feature/one", "master", new SemanticVersion(4, 1) }, - new object[] { "Merge remote-tracking branch 'v://10.10.10.10' into master", "v://10.10.10.10", "master", null } + new object[] { "Merge remote-tracking branch 'feature/one' into master", "feature/one", "master", null }, + new object[] { "Merge remote-tracking branch 'origin/feature/one' into dev", "origin/feature/one", "dev", null }, + new object[] { "Merge remote-tracking branch 'v4.0.0' into master", "v4.0.0", "master", new SemanticVersion(4) }, + new object[] { "Merge remote-tracking branch 'V4.0.0' into master", "V4.0.0", "master", new SemanticVersion(4) }, + new object[] { "Merge remote-tracking branch 'feature/4.1/one' into dev", "feature/4.1/one", "dev", new SemanticVersion(4, 1) }, + new object[] { "Merge remote-tracking branch 'origin/4.1/feature/one' into master", "origin/4.1/feature/one", "master", new SemanticVersion(4, 1) }, + new object[] { "Merge remote-tracking branch 'v://10.10.10.10' into master", "v://10.10.10.10", "master", null } }; [TestCaseSource(nameof(RemoteTrackingMergeMessages))] public void ParsesRemoteTrackingMergeMessage( - string message, - string expectedMergedBranch, - string expectedTargetBranch, - SemanticVersion expectedVersion) + string message, + string expectedMergedBranch, + string expectedTargetBranch, + SemanticVersion expectedVersion) { // Act var sut = new MergeMessage(message, _config); // Assert - sut.MatchDefinition.ShouldBe("RemoteTracking"); + sut.FormatName.ShouldBe("RemoteTracking"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeFalse(); @@ -208,88 +209,131 @@ public void ParsesRemoteTrackingMergeMessage( sut.Version.ShouldBe(expectedVersion); } - private static readonly object[] ParsesTfsEnglishUSMergeMessages = + private static readonly object[] InvalidMergeMessages = { - new object[] { "Merge feature/one to master", "feature/one", "master", null }, - new object[] { "Merge v://10.10.10.10 to master", "v://10.10.10.10", "master", null }, - new object[] { "Merge feature/one to v://10.10.10.10", "feature/one", "v://10.10.10.10", null }, - new object[] { "Merge V4.0.0 to master", "V4.0.0", "master", new SemanticVersion(4) }, - new object[] { "Merge feature/4.1/one to master", "feature/4.1/one", "master", new SemanticVersion(4, 1) } - }; + new object[] { "Merge pull request # from feature/one", "", null, null, null }, + new object[] { "Merge pull request # in feature/one from feature/two to master" , "", null, null, null }, + new object[] { "Zusammengeführter PR : feature/one mit master mergen", "", null, null, null } + }; - [TestCaseSource(nameof(ParsesTfsEnglishUSMergeMessages))] - public void ParsesTfsEnglishUSMessage( - string message, - string expectedMergedBranch, - string expectedTargetBranch, - SemanticVersion expectedVersion) + [TestCaseSource(nameof(InvalidMergeMessages))] + public void ParsesInvalidMergeMessage( + string message, + string expectedMergedBranch, + string expectedTargetBranch, + SemanticVersion expectedVersion, + int? expectedPullRequestNumber) { // Act var sut = new MergeMessage(message, _config); // Assert - sut.MatchDefinition.ShouldBe("TfsMergeMessageEnglishUS"); + sut.FormatName.ShouldBeNull(); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeFalse(); - sut.PullRequestNumber.ShouldBeNull(); + sut.PullRequestNumber.ShouldBe(expectedPullRequestNumber); sut.Version.ShouldBe(expectedVersion); } - private static readonly object[] ParsesTfsGermanDEMergeMessages = + [Test] + public void MatchesSingleCustomMessage() { - new object[] { "Zusammengeführter PR \"1234\": feature/one mit master mergen", "feature/one", "master", null, 1234 }, - new object[] { "Zusammengeführter PR \"1234\": v://10.10.10.10 mit master mergen", "v://10.10.10.10", "master", null, 1234 }, - new object[] { "Zusammengeführter PR \"1234\": feature/one mit v://10.10.10.10 mergen", "feature/one", "v://10.10.10.10", null, 1234 }, - new object[] { "Zusammengeführter PR \"1234\": V4.0.0 mit master mergen", "V4.0.0", "master", new SemanticVersion(4), 1234 }, - new object[] { "Zusammengeführter PR \"1234\": feature/4.1/one mit master mergen", "feature/4.1/one", "master", new SemanticVersion(4, 1), 1234 } - }; + // Arrange + var message = "My custom message"; + var definition = "Mycustom"; + _config.MergeMessageFormats = new Dictionary + { + [definition] = message + }; - [TestCaseSource(nameof(ParsesTfsGermanDEMergeMessages))] - public void ParseTfsGermanDEMessage( - string message, - string expectedMergedBranch, - string expectedTargetBranch, - SemanticVersion expectedVersion, - int? expectedPullRequestNumber) - { // Act var sut = new MergeMessage(message, _config); // Assert - sut.MatchDefinition.ShouldBe("TfsMergeMessageGermanDE"); - sut.TargetBranch.ShouldBe(expectedTargetBranch); - sut.MergedBranch.ShouldBe(expectedMergedBranch); - sut.IsMergedPullRequest.ShouldBeTrue(); - sut.PullRequestNumber.ShouldBe(expectedPullRequestNumber); - sut.Version.ShouldBe(expectedVersion); + sut.FormatName.ShouldBe(definition); + sut.TargetBranch.ShouldBeNull(); + sut.MergedBranch.ShouldBeEmpty(); + sut.IsMergedPullRequest.ShouldBeFalse(); + sut.PullRequestNumber.ShouldBeNull(); + sut.Version.ShouldBeNull(); } - private static readonly object[] InvalidMergeMessages = + [Test] + public void MatchesMultipleCustomMessages() { - new object[] { "Merge pull request # from feature/one", "", null, null, null }, - new object[] { "Merge pull request # in feature/one from feature/two to master" , "", null, null, null }, - new object[] { "Zusammengeführter PR : feature/one mit master mergen", "", null, null, null } - }; + // Arrange + var format = "My custom message"; + var definition = "Mycustom"; + _config.MergeMessageFormats = new Dictionary + { + ["Default2"] = "some example", + ["Default3"] = "another example", + [definition] = format + }; - [TestCaseSource(nameof(InvalidMergeMessages))] - public void ParsesInvalidMergeMessage( - string message, - string expectedMergedBranch, - string expectedTargetBranch, - SemanticVersion expectedVersion, - int? expectedPullRequestNumber) + // Act + var sut = new MergeMessage(format, _config); + + // Assert + sut.FormatName.ShouldBe(definition); + sut.TargetBranch.ShouldBeNull(); + sut.MergedBranch.ShouldBeEmpty(); + sut.IsMergedPullRequest.ShouldBeFalse(); + sut.PullRequestNumber.ShouldBeNull(); + sut.Version.ShouldBeNull(); + } + + [Test] + public void MatchesCaptureGroupsFromCustomMessages() + { + // Arrange + var format = @"^Merged PR #(?\d+) into (?[^\s]*) from (?:(?[^\s]*))"; + var definition = "Mycustom"; + _config.MergeMessageFormats = new Dictionary + { + [definition] = format + }; + var pr = 1234; + var target = "master"; + var source = "feature/2.0/example"; + + + // Act + var sut = new MergeMessage($"Merged PR #{pr} into {target} from {source}", _config); + + // Assert + sut.FormatName.ShouldBe(definition); + sut.TargetBranch.ShouldBe(target); + sut.MergedBranch.ShouldBe(source); + sut.IsMergedPullRequest.ShouldBeTrue(); + sut.PullRequestNumber.ShouldBe(pr); + sut.Version.ShouldBe(new SemanticVersion(2, 0)); + } + + [Test] + public void ReturnsAfterFirstMatchingPattern() { + // Arrange + var format = @"^Merge (branch|tag) '(?[^']*)'(?: into (?[^\s]*))*"; + var definition = "Mycustom"; + _config.MergeMessageFormats = new Dictionary + { + [definition] = format, + ["Default2"] = format, + ["Default3"] = format + }; + // Act - var sut = new MergeMessage(message, _config); + var sut = new MergeMessage("Merge branch 'this'", _config); // Assert - sut.MatchDefinition.ShouldBeNull(); - sut.TargetBranch.ShouldBe(expectedTargetBranch); - sut.MergedBranch.ShouldBe(expectedMergedBranch); + sut.FormatName.ShouldBe(definition); + sut.TargetBranch.ShouldBeNull(); + sut.MergedBranch.ShouldBe("this"); sut.IsMergedPullRequest.ShouldBeFalse(); - sut.PullRequestNumber.ShouldBe(expectedPullRequestNumber); - sut.Version.ShouldBe(expectedVersion); + sut.PullRequestNumber.ShouldBeNull(); + sut.Version.ShouldBeNull(); } } } diff --git a/src/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs b/src/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs index 099fc536ba..b5fe2e3e43 100644 --- a/src/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs +++ b/src/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs @@ -47,8 +47,6 @@ public void ShouldNotAllowIncrementOfVersion() [TestCase("Merge branch 'Release-v2.2'", true, "2.2.0")] [TestCase("Merge remote-tracking branch 'origin/release/0.8.0' into develop/master", true, "0.8.0")] [TestCase("Merge remote-tracking branch 'refs/remotes/origin/release/2.0.0'", true, "2.0.0")] - [TestCase("Merge release/5.1.0 to master", true, "5.1.0")] // Team Foundation Server 2017 default merge message (en-US) - [TestCase("Zusammengeführter PR \"9\": release/5.1.0 mit master mergen", true, "5.1.0")] // Team Foundation Server 2017 default merge message (de-DE) public void TakesVersionFromMergeOfReleaseBranch(string message, bool isMergeCommit, string expectedVersion) { var parents = GetParents(isMergeCommit); diff --git a/src/GitVersionCore/Configuration/Config.cs b/src/GitVersionCore/Configuration/Config.cs index ab2ac963b2..71a4f0a7ec 100644 --- a/src/GitVersionCore/Configuration/Config.cs +++ b/src/GitVersionCore/Configuration/Config.cs @@ -145,5 +145,8 @@ T MergeObjects(T target, T source) [YamlMember(Alias = "commit-date-format")] public string CommitDateFormat { get; set; } + + [YamlMember(Alias = "merge-message-formats")] + public Dictionary MergeMessageFormats { get; set; } = new Dictionary(); } } diff --git a/src/GitVersionCore/MergeMessage.cs b/src/GitVersionCore/MergeMessage.cs index accad9b726..831e3cb31b 100644 --- a/src/GitVersionCore/MergeMessage.cs +++ b/src/GitVersionCore/MergeMessage.cs @@ -1,20 +1,19 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace GitVersion { - class MergeMessage + public class MergeMessage { - private static readonly IList Patterns = new List + private static readonly IList DefaultFormats = new List { - new MergeMessagePattern("Default", @"^Merge (branch|tag) '(?[^']*)'(?: into (?[^\s]*))*"), - new MergeMessagePattern("SmartGit", @"^Finish (?[^\s]*)(?: into (?[^\s]*))*"), - new MergeMessagePattern("BitBucketPull", @"^Merge pull request #(?\d+) (from|in) (?.*) from (?[^\s]*) to (?[^\s]*)"), - new MergeMessagePattern("GitHubPull", @"^Merge pull request #(?\d+) (from|in) (?:(?[^\s]*))(?: into (?[^\s]*))*"), - new MergeMessagePattern("RemoteTracking", @"^Merge remote-tracking branch '(?[^\s]*)'(?: into (?[^\s]*))*"), - new MergeMessagePattern("TfsMergeMessageEnglishUS", @"^Merge (?[^\s]*) to (?[^\s]*)"), - new MergeMessagePattern("TfsMergeMessageGermanDE",@"^Zusammengeführter PR ""(?\d+)""\: (?.*) mit (?.*) mergen") + new MergeMessageFormat("Default", @"^Merge (branch|tag) '(?[^']*)'(?: into (?[^\s]*))*"), + new MergeMessageFormat("SmartGit", @"^Finish (?[^\s]*)(?: into (?[^\s]*))*"), + new MergeMessageFormat("BitBucketPull", @"^Merge pull request #(?\d+) (from|in) (?.*) from (?[^\s]*) to (?[^\s]*)"), + new MergeMessageFormat("GitHubPull", @"^Merge pull request #(?\d+) (from|in) (?:(?[^\s]*))(?: into (?[^\s]*))*"), + new MergeMessageFormat("RemoteTracking", @"^Merge remote-tracking branch '(?[^\s]*)'(?: into (?[^\s]*))*") }; public MergeMessage(string mergeMessage, Config config) @@ -22,12 +21,18 @@ public MergeMessage(string mergeMessage, Config config) if (mergeMessage == null) throw new NullReferenceException(); - foreach (var pattern in Patterns) + // Concat config formats with the defaults. + // Ensure configs are processed first. + var allFormats = config.MergeMessageFormats + .Select(x => new MergeMessageFormat(x.Key, x.Value)) + .Concat(DefaultFormats); + + foreach (var format in allFormats) { - var match = pattern.Format.Match(mergeMessage); + var match = format.Pattern.Match(mergeMessage); if (match.Success) { - MatchDefinition = pattern.Name; + FormatName = format.Name; MergedBranch = match.Groups["SourceBranch"].Value; if (match.Groups["TargetBranch"].Success) @@ -40,22 +45,21 @@ public MergeMessage(string mergeMessage, Config config) PullRequestNumber = pullNumber; } - Version = ParseVersion(MergedBranch, config.TagPrefix); + Version = ParseVersion(config.TagPrefix); break; } } } - public string MatchDefinition { get; } + public string FormatName { get; } public string TargetBranch { get; } public string MergedBranch { get; } = ""; public bool IsMergedPullRequest => PullRequestNumber != null; public int? PullRequestNumber { get; } public SemanticVersion Version { get; } - - private SemanticVersion ParseVersion(string branchName, string tagPrefix) + private SemanticVersion ParseVersion(string tagPrefix) { // Remove remotes and branch prefixes like release/ feature/ hotfix/ etc var toMatch = Regex.Replace(MergedBranch, @"^(\w+[-/])*", "", RegexOptions.IgnoreCase); @@ -72,17 +76,17 @@ private SemanticVersion ParseVersion(string branchName, string tagPrefix) return null; } - private class MergeMessagePattern + private class MergeMessageFormat { - public MergeMessagePattern(string name, string format) + public MergeMessageFormat(string name, string pattern) { Name = name; - Format = new Regex(format, RegexOptions.IgnoreCase | RegexOptions.Compiled); + Pattern = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); } public string Name { get; } - public Regex Format { get; } + public Regex Pattern { get; } } } } diff --git a/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/MergeMessageBaseVersionStrategy.cs b/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/MergeMessageBaseVersionStrategy.cs index 3b2d4f228f..f5c99450e8 100644 --- a/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/MergeMessageBaseVersionStrategy.cs +++ b/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/MergeMessageBaseVersionStrategy.cs @@ -23,6 +23,7 @@ public override IEnumerable GetVersions(GitVersionContext context) mergeMessage.Version != null && context.FullConfiguration.IsReleaseBranch(TrimRemote(mergeMessage.MergedBranch))) { + Logger.WriteInfo($"Found commit [{context.CurrentCommit.Sha}] matching merge message format: {mergeMessage.FormatName}"); var shouldIncrement = !context.Configuration.PreventIncrementForMergedBranchVersion; return new[] {