From 869f9afbd5bffd6f02952d77066405eac7027a2d Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 31 Aug 2026 20:50:35 -0600 Subject: [PATCH 1/2] Preserve LibGit2 object lifetimes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../LibGit2/LibGit2GitExtensions.cs | 2 +- src/NerdBank.GitVersioning/VersionOracle.cs | 22 ++++++++++++------- .../LibGit2GitExtensionsTests.cs | 16 ++++++++++++++ .../VersionOracleTests.cs | 16 ++++++++++++++ 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/NerdBank.GitVersioning/LibGit2/LibGit2GitExtensions.cs b/src/NerdBank.GitVersioning/LibGit2/LibGit2GitExtensions.cs index 5a9d0268..f8c395f0 100644 --- a/src/NerdBank.GitVersioning/LibGit2/LibGit2GitExtensions.cs +++ b/src/NerdBank.GitVersioning/LibGit2/LibGit2GitExtensions.cs @@ -110,7 +110,7 @@ public static IEnumerable GetCommitsFromVersion(LibGit2Context context, where !IsVersionHeightMismatch(version, commitVersionOptions, commit, tracker) select commit; - return possibleCommits; + return possibleCommits.ToList(); } /// diff --git a/src/NerdBank.GitVersioning/VersionOracle.cs b/src/NerdBank.GitVersioning/VersionOracle.cs index 529cb61f..4d37a2ad 100644 --- a/src/NerdBank.GitVersioning/VersionOracle.cs +++ b/src/NerdBank.GitVersioning/VersionOracle.cs @@ -21,10 +21,14 @@ public class VersionOracle private const bool UseLibGit2 = false; - private readonly GitContext context; - private readonly string? gitCommitId; + private readonly DateTimeOffset? gitCommitDate; + + private readonly DateTimeOffset? gitCommitAuthorDate; + + private readonly IReadOnlyCollection? tags; + private readonly bool isWorkingTreeDirty; private readonly string? versionGitCommitId; @@ -46,7 +50,9 @@ public class VersionOracle /// An optional value to override the version height offset. public VersionOracle(GitContext context, ICloudBuild? cloudBuild = null, int? overrideVersionHeightOffset = null) { - this.context = context; + this.gitCommitDate = context.GitCommitDate; + this.gitCommitAuthorDate = context.GitCommitAuthorDate; + this.tags = context.HeadTags?.ToArray(); string? rawGitCommitId = context.GitCommitId ?? cloudBuild?.GitCommitId; this.gitCommitId = rawGitCommitId; this.CommittedVersion = context.VersionFile.GetVersion(); @@ -116,7 +122,7 @@ public VersionOracle(GitContext context, ICloudBuild? cloudBuild = null, int? ov // Get it from the git repository if there is a repository present and it is enabled. string gitCommitIdShort = gitCommitIdShortAutoMinimum > 0 - ? this.context.GetShortUniqueCommitId(gitCommitIdShortAutoMinimum) + ? context.GetShortUniqueCommitId(gitCommitIdShortAutoMinimum) : rawGitCommitId!.Substring(0, gitCommitIdShortFixedLength); this.GitCommitIdShort = this.isWorkingTreeDirty ? gitCommitIdShort + "-dirty" : gitCommitIdShort; } @@ -126,7 +132,7 @@ public VersionOracle(GitContext context, ICloudBuild? cloudBuild = null, int? ov int gitCommitIdShortFixedLength = this.VersionOptions?.GitCommitIdShortFixedLength ?? VersionOptions.DefaultGitCommitIdShortFixedLength; int gitCommitIdShortAutoMinimum = this.VersionOptions?.GitCommitIdShortAutoMinimum ?? 0; this.versionGitCommitIdShort = gitCommitIdShortAutoMinimum > 0 - ? this.context.GetShortUniqueCommitId(this.versionGitCommitId!, gitCommitIdShortAutoMinimum) + ? context.GetShortUniqueCommitId(this.versionGitCommitId!, gitCommitIdShortAutoMinimum) : this.versionGitCommitId!.Substring(0, gitCommitIdShortFixedLength); } @@ -314,12 +320,12 @@ public string PrereleaseVersion /// /// Gets the Git revision control commit date for HEAD (the current source code version). /// - public DateTimeOffset? GitCommitDate => this.context.GitCommitDate; + public DateTimeOffset? GitCommitDate => this.gitCommitDate; /// /// Gets the Git revision control commit author date for HEAD (the current source code version). /// - public DateTimeOffset? GitCommitAuthorDate => this.context.GitCommitAuthorDate; + public DateTimeOffset? GitCommitAuthorDate => this.gitCommitAuthorDate; /// /// Gets or sets the number of commits in the longest single path between @@ -353,7 +359,7 @@ public string PrereleaseVersion /// Gets a collection of the tags that reference HEAD. /// [Ignore] - public IReadOnlyCollection? Tags => this.context.HeadTags; + public IReadOnlyCollection? Tags => this.tags; /// /// Gets or sets the version for this project, with up to 4 components. diff --git a/test/Nerdbank.GitVersioning.Tests/LibGit2GitExtensionsTests.cs b/test/Nerdbank.GitVersioning.Tests/LibGit2GitExtensionsTests.cs index f744f828..53ed33d0 100644 --- a/test/Nerdbank.GitVersioning.Tests/LibGit2GitExtensionsTests.cs +++ b/test/Nerdbank.GitVersioning.Tests/LibGit2GitExtensionsTests.cs @@ -306,6 +306,22 @@ public void GetCommitsFromVersion_MatchesOnEitherEndian() Assert.Contains(commit, LibGit2GitExtensions.GetCommitsFromVersion(this.Context, swappedEndian)); } + [Fact] + public void GetCommitsFromVersion_MaterializesBeforeContextIsDisposed() + { + this.InitializeSourceControl(); + Commit commit = this.WriteVersionFile(new VersionOptions { Version = SemanticVersion.Parse("1.2") }); + Version version = new VersionOracle(this.Context).Version; + IEnumerable matchingCommits; + + using (LibGit2Context context = LibGit2Context.Create(this.RepoPath)) + { + matchingCommits = LibGit2GitExtensions.GetCommitsFromVersion(context, version); + } + + Assert.Single(matchingCommits, candidate => candidate.Sha == commit.Sha); + } + [Fact] public void GetIdAsVersion_Roundtrip_WithSubdirectoryVersionFiles() { diff --git a/test/Nerdbank.GitVersioning.Tests/VersionOracleTests.cs b/test/Nerdbank.GitVersioning.Tests/VersionOracleTests.cs index 0b3427ad..1816322b 100644 --- a/test/Nerdbank.GitVersioning.Tests/VersionOracleTests.cs +++ b/test/Nerdbank.GitVersioning.Tests/VersionOracleTests.cs @@ -58,6 +58,22 @@ public void NotRepo() Assert.Equal(0, oracle.VersionHeight); } + [Fact] + public void GetVersionOracle_CapturesContextDataBeforeDisposal() + { + this.WriteVersionFile(); + this.InitializeSourceControl(); + this.LibGit2Repository.ApplyTag("test"); + + VersionOracle oracle = this.GetVersionOracle(); + + Assert.Equal(this.LibGit2Repository.Head.Tip.Committer.When, oracle.GitCommitDate); + Assert.Equal(this.LibGit2Repository.Head.Tip.Author.When, oracle.GitCommitAuthorDate); + Assert.Equal("refs/tags/test", Assert.Single(oracle.Tags)); + Assert.Contains("NBGV_GitCommitDate", oracle.CloudBuildAllVars.Keys); + Assert.Contains("NBGV_GitCommitAuthorDate", oracle.CloudBuildAllVars.Keys); + } + [Fact] public void EmptyRepoWithCloudCommitNotInRepository() { From d4b78ebd9edde313171e1be335a776c472464972 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 31 Aug 2026 21:24:17 -0600 Subject: [PATCH 2/2] Serialize LibGit2 test collections Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/Nerdbank.GitVersioning.Tests/xunit.runner.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Nerdbank.GitVersioning.Tests/xunit.runner.json b/test/Nerdbank.GitVersioning.Tests/xunit.runner.json index 8465a454..61d5d751 100644 --- a/test/Nerdbank.GitVersioning.Tests/xunit.runner.json +++ b/test/Nerdbank.GitVersioning.Tests/xunit.runner.json @@ -1,4 +1,5 @@ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false, "shadowCopy": false }