Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
67f794a
Enhance branch label configuration with environment variable support
W0GER Nov 13, 2025
96c6faa
Refactor environment variable processing in branch label configuration
W0GER Nov 13, 2025
ddaed6a
Introduce EmptyFormatSource for environment variable processing in Fo…
W0GER Nov 13, 2025
bb3ad4a
Remove unused import of GitVersion.Core in ConfigurationExtensionsTests
W0GER Nov 13, 2025
5c147ed
fix(unittest): Fixed unit test so that when the pull request regex do…
W0GER Nov 14, 2025
a14f985
Updates dotnet and docker versions
arturcic Nov 12, 2025
3046c45
updates .net TFM to net10.0
arturcic Nov 12, 2025
10deb84
updates to net10.0
arturcic Nov 12, 2025
f2c8313
adds net10.0 target framework
arturcic Nov 12, 2025
7a9c93c
use extensions members
arturcic Nov 12, 2025
407b8be
refactors tag prefix configuration
arturcic Nov 12, 2025
fe0f99a
refactors reference name extensions
arturcic Nov 12, 2025
7a22438
targets net10 in tests
arturcic Nov 13, 2025
8256efb
suppresses specific SonarAnalyzer warning
arturcic Nov 13, 2025
c85ea5f
fix copilot review comments
arturcic Nov 13, 2025
866b3e8
fix(merge): Fix merge mishap on previous commit
W0GER Nov 14, 2025
f4c1387
fix(sonarcloud): Define a constant instead of using this literal 'pul…
W0GER Nov 14, 2025
a9a6d52
Add FormatWith overload accepting Dictionary<string, object> for labe…
W0GER Feb 23, 2026
42c6f64
Refactor GetBranchSpecificLabel to use dictionary and single FormatWi…
W0GER Feb 23, 2026
e7b49e6
Thread IEnvironment through MainlineContext and MainlineVersionStrategy
W0GER Feb 23, 2026
47233d1
Inject IEnvironment into version strategies calling GetBranchSpecific…
W0GER Feb 23, 2026
9a16310
Use context.Environment in mainline incrementers and enrichers
W0GER Feb 23, 2026
d3e73df
Update tests and public API for required IEnvironment and throwIfNotF…
W0GER Feb 23, 2026
8e94862
fix: Force the dictionary overload by casting the built dictionary to…
W0GER Feb 23, 2026
9cacd51
Fix after rebasing.
W0GER Feb 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/input/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?[\/-](?<StoryNo>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?[\/-](?<BranchName>.+)'
```

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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GitVersion.Configuration;
using GitVersion.Core;

Check warning on line 2 in src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / DotNet Format

Using directive is unnecessary.

Check warning on line 2 in src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / DotNet Format

Using directive is unnecessary.

Check warning on line 2 in src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / DotNet Format

Using directive is unnecessary.

Check warning on line 2 in src/GitVersion.Configuration.Tests/Configuration/ConfigurationExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / DotNet Format

Using directive is unnecessary.
using GitVersion.Core.Tests.Helpers;
using GitVersion.Git;

Expand Down Expand Up @@ -55,4 +56,91 @@
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?[\/-](?<BranchName>.+)"))
.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?[\/-](?<BranchName>.+)"))
.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");
}
}
59 changes: 54 additions & 5 deletions src/GitVersion.Core/Extensions/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();

Expand All @@ -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);
Comment thread
W0GER marked this conversation as resolved.
Outdated
}
catch (ArgumentException)
{
// If environment variable is missing and no fallback, return label as-is
// This maintains backward compatibility
}
Comment thread
W0GER marked this conversation as resolved.
Outdated
}
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;
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ internal class NextVersionCalculator(
IEnumerable<IDeploymentModeCalculator> deploymentModeCalculators,
IEnumerable<IVersionStrategy> versionStrategies,
IEffectiveBranchConfigurationFinder effectiveBranchConfigurationFinder,
ITaggedSemanticVersionService taggedSemanticVersionService)
ITaggedSemanticVersionService taggedSemanticVersionService,
IEnvironment environment)
: INextVersionCalculator
{
private readonly ILog log = log.NotNull();
private readonly Lazy<GitVersionContext> 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;

Expand Down Expand Up @@ -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();

Expand Down
Loading