diff --git a/UnityXrefMaps.Tests/RepositoryExtensionsTests.cs b/UnityXrefMaps.Tests/RepositoryExtensionsTests.cs
new file mode 100644
index 0000000..98b7645
--- /dev/null
+++ b/UnityXrefMaps.Tests/RepositoryExtensionsTests.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using LibGit2Sharp;
+
+namespace UnityXrefMaps.Tests;
+
+public sealed class RepositoryExtensionsTests : IDisposable
+{
+ private readonly string _tempPath;
+ private readonly Repository _repository;
+
+ public RepositoryExtensionsTests()
+ {
+ _tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ Directory.CreateDirectory(_tempPath);
+ Repository.Init(_tempPath);
+ _repository = new Repository(_tempPath);
+ }
+
+ ///
+ /// Annotated tags (created with a message) have a TagAnnotation as their Target, not a Commit.
+ /// GetTags() was casting Target directly to Commit via (tag.Target as Commit)!, which returned
+ /// null for annotated tags and then threw NullReferenceException on .Author.When.
+ /// Unity's UnityCsReference repository uses annotated tags.
+ ///
+ [Fact]
+ public void GetTags_WithAnnotatedTag_ReturnsTagName()
+ {
+ var (commit, signature) = CreateCommit();
+ _repository.Tags.Add("6000.0.1f1", commit.Sha, signature, "Unity 6000.0.1f1 release");
+
+ Assert.Contains("6000.0.1f1", _repository.GetTags());
+ }
+
+ [Fact]
+ public void GetTags_WithLightweightTag_ReturnsTagName()
+ {
+ // tag.Target is the Commit directly; while-loop in GetTaggedCommit never iterates
+ var (commit, _) = CreateCommit();
+ _repository.Tags.Add("2023.1.0f1", commit);
+
+ Assert.Contains("2023.1.0f1", _repository.GetTags());
+ }
+
+ [Fact]
+ public void GetTags_WithNestedAnnotatedTag_ReturnsTagName()
+ {
+ // outer annotated tag → inner TagAnnotation → Commit; while-loop iterates twice
+ var (commit, signature) = CreateCommit();
+ var innerTag = _repository.Tags.Add("inner", commit.Sha, signature, "Inner annotated tag");
+ _repository.Tags.Add("outer", innerTag.Target.Sha, signature, "Outer tag pointing to inner annotation");
+
+ Assert.Contains("outer", _repository.GetTags());
+ }
+
+ [Fact]
+ public void GetTags_WithTagNotPointingToCommit_IsStillReturned()
+ {
+ // GetTaggedCommit returns null for a blob target; tag sorts last but still appears in output
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes("blob content"));
+ var blob = _repository.ObjectDatabase.CreateBlob(stream);
+ _repository.Tags.Add("blob-tag", blob);
+
+ Assert.Contains("blob-tag", _repository.GetTags());
+ }
+
+ public void Dispose()
+ {
+ _repository.Dispose();
+ try { Directory.Delete(_tempPath, recursive: true); } catch { }
+ }
+
+ private (Commit commit, Signature signature) CreateCommit()
+ {
+ var signature = new Signature("test", "test@test.com", DateTimeOffset.UtcNow);
+ File.WriteAllText(Path.Combine(_tempPath, "file.txt"), "content");
+ _repository.Index.Add("file.txt");
+ _repository.Index.Write();
+ return (_repository.Commit("Initial commit", signature, signature), signature);
+ }
+}
diff --git a/UnityXrefMaps.Tests/UnityXrefMaps.Tests.csproj b/UnityXrefMaps.Tests/UnityXrefMaps.Tests.csproj
index 62ef39c..ccc2e1b 100644
--- a/UnityXrefMaps.Tests/UnityXrefMaps.Tests.csproj
+++ b/UnityXrefMaps.Tests/UnityXrefMaps.Tests.csproj
@@ -8,6 +8,7 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/UnityXrefMaps/RepositoryExtensions.cs b/UnityXrefMaps/RepositoryExtensions.cs
index e3fcda9..c5055c5 100644
--- a/UnityXrefMaps/RepositoryExtensions.cs
+++ b/UnityXrefMaps/RepositoryExtensions.cs
@@ -20,10 +20,20 @@ internal static partial class RepositoryExtensions
public static IEnumerable GetTags(this Repository repository)
{
return repository.Tags
- .OrderByDescending(tag => (tag.Target as Commit)!.Author.When)
+ .OrderByDescending(tag => GetTaggedCommit(tag)?.Author.When ?? DateTimeOffset.MinValue)
.Select(tag => tag.FriendlyName);
}
+ // Lightweight tags point directly to a Commit; annotated tags point to a TagAnnotation
+ // whose own Target is the Commit (possibly through multiple layers of annotation).
+ private static Commit? GetTaggedCommit(Tag tag)
+ {
+ GitObject target = tag.Target;
+ while (target is TagAnnotation annotation)
+ target = annotation.Target;
+ return target as Commit;
+ }
+
///
/// Hard resets the specified to the specified commit.
///
@@ -42,8 +52,8 @@ public static void HardReset(this Repository repository, string commit, ILogger
repository.RemoveUntrackedFiles();
}
catch (Exception) { }
- }
-
+ }
+
public static IEnumerable<(string name, string release)> GetLatestVersions(this Repository unityRepository)
{
return unityRepository