From c79691cb6f4a62ae5210b10ddbb043735eaf72a6 Mon Sep 17 00:00:00 2001 From: "T. R. Bernstein" Date: Sun, 13 Sep 2026 23:15:21 +0200 Subject: [PATCH] Stop descending into excluded directories The resolver skipped an excluded directory in its result but still walked its subtree, so watches were installed below names such as `.git` or `node_modules`. Exclusion now prunes the walk. --- Sources/Inotify/DirectoryResolver.swift | 15 ++++++--------- .../DirectoryResolverTests.swift | 10 ++++++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Sources/Inotify/DirectoryResolver.swift b/Sources/Inotify/DirectoryResolver.swift index 8646d6b..85017b7 100644 --- a/Sources/Inotify/DirectoryResolver.swift +++ b/Sources/Inotify/DirectoryResolver.swift @@ -13,11 +13,7 @@ public struct DirectoryResolver { for path in paths { let path = FilePath(path) resolved.append(path) - try await withSubdirectories(at: path, recursive: true) { subdirectoryPath in - guard let basename = subdirectoryPath.lastComponent?.description else { return } - guard !itemNames.contains(basename) else { return } - resolved.append(subdirectoryPath) - } + try await withSubdirectories(at: path, excluding: itemNames) { resolved.append($0) } } return resolved @@ -36,14 +32,15 @@ public struct DirectoryResolver { return entries } - private static func withSubdirectories(at path: FilePath, recursive: Bool = false, body: (FilePath) async throws -> Void) async throws { + /// Calls `body` for every subdirectory below `path`, depth first. Excluded + /// names are neither reported nor descended into. + private static func withSubdirectories(at path: FilePath, excluding itemNames: Set, body: (FilePath) async throws -> Void) async throws { let directoryHandle = try await fileManager.openDirectory(atPath: path) for try await childContent in directoryHandle.listContents() { guard childContent.type == .directory else { continue } + guard let name = childContent.path.lastComponent?.string, !itemNames.contains(name) else { continue } try await body(childContent.path) - if recursive { - try await withSubdirectories(at: childContent.path, recursive: recursive, body: body) - } + try await withSubdirectories(at: childContent.path, excluding: itemNames, body: body) } try await directoryHandle.close() } diff --git a/Tests/InotifyIntegrationTests/DirectoryResolverTests.swift b/Tests/InotifyIntegrationTests/DirectoryResolverTests.swift index 1d1012c..12210a7 100644 --- a/Tests/InotifyIntegrationTests/DirectoryResolverTests.swift +++ b/Tests/InotifyIntegrationTests/DirectoryResolverTests.swift @@ -14,4 +14,14 @@ struct DirectoryResolverTests { #expect(directories.map { $0.description } == [dir, "\(dir)/Subfolder", subDirectory]) } } + + @Test func doesNotDescendIntoExcludedDirectories() async throws { + try await withTempDir { dir in + let excludedSubdirectory = "\(dir)/Excluded/Inside" + try FileManager.default.createDirectory(atPath: excludedSubdirectory, withIntermediateDirectories: true) + let directories = try await DirectoryResolver.resolve(dir, excluding: ["Excluded"]) + + #expect(directories.map { $0.description } == [dir]) + } + } }