diff --git a/src/GitVersionCore.Tests/MergeMessageTests.cs b/src/GitVersionCore.Tests/MergeMessageTests.cs index 42d9ca6966..7a8e01f7fa 100644 --- a/src/GitVersionCore.Tests/MergeMessageTests.cs +++ b/src/GitVersionCore.Tests/MergeMessageTests.cs @@ -75,6 +75,7 @@ public void ParsesMergeMessage( var sut = new MergeMessage(message, _config); // Assert + sut.MatchDefinition.ShouldBe("Default"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeFalse(); @@ -91,13 +92,7 @@ public void ParsesMergeMessage( 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 }, - - - //TODO: Investigate successful github merge messages that may be invalid - // Should an empty PR number be valid? - new object[] { "Merge pull request # from feature/one", "feature/one", null, null, 0 }, - // The branch name appears to be incorrect - new object[] { "Merge pull request #1234 from feature/one into dev", "feature/one into dev", "dev", null, 1234 }, + new object[] { "Merge pull request #1234 from feature/one into dev", "feature/one", "dev", null, 1234 } }; [TestCaseSource(nameof(GitHubPullPullMergeMessages))] @@ -112,31 +107,32 @@ public void ParsesGitHubPullMergeMessage( var sut = new MergeMessage(message, _config); // Assert + sut.MatchDefinition.ShouldBe("GitHubPull"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeTrue(); sut.PullRequestNumber.ShouldBe(expectedPullRequestNumber); sut.Version.ShouldBe(expectedVersion); } - + private static readonly object[] BitBucketPullMergeMessages = { - new object[] { "Merge pull request #1234 from feature/one from feature/two to dev", "feature/two", null, null, 1234 }, - new object[] { "Merge pull request #1234 in feature/one from feature/two to dev", "feature/two", null, null, 1234 }, - new object[] { "Merge pull request #1234 in v4.0.0 from v4.1.0 to dev", "v4.1.0", null, new SemanticVersion(4,1), 1234 }, - new object[] { "Merge pull request #1234 in V4.0.0 from V4.1.0 to dev", "V4.1.0", null, 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", null, 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", null, new SemanticVersion(4,2), 1234 }, - new object[] { "Merge pull request #1234 in feature/4.1/one from feature/4.2/two to dev into master", "feature/4.2/two", "master", new SemanticVersion(4,2), 1234 }, - new object[] { "Merge pull request #1234 in V4.1.0 from V://10.10.10.10 to dev", "V://10.10.10.10", null, null, 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 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 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 in feature/4.1/one from feature/4.2/two to dev", "feature/4.2/two", null, new SemanticVersion(4,2), 1234 }, - new object[] { "Merge pull request #1234 from feature/one from v4.0.0 to master", "v4.0.0", null, new SemanticVersion(4), 1234 }, - // target branch is not resolved from targetbranch group - new object[] { "Merge pull request #1234 from feature/one from feature/two to master" , "feature/two", null, null, 1234 }, - // Should an empty PR number be valid? - new object[] { "Merge pull request # in feature/one from feature/two to master" , "feature/two", null, null, 0 } + 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 v4.0.0 to master", "v4.0.0", "master", new SemanticVersion(4), 1234 } + + + }; [TestCaseSource(nameof(BitBucketPullMergeMessages))] @@ -151,6 +147,7 @@ public void ParsesBitBucketPullMergeMessage( var sut = new MergeMessage(message, _config); // Assert + sut.MatchDefinition.ShouldBe("BitBucketPull"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeTrue(); @@ -158,6 +155,7 @@ public void ParsesBitBucketPullMergeMessage( sut.Version.ShouldBe(expectedVersion); } + private static readonly object[] SmartGitMergeMessages = { new object[] { "Finish feature/one", "feature/one", null, null }, @@ -166,10 +164,7 @@ public void ParsesBitBucketPullMergeMessage( 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 }, - - //TODO: Investigate successful smart git merge messages that may be invalid - // The branch name appears to be incorrect - new object[] { "Finish V4.0.0 into master", "V4.0.0 into master", "master", new SemanticVersion(4) } + new object[] { "Finish V4.0.0 into master", "V4.0.0", "master", new SemanticVersion(4) } }; [TestCaseSource(nameof(SmartGitMergeMessages))] @@ -183,6 +178,7 @@ public void ParsesSmartGitMergeMessage( var sut = new MergeMessage(message, _config); // Assert + sut.MatchDefinition.ShouldBe("SmartGit"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeFalse(); @@ -212,11 +208,38 @@ public void ParsesRemoteTrackingMergeMessage( var sut = new MergeMessage(message, _config); // Assert + sut.MatchDefinition.ShouldBe("RemoteTracking"); sut.TargetBranch.ShouldBe(expectedTargetBranch); sut.MergedBranch.ShouldBe(expectedMergedBranch); sut.IsMergedPullRequest.ShouldBeFalse(); sut.PullRequestNumber.ShouldBeNull(); sut.Version.ShouldBe(expectedVersion); } + + private static readonly object[] InvalidMergeMessages = + { + 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 } + }; + + [TestCaseSource(nameof(InvalidMergeMessages))] + public void ParsesInvalidBitBucketPullMergeMessage( + string message, + string expectedMergedBranch, + string expectedTargetBranch, + SemanticVersion expectedVersion, + int? expectedPullRequestNumber) + { + // Act + var sut = new MergeMessage(message, _config); + + // Assert + sut.MatchDefinition.ShouldBeNull(); + sut.TargetBranch.ShouldBe(expectedTargetBranch); + sut.MergedBranch.ShouldBe(expectedMergedBranch); + sut.IsMergedPullRequest.ShouldBeFalse(); + sut.PullRequestNumber.ShouldBe(expectedPullRequestNumber); + sut.Version.ShouldBe(expectedVersion); + } } } diff --git a/src/GitVersionCore/MergeMessage.cs b/src/GitVersionCore/MergeMessage.cs index 9c3a03a88b..86df3297e6 100644 --- a/src/GitVersionCore/MergeMessage.cs +++ b/src/GitVersionCore/MergeMessage.cs @@ -1,138 +1,86 @@ using System; +using System.Collections.Generic; 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 parseBitBucketPullMergeMessage = new Regex( - @"^Merge pull request #(?\d*) (from|in) (?.*) from (?.*) to (?.*)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - static Regex smartGitMergeMessage = new Regex( - @"^Finish (?.*)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - static Regex parseRemoteTrackingMergeMessage = new Regex( - @"^Merge remote-tracking branch '(?.*)'( into (?.*))?", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - static Regex parseTfsMergeMessageEnglishUS = new Regex( - @"^Merge (?.*) to (?.*)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - // Zusammengeführter PR \"9\": release/5.0.1 mit master mergen - static Regex parseTfsMergeMessageGermanDE = new Regex( - @"^Zusammengeführter PR ""(?\d*)""\: (?.*) mit (?.*) mergen", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - private string mergeMessage; + private static readonly IList Patterns = 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]*))*") + }; public MergeMessage(string mergeMessage, Config config) { - this.mergeMessage = mergeMessage; + if (mergeMessage == null) + throw new NullReferenceException(); - var lastIndexOf = mergeMessage.LastIndexOf("into", StringComparison.OrdinalIgnoreCase); - if (lastIndexOf != -1) + foreach (var pattern in Patterns) { - // If we have into in the merge message the rest should be the target branch - TargetBranch = mergeMessage.Substring(lastIndexOf + 5); - } + var match = pattern.Format.Match(mergeMessage); + if (match.Success) + { + MatchDefinition = pattern.Name; + MergedBranch = match.Groups["SourceBranch"].Value; - MergedBranch = ParseBranch(); + if (match.Groups["TargetBranch"].Success) + { + TargetBranch = match.Groups["TargetBranch"].Value; + } - // 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(@"^(? PullRequestNumber != null; + public int? PullRequestNumber { get; } + public SemanticVersion Version { get; } - match = parseRemoteTrackingMergeMessage.Match(mergeMessage); - if (match.Success) - { - var from = match.Groups["SourceBranch"].Value; - // TODO We could remove/separate the remote name at this point? - return from; - } - match = parseTfsMergeMessageEnglishUS.Match(mergeMessage); - if (match.Success) - { - IsMergedPullRequest = true; - var from = match.Groups["SourceBranch"].Value; - return from; - } + private SemanticVersion ParseVersion(string branchName, string tagPrefix) + { + // Remove remotes and branch prefixes like release/ feature/ hotfix/ etc + var toMatch = Regex.Replace(MergedBranch, @"^(\w+[-/])*", "", RegexOptions.IgnoreCase); + toMatch = Regex.Replace(toMatch, $"^{tagPrefix}", ""); + // We don't match if the version is likely an ip (i.e starts with http://) + var versionMatch = new Regex(@"^(?