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
5 changes: 4 additions & 1 deletion src/ProjectDiff.Core/BuildGraphDiff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ public IEnumerable<DiffProject> Execute(BuildGraph previous, FrozenSet<string> m
var existsInCurrent = _graph.Projects.Any(it => it.FullPath == previousProject.FullPath);
if (!existsInCurrent)
{
_logger.LogDebug("Project {Path} not found in current graph, marking as removed", previousProject.FullPath);
_logger.LogDebug(
"Project {Path} not found in current graph, marking as removed",
previousProject.FullPath
);
yield return new DiffProject
{
Path = previousProject.FullPath,
Expand Down
10 changes: 8 additions & 2 deletions src/ProjectDiff.Core/BuildGraphFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ public void AddInputFile(string path, ProjectInstance projectInstance, string pr
{
path = Path.GetFullPath(path, projectInstance.Directory);
}
else
{
path = Path.GetFullPath(path);
}

// Only include files that are part of this repository
if (!path.StartsWith(_repository.Info.WorkingDirectory))
Expand Down Expand Up @@ -113,7 +117,7 @@ public BuildGraph Build()

return new BuildGraph
{
Projects = projects.OrderBy(it => it.References.Count).ToList(),
Projects = projects.ToList(),
};
}

Expand All @@ -132,7 +136,9 @@ private IEnumerable<BuildGraphProject> BuildGraphProjects()
collector.AddReferences(references);
}

return _collectors.Values.Select(it => it.ToBuildGraphProject());
return _projectGraph.ProjectNodesTopologicallySorted
.DistinctBy(it => it.ProjectInstance.FullPath)
.Select(it => _collectors[it.ProjectInstance.FullPath].ToBuildGraphProject());
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.Build.FileSystem;
using Microsoft.Build.Graph;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace ProjectDiff.Core.Entrypoints;

public sealed class DirectoryScanProjectGraphEntryPointProvider : IProjectGraphEntryPointProvider
{
private readonly ILogger<DirectoryScanProjectGraphEntryPointProvider> _logger;
private readonly string _repositoryWorkingDirectory;


public DirectoryScanProjectGraphEntryPointProvider(
string repositoryWorkingDirectory,
ILogger<DirectoryScanProjectGraphEntryPointProvider>? logger = null
)
{
_repositoryWorkingDirectory = repositoryWorkingDirectory;
_logger = logger ?? NullLogger<DirectoryScanProjectGraphEntryPointProvider>.Instance;
}

public Task<IEnumerable<ProjectGraphEntryPoint>> GetEntryPoints(
MSBuildFileSystemBase fs,
CancellationToken cancellationToken
)
{
_logger.LogDebug("Scanning directory '{Directory}' for project files", _repositoryWorkingDirectory);
var entrypoints = fs.EnumerateFiles(_repositoryWorkingDirectory, "*.csproj", SearchOption.AllDirectories)
.Select(it => new ProjectGraphEntryPoint(it));


return Task.FromResult(entrypoints);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@

namespace ProjectDiff.Core.Entrypoints;

public interface IEntrypointProvider
public interface IProjectGraphEntryPointProvider
{
Task<IEnumerable<ProjectGraphEntryPoint>> GetEntrypoints(
string repositoryWorkingDirectory,
Task<IEnumerable<ProjectGraphEntryPoint>> GetEntryPoints(
MSBuildFileSystemBase fs,
CancellationToken cancellationToken
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@

namespace ProjectDiff.Core.Entrypoints;

public sealed class SolutionEntrypointProvider : IEntrypointProvider
public sealed class SolutionProjectGraphEntryPointProvider : IProjectGraphEntryPointProvider
{
private readonly FileInfo _solution;
private readonly ILogger<SolutionEntrypointProvider> _logger;
private readonly ILogger<SolutionProjectGraphEntryPointProvider> _logger;

public SolutionEntrypointProvider(FileInfo solution, ILogger<SolutionEntrypointProvider> logger)
public SolutionProjectGraphEntryPointProvider(
FileInfo solution,
ILogger<SolutionProjectGraphEntryPointProvider> logger
)
{
_solution = solution;
_logger = logger;
}

public async Task<IEnumerable<ProjectGraphEntryPoint>> GetEntrypoints(
string repositoryWorkingDirectory,
public async Task<IEnumerable<ProjectGraphEntryPoint>> GetEntryPoints(
MSBuildFileSystemBase fs,
CancellationToken cancellationToken
)
Expand Down
1 change: 0 additions & 1 deletion src/ProjectDiff.Core/ProjectDiffExecutionStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public enum ProjectDiffExecutionStatus
{
Success,

RepositoryNotFound,
BaseCommitNotFound,
HeadCommitNotFound,
MergeBaseNotFound,
Expand Down
123 changes: 58 additions & 65 deletions src/ProjectDiff.Core/ProjectDiffExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,20 @@ public ProjectDiffExecutor(ProjectDiffExecutorOptions options, ILoggerFactory? l
}

public async Task<ProjectDiffResult> GetProjectDiff(
string repositoryPath,
IEntrypointProvider entrypointProvider,
Repository repository,
IProjectGraphEntryPointProvider projectGraphEntryPointProvider,
string baseCommitRef = "HEAD",
string? headCommitRef = null,
CancellationToken cancellationToken = default
)
{
_logger.LogDebug("Discovering repository from path '{Path}'", repositoryPath);
var repoPath = Repository.Discover(repositoryPath);
if (repoPath is null)
if (repository.Info.IsShallow)
{
_logger.LogError("Could not find a Git repository for path '{Path}'", repositoryPath);
return new ProjectDiffResult
{
Status = ProjectDiffExecutionStatus.RepositoryNotFound
};
}

_logger.LogDebug("Found repository at '{RepoPath}'", repoPath);

using var repo = new Repository(repoPath);
if (repo.Info.IsShallow)
{
_logger.LogWarning("Repository at is shallow, some operations may not work as expected");
_logger.LogWarning("Repository is shallow, some operations may not work as expected");
}

_logger.LogDebug("Looking up base commit '{BaseCommitRef}'", baseCommitRef);
var baseCommit = repo.Lookup<Commit>(baseCommitRef);
var baseCommit = repository.Lookup<Commit>(baseCommitRef);
if (baseCommit is null)
{
_logger.LogError("Base commit '{BaseCommitRef}' not found in repository", baseCommitRef);
Expand All @@ -62,7 +48,7 @@ public async Task<ProjectDiffResult> GetProjectDiff(
if (headCommitRef is not null)
{
_logger.LogDebug("Looking up head commit '{HeadCommitRef}'", headCommitRef);
headCommit = repo.Lookup<Commit>(headCommitRef);
headCommit = repository.Lookup<Commit>(headCommitRef);

if (headCommit is null)
{
Expand All @@ -86,13 +72,14 @@ public async Task<ProjectDiffResult> GetProjectDiff(
baseCommitRef,
headCommitRef
);
var mergeBaseCommit = repo.ObjectDatabase.FindMergeBase(baseCommit, headCommit ?? repo.Head.Tip);
var head = headCommit ?? repository.Head.Tip;
var mergeBaseCommit = repository.ObjectDatabase.FindMergeBase(baseCommit, head);
if (mergeBaseCommit is null)
{
_logger.LogError(
"Could not find merge base between base commit '{BaseCommitRef}' and head commit '{HeadCommitRef}'",
baseCommitRef,
headCommitRef
head.Sha
);
return new ProjectDiffResult
{
Expand All @@ -113,11 +100,10 @@ public async Task<ProjectDiffResult> GetProjectDiff(
baseCommitRef,
headCommitRef ?? "working directory"
);
var changedFiles = GetGitModifiedFiles(repo, baseCommit, headCommit)
var changedFiles = GetGitModifiedFiles(repository, baseCommit, headCommit)
.Where(ShouldIncludeFile)
.ToList();


if (changedFiles.Count == 0)
{
_logger.LogInformation(
Expand All @@ -133,7 +119,6 @@ public async Task<ProjectDiffResult> GetProjectDiff(
};
}


_logger.LogInformation("Found {NumChangedFiles} changed files", changedFiles.Count);
if (_logger.IsEnabled(LogLevel.Debug))
{
Expand All @@ -142,56 +127,32 @@ public async Task<ProjectDiffResult> GetProjectDiff(

var projectGraphFactory = new ProjectGraphFactory(_loggerFactory);

var baseGraph = await projectGraphFactory.BuildForGitTree(
repo,
baseCommit.Tree,
entrypointProvider,
cancellationToken
);
_logger.LogInformation(
"Base project graph built with {NumProjects} projects",
baseGraph.ProjectNodes.Count
);

ProjectGraph headGraph;
if (headCommit is null)
BuildGraph baseBuildGraph;
using (_logger.BeginScope("Building base graph"))
{
headGraph = await projectGraphFactory.BuildForWorkingDirectory(
repo,
entrypointProvider,
baseBuildGraph = await CreateBuildGraph(
repository,
projectGraphFactory,
projectGraphEntryPointProvider,
baseCommit,
cancellationToken
);
}
else

BuildGraph headBuildGraph;
using (_logger.BeginScope("Building head graph"))
{
headGraph = await projectGraphFactory.BuildForGitTree(
repo,
headCommit.Tree,
entrypointProvider,
headBuildGraph = await CreateBuildGraph(
repository,
projectGraphFactory,
projectGraphEntryPointProvider,
headCommit,
cancellationToken
);
}

_logger.LogInformation(
"Head project graph built with {NumProjects} projects",
headGraph.ProjectNodes.Count
);


var headBuildGraph = BuildGraphFactory.CreateForProjectGraph(
headGraph,
repo,
_options.IgnoreChangedFiles
);
var baseBuildGraph = BuildGraphFactory.CreateForProjectGraph(
baseGraph,
repo,
_options.IgnoreChangedFiles
);

var projects = BuildGraphDiff.Diff(baseBuildGraph, headBuildGraph, changedFiles, _loggerFactory)
.OrderBy(it => it.ReferencedProjects.Count)
.ThenBy(it => it.Name);
var projects = BuildGraphDiff.Diff(baseBuildGraph, headBuildGraph, changedFiles, _loggerFactory);
return new ProjectDiffResult
{
Status = ProjectDiffExecutionStatus.Success,
Expand All @@ -203,6 +164,38 @@ bool ShouldIncludeFile(string file) =>
_options.IgnoreChangedFiles.Length == 0 || _options.IgnoreChangedFiles.All(it => it.FullName != file);
}

private async Task<BuildGraph> CreateBuildGraph(
Repository repository,
ProjectGraphFactory projectGraphFactory,
IProjectGraphEntryPointProvider projectGraphEntryPointProvider,
Commit? headCommit,
CancellationToken cancellationToken
)
{
ProjectGraph projectGraph;
if (headCommit is null)
{
projectGraph = await projectGraphFactory.BuildForWorkingDirectory(
projectGraphEntryPointProvider,
cancellationToken
);
}
else
{
projectGraph = await projectGraphFactory.BuildForGitTree(
repository,
headCommit.Tree,
projectGraphEntryPointProvider,
cancellationToken
);
}

return BuildGraphFactory.CreateForProjectGraph(
projectGraph,
repository,
_options.IgnoreChangedFiles
);
}

private static IEnumerable<string> GetGitModifiedFiles(
Repository repository,
Expand Down
1 change: 1 addition & 0 deletions src/ProjectDiff.Core/ProjectDiffExecutorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ public sealed class ProjectDiffExecutorOptions
{
public bool FindMergeBase { get; init; }
public FileInfo[] IgnoreChangedFiles { get; init; } = [];
public IReadOnlyDictionary<string, string> GlobalProperties { get; init; } = new Dictionary<string, string>(0);
}
2 changes: 2 additions & 0 deletions src/ProjectDiff.Core/ProjectDiffResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ public sealed record ProjectDiffResult
public required ProjectDiffExecutionStatus Status { get; init; }
public IEnumerable<DiffProject> Projects { get; init; } = [];
public IReadOnlyCollection<string> ChangedFiles { get; init; } = [];

public bool IsSuccessful => Status == ProjectDiffExecutionStatus.Success;
}
Loading
Loading