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