Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion docfx/docs/versionJson.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ The content of the version.json file is a JSON serialized object with these prop
"versionIncrement" : "minor",
"firstUnstableTag" : "alpha"
},
"inherit": false // optional. Set to true in secondary version.json files used to tweak settings for subsets of projects.
"inherit": false, // optional. Set to true in secondary version.json files used to tweak settings for subsets of projects.
"prerelease": "beta" // optional. Only valid when inherit is true. Adds or overrides the prerelease tag of the inherited version.
}
```

Expand Down Expand Up @@ -109,4 +110,80 @@ In this example, the offset of 100 will be applied as long as the version remain
> [!NOTE]
> 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`.

## Prerelease Tag in Inheriting Files

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.

### Usage Rules

- The `prerelease` property can **only** be used when `"inherit": true` is set.
- The `version` property in the inheriting file must **not** include a prerelease tag (the `-suffix` part).
- Setting `"prerelease": "beta"` will append `-beta` to the inherited version.
- Setting `"prerelease": ""` (empty string) will explicitly **suppress** any prerelease tag inherited from the parent.
- Omitting the `prerelease` property will inherit the prerelease tag as-is from the parent.

### Examples

**Example 1: Adding a prerelease tag to a stable inherited version**

Parent `version.json` at repository root:
```json
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.2"
}
```

Child `version.json` in a subdirectory (e.g., `src/ExperimentalPackage/version.json`):
```json
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"inherit": true,
"prerelease": "beta"
}
```

Result: The subdirectory will use version `1.2-beta` while the rest of the repository uses `1.2`.

**Example 2: Suppressing an inherited prerelease tag**

Parent `version.json` at repository root:
```json
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.2-alpha"
}
```

Child `version.json` in a subdirectory (e.g., `src/StablePackage/version.json`):
```json
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"inherit": true,
"prerelease": ""
}
```

Result: The subdirectory will use version `1.2` (stable) while the rest of the repository uses `1.2-alpha`.

**Example 3: Inheriting the prerelease tag as-is**

Parent `version.json` at repository root:
```json
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.2-rc"
}
```

Child `version.json` in a subdirectory:
```json
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"inherit": true
}
```

Result: The subdirectory will inherit and use version `1.2-rc` from the parent.

[Learn more about pathFilters](path-filters.md).
1 change: 1 addition & 0 deletions src/NerdBank.GitVersioning/LibGit2/LibGit2VersionFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ internal LibGit2VersionFile(LibGit2Context context)
}

JsonConvert.PopulateObject(versionJsonContent, result, VersionOptions.GetJsonSettings(repoRelativeBaseDirectory: searchDirectory));
ApplyPrereleaseProperty(result);
result.Inherit = false;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/NerdBank.GitVersioning/Managed/ManagedVersionFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ public ManagedVersionFile(GitContext context)
}

JsonConvert.PopulateObject(versionJsonContent, result, VersionOptions.GetJsonSettings(repoRelativeBaseDirectory: searchDirectory));
ApplyPrereleaseProperty(result);
result.Inherit = false;
}
else
Expand Down
65 changes: 65 additions & 0 deletions src/NerdBank.GitVersioning/VersionFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,70 @@ protected static bool VersionOptionsSatisfyRequirements(VersionOptions? options,
return true;
}

/// <summary>
/// Applies the standalone <see cref="VersionOptions.Prerelease"/> property to the <see cref="VersionOptions.Version"/> property.
/// </summary>
/// <param name="options">The version options to modify.</param>
/// <remarks>
/// This method should be called after merging a child version.json with a parent version.json.
/// If the child specified a <see cref="VersionOptions.Prerelease"/> property, this method will apply it to the version.
/// </remarks>
protected static void ApplyPrereleaseProperty(VersionOptions options)
{
Requires.NotNull(options, nameof(options));

// Only apply if the Prerelease property was explicitly set
if (options.Prerelease is null)
{
return;
}

// The version must exist to apply a prerelease tag
if (options.Version is null)
{
throw new InvalidOperationException("The 'prerelease' property cannot be used without a 'version' property.");
}

// If prerelease is an empty string, it suppresses any inherited prerelease tag
if (options.Prerelease == string.Empty)
{
// Remove any existing prerelease tag
if (!string.IsNullOrEmpty(options.Version.Prerelease))
{
options.Version = new SemanticVersion(
options.Version.Version,
null,
options.Version.BuildMetadata);
}

options.Prerelease = null;
return;
}

// Validate that the version doesn't already have a prerelease tag (non-empty prerelease being applied)
if (!string.IsNullOrEmpty(options.Version.Prerelease))
{
throw new InvalidOperationException("The 'prerelease' property cannot be used when the 'version' property already includes a prerelease tag.");
}

// Apply the prerelease tag to the version
string prereleaseTag = options.Prerelease;
if (!prereleaseTag.StartsWith("-", StringComparison.Ordinal))
{
// Add the hyphen prefix if not present
prereleaseTag = "-" + prereleaseTag;
}

// Create a new SemanticVersion with the prerelease tag applied
options.Version = new SemanticVersion(
options.Version.Version,
prereleaseTag,
options.Version.BuildMetadata);

// Clear the Prerelease property since it has been applied
options.Prerelease = null;
}

protected static string TrimTrailingPathSeparator(string path)
=> path.Length > 0 && (path[^1] == Path.DirectorySeparatorChar || path[^1] == Path.AltDirectorySeparatorChar) ? path[..^1] : path;

Expand Down Expand Up @@ -319,6 +383,7 @@ protected void ApplyLocations(VersionOptions? options, string currentLocation, r
versionJsonContent,
result,
VersionOptions.GetJsonSettings(repoRelativeBaseDirectory: repoRelativeBaseDirectory));
ApplyPrereleaseProperty(result);
result.Inherit = false;
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/NerdBank.GitVersioning/VersionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ public class VersionOptions : IEquatable<VersionOptions>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private bool inherit;

/// <summary>
/// Backing field for the <see cref="Prerelease"/> property.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string? prerelease;

/// <summary>
/// Initializes a new instance of the <see cref="VersionOptions"/> class.
/// </summary>
Expand Down Expand Up @@ -161,6 +167,7 @@ public VersionOptions(VersionOptions copyFrom)
this.cloudBuild = copyFrom.cloudBuild is object ? new CloudBuildOptions(copyFrom.cloudBuild) : null;
this.release = copyFrom.release is object ? new ReleaseOptions(copyFrom.release) : null;
this.pathFilters = copyFrom.pathFilters?.ToList();
this.prerelease = copyFrom.prerelease;
}

/// <summary>
Expand Down Expand Up @@ -556,6 +563,27 @@ public bool Inherit
set => this.SetIfNotReadOnly(ref this.inherit, value);
}

/// <summary>
/// Gets or sets a prerelease tag to append to an inherited version.
/// </summary>
/// <remarks>
/// <para>
/// This property is only valid when <see cref="Inherit"/> is <see langword="true"/> and the <see cref="Version"/> property
/// does not already include a prerelease tag. When set, this prerelease tag will be appended to the version number
/// inherited from the parent version.json file.
/// </para>
/// <para>
/// Setting this to an empty string explicitly suppresses any prerelease tag that might be inherited.
/// Omitting this property (leaving it as <see langword="null"/>) means the prerelease tag will be inherited as-is from the parent.
/// </para>
/// </remarks>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string? Prerelease
{
get => this.prerelease;
set => this.SetIfNotReadOnly(ref this.prerelease, value);
}

/// <summary>
/// Gets a value indicating whether this instance rejects all attempts to mutate it.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/NerdBank.GitVersioning/version.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"inheritingFile": {
"allOf": [
{ "$ref": "#/definitions/allProperties" },
{ "$ref": "#/definitions/inheritingProperties" },
{ "required": [ "inherit" ] },
{
"properties": {
Expand Down Expand Up @@ -217,6 +218,15 @@
}
}
},
"inheritingProperties": {
"properties": {
"prerelease": {
"type": "string",
"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.",
"pattern": "^[\\da-zA-Z\\-]*$"
}
}
},
"twoToFourComponentVersion": {
"type": "string",
"description": "A major.minor[.build[.revision]] version (2-4 version components).",
Expand Down
Loading
Loading