diff --git a/new-cli/GitVersion.Common/GitVersion.Common.csproj b/new-cli/GitVersion.Common/GitVersion.Common.csproj index 96f23e8568..f224f9e121 100644 --- a/new-cli/GitVersion.Common/GitVersion.Common.csproj +++ b/new-cli/GitVersion.Common/GitVersion.Common.csproj @@ -7,6 +7,7 @@ + diff --git a/src/GitVersion.App.Tests/ArgumentParserTests.cs b/src/GitVersion.App.Tests/ArgumentParserTests.cs index d8456c7d12..b8650499c8 100644 --- a/src/GitVersion.App.Tests/ArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/ArgumentParserTests.cs @@ -450,7 +450,7 @@ private static IEnumerable OverrideConfigWithSingleOptionTestData( "tag-prefix=sample", new GitVersionConfiguration { - TagPrefix = "sample" + TagPrefixPattern = "sample" } ); yield return new TestCaseData( @@ -546,7 +546,7 @@ private static IEnumerable OverrideConfigWithMultipleOptionsTestDa "/overrideconfig tag-prefix=sample /overrideconfig assembly-versioning-scheme=MajorMinor", new GitVersionConfiguration { - TagPrefix = "sample", + TagPrefixPattern = "sample", AssemblyVersioningScheme = AssemblyVersioningScheme.MajorMinor } ); @@ -554,7 +554,7 @@ private static IEnumerable OverrideConfigWithMultipleOptionsTestDa "/overrideconfig tag-prefix=sample /overrideconfig assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", new GitVersionConfiguration { - TagPrefix = "sample", + TagPrefixPattern = "sample", AssemblyVersioningFormat = "{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}" } ); @@ -562,7 +562,7 @@ private static IEnumerable OverrideConfigWithMultipleOptionsTestDa "/overrideconfig tag-prefix=sample /overrideconfig assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\" /overrideconfig update-build-number=true /overrideconfig assembly-versioning-scheme=MajorMinorPatchTag /overrideconfig mode=ContinuousDelivery /overrideconfig tag-pre-release-weight=4", new GitVersionConfiguration { - TagPrefix = "sample", + TagPrefixPattern = "sample", AssemblyVersioningFormat = "{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}", UpdateBuildNumber = true, AssemblyVersioningScheme = AssemblyVersioningScheme.MajorMinorPatchTag, diff --git a/src/GitVersion.BuildAgents/Agents/AzurePipelines.cs b/src/GitVersion.BuildAgents/Agents/AzurePipelines.cs index 0978007fde..31d44ea671 100644 --- a/src/GitVersion.BuildAgents/Agents/AzurePipelines.cs +++ b/src/GitVersion.BuildAgents/Agents/AzurePipelines.cs @@ -1,4 +1,3 @@ -using System.Text.RegularExpressions; using GitVersion.Extensions; using GitVersion.Logging; using GitVersion.OutputVariables; @@ -52,7 +51,7 @@ private static string ReplaceVariables(string buildNumberEnv, KeyValuePair buildNumberEnv, - _ => buildNumberEnv.RegexReplace(pattern, replacement, RegexOptions.IgnoreCase) + _ => buildNumberEnv.RegexReplace(pattern, replacement) }; } } diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.cs b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.cs index a07d47d8c9..9ece53a3f5 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.cs @@ -246,7 +246,7 @@ public void CanReadDefaultDocument() configuration.AssemblyInformationalFormat.ShouldBe(null); configuration.Branches["develop"].Label.ShouldBe("alpha"); configuration.Branches["release"].Label.ShouldBe("beta"); - configuration.TagPrefix.ShouldBe(ConfigurationConstants.DefaultTagPrefix); + configuration.TagPrefixPattern.ShouldBe(RegexPatterns.Configuration.DefaultTagPrefixPattern); configuration.NextVersion.ShouldBe(null); } @@ -361,7 +361,7 @@ public void ShouldNotOverrideAnythingWhenOverrideConfigIsEmpty() var expectedConfig = GitFlowConfigurationBuilder.New .WithNextVersion("1.2.3") - .WithTagPrefix("custom-tag-prefix-from-yml") + .WithTagPrefixPattern("custom-tag-prefix-from-yml") .Build(); var configuration = this.configurationProvider.ProvideForDirectory(this.repoPath); @@ -370,7 +370,7 @@ public void ShouldNotOverrideAnythingWhenOverrideConfigIsEmpty() configuration.AssemblyInformationalFormat.ShouldBe(expectedConfig.AssemblyInformationalFormat); configuration.AssemblyVersioningFormat.ShouldBe(expectedConfig.AssemblyVersioningFormat); configuration.AssemblyFileVersioningFormat.ShouldBe(expectedConfig.AssemblyFileVersioningFormat); - configuration.TagPrefix.ShouldBe(expectedConfig.TagPrefix); + configuration.TagPrefixPattern.ShouldBe(expectedConfig.TagPrefixPattern); configuration.NextVersion.ShouldBe(expectedConfig.NextVersion); configuration.MajorVersionBumpMessage.ShouldBe(expectedConfig.MajorVersionBumpMessage); configuration.MinorVersionBumpMessage.ShouldBe(expectedConfig.MinorVersionBumpMessage); @@ -398,7 +398,7 @@ public void ShouldUseDefaultTagPrefixWhenNotSetInConfigFile() using var _ = this.fileSystem.SetupConfigFile(path: this.repoPath, text: text); var configuration = this.configurationProvider.ProvideForDirectory(this.repoPath); - configuration.TagPrefix.ShouldBe(ConfigurationConstants.DefaultTagPrefix); + configuration.TagPrefixPattern.ShouldBe(RegexPatterns.Configuration.DefaultTagPrefixPattern); } [Test] @@ -408,7 +408,7 @@ public void ShouldUseTagPrefixFromConfigFileWhenProvided() using var _ = this.fileSystem.SetupConfigFile(path: this.repoPath, text: text); var configuration = this.configurationProvider.ProvideForDirectory(this.repoPath); - configuration.TagPrefix.ShouldBe("custom-tag-prefix-from-yml"); + configuration.TagPrefixPattern.ShouldBe("custom-tag-prefix-from-yml"); } [Test] @@ -422,7 +422,7 @@ public void ShouldOverrideTagPrefixWithOverrideConfigValue([Values] bool tagPref }; var configuration = this.configurationProvider.ProvideForDirectory(this.repoPath, overrideConfiguration); - configuration.TagPrefix.ShouldBe("tag-prefix-from-override-configuration"); + configuration.TagPrefixPattern.ShouldBe("tag-prefix-from-override-configuration"); } [Test] @@ -437,7 +437,7 @@ public void ShouldNotOverrideDefaultTagPrefixWhenNotSetInOverrideConfig() var configuration = this.configurationProvider.ProvideForDirectory(this.repoPath, overrideConfiguration); - configuration.TagPrefix.ShouldBe(ConfigurationConstants.DefaultTagPrefix); + configuration.TagPrefixPattern.ShouldBe(RegexPatterns.Configuration.DefaultTagPrefixPattern); } [Test] @@ -451,7 +451,7 @@ public void ShouldNotOverrideTagPrefixFromConfigFileWhenNotSetInOverrideConfig() }; var configuration = this.configurationProvider.ProvideForDirectory(this.repoPath, overrideConfiguration); - configuration.TagPrefix.ShouldBe("custom-tag-prefix-from-yml"); + configuration.TagPrefixPattern.ShouldBe("custom-tag-prefix-from-yml"); } [Test] @@ -465,6 +465,6 @@ public void ShouldOverrideTagPrefixFromConfigFileWhenSetInOverrideConfig() }; var configuration = this.configurationProvider.ProvideForDirectory(this.repoPath, overrideConfiguration); - configuration.TagPrefix.ShouldBe("custom-tag-prefix-from-console"); + configuration.TagPrefixPattern.ShouldBe("custom-tag-prefix-from-console"); } } diff --git a/src/GitVersion.Configuration/BranchConfiguration.cs b/src/GitVersion.Configuration/BranchConfiguration.cs index b49054a7d3..5f8cbd07bf 100644 --- a/src/GitVersion.Configuration/BranchConfiguration.cs +++ b/src/GitVersion.Configuration/BranchConfiguration.cs @@ -1,4 +1,5 @@ using GitVersion.Configuration.Attributes; +using GitVersion.Core; using GitVersion.Extensions; using GitVersion.VersionCalculation; @@ -26,8 +27,8 @@ internal record BranchConfiguration : IBranchConfiguration public PreventIncrementConfiguration PreventIncrement { get; internal set; } = new(); [JsonPropertyName("label-number-pattern")] - [JsonPropertyDescription($"The regular expression pattern to use to extract the number from the branch name. Defaults to '{ConfigurationConstants.DefaultLabelNumberPattern}'.")] - [JsonPropertyDefault(ConfigurationConstants.DefaultLabelNumberPattern)] + [JsonPropertyDescription($"The regular expression pattern to use to extract the number from the branch name. Defaults to '{RegexPatterns.Configuration.DefaultLabelNumberPattern}'.")] + [JsonPropertyDefault(RegexPatterns.Configuration.DefaultLabelNumberPattern)] [JsonPropertyFormat(Format.Regex)] public string? LabelNumberPattern { get; internal set; } diff --git a/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs b/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs index 465b16c59c..80d6437579 100644 --- a/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs +++ b/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs @@ -1,3 +1,4 @@ +using GitVersion.Core; using GitVersion.Extensions; using GitVersion.Helpers; using GitVersion.VersionCalculation; @@ -47,49 +48,49 @@ internal abstract class ConfigurationBuilderBase : IConfi protected readonly BranchMetaData MainBranch = new() { Name = ConfigurationConstants.MainBranchKey, - RegexPattern = ConfigurationConstants.MainBranchRegex + RegexPattern = RegexPatterns.Configuration.MainBranchRegexPattern }; protected readonly BranchMetaData DevelopBranch = new() { Name = ConfigurationConstants.DevelopBranchKey, - RegexPattern = ConfigurationConstants.DevelopBranchRegex + RegexPattern = RegexPatterns.Configuration.DevelopBranchRegexPattern }; protected readonly BranchMetaData ReleaseBranch = new() { Name = ConfigurationConstants.ReleaseBranchKey, - RegexPattern = ConfigurationConstants.ReleaseBranchRegex + RegexPattern = RegexPatterns.Configuration.ReleaseBranchRegexPattern }; protected readonly BranchMetaData FeatureBranch = new() { Name = ConfigurationConstants.FeatureBranchKey, - RegexPattern = ConfigurationConstants.FeatureBranchRegex + RegexPattern = RegexPatterns.Configuration.FeatureBranchRegexPattern }; protected readonly BranchMetaData PullRequestBranch = new() { Name = ConfigurationConstants.PullRequestBranchKey, - RegexPattern = ConfigurationConstants.PullRequestBranchRegex + RegexPattern = RegexPatterns.Configuration.PullRequestBranchRegexPattern }; protected readonly BranchMetaData HotfixBranch = new() { Name = ConfigurationConstants.HotfixBranchKey, - RegexPattern = ConfigurationConstants.HotfixBranchRegex + RegexPattern = RegexPatterns.Configuration.HotfixBranchRegexPattern }; protected readonly BranchMetaData SupportBranch = new() { Name = ConfigurationConstants.SupportBranchKey, - RegexPattern = ConfigurationConstants.SupportBranchRegex + RegexPattern = RegexPatterns.Configuration.SupportBranchRegexPattern }; protected readonly BranchMetaData UnknownBranch = new() { Name = ConfigurationConstants.UnknownBranchKey, - RegexPattern = ConfigurationConstants.UnknownBranchRegex + RegexPattern = RegexPatterns.Configuration.UnknownBranchRegexPattern }; protected ConfigurationBuilderBase() @@ -130,7 +131,7 @@ public virtual TConfigurationBuilder WithAssemblyFileVersioningFormat(string? va return (TConfigurationBuilder)this; } - public virtual TConfigurationBuilder WithTagPrefix(string? value) + public virtual TConfigurationBuilder WithTagPrefixPattern(string? value) { this.tagPrefix = value; return (TConfigurationBuilder)this; @@ -338,7 +339,7 @@ public virtual TConfigurationBuilder WithConfiguration(IGitVersionConfiguration WithAssemblyInformationalFormat(value.AssemblyInformationalFormat); WithAssemblyVersioningFormat(value.AssemblyVersioningFormat); WithAssemblyFileVersioningFormat(value.AssemblyFileVersioningFormat); - WithTagPrefix(value.TagPrefix); + WithTagPrefixPattern(value.TagPrefixPattern); WithVersionInBranchPattern(value.VersionInBranchPattern); WithNextVersion(value.NextVersion); WithMajorVersionBumpMessage(value.MajorVersionBumpMessage); @@ -397,7 +398,7 @@ public virtual IGitVersionConfiguration Build() AssemblyInformationalFormat = this.assemblyInformationalFormat, AssemblyVersioningFormat = this.assemblyVersioningFormat, AssemblyFileVersioningFormat = this.assemblyFileVersioningFormat, - TagPrefix = this.tagPrefix, + TagPrefixPattern = this.tagPrefix, VersionInBranchPattern = this.versionInBranchPattern, NextVersion = this.nextVersion, MajorVersionBumpMessage = this.majorVersionBumpMessage, diff --git a/src/GitVersion.Configuration/Builders/GitFlowConfigurationBuilder.cs b/src/GitVersion.Configuration/Builders/GitFlowConfigurationBuilder.cs index b7ed9aff44..0122d54b8d 100644 --- a/src/GitVersion.Configuration/Builders/GitFlowConfigurationBuilder.cs +++ b/src/GitVersion.Configuration/Builders/GitFlowConfigurationBuilder.cs @@ -20,8 +20,8 @@ private GitFlowConfigurationBuilder() PatchVersionBumpMessage = RegexPatterns.VersionCalculation.DefaultPatchPattern, SemanticVersionFormat = ConfigurationConstants.DefaultSemanticVersionFormat, VersionStrategies = ConfigurationConstants.DefaultVersionStrategies, - TagPrefix = ConfigurationConstants.DefaultTagPrefix, - VersionInBranchPattern = ConfigurationConstants.DefaultVersionInBranchPattern, + TagPrefixPattern = RegexPatterns.Configuration.DefaultTagPrefixPattern, + VersionInBranchPattern = RegexPatterns.Configuration.DefaultVersionInBranchPattern, TagPreReleaseWeight = ConfigurationConstants.DefaultTagPreReleaseWeight, UpdateBuildNumber = ConfigurationConstants.DefaultUpdateBuildNumber, DeploymentMode = DeploymentMode.ContinuousDelivery, @@ -145,7 +145,7 @@ private GitFlowConfigurationBuilder() OfMergedBranch = true, WhenCurrentCommitTagged = false }, - LabelNumberPattern = ConfigurationConstants.DefaultLabelNumberPattern, + LabelNumberPattern = RegexPatterns.Configuration.DefaultLabelNumberPattern, TrackMergeMessage = true, PreReleaseWeight = 30000 }); diff --git a/src/GitVersion.Configuration/Builders/GitHubFlowConfigurationBuilder.cs b/src/GitVersion.Configuration/Builders/GitHubFlowConfigurationBuilder.cs index 8af099bcdc..b31db0c152 100644 --- a/src/GitVersion.Configuration/Builders/GitHubFlowConfigurationBuilder.cs +++ b/src/GitVersion.Configuration/Builders/GitHubFlowConfigurationBuilder.cs @@ -20,8 +20,8 @@ private GitHubFlowConfigurationBuilder() PatchVersionBumpMessage = RegexPatterns.VersionCalculation.DefaultPatchPattern, SemanticVersionFormat = ConfigurationConstants.DefaultSemanticVersionFormat, VersionStrategies = ConfigurationConstants.DefaultVersionStrategies, - TagPrefix = ConfigurationConstants.DefaultTagPrefix, - VersionInBranchPattern = ConfigurationConstants.DefaultVersionInBranchPattern, + TagPrefixPattern = RegexPatterns.Configuration.DefaultTagPrefixPattern, + VersionInBranchPattern = RegexPatterns.Configuration.DefaultVersionInBranchPattern, TagPreReleaseWeight = ConfigurationConstants.DefaultTagPreReleaseWeight, UpdateBuildNumber = ConfigurationConstants.DefaultUpdateBuildNumber, DeploymentMode = DeploymentMode.ContinuousDelivery, @@ -114,7 +114,7 @@ private GitHubFlowConfigurationBuilder() OfMergedBranch = true, WhenCurrentCommitTagged = false }, - LabelNumberPattern = ConfigurationConstants.DefaultLabelNumberPattern, + LabelNumberPattern = RegexPatterns.Configuration.DefaultLabelNumberPattern, RegularExpression = PullRequestBranch.RegexPattern, SourceBranches = [ diff --git a/src/GitVersion.Configuration/Builders/TrunkBasedConfigurationBuilder.cs b/src/GitVersion.Configuration/Builders/TrunkBasedConfigurationBuilder.cs index c1fbcdf519..74ccd921e6 100644 --- a/src/GitVersion.Configuration/Builders/TrunkBasedConfigurationBuilder.cs +++ b/src/GitVersion.Configuration/Builders/TrunkBasedConfigurationBuilder.cs @@ -23,8 +23,8 @@ private TrunkBasedConfigurationBuilder() VersionStrategies.ConfiguredNextVersion, VersionStrategies.Mainline ], - TagPrefix = ConfigurationConstants.DefaultTagPrefix, - VersionInBranchPattern = ConfigurationConstants.DefaultVersionInBranchPattern, + TagPrefixPattern = RegexPatterns.Configuration.DefaultTagPrefixPattern, + VersionInBranchPattern = RegexPatterns.Configuration.DefaultVersionInBranchPattern, TagPreReleaseWeight = ConfigurationConstants.DefaultTagPreReleaseWeight, UpdateBuildNumber = ConfigurationConstants.DefaultUpdateBuildNumber, DeploymentMode = DeploymentMode.ContinuousDelivery, @@ -112,7 +112,7 @@ private TrunkBasedConfigurationBuilder() OfMergedBranch = true, WhenCurrentCommitTagged = false }, - LabelNumberPattern = ConfigurationConstants.DefaultLabelNumberPattern, + LabelNumberPattern = RegexPatterns.Configuration.DefaultLabelNumberPattern, RegularExpression = PullRequestBranch.RegexPattern, SourceBranches = [ diff --git a/src/GitVersion.Configuration/GitVersionConfiguration.cs b/src/GitVersion.Configuration/GitVersionConfiguration.cs index ee6981920c..0a8c23e4d3 100644 --- a/src/GitVersion.Configuration/GitVersionConfiguration.cs +++ b/src/GitVersion.Configuration/GitVersionConfiguration.cs @@ -1,8 +1,6 @@ using System.Globalization; -using System.Text.RegularExpressions; using GitVersion.Configuration.Attributes; using GitVersion.Core; -using GitVersion.Extensions; using GitVersion.VersionCalculation; using static GitVersion.Configuration.ConfigurationConstants; @@ -38,28 +36,17 @@ internal sealed record GitVersionConfiguration : BranchConfiguration, IGitVersio public string? AssemblyFileVersioningFormat { get; internal set; } [JsonPropertyName("tag-prefix")] - [JsonPropertyDescription($"A regular expression which is used to trim Git tags before processing. Defaults to '{DefaultTagPrefix}'")] - [JsonPropertyDefault(DefaultTagPrefix)] + [JsonPropertyDescription($"A regular expression which is used to trim Git tags before processing. Defaults to '{RegexPatterns.Configuration.DefaultTagPrefixPattern}'")] + [JsonPropertyDefault(RegexPatterns.Configuration.DefaultTagPrefixPattern)] [JsonPropertyFormat(Format.Regex)] - public string? TagPrefix { get; internal set; } + public string? TagPrefixPattern { get; internal set; } [JsonPropertyName("version-in-branch-pattern")] - [JsonPropertyDescription($"A regular expression which is used to determine the version number in the branch name or commit message (e.g., v1.0.0-LTS). Defaults to '{DefaultVersionInBranchPattern}'.")] - [JsonPropertyDefault(DefaultVersionInBranchPattern)] + [JsonPropertyDescription($"A regular expression which is used to determine the version number in the branch name or commit message (e.g., v1.0.0-LTS). Defaults to '{RegexPatterns.Configuration.DefaultVersionInBranchPattern}'.")] + [JsonPropertyDefault(RegexPatterns.Configuration.DefaultVersionInBranchPattern)] [JsonPropertyFormat(Format.Regex)] public string? VersionInBranchPattern { get; internal set; } - [JsonIgnore] - public Regex VersionInBranchRegex => versionInBranchRegex ??= new(GetVersionInBranchPattern(), RegexOptions.Compiled); - private Regex? versionInBranchRegex; - - private string GetVersionInBranchPattern() - { - var versionInBranchPattern = VersionInBranchPattern; - if (versionInBranchPattern.IsNullOrEmpty()) versionInBranchPattern = DefaultVersionInBranchPattern; - return $"^{versionInBranchPattern.TrimStart('^')}"; - } - [JsonPropertyName("next-version")] [JsonPropertyDescription("Allows you to bump the next version explicitly. Useful for bumping main or a feature branch with breaking changes")] public string? NextVersion @@ -103,9 +90,7 @@ public string? NextVersion [JsonPropertyName("commit-date-format")] [JsonPropertyDescription($"The format to use when calculating the commit date. Defaults to '{DefaultCommitDateFormat}'. See [Standard Date and Time Format Strings](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings) and [Custom Date and Time Format Strings](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings).")] [JsonPropertyDefault(DefaultCommitDateFormat)] -#if NET7_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.StringSyntax("DateTimeFormat")] // See https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.stringsyntaxattribute, https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.stringsyntaxattribute.datetimeformat?view=net-7.0#system-diagnostics-codeanalysis-stringsyntaxattribute-datetimeformat -#endif + [System.Diagnostics.CodeAnalysis.StringSyntax("DateTimeFormat")] public string? CommitDateFormat { get; internal set; } [JsonPropertyName("merge-message-formats")] diff --git a/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs b/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs index 6d02601c72..ebd81f0957 100644 --- a/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs +++ b/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs @@ -205,7 +205,7 @@ public void CacheFileExistsOnDiskWhenOverrideConfigIsSpecifiedVersionShouldBeDyn var cacheDirectoryTimestamp = this.fileSystem.GetLastDirectoryWrite(cacheDirectory); - var configuration = GitFlowConfigurationBuilder.New.WithTagPrefix("prefix").Build(); + var configuration = GitFlowConfigurationBuilder.New.WithTagPrefixPattern("prefix").Build(); var overrideConfiguration = new ConfigurationHelper(configuration).Dictionary; gitVersionOptions = new() { WorkingDirectory = fixture.RepositoryPath, ConfigurationInfo = { OverrideConfiguration = overrideConfiguration } }; diff --git a/src/GitVersion.Core.Tests/IntegrationTests/MainScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/MainScenarios.cs index 709263c825..9609853812 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/MainScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/MainScenarios.cs @@ -208,14 +208,14 @@ public void CanSpecifyTagPrefixes() fixture.Repository.MakeATaggedCommit(taggedVersion); fixture.Repository.MakeCommits(5); - var configuration = GitFlowConfigurationBuilder.New.WithTagPrefix("version-").Build(); + var configuration = GitFlowConfigurationBuilder.New.WithTagPrefixPattern("version-").Build(); fixture.AssertFullSemver("1.0.4-5", configuration); } [Test] public void CanSpecifyTagPrefixesAsRegex() { - var configuration = GitFlowConfigurationBuilder.New.WithTagPrefix($"version-|{ConfigurationConstants.DefaultTagPrefix}").Build(); + var configuration = GitFlowConfigurationBuilder.New.WithTagPrefixPattern($"version-|{RegexPatterns.Configuration.DefaultTagPrefixPattern}").Build(); using var fixture = new EmptyRepositoryFixture(); var taggedVersion = "v1.0.3"; fixture.Repository.MakeATaggedCommit(taggedVersion); @@ -233,7 +233,7 @@ public void CanSpecifyTagPrefixesAsRegex() [Test] public void AreTagsNotAdheringToTagPrefixIgnored() { - var configuration = GitFlowConfigurationBuilder.New.WithTagPrefix("").Build(); + var configuration = GitFlowConfigurationBuilder.New.WithTagPrefixPattern("").Build(); using var fixture = new EmptyRepositoryFixture(); var taggedVersion = "version-1.0.3"; fixture.Repository.MakeATaggedCommit(taggedVersion); diff --git a/src/GitVersion.Core.Tests/IntegrationTests/OtherBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/OtherBranchScenarios.cs index 597ac26ba2..8412d0b015 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/OtherBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/OtherBranchScenarios.cs @@ -22,7 +22,7 @@ public void CanUseCommitMessagesToBumpVersion_TagsTakePriorityOnlyIfVersions( string expectedVersionAfterNewCommit) { var configuration = GitFlowConfigurationBuilder.New - .WithTagPrefix(tagPrefix) + .WithTagPrefixPattern(tagPrefix) .Build(); using var fixture = new EmptyRepositoryFixture(); @@ -61,7 +61,7 @@ public void CanUseCommitMessagesToBumpVersion_TagsTakePriorityOnlyIfVersions( [TestCase("prefix", "bar", "2.1.0-1", "2.0.0-bar.1+1", ExpectedResult = "2.0.0-bar.1+1")] public string WhenTaggingACommitAsPreRelease(string tagPrefix, string? label, string tag, string expectedVersion) { - var configuration = GitFlowConfigurationBuilder.New.WithLabel(null).WithTagPrefix(tagPrefix) + var configuration = GitFlowConfigurationBuilder.New.WithLabel(null).WithTagPrefixPattern(tagPrefix) .WithBranch("main", b => b.WithLabel(label).WithDeploymentMode(DeploymentMode.ManualDeployment)) .Build(); diff --git a/src/GitVersion.Core.Tests/MergeMessageTests.cs b/src/GitVersion.Core.Tests/MergeMessageTests.cs index 546355d2b6..79d7590f39 100644 --- a/src/GitVersion.Core.Tests/MergeMessageTests.cs +++ b/src/GitVersion.Core.Tests/MergeMessageTests.cs @@ -35,7 +35,7 @@ public void EmptyTagPrefix(string? prefix) { // Arrange const string message = "Updated some code."; - var configuration = GitHubFlowConfigurationBuilder.New.WithTagPrefix(prefix).Build(); + var configuration = GitHubFlowConfigurationBuilder.New.WithTagPrefixPattern(prefix).Build(); // Act var sut = new MergeMessage(message, configuration); diff --git a/src/GitVersion.Core.Tests/VersionCalculation/SemanticVersionTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/SemanticVersionTests.cs index 6a59b13317..ae8fdb55db 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/SemanticVersionTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/SemanticVersionTests.cs @@ -1,4 +1,3 @@ -using GitVersion.Configuration; using GitVersion.Core.Tests.Helpers; namespace GitVersion.Core.Tests; @@ -23,9 +22,9 @@ public class SemanticVersionTests : TestBase [TestCase("1.2.3+4.Branch.Foo", 1, 2, 3, "", null, 4, "Foo", null, null, null, null, SemanticVersionFormat.Strict)] [TestCase("1.2.3+randomMetaData", 1, 2, 3, "", null, null, null, null, "randomMetaData", null, null, SemanticVersionFormat.Strict)] [TestCase("1.2.3-beta.1+4.Sha.12234.Othershiz", 1, 2, 3, "beta", 1, 4, null, "12234", "Othershiz", null, null, SemanticVersionFormat.Strict)] - [TestCase("1.2.3", 1, 2, 3, "", null, null, null, null, null, null, ConfigurationConstants.DefaultTagPrefix, SemanticVersionFormat.Strict)] - [TestCase("v1.2.3", 1, 2, 3, "", null, null, null, null, null, "1.2.3", ConfigurationConstants.DefaultTagPrefix, SemanticVersionFormat.Strict)] - [TestCase("V1.2.3", 1, 2, 3, "", null, null, null, null, null, "1.2.3", ConfigurationConstants.DefaultTagPrefix, SemanticVersionFormat.Strict)] + [TestCase("1.2.3", 1, 2, 3, "", null, null, null, null, null, null, RegexPatterns.Configuration.DefaultTagPrefixPattern, SemanticVersionFormat.Strict)] + [TestCase("v1.2.3", 1, 2, 3, "", null, null, null, null, null, "1.2.3", RegexPatterns.Configuration.DefaultTagPrefixPattern, SemanticVersionFormat.Strict)] + [TestCase("V1.2.3", 1, 2, 3, "", null, null, null, null, null, "1.2.3", RegexPatterns.Configuration.DefaultTagPrefixPattern, SemanticVersionFormat.Strict)] [TestCase("version-1.2.3", 1, 2, 3, "", null, null, null, null, null, "1.2.3", "version-", SemanticVersionFormat.Strict)] [TestCase("1.0.0-develop-20201007113711", 1, 0, 0, "develop-20201007113711", null, null, null, null, null, "1.0.0-develop-20201007113711", null, SemanticVersionFormat.Strict)] [TestCase("20201007113711.658165168461351.64136516984163213-develop-20201007113711.98848747823+65416321321", 20201007113711, 658165168461351, 64136516984163213, "develop-20201007113711", 98848747823, 65416321321, null, null, null, "20201007113711.658165168461351.64136516984163213-develop-20201007113711.98848747823+65416321321", null, SemanticVersionFormat.Strict)] @@ -63,7 +62,7 @@ public void ValidateVersionParsing( [TestCase("someText")] [TestCase("some-T-ext")] - [TestCase("v.1.2.3", ConfigurationConstants.DefaultTagPrefix)] + [TestCase("v.1.2.3", RegexPatterns.Configuration.DefaultTagPrefixPattern)] public void ValidateInvalidVersionParsing(string versionString, string? tagPrefixRegex = null) => Assert.That(SemanticVersion.TryParse(versionString, tagPrefixRegex, out _), Is.False, "TryParse Result"); diff --git a/src/GitVersion.Core/Configuration/ConfigurationConstants.cs b/src/GitVersion.Core/Configuration/ConfigurationConstants.cs index f09f24f83d..f8774c93ba 100644 --- a/src/GitVersion.Core/Configuration/ConfigurationConstants.cs +++ b/src/GitVersion.Core/Configuration/ConfigurationConstants.cs @@ -22,14 +22,8 @@ internal static class ConfigurationConstants VersionStrategies.VersionInBranchName ]; public const string DefaultAssemblyInformationalFormat = "{InformationalVersion}"; - //language=regexp - public const string DefaultTagPrefix = "[vV]?"; - //language=regexp - public const string DefaultVersionInBranchPattern = @"(?[vV]?\d+(\.\d+)?(\.\d+)?).*"; public const string DefaultCommitDateFormat = "yyyy-MM-dd"; public const string BranchNamePlaceholder = "{BranchName}"; - //language=regexp - public const string DefaultLabelNumberPattern = @"[/-](?\d+)"; public const bool DefaultUpdateBuildNumber = true; public const int DefaultTagPreReleaseWeight = 60000; @@ -42,21 +36,4 @@ internal static class ConfigurationConstants public const string HotfixBranchKey = "hotfix"; public const string SupportBranchKey = "support"; public const string UnknownBranchKey = "unknown"; - - //language=regexp - public const string MainBranchRegex = "^master$|^main$"; - //language=regexp - public const string DevelopBranchRegex = "^dev(elop)?(ment)?$"; - //language=regexp - public const string ReleaseBranchRegex = "^releases?[/-](?.+)"; - //language=regexp - public const string FeatureBranchRegex = "^features?[/-](?.+)"; - //language=regexp - public const string PullRequestBranchRegex = @"^(pull|pull\-requests|pr)[/-]"; - //language=regexp - public const string HotfixBranchRegex = "^hotfix(es)?[/-](?.+)"; - //language=regexp - public const string SupportBranchRegex = "^support[/-](?.+)"; - //language=regexp - public const string UnknownBranchRegex = "(?.+)"; } diff --git a/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs b/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs index 9708f95329..c7514c34cc 100644 --- a/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs +++ b/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs @@ -1,4 +1,3 @@ -using System.Text.RegularExpressions; using GitVersion.Extensions; using GitVersion.VersionCalculation; @@ -10,7 +9,9 @@ namespace GitVersion.Configuration; /// public record EffectiveConfiguration { - public EffectiveConfiguration(IGitVersionConfiguration configuration, IBranchConfiguration branchConfiguration, + public EffectiveConfiguration( + IGitVersionConfiguration configuration, + IBranchConfiguration branchConfiguration, EffectiveConfiguration? fallbackConfiguration = null) { configuration.NotNull(); @@ -50,8 +51,8 @@ public EffectiveConfiguration(IGitVersionConfiguration configuration, IBranchCon AssemblyVersioningFormat = configuration.AssemblyVersioningFormat; AssemblyFileVersioningFormat = configuration.AssemblyFileVersioningFormat; DeploymentMode = branchConfiguration.DeploymentMode.Value; - TagPrefix = configuration.TagPrefix; - VersionInBranchRegex = configuration.VersionInBranchRegex; + TagPrefix = configuration.TagPrefixPattern; + VersionInBranchPattern = configuration.VersionInBranchPattern; Label = branchConfiguration.Label; NextVersion = configuration.NextVersion; Increment = branchConfiguration.Increment; @@ -90,16 +91,10 @@ public EffectiveConfiguration(IGitVersionConfiguration configuration, IBranchCon public string? AssemblyVersioningFormat { get; } public string? AssemblyFileVersioningFormat { get; } - /// - /// Git tag prefix - /// public string? TagPrefix { get; } - public Regex VersionInBranchRegex { get; } + public string? VersionInBranchPattern { get; } - /// - /// Label to use when calculating SemVer - /// public string? Label { get; } public string? NextVersion { get; } diff --git a/src/GitVersion.Core/Configuration/IBranchConfiguration.cs b/src/GitVersion.Core/Configuration/IBranchConfiguration.cs index 52d05a612f..cf81ef3dcc 100644 --- a/src/GitVersion.Core/Configuration/IBranchConfiguration.cs +++ b/src/GitVersion.Core/Configuration/IBranchConfiguration.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using GitVersion.Core; +using GitVersion.Extensions; using GitVersion.VersionCalculation; namespace GitVersion.Configuration; @@ -24,7 +25,15 @@ public interface IBranchConfiguration public string? RegularExpression { get; } public bool IsMatch(string branchName) - => RegularExpression != null && Regex.IsMatch(branchName, RegularExpression, RegexOptions.IgnoreCase); + { + if (string.IsNullOrWhiteSpace(RegularExpression)) + { + return false; + } + + var regex = RegexPatterns.Cache.GetOrAdd(RegularExpression); + return regex.IsMatch(branchName); + } IReadOnlyCollection SourceBranches { get; } diff --git a/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs b/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs index 60307b3cd1..1461d38034 100644 --- a/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs +++ b/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs @@ -1,4 +1,3 @@ -using System.Text.RegularExpressions; using GitVersion.VersionCalculation; namespace GitVersion.Configuration; @@ -17,12 +16,10 @@ public interface IGitVersionConfiguration : IBranchConfiguration string? AssemblyFileVersioningFormat { get; } - string? TagPrefix { get; } + string? TagPrefixPattern { get; } string? VersionInBranchPattern { get; } - Regex VersionInBranchRegex { get; } - string? NextVersion { get; } string? MajorVersionBumpMessage { get; } diff --git a/src/GitVersion.Core/Configuration/ReferenceNameExtensions.cs b/src/GitVersion.Core/Configuration/ReferenceNameExtensions.cs index dd05cab0f2..630d637746 100644 --- a/src/GitVersion.Core/Configuration/ReferenceNameExtensions.cs +++ b/src/GitVersion.Core/Configuration/ReferenceNameExtensions.cs @@ -1,14 +1,54 @@ +using GitVersion.Core; +using GitVersion.Extensions; using GitVersion.Git; namespace GitVersion.Configuration; public static class ReferenceNameExtensions { - public static bool TryGetSemanticVersion( - this ReferenceName source, out (SemanticVersion Value, string? Name) result, IGitVersionConfiguration configuration) - => source.TryGetSemanticVersion(out result, configuration.VersionInBranchRegex, configuration.TagPrefix, configuration.SemanticVersionFormat); + public static bool TryGetSemanticVersion(this ReferenceName source, out (SemanticVersion Value, string? Name) result, EffectiveConfiguration configuration) + => source.TryGetSemanticVersion(out result, configuration.VersionInBranchPattern, configuration.TagPrefix, configuration.SemanticVersionFormat); - public static bool TryGetSemanticVersion( - this ReferenceName source, out (SemanticVersion Value, string? Name) result, EffectiveConfiguration configuration) - => source.TryGetSemanticVersion(out result, configuration.VersionInBranchRegex, configuration.TagPrefix, configuration.SemanticVersionFormat); + public static bool TryGetSemanticVersion(this ReferenceName source, out (SemanticVersion Value, string? Name) result, IGitVersionConfiguration configuration) + => source.TryGetSemanticVersion(out result, configuration.VersionInBranchPattern, configuration.TagPrefixPattern, configuration.SemanticVersionFormat); + + private static bool TryGetSemanticVersion(this ReferenceName referenceName, out (SemanticVersion Value, string? Name) result, + string? versionPatternPattern, + string? tagPrefix, + SemanticVersionFormat format) + { + var versionPatternRegex = RegexPatterns.Cache.GetOrAdd(GetVersionInBranchPattern(versionPatternPattern)); + result = default; + + int length = 0; + foreach (var branchPart in referenceName.WithoutOrigin.Split(GetBranchSeparator())) + { + if (string.IsNullOrEmpty(branchPart)) return false; + + var match = versionPatternRegex.Match(branchPart); + if (match.Success) + { + var versionPart = match.Groups["version"].Value; + if (SemanticVersion.TryParse(versionPart, tagPrefix, out var semanticVersion, format)) + { + length += versionPart.Length; + var name = referenceName.WithoutOrigin[length..].Trim('-'); + result = new(semanticVersion, name.Length == 0 ? null : name); + return true; + } + } + + length += branchPart.Length + 1; + } + + return false; + + char GetBranchSeparator() => referenceName.WithoutOrigin.Contains('/') || !referenceName.WithoutOrigin.Contains('-') ? '/' : '-'; + + static string GetVersionInBranchPattern(string? versionInBranchPattern) + { + if (versionInBranchPattern.IsNullOrEmpty()) versionInBranchPattern = RegexPatterns.Configuration.DefaultVersionInBranchPattern; + return $"^{versionInBranchPattern.TrimStart('^')}"; + } + } } diff --git a/src/GitVersion.Core/Core/GitVersionContextFactory.cs b/src/GitVersion.Core/Core/GitVersionContextFactory.cs index d75bc4ab6b..537c6145b9 100644 --- a/src/GitVersion.Core/Core/GitVersionContextFactory.cs +++ b/src/GitVersion.Core/Core/GitVersionContextFactory.cs @@ -41,7 +41,7 @@ public GitVersionContext Create(GitVersionOptions gitVersionOptions) } bool isCurrentCommitTagged = this.taggedSemanticVersionRepository.GetTaggedSemanticVersions( - tagPrefix: configuration.TagPrefix, + tagPrefix: configuration.TagPrefixPattern, format: configuration.SemanticVersionFormat, ignore: configuration.Ignore ).Contains(currentCommit); diff --git a/src/GitVersion.Core/Core/MainlineBranchFinder.cs b/src/GitVersion.Core/Core/MainlineBranchFinder.cs index 2e2ad2fe96..8f818cf303 100644 --- a/src/GitVersion.Core/Core/MainlineBranchFinder.cs +++ b/src/GitVersion.Core/Core/MainlineBranchFinder.cs @@ -1,6 +1,6 @@ -using System.Text.RegularExpressions; using GitVersion.Common; using GitVersion.Configuration; +using GitVersion.Core; using GitVersion.Extensions; using GitVersion.Git; using GitVersion.Logging; @@ -47,10 +47,10 @@ public bool IsMainBranch(IBranchConfiguration value) if (value.RegularExpression == null) return false; - var mainlineRegex = value.RegularExpression; + var regex = RegexPatterns.Cache.GetOrAdd(value.RegularExpression); var branchName = this.branch.Name.WithoutOrigin; - var match = Regex.IsMatch(branchName, mainlineRegex); - this.log.Info($"'{mainlineRegex}' {(match ? "matches" : "does not match")} '{branchName}'."); + var match = regex.IsMatch(branchName); + this.log.Info($"'{value.RegularExpression}' {(match ? "matches" : "does not match")} '{branchName}'."); return match; } } diff --git a/src/GitVersion.Core/Core/RegexPatterns.cs b/src/GitVersion.Core/Core/RegexPatterns.cs index 3b57dc5c4f..23f7b82d0a 100644 --- a/src/GitVersion.Core/Core/RegexPatterns.cs +++ b/src/GitVersion.Core/Core/RegexPatterns.cs @@ -1,61 +1,168 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace GitVersion.Core; internal static class RegexPatterns { + private const RegexOptions Options = RegexOptions.IgnoreCase | RegexOptions.Compiled; + + internal static readonly ConcurrentDictionary Cache = new(); + + static RegexPatterns() + { + Cache.TryAdd(Common.SwitchArgumentRegex.ToString(), Common.SwitchArgumentRegex); + Cache.TryAdd(Common.ObscurePasswordRegex.ToString(), Common.ObscurePasswordRegex); + Cache.TryAdd(Common.ExpandTokensRegex.ToString(), Common.ExpandTokensRegex); + + Cache.TryAdd(Configuration.DefaultTagPrefixRegex.ToString(), Configuration.DefaultTagPrefixRegex); + Cache.TryAdd(Configuration.DefaultVersionInBranchRegex.ToString(), Configuration.DefaultVersionInBranchRegex); + Cache.TryAdd(Configuration.DefaultLabelNumberRegex.ToString(), Configuration.DefaultLabelNumberRegex); + Cache.TryAdd(Configuration.MainBranchRegex.ToString(), Configuration.MainBranchRegex); + Cache.TryAdd(Configuration.DevelopBranchRegex.ToString(), Configuration.DevelopBranchRegex); + Cache.TryAdd(Configuration.ReleaseBranchRegex.ToString(), Configuration.ReleaseBranchRegex); + Cache.TryAdd(Configuration.FeatureBranchRegex.ToString(), Configuration.FeatureBranchRegex); + Cache.TryAdd(Configuration.PullRequestBranchRegex.ToString(), Configuration.PullRequestBranchRegex); + Cache.TryAdd(Configuration.HotfixBranchRegex.ToString(), Configuration.HotfixBranchRegex); + Cache.TryAdd(Configuration.SupportBranchRegex.ToString(), Configuration.SupportBranchRegex); + Cache.TryAdd(Configuration.UnknownBranchRegex.ToString(), Configuration.UnknownBranchRegex); + + Cache.TryAdd(MergeMessage.DefaultMergeMessageRegex.ToString(), MergeMessage.DefaultMergeMessageRegex); + Cache.TryAdd(MergeMessage.SmartGitMergeMessageRegex.ToString(), MergeMessage.SmartGitMergeMessageRegex); + Cache.TryAdd(MergeMessage.BitBucketPullMergeMessageRegex.ToString(), MergeMessage.BitBucketPullMergeMessageRegex); + Cache.TryAdd(MergeMessage.BitBucketPullv7MergeMessageRegex.ToString(), MergeMessage.BitBucketPullv7MergeMessageRegex); + Cache.TryAdd(MergeMessage.BitBucketCloudPullMergeMessageRegex.ToString(), MergeMessage.BitBucketCloudPullMergeMessageRegex); + Cache.TryAdd(MergeMessage.GitHubPullMergeMessageRegex.ToString(), MergeMessage.GitHubPullMergeMessageRegex); + Cache.TryAdd(MergeMessage.RemoteTrackingMergeMessageRegex.ToString(), MergeMessage.RemoteTrackingMergeMessageRegex); + Cache.TryAdd(MergeMessage.AzureDevOpsPullMergeMessageRegex.ToString(), MergeMessage.AzureDevOpsPullMergeMessageRegex); + + Cache.TryAdd(Output.AssemblyVersionRegex.ToString(), Output.AssemblyVersionRegex); + Cache.TryAdd(Output.AssemblyInfoVersionRegex.ToString(), Output.AssemblyInfoVersionRegex); + Cache.TryAdd(Output.AssemblyFileVersionRegex.ToString(), Output.AssemblyFileVersionRegex); + Cache.TryAdd(Output.CsharpAssemblyAttributeRegex.ToString(), Output.CsharpAssemblyAttributeRegex); + Cache.TryAdd(Output.FsharpAssemblyAttributeRegex.ToString(), Output.FsharpAssemblyAttributeRegex); + Cache.TryAdd(Output.VisualBasicAssemblyAttributeRegex.ToString(), Output.VisualBasicAssemblyAttributeRegex); + + Cache.TryAdd(VersionCalculation.DefaultMajorRegex.ToString(), VersionCalculation.DefaultMajorRegex); + Cache.TryAdd(VersionCalculation.DefaultMinorRegex.ToString(), VersionCalculation.DefaultMinorRegex); + Cache.TryAdd(VersionCalculation.DefaultPatchRegex.ToString(), VersionCalculation.DefaultPatchRegex); + Cache.TryAdd(VersionCalculation.DefaultNoBumpRegex.ToString(), VersionCalculation.DefaultNoBumpRegex); + + Cache.TryAdd(SemanticVersion.ParseStrictRegex.ToString(), SemanticVersion.ParseStrictRegex); + Cache.TryAdd(SemanticVersion.ParseLooseRegex.ToString(), SemanticVersion.ParseLooseRegex); + Cache.TryAdd(SemanticVersion.ParseBuildMetaDataRegex.ToString(), SemanticVersion.ParseBuildMetaDataRegex); + Cache.TryAdd(SemanticVersion.FormatBuildMetaDataRegex.ToString(), SemanticVersion.FormatBuildMetaDataRegex); + Cache.TryAdd(SemanticVersion.ParsePreReleaseTagRegex.ToString(), SemanticVersion.ParsePreReleaseTagRegex); + + Cache.TryAdd(AssemblyVersion.CSharp.TriviaRegex.ToString(), AssemblyVersion.CSharp.TriviaRegex); + Cache.TryAdd(AssemblyVersion.CSharp.AttributeRegex.ToString(), AssemblyVersion.CSharp.AttributeRegex); + Cache.TryAdd(AssemblyVersion.FSharp.TriviaRegex.ToString(), AssemblyVersion.FSharp.TriviaRegex); + Cache.TryAdd(AssemblyVersion.FSharp.AttributeRegex.ToString(), AssemblyVersion.FSharp.AttributeRegex); + Cache.TryAdd(AssemblyVersion.VisualBasic.TriviaRegex.ToString(), AssemblyVersion.VisualBasic.TriviaRegex); + Cache.TryAdd(AssemblyVersion.VisualBasic.AttributeRegex.ToString(), AssemblyVersion.VisualBasic.AttributeRegex); + } + internal static class Common { - public static Regex SwitchArgumentRegex { get; } = new(@"/\w+:", RegexOptions.Compiled); - public static Regex ObscurePasswordRegex { get; } = new("(https?://)(.+)(:.+@)", RegexOptions.Compiled); + public static Regex SwitchArgumentRegex { get; } = new(@"/\w+:", Options); + public static Regex ObscurePasswordRegex { get; } = new("(https?://)(.+)(:.+@)", Options); // This regex matches an expression to replace. // - env:ENV name OR a member name // - optional fallback value after " ?? " // - the fallback value should be a quoted string, but simple unquoted text is allowed for back compat - public static Regex ExpandTokensRegex { get; } = new("""{((env:(?\w+))|(?\w+))(\s+(\?\?)??\s+((?\w+)|"(?.*)"))??}""", RegexOptions.Compiled); + public static Regex ExpandTokensRegex { get; } = new("""{((env:(?\w+))|(?\w+))(\s+(\?\?)??\s+((?\w+)|"(?.*)"))??}""", Options); + } + + internal static class Configuration + { + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string DefaultTagPrefixPattern = "[vV]?"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string DefaultVersionInBranchPattern = @"(?[vV]?\d+(\.\d+)?(\.\d+)?).*"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string DefaultLabelNumberPattern = @"[/-](?\d+)"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string MainBranchRegexPattern = "^master$|^main$"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string DevelopBranchRegexPattern = "^dev(elop)?(ment)?$"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string ReleaseBranchRegexPattern = "^releases?[/-](?.+)"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string FeatureBranchRegexPattern = "^features?[/-](?.+)"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string PullRequestBranchRegexPattern = @"^(pull|pull\-requests|pr)[/-]"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string HotfixBranchRegexPattern = "^hotfix(es)?[/-](?.+)"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string SupportBranchRegexPattern = "^support[/-](?.+)"; + + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string UnknownBranchRegexPattern = "(?.+)"; + + public static Regex DefaultTagPrefixRegex { get; } = new(DefaultTagPrefixPattern, Options); + public static Regex DefaultVersionInBranchRegex { get; } = new(DefaultVersionInBranchPattern, Options); + public static Regex DefaultLabelNumberRegex { get; } = new(DefaultLabelNumberPattern, Options); + public static Regex MainBranchRegex { get; } = new(MainBranchRegexPattern, Options); + public static Regex DevelopBranchRegex { get; } = new(DevelopBranchRegexPattern, Options); + public static Regex ReleaseBranchRegex { get; } = new(ReleaseBranchRegexPattern, Options); + public static Regex FeatureBranchRegex { get; } = new(FeatureBranchRegexPattern, Options); + public static Regex PullRequestBranchRegex { get; } = new(PullRequestBranchRegexPattern, Options); + public static Regex HotfixBranchRegex { get; } = new(HotfixBranchRegexPattern, Options); + public static Regex SupportBranchRegex { get; } = new(SupportBranchRegexPattern, Options); + public static Regex UnknownBranchRegex { get; } = new(UnknownBranchRegexPattern, Options); } internal static class MergeMessage { - public static Regex DefaultMergeMessageRegex { get; } = new(@"^Merge (branch|tag) '(?[^']*)'(?: into (?[^\s]*))*", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static Regex SmartGitMergeMessageRegex { get; } = new(@"^Finish (?[^\s]*)(?: into (?[^\s]*))*", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static Regex BitBucketPullMergeMessageRegex { get; } = new(@"^Merge pull request #(?\d+) (from|in) (?.*) from (?[^\s]*) to (?[^\s]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static Regex BitBucketPullv7MergeMessageRegex { get; } = new(@"^Pull request #(?\d+).*\r?\n\r?\nMerge in (?.*) from (?[^\s]*) to (?[^\s]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static Regex BitBucketCloudPullMergeMessageRegex { get; } = new(@"^Merged in (?[^\s]*) \(pull request #(?\d+)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static Regex GitHubPullMergeMessageRegex { get; } = new(@"^Merge pull request #(?\d+) (from|in) (?:[^\s\/]+\/)?(?[^\s]*)(?: into (?[^\s]*))*", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static Regex RemoteTrackingMergeMessageRegex { get; } = new(@"^Merge remote-tracking branch '(?[^\s]*)'(?: into (?[^\s]*))*", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static Regex AzureDevOpsPullMergeMessageRegex { get; } = new(@"^Merge pull request (?\d+) from (?[^\s]*) into (?[^\s]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + public static Regex DefaultMergeMessageRegex { get; } = new(@"^Merge (branch|tag) '(?[^']*)'(?: into (?[^\s]*))*", Options); + public static Regex SmartGitMergeMessageRegex { get; } = new(@"^Finish (?[^\s]*)(?: into (?[^\s]*))*", Options); + public static Regex BitBucketPullMergeMessageRegex { get; } = new(@"^Merge pull request #(?\d+) (from|in) (?.*) from (?[^\s]*) to (?[^\s]*)", Options); + public static Regex BitBucketPullv7MergeMessageRegex { get; } = new(@"^Pull request #(?\d+).*\r?\n\r?\nMerge in (?.*) from (?[^\s]*) to (?[^\s]*)", Options); + public static Regex BitBucketCloudPullMergeMessageRegex { get; } = new(@"^Merged in (?[^\s]*) \(pull request #(?\d+)\)", Options); + public static Regex GitHubPullMergeMessageRegex { get; } = new(@"^Merge pull request #(?\d+) (from|in) (?:[^\s\/]+\/)?(?[^\s]*)(?: into (?[^\s]*))*", Options); + public static Regex RemoteTrackingMergeMessageRegex { get; } = new(@"^Merge remote-tracking branch '(?[^\s]*)'(?: into (?[^\s]*))*", Options); + public static Regex AzureDevOpsPullMergeMessageRegex { get; } = new(@"^Merge pull request (?\d+) from (?[^\s]*) into (?[^\s]*)", Options); } internal static class Output { - public static Regex AssemblyVersionRegex { get; } = new(@"AssemblyVersion(Attribute)?\s*\(.*\)\s*"); - public static Regex AssemblyInfoVersionRegex { get; } = new(@"AssemblyInformationalVersion(Attribute)?\s*\(.*\)\s*"); - public static Regex AssemblyFileVersionRegex { get; } = new(@"AssemblyFileVersion(Attribute)?\s*\(.*\)\s*"); - public static Regex CsharpAssemblyAttributeRegex { get; } = new(@"(\s*\[\s*assembly:\s*(?:.*)\s*\]\s*$(\r?\n)?)", RegexOptions.Multiline); - public static Regex FsharpAssemblyAttributeRegex { get; } = new(@"(\s*\[\s*\\s*\]\s*$(\r?\n)?)", RegexOptions.Multiline); - public static Regex VisualBasicAssemblyAttributeRegex { get; } = new(@"(\s*\\s*$(\r?\n)?)", RegexOptions.Multiline); + public static Regex AssemblyVersionRegex { get; } = new(@"AssemblyVersion(Attribute)?\s*\(.*\)\s*", Options); + public static Regex AssemblyInfoVersionRegex { get; } = new(@"AssemblyInformationalVersion(Attribute)?\s*\(.*\)\s*", Options); + public static Regex AssemblyFileVersionRegex { get; } = new(@"AssemblyFileVersion(Attribute)?\s*\(.*\)\s*", Options); + public static Regex CsharpAssemblyAttributeRegex { get; } = new(@"(\s*\[\s*assembly:\s*(?:.*)\s*\]\s*$(\r?\n)?)", Options | RegexOptions.Multiline); + public static Regex FsharpAssemblyAttributeRegex { get; } = new(@"(\s*\[\s*\\s*\]\s*$(\r?\n)?)", Options | RegexOptions.Multiline); + public static Regex VisualBasicAssemblyAttributeRegex { get; } = new(@"(\s*\\s*$(\r?\n)?)", Options | RegexOptions.Multiline); } internal static class VersionCalculation { - //language=regexp + [StringSyntax(StringSyntaxAttribute.Regex)] public const string DefaultMajorPattern = @"\+semver:\s?(breaking|major)"; - //language=regexp + [StringSyntax(StringSyntaxAttribute.Regex)] public const string DefaultMinorPattern = @"\+semver:\s?(feature|minor)"; - //language=regexp + [StringSyntax(StringSyntaxAttribute.Regex)] public const string DefaultPatchPattern = @"\+semver:\s?(fix|patch)"; - //language=regexp + [StringSyntax(StringSyntaxAttribute.Regex)] public const string DefaultNoBumpPattern = @"\+semver:\s?(none|skip)"; - public static Regex DefaultMajorPatternRegex { get; } = new(DefaultMajorPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); - public static Regex DefaultMinorPatternRegex { get; } = new(DefaultMinorPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); - public static Regex DefaultPatchPatternRegex { get; } = new(DefaultPatchPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); - public static Regex DefaultNoBumpPatternRegex { get; } = new(DefaultNoBumpPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static Regex DefaultMajorRegex { get; } = new(DefaultMajorPattern, Options); + public static Regex DefaultMinorRegex { get; } = new(DefaultMinorPattern, Options); + public static Regex DefaultPatchRegex { get; } = new(DefaultPatchPattern, Options); + public static Regex DefaultNoBumpRegex { get; } = new(DefaultNoBumpPattern, Options); } internal static class SemanticVersion @@ -63,21 +170,71 @@ internal static class SemanticVersion // uses the git-semver spec https://github.com/semver/semver/blob/master/semver.md public static Regex ParseStrictRegex { get; } = new( @"^(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-(?(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + Options); public static Regex ParseLooseRegex { get; } = new( @"^(?(?\d+)(\.(?\d+))?(\.(?\d+))?)(\.(?\d+))?(-(?[^\+]*))?(\+(?.*))?$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + Options); public static Regex ParseBuildMetaDataRegex { get; } = new( @"(?\d+)?(\.?Branch(Name)?\.(?[^\.]+))?(\.?Sha?\.(?[^\.]+))?(?.*)", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + Options); public static Regex FormatBuildMetaDataRegex { get; } = new("[^0-9A-Za-z-.]", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + Options); public static Regex ParsePreReleaseTagRegex { get; } = new( @"(?.*?)\.?(?\d+)?$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + Options); + } + + internal static class AssemblyVersion + { + internal static class CSharp + { + public static Regex TriviaRegex { get; } = new(@" + /\*(.*?)\*/ # Block comments: matches /* ... */ + |//(.*?)\r?\n # Line comments: matches // ... followed by a newline + |""((\\[^\n]|[^""\n])*)"" # Strings: matches "" ... "" including escaped quotes", + RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | Options); + + public static Regex AttributeRegex { get; } = new(@"(?x) # IgnorePatternWhitespace +\[\s*assembly\s*:\s* # The [assembly: part +(System\s*\.\s*Reflection\s*\.\s*)? # The System.Reflection. part (optional) +Assembly(File|Informational)?Version # The attribute AssemblyVersion, AssemblyFileVersion, or AssemblyInformationalVersion +\s*\(\s*\)\s*\] # End brackets ()]", + RegexOptions.IgnorePatternWhitespace | Options); + } + + internal static class FSharp + { + public static Regex TriviaRegex { get; } = new(@" + /\*(.*?)\*/ # Block comments: matches /* ... */ + |//(.*?)\r?\n # Line comments: matches // ... followed by a newline + |""((\\[^\n]|[^""\n])*)"" # Strings: matches "" ... "" including escaped quotes", + RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | Options); + + public static Regex AttributeRegex { get; } = new(@"(?x) # IgnorePatternWhitespace +\[\s*<\s*assembly\s*:\s* # The [\s*\] # End brackets ()>]", + RegexOptions.IgnorePatternWhitespace | Options); + } + + internal static class VisualBasic + { + public static Regex TriviaRegex { get; } = new(@" + '(.*?)\r?\n # Line comments: matches // ... followed by a newline + |""((\\[^\n]|[^""\n])*)"" # Strings: matches "" ... "" including escaped quotes", + RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | Options); + + public static Regex AttributeRegex { get; } = new(@"(?x) # IgnorePatternWhitespace +\<\s*Assembly\s*:\s* # The # End brackets ()>", + RegexOptions.IgnorePatternWhitespace | Options); + } } } diff --git a/src/GitVersion.Core/Core/SourceBranchFinder.cs b/src/GitVersion.Core/Core/SourceBranchFinder.cs index 21dad72d68..5eccba40bf 100644 --- a/src/GitVersion.Core/Core/SourceBranchFinder.cs +++ b/src/GitVersion.Core/Core/SourceBranchFinder.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using GitVersion.Configuration; +using GitVersion.Core; using GitVersion.Extensions; using GitVersion.Git; @@ -18,7 +19,7 @@ public IEnumerable FindSourceBranchesOf(IBranch branch) private class SourceBranchPredicate(IBranch branch, IGitVersionConfiguration configuration) { - private readonly IEnumerable sourceBranchRegexes = GetSourceBranchRegexes(branch, configuration); + private readonly IEnumerable sourceBranchRegexes = GetSourceBranchRegexes(branch, configuration); public bool IsSourceBranch(INamedReference sourceBranchCandidate) { @@ -27,15 +28,15 @@ public bool IsSourceBranch(INamedReference sourceBranchCandidate) var branchName = sourceBranchCandidate.Name.WithoutOrigin; - return this.sourceBranchRegexes.Any(regex => Regex.IsMatch(branchName, regex)); + return this.sourceBranchRegexes.Any(regex => regex.IsMatch(branchName)); } - private static IEnumerable GetSourceBranchRegexes(INamedReference branch, IGitVersionConfiguration configuration) + private static IEnumerable GetSourceBranchRegexes(INamedReference branch, IGitVersionConfiguration configuration) { var currentBranchConfig = configuration.GetBranchConfiguration(branch.Name); if (currentBranchConfig.SourceBranches == null) { - yield return ".*"; + yield return RegexPatterns.Cache.GetOrAdd(".*"); } else { @@ -44,7 +45,7 @@ private static IEnumerable GetSourceBranchRegexes(INamedReference branch { var regex = branches[sourceBranch].RegularExpression; if (regex != null) - yield return regex; + yield return RegexPatterns.Cache.GetOrAdd(regex); } } } diff --git a/src/GitVersion.Core/Core/TaggedSemanticVersionService.cs b/src/GitVersion.Core/Core/TaggedSemanticVersionService.cs index e7e8acffa9..86a99c8849 100644 --- a/src/GitVersion.Core/Core/TaggedSemanticVersionService.cs +++ b/src/GitVersion.Core/Core/TaggedSemanticVersionService.cs @@ -25,7 +25,7 @@ IEnumerable>> GetEleme { yield return GetTaggedSemanticVersionsOfBranchInternal( branch: branch, - tagPrefix: configuration.TagPrefix, + tagPrefix: configuration.TagPrefixPattern, format: configuration.SemanticVersionFormat, ignore: configuration.Ignore, label: label, @@ -37,7 +37,7 @@ IEnumerable>> GetEleme { yield return GetTaggedSemanticVersionsOfMergeTargetInternal( branch: branch, - tagPrefix: configuration.TagPrefix, + tagPrefix: configuration.TagPrefixPattern, format: configuration.SemanticVersionFormat, ignore: configuration.Ignore, label: label, @@ -192,7 +192,7 @@ private IEnumerable> GetTaggedSema { var taggedSemanticVersions = GetTaggedSemanticVersionsOfBranchInternal( branch: releaseBranch, - tagPrefix: configuration.TagPrefix, + tagPrefix: configuration.TagPrefixPattern, format: configuration.SemanticVersionFormat, ignore: configuration.Ignore, label: label, @@ -232,7 +232,7 @@ private IEnumerable> GetTaggedSema { var taggedSemanticVersions = GetTaggedSemanticVersionsOfBranchInternal( branch: releaseBranch, - tagPrefix: configuration.TagPrefix, + tagPrefix: configuration.TagPrefixPattern, format: configuration.SemanticVersionFormat, ignore: configuration.Ignore, label: label, diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index 66686ba81b..caa4032bdd 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using GitVersion.Core; using GitVersion.Extensions; using GitVersion.Git; using GitVersion.Helpers; @@ -94,12 +94,12 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, if (!configuration.RegularExpression.IsNullOrWhiteSpace() && !effectiveBranchName.IsNullOrEmpty()) { effectiveBranchName = effectiveBranchName.RegexReplace("[^a-zA-Z0-9-_]", "-"); - var pattern = new Regex(configuration.RegularExpression, RegexOptions.IgnoreCase); - var match = pattern.Match(effectiveBranchName); + var regex = RegexPatterns.Cache.GetOrAdd(configuration.RegularExpression); + var match = regex.Match(effectiveBranchName); if (match.Success) { // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var groupName in pattern.GetGroupNames()) + foreach (var groupName in regex.GetGroupNames()) { label = label.Replace("{" + groupName + "}", match.Groups[groupName].Value); } @@ -111,7 +111,8 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, // Evaluate tag number pattern and append to prerelease tag, preserving build metadata if (!configuration.LabelNumberPattern.IsNullOrEmpty() && !effectiveBranchName.IsNullOrEmpty()) { - var match = Regex.Match(effectiveBranchName, configuration.LabelNumberPattern); + var regex = RegexPatterns.Cache.GetOrAdd(configuration.LabelNumberPattern); + var match = regex.Match(effectiveBranchName); var numberGroup = match.Groups["number"]; if (numberGroup.Success) { diff --git a/src/GitVersion.Core/Extensions/DictionaryExtensions.cs b/src/GitVersion.Core/Extensions/DictionaryExtensions.cs index 7a1ab3723c..4f5c670692 100644 --- a/src/GitVersion.Core/Extensions/DictionaryExtensions.cs +++ b/src/GitVersion.Core/Extensions/DictionaryExtensions.cs @@ -1,17 +1,27 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + namespace GitVersion.Extensions; public static class DictionaryExtensions { - public static TValue GetOrAdd(this IDictionary dict, TKey key, Func getValue) + public static TValue GetOrAdd(this Dictionary dict, TKey key, Func getValue) where TKey : notnull { - if (dict is null) throw new ArgumentNullException(nameof(dict)); - if (getValue is null) throw new ArgumentNullException(nameof(getValue)); + ArgumentNullException.ThrowIfNull(dict); + ArgumentNullException.ThrowIfNull(getValue); - if (!dict.TryGetValue(key, out var value)) - { - value = getValue(); - dict.Add(key, value); - } + if (dict.TryGetValue(key, out var value)) return value; + value = getValue(); + dict.Add(key, value); return value; } + + public static Regex GetOrAdd(this ConcurrentDictionary dict, [StringSyntax(StringSyntaxAttribute.Regex)] string pattern) + { + ArgumentNullException.ThrowIfNull(dict); + ArgumentNullException.ThrowIfNull(pattern); + + return dict.GetOrAdd(pattern, regex => new(regex, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } } diff --git a/src/GitVersion.Core/Extensions/StringExtensions.cs b/src/GitVersion.Core/Extensions/StringExtensions.cs index b45126b8c2..317fe51704 100644 --- a/src/GitVersion.Core/Extensions/StringExtensions.cs +++ b/src/GitVersion.Core/Extensions/StringExtensions.cs @@ -1,5 +1,5 @@ using System.Diagnostics.CodeAnalysis; -using System.Text.RegularExpressions; +using GitVersion.Core; namespace GitVersion.Extensions; @@ -11,7 +11,11 @@ public static void AppendLineFormat(this StringBuilder stringBuilder, string for stringBuilder.AppendLine(); } - public static string RegexReplace(this string input, string pattern, string replace, RegexOptions options = RegexOptions.None) => Regex.Replace(input, pattern, replace, options); + public static string RegexReplace(this string input, string pattern, string replace) + { + var regex = RegexPatterns.Cache.GetOrAdd(pattern); + return regex.Replace(input, replace); + } public static bool IsEquivalentTo(this string self, string? other) => string.Equals(self, other, StringComparison.OrdinalIgnoreCase); diff --git a/src/GitVersion.Core/Git/ReferenceName.cs b/src/GitVersion.Core/Git/ReferenceName.cs index bb86f2d09e..5e38c3cc60 100644 --- a/src/GitVersion.Core/Git/ReferenceName.cs +++ b/src/GitVersion.Core/Git/ReferenceName.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using System.Text.RegularExpressions; using GitVersion.Extensions; using GitVersion.Helpers; @@ -42,39 +41,33 @@ public static ReferenceName Parse(string canonicalName) throw new ArgumentException($"The {nameof(canonicalName)} is not a Canonical name"); } - public static bool TryParse([NotNullWhen(true)] out ReferenceName? value, string canonicalName) - { - value = null; - - if (IsPrefixedBy(canonicalName, LocalBranchPrefix) - || IsPrefixedBy(canonicalName, RemoteTrackingBranchPrefix) - || IsPrefixedBy(canonicalName, TagPrefix) - || IsPrefixedBy(canonicalName, PullRequestPrefixes)) - { - value = new(canonicalName); - } - - return value is not null; - } - public static ReferenceName FromBranchName(string branchName) - { - if (TryParse(out ReferenceName? value, branchName)) return value; - return Parse(LocalBranchPrefix + branchName); - } + => TryParse(out ReferenceName? value, branchName) + ? value + : Parse(LocalBranchPrefix + branchName); public string Canonical { get; } + public string Friendly { get; } + public string WithoutOrigin { get; } + public bool IsLocalBranch { get; } + public bool IsRemoteBranch { get; } + public bool IsTag { get; } + public bool IsPullRequest { get; } public bool Equals(ReferenceName? other) => equalityHelper.Equals(this, other); + public int CompareTo(ReferenceName? other) => comparerHelper.Compare(this, other); + public override bool Equals(object? obj) => Equals(obj as ReferenceName); + public override int GetHashCode() => equalityHelper.GetHashCode(this); + public override string ToString() => Friendly; public static bool operator ==(ReferenceName? left, ReferenceName? right) @@ -86,38 +79,6 @@ public static ReferenceName FromBranchName(string branchName) public static bool operator !=(ReferenceName? left, ReferenceName? right) => !(left == right); - public bool TryGetSemanticVersion(out (SemanticVersion Value, string? Name) result, - Regex versionPatternRegex, - string? tagPrefix, - SemanticVersionFormat format) - { - result = default; - - int length = 0; - foreach (var branchPart in WithoutOrigin.Split(GetBranchSeparator())) - { - if (string.IsNullOrEmpty(branchPart)) return false; - - var match = versionPatternRegex.NotNull().Match(branchPart); - if (match.Success) - { - var versionPart = match.Groups["version"].Value; - if (SemanticVersion.TryParse(versionPart, tagPrefix, out var semanticVersion, format)) - { - length += versionPart.Length; - var name = WithoutOrigin[length..].Trim('-'); - result = new(semanticVersion, name == string.Empty ? null : name); - return true; - } - } - length += branchPart.Length + 1; - } - - return false; - } - - private char GetBranchSeparator() => WithoutOrigin.Contains('/') || !WithoutOrigin.Contains('-') ? '/' : '-'; - public bool EquivalentTo(string? name) => Canonical.Equals(name, StringComparison.OrdinalIgnoreCase) || Friendly.Equals(name, StringComparison.OrdinalIgnoreCase) @@ -143,9 +104,25 @@ private string RemoveOrigin() { return Friendly[OriginPrefix.Length..]; } + return Friendly; } + private static bool TryParse([NotNullWhen(true)] out ReferenceName? value, string canonicalName) + { + value = null; + + if (IsPrefixedBy(canonicalName, LocalBranchPrefix) + || IsPrefixedBy(canonicalName, RemoteTrackingBranchPrefix) + || IsPrefixedBy(canonicalName, TagPrefix) + || IsPrefixedBy(canonicalName, PullRequestPrefixes)) + { + value = new(canonicalName); + } + + return value is not null; + } + private static bool IsPrefixedBy(string input, string prefix) => input.StartsWith(prefix, StringComparison.Ordinal); private static bool IsPrefixedBy(string input, string[] prefixes) => prefixes.Any(prefix => IsPrefixedBy(input, prefix)); diff --git a/src/GitVersion.Core/MergeMessage.cs b/src/GitVersion.Core/MergeMessage.cs index 036fb7c177..8d195f1b22 100644 --- a/src/GitVersion.Core/MergeMessage.cs +++ b/src/GitVersion.Core/MergeMessage.cs @@ -9,7 +9,7 @@ namespace GitVersion; public class MergeMessage { - private static readonly IList DefaultFormats = + private static readonly IList<(string Name, Regex Pattern)> DefaultFormats = [ new("Default", RegexPatterns.MergeMessage.DefaultMergeMessageRegex), new("SmartGit", RegexPatterns.MergeMessage.SmartGitMergeMessageRegex), @@ -25,21 +25,21 @@ public MergeMessage(string mergeMessage, IGitVersionConfiguration configuration) { mergeMessage.NotNull(); - if (mergeMessage == string.Empty) return; + if (mergeMessage.Length == 0) return; // Concatenate configuration formats with the defaults. // Ensure configurations are processed first. var allFormats = configuration.MergeMessageFormats - .Select(x => new MergeMessageFormat(x.Key, new(x.Value, RegexOptions.IgnoreCase | RegexOptions.Compiled))) + .Select(x => (Name: x.Key, Pattern: RegexPatterns.Cache.GetOrAdd(x.Value))) .Concat(DefaultFormats); - foreach (var format in allFormats) + foreach (var (Name, Pattern) in allFormats) { - var match = format.Pattern.Match(mergeMessage); + var match = Pattern.Match(mergeMessage); if (!match.Success) continue; - FormatName = format.Name; + FormatName = Name; var sourceBranch = match.Groups["SourceBranch"].Value; MergedBranch = GetMergedBranchName(sourceBranch); @@ -53,9 +53,7 @@ public MergeMessage(string mergeMessage, IGitVersionConfiguration configuration) PullRequestNumber = pullNumber; } - Version = ParseVersion( - configuration.VersionInBranchRegex, configuration.TagPrefix, configuration.SemanticVersionFormat - ); + Version = MergedBranch?.TryGetSemanticVersion(out var result, configuration) == true ? result.Value : null; break; } @@ -69,20 +67,6 @@ public MergeMessage(string mergeMessage, IGitVersionConfiguration configuration) public int? PullRequestNumber { get; } public SemanticVersion? Version { get; } - private SemanticVersion? ParseVersion(Regex versionInBranchRegex, string? tagPrefix, SemanticVersionFormat format) - { - if (MergedBranch?.TryGetSemanticVersion(out var result, versionInBranchRegex, tagPrefix, format) == true) - return result.Value; - return null; - } - - private class MergeMessageFormat(string name, Regex pattern) - { - public string Name { get; } = name; - - public Regex Pattern { get; } = pattern; - } - private ReferenceName GetMergedBranchName(string mergedBranch) { if (FormatName == "RemoteTracking" && !mergedBranch.StartsWith(ReferenceName.RemoteTrackingBranchPrefix)) @@ -106,7 +90,7 @@ public static bool TryParse( if (isValidMergeCommit) { - mergeMessage = new(mergeCommit.Message, configuration); + mergeMessage = new MergeMessage(mergeCommit.Message, configuration); } return isValidMergeCommit; diff --git a/src/GitVersion.Core/Polyfills/StringSyntaxAttribute.cs b/src/GitVersion.Core/Polyfills/StringSyntaxAttribute.cs new file mode 100644 index 0000000000..f3faee9ed6 --- /dev/null +++ b/src/GitVersion.Core/Polyfills/StringSyntaxAttribute.cs @@ -0,0 +1,35 @@ +#if !NET7_0_OR_GREATER + +// The namespace is important +namespace System.Diagnostics.CodeAnalysis; + +/// Fake version of the StringSyntaxAttribute, which was introduced in .NET 7 +[SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API")] +[SuppressMessage("Style", "IDE0060:Remove unused parameter")] +[AttributeUsage(AttributeTargets.All)] +public sealed class StringSyntaxAttribute : Attribute +{ + /// The syntax identifier for strings containing composite formats. + public const string CompositeFormat = nameof(CompositeFormat); + + /// The syntax identifier for strings containing regular expressions. + public const string Regex = nameof(Regex); + + /// The syntax identifier for strings containing date information. + public const string DateTimeFormat = nameof(DateTimeFormat); + + /// + /// Initializes a new instance of the class. + /// + public StringSyntaxAttribute(string syntax) + { + } + + /// + /// Initializes a new instance of the class. + /// + public StringSyntaxAttribute(string syntax, params object?[] arguments) + { + } +} +#endif diff --git a/src/GitVersion.Core/PublicAPI.Shipped.txt b/src/GitVersion.Core/PublicAPI.Shipped.txt index e68731b648..143462941c 100644 --- a/src/GitVersion.Core/PublicAPI.Shipped.txt +++ b/src/GitVersion.Core/PublicAPI.Shipped.txt @@ -86,7 +86,6 @@ GitVersion.Configuration.EffectiveConfiguration.TrackMergeTarget.get -> bool GitVersion.Configuration.EffectiveConfiguration.TracksReleaseBranches.get -> bool GitVersion.Configuration.EffectiveConfiguration.UpdateBuildNumber.get -> bool GitVersion.Configuration.EffectiveConfiguration.VersionFilters.get -> System.Collections.Generic.IEnumerable! -GitVersion.Configuration.EffectiveConfiguration.VersionInBranchRegex.get -> System.Text.RegularExpressions.Regex! GitVersion.Configuration.EffectiveConfiguration.VersionStrategy.get -> GitVersion.VersionCalculation.VersionStrategies GitVersion.Configuration.IBranchConfiguration GitVersion.Configuration.IBranchConfiguration.CommitMessageIncrementing.get -> GitVersion.VersionCalculation.CommitMessageIncrementMode? @@ -132,11 +131,9 @@ GitVersion.Configuration.IGitVersionConfiguration.NextVersion.get -> string? GitVersion.Configuration.IGitVersionConfiguration.NoBumpMessage.get -> string? GitVersion.Configuration.IGitVersionConfiguration.PatchVersionBumpMessage.get -> string? GitVersion.Configuration.IGitVersionConfiguration.SemanticVersionFormat.get -> GitVersion.SemanticVersionFormat -GitVersion.Configuration.IGitVersionConfiguration.TagPrefix.get -> string? GitVersion.Configuration.IGitVersionConfiguration.TagPreReleaseWeight.get -> int? GitVersion.Configuration.IGitVersionConfiguration.UpdateBuildNumber.get -> bool GitVersion.Configuration.IGitVersionConfiguration.VersionInBranchPattern.get -> string? -GitVersion.Configuration.IGitVersionConfiguration.VersionInBranchRegex.get -> System.Text.RegularExpressions.Regex! GitVersion.Configuration.IGitVersionConfiguration.VersionStrategy.get -> GitVersion.VersionCalculation.VersionStrategies GitVersion.Configuration.IGitVersionConfiguration.Workflow.get -> string? GitVersion.Configuration.IIgnoreConfiguration @@ -287,7 +284,6 @@ GitVersion.Git.ReferenceName.IsPullRequest.get -> bool GitVersion.Git.ReferenceName.IsRemoteBranch.get -> bool GitVersion.Git.ReferenceName.IsTag.get -> bool GitVersion.Git.ReferenceName.ReferenceName(string! canonical) -> void -GitVersion.Git.ReferenceName.TryGetSemanticVersion(out (GitVersion.SemanticVersion! Value, string? Name) result, System.Text.RegularExpressions.Regex! versionPatternRegex, string? tagPrefix, GitVersion.SemanticVersionFormat format) -> bool GitVersion.Git.ReferenceName.WithoutOrigin.get -> string! GitVersion.Git.RefSpecDirection GitVersion.Git.RefSpecDirection.Fetch = 0 -> GitVersion.Git.RefSpecDirection @@ -675,7 +671,6 @@ GitVersion.VersionCalculation.IEffectiveBranchConfigurationFinder.GetConfigurati GitVersion.VersionCalculation.IIncrementStrategyFinder GitVersion.VersionCalculation.IIncrementStrategyFinder.DetermineIncrementedField(GitVersion.Git.ICommit! currentCommit, GitVersion.Git.ICommit? baseVersionSource, bool shouldIncrement, GitVersion.Configuration.EffectiveConfiguration! configuration, string? label) -> GitVersion.VersionField GitVersion.VersionCalculation.IIncrementStrategyFinder.GetIncrementForcedByCommit(GitVersion.Git.ICommit! commit, GitVersion.Configuration.IGitVersionConfiguration! configuration) -> GitVersion.VersionField -GitVersion.VersionCalculation.IIncrementStrategyFinder.GetIncrementForCommits(string? majorVersionBumpMessage, string? minorVersionBumpMessage, string? patchVersionBumpMessage, string? noBumpMessage, GitVersion.Git.ICommit![]! commits) -> GitVersion.VersionField? GitVersion.VersionCalculation.IIncrementStrategyFinder.GetMergedCommits(GitVersion.Git.ICommit! mergeCommit, int index, GitVersion.Configuration.IIgnoreConfiguration! ignore) -> System.Collections.Generic.IEnumerable! GitVersion.VersionCalculation.INextVersionCalculator GitVersion.VersionCalculation.INextVersionCalculator.FindVersion() -> GitVersion.SemanticVersion! @@ -742,13 +737,11 @@ override GitVersion.VersionCalculation.NextVersion.Equals(object? other) -> bool override GitVersion.VersionCalculation.NextVersion.GetHashCode() -> int override GitVersion.VersionCalculation.NextVersion.ToString() -> string! static GitVersion.Configuration.ReferenceNameExtensions.TryGetSemanticVersion(this GitVersion.Git.ReferenceName! source, out (GitVersion.SemanticVersion! Value, string? Name) result, GitVersion.Configuration.EffectiveConfiguration! configuration) -> bool -static GitVersion.Configuration.ReferenceNameExtensions.TryGetSemanticVersion(this GitVersion.Git.ReferenceName! source, out (GitVersion.SemanticVersion! Value, string? Name) result, GitVersion.Configuration.IGitVersionConfiguration! configuration) -> bool static GitVersion.Extensions.AssemblyVersionsGeneratorExtensions.GetAssemblyFileVersion(this GitVersion.SemanticVersion! sv, GitVersion.Configuration.AssemblyFileVersioningScheme scheme) -> string? static GitVersion.Extensions.AssemblyVersionsGeneratorExtensions.GetAssemblyVersion(this GitVersion.SemanticVersion! sv, GitVersion.Configuration.AssemblyVersioningScheme scheme) -> string? static GitVersion.Extensions.CommonExtensions.NotNull(this T? value, string! name = "") -> T! static GitVersion.Extensions.CommonExtensions.NotNullOrEmpty(this string? value, string! name = "") -> string! static GitVersion.Extensions.CommonExtensions.NotNullOrWhitespace(this string? value, string! name = "") -> string! -static GitVersion.Extensions.DictionaryExtensions.GetOrAdd(this System.Collections.Generic.IDictionary! dict, TKey key, System.Func! getValue) -> TValue static GitVersion.Extensions.EnumerableExtensions.AddRange(this System.Collections.Generic.ICollection! source, System.Collections.Generic.IEnumerable! items) -> void static GitVersion.Extensions.EnumerableExtensions.OnlyOrDefault(this System.Collections.Generic.IEnumerable! source) -> T? static GitVersion.Extensions.EnumerableExtensions.SingleOfType(this System.Collections.IEnumerable! source) -> T @@ -764,7 +757,6 @@ static GitVersion.Extensions.StringExtensions.IsEmpty(this string? value) -> boo static GitVersion.Extensions.StringExtensions.IsEquivalentTo(this string! self, string? other) -> bool static GitVersion.Extensions.StringExtensions.IsNullOrEmpty(this string? value) -> bool static GitVersion.Extensions.StringExtensions.IsNullOrWhiteSpace(this string? value) -> bool -static GitVersion.Extensions.StringExtensions.RegexReplace(this string! input, string! pattern, string! replace, System.Text.RegularExpressions.RegexOptions options = System.Text.RegularExpressions.RegexOptions.None) -> string! static GitVersion.Extensions.StringExtensions.WithPrefixIfNotNullOrEmpty(this string! value, string! prefix) -> string! static GitVersion.Git.BranchCommit.operator !=(GitVersion.Git.BranchCommit left, GitVersion.Git.BranchCommit right) -> bool static GitVersion.Git.BranchCommit.operator ==(GitVersion.Git.BranchCommit left, GitVersion.Git.BranchCommit right) -> bool @@ -773,7 +765,6 @@ static GitVersion.Git.ReferenceName.FromBranchName(string! branchName) -> GitVer static GitVersion.Git.ReferenceName.operator !=(GitVersion.Git.ReferenceName? left, GitVersion.Git.ReferenceName? right) -> bool static GitVersion.Git.ReferenceName.operator ==(GitVersion.Git.ReferenceName? left, GitVersion.Git.ReferenceName? right) -> bool static GitVersion.Git.ReferenceName.Parse(string! canonicalName) -> GitVersion.Git.ReferenceName! -static GitVersion.Git.ReferenceName.TryParse(out GitVersion.Git.ReferenceName? value, string! canonicalName) -> bool static GitVersion.Helpers.Disposable.Create(System.Action! disposer) -> System.IDisposable! static GitVersion.Helpers.Disposable.Create(T value, System.Action! disposer) -> GitVersion.Helpers.IDisposable! static GitVersion.Helpers.EncodingHelper.DetectEncoding(string? filename) -> System.Text.Encoding? diff --git a/src/GitVersion.Core/PublicAPI.Unshipped.txt b/src/GitVersion.Core/PublicAPI.Unshipped.txt index 7dc5c58110..439fac159f 100644 --- a/src/GitVersion.Core/PublicAPI.Unshipped.txt +++ b/src/GitVersion.Core/PublicAPI.Unshipped.txt @@ -1 +1,7 @@ #nullable enable +GitVersion.Configuration.EffectiveConfiguration.VersionInBranchPattern.get -> string? +GitVersion.Configuration.IGitVersionConfiguration.TagPrefixPattern.get -> string? +static GitVersion.Configuration.ReferenceNameExtensions.TryGetSemanticVersion(this GitVersion.Git.ReferenceName! source, out (GitVersion.SemanticVersion! Value, string? Name) result, GitVersion.Configuration.IGitVersionConfiguration! configuration) -> bool +static GitVersion.Extensions.DictionaryExtensions.GetOrAdd(this System.Collections.Concurrent.ConcurrentDictionary! dict, string! pattern) -> System.Text.RegularExpressions.Regex! +static GitVersion.Extensions.DictionaryExtensions.GetOrAdd(this System.Collections.Generic.Dictionary! dict, TKey key, System.Func! getValue) -> TValue +static GitVersion.Extensions.StringExtensions.RegexReplace(this string! input, string! pattern, string! replace) -> string! diff --git a/src/GitVersion.Core/SemVer/SemanticVersion.cs b/src/GitVersion.Core/SemVer/SemanticVersion.cs index beeb96321c..866bffe6af 100644 --- a/src/GitVersion.Core/SemVer/SemanticVersion.cs +++ b/src/GitVersion.Core/SemVer/SemanticVersion.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Text.RegularExpressions; using GitVersion.Core; using GitVersion.Extensions; @@ -76,18 +75,7 @@ public override bool Equals(object? obj) return obj.GetType() == GetType() && Equals((SemanticVersion)obj); } - public override int GetHashCode() - { - unchecked - { - var hashCode = this.Major.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Minor.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Patch.GetHashCode(); - hashCode = (hashCode * 397) ^ this.PreReleaseTag.GetHashCode(); - hashCode = (hashCode * 397) ^ this.BuildMetaData.GetHashCode(); - return hashCode; - } - } + public override int GetHashCode() => HashCode.Combine(Major, Minor, Patch, PreReleaseTag, BuildMetaData); public static bool operator ==(SemanticVersion? v1, SemanticVersion? v2) { @@ -102,40 +90,32 @@ public override int GetHashCode() public static bool operator >(SemanticVersion v1, SemanticVersion v2) { - if (v1 == null) - throw new ArgumentNullException(nameof(v1)); - if (v2 == null) - throw new ArgumentNullException(nameof(v2)); + ArgumentNullException.ThrowIfNull(v1); + ArgumentNullException.ThrowIfNull(v2); return v1.CompareTo(v2) > 0; } public static bool operator >=(SemanticVersion v1, SemanticVersion v2) { - if (v1 == null) - throw new ArgumentNullException(nameof(v1)); - if (v2 == null) - throw new ArgumentNullException(nameof(v2)); + ArgumentNullException.ThrowIfNull(v1); + ArgumentNullException.ThrowIfNull(v2); return v1.CompareTo(v2) >= 0; } public static bool operator <=(SemanticVersion v1, SemanticVersion v2) { - if (v1 == null) - throw new ArgumentNullException(nameof(v1)); - if (v2 == null) - throw new ArgumentNullException(nameof(v2)); + ArgumentNullException.ThrowIfNull(v1); + ArgumentNullException.ThrowIfNull(v2); return v1.CompareTo(v2) <= 0; } public static bool operator <(SemanticVersion v1, SemanticVersion v2) { - if (v1 == null) - throw new ArgumentNullException(nameof(v1)); - if (v2 == null) - throw new ArgumentNullException(nameof(v2)); + ArgumentNullException.ThrowIfNull(v1); + ArgumentNullException.ThrowIfNull(v2); return v1.CompareTo(v2) < 0; } @@ -152,7 +132,8 @@ public static SemanticVersion Parse( public static bool TryParse(string version, string? tagPrefixRegex, [NotNullWhen(true)] out SemanticVersion? semanticVersion, SemanticVersionFormat format = SemanticVersionFormat.Strict) { - var match = Regex.Match(version, $"^({tagPrefixRegex})(?.*)$"); + var regex = RegexPatterns.Cache.GetOrAdd($"^({tagPrefixRegex})(?.*)$"); + var match = regex.Match(version); if (!match.Success) { diff --git a/src/GitVersion.Core/VersionCalculation/Abstractions/IIncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/Abstractions/IIncrementStrategyFinder.cs index 5bd1ce41eb..6331be284d 100644 --- a/src/GitVersion.Core/VersionCalculation/Abstractions/IIncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/Abstractions/IIncrementStrategyFinder.cs @@ -8,11 +8,6 @@ public interface IIncrementStrategyFinder VersionField DetermineIncrementedField( ICommit currentCommit, ICommit? baseVersionSource, bool shouldIncrement, EffectiveConfiguration configuration, string? label); - VersionField? GetIncrementForCommits( - string? majorVersionBumpMessage, string? minorVersionBumpMessage, string? patchVersionBumpMessage, string? noBumpMessage, - ICommit[] commits - ); - IEnumerable GetMergedCommits(ICommit mergeCommit, int index, IIgnoreConfiguration ignore); VersionField GetIncrementForcedByCommit(ICommit commit, IGitVersionConfiguration configuration); diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 957974df4a..26ebccda50 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using System.Text.RegularExpressions; using GitVersion.Configuration; using GitVersion.Core; @@ -10,7 +9,6 @@ namespace GitVersion.VersionCalculation; internal class IncrementStrategyFinder(IGitRepository repository, ITaggedSemanticVersionRepository taggedSemanticVersionRepository) : IIncrementStrategyFinder { - private static readonly ConcurrentDictionary CompiledRegexCache = new(); private readonly Dictionary commitIncrementCache = []; private readonly Dictionary> headCommitsMapCache = []; private readonly Dictionary headCommitsCache = []; @@ -44,18 +42,17 @@ public VersionField DetermineIncrementedField( return commitMessageIncrement.Value; } - public VersionField? GetIncrementForCommits(string? majorVersionBumpMessage, string? minorVersionBumpMessage, - string? patchVersionBumpMessage, string? noBumpMessage, ICommit[] commits) + private VersionField? GetIncrementForCommits(EffectiveConfiguration configuration, ICommit[] commits) { commits.NotNull(); - var majorRegex = TryGetRegexOrDefault(majorVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultMajorPatternRegex); - var minorRegex = TryGetRegexOrDefault(minorVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultMinorPatternRegex); - var patchRegex = TryGetRegexOrDefault(patchVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultPatchPatternRegex); - var none = TryGetRegexOrDefault(noBumpMessage, RegexPatterns.VersionCalculation.DefaultNoBumpPatternRegex); + var majorRegex = TryGetRegexOrDefault(configuration.MajorVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultMajorRegex); + var minorRegex = TryGetRegexOrDefault(configuration.MinorVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultMinorRegex); + var patchRegex = TryGetRegexOrDefault(configuration.PatchVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultPatchRegex); + var noBumpRegex = TryGetRegexOrDefault(configuration.NoBumpMessage, RegexPatterns.VersionCalculation.DefaultNoBumpRegex); var increments = commits - .Select(c => GetIncrementFromCommit(c, majorRegex, minorRegex, patchRegex, none)) + .Select(c => GetIncrementFromCommit(c, majorRegex, minorRegex, patchRegex, noBumpRegex)) .Where(v => v != null) .ToList(); @@ -86,11 +83,7 @@ public VersionField DetermineIncrementedField( commits = commits.Where(c => c.Parents.Count > 1); } - return GetIncrementForCommits( - majorVersionBumpMessage: configuration.MajorVersionBumpMessage, - minorVersionBumpMessage: configuration.MinorVersionBumpMessage, - patchVersionBumpMessage: configuration.PatchVersionBumpMessage, - noBumpMessage: configuration.NoBumpMessage, + return GetIncrementForCommits(configuration, commits: commits.ToArray() ); } @@ -98,7 +91,7 @@ public VersionField DetermineIncrementedField( private static Regex TryGetRegexOrDefault(string? messageRegex, Regex defaultRegex) => messageRegex == null ? defaultRegex - : CompiledRegexCache.GetOrAdd(messageRegex, pattern => new(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase)); + : RegexPatterns.Cache.GetOrAdd(messageRegex); private IReadOnlyCollection GetCommitHistory(string? tagPrefix, SemanticVersionFormat semanticVersionFormat, ICommit? baseVersionSource, ICommit currentCommit, string? label, IIgnoreConfiguration ignore) @@ -175,13 +168,13 @@ private ICommit[] GetHeadCommits(ICommit? headCommit, IIgnoreConfiguration ignor this.headCommitsCache.GetOrAdd(headCommit?.Sha ?? "NULL", () => GetCommitsReacheableFromHead(headCommit, ignore).ToArray()); - private VersionField? GetIncrementFromCommit(ICommit commit, Regex majorRegex, Regex minorRegex, Regex patchRegex, Regex none) => + private VersionField? GetIncrementFromCommit(ICommit commit, Regex majorRegex, Regex minorRegex, Regex patchRegex, Regex noBumpRegex) => this.commitIncrementCache.GetOrAdd(commit.Sha, () => - GetIncrementFromMessage(commit.Message, majorRegex, minorRegex, patchRegex, none)); + GetIncrementFromMessage(commit.Message, majorRegex, minorRegex, patchRegex, noBumpRegex)); - private static VersionField? GetIncrementFromMessage(string message, Regex majorRegex, Regex minorRegex, Regex patchRegex, Regex none) + private static VersionField? GetIncrementFromMessage(string message, Regex majorRegex, Regex minorRegex, Regex patchRegex, Regex noBumpRegex) { - if (none.IsMatch(message)) return VersionField.None; + if (noBumpRegex.IsMatch(message)) return VersionField.None; if (majorRegex.IsMatch(message)) return VersionField.Major; if (minorRegex.IsMatch(message)) return VersionField.Minor; if (patchRegex.IsMatch(message)) return VersionField.Patch; @@ -232,10 +225,10 @@ public VersionField GetIncrementForcedByCommit(ICommit commit, IGitVersionConfig commit.NotNull(); configuration.NotNull(); - var majorRegex = TryGetRegexOrDefault(configuration.MajorVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultMajorPatternRegex); - var minorRegex = TryGetRegexOrDefault(configuration.MinorVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultMinorPatternRegex); - var patchRegex = TryGetRegexOrDefault(configuration.PatchVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultPatchPatternRegex); - var none = TryGetRegexOrDefault(configuration.NoBumpMessage, RegexPatterns.VersionCalculation.DefaultNoBumpPatternRegex); + var majorRegex = TryGetRegexOrDefault(configuration.MajorVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultMajorRegex); + var minorRegex = TryGetRegexOrDefault(configuration.MinorVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultMinorRegex); + var patchRegex = TryGetRegexOrDefault(configuration.PatchVersionBumpMessage, RegexPatterns.VersionCalculation.DefaultPatchRegex); + var none = TryGetRegexOrDefault(configuration.NoBumpMessage, RegexPatterns.VersionCalculation.DefaultNoBumpRegex); return GetIncrementFromCommit(commit, majorRegex, minorRegex, patchRegex, none) ?? VersionField.None; } diff --git a/src/GitVersion.Core/VersionCalculation/VersionCalculators/NextVersionCalculator.cs b/src/GitVersion.Core/VersionCalculation/VersionCalculators/NextVersionCalculator.cs index 41e16691e5..5111804989 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionCalculators/NextVersionCalculator.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionCalculators/NextVersionCalculator.cs @@ -79,7 +79,7 @@ public virtual SemanticVersion FindVersion() var ignore = Context.Configuration.Ignore; var alternativeSemanticVersion = taggedSemanticVersionService.GetTaggedSemanticVersionsOfBranch( branch: nextVersion.BranchConfiguration.Branch, - tagPrefix: Context.Configuration.TagPrefix, + tagPrefix: Context.Configuration.TagPrefixPattern, format: Context.Configuration.SemanticVersionFormat, ignore: Context.Configuration.Ignore, notOlderThan: Context.CurrentCommit.When diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs index 1fa219988c..87645553ca 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs @@ -25,7 +25,7 @@ public IEnumerable GetBaseVersions(EffectiveBranchConfiguration con if (!nextVersion.IsNullOrEmpty()) { var semanticVersion = SemanticVersion.Parse( - nextVersion, Context.Configuration.TagPrefix, Context.Configuration.SemanticVersionFormat + nextVersion, Context.Configuration.TagPrefixPattern, Context.Configuration.SemanticVersionFormat ); var label = configuration.Value.GetBranchSpecificLabel(Context.CurrentBranch.Name, null); diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/VersionInBranchNameVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/VersionInBranchNameVersionStrategy.cs index 8c6af0f781..cd6880601d 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/VersionInBranchNameVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/VersionInBranchNameVersionStrategy.cs @@ -35,8 +35,7 @@ public bool TryGetBaseVersion(EffectiveBranchConfiguration configuration, [NotNu foreach (var branch in new[] { Context.CurrentBranch, configuration.Branch }) { - if (branch.Name.TryGetSemanticVersion(out var result, configuration.Value.VersionInBranchRegex, - configuration.Value.TagPrefix, configuration.Value.SemanticVersionFormat)) + if (branch.Name.TryGetSemanticVersion(out var result, configuration.Value)) { string? branchNameOverride = null; if (!result.Name.IsNullOrEmpty() && (Context.CurrentBranch.Name.Equals(branch.Name) diff --git a/src/GitVersion.MsBuild.Tests/InvalidFileCheckerTests.cs b/src/GitVersion.MsBuild.Tests/InvalidFileCheckerTests.cs index 12b6315a1e..5eb2c3f2f9 100644 --- a/src/GitVersion.MsBuild.Tests/InvalidFileCheckerTests.cs +++ b/src/GitVersion.MsBuild.Tests/InvalidFileCheckerTests.cs @@ -1,7 +1,6 @@ using GitVersion.Core.Tests.Helpers; using GitVersion.Helpers; using GitVersion.MsBuild.Tests.Mocks; -using Microsoft.Build.Framework; namespace GitVersion.MsBuild.Tests; @@ -30,15 +29,17 @@ public void VerifyIgnoreNonAssemblyInfoFile() { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "SomeOtherFile.cs"))) { - writer.Write(@" -using System; -using System.Reflection; + writer.Write(""" -[assembly: AssemblyVersion(""1.0.0.0"")] -"); + using System; + using System.Reflection; + + [assembly: AssemblyVersion("1.0.0.0")] + + """); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "SomeOtherFile.cs" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "SomeOtherFile.cs" }], this.projectFile); } [Test] @@ -46,15 +47,17 @@ public void VerifyAttributeFoundCSharp([Values("AssemblyVersion", "AssemblyFileV { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.cs"))) { - writer.Write(@" -using System; -using System.Reflection; + writer.Write(""" + + using System; + using System.Reflection; -[assembly:{0}(""1.0.0.0"")] -", attribute); + [assembly:{0}("1.0.0.0")] + + """, attribute); } - var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.cs" } }, this.projectFile), attribute); + var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.cs" }], this.projectFile), attribute); ex.ShouldNotBeNull(); Assert.That(ex.Message, Is.EqualTo("File contains assembly version attributes which conflict with the attributes generated by GitVersion AssemblyInfo.cs")); } @@ -64,16 +67,18 @@ public void VerifyUnformattedAttributeFoundCSharp([Values("AssemblyVersion", "As { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.cs"))) { - writer.Write(@" -using System; -using System.Reflection; + writer.Write(""" + + using System; + using System.Reflection; + + [ assembly : + {0} ( "1.0.0.0")] -[ assembly : -{0} ( ""1.0.0.0"")] -", attribute); + """, attribute); } - var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.cs" } }, this.projectFile), attribute); + var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.cs" }], this.projectFile), attribute); ex.ShouldNotBeNull(); Assert.That(ex.Message, Is.EqualTo("File contains assembly version attributes which conflict with the attributes generated by GitVersion AssemblyInfo.cs")); } @@ -83,15 +88,17 @@ public void VerifyCommentWorksCSharp([Values("AssemblyVersion", "AssemblyFileVer { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.cs"))) { - writer.Write(@" -using System; -using System.Reflection; + writer.Write(""" -//[assembly: {0}(""1.0.0.0"")] -", attribute); + using System; + using System.Reflection; + + //[assembly: {0}("1.0.0.0")] + + """, attribute); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.cs" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.cs" }], this.projectFile); } [Test] @@ -99,14 +106,16 @@ public void VerifyCommentWithNoNewLineAtEndWorksCSharp([Values("AssemblyVersion" { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.cs"))) { - writer.Write(@" -using System; -using System.Reflection; + writer.Write(""" + + using System; + using System.Reflection; -//[assembly: {0}(""1.0.0.0"")]", attribute); + //[assembly: {0}("1.0.0.0")] + """, attribute); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.cs" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.cs" }], this.projectFile); } [Test] @@ -114,18 +123,20 @@ public void VerifyStringWorksCSharp([Values("AssemblyVersion", "AssemblyFileVers { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.cs"))) { - writer.Write(@" -using System; -using System.Reflection; + writer.Write(""" + + using System; + using System.Reflection; + + public class Temp + {{ + static const string Foo = "[assembly: {0}(""1.0.0.0"")]"; + }} -public class Temp -{{ - static const string Foo = ""[assembly: {0}(""""1.0.0.0"""")]""; -}} -", attribute); + """, attribute); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.cs" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.cs" }], this.projectFile); } [Test] @@ -133,17 +144,19 @@ public void VerifyIdentifierWorksCSharp([Values("AssemblyVersion", "AssemblyFile { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.cs"))) { - writer.Write(@" -using System; -using System.Reflection; + writer.Write(""" -public class {0} -{{ -}} -", attribute); + using System; + using System.Reflection; + + public class {0} + {{ + }} + + """, attribute); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.cs" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.cs" }], this.projectFile); } [Test] @@ -151,15 +164,17 @@ public void VerifyAttributeFoundVisualBasic([Values("AssemblyVersion", "Assembly { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.vb"))) { - writer.Write(@" -Imports System -Imports System.Reflection + writer.Write(""" + + Imports System + Imports System.Reflection - -", attribute); + + + """, attribute); } - var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.vb" } }, this.projectFile), attribute); + var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.vb" }], this.projectFile), attribute); ex.ShouldNotBeNull(); Assert.That(ex.Message, Is.EqualTo("File contains assembly version attributes which conflict with the attributes generated by GitVersion AssemblyInfo.vb")); } @@ -169,16 +184,18 @@ public void VerifyUnformattedAttributeFoundVisualBasic([Values("AssemblyVersion" { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.vb"))) { - writer.Write(@" -Imports System -Imports System.Reflection + writer.Write(""" + + Imports System + Imports System.Reflection + + < Assembly : + {0} ( "1.0.0.0")> -< Assembly : -{0} ( ""1.0.0.0"")> -", attribute); + """, attribute); } - var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.vb" } }, this.projectFile), attribute); + var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.vb" }], this.projectFile), attribute); ex.ShouldNotBeNull(); Assert.That(ex.Message, Is.EqualTo("File contains assembly version attributes which conflict with the attributes generated by GitVersion AssemblyInfo.vb")); } @@ -188,15 +205,17 @@ public void VerifyCommentWorksVisualBasic([Values("AssemblyVersion", "AssemblyFi { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.vb"))) { - writer.Write(@" -Imports System -Imports System.Reflection + writer.Write(""" -' -", attribute); + Imports System + Imports System.Reflection + + ' + + """, attribute); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.vb" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.vb" }], this.projectFile); } [Test] @@ -204,14 +223,16 @@ public void VerifyCommentWithNoNewLineAtEndWorksVisualBasic([Values("AssemblyVer { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.vb"))) { - writer.Write(@" -Imports System -Imports System.Reflection + writer.Write(""" + + Imports System + Imports System.Reflection -'", attribute); + ' + """, attribute); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.vb" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.vb" }], this.projectFile); } [Test] @@ -219,17 +240,19 @@ public void VerifyStringWorksVisualBasic([Values("AssemblyVersion", "AssemblyFil { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.vb"))) { - writer.Write(@" -Imports System -Imports System.Reflection + writer.Write(""" + + Imports System + Imports System.Reflection + + Public Class Temp + static const string Foo = ""; + End Class -Public Class Temp - static const string Foo = """"; -End Class -", attribute); + """, attribute); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.vb" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.vb" }], this.projectFile); } [Test] @@ -237,15 +260,130 @@ public void VerifyIdentifierWorksVisualBasic([Values("AssemblyVersion", "Assembl { using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.vb"))) { - writer.Write(@" -Imports System -Imports System.Reflection + writer.Write(""" + + Imports System + Imports System.Reflection + + Public Class {0} + End Class + + """, attribute); + } + + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.vb" }], this.projectFile); + } + + [Test] + public void VerifyAttributeFoundFSharp([Values("AssemblyVersion", "AssemblyFileVersion", "AssemblyInformationalVersion", "System.Reflection.AssemblyVersion")] string attribute) + { + using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.fs"))) + { + writer.Write(""" + + open System + open System.Reflection + + [] + + """, attribute); + } + + var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.fs" }], this.projectFile), attribute); + ex.ShouldNotBeNull(); + Assert.That(ex.Message, Is.EqualTo("File contains assembly version attributes which conflict with the attributes generated by GitVersion AssemblyInfo.fs")); + } + + [Test] + public void VerifyUnformattedAttributeFoundFSharp([Values("AssemblyVersion", "AssemblyFileVersion", "AssemblyInformationalVersion", "System . Reflection . AssemblyVersion")] string attribute) + { + using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.fs"))) + { + writer.Write(""" + + open System + open System.Reflection + + [< assembly : + {0} ( "1.0.0.0")>] + + """, attribute); + } + + var ex = Assert.Throws(() => FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.fs" }], this.projectFile), attribute); + ex.ShouldNotBeNull(); + Assert.That(ex.Message, Is.EqualTo("File contains assembly version attributes which conflict with the attributes generated by GitVersion AssemblyInfo.fs")); + } + + [Test] + public void VerifyCommentWorksFSharp([Values("AssemblyVersion", "AssemblyFileVersion", "AssemblyInformationalVersion")] string attribute) + { + using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.fs"))) + { + writer.Write(""" + + open System + open System.Reflection + + //[] + + """, attribute); + } + + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.fs" }], this.projectFile); + } + + [Test] + public void VerifyCommentWithNoNewLineAtEndWorksFSharp([Values("AssemblyVersion", "AssemblyFileVersion", "AssemblyInformationalVersion")] string attribute) + { + using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.fs"))) + { + writer.Write(""" + + open System + open System.Reflection + + //[] + """, attribute); + } + + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.fs" }], this.projectFile); + } + + [Test] + public void VerifyStringWorksFSharp([Values("AssemblyVersion", "AssemblyFileVersion", "AssemblyInformationalVersion")] string attribute) + { + using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.fs"))) + { + writer.Write(""" + + open System + open System.Reflection + + type Temp() = + static let Foo = "[]" + + """, attribute); + } + + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.fs" }], this.projectFile); + } + + [Test] + public void VerifyIdentifierWorksFSharp([Values("AssemblyVersion", "AssemblyFileVersion", "AssemblyInformationalVersion")] string attribute) + { + using (var writer = File.CreateText(PathHelper.Combine(this.projectDirectory, "AssemblyInfo.fs"))) + { + writer.Write(""" + + open System + open System.Reflection + + type {0}() = class end -Public Class {0} -End Class -", attribute); + """, attribute); } - FileHelper.CheckForInvalidFiles(new ITaskItem[] { new MockTaskItem { ItemSpec = "AssemblyInfo.vb" } }, this.projectFile); + FileHelper.CheckForInvalidFiles([new MockTaskItem { ItemSpec = "AssemblyInfo.fs" }], this.projectFile); } } diff --git a/src/GitVersion.MsBuild/Helpers/FileHelper.cs b/src/GitVersion.MsBuild/Helpers/FileHelper.cs index 10618dc116..e8b478ee42 100644 --- a/src/GitVersion.MsBuild/Helpers/FileHelper.cs +++ b/src/GitVersion.MsBuild/Helpers/FileHelper.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using GitVersion.Core; using GitVersion.Helpers; using Microsoft.Build.Framework; @@ -6,12 +7,6 @@ namespace GitVersion.MsBuild; internal static class FileHelper { - private static readonly Dictionary> VersionAttributeFinders = new() - { - { ".cs", CSharpFileContainsVersionAttribute }, - { ".vb", VisualBasicFileContainsVersionAttribute } - }; - public static readonly string TempPath = MakeAndGetTempPath(); private static string MakeAndGetTempPath() @@ -61,7 +56,8 @@ public static void DeleteTempFiles() public static void CheckForInvalidFiles(IEnumerable compileFiles, string projectFile) { - if (GetInvalidFiles(compileFiles, projectFile).FirstOrDefault() is { } invalidCompileFile) + var invalidCompileFile = GetInvalidFiles(compileFiles, projectFile).FirstOrDefault(); + if (invalidCompileFile != null) { throw new WarningException("File contains assembly version attributes which conflict with the attributes generated by GitVersion " + invalidCompileFile); } @@ -71,71 +67,31 @@ private static bool FileContainsVersionAttribute(string compileFile, string proj { var compileFileExtension = Path.GetExtension(compileFile); - if (VersionAttributeFinders.TryGetValue(compileFileExtension, out var languageSpecificFileContainsVersionAttribute)) + var (attributeRegex, triviaRegex) = compileFileExtension switch { - return languageSpecificFileContainsVersionAttribute(compileFile, projectFile); - } + ".cs" => (RegexPatterns.AssemblyVersion.CSharp.AttributeRegex, RegexPatterns.AssemblyVersion.CSharp.TriviaRegex), + ".fs" => (RegexPatterns.AssemblyVersion.FSharp.AttributeRegex, RegexPatterns.AssemblyVersion.FSharp.TriviaRegex), + ".vb" => (RegexPatterns.AssemblyVersion.VisualBasic.AttributeRegex, RegexPatterns.AssemblyVersion.VisualBasic.TriviaRegex), + _ => throw new WarningException("File with name containing AssemblyInfo could not be checked for assembly version attributes which conflict with the attributes generated by GitVersion " + compileFile) + }; - throw new WarningException("File with name containing AssemblyInfo could not be checked for assembly version attributes which conflict with the attributes generated by GitVersion " + compileFile); + return FileContainsVersionAttribute(compileFile, projectFile, attributeRegex, triviaRegex); } - private static bool CSharpFileContainsVersionAttribute(string compileFile, string projectFile) + private static bool FileContainsVersionAttribute(string compileFile, string projectFile, Regex attributeRegex, Regex triviaRegex) { var combine = PathHelper.Combine(Path.GetDirectoryName(projectFile), compileFile); var allText = File.ReadAllText(combine); + allText += PathHelper.NewLine; // Always add a new line, this handles the case for when a file ends with the EOF marker and no new line. - allText += PathHelper.NewLine; // Always add a new line, this handles the case for when a file ends with the EOF marker and no new line. If you don't have this newline, the regex will match commented out Assembly*Version tags on the last line. - - const string blockComments = @"/\*(.*?)\*/"; - const string lineComments = @"//(.*?)\r?\n"; - const string strings = @"""((\\[^\n]|[^""\n])*)"""; - const string verbatimStrings = @"@(""[^""]*"")+"; - - var noCommentsOrStrings = Regex.Replace(allText, - blockComments + "|" + lineComments + "|" + strings + "|" + verbatimStrings, - me => me.Value.StartsWith("//") ? PathHelper.NewLine : string.Empty, - RegexOptions.Singleline); - - return Regex.IsMatch(noCommentsOrStrings, @"(?x) # IgnorePatternWhitespace - -\[\s*assembly\s*:\s* # The [assembly: part - -(System\s*\.\s*Reflection\s*\.\s*)? # The System.Reflection. part (optional) - -Assembly(File|Informational)?Version # The attribute AssemblyVersion, AssemblyFileVersion, or AssemblyInformationalVersion - -\s*\(\s*\)\s*\] # End brackets ()]"); + var noCommentsOrStrings = triviaRegex.Replace(allText, me => me.Value.StartsWith("//") || me.Value.StartsWith("'") ? PathHelper.NewLine : string.Empty); + return attributeRegex.IsMatch(noCommentsOrStrings); } - private static bool VisualBasicFileContainsVersionAttribute(string compileFile, string projectFile) - { - var combine = PathHelper.Combine(Path.GetDirectoryName(projectFile), compileFile); - var allText = File.ReadAllText(combine); - - allText += PathHelper.NewLine; // Always add a new line, this handles the case for when a file ends with the EOF marker and no new line. If you don't have this newline, the regex will match commented out Assembly*Version tags on the last line. - - const string lineComments = @"'(.*?)\r?\n"; - const string strings = @"""((\\[^\n]|[^""\n])*)"""; - - var noCommentsOrStrings = Regex.Replace(allText, - lineComments + "|" + strings, - me => me.Value.StartsWith("'") ? PathHelper.NewLine : string.Empty, - RegexOptions.Singleline); - - return Regex.IsMatch(noCommentsOrStrings, @"(?x) # IgnorePatternWhitespace - -\<\s*Assembly\s*:\s* # The # End brackets ()>"); - } - - private static IEnumerable GetInvalidFiles(IEnumerable compileFiles, string projectFile) => compileFiles.Select(x => x.ItemSpec) - .Where(compileFile => compileFile.Contains("AssemblyInfo")) - .Where(s => FileContainsVersionAttribute(s, projectFile)); + private static IEnumerable GetInvalidFiles(IEnumerable compileFiles, string projectFile) + => compileFiles.Select(x => x.ItemSpec) + .Where(compileFile => compileFile.Contains("AssemblyInfo")) + .Where(s => FileContainsVersionAttribute(s, projectFile)); public static FileWriteInfo GetFileWriteInfo(this string? intermediateOutputPath, string language, string projectFile, string outputFileName) { @@ -152,6 +108,7 @@ public static FileWriteInfo GetFileWriteInfo(this string? intermediateOutputPath fileName = $"{outputFileName}.g.{fileExtension}"; workingDirectory = intermediateOutputPath; } + return new FileWriteInfo(workingDirectory, fileName, fileExtension); } } diff --git a/src/GitVersion.Output.Tests/Output/AssemblyInfoFileUpdaterTests.cs b/src/GitVersion.Output.Tests/Output/AssemblyInfoFileUpdaterTests.cs index 7082e33020..8ce95285be 100644 --- a/src/GitVersion.Output.Tests/Output/AssemblyInfoFileUpdaterTests.cs +++ b/src/GitVersion.Output.Tests/Output/AssemblyInfoFileUpdaterTests.cs @@ -45,7 +45,7 @@ public void ShouldCreateAssemblyInfoFileWhenNotExistsAndEnsureAssemblyInfo(strin var fullPath = PathHelper.Combine(workingDir, assemblyInfoFile); var variables = this.variableProvider.GetVariablesFor( - SemanticVersion.Parse("1.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0); + SemanticVersion.Parse("1.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0); using var assemblyInfoFileUpdater = new AssemblyInfoFileUpdater(this.log, this.fileSystem); assemblyInfoFileUpdater.Execute(variables, new(workingDir, true, assemblyInfoFile)); @@ -61,7 +61,7 @@ public void ShouldCreateAssemblyInfoFileAtPathWhenNotExistsAndEnsureAssemblyInfo var assemblyInfoFile = PathHelper.Combine("src", "Project", "Properties", $"VersionAssemblyInfo.{fileExtension}"); var fullPath = PathHelper.Combine(workingDir, assemblyInfoFile); var variables = this.variableProvider.GetVariablesFor( - SemanticVersion.Parse("1.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0 + SemanticVersion.Parse("1.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0 ); using var assemblyInfoFileUpdater = new AssemblyInfoFileUpdater(this.log, this.fileSystem); @@ -76,7 +76,7 @@ public void ShouldCreateAssemblyInfoFileAtPathWhenNotExistsAndEnsureAssemblyInfo public void ShouldCreateAssemblyInfoFilesAtPathWhenNotExistsAndEnsureAssemblyInfo(string fileExtension) { var assemblyInfoFiles = new HashSet { "AssemblyInfo." + fileExtension, PathHelper.Combine("src", "Project", "Properties", "VersionAssemblyInfo." + fileExtension) }; - var variables = this.variableProvider.GetVariablesFor(SemanticVersion.Parse("1.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0); + var variables = this.variableProvider.GetVariablesFor(SemanticVersion.Parse("1.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0); using var assemblyInfoFileUpdater = new AssemblyInfoFileUpdater(this.log, this.fileSystem); assemblyInfoFileUpdater.Execute(variables, new(workingDir, true, [.. assemblyInfoFiles])); @@ -96,7 +96,7 @@ public void ShouldNotCreateAssemblyInfoFileWhenNotExistsAndNotEnsureAssemblyInfo var assemblyInfoFile = "VersionAssemblyInfo." + fileExtension; var fullPath = PathHelper.Combine(workingDir, assemblyInfoFile); var variables = this.variableProvider.GetVariablesFor( - SemanticVersion.Parse("1.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0 + SemanticVersion.Parse("1.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0 ); using var assemblyInfoFileUpdater = new AssemblyInfoFileUpdater(this.log, this.fileSystem); @@ -113,7 +113,7 @@ public void ShouldNotCreateAssemblyInfoFileForUnknownSourceCodeAndEnsureAssembly const string assemblyInfoFile = "VersionAssemblyInfo.js"; var fullPath = PathHelper.Combine(workingDir, assemblyInfoFile); var variables = this.variableProvider.GetVariablesFor( - SemanticVersion.Parse("1.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0 + SemanticVersion.Parse("1.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0 ); using var assemblyInfoFileUpdater = new AssemblyInfoFileUpdater(this.log, this.fileSystem); @@ -129,7 +129,7 @@ public void ShouldStartSearchFromWorkingDirectory() string[] assemblyInfoFiles = []; var variables = this.variableProvider.GetVariablesFor( - SemanticVersion.Parse("1.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0 + SemanticVersion.Parse("1.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0 ); using var assemblyInfoFileUpdater = new AssemblyInfoFileUpdater(this.log, this.fileSystem); diff --git a/src/GitVersion.Output.Tests/Output/ProjectFileUpdaterTests.cs b/src/GitVersion.Output.Tests/Output/ProjectFileUpdaterTests.cs index 0eff6f3bb9..4665479e8f 100644 --- a/src/GitVersion.Output.Tests/Output/ProjectFileUpdaterTests.cs +++ b/src/GitVersion.Output.Tests/Output/ProjectFileUpdaterTests.cs @@ -145,7 +145,7 @@ public void CannotUpdateProjectFileWithoutAPropertyGroup(string xml) public void UpdateProjectXmlVersionElementWithStandardXmlInsertsElement(string xml) { var variables = this.variableProvider.GetVariablesFor( - SemanticVersion.Parse("2.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0 + SemanticVersion.Parse("2.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0 ); var xmlRoot = XElement.Parse(xml); variables.AssemblySemVer.ShouldNotBeNull(); @@ -176,7 +176,7 @@ public void UpdateProjectXmlVersionElementWithStandardXmlInsertsElement(string x )] public void UpdateProjectXmlVersionElementWithStandardXmlModifiesElement(string xml) { - var variables = this.variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0); + var variables = this.variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0); var xmlRoot = XElement.Parse(xml); variables.AssemblySemVer.ShouldNotBeNull(); ProjectFileUpdater.UpdateProjectVersionElement(xmlRoot, ProjectFileUpdater.AssemblyVersionElement, variables.AssemblySemVer); @@ -209,7 +209,7 @@ public void UpdateProjectXmlVersionElementWithStandardXmlModifiesElement(string )] public void UpdateProjectXmlVersionElementWithDuplicatePropertyGroupsModifiesLastElement(string xml) { - var variables = this.variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0); + var variables = this.variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0); var xmlRoot = XElement.Parse(xml); variables.AssemblySemVer.ShouldNotBeNull(); ProjectFileUpdater.UpdateProjectVersionElement(xmlRoot, ProjectFileUpdater.AssemblyVersionElement, variables.AssemblySemVer); @@ -243,7 +243,7 @@ public void UpdateProjectXmlVersionElementWithDuplicatePropertyGroupsModifiesLas )] public void UpdateProjectXmlVersionElementWithMultipleVersionElementsLastOneIsModified(string xml) { - var variables = this.variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", ConfigurationConstants.DefaultTagPrefix), EmptyConfigurationBuilder.New.Build(), 0); + var variables = this.variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", RegexPatterns.Configuration.DefaultTagPrefixPattern), EmptyConfigurationBuilder.New.Build(), 0); var xmlRoot = XElement.Parse(xml); variables.AssemblySemVer.ShouldNotBeNull(); ProjectFileUpdater.UpdateProjectVersionElement(xmlRoot, ProjectFileUpdater.AssemblyVersionElement, variables.AssemblySemVer); diff --git a/src/GitVersion.Output/AssemblyInfo/AssemblyInfoFileUpdater.cs b/src/GitVersion.Output/AssemblyInfo/AssemblyInfoFileUpdater.cs index e731eb72a3..e445500585 100644 --- a/src/GitVersion.Output/AssemblyInfo/AssemblyInfoFileUpdater.cs +++ b/src/GitVersion.Output/AssemblyInfo/AssemblyInfoFileUpdater.cs @@ -16,9 +16,9 @@ internal sealed class AssemblyInfoFileUpdater(ILog log, IFileSystem fileSystem) private readonly Dictionary assemblyAttributeRegexes = new() { - {".cs", RegexPatterns.Output.CsharpAssemblyAttributeRegex }, - {".fs", RegexPatterns.Output.FsharpAssemblyAttributeRegex }, - {".vb", RegexPatterns.Output.VisualBasicAssemblyAttributeRegex } + [".cs"] = RegexPatterns.Output.CsharpAssemblyAttributeRegex, + [".fs"] = RegexPatterns.Output.FsharpAssemblyAttributeRegex, + [".vb"] = RegexPatterns.Output.VisualBasicAssemblyAttributeRegex }; private const string NewLine = "\r\n";