From 5e320663a18e5474b9782f2501149814857175c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Strandg=C3=A5rd?= Date: Sun, 13 Jul 2025 16:22:16 +0200 Subject: [PATCH] Fix GitTreeFileSystem enumeration methods --- src/ProjectDiff.Core/GitTreeFileSystem.cs | 199 ++++++------ src/ProjectDiff.Core/ProjectGraphFactory.cs | 7 +- src/ProjectDiff.Tool/IConsole.cs | 10 + src/ProjectDiff.Tool/ProjectDiffCommand.cs | 11 +- src/ProjectDiff.Tool/ProjectDiffTool.cs | 6 +- .../{ExtendedConsole.cs => SystemConsole.cs} | 14 +- .../Core/GitTreeFileSystemTests.cs | 284 ++++++++++++++++++ ...ithDirectoryScan_format=Json.verified.json | 2 +- ...ithDirectoryScan_format=Plain.verified.txt | 1 - ...irectoryScan_format=Traversal.verified.xml | 1 - .../ProjectDiff.Tests/Utils/TestRepository.cs | 2 + 11 files changed, 423 insertions(+), 114 deletions(-) create mode 100644 src/ProjectDiff.Tool/IConsole.cs rename src/ProjectDiff.Tool/{ExtendedConsole.cs => SystemConsole.cs} (50%) create mode 100644 test/ProjectDiff.Tests/Core/GitTreeFileSystemTests.cs diff --git a/src/ProjectDiff.Core/GitTreeFileSystem.cs b/src/ProjectDiff.Core/GitTreeFileSystem.cs index a70a4e2..631c372 100644 --- a/src/ProjectDiff.Core/GitTreeFileSystem.cs +++ b/src/ProjectDiff.Core/GitTreeFileSystem.cs @@ -8,29 +8,37 @@ namespace ProjectDiff.Core; -internal sealed class GitTreeFileSystem : MSBuildFileSystemBase +public sealed class GitTreeFileSystem : MSBuildFileSystemBase { - private readonly DirectoryInfo _directory; + private readonly Repository _repository; private readonly Tree _tree; private readonly ProjectCollection _projectCollection; private readonly Dictionary _globalProperties; public GitTreeFileSystem( - DirectoryInfo directory, + Repository repository, Tree tree, ProjectCollection projectCollection, Dictionary globalProperties ) { - _directory = directory; + _repository = repository; _tree = tree; _projectCollection = projectCollection; _globalProperties = globalProperties; } + public bool LazyLoadProjects { get; set; } = true; + public override TextReader ReadFile(string path) { - throw new NotSupportedException("ReadFile"); + if (!ShouldUseTree(path)) + { + return base.ReadFile(path); + } + + using var stream = GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + return new StreamReader(stream); } public override Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) @@ -62,12 +70,27 @@ public override Stream GetFileStream(string path, FileMode mode, FileAccess acce public override string ReadFileAllText(string path) { - throw new NotSupportedException("ReadFileAllText"); + if (!ShouldUseTree(path)) + { + return base.ReadFileAllText(path); + } + + using var stream = GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); } public override byte[] ReadFileAllBytes(string path) { - throw new NotSupportedException("ReadFileAllBytes"); + if (!ShouldUseTree(path)) + { + return base.ReadFileAllBytes(path); + } + + using var stream = GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + return memoryStream.ToArray(); } public override IEnumerable EnumerateFiles( @@ -81,21 +104,10 @@ public override IEnumerable EnumerateFiles( return base.EnumerateFiles(path, searchPattern, searchOption); } - - var entries = EnumerateTreeFileSystemEntries(path, searchPattern, TreeEntryTargetType.Blob); - - if (searchOption == SearchOption.TopDirectoryOnly) - { - return entries; - } - - - return entries.Concat( - EnumerateDirectories(path) - .SelectMany(dir => EnumerateFiles(dir, searchPattern, searchOption)) - ); + return EnumerateTree(path, searchPattern, searchOption, TreeEntryTargetType.Blob); } + public override IEnumerable EnumerateDirectories( string path, string searchPattern = "*", @@ -107,13 +119,7 @@ public override IEnumerable EnumerateDirectories( return base.EnumerateDirectories(path, searchPattern, searchOption); } - if (searchOption != SearchOption.TopDirectoryOnly) - { - throw new NotSupportedException("EnumerateDirectories"); - } - - - return EnumerateTreeFileSystemEntries(path, searchPattern, TreeEntryTargetType.Tree); + return EnumerateTree(path, searchPattern, searchOption, TreeEntryTargetType.Tree); } @@ -128,52 +134,7 @@ public override IEnumerable EnumerateFileSystemEntries( return base.EnumerateFileSystemEntries(path, searchPattern, searchOption); } - if (searchOption != SearchOption.TopDirectoryOnly) - { - throw new NotSupportedException("EnumerateFileSystemEntries"); - } - - return EnumerateTreeFileSystemEntries(path, searchPattern, null); - } - - private IEnumerable EnumerateTreeFileSystemEntries( - string path, - string searchPattern, - TreeEntryTargetType? targetType - ) - { - var p = RelativePath(path); - - var entry = _tree[p]; - if (entry == null) - { - yield break; - } - - if (entry.TargetType != TreeEntryTargetType.Tree) - { - yield break; - } - - var treeEntry = (Tree)entry.Target; - - foreach (var e in treeEntry) - { - if (targetType.HasValue && e.TargetType != targetType) - { - continue; - } - - if (!FileSystemName.MatchesWin32Expression(searchPattern, e.Name)) - { - continue; - } - - - var fullPath = Path.GetFullPath(e.Path, _directory.FullName); - - yield return fullPath; - } + return EnumerateTree(path, searchPattern, searchOption, null); } public override FileAttributes GetAttributes(string path) @@ -214,18 +175,16 @@ public override bool FileExists(string path) return false; } - if (!IsProject(entry)) - { - return true; - } - - // HACK: Since Imports doesn't use the file system we have to manually load the projects - // whenever msbuild tries to load them. - lock (_projectLoadLock) + if (LazyLoadProjects && IsProject(entry)) { - if (_projectCollection.GetLoadedProjects(path).Count == 0) + // HACK: Since Imports doesn't use the file system we have to manually load the projects + // whenever msbuild tries to load them. + lock (_projectLoadLock) { - LoadProject(path, _globalProperties, _projectCollection); + if (_projectCollection.GetLoadedProjects(path).Count == 0) + { + LoadProject(path, _globalProperties, _projectCollection); + } } } @@ -254,11 +213,11 @@ public override bool FileOrDirectoryExists(string path) } private string RelativePath(string path) => - Path.GetRelativePath(_directory.FullName, path) + Path.GetRelativePath(_repository.Info.WorkingDirectory, path) .Replace('\\', '/'); private bool ShouldUseTree(string path) => - path.StartsWith(_directory.FullName); + path.StartsWith(_repository.Info.WorkingDirectory); public Project LoadProject( string path, @@ -277,7 +236,7 @@ ProjectCollection projects if (entry is null || entry.TargetType != TreeEntryTargetType.Blob) { - throw new NotImplementedException(); + throw new InvalidOperationException("Tried loading a project that is not a blob"); } var blob = (Blob)entry.Target; @@ -296,4 +255,72 @@ ProjectCollection projects } ); } + + + private IEnumerable EnumerateTree( + string path, + string searchPattern, + SearchOption searchOption, + TreeEntryTargetType? targetType + ) + { + var (tree, treePath) = FindTree(path); + if (searchOption == SearchOption.TopDirectoryOnly) + { + return tree + .Where(ShouldInclude) + .Select(it => Path.GetFullPath(Path.Combine(treePath, it.Path))); + } + + return ExpandTree(tree, treePath) + .Where(e => ShouldInclude(e.Entry)) + .Select(it => it.Path); + + bool ShouldInclude(TreeEntry entry) + { + if (targetType.HasValue && entry.TargetType != targetType) + { + return false; + } + + return FileSystemName.MatchesWin32Expression(searchPattern, entry.Name); + } + } + + private static IEnumerable<(string Path, TreeEntry Entry)> ExpandTree(Tree tree, string parentPath) + { + foreach (var entry in tree) + { + var fullPath = Path.Combine(parentPath, entry.Path); + + yield return (fullPath, entry); + + if (entry.TargetType == TreeEntryTargetType.Tree) + { + var subTree = entry.Target.Peel(); + foreach (var subEntry in ExpandTree(subTree, fullPath)) + { + yield return subEntry; + } + } + } + } + + private (Tree Tree, string Path) FindTree(string path) + { + var relativePath = RelativePath(path); + if (relativePath == ".") + { + return (_tree, _repository.Info.WorkingDirectory); + } + + var entry = _tree[relativePath]; + if (entry == null || entry.TargetType != TreeEntryTargetType.Tree) + { + throw new InvalidOperationException("Tried to enumerate files in a path that is not a tree"); + } + + return (entry.Target.Peel(), + Path.GetFullPath(Path.Combine(_repository.Info.WorkingDirectory, relativePath))); + } } \ No newline at end of file diff --git a/src/ProjectDiff.Core/ProjectGraphFactory.cs b/src/ProjectDiff.Core/ProjectGraphFactory.cs index 31ac85b..c3cbba8 100644 --- a/src/ProjectDiff.Core/ProjectGraphFactory.cs +++ b/src/ProjectDiff.Core/ProjectGraphFactory.cs @@ -21,13 +21,15 @@ public static async Task BuildForGitTree( using var projectCollection = new ProjectCollection(); var fs = new GitTreeFileSystem( - new DirectoryInfo(repository.Info.WorkingDirectory), + repository, tree, projectCollection, [] ); + fs.LazyLoadProjects = false; var entrypoints = await entrypointProvider.GetEntrypoints(fs, cancellationToken); + fs.LazyLoadProjects = true; var graph = new ProjectGraph( entrypoints, @@ -57,7 +59,7 @@ public static async Task BuildForWorkingDirectory( using var projectCollection = new ProjectCollection(); var fs = new DefaultFileSystem(); - var entrypoints = await solutionFile.GetEntrypoints(fs, cancellationToken); + var entrypoints = (await solutionFile.GetEntrypoints(fs, cancellationToken)).ToList(); var graph = new ProjectGraph( entrypoints, projectCollection, @@ -76,6 +78,5 @@ public static async Task BuildForWorkingDirectory( } - private sealed class DefaultFileSystem : MSBuildFileSystemBase; } \ No newline at end of file diff --git a/src/ProjectDiff.Tool/IConsole.cs b/src/ProjectDiff.Tool/IConsole.cs new file mode 100644 index 0000000..2ca407a --- /dev/null +++ b/src/ProjectDiff.Tool/IConsole.cs @@ -0,0 +1,10 @@ +namespace ProjectDiff.Tool; + +public interface IConsole +{ + TextWriter Error { get; } + TextWriter Out { get; } + + Stream OpenStandardOutput(); + string WorkingDirectory { get; } +} \ No newline at end of file diff --git a/src/ProjectDiff.Tool/ProjectDiffCommand.cs b/src/ProjectDiff.Tool/ProjectDiffCommand.cs index 470f0f5..cc36524 100644 --- a/src/ProjectDiff.Tool/ProjectDiffCommand.cs +++ b/src/ProjectDiff.Tool/ProjectDiffCommand.cs @@ -203,7 +203,7 @@ CancellationToken cancellationToken if (result.Status != ProjectDiffExecutionStatus.Success) { - await _console.Error.WriteLineAsync(result.Status.ToString()); + WriteError(_console, $"Failed to calculate project diff: {result.Status}"); return 1; } @@ -282,10 +282,11 @@ bool absolutePaths ), ReferencedProjects = project.ReferencedProjects .Select(refProject => NormalizePath( - output.RootDirectory, - refProject, - absolutePaths - )).ToList() + output.RootDirectory, + refProject, + absolutePaths + ) + ).ToList() } ); diff --git a/src/ProjectDiff.Tool/ProjectDiffTool.cs b/src/ProjectDiff.Tool/ProjectDiffTool.cs index 988ccee..58d5e87 100644 --- a/src/ProjectDiff.Tool/ProjectDiffTool.cs +++ b/src/ProjectDiff.Tool/ProjectDiffTool.cs @@ -5,12 +5,10 @@ namespace ProjectDiff.Tool; public sealed class ProjectDiffTool { private readonly CommandLineConfiguration _cli; - private readonly IConsole _console; - private ProjectDiffTool(CommandLineConfiguration cli, IConsole console) + private ProjectDiffTool(CommandLineConfiguration cli) { _cli = cli; - _console = console; } @@ -23,7 +21,7 @@ public Task InvokeAsync(string[] args) public static ProjectDiffTool Create(IConsole console) { var parser = BuildCli(console); - return new ProjectDiffTool(parser, console); + return new ProjectDiffTool(parser); } private static CommandLineConfiguration BuildCli(IConsole console) diff --git a/src/ProjectDiff.Tool/ExtendedConsole.cs b/src/ProjectDiff.Tool/SystemConsole.cs similarity index 50% rename from src/ProjectDiff.Tool/ExtendedConsole.cs rename to src/ProjectDiff.Tool/SystemConsole.cs index 12e9f2d..674b9fb 100644 --- a/src/ProjectDiff.Tool/ExtendedConsole.cs +++ b/src/ProjectDiff.Tool/SystemConsole.cs @@ -5,19 +5,7 @@ public sealed class SystemConsole : IConsole public TextWriter Error { get; } = Console.Error; public TextWriter Out { get; } = Console.Out; - public Stream OpenStandardOutput() - { - return Console.OpenStandardOutput(); - } + public Stream OpenStandardOutput() => Console.OpenStandardOutput(); public string WorkingDirectory { get; } = Directory.GetCurrentDirectory(); -} - -public interface IConsole -{ - TextWriter Error { get; } - TextWriter Out { get; } - - Stream OpenStandardOutput(); - string WorkingDirectory { get; } } \ No newline at end of file diff --git a/test/ProjectDiff.Tests/Core/GitTreeFileSystemTests.cs b/test/ProjectDiff.Tests/Core/GitTreeFileSystemTests.cs new file mode 100644 index 0000000..a48c09e --- /dev/null +++ b/test/ProjectDiff.Tests/Core/GitTreeFileSystemTests.cs @@ -0,0 +1,284 @@ +using LibGit2Sharp; +using Microsoft.Build.Evaluation; +using ProjectDiff.Core; +using ProjectDiff.Tests.Utils; + +namespace ProjectDiff.Tests.Core; + +public sealed class GitTreeFileSystemTests +{ + [Fact] + public async Task EnumerateDirectoriesReturnsDirectories() + { + using var repo = await TestRepository.SetupAsync(async repo => + { + repo.CreateDirectory("subdir1"); + repo.CreateDirectory("subdir2"); + await repo.WriteFileAsync("subdir1/test.txt", "Hello, World!"); + await repo.WriteFileAsync("subdir2/another.txt", "Another file"); + } + ); + + using var projects = new ProjectCollection(); + var fileSystem = new GitTreeFileSystem( + repo, + repo.HeadTree, + projects, + [] + ); + + var directories = fileSystem.EnumerateDirectories( + repo.WorkingDirectory, + "*", + SearchOption.TopDirectoryOnly + ).ToList(); + + var expectedDirectories = new[] + { + Path.Combine(repo.WorkingDirectory, "subdir1"), + Path.Combine(repo.WorkingDirectory, "subdir2") + }; + + Assert.Equivalent(expectedDirectories, directories); + } + + [Fact] + public async Task EnumerateDirectoriesInSubdirectoryReturnsSubdirectories() + { + using var repo = await TestRepository.SetupAsync(async repo => + { + repo.CreateDirectory("subdir"); + repo.CreateDirectory("subdir/nested"); + await repo.WriteFileAsync("subdir/test.txt", "Hello, World!"); + await repo.WriteFileAsync("subdir/nested/nested.txt", "Nested file"); + } + ); + + using var projects = new ProjectCollection(); + var fileSystem = new GitTreeFileSystem( + repo, + repo.HeadTree, + projects, + [] + ); + + var directories = fileSystem.EnumerateDirectories( + Path.Combine(repo.WorkingDirectory, "subdir"), + "*", + SearchOption.TopDirectoryOnly + ).ToList(); + + var expectedDirectories = new[] + { + Path.Combine(repo.WorkingDirectory, "subdir", "nested") + }; + + Assert.Equivalent(expectedDirectories, directories); + } + + [Fact] + public async Task EnumerateDirectoriesWithSearchOptionAllDirectoriesReturnsAllDirectories() + { + using var repo = await TestRepository.SetupAsync(async repo => + { + repo.CreateDirectory("subdir1"); + repo.CreateDirectory("subdir2"); + repo.CreateDirectory("subdir1/nested"); + repo.CreateDirectory("subdir2/another"); + await repo.WriteFileAsync("subdir1/test.txt", "Hello, World!"); + await repo.WriteFileAsync("subdir2/another/another.txt", "Another file"); + await repo.WriteFileAsync("subdir1/nested/nested.txt", "Nested file"); + await repo.WriteFileAsync("test.txt", "Root file"); + } + ); + + using var projects = new ProjectCollection(); + var fileSystem = new GitTreeFileSystem( + repo, + repo.HeadTree, + projects, + [] + ); + + var directories = fileSystem.EnumerateDirectories( + repo.WorkingDirectory, + "*", + SearchOption.AllDirectories + ).ToList(); + + string[] expectedDirectories = [ + Path.Combine(repo.WorkingDirectory, "subdir1"), + Path.Combine(repo.WorkingDirectory, "subdir1", "nested"), + Path.Combine(repo.WorkingDirectory, "subdir2"), + Path.Combine(repo.WorkingDirectory, "subdir2", "another") + ]; + + Assert.Equivalent(expectedDirectories, directories); + } + + [Fact] + public void EnumerateFilesInEmptyDirectoryReturnsEmpty() + { + using var repo = TestRepository.CreateEmpty(); + + using var projects = new ProjectCollection(); + var fileSystem = new GitTreeFileSystem( + repo, + repo.HeadTree, + projects, + [] + ); + + var files = fileSystem.EnumerateFiles( + repo.WorkingDirectory, + "*", + SearchOption.TopDirectoryOnly + ); + + Assert.Empty(files); + } + + [Fact] + public async Task EnumerateFilesInDirectoryWithFileReturnsFile() + { + using var repo = await TestRepository.SetupAsync(async repo => + { + await repo.WriteFileAsync("test.txt", "Hello, World!"); + } + ); + + using var projects = new ProjectCollection(); + var fileSystem = new GitTreeFileSystem( + repo, + repo.HeadTree, + projects, + [] + ); + + var files = fileSystem.EnumerateFiles( + repo.WorkingDirectory, + "*", + SearchOption.TopDirectoryOnly + ); + + var file = Assert.Single(files); + + Assert.Equal( + Path.Combine(repo.WorkingDirectory, "test.txt"), + file + ); + } + + [Fact] + public async Task EnumerateFilesInDirectoryWithSubdirectoryReturnsFiles() + { + using var repo = await TestRepository.SetupAsync(async repo => + { + repo.CreateDirectory("subdir"); + await repo.WriteFileAsync("subdir/test.txt", "Hello, World!"); + await repo.WriteFileAsync("subdir/another.txt", "Another file"); + + // Should not be included + await repo.WriteFileAsync("test.txt", "Root file"); + + repo.CreateDirectory("subdir/nested"); + await repo.WriteFileAsync("subdir/nested/nested.txt", "Nested file"); + } + ); + + using var projects = new ProjectCollection(); + var fileSystem = new GitTreeFileSystem( + repo, + repo.HeadTree, + projects, + [] + ); + + var files = fileSystem.EnumerateFiles( + Path.Combine(repo.WorkingDirectory, "subdir"), + "*", + SearchOption.TopDirectoryOnly + ).ToList(); + + string[] expectedFiles = + [ + Path.Combine(repo.WorkingDirectory, "subdir", "test.txt"), + Path.Combine(repo.WorkingDirectory, "subdir", "another.txt") + ]; + + Assert.Equivalent(expectedFiles, files); + } + + [Fact] + public async Task EnumerateFilesWithSearchOptionAllDirectoriesReturnsAllFiles() + { + using var repo = await TestRepository.SetupAsync(async repo => + { + repo.CreateDirectory("subdir"); + await repo.WriteFileAsync("subdir/test.txt", "Hello, World!"); + await repo.WriteFileAsync("subdir/another.txt", "Another file"); + await repo.WriteFileAsync("test.txt", "Root file"); + } + ); + + using var projects = new ProjectCollection(); + var fileSystem = new GitTreeFileSystem( + repo, + repo.HeadTree, + projects, + [] + ); + + var files = fileSystem.EnumerateFiles( + repo.WorkingDirectory, + "*", + SearchOption.AllDirectories + ); + + string[] expectedFiles = + [ + Path.Combine(repo.WorkingDirectory, "subdir", "test.txt"), + Path.Combine(repo.WorkingDirectory, "subdir", "another.txt"), + Path.Combine(repo.WorkingDirectory, "test.txt") + ]; + + + Assert.Equivalent(expectedFiles, files); + } + + + [Fact] + public async Task EnumerateFilesInSubdirectoryWithSearchOptionAllDirectoriesReturnsFilesInSubdirectory() + { + using var repo = await TestRepository.SetupAsync(async repo => + { + repo.CreateDirectory("subdir"); + await repo.WriteFileAsync("subdir/test.txt", "Hello, World!"); + await repo.WriteFileAsync("subdir/another.txt", "Another file"); + await repo.WriteFileAsync("test.txt", "Root file"); + } + ); + + using var projects = new ProjectCollection(); + var fileSystem = new GitTreeFileSystem( + repo, + repo.HeadTree, + projects, + [] + ); + + var files = fileSystem.EnumerateFiles( + Path.Combine(repo.WorkingDirectory, "subdir"), + "*", + SearchOption.AllDirectories + ); + + string[] expectedFiles = + [ + Path.Combine(repo.WorkingDirectory, "subdir", "test.txt"), + Path.Combine(repo.WorkingDirectory, "subdir", "another.txt"), + ]; + + Assert.Equivalent(expectedFiles, files); + } +} \ No newline at end of file diff --git a/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Json.verified.json b/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Json.verified.json index cd0c935..befbb70 100644 --- a/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Json.verified.json +++ b/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Json.verified.json @@ -1 +1 @@ -[{"path":"Added/Added.csproj","status":"Added","referencedProjects":[]},{"path":"Sample/Sample.csproj","status":"Added","referencedProjects":[]}] \ No newline at end of file +[{"path":"Added/Added.csproj","status":"Added","referencedProjects":[]}] \ No newline at end of file diff --git a/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Plain.verified.txt b/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Plain.verified.txt index ffbcbb9..1adaf30 100644 --- a/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Plain.verified.txt +++ b/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Plain.verified.txt @@ -1,2 +1 @@ Added/Added.csproj -Sample/Sample.csproj diff --git a/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Traversal.verified.xml b/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Traversal.verified.xml index 0b7db70..50cf172 100644 --- a/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Traversal.verified.xml +++ b/test/ProjectDiff.Tests/Tool/ProjectDiffTests.DetectsAddedProjectsWithDirectoryScan_format=Traversal.verified.xml @@ -1,6 +1,5 @@  - \ No newline at end of file diff --git a/test/ProjectDiff.Tests/Utils/TestRepository.cs b/test/ProjectDiff.Tests/Utils/TestRepository.cs index b1568e6..6ff6b0a 100644 --- a/test/ProjectDiff.Tests/Utils/TestRepository.cs +++ b/test/ProjectDiff.Tests/Utils/TestRepository.cs @@ -50,6 +50,8 @@ public static TestRepository CreateEmpty() public Identity Identity { get; set; } = DefaultIdentity; public string WorkingDirectory => _repository.Info.WorkingDirectory; + public Tree HeadTree => _repository.Head.Tip.Tree; + public void DeleteDirectory(string path, bool recursive = false) { Directory.Delete(GetPath(path), recursive);