Skip to content

Commit dbddc0a

Browse files
authored
Merge pull request #1317 from dotnet/copilot/add-inherit-version-json-feature
Add prerelease property to inheriting version.json files
2 parents 0a6940b + 3bc31de commit dbddc0a

File tree

7 files changed

+390
-1
lines changed

7 files changed

+390
-1
lines changed

docfx/docs/versionJson.md

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ The content of the version.json file is a JSON serialized object with these prop
6565
"versionIncrement" : "minor",
6666
"firstUnstableTag" : "alpha"
6767
},
68-
"inherit": false // optional. Set to true in secondary version.json files used to tweak settings for subsets of projects.
68+
"inherit": false, // optional. Set to true in secondary version.json files used to tweak settings for subsets of projects.
69+
"prerelease": "beta" // optional. Only valid when inherit is true. Adds or overrides the prerelease tag of the inherited version.
6970
}
7071
```
7172

@@ -109,4 +110,80 @@ In this example, the offset of 100 will be applied as long as the version remain
109110
> [!NOTE]
110111
> This feature is particularly useful when a `version.json` file uses `"inherit": true` to get the version from a parent `version.json` file higher in the source tree. In such cases, you can set `versionHeightOffset` and `versionHeightOffsetAppliesTo` in the inheriting file without having to update it when the parent version changes. The offset will automatically stop applying when the inherited version no longer matches `versionHeightOffsetAppliesTo`.
111112
113+
## Prerelease Tag in Inheriting Files
114+
115+
The `prerelease` property allows a version.json file that inherits from a parent to add or override the prerelease tag without duplicating the version number. This is particularly useful when you want to publish an "unstable" prerelease from a stable branch, or when one package is still considered unstable while its peers are already stable.
116+
117+
### Usage Rules
118+
119+
- The `prerelease` property can **only** be used when `"inherit": true` is set.
120+
- The `version` property in the inheriting file must **not** include a prerelease tag (the `-suffix` part).
121+
- Setting `"prerelease": "beta"` will append `-beta` to the inherited version.
122+
- Setting `"prerelease": ""` (empty string) will explicitly **suppress** any prerelease tag inherited from the parent.
123+
- Omitting the `prerelease` property will inherit the prerelease tag as-is from the parent.
124+
125+
### Examples
126+
127+
**Example 1: Adding a prerelease tag to a stable inherited version**
128+
129+
Parent `version.json` at repository root:
130+
```json
131+
{
132+
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
133+
"version": "1.2"
134+
}
135+
```
136+
137+
Child `version.json` in a subdirectory (e.g., `src/ExperimentalPackage/version.json`):
138+
```json
139+
{
140+
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
141+
"inherit": true,
142+
"prerelease": "beta"
143+
}
144+
```
145+
146+
Result: The subdirectory will use version `1.2-beta` while the rest of the repository uses `1.2`.
147+
148+
**Example 2: Suppressing an inherited prerelease tag**
149+
150+
Parent `version.json` at repository root:
151+
```json
152+
{
153+
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
154+
"version": "1.2-alpha"
155+
}
156+
```
157+
158+
Child `version.json` in a subdirectory (e.g., `src/StablePackage/version.json`):
159+
```json
160+
{
161+
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
162+
"inherit": true,
163+
"prerelease": ""
164+
}
165+
```
166+
167+
Result: The subdirectory will use version `1.2` (stable) while the rest of the repository uses `1.2-alpha`.
168+
169+
**Example 3: Inheriting the prerelease tag as-is**
170+
171+
Parent `version.json` at repository root:
172+
```json
173+
{
174+
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
175+
"version": "1.2-rc"
176+
}
177+
```
178+
179+
Child `version.json` in a subdirectory:
180+
```json
181+
{
182+
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
183+
"inherit": true
184+
}
185+
```
186+
187+
Result: The subdirectory will inherit and use version `1.2-rc` from the parent.
188+
112189
[Learn more about pathFilters](path-filters.md).

src/NerdBank.GitVersioning/LibGit2/LibGit2VersionFile.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ internal LibGit2VersionFile(LibGit2Context context)
126126
}
127127

128128
JsonConvert.PopulateObject(versionJsonContent, result, VersionOptions.GetJsonSettings(repoRelativeBaseDirectory: searchDirectory));
129+
ApplyPrereleaseProperty(result);
129130
result.Inherit = false;
130131
}
131132
}

src/NerdBank.GitVersioning/Managed/ManagedVersionFile.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public ManagedVersionFile(GitContext context)
141141
}
142142

143143
JsonConvert.PopulateObject(versionJsonContent, result, VersionOptions.GetJsonSettings(repoRelativeBaseDirectory: searchDirectory));
144+
ApplyPrereleaseProperty(result);
144145
result.Inherit = false;
145146
}
146147
else

src/NerdBank.GitVersioning/VersionFile.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,70 @@ protected static bool VersionOptionsSatisfyRequirements(VersionOptions? options,
197197
return true;
198198
}
199199

200+
/// <summary>
201+
/// Applies the standalone <see cref="VersionOptions.Prerelease"/> property to the <see cref="VersionOptions.Version"/> property.
202+
/// </summary>
203+
/// <param name="options">The version options to modify.</param>
204+
/// <remarks>
205+
/// This method should be called after merging a child version.json with a parent version.json.
206+
/// If the child specified a <see cref="VersionOptions.Prerelease"/> property, this method will apply it to the version.
207+
/// </remarks>
208+
protected static void ApplyPrereleaseProperty(VersionOptions options)
209+
{
210+
Requires.NotNull(options, nameof(options));
211+
212+
// Only apply if the Prerelease property was explicitly set
213+
if (options.Prerelease is null)
214+
{
215+
return;
216+
}
217+
218+
// The version must exist to apply a prerelease tag
219+
if (options.Version is null)
220+
{
221+
throw new InvalidOperationException("The 'prerelease' property cannot be used without a 'version' property.");
222+
}
223+
224+
// If prerelease is an empty string, it suppresses any inherited prerelease tag
225+
if (options.Prerelease == string.Empty)
226+
{
227+
// Remove any existing prerelease tag
228+
if (!string.IsNullOrEmpty(options.Version.Prerelease))
229+
{
230+
options.Version = new SemanticVersion(
231+
options.Version.Version,
232+
null,
233+
options.Version.BuildMetadata);
234+
}
235+
236+
options.Prerelease = null;
237+
return;
238+
}
239+
240+
// Validate that the version doesn't already have a prerelease tag (non-empty prerelease being applied)
241+
if (!string.IsNullOrEmpty(options.Version.Prerelease))
242+
{
243+
throw new InvalidOperationException("The 'prerelease' property cannot be used when the 'version' property already includes a prerelease tag.");
244+
}
245+
246+
// Apply the prerelease tag to the version
247+
string prereleaseTag = options.Prerelease;
248+
if (!prereleaseTag.StartsWith("-", StringComparison.Ordinal))
249+
{
250+
// Add the hyphen prefix if not present
251+
prereleaseTag = "-" + prereleaseTag;
252+
}
253+
254+
// Create a new SemanticVersion with the prerelease tag applied
255+
options.Version = new SemanticVersion(
256+
options.Version.Version,
257+
prereleaseTag,
258+
options.Version.BuildMetadata);
259+
260+
// Clear the Prerelease property since it has been applied
261+
options.Prerelease = null;
262+
}
263+
200264
protected static string TrimTrailingPathSeparator(string path)
201265
=> path.Length > 0 && (path[^1] == Path.DirectorySeparatorChar || path[^1] == Path.AltDirectorySeparatorChar) ? path[..^1] : path;
202266

@@ -319,6 +383,7 @@ protected void ApplyLocations(VersionOptions? options, string currentLocation, r
319383
versionJsonContent,
320384
result,
321385
VersionOptions.GetJsonSettings(repoRelativeBaseDirectory: repoRelativeBaseDirectory));
386+
ApplyPrereleaseProperty(result);
322387
result.Inherit = false;
323388
}
324389
}

src/NerdBank.GitVersioning/VersionOptions.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ public class VersionOptions : IEquatable<VersionOptions>
133133
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
134134
private bool inherit;
135135

136+
/// <summary>
137+
/// Backing field for the <see cref="Prerelease"/> property.
138+
/// </summary>
139+
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
140+
private string? prerelease;
141+
136142
/// <summary>
137143
/// Initializes a new instance of the <see cref="VersionOptions"/> class.
138144
/// </summary>
@@ -161,6 +167,7 @@ public VersionOptions(VersionOptions copyFrom)
161167
this.cloudBuild = copyFrom.cloudBuild is object ? new CloudBuildOptions(copyFrom.cloudBuild) : null;
162168
this.release = copyFrom.release is object ? new ReleaseOptions(copyFrom.release) : null;
163169
this.pathFilters = copyFrom.pathFilters?.ToList();
170+
this.prerelease = copyFrom.prerelease;
164171
}
165172

166173
/// <summary>
@@ -556,6 +563,27 @@ public bool Inherit
556563
set => this.SetIfNotReadOnly(ref this.inherit, value);
557564
}
558565

566+
/// <summary>
567+
/// Gets or sets a prerelease tag to append to an inherited version.
568+
/// </summary>
569+
/// <remarks>
570+
/// <para>
571+
/// This property is only valid when <see cref="Inherit"/> is <see langword="true"/> and the <see cref="Version"/> property
572+
/// does not already include a prerelease tag. When set, this prerelease tag will be appended to the version number
573+
/// inherited from the parent version.json file.
574+
/// </para>
575+
/// <para>
576+
/// Setting this to an empty string explicitly suppresses any prerelease tag that might be inherited.
577+
/// Omitting this property (leaving it as <see langword="null"/>) means the prerelease tag will be inherited as-is from the parent.
578+
/// </para>
579+
/// </remarks>
580+
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
581+
public string? Prerelease
582+
{
583+
get => this.prerelease;
584+
set => this.SetIfNotReadOnly(ref this.prerelease, value);
585+
}
586+
559587
/// <summary>
560588
/// Gets a value indicating whether this instance rejects all attempts to mutate it.
561589
/// </summary>

src/NerdBank.GitVersioning/version.schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"inheritingFile": {
1717
"allOf": [
1818
{ "$ref": "#/definitions/allProperties" },
19+
{ "$ref": "#/definitions/inheritingProperties" },
1920
{ "required": [ "inherit" ] },
2021
{
2122
"properties": {
@@ -217,6 +218,15 @@
217218
}
218219
}
219220
},
221+
"inheritingProperties": {
222+
"properties": {
223+
"prerelease": {
224+
"type": "string",
225+
"description": "A prerelease tag to append to the version inherited from a parent version.json file. This property can only be used when 'inherit' is true and the 'version' property does not include a prerelease tag. Set to an empty string to explicitly suppress an inherited prerelease tag. Omit this property to inherit the prerelease tag as-is from the parent.",
226+
"pattern": "^[\\da-zA-Z\\-]*$"
227+
}
228+
}
229+
},
220230
"twoToFourComponentVersion": {
221231
"type": "string",
222232
"description": "A major.minor[.build[.revision]] version (2-4 version components).",

0 commit comments

Comments
 (0)