Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 113 additions & 86 deletions src/ProjectDiff.Core/GitTreeFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> _globalProperties;

public GitTreeFileSystem(
DirectoryInfo directory,
Repository repository,
Tree tree,
ProjectCollection projectCollection,
Dictionary<string, string> 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)
Expand Down Expand Up @@ -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<string> EnumerateFiles(
Expand All @@ -81,21 +104,10 @@ public override IEnumerable<string> 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<string> EnumerateDirectories(
string path,
string searchPattern = "*",
Expand All @@ -107,13 +119,7 @@ public override IEnumerable<string> 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);
}


Expand All @@ -128,52 +134,7 @@ public override IEnumerable<string> EnumerateFileSystemEntries(
return base.EnumerateFileSystemEntries(path, searchPattern, searchOption);
}

if (searchOption != SearchOption.TopDirectoryOnly)
{
throw new NotSupportedException("EnumerateFileSystemEntries");
}

return EnumerateTreeFileSystemEntries(path, searchPattern, null);
}

private IEnumerable<string> 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)
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -296,4 +255,72 @@ ProjectCollection projects
}
);
}


private IEnumerable<string> 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<Tree>();
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<Tree>(),
Path.GetFullPath(Path.Combine(_repository.Info.WorkingDirectory, relativePath)));
}
}
7 changes: 4 additions & 3 deletions src/ProjectDiff.Core/ProjectGraphFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ public static async Task<ProjectGraph> 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,
Expand Down Expand Up @@ -57,7 +59,7 @@ public static async Task<ProjectGraph> 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,
Expand All @@ -76,6 +78,5 @@ public static async Task<ProjectGraph> BuildForWorkingDirectory(
}



private sealed class DefaultFileSystem : MSBuildFileSystemBase;
}
10 changes: 10 additions & 0 deletions src/ProjectDiff.Tool/IConsole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace ProjectDiff.Tool;

public interface IConsole
{
TextWriter Error { get; }
TextWriter Out { get; }

Stream OpenStandardOutput();
string WorkingDirectory { get; }
}
11 changes: 6 additions & 5 deletions src/ProjectDiff.Tool/ProjectDiffCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -282,10 +282,11 @@ bool absolutePaths
),
ReferencedProjects = project.ReferencedProjects
.Select(refProject => NormalizePath(
output.RootDirectory,
refProject,
absolutePaths
)).ToList()
output.RootDirectory,
refProject,
absolutePaths
)
).ToList()
}
);

Expand Down
6 changes: 2 additions & 4 deletions src/ProjectDiff.Tool/ProjectDiffTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}


Expand All @@ -23,7 +21,7 @@ public Task<int> 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)
Expand Down
Loading
Loading