diff --git a/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs b/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs
index 35652918..2bc76347 100644
--- a/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs
+++ b/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Linq;
using LibGit2Sharp;
using Nerdbank.GitVersioning;
@@ -15,6 +16,14 @@ public VersionOracleTests(ITestOutputHelper logger)
private string CommitIdShort => this.Repo.Head.Commits.First().Id.Sha.Substring(0, 10);
+ [Fact]
+ public void NotRepo()
+ {
+ // Seems safe to assume the system directory is not a repository.
+ var oracle = VersionOracle.Create(Environment.SystemDirectory);
+ Assert.Equal(0, oracle.VersionHeight);
+ }
+
[Fact(Skip = "Unstable test. See issue #125")]
public void Submodule_RecognizedWithCorrectVersion()
{
diff --git a/src/NerdBank.GitVersioning/GitExtensions.cs b/src/NerdBank.GitVersioning/GitExtensions.cs
index 3b098a60..5432ae51 100644
--- a/src/NerdBank.GitVersioning/GitExtensions.cs
+++ b/src/NerdBank.GitVersioning/GitExtensions.cs
@@ -33,13 +33,18 @@ public static class GitExtensions
///
/// The commit to measure the height of.
/// The repo-relative project directory for which to calculate the version.
+ /// Optional base version to calculate the height. If not specified, the base version will be calculated by scanning the repository.
/// The height of the commit. Always a positive integer.
- public static int GetVersionHeight(this Commit commit, string repoRelativeProjectDirectory = null)
+ public static int GetVersionHeight(this Commit commit, string repoRelativeProjectDirectory = null, Version baseVersion = null)
{
Requires.NotNull(commit, nameof(commit));
Requires.Argument(repoRelativeProjectDirectory == null || !Path.IsPathRooted(repoRelativeProjectDirectory), nameof(repoRelativeProjectDirectory), "Path should be relative to repo root.");
- var baseVersion = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.Version?.Version ?? Version0;
+ if (baseVersion == null)
+ {
+ baseVersion = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.Version?.Version ?? Version0;
+ }
+
int height = commit.GetHeight(c => CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory));
return height;
}
@@ -170,7 +175,7 @@ public static Commit GetCommitFromTruncatedIdInteger(this Repository repo, int t
/// The commit whose ID and position in history is to be encoded.
/// The repo-relative project directory for which to calculate the version.
///
- /// The version height, previously calculated by a call to
+ /// The version height, previously calculated by a call to
/// with the same value for .
///
///
@@ -188,7 +193,13 @@ public static Version GetIdAsVersion(this Commit commit, string repoRelativeProj
Requires.Argument(repoRelativeProjectDirectory == null || !Path.IsPathRooted(repoRelativeProjectDirectory), nameof(repoRelativeProjectDirectory), "Path should be relative to repo root.");
var versionOptions = VersionFile.GetVersion(commit, repoRelativeProjectDirectory);
- return GetIdAsVersionHelper(commit, versionOptions, repoRelativeProjectDirectory, versionHeight);
+
+ if (!versionHeight.HasValue)
+ {
+ versionHeight = GetVersionHeight(commit, repoRelativeProjectDirectory);
+ }
+
+ return GetIdAsVersionHelper(commit, versionOptions, versionHeight.Value);
}
///
@@ -198,7 +209,7 @@ public static Version GetIdAsVersion(this Commit commit, string repoRelativeProj
/// The repo whose ID and position in history is to be encoded.
/// The repo-relative project directory for which to calculate the version.
///
- /// The version height, previously calculated by a call to
+ /// The version height, previously calculated by a call to
/// with the same value for .
///
///
@@ -219,7 +230,13 @@ public static Version GetIdAsVersion(this Repository repo, string repoRelativePr
if (IsVersionFileChangedInWorkingCopy(repo, repoRelativeProjectDirectory, out committedVersionOptions, out workingCopyVersionOptions))
{
// Apply ordinary logic, but to the working copy version info.
- Version result = GetIdAsVersionHelper(headCommit, workingCopyVersionOptions, repoRelativeProjectDirectory, versionHeight);
+ if (!versionHeight.HasValue)
+ {
+ var baseVersion = workingCopyVersionOptions?.Version?.Version;
+ versionHeight = GetVersionHeight(headCommit, repoRelativeProjectDirectory, baseVersion);
+ }
+
+ Version result = GetIdAsVersionHelper(headCommit, workingCopyVersionOptions, versionHeight.Value);
return result;
}
@@ -367,7 +384,7 @@ public static string FindLibGit2NativeBinaries(string basePath)
/// The version to test for in the commit
/// The repo-relative directory from which was originally calculated.
/// true if the matches the major and minor components of .
- private static bool CommitMatchesMajorMinorVersion(Commit commit, Version expectedVersion, string repoRelativeProjectDirectory)
+ internal static bool CommitMatchesMajorMinorVersion(this Commit commit, Version expectedVersion, string repoRelativeProjectDirectory)
{
Requires.NotNull(commit, nameof(commit));
Requires.NotNull(expectedVersion, nameof(expectedVersion));
@@ -494,11 +511,7 @@ private static void AddReachableCommitsFrom(Commit startingCommit, HashSet
/// The commit whose ID and position in history is to be encoded.
/// The version options applicable at this point (either from commit or working copy).
- /// The repo-relative project directory for which to calculate the version.
- ///
- /// The version height, previously calculated by a call to
- /// with the same value for .
- ///
+ /// The version height, previously calculated by a call to .
///
/// A version whose and
/// components are calculated based on the commit.
@@ -509,7 +522,7 @@ private static void AddReachableCommitsFrom(Commit startingCommit, HashSet
///
- private static Version GetIdAsVersionHelper(Commit commit, VersionOptions versionOptions, string repoRelativeProjectDirectory, int? versionHeight)
+ internal static Version GetIdAsVersionHelper(this Commit commit, VersionOptions versionOptions, int versionHeight)
{
var baseVersion = versionOptions?.Version?.Version ?? Version0;
int buildNumber = baseVersion.Build;
@@ -522,14 +535,8 @@ private static Version GetIdAsVersionHelper(Commit commit, VersionOptions versio
// The build number is set to the git height. This helps ensure that
// within a major.minor release, each patch has an incrementing integer.
// The revision is set to the first two bytes of the git commit ID.
- if (!versionHeight.HasValue)
- {
- versionHeight = commit != null
- ? commit.GetHeight(c => CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory))
- : 0;
- }
- int adjustedVersionHeight = versionHeight.Value == 0 ? 0 : versionHeight.Value + (versionOptions?.BuildNumberOffset ?? 0);
+ int adjustedVersionHeight = versionHeight == 0 ? 0 : versionHeight + (versionOptions?.BuildNumberOffset ?? 0);
Verify.Operation(adjustedVersionHeight <= MaximumBuildNumberOrRevisionComponent, "Git height is {0}, which is greater than the maximum allowed {0}.", adjustedVersionHeight, MaximumBuildNumberOrRevisionComponent);
if (buildNumber < 0)
diff --git a/src/NerdBank.GitVersioning/VersionOracle.cs b/src/NerdBank.GitVersioning/VersionOracle.cs
index 4b241cb8..56f635c7 100644
--- a/src/NerdBank.GitVersioning/VersionOracle.cs
+++ b/src/NerdBank.GitVersioning/VersionOracle.cs
@@ -5,9 +5,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
- using System.Text;
using System.Text.RegularExpressions;
- using System.Threading.Tasks;
using Validation;
///
@@ -21,9 +19,9 @@ public class VersionOracle
private static readonly Regex NumericIdentifierRegex = new Regex(@"(?
- /// The cloud build suppport, if any.
+ /// The 0.0 version.
///
- private readonly ICloudBuild cloudBuild;
+ private static readonly Version Version0 = new Version(0, 0);
///
/// Initializes a new instance of the class.
@@ -51,24 +49,33 @@ public static VersionOracle Create(string projectDirectory, string gitRepoDirect
///
public VersionOracle(string projectDirectory, LibGit2Sharp.Repository repo, ICloudBuild cloudBuild)
{
- this.cloudBuild = cloudBuild;
- this.VersionOptions =
- VersionFile.GetVersion(repo, projectDirectory) ??
- VersionFile.GetVersion(projectDirectory);
-
var repoRoot = repo?.Info?.WorkingDirectory?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var relativeRepoProjectDirectory = !string.IsNullOrWhiteSpace(repoRoot)
? projectDirectory.Substring(repoRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
: null;
var commit = repo?.Head.Commits.FirstOrDefault();
+
+ var committedVersion = VersionFile.GetVersion(commit, relativeRepoProjectDirectory);
+
+ var workingVersion = VersionFile.GetVersion(projectDirectory);
+
+ this.VersionOptions = committedVersion ?? workingVersion;
+
this.GitCommitId = commit?.Id.Sha ?? cloudBuild?.GitCommitId ?? null;
- this.VersionHeight = repo?.GetVersionHeight(relativeRepoProjectDirectory) ?? 0;
+ this.VersionHeight = CalculateVersionHeight(relativeRepoProjectDirectory, commit, committedVersion, workingVersion);
this.BuildingRef = cloudBuild?.BuildingTag ?? cloudBuild?.BuildingBranch ?? repo?.Head.CanonicalName;
// Override the typedVersion with the special build number and revision components, when available.
- this.Version = repo?.GetIdAsVersion(relativeRepoProjectDirectory, this.VersionHeight) ?? this.VersionOptions?.Version.Version;
- this.Version = this.Version ?? new Version(0, 0);
+ if (repo != null)
+ {
+ this.Version = GetIdAsVersion(commit, committedVersion, workingVersion, this.VersionHeight);
+ }
+ else
+ {
+ this.Version = this.VersionOptions?.Version.Version ?? Version0;
+ }
+
this.VersionHeightOffset = this.VersionOptions?.BuildNumberOffset ?? 0;
this.PrereleaseVersion = ReplaceMacros(this.VersionOptions?.Version.Prerelease ?? string.Empty);
@@ -391,5 +398,42 @@ private static string MakePrereleaseSemVer1Compliant(string prerelease, int padd
return semver1;
}
+
+ private static int CalculateVersionHeight(string relativeRepoProjectDirectory, LibGit2Sharp.Commit headCommit, VersionOptions committedVersion, VersionOptions workingVersion)
+ {
+ var headCommitVersion = committedVersion?.Version?.Version ?? Version0;
+
+ if (IsVersionFileChangedInWorkingTree(committedVersion, workingVersion))
+ {
+ var workingCopyVersion = workingVersion?.Version?.Version;
+
+ if (workingCopyVersion == null || !workingCopyVersion.Equals(headCommitVersion))
+ {
+ // The working copy has changed the major.minor version.
+ // So by definition the version height is 0, since no commit represents it yet.
+ return 0;
+ }
+ }
+
+ return headCommit?.GetHeight(c => c.CommitMatchesMajorMinorVersion(headCommitVersion, relativeRepoProjectDirectory)) ?? 0;
+ }
+
+ private static Version GetIdAsVersion(LibGit2Sharp.Commit headCommit, VersionOptions committedVersion, VersionOptions workingVersion, int versionHeight)
+ {
+ var version = IsVersionFileChangedInWorkingTree(committedVersion, workingVersion) ? workingVersion : committedVersion;
+
+ return headCommit.GetIdAsVersionHelper(version, versionHeight);
+ }
+
+ private static bool IsVersionFileChangedInWorkingTree(VersionOptions committedVersion, VersionOptions workingVersion)
+ {
+ if (workingVersion != null)
+ {
+ return !EqualityComparer.Default.Equals(workingVersion, committedVersion);
+ }
+
+ // A missing working version is a change only if it was previously commited.
+ return committedVersion != null;
+ }
}
}