Skip to content

Commit 4d80666

Browse files
committed
Improve semver1 conversion from semver2
1 parent 531f7b6 commit 4d80666

File tree

6 files changed

+103
-3
lines changed

6 files changed

+103
-3
lines changed

doc/versionJson.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ The content of the version.json file is a JSON serialized object with these prop
2424
"version": "x.y-prerelease", // required
2525
"assemblyVersion": "x.y", // optional. Use when x.y for AssemblyVersionAttribute differs from the default version property.
2626
"buildNumberOffset": "zOffset", // optional. Use when you need to add/subtract a fixed value from the computed build number.
27+
"semVer1NumericIdentifierPadding": 4, // optional. Use when your -prerelease includes numeric identifiers and need semver1 support.
2728
"publicReleaseRefSpec": [
2829
"^refs/heads/master$", // we release out of master
29-
"^refs/tags/v\\d\\.\\d" // we also release tags starting with vN.N
30+
"^refs/tags/v\\d+\\.\\d+" // we also release tags starting with vN.N
3031
],
3132
"cloudBuild": {
3233
"setVersionVariables": true,
@@ -43,6 +44,11 @@ The content of the version.json file is a JSON serialized object with these prop
4344

4445
The `x` and `y` variables are for your use to specify a version that is meaningful
4546
to your customers. Consider using [semantic versioning][semver] for guidance.
47+
You may optionally supply a third integer in the version (i.e. x.y.z),
48+
in which case the git version height is specified as the fourth integer,
49+
which only appears in certain version representations.
50+
Alternatively, you can include the git version height in the -prerelease tag using
51+
syntax such as: `1.2.3-beta.{height}`
4652

4753
The optional -prerelease tag allows you to indicate that you are building prerelease software.
4854

src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public async Task GetBuildVersion_WithThreeVersionIntegers()
104104
VersionOptions workingCopyVersion = new VersionOptions
105105
{
106106
Version = SemanticVersion.Parse("7.8.9-beta.3"),
107+
SemVer1NumericIdentifierPadding = 1,
107108
};
108109
this.WriteVersionFile(workingCopyVersion);
109110
this.InitializeSourceControl();
@@ -860,7 +861,7 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
860861
string pkgVersionSuffix = buildResult.PublicRelease
861862
? string.Empty
862863
: $"-g{commitIdShort}";
863-
Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{versionOptions.Version.Prerelease}{pkgVersionSuffix}", buildResult.NuGetPackageVersion);
864+
Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{GetSemVer1PrereleaseTag(versionOptions)}{pkgVersionSuffix}", buildResult.NuGetPackageVersion);
864865

865866
var buildNumberOptions = versionOptions.CloudBuild?.BuildNumber ?? new VersionOptions.CloudBuildNumberOptions();
866867
if (buildNumberOptions.Enabled)
@@ -892,6 +893,11 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
892893
}
893894
}
894895

896+
private static string GetSemVer1PrereleaseTag(VersionOptions versionOptions)
897+
{
898+
return versionOptions.Version.Prerelease?.Replace('.', '-');
899+
}
900+
895901
private async Task<BuildResults> BuildAsync(string target = Targets.GetBuildVersion, LoggerVerbosity logVerbosity = LoggerVerbosity.Detailed, bool assertSuccessfulBuild = true)
896902
{
897903
var eventLogger = new MSBuildLogger { Verbosity = LoggerVerbosity.Minimal };

src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs

+37
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,41 @@ public void HeightInBuildMetadata()
112112
Assert.Equal(1, oracle.VersionHeight);
113113
Assert.Equal(2, oracle.VersionHeightOffset);
114114
}
115+
116+
[Theory]
117+
[InlineData("7.8.9-foo.25", "7.8.9-foo-0025")]
118+
[InlineData("7.8.9-foo.25s", "7.8.9-foo-25s")]
119+
[InlineData("7.8.9-foo.s25", "7.8.9-foo-s25")]
120+
[InlineData("7.8.9-foo.25.bar-24.13-11", "7.8.9-foo-0025-bar-24-13-11")]
121+
[InlineData("7.8.9-25.bar.baz-25", "7.8.9-0025-bar-baz-25")]
122+
[InlineData("7.8.9-foo.5.bar.1.43.baz", "7.8.9-foo-0005-bar-0001-0043-baz")]
123+
public void SemVer1PrereleaseConversion(string semVer2, string semVer1)
124+
{
125+
VersionOptions workingCopyVersion = new VersionOptions
126+
{
127+
Version = SemanticVersion.Parse(semVer2),
128+
BuildNumberOffset = 2,
129+
};
130+
this.WriteVersionFile(workingCopyVersion);
131+
this.InitializeSourceControl();
132+
var oracle = VersionOracle.Create(this.RepoPath);
133+
oracle.PublicRelease = true;
134+
Assert.Equal(semVer1, oracle.SemVer1);
135+
}
136+
137+
[Fact]
138+
public void SemVer1PrereleaseConversionPadding()
139+
{
140+
VersionOptions workingCopyVersion = new VersionOptions
141+
{
142+
Version = SemanticVersion.Parse("7.8.9-foo.25"),
143+
BuildNumberOffset = 2,
144+
SemVer1NumericIdentifierPadding = 3,
145+
};
146+
this.WriteVersionFile(workingCopyVersion);
147+
this.InitializeSourceControl();
148+
var oracle = VersionOracle.Create(this.RepoPath);
149+
oracle.PublicRelease = true;
150+
Assert.Equal("7.8.9-foo-025", oracle.SemVer1);
151+
}
115152
}

src/NerdBank.GitVersioning/VersionOptions.cs

+6
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ public class VersionOptions : IEquatable<VersionOptions>
5959
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
6060
public int BuildNumberOffset { get; set; }
6161

62+
/// <summary>
63+
/// Gets or sets the minimum number of digits to use for numeric identifiers in SemVer 1.
64+
/// </summary>
65+
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
66+
public int? SemVer1NumericIdentifierPadding { get; set; }
67+
6268
/// <summary>
6369
/// Gets or sets an array of regular expressions that describes branch or tag names that should
6470
/// be built with PublicRelease=true as the default value on build servers.

src/NerdBank.GitVersioning/VersionOracle.cs

+39-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
/// </summary>
1616
public class VersionOracle
1717
{
18+
/// <summary>
19+
/// A regex that matches on numeric identifiers for prerelease or build metadata.
20+
/// </summary>
21+
private static readonly Regex NumericIdentifierRegex = new Regex(@"(?<![\w-])(\d+)(?![\w-])");
22+
1823
/// <summary>
1924
/// The cloud build suppport, if any.
2025
/// </summary>
@@ -249,7 +254,7 @@ public IDictionary<string, string> CloudBuildVersionVars
249254
/// when <see cref="PublicRelease"/> is <c>false</c>.
250255
/// </summary>
251256
public string SemVer1 =>
252-
$"{this.Version.ToStringSafe(3)}{this.PrereleaseVersion}{this.SemVer1BuildMetadata}";
257+
$"{this.Version.ToStringSafe(3)}{this.PrereleaseVersionSemVer1}{this.SemVer1BuildMetadata}";
253258

254259
/// <summary>
255260
/// Gets a SemVer 2.0 compliant string that represents this version, including a +gCOMMITID suffix
@@ -258,12 +263,19 @@ public IDictionary<string, string> CloudBuildVersionVars
258263
public string SemVer2 =>
259264
$"{this.Version.ToStringSafe(3)}{this.PrereleaseVersion}{this.SemVer2BuildMetadata}";
260265

266+
/// <summary>
267+
/// Gets the minimum number of digits to use for numeric identifiers in SemVer 1.
268+
/// </summary>
269+
public int SemVer1NumericIdentifierPadding => this.VersionOptions?.SemVer1NumericIdentifierPadding ?? 4;
270+
261271
private string SemVer1BuildMetadata =>
262272
this.PublicRelease ? string.Empty : $"-g{this.GitCommitIdShort}";
263273

264274
private string SemVer2BuildMetadata =>
265275
FormatBuildMetadata(this.PublicRelease ? this.BuildMetadata : this.BuildMetadataWithCommitId);
266276

277+
private string PrereleaseVersionSemVer1 => MakePrereleaseSemVer1Compliant(this.PrereleaseVersion, SemVer1NumericIdentifierPadding);
278+
267279
private VersionOptions.CloudBuildNumberOptions CloudBuildNumberOptions { get; }
268280

269281
private int VersionHeightWithOffset => this.VersionHeight + this.VersionHeightOffset;
@@ -343,5 +355,31 @@ private static Version GetAssemblyVersion(Version version, VersionOptions versio
343355
/// <param name="prereleaseOrBuildMetadata">The prerelease or build metadata.</param>
344356
/// <returns>The specified string, with macros substituted for actual values.</returns>
345357
private string ReplaceMacros(string prereleaseOrBuildMetadata) => prereleaseOrBuildMetadata?.Replace("{height}", this.VersionHeightWithOffset.ToString(CultureInfo.InvariantCulture));
358+
359+
/// <summary>
360+
/// Converts a semver 2 compliant "-beta.5" prerelease tag to a semver 1 compatible one.
361+
/// </summary>
362+
/// <param name="prerelease">The semver 2 prerelease tag, including its leading hyphen.</param>
363+
/// <param name="paddingSize">The minimum number of digits to use for any numeric identifier.</param>
364+
/// <returns>A semver 1 compliant prerelease tag. For example "-beta-0005".</returns>
365+
private static string MakePrereleaseSemVer1Compliant(string prerelease, int paddingSize)
366+
{
367+
if (string.IsNullOrEmpty(prerelease))
368+
{
369+
return prerelease;
370+
}
371+
372+
string paddingFormatter = "{0:" + new string('0', paddingSize) + "}";
373+
374+
string semver1 = prerelease;
375+
376+
// Identify numeric identifiers and pad them.
377+
Assumes.True(prerelease.StartsWith("-"));
378+
semver1 = "-" + NumericIdentifierRegex.Replace(semver1.Substring(1), m => string.Format(CultureInfo.InvariantCulture, paddingFormatter, int.Parse(m.Groups[1].Value)));
379+
380+
semver1 = semver1.Replace('.', '-');
381+
382+
return semver1;
383+
}
346384
}
347385
}

src/NerdBank.GitVersioning/version.schema.json

+7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
"description": "A number to add to the git height when calculating the build number.",
3333
"default": 0
3434
},
35+
"semVer1NumericIdentifierPadding": {
36+
"type": "integer",
37+
"description": "The minimum number of digits to use for numeric identifiers in SemVer 1.",
38+
"default": 4,
39+
"minimum": 1,
40+
"maximum": 6
41+
},
3542
"publicReleaseRefSpec": {
3643
"type": "array",
3744
"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)",

0 commit comments

Comments
 (0)