Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use nearest relevant commit id based on path filters (fixes #643) #662

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/NerdBank.GitVersioning/GitContext.cs
Expand Up @@ -176,7 +176,7 @@ public static GitContext Create(string path, string? committish = null, bool wri
/// <returns>A string that is at least <paramref name="minLength"/> in length but may be more as required to uniquely identify the git object identified by <see cref="GitCommitId"/>.</returns>
public abstract string GetShortUniqueCommitId(int minLength);

internal abstract int CalculateVersionHeight(VersionOptions? committedVersion, VersionOptions? workingVersion);
internal abstract (int height, string? nearestRelevantCommit) CalculateVersionHeightAndNearestRelevantCommit(VersionOptions? committedVersion, VersionOptions? workingVersion);

internal abstract Version GetIdAsVersion(VersionOptions? committedVersion, VersionOptions? workingVersion, int versionHeight);

Expand Down
6 changes: 5 additions & 1 deletion src/NerdBank.GitVersioning/LibGit2/LibGit2Context.cs
Expand Up @@ -82,8 +82,11 @@ public override bool TrySelectCommit(string committish)
/// <inheritdoc/>
public override string GetShortUniqueCommitId(int minLength) => this.Repository.ObjectDatabase.ShortenObjectId(this.Commit, minLength);

internal override int CalculateVersionHeight(VersionOptions? committedVersion, VersionOptions? workingVersion)
internal override (int height, string? nearestRelevantCommit) CalculateVersionHeightAndNearestRelevantCommit(VersionOptions? committedVersion, VersionOptions? workingVersion)
{
throw new NotImplementedException("nearestRelevantCommit for libgit2");

#if false
var headCommitVersion = committedVersion?.Version ?? SemVer0;

if (IsVersionFileChangedInWorkingTree(committedVersion, workingVersion))
Expand All @@ -99,6 +102,7 @@ internal override int CalculateVersionHeight(VersionOptions? committedVersion, V
}

return LibGit2GitExtensions.GetVersionHeight(this);
#endif
}

internal override System.Version GetIdAsVersion(VersionOptions? committedVersion, VersionOptions? workingVersion, int versionHeight)
Expand Down
7 changes: 4 additions & 3 deletions src/NerdBank.GitVersioning/Managed/ManagedGitContext.cs
Expand Up @@ -86,7 +86,7 @@ public static ManagedGitContext Create(string path, string? committish = null)
};
}

internal override int CalculateVersionHeight(VersionOptions? committedVersion, VersionOptions? workingVersion)
internal override (int height, string? nearestRelevantCommit) CalculateVersionHeightAndNearestRelevantCommit(VersionOptions? committedVersion, VersionOptions? workingVersion)
{
var headCommitVersion = committedVersion?.Version ?? SemVer0;

Expand All @@ -98,11 +98,12 @@ internal override int CalculateVersionHeight(VersionOptions? committedVersion, V
{
// 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 (0, null);
}
}

return GitExtensions.GetVersionHeight(this);
var (height, nearestRelevantCommit) = GitExtensions.GetVersionHeight(this);
return (height, nearestRelevantCommit?.Sha.ToString());
}

internal override Version GetIdAsVersion(VersionOptions? committedVersion, VersionOptions? workingVersion, int versionHeight)
Expand Down
37 changes: 26 additions & 11 deletions src/NerdBank.GitVersioning/Managed/ManagedGitExtensions.cs
Expand Up @@ -20,38 +20,40 @@ internal static class GitExtensions
/// <summary>
/// Gets the number of commits in the longest single path between
/// the specified commit and the most distant ancestor (inclusive)
/// that set the version to the value at <paramref name="context"/>.
/// that set the version to the value at <paramref name="context"/>,
/// and the nearest commit that is part of that path, taking into
/// account path filters.
/// </summary>
/// <param name="context">The git context for which to calculate the height.</param>
/// <param name="baseVersion">Optional base version to calculate the height. If not specified, the base version will be calculated by scanning the repository.</param>
/// <returns>The height of the commit. Always a positive integer.</returns>
internal static int GetVersionHeight(ManagedGitContext context, Version? baseVersion = null)
/// <returns>The height of the commit (always a positive integer), and a <see cref="GitContext"/> representing the nearest relevant commit, or <see langword="null"/> if there is no relevant commit.</returns>
internal static (int height, GitCommit? nearestRelevantCommit) GetVersionHeight(ManagedGitContext context, Version? baseVersion = null)
{
if (context.Commit is null)
{
return 0;
return (0, null);
}

var tracker = new GitWalkTracker(context);

var versionOptions = tracker.GetVersion(context.Commit.Value);
if (versionOptions is null)
{
return 0;
return (0, null);
}

var baseSemVer =
baseVersion is not null ? SemanticVersion.Parse(baseVersion.ToString()) :
versionOptions.Version ?? SemVer0;

// TODO: What if versionHeightPosition is not set, but we still want to know the nearest relevant commit id?
var versionHeightPosition = versionOptions.VersionHeightPosition;
if (versionHeightPosition.HasValue)
{
int height = GetHeight(context, c => CommitMatchesVersion(c, baseSemVer, versionHeightPosition.Value, tracker));
return height;
return GetHeight(context, c => CommitMatchesVersion(c, baseSemVer, versionHeightPosition.Value, tracker));
}

return 0;
return (0, null);
}

/// <summary>
Expand Down Expand Up @@ -94,11 +96,11 @@ private static bool CommitMatchesVersion(GitCommit commit, SemanticVersion expec
/// May be null to count the height to the original commit.
/// </param>
/// <returns>The height of the commit. Always a positive integer.</returns>
public static int GetHeight(ManagedGitContext context, Func<GitCommit, bool>? continueStepping = null)
public static (int height, GitCommit? nearestRelevantCommit) GetHeight(ManagedGitContext context, Func<GitCommit, bool>? continueStepping = null)
{
Verify.Operation(context.Commit.HasValue, "No commit is selected.");
var tracker = new GitWalkTracker(context);
return GetCommitHeight(context.Repository, context.Commit.Value, tracker, continueStepping);
return (GetCommitHeight(context.Repository, context.Commit.Value, tracker, continueStepping), tracker.NearestCommit);
}

/// <summary>
Expand Down Expand Up @@ -316,14 +318,27 @@ private class GitWalkTracker
private readonly Dictionary<GitObjectId, int> heights = new Dictionary<GitObjectId, int>();
private readonly ManagedGitContext context;

private int nearestCommitHeight = -1;

internal GitCommit? NearestCommit { get; private set; }

internal GitWalkTracker(ManagedGitContext context)
{
this.context = context;
}

internal bool TryGetVersionHeight(GitCommit commit, out int height) => this.heights.TryGetValue(commit.Sha, out height);

internal void RecordHeight(GitCommit commit, int height) => this.heights.Add(commit.Sha, height);
internal void RecordHeight(GitCommit commit, int height)
{
if ( height > this.nearestCommitHeight)
{
this.NearestCommit = commit;
this.nearestCommitHeight = height;
}

this.heights.Add(commit.Sha, height);
}

internal VersionOptions? GetVersion(GitCommit commit)
{
Expand Down
3 changes: 3 additions & 0 deletions src/NerdBank.GitVersioning/ManagedGit/GitObjectId.cs
Expand Up @@ -116,6 +116,9 @@ public static GitObjectId ParseHex(ReadOnlySpan<byte> value)

bytes[i >> 1] = (byte)(c1 + c2);
}

// Clear any cached sha. This can happen when debugging, and is very confusing.
objectId.sha = null;

return objectId;
}
Expand Down
2 changes: 1 addition & 1 deletion src/NerdBank.GitVersioning/NoGit/NoGitContext.cs
Expand Up @@ -32,7 +32,7 @@ public NoGitContext(string workingTreePath)
public override void Stage(string path) => throw new InvalidOperationException(NotAGitRepoMessage);
public override string GetShortUniqueCommitId(int minLength) => throw new InvalidOperationException(NotAGitRepoMessage);
public override bool TrySelectCommit(string committish) => throw new InvalidOperationException(NotAGitRepoMessage);
internal override int CalculateVersionHeight(VersionOptions? committedVersion, VersionOptions? workingVersion) => 0;
internal override (int height, string? nearestRelevantCommit) CalculateVersionHeightAndNearestRelevantCommit(VersionOptions? committedVersion, VersionOptions? workingVersion) => (0, null);
internal override Version GetIdAsVersion(VersionOptions? committedVersion, VersionOptions? workingVersion, int versionHeight) => throw new NotImplementedException();
}
}
13 changes: 12 additions & 1 deletion src/NerdBank.GitVersioning/VersionOracle.cs
Expand Up @@ -61,9 +61,10 @@ public VersionOracle(GitContext context, ICloudBuild? cloudBuild = null, int? ov
}

this.BuildingRef = cloudBuild?.BuildingTag ?? cloudBuild?.BuildingBranch ?? context.HeadCanonicalName;
string? nearestRelevantCommit = null;
try
{
this.VersionHeight = context.CalculateVersionHeight(this.CommittedVersion, this.WorkingVersion);
(this.VersionHeight, nearestRelevantCommit) = context.CalculateVersionHeightAndNearestRelevantCommit(this.CommittedVersion, this.WorkingVersion);
}
catch (GitException ex) when (context.IsShallow && ex.ErrorCode == GitException.ErrorCodes.ObjectNotFound)
{
Expand All @@ -78,6 +79,7 @@ public VersionOracle(GitContext context, ICloudBuild? cloudBuild = null, int? ov

static Exception ThrowShallowClone(Exception inner) => throw new GitException("Shallow clone lacks the objects required to calculate version height. Use full clones or clones with a history at least as deep as the last version height resetting change.", inner) { iSShallowClone = true, ErrorCode = GitException.ErrorCodes.ObjectNotFound };


this.VersionOptions = this.CommittedVersion ?? this.WorkingVersion;
this.Version = this.VersionOptions?.Version?.Version ?? Version0;
this.assemblyInformationalVersionComponentCount = this.VersionOptions?.VersionHeightPosition == SemanticVersion.Position.Revision ? 4 : 3;
Expand All @@ -90,6 +92,15 @@ public VersionOracle(GitContext context, ICloudBuild? cloudBuild = null, int? ov

this.CloudBuildNumberOptions = this.VersionOptions?.CloudBuild?.BuildNumberOrDefault ?? VersionOptions.CloudBuildNumberOptions.DefaultInstance;

if (!string.IsNullOrEmpty(nearestRelevantCommit) && context.GitCommitId != nearestRelevantCommit)
{
if (!context.TrySelectCommit(nearestRelevantCommit!))
{
// This would be very unexpected, given that the context itself provided the commit id we're selecting.
throw new GitException("Failed to select the nearest relevant commit.");
}
}

// get the commit id abbreviation only if the commit id is set
if (!string.IsNullOrEmpty(this.GitCommitId))
{
Expand Down