From 8b9ea20c17281b10e7c4b9195f4ed3647c14aee0 Mon Sep 17 00:00:00 2001 From: Artur Stolear Date: Wed, 19 Aug 2026 08:53:55 +0200 Subject: [PATCH 1/4] feat: select configuration format version --- .../Core/ConfigurationVersionSelectorTests.cs | 48 +++++++++++++++++++ .../Configuration/ConfigurationVersion.cs | 28 +++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/GitVersion.Core.Tests/Core/ConfigurationVersionSelectorTests.cs create mode 100644 src/GitVersion.Core/Configuration/ConfigurationVersion.cs diff --git a/src/GitVersion.Core.Tests/Core/ConfigurationVersionSelectorTests.cs b/src/GitVersion.Core.Tests/Core/ConfigurationVersionSelectorTests.cs new file mode 100644 index 0000000000..824adc42e5 --- /dev/null +++ b/src/GitVersion.Core.Tests/Core/ConfigurationVersionSelectorTests.cs @@ -0,0 +1,48 @@ +using GitVersion.Configuration; + +namespace GitVersion.Tests; + +[TestFixture] +[NonParallelizable] +public class ConfigurationVersionSelectorTests : TestBase +{ + [TestCase(null, false)] + [TestCase("", false)] + [TestCase("v6", true)] + [TestCase("V6", true)] + [TestCase(" v6 ", true)] + [TestCase("v7", false)] + [TestCase("V7", false)] + [TestCase(" v7 ", false)] + public void ResolvesKnownValues(string? value, bool isV6) + { + using var scope = new EnvironmentVariableScope(value); + + ConfigurationVersionSelector.Resolve().ShouldBe(isV6 ? ConfigurationVersion.V6 : ConfigurationVersion.V7); + } + + [TestCase("6")] + [TestCase("7")] + [TestCase("true")] + [TestCase("legacy")] + public void FailsFastOnUnknownValues(string value) + { + using var scope = new EnvironmentVariableScope(value); + + var exception = Should.Throw(() => ConfigurationVersionSelector.Resolve()); + exception.Message.ShouldContain(value); + exception.Message.ShouldContain("v6"); + exception.Message.ShouldContain("v7"); + } + + private sealed class EnvironmentVariableScope : IDisposable + { + private readonly string? original = System.Environment.GetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName); + + public EnvironmentVariableScope(string? value) => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, value); + + public void Dispose() => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, this.original); + } +} diff --git a/src/GitVersion.Core/Configuration/ConfigurationVersion.cs b/src/GitVersion.Core/Configuration/ConfigurationVersion.cs new file mode 100644 index 0000000000..fc9710f637 --- /dev/null +++ b/src/GitVersion.Core/Configuration/ConfigurationVersion.cs @@ -0,0 +1,28 @@ +namespace GitVersion.Configuration; + +internal enum ConfigurationVersion +{ + V6, + V7 +} + +internal static class ConfigurationVersionSelector +{ + public const string EnvironmentVariableName = "GITVERSION_CONFIGURATION_VERSION"; + + public static ConfigurationVersion Resolve() + { + var value = SysEnv.GetEnvironmentVariable(EnvironmentVariableName)?.Trim(); + + return value switch + { + null or "" => ConfigurationVersion.V7, + _ when value.Equals("v6", StringComparison.OrdinalIgnoreCase) => ConfigurationVersion.V6, + _ when value.Equals("v7", StringComparison.OrdinalIgnoreCase) => ConfigurationVersion.V7, + _ => throw new WarningException( + $"Unrecognized {EnvironmentVariableName} value '{value}'. Valid values are 'v6' and 'v7'.") + }; + } + + public static string ResolveName() => Resolve() == ConfigurationVersion.V6 ? "v6" : "v7"; +} From 409fd5deea358dc585dd8a8185585a09736d4604 Mon Sep 17 00:00:00 2001 From: Artur Stolear Date: Wed, 19 Aug 2026 08:54:10 +0200 Subject: [PATCH 2/4] feat: add calculation and output configuration sections --- .gitversion.yml | 18 +- docs/input/docs/reference/configuration.md | 822 +++++++++--------- .../mdsource/configuration.source.md | 5 +- docs/input/docs/workflows/GitFlow/v1.yml | 350 ++++---- docs/input/docs/workflows/GitHubFlow/v1.yml | 245 +++--- .../docs/workflows/TrunkBased/preview1.yml | 216 ++--- .../ConfigurationVersionIntegrationTests.cs | 90 ++ .../PullRequestInBuildAgentTest.cs | 10 +- src/GitVersion.App/GitVersionExecutor.cs | 9 +- .../AssemblyParallelizable.cs | 1 + .../ConfigurationDocumentMapperTests.cs | 155 ++++ .../ConfigurationProviderTests.cs | 114 +++ .../ConfigurationSerializerTests.cs | 85 +- .../Configuration/IgnoreConfigurationTests.cs | 31 +- .../Workflows/approved/GitFlow/v1.yml | 350 ++++---- .../Workflows/approved/GitHubFlow/v1.yml | 245 +++--- .../approved/TrunkBased/preview1.yml | 216 ++--- .../BranchConfiguration.cs | 9 + .../Builders/ConfigurationBuilderBase.cs | 72 +- .../ConfigurationDocumentMapper.cs | 318 +++++++ .../ConfigurationHelper.cs | 8 +- .../ConfigurationProvider.cs | 21 +- .../ConfigurationSerializer.cs | 43 +- .../GitVersionConfiguration.cs | 64 +- .../Core/GitVersionExecutorTests.cs | 12 + .../Configuration/IBranchConfiguration.cs | 32 +- .../ICalculationBranchConfiguration.cs | 48 + .../ICalculationConfiguration.cs | 49 ++ .../Configuration/IGitVersionConfiguration.cs | 6 + .../IOutputBranchConfiguration.cs | 11 + .../Configuration/IOutputConfiguration.cs | 32 + src/GitVersion.Core/PublicAPI.Unshipped.txt | 44 + .../Tasks/WriteVersionInfoTest.cs | 6 +- 33 files changed, 2452 insertions(+), 1285 deletions(-) create mode 100644 src/GitVersion.App.Tests/ConfigurationVersionIntegrationTests.cs create mode 100644 src/GitVersion.Configuration.Tests/AssemblyParallelizable.cs create mode 100644 src/GitVersion.Configuration.Tests/Configuration/ConfigurationDocumentMapperTests.cs create mode 100644 src/GitVersion.Configuration/ConfigurationDocumentMapper.cs create mode 100644 src/GitVersion.Core/Configuration/ICalculationBranchConfiguration.cs create mode 100644 src/GitVersion.Core/Configuration/ICalculationConfiguration.cs create mode 100644 src/GitVersion.Core/Configuration/IOutputBranchConfiguration.cs create mode 100644 src/GitVersion.Core/Configuration/IOutputConfiguration.cs diff --git a/.gitversion.yml b/.gitversion.yml index 7f3e82a62b..a57118c2e5 100644 --- a/.gitversion.yml +++ b/.gitversion.yml @@ -1,9 +1,11 @@ # $schema: https://gitversion.net/schemas/7.0/GitVersion.configuration.json -workflow: GitFlow/v1 -mode: ManualDeployment -next-version: 7.0.0 -branches: - main: - label: alpha - support: - label: beta +calculation: + workflow: GitFlow/v1 + mode: ManualDeployment + next-version: 7.0.0 + branches: + main: + label: alpha + support: + label: beta +output: {} diff --git a/docs/input/docs/reference/configuration.md b/docs/input/docs/reference/configuration.md index 0015f1e1b6..427ec535b4 100644 --- a/docs/input/docs/reference/configuration.md +++ b/docs/input/docs/reference/configuration.md @@ -32,8 +32,9 @@ The following supported workflow configurations are available in GitVersion and Example of using a `GitHubFlow` workflow with a different `tag-prefix`: ```yaml -workflow: GitHubFlow/v1 -tag-prefix: '[abc]' +calculation: + workflow: GitHubFlow/v1 + tag-prefix: '[abc]' ``` The built-in configuration for the `GitFlow` workflow (`workflow: GitFlow/v1`) looks like: @@ -41,178 +42,188 @@ The built-in configuration for the `GitFlow` workflow (`workflow: GitFlow/v1`) l ```yml -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - develop: - increment: Minor - is-main-branch: false - is-release-branch: false - is-source-branch-for: [] - label: alpha - mode: ContinuousDelivery - pre-release-weight: 0 - prevent-increment: - when-current-commit-tagged: false - regex: ^dev(elop)?(ment)?$ - source-branches: - - main - track-merge-message: true - track-merge-target: true - tracks-release-branches: true - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - release: - increment: Minor - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^releases?[\\/-](?.+)" - source-branches: - - main - - support - track-merge-target: false - tracks-release-branches: false - feature: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - develop - - main - - release - - support - - hotfix - track-merge-message: true - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - develop - - main - - release - - feature - - support - - hotfix - track-merge-message: true - hotfix: - increment: Inherit - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^hotfix(es)?[\\/-](?.+)" - source-branches: - - main - - support - support: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^support[\\/-](?.+)" - source-branches: - - main - track-merge-target: false - tracks-release-branches: false - unknown: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - prevent-increment: - when-current-commit-tagged: true - regex: "(?.+)" - source-branches: - - main - - develop - - release - - feature - - pull-request - - hotfix - - support -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - Fallback - - ConfiguredNextVersion - - MergeMessage - - TaggedCommit - - TrackReleaseBranches - - VersionInBranchName -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + develop: + increment: Minor + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: alpha + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: ^dev(elop)?(ment)?$ + source-branches: + - main + track-merge-message: true + track-merge-target: true + tracks-release-branches: true + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + release: + increment: Minor + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^releases?[\\/-](?.+)" + source-branches: + - main + - support + track-merge-target: false + tracks-release-branches: false + feature: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - develop + - main + - release + - support + - hotfix + track-merge-message: true + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - develop + - main + - release + - feature + - support + - hotfix + track-merge-message: true + hotfix: + increment: Inherit + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^hotfix(es)?[\\/-](?.+)" + source-branches: + - main + - support + support: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^support[\\/-](?.+)" + source-branches: + - main + track-merge-target: false + tracks-release-branches: false + unknown: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: true + regex: "(?.+)" + source-branches: + - main + - develop + - release + - feature + - pull-request + - hotfix + - support + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - Fallback + - ConfiguredNextVersion + - MergeMessage + - TaggedCommit + - TrackReleaseBranches + - VersionInBranchName + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + develop: + pre-release-weight: 0 + main: + pre-release-weight: 55000 + release: + pre-release-weight: 30000 + feature: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + hotfix: + pre-release-weight: 30000 + support: + pre-release-weight: 55000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true ``` -snippet source | anchor +snippet source | anchor The supported built-in configuration for the `GitHubFlow` workflow (`workflow: GitHubFlow/v1`) looks like: @@ -220,127 +231,134 @@ The supported built-in configuration for the `GitHubFlow` workflow (`workflow: G ```yml -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - release: - increment: Patch - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-branch-merged: false - when-current-commit-tagged: false - regex: "^releases?[\\/-](?.+)" - source-branches: - - main - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - feature: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - main - - release - track-merge-message: true - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - main - - release - - feature - track-merge-message: true - unknown: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - prevent-increment: - when-current-commit-tagged: false - regex: "(?.+)" - source-branches: - - main - - release - - feature - - pull-request - track-merge-message: false -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - Fallback - - ConfiguredNextVersion - - MergeMessage - - TaggedCommit - - TrackReleaseBranches - - VersionInBranchName -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + release: + increment: Patch + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + of-merged-branch: true + when-branch-merged: false + when-current-commit-tagged: false + regex: "^releases?[\\/-](?.+)" + source-branches: + - main + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + feature: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - main + - release + track-merge-message: true + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - main + - release + - feature + track-merge-message: true + unknown: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "(?.+)" + source-branches: + - main + - release + - feature + - pull-request + track-merge-message: false + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - Fallback + - ConfiguredNextVersion + - MergeMessage + - TaggedCommit + - TrackReleaseBranches + - VersionInBranchName + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + main: + pre-release-weight: 55000 + release: + pre-release-weight: 30000 + feature: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true ``` -snippet source | anchor +snippet source | anchor The preview built-in configuration (experimental usage only) for the `TrunkBased` workflow (`workflow: TrunkBased/preview1`) looks like: @@ -348,112 +366,120 @@ The preview built-in configuration (experimental usage only) for the `TrunkBased ```yml -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - mode: ContinuousDeployment - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - feature: - increment: Minor - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - main - track-merge-message: true - hotfix: - increment: Patch - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: "{BranchName}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^hotfix(es)?[\\/-](?.+)" - source-branches: - - main - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - main - - feature - - hotfix - track-merge-message: true - unknown: - increment: Patch - is-source-branch-for: [] - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "(?.+)" - source-branches: - - main -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - ConfiguredNextVersion - - Mainline -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + mode: ContinuousDeployment + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + feature: + increment: Minor + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - main + track-merge-message: true + hotfix: + increment: Patch + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: "{BranchName}" + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: "^hotfix(es)?[\\/-](?.+)" + source-branches: + - main + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - main + - feature + - hotfix + track-merge-message: true + unknown: + increment: Patch + is-source-branch-for: [] + prevent-increment: + when-current-commit-tagged: false + regex: "(?.+)" + source-branches: + - main + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - ConfiguredNextVersion + - Mainline + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + main: + pre-release-weight: 55000 + feature: + pre-release-weight: 30000 + hotfix: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + unknown: + pre-release-weight: 30000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true ``` -snippet source | anchor +snippet source | anchor The details of the available options are as follows: diff --git a/docs/input/docs/reference/mdsource/configuration.source.md b/docs/input/docs/reference/mdsource/configuration.source.md index 42454a39f1..18d6fe52b9 100644 --- a/docs/input/docs/reference/mdsource/configuration.source.md +++ b/docs/input/docs/reference/mdsource/configuration.source.md @@ -32,8 +32,9 @@ The following supported workflow configurations are available in GitVersion and Example of using a `GitHubFlow` workflow with a different `tag-prefix`: ```yaml -workflow: GitHubFlow/v1 -tag-prefix: '[abc]' +calculation: + workflow: GitHubFlow/v1 + tag-prefix: '[abc]' ``` The built-in configuration for the `GitFlow` workflow (`workflow: GitFlow/v1`) looks like: diff --git a/docs/input/docs/workflows/GitFlow/v1.yml b/docs/input/docs/workflows/GitFlow/v1.yml index cb02dab624..eb3ec5ba8a 100644 --- a/docs/input/docs/workflows/GitFlow/v1.yml +++ b/docs/input/docs/workflows/GitFlow/v1.yml @@ -1,170 +1,180 @@ -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - develop: - increment: Minor - is-main-branch: false - is-release-branch: false - is-source-branch-for: [] - label: alpha - mode: ContinuousDelivery - pre-release-weight: 0 - prevent-increment: - when-current-commit-tagged: false - regex: ^dev(elop)?(ment)?$ - source-branches: - - main - track-merge-message: true - track-merge-target: true - tracks-release-branches: true - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - release: - increment: Minor - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^releases?[\\/-](?.+)" - source-branches: - - main - - support - track-merge-target: false - tracks-release-branches: false - feature: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - develop - - main - - release - - support - - hotfix - track-merge-message: true - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - develop - - main - - release - - feature - - support - - hotfix - track-merge-message: true - hotfix: - increment: Inherit - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^hotfix(es)?[\\/-](?.+)" - source-branches: - - main - - support - support: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^support[\\/-](?.+)" - source-branches: - - main - track-merge-target: false - tracks-release-branches: false - unknown: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - prevent-increment: - when-current-commit-tagged: true - regex: "(?.+)" - source-branches: - - main - - develop - - release - - feature - - pull-request - - hotfix - - support -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - Fallback - - ConfiguredNextVersion - - MergeMessage - - TaggedCommit - - TrackReleaseBranches - - VersionInBranchName -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + develop: + increment: Minor + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: alpha + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: ^dev(elop)?(ment)?$ + source-branches: + - main + track-merge-message: true + track-merge-target: true + tracks-release-branches: true + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + release: + increment: Minor + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^releases?[\\/-](?.+)" + source-branches: + - main + - support + track-merge-target: false + tracks-release-branches: false + feature: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - develop + - main + - release + - support + - hotfix + track-merge-message: true + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - develop + - main + - release + - feature + - support + - hotfix + track-merge-message: true + hotfix: + increment: Inherit + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^hotfix(es)?[\\/-](?.+)" + source-branches: + - main + - support + support: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^support[\\/-](?.+)" + source-branches: + - main + track-merge-target: false + tracks-release-branches: false + unknown: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: true + regex: "(?.+)" + source-branches: + - main + - develop + - release + - feature + - pull-request + - hotfix + - support + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - Fallback + - ConfiguredNextVersion + - MergeMessage + - TaggedCommit + - TrackReleaseBranches + - VersionInBranchName + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + develop: + pre-release-weight: 0 + main: + pre-release-weight: 55000 + release: + pre-release-weight: 30000 + feature: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + hotfix: + pre-release-weight: 30000 + support: + pre-release-weight: 55000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true diff --git a/docs/input/docs/workflows/GitHubFlow/v1.yml b/docs/input/docs/workflows/GitHubFlow/v1.yml index 70e3a4d8bf..0e1d664674 100644 --- a/docs/input/docs/workflows/GitHubFlow/v1.yml +++ b/docs/input/docs/workflows/GitHubFlow/v1.yml @@ -1,119 +1,126 @@ -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - release: - increment: Patch - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-branch-merged: false - when-current-commit-tagged: false - regex: "^releases?[\\/-](?.+)" - source-branches: - - main - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - feature: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - main - - release - track-merge-message: true - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - main - - release - - feature - track-merge-message: true - unknown: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - prevent-increment: - when-current-commit-tagged: false - regex: "(?.+)" - source-branches: - - main - - release - - feature - - pull-request - track-merge-message: false -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - Fallback - - ConfiguredNextVersion - - MergeMessage - - TaggedCommit - - TrackReleaseBranches - - VersionInBranchName -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + release: + increment: Patch + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + of-merged-branch: true + when-branch-merged: false + when-current-commit-tagged: false + regex: "^releases?[\\/-](?.+)" + source-branches: + - main + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + feature: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - main + - release + track-merge-message: true + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - main + - release + - feature + track-merge-message: true + unknown: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "(?.+)" + source-branches: + - main + - release + - feature + - pull-request + track-merge-message: false + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - Fallback + - ConfiguredNextVersion + - MergeMessage + - TaggedCommit + - TrackReleaseBranches + - VersionInBranchName + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + main: + pre-release-weight: 55000 + release: + pre-release-weight: 30000 + feature: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true diff --git a/docs/input/docs/workflows/TrunkBased/preview1.yml b/docs/input/docs/workflows/TrunkBased/preview1.yml index 2998ca1498..85b8f4d372 100644 --- a/docs/input/docs/workflows/TrunkBased/preview1.yml +++ b/docs/input/docs/workflows/TrunkBased/preview1.yml @@ -1,104 +1,112 @@ -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - mode: ContinuousDeployment - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - feature: - increment: Minor - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - main - track-merge-message: true - hotfix: - increment: Patch - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: "{BranchName}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^hotfix(es)?[\\/-](?.+)" - source-branches: - - main - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - main - - feature - - hotfix - track-merge-message: true - unknown: - increment: Patch - is-source-branch-for: [] - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "(?.+)" - source-branches: - - main -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - ConfiguredNextVersion - - Mainline -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + mode: ContinuousDeployment + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + feature: + increment: Minor + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - main + track-merge-message: true + hotfix: + increment: Patch + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: "{BranchName}" + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: "^hotfix(es)?[\\/-](?.+)" + source-branches: + - main + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - main + - feature + - hotfix + track-merge-message: true + unknown: + increment: Patch + is-source-branch-for: [] + prevent-increment: + when-current-commit-tagged: false + regex: "(?.+)" + source-branches: + - main + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - ConfiguredNextVersion + - Mainline + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + main: + pre-release-weight: 55000 + feature: + pre-release-weight: 30000 + hotfix: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + unknown: + pre-release-weight: 30000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true diff --git a/src/GitVersion.App.Tests/ConfigurationVersionIntegrationTests.cs b/src/GitVersion.App.Tests/ConfigurationVersionIntegrationTests.cs new file mode 100644 index 0000000000..5d4e486f9e --- /dev/null +++ b/src/GitVersion.App.Tests/ConfigurationVersionIntegrationTests.cs @@ -0,0 +1,90 @@ +using GitVersion.App.Tests.Helpers; +using GitVersion.Configuration; +using GitVersion.Helpers; + +namespace GitVersion.App.Tests; + +[TestFixture] +public class ConfigurationVersionIntegrationTests +{ + [Test] + public void V6AndV7ConfigurationCalculateTheSameVersion() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.MakeACommit(); + var configurationPath = FileSystemHelper.Path.Combine(fixture.RepositoryPath, ConfigurationFileLocator.DefaultFileName); + + FileSystemHelper.File.WriteAllText(configurationPath, "next-version: 2.0.0"); + var v6Result = Execute(fixture.RepositoryPath, "v6"); + + FileSystemHelper.File.WriteAllText(configurationPath, """ + calculation: + next-version: 2.0.0 + output: {} + """); + var v7Result = Execute(fixture.RepositoryPath, "v7"); + + v6Result.ExitCode.ShouldBe(0); + v7Result.ExitCode.ShouldBe(0); + GetFullSemVer(v7Result.Output!).ShouldBe(GetFullSemVer(v6Result.Output!)); + } + + [TestCase("v6", false)] + [TestCase("v7", true)] + public void ShowConfigUsesSelectedConfigurationStructure(string version, bool nested) + { + using var fixture = new EmptyRepositoryFixture(); + var result = GitVersionHelper.ExecuteIn( + fixture.RepositoryPath, + " --show-config", + logToFile: false, + new KeyValuePair(ConfigurationVersionSelector.EnvironmentVariableName, version)); + + result.ExitCode.ShouldBe(0); + result.Output.ShouldNotBeNull(); + result.Output.Contains("calculation:", StringComparison.Ordinal).ShouldBe(nested); + result.Output.Contains("output:", StringComparison.Ordinal).ShouldBe(nested); + } + + [Test] + public void DefaultCalculationDisplaysConfigurationVersionMismatchOnStandardError() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.MakeACommit(); + var configurationPath = FileSystemHelper.Path.Combine(fixture.RepositoryPath, ConfigurationFileLocator.DefaultFileName); + FileSystemHelper.File.WriteAllText(configurationPath, "next-version: 2.0.0"); + + var result = Execute(fixture.RepositoryPath, "v7"); + + result.ExitCode.ShouldBe(1); + result.Output.ShouldNotBeNull(); + result.Output.ShouldContain("An error occurred:"); + result.Output.ShouldContain("uses the legacy v6 configuration structure"); + result.Output.ShouldContain("gitversion config migrate"); + } + + [Test] + public void InvalidConfigurationVersionDisplaysDiagnosticOnStandardError() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.MakeACommit(); + + var result = Execute(fixture.RepositoryPath, "invalid"); + + result.ExitCode.ShouldBe(1); + result.Output.ShouldNotBeNull(); + result.Output.ShouldContain("An error occurred:"); + result.Output.ShouldContain("Unrecognized GITVERSION_CONFIGURATION_VERSION value 'invalid'"); + result.Output.ShouldContain("Valid values are 'v6' and 'v7'"); + } + + private static ExecutionResults Execute(string repositoryPath, string version) => + GitVersionHelper.ExecuteIn( + repositoryPath, + arguments: null, + logToFile: false, + new KeyValuePair(ConfigurationVersionSelector.EnvironmentVariableName, version)); + + private static string? GetFullSemVer(string json) => + JsonDocument.Parse(json).RootElement.GetProperty("FullSemVer").GetString(); +} diff --git a/src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs b/src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs index 408d50dc65..24f08780fd 100644 --- a/src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs +++ b/src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs @@ -150,10 +150,12 @@ public async Task VerifyBitBucketPipelinesPullRequest(string pullRequestRef) } private const string GitLabMergeRequestPullRequestConfig = """ - workflow: GitFlow/v1 - branches: - pull-request: - regex: ^merge-requests/(?\d+)/(head|merge)$ + calculation: + workflow: GitFlow/v1 + branches: + pull-request: + regex: ^merge-requests/(?\d+)/(head|merge)$ + output: {} """; private static async Task VerifyGitLabMergeRequestVersionIsCalculatedProperly(string mergeRequestRef, Dictionary env) diff --git a/src/GitVersion.App/GitVersionExecutor.cs b/src/GitVersion.App/GitVersionExecutor.cs index 1d102033c4..2bd462d3f8 100644 --- a/src/GitVersion.App/GitVersionExecutor.cs +++ b/src/GitVersion.App/GitVersionExecutor.cs @@ -70,8 +70,9 @@ private int RunGitVersionTool(GitVersionOptions gitVersionOptions) this.gitVersionOutputTool.UpdateAssemblyInfo(variables); this.gitVersionOutputTool.UpdateWixVersionFile(variables); } - catch (WarningException exception) + catch (Exception exception) when (exception is WarningException or ConfigurationException) { + WriteError(exception); this.logger.LogError(exception, """ An error occurred: {Message} @@ -103,6 +104,12 @@ private int RunGitVersionTool(GitVersionOptions gitVersionOptions) return 0; } + private static void WriteError(Exception exception) + { + Console.Error.WriteLine("An error occurred:"); + Console.Error.WriteLine(exception.Message); + } + private void Initialize(GitVersionOptions gitVersionOptions) { if (gitVersionOptions.Diag) diff --git a/src/GitVersion.Configuration.Tests/AssemblyParallelizable.cs b/src/GitVersion.Configuration.Tests/AssemblyParallelizable.cs new file mode 100644 index 0000000000..3652cede34 --- /dev/null +++ b/src/GitVersion.Configuration.Tests/AssemblyParallelizable.cs @@ -0,0 +1 @@ +[assembly: Parallelizable(ParallelScope.None)] diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationDocumentMapperTests.cs b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationDocumentMapperTests.cs new file mode 100644 index 0000000000..8fa93b490f --- /dev/null +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationDocumentMapperTests.cs @@ -0,0 +1,155 @@ +namespace GitVersion.Configuration.Tests; + +[TestFixture] +public class ConfigurationDocumentMapperTests +{ + [Test] + public void AssignsEverySerializedPropertyToExactlyOneV7Section() + { + var serializedProperties = typeof(GitVersionConfiguration) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(property => property.GetCustomAttribute() is not null) + .ToArray(); + var calculationProperties = GetInterfacePropertyNames(typeof(ICalculationConfiguration)); + calculationProperties.Remove(nameof(ICalculationConfiguration.VersionStrategy)); + calculationProperties.Add(nameof(GitVersionConfiguration.VersionStrategies)); + var outputProperties = GetInterfacePropertyNames(typeof(IOutputConfiguration)); + + calculationProperties.Intersect(outputProperties) + .ShouldBe([nameof(ICalculationConfiguration.Branches)]); + calculationProperties.Union(outputProperties).Order() + .ShouldBe(serializedProperties.Select(property => property.Name).Order()); + + var expectedOutputPropertyNames = serializedProperties + .Where(property => outputProperties.Contains(property.Name) + && property.Name != nameof(IOutputConfiguration.Branches)) + .Select(property => property.GetCustomAttribute()!.Name) + .Order(); + serializedProperties + .Select(property => property.GetCustomAttribute()!.Name) + .Where(ConfigurationDocumentMapper.IsOutputProperty) + .Order() + .ShouldBe(expectedOutputPropertyNames); + + var branchProperties = typeof(BranchConfiguration) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(property => property.GetCustomAttribute() is not null) + .ToArray(); + var calculationBranchProperties = GetInterfacePropertyNames(typeof(ICalculationBranchConfiguration)); + var outputBranchProperties = GetInterfacePropertyNames(typeof(IOutputBranchConfiguration)); + + calculationBranchProperties.Intersect(outputBranchProperties).ShouldBeEmpty(); + calculationBranchProperties.Union(outputBranchProperties).Order() + .ShouldBe(branchProperties.Select(property => property.Name).Order()); + branchProperties + .Select(property => property.GetCustomAttribute()!.Name) + .Where(ConfigurationDocumentMapper.IsOutputBranchProperty) + .Order() + .ShouldBe(branchProperties + .Where(property => outputBranchProperties.Contains(property.Name)) + .Select(property => property.GetCustomAttribute()!.Name) + .Order()); + } + + [Test] + public void DetectsEmptyFlatNestedAndMixedDocuments() + { + ConfigurationDocumentMapper.Detect(new Dictionary()) + .ShouldBe(ConfigurationDocumentKind.Empty); + ConfigurationDocumentMapper.Detect(new Dictionary { ["tag-prefix"] = "v" }) + .ShouldBe(ConfigurationDocumentKind.V6); + ConfigurationDocumentMapper.Detect(new Dictionary { ["calculation"] = new Dictionary() }) + .ShouldBe(ConfigurationDocumentKind.V7); + ConfigurationDocumentMapper.Detect(new Dictionary + { + ["calculation"] = new Dictionary(), + ["tag-prefix"] = "v" + }) + .ShouldBe(ConfigurationDocumentKind.Mixed); + } + + [Test] + public void FlattensAndMergesCalculationAndOutputBranches() + { + Dictionary document = new() + { + ["calculation"] = new Dictionary + { + ["tag-prefix"] = "v", + ["branches"] = new Dictionary + { + ["main"] = new Dictionary { ["increment"] = "Patch" } + } + }, + ["output"] = new Dictionary + { + ["update-build-number"] = false, + ["branches"] = new Dictionary + { + ["main"] = new Dictionary { ["pre-release-weight"] = 42 }, + ["develop"] = new Dictionary { ["custom-version-format"] = "{SemVer}" } + } + } + }; + + var result = ConfigurationDocumentMapper.Flatten(document); + + result["tag-prefix"].ShouldBe("v"); + result["update-build-number"].ShouldBe(false); + var branches = result["branches"].ShouldBeOfType>(); + var main = branches["main"].ShouldBeOfType>(); + main["increment"].ShouldBe("Patch"); + main["pre-release-weight"].ShouldBe(42); + branches.ContainsKey("develop").ShouldBeTrue(); + } + + [Test] + public void RejectsMixedAndSelectedVersionMismatches() + { + Dictionary flat = new() { ["tag-prefix"] = "v" }; + Dictionary nested = new() { ["calculation"] = new Dictionary() }; + Dictionary mixed = new() + { + ["calculation"] = new Dictionary(), + ["tag-prefix"] = "v" + }; + + Should.Throw(() => + ConfigurationDocumentMapper.Normalize(flat, ConfigurationVersion.V7, "test")); + Should.Throw(() => + ConfigurationDocumentMapper.Normalize(nested, ConfigurationVersion.V6, "test")); + Should.Throw(() => + ConfigurationDocumentMapper.Normalize(mixed, ConfigurationVersion.V7, "test")); + } + + [Test] + public void RejectsPropertiesInTheWrongV7SectionWithReplacement() + { + Dictionary wrongRoot = new() + { + ["calculation"] = new Dictionary { ["update-build-number"] = false } + }; + Dictionary wrongBranch = new() + { + ["output"] = new Dictionary + { + ["branches"] = new Dictionary + { + ["main"] = new Dictionary { ["increment"] = "Major" } + } + } + }; + + Should.Throw(() => ConfigurationDocumentMapper.Flatten(wrongRoot)) + .Message.ShouldContain("output.update-build-number"); + Should.Throw(() => ConfigurationDocumentMapper.Flatten(wrongBranch)) + .Message.ShouldContain("calculation.branches..increment"); + } + + private static HashSet GetInterfacePropertyNames(Type interfaceType) => + interfaceType.GetInterfaces() + .Append(interfaceType) + .SelectMany(type => type.GetProperties()) + .Select(property => property.Name) + .ToHashSet(StringComparer.Ordinal); +} diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.cs b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.cs index 7529754351..3f0a1b4a7f 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.cs @@ -7,15 +7,19 @@ namespace GitVersion.Configuration.Tests; [TestFixture] +[NonParallelizable] public class ConfigurationProviderTests : TestBase { private string repoPath = null!; private ConfigurationProvider configurationProvider = null!; private IFileSystem fileSystem = null!; + private string? originalConfigurationVersion; [SetUp] public void Setup() { + this.originalConfigurationVersion = System.Environment.GetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName); + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v6"); this.repoPath = FileSystemHelper.Path.Combine(FileSystemHelper.Path.GetTempPath(), "MyGitRepo"); var options = Options.Create(new GitVersionOptions { WorkingDirectory = this.repoPath }); var sp = ConfigureServices(services => services.AddSingleton(options)); @@ -25,6 +29,116 @@ public void Setup() ShouldlyConfiguration.ShouldMatchApprovedDefaults.LocateTestMethodUsingAttribute(); } + [TearDown] + public void TearDown() => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, this.originalConfigurationVersion); + + [Test] + public void ProvidesNestedV7ConfigurationAndMergesOutputOnlyWorkflowBranch() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + const string text = """ + calculation: + workflow: GitFlow/v1 + tag-prefix: custom- + output: + update-build-number: false + branches: + develop: + custom-version-format: '{SemVer}' + pre-release-weight: 42 + """; + using var _ = this.fileSystem.SetupConfigFile(path: this.repoPath, text: text); + + var configuration = this.configurationProvider.ProvideForDirectory(this.repoPath); + + configuration.Calculation.TagPrefixPattern.ShouldBe("custom-"); + configuration.Output.UpdateBuildNumber.ShouldBeFalse(); + configuration.Calculation.Branches.ShouldContainKey("develop"); + configuration.Output.Branches["develop"].CustomVersionFormat.ShouldBe("{SemVer}"); + configuration.Output.Branches["develop"].PreReleaseWeight.ShouldBe(42); + } + + [Test] + public void ResolvesEquivalentV6AndV7Configuration() + { + const string v6 = """ + tag-prefix: custom- + update-build-number: false + branches: + main: + increment: Major + pre-release-weight: 42 + """; + const string v7 = """ + calculation: + tag-prefix: custom- + branches: + main: + increment: Major + output: + update-build-number: false + branches: + main: + pre-release-weight: 42 + """; + + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v6"); + IGitVersionConfiguration legacy; + using (this.fileSystem.SetupConfigFile(path: this.repoPath, text: v6)) + { + legacy = this.configurationProvider.ProvideForDirectory(this.repoPath); + } + + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + using (this.fileSystem.SetupConfigFile(path: this.repoPath, text: v7)) + { + var nested = this.configurationProvider.ProvideForDirectory(this.repoPath); + + ConfigurationSerializer.SerializeLegacy(nested).ShouldBe(ConfigurationSerializer.SerializeLegacy(legacy)); + } + } + + [TestCase(null)] + [TestCase("GitFlow/v1")] + [TestCase("GitHubFlow/v1")] + [TestCase("TrunkBased/preview1")] + public void V7SerializationRoundTripsDefaultsAndBuiltInWorkflows(string? workflow) + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v6"); + using var configurationFile = workflow is null + ? null + : this.fileSystem.SetupConfigFile(path: this.repoPath, text: $"workflow: {workflow}"); + var legacy = this.configurationProvider.ProvideForDirectory(this.repoPath); + + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + var nestedYaml = new ConfigurationSerializer().Serialize(legacy); + var roundTrip = ConfigurationSerializer.ReadConfiguration(nestedYaml); + + nestedYaml.ShouldContain("calculation:"); + nestedYaml.ShouldContain("output:"); + roundTrip.ShouldNotBeNull(); + ConfigurationSerializer.SerializeLegacy(roundTrip) + .ShouldBe(ConfigurationSerializer.SerializeLegacy(legacy)); + } + + [Test] + public void RejectsMixedV7Configuration() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + const string text = """ + calculation: + tag-prefix: custom- + update-build-number: false + """; + using var _ = this.fileSystem.SetupConfigFile(path: this.repoPath, text: text); + + var exception = Should.Throw(() => + this.configurationProvider.ProvideForDirectory(this.repoPath)); + + exception.Message.ShouldContain("mixes the v6 flat configuration structure"); + } + [Test] public void OverwritesDefaultsWithProvidedConfig() { diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationSerializerTests.cs b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationSerializerTests.cs index c698aeff45..2cff57f136 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationSerializerTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationSerializerTests.cs @@ -1,10 +1,52 @@ namespace GitVersion.Configuration.Tests; [TestFixture] +[NonParallelizable] public class ConfigurationSerializerTests { private readonly ConfigurationSerializer serializer = new(); + [Test] + public void SerializesV7CalculationAndOutputSectionsAndRoundTrips() + { + using var scope = new EnvironmentVariableScope("v7"); + var configuration = new GitVersionConfiguration + { + TagPrefixPattern = "custom-", + UpdateBuildNumber = false, + Branches = new Dictionary + { + ["main"] = new() { Increment = IncrementStrategy.Major, PreReleaseWeight = 42 } + } + }; + + var yaml = this.serializer.Serialize(configuration); + var roundTrip = ConfigurationSerializer.ReadConfiguration(yaml); + + yaml.ShouldContain("calculation:"); + yaml.ShouldContain("output:"); + yaml.ShouldContain(" tag-prefix: custom-"); + yaml.ShouldContain(" main:"); + roundTrip.ShouldNotBeNull(); + roundTrip.TagPrefixPattern.ShouldBe("custom-"); + roundTrip.UpdateBuildNumber.ShouldBeFalse(); + roundTrip.Branches["main"].Increment.ShouldBe(IncrementStrategy.Major); + roundTrip.Branches["main"].PreReleaseWeight.ShouldBe(42); + } + + [Test] + public void SerializesFlatConfigurationWhenV6IsSelected() + { + using var scope = new EnvironmentVariableScope("v6"); + var configuration = new GitVersionConfiguration { TagPrefixPattern = "custom-" }; + + var yaml = this.serializer.Serialize(configuration); + + yaml.ShouldContain("tag-prefix: custom-"); + yaml.ShouldNotContain("calculation:"); + yaml.ShouldNotContain("output:"); + } + [Test] public void Serialize_OrdersConfigurationPropertiesAndPreservesBranchNames() { @@ -25,18 +67,51 @@ public void Serialize_OrdersConfigurationPropertiesAndPreservesBranchNames() .ToArray(); rootPropertyNames.ShouldBe(rootPropertyNames.Order(StringComparer.Ordinal)); - yaml.IndexOf(" z-last:", StringComparison.Ordinal) - .ShouldBeLessThan(yaml.IndexOf(" a-first:", StringComparison.Ordinal)); + var firstBranchIndex = Array.FindIndex(lines, line => line.TrimStart() == "z-last:"); + var secondBranchIndex = Array.FindIndex(lines, line => line.TrimStart() == "a-first:"); + firstBranchIndex.ShouldBeGreaterThanOrEqualTo(0); + firstBranchIndex.ShouldBeLessThan(secondBranchIndex); + var branchIndentation = GetIndentation(lines[firstBranchIndex]); var firstBranchProperties = lines - .SkipWhile(line => line != " z-last:") - .Skip(1) - .TakeWhile(line => line.StartsWith(" ", StringComparison.Ordinal)) + .Skip(firstBranchIndex + 1) + .TakeWhile(line => GetIndentation(line) > branchIndentation) + .Where(line => GetIndentation(line) == branchIndentation + 2) .Select(GetPropertyName) .ToArray(); + firstBranchProperties.ShouldNotBeEmpty(); firstBranchProperties.ShouldBe(firstBranchProperties.Order(StringComparer.Ordinal)); } + [Test] + public void ReusesConfigurationFacadesAndBranchProjections() + { + var configuration = new GitVersionConfiguration + { + Branches = new Dictionary { ["main"] = new() } + }; + var effectiveConfiguration = (IGitVersionConfiguration)configuration; + + configuration.Calculation.ShouldBeSameAs(configuration.Calculation); + configuration.Output.ShouldBeSameAs(configuration.Output); + configuration.Calculation.Branches.ShouldBeSameAs(configuration.Calculation.Branches); + configuration.Output.Branches.ShouldBeSameAs(configuration.Output.Branches); + effectiveConfiguration.Branches.ShouldBeSameAs(effectiveConfiguration.Branches); + } + private static string GetPropertyName(string line) => line.TrimStart().Split(':', 2)[0]; + + private static int GetIndentation(string line) => line.Length - line.TrimStart().Length; + + private sealed class EnvironmentVariableScope : IDisposable + { + private readonly string? original = System.Environment.GetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName); + + public EnvironmentVariableScope(string? value) => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, value); + + public void Dispose() => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, this.original); + } } diff --git a/src/GitVersion.Configuration.Tests/Configuration/IgnoreConfigurationTests.cs b/src/GitVersion.Configuration.Tests/Configuration/IgnoreConfigurationTests.cs index ed8039932d..dd2620b35b 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/IgnoreConfigurationTests.cs +++ b/src/GitVersion.Configuration.Tests/Configuration/IgnoreConfigurationTests.cs @@ -6,9 +6,22 @@ namespace GitVersion.Configuration.Tests; [TestFixture] +[NonParallelizable] public class IgnoreConfigurationTests : TestBase { private readonly ConfigurationSerializer serializer = new(); + private string? originalConfigurationVersion; + + [SetUp] + public void Setup() + { + this.originalConfigurationVersion = System.Environment.GetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName); + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v6"); + } + + [TearDown] + public void TearDown() => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, this.originalConfigurationVersion); [Test] public void CanDeserialize() @@ -20,7 +33,7 @@ public void CanDeserialize() sha: [b6c0c9fda88830ebcd563e500a5a7da5a1658e98] """; - var configuration = this.serializer.ReadConfiguration(yaml); + var configuration = ConfigurationSerializer.ReadConfiguration(yaml); configuration.ShouldNotBeNull(); configuration.Ignore.ShouldNotBeNull(); @@ -40,7 +53,7 @@ public void ShouldSupportsOtherSequenceFormat() - 6c19c7c219ecf8dbc468042baefa73a1b213e8b1 """; - var configuration = this.serializer.ReadConfiguration(yaml); + var configuration = ConfigurationSerializer.ReadConfiguration(yaml); configuration.ShouldNotBeNull(); configuration.Ignore.ShouldNotBeNull(); @@ -53,7 +66,7 @@ public void CanDeserializeCompactBranchesSequence() { const string yaml = "ignore:\n branches: ['^legacy/', '^release/old$']"; - var configuration = this.serializer.ReadConfiguration(yaml); + var configuration = ConfigurationSerializer.ReadConfiguration(yaml); configuration.ShouldNotBeNull(); configuration.Ignore.Branches.ShouldBe(["^legacy/", "^release/old$"]); @@ -64,7 +77,7 @@ public void CanDeserializeCompactTagsSequence() { const string yaml = "ignore:\n tags: ['^preview-', '^v0\\.']"; - var configuration = this.serializer.ReadConfiguration(yaml); + var configuration = ConfigurationSerializer.ReadConfiguration(yaml); configuration.ShouldNotBeNull(); configuration.Ignore.Tags.ShouldBe(["^preview-", "^v0\\."]); @@ -83,7 +96,7 @@ public void CanDeserializeMultilineBranchesAndTagsSequences() - ^preview- """; - var configuration = this.serializer.ReadConfiguration(yaml); + var configuration = ConfigurationSerializer.ReadConfiguration(yaml); configuration.ShouldNotBeNull(); configuration.Ignore.Branches.ShouldBe(["^legacy/", "^release/old$"]); @@ -101,7 +114,7 @@ public void CanDeserializeMultilineTagsSequence() - ^v0\. """; - var configuration = this.serializer.ReadConfiguration(yaml); + var configuration = ConfigurationSerializer.ReadConfiguration(yaml); configuration.ShouldNotBeNull(); configuration.Ignore.Tags.ShouldBe(["^preview-", "^v0\\."]); @@ -112,7 +125,7 @@ public void WhenNotInConfigShouldHaveDefaults() { const string yaml = "next-version: 1.0"; - var configuration = this.serializer.ReadConfiguration(yaml); + var configuration = ConfigurationSerializer.ReadConfiguration(yaml); configuration.ShouldNotBeNull(); configuration.Ignore.ShouldNotBeNull(); @@ -212,7 +225,7 @@ public void WhenBadDateFormatShouldFail() commits-before: bad format date """; - Should.Throw(() => this.serializer.ReadConfiguration(yaml)); + Should.Throw(() => ConfigurationSerializer.ReadConfiguration(yaml)); } [Test] @@ -220,7 +233,7 @@ public void ShouldSupportScalarVersionStrategiesOverrideFormat() { const string yaml = "strategies: ConfiguredNextVersion, TaggedCommit"; - var configuration = this.serializer.ReadConfiguration(yaml); + var configuration = ConfigurationSerializer.ReadConfiguration(yaml); configuration.ShouldNotBeNull(); configuration.VersionStrategy.ShouldBe(VersionStrategies.ConfiguredNextVersion | VersionStrategies.TaggedCommit); diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml index cb02dab624..eb3ec5ba8a 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml @@ -1,170 +1,180 @@ -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - develop: - increment: Minor - is-main-branch: false - is-release-branch: false - is-source-branch-for: [] - label: alpha - mode: ContinuousDelivery - pre-release-weight: 0 - prevent-increment: - when-current-commit-tagged: false - regex: ^dev(elop)?(ment)?$ - source-branches: - - main - track-merge-message: true - track-merge-target: true - tracks-release-branches: true - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - release: - increment: Minor - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^releases?[\\/-](?.+)" - source-branches: - - main - - support - track-merge-target: false - tracks-release-branches: false - feature: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - develop - - main - - release - - support - - hotfix - track-merge-message: true - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - develop - - main - - release - - feature - - support - - hotfix - track-merge-message: true - hotfix: - increment: Inherit - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^hotfix(es)?[\\/-](?.+)" - source-branches: - - main - - support - support: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^support[\\/-](?.+)" - source-branches: - - main - track-merge-target: false - tracks-release-branches: false - unknown: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - prevent-increment: - when-current-commit-tagged: true - regex: "(?.+)" - source-branches: - - main - - develop - - release - - feature - - pull-request - - hotfix - - support -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - Fallback - - ConfiguredNextVersion - - MergeMessage - - TaggedCommit - - TrackReleaseBranches - - VersionInBranchName -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + develop: + increment: Minor + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: alpha + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: ^dev(elop)?(ment)?$ + source-branches: + - main + track-merge-message: true + track-merge-target: true + tracks-release-branches: true + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + release: + increment: Minor + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^releases?[\\/-](?.+)" + source-branches: + - main + - support + track-merge-target: false + tracks-release-branches: false + feature: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - develop + - main + - release + - support + - hotfix + track-merge-message: true + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - develop + - main + - release + - feature + - support + - hotfix + track-merge-message: true + hotfix: + increment: Inherit + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^hotfix(es)?[\\/-](?.+)" + source-branches: + - main + - support + support: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^support[\\/-](?.+)" + source-branches: + - main + track-merge-target: false + tracks-release-branches: false + unknown: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: true + regex: "(?.+)" + source-branches: + - main + - develop + - release + - feature + - pull-request + - hotfix + - support + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - Fallback + - ConfiguredNextVersion + - MergeMessage + - TaggedCommit + - TrackReleaseBranches + - VersionInBranchName + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + develop: + pre-release-weight: 0 + main: + pre-release-weight: 55000 + release: + pre-release-weight: 30000 + feature: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + hotfix: + pre-release-weight: 30000 + support: + pre-release-weight: 55000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml index 70e3a4d8bf..0e1d664674 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml @@ -1,119 +1,126 @@ -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - release: - increment: Patch - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: beta - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-branch-merged: false - when-current-commit-tagged: false - regex: "^releases?[\\/-](?.+)" - source-branches: - - main - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - feature: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - main - - release - track-merge-message: true - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - main - - release - - feature - track-merge-message: true - unknown: - increment: Inherit - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ManualDeployment - prevent-increment: - when-current-commit-tagged: false - regex: "(?.+)" - source-branches: - - main - - release - - feature - - pull-request - track-merge-message: false -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - Fallback - - ConfiguredNextVersion - - MergeMessage - - TaggedCommit - - TrackReleaseBranches - - VersionInBranchName -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + release: + increment: Patch + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: beta + mode: ManualDeployment + prevent-increment: + of-merged-branch: true + when-branch-merged: false + when-current-commit-tagged: false + regex: "^releases?[\\/-](?.+)" + source-branches: + - main + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + feature: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - main + - release + track-merge-message: true + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - main + - release + - feature + track-merge-message: true + unknown: + increment: Inherit + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ManualDeployment + prevent-increment: + when-current-commit-tagged: false + regex: "(?.+)" + source-branches: + - main + - release + - feature + - pull-request + track-merge-message: false + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - Fallback + - ConfiguredNextVersion + - MergeMessage + - TaggedCommit + - TrackReleaseBranches + - VersionInBranchName + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + main: + pre-release-weight: 55000 + release: + pre-release-weight: 30000 + feature: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml index 2998ca1498..85b8f4d372 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml @@ -1,104 +1,112 @@ -assembly-file-versioning-scheme: MajorMinorPatch -assembly-versioning-scheme: MajorMinorPatch -branches: - main: - increment: Patch - is-main-branch: true - is-release-branch: false - is-source-branch-for: [] - label: '' - mode: ContinuousDeployment - pre-release-weight: 55000 - prevent-increment: - of-merged-branch: true - regex: "^master$|^main$" - source-branches: [] - track-merge-message: true - track-merge-target: false - tracks-release-branches: false - feature: - increment: Minor - is-main-branch: false - is-source-branch-for: [] - label: "{BranchName}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^features?[\\/-](?.+)" - source-branches: - - main - track-merge-message: true - hotfix: - increment: Patch - is-main-branch: false - is-release-branch: true - is-source-branch-for: [] - label: "{BranchName}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "^hotfix(es)?[\\/-](?.+)" - source-branches: - - main - pull-request: - increment: Inherit - is-source-branch-for: [] - label: "PullRequest{Number}" - mode: ContinuousDelivery - pre-release-weight: 30000 - prevent-increment: - of-merged-branch: true - when-current-commit-tagged: false - regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" - source-branches: - - main - - feature - - hotfix - track-merge-message: true - unknown: - increment: Patch - is-source-branch-for: [] - pre-release-weight: 30000 - prevent-increment: - when-current-commit-tagged: false - regex: "(?.+)" - source-branches: - - main -commit-date-format: yyyy-MM-dd -commit-message-incrementing: Enabled -ignore: - branches: [] - paths: [] - sha: [] - tags: [] -increment: Inherit -is-main-branch: false -is-release-branch: false -is-source-branch-for: [] -label: "{BranchName}" -major-version-bump-message: "[+=]semver:\\s?(breaking|major)" -merge-message-formats: {} -minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" -mode: ContinuousDelivery -no-bump-message: "[+=]semver:\\s?(none|skip)" -patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" -prevent-increment: - of-merged-branch: false - when-branch-merged: false - when-current-commit-tagged: true -regex: '' -semantic-version-format: Strict -source-branches: [] -strategies: - - ConfiguredNextVersion - - Mainline -tag-pre-release-weight: 60000 -tag-prefix: "[vV]?" -track-merge-message: true -track-merge-target: false -tracks-release-branches: false -update-build-number: true -version-bump-reset-message: "=semver:" -version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +calculation: + branches: + main: + increment: Patch + is-main-branch: true + is-release-branch: false + is-source-branch-for: [] + label: '' + mode: ContinuousDeployment + prevent-increment: + of-merged-branch: true + regex: "^master$|^main$" + source-branches: [] + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + feature: + increment: Minor + is-main-branch: false + is-source-branch-for: [] + label: "{BranchName}" + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: "^features?[\\/-](?.+)" + source-branches: + - main + track-merge-message: true + hotfix: + increment: Patch + is-main-branch: false + is-release-branch: true + is-source-branch-for: [] + label: "{BranchName}" + mode: ContinuousDelivery + prevent-increment: + when-current-commit-tagged: false + regex: "^hotfix(es)?[\\/-](?.+)" + source-branches: + - main + pull-request: + increment: Inherit + is-source-branch-for: [] + label: "PullRequest{Number}" + mode: ContinuousDelivery + prevent-increment: + of-merged-branch: true + when-current-commit-tagged: false + regex: "^(pull-requests|pull|pr)[\\/-](?\\d*)" + source-branches: + - main + - feature + - hotfix + track-merge-message: true + unknown: + increment: Patch + is-source-branch-for: [] + prevent-increment: + when-current-commit-tagged: false + regex: "(?.+)" + source-branches: + - main + commit-message-incrementing: Enabled + ignore: + branches: [] + paths: [] + sha: [] + tags: [] + increment: Inherit + is-main-branch: false + is-release-branch: false + is-source-branch-for: [] + label: "{BranchName}" + major-version-bump-message: "[+=]semver:\\s?(breaking|major)" + merge-message-formats: {} + minor-version-bump-message: "[+=]semver:\\s?(feature|minor)" + mode: ContinuousDelivery + no-bump-message: "[+=]semver:\\s?(none|skip)" + patch-version-bump-message: "[+=]semver:\\s?(fix|patch)" + prevent-increment: + of-merged-branch: false + when-branch-merged: false + when-current-commit-tagged: true + regex: '' + semantic-version-format: Strict + source-branches: [] + strategies: + - ConfiguredNextVersion + - Mainline + tag-prefix: "[vV]?" + track-merge-message: true + track-merge-target: false + tracks-release-branches: false + version-bump-reset-message: "=semver:" + version-in-branch-pattern: "(?[vV]?\\d+(\\.\\d+)?(\\.\\d+)?).*" +output: + assembly-file-versioning-scheme: MajorMinorPatch + assembly-versioning-scheme: MajorMinorPatch + branches: + main: + pre-release-weight: 55000 + feature: + pre-release-weight: 30000 + hotfix: + pre-release-weight: 30000 + pull-request: + pre-release-weight: 30000 + unknown: + pre-release-weight: 30000 + commit-date-format: yyyy-MM-dd + tag-pre-release-weight: 60000 + update-build-number: true diff --git a/src/GitVersion.Configuration/BranchConfiguration.cs b/src/GitVersion.Configuration/BranchConfiguration.cs index 4fd0a61bd5..c7e0a06c36 100644 --- a/src/GitVersion.Configuration/BranchConfiguration.cs +++ b/src/GitVersion.Configuration/BranchConfiguration.cs @@ -25,6 +25,9 @@ internal record BranchConfiguration : IBranchConfiguration [JsonIgnore] IPreventIncrementConfiguration IBranchConfiguration.PreventIncrement => PreventIncrement; + [JsonIgnore] + IPreventIncrementConfiguration ICalculationBranchConfiguration.PreventIncrement => PreventIncrement; + [JsonPropertyName("prevent-increment")] [JsonPropertyDescription("The prevent increment configuration section.")] public PreventIncrementConfiguration PreventIncrement { get; set; } = new(); @@ -56,6 +59,9 @@ internal record BranchConfiguration : IBranchConfiguration [JsonIgnore] IReadOnlyCollection IBranchConfiguration.SourceBranches => SourceBranches; + [JsonIgnore] + IReadOnlyCollection ICalculationBranchConfiguration.SourceBranches => SourceBranches; + [JsonPropertyName("is-source-branch-for")] [JsonPropertyDescription("The branches that this branch is a source branch.")] public HashSet IsSourceBranchFor { get; set; } = []; @@ -63,6 +69,9 @@ internal record BranchConfiguration : IBranchConfiguration [JsonIgnore] IReadOnlyCollection IBranchConfiguration.IsSourceBranchFor => IsSourceBranchFor; + [JsonIgnore] + IReadOnlyCollection ICalculationBranchConfiguration.IsSourceBranchFor => IsSourceBranchFor; + [JsonPropertyName("tracks-release-branches")] [JsonPropertyDescription("Indicates this branch configuration represents develop in GitFlow.")] public bool? TracksReleaseBranches { get; set; } diff --git a/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs b/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs index b3319f23d6..827cf30fa2 100644 --- a/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs +++ b/src/GitVersion.Configuration/Builders/ConfigurationBuilderBase.cs @@ -338,45 +338,47 @@ public TConfigurationBuilder WithPreReleaseWeight(int? value) public TConfigurationBuilder WithConfiguration(IGitVersionConfiguration value) { - WithAssemblyVersioningScheme(value.AssemblyVersioningScheme); - WithAssemblyFileVersioningScheme(value.AssemblyFileVersioningScheme); - WithAssemblyInformationalFormat(value.AssemblyInformationalFormat); - WithAssemblyVersioningFormat(value.AssemblyVersioningFormat); - WithAssemblyFileVersioningFormat(value.AssemblyFileVersioningFormat); - WithCustomVersionFormat(value.CustomVersionFormat); - WithTagPrefixPattern(value.TagPrefixPattern); - WithVersionInBranchPattern(value.VersionInBranchPattern); - WithNextVersion(value.NextVersion); - WithMajorVersionBumpMessage(value.MajorVersionBumpMessage); - WithMinorVersionBumpMessage(value.MinorVersionBumpMessage); - WithPatchVersionBumpMessage(value.PatchVersionBumpMessage); - WithNoBumpMessage(value.NoBumpMessage); - WithVersionBumpResetMessage(value.VersionBumpResetMessage); - WithTagPreReleaseWeight(value.TagPreReleaseWeight); - WithIgnoreConfiguration(value.Ignore); - WithCommitDateFormat(value.CommitDateFormat); - WithUpdateBuildNumber(value.UpdateBuildNumber); - WithSemanticVersionFormat(value.SemanticVersionFormat); - WithVersionStrategy(value.VersionStrategy); - WithMergeMessageFormats(value.MergeMessageFormats); + var calculation = value.Calculation; + var output = value.Output; + WithAssemblyVersioningScheme(output.AssemblyVersioningScheme); + WithAssemblyFileVersioningScheme(output.AssemblyFileVersioningScheme); + WithAssemblyInformationalFormat(output.AssemblyInformationalFormat); + WithAssemblyVersioningFormat(output.AssemblyVersioningFormat); + WithAssemblyFileVersioningFormat(output.AssemblyFileVersioningFormat); + WithCustomVersionFormat(output.CustomVersionFormat); + WithTagPrefixPattern(calculation.TagPrefixPattern); + WithVersionInBranchPattern(calculation.VersionInBranchPattern); + WithNextVersion(calculation.NextVersion); + WithMajorVersionBumpMessage(calculation.MajorVersionBumpMessage); + WithMinorVersionBumpMessage(calculation.MinorVersionBumpMessage); + WithPatchVersionBumpMessage(calculation.PatchVersionBumpMessage); + WithNoBumpMessage(calculation.NoBumpMessage); + WithVersionBumpResetMessage(calculation.VersionBumpResetMessage); + WithTagPreReleaseWeight(output.TagPreReleaseWeight); + WithIgnoreConfiguration(calculation.Ignore); + WithCommitDateFormat(output.CommitDateFormat); + WithUpdateBuildNumber(output.UpdateBuildNumber); + WithSemanticVersionFormat(calculation.SemanticVersionFormat); + WithVersionStrategy(calculation.VersionStrategy); + WithMergeMessageFormats(calculation.MergeMessageFormats); foreach (var (name, branchConfiguration) in value.Branches) { WithBranch(name).WithConfiguration(branchConfiguration); } - WithDeploymentMode(value.DeploymentMode); - WithLabel(value.Label); - WithIncrement(value.Increment); - WithPreventIncrementOfMergedBranch(value.PreventIncrement.OfMergedBranch); - WithPreventIncrementWhenBranchMerged(value.PreventIncrement.WhenBranchMerged); - WithPreventIncrementWhenCurrentCommitTagged(value.PreventIncrement.WhenCurrentCommitTagged); - WithTrackMergeTarget(value.TrackMergeTarget); - WithTrackMergeMessage(value.TrackMergeMessage); - WithCommitMessageIncrementing(value.CommitMessageIncrementing); - WithRegularExpression(value.RegularExpression); - WithTracksReleaseBranches(value.TracksReleaseBranches); - WithIsReleaseBranch(value.IsReleaseBranch); - WithIsMainBranch(value.IsMainBranch); - WithPreReleaseWeight(value.PreReleaseWeight); + WithDeploymentMode(calculation.DeploymentMode); + WithLabel(calculation.Label); + WithIncrement(calculation.Increment); + WithPreventIncrementOfMergedBranch(calculation.PreventIncrement.OfMergedBranch); + WithPreventIncrementWhenBranchMerged(calculation.PreventIncrement.WhenBranchMerged); + WithPreventIncrementWhenCurrentCommitTagged(calculation.PreventIncrement.WhenCurrentCommitTagged); + WithTrackMergeTarget(calculation.TrackMergeTarget); + WithTrackMergeMessage(calculation.TrackMergeMessage); + WithCommitMessageIncrementing(calculation.CommitMessageIncrementing); + WithRegularExpression(calculation.RegularExpression); + WithTracksReleaseBranches(calculation.TracksReleaseBranches); + WithIsReleaseBranch(calculation.IsReleaseBranch); + WithIsMainBranch(calculation.IsMainBranch); + WithPreReleaseWeight(output.PreReleaseWeight); return (TConfigurationBuilder)this; } diff --git a/src/GitVersion.Configuration/ConfigurationDocumentMapper.cs b/src/GitVersion.Configuration/ConfigurationDocumentMapper.cs new file mode 100644 index 0000000000..43a7d1de2c --- /dev/null +++ b/src/GitVersion.Configuration/ConfigurationDocumentMapper.cs @@ -0,0 +1,318 @@ +namespace GitVersion.Configuration; + +internal enum ConfigurationDocumentKind +{ + Empty, + V6, + V7, + Mixed +} + +internal static class ConfigurationDocumentMapper +{ + public const string CalculationSectionName = "calculation"; + public const string OutputSectionName = "output"; + public const string BranchesPropertyName = "branches"; + + private static readonly HashSet OutputPropertyNames = + [ + "assembly-file-versioning-format", + "assembly-file-versioning-scheme", + "assembly-informational-format", + "assembly-versioning-format", + "assembly-versioning-scheme", + "commit-date-format", + "custom-version-format", + "pre-release-weight", + "tag-pre-release-weight", + "update-build-number" + ]; + + private static readonly HashSet OutputBranchPropertyNames = + [ + "custom-version-format", + "pre-release-weight" + ]; + + private static readonly HashSet KnownPropertyNames = typeof(GitVersionConfiguration) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(property => property.GetCustomAttribute()?.Name) + .OfType() + .ToHashSet(StringComparer.Ordinal); + + private static readonly HashSet KnownBranchPropertyNames = typeof(BranchConfiguration) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(property => property.GetCustomAttribute()?.Name) + .OfType() + .ToHashSet(StringComparer.Ordinal); + + public static ConfigurationDocumentKind Detect(IReadOnlyDictionary document) + { + if (document.Count == 0) + { + return ConfigurationDocumentKind.Empty; + } + + var hasNested = document.ContainsKey(CalculationSectionName) || document.ContainsKey(OutputSectionName); + var hasFlat = document.Keys.OfType().Any(key => + !key.Equals(CalculationSectionName, StringComparison.Ordinal) + && !key.Equals(OutputSectionName, StringComparison.Ordinal)); + + return (hasNested, hasFlat) switch + { + (true, true) => ConfigurationDocumentKind.Mixed, + (true, false) => ConfigurationDocumentKind.V7, + _ => ConfigurationDocumentKind.V6 + }; + } + + public static Dictionary Normalize( + IReadOnlyDictionary document, + ConfigurationVersion selectedVersion, + string source) + { + var kind = Detect(document); + if (kind == ConfigurationDocumentKind.Mixed) + { + throw new ConfigurationException( + $"The {source} mixes the v6 flat configuration structure with the v7 'calculation'/'output' structure. " + + "Use only one structure. Run 'gitversion config migrate' to convert a v6 configuration."); + } + + if (kind == ConfigurationDocumentKind.V6 && selectedVersion == ConfigurationVersion.V7) + { + throw new ConfigurationException( + $"The {source} uses the legacy v6 configuration structure, but {ConfigurationVersionSelector.EnvironmentVariableName}=v7 is selected. " + + $"Run 'gitversion config migrate' or set {ConfigurationVersionSelector.EnvironmentVariableName}=v6 temporarily."); + } + + if (kind == ConfigurationDocumentKind.V7 && selectedVersion == ConfigurationVersion.V6) + { + throw new ConfigurationException( + $"The {source} uses the v7 configuration structure, but {ConfigurationVersionSelector.EnvironmentVariableName}=v6 is selected. " + + $"Remove the override or set {ConfigurationVersionSelector.EnvironmentVariableName}=v7."); + } + + return kind == ConfigurationDocumentKind.V7 ? Flatten(document) : CloneDictionary(document); + } + + public static Dictionary NormalizeInternal(IReadOnlyDictionary document, string source) + { + var kind = Detect(document); + return kind switch + { + ConfigurationDocumentKind.Empty => [], + ConfigurationDocumentKind.V6 => CloneDictionary(document), + ConfigurationDocumentKind.V7 => Flatten(document), + _ => throw new ConfigurationException( + $"The {source} mixes the v6 flat configuration structure with the v7 'calculation'/'output' structure.") + }; + } + + public static Dictionary Flatten(IReadOnlyDictionary document) + { + Dictionary result = []; + Dictionary branches = []; + + FlattenSection(document, CalculationSectionName, result, branches); + FlattenSection(document, OutputSectionName, result, branches); + + if (branches.Count != 0) + { + result[BranchesPropertyName] = branches; + } + + return result; + } + + public static Dictionary Nest(IReadOnlyDictionary document) + { + Dictionary calculation = []; + Dictionary output = []; + + foreach (var (key, value) in document) + { + if (key.Equals(BranchesPropertyName, StringComparison.Ordinal)) + { + SplitBranches(value, calculation, output); + } + else if (OutputPropertyNames.Contains(key)) + { + output[key] = value; + } + else + { + calculation[key] = value; + } + } + + return new Dictionary + { + [CalculationSectionName] = calculation, + [OutputSectionName] = output + }; + } + + public static bool IsOutputProperty(string propertyName) => OutputPropertyNames.Contains(propertyName); + + public static bool IsOutputBranchProperty(string propertyName) => OutputBranchPropertyNames.Contains(propertyName); + + private static void FlattenSection( + IReadOnlyDictionary document, + string sectionName, + IDictionary result, + IDictionary branches) + { + if (!document.TryGetValue(sectionName, out var sectionValue) || sectionValue is null) + { + return; + } + + if (sectionValue is not IReadOnlyDictionary section) + { + throw new ConfigurationException($"Configuration section '{sectionName}' must be a mapping."); + } + + foreach (var (key, value) in section) + { + if (key is string propertyName && propertyName.Equals(BranchesPropertyName, StringComparison.Ordinal)) + { + MergeBranches(branches, value, sectionName); + continue; + } + + if (key is string configuredPropertyName) + { + ValidatePropertyOwnership(sectionName, configuredPropertyName, branchProperty: false); + } + + if (!result.TryAdd(key, CloneValue(value))) + { + throw new ConfigurationException( + $"Configuration property '{key}' is defined in both '{CalculationSectionName}' and '{OutputSectionName}'."); + } + } + } + + private static void MergeBranches(IDictionary target, object? value, string sectionName) + { + if (value is not IReadOnlyDictionary source) + { + throw new ConfigurationException($"Configuration property '{sectionName}.{BranchesPropertyName}' must be a mapping."); + } + + foreach (var (branchName, branchValue) in source) + { + if (branchValue is not IReadOnlyDictionary branch) + { + throw new ConfigurationException( + $"Configuration branch '{sectionName}.{BranchesPropertyName}.{branchName}' must be a mapping."); + } + + foreach (var propertyName in branch.Keys.OfType()) + { + ValidatePropertyOwnership(sectionName, propertyName, branchProperty: true); + } + + if (!target.TryGetValue(branchName, out var existing)) + { + target[branchName] = CloneDictionary(branch); + continue; + } + + if (existing is not IDictionary targetBranch) + { + throw new ConfigurationException($"Configuration branch '{branchName}' must be a mapping."); + } + + foreach (var (propertyName, propertyValue) in branch) + { + if (!targetBranch.TryAdd(propertyName, CloneValue(propertyValue))) + { + throw new ConfigurationException( + $"Configuration branch property '{branchName}.{propertyName}' is defined in both " + + $"'{CalculationSectionName}' and '{OutputSectionName}'."); + } + } + } + } + + private static void SplitBranches( + object? value, + IDictionary calculation, + IDictionary output) + { + if (value is not IReadOnlyDictionary branches) + { + return; + } + + Dictionary calculationBranches = []; + Dictionary outputBranches = []; + + foreach (var (branchName, branchValue) in branches) + { + if (branchValue is not IReadOnlyDictionary branch) + { + continue; + } + + Dictionary calculationBranch = []; + Dictionary outputBranch = []; + foreach (var (propertyName, propertyValue) in branch) + { + (OutputBranchPropertyNames.Contains(propertyName) ? outputBranch : calculationBranch)[propertyName] = propertyValue; + } + + if (calculationBranch.Count != 0) + { + calculationBranches[branchName] = calculationBranch; + } + + if (outputBranch.Count != 0) + { + outputBranches[branchName] = outputBranch; + } + } + + if (calculationBranches.Count != 0) + { + calculation[BranchesPropertyName] = calculationBranches; + } + + if (outputBranches.Count != 0) + { + output[BranchesPropertyName] = outputBranches; + } + } + + private static Dictionary CloneDictionary(IReadOnlyDictionary dictionary) + => dictionary.ToDictionary(item => item.Key, item => CloneValue(item.Value)); + + private static void ValidatePropertyOwnership(string sectionName, string propertyName, bool branchProperty) + { + var knownProperties = branchProperty ? KnownBranchPropertyNames : KnownPropertyNames; + if (!knownProperties.Contains(propertyName)) + { + return; + } + + var belongsToOutput = branchProperty + ? OutputBranchPropertyNames.Contains(propertyName) + : OutputPropertyNames.Contains(propertyName); + var expectedSection = belongsToOutput ? OutputSectionName : CalculationSectionName; + if (!sectionName.Equals(expectedSection, StringComparison.Ordinal)) + { + var branchPath = branchProperty ? $"{BranchesPropertyName}.." : string.Empty; + throw new ConfigurationException( + $"Configuration property '{sectionName}.{branchPath}{propertyName}' belongs under " + + $"'{expectedSection}.{branchPath}{propertyName}'."); + } + } + + private static object? CloneValue(object? value) => value switch + { + IReadOnlyDictionary dictionary => CloneDictionary(dictionary), + _ => value + }; +} diff --git a/src/GitVersion.Configuration/ConfigurationHelper.cs b/src/GitVersion.Configuration/ConfigurationHelper.cs index 1233a8861b..a416db1905 100644 --- a/src/GitVersion.Configuration/ConfigurationHelper.cs +++ b/src/GitVersion.Configuration/ConfigurationHelper.cs @@ -6,8 +6,8 @@ internal class ConfigurationHelper { private static ConfigurationSerializer Serializer => new(); private string Yaml => this.yaml ??= this.dictionary == null - ? Serializer.Serialize(this.configuration!) - : Serializer.Serialize(this.dictionary); + ? ConfigurationSerializer.SerializeLegacy(this.configuration!) + : ConfigurationSerializer.SerializeLegacy(this.dictionary); private string? yaml; internal IReadOnlyDictionary Dictionary @@ -19,14 +19,14 @@ internal class ConfigurationHelper return this.dictionary; } - this.yaml ??= Serializer.Serialize(this.configuration!); + this.yaml ??= ConfigurationSerializer.SerializeLegacy(this.configuration!); this.dictionary = Serializer.Deserialize>(this.yaml); return this.dictionary; } } private IReadOnlyDictionary? dictionary; - public IGitVersionConfiguration Configuration => this.configuration ??= Serializer.Deserialize(Yaml); + public IGitVersionConfiguration Configuration => this.configuration ??= ConfigurationSerializer.DeserializeLegacyConfiguration(Yaml)!; private IGitVersionConfiguration? configuration; internal ConfigurationHelper(string yaml) => this.yaml = yaml.NotNull(); diff --git a/src/GitVersion.Configuration/ConfigurationProvider.cs b/src/GitVersion.Configuration/ConfigurationProvider.cs index d8258a4dd5..aa45b47a1f 100644 --- a/src/GitVersion.Configuration/ConfigurationProvider.cs +++ b/src/GitVersion.Configuration/ConfigurationProvider.cs @@ -43,16 +43,27 @@ internal IGitVersionConfiguration ProvideForDirectory(string? workingDirectory, private IGitVersionConfiguration ProvideConfiguration(string? configFile, IReadOnlyDictionary? overrideConfiguration = null) { - var overrideConfigurationFromFile = ReadOverrideConfiguration(configFile); - - var workflow = GetWorkflow(overrideConfiguration, overrideConfigurationFromFile); + var configurationVersion = ConfigurationVersionSelector.Resolve(); + this.logger.LogInformation("Configuration version: {ConfigurationVersion}", ConfigurationVersionSelector.ResolveName()); + var configurationFromFile = ReadOverrideConfiguration(configFile); + var overrideConfigurationFromFile = configurationFromFile is null + ? null + : ConfigurationDocumentMapper.Normalize(configurationFromFile, configurationVersion, "configuration file"); + var normalizedOverrideConfiguration = overrideConfiguration is null + ? null + : ConfigurationDocumentMapper.NormalizeInternal(overrideConfiguration, "runtime override configuration"); + + var workflow = GetWorkflow(normalizedOverrideConfiguration, overrideConfigurationFromFile); IConfigurationBuilder configurationBuilder = (workflow is null) ? GitFlowConfigurationBuilder.New : ConfigurationBuilder.New; - var overrideConfigurationFromWorkflow = WorkflowManager.GetOverrideConfiguration(workflow); - foreach (var item in new[] { overrideConfigurationFromWorkflow, overrideConfigurationFromFile, overrideConfiguration } + var workflowConfiguration = WorkflowManager.GetOverrideConfiguration(workflow); + var overrideConfigurationFromWorkflow = workflowConfiguration is null + ? null + : ConfigurationDocumentMapper.NormalizeInternal(workflowConfiguration, "embedded workflow"); + foreach (var item in new[] { overrideConfigurationFromWorkflow, overrideConfigurationFromFile, normalizedOverrideConfiguration } .OfType>()) { configurationBuilder.AddOverride(item); diff --git a/src/GitVersion.Configuration/ConfigurationSerializer.cs b/src/GitVersion.Configuration/ConfigurationSerializer.cs index 9138e1fce7..d9159cf6ac 100644 --- a/src/GitVersion.Configuration/ConfigurationSerializer.cs +++ b/src/GitVersion.Configuration/ConfigurationSerializer.cs @@ -29,14 +29,7 @@ public T Deserialize(string input) if (typeof(T) == typeof(GitVersionConfiguration)) { - try - { - return (T)(object)YamlSerializer.Deserialize(input, GeneratedContext)!; - } - catch (Exception exception) when (exception is not YamlException) - { - throw new YamlException(exception.Message, exception); - } + return (T)(object)DeserializeConfiguration(input, ConfigurationVersionSelector.Resolve(), "configuration document")!; } return YamlSerializer.Deserialize(input, SerializerOptions)!; @@ -44,12 +37,42 @@ public T Deserialize(string input) public string Serialize(object graph) { - var yaml = YamlSerializer.Serialize(graph, SerializerOptions); + var yaml = SerializeLegacy(graph); var configuration = YamlSerializer.Deserialize>(yaml, SerializerOptions) ?? []; + if (graph is IGitVersionConfiguration && ConfigurationVersionSelector.Resolve() == ConfigurationVersion.V7) + { + configuration = ConfigurationDocumentMapper.Nest(configuration); + } + return YamlSerializer.Serialize(OrderProperties(configuration), SerializerOptions); } - public IGitVersionConfiguration? ReadConfiguration(string input) => Deserialize(input); + public static IGitVersionConfiguration? ReadConfiguration(string input) + => DeserializeConfiguration(input, ConfigurationVersionSelector.Resolve(), "configuration document"); + + internal static string SerializeLegacy(object graph) => YamlSerializer.Serialize(graph, SerializerOptions); + + internal static GitVersionConfiguration? DeserializeLegacyConfiguration(string input) + => DeserializeConfiguration(input, ConfigurationVersion.V6, "internal effective configuration"); + + private static GitVersionConfiguration? DeserializeConfiguration( + string input, + ConfigurationVersion version, + string source) + { + try + { + var graph = YamlSerializer.Deserialize>(input, SerializerOptions); + var objectGraph = ConvertToObjectDictionary(graph); + var normalized = ConfigurationDocumentMapper.Normalize(objectGraph, version, source); + var normalizedYaml = YamlSerializer.Serialize(normalized, SerializerOptions); + return YamlSerializer.Deserialize(normalizedYaml, GeneratedContext); + } + catch (Exception exception) when (exception is not YamlException and not ConfigurationException) + { + throw new YamlException(exception.Message, exception); + } + } private static Dictionary ConvertToObjectDictionary(IReadOnlyDictionary? source) { diff --git a/src/GitVersion.Configuration/GitVersionConfiguration.cs b/src/GitVersion.Configuration/GitVersionConfiguration.cs index 7e0b3fb6e2..f5027acc6b 100644 --- a/src/GitVersion.Configuration/GitVersionConfiguration.cs +++ b/src/GitVersion.Configuration/GitVersionConfiguration.cs @@ -7,6 +7,16 @@ namespace GitVersion.Configuration; internal sealed record GitVersionConfiguration : BranchConfiguration, IGitVersionConfiguration { + private ICalculationConfiguration? calculation; + private IOutputConfiguration? output; + private IReadOnlyDictionary? effectiveBranches; + + [JsonIgnore] + public ICalculationConfiguration Calculation => this.calculation ??= new CalculationConfiguration(this); + + [JsonIgnore] + public IOutputConfiguration Output => this.output ??= new OutputConfiguration(this); + [JsonPropertyName("workflow")] [JsonPropertyDescription("The base template of the configuration to use. Possible values are: 'GitFlow/v1' or 'GitHubFlow/v1'")] public string? Workflow { get; set; } @@ -125,7 +135,7 @@ public string? NextVersion [JsonIgnore] IReadOnlyDictionary IGitVersionConfiguration.Branches - => Branches.ToDictionary(element => element.Key, IBranchConfiguration (element) => element.Value); + => this.effectiveBranches ??= Branches.ToDictionary(element => element.Key, IBranchConfiguration (element) => element.Value); [JsonPropertyName("branches")] [JsonPropertyDescription("The header for all the individual branch configuration.")] @@ -148,4 +158,56 @@ IReadOnlyDictionary IGitVersionConfiguration.Branc Label = BranchNamePlaceholder, Increment = IncrementStrategy.Inherit }; + + private sealed class CalculationConfiguration(GitVersionConfiguration configuration) : ICalculationConfiguration + { + private IReadOnlyDictionary? branches; + + public string? Workflow => configuration.Workflow; + public string? TagPrefixPattern => configuration.TagPrefixPattern; + public string? VersionInBranchPattern => configuration.VersionInBranchPattern; + public string? NextVersion => configuration.NextVersion; + public string? MajorVersionBumpMessage => configuration.MajorVersionBumpMessage; + public string? MinorVersionBumpMessage => configuration.MinorVersionBumpMessage; + public string? PatchVersionBumpMessage => configuration.PatchVersionBumpMessage; + public string? NoBumpMessage => configuration.NoBumpMessage; + public string? VersionBumpResetMessage => configuration.VersionBumpResetMessage; + public IReadOnlyDictionary MergeMessageFormats => configuration.MergeMessageFormats; + public SemanticVersionFormat SemanticVersionFormat => configuration.SemanticVersionFormat; + public VersionStrategies VersionStrategy => ((IGitVersionConfiguration)configuration).VersionStrategy; + public IReadOnlyDictionary Branches + => this.branches ??= configuration.Branches.ToDictionary(element => element.Key, ICalculationBranchConfiguration (element) => element.Value); + public IIgnoreConfiguration Ignore => configuration.Ignore; + public DeploymentMode? DeploymentMode => configuration.DeploymentMode; + public string? Label => configuration.Label; + public IncrementStrategy Increment => configuration.Increment; + public IPreventIncrementConfiguration PreventIncrement => configuration.PreventIncrement; + public bool? TrackMergeTarget => configuration.TrackMergeTarget; + public bool? TrackMergeMessage => configuration.TrackMergeMessage; + public CommitMessageIncrementMode? CommitMessageIncrementing => configuration.CommitMessageIncrementing; + public string? RegularExpression => configuration.RegularExpression; + public IReadOnlyCollection SourceBranches => configuration.SourceBranches; + public IReadOnlyCollection IsSourceBranchFor => configuration.IsSourceBranchFor; + public bool? TracksReleaseBranches => configuration.TracksReleaseBranches; + public bool? IsReleaseBranch => configuration.IsReleaseBranch; + public bool? IsMainBranch => configuration.IsMainBranch; + } + + private sealed class OutputConfiguration(GitVersionConfiguration configuration) : IOutputConfiguration + { + private IReadOnlyDictionary? branches; + + public AssemblyVersioningScheme? AssemblyVersioningScheme => configuration.AssemblyVersioningScheme; + public AssemblyFileVersioningScheme? AssemblyFileVersioningScheme => configuration.AssemblyFileVersioningScheme; + public string? AssemblyInformationalFormat => configuration.AssemblyInformationalFormat; + public string? AssemblyVersioningFormat => configuration.AssemblyVersioningFormat; + public string? AssemblyFileVersioningFormat => configuration.AssemblyFileVersioningFormat; + public int? TagPreReleaseWeight => configuration.TagPreReleaseWeight; + public string? CommitDateFormat => configuration.CommitDateFormat; + public bool UpdateBuildNumber => configuration.UpdateBuildNumber; + public IReadOnlyDictionary Branches + => this.branches ??= configuration.Branches.ToDictionary(element => element.Key, IOutputBranchConfiguration (element) => element.Value); + public string? CustomVersionFormat => configuration.CustomVersionFormat; + public int? PreReleaseWeight => configuration.PreReleaseWeight; + } } diff --git a/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs b/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs index 057a66da0b..5b9f88be7d 100644 --- a/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs +++ b/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs @@ -18,6 +18,7 @@ public class GitVersionExecutorTests : TestBase private IFileSystem fileSystem = null!; private GitVersionCacheProvider gitVersionCacheProvider = null!; private IServiceProvider sp = null!; + private string? originalConfigurationVersion; private const string versionCacheFileContent = """ @@ -53,6 +54,17 @@ public class GitVersionExecutorTests : TestBase } """; + [SetUp] + public void SetupConfigurationVersion() + { + this.originalConfigurationVersion = System.Environment.GetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName); + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v6"); + } + + [TearDown] + public void RestoreConfigurationVersion() => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, this.originalConfigurationVersion); + [Test] public void ResolvedConfiguration_IsCreatedOncePerExecution() { diff --git a/src/GitVersion.Core/Configuration/IBranchConfiguration.cs b/src/GitVersion.Core/Configuration/IBranchConfiguration.cs index d327079733..2851ec0538 100644 --- a/src/GitVersion.Core/Configuration/IBranchConfiguration.cs +++ b/src/GitVersion.Core/Configuration/IBranchConfiguration.cs @@ -4,35 +4,35 @@ namespace GitVersion.Configuration; /// Represents the version-related configuration for a specific branch or branch pattern. -public interface IBranchConfiguration +public interface IBranchConfiguration : ICalculationBranchConfiguration, IOutputBranchConfiguration { /// Gets the deployment mode used to compute versions on this branch. - DeploymentMode? DeploymentMode { get; } + new DeploymentMode? DeploymentMode { get; } /// Gets the pre-release label applied to versions produced on this branch. - string? Label { get; } + new string? Label { get; } /// Gets the format string used to compute the custom version output on this branch. - string? CustomVersionFormat => null; + new string? CustomVersionFormat => null; /// Gets the version field that is incremented when creating a new release from this branch. - IncrementStrategy Increment { get; } + new IncrementStrategy Increment { get; } /// Gets the configuration that controls under what conditions automatic version increments are suppressed. - IPreventIncrementConfiguration PreventIncrement { get; } + new IPreventIncrementConfiguration PreventIncrement { get; } /// Gets a value indicating whether to track the merge target branch for version calculation. - bool? TrackMergeTarget { get; } + new bool? TrackMergeTarget { get; } /// Gets a value indicating whether merge commit messages are considered when determining version increments. - bool? TrackMergeMessage { get; } + new bool? TrackMergeMessage { get; } /// Gets the mode that controls how commit messages drive version increments. - CommitMessageIncrementMode? CommitMessageIncrementing { get; } + new CommitMessageIncrementMode? CommitMessageIncrementing { get; } /// Gets the regular expression that matches branch names eligible for this configuration. [StringSyntax(StringSyntaxAttribute.Regex)] - string? RegularExpression { get; } + new string? RegularExpression { get; } /// Returns whether the given branch name matches the for this configuration. bool IsMatch(string branchName) @@ -47,22 +47,22 @@ bool IsMatch(string branchName) } /// Gets the names of branches that this branch may be branched from. - IReadOnlyCollection SourceBranches { get; } + new IReadOnlyCollection SourceBranches { get; } /// Gets the names of branches for which this branch may act as a source. - IReadOnlyCollection IsSourceBranchFor { get; } + new IReadOnlyCollection IsSourceBranchFor { get; } /// Gets a value indicating whether this branch tracks release branches. - bool? TracksReleaseBranches { get; } + new bool? TracksReleaseBranches { get; } /// Gets a value indicating whether this branch is a release branch. - bool? IsReleaseBranch { get; } + new bool? IsReleaseBranch { get; } /// Gets a value indicating whether this branch is treated as a main/trunk branch. - bool? IsMainBranch { get; } + new bool? IsMainBranch { get; } /// Gets the numeric weight applied to the pre-release tag number to produce a weighted pre-release number. - int? PreReleaseWeight { get; } + new int? PreReleaseWeight { get; } /// Returns a new configuration that inherits unset values from . IBranchConfiguration Inherit(IBranchConfiguration configuration); diff --git a/src/GitVersion.Core/Configuration/ICalculationBranchConfiguration.cs b/src/GitVersion.Core/Configuration/ICalculationBranchConfiguration.cs new file mode 100644 index 0000000000..cc4f4aeb49 --- /dev/null +++ b/src/GitVersion.Core/Configuration/ICalculationBranchConfiguration.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using GitVersion.VersionCalculation; + +namespace GitVersion.Configuration; + +/// Represents branch settings that affect semantic-version calculation. +public interface ICalculationBranchConfiguration +{ + /// Gets the deployment mode used to compute versions on this branch. + DeploymentMode? DeploymentMode { get; } + + /// Gets the pre-release label applied to versions produced on this branch. + string? Label { get; } + + /// Gets the version field that is incremented when creating a new release from this branch. + IncrementStrategy Increment { get; } + + /// Gets the configuration that controls under what conditions automatic version increments are suppressed. + IPreventIncrementConfiguration PreventIncrement { get; } + + /// Gets a value indicating whether to track the merge target branch for version calculation. + bool? TrackMergeTarget { get; } + + /// Gets a value indicating whether merge commit messages are considered when determining version increments. + bool? TrackMergeMessage { get; } + + /// Gets the mode that controls how commit messages drive version increments. + CommitMessageIncrementMode? CommitMessageIncrementing { get; } + + /// Gets the regular expression that matches branch names eligible for this configuration. + [StringSyntax(StringSyntaxAttribute.Regex)] + string? RegularExpression { get; } + + /// Gets the names of branches that this branch may be branched from. + IReadOnlyCollection SourceBranches { get; } + + /// Gets the names of branches for which this branch may act as a source. + IReadOnlyCollection IsSourceBranchFor { get; } + + /// Gets a value indicating whether this branch tracks release branches. + bool? TracksReleaseBranches { get; } + + /// Gets a value indicating whether this branch is a release branch. + bool? IsReleaseBranch { get; } + + /// Gets a value indicating whether this branch is treated as a main/trunk branch. + bool? IsMainBranch { get; } +} diff --git a/src/GitVersion.Core/Configuration/ICalculationConfiguration.cs b/src/GitVersion.Core/Configuration/ICalculationConfiguration.cs new file mode 100644 index 0000000000..03ddcadf4b --- /dev/null +++ b/src/GitVersion.Core/Configuration/ICalculationConfiguration.cs @@ -0,0 +1,49 @@ +using GitVersion.VersionCalculation; + +namespace GitVersion.Configuration; + +/// Represents settings that participate in semantic-version calculation. +public interface ICalculationConfiguration : ICalculationBranchConfiguration +{ + /// + string? Workflow { get; } + + /// + string? TagPrefixPattern { get; } + + /// + string? VersionInBranchPattern { get; } + + /// + string? NextVersion { get; } + + /// + string? MajorVersionBumpMessage { get; } + + /// + string? MinorVersionBumpMessage { get; } + + /// + string? PatchVersionBumpMessage { get; } + + /// + string? NoBumpMessage { get; } + + /// + string? VersionBumpResetMessage { get; } + + /// + IReadOnlyDictionary MergeMessageFormats { get; } + + /// + SemanticVersionFormat SemanticVersionFormat { get; } + + /// + VersionStrategies VersionStrategy { get; } + + /// Gets calculation settings for each configured branch. + IReadOnlyDictionary Branches { get; } + + /// + IIgnoreConfiguration Ignore { get; } +} diff --git a/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs b/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs index 027c5004b8..86eb7f2a62 100644 --- a/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs +++ b/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs @@ -5,6 +5,12 @@ namespace GitVersion.Configuration; /// Represents the top-level GitVersion configuration, extending branch-level configuration with global settings. public interface IGitVersionConfiguration : IBranchConfiguration { + /// Gets the settings that participate in semantic-version calculation. + ICalculationConfiguration Calculation { get; } + + /// Gets the settings that affect rendered version output. + IOutputConfiguration Output { get; } + /// Gets the name of the workflow preset (e.g. GitFlow/v1 or GitHubFlow/v1) used as a base configuration. string? Workflow { get; } diff --git a/src/GitVersion.Core/Configuration/IOutputBranchConfiguration.cs b/src/GitVersion.Core/Configuration/IOutputBranchConfiguration.cs new file mode 100644 index 0000000000..e8c250e7c0 --- /dev/null +++ b/src/GitVersion.Core/Configuration/IOutputBranchConfiguration.cs @@ -0,0 +1,11 @@ +namespace GitVersion.Configuration; + +/// Represents branch settings that affect rendered version output. +public interface IOutputBranchConfiguration +{ + /// Gets the format string used to compute the custom version output on this branch. + string? CustomVersionFormat { get; } + + /// Gets the numeric weight applied to the pre-release tag number to produce a weighted pre-release number. + int? PreReleaseWeight { get; } +} diff --git a/src/GitVersion.Core/Configuration/IOutputConfiguration.cs b/src/GitVersion.Core/Configuration/IOutputConfiguration.cs new file mode 100644 index 0000000000..99459ae9b0 --- /dev/null +++ b/src/GitVersion.Core/Configuration/IOutputConfiguration.cs @@ -0,0 +1,32 @@ +namespace GitVersion.Configuration; + +/// Represents settings that affect assembly, build-server, and formatted version output. +public interface IOutputConfiguration : IOutputBranchConfiguration +{ + /// + AssemblyVersioningScheme? AssemblyVersioningScheme { get; } + + /// + AssemblyFileVersioningScheme? AssemblyFileVersioningScheme { get; } + + /// + string? AssemblyInformationalFormat { get; } + + /// + string? AssemblyVersioningFormat { get; } + + /// + string? AssemblyFileVersioningFormat { get; } + + /// + int? TagPreReleaseWeight { get; } + + /// + string? CommitDateFormat { get; } + + /// + bool UpdateBuildNumber { get; } + + /// Gets output settings for each configured branch. + IReadOnlyDictionary Branches { get; } +} diff --git a/src/GitVersion.Core/PublicAPI.Unshipped.txt b/src/GitVersion.Core/PublicAPI.Unshipped.txt index a79dcedde9..71b34a9b20 100644 --- a/src/GitVersion.Core/PublicAPI.Unshipped.txt +++ b/src/GitVersion.Core/PublicAPI.Unshipped.txt @@ -2,10 +2,54 @@ GitVersion.Configuration.EffectiveConfiguration.CustomVersionFormat.get -> string? GitVersion.Configuration.EffectiveConfiguration.VersionBumpResetMessage.get -> string? GitVersion.Configuration.IBranchConfiguration.CustomVersionFormat.get -> string? +GitVersion.Configuration.ICalculationBranchConfiguration +GitVersion.Configuration.ICalculationBranchConfiguration.CommitMessageIncrementing.get -> GitVersion.VersionCalculation.CommitMessageIncrementMode? +GitVersion.Configuration.ICalculationBranchConfiguration.DeploymentMode.get -> GitVersion.VersionCalculation.DeploymentMode? +GitVersion.Configuration.ICalculationBranchConfiguration.Increment.get -> GitVersion.IncrementStrategy +GitVersion.Configuration.ICalculationBranchConfiguration.IsMainBranch.get -> bool? +GitVersion.Configuration.ICalculationBranchConfiguration.IsReleaseBranch.get -> bool? +GitVersion.Configuration.ICalculationBranchConfiguration.IsSourceBranchFor.get -> System.Collections.Generic.IReadOnlyCollection! +GitVersion.Configuration.ICalculationBranchConfiguration.Label.get -> string? +GitVersion.Configuration.ICalculationBranchConfiguration.PreventIncrement.get -> GitVersion.Configuration.IPreventIncrementConfiguration! +GitVersion.Configuration.ICalculationBranchConfiguration.RegularExpression.get -> string? +GitVersion.Configuration.ICalculationBranchConfiguration.SourceBranches.get -> System.Collections.Generic.IReadOnlyCollection! +GitVersion.Configuration.ICalculationBranchConfiguration.TrackMergeMessage.get -> bool? +GitVersion.Configuration.ICalculationBranchConfiguration.TrackMergeTarget.get -> bool? +GitVersion.Configuration.ICalculationBranchConfiguration.TracksReleaseBranches.get -> bool? +GitVersion.Configuration.ICalculationConfiguration +GitVersion.Configuration.ICalculationConfiguration.Branches.get -> System.Collections.Generic.IReadOnlyDictionary! +GitVersion.Configuration.ICalculationConfiguration.Ignore.get -> GitVersion.Configuration.IIgnoreConfiguration! +GitVersion.Configuration.ICalculationConfiguration.MajorVersionBumpMessage.get -> string? +GitVersion.Configuration.ICalculationConfiguration.MergeMessageFormats.get -> System.Collections.Generic.IReadOnlyDictionary! +GitVersion.Configuration.ICalculationConfiguration.MinorVersionBumpMessage.get -> string? +GitVersion.Configuration.ICalculationConfiguration.NextVersion.get -> string? +GitVersion.Configuration.ICalculationConfiguration.NoBumpMessage.get -> string? +GitVersion.Configuration.ICalculationConfiguration.PatchVersionBumpMessage.get -> string? +GitVersion.Configuration.ICalculationConfiguration.SemanticVersionFormat.get -> GitVersion.SemanticVersionFormat +GitVersion.Configuration.ICalculationConfiguration.TagPrefixPattern.get -> string? +GitVersion.Configuration.ICalculationConfiguration.VersionBumpResetMessage.get -> string? +GitVersion.Configuration.ICalculationConfiguration.VersionInBranchPattern.get -> string? +GitVersion.Configuration.ICalculationConfiguration.VersionStrategy.get -> GitVersion.VersionCalculation.VersionStrategies +GitVersion.Configuration.ICalculationConfiguration.Workflow.get -> string? +GitVersion.Configuration.IGitVersionConfiguration.Calculation.get -> GitVersion.Configuration.ICalculationConfiguration! +GitVersion.Configuration.IGitVersionConfiguration.Output.get -> GitVersion.Configuration.IOutputConfiguration! GitVersion.Configuration.IGitVersionConfiguration.VersionBumpResetMessage.get -> string? GitVersion.Configuration.IIgnoreConfiguration.Branches.get -> System.Collections.Generic.IReadOnlySet! GitVersion.Configuration.IIgnoreConfiguration.Tags.get -> System.Collections.Generic.IReadOnlySet! GitVersion.Git.ReferenceName.WithoutRemote.get -> string! +GitVersion.Configuration.IOutputBranchConfiguration +GitVersion.Configuration.IOutputBranchConfiguration.CustomVersionFormat.get -> string? +GitVersion.Configuration.IOutputBranchConfiguration.PreReleaseWeight.get -> int? +GitVersion.Configuration.IOutputConfiguration +GitVersion.Configuration.IOutputConfiguration.AssemblyFileVersioningFormat.get -> string? +GitVersion.Configuration.IOutputConfiguration.AssemblyFileVersioningScheme.get -> GitVersion.Configuration.AssemblyFileVersioningScheme? +GitVersion.Configuration.IOutputConfiguration.AssemblyInformationalFormat.get -> string? +GitVersion.Configuration.IOutputConfiguration.AssemblyVersioningFormat.get -> string? +GitVersion.Configuration.IOutputConfiguration.AssemblyVersioningScheme.get -> GitVersion.Configuration.AssemblyVersioningScheme? +GitVersion.Configuration.IOutputConfiguration.Branches.get -> System.Collections.Generic.IReadOnlyDictionary! +GitVersion.Configuration.IOutputConfiguration.CommitDateFormat.get -> string? +GitVersion.Configuration.IOutputConfiguration.TagPreReleaseWeight.get -> int? +GitVersion.Configuration.IOutputConfiguration.UpdateBuildNumber.get -> bool GitVersion.VersionCalculation.CommitMessageIncrement GitVersion.VersionCalculation.CommitMessageIncrement.CommitMessageIncrement() -> void GitVersion.VersionCalculation.CommitMessageIncrement.CommitMessageIncrement(GitVersion.VersionField Increment, bool VersionBumpNeedsToBeReset) -> void diff --git a/src/GitVersion.MsBuild.Tests/Tasks/WriteVersionInfoTest.cs b/src/GitVersion.MsBuild.Tests/Tasks/WriteVersionInfoTest.cs index ef7900cd0a..ec798952f0 100644 --- a/src/GitVersion.MsBuild.Tests/Tasks/WriteVersionInfoTest.cs +++ b/src/GitVersion.MsBuild.Tests/Tasks/WriteVersionInfoTest.cs @@ -36,7 +36,11 @@ public void WriteVersionInfoTaskShouldLogOutputVariablesToBuildOutputInAzurePipe public void WriteVersionInfoTaskShouldNotUpdateBuildNumberInAzurePipeline(string buildNumber) { var task = new WriteVersionInfoToBuildLog(); - const string content = "update-build-number: false"; + const string content = """ + calculation: {} + output: + update-build-number: false + """; using var result = ExecuteMsBuildTaskInAzurePipeline(task, buildNumber, content); From 25b4e2cb3986055e5d90128ca755472f2280415b Mon Sep 17 00:00:00 2001 From: Artur Stolear Date: Wed, 19 Aug 2026 08:54:49 +0200 Subject: [PATCH 3/4] feat: honor configuration version in overrides and cache --- .../ArgumentParserTests.cs | 118 +++++++++ .../LegacyArgumentParserTests.cs | 39 +++ src/GitVersion.App/ArgumentParser.cs | 19 +- src/GitVersion.App/LegacyArgumentParser.cs | 20 +- .../OverrideConfigurationOptionParser.cs | 228 +++++++++++++++++- .../Core/GitVersionExecutorTests.cs | 17 ++ .../Caching/GitVersionCacheKeyFactory.cs | 3 +- 7 files changed, 405 insertions(+), 39 deletions(-) diff --git a/src/GitVersion.App.Tests/ArgumentParserTests.cs b/src/GitVersion.App.Tests/ArgumentParserTests.cs index 2b9bd12c8f..66185de591 100644 --- a/src/GitVersion.App.Tests/ArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/ArgumentParserTests.cs @@ -9,21 +9,139 @@ namespace GitVersion.App.Tests; [TestFixture] +[NonParallelizable] public class ArgumentParserTests : TestBase { private IEnvironment environment = null!; private IArgumentParser argumentParser = null!; private IFileSystem fileSystem = null!; + private string? originalConfigurationVersion; [SetUp] public void SetUp() { + this.originalConfigurationVersion = System.Environment.GetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName); + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v6"); var sp = ConfigureServices(services => services.AddModule(new GitVersionAppModule())); this.environment = sp.GetRequiredService(); this.argumentParser = sp.GetRequiredService(); this.fileSystem = sp.GetRequiredService(); } + [TearDown] + public void TearDown() => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, this.originalConfigurationVersion); + + [Test] + public void OverrideConfigSupportsNestedV7RootAndBranchPaths() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + + var arguments = this.argumentParser.ParseArguments( + "--override-config calculation.tag-prefix=custom- " + + "--override-config calculation.branches.main.increment=Major " + + "--override-config output.branches.main.pre-release-weight=42"); + + var normalized = ConfigurationDocumentMapper.Normalize( + arguments.OverrideConfiguration!, ConfigurationVersion.V7, "test override"); + ConfigurationHelper configurationHelper = new(normalized); + var configuration = configurationHelper.Configuration; + configuration.TagPrefixPattern.ShouldBe("custom-"); + configuration.Branches["main"].Increment.ShouldBe(IncrementStrategy.Major); + configuration.Branches["main"].PreReleaseWeight.ShouldBe(42); + } + + [Test] + public void OverrideConfigPreservesV7BranchNameCasing() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + + var arguments = this.argumentParser.ParseArguments( + "--override-config Calculation.Branches.ReleaseCandidate.Increment=Major"); + + var normalized = ConfigurationDocumentMapper.Normalize( + arguments.OverrideConfiguration!, ConfigurationVersion.V7, "test override"); + ConfigurationHelper configurationHelper = new(normalized); + configurationHelper.Configuration.Branches["ReleaseCandidate"].Increment.ShouldBe(IncrementStrategy.Major); + } + + [Test] + public void OverrideConfigRejectsDottedV7BranchNameWithClearDiagnostic() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + + var exception = Should.Throw(() => + this.argumentParser.ParseArguments( + "--override-config calculation.branches.release.1.increment=Major")); + + exception.Message.ShouldContain("Branch name 'release.1' contains '.'"); + exception.Message.ShouldContain("Use a configuration file instead"); + } + + [Test] + public void OverrideConfigRejectsV6PathInV7WithReplacement() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + + var exception = Should.Throw(() => + this.argumentParser.ParseArguments("--override-config tag-prefix=custom-")); + + exception.Message.ShouldContain("calculation.tag-prefix"); + exception.Message.ShouldContain("config migrate"); + } + + [Test] + public void OverrideConfigRejectsPropertyInWrongV7SectionWithReplacement() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + + var exception = Should.Throw(() => + this.argumentParser.ParseArguments("--override-config calculation.update-build-number=false")); + + exception.Message.ShouldContain("output.update-build-number"); + } + + [Test] + public void OverrideConfigSupportsV6BranchPath() + { + var arguments = this.argumentParser.ParseArguments("--override-config branches.main.increment=Major"); + + ConfigurationHelper configurationHelper = new(arguments.OverrideConfiguration); + configurationHelper.Configuration.Branches["main"].Increment.ShouldBe(IncrementStrategy.Major); + } + + [Test] + public void OverrideConfigPreservesV6BranchNameCasing() + { + var arguments = this.argumentParser.ParseArguments( + "--override-config Branches.ReleaseCandidate.Increment=Major"); + + ConfigurationHelper configurationHelper = new(arguments.OverrideConfiguration); + configurationHelper.Configuration.Branches["ReleaseCandidate"].Increment.ShouldBe(IncrementStrategy.Major); + } + + [Test] + public void OverrideConfigRejectsV7PathInV6WithReplacement() + { + var exception = Should.Throw(() => + this.argumentParser.ParseArguments("--override-config output.update-build-number=false")); + + exception.Message.ShouldContain("update-build-number"); + exception.Message.ShouldContain("config migrate"); + } + + [Test] + public void OverrideConfigBatchValidationDoesNotApplyAnyValues() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + var parser = new OverrideConfigurationOptionParser(); + + Should.Throw(() => parser.SetValues( + ["calculation.tag-prefix=custom-", "tag-prefix=legacy"], "--override-config")); + + parser.GetOverrideConfiguration().ShouldBeEmpty(); + } + [Test] public void EmptyMeansUseCurrentDirectory() { diff --git a/src/GitVersion.App.Tests/LegacyArgumentParserTests.cs b/src/GitVersion.App.Tests/LegacyArgumentParserTests.cs index deee50c64d..42fc182b28 100644 --- a/src/GitVersion.App.Tests/LegacyArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/LegacyArgumentParserTests.cs @@ -9,21 +9,60 @@ namespace GitVersion.App.Tests; [TestFixture] +[NonParallelizable] public class LegacyArgumentParserTests : TestBase { private IEnvironment environment = null!; private IArgumentParser argumentParser = null!; private IFileSystem fileSystem = null!; + private string? originalConfigurationVersion; [SetUp] public void SetUp() { + this.originalConfigurationVersion = System.Environment.GetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName); + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v6"); var sp = ConfigureServices(services => services.AddModule(new GitVersionAppModule(useLegacyParser: true))); this.environment = sp.GetRequiredService(); this.argumentParser = sp.GetRequiredService(); this.fileSystem = sp.GetRequiredService(); } + [TearDown] + public void TearDown() => + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, this.originalConfigurationVersion); + + [Test] + public void OverrideConfigSupportsNestedV7RootAndBranchPaths() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + + var arguments = this.argumentParser.ParseArguments( + "/overrideconfig calculation.tag-prefix=custom- " + + "/overrideconfig calculation.branches.main.increment=Major " + + "/overrideconfig output.branches.main.pre-release-weight=42"); + + var normalized = ConfigurationDocumentMapper.Normalize( + arguments.OverrideConfiguration!, ConfigurationVersion.V7, "test override"); + ConfigurationHelper configurationHelper = new(normalized); + var configuration = configurationHelper.Configuration; + configuration.TagPrefixPattern.ShouldBe("custom-"); + configuration.Branches["main"].Increment.ShouldBe(IncrementStrategy.Major); + configuration.Branches["main"].PreReleaseWeight.ShouldBe(42); + } + + [Test] + public void OverrideConfigRejectsV6PathInV7WithReplacement() + { + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + + var exception = Should.Throw(() => + this.argumentParser.ParseArguments("/overrideconfig tag-prefix=custom-")); + + exception.Message.ShouldContain("calculation.tag-prefix"); + exception.Message.ShouldContain("config migrate"); + } + [Test] public void EmptyMeansUseCurrentDirectory() { diff --git a/src/GitVersion.App/ArgumentParser.cs b/src/GitVersion.App/ArgumentParser.cs index efdf5c24b0..f271ddb9bd 100644 --- a/src/GitVersion.App/ArgumentParser.cs +++ b/src/GitVersion.App/ArgumentParser.cs @@ -591,24 +591,7 @@ private static void ParseOverrideConfig(Arguments arguments, IReadOnlyCollection } var parser = new OverrideConfigurationOptionParser(); - - foreach (var keyValueOption in values) - { - var keyAndValue = QuotedStringHelpers.SplitUnquoted(keyValueOption, '='); - if (keyAndValue.Length != 2) - { - throw new WarningException($"Could not parse --override-config option: {keyValueOption}. Ensure it is in format 'key=value'."); - } - - var optionKey = keyAndValue[0].ToLowerInvariant(); - if (!OverrideConfigurationOptionParser.SupportedProperties.Contains(optionKey)) - { - throw new WarningException($"Could not parse --override-config option: {keyValueOption}. Unsupported key '{optionKey}'."); - } - - parser.SetValue(optionKey, keyAndValue[1]); - } - + parser.SetValues(values, "--override-config"); arguments.OverrideConfiguration = parser.GetOverrideConfiguration(); } diff --git a/src/GitVersion.App/LegacyArgumentParser.cs b/src/GitVersion.App/LegacyArgumentParser.cs index 896f74f3ce..24a8410bfc 100644 --- a/src/GitVersion.App/LegacyArgumentParser.cs +++ b/src/GitVersion.App/LegacyArgumentParser.cs @@ -511,25 +511,7 @@ private static void ParseOverrideConfig(Arguments arguments, IReadOnlyCollection } var parser = new OverrideConfigurationOptionParser(); - - // key=value - foreach (var keyValueOption in values) - { - var keyAndValue = QuotedStringHelpers.SplitUnquoted(keyValueOption, '='); - if (keyAndValue.Length != 2) - { - throw new WarningException($"Could not parse /overrideconfig option: {keyValueOption}. Ensure it is in format 'key=value'."); - } - - var optionKey = keyAndValue[0].ToLowerInvariant(); - if (!OverrideConfigurationOptionParser.SupportedProperties.Contains(optionKey)) - { - throw new WarningException($"Could not parse /overrideconfig option: {keyValueOption}. Unsupported key '{optionKey}'."); - } - - parser.SetValue(optionKey, keyAndValue[1]); - } - + parser.SetValues(values, "/overrideconfig"); arguments.OverrideConfiguration = parser.GetOverrideConfiguration(); } diff --git a/src/GitVersion.App/OverrideConfigurationOptionParser.cs b/src/GitVersion.App/OverrideConfigurationOptionParser.cs index 9b7a3ee403..a25538631e 100644 --- a/src/GitVersion.App/OverrideConfigurationOptionParser.cs +++ b/src/GitVersion.App/OverrideConfigurationOptionParser.cs @@ -12,6 +12,9 @@ internal class OverrideConfigurationOptionParser internal static ILookup SupportedProperties => _lazySupportedProperties.Value; + private static readonly Lazy> _lazySupportedBranchProperties = + new(GetSupportedBranchProperties, true); + /// /// Dynamically creates of /// properties supported as a part of command line '/overrideconfig' option. @@ -50,7 +53,230 @@ private static bool IsSupportedPropertyType(Type propertyType) || unwrappedType == typeof(VersionStrategies[]); } - internal void SetValue(string key, string value) => this.overrideConfiguration[key] = QuotedStringHelpers.UnquoteText(value); + private static bool TryNormalizeAndValidate(string key, out string normalizedKey, out string? error) + { + var version = ConfigurationVersionSelector.Resolve(); + var segments = key.Split('.'); + NormalizeSegments(segments); + normalizedKey = string.Join('.', segments); + + if (IsAmbiguousBranchPath(segments, out var branchName)) + { + error = $"Branch name '{branchName}' contains '.', which cannot be addressed by an override configuration path. " + + "Use a configuration file instead."; + return false; + } + + if (IsValidPath(version, segments)) + { + error = null; + return true; + } + + var replacement = GetReplacement(version, segments); + error = replacement is null + ? $"Unsupported key '{normalizedKey}'." + : $"Key '{normalizedKey}' is not valid in the selected configuration structure. Use '{replacement}' instead. " + + "Run 'gitversion config migrate' to convert a legacy configuration."; + return false; + } + + internal void SetValues(IReadOnlyCollection values, string optionName) + { + List<(string Key, string Value)> parsedOptions = []; + + foreach (var keyValueOption in values) + { + var keyAndValue = QuotedStringHelpers.SplitUnquoted(keyValueOption, '='); + if (keyAndValue.Length != 2) + { + throw new WarningException($"Could not parse {optionName} option: {keyValueOption}. Ensure it is in format 'key=value'."); + } + + if (!TryNormalizeAndValidate(keyAndValue[0], out var optionKey, out var error)) + { + throw new WarningException($"Could not parse {optionName} option: {keyValueOption}. {error}"); + } + + parsedOptions.Add((optionKey, keyAndValue[1])); + } + + foreach (var (key, value) in parsedOptions) + { + SetValue(key, value); + } + } + + internal void SetValue(string key, string value) + { + var segments = key.Split('.'); + IDictionary current = this.overrideConfiguration; + for (var index = 0; index < segments.Length - 1; index++) + { + var segment = segments[index]; + if (!current.TryGetValue(segment, out var nested)) + { + nested = new Dictionary(); + current[segment] = nested; + } + + current = (IDictionary)nested!; + } + + current[segments[^1]] = QuotedStringHelpers.UnquoteText(value); + } internal IReadOnlyDictionary GetOverrideConfiguration() => this.overrideConfiguration; + + private static HashSet GetSupportedBranchProperties() => typeof(BranchConfiguration) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(property => IsSupportedPropertyType(property.PropertyType) && property.CanWrite) + .Select(property => property.GetCustomAttribute()?.Name) + .OfType() + .ToHashSet(StringComparer.Ordinal); + + private static bool IsValidPath(ConfigurationVersion version, IReadOnlyList segments) + { + if (version == ConfigurationVersion.V6) + { + return segments.Count switch + { + 1 => SupportedProperties.Contains(segments[0]), + 3 when segments[0] == ConfigurationDocumentMapper.BranchesPropertyName + => _lazySupportedBranchProperties.Value.Contains(segments[2]), + _ => false + }; + } + + if (segments.Count == 2 && IsSection(segments[0])) + { + return SupportedProperties.Contains(segments[1]) + && IsCorrectSection(segments[0], segments[1], branchProperty: false); + } + + return segments.Count == 4 + && IsSection(segments[0]) + && segments[1] == ConfigurationDocumentMapper.BranchesPropertyName + && _lazySupportedBranchProperties.Value.Contains(segments[3]) + && IsCorrectSection(segments[0], segments[3], branchProperty: true); + } + + private static string? GetReplacement(ConfigurationVersion version, IReadOnlyList segments) => + version == ConfigurationVersion.V7 + ? GetV7Replacement(segments) + : GetV6Replacement(segments); + + private static string? GetV7Replacement(IReadOnlyList segments) + { + if (IsFlatRootPath(segments)) + { + return $"{GetSection(segments[0], branchProperty: false)}.{segments[0]}"; + } + + if (IsFlatBranchPath(segments)) + { + return $"{GetSection(segments[2], branchProperty: true)}.{string.Join('.', segments)}"; + } + + if (IsNestedRootPath(segments)) + { + return $"{GetSection(segments[1], branchProperty: false)}.{segments[1]}"; + } + + return IsNestedBranchPath(segments) + ? $"{GetSection(segments[3], branchProperty: true)}.{string.Join('.', segments.Skip(1))}" + : null; + } + + private static string? GetV6Replacement(IReadOnlyList segments) + { + if (IsNestedRootPath(segments)) + { + return segments[1]; + } + + return IsNestedBranchPath(segments) ? string.Join('.', segments.Skip(1)) : null; + } + + private static bool IsFlatRootPath(IReadOnlyList segments) => + segments.Count == 1 && SupportedProperties.Contains(segments[0]); + + private static bool IsFlatBranchPath(IReadOnlyList segments) => + segments.Count == 3 + && segments[0] == ConfigurationDocumentMapper.BranchesPropertyName + && _lazySupportedBranchProperties.Value.Contains(segments[2]); + + private static bool IsNestedRootPath(IReadOnlyList segments) => + segments.Count == 2 + && IsSection(segments[0]) + && SupportedProperties.Contains(segments[1]); + + private static bool IsNestedBranchPath(IReadOnlyList segments) => + segments.Count == 4 + && IsSection(segments[0]) + && segments[1] == ConfigurationDocumentMapper.BranchesPropertyName + && _lazySupportedBranchProperties.Value.Contains(segments[3]); + + private static void NormalizeSegments(IList segments) + { + var branchesIndex = -1; + if (segments.Count >= 3 + && segments[0].Equals(ConfigurationDocumentMapper.BranchesPropertyName, StringComparison.OrdinalIgnoreCase)) + { + branchesIndex = 0; + } + else if (segments.Count >= 4 + && segments[1].Equals(ConfigurationDocumentMapper.BranchesPropertyName, StringComparison.OrdinalIgnoreCase)) + { + branchesIndex = 1; + } + + for (var index = 0; index < segments.Count; index++) + { + var isBranchNameSegment = branchesIndex >= 0 + && index > branchesIndex + && index < segments.Count - 1; + if (!isBranchNameSegment) + { + segments[index] = segments[index].ToLowerInvariant(); + } + } + } + + private static bool IsAmbiguousBranchPath(IReadOnlyList segments, out string? branchName) + { + var branchesIndex = -1; + if (segments.Count > 3 && segments[0] == ConfigurationDocumentMapper.BranchesPropertyName) + { + branchesIndex = 0; + } + else if (segments.Count > 4 + && IsSection(segments[0]) + && segments[1] == ConfigurationDocumentMapper.BranchesPropertyName) + { + branchesIndex = 1; + } + + var isAmbiguous = branchesIndex >= 0 + && _lazySupportedBranchProperties.Value.Contains(segments[^1]); + + branchName = isAmbiguous + ? string.Join('.', segments.Skip(branchesIndex + 1).SkipLast(1)) + : null; + return isAmbiguous; + } + + private static bool IsSection(string value) + => value is ConfigurationDocumentMapper.CalculationSectionName or ConfigurationDocumentMapper.OutputSectionName; + + private static bool IsCorrectSection(string section, string propertyName, bool branchProperty) + => section == GetSection(propertyName, branchProperty); + + private static string GetSection(string propertyName, bool branchProperty) + { + var isOutput = branchProperty + ? ConfigurationDocumentMapper.IsOutputBranchProperty(propertyName) + : ConfigurationDocumentMapper.IsOutputProperty(propertyName); + return isOutput ? ConfigurationDocumentMapper.OutputSectionName : ConfigurationDocumentMapper.CalculationSectionName; + } } diff --git a/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs b/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs index 5b9f88be7d..143e76287b 100644 --- a/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs +++ b/src/GitVersion.Core.Tests/Core/GitVersionExecutorTests.cs @@ -65,6 +65,23 @@ public void SetupConfigurationVersion() public void RestoreConfigurationVersion() => System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, this.originalConfigurationVersion); + [Test] + public void ConfigurationVersionChangeInvalidatesCache() + { + using var fixture = new EmptyRepositoryFixture(); + fixture.Repository.MakeACommit(); + var options = new GitVersionOptions { WorkingDirectory = fixture.RepositoryPath }; + _ = GetGitVersionCalculator(options); + var cacheKeyFactory = this.sp.GetRequiredService(); + + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v6"); + var v6Key = cacheKeyFactory.Create(null); + System.Environment.SetEnvironmentVariable(ConfigurationVersionSelector.EnvironmentVariableName, "v7"); + var v7Key = cacheKeyFactory.Create(null); + + v7Key.ShouldNotBe(v6Key); + } + [Test] public void ResolvedConfiguration_IsCreatedOncePerExecution() { diff --git a/src/GitVersion.Core/VersionCalculation/Caching/GitVersionCacheKeyFactory.cs b/src/GitVersion.Core/VersionCalculation/Caching/GitVersionCacheKeyFactory.cs index 278ef058f0..14986d849c 100644 --- a/src/GitVersion.Core/VersionCalculation/Caching/GitVersionCacheKeyFactory.cs +++ b/src/GitVersion.Core/VersionCalculation/Caching/GitVersionCacheKeyFactory.cs @@ -32,8 +32,9 @@ public GitVersionCacheKey Create(IReadOnlyDictionary? overrideC var repositorySnapshotHash = GetRepositorySnapshotHash(); var repositoryTargetHash = GetRepositoryTargetHash(); var overrideConfigHash = GetOverrideConfigHash(overrideConfiguration); + var configurationVersionHash = GetHash(ConfigurationVersionSelector.ResolveName()); - var compositeHash = GetHash(gitSystemHash, configFileHash, repositorySnapshotHash, repositoryTargetHash, overrideConfigHash); + var compositeHash = GetHash(gitSystemHash, configFileHash, repositorySnapshotHash, repositoryTargetHash, overrideConfigHash, configurationVersionHash); return new(compositeHash); } From eb5b85a94c0a6e181f289b47dc10949b6ce2c169 Mon Sep 17 00:00:00 2001 From: Artur Stolear Date: Wed, 19 Aug 2026 23:48:51 +0200 Subject: [PATCH 4/4] fix: invalidate build tools for configuration changes --- .github/actions/cache-restore/action.yml | 2 +- .github/workflows/_prepare.yml | 2 +- .github/workflows/docs.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/cache-restore/action.yml b/.github/actions/cache-restore/action.yml index 153f2d15f0..ae5e9f409e 100644 --- a/.github/actions/cache-restore/action.yml +++ b/.github/actions/cache-restore/action.yml @@ -16,7 +16,7 @@ runs: uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: tools - key: tools-${{ runner.os }}-${{ hashFiles('./build/**') }} + key: tools-${{ runner.os }}-${{ hashFiles('./build/**', './.gitversion.yml') }} - name: Cache NuGet packages uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 diff --git a/.github/workflows/_prepare.yml b/.github/workflows/_prepare.yml index 0fc2528e34..3059ff75d6 100644 --- a/.github/workflows/_prepare.yml +++ b/.github/workflows/_prepare.yml @@ -42,7 +42,7 @@ jobs: uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: tools - key: tools-${{ runner.os }}-${{ hashFiles('./build/**') }} + key: tools-${{ runner.os }}-${{ hashFiles('./build/**', './.gitversion.yml') }} - name: Cache NuGet packages uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cb7eaeb577..6e19867296 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -60,7 +60,7 @@ jobs: uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: tools - key: tools-${{ runner.os }}-${{ hashFiles('./build/**') }} + key: tools-${{ runner.os }}-${{ hashFiles('./build/**', './.gitversion.yml') }} - name: Setup .NET SDK uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0