From 73bae1bb73513f900940dad94d45dc8b11e7fa81 Mon Sep 17 00:00:00 2001 From: Artur Stolear Date: Mon, 17 Aug 2026 22:12:32 +0200 Subject: [PATCH 1/3] feat: support regex tag exclusions --- docs/input/docs/reference/configuration.md | 26 +++++- .../mdsource/configuration.source.md | 17 ++++ docs/input/docs/workflows/GitFlow/v1.yml | 1 + docs/input/docs/workflows/GitHubFlow/v1.yml | 1 + .../docs/workflows/TrunkBased/preview1.yml | 1 + schemas/7.0/GitVersion.configuration.json | 4 + ...riteOutEffectiveConfiguration.approved.txt | 1 + .../Configuration/IgnoreConfigurationTests.cs | 69 +++++++++++++++ .../Workflows/approved/GitFlow/v1.yml | 1 + .../Workflows/approved/GitHubFlow/v1.yml | 1 + .../approved/TrunkBased/preview1.yml | 1 + .../Builders/ConfigurationBuilderBase.cs | 19 ++++ .../Builders/IgnoreConfigurationBuilder.cs | 23 ++++- .../IgnoreConfiguration.cs | 8 +- .../TaggedSemanticVersionRepositoryTests.cs | 86 +++++++++++++++++++ .../IgnoreReferenceScenarios.cs | 35 ++++++++ .../Configuration/IIgnoreConfiguration.cs | 3 + .../Core/TaggedSemanticVersionRepository.cs | 23 +++-- .../Extensions/ConfigurationExtensions.cs | 11 ++- src/GitVersion.Core/PublicAPI.Unshipped.txt | 1 + 20 files changed, 318 insertions(+), 14 deletions(-) create mode 100644 src/GitVersion.Core.Tests/Core/TaggedSemanticVersionRepositoryTests.cs create mode 100644 src/GitVersion.Core.Tests/IntegrationTests/IgnoreReferenceScenarios.cs diff --git a/docs/input/docs/reference/configuration.md b/docs/input/docs/reference/configuration.md index f0cc4a86f1..d18049eac5 100644 --- a/docs/input/docs/reference/configuration.md +++ b/docs/input/docs/reference/configuration.md @@ -176,6 +176,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false @@ -210,7 +211,7 @@ update-build-number: true version-bump-reset-message: "=semver:" version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" ``` -snippet source | anchor +snippet source | anchor The supported built-in configuration for the `GitHubFlow` workflow (`workflow: GitHubFlow/v1`) looks like: @@ -302,6 +303,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false @@ -336,7 +338,7 @@ update-build-number: true version-bump-reset-message: "=semver:" version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" ``` -snippet source | anchor +snippet source | anchor The preview built-in configuration (experimental usage only) for the `TrunkBased` workflow (`workflow: TrunkBased/preview1`) looks like: @@ -417,6 +419,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false @@ -447,7 +450,7 @@ update-build-number: true version-bump-reset-message: "=semver:" version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" ``` -snippet source | anchor +snippet source | anchor The details of the available options are as follows: @@ -719,6 +722,23 @@ ignore: - 764e16321318f2fdb9cdeaa56d1156a1cba307d7 ``` +#### tags + +A sequence of regular expressions matching tag names that should not be used as +[version sources][version-sources]. Patterns are matched case-insensitively +against the friendly tag name without the `refs/tags/` prefix. Multiple patterns +use OR semantics, and `^` and `$` can be used to anchor a match. + +```yaml +ignore: + tags: + - ^experimental- + - ^v0\. +``` + +Ignoring a tag does not ignore the commit it points to. The commit remains part +of version calculation and can still be reached through other references. + ### increment The part of the SemVer to increment when GitVersion detects it needs to be diff --git a/docs/input/docs/reference/mdsource/configuration.source.md b/docs/input/docs/reference/mdsource/configuration.source.md index 658c7602d7..62c2fe537a 100644 --- a/docs/input/docs/reference/mdsource/configuration.source.md +++ b/docs/input/docs/reference/mdsource/configuration.source.md @@ -317,6 +317,23 @@ ignore: - 764e16321318f2fdb9cdeaa56d1156a1cba307d7 ``` +#### tags + +A sequence of regular expressions matching tag names that should not be used as +[version sources][version-sources]. Patterns are matched case-insensitively +against the friendly tag name without the `refs/tags/` prefix. Multiple patterns +use OR semantics, and `^` and `$` can be used to anchor a match. + +```yaml +ignore: + tags: + - ^experimental- + - ^v0\. +``` + +Ignoring a tag does not ignore the commit it points to. The commit remains part +of version calculation and can still be reached through other references. + ### increment The part of the SemVer to increment when GitVersion detects it needs to be diff --git a/docs/input/docs/workflows/GitFlow/v1.yml b/docs/input/docs/workflows/GitFlow/v1.yml index 6d9dc6ae1a..abe425fc5e 100644 --- a/docs/input/docs/workflows/GitFlow/v1.yml +++ b/docs/input/docs/workflows/GitFlow/v1.yml @@ -133,6 +133,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false diff --git a/docs/input/docs/workflows/GitHubFlow/v1.yml b/docs/input/docs/workflows/GitHubFlow/v1.yml index b3d5d08601..2474e1c6f6 100644 --- a/docs/input/docs/workflows/GitHubFlow/v1.yml +++ b/docs/input/docs/workflows/GitHubFlow/v1.yml @@ -82,6 +82,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false diff --git a/docs/input/docs/workflows/TrunkBased/preview1.yml b/docs/input/docs/workflows/TrunkBased/preview1.yml index fe8ebe9e39..5f62fd5600 100644 --- a/docs/input/docs/workflows/TrunkBased/preview1.yml +++ b/docs/input/docs/workflows/TrunkBased/preview1.yml @@ -71,6 +71,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false diff --git a/schemas/7.0/GitVersion.configuration.json b/schemas/7.0/GitVersion.configuration.json index e1e4dbcd98..4f035ebdab 100644 --- a/schemas/7.0/GitVersion.configuration.json +++ b/schemas/7.0/GitVersion.configuration.json @@ -201,6 +201,10 @@ "sha": { "description": "A sequence of SHAs to be excluded from the version calculations.", "$ref": "#/$defs/hashSetOfString" + }, + "tags": { + "description": "A sequence of regular expressions matching friendly tag names to be excluded as version sources.", + "$ref": "#/$defs/hashSetOfString" } } }, diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt index 6d9dc6ae1a..abe425fc5e 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt @@ -133,6 +133,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false diff --git a/src/GitVersion.Configuration.Tests/Configuration/IgnoreConfigurationTests.cs b/src/GitVersion.Configuration.Tests/Configuration/IgnoreConfigurationTests.cs index f5782f7a9f..fe493930fa 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/IgnoreConfigurationTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/IgnoreConfigurationTests.cs @@ -48,6 +48,34 @@ public void ShouldSupportsOtherSequenceFormat() configuration.Ignore.Shas.ShouldBe(["b6c0c9fda88830ebcd563e500a5a7da5a1658e98", "6c19c7c219ecf8dbc468042baefa73a1b213e8b1"]); } + [Test] + public void CanDeserializeCompactTagsSequence() + { + const string yaml = "ignore:\n tags: ['^preview-', '^v0\\.']"; + + var configuration = this.serializer.ReadConfiguration(yaml); + + configuration.ShouldNotBeNull(); + configuration.Ignore.Tags.ShouldBe(["^preview-", "^v0\\."]); + } + + [Test] + public void CanDeserializeMultilineTagsSequence() + { + const string yaml = + """ + ignore: + tags: + - ^preview- + - ^v0\. + """; + + var configuration = this.serializer.ReadConfiguration(yaml); + + configuration.ShouldNotBeNull(); + configuration.Ignore.Tags.ShouldBe(["^preview-", "^v0\\."]); + } + [Test] public void WhenNotInConfigShouldHaveDefaults() { @@ -60,6 +88,47 @@ public void WhenNotInConfigShouldHaveDefaults() configuration.Ignore.Before.ShouldBe(null); configuration.Ignore.Paths.ShouldBeEmpty(); configuration.Ignore.Shas.ShouldBeEmpty(); + configuration.Ignore.Tags.ShouldBeEmpty(); + } + + [Test] + public void IsEmpty_WhenTagPatternIsConfigured_ReturnsFalse() + { + var ignoreConfig = new IgnoreConfiguration { Tags = ["^preview-"] }; + + ignoreConfig.IsEmpty.ShouldBeFalse(); + } + + [Test] + public void InvalidIgnoreTagExpression_ThrowsConfigurationExceptionWithPropertyAndPattern() + { + const string invalidExpression = "[invalid"; + + var exception = Should.Throw(() => GitFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Tags = [invalidExpression] }) + .Build()); + + exception.Message.ShouldContain("ignore.tags"); + exception.Message.ShouldContain(invalidExpression); + } + + [Test] + public void Serialize_IgnoreTagPatterns_UsesTagsPropertyName() + { + var ignoreConfig = new IgnoreConfiguration { Tags = ["^preview-"] }; + + var yaml = this.serializer.Serialize(ignoreConfig); + + yaml.ShouldContain("tags:"); + yaml.ShouldContain("^preview-"); + } + + [Test] + public void IgnoreConfigurationBuilder_WithTags_PreservesCollection() + { + var ignoreConfig = IgnoreConfigurationBuilder.New.WithTags("^preview-", "^v0\\.").Build(); + + ignoreConfig.Tags.ShouldBe(["^preview-", "^v0\\."]); } [Test] diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml index 6d9dc6ae1a..abe425fc5e 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml @@ -133,6 +133,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml index b3d5d08601..2474e1c6f6 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml @@ -82,6 +82,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml index fe8ebe9e39..5f62fd5600 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml @@ -71,6 +71,7 @@ commit-message-incrementing: Enabled ignore: paths: [] sha: [] + tags: [] increment: Inherit is-main-branch: false is-release-branch: false diff --git a/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs b/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs index 79dae92447..9032356167 100644 --- a/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs +++ b/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs @@ -476,6 +476,8 @@ private static void FinalizeBranchConfiguration(IGitVersionConfiguration configu private static void ValidateConfiguration(IGitVersionConfiguration configuration) { + ValidateIgnoreExpressions("ignore.tags", configuration.Ignore.Tags); + foreach (var (name, branchConfiguration) in configuration.Branches) { var helpUrl = $"{FileSystemHelper.Path.NewLine}See https://gitversion.net/docs/reference/configuration for more info"; @@ -494,6 +496,23 @@ private static void ValidateConfiguration(IGitVersionConfiguration configuration } } + private static void ValidateIgnoreExpressions(string propertyName, IEnumerable expressions) + { + foreach (var expression in expressions) + { + try + { + _ = RegexPatterns.Cache.GetOrAdd(expression); + } + catch (ArgumentException exception) + { + throw new ConfigurationException( + $"Configuration property '{propertyName}' contains invalid regular expression '{expression}'.", + exception); + } + } + } + protected record BranchMetaData { public required string Name { get; init; } diff --git a/src/GitVersion.Configuration/Builders/IgnoreConfigurationBuilder.cs b/src/GitVersion.Configuration/Builders/IgnoreConfigurationBuilder.cs index b6cdf38578..ee69fd7e5e 100644 --- a/src/GitVersion.Configuration/Builders/IgnoreConfigurationBuilder.cs +++ b/src/GitVersion.Configuration/Builders/IgnoreConfigurationBuilder.cs @@ -8,6 +8,8 @@ internal sealed class IgnoreConfigurationBuilder private HashSet shas = []; + private HashSet tags = []; + public IgnoreConfigurationBuilder WithBefore(DateTimeOffset? value) { this.before = value; @@ -32,9 +34,28 @@ public IgnoreConfigurationBuilder WithShas(HashSet value) return this; } + public IgnoreConfigurationBuilder WithTags(IEnumerable values) + { + WithTags(new HashSet(values)); + return this; + } + + public IgnoreConfigurationBuilder WithTags(params string[] values) + { + WithTags(new HashSet(values)); + return this; + } + + public IgnoreConfigurationBuilder WithTags(HashSet value) + { + this.tags = value; + return this; + } + public IIgnoreConfiguration Build() => new IgnoreConfiguration { Before = this.before, - Shas = this.shas + Shas = this.shas, + Tags = this.tags }; } diff --git a/src/GitVersion.Configuration/IgnoreConfiguration.cs b/src/GitVersion.Configuration/IgnoreConfiguration.cs index 408d435df6..7f5ac38290 100644 --- a/src/GitVersion.Configuration/IgnoreConfiguration.cs +++ b/src/GitVersion.Configuration/IgnoreConfiguration.cs @@ -30,6 +30,12 @@ public string? BeforeString [JsonPropertyDescription("A sequence of SHAs to be excluded from the version calculations.")] public HashSet Shas { get; set; } = []; + IReadOnlySet IIgnoreConfiguration.Tags => Tags; + + [JsonPropertyName("tags")] + [JsonPropertyDescription("A sequence of regular expressions matching friendly tag names to be excluded as version sources.")] + public HashSet Tags { get; set; } = []; + [JsonIgnore] - public bool IsEmpty => Before == null && Paths.Count == 0 && Shas.Count == 0; + public bool IsEmpty => Before == null && Paths.Count == 0 && Shas.Count == 0 && Tags.Count == 0; } diff --git a/src/GitVersion.Core.Tests/Core/TaggedSemanticVersionRepositoryTests.cs b/src/GitVersion.Core.Tests/Core/TaggedSemanticVersionRepositoryTests.cs new file mode 100644 index 0000000000..43258528fe --- /dev/null +++ b/src/GitVersion.Core.Tests/Core/TaggedSemanticVersionRepositoryTests.cs @@ -0,0 +1,86 @@ +using GitVersion.Configuration; +using GitVersion.Testing.Extensions; + +namespace GitVersion.Tests; + +[TestFixture] +public class TaggedSemanticVersionRepositoryTests : TestBase +{ + [Test] + public void GetTaggedSemanticVersions_IgnoredAndEligibleTagsShareCommit_ReturnsOnlyEligibleTag() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.Repository.MakeACommit(); + fixture.ApplyTag("1.0.0"); + fixture.ApplyTag("2.0.0"); + var sut = CreateSut(fixture); + + var actual = GetTagNames(sut, new IgnoreConfiguration { Tags = ["^2\\.0\\.0$"] }); + + actual.ShouldBe(["1.0.0"]); + } + + [Test] + public void GetTaggedSemanticVersions_MultiplePatterns_UsesCaseInsensitiveOrSemanticsOnFriendlyName() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.Repository.MakeACommit(); + fixture.ApplyTag("v1.0.0"); + fixture.ApplyTag("v2.0.0"); + fixture.ApplyTag("v3.0.0"); + var sut = CreateSut(fixture); + var ignore = new IgnoreConfiguration { Tags = ["^v1\\.", "^V2\\.", "^refs/tags/v3"] }; + + var actual = GetTagNames(sut, ignore, tagPrefix: "^v"); + + actual.ShouldBe(["v3.0.0"]); + } + + [Test] + public void GetTaggedSemanticVersions_WhenIgnoreTagsChanges_DoesNotReuseCachedResult() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.Repository.MakeACommit(); + fixture.ApplyTag("1.0.0"); + fixture.ApplyTag("2.0.0"); + var sut = CreateSut(fixture); + + var first = GetTagNames(sut, new IgnoreConfiguration { Tags = ["^2\\."] }); + var second = GetTagNames(sut, new IgnoreConfiguration { Tags = ["^1\\."] }); + + first.ShouldBe(["1.0.0"]); + second.ShouldBe(["2.0.0"]); + } + + [TestCase(null)] + [TestCase("^preview-")] + public void GetTaggedSemanticVersions_EmptyOrNonMatchingTags_PreservesCandidates(string? expression) + { + using var fixture = new EmptyRepositoryFixture(); + fixture.Repository.MakeATaggedCommit("1.0.0"); + var sut = CreateSut(fixture); + var ignore = new IgnoreConfiguration { Tags = expression is null ? [] : [expression] }; + + var actual = GetTagNames(sut, ignore); + + actual.ShouldBe(["1.0.0"]); + } + + private static TaggedSemanticVersionRepository CreateSut(EmptyRepositoryFixture fixture) + { + var repositoryStore = new RepositoryStore( + NullLogger.Instance, + fixture.Repository.ToGitRepository()); + + return new(NullLogger.Instance, repositoryStore); + } + + private static string[] GetTagNames( + TaggedSemanticVersionRepository sut, + IIgnoreConfiguration ignore, + string tagPrefix = "") + => [.. sut.GetTaggedSemanticVersions(tagPrefix, SemanticVersionFormat.Strict, ignore) + .SelectMany(group => group) + .Select(version => version.Tag.Name.Friendly) + .Order(StringComparer.Ordinal)]; +} diff --git a/src/GitVersion.Core.Tests/IntegrationTests/IgnoreReferenceScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/IgnoreReferenceScenarios.cs new file mode 100644 index 0000000000..15bc7ac2e3 --- /dev/null +++ b/src/GitVersion.Core.Tests/IntegrationTests/IgnoreReferenceScenarios.cs @@ -0,0 +1,35 @@ +using GitVersion.Configuration; +using GitVersion.Testing.Extensions; + +namespace GitVersion.Tests.IntegrationTests; + +[TestFixture] +public class IgnoreReferenceScenarios : TestBase +{ + [Test] + public void GivenIgnoredTagAndEarlierEligibleTag_WhenCalculatingVersion_UsesEarlierTagAndCountsTargetCommit() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.Repository.MakeATaggedCommit("1.0.0"); + fixture.Repository.MakeATaggedCommit("2.0.0"); + fixture.Repository.MakeACommit(); + var configuration = GitFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Tags = ["^2\\.0\\.0$"] }) + .Build(); + + fixture.AssertFullSemver("1.0.1-2", configuration); + } + + [Test] + public void GivenNonMatchingTagPattern_WhenCalculatingVersion_PreservesCurrentVersion() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.Repository.MakeATaggedCommit("1.0.0"); + fixture.Repository.MakeACommit(); + var configuration = GitFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Tags = ["^preview-"] }) + .Build(); + + fixture.AssertFullSemver("1.0.1-1", configuration); + } +} diff --git a/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs b/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs index a8b5903bc0..530c38979e 100644 --- a/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs +++ b/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs @@ -12,6 +12,9 @@ public interface IIgnoreConfiguration /// Gets the set of commit SHAs that should be excluded from version calculation. IReadOnlySet Shas { get; } + /// Gets the regular expressions matching tag names that should be excluded as version sources. + IReadOnlySet Tags { get; } + /// Gets a value indicating whether this configuration contains no ignore rules. bool IsEmpty { get; } } diff --git a/src/GitVersion.Core/Core/TaggedSemanticVersionRepository.cs b/src/GitVersion.Core/Core/TaggedSemanticVersionRepository.cs index fca20d6dcb..4fd1d8987c 100644 --- a/src/GitVersion.Core/Core/TaggedSemanticVersionRepository.cs +++ b/src/GitVersion.Core/Core/TaggedSemanticVersionRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Globalization; using GitVersion.Configuration; using GitVersion.Extensions; using GitVersion.Git; @@ -9,11 +10,11 @@ namespace GitVersion; internal sealed class TaggedSemanticVersionRepository(ILogger logger, IRepositoryStore repositoryStore) : ITaggedSemanticVersionRepository { private readonly ILogger logger = logger.NotNull(); - private readonly ConcurrentDictionary<(IBranch, string, SemanticVersionFormat), IReadOnlyList> + private readonly ConcurrentDictionary<(IBranch, string, SemanticVersionFormat, string), IReadOnlyList> taggedSemanticVersionsOfBranchCache = new(); - private readonly ConcurrentDictionary<(IBranch, string, SemanticVersionFormat), IReadOnlyList<(ICommit Key, SemanticVersionWithTag Value)>> + private readonly ConcurrentDictionary<(IBranch, string, SemanticVersionFormat, string), IReadOnlyList<(ICommit Key, SemanticVersionWithTag Value)>> taggedSemanticVersionsOfMergeTargetCache = new(); - private readonly ConcurrentDictionary<(string, SemanticVersionFormat), IReadOnlyList> + private readonly ConcurrentDictionary<(string, SemanticVersionFormat, string), IReadOnlyList> taggedSemanticVersionsCache = new(); private readonly IRepositoryStore repositoryStore = repositoryStore.NotNull(); @@ -25,7 +26,7 @@ public ILookup GetTaggedSemanticVersionsOfBranc tagPrefix ??= string.Empty; var isCached = true; - var result = this.taggedSemanticVersionsOfBranchCache.GetOrAdd(new(branch, tagPrefix, format), _ => + var result = this.taggedSemanticVersionsOfBranchCache.GetOrAdd(new(branch, tagPrefix, format, GetIgnoreCacheKey(ignore)), _ => { isCached = false; return [.. GetElements().Distinct().OrderByDescending(element => element.Tag.Commit.When)]; @@ -67,7 +68,7 @@ public ILookup GetTaggedSemanticVersionsOfMerge tagPrefix ??= string.Empty; var isCached = true; - var result = this.taggedSemanticVersionsOfMergeTargetCache.GetOrAdd(new(branch, tagPrefix, format), _ => + var result = this.taggedSemanticVersionsOfMergeTargetCache.GetOrAdd(new(branch, tagPrefix, format, GetIgnoreCacheKey(ignore)), _ => { isCached = false; return [.. GetElements().Distinct().OrderByDescending(element => element.Key.When)]; @@ -108,7 +109,7 @@ public ILookup GetTaggedSemanticVersions( tagPrefix ??= string.Empty; var isCached = true; - var result = this.taggedSemanticVersionsCache.GetOrAdd(new(tagPrefix, format), _ => + var result = this.taggedSemanticVersionsCache.GetOrAdd(new(tagPrefix, format, GetIgnoreCacheKey(ignore)), _ => { isCached = false; return [.. GetElements().OrderByDescending(element => element.Tag.Commit.When)]; @@ -134,4 +135,14 @@ IEnumerable GetElements() } } } + + private static string GetIgnoreCacheKey(IIgnoreConfiguration ignore) + => string.Join('\u001f', + ignore.Before?.UtcTicks.ToString(CultureInfo.InvariantCulture) ?? string.Empty, + Serialize(ignore.Paths), + Serialize(ignore.Shas), + Serialize(ignore.Tags)); + + private static string Serialize(IEnumerable values) + => string.Join('\u001e', values.Order(StringComparer.Ordinal).Select(value => $"{value.Length}:{value}")); } diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index 5bef7de98b..47fc2ee104 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -104,7 +104,9 @@ public IEnumerable Filter(ITag[] source) ignoreConfig.NotNull(); source.NotNull(); - return !ignoreConfig.IsEmpty ? source.Where(element => ShouldBeIgnored(element.Commit, ignoreConfig)) : source; + return !ignoreConfig.IsEmpty + ? source.Where(element => ShouldBeIncluded(element.Commit, ignoreConfig) && !IsTagIgnored(element.Name.Friendly, ignoreConfig)) + : source; } public IEnumerable Filter(ICommit[] source) @@ -112,11 +114,14 @@ public IEnumerable Filter(ICommit[] source) ignoreConfig.NotNull(); source.NotNull(); - return !ignoreConfig.IsEmpty ? source.Where(element => ShouldBeIgnored(element, ignoreConfig)) : source; + return !ignoreConfig.IsEmpty ? source.Where(element => ShouldBeIncluded(element, ignoreConfig)) : source; } } - private static bool ShouldBeIgnored(ICommit commit, IIgnoreConfiguration ignore) + private static bool IsTagIgnored(string tagName, IIgnoreConfiguration ignore) + => ignore.Tags.Any(expression => RegexPatterns.Cache.GetOrAdd(expression).IsMatch(tagName)); + + private static bool ShouldBeIncluded(ICommit commit, IIgnoreConfiguration ignore) => !ignore.ToFilters().Any(filter => filter.Exclude(commit, out _)); extension(EffectiveConfiguration configuration) diff --git a/src/GitVersion.Core/PublicAPI.Unshipped.txt b/src/GitVersion.Core/PublicAPI.Unshipped.txt index c8bb72b678..1bed15ef7d 100644 --- a/src/GitVersion.Core/PublicAPI.Unshipped.txt +++ b/src/GitVersion.Core/PublicAPI.Unshipped.txt @@ -3,6 +3,7 @@ GitVersion.Configuration.EffectiveConfiguration.CustomVersionFormat.get -> strin GitVersion.Configuration.EffectiveConfiguration.VersionBumpResetMessage.get -> string? GitVersion.Configuration.IBranchConfiguration.CustomVersionFormat.get -> string? GitVersion.Configuration.IGitVersionConfiguration.VersionBumpResetMessage.get -> string? +GitVersion.Configuration.IIgnoreConfiguration.Tags.get -> System.Collections.Generic.IReadOnlySet! GitVersion.VersionCalculation.CommitMessageIncrement GitVersion.VersionCalculation.CommitMessageIncrement.CommitMessageIncrement() -> void GitVersion.VersionCalculation.CommitMessageIncrement.CommitMessageIncrement(GitVersion.VersionField Increment, bool VersionBumpNeedsToBeReset) -> void From 6fe09b0429ec42467b942cab43954d086ccb0fd1 Mon Sep 17 00:00:00 2001 From: Artur Stolear Date: Wed, 19 Aug 2026 11:55:08 +0200 Subject: [PATCH 2/3] refactor: simplify ignore filtering and caching --- .../TaggedSemanticVersionRepositoryTests.cs | 16 ---------- src/GitVersion.Core/Core/RepositoryStore.cs | 2 +- .../Core/TaggedSemanticVersionRepository.cs | 29 ++++++------------- .../Extensions/ConfigurationExtensions.cs | 4 +-- .../MainlineVersionStrategy.cs | 2 +- .../MergeMessageVersionStrategy.cs | 2 +- 6 files changed, 14 insertions(+), 41 deletions(-) diff --git a/src/GitVersion.Core.Tests/Core/TaggedSemanticVersionRepositoryTests.cs b/src/GitVersion.Core.Tests/Core/TaggedSemanticVersionRepositoryTests.cs index 43258528fe..5abaa7a23f 100644 --- a/src/GitVersion.Core.Tests/Core/TaggedSemanticVersionRepositoryTests.cs +++ b/src/GitVersion.Core.Tests/Core/TaggedSemanticVersionRepositoryTests.cs @@ -36,22 +36,6 @@ public void GetTaggedSemanticVersions_MultiplePatterns_UsesCaseInsensitiveOrSema actual.ShouldBe(["v3.0.0"]); } - [Test] - public void GetTaggedSemanticVersions_WhenIgnoreTagsChanges_DoesNotReuseCachedResult() - { - using var fixture = new EmptyRepositoryFixture(); - fixture.Repository.MakeACommit(); - fixture.ApplyTag("1.0.0"); - fixture.ApplyTag("2.0.0"); - var sut = CreateSut(fixture); - - var first = GetTagNames(sut, new IgnoreConfiguration { Tags = ["^2\\."] }); - var second = GetTagNames(sut, new IgnoreConfiguration { Tags = ["^1\\."] }); - - first.ShouldBe(["1.0.0"]); - second.ShouldBe(["2.0.0"]); - } - [TestCase(null)] [TestCase("^preview-")] public void GetTaggedSemanticVersions_EmptyOrNonMatchingTags_PreservesCandidates(string? expression) diff --git a/src/GitVersion.Core/Core/RepositoryStore.cs b/src/GitVersion.Core/Core/RepositoryStore.cs index f2959ebfb4..61cfbec2ee 100644 --- a/src/GitVersion.Core/Core/RepositoryStore.cs +++ b/src/GitVersion.Core/Core/RepositoryStore.cs @@ -60,7 +60,7 @@ internal class RepositoryStore(ILogger logger, IGitRepository r this.logger.LogInformation("Using latest commit on specified branch"); } - commits = ignore.Filter(commits.ToArray()); + commits = ignore.Filter(commits); return commits.FirstOrDefault(); } diff --git a/src/GitVersion.Core/Core/TaggedSemanticVersionRepository.cs b/src/GitVersion.Core/Core/TaggedSemanticVersionRepository.cs index 4fd1d8987c..131626c776 100644 --- a/src/GitVersion.Core/Core/TaggedSemanticVersionRepository.cs +++ b/src/GitVersion.Core/Core/TaggedSemanticVersionRepository.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Globalization; using GitVersion.Configuration; using GitVersion.Extensions; using GitVersion.Git; @@ -10,11 +9,11 @@ namespace GitVersion; internal sealed class TaggedSemanticVersionRepository(ILogger logger, IRepositoryStore repositoryStore) : ITaggedSemanticVersionRepository { private readonly ILogger logger = logger.NotNull(); - private readonly ConcurrentDictionary<(IBranch, string, SemanticVersionFormat, string), IReadOnlyList> + private readonly ConcurrentDictionary<(IBranch, string, SemanticVersionFormat), IReadOnlyList> taggedSemanticVersionsOfBranchCache = new(); - private readonly ConcurrentDictionary<(IBranch, string, SemanticVersionFormat, string), IReadOnlyList<(ICommit Key, SemanticVersionWithTag Value)>> + private readonly ConcurrentDictionary<(IBranch, string, SemanticVersionFormat), IReadOnlyList<(ICommit Key, SemanticVersionWithTag Value)>> taggedSemanticVersionsOfMergeTargetCache = new(); - private readonly ConcurrentDictionary<(string, SemanticVersionFormat, string), IReadOnlyList> + private readonly ConcurrentDictionary<(string, SemanticVersionFormat), IReadOnlyList> taggedSemanticVersionsCache = new(); private readonly IRepositoryStore repositoryStore = repositoryStore.NotNull(); @@ -26,7 +25,7 @@ public ILookup GetTaggedSemanticVersionsOfBranc tagPrefix ??= string.Empty; var isCached = true; - var result = this.taggedSemanticVersionsOfBranchCache.GetOrAdd(new(branch, tagPrefix, format, GetIgnoreCacheKey(ignore)), _ => + var result = this.taggedSemanticVersionsOfBranchCache.GetOrAdd(new(branch, tagPrefix, format), _ => { isCached = false; return [.. GetElements().Distinct().OrderByDescending(element => element.Tag.Commit.When)]; @@ -50,7 +49,7 @@ IEnumerable GetElements() { var semanticVersions = GetTaggedSemanticVersions(tagPrefix, format, ignore); - foreach (var commit in ignore.Filter(branch.Commits.ToArray())) + foreach (var commit in ignore.Filter(branch.Commits)) { foreach (var semanticVersion in semanticVersions[commit]) { @@ -68,7 +67,7 @@ public ILookup GetTaggedSemanticVersionsOfMerge tagPrefix ??= string.Empty; var isCached = true; - var result = this.taggedSemanticVersionsOfMergeTargetCache.GetOrAdd(new(branch, tagPrefix, format, GetIgnoreCacheKey(ignore)), _ => + var result = this.taggedSemanticVersionsOfMergeTargetCache.GetOrAdd(new(branch, tagPrefix, format), _ => { isCached = false; return [.. GetElements().Distinct().OrderByDescending(element => element.Key.When)]; @@ -90,7 +89,7 @@ public ILookup GetTaggedSemanticVersionsOfMerge using (this.logger.StartIndentedScope($"Getting tagged semantic versions by track merge target '{branch.Name.Canonical}'. " + $"TagPrefix: {tagPrefix} and Format: {format}")) { - var shaHashSet = new HashSet(ignore.Filter(branch.Commits.ToArray()).Select(element => element.Id.Sha)); + var shaHashSet = new HashSet(ignore.Filter(branch.Commits).Select(element => element.Id.Sha)); foreach (var semanticVersion in GetTaggedSemanticVersions(tagPrefix, format, ignore).SelectMany(v => v)) { @@ -109,7 +108,7 @@ public ILookup GetTaggedSemanticVersions( tagPrefix ??= string.Empty; var isCached = true; - var result = this.taggedSemanticVersionsCache.GetOrAdd(new(tagPrefix, format, GetIgnoreCacheKey(ignore)), _ => + var result = this.taggedSemanticVersionsCache.GetOrAdd(new(tagPrefix, format), _ => { isCached = false; return [.. GetElements().OrderByDescending(element => element.Tag.Commit.When)]; @@ -126,7 +125,7 @@ IEnumerable GetElements() { this.logger.LogInformation("Getting tagged semantic versions. TagPrefix: {TagPrefix} and Format: {Format}", tagPrefix, format); - foreach (var tag in ignore.Filter(this.repositoryStore.Tags.ToArray())) + foreach (var tag in ignore.Filter(this.repositoryStore.Tags)) { if (SemanticVersion.TryParse(tag.Name.Friendly, tagPrefix, out var semanticVersion, format)) { @@ -135,14 +134,4 @@ IEnumerable GetElements() } } } - - private static string GetIgnoreCacheKey(IIgnoreConfiguration ignore) - => string.Join('\u001f', - ignore.Before?.UtcTicks.ToString(CultureInfo.InvariantCulture) ?? string.Empty, - Serialize(ignore.Paths), - Serialize(ignore.Shas), - Serialize(ignore.Tags)); - - private static string Serialize(IEnumerable values) - => string.Join('\u001e', values.Order(StringComparer.Ordinal).Select(value => $"{value.Length}:{value}")); } diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index 47fc2ee104..67d8d4bc0c 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -99,7 +99,7 @@ public IEnumerable ToFilters() } } - public IEnumerable Filter(ITag[] source) + public IEnumerable Filter(IEnumerable source) { ignoreConfig.NotNull(); source.NotNull(); @@ -109,7 +109,7 @@ public IEnumerable Filter(ITag[] source) : source; } - public IEnumerable Filter(ICommit[] source) + public IEnumerable Filter(IEnumerable source) { ignoreConfig.NotNull(); source.NotNull(); diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs index ce1a3d6c0f..8af7a3e29a 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs @@ -85,7 +85,7 @@ public IEnumerable GetBaseVersions(EffectiveBranchConfiguration con configuration: branchConfiguration ); - var commitsInReverseOrder = Context.Configuration.Ignore.Filter(Context.CurrentBranchCommits.ToArray()); + var commitsInReverseOrder = Context.Configuration.Ignore.Filter(Context.CurrentBranchCommits); var taggedSemanticVersion = TaggedSemanticVersions.OfBranch; if (branchConfiguration.TrackMergeTarget == true) diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MergeMessageVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MergeMessageVersionStrategy.cs index 187a2c7ca2..48494d7379 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MergeMessageVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MergeMessageVersionStrategy.cs @@ -33,7 +33,7 @@ private IEnumerable GetBaseVersionsInternal(EffectiveBranchConfigur yield break; } - foreach (var commit in configuration.Value.Ignore.Filter(Context.CurrentBranchCommits.ToArray())) + foreach (var commit in configuration.Value.Ignore.Filter(Context.CurrentBranchCommits)) { if (!MergeMessage.TryParse(commit, Context.Configuration, out var mergeMessage) || mergeMessage.Version is null From a1c81bc80ded9b8b87153c919b813a598bf59941 Mon Sep 17 00:00:00 2001 From: Artur Stolear Date: Wed, 19 Aug 2026 12:21:14 +0200 Subject: [PATCH 3/3] docs: clarify configuration regex case sensitivity --- docs/input/docs/reference/configuration.md | 16 ++++++++++--- .../mdsource/configuration.source.md | 16 ++++++++++--- schemas/7.0/GitVersion.configuration.json | 24 +++++++++---------- .../BranchConfiguration.cs | 2 +- .../GitVersionConfiguration.cs | 16 ++++++------- .../IgnoreConfiguration.cs | 4 ++-- 6 files changed, 49 insertions(+), 29 deletions(-) diff --git a/docs/input/docs/reference/configuration.md b/docs/input/docs/reference/configuration.md index d18049eac5..1aaea48b13 100644 --- a/docs/input/docs/reference/configuration.md +++ b/docs/input/docs/reference/configuration.md @@ -455,6 +455,14 @@ version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" The details of the available options are as follows: +:::{.alert .alert-info} +**Regular expression matching** + +Unless stated otherwise, regular expressions in GitVersion configuration are +matched case-insensitively. To require case-sensitive matching, prefix the +expression with `(?-i)`, for example `(?-i)^experimental-`. +::: + ### assembly-file-versioning-format Specifies the format of `AssemblyFileVersion` and @@ -694,8 +702,9 @@ A commit having changes only in `/ProjectB/*` path would be ignored. A commit ha * `/ProductA/*` and `/ProductB/*` and `/LibraryC/*` ::: -Note: The `ignore.paths` configuration is case-sensitive. -This can lead to unexpected behavior on case-insensitive file systems, such as Windows. To ensure consistent matching regardless of case, you can prefix your regular expressions with the case-insensitive flag `(?i)`. For example, `(?i)^docs\/` will match both `docs/` and `Docs/`. +Note: The `ignore.paths` configuration is case-insensitive. To require +case-sensitive path matching, prefix the regular expression with `(?-i)`. For +example, `(?-i)^docs\/` matches `docs/` but not `Docs/`. ::: ::: {.alert .alert-warning} @@ -727,7 +736,8 @@ ignore: A sequence of regular expressions matching tag names that should not be used as [version sources][version-sources]. Patterns are matched case-insensitively against the friendly tag name without the `refs/tags/` prefix. Multiple patterns -use OR semantics, and `^` and `$` can be used to anchor a match. +use OR semantics, and `^` and `$` can be used to anchor a match. To require +case-sensitive matching, prefix a pattern with `(?-i)`. ```yaml ignore: diff --git a/docs/input/docs/reference/mdsource/configuration.source.md b/docs/input/docs/reference/mdsource/configuration.source.md index 62c2fe537a..fb4e45bcbf 100644 --- a/docs/input/docs/reference/mdsource/configuration.source.md +++ b/docs/input/docs/reference/mdsource/configuration.source.md @@ -50,6 +50,14 @@ snippet: /docs/workflows/TrunkBased/preview1.yml The details of the available options are as follows: +:::{.alert .alert-info} +**Regular expression matching** + +Unless stated otherwise, regular expressions in GitVersion configuration are +matched case-insensitively. To require case-sensitive matching, prefix the +expression with `(?-i)`, for example `(?-i)^experimental-`. +::: + ### assembly-file-versioning-format Specifies the format of `AssemblyFileVersion` and @@ -289,8 +297,9 @@ A commit having changes only in `/ProjectB/*` path would be ignored. A commit ha * `/ProductA/*` and `/ProductB/*` and `/LibraryC/*` ::: -Note: The `ignore.paths` configuration is case-sensitive. -This can lead to unexpected behavior on case-insensitive file systems, such as Windows. To ensure consistent matching regardless of case, you can prefix your regular expressions with the case-insensitive flag `(?i)`. For example, `(?i)^docs\/` will match both `docs/` and `Docs/`. +Note: The `ignore.paths` configuration is case-insensitive. To require +case-sensitive path matching, prefix the regular expression with `(?-i)`. For +example, `(?-i)^docs\/` matches `docs/` but not `Docs/`. ::: ::: {.alert .alert-warning} @@ -322,7 +331,8 @@ ignore: A sequence of regular expressions matching tag names that should not be used as [version sources][version-sources]. Patterns are matched case-insensitively against the friendly tag name without the `refs/tags/` prefix. Multiple patterns -use OR semantics, and `^` and `$` can be used to anchor a match. +use OR semantics, and `^` and `$` can be used to anchor a match. To require +case-sensitive matching, prefix a pattern with `(?-i)`. ```yaml ignore: diff --git a/schemas/7.0/GitVersion.configuration.json b/schemas/7.0/GitVersion.configuration.json index 4f035ebdab..24c4890971 100644 --- a/schemas/7.0/GitVersion.configuration.json +++ b/schemas/7.0/GitVersion.configuration.json @@ -124,7 +124,7 @@ }, "regex": { "format": "regex", - "description": "The regular expression pattern to use to match this branch.", + "description": "The regular expression pattern to use to match this branch. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "type": [ "null", "string" @@ -195,7 +195,7 @@ ] }, "paths": { - "description": "A sequence of file paths to be excluded from the version calculations.", + "description": "A sequence of regular expressions matching file paths to be excluded from the version calculations. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "$ref": "#/$defs/hashSetOfString" }, "sha": { @@ -203,7 +203,7 @@ "$ref": "#/$defs/hashSetOfString" }, "tags": { - "description": "A sequence of regular expressions matching friendly tag names to be excluded as version sources.", + "description": "A sequence of regular expressions matching friendly tag names to be excluded as version sources. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "$ref": "#/$defs/hashSetOfString" } } @@ -239,7 +239,7 @@ }, "major-version-bump-message": { "format": "regex", - "description": "The regular expression to match commit messages with to perform a major version increment. Defaults to '[+=]semver:\\s?(breaking|major)'", + "description": "The regular expression to match commit messages with to perform a major version increment. Defaults to '[+=]semver:\\s?(breaking|major)'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "default": "[+=]semver:\\s?(breaking|major)", "type": [ "null", @@ -247,7 +247,7 @@ ] }, "merge-message-formats": { - "description": "Custom merge message formats to enable identification of merge messages that do not follow the built-in conventions.", + "description": "Custom regular expression merge message formats to enable identification of merge messages that do not follow the built-in conventions. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "type": "object", "additionalProperties": { "type": "string" @@ -255,7 +255,7 @@ }, "minor-version-bump-message": { "format": "regex", - "description": "The regular expression to match commit messages with to perform a minor version increment. Defaults to '[+=]semver:\\s?(feature|minor)'", + "description": "The regular expression to match commit messages with to perform a minor version increment. Defaults to '[+=]semver:\\s?(feature|minor)'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "default": "[+=]semver:\\s?(feature|minor)", "type": [ "null", @@ -280,7 +280,7 @@ }, "no-bump-message": { "format": "regex", - "description": "Used to tell GitVersion not to increment when in Mainline development mode. Defaults to '[+=]semver:\\s?(none|skip)'", + "description": "Used to tell GitVersion not to increment when in Mainline development mode. Defaults to '[+=]semver:\\s?(none|skip)'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "default": "[+=]semver:\\s?(none|skip)", "type": [ "null", @@ -289,7 +289,7 @@ }, "patch-version-bump-message": { "format": "regex", - "description": "The regular expression to match commit messages with to perform a patch version increment. Defaults to '[+=]semver:\\s?(fix|patch)'", + "description": "The regular expression to match commit messages with to perform a patch version increment. Defaults to '[+=]semver:\\s?(fix|patch)'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "default": "[+=]semver:\\s?(fix|patch)", "type": [ "null", @@ -309,7 +309,7 @@ }, "regex": { "format": "regex", - "description": "The regular expression pattern to use to match this branch.", + "description": "The regular expression pattern to use to match this branch. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "type": [ "null", "string" @@ -352,7 +352,7 @@ }, "tag-prefix": { "format": "regex", - "description": "A regular expression which is used to trim Git tags before processing. Defaults to '[vV]?'", + "description": "A regular expression which is used to trim Git tags before processing. Defaults to '[vV]?'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "default": "[vV]?", "type": [ "null", @@ -387,7 +387,7 @@ }, "version-bump-reset-message": { "format": "regex", - "description": "The regular expression to match commit messages that reset the version bump baseline and suppress the configured branch increment. Defaults to '=semver:'", + "description": "The regular expression to match commit messages that reset the version bump baseline and suppress the configured branch increment. Defaults to '=semver:'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "default": "=semver:", "type": [ "null", @@ -396,7 +396,7 @@ }, "version-in-branch-pattern": { "format": "regex", - "description": "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 '(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*'.", + "description": "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 '(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.", "default": "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*", "type": [ "null", diff --git a/src/GitVersion.Configuration/BranchConfiguration.cs b/src/GitVersion.Configuration/BranchConfiguration.cs index 5b408bd999..4fd0a61bd5 100644 --- a/src/GitVersion.Configuration/BranchConfiguration.cs +++ b/src/GitVersion.Configuration/BranchConfiguration.cs @@ -42,7 +42,7 @@ internal record BranchConfiguration : IBranchConfiguration public CommitMessageIncrementMode? CommitMessageIncrementing { get; set; } [JsonPropertyName("regex")] - [JsonPropertyDescription("The regular expression pattern to use to match this branch.")] + [JsonPropertyDescription("The regular expression pattern to use to match this branch. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] [JsonPropertyFormat(Format.Regex)] public string? RegularExpression { get; set; } diff --git a/src/GitVersion.Configuration/GitVersionConfiguration.cs b/src/GitVersion.Configuration/GitVersionConfiguration.cs index e1e1fdeb11..7e0b3fb6e2 100644 --- a/src/GitVersion.Configuration/GitVersionConfiguration.cs +++ b/src/GitVersion.Configuration/GitVersionConfiguration.cs @@ -35,13 +35,13 @@ internal sealed record GitVersionConfiguration : BranchConfiguration, IGitVersio public string? AssemblyFileVersioningFormat { get; set; } [JsonPropertyName("tag-prefix")] - [JsonPropertyDescription($"A regular expression which is used to trim Git tags before processing. Defaults to '{RegexPatterns.Configuration.DefaultTagPrefixRegexPattern}'")] + [JsonPropertyDescription($"A regular expression which is used to trim Git tags before processing. Defaults to '{RegexPatterns.Configuration.DefaultTagPrefixRegexPattern}'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] [JsonPropertyDefault(RegexPatterns.Configuration.DefaultTagPrefixRegexPattern)] [JsonPropertyFormat(Format.Regex)] public string? TagPrefixPattern { get; 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 '{RegexPatterns.Configuration.DefaultVersionInBranchRegexPattern}'.")] + [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.DefaultVersionInBranchRegexPattern}'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] [JsonPropertyDefault(RegexPatterns.Configuration.DefaultVersionInBranchRegexPattern)] [JsonPropertyFormat(Format.Regex)] public string? VersionInBranchPattern { get; set; } @@ -59,31 +59,31 @@ public string? NextVersion private string? nextVersion; [JsonPropertyName("major-version-bump-message")] - [JsonPropertyDescription($"The regular expression to match commit messages with to perform a major version increment. Defaults to '{RegexPatterns.VersionCalculation.DefaultMajorRegexPattern}'")] + [JsonPropertyDescription($"The regular expression to match commit messages with to perform a major version increment. Defaults to '{RegexPatterns.VersionCalculation.DefaultMajorRegexPattern}'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] [JsonPropertyDefault(RegexPatterns.VersionCalculation.DefaultMajorRegexPattern)] [JsonPropertyFormat(Format.Regex)] public string? MajorVersionBumpMessage { get; set; } [JsonPropertyName("minor-version-bump-message")] - [JsonPropertyDescription($"The regular expression to match commit messages with to perform a minor version increment. Defaults to '{RegexPatterns.VersionCalculation.DefaultMinorRegexPattern}'")] + [JsonPropertyDescription($"The regular expression to match commit messages with to perform a minor version increment. Defaults to '{RegexPatterns.VersionCalculation.DefaultMinorRegexPattern}'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] [JsonPropertyDefault(RegexPatterns.VersionCalculation.DefaultMinorRegexPattern)] [JsonPropertyFormat(Format.Regex)] public string? MinorVersionBumpMessage { get; set; } [JsonPropertyName("patch-version-bump-message")] - [JsonPropertyDescription($"The regular expression to match commit messages with to perform a patch version increment. Defaults to '{RegexPatterns.VersionCalculation.DefaultPatchRegexPattern}'")] + [JsonPropertyDescription($"The regular expression to match commit messages with to perform a patch version increment. Defaults to '{RegexPatterns.VersionCalculation.DefaultPatchRegexPattern}'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] [JsonPropertyDefault(RegexPatterns.VersionCalculation.DefaultPatchRegexPattern)] [JsonPropertyFormat(Format.Regex)] public string? PatchVersionBumpMessage { get; set; } [JsonPropertyName("no-bump-message")] - [JsonPropertyDescription($"Used to tell GitVersion not to increment when in Mainline development mode. Defaults to '{RegexPatterns.VersionCalculation.DefaultNoBumpRegexPattern}'")] + [JsonPropertyDescription($"Used to tell GitVersion not to increment when in Mainline development mode. Defaults to '{RegexPatterns.VersionCalculation.DefaultNoBumpRegexPattern}'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] [JsonPropertyDefault(RegexPatterns.VersionCalculation.DefaultNoBumpRegexPattern)] [JsonPropertyFormat(Format.Regex)] public string? NoBumpMessage { get; set; } [JsonPropertyName("version-bump-reset-message")] - [JsonPropertyDescription($"The regular expression to match commit messages that reset the version bump baseline and suppress the configured branch increment. Defaults to '{RegexPatterns.VersionCalculation.DefaultVersionBumpResetRegexPattern}'")] + [JsonPropertyDescription($"The regular expression to match commit messages that reset the version bump baseline and suppress the configured branch increment. Defaults to '{RegexPatterns.VersionCalculation.DefaultVersionBumpResetRegexPattern}'. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] [JsonPropertyDefault(RegexPatterns.VersionCalculation.DefaultVersionBumpResetRegexPattern)] [JsonPropertyFormat(Format.Regex)] public string? VersionBumpResetMessage { get; set; } @@ -99,7 +99,7 @@ public string? NextVersion public string? CommitDateFormat { get; set; } [JsonPropertyName("merge-message-formats")] - [JsonPropertyDescription("Custom merge message formats to enable identification of merge messages that do not follow the built-in conventions.")] + [JsonPropertyDescription("Custom regular expression merge message formats to enable identification of merge messages that do not follow the built-in conventions. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] public Dictionary MergeMessageFormats { get; set; } = []; [JsonIgnore] diff --git a/src/GitVersion.Configuration/IgnoreConfiguration.cs b/src/GitVersion.Configuration/IgnoreConfiguration.cs index 7f5ac38290..eadf19114f 100644 --- a/src/GitVersion.Configuration/IgnoreConfiguration.cs +++ b/src/GitVersion.Configuration/IgnoreConfiguration.cs @@ -20,7 +20,7 @@ public string? BeforeString IReadOnlySet IIgnoreConfiguration.Paths => Paths; [JsonPropertyName("paths")] - [JsonPropertyDescription("A sequence of file paths to be excluded from the version calculations.")] + [JsonPropertyDescription("A sequence of regular expressions matching file paths to be excluded from the version calculations. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] public HashSet Paths { get; set; } = []; [JsonIgnore] @@ -33,7 +33,7 @@ public string? BeforeString IReadOnlySet IIgnoreConfiguration.Tags => Tags; [JsonPropertyName("tags")] - [JsonPropertyDescription("A sequence of regular expressions matching friendly tag names to be excluded as version sources.")] + [JsonPropertyDescription("A sequence of regular expressions matching friendly tag names to be excluded as version sources. Matching is case-insensitive by default; use (?-i) to enable case-sensitive matching.")] public HashSet Tags { get; set; } = []; [JsonIgnore]