Skip to content

Commit af75d73

Browse files
authored
Merge pull request #134 from robmen/114-partialperf
Partial performance improvement for #114
2 parents 7cb9894 + 9edf81a commit af75d73

File tree

3 files changed

+93
-33
lines changed

3 files changed

+93
-33
lines changed

src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23
using System.Linq;
34
using LibGit2Sharp;
45
using Nerdbank.GitVersioning;
@@ -15,6 +16,14 @@ public VersionOracleTests(ITestOutputHelper logger)
1516

1617
private string CommitIdShort => this.Repo.Head.Commits.First().Id.Sha.Substring(0, 10);
1718

19+
[Fact]
20+
public void NotRepo()
21+
{
22+
// Seems safe to assume the system directory is not a repository.
23+
var oracle = VersionOracle.Create(Environment.SystemDirectory);
24+
Assert.Equal(0, oracle.VersionHeight);
25+
}
26+
1827
[Fact(Skip = "Unstable test. See issue #125")]
1928
public void Submodule_RecognizedWithCorrectVersion()
2029
{

src/NerdBank.GitVersioning/GitExtensions.cs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,18 @@ public static class GitExtensions
3333
/// </summary>
3434
/// <param name="commit">The commit to measure the height of.</param>
3535
/// <param name="repoRelativeProjectDirectory">The repo-relative project directory for which to calculate the version.</param>
36+
/// <param name="baseVersion">Optional base version to calculate the height. If not specified, the base version will be calculated by scanning the repository.</param>
3637
/// <returns>The height of the commit. Always a positive integer.</returns>
37-
public static int GetVersionHeight(this Commit commit, string repoRelativeProjectDirectory = null)
38+
public static int GetVersionHeight(this Commit commit, string repoRelativeProjectDirectory = null, Version baseVersion = null)
3839
{
3940
Requires.NotNull(commit, nameof(commit));
4041
Requires.Argument(repoRelativeProjectDirectory == null || !Path.IsPathRooted(repoRelativeProjectDirectory), nameof(repoRelativeProjectDirectory), "Path should be relative to repo root.");
4142

42-
var baseVersion = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.Version?.Version ?? Version0;
43+
if (baseVersion == null)
44+
{
45+
baseVersion = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.Version?.Version ?? Version0;
46+
}
47+
4348
int height = commit.GetHeight(c => CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory));
4449
return height;
4550
}
@@ -170,7 +175,7 @@ public static Commit GetCommitFromTruncatedIdInteger(this Repository repo, int t
170175
/// <param name="commit">The commit whose ID and position in history is to be encoded.</param>
171176
/// <param name="repoRelativeProjectDirectory">The repo-relative project directory for which to calculate the version.</param>
172177
/// <param name="versionHeight">
173-
/// The version height, previously calculated by a call to <see cref="GetVersionHeight(Commit, string)"/>
178+
/// The version height, previously calculated by a call to <see cref="GetVersionHeight(Commit, string, Version)"/>
174179
/// with the same value for <paramref name="repoRelativeProjectDirectory"/>.
175180
/// </param>
176181
/// <returns>
@@ -188,7 +193,13 @@ public static Version GetIdAsVersion(this Commit commit, string repoRelativeProj
188193
Requires.Argument(repoRelativeProjectDirectory == null || !Path.IsPathRooted(repoRelativeProjectDirectory), nameof(repoRelativeProjectDirectory), "Path should be relative to repo root.");
189194

190195
var versionOptions = VersionFile.GetVersion(commit, repoRelativeProjectDirectory);
191-
return GetIdAsVersionHelper(commit, versionOptions, repoRelativeProjectDirectory, versionHeight);
196+
197+
if (!versionHeight.HasValue)
198+
{
199+
versionHeight = GetVersionHeight(commit, repoRelativeProjectDirectory);
200+
}
201+
202+
return GetIdAsVersionHelper(commit, versionOptions, versionHeight.Value);
192203
}
193204

194205
/// <summary>
@@ -198,7 +209,7 @@ public static Version GetIdAsVersion(this Commit commit, string repoRelativeProj
198209
/// <param name="repo">The repo whose ID and position in history is to be encoded.</param>
199210
/// <param name="repoRelativeProjectDirectory">The repo-relative project directory for which to calculate the version.</param>
200211
/// <param name="versionHeight">
201-
/// The version height, previously calculated by a call to <see cref="GetVersionHeight(Commit, string)"/>
212+
/// The version height, previously calculated by a call to <see cref="GetVersionHeight(Commit, string, Version)"/>
202213
/// with the same value for <paramref name="repoRelativeProjectDirectory"/>.
203214
/// </param>
204215
/// <returns>
@@ -219,7 +230,13 @@ public static Version GetIdAsVersion(this Repository repo, string repoRelativePr
219230
if (IsVersionFileChangedInWorkingCopy(repo, repoRelativeProjectDirectory, out committedVersionOptions, out workingCopyVersionOptions))
220231
{
221232
// Apply ordinary logic, but to the working copy version info.
222-
Version result = GetIdAsVersionHelper(headCommit, workingCopyVersionOptions, repoRelativeProjectDirectory, versionHeight);
233+
if (!versionHeight.HasValue)
234+
{
235+
var baseVersion = workingCopyVersionOptions?.Version?.Version;
236+
versionHeight = GetVersionHeight(headCommit, repoRelativeProjectDirectory, baseVersion);
237+
}
238+
239+
Version result = GetIdAsVersionHelper(headCommit, workingCopyVersionOptions, versionHeight.Value);
223240
return result;
224241
}
225242

@@ -367,7 +384,7 @@ public static string FindLibGit2NativeBinaries(string basePath)
367384
/// <param name="expectedVersion">The version to test for in the commit</param>
368385
/// <param name="repoRelativeProjectDirectory">The repo-relative directory from which <paramref name="expectedVersion"/> was originally calculated.</param>
369386
/// <returns><c>true</c> if the <paramref name="commit"/> matches the major and minor components of <paramref name="expectedVersion"/>.</returns>
370-
private static bool CommitMatchesMajorMinorVersion(Commit commit, Version expectedVersion, string repoRelativeProjectDirectory)
387+
internal static bool CommitMatchesMajorMinorVersion(this Commit commit, Version expectedVersion, string repoRelativeProjectDirectory)
371388
{
372389
Requires.NotNull(commit, nameof(commit));
373390
Requires.NotNull(expectedVersion, nameof(expectedVersion));
@@ -494,11 +511,7 @@ private static void AddReachableCommitsFrom(Commit startingCommit, HashSet<Commi
494511
/// </summary>
495512
/// <param name="commit">The commit whose ID and position in history is to be encoded.</param>
496513
/// <param name="versionOptions">The version options applicable at this point (either from commit or working copy).</param>
497-
/// <param name="repoRelativeProjectDirectory">The repo-relative project directory for which to calculate the version.</param>
498-
/// <param name="versionHeight">
499-
/// The version height, previously calculated by a call to <see cref="GetVersionHeight(Commit, string)"/>
500-
/// with the same value for <paramref name="repoRelativeProjectDirectory"/>.
501-
/// </param>
514+
/// <param name="versionHeight">The version height, previously calculated by a call to <see cref="GetVersionHeight(Commit, string, Version)"/>.</param>
502515
/// <returns>
503516
/// A version whose <see cref="Version.Build"/> and
504517
/// <see cref="Version.Revision"/> components are calculated based on the commit.
@@ -509,7 +522,7 @@ private static void AddReachableCommitsFrom(Commit startingCommit, HashSet<Commi
509522
/// component is the first four bytes of the git commit id (forced to be a positive integer).
510523
/// </remarks>
511524
/// <returns></returns>
512-
private static Version GetIdAsVersionHelper(Commit commit, VersionOptions versionOptions, string repoRelativeProjectDirectory, int? versionHeight)
525+
internal static Version GetIdAsVersionHelper(this Commit commit, VersionOptions versionOptions, int versionHeight)
513526
{
514527
var baseVersion = versionOptions?.Version?.Version ?? Version0;
515528
int buildNumber = baseVersion.Build;
@@ -522,14 +535,8 @@ private static Version GetIdAsVersionHelper(Commit commit, VersionOptions versio
522535
// The build number is set to the git height. This helps ensure that
523536
// within a major.minor release, each patch has an incrementing integer.
524537
// 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-
}
531538

532-
int adjustedVersionHeight = versionHeight.Value == 0 ? 0 : versionHeight.Value + (versionOptions?.BuildNumberOffset ?? 0);
539+
int adjustedVersionHeight = versionHeight == 0 ? 0 : versionHeight + (versionOptions?.BuildNumberOffset ?? 0);
533540
Verify.Operation(adjustedVersionHeight <= MaximumBuildNumberOrRevisionComponent, "Git height is {0}, which is greater than the maximum allowed {0}.", adjustedVersionHeight, MaximumBuildNumberOrRevisionComponent);
534541

535542
if (buildNumber < 0)

src/NerdBank.GitVersioning/VersionOracle.cs

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
using System.Globalization;
66
using System.IO;
77
using System.Linq;
8-
using System.Text;
98
using System.Text.RegularExpressions;
10-
using System.Threading.Tasks;
119
using Validation;
1210

1311
/// <summary>
@@ -21,9 +19,9 @@ public class VersionOracle
2119
private static readonly Regex NumericIdentifierRegex = new Regex(@"(?<![\w-])(\d+)(?![\w-])");
2220

2321
/// <summary>
24-
/// The cloud build suppport, if any.
22+
/// The 0.0 version.
2523
/// </summary>
26-
private readonly ICloudBuild cloudBuild;
24+
private static readonly Version Version0 = new Version(0, 0);
2725

2826
/// <summary>
2927
/// Initializes a new instance of the <see cref="VersionOracle"/> class.
@@ -51,24 +49,33 @@ public static VersionOracle Create(string projectDirectory, string gitRepoDirect
5149
/// </summary>
5250
public VersionOracle(string projectDirectory, LibGit2Sharp.Repository repo, ICloudBuild cloudBuild)
5351
{
54-
this.cloudBuild = cloudBuild;
55-
this.VersionOptions =
56-
VersionFile.GetVersion(repo, projectDirectory) ??
57-
VersionFile.GetVersion(projectDirectory);
58-
5952
var repoRoot = repo?.Info?.WorkingDirectory?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
6053
var relativeRepoProjectDirectory = !string.IsNullOrWhiteSpace(repoRoot)
6154
? projectDirectory.Substring(repoRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
6255
: null;
6356

6457
var commit = repo?.Head.Commits.FirstOrDefault();
58+
59+
var committedVersion = VersionFile.GetVersion(commit, relativeRepoProjectDirectory);
60+
61+
var workingVersion = VersionFile.GetVersion(projectDirectory);
62+
63+
this.VersionOptions = committedVersion ?? workingVersion;
64+
6565
this.GitCommitId = commit?.Id.Sha ?? cloudBuild?.GitCommitId ?? null;
66-
this.VersionHeight = repo?.GetVersionHeight(relativeRepoProjectDirectory) ?? 0;
66+
this.VersionHeight = CalculateVersionHeight(relativeRepoProjectDirectory, commit, committedVersion, workingVersion);
6767
this.BuildingRef = cloudBuild?.BuildingTag ?? cloudBuild?.BuildingBranch ?? repo?.Head.CanonicalName;
6868

6969
// Override the typedVersion with the special build number and revision components, when available.
70-
this.Version = repo?.GetIdAsVersion(relativeRepoProjectDirectory, this.VersionHeight) ?? this.VersionOptions?.Version.Version;
71-
this.Version = this.Version ?? new Version(0, 0);
70+
if (repo != null)
71+
{
72+
this.Version = GetIdAsVersion(commit, committedVersion, workingVersion, this.VersionHeight);
73+
}
74+
else
75+
{
76+
this.Version = this.VersionOptions?.Version.Version ?? Version0;
77+
}
78+
7279
this.VersionHeightOffset = this.VersionOptions?.BuildNumberOffset ?? 0;
7380

7481
this.PrereleaseVersion = ReplaceMacros(this.VersionOptions?.Version.Prerelease ?? string.Empty);
@@ -391,5 +398,42 @@ private static string MakePrereleaseSemVer1Compliant(string prerelease, int padd
391398

392399
return semver1;
393400
}
401+
402+
private static int CalculateVersionHeight(string relativeRepoProjectDirectory, LibGit2Sharp.Commit headCommit, VersionOptions committedVersion, VersionOptions workingVersion)
403+
{
404+
var headCommitVersion = committedVersion?.Version?.Version ?? Version0;
405+
406+
if (IsVersionFileChangedInWorkingTree(committedVersion, workingVersion))
407+
{
408+
var workingCopyVersion = workingVersion?.Version?.Version;
409+
410+
if (workingCopyVersion == null || !workingCopyVersion.Equals(headCommitVersion))
411+
{
412+
// The working copy has changed the major.minor version.
413+
// So by definition the version height is 0, since no commit represents it yet.
414+
return 0;
415+
}
416+
}
417+
418+
return headCommit?.GetHeight(c => c.CommitMatchesMajorMinorVersion(headCommitVersion, relativeRepoProjectDirectory)) ?? 0;
419+
}
420+
421+
private static Version GetIdAsVersion(LibGit2Sharp.Commit headCommit, VersionOptions committedVersion, VersionOptions workingVersion, int versionHeight)
422+
{
423+
var version = IsVersionFileChangedInWorkingTree(committedVersion, workingVersion) ? workingVersion : committedVersion;
424+
425+
return headCommit.GetIdAsVersionHelper(version, versionHeight);
426+
}
427+
428+
private static bool IsVersionFileChangedInWorkingTree(VersionOptions committedVersion, VersionOptions workingVersion)
429+
{
430+
if (workingVersion != null)
431+
{
432+
return !EqualityComparer<VersionOptions>.Default.Equals(workingVersion, committedVersion);
433+
}
434+
435+
// A missing working version is a change only if it was previously commited.
436+
return committedVersion != null;
437+
}
394438
}
395439
}

0 commit comments

Comments
 (0)