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
3 changes: 1 addition & 2 deletions src/ProjectDiff.Core/ProjectDiff.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GitVersion.MsBuild" Version="6.1.0">
<PackageReference Include="GitVersion.MsBuild" Version="6.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="LibGit2Sharp" Version="0.31.0"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
Expand Down
35 changes: 10 additions & 25 deletions src/ProjectDiff.Tool/ExtendedConsole.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,23 @@
using System.CommandLine;
using System.CommandLine.IO;
namespace ProjectDiff.Tool;

namespace ProjectDiff.Tool;

public sealed class ExtendedConsole : IExtendedConsole
public sealed class SystemConsole : IConsole
{
private readonly SystemConsole _console;

public ExtendedConsole()
{
_console = new SystemConsole();
WorkingDirectory = Directory.GetCurrentDirectory();
}

public IStandardStreamWriter Error => _console.Error;

public bool IsErrorRedirected => _console.IsErrorRedirected;

public IStandardStreamWriter Out => _console.Out;

public bool IsOutputRedirected => _console.IsOutputRedirected;

public bool IsInputRedirected => _console.IsInputRedirected;

public string WorkingDirectory { get; }
public TextWriter Error { get; } = Console.Error;
public TextWriter Out { get; } = Console.Out;

public Stream OpenStandardOutput()
{
return Console.OpenStandardOutput();
}

public string WorkingDirectory { get; } = Directory.GetCurrentDirectory();
}

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

Stream OpenStandardOutput();
string WorkingDirectory { get; }
}
5 changes: 3 additions & 2 deletions src/ProjectDiff.Tool/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using Microsoft.Build.Locator;
using System.CommandLine;
using Microsoft.Build.Locator;
using ProjectDiff.Tool;

MSBuildLocator.RegisterDefaults();

var tool = ProjectDiffTool.Create(new ExtendedConsole());
var tool = ProjectDiffTool.Create(new SystemConsole());

return await tool.InvokeAsync(args);
7 changes: 3 additions & 4 deletions src/ProjectDiff.Tool/ProjectDiff.Tool.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GitVersion.MsBuild" Version="6.1.0">
<PackageReference Include="GitVersion.MsBuild" Version="6.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Build.Locator" Version="1.9.1"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta5.25306.1" />
</ItemGroup>

<ItemGroup>
Expand Down
236 changes: 128 additions & 108 deletions src/ProjectDiff.Tool/ProjectDiffCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.CommandLine;
using System.CommandLine.IO;
using System.CommandLine.NamingConventionBinder;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
Expand All @@ -21,126 +19,149 @@ public sealed class ProjectDiffCommand : RootCommand
}
};

private static readonly Argument<FileInfo> SolutionArgument = new(
"solution",
"Path to solution file to derive projects from"
)
private static readonly Argument<FileInfo> SolutionArgument = new("solution")
{
Arity = ArgumentArity.ExactlyOne
};

private static readonly Option<string> BaseCommitOption = new(
["--base-ref", "--base"],
() => "HEAD",
"Base git reference to compare against"
)
{
IsRequired = true
};

private static readonly Option<string?> HeadCommitOption = new(
["--head-ref", "--head"],
"Head git reference to compare against. If not specified current working tree will be used"
)
{
IsRequired = false
};

private static readonly Option<bool> MergeBaseOption = new(
"--merge-base",
() => true,
"If true instead of using --base use the merge base of --base and --head as the --base reference, if --head is not specified 'HEAD' will be used"
);

private static readonly Option<bool> IncludeDeleted = new(
"--include-deleted",
() => false,
"If true deleted projects will be included in output"
);

private static readonly Option<bool> IncludeModified = new(
"--include-modified",
() => true,
"If true modified projects will be included in output"
);

private static readonly Option<bool> IncludeAdded = new(
"--include-added",
() => true,
"If true added projects will be included in output"
);

private static readonly Option<bool> IncludeReferencing = new(
"--include-referencing",
() => true,
"if true projects referencing modified/deleted/added projects will be included in output"
);

private static readonly Option<OutputFormat?> Format = new(
["--format", "-f"],
"Output format, if --output is specified format will be derived from file extension. Otherwise this defaults to 'plain'"
);

private static readonly Option<bool> AbsolutePaths = new(
"--absolute-paths",
() => false,
"Output absolute paths, if not specified paths will be relative to the working directory. Or relative to --output if specified. This option will not affect slnf format as this requires relative paths"
);

private static readonly Option<FileInfo?> OutputOption = new(
["--output", "--out", "-o"],
"Output file, if not set stdout will be used"
);

private static readonly Option<FileInfo[]> IgnoreChangedFilesOption = new(
"--ignore-changed-file",
() => [],
"Ignore changes in specific files. If these files are a part of the build evaluation process they will still be evaluated, however these files will be considered unchanged by the diff process"
);

private readonly IExtendedConsole _console;


public ProjectDiffCommand(IExtendedConsole console)
{
_console = console;
Name = "dotnet-proj-diff";
Description = "Calculate which projects in a solution has changed since a specific commit";
SolutionArgument.AddValidator(
Arity = ArgumentArity.ExactlyOne,
Description = "Path to solution file to derive projects from",
Validators =
{
x =>
{
var f = x.GetValueOrDefault<FileInfo?>();
if (f is null)
{
x.ErrorMessage = $"{x.Argument.Name} must be specified";
x.AddError("{x.Argument.Name} must be specified");
}
else if (!f.Exists)
{
x.ErrorMessage = $"File '{f.FullName}' does not exist.";
x.AddError($"File '{f.FullName}' does not exist.");
}
else if (f.Extension is not (".sln" or ".slnx"))
{
x.ErrorMessage = $"File '{f.FullName}' is not a valid sln file.";
x.AddError($"File '{f.FullName}' is not a valid sln file.");
}
}
);
AddArgument(SolutionArgument);
AddOption(BaseCommitOption);
AddOption(HeadCommitOption);
AddOption(MergeBaseOption);
AddOption(IncludeDeleted);
AddOption(IncludeModified);
AddOption(IncludeAdded);
AddOption(IncludeReferencing);
AddOption(AbsolutePaths);
AddOption(Format);
AddOption(OutputOption);
AddOption(IgnoreChangedFilesOption);
Handler = CommandHandler.Create(ExecuteAsync);
}
};

private static readonly Option<string> BaseCommitOption = new(
"--base-ref",
"--base"
)
{
Description = "Base git reference to compare against, if not specified 'HEAD' will be used",
DefaultValueFactory = _ => "HEAD",
Required = true
};

private static readonly Option<string?> HeadCommitOption = new("--head-ref", "--head")

{
Description = "Head git reference to compare against. If not specified current working tree will be used",
Required = false
};

private static readonly Option<bool> MergeBaseOption = new("--merge-base")
{
Description =
"If true instead of using --base use the merge base of --base and --head as the --base reference, if --head is not specified 'HEAD' will be used",
DefaultValueFactory = _ => true,
};

private static readonly Option<bool> IncludeDeleted = new("--include-deleted")
{
DefaultValueFactory = _ => false,
Description = "If true deleted projects will be included in output"
};

private static readonly Option<bool> IncludeModified = new("--include-modified")
{
DefaultValueFactory = _ => true,
Description = "If true modified projects will be included in output"
};

private static readonly Option<bool> IncludeAdded = new("--include-added")
{
DefaultValueFactory = _ => true,
Description = "If true added projects will be included in output"
};

private static readonly Option<bool> IncludeReferencing = new("--include-referencing")
{
DefaultValueFactory = _ => true,
Description = "if true projects referencing modified/deleted/added projects will be included in output"
};

private static readonly Option<OutputFormat?> Format = new("--format", "-f")
{
Description =
"Output format, if --output is specified format will be derived from file extension. Otherwise this defaults to 'plain'"
};

private static readonly Option<bool> AbsolutePaths = new("--absolute-paths")
{
DefaultValueFactory = _ => false,
Description =
"Output absolute paths, if not specified paths will be relative to the working directory. Or relative to --output if specified. This option will not affect slnf format as this requires relative paths"
};

private static readonly Option<FileInfo?> OutputOption = new("--output", "--out", "-o")
{
Description = "Output file, if not set stdout will be used"
};

private static readonly Option<FileInfo[]> IgnoreChangedFilesOption = new("--ignore-changed-file")
{

DefaultValueFactory = _ => [],
Description =
"Ignore changes in specific files. If these files are a part of the build evaluation process they will still be evaluated, however these files will be considered unchanged by the diff process"
};

private readonly IConsole _console;


public ProjectDiffCommand(IConsole console)
{
_console = console;
Description = "Calculate which projects in a solution has changed since a specific commit";
Arguments.Add(SolutionArgument);
Options.Add(BaseCommitOption);
Options.Add(HeadCommitOption);
Options.Add(MergeBaseOption);
Options.Add(IncludeDeleted);
Options.Add(IncludeModified);
Options.Add(IncludeAdded);
Options.Add(IncludeReferencing);
Options.Add(AbsolutePaths);
Options.Add(Format);
Options.Add(OutputOption);
Options.Add(IgnoreChangedFilesOption);
SetAction(ExecuteAsync);
}

private Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
{
var settings = new ProjectDiffSettings
{
Format = parseResult.GetValue(Format),
Output = parseResult.GetValue(OutputOption),
Solution = parseResult.GetRequiredValue(SolutionArgument),
BaseRef = parseResult.GetRequiredValue(BaseCommitOption),
HeadRef = parseResult.GetValue(HeadCommitOption),
MergeBase = parseResult.GetValue(MergeBaseOption),
IncludeDeleted = parseResult.GetValue(IncludeDeleted),
IncludeModified = parseResult.GetValue(IncludeModified),
IncludeAdded = parseResult.GetValue(IncludeAdded),
IncludeReferencing = parseResult.GetValue(IncludeReferencing),
AbsolutePaths = parseResult.GetValue(AbsolutePaths),
IgnoreChangedFile = parseResult.GetRequiredValue(IgnoreChangedFilesOption)
};

return ExecuteCoreAsync(settings, cancellationToken);
}

private async Task<int> ExecuteAsync(
private async Task<int> ExecuteCoreAsync(
ProjectDiffSettings settings,
CancellationToken cancellationToken
)
Expand Down Expand Up @@ -239,8 +260,7 @@ private static async Task WriteJson(
bool absolutePaths
)
{
diff = diff.Select(
project => project with
diff = diff.Select(project => project with
{
Path = NormalizePath(
output.RootDirectory,
Expand Down Expand Up @@ -326,7 +346,7 @@ private static void WriteError(IConsole console, string error)
};


private sealed class DiffOutput(FileInfo? outputFile, IExtendedConsole console)
private sealed class DiffOutput(FileInfo? outputFile, IConsole console)
{
public string RootDirectory => outputFile?.DirectoryName ?? console.WorkingDirectory;

Expand Down
Loading