Skip to content

Commit 84c86fb

Browse files
feat: exclude projects (#82)
Adds feature to exclude projects to CLI using `--exclude`. Co-authored-by: xIceFox <[email protected]>
1 parent 206f946 commit 84c86fb

File tree

11 files changed

+275
-15
lines changed

11 files changed

+275
-15
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,39 @@ WRITE: /home/lchaia/dev/dotnet-affected/affected.proj
271271
WRITE: /home/lchaia/dev/dotnet-affected/affected.json
272272
```
273273

274+
## Excluding Projects
275+
276+
Projects can be excluded by using the `--exclude` (shorthand `-e`) argument. It expects a dotnet Regular Expression
277+
that will be matched against each Project's Full Path.
278+
279+
In the below example, `dotnet-affected.Tests` is excluded due to the regular expression provided.
280+
```shell
281+
$ dotnet affected --dry-run --verbose -e .Tests.
282+
1 files have changed referenced by 1 projects
283+
0 NuGet Packages have changed
284+
1 projects are affected by these changes
285+
1 projects were excluded
286+
Changed Projects
287+
Name Path
288+
dotnet-affected /home/lchaia/dev/dotnet-affected/src/dotnet-affected/dotnet-affected.csproj
289+
290+
Affected Projects
291+
Name Path
292+
dotnet-affected.Benchmarks /home/lchaia/dev/dotnet-affected/benchmarks/dotnet-affected.Benchmarks/dotnet-affected.Benchmarks.csproj
293+
294+
Excluded Projects
295+
Name Path
296+
dotnet-affected.Tests /home/lchaia/dev/dotnet-affected/test/dotnet-affected.Tests/dotnet-affected.Tests.csproj
297+
DRY-RUN: WRITE /home/lchaia/dev/dotnet-affected/affected.proj
298+
DRY-RUN: CONTENTS:
299+
<Project Sdk="Microsoft.Build.Traversal/3.0.3">
300+
<ItemGroup>
301+
<ProjectReference Include="/home/lchaia/dev/dotnet-affected/benchmarks/dotnet-affected.Benchmarks/dotnet-affected.Benchmarks.csproj" />
302+
<ProjectReference Include="/home/lchaia/dev/dotnet-affected/src/dotnet-affected/dotnet-affected.csproj" />
303+
</ItemGroup>
304+
</Project>
305+
```
306+
274307
## Continuous Integration
275308

276309
For usage in CI, it's recommended to use the `--from` and `--to` options with the environment variables provided by your

src/DotnetAffected.Abstractions/AffectedSummary.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ public class AffectedSummary
1313
/// <param name="filesThatChanged"></param>
1414
/// <param name="projectsWithChangedFiles"></param>
1515
/// <param name="affectedProjects"></param>
16+
/// <param name="excludedProjects"></param>
1617
/// <param name="changedPackages"></param>
1718
public AffectedSummary(
1819
string[] filesThatChanged,
1920
ProjectGraphNode[] projectsWithChangedFiles,
2021
ProjectGraphNode[] affectedProjects,
22+
ProjectGraphNode[] excludedProjects,
2123
PackageChange[] changedPackages)
2224
{
2325
FilesThatChanged = filesThatChanged;
2426
ProjectsWithChangedFiles = projectsWithChangedFiles;
2527
AffectedProjects = affectedProjects;
28+
ExcludedProjects = excludedProjects;
2629
ChangedPackages = changedPackages;
2730
}
2831

@@ -41,6 +44,11 @@ public AffectedSummary(
4144
/// </summary>
4245
public ProjectGraphNode[] AffectedProjects { get; }
4346

47+
/// <summary>
48+
/// Gets a list of projects that had changes or were affected but were excluded from discovery.
49+
/// </summary>
50+
public ProjectGraphNode[] ExcludedProjects { get; }
51+
4452
/// <summary>
4553
/// Gets the list of packages that changed.
4654
/// </summary>

src/DotnetAffected.Core/AffectedOptions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@ public class AffectedOptions : IDiscoveryOptions
1616
/// <param name="solutionPath"></param>
1717
/// <param name="fromRef"></param>
1818
/// <param name="toRef"></param>
19+
/// <param name="exclusionRegex"></param>
1920
public AffectedOptions(
2021
string? repositoryPath = null,
2122
string? solutionPath = null,
2223
string? fromRef = null,
23-
string? toRef = null)
24+
string? toRef = null,
25+
string? exclusionRegex = null)
2426
{
2527
RepositoryPath = DetermineRepositoryPath(repositoryPath, solutionPath);
2628
SolutionPath = solutionPath;
2729
FromRef = fromRef ?? string.Empty;
2830
ToRef = toRef ?? string.Empty;
31+
ExclusionRegex = exclusionRegex;
2932
}
3033

3134
/// <summary>
@@ -48,6 +51,11 @@ public AffectedOptions(
4851
/// </summary>
4952
public string ToRef { get; }
5053

54+
/// <summary>
55+
/// Gets the regular expression to use for excluding projects.
56+
/// </summary>
57+
public string? ExclusionRegex { get; }
58+
5159
private static string DetermineRepositoryPath(string? repositoryPath, string? solutionPath)
5260
{
5361
// the argument takes precedence.

src/DotnetAffected.Core/Processor/AffectedProcessorBase.cs

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.Build.Graph;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using System.Text.RegularExpressions;
56

67
namespace DotnetAffected.Core.Processor
78
{
@@ -18,27 +19,41 @@ internal abstract class AffectedProcessorBase
1819
public AffectedSummary Process(AffectedProcessorContext context)
1920
{
2021
// Get files that changed according to changes provider.
21-
context.ChangedFiles = DiscoverChangedFiles(context).ToArray();
22+
context.ChangedFiles = DiscoverChangedFiles(context)
23+
.ToArray();
2224

2325
// Map the files that changed to their corresponding project/s.
24-
context.ChangedProjects = DiscoverProjectsForFiles(context).ToArray();
26+
var excludedProjects = new List<ProjectGraphNode>();
27+
context.ChangedProjects = ApplyExclusionPattern(
28+
DiscoverProjectsForFiles(context),
29+
context.Options,
30+
excludedProjects);
2531

2632
// Get packages that have changed, either from central package management or from the project file
2733
context.ChangedPackages = DiscoverPackageChanges(context);
2834

2935
// Determine which projects are affected by the projects and packages that have changed.
30-
context.AffectedProjects = DiscoverAffectedProjects(context);
36+
context.AffectedProjects = ApplyExclusionPattern(
37+
DiscoverAffectedProjects(context),
38+
context.Options,
39+
excludedProjects);
3140

3241
// Output a summary of the operation.
33-
return new AffectedSummary(context.ChangedFiles, context.ChangedProjects, context.AffectedProjects, context.ChangedPackages);
42+
return new AffectedSummary(
43+
context.ChangedFiles,
44+
context.ChangedProjects,
45+
context.AffectedProjects,
46+
excludedProjects.Distinct()
47+
.ToArray(),
48+
context.ChangedPackages);
3449
}
35-
50+
3651
/// <summary>
3752
/// Discover which files have changes
3853
/// </summary>
3954
/// <param name="context"></param>
4055
/// <returns></returns>
41-
protected virtual IEnumerable<string> DiscoverChangedFiles(AffectedProcessorContext context)
56+
protected virtual IEnumerable<string> DiscoverChangedFiles(AffectedProcessorContext context)
4257
=> context.ChangesProvider.GetChangedFiles(context.RepositoryPath, context.FromRef, context.ToRef);
4358

4459
/// <summary>
@@ -49,11 +64,43 @@ protected virtual IEnumerable<string> DiscoverChangedFiles(AffectedProcessorCont
4964
protected virtual IEnumerable<ProjectGraphNode> DiscoverProjectsForFiles(AffectedProcessorContext context)
5065
{
5166
// We init now because we want the graph to initialize late (lazy)
52-
var provider = context.ChangedProjectsProvider ?? new PredictionChangedProjectsProvider(context.Graph, context.Options);
67+
var provider = context.ChangedProjectsProvider ??
68+
new PredictionChangedProjectsProvider(context.Graph, context.Options);
5369
// Match which files belong to which of our known projects
5470
return provider.GetReferencingProjects(context.ChangedFiles);
5571
}
5672

73+
/// <summary>
74+
/// Applies the <see cref="AffectedOptions.ExclusionRegex"/> to exclude
75+
/// projects that matches the regular expression.
76+
/// </summary>
77+
/// <param name="inputProjects">List of projects that changed.</param>
78+
/// <param name="options">Affected options.</param>
79+
/// <param name="excludedProjects">Collection of excluded projects</param>
80+
/// <returns>Project lis excluding the ones that matches the exclusion regex.</returns>
81+
protected virtual ProjectGraphNode[] ApplyExclusionPattern(
82+
IEnumerable<ProjectGraphNode> inputProjects,
83+
AffectedOptions options,
84+
ICollection<ProjectGraphNode> excludedProjects)
85+
{
86+
var pattern = options.ExclusionRegex;
87+
88+
if (string.IsNullOrEmpty(pattern))
89+
return inputProjects.ToArray();
90+
91+
var changedProjects = new List<ProjectGraphNode>();
92+
var regex = new Regex(pattern);
93+
foreach (var project in inputProjects)
94+
{
95+
if (regex.IsMatch(project.GetFullPath()))
96+
excludedProjects.Add(project);
97+
else
98+
changedProjects.Add(project);
99+
}
100+
101+
return changedProjects.ToArray();
102+
}
103+
57104
/// <summary>
58105
/// Discover which packages have changed. <br/>
59106
/// </summary>

src/dotnet-affected/Commands/AffectedGlobalOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ internal static class AffectedGlobalOptions
4545
description: "A branch or commit to compare against --to.");
4646

4747
public static readonly ToOption ToOption = new(FromOption);
48+
49+
public static readonly Option<string> ExclusionRegexOption = new(
50+
new[]
51+
{
52+
"--exclude", "-e"
53+
},
54+
description: "A dotnet Regular Expression used to exclude discovered and affected projects.");
4855
}
4956

5057
internal sealed class ToOption : Option<string>

src/dotnet-affected/Commands/AffectedRootCommand.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public AffectedRootCommand()
2626
this.AddGlobalOption(AffectedGlobalOptions.AssumeChangesOption);
2727
this.AddGlobalOption(AffectedGlobalOptions.FromOption);
2828
this.AddGlobalOption(AffectedGlobalOptions.ToOption);
29+
this.AddGlobalOption(AffectedGlobalOptions.ExclusionRegexOption);
2930

3031
this.AddOption(FormatOption);
3132
this.AddOption(DryRunOption);
@@ -74,7 +75,7 @@ public FormatOption()
7475
})
7576
{
7677
this.Description = "Space-seperated output file formats. Possible values: <traversal, text, json>.";
77-
78+
7879
this.SetDefaultValue(new[]
7980
{
8081
"traversal"

src/dotnet-affected/Commands/Binding/AffectedOptionsBinder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ protected override AffectedOptions GetBoundValue(BindingContext bindingContext)
1515
parseResult.GetValueForOption(AffectedGlobalOptions.RepositoryPathOptions),
1616
parseResult.GetValueForOption(AffectedGlobalOptions.SolutionPathOption),
1717
parseResult.GetValueForOption(AffectedGlobalOptions.FromOption),
18-
parseResult.GetValueForOption(AffectedGlobalOptions.ToOption)
18+
parseResult.GetValueForOption(AffectedGlobalOptions.ToOption),
19+
parseResult.GetValueForOption(AffectedGlobalOptions.ExclusionRegexOption)
1920
);
2021
}
2122
}

src/dotnet-affected/Views/AffectedInfoView.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public AffectedInfoView(AffectedSummary summary)
1212
$"referenced by {summary.ProjectsWithChangedFiles.Count()} projects"));
1313
Add(new ContentView($"{summary.ChangedPackages.Count()} NuGet Packages have changed"));
1414
Add(new ContentView($"{summary.AffectedProjects.Count()} projects are affected by these changes"));
15+
Add(new ContentView($"{summary.ExcludedProjects.Count()} projects were excluded"));
1516

1617
Add(new WithChangesAndAffectedView(summary));
1718
}

src/dotnet-affected/Views/WithChangesAndAffectedView.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ public WithChangesAndAffectedView(AffectedSummary summary)
2929

3030
Add(new ContentView("\nAffected Projects"));
3131

32-
if (!summary.AffectedProjects.Any())
33-
{
32+
if (summary.AffectedProjects.Any())
33+
Add(new ProjectInfoTable(summary.AffectedProjects));
34+
else
3435
Add(new ContentView("No projects where affected by any of the changed projects."));
35-
return;
36-
}
3736

38-
Add(new ProjectInfoTable(summary.AffectedProjects));
37+
if (summary.ExcludedProjects.Any())
38+
{
39+
Add(new ContentView("\nExcluded Projects"));
40+
Add(new ProjectInfoTable(summary.ExcludedProjects));
41+
}
3942
}
4043
}
4144
}

0 commit comments

Comments
 (0)