diff --git a/src/Shared/FileMatcher.cs b/src/Shared/FileMatcher.cs index 1bfdc57490e..ebc622953da 100644 --- a/src/Shared/FileMatcher.cs +++ b/src/Shared/FileMatcher.cs @@ -131,7 +131,7 @@ internal FileMatcher(IFileSystem fileSystem, GetFileSystemEntries getFileSystemE "*", directory, false)); - IEnumerable filteredEntriesForPath = (pattern != null && pattern != "*" && pattern != "*.*") + IEnumerable filteredEntriesForPath = (pattern != null && !IsAllFilesWildcard(pattern)) ? allEntriesForPath.Where(o => IsMatch(Path.GetFileName(o), pattern)) : allEntriesForPath; return stripProjectDirectory @@ -886,7 +886,7 @@ struct RecursionState // The wildcard path portion of the excluded search matches the include search searchToExclude.RemainingWildcardDirectory == recursionState.RemainingWildcardDirectory && // The exclude search will match ALL filenames OR - (searchToExclude.SearchData.Filespec == "*" || searchToExclude.SearchData.Filespec == "*.*" || + (IsAllFilesWildcard(searchToExclude.SearchData.Filespec) || // The exclude search filename pattern matches the include search's pattern searchToExclude.SearchData.Filespec == recursionState.SearchData.Filespec)) { @@ -1091,7 +1091,11 @@ struct RecursionState private static bool MatchFileRecursionStep(RecursionState recursionState, string file) { - if (recursionState.SearchData.Filespec != null) + if (IsAllFilesWildcard(recursionState.SearchData.Filespec)) + { + return true; + } + else if (recursionState.SearchData.Filespec != null) { return IsMatch(Path.GetFileName(file), recursionState.SearchData.Filespec); } @@ -2564,6 +2568,17 @@ private static bool DirectoryEndsWithPattern(string directoryPath, string patter return (index != -1 && IsMatch(directoryPath.Substring(index + 1), pattern)); } + /// + /// Returns true if is * or *.*. + /// + /// The filename pattern to check. + private static bool IsAllFilesWildcard(string pattern) => pattern?.Length switch + { + 1 => pattern[0] == '*', + 3 => pattern[0] == '*' && pattern[1] == '.' && pattern[2] == '*', + _ => false + }; + internal static bool IsRecursiveDirectoryMatch(string path) => path.TrimTrailingSlashes() == recursiveDirectoryMatch; } } diff --git a/src/Shared/UnitTests/FileMatcher_Tests.cs b/src/Shared/UnitTests/FileMatcher_Tests.cs index c3d200829e3..f8d902e2b5e 100644 --- a/src/Shared/UnitTests/FileMatcher_Tests.cs +++ b/src/Shared/UnitTests/FileMatcher_Tests.cs @@ -154,6 +154,7 @@ public class GetFilesComplexGlobbingMatchingInfo @"src\bar.cs", @"src\baz.cs", @"src\foo\foo.cs", + @"src\foo\licence", @"src\bar\bar.cs", @"src\baz\baz.cs", @"src\foo\inner\foo.cs", @@ -368,7 +369,8 @@ public static IEnumerable GetTestData() ExpectedMatches = new[] { @"readme.txt", - @"licence" + @"licence", + @"src\foo\licence", } } }; @@ -422,6 +424,30 @@ public static IEnumerable GetTestData() } }; + // Regression test for https://github.com/Microsoft/msbuild/issues/6502 + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\**", + Excludes = new[] + { + @"**\foo\**", + }, + ExpectedMatches = new[] + { + @"src\foo.cs", + @"src\bar.cs", + @"src\baz.cs", + @"src\bar\bar.cs", + @"src\baz\baz.cs", + @"src\bar\inner\baz.cs", + @"src\bar\inner\baz\baz.cs", + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + // Hits the early elimination of exclude file patterns that do not intersect with the include. // The exclude is redundant and can be eliminated before starting the file system walk. yield return new object[]