-
Notifications
You must be signed in to change notification settings - Fork 184
Enhance RootPathFilter to support glob-style pattern matching #1125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Copilot
wants to merge
13
commits into
main
Choose a base branch
from
copilot/fix-1040
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
448f77b
Initial plan
Copilot bbca699
Add RootPathPatterns configuration and implementation - fixing patter…
Copilot d7f04c3
Complete RootPathPatterns implementation with working pattern matching
Copilot 980d3e8
Address PR feedback: restructure code flow and fix test comment
Copilot b3b21d9
Fix PathPatternMatcher for cross-platform compatibility and double wi…
Copilot e4896c8
Merge branch 'main' into copilot/fix-1040
pragnya17 d6b3d56
Merge branch 'main' into copilot/fix-1040
pragnya17 72397ff
Merge branch 'main' into copilot/fix-1040
pragnya17 5fd117a
Refactor RootPathFilter to support glob patterns without separate Roo…
Copilot 2d337df
Address PR review feedback: simplify skipValidation logic and add tes…
Copilot 8025588
Merge branch 'main' into copilot/fix-1040
pragnya17 806585d
Refactor PathPatternMatcher to use .NET's built-in FileSystemGlobbing…
Copilot ba7f30d
Add glob pattern documentation to RootPathFilter help text in Validat…
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
|
||
| using System; | ||
| using System.IO; | ||
| using Microsoft.Extensions.FileSystemGlobbing; | ||
|
|
||
| namespace Microsoft.Sbom.Api.Utils; | ||
|
|
||
| /// <summary> | ||
| /// Provides pattern matching functionality for file paths using glob-style patterns. | ||
| /// Leverages .NET's built-in Microsoft.Extensions.FileSystemGlobbing for robust cross-platform support. | ||
| /// | ||
| /// Supported patterns: | ||
| /// - * matches zero or more characters (excluding directory separators) | ||
| /// - ** matches zero or more characters (including directory separators) | ||
| /// | ||
| /// Note: The ? wildcard for single character matching is not supported by the underlying .NET implementation. | ||
| /// </summary> | ||
| public static class PathPatternMatcher | ||
| { | ||
| /// <summary> | ||
| /// Checks if a file path matches a glob-style pattern. | ||
| /// Uses .NET's built-in globbing which supports * and ** patterns but not ? for single character matching. | ||
| /// </summary> | ||
| /// <param name="filePath">The file path to check.</param> | ||
| /// <param name="pattern">The glob pattern to match against.</param> | ||
| /// <param name="basePath">The base path to resolve relative patterns.</param> | ||
| /// <returns>True if the path matches the pattern, false otherwise.</returns> | ||
| public static bool IsMatch(string filePath, string pattern, string basePath = null) | ||
| { | ||
| if (string.IsNullOrEmpty(filePath) || string.IsNullOrEmpty(pattern)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| // Normalize paths for cross-platform compatibility | ||
| var normalizedFilePath = NormalizePath(filePath); | ||
| var normalizedPattern = NormalizePath(pattern); | ||
|
|
||
| // Use case-insensitive matching for cross-platform compatibility | ||
| var matcher = new Matcher(StringComparison.OrdinalIgnoreCase); | ||
| matcher.AddInclude(normalizedPattern); | ||
|
|
||
| // Determine the path to match against | ||
| string pathToMatch; | ||
| if (!string.IsNullOrEmpty(basePath) && !Path.IsPathRooted(normalizedPattern)) | ||
| { | ||
| // For relative patterns with base path, get the relative path | ||
| if (!IsPathWithinBase(normalizedFilePath, basePath)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| pathToMatch = GetRelativePath(basePath, normalizedFilePath); | ||
| } | ||
| else | ||
| { | ||
| // For absolute patterns or no base path, use the normalized full path | ||
| pathToMatch = normalizedFilePath; | ||
| } | ||
|
|
||
| // Use the matcher to check if the path matches the pattern | ||
| var result = matcher.Match(pathToMatch); | ||
| return result.HasMatches; | ||
| } | ||
| catch | ||
| { | ||
| // If any exception occurs during matching, return false | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if a file path is within the specified base path. | ||
| /// </summary> | ||
| /// <param name="filePath">The file path to check.</param> | ||
| /// <param name="basePath">The base path.</param> | ||
| /// <returns>True if the file path is within the base path, false otherwise.</returns> | ||
| private static bool IsPathWithinBase(string filePath, string basePath) | ||
| { | ||
| try | ||
| { | ||
| // Normalize paths for cross-platform compatibility | ||
| var normalizedFilePath = NormalizePath(filePath); | ||
| var normalizedBasePath = NormalizePath(basePath); | ||
|
|
||
| // For Windows-style paths on non-Windows systems, use manual comparison | ||
| if (IsWindowsStylePath(normalizedBasePath) && IsWindowsStylePath(normalizedFilePath)) | ||
| { | ||
| var normalizedBase = normalizedBasePath.TrimEnd('/') + "/"; | ||
| return normalizedFilePath.StartsWith(normalizedBase, StringComparison.OrdinalIgnoreCase); | ||
| } | ||
|
|
||
| // Use Path.GetFullPath for proper platform-specific handling | ||
| var fullFilePath = Path.GetFullPath(normalizedFilePath); | ||
| var fullBasePath = Path.GetFullPath(normalizedBasePath); | ||
|
|
||
| // Ensure base path ends with separator for proper prefix check | ||
| if (!fullBasePath.EndsWith(Path.DirectorySeparatorChar) && | ||
| !fullBasePath.EndsWith(Path.AltDirectorySeparatorChar)) | ||
| { | ||
| fullBasePath += Path.DirectorySeparatorChar; | ||
| } | ||
|
|
||
| return fullFilePath.StartsWith(fullBasePath, StringComparison.OrdinalIgnoreCase); | ||
| } | ||
| catch | ||
| { | ||
| // Fallback: manual comparison with normalized paths | ||
| var normalizedFilePath = NormalizePath(filePath); | ||
| var normalizedBasePath = NormalizePath(basePath).TrimEnd('/') + "/"; | ||
| return normalizedFilePath.StartsWith(normalizedBasePath, StringComparison.OrdinalIgnoreCase); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the relative path from a base path to a target path. | ||
| /// </summary> | ||
| /// <param name="basePath">The base path.</param> | ||
| /// <param name="targetPath">The target path.</param> | ||
| /// <returns>The relative path from base to target.</returns> | ||
| private static string GetRelativePath(string basePath, string targetPath) | ||
| { | ||
| try | ||
| { | ||
| // Normalize paths for cross-platform compatibility | ||
| var normalizedBasePath = NormalizePath(basePath); | ||
| var normalizedTargetPath = NormalizePath(targetPath); | ||
|
|
||
| // For cross-platform compatibility, handle Windows-style paths manually | ||
| if (IsWindowsStylePath(normalizedBasePath) && IsWindowsStylePath(normalizedTargetPath)) | ||
| { | ||
| return GetRelativePathManual(normalizedBasePath, normalizedTargetPath); | ||
| } | ||
|
|
||
| // Use Path.GetRelativePath for Unix-style or when both paths are in the same format | ||
| var fullBasePath = Path.GetFullPath(normalizedBasePath); | ||
| var fullTargetPath = Path.GetFullPath(normalizedTargetPath); | ||
| return Path.GetRelativePath(fullBasePath, fullTargetPath); | ||
| } | ||
| catch | ||
| { | ||
| // Fallback: manual calculation | ||
| return GetRelativePathManual(basePath, targetPath); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Normalizes a path by converting backslashes to forward slashes. | ||
| /// </summary> | ||
| /// <param name="path">The path to normalize.</param> | ||
| /// <returns>The normalized path.</returns> | ||
| private static string NormalizePath(string path) | ||
| { | ||
| if (string.IsNullOrEmpty(path)) | ||
| { | ||
| return path; | ||
| } | ||
|
|
||
| return path.Replace('\\', '/'); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if a path is in Windows style (has a drive letter). | ||
| /// </summary> | ||
| /// <param name="path">The path to check.</param> | ||
| /// <returns>True if the path is Windows-style, false otherwise.</returns> | ||
| private static bool IsWindowsStylePath(string path) | ||
| { | ||
| return !string.IsNullOrEmpty(path) && | ||
| path.Length >= 2 && | ||
| char.IsLetter(path[0]) && | ||
| path[1] == ':'; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Manually calculates relative path for cross-platform compatibility. | ||
| /// </summary> | ||
| /// <param name="basePath">The base path.</param> | ||
| /// <param name="targetPath">The target path.</param> | ||
| /// <returns>The relative path.</returns> | ||
| private static string GetRelativePathManual(string basePath, string targetPath) | ||
| { | ||
| if (string.IsNullOrEmpty(basePath) || string.IsNullOrEmpty(targetPath)) | ||
| { | ||
| return targetPath; | ||
| } | ||
|
|
||
| var normalizedBase = NormalizePath(basePath).TrimEnd('/') + "/"; | ||
| var normalizedTarget = NormalizePath(targetPath); | ||
|
|
||
| if (normalizedTarget.StartsWith(normalizedBase, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| return normalizedTarget.Substring(normalizedBase.Length); | ||
| } | ||
|
|
||
| return normalizedTarget; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.NET already provides cross-platform support for globbing that we should leverage unless there's some reason that it doesn't do what we need it to do. See https://learn.microsoft.com/en-us/dotnet/core/extensions/file-globbing for details and syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great suggestion! I've refactored the PathPatternMatcher to use .NET's built-in Microsoft.Extensions.FileSystemGlobbing library instead of the custom implementation. This provides better reliability, cross-platform support, and leverages Microsoft's well-tested globbing functionality.
Key changes:
Commit: 806585d