Skip to content

Commit de3f28e

Browse files
ladiprorainersigwald
authored andcommitted
Introduce IsAllFilesWildcard() and call it from MatchFileRecursionStep
Fixes dotnet#6502 Summary This change fixes a regression in glob matching where files without extension are erroneously not matched when taking a specific globbing code path. Customer impact Any customer who uses a glob pattern susceptible to the bug and has files without extensions in their source tree is affected. The bug was reported by external customers. Regression? Yes, caused by dotnet#6151 where glob matching was optimized which internally made it take a different code path. Changes Made Fixes the regression by properly handling `*.*` to mean all files, not just files with a dot in the name. This convention is used in .NET APIs on all platforms and matches the pre-regression behavior. Testing Added unit test coverage. Also verified locally with the repro provided by the original bug reporter. Risk Low. The star patterns are special-cased to mean all files, other patterns are unaffected.
1 parent 2fd48ab commit de3f28e

2 files changed

Lines changed: 45 additions & 4 deletions

File tree

src/Shared/FileMatcher.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ internal FileMatcher(IFileSystem fileSystem, GetFileSystemEntries getFileSystemE
131131
"*",
132132
directory,
133133
false));
134-
IEnumerable<string> filteredEntriesForPath = (pattern != null && pattern != "*" && pattern != "*.*")
134+
IEnumerable<string> filteredEntriesForPath = (pattern != null && !IsAllFilesWildcard(pattern))
135135
? allEntriesForPath.Where(o => IsMatch(Path.GetFileName(o), pattern))
136136
: allEntriesForPath;
137137
return stripProjectDirectory
@@ -886,7 +886,7 @@ private void GetFilesRecursive(
886886
// The wildcard path portion of the excluded search matches the include search
887887
searchToExclude.RemainingWildcardDirectory == recursionState.RemainingWildcardDirectory &&
888888
// The exclude search will match ALL filenames OR
889-
(searchToExclude.SearchData.Filespec == "*" || searchToExclude.SearchData.Filespec == "*.*" ||
889+
(IsAllFilesWildcard(searchToExclude.SearchData.Filespec) ||
890890
// The exclude search filename pattern matches the include search's pattern
891891
searchToExclude.SearchData.Filespec == recursionState.SearchData.Filespec))
892892
{
@@ -1091,7 +1091,11 @@ private IEnumerable<string> GetFilesForStep(
10911091

10921092
private static bool MatchFileRecursionStep(RecursionState recursionState, string file)
10931093
{
1094-
if (recursionState.SearchData.Filespec != null)
1094+
if (IsAllFilesWildcard(recursionState.SearchData.Filespec))
1095+
{
1096+
return true;
1097+
}
1098+
else if (recursionState.SearchData.Filespec != null)
10951099
{
10961100
return IsMatch(Path.GetFileName(file), recursionState.SearchData.Filespec);
10971101
}
@@ -2564,6 +2568,17 @@ private static bool DirectoryEndsWithPattern(string directoryPath, string patter
25642568
return (index != -1 && IsMatch(directoryPath.Substring(index + 1), pattern));
25652569
}
25662570

2571+
/// <summary>
2572+
/// Returns true if <paramref name="pattern"/> is <code>*</code> or <code>*.*</code>.
2573+
/// </summary>
2574+
/// <param name="pattern">The filename pattern to check.</param>
2575+
private static bool IsAllFilesWildcard(string pattern) => pattern?.Length switch
2576+
{
2577+
1 => pattern[0] == '*',
2578+
3 => pattern[0] == '*' && pattern[1] == '.' && pattern[2] == '*',
2579+
_ => false
2580+
};
2581+
25672582
internal static bool IsRecursiveDirectoryMatch(string path) => path.TrimTrailingSlashes() == recursiveDirectoryMatch;
25682583
}
25692584
}

src/Shared/UnitTests/FileMatcher_Tests.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ public class GetFilesComplexGlobbingMatchingInfo
154154
@"src\bar.cs",
155155
@"src\baz.cs",
156156
@"src\foo\foo.cs",
157+
@"src\foo\licence",
157158
@"src\bar\bar.cs",
158159
@"src\baz\baz.cs",
159160
@"src\foo\inner\foo.cs",
@@ -368,7 +369,8 @@ public static IEnumerable<object[]> GetTestData()
368369
ExpectedMatches = new[]
369370
{
370371
@"readme.txt",
371-
@"licence"
372+
@"licence",
373+
@"src\foo\licence",
372374
}
373375
}
374376
};
@@ -422,6 +424,30 @@ public static IEnumerable<object[]> GetTestData()
422424
}
423425
};
424426

427+
// Regression test for https://github.com/Microsoft/msbuild/issues/6502
428+
yield return new object[]
429+
{
430+
new GetFilesComplexGlobbingMatchingInfo
431+
{
432+
Include = @"src\**",
433+
Excludes = new[]
434+
{
435+
@"**\foo\**",
436+
},
437+
ExpectedMatches = new[]
438+
{
439+
@"src\foo.cs",
440+
@"src\bar.cs",
441+
@"src\baz.cs",
442+
@"src\bar\bar.cs",
443+
@"src\baz\baz.cs",
444+
@"src\bar\inner\baz.cs",
445+
@"src\bar\inner\baz\baz.cs",
446+
},
447+
ExpectNoMatches = NativeMethodsShared.IsLinux,
448+
}
449+
};
450+
425451
// Hits the early elimination of exclude file patterns that do not intersect with the include.
426452
// The exclude is redundant and can be eliminated before starting the file system walk.
427453
yield return new object[]

0 commit comments

Comments
 (0)