diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 5df5e9ff4a..938f6449f3 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -23,8 +23,10 @@ * Following root configuration properties have been removed: * continuous-delivery-fallback-tag * A new branch related property with name `track-merge-message` has been introduced. Consider we have a `main` branch and a `release/1.0.0` branch and merge changes from `release/1.0.0` to the main branch. In this scenario the merge message will be interpreted as a next version `1.0.0` when `track-merge-message` is set to `true` otherwise `0.0.1`. -* The pre-release tags are only considered when they are matching with the label name of the branch. This has an effect on the way how the `CommitCountSource` will be determined. -* The process of increasing the version with bump message when `CommitMessageIncrementing` is enabled and increment strategy is `None` has been changed. +* The pre-release tags are only considered when they are matching with the label name of the branch. This has an effect on the way how the `CommitCountSource` will be determined. +* The process of increasing the version with bump message when `CommitMessageIncrementing` is enabled and increment strategy is `None` has been changed. +* A new configuration property with name `version-in-branch-pattern` has been introduced. This setting only applies on branches where the option `is-release-branch` is set to `true`. Please notice that the branch name needs to be defined after the version number by default (instead of `support/lts-2.0.0` please name the branch like `support/2.0.0-lts`). +* The `is-release-branch` property of the `hotfix` branch setting has been changed from `false` to `true`. If present the hotfix number will be considered now by default. ## v5.0.0 diff --git a/docs/input/docs/reference/configuration.md b/docs/input/docs/reference/configuration.md index eecfcfbf06..296612439d 100644 --- a/docs/input/docs/reference/configuration.md +++ b/docs/input/docs/reference/configuration.md @@ -43,6 +43,7 @@ The global configuration looks like this: assembly-versioning-scheme: MajorMinorPatch assembly-file-versioning-scheme: MajorMinorPatch label-prefix: '[vV]?' +version-in-branch-pattern: (?[vV]?\d+(\.\d+)?(\.\d+)?).* major-version-bump-message: '\+semver:\s?(breaking|major)' minor-version-bump-message: '\+semver:\s?(feature|minor)' patch-version-bump-message: '\+semver:\s?(fix|patch)' @@ -61,6 +62,7 @@ branches: track-merge-target: true regex: ^dev(elop)?(ment)?$ source-branches: [] + is-source-branch-for: [] tracks-release-branches: true is-release-branch: false is-mainline: false @@ -74,6 +76,7 @@ branches: source-branches: - develop - release + is-source-branch-for: [] tracks-release-branches: false is-release-branch: false is-mainline: true @@ -89,6 +92,7 @@ branches: - main - support - release + is-source-branch-for: [] tracks-release-branches: false is-release-branch: true is-mainline: false @@ -105,6 +109,7 @@ branches: - feature - support - hotfix + is-source-branch-for: [] pre-release-weight: 30000 pull-request: mode: ContinuousDelivery @@ -119,6 +124,7 @@ branches: - feature - support - hotfix + is-source-branch-for: [] pre-release-weight: 30000 hotfix: mode: ContinuousDelivery @@ -130,6 +136,8 @@ branches: - main - support - hotfix + is-source-branch-for: [] + is-release-branch: true pre-release-weight: 30000 support: label: '' @@ -139,6 +147,7 @@ branches: regex: ^support[/-] source-branches: - main + is-source-branch-for: [] tracks-release-branches: false is-release-branch: false is-mainline: true @@ -156,6 +165,7 @@ branches: - pull-request - hotfix - support + is-source-branch-for: [] ignore: sha: [] mode: ContinuousDelivery @@ -166,6 +176,8 @@ track-merge-target: false track-merge-message: true commit-message-incrementing: Enabled regex: '' +source-branches: [] +is-source-branch-for: [] tracks-release-branches: false is-release-branch: false is-mainline: false @@ -258,9 +270,15 @@ and [tracks-release-branches](#tracks-release-branches). ### label-prefix -A regex which is used to trim Git tags before processing (e.g., v1.0.0). Default -is `[vV]`, although this is just for illustrative purposes as we do a IgnoreCase -match and could be `v`. +A regular expression which is used to trim Git tags before processing (e.g., +v1.0.0). The default value is `[vV]`. + +### version-in-branch-pattern + +A regular expression which is used to determine the version number in the branch +name or commit message (e.g., v1.0.0-LTS). This setting only applies on branches +where the option `is-release-branch` is set to `true`. The default value is +`(?[vV]?\d+(\.\d+)?(\.\d+)?).*`. ### major-version-bump-message diff --git a/new-cli/Directory.Build.props b/new-cli/Directory.Build.props index 00858bc1be..28d31920d5 100644 --- a/new-cli/Directory.Build.props +++ b/new-cli/Directory.Build.props @@ -16,6 +16,7 @@ + diff --git a/new-cli/GitVersion.Common/GitVersion.Common.csproj b/new-cli/GitVersion.Common/GitVersion.Common.csproj index f2507e4fbd..6b7ac37412 100644 --- a/new-cli/GitVersion.Common/GitVersion.Common.csproj +++ b/new-cli/GitVersion.Common/GitVersion.Common.csproj @@ -3,6 +3,7 @@ + Git\AuthenticationInfo.cs @@ -63,5 +64,23 @@ Git\RefSpecDirection.cs + + Git\SemanticVersion.cs + + + Git\SemanticVersionBuildMetaData.cs + + + Git\SemanticVersionFormat.cs + + + Git\SemanticVersionPreReleaseTag.cs + + + Git\VersionField.cs + + + Git\WarningException.cs + diff --git a/new-cli/GitVersion.Common/Helpers/EncodingHelper.cs b/new-cli/GitVersion.Common/Helpers/EncodingHelper.cs index 8615d10b64..8f0e3af85f 100644 --- a/new-cli/GitVersion.Common/Helpers/EncodingHelper.cs +++ b/new-cli/GitVersion.Common/Helpers/EncodingHelper.cs @@ -1,5 +1,3 @@ -using System.Text; - namespace GitVersion.Helpers; public static class EncodingHelper diff --git a/new-cli/GitVersion.Common/Infrastructure/IFileSystem.cs b/new-cli/GitVersion.Common/Infrastructure/IFileSystem.cs index b446910030..1c1f8dd9db 100644 --- a/new-cli/GitVersion.Common/Infrastructure/IFileSystem.cs +++ b/new-cli/GitVersion.Common/Infrastructure/IFileSystem.cs @@ -1,5 +1,3 @@ -using System.Text; - namespace GitVersion.Infrastructure; public interface IFileSystem diff --git a/new-cli/GitVersion.Core/Infrastructure/FileSystem.cs b/new-cli/GitVersion.Core/Infrastructure/FileSystem.cs index 5893736006..f656879283 100644 --- a/new-cli/GitVersion.Core/Infrastructure/FileSystem.cs +++ b/new-cli/GitVersion.Core/Infrastructure/FileSystem.cs @@ -1,4 +1,3 @@ -using System.Text; using GitVersion.Helpers; namespace GitVersion.Infrastructure; diff --git a/schemas/6.0/GitVersion.configuration.json b/schemas/6.0/GitVersion.configuration.json index 61160e5161..3bcbfb44f5 100644 --- a/schemas/6.0/GitVersion.configuration.json +++ b/schemas/6.0/GitVersion.configuration.json @@ -161,6 +161,10 @@ "description": "Whether to update the build number in the project file. Defaults to true.", "type": "boolean" }, + "version-in-branch-pattern": { + "description": "A regular expression which is used to determine the version number in the branch name or commit message (e.g., v1.0.0-LTS). The default value is \u0027(?\u003Cversion\u003E[vV]?\\d\u002B(\\.\\d\u002B)?(\\.\\d\u002B)?).*\u0027.", + "type": "string" + }, "mode": { "$ref": "#/$defs/Nullable\u006018" }, diff --git a/src/GitVersion.Core.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt b/src/GitVersion.Core.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt index 24ef92139a..a04994326f 100644 --- a/src/GitVersion.Core.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt +++ b/src/GitVersion.Core.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt @@ -1,6 +1,7 @@ assembly-versioning-scheme: MajorMinorPatch assembly-file-versioning-scheme: MajorMinorPatch label-prefix: '[vV]?' +version-in-branch-pattern: (?[vV]?\d+(\.\d+)?(\.\d+)?).* major-version-bump-message: '\+semver:\s?(breaking|major)' minor-version-bump-message: '\+semver:\s?(feature|minor)' patch-version-bump-message: '\+semver:\s?(fix|patch)' @@ -94,6 +95,7 @@ branches: - support - hotfix is-source-branch-for: [] + is-release-branch: true pre-release-weight: 30000 support: label: '' diff --git a/src/GitVersion.Core.Tests/IntegrationTests/HotfixBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/HotfixBranchScenarios.cs index ab7217bf6b..d8ce805c34 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/HotfixBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/HotfixBranchScenarios.cs @@ -60,7 +60,7 @@ public void CanTakeVersionFromHotfixesBranch() // create hotfix branch Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("hotfixes/1.1.1")); - fixture.AssertFullSemver("1.1.0"); // We are still on a tagged commit + fixture.AssertFullSemver("1.1.1+0"); fixture.Repository.MakeACommit(); fixture.AssertFullSemver("1.1.1-beta.1+1"); @@ -78,44 +78,44 @@ public void PatchOlderReleaseExample() r.MakeATaggedCommit("2.0.0"); }); // Merge hotfix branch to support - Commands.Checkout(fixture.Repository, MainBranch); + fixture.Checkout(MainBranch); var tag = fixture.Repository.Tags.Single(t => t.FriendlyName == "1.1.0"); - var supportBranch = fixture.Repository.CreateBranch("support-1.1", (LibGit2Sharp.Commit)tag.Target); - Commands.Checkout(fixture.Repository, supportBranch); + fixture.Repository.CreateBranch("support-1.1", (LibGit2Sharp.Commit)tag.Target); + fixture.Checkout("support-1.1"); fixture.AssertFullSemver("1.1.0"); // create hotfix branch - Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("hotfix-1.1.1")); - fixture.AssertFullSemver("1.1.0"); // We are still on a tagged commit - fixture.Repository.MakeACommit(); + fixture.BranchTo("hotfix-1.1.1"); + fixture.AssertFullSemver("1.1.1+0"); + fixture.MakeACommit(); fixture.AssertFullSemver("1.1.1-beta.1+1"); - fixture.Repository.MakeACommit(); + fixture.MakeACommit(); fixture.AssertFullSemver("1.1.1-beta.1+2"); // Create feature branch off hotfix branch and complete - Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("feature/fix")); + fixture.BranchTo("feature/fix"); fixture.AssertFullSemver("1.1.1-fix.1+2"); - fixture.Repository.MakeACommit(); + fixture.MakeACommit(); fixture.AssertFullSemver("1.1.1-fix.1+3"); fixture.Repository.CreatePullRequestRef("feature/fix", "hotfix-1.1.1", prNumber: 8, normalise: true); fixture.AssertFullSemver("1.1.1-PullRequest8.4"); - Commands.Checkout(fixture.Repository, "hotfix-1.1.1"); - fixture.Repository.MergeNoFF("feature/fix", Generate.SignatureNow()); + fixture.Checkout("hotfix-1.1.1"); + fixture.MergeNoFF("feature/fix"); fixture.AssertFullSemver("1.1.1-beta.1+4"); // Merge hotfix into support branch to complete hotfix - Commands.Checkout(fixture.Repository, "support-1.1"); - fixture.Repository.MergeNoFF("hotfix-1.1.1", Generate.SignatureNow()); + fixture.Checkout("support-1.1"); + fixture.MergeNoFF("hotfix-1.1.1"); fixture.AssertFullSemver("1.1.1+5"); - fixture.Repository.ApplyTag("1.1.1"); + fixture.ApplyTag("1.1.1"); fixture.AssertFullSemver("1.1.1"); // Verify develop version - Commands.Checkout(fixture.Repository, "develop"); + fixture.Checkout("develop"); fixture.AssertFullSemver("2.1.0-alpha.1"); - fixture.Repository.MergeNoFF("support-1.1", Generate.SignatureNow()); + fixture.MergeNoFF("support-1.1"); fixture.AssertFullSemver("2.1.0-alpha.7"); } @@ -167,7 +167,7 @@ public void FeatureOnHotfixFeatureBranchDeleted() fixture.Checkout(hotfix451); fixture.MergeNoFF(featureBranch); // commit 2 fixture.Repository.Branches.Remove(featureBranch); - fixture.AssertFullSemver("4.5.1-beta.2", configuration); + fixture.AssertFullSemver("4.5.1-beta.3", configuration); } /// @@ -217,7 +217,8 @@ public void FeatureOnHotfixFeatureBranchNotDeleted() fixture.MakeACommit("blabla"); // commit 1 fixture.Checkout(hotfix451); fixture.MergeNoFF(featureBranch); // commit 2 - fixture.AssertFullSemver("4.5.1-beta.2", configuration); + + fixture.AssertFullSemver("4.5.1-beta.3", configuration); } [Test] diff --git a/src/GitVersion.Core.Tests/IntegrationTests/OtherBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/OtherBranchScenarios.cs index 6344f1828a..7580804108 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/OtherBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/OtherBranchScenarios.cs @@ -33,14 +33,36 @@ public void ShouldOnlyConsiderTagsMatchingOfCurrentBranch() [Test] public void CanTakeVersionFromReleaseBranch() { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("release", _ => _.WithLabel("{BranchName}")) + .Build(); + using var fixture = new EmptyRepositoryFixture(); + const string taggedVersion = "1.0.3"; - fixture.Repository.MakeATaggedCommit(taggedVersion); + fixture.MakeATaggedCommit(taggedVersion); fixture.Repository.MakeCommits(5); - fixture.Repository.CreateBranch("release/beta-2.0.0"); - Commands.Checkout(fixture.Repository, "release/beta-2.0.0"); + fixture.BranchTo("release/2.0.0-LTS"); + fixture.MakeACommit(); + + fixture.AssertFullSemver("2.0.0-LTS.1+1", configuration); + } + + [Test] + public void CanTakeVersionFromHotfixBranch() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("hotfix", _ => _.WithLabel("{BranchName}")) + .Build(); + + using var fixture = new EmptyRepositoryFixture(); + + const string taggedVersion = "1.0.3"; + fixture.MakeATaggedCommit(taggedVersion); + fixture.BranchTo("hotfix/1.0.5-LTS"); + fixture.MakeACommit(); - fixture.AssertFullSemver("2.0.0-beta.1+0"); + fixture.AssertFullSemver("1.0.5-LTS.1+1", configuration); } [Test] diff --git a/src/GitVersion.Core.Tests/IntegrationTests/SupportBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/SupportBranchScenarios.cs index d43aff23d8..42c4431f6c 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/SupportBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/SupportBranchScenarios.cs @@ -12,41 +12,41 @@ public void SupportIsCalculatedCorrectly() { using var fixture = new EmptyRepositoryFixture(); // Start at 1.0.0 - fixture.Repository.MakeACommit(); - fixture.Repository.ApplyTag("1.1.0"); + fixture.MakeACommit(); + fixture.ApplyTag("1.1.0"); // Create 2.0.0 release - Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("release-2.0.0")); + fixture.BranchTo("release-2.0.0"); fixture.Repository.MakeCommits(2); // Merge into develop and main - Commands.Checkout(fixture.Repository, MainBranch); - fixture.Repository.MergeNoFF("release-2.0.0"); - fixture.Repository.ApplyTag("2.0.0"); + fixture.Checkout(MainBranch); + fixture.MergeNoFF("release-2.0.0"); + fixture.ApplyTag("2.0.0"); fixture.AssertFullSemver("2.0.0"); // Now lets support 1.x release - Commands.Checkout(fixture.Repository, "1.1.0"); - Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("support/1.0.0")); + fixture.Checkout("1.1.0"); + fixture.BranchTo("support/1.0.0"); fixture.AssertFullSemver("1.1.0"); // Create release branch from support branch - Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("release/1.2.0")); - fixture.Repository.MakeACommit(); + fixture.BranchTo("release/1.2.0"); + fixture.MakeACommit(); fixture.AssertFullSemver("1.2.0-beta.1+1"); // Create 1.2.0 release - Commands.Checkout(fixture.Repository, "support/1.0.0"); - fixture.Repository.MergeNoFF("release/1.2.0"); + fixture.Checkout("support/1.0.0"); + fixture.MergeNoFF("release/1.2.0"); fixture.AssertFullSemver("1.2.0+0"); - fixture.Repository.ApplyTag("1.2.0"); + fixture.ApplyTag("1.2.0"); // Create 1.2.1 hotfix - Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("hotfix/1.2.1")); - fixture.Repository.MakeACommit(); - fixture.AssertFullSemver("1.2.1-beta.1+1"); - Commands.Checkout(fixture.Repository, "support/1.0.0"); - fixture.Repository.MergeNoFF("hotfix/1.2.1"); + fixture.BranchTo("hotfix/1.2.1"); + fixture.MakeACommit(); + fixture.AssertFullSemver("1.2.1-beta.1+3"); + fixture.Checkout("support/1.0.0"); + fixture.MergeNoFF("hotfix/1.2.1"); fixture.AssertFullSemver("1.2.1+2"); } diff --git a/src/GitVersion.Core.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs index 63e297e7bd..39ca78f3c2 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs @@ -26,6 +26,21 @@ public void DoesNotTakeVersionFromNameOfNonReleaseBranch() fixture.AssertFullSemver("1.1.0-alpha.5"); } + [TestCase("release")] + [TestCase("hotfix")] + public void DoesNotTakeVersionFromBranchWithAccidentalVersion(string branch) + { + using var fixture = new EmptyRepositoryFixture("main"); + + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo($"{branch}/downgrade-some-lib-to-3.2.1"); + fixture.MakeACommit(); + fixture.Checkout("main"); + fixture.MergeNoFF($"{branch}/downgrade-some-lib-to-3.2.1"); + + fixture.AssertFullSemver("1.0.1+2"); + } + [Test] public void TakesVersionFromNameOfBranchThatIsReleaseByConfig() { diff --git a/src/GitVersion.Core.Tests/MergeMessageTests.cs b/src/GitVersion.Core.Tests/MergeMessageTests.cs index 2468ade21e..0e93c7f16d 100644 --- a/src/GitVersion.Core.Tests/MergeMessageTests.cs +++ b/src/GitVersion.Core.Tests/MergeMessageTests.cs @@ -111,11 +111,11 @@ public void ParsesGitHubPullMergeMessage( private static readonly object?[] BitBucketPullMergeMessages = { - new object?[] { "Merge pull request #1234 from feature/one from feature/two to dev", "feature/two", "dev", null, 1234 }, + new object?[] { "Merge pull request #1234 from feature/one from feature/two to dev", "feature/two", "dev", null, 1234 }, new object?[] { "Merge pull request #1234 in feature/one from feature/two to dev", "feature/two", "dev", null, 1234 }, - new object?[] { "Merge pull request #1234 in v4.0.0 from v4.1.0 to dev", "v4.1.0", "dev", new SemanticVersion(4,1), 1234 }, - new object?[] { "Merge pull request #1234 from origin/feature/one from origin/feature/4.2.0/two to dev", "origin/feature/4.2.0/two", "dev", new SemanticVersion(4,2), 1234 }, - new object?[] { "Merge pull request #1234 in feature/4.1.0/one from feature/4.2.0/two to dev", "feature/4.2.0/two", "dev", new SemanticVersion(4,2), 1234 }, + new object?[] { "Merge pull request #1234 in v4.0.0 from v4.1.0 to dev", "v4.1.0", "dev", new SemanticVersion(4,1), 1234 }, + new object?[] { "Merge pull request #1234 from origin/feature/one from origin/feature/4.2.0/two to dev", "origin/feature/4.2.0/two", "dev", new SemanticVersion(4,2), 1234 }, + new object?[] { "Merge pull request #1234 in feature/4.1.0/one from feature/4.2.0/two to dev", "feature/4.2.0/two", "dev", new SemanticVersion(4,2), 1234 }, new object?[] { $"Merge pull request #1234 from feature/one from feature/two to {MainBranch}" , "feature/two", MainBranch, null, 1234 }, new object?[] { "Merge pull request #1234 in V4.1.0 from V://10.10.10.10 to dev", "V://10.10.10.10", "dev", null, 1234 }, //TODO: Investigate successful bitbucket merge messages that may be invalid diff --git a/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs index 3daeee88c6..bc7cf4e150 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs @@ -75,14 +75,7 @@ public void TakesVersionFromMergeOfReleaseBranch(string message, bool isMergeCom [TestCase("Merge branch '4.0.3'", true)] [TestCase("Merge branch 's'", true)] [TestCase("Merge tag '10.10.50'", true)] - [TestCase("Merge branch 'hotfix-4.6.6' into support-4.6", true)] - [TestCase("Merge branch 'hotfix-10.10.50'", true)] - [TestCase("Merge branch 'Hotfix-10.10.50'", true)] - [TestCase("Merge branch 'Hotfix/10.10.50'", true)] - [TestCase("Merge branch 'hotfix-0.1.5'", true)] - [TestCase("Merge branch 'hotfix-4.2.2' into support-4.2", true)] [TestCase("Merge branch 'somebranch' into release-3.0.0", true)] - [TestCase("Merge branch 'hotfix-0.1.5'\n\nRelates to: TicketId", true)] [TestCase("Merge branch 'alpha-0.1.5'", true)] [TestCase("Merge pull request #95 from Particular/issue-94", false)] [TestCase("Merge pull request #95 in Particular/issue-94", true)] diff --git a/src/GitVersion.Core.Tests/VersionCalculation/Strategies/VersionInBranchNameBaseVersionStrategyTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/Strategies/VersionInBranchNameBaseVersionStrategyTests.cs index f0f2d9db30..c6c01ae80e 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/Strategies/VersionInBranchNameBaseVersionStrategyTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/Strategies/VersionInBranchNameBaseVersionStrategyTests.cs @@ -33,10 +33,8 @@ public void CanTakeVersionFromNameOfReleaseBranch(string branchName, string expe baseVersion.SemanticVersion.ToString().ShouldBe(expectedBaseVersion); } - [TestCase("hotfix-2.0.0")] [TestCase("origin/hotfix-2.0.0")] [TestCase("remotes/origin/hotfix-2.0.0")] - [TestCase("hotfix/2.0.0")] [TestCase("origin/hotfix/2.0.0")] [TestCase("remotes/origin/hotfix/2.0.0")] [TestCase("custom/JIRA-123")] @@ -68,8 +66,12 @@ public void ShouldNotTakeVersionFromNameOfNonReleaseBranch(string branchName) [TestCase("release-2.0.0", "2.0.0")] [TestCase("release/3.0.0", "3.0.0")] - [TestCase("support/lts-2.0.0", "2.0.0")] + [TestCase("support/2.0.0-lts", "2.0.0")] [TestCase("support-3.0.0-lts", "3.0.0")] + [TestCase("hotfix/2.0.0", "2.0.0")] + [TestCase("hotfix-3.0.0", "3.0.0")] + [TestCase("hotfix/2.0.0-lts", "2.0.0")] + [TestCase("hotfix-3.0.0-lts", "3.0.0")] public void CanTakeVersionFromNameOfConfiguredReleaseBranch(string branchName, string expectedBaseVersion) { using var fixture = new EmptyRepositoryFixture(); diff --git a/src/GitVersion.Core/Configuration/ConfigurationBuilderBase.cs b/src/GitVersion.Core/Configuration/ConfigurationBuilderBase.cs index 85d31138f7..994c8bf080 100644 --- a/src/GitVersion.Core/Configuration/ConfigurationBuilderBase.cs +++ b/src/GitVersion.Core/Configuration/ConfigurationBuilderBase.cs @@ -12,6 +12,7 @@ internal abstract class ConfigurationBuilderBase : IConfi private string? assemblyVersioningFormat; private string? assemblyFileVersioningFormat; private string? labelPrefix; + private string? versionInBranchPattern; private string? nextVersion; private string? majorVersionBumpMessage; private string? minorVersionBumpMessage; @@ -131,6 +132,12 @@ public virtual TConfigurationBuilder WithLabelPrefix(string? value) return (TConfigurationBuilder)this; } + public virtual TConfigurationBuilder WithVersionInBranchPattern(string? value) + { + this.versionInBranchPattern = value; + return (TConfigurationBuilder)this; + } + public virtual TConfigurationBuilder WithNextVersion(string? value) { this.nextVersion = value; @@ -302,6 +309,7 @@ public virtual TConfigurationBuilder WithConfiguration(IGitVersionConfiguration WithAssemblyVersioningFormat(value.AssemblyVersioningFormat); WithAssemblyFileVersioningFormat(value.AssemblyFileVersioningFormat); WithLabelPrefix(value.LabelPrefix); + WithVersionInBranchPattern(value.VersionInBranchPattern); WithNextVersion(value.NextVersion); WithMajorVersionBumpMessage(value.MajorVersionBumpMessage); WithMinorVersionBumpMessage(value.MinorVersionBumpMessage); @@ -357,6 +365,7 @@ public virtual IGitVersionConfiguration Build() AssemblyVersioningFormat = this.assemblyVersioningFormat, AssemblyFileVersioningFormat = this.assemblyFileVersioningFormat, LabelPrefix = this.labelPrefix, + VersionInBranchPattern = this.versionInBranchPattern, NextVersion = this.nextVersion, MajorVersionBumpMessage = this.majorVersionBumpMessage, MinorVersionBumpMessage = this.minorVersionBumpMessage, diff --git a/src/GitVersion.Core/Configuration/ConfigurationConstants.cs b/src/GitVersion.Core/Configuration/ConfigurationConstants.cs index 85966d7759..7e862ab4e7 100644 --- a/src/GitVersion.Core/Configuration/ConfigurationConstants.cs +++ b/src/GitVersion.Core/Configuration/ConfigurationConstants.cs @@ -3,6 +3,7 @@ namespace GitVersion.Configuration; internal static class ConfigurationConstants { public const string DefaultLabelPrefix = "[vV]?"; + public const string DefaultVersionInBranchPattern = @"(?[vV]?\d+(\.\d+)?(\.\d+)?).*"; public const string BranchNamePlaceholder = "{BranchName}"; public const string MainBranchKey = "main"; diff --git a/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs b/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs index 20962004bc..eef699cb3e 100644 --- a/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs +++ b/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using GitVersion.Extensions; using GitVersion.VersionCalculation; @@ -39,6 +40,7 @@ public EffectiveConfiguration(IGitVersionConfiguration configuration, IBranchCon AssemblyFileVersioningFormat = configuration.AssemblyFileVersioningFormat; VersioningMode = branchConfiguration.VersioningMode.Value; LabelPrefix = configuration.LabelPrefix; + VersionInBranchRegex = configuration.VersionInBranchRegex; Label = branchConfiguration.Label; NextVersion = configuration.NextVersion; Increment = branchConfiguration.Increment; @@ -137,6 +139,8 @@ protected EffectiveConfiguration(AssemblyVersioningScheme assemblyVersioningSche /// public string? LabelPrefix { get; } + public Regex VersionInBranchRegex { get; } + /// /// Label to use when calculating SemVer /// diff --git a/src/GitVersion.Core/Configuration/GitFlowConfigurationBuilder.cs b/src/GitVersion.Core/Configuration/GitFlowConfigurationBuilder.cs index aa9635910d..5d332dcc32 100644 --- a/src/GitVersion.Core/Configuration/GitFlowConfigurationBuilder.cs +++ b/src/GitVersion.Core/Configuration/GitFlowConfigurationBuilder.cs @@ -20,6 +20,7 @@ private GitFlowConfigurationBuilder() PatchVersionBumpMessage = IncrementStrategyFinder.DefaultPatchPattern, SemanticVersionFormat = SemanticVersionFormat.Strict, LabelPrefix = ConfigurationConstants.DefaultLabelPrefix, + VersionInBranchPattern = ConfigurationConstants.DefaultVersionInBranchPattern, LabelPreReleaseWeight = 60000, UpdateBuildNumber = true, VersioningMode = VersioningMode.ContinuousDelivery, @@ -133,6 +134,7 @@ private GitFlowConfigurationBuilder() HotfixBranch.Name }, Label = "beta", + IsReleaseBranch = true, PreReleaseWeight = 30000 }); diff --git a/src/GitVersion.Core/Configuration/GitHubFlowConfigurationBuilder.cs b/src/GitVersion.Core/Configuration/GitHubFlowConfigurationBuilder.cs index 236c652968..fbcc27b90a 100644 --- a/src/GitVersion.Core/Configuration/GitHubFlowConfigurationBuilder.cs +++ b/src/GitVersion.Core/Configuration/GitHubFlowConfigurationBuilder.cs @@ -20,6 +20,7 @@ private GitHubFlowConfigurationBuilder() PatchVersionBumpMessage = IncrementStrategyFinder.DefaultPatchPattern, SemanticVersionFormat = SemanticVersionFormat.Strict, LabelPrefix = ConfigurationConstants.DefaultLabelPrefix, + VersionInBranchPattern = ConfigurationConstants.DefaultVersionInBranchPattern, LabelPreReleaseWeight = 60000, UpdateBuildNumber = true, VersioningMode = VersioningMode.ContinuousDelivery, diff --git a/src/GitVersion.Core/Configuration/GitVersionConfiguration.cs b/src/GitVersion.Core/Configuration/GitVersionConfiguration.cs index ebb11723c3..a21cdc76ed 100644 --- a/src/GitVersion.Core/Configuration/GitVersionConfiguration.cs +++ b/src/GitVersion.Core/Configuration/GitVersionConfiguration.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Text.RegularExpressions; using GitVersion.Attributes; using GitVersion.Extensions; @@ -34,6 +35,21 @@ internal sealed record GitVersionConfiguration : BranchConfiguration, IGitVersio [JsonPropertyDescription($"A regular expression which is used to trim Git tags before processing. Defaults to {ConfigurationConstants.DefaultLabelPrefix}")] public string? LabelPrefix { get; internal set; } + [JsonPropertyName("version-in-branch-pattern")] + [JsonPropertyDescription($"A regular expression which is used to determine the version number in the branch name or commit message (e.g., v1.0.0-LTS). The default value is '{ConfigurationConstants.DefaultVersionInBranchPattern}'.")] + public string? VersionInBranchPattern { get; internal set; } + + [JsonIgnore] + public Regex VersionInBranchRegex => versionInBranchRegex ??= new Regex(GetVersionInBranchPattern(), RegexOptions.Compiled); + private Regex? versionInBranchRegex; + + private string GetVersionInBranchPattern() + { + var versionInBranchPattern = VersionInBranchPattern; + if (versionInBranchPattern.IsNullOrEmpty()) versionInBranchPattern = ConfigurationConstants.DefaultVersionInBranchPattern; + return $"^{versionInBranchPattern.TrimStart('^')}"; + } + [JsonPropertyName("next-version")] [JsonPropertyDescription("Allows you to bump the next version explicitly. Useful for bumping main or a feature branch with breaking changes")] public string? NextVersion diff --git a/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs b/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs index 272430587c..5998585d36 100644 --- a/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs +++ b/src/GitVersion.Core/Configuration/IGitVersionConfiguration.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using GitVersion.Extensions; namespace GitVersion.Configuration; @@ -18,6 +19,10 @@ public interface IGitVersionConfiguration : IBranchConfiguration string? LabelPrefix { get; } + string? VersionInBranchPattern { get; } + + Regex VersionInBranchRegex { get; } + string? NextVersion { get; } string? MajorVersionBumpMessage { get; } diff --git a/src/GitVersion.Core/Git/ReferenceName.cs b/src/GitVersion.Core/Git/ReferenceName.cs index 3f600f6b43..960df6de18 100644 --- a/src/GitVersion.Core/Git/ReferenceName.cs +++ b/src/GitVersion.Core/Git/ReferenceName.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Text.RegularExpressions; using GitVersion.Extensions; using GitVersion.Helpers; @@ -76,6 +78,40 @@ public static ReferenceName FromBranchName(string branchName) public override int GetHashCode() => equalityHelper.GetHashCode(this); public override string ToString() => Friendly; + public bool TryGetSemanticVersion([NotNullWhen(true)] out (SemanticVersion Value, string? Name) result, + Regex versionPatternRegex, + string? labelPrefix, + SemanticVersionFormat format) + { + result = default; + + Contract.Assume(versionPatternRegex.ToString().StartsWith("^")); + + int length = 0; + foreach (var branchPart in WithoutOrigin.Split(GetBranchSeparator())) + { + if (string.IsNullOrEmpty(branchPart)) return false; + + var match = versionPatternRegex.NotNull().Match(branchPart); + if (match.Success) + { + var versionPart = match.Groups["version"].Value; + if (SemanticVersion.TryParse(versionPart, labelPrefix, out var semanticVersion, format)) + { + length += versionPart.Length; + var name = WithoutOrigin[length..].Trim('-'); + result = new(semanticVersion, name == string.Empty ? null : name); + return true; + } + } + length += branchPart.Length + 1; + } + + return false; + } + + private char GetBranchSeparator() => WithoutOrigin.Contains('/') || !WithoutOrigin.Contains('-') ? '/' : '-'; + public bool EquivalentTo(string? name) => Canonical.Equals(name, StringComparison.OrdinalIgnoreCase) || Friendly.Equals(name, StringComparison.OrdinalIgnoreCase) diff --git a/src/GitVersion.Core/VersionCalculation/SemanticVersioning/SemanticVersion.cs b/src/GitVersion.Core/Git/SemanticVersion.cs similarity index 100% rename from src/GitVersion.Core/VersionCalculation/SemanticVersioning/SemanticVersion.cs rename to src/GitVersion.Core/Git/SemanticVersion.cs diff --git a/src/GitVersion.Core/VersionCalculation/SemanticVersioning/SemanticVersionBuildMetaData.cs b/src/GitVersion.Core/Git/SemanticVersionBuildMetaData.cs similarity index 100% rename from src/GitVersion.Core/VersionCalculation/SemanticVersioning/SemanticVersionBuildMetaData.cs rename to src/GitVersion.Core/Git/SemanticVersionBuildMetaData.cs diff --git a/src/GitVersion.Core/VersionCalculation/SemanticVersioning/SemanticVersionFormat.cs b/src/GitVersion.Core/Git/SemanticVersionFormat.cs similarity index 100% rename from src/GitVersion.Core/VersionCalculation/SemanticVersioning/SemanticVersionFormat.cs rename to src/GitVersion.Core/Git/SemanticVersionFormat.cs diff --git a/src/GitVersion.Core/VersionCalculation/SemanticVersioning/SemanticVersionPreReleaseTag.cs b/src/GitVersion.Core/Git/SemanticVersionPreReleaseTag.cs similarity index 100% rename from src/GitVersion.Core/VersionCalculation/SemanticVersioning/SemanticVersionPreReleaseTag.cs rename to src/GitVersion.Core/Git/SemanticVersionPreReleaseTag.cs diff --git a/src/GitVersion.Core/VersionCalculation/SemanticVersioning/VersionField.cs b/src/GitVersion.Core/Git/VersionField.cs similarity index 100% rename from src/GitVersion.Core/VersionCalculation/SemanticVersioning/VersionField.cs rename to src/GitVersion.Core/Git/VersionField.cs diff --git a/src/GitVersion.Core/Core/Exceptions/WarningException.cs b/src/GitVersion.Core/Git/WarningException.cs similarity index 100% rename from src/GitVersion.Core/Core/Exceptions/WarningException.cs rename to src/GitVersion.Core/Git/WarningException.cs diff --git a/src/GitVersion.Core/MergeMessage.cs b/src/GitVersion.Core/MergeMessage.cs index 5f13789565..133bac3194 100644 --- a/src/GitVersion.Core/MergeMessage.cs +++ b/src/GitVersion.Core/MergeMessage.cs @@ -48,7 +48,9 @@ public MergeMessage(string mergeMessage, IGitVersionConfiguration configuration) PullRequestNumber = pullNumber; } - Version = ParseVersion(configuration.LabelPrefix, configuration.SemanticVersionFormat); + Version = ParseVersion( + configuration.VersionInBranchRegex, configuration.LabelPrefix, configuration.SemanticVersionFormat + ); break; } @@ -62,22 +64,10 @@ public MergeMessage(string mergeMessage, IGitVersionConfiguration configuration) public int? PullRequestNumber { get; } public SemanticVersion? Version { get; } - private SemanticVersion? ParseVersion(string? tagPrefix, SemanticVersionFormat versionFormat) + private SemanticVersion? ParseVersion(Regex versionInBranchRegex, string? labelPrefix, SemanticVersionFormat format) { - if (tagPrefix is null || MergedBranch is null) - return null; - // Remove remotes and branch prefixes like release/ feature/ hotfix/ etc - var toMatch = Regex.Replace(MergedBranch.WithoutOrigin, @"^(\w+[-/])*", "", RegexOptions.IgnoreCase); - toMatch = Regex.Replace(toMatch, $"^{tagPrefix}", ""); - // We don't match if the version is likely an ip (i.e starts with http://) - var versionMatch = new Regex(@"^(? bool GitVersion.Configuration.EffectiveConfiguration.TracksReleaseBranches.get -> bool GitVersion.Configuration.EffectiveConfiguration.UpdateBuildNumber.get -> bool GitVersion.Configuration.EffectiveConfiguration.VersionFilters.get -> System.Collections.Generic.IEnumerable! +GitVersion.Configuration.EffectiveConfiguration.VersionInBranchRegex.get -> System.Text.RegularExpressions.Regex! GitVersion.Configuration.EffectiveConfiguration.VersioningMode.get -> GitVersion.VersionCalculation.VersioningMode GitVersion.Configuration.IBranchConfiguration GitVersion.Configuration.IBranchConfiguration.CommitMessageIncrementing.get -> GitVersion.VersionCalculation.CommitMessageIncrementMode? @@ -180,6 +181,8 @@ GitVersion.Configuration.IGitVersionConfiguration.NoBumpMessage.get -> string? GitVersion.Configuration.IGitVersionConfiguration.PatchVersionBumpMessage.get -> string? GitVersion.Configuration.IGitVersionConfiguration.SemanticVersionFormat.get -> GitVersion.SemanticVersionFormat GitVersion.Configuration.IGitVersionConfiguration.UpdateBuildNumber.get -> bool +GitVersion.Configuration.IGitVersionConfiguration.VersionInBranchPattern.get -> string? +GitVersion.Configuration.IGitVersionConfiguration.VersionInBranchRegex.get -> System.Text.RegularExpressions.Regex! GitVersion.Configuration.IGitVersionConfiguration.Workflow.get -> string? GitVersion.Configuration.IIgnoreConfiguration GitVersion.Configuration.IIgnoreConfiguration.Before.get -> System.DateTimeOffset? @@ -554,6 +557,7 @@ GitVersion.OutputVariables.VersionVariablesJsonModel.WeightedPreReleaseNumber.ge GitVersion.OutputVariables.VersionVariablesJsonModel.WeightedPreReleaseNumber.set -> void GitVersion.OutputVariables.VersionVariablesJsonStringConverter GitVersion.OutputVariables.VersionVariablesJsonStringConverter.VersionVariablesJsonStringConverter() -> void +GitVersion.ReferenceName.TryGetSemanticVersion(out (GitVersion.SemanticVersion! Value, string? Name) result, System.Text.RegularExpressions.Regex! versionPatternRegex, string? labelPrefix, GitVersion.SemanticVersionFormat format) -> bool GitVersion.RefSpecDirection GitVersion.RefSpecDirection.Fetch = 0 -> GitVersion.RefSpecDirection GitVersion.RefSpecDirection.Push = 1 -> GitVersion.RefSpecDirection diff --git a/src/GitVersion.Core/VersionCalculation/BaseVersionCalculators/VersionInBranchNameVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/BaseVersionCalculators/VersionInBranchNameVersionStrategy.cs index 93596a8b00..55e7a1e021 100644 --- a/src/GitVersion.Core/VersionCalculation/BaseVersionCalculators/VersionInBranchNameVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/BaseVersionCalculators/VersionInBranchNameVersionStrategy.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using GitVersion.Common; using GitVersion.Configuration; using GitVersion.Extensions; @@ -18,33 +19,38 @@ public VersionInBranchNameVersionStrategy(IRepositoryStore repositoryStore, Lazy public override IEnumerable GetBaseVersions(EffectiveBranchConfiguration configuration) { - if (!configuration.Value.IsReleaseBranch) yield break; - - var versionInBranch = GetVersionInBranch( - configuration.Branch.Name, configuration.Value.LabelPrefix, configuration.Value.SemanticVersionFormat - ); - if (versionInBranch != null) + if (configuration.Value.IsReleaseBranch && TryGetBaseVersion(out var baseVersion, configuration)) { - var commitBranchWasBranchedFrom = this.repositoryStore.FindCommitBranchWasBranchedFrom( - configuration.Branch, Context.Configuration - ); - var branchNameOverride = Context.CurrentBranch.Name.Friendly.RegexReplace("[-/]" + versionInBranch.Item1, string.Empty); - yield return new BaseVersion("Version in branch name", false, versionInBranch.Item2, commitBranchWasBranchedFrom.Commit, branchNameOverride); + yield return baseVersion; } } - private static Tuple? GetVersionInBranch( - ReferenceName branchName, string? tagPrefixRegex, SemanticVersionFormat versionFormat) + private bool TryGetBaseVersion([NotNullWhen(true)] out BaseVersion? baseVersion, EffectiveBranchConfiguration configuration) { - var branchParts = branchName.WithoutOrigin.Split('/', '-'); - foreach (var part in branchParts) + baseVersion = null; + + Lazy commitBranchWasBranchedFrom = new( + () => this.repositoryStore.FindCommitBranchWasBranchedFrom(configuration.Branch, Context.Configuration) + ); + foreach (var branch in new[] { Context.CurrentBranch, configuration.Branch }) { - if (SemanticVersion.TryParse(part, tagPrefixRegex, out var semanticVersion, versionFormat)) + if (branch.Name.TryGetSemanticVersion(out var result, configuration.Value.VersionInBranchRegex, + configuration.Value.LabelPrefix, configuration.Value.SemanticVersionFormat)) { - return Tuple.Create(part, semanticVersion); + string? branchNameOverride = null; + if (!result.Name.IsNullOrEmpty() && (Context.CurrentBranch.Name.Equals(branch.Name) + || Context.Configuration.GetBranchConfiguration(Context.CurrentBranch.Name).Label is null)) + { + branchNameOverride = result.Name; + } + + baseVersion = new BaseVersion( + "Version in branch name", false, result.Value, commitBranchWasBranchedFrom.Value.Commit, branchNameOverride + ); + break; } } - return null; + return baseVersion != null; } }