From 67f794af6155e0ba764a5e92ab687c62ba5cf404 Mon Sep 17 00:00:00 2001 From: Roger Noden <11900533+W0GER@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:37:37 -0600 Subject: [PATCH 1/7] Enhance branch label configuration with environment variable support Added the ability to use environment variable placeholders in branch labels, allowing for dynamic label generation in CI/CD scenarios. Introduced fallback values for environment variables and updated related tests to ensure correct processing of these new features. Updated the GetBranchSpecificLabel method to handle environment variables even when regex does not match, maintaining backward compatibility. --- docs/input/docs/reference/configuration.md | 26 ++++++ .../ConfigurationExtensionsTests.cs | 88 +++++++++++++++++++ .../Extensions/ConfigurationExtensions.cs | 59 +++++++++++-- .../NextVersionCalculator.cs | 6 +- 4 files changed, 172 insertions(+), 7 deletions(-) diff --git a/docs/input/docs/reference/configuration.md b/docs/input/docs/reference/configuration.md index 9c4dd1a8f6..0b0f470999 100644 --- a/docs/input/docs/reference/configuration.md +++ b/docs/input/docs/reference/configuration.md @@ -892,6 +892,32 @@ of `alpha.foo` with `label: 'alpha.{BranchName}'` and `regex: '^features?[\/-](? Another example: branch `features/sc-12345/some-description` would become a pre-release label of `sc-12345` with `label: '{StoryNo}'` and `regex: '^features?[\/-](?sc-\d+)[-/].+'`. +You can also use environment variable placeholders with the `{env:VARIABLE_NAME}` syntax. This is particularly useful for CI/CD scenarios, such as using GitHub Actions context variables in pull request labels. + +For example, to use the GitHub Actions head ref in a pull request label: +```yaml +branches: + pull-request: + label: 'pr-{env:GITHUB_HEAD_REF}' + regex: '^pull[/-]' +``` + +You can combine regex placeholders with environment variables: +```yaml +branches: + feature: + label: '{BranchName}-{env:GITHUB_HEAD_REF}' + regex: '^features?[\/-](?.+)' +``` + +Environment variables support fallback values using the `{env:VARIABLE_NAME ?? "fallback"}` syntax: +```yaml +branches: + pull-request: + label: 'pr-{env:GITHUB_HEAD_REF ?? "unknown"}' + regex: '^pull[/-]' +``` + **Note:** To clear a default use an empty string: `label: ''` ### increment diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs index 97e225f752..a84659e7d4 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs @@ -1,4 +1,5 @@ using GitVersion.Configuration; +using GitVersion.Core; using GitVersion.Core.Tests.Helpers; using GitVersion.Git; @@ -55,4 +56,91 @@ public void EnsureGetBranchSpecificLabelWorksAsExpected(string branchName, strin var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName(branchName), null); actual.ShouldBe(expectedLabel); } + + [Test] + public void EnsureGetBranchSpecificLabelProcessesEnvironmentVariables() + { + var environment = new TestEnvironment(); + environment.SetEnvironmentVariable("GITHUB_HEAD_REF", "feature-branch"); + + var configuration = GitFlowConfigurationBuilder.New + .WithoutBranches() + .WithBranch("pull-request", builder => builder + .WithLabel("pr-{env:GITHUB_HEAD_REF}") + .WithRegularExpression(@"^pull[/-]")) + .Build(); + + var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("pull-request")); + var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("pull-request"), null, environment); + actual.ShouldBe("pr-feature-branch"); + } + + [Test] + public void EnsureGetBranchSpecificLabelProcessesEnvironmentVariablesWithFallback() + { + var environment = new TestEnvironment(); + // Don't set GITHUB_HEAD_REF to test fallback + + var configuration = GitFlowConfigurationBuilder.New + .WithoutBranches() + .WithBranch("pull-request", builder => builder + .WithLabel("pr-{env:GITHUB_HEAD_REF ?? \"unknown\"}") + .WithRegularExpression(@"^pull[/-]")) + .Build(); + + var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("pull-request")); + var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("pull-request"), null, environment); + actual.ShouldBe("pr-unknown"); + } + + [Test] + public void EnsureGetBranchSpecificLabelProcessesEnvironmentVariablesAndRegexPlaceholders() + { + var environment = new TestEnvironment(); + environment.SetEnvironmentVariable("GITHUB_HEAD_REF", "feature-branch"); + + var configuration = GitFlowConfigurationBuilder.New + .WithoutBranches() + .WithBranch("feature/test-branch", builder => builder + .WithLabel("{BranchName}-{env:GITHUB_HEAD_REF}") + .WithRegularExpression(@"^features?[\/-](?.+)")) + .Build(); + + var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("feature/test-branch")); + var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("feature/test-branch"), null, environment); + actual.ShouldBe("test-branch-feature-branch"); + } + + [Test] + public void EnsureGetBranchSpecificLabelWorksWithoutEnvironmentWhenNoEnvPlaceholders() + { + var configuration = GitFlowConfigurationBuilder.New + .WithoutBranches() + .WithBranch("feature/test", builder => builder + .WithLabel("{BranchName}") + .WithRegularExpression(@"^features?[\/-](?.+)")) + .Build(); + + var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("feature/test")); + var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("feature/test"), null, null); + actual.ShouldBe("test"); + } + + [Test] + public void EnsureGetBranchSpecificLabelProcessesEnvironmentVariablesEvenWhenRegexDoesNotMatch() + { + var environment = new TestEnvironment(); + environment.SetEnvironmentVariable("GITHUB_HEAD_REF", "feature-branch"); + + var configuration = GitFlowConfigurationBuilder.New + .WithoutBranches() + .WithBranch("pull-request", builder => builder + .WithLabel("pr-{env:GITHUB_HEAD_REF}") + .WithRegularExpression(@"^pull[/-]")) + .Build(); + + var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("other-branch")); + var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("other-branch"), null, environment); + actual.ShouldBe("pr-feature-branch"); + } } diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index 20d51dbdcc..4cb19227f8 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -1,6 +1,7 @@ using System.IO.Abstractions; using GitVersion.Core; using GitVersion.Extensions; +using GitVersion.Formatting; using GitVersion.Git; using GitVersion.Helpers; using GitVersion.VersionCalculation; @@ -75,11 +76,11 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, => configuration.GetBranchConfiguration(branchName).IsReleaseBranch ?? false; public static string? GetBranchSpecificLabel( - this EffectiveConfiguration configuration, ReferenceName branchName, string? branchNameOverride) - => GetBranchSpecificLabel(configuration, branchName.WithoutOrigin, branchNameOverride); + this EffectiveConfiguration configuration, ReferenceName branchName, string? branchNameOverride, IEnvironment? environment = null) + => GetBranchSpecificLabel(configuration, branchName.WithoutOrigin, branchNameOverride, environment); public static string? GetBranchSpecificLabel( - this EffectiveConfiguration configuration, string? branchName, string? branchNameOverride) + this EffectiveConfiguration configuration, string? branchName, string? branchNameOverride, IEnvironment? environment = null) { configuration.NotNull(); @@ -90,10 +91,43 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, } var effectiveBranchName = branchNameOverride ?? branchName; - if (configuration.RegularExpression.IsNullOrWhiteSpace() || effectiveBranchName.IsNullOrEmpty()) return label; + if (configuration.RegularExpression.IsNullOrWhiteSpace() || effectiveBranchName.IsNullOrEmpty()) + { + // Even if regex doesn't match, we should still process environment variables + if (environment is not null) + { + try + { + label = label.FormatWith(new { }, environment); + } + catch (ArgumentException) + { + // If environment variable is missing and no fallback, return label as-is + // This maintains backward compatibility + } + } + return label; + } + var regex = RegexPatterns.Cache.GetOrAdd(configuration.RegularExpression); var match = regex.Match(effectiveBranchName); - if (!match.Success) return label; + if (!match.Success) + { + // Even if regex doesn't match, we should still process environment variables + if (environment is not null) + { + try + { + label = label.FormatWith(new { }, environment); + } + catch (ArgumentException) + { + // If environment variable is missing and no fallback, return label as-is + } + } + return label; + } + foreach (var groupName in regex.GetGroupNames()) { var groupValue = match.Groups[groupName].Value; @@ -107,6 +141,21 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, startIndex = index + escapedGroupValue.Length; } } + + // Process environment variable placeholders after regex placeholders + if (environment is not null) + { + try + { + label = label.FormatWith(new { }, environment); + } + catch (ArgumentException) + { + // If environment variable is missing and no fallback, return label as-is + // This maintains backward compatibility + } + } + return label; } diff --git a/src/GitVersion.Core/VersionCalculation/VersionCalculators/NextVersionCalculator.cs b/src/GitVersion.Core/VersionCalculation/VersionCalculators/NextVersionCalculator.cs index fc38d26801..37cb2558cf 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionCalculators/NextVersionCalculator.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionCalculators/NextVersionCalculator.cs @@ -14,13 +14,15 @@ internal class NextVersionCalculator( IEnumerable deploymentModeCalculators, IEnumerable versionStrategies, IEffectiveBranchConfigurationFinder effectiveBranchConfigurationFinder, - ITaggedSemanticVersionService taggedSemanticVersionService) + ITaggedSemanticVersionService taggedSemanticVersionService, + IEnvironment environment) : INextVersionCalculator { private readonly ILog log = log.NotNull(); private readonly Lazy versionContext = versionContext.NotNull(); private readonly IVersionStrategy[] versionStrategies = [.. versionStrategies.NotNull()]; private readonly IEffectiveBranchConfigurationFinder effectiveBranchConfigurationFinder = effectiveBranchConfigurationFinder.NotNull(); + private readonly IEnvironment environment = environment.NotNull(); private GitVersionContext Context => this.versionContext.Value; @@ -108,7 +110,7 @@ private bool TryGetSemanticVersion( { result = null; - var label = effectiveConfiguration.GetBranchSpecificLabel(Context.CurrentBranch.Name, null); + var label = effectiveConfiguration.GetBranchSpecificLabel(Context.CurrentBranch.Name, null, this.environment); var currentCommitTaggedVersion = taggedSemanticVersionsOfCurrentCommit .Where(element => element.Value.IsMatchForBranchSpecificLabel(label)).Max(); From 96c6faaff9555d7234a7bc1096e319782a90d041 Mon Sep 17 00:00:00 2001 From: Roger Noden <11900533+W0GER@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:32:47 -0600 Subject: [PATCH 2/7] Refactor environment variable processing in branch label configuration Consolidated the handling of environment variables in branch label generation by introducing a dedicated method, ProcessEnvironmentVariables. This change simplifies the code and maintains backward compatibility while ensuring that environment variables are processed consistently, even when regex does not match. --- .../Extensions/ConfigurationExtensions.cs | 37 ++++++------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index 4cb19227f8..4713637d13 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -94,18 +94,7 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, if (configuration.RegularExpression.IsNullOrWhiteSpace() || effectiveBranchName.IsNullOrEmpty()) { // Even if regex doesn't match, we should still process environment variables - if (environment is not null) - { - try - { - label = label.FormatWith(new { }, environment); - } - catch (ArgumentException) - { - // If environment variable is missing and no fallback, return label as-is - // This maintains backward compatibility - } - } + label = ProcessEnvironmentVariables(label, environment); return label; } @@ -114,17 +103,7 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, if (!match.Success) { // Even if regex doesn't match, we should still process environment variables - if (environment is not null) - { - try - { - label = label.FormatWith(new { }, environment); - } - catch (ArgumentException) - { - // If environment variable is missing and no fallback, return label as-is - } - } + label = ProcessEnvironmentVariables(label, environment); return label; } @@ -143,6 +122,15 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, } // Process environment variable placeholders after regex placeholders + label = ProcessEnvironmentVariables(label, environment); + + return label; + } + + private static string EscapeInvalidCharacters(string groupValue) => groupValue.RegexReplace(RegexPatterns.Common.SanitizeNameRegexPattern, "-"); + + private static string ProcessEnvironmentVariables(string label, IEnvironment? environment) + { if (environment is not null) { try @@ -155,12 +143,9 @@ public static bool IsReleaseBranch(this IGitVersionConfiguration configuration, // This maintains backward compatibility } } - return label; } - private static string EscapeInvalidCharacters(string groupValue) => groupValue.RegexReplace(RegexPatterns.Common.SanitizeNameRegexPattern, "-"); - public static (string GitDirectory, string WorkingTreeDirectory)? FindGitDir(this IFileSystem fileSystem, string? path) { var startingDir = path; From ddaed6a55c26e352e2679bb313ea0a8d1e9247a9 Mon Sep 17 00:00:00 2001 From: Roger Noden <11900533+W0GER@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:36:04 -0600 Subject: [PATCH 3/7] Introduce EmptyFormatSource for environment variable processing in FormatWith Added a static object, EmptyFormatSource, to serve as a non-null source parameter when processing environment variables in the FormatWith method. This change simplifies the handling of environment variables by ensuring that no unnecessary properties are required when only using env: placeholders. --- src/GitVersion.Core/Extensions/ConfigurationExtensions.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index 4713637d13..bc623884a6 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -10,6 +10,10 @@ namespace GitVersion.Configuration; internal static class ConfigurationExtensions { + // Empty object used as source parameter when only processing environment variables in FormatWith + // FormatWith requires a non-null source, but we don't need any properties from it when only using env: placeholders + private static readonly object EmptyFormatSource = new { }; + public static EffectiveBranchConfiguration GetEffectiveBranchConfiguration( this IGitVersionConfiguration configuration, IBranch branch, EffectiveConfiguration? parentConfiguration = null) { @@ -135,7 +139,7 @@ private static string ProcessEnvironmentVariables(string label, IEnvironment? en { try { - label = label.FormatWith(new { }, environment); + label = label.FormatWith(EmptyFormatSource, environment); } catch (ArgumentException) { From bb3ad4a15a11eab47cd4f0df24b5b3d9508d968c Mon Sep 17 00:00:00 2001 From: Roger Noden <11900533+W0GER@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:41:53 -0600 Subject: [PATCH 4/7] Remove unused import of GitVersion.Core in ConfigurationExtensionsTests --- .../Configuration/ConfigurationExtensionsTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs index a84659e7d4..56430f2c1d 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs @@ -1,5 +1,4 @@ using GitVersion.Configuration; -using GitVersion.Core; using GitVersion.Core.Tests.Helpers; using GitVersion.Git; From 5c147ed3e846a40d33871e513137f50295d24017 Mon Sep 17 00:00:00 2001 From: Roger Noden <11900533+W0GER@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:18:45 -0600 Subject: [PATCH 5/7] fix(unittest): Fixed unit test so that when the pull request regex does not match we confirm a different label is used --- .../Configuration/ConfigurationExtensionsTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs index 56430f2c1d..1ef40b11b8 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs @@ -136,10 +136,13 @@ public void EnsureGetBranchSpecificLabelProcessesEnvironmentVariablesEvenWhenReg .WithBranch("pull-request", builder => builder .WithLabel("pr-{env:GITHUB_HEAD_REF}") .WithRegularExpression(@"^pull[/-]")) + .WithBranch("feature", builder => builder + .WithLabel("{BranchName}") + .WithRegularExpression(@"^features?[\/-](?.+)")) .Build(); - var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("other-branch")); - var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("other-branch"), null, environment); - actual.ShouldBe("pr-feature-branch"); + var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("feature/other-branch")); + var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("feature/other-branch"), null, environment); + actual.ShouldBe("other-branch"); } } From 2031196b948280083c42f78091127cf3151e6aed Mon Sep 17 00:00:00 2001 From: Roger Noden <11900533+W0GER@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:44:59 -0600 Subject: [PATCH 6/7] fix(merge): Fix merge mishap on previous commit --- .../Extensions/ConfigurationExtensions.cs | 113 ++++++++++-------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index b64b0acadd..4267236530 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -12,8 +12,7 @@ internal static class ConfigurationExtensions // FormatWith requires a non-null source, but we don't need any properties from it when only using env: placeholders private static readonly object EmptyFormatSource = new { }; - public static EffectiveBranchConfiguration GetEffectiveBranchConfiguration( - this IGitVersionConfiguration configuration, IBranch branch, EffectiveConfiguration? parentConfiguration = null) + extension(IGitVersionConfiguration configuration) { public EffectiveBranchConfiguration GetEffectiveBranchConfiguration(IBranch branch, EffectiveConfiguration? parentConfiguration = null) { @@ -66,12 +65,11 @@ static IEnumerable GetBranchConfigurations(IGitVersionConf public bool IsReleaseBranch(IBranch branch) => IsReleaseBranch(configuration, branch.NotNull().Name); - public static string? GetBranchSpecificLabel( - this EffectiveConfiguration configuration, ReferenceName branchName, string? branchNameOverride, IEnvironment? environment = null) - => GetBranchSpecificLabel(configuration, branchName.WithoutOrigin, branchNameOverride, environment); + public bool IsReleaseBranch(ReferenceName branchName) + => configuration.GetBranchConfiguration(branchName).IsReleaseBranch ?? false; + } - public static string? GetBranchSpecificLabel( - this EffectiveConfiguration configuration, string? branchName, string? branchNameOverride, IEnvironment? environment = null) + extension(IIgnoreConfiguration ignoreConfig) { public IEnumerable ToFilters() { @@ -90,63 +88,24 @@ public IEnumerable Filter(ITag[] source) return !ignoreConfig.IsEmpty ? source.Where(element => ShouldBeIgnored(element.Commit, ignoreConfig)) : source; } - var effectiveBranchName = branchNameOverride ?? branchName; - if (configuration.RegularExpression.IsNullOrWhiteSpace() || effectiveBranchName.IsNullOrEmpty()) - { - // Even if regex doesn't match, we should still process environment variables - label = ProcessEnvironmentVariables(label, environment); - return label; - } - - var regex = RegexPatterns.Cache.GetOrAdd(configuration.RegularExpression); - var match = regex.Match(effectiveBranchName); - if (!match.Success) - { - // Even if regex doesn't match, we should still process environment variables - label = ProcessEnvironmentVariables(label, environment); - return label; - } - - foreach (var groupName in regex.GetGroupNames()) + public IEnumerable Filter(ICommit[] source) { ignoreConfig.NotNull(); source.NotNull(); return !ignoreConfig.IsEmpty ? source.Where(element => ShouldBeIgnored(element, ignoreConfig)) : source; } - - // Process environment variable placeholders after regex placeholders - label = ProcessEnvironmentVariables(label, environment); - - return label; } private static bool ShouldBeIgnored(ICommit commit, IIgnoreConfiguration ignore) => !ignore.ToFilters().Any(filter => filter.Exclude(commit, out _)); - private static string ProcessEnvironmentVariables(string label, IEnvironment? environment) - { - if (environment is not null) - { - try - { - label = label.FormatWith(EmptyFormatSource, environment); - } - catch (ArgumentException) - { - // If environment variable is missing and no fallback, return label as-is - // This maintains backward compatibility - } - } - return label; - } - - public static (string GitDirectory, string WorkingTreeDirectory)? FindGitDir(this IFileSystem fileSystem, string? path) + extension(EffectiveConfiguration configuration) { - public string? GetBranchSpecificLabel(ReferenceName branchName, string? branchNameOverride) - => GetBranchSpecificLabel(configuration, branchName.WithoutOrigin, branchNameOverride); + public string? GetBranchSpecificLabel(ReferenceName branchName, string? branchNameOverride, IEnvironment? environment = null) + => GetBranchSpecificLabel(configuration, branchName.WithoutOrigin, branchNameOverride, environment); - public string? GetBranchSpecificLabel(string? branchName, string? branchNameOverride) + public string? GetBranchSpecificLabel(string? branchName, string? branchNameOverride, IEnvironment? environment = null) { configuration.NotNull(); @@ -157,10 +116,43 @@ public static (string GitDirectory, string WorkingTreeDirectory)? FindGitDir(thi } var effectiveBranchName = branchNameOverride ?? branchName; - if (configuration.RegularExpression.IsNullOrWhiteSpace() || effectiveBranchName.IsNullOrEmpty()) return label; + if (configuration.RegularExpression.IsNullOrWhiteSpace() || effectiveBranchName.IsNullOrEmpty()) + { + // Even if regex doesn't match, we should still process environment variables + if (environment is not null) + { + try + { + label = label.FormatWith(new { }, environment); + } + catch (ArgumentException) + { + // If environment variable is missing and no fallback, return label as-is + // This maintains backward compatibility + } + } + return label; + } + var regex = RegexPatterns.Cache.GetOrAdd(configuration.RegularExpression); var match = regex.Match(effectiveBranchName); - if (!match.Success) return label; + if (!match.Success) + { + // Even if regex doesn't match, we should still process environment variables + if (environment is not null) + { + try + { + label = label.FormatWith(new { }, environment); + } + catch (ArgumentException) + { + // If environment variable is missing and no fallback, return label as-is + } + } + return label; + } + foreach (var groupName in regex.GetGroupNames()) { var groupValue = match.Groups[groupName].Value; @@ -174,6 +166,21 @@ public static (string GitDirectory, string WorkingTreeDirectory)? FindGitDir(thi startIndex = index + escapedGroupValue.Length; } } + + // Process environment variable placeholders after regex placeholders + if (environment is not null) + { + try + { + label = label.FormatWith(new { }, environment); + } + catch (ArgumentException) + { + // If environment variable is missing and no fallback, return label as-is + // This maintains backward compatibility + } + } + return label; } From 99be2c3e2838d7f72e1937b0270df92153941229 Mon Sep 17 00:00:00 2001 From: Roger Noden <11900533+W0GER@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:49:44 -0600 Subject: [PATCH 7/7] fix(sonarcloud): Define a constant instead of using this literal 'pull-request' 7 times. --- .../Configuration/ConfigurationExtensionsTests.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs index 1ef40b11b8..0b90186a54 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs @@ -7,6 +7,7 @@ namespace GitVersion.Core.Tests.Configuration; [TestFixture] public class ConfigurationExtensionsTests : TestBase { + private const string PullRequestBranch = "pull-request"; [TestCase("release/2.0.0", "refs/heads/release/2.0.0", "release/2.0.0", "release/2.0.0", true, false, false, false, true)] @@ -64,13 +65,13 @@ public void EnsureGetBranchSpecificLabelProcessesEnvironmentVariables() var configuration = GitFlowConfigurationBuilder.New .WithoutBranches() - .WithBranch("pull-request", builder => builder + .WithBranch(PullRequestBranch, builder => builder .WithLabel("pr-{env:GITHUB_HEAD_REF}") .WithRegularExpression(@"^pull[/-]")) .Build(); - var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("pull-request")); - var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("pull-request"), null, environment); + var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName(PullRequestBranch)); + var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName(PullRequestBranch), null, environment); actual.ShouldBe("pr-feature-branch"); } @@ -82,13 +83,13 @@ public void EnsureGetBranchSpecificLabelProcessesEnvironmentVariablesWithFallbac var configuration = GitFlowConfigurationBuilder.New .WithoutBranches() - .WithBranch("pull-request", builder => builder + .WithBranch(PullRequestBranch, builder => builder .WithLabel("pr-{env:GITHUB_HEAD_REF ?? \"unknown\"}") .WithRegularExpression(@"^pull[/-]")) .Build(); - var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName("pull-request")); - var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName("pull-request"), null, environment); + var effectiveConfiguration = configuration.GetEffectiveConfiguration(ReferenceName.FromBranchName(PullRequestBranch)); + var actual = effectiveConfiguration.GetBranchSpecificLabel(ReferenceName.FromBranchName(PullRequestBranch), null, environment); actual.ShouldBe("pr-unknown"); } @@ -133,7 +134,7 @@ public void EnsureGetBranchSpecificLabelProcessesEnvironmentVariablesEvenWhenReg var configuration = GitFlowConfigurationBuilder.New .WithoutBranches() - .WithBranch("pull-request", builder => builder + .WithBranch(PullRequestBranch, builder => builder .WithLabel("pr-{env:GITHUB_HEAD_REF}") .WithRegularExpression(@"^pull[/-]")) .WithBranch("feature", builder => builder