Class: Mbeditor::ExclusionMatcher
- Inherits:
-
Object
- Object
- Mbeditor::ExclusionMatcher
- Defined in:
- app/services/mbeditor/exclusion_matcher.rb
Overview
Decides whether a workspace-relative path falls under a configured
exclusion (Mbeditor.configuration.excluded_paths).
Comparison is normalized rather than byte-for-byte, because the filesystem the workspace lives on may resolve two different spellings of a name to the same file:
* Case. APFS/HFS+ (and NTFS) resolve "TMP/cache.txt" to the excluded
"tmp/", and ".GIT/hooks/post-checkout" to the real ".git/hooks/" — the
latter is arbitrary code execution on the next git operation. Folding is
applied only when the filesystem backing the workspace really is
case-insensitive, probed at runtime: a case-sensitive volume can be
mounted on macOS, and folding there would hide legitimately distinct
paths.
* Unicode normalization. APFS stores one file for the NFC and NFD
spellings of a name, so an NFD request path slips past an NFC pattern.
NFC folding is applied unconditionally: filesystems that do keep the two
spellings apart are rare enough, and the cost there is over-exclusion
(hiding a path) rather than the write-through this class exists to
prevent.
Class Method Summary collapse
-
.case_insensitive_filesystem?(root) ⇒ Boolean
Whether the filesystem backing
rootresolves paths case-insensitively. - .reset_filesystem_probe! ⇒ Object
Instance Method Summary collapse
- #excluded?(relative_path) ⇒ Boolean
-
#initialize(patterns, root: nil) ⇒ ExclusionMatcher
constructor
rootis the workspace the paths are relative to; it decides only whether case is folded.
Constructor Details
#initialize(patterns, root: nil) ⇒ ExclusionMatcher
root is the workspace the paths are relative to; it decides only whether
case is folded. Defaults to the resolved workspace root.
88 89 90 91 |
# File 'app/services/mbeditor/exclusion_matcher.rb', line 88 def initialize(patterns, root: nil) @fold_case = self.class.case_insensitive_filesystem?(root || WorkspaceRootResolver.call) @patterns = patterns.map { |pattern| normalize(pattern.to_s) }.reject(&:empty?) end |
Class Method Details
.case_insensitive_filesystem?(root) ⇒ Boolean
Whether the filesystem backing root resolves paths case-insensitively.
Probed once per root, then memoized for the life of the process.
37 38 39 40 41 42 43 44 45 46 47 |
# File 'app/services/mbeditor/exclusion_matcher.rb', line 37 def case_insensitive_filesystem?(root) key = File.(root.to_s) MUTEX.synchronize do @probe_cache ||= {} return @probe_cache[key] if @probe_cache.key?(key) @probe_cache = {} if @probe_cache.size >= MAX_PROBE_CACHE @probe_cache[key] = probe_case_insensitive(key) end end |
.reset_filesystem_probe! ⇒ Object
49 50 51 |
# File 'app/services/mbeditor/exclusion_matcher.rb', line 49 def reset_filesystem_probe! MUTEX.synchronize { @probe_cache = {} } end |
Instance Method Details
#excluded?(relative_path) ⇒ Boolean
93 94 95 96 |
# File 'app/services/mbeditor/exclusion_matcher.rb', line 93 def excluded?(relative_path) rel = normalize(relative_path.to_s) @patterns.any? { |pattern| matches?(pattern, rel) } end |