Skip to content

Commit 68405a7

Browse files
committed
Provide greater flexibility in where git height appears in version info
1 parent df0750d commit 68405a7

File tree

11 files changed

+262
-40
lines changed

11 files changed

+262
-40
lines changed

src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ public async Task GetBuildVersion_Without_Git()
9898
Assert.Equal("3.4.0", buildResult.AssemblyInformationalVersion);
9999
}
100100

101+
[Fact]
102+
public async Task GetBuildVersion_WithThreeVersionIntegers()
103+
{
104+
VersionOptions workingCopyVersion = new VersionOptions
105+
{
106+
Version = SemanticVersion.Parse("7.8.9-beta.3"),
107+
};
108+
this.WriteVersionFile(workingCopyVersion);
109+
this.InitializeSourceControl();
110+
var buildResult = await this.BuildAsync();
111+
this.AssertStandardProperties(workingCopyVersion, buildResult);
112+
}
113+
101114
[Fact]
102115
public async Task GetBuildVersion_Without_Git_HighPrecisionAssemblyVersion()
103116
{
@@ -836,7 +849,6 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
836849
Assert.Equal($"{version}", buildResult.BuildVersion);
837850
Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersion3Components);
838851
Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildVersionNumberComponent);
839-
Assert.Equal(versionOptions.Version.Version.Build != -1 ? versionOptions.Version.Version.Build.ToString() : string.Empty, buildResult.BuildNumberFromVersionJson);
840852
Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersionSimple);
841853
Assert.Equal(this.Repo.Head.Commits.First().Id.Sha, buildResult.GitCommitId);
842854
Assert.Equal(commitIdShort, buildResult.GitCommitIdShort);
@@ -996,7 +1008,6 @@ internal BuildResults(BuildResult buildResult, IReadOnlyList<BuildEventArgs> log
9961008
public string PrereleaseVersion => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("PrereleaseVersion");
9971009
public string MajorMinorVersion => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("MajorMinorVersion");
9981010
public string BuildVersionNumberComponent => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildVersionNumberComponent");
999-
public string BuildNumberFromVersionJson => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildNumberFromVersionJson");
10001011
public string GitCommitIdShort => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("GitCommitIdShort");
10011012
public string GitVersionHeight => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("GitVersionHeight");
10021013
public string SemVerBuildSuffix => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("SemVerBuildSuffix");

src/NerdBank.GitVersioning.Tests/GitExtensionsTests.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public void GetIdAsVersion_MissingVersionTxt()
172172
}
173173

174174
[Fact]
175-
public void GetIdAsVersion_VersionFileNeverCheckedIn()
175+
public void GetIdAsVersion_VersionFileNeverCheckedIn_3Ints()
176176
{
177177
this.AddCommits();
178178
var expectedVersion = new Version(1, 1, 0);
@@ -182,6 +182,23 @@ public void GetIdAsVersion_VersionFileNeverCheckedIn()
182182
Assert.Equal(expectedVersion.Major, actualVersion.Major);
183183
Assert.Equal(expectedVersion.Minor, actualVersion.Minor);
184184
Assert.Equal(expectedVersion.Build, actualVersion.Build);
185+
186+
// Height is expressed in the 4th integer since 3 were specified in version.json.
187+
// height is 0 since the change hasn't been committed.
188+
Assert.Equal(0, actualVersion.Revision);
189+
}
190+
191+
[Fact]
192+
public void GetIdAsVersion_VersionFileNeverCheckedIn_2Ints()
193+
{
194+
this.AddCommits();
195+
var expectedVersion = new Version(1, 1);
196+
var unstagedVersionData = VersionOptions.FromVersion(expectedVersion);
197+
string versionFilePath = VersionFile.SetVersion(this.RepoPath, unstagedVersionData);
198+
Version actualVersion = this.Repo.GetIdAsVersion();
199+
Assert.Equal(expectedVersion.Major, actualVersion.Major);
200+
Assert.Equal(expectedVersion.Minor, actualVersion.Minor);
201+
Assert.Equal(0, actualVersion.Build); // height is 0 since the change hasn't been committed.
185202
Assert.Equal(this.Repo.Head.Commits.First().GetTruncatedCommitIdAsUInt16(), actualVersion.Revision);
186203
}
187204

src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</EmbeddedResource>
1010
<EmbeddedResource Include="Keys\*.snk" />
1111
<EmbeddedResource Include="Keys\*.pfx" />
12+
<EmbeddedResource Include="..\NerdBank.GitVersioning\version.schema.json" Link="version.schema.json" />
1213
<EmbeddedResource Include="test.prj" />
1314
<EmbeddedResource Include="repos\submodules.7z" />
1415
</ItemGroup>
@@ -18,6 +19,7 @@
1819
</ItemGroup>
1920
<ItemGroup>
2021
<PackageReference Include="7z.NET" Version="1.0.3" />
22+
<PackageReference Include="Newtonsoft.Json.Schema" Version="2.0.11" />
2123
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
2224
<PackageReference Include="Microsoft.Build" Version="14.3" Condition=" '$(TargetFramework)' == 'net45' " />
2325
<PackageReference Include="Xunit.Combinatorial" Version="1.1.12" />

src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,88 @@ public void Submodule_RecognizedWithCorrectVersion()
2828
Assert.Equal("3ea7f010c3", oracleB.GitCommitIdShort);
2929
}
3030
}
31+
32+
[Fact]
33+
public void MajorMinorPrereleaseBuildMetadata()
34+
{
35+
VersionOptions workingCopyVersion = new VersionOptions
36+
{
37+
Version = SemanticVersion.Parse("7.8-beta.3+metadata.4"),
38+
};
39+
this.WriteVersionFile(workingCopyVersion);
40+
this.InitializeSourceControl();
41+
var oracle = VersionOracle.Create(this.RepoPath);
42+
Assert.Equal("7.8", oracle.MajorMinorVersion.ToString());
43+
Assert.Equal(oracle.VersionHeight, oracle.BuildNumber);
44+
45+
Assert.Equal("-beta.3", oracle.PrereleaseVersion);
46+
////Assert.Equal("+metadata.4", oracle.BuildMetadataFragment);
47+
48+
Assert.Equal(1, oracle.VersionHeight);
49+
Assert.Equal(0, oracle.VersionHeightOffset);
50+
}
51+
52+
[Fact]
53+
public void MajorMinorBuildPrereleaseBuildMetadata()
54+
{
55+
VersionOptions workingCopyVersion = new VersionOptions
56+
{
57+
Version = SemanticVersion.Parse("7.8.9-beta.3+metadata.4"),
58+
};
59+
this.WriteVersionFile(workingCopyVersion);
60+
this.InitializeSourceControl();
61+
var oracle = VersionOracle.Create(this.RepoPath);
62+
Assert.Equal("7.8", oracle.MajorMinorVersion.ToString());
63+
Assert.Equal(9, oracle.BuildNumber);
64+
Assert.Equal(oracle.VersionHeight + oracle.VersionHeightOffset, oracle.Version.Revision);
65+
66+
Assert.Equal("-beta.3", oracle.PrereleaseVersion);
67+
////Assert.Equal("+metadata.4", oracle.BuildMetadataFragment);
68+
69+
Assert.Equal(1, oracle.VersionHeight);
70+
Assert.Equal(0, oracle.VersionHeightOffset);
71+
}
72+
73+
[Fact]
74+
public void HeightInPrerelease()
75+
{
76+
VersionOptions workingCopyVersion = new VersionOptions
77+
{
78+
Version = SemanticVersion.Parse("7.8.9-beta.{height}.foo"),
79+
BuildNumberOffset = 2,
80+
};
81+
this.WriteVersionFile(workingCopyVersion);
82+
this.InitializeSourceControl();
83+
var oracle = VersionOracle.Create(this.RepoPath);
84+
Assert.Equal("7.8", oracle.MajorMinorVersion.ToString());
85+
Assert.Equal(9, oracle.BuildNumber);
86+
Assert.Equal(oracle.VersionHeight + oracle.VersionHeightOffset, oracle.Version.Revision);
87+
88+
Assert.Equal("-beta." + (oracle.VersionHeight + oracle.VersionHeightOffset) + ".foo", oracle.PrereleaseVersion);
89+
90+
Assert.Equal(1, oracle.VersionHeight);
91+
Assert.Equal(2, oracle.VersionHeightOffset);
92+
}
93+
94+
[Fact(Skip = "Build metadata not yet retained from version.json")]
95+
public void HeightInBuildMetadata()
96+
{
97+
VersionOptions workingCopyVersion = new VersionOptions
98+
{
99+
Version = SemanticVersion.Parse("7.8.9-beta+another.{height}.foo"),
100+
BuildNumberOffset = 2,
101+
};
102+
this.WriteVersionFile(workingCopyVersion);
103+
this.InitializeSourceControl();
104+
var oracle = VersionOracle.Create(this.RepoPath);
105+
Assert.Equal("7.8", oracle.MajorMinorVersion.ToString());
106+
Assert.Equal(9, oracle.BuildNumber);
107+
Assert.Equal(oracle.VersionHeight + oracle.VersionHeightOffset, oracle.Version.Revision);
108+
109+
Assert.Equal("-beta", oracle.PrereleaseVersion);
110+
Assert.Equal("+another." + (oracle.VersionHeight + oracle.VersionHeightOffset) + ".foo", oracle.BuildMetadataFragment);
111+
112+
Assert.Equal(1, oracle.VersionHeight);
113+
Assert.Equal(2, oracle.VersionHeightOffset);
114+
}
31115
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.IO;
2+
using System.Reflection;
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Linq;
5+
using Newtonsoft.Json.Schema;
6+
using Xunit;
7+
using Xunit.Abstractions;
8+
9+
public class VersionSchemaTests
10+
{
11+
private readonly ITestOutputHelper Logger;
12+
13+
private readonly JSchema schema;
14+
15+
private JObject json;
16+
17+
public VersionSchemaTests(ITestOutputHelper logger)
18+
{
19+
this.Logger = logger;
20+
using (var schemaStream = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.version.schema.json")))
21+
{
22+
this.schema = JSchema.Load(new JsonTextReader(schemaStream));
23+
}
24+
}
25+
26+
[Fact]
27+
public void VersionField_BasicScenarios()
28+
{
29+
json = JObject.Parse(@"{ ""version"": ""2.3"" }");
30+
Assert.True(json.IsValid(this.schema));
31+
json = JObject.Parse(@"{ ""version"": ""2.3-beta"" }");
32+
Assert.True(json.IsValid(this.schema));
33+
json = JObject.Parse(@"{ ""version"": ""2.3-beta-final"" }");
34+
Assert.True(json.IsValid(this.schema));
35+
json = JObject.Parse(@"{ ""version"": ""2.3-beta.2"" }");
36+
Assert.True(json.IsValid(this.schema));
37+
json = JObject.Parse(@"{ ""version"": ""2.3-beta.0"" }");
38+
Assert.True(json.IsValid(this.schema));
39+
json = JObject.Parse(@"{ ""version"": ""2.3-beta.01"" }");
40+
Assert.True(json.IsValid(this.schema));
41+
json = JObject.Parse(@"{ ""version"": ""1.2.3"" }");
42+
Assert.True(json.IsValid(this.schema));
43+
json = JObject.Parse(@"{ ""version"": ""1.2.3.4"" }");
44+
Assert.True(json.IsValid(this.schema));
45+
46+
json = JObject.Parse(@"{ ""version"": ""02.3"" }");
47+
Assert.False(json.IsValid(this.schema));
48+
json = JObject.Parse(@"{ ""version"": ""2.03"" }");
49+
Assert.False(json.IsValid(this.schema));
50+
}
51+
52+
[Fact]
53+
public void VersionField_HeightMacroPlacement()
54+
{
55+
// Valid uses
56+
json = JObject.Parse(@"{ ""version"": ""2.3.0-{height}"" }");
57+
Assert.True(json.IsValid(this.schema));
58+
json = JObject.Parse(@"{ ""version"": ""2.3.0-{height}.beta"" }");
59+
Assert.True(json.IsValid(this.schema));
60+
json = JObject.Parse(@"{ ""version"": ""2.3.0-beta.{height}"" }");
61+
Assert.True(json.IsValid(this.schema));
62+
json = JObject.Parse(@"{ ""version"": ""2.3.0-beta+{height}"" }");
63+
Assert.True(json.IsValid(this.schema));
64+
65+
// Invalid uses
66+
json = JObject.Parse(@"{ ""version"": ""2.3.{height}-beta"" }");
67+
Assert.False(json.IsValid(this.schema));
68+
json = JObject.Parse(@"{ ""version"": ""2.3.0-beta-{height}"" }");
69+
Assert.False(json.IsValid(this.schema));
70+
json = JObject.Parse(@"{ ""version"": ""2.3.0-beta+height-{height}"" }");
71+
Assert.False(json.IsValid(this.schema));
72+
}
73+
}

src/NerdBank.GitVersioning/GitExtensions.cs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -512,26 +512,40 @@ private static void AddReachableCommitsFrom(Commit startingCommit, HashSet<Commi
512512
private static Version GetIdAsVersionHelper(Commit commit, VersionOptions versionOptions, string repoRelativeProjectDirectory, int? versionHeight)
513513
{
514514
var baseVersion = versionOptions?.Version?.Version ?? Version0;
515+
int buildNumber = baseVersion.Build;
516+
int revision = baseVersion.Revision;
515517

516-
// The compiler (due to WinPE header requirements) only allows 16-bit version components,
517-
// and forbids 0xffff as a value.
518-
// The build number is set to the git height. This helps ensure that
519-
// within a major.minor release, each patch has an incrementing integer.
520-
// The revision is set to the first two bytes of the git commit ID.
521-
if (!versionHeight.HasValue)
518+
if (revision < 0)
522519
{
523-
versionHeight = commit != null
524-
? commit.GetHeight(c => CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory))
525-
: 0;
526-
}
520+
// The compiler (due to WinPE header requirements) only allows 16-bit version components,
521+
// and forbids 0xffff as a value.
522+
// The build number is set to the git height. This helps ensure that
523+
// within a major.minor release, each patch has an incrementing integer.
524+
// The revision is set to the first two bytes of the git commit ID.
525+
if (!versionHeight.HasValue)
526+
{
527+
versionHeight = commit != null
528+
? commit.GetHeight(c => CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory))
529+
: 0;
530+
}
527531

528-
int build = versionHeight.Value == 0 ? 0 : versionHeight.Value + (versionOptions?.BuildNumberOffset ?? 0);
529-
Verify.Operation(build <= MaximumBuildNumberOrRevisionComponent, "Git height is {0}, which is greater than the maximum allowed {0}.", build, MaximumBuildNumberOrRevisionComponent);
530-
int revision = commit != null
531-
? Math.Min(MaximumBuildNumberOrRevisionComponent, commit.GetTruncatedCommitIdAsUInt16())
532-
: 0;
532+
int adjustedVersionHeight = versionHeight.Value == 0 ? 0 : versionHeight.Value + (versionOptions?.BuildNumberOffset ?? 0);
533+
Verify.Operation(adjustedVersionHeight <= MaximumBuildNumberOrRevisionComponent, "Git height is {0}, which is greater than the maximum allowed {0}.", adjustedVersionHeight, MaximumBuildNumberOrRevisionComponent);
534+
535+
if (buildNumber < 0)
536+
{
537+
buildNumber = adjustedVersionHeight;
538+
revision = commit != null
539+
? Math.Min(MaximumBuildNumberOrRevisionComponent, commit.GetTruncatedCommitIdAsUInt16())
540+
: 0;
541+
}
542+
else
543+
{
544+
revision = adjustedVersionHeight;
545+
}
546+
}
533547

534-
return new Version(baseVersion.Major, baseVersion.Minor, build, revision);
548+
return new Version(baseVersion.Major, baseVersion.Minor, buildNumber, revision);
535549
}
536550

537551
/// <summary>

src/NerdBank.GitVersioning/SemanticVersion.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,27 @@ public class SemanticVersion : IEquatable<SemanticVersion>
2323
/// <summary>
2424
/// The regex pattern that a prerelease must match.
2525
/// </summary>
26-
private static readonly Regex PrereleasePattern = new Regex(@"^-(?:[0-9A-Za-z-]+)(?:\.[0-9A-Za-z-]+)*$");
26+
/// <remarks>
27+
/// Keep in sync with the regex for the version field found in the version.schema.json file.
28+
/// </remarks>
29+
private static readonly Regex PrereleasePattern = new Regex("-(?:[\\da-z\\-]+|\\{height\\})(?:\\.(?:[\\da-z\\-]+|\\{height\\}))*", RegexOptions.IgnoreCase);
2730

2831
/// <summary>
2932
/// The regex pattern that build metadata must match.
3033
/// </summary>
31-
private static readonly Regex BuildMetadataPattern = new Regex(@"^\+(?:[0-9A-Za-z-]+)(?:\.[0-9A-Za-z-]+)*$");
34+
/// <remarks>
35+
/// Keep in sync with the regex for the version field found in the version.schema.json file.
36+
/// </remarks>
37+
private static readonly Regex BuildMetadataPattern = new Regex("\\+(?:[\\da-z\\-]+|\\{height\\})(?:\\.(?:[\\da-z\\-]+|\\{height\\}))*", RegexOptions.IgnoreCase);
38+
39+
/// <summary>
40+
/// The regular expression with capture groups for semantic versioning,
41+
/// allowing for macros such as {height}.
42+
/// </summary>
43+
/// <remarks>
44+
/// Keep in sync with the regex for the version field found in the version.schema.json file.
45+
/// </remarks>
46+
private static readonly Regex FullSemVerWithMacrosPattern = new Regex("^v?(?<major>0|[1-9][0-9]*)\\.(?<minor>0|[1-9][0-9]*)(?:\\.(?<patch>0|[1-9][0-9]*)(?:\\.(?<revision>0|[1-9][0-9]*))?)?(?<prerelease>" + PrereleasePattern + ")?(?<buildMetadata>" + BuildMetadataPattern + ")?$", RegexOptions.IgnoreCase);
3247

3348
/// <summary>
3449
/// Initializes a new instance of the <see cref="SemanticVersion"/> class.
@@ -90,7 +105,7 @@ public static bool TryParse(string semanticVersion, out SemanticVersion version)
90105
{
91106
Requires.NotNullOrEmpty(semanticVersion, nameof(semanticVersion));
92107

93-
Match m = FullSemVerPattern.Match(semanticVersion);
108+
Match m = FullSemVerWithMacrosPattern.Match(semanticVersion);
94109
if (m.Success)
95110
{
96111
var major = int.Parse(m.Groups["major"].Value);

0 commit comments

Comments
 (0)