Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into IsNullmaster
Browse files Browse the repository at this point in the history
  • Loading branch information
AArnott committed Aug 23, 2021
2 parents 9ead478 + 5789043 commit 813cbf1
Show file tree
Hide file tree
Showing 19 changed files with 210 additions and 66 deletions.
75 changes: 75 additions & 0 deletions doc/msbuild.md
Expand Up @@ -2,6 +2,23 @@

Installing the `Nerdbank.GitVersioning` package from NuGet into your MSBuild-based projects is the recommended way to add version information to your MSBuild project outputs including assemblies, native dll's, and packages.

If you want the package to affect all the projects in your repo, and you use `PackageReference` (rather than `packages.config`),
you can add this to a repo-level `Directory.Build.props` file:

```xml
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning"
Version="(latest-version-here)"
PrivateAssets="all"
Condition="!Exists('packages.config')" />
</ItemGroup>
```

The condition prevents the `PackageReference` from impacting any `packages.config`-based projects
such as vcxproj that may be in your repo.
Such projects will require individual installation of the Nerdbank.GitVersioning nuget package
using the NuGet Package Manager in Visual Studio.

## Public releases

By default, each build of a Nuget package will include the git commit ID.
Expand All @@ -21,6 +38,64 @@ You should only build with this property set from one release branch per
major.minor version to avoid the risk of producing multiple unique NuGet
packages with a colliding version spec.

## Custom build authoring

If you are writing your own MSBuild targets or properties and need to consume version information,
Nerdbank.GitVersioning is there to help.
The version information created by this package is expressed as MSBuild properties.
These properties are set by the `GetBuildVersion` target defined in this package.
This means any dependency you have on these properties must ensure this target has already executed.
This can be done by defining your own msbuild target like so:

```xml
<Target Name="MyPropertySetter" DependsOnTargets="GetBuildVersion">
<PropertyGroup>
<MyOwnProperty>My assembly version is: $(AssemblyVersion)</MyOwnProperty>
</PropertyGroup>
</Target>
```

In the above example, the `AssemblyVersion` property, which is set by the
`GetBuildVersion` target defined by Nerdbank.GitVersioning, is used to define
another property.
It could also be used to define msbuild items or anything else you want.

### MSBuild properties defined in `GetBuildVersion`

Many MSBuild properties are set by `GetBuildVersion` allowing you to define or redefine
properties in virtually any format you like.
The authoritative list is [here](https://github.com/dotnet/Nerdbank.GitVersioning/blob/dae20a6d15f04d8161fd092c36fdf1f57c021ba1/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs#L300-L323) (switch to the default branch to see the current set).

Below is a snapshot of the properties with example values.
Note that the values and formats can vary depending on your `version.json` settings and version height.

Property | Example value
--|--
AssemblyFileVersion | 2.7.74.11627
AssemblyInformationalVersion | 2.7.74-alpha+6b2d14ba68
AssemblyVersion | 2.7.0.0
BuildingRef | refs/heads/fix299
BuildNumber | 74
BuildVersion | 2.7.74.11627
BuildVersion3Components | 2.7.74
BuildVersionNumberComponent | 74
BuildVersionSimple | 2.7.74
ChocolateyPackageVersion | 2.7.74-alpha-g6b2d14ba68
CloudBuildNumber | (empty except in cloud build)
FileVersion | 2.7.74.11627
GitCommitDateTicks | 637547960670000000
GitCommitId | 6b2d14ba6844d2152c48268a8d2c1933759e7229
GitCommitIdShort | 6b2d14ba68
GitVersionHeight | 74
MajorMinorVersion | 2.7
NPMPackageVersion | 2.7.74-alpha.g6b2d14ba68
NuGetPackageVersion | 2.7.74-alpha-g6b2d14ba68
PackageVersion | 2.7.74-alpha-g6b2d14ba68
PrereleaseVersion | -alpha
PublicRelease | False
SemVerBuildSuffix | +6b2d14ba68
Version | 2.7.74-alpha-g6b2d14ba68

## Build performance

Repos with many projects or many commits between major/minor version bumps can suffer from compromised build performance due to the MSBuild task that computes the version information for each project.
Expand Down
2 changes: 1 addition & 1 deletion doc/public_vs_stable.md
Expand Up @@ -25,7 +25,7 @@ When a branch becomes stable, the `-prerelease` tag can be removed by adding a c
To remove the `-prerelease`, the version.json file must be changed to remove it.
Committing this change communicates to everyone looking at the repo that this software is stable.

The natural evolution of a product includes usually includes entering and exiting a `-prerelease` stage many times, but within a branded release (usually recognized by an intentional version number like "1.2") the progression usually transitions only one direction: from `-prerelease` to stable quality.
The natural evolution of a product usually includes entering and exiting a `-prerelease` stage many times, but within a branded release (usually recognized by an intentional version number like "1.2") the progression usually transitions only one direction: from `-prerelease` to stable quality.
For example, an anticipated version 1.2 might first be released to the public as 1.2-beta before releasing as 1.2 (without the `-beta` suffix).
If the product is undergoing significant changes that warrant downgrading the stability rating to pre-release quality, the version number tends to be incremented at the same time.
So a 1.2 product's subsequent release might appear as 1.3-beta or 2.0-beta.
Expand Down
8 changes: 4 additions & 4 deletions src/Cake.GitVersioning/Cake.GitVersioning.csproj
Expand Up @@ -8,9 +8,9 @@
<Company>andarno</Company>
<Description>Cake wrapper for Nerdbank.GitVersioning. Stamps your assemblies with semver 2.0 compliant git commit specific version information and provides NuGet versioning information as well.</Description>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageTags>git commit versioning version assemblyinfo</PackageTags>
<PackageIconUrl>https://cdn.jsdelivr.net/gh/cake-contrib/graphics/png/cake-contrib-medium.png</PackageIconUrl>
<PackageIcon>cake-contrib-medium.png</PackageIcon>
<PackageTags>git commit versioning version assemblyinfo cake-addin</PackageTags>
<PackageIconUrl>https://cdn.jsdelivr.net/gh/cake-contrib/graphics/png/addin/cake-contrib-addin-medium.png</PackageIconUrl>
<PackageIcon>cake-contrib-addin-medium.png</PackageIcon>
<PackageProjectUrl>http://github.com/dotnet/Nerdbank.GitVersioning</PackageProjectUrl>
<SignAssembly>false</SignAssembly>

Expand All @@ -36,7 +36,7 @@
</ItemGroup>

<ItemGroup>
<None Include="cake-contrib-medium.png" Pack="true" PackagePath="" />
<None Include="cake-contrib-addin-medium.png" Pack="true" PackagePath="" />

<!-- Include native binaries -->
<None Include="$(LibGit2SharpNativeBinaries)runtimes\**\*.*" Pack="true" PackagePath="lib\netstandard2.0\lib\" LinkBase="lib" />
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/Cake.GitVersioning/cake-contrib-medium.png
Binary file not shown.
@@ -1,4 +1,5 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand All @@ -11,9 +12,9 @@
using Xunit.Abstractions;
using Version = System.Version;

public partial class GitExtensionsTests : RepoTestBase
public class LibGit2GitExtensionsTests : RepoTestBase
{
public GitExtensionsTests(ITestOutputHelper Logger)
public LibGit2GitExtensionsTests(ITestOutputHelper Logger)
: base(Logger)
{
this.InitializeSourceControl();
Expand Down Expand Up @@ -166,22 +167,6 @@ public void GetVersionHeight_ProgressAndReset(string version1, string version2,
Assert.Equal(!versionHeightReset, height2 > height1);
}

[Fact]
public void GetTruncatedCommitIdAsInteger_Roundtrip()
{
var firstCommit = this.LibGit2Repository.Commit("First", this.Signer, this.Signer, new CommitOptions { AllowEmptyCommit = true });
var secondCommit = this.LibGit2Repository.Commit("Second", this.Signer, this.Signer, new CommitOptions { AllowEmptyCommit = true });

int id1 = firstCommit.GetTruncatedCommitIdAsInt32();
int id2 = secondCommit.GetTruncatedCommitIdAsInt32();

this.Logger.WriteLine($"Commit {firstCommit.Id.Sha.Substring(0, 8)} as int: {id1}");
this.Logger.WriteLine($"Commit {secondCommit.Id.Sha.Substring(0, 8)} as int: {id2}");

Assert.Equal(firstCommit, this.LibGit2Repository.GetCommitFromTruncatedIdInteger(id1));
Assert.Equal(secondCommit, this.LibGit2Repository.GetCommitFromTruncatedIdInteger(id2));
}

[Fact]
public void GetIdAsVersion_ReadsMajorMinorFromVersionTxt()
{
Expand Down Expand Up @@ -384,6 +369,19 @@ public void GetIdAsVersion_Roundtrip_UnstableOffset(int startingOffset, int offs
}
}

[Fact]
public void GetCommitsFromVersion_MatchesOnEitherEndian()
{
this.InitializeSourceControl();
Commit commit = this.WriteVersionFile(new VersionOptions { Version = SemanticVersion.Parse("1.2"), GitCommitIdShortAutoMinimum = 4 });

Version originalVersion = new VersionOracle(this.Context).Version;
Version swappedEndian = new Version(originalVersion.Major, originalVersion.Minor, originalVersion.Build, BinaryPrimitives.ReverseEndianness((ushort)originalVersion.Revision));
ushort twoBytesFromCommitId = checked((ushort)originalVersion.Revision);
Assert.Contains(commit, LibGit2GitExtensions.GetCommitsFromVersion(this.Context, originalVersion));
Assert.Contains(commit, LibGit2GitExtensions.GetCommitsFromVersion(this.Context, swappedEndian));
}

[Fact]
public void GetIdAsVersion_Roundtrip_WithSubdirectoryVersionFiles()
{
Expand Down
Expand Up @@ -93,7 +93,7 @@ public void AsUInt16Test()
{
// The hash code is the int32 representation of the first 4 bytes
var objectId = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray);
Assert.Equal(0x914e, objectId.AsUInt16());
Assert.Equal(0x4e91, objectId.AsUInt16());
Assert.Equal(0, GitObjectId.Empty.GetHashCode());
}

Expand Down
20 changes: 19 additions & 1 deletion src/NerdBank.GitVersioning.Tests/ReleaseManagerTests.cs
Expand Up @@ -392,12 +392,30 @@ public void PrepareRelease_MasterWithVersionDecrement(string initialVersion, str
this.WriteVersionFile(versionOptions);

// running PrepareRelease should result in an error
// because we're trying to add a prerelease tag to a version without prerelease tag
// because we're setting the version on master to a lower version
this.AssertError(
() => new ReleaseManager().PrepareRelease(this.RepoPath, releaseUnstableTag, (nextVersion is null ? null : Version.Parse(nextVersion))),
ReleasePreparationError.VersionDecrement);
}

[Theory]
[InlineData("1.2", "1.2")]
public void PrepareRelease_MasterWithoutVersionIncrement(string initialVersion, string nextVersion)
{
// create and configure repository
this.InitializeSourceControl();

// create version.json
var versionOptions = new VersionOptions() { Version = SemanticVersion.Parse(initialVersion) };
this.WriteVersionFile(versionOptions);

// running PrepareRelease should result in an error
// because we're trying to set master to the version it already has
this.AssertError(
() => new ReleaseManager().PrepareRelease(this.RepoPath, null, (nextVersion is null ? null : Version.Parse(nextVersion))),
ReleasePreparationError.NoVersionIncrement);
}

[Fact]
public void PrepareRelease_DetachedHead()
{
Expand Down
4 changes: 4 additions & 0 deletions src/NerdBank.GitVersioning.Tests/TestUtilities.cs
Expand Up @@ -79,6 +79,10 @@ internal static ExpandedRepo ExtractRepoArchive(string repoArchiveName)
}
}

internal static string ToHex(ushort number) => number.ToString("X");

internal static ushort FromHex(string hex) => ushort.Parse(hex, System.Globalization.NumberStyles.HexNumber);

internal class ExpandedRepo : IDisposable
{
internal ExpandedRepo(string repoPath)
Expand Down
17 changes: 17 additions & 0 deletions src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs
Expand Up @@ -845,6 +845,23 @@ public void GitCommitIdShort()
}
}

[Fact]
public void GitCommidIdLeading16BitsDecodedWithBigEndian()
{
this.WriteVersionFile(new VersionOptions { Version = SemanticVersion.Parse("1.2"), GitCommitIdShortAutoMinimum = 4 });
this.InitializeSourceControl();
this.AddCommits(1);
var oracle = new VersionOracle(this.Context);

string leadingFourChars = this.Context.GitCommitId.Substring(0, 4);
ushort expectedNumber = TestUtilities.FromHex(leadingFourChars);
ushort actualNumber = checked((ushort)oracle.Version.Revision);
this.Logger.WriteLine("First two characters from commit ID in hex is {0}", leadingFourChars);
this.Logger.WriteLine("First two characters, converted to a number is {0}", expectedNumber);
this.Logger.WriteLine("Generated 16-bit ushort from commit ID is {0}, whose hex representation is {1}", actualNumber, TestUtilities.ToHex(actualNumber));
Assert.Equal(expectedNumber, actualNumber);
}

[Fact(Skip = "Slow test")]
public void GetVersionHeight_VeryLongHistory()
{
Expand Down
1 change: 1 addition & 0 deletions src/NerdBank.GitVersioning/CloudBuild.cs
Expand Up @@ -21,6 +21,7 @@ public static class CloudBuild
new Jenkins(),
new GitLab(),
new Travis(),
new SpaceAutomation(),
};

/// <summary>
Expand Down
38 changes: 38 additions & 0 deletions src/NerdBank.GitVersioning/CloudBuildServices/SpaceAutomation.cs
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;

namespace Nerdbank.GitVersioning.CloudBuildServices
{
/// <summary>
/// SpaceAutomation CI build support.
/// </summary>
/// <remarks>
/// The SpaceAutomation-specific properties referenced here are documented here:
/// https://www.jetbrains.com/help/space/automation-environment-variables.html
/// </remarks>
internal class SpaceAutomation : ICloudBuild
{
public string BuildingBranch => CloudBuild.IfStartsWith(BuildingRef, "refs/heads/");

public string BuildingTag => CloudBuild.IfStartsWith(BuildingRef, "refs/tags/");

public string GitCommitId => Environment.GetEnvironmentVariable("JB_SPACE_GIT_REVISION");

public bool IsApplicable => this.GitCommitId is not null;

public bool IsPullRequest => false;

private static string BuildingRef => Environment.GetEnvironmentVariable("JB_SPACE_GIT_BRANCH");

public IReadOnlyDictionary<string, string> SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr)
{
return new Dictionary<string, string>();
}

public IReadOnlyDictionary<string, string> SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr)
{
return new Dictionary<string, string>();
}
}
}
4 changes: 2 additions & 2 deletions src/NerdBank.GitVersioning/CloudBuildServices/TeamCity.cs
Expand Up @@ -8,8 +8,8 @@
/// TeamCity CI build support.
/// </summary>
/// <remarks>
/// The TeamCIty-specific properties referenced here are documented here:
/// https://confluence.jetbrains.com/display/TCD8/Predefined+Build+Parameters
/// The TeamCity-specific properties referenced here are documented here:
/// https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html
/// </remarks>
internal class TeamCity : ICloudBuild
{
Expand Down
41 changes: 10 additions & 31 deletions src/NerdBank.GitVersioning/LibGit2/LibGit2GitExtensions.cs
Expand Up @@ -3,8 +3,8 @@
namespace Nerdbank.GitVersioning.LibGit2
{
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -97,18 +97,6 @@ public static int GetHeight(LibGit2Context context, Func<Commit, bool>? continue
return GetCommitHeight(context.Commit, tracker, continueStepping);
}

/// <summary>
/// Takes the first 4 bytes of a commit ID (i.e. first 8 characters of its hex-encoded SHA)
/// and returns them as an integer.
/// </summary>
/// <param name="commit">The commit to identify with an integer.</param>
/// <returns>The integer which identifies a commit.</returns>
public static int GetTruncatedCommitIdAsInt32(this Commit commit)
{
Requires.NotNull(commit, nameof(commit));
return BitConverter.ToInt32(commit.Id.RawId, 0);
}

/// <summary>
/// Takes the first 2 bytes of a commit ID (i.e. first 4 characters of its hex-encoded SHA)
/// and returns them as an 16-bit unsigned integer.
Expand All @@ -118,21 +106,7 @@ public static int GetTruncatedCommitIdAsInt32(this Commit commit)
public static ushort GetTruncatedCommitIdAsUInt16(this Commit commit)
{
Requires.NotNull(commit, nameof(commit));
return BitConverter.ToUInt16(commit.Id.RawId, 0);
}

/// <summary>
/// Looks up a commit by an integer that captures the first for bytes of its ID.
/// </summary>
/// <param name="repo">The repo to search for a matching commit.</param>
/// <param name="truncatedId">The value returned from <see cref="GetTruncatedCommitIdAsInt32(Commit)"/>.</param>
/// <returns>A matching commit.</returns>
public static Commit GetCommitFromTruncatedIdInteger(this Repository repo, int truncatedId)
{
Requires.NotNull(repo, nameof(repo));

byte[] rawId = BitConverter.GetBytes(truncatedId);
return repo.Lookup<Commit>(EncodeAsHex(rawId));
return BinaryPrimitives.ReadUInt16BigEndian(commit.Id.RawId);
}

/// <summary>
Expand Down Expand Up @@ -310,7 +284,11 @@ private static bool IsCommitIdMismatch(Version version, VersionOptions versionOp
ushort objectIdLeadingValue = (ushort)expectedCommitIdLeadingValue;
ushort objectIdMask = (ushort)(objectIdLeadingValue == MaximumBuildNumberOrRevisionComponent ? 0xfffe : 0xffff);

return !commit.Id.StartsWith(objectIdLeadingValue, objectIdMask);
// Accept a big endian match or a little endian match.
// Nerdbank.GitVersioning up to v3.4 would produce versions based on the endianness of the CPU it ran on (typically little endian).
// Starting with v3.5, it deterministically used big endian. In order for `nbgv get-commits` to match on versions computed before and after the change,
// we match on either endian setting.
return !(commit.Id.StartsWith(objectIdLeadingValue, bigEndian: true, objectIdMask) || commit.Id.StartsWith(objectIdLeadingValue, bigEndian: false, objectIdMask));
}
}

Expand All @@ -324,10 +302,11 @@ private static bool IsCommitIdMismatch(Version version, VersionOptions versionOp
/// <param name="object">The object whose ID is to be tested.</param>
/// <param name="leadingBytes">The leading 16-bits to be tested.</param>
/// <param name="bitMask">The mask that indicates which bits should be compared.</param>
/// <param name="bigEndian"><see langword="true"/> to read the first two bytes as big endian (v3.5+ behavior); <see langword="false"/> to use little endian (v3.4 and earlier behavior).</param>
/// <returns><c>True</c> if the object's ID starts with <paramref name="leadingBytes"/> after applying the <paramref name="bitMask"/>.</returns>
private static bool StartsWith(this ObjectId @object, ushort leadingBytes, ushort bitMask = 0xffff)
private static bool StartsWith(this ObjectId @object, ushort leadingBytes, bool bigEndian, ushort bitMask = 0xffff)
{
ushort truncatedObjectId = BitConverter.ToUInt16(@object.RawId, 0);
ushort truncatedObjectId = bigEndian ? BinaryPrimitives.ReadUInt16BigEndian(@object.RawId) : BinaryPrimitives.ReadUInt16LittleEndian(@object.RawId);
return (truncatedObjectId & bitMask) == leadingBytes;
}

Expand Down

0 comments on commit 813cbf1

Please sign in to comment.