diff --git a/doc/nbgv-cli.md b/doc/nbgv-cli.md index b7e58610e..dfe4f55a5 100644 --- a/doc/nbgv-cli.md +++ b/doc/nbgv-cli.md @@ -165,6 +165,23 @@ For each branch, the following properties are provided: **Note:** When the current branch is already the release branch for the current version, no new branch will be created. In that case, the `NewBranch` property will be `null`. +### Customizing the `prepare-release` commit message + +By default, the `prepare-release` command generates a commit message with the format "Set version to {version}". +A switch allows you to customize the commit message, using `{0}` as a placeholder for the version. + +For example, running the following command: + +``` +nbgv prepare-release --commit-message-pattern "Custom commit message pattern - {0} custom message" +``` + +So your commit message is going to be this: + +``` +Custom commit message pattern - 1.0 custom message +``` + ## Creating a version tag The `tag` command automates the task of tagging a commit with a version. diff --git a/src/NerdBank.GitVersioning/ReleaseManager.cs b/src/NerdBank.GitVersioning/ReleaseManager.cs index 9dc198010..e937bc2d2 100644 --- a/src/NerdBank.GitVersioning/ReleaseManager.cs +++ b/src/NerdBank.GitVersioning/ReleaseManager.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Globalization; using LibGit2Sharp; using Nerdbank.GitVersioning.LibGit2; using Newtonsoft.Json; @@ -127,7 +128,10 @@ public enum ReleaseManagerOutputMode /// /// The output format to use for writing to stdout. /// - public void PrepareRelease(string projectDirectory, string releaseUnstableTag = null, Version nextVersion = null, VersionOptions.ReleaseVersionIncrement? versionIncrement = null, ReleaseManagerOutputMode outputMode = default) + /// + /// An optional, custom message to use for the commit that sets the new version number. May use {0} to substitute the new version number. + /// + public void PrepareRelease(string projectDirectory, string releaseUnstableTag = null, Version nextVersion = null, VersionOptions.ReleaseVersionIncrement? versionIncrement = null, ReleaseManagerOutputMode outputMode = default, string unformattedCommitMessage = null) { Requires.NotNull(projectDirectory, nameof(projectDirectory)); @@ -168,7 +172,7 @@ public void PrepareRelease(string projectDirectory, string releaseUnstableTag = this.WriteToOutput(releaseInfo); } - this.UpdateVersion(context, versionOptions.Version, releaseVersion); + this.UpdateVersion(context, versionOptions.Version, releaseVersion, unformattedCommitMessage); return; } @@ -192,7 +196,7 @@ public void PrepareRelease(string projectDirectory, string releaseUnstableTag = // create release branch and update version Branch releaseBranch = repository.CreateBranch(releaseBranchName); global::LibGit2Sharp.Commands.Checkout(repository, releaseBranch); - this.UpdateVersion(context, versionOptions.Version, releaseVersion); + this.UpdateVersion(context, versionOptions.Version, releaseVersion, unformattedCommitMessage); if (outputMode == ReleaseManagerOutputMode.Text) { @@ -201,7 +205,7 @@ public void PrepareRelease(string projectDirectory, string releaseUnstableTag = // update version on main branch global::LibGit2Sharp.Commands.Checkout(repository, originalBranchName); - this.UpdateVersion(context, versionOptions.Version, nextDevVersion); + this.UpdateVersion(context, versionOptions.Version, nextDevVersion, unformattedCommitMessage); if (outputMode == ReleaseManagerOutputMode.Text) { @@ -261,7 +265,7 @@ private string GetReleaseBranchName(VersionOptions versionOptions) return branchNameFormat.Replace("{version}", versionOptions.Version.Version.ToString()); } - private void UpdateVersion(LibGit2Context context, SemanticVersion oldVersion, SemanticVersion newVersion) + private void UpdateVersion(LibGit2Context context, SemanticVersion oldVersion, SemanticVersion newVersion, string unformattedCommitMessage) { Requires.NotNull(context, nameof(context)); @@ -290,7 +294,13 @@ private void UpdateVersion(LibGit2Context context, SemanticVersion oldVersion, S // Author a commit only if we effectively changed something. if (!context.Repository.Head.Tip.Tree.Equals(context.Repository.Index.WriteToTree())) { - context.Repository.Commit($"Set version to '{versionOptions.Version}'", signature, signature, new CommitOptions() { AllowEmptyCommit = false }); + if (string.IsNullOrEmpty(unformattedCommitMessage)) + { + unformattedCommitMessage = "Set version to '{0}'"; + } + + string commitMessage = string.Format(CultureInfo.CurrentCulture, unformattedCommitMessage, versionOptions.Version); + context.Repository.Commit(commitMessage, signature, signature, new CommitOptions() { AllowEmptyCommit = false }); } } } diff --git a/src/nbgv/Program.cs b/src/nbgv/Program.cs index 960e2062f..5eaa19cb1 100644 --- a/src/nbgv/Program.cs +++ b/src/nbgv/Program.cs @@ -10,6 +10,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Build.Construction; @@ -69,6 +70,7 @@ private enum ExitCodes ShallowClone, InternalError, InvalidTagNameSetting, + InvalidUnformattedCommitMessage, } private static bool AlwaysUseLibGit2 => string.Equals(Environment.GetEnvironmentVariable("NBGV_GitEngine"), "LibGit2", StringComparison.Ordinal); @@ -217,6 +219,7 @@ private static Parser BuildCommandLine() var nextVersion = new Option("--nextVersion", "The version to set for the current branch. If omitted, the next version is determined automatically by incrementing the current version."); var versionIncrement = new Option("--versionIncrement", "Overrides the 'versionIncrement' setting set in version.json for determining the next version of the current branch."); var format = new Option(new[] { "--format", "-f" }, $"The format to write information about the release. Allowed values are: {string.Join(", ", SupportedFormats)}. The default is {DefaultOutputFormat}.").FromAmong(SupportedFormats); + var unformattedCommitMessage = new Option("--commit-message-pattern", "A custom message to use for the commit that changes the version number. May include {0} for the version number. If not specified, the default is \"Set version to '{0}'\"."); var tagArgument = new Argument("tag", "The prerelease tag to apply on the release branch (if any). If not specified, any existing prerelease tag will be removed. The preceding hyphen may be omitted.") { Arity = ArgumentArity.ZeroOrOne, @@ -227,10 +230,11 @@ private static Parser BuildCommandLine() nextVersion, versionIncrement, format, + unformattedCommitMessage, tagArgument, }; - prepareRelease.SetHandler(OnPrepareReleaseCommand, project, nextVersion, versionIncrement, format, tagArgument); + prepareRelease.SetHandler(OnPrepareReleaseCommand, project, nextVersion, versionIncrement, format, tagArgument, unformattedCommitMessage); } var root = new RootCommand($"{ThisAssembly.AssemblyTitle} v{ThisAssembly.AssemblyInformationalVersion}") @@ -710,7 +714,7 @@ private static Task OnCloudCommand(string project, string[] metadata, strin return Task.FromResult((int)ExitCodes.OK); } - private static Task OnPrepareReleaseCommand(string project, string nextVersion, string versionIncrement, string format, string tag) + private static Task OnPrepareReleaseCommand(string project, string nextVersion, string versionIncrement, string format, string tag, string unformattedCommitMessage) { // validate project path property string searchPath = GetSpecifiedOrCurrentDirectoryPath(project); @@ -763,11 +767,24 @@ private static Task OnPrepareReleaseCommand(string project, string nextVers return Task.FromResult((int)ExitCodes.UnsupportedFormat); } + if (!string.IsNullOrEmpty(unformattedCommitMessage)) + { + try + { + string.Format(unformattedCommitMessage, "FormatValidator"); + } + catch (FormatException ex) + { + Console.Error.WriteLine($"Invalid commit message pattern: {ex.Message}"); + return Task.FromResult((int)ExitCodes.InvalidUnformattedCommitMessage); + } + } + // run prepare-release try { var releaseManager = new ReleaseManager(Console.Out, Console.Error); - releaseManager.PrepareRelease(searchPath, tag, nextVersionParsed, versionIncrementParsed, outputMode); + releaseManager.PrepareRelease(searchPath, tag, nextVersionParsed, versionIncrementParsed, outputMode, unformattedCommitMessage); return Task.FromResult((int)ExitCodes.OK); } catch (ReleaseManager.ReleasePreparationException ex) diff --git a/test/Nerdbank.GitVersioning.Tests/ReleaseManagerTests.cs b/test/Nerdbank.GitVersioning.Tests/ReleaseManagerTests.cs index 9ced1c7a2..0c170b81d 100644 --- a/test/Nerdbank.GitVersioning.Tests/ReleaseManagerTests.cs +++ b/test/Nerdbank.GitVersioning.Tests/ReleaseManagerTests.cs @@ -638,6 +638,34 @@ public void PrepareRelease_ResetsVersionHeightOffset() Assert.Equal(expectedReleaseVersionOptions, releaseVersion); } + [Theory] + [InlineData("1.0-beta", "{0} Custom commit message pattern", "1.0 Custom commit message pattern")] + [InlineData("1.0-beta", "Custom commit message pattern - {0} custom message", "Custom commit message pattern - 1.0 custom message")] + [InlineData("1.0-beta", "Custom commit message pattern - {0}", "Custom commit message pattern - 1.0")] + [InlineData("1.0-beta", "{0}", "1.0")] + public void PrepareRelease_WithCustomCommitMessagePattern(string initialVersion, string commitMessagePattern, string expectedCommitMessage) + { + // Create and configure the repository + this.InitializeSourceControl(); + + var versionOptions = new VersionOptions() + { + Version = SemanticVersion.Parse(initialVersion), + }; + + this.WriteVersionFile(versionOptions); + + // Run PrepareRelease with the custom commit message pattern + var releaseManager = new ReleaseManager(); + releaseManager.PrepareRelease(this.RepoPath, unformattedCommitMessage: commitMessagePattern); + + // Verify that the commit message on the release branch matches the expected pattern + string releaseBranchName = "v1.0"; + Branch releaseBranch = this.LibGit2Repository.Branches[releaseBranchName]; + Commit releaseBranchCommit = releaseBranch.Tip; + Assert.Equal(expectedCommitMessage, releaseBranchCommit.MessageShort); + } + /// protected override void InitializeSourceControl(bool withInitialCommit = true) {