From 0f6370085ca9b61c96e315a47f526a5e0c5b0eba Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 15 Dec 2022 16:54:35 +0100 Subject: [PATCH 1/4] Add tag name option --- src/NerdBank.GitVersioning/VersionOptions.cs | 28 +++++++++++++++++++ .../version.schema.json | 10 +++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/NerdBank.GitVersioning/VersionOptions.cs b/src/NerdBank.GitVersioning/VersionOptions.cs index b631b39e..2d779160 100644 --- a/src/NerdBank.GitVersioning/VersionOptions.cs +++ b/src/NerdBank.GitVersioning/VersionOptions.cs @@ -46,6 +46,11 @@ public class VersionOptions : IEquatable /// private const int DefaultSemVer1NumericIdentifierPadding = 4; + /// + /// The default value for the property. + /// + private const string DefaultTagName = "v{version}"; + /// /// A value indicating whether mutations of this instance are not allowed. /// @@ -109,6 +114,12 @@ public class VersionOptions : IEquatable [DebuggerBrowsable(DebuggerBrowsableState.Never)] private CloudBuildOptions? cloudBuild; + /// + /// Backing field for the property. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string? tagName; + /// /// Backing field for the property. /// @@ -153,6 +164,7 @@ public VersionOptions(VersionOptions copyFrom) this.publicReleaseRefSpec = copyFrom.publicReleaseRefSpec?.ToList(); this.cloudBuild = copyFrom.cloudBuild is object ? new CloudBuildOptions(copyFrom.cloudBuild) : null; this.release = copyFrom.release is object ? new ReleaseOptions(copyFrom.release) : null; + this.tagName = copyFrom.tagName; this.pathFilters = copyFrom.pathFilters?.ToList(); } @@ -472,6 +484,22 @@ public ReleaseOptions? Release [JsonIgnore] public ReleaseOptions ReleaseOrDefault => this.Release ?? ReleaseOptions.DefaultInstance; + /// + /// Gets or sets the tag name template for tagging. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string? TagName + { + get => this.tagName; + set => this.SetIfNotReadOnly(ref this.tagName, value); + } + + /// + /// Gets the tag name template for tagging. + /// + [JsonIgnore] + public string? TagNameOrDefault => this.TagName ?? DefaultTagName; + /// /// Gets or sets a list of paths to use to filter commits when calculating version height. /// If a given commit does not affect any paths in this filter, it is ignored for version height calculations. diff --git a/src/NerdBank.GitVersioning/version.schema.json b/src/NerdBank.GitVersioning/version.schema.json index 14863b3c..efc3afa2 100644 --- a/src/NerdBank.GitVersioning/version.schema.json +++ b/src/NerdBank.GitVersioning/version.schema.json @@ -176,7 +176,7 @@ "type": "object", "properties": { "branchName": { - "description": "Defines the format of release branch names. Format must include a placeholder '{version}' for the version", + "description": "Defines the format of release branch names. Format must include a placeholder '{version}' for the version.", "type": "string", "pattern": ".*\\{version\\}.*", "default": "v{version}" @@ -188,13 +188,19 @@ "default": "minor" }, "firstUnstableTag": { - "description": "Specifies the first/default prerelease tag for new versions", + "description": "Specifies the first/default prerelease tag for new versions.", "type": "string", "default": "alpha" } }, "additionalProperties": false }, + "tagName": { + "description": "Defines the format of tag names. Format must include a placeholder '{version}' for the version.", + "type": "string", + "pattern": ".*\\{version\\}.*", + "default": "v{version}" + }, "pathFilters": { "type": "array", "description": "An array of pathspec-like strings that are used to filter commits when calculating the version height. A commit will not increment the version height if its changed files are not included by these filters.\nPaths are relative to this file. Paths relative to the root of the repository can be specified with the `:/` prefix.\nExclusions can be specified with a `:^` prefix for relative paths, or a `:^/` prefix for paths relative to the root of the repository.\nAfter a path matches any non-exclude filter, it will be run through all exclude filters. If it matches, the path is ignored.", From 0c947f2abd18d319f878129a6d912ab93c7182a2 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 15 Dec 2022 16:56:03 +0100 Subject: [PATCH 2/4] Use tag name format --- src/nbgv/Program.cs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/nbgv/Program.cs b/src/nbgv/Program.cs index 03d372b8..2590553a 100644 --- a/src/nbgv/Program.cs +++ b/src/nbgv/Program.cs @@ -68,6 +68,7 @@ private enum ExitCodes PackageIdNotFound, ShallowClone, InternalError, + InvalidTagNameSetting, } private static bool AlwaysUseLibGit2 => string.Equals(Environment.GetEnvironmentVariable("NBGV_GitEngine"), "LibGit2", StringComparison.Ordinal); @@ -545,6 +546,24 @@ private static Task OnTagCommand(string project, string versionOrRef) return Task.FromResult((int)ExitCodes.NoGitRepo); } + // get tag name format + VersionOptions versionOptions = context.VersionFile.GetVersion(); + if (versionOptions is null) + { + Console.Error.WriteLine($"Failed to load version file for directory '{searchPath}'."); + return Task.FromResult((int)ExitCodes.NoVersionJsonFound); + } + + string tagNameFormat = versionOptions.TagNameOrDefault; + + // ensure there is a '{version}' placeholder in the tag name + if (string.IsNullOrEmpty(tagNameFormat) || !tagNameFormat.Contains("{version}")) + { + Console.Error.WriteLine($"Invalid 'tagName' setting '{tagNameFormat}'. Missing version placeholder '{{version}}'."); + return Task.FromResult((int)ExitCodes.InvalidTagNameSetting); + } + + // get commit to tag LibGit2Sharp.Repository repository = context.Repository; if (!context.TrySelectCommit(versionOrRef)) { @@ -585,8 +604,12 @@ private static Task OnTagCommand(string project, string versionOrRef) return Task.FromResult((int)ExitCodes.NoVersionJsonFound); } - oracle.PublicRelease = true; // assume a public release so we don't get a redundant -gCOMMITID in the tag name - string tagName = $"v{oracle.SemVer2}"; + // assume a public release so we don't get a redundant -gCOMMITID in the tag name + oracle.PublicRelease = true; + + // replace the "{version}" placeholder with the actual version + string tagName = tagNameFormat.Replace("{version}", oracle.SemVer2); + try { context.ApplyTag(tagName); From 78104ee5ca30657c44d76d851f3f4192ec504770 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 19 Dec 2022 17:29:50 +0100 Subject: [PATCH 3/4] Move tag name option to release object --- src/NerdBank.GitVersioning/VersionOptions.cs | 59 +++++++++---------- .../VersionOptionsContractResolver.cs | 5 ++ .../version.schema.json | 22 +++---- src/nbgv/Program.cs | 2 +- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/NerdBank.GitVersioning/VersionOptions.cs b/src/NerdBank.GitVersioning/VersionOptions.cs index 2d779160..7e47f06c 100644 --- a/src/NerdBank.GitVersioning/VersionOptions.cs +++ b/src/NerdBank.GitVersioning/VersionOptions.cs @@ -46,11 +46,6 @@ public class VersionOptions : IEquatable /// private const int DefaultSemVer1NumericIdentifierPadding = 4; - /// - /// The default value for the property. - /// - private const string DefaultTagName = "v{version}"; - /// /// A value indicating whether mutations of this instance are not allowed. /// @@ -114,12 +109,6 @@ public class VersionOptions : IEquatable [DebuggerBrowsable(DebuggerBrowsableState.Never)] private CloudBuildOptions? cloudBuild; - /// - /// Backing field for the property. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private string? tagName; - /// /// Backing field for the property. /// @@ -164,7 +153,6 @@ public VersionOptions(VersionOptions copyFrom) this.publicReleaseRefSpec = copyFrom.publicReleaseRefSpec?.ToList(); this.cloudBuild = copyFrom.cloudBuild is object ? new CloudBuildOptions(copyFrom.cloudBuild) : null; this.release = copyFrom.release is object ? new ReleaseOptions(copyFrom.release) : null; - this.tagName = copyFrom.tagName; this.pathFilters = copyFrom.pathFilters?.ToList(); } @@ -484,22 +472,6 @@ public ReleaseOptions? Release [JsonIgnore] public ReleaseOptions ReleaseOrDefault => this.Release ?? ReleaseOptions.DefaultInstance; - /// - /// Gets or sets the tag name template for tagging. - /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public string? TagName - { - get => this.tagName; - set => this.SetIfNotReadOnly(ref this.tagName, value); - } - - /// - /// Gets the tag name template for tagging. - /// - [JsonIgnore] - public string? TagNameOrDefault => this.TagName ?? DefaultTagName; - /// /// Gets or sets a list of paths to use to filter commits when calculating version height. /// If a given commit does not affect any paths in this filter, it is ignored for version height calculations. @@ -1470,7 +1442,7 @@ public int GetHashCode(CloudBuildNumberCommitIdOptions? obj) } /// - /// Encapsulates settings for the "prepare-release" command. + /// Encapsulates settings for the "prepare-release" and "tag" commands. /// public class ReleaseOptions : IEquatable { @@ -1480,6 +1452,7 @@ public class ReleaseOptions : IEquatable internal static readonly ReleaseOptions DefaultInstance = new ReleaseOptions() { isFrozen = true, + tagName = "v{version}", branchName = "v{version}", versionIncrement = ReleaseVersionIncrement.Minor, firstUnstableTag = "alpha", @@ -1488,6 +1461,9 @@ public class ReleaseOptions : IEquatable [DebuggerBrowsable(DebuggerBrowsableState.Never)] private bool isFrozen; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string? tagName; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string? branchName; @@ -1510,11 +1486,28 @@ public ReleaseOptions() /// The existing instance to copy from. public ReleaseOptions(ReleaseOptions copyFrom) { + this.tagName = copyFrom.tagName; this.branchName = copyFrom.branchName; this.versionIncrement = copyFrom.versionIncrement; this.firstUnstableTag = copyFrom.firstUnstableTag; } + /// + /// Gets or sets the tag name template for tagging. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string? TagName + { + get => this.tagName; + set => this.SetIfNotReadOnly(ref this.tagName, value); + } + + /// + /// Gets the tag name template for tagging. + /// + [JsonIgnore] + public string TagNameOrDefault => this.TagName ?? DefaultInstance.TagName!; + /// /// Gets or sets the branch name template for release branches. /// @@ -1526,7 +1519,7 @@ public string? BranchName } /// - /// Gets the set branch name template for release branches. + /// Gets the branch name template for release branches. /// [JsonIgnore] public string BranchNameOrDefault => this.BranchName ?? DefaultInstance.BranchName!; @@ -1621,7 +1614,8 @@ public bool Equals(ReleaseOptions? x, ReleaseOptions? y) return false; } - return StringComparer.Ordinal.Equals(x.BranchNameOrDefault, y.BranchNameOrDefault) && + return StringComparer.Ordinal.Equals(x.TagNameOrDefault, y.TagNameOrDefault) && + StringComparer.Ordinal.Equals(x.BranchNameOrDefault, y.BranchNameOrDefault) && x.VersionIncrementOrDefault == y.VersionIncrementOrDefault && StringComparer.Ordinal.Equals(x.FirstUnstableTagOrDefault, y.FirstUnstableTagOrDefault); } @@ -1636,7 +1630,8 @@ public int GetHashCode(ReleaseOptions? obj) unchecked { - int hash = StringComparer.Ordinal.GetHashCode(obj.BranchNameOrDefault) * 397; + int hash = StringComparer.Ordinal.GetHashCode(obj.TagNameOrDefault) * 397; + hash ^= StringComparer.Ordinal.GetHashCode(obj.BranchNameOrDefault); hash ^= (int)obj.VersionIncrementOrDefault; hash ^= StringComparer.Ordinal.GetHashCode(obj.FirstUnstableTagOrDefault); return hash; diff --git a/src/NerdBank.GitVersioning/VersionOptionsContractResolver.cs b/src/NerdBank.GitVersioning/VersionOptionsContractResolver.cs index 4975daa3..5b0355fa 100644 --- a/src/NerdBank.GitVersioning/VersionOptionsContractResolver.cs +++ b/src/NerdBank.GitVersioning/VersionOptionsContractResolver.cs @@ -126,6 +126,11 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ property.ShouldSerialize = instance => !((VersionOptions)instance).ReleaseOrDefault.IsDefault; } + if (property.DeclaringType == typeof(VersionOptions.ReleaseOptions) && member.Name == nameof(VersionOptions.ReleaseOptions.TagName)) + { + property.ShouldSerialize = instance => ((VersionOptions.ReleaseOptions)instance).TagNameOrDefault != VersionOptions.ReleaseOptions.DefaultInstance.TagName; + } + if (property.DeclaringType == typeof(VersionOptions.ReleaseOptions) && member.Name == nameof(VersionOptions.ReleaseOptions.BranchName)) { property.ShouldSerialize = instance => ((VersionOptions.ReleaseOptions)instance).BranchNameOrDefault != VersionOptions.ReleaseOptions.DefaultInstance.BranchName; diff --git a/src/NerdBank.GitVersioning/version.schema.json b/src/NerdBank.GitVersioning/version.schema.json index efc3afa2..44a0bb72 100644 --- a/src/NerdBank.GitVersioning/version.schema.json +++ b/src/NerdBank.GitVersioning/version.schema.json @@ -119,7 +119,7 @@ }, "publicReleaseRefSpec": { "type": "array", - "description": "An array of regular expressions that may match a ref (branch or tag) that should be built with PublicRelease=true as the default value. The ref matched against is in its canonical form (e.g. refs/heads/master)", + "description": "An array of regular expressions that may match a ref (branch or tag) that should be built with PublicRelease=true as the default value. The ref matched against is in its canonical form (e.g. refs/heads/master).", "items": { "type": "string", "format": "regex" @@ -128,12 +128,12 @@ }, "cloudBuild": { "type": "object", - "description": "Options that are applicable specifically to cloud builds (e.g. VSTS, AppVeyor, TeamCity)", + "description": "Options that are applicable specifically to cloud builds (e.g. VSTS, AppVeyor, TeamCity).", "properties": { "setAllVariables": { "type": "boolean", "default": false, - "description": "Elevates all build properties to cloud build variables prefaced with \"NBGV_\"" + "description": "Elevates all build properties to cloud build variables prefaced with \"NBGV_\"." }, "setVersionVariables": { "type": "boolean", @@ -172,13 +172,19 @@ } }, "release": { - "description": "Settings for the prepare-release command", + "description": "Settings for the prepare-release and tag commands.", "type": "object", "properties": { + "tagName": { + "description": "Defines the format of tag names. Format must include a placeholder '{version}' for the version.", + "type": "string", + "pattern": "\\{version\\}", + "default": "v{version}" + }, "branchName": { "description": "Defines the format of release branch names. Format must include a placeholder '{version}' for the version.", "type": "string", - "pattern": ".*\\{version\\}.*", + "pattern": "\\{version\\}", "default": "v{version}" }, "versionIncrement": { @@ -195,12 +201,6 @@ }, "additionalProperties": false }, - "tagName": { - "description": "Defines the format of tag names. Format must include a placeholder '{version}' for the version.", - "type": "string", - "pattern": ".*\\{version\\}.*", - "default": "v{version}" - }, "pathFilters": { "type": "array", "description": "An array of pathspec-like strings that are used to filter commits when calculating the version height. A commit will not increment the version height if its changed files are not included by these filters.\nPaths are relative to this file. Paths relative to the root of the repository can be specified with the `:/` prefix.\nExclusions can be specified with a `:^` prefix for relative paths, or a `:^/` prefix for paths relative to the root of the repository.\nAfter a path matches any non-exclude filter, it will be run through all exclude filters. If it matches, the path is ignored.", diff --git a/src/nbgv/Program.cs b/src/nbgv/Program.cs index 2590553a..19457436 100644 --- a/src/nbgv/Program.cs +++ b/src/nbgv/Program.cs @@ -554,7 +554,7 @@ private static Task OnTagCommand(string project, string versionOrRef) return Task.FromResult((int)ExitCodes.NoVersionJsonFound); } - string tagNameFormat = versionOptions.TagNameOrDefault; + string tagNameFormat = versionOptions.ReleaseOrDefault.TagNameOrDefault; // ensure there is a '{version}' placeholder in the tag name if (string.IsNullOrEmpty(tagNameFormat) || !tagNameFormat.Contains("{version}")) From edb2d5f3b821c3b9b73e87b3dcaeb1cfdaccc329 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 19 Dec 2022 22:07:39 +0100 Subject: [PATCH 4/4] Update documentation --- doc/nbgv-cli.md | 38 ++++++++++++++++++++++++++++++++++++++ doc/versionJson.md | 1 + 2 files changed, 39 insertions(+) diff --git a/doc/nbgv-cli.md b/doc/nbgv-cli.md index 3c9f46af..ac48a69b 100644 --- a/doc/nbgv-cli.md +++ b/doc/nbgv-cli.md @@ -164,6 +164,44 @@ 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`. +## Creating a version tag + +The `tag` command automates the task of tagging a commit with a version. + +To create a version tag, run: + +```ps1 +nbgv tag +``` + +This will: + +1. Read version.json to ascertain the version under development, and the naming convention of tag names. +1. Create a new tag for that version. + +You can optionally include a version or commit id to create a new tag for an older version/commit, e.g.: + +```ps1 +nbgv tag 1.0.0 +``` + +### Customizing the behaviour of `tag` + +The behaviour of the `tag` command can be customized in `version.json`: + +```json +{ + "version": "1.0", + "release": { + "tagName" : "v{version}" + } +} +``` + +| Property | Default value | Description | +|----------|---------------|-------------------------------------------------------------------------------------------------| +| tagName | `v{version}` | Defines the format of tag names. Format must include a placeholder '{version}' for the version. | + ## Learn more There are several more sub-commands and switches to each to help you build and maintain your projects, find a commit that built a particular version later on, create tags, etc. diff --git a/doc/versionJson.md b/doc/versionJson.md index c3e14829..a56adb55 100644 --- a/doc/versionJson.md +++ b/doc/versionJson.md @@ -59,6 +59,7 @@ The content of the version.json file is a JSON serialized object with these prop } }, "release" : { + "tagName" : "v{version}", "branchName" : "v{version}", "versionIncrement" : "minor", "firstUnstableTag" : "alpha"