From fc202a8c59a16c321f161d32237450a583f7c25e Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Sat, 29 Nov 2025 08:56:53 -0500 Subject: [PATCH 1/4] Initial start of path matching --- .../jsync/vfs/util/VirtualPathMatchers.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java diff --git a/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java new file mode 100644 index 0000000..1347a53 --- /dev/null +++ b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java @@ -0,0 +1,37 @@ +package com.fizzed.jsync.vfs.util; + +import java.nio.file.FileSystems; +import java.nio.file.PathMatcher; + +public class VirtualPathMatchers { + + + + static public PathMatcher compileRule(String rule) { + String glob = rule.trim(); + + // 1. Handle directory-only rules (e.g., "build/") + // Git implies "everything inside this directory" + if (glob.endsWith("/")) { + glob = glob + "**"; + } + + // 2. Handle "rooted" vs "anywhere" rules + // If it starts with '/', it matches from the root only. + // If NOT, it matches anywhere (e.g., "*.log" -> "** /*.log") + if (glob.startsWith("/")) { + // Remove leading slash for Java PathMatcher consistency on relative paths + glob = glob.substring(1); + } else { + // If it's not rooted, allow it to match deep in the tree + // Example: "tmp" becomes "**/tmp" + if (!glob.startsWith("**/") && !glob.equals("*")) { + glob = "**/" + glob; + } + } + + // Create the matcher using "glob" syntax + return FileSystems.getDefault().getPathMatcher("glob:" + glob); + } + +} \ No newline at end of file From 2305c921f7ad9376331a414bd7aa71ad1d304f47 Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Sun, 30 Nov 2025 09:19:09 -0500 Subject: [PATCH 2/4] New path matching feature to handle exclude/ignore rules similar to .gitignore --- .../engine/DefaultJsyncEventHandler.java | 9 +- .../com/fizzed/jsync/engine/JsyncEngine.java | 73 +++++---------- .../jsync/engine/JsyncEventHandler.java | 6 +- .../jsync/vfs/util/VirtualPathMatcher.java | 93 +++++++++++++++++++ .../jsync/vfs/util/VirtualPathMatchers.java | 44 +++++---- .../vfs/util/VirtualPathMatcherTest.java | 86 +++++++++++++++++ 6 files changed, 232 insertions(+), 79 deletions(-) create mode 100644 jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatcher.java create mode 100644 jsync-vfs/src/test/java/com/fizzed/jsync/vfs/util/VirtualPathMatcherTest.java diff --git a/jsync-engine/src/main/java/com/fizzed/jsync/engine/DefaultJsyncEventHandler.java b/jsync-engine/src/main/java/com/fizzed/jsync/engine/DefaultJsyncEventHandler.java index 455c9d5..74b2fb5 100644 --- a/jsync-engine/src/main/java/com/fizzed/jsync/engine/DefaultJsyncEventHandler.java +++ b/jsync-engine/src/main/java/com/fizzed/jsync/engine/DefaultJsyncEventHandler.java @@ -34,8 +34,13 @@ public void willExcludePath(VirtualPath sourcePath) { } @Override - public void willIgnorePath(VirtualPath sourcePath) { - log.debug("Ignoring path {}", sourcePath); + public void willIgnoreSourcePath(VirtualPath sourcePath) { + log.debug("Ignoring source path {}", sourcePath); + } + + @Override + public void willIgnoreTargetPath(VirtualPath targetPath) { + log.debug("Ignoring target path {}", targetPath); } @Override diff --git a/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java b/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java index 96c11cf..aa7a65a 100644 --- a/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java +++ b/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java @@ -2,6 +2,7 @@ import com.fizzed.jsync.vfs.*; import com.fizzed.jsync.vfs.util.Permissions; +import com.fizzed.jsync.vfs.util.VirtualPathMatchers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,9 +32,10 @@ public class JsyncEngine { private List ignores; // when running a sync private Checksum negotiatedChecksum; - private List excludePaths; - private List ignoreSourcePaths; - private List ignoreTargetPaths; + private VirtualPathMatchers excludeMatchers; + private VirtualPathMatchers ignoreMatchers; + private VirtualPath sourceRootPath; + private VirtualPath targetRootPath; public JsyncEngine() { this.eventHandler = new DefaultJsyncEventHandler(); @@ -225,6 +227,9 @@ public JsyncResult sync(VirtualFileSystem sourceVfs, String sourcePath, VirtualF final VirtualPath sourcePathAbsFinal = sourcePathAbs.normalize(); final VirtualPath targetPathAbsFinal = targetPathAbs.normalize(); + this.sourceRootPath = sourcePathAbsFinal; + this.targetRootPath = targetPathAbsFinal; + // // Negotiate checksum methods between source and target filesystems if necessary @@ -236,29 +241,9 @@ public JsyncResult sync(VirtualFileSystem sourceVfs, String sourcePath, VirtualF log.debug("Source filesystem stat mode: {}", sourceVfs.getStatModel()); log.debug("Target filesystem stat mode: {}", targetVfs.getStatModel()); - // build exclude and ignore paths - if (this.excludes != null) { - this.excludePaths = this.excludes.stream() - .map(VirtualPath::parse) - .map(sourcePathAbsFinal::resolve) - .collect(toList()); - } else { - this.excludePaths = Collections.emptyList(); - } - - if (this.ignores != null) { - this.ignoreSourcePaths = this.ignores.stream() - .map(VirtualPath::parse) - .map(sourcePathAbsFinal::resolve) - .collect(toList()); - this.ignoreTargetPaths = this.ignores.stream() - .map(VirtualPath::parse) - .map(targetPathAbsFinal::resolve) - .collect(toList()); - } else { - this.ignoreSourcePaths = Collections.emptyList(); - this.ignoreTargetPaths = Collections.emptyList(); - } + // build exclude and ignore matchers + this.excludeMatchers = VirtualPathMatchers.compile(this.excludes); + this.ignoreMatchers = VirtualPathMatchers.compile(this.ignores); final long now = System.currentTimeMillis(); @@ -272,17 +257,6 @@ public JsyncResult sync(VirtualFileSystem sourceVfs, String sourcePath, VirtualF final List deferredFiles = new ArrayList<>(); if (sourcePathAbsFinal.isDirectory()) { - // any excludes, let's resolve them against pwd of the source to make it easier to exclude them - final List excludePaths; - if (this.excludes != null) { - excludePaths = this.excludes.stream() - .map(VirtualPath::parse) - .map(sourcePathAbsFinal::resolve) - .collect(toList()); - } else { - excludePaths = Collections.emptyList(); - } - // as we process files, only a subset may require more advanced methods of detecting whether they were modified // since that process could be "expensive", we keep a list of files on source/target that we will defer processing // until we have a chance to do some bulk processing of checksums, etc. @@ -409,23 +383,19 @@ protected void syncDirectory(int level, JsyncResult result, List sourceChildPaths = sourceVfs.ls(sourcePath).stream() + final List sourceChildPaths = sourceVfs.ls(sourcePath).stream() // apply filter to source files if they are on the exclude list .filter(v -> { - for (VirtualPath p : this.excludePaths) { - if (v.startsWith(p)) { - this.eventHandler.willExcludePath(v); - return false; - } + if (this.excludeMatchers.matches(this.sourceRootPath, v)) { + this.eventHandler.willExcludePath(v); + return false; } return true; }) .filter(v -> { - for (VirtualPath p : this.ignoreSourcePaths) { - if (v.startsWith(p)) { - this.eventHandler.willIgnorePath(v); - return false; - } + if (this.ignoreMatchers.matches(this.sourceRootPath, v)) { + this.eventHandler.willIgnoreSourcePath(v); + return false; } return true; }) @@ -446,10 +416,9 @@ protected void syncDirectory(int level, JsyncResult result, List targetChildPaths = targetVfs.ls(targetPath).stream() .filter(v -> { - for (VirtualPath p : this.ignoreTargetPaths) { - if (v.startsWith(p)) { - return false; - } + if (this.ignoreMatchers.matches(this.targetRootPath, v)) { + this.eventHandler.willIgnoreTargetPath(v); + return false; } return true; }) diff --git a/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEventHandler.java b/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEventHandler.java index f9f5c54..e3ab3af 100644 --- a/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEventHandler.java +++ b/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEventHandler.java @@ -16,9 +16,11 @@ public interface JsyncEventHandler { void willEnd(VirtualFileSystem sourceVfs, VirtualPath sourcePath, VirtualFileSystem targetVfs, VirtualPath targetPath, JsyncResult result, long timeMillis); - void willExcludePath(VirtualPath targetPath); + void willExcludePath(VirtualPath sourcePath); - void willIgnorePath(VirtualPath targetPath); + void willIgnoreSourcePath(VirtualPath sourcePath); + + void willIgnoreTargetPath(VirtualPath targetPath); void willCreateDirectory(VirtualPath targetPath, boolean recursively); diff --git a/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatcher.java b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatcher.java new file mode 100644 index 0000000..f8369be --- /dev/null +++ b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatcher.java @@ -0,0 +1,93 @@ +package com.fizzed.jsync.vfs.util; + +import com.fizzed.jsync.vfs.VirtualPath; + +import java.nio.file.FileSystems; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; + +public class VirtualPathMatcher { + + private final PathMatcher matcher; + + public VirtualPathMatcher(PathMatcher matcher) { + this.matcher = matcher; + } + + public boolean matches(VirtualPath rootPath, VirtualPath currentPath) { + final String relativePath; + + // resolve the current path against the root path, so we're left with the relative path we're matching against + if (currentPath.isAbsolute()) { + String rootFullPath = rootPath.toFullPath(); + String currentFullPath = currentPath.toFullPath(); + int pathStartPos = currentFullPath.indexOf(rootFullPath); + if (pathStartPos >= 0) { + // remove the leading path PLUS the file separator + relativePath = currentFullPath.substring(pathStartPos + rootFullPath.length() + 1); + } else { + relativePath = currentFullPath; + } + } else { + relativePath = currentPath.toString(); + } + + return this.matcher.matches(Paths.get(relativePath)); + } + + static public VirtualPathMatcher compile(String rule) { + String glob = rule.trim(); + boolean isDirectory = false; + boolean isRooted = false; + + // 1. Check for Directory marker + if (glob.endsWith("/")) { + isDirectory = true; + glob = glob.substring(0, glob.length() - 1); // Strip trailing slash + } + + // 2. Check for Root anchor + if (glob.startsWith("/")) { + isRooted = true; + glob = glob.substring(1); // Strip leading slash + } + + // FIX: Handle "/**/" usually found in the middle of paths + // Git: "docs/**/*.md" -> Zero or more dirs + // Java: "docs/**/*.md" -> One or more dirs (fails on docs/file.md) + // Solution: Replace "/**/" with "/{,**/}" which means "Empty OR /**/" +// if (glob.contains("/**/")) { +// glob = glob.replace("/**/", "/{,**/}"); +// } + + // 3. Build the Glob + // We need to construct a robust brace expansion {A,B,C...} + StringBuilder finalGlob = new StringBuilder(); + finalGlob.append("glob:{"); + + if (isRooted) { + // Rule: /target/ or /target + finalGlob.append(glob); // Matches "target" at root + if (isDirectory) { + finalGlob.append(",").append(glob).append("/**"); // Matches "target/..." at root + } + } else { + // Rule: target/ or target + finalGlob.append(glob); // Matches "target" at root + finalGlob.append(",**/").append(glob); // Matches "src/target" (nested) + + if (isDirectory) { + // If it's a directory, we must ALSO match the contents + finalGlob.append(",").append(glob).append("/**"); // Matches "target/file.txt" (root) + finalGlob.append(",**/").append(glob).append("/**"); // Matches "src/target/file.txt" (nested) + } + } + + finalGlob.append("}"); + + PathMatcher matcher = FileSystems.getDefault().getPathMatcher(finalGlob.toString()); + + return new VirtualPathMatcher(matcher); + } + +} \ No newline at end of file diff --git a/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java index 1347a53..87bc1dd 100644 --- a/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java +++ b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java @@ -1,37 +1,35 @@ package com.fizzed.jsync.vfs.util; -import java.nio.file.FileSystems; -import java.nio.file.PathMatcher; +import com.fizzed.jsync.vfs.VirtualPath; + +import java.util.ArrayList; +import java.util.List; public class VirtualPathMatchers { - + private final List matchers = new ArrayList<>(); - static public PathMatcher compileRule(String rule) { - String glob = rule.trim(); + public VirtualPathMatchers(List matchers) { + this.matchers.addAll(matchers); + } - // 1. Handle directory-only rules (e.g., "build/") - // Git implies "everything inside this directory" - if (glob.endsWith("/")) { - glob = glob + "**"; + public boolean matches(VirtualPath rootPath, VirtualPath path) { + for (VirtualPathMatcher matcher : matchers) { + if (matcher.matches(rootPath, path)) { + return true; + } } + return false; + } - // 2. Handle "rooted" vs "anywhere" rules - // If it starts with '/', it matches from the root only. - // If NOT, it matches anywhere (e.g., "*.log" -> "** /*.log") - if (glob.startsWith("/")) { - // Remove leading slash for Java PathMatcher consistency on relative paths - glob = glob.substring(1); - } else { - // If it's not rooted, allow it to match deep in the tree - // Example: "tmp" becomes "**/tmp" - if (!glob.startsWith("**/") && !glob.equals("*")) { - glob = "**/" + glob; + static public VirtualPathMatchers compile(List rules) { + List matchers = new ArrayList<>(); + if (rules != null) { + for (String rule : rules) { + matchers.add(VirtualPathMatcher.compile(rule)); } } - - // Create the matcher using "glob" syntax - return FileSystems.getDefault().getPathMatcher("glob:" + glob); + return new VirtualPathMatchers(matchers); } } \ No newline at end of file diff --git a/jsync-vfs/src/test/java/com/fizzed/jsync/vfs/util/VirtualPathMatcherTest.java b/jsync-vfs/src/test/java/com/fizzed/jsync/vfs/util/VirtualPathMatcherTest.java new file mode 100644 index 0000000..f9fd550 --- /dev/null +++ b/jsync-vfs/src/test/java/com/fizzed/jsync/vfs/util/VirtualPathMatcherTest.java @@ -0,0 +1,86 @@ +package com.fizzed.jsync.vfs.util; + +import com.fizzed.jsync.vfs.VirtualPath; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class VirtualPathMatcherTest { + + @Test + public void absoluteRootOnlyRuleForDirectory() { + VirtualPath rootPath = VirtualPath.parse("/home/jjlauer"); + + VirtualPathMatcher matcher = VirtualPathMatcher.compile("/target"); + + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target"))).isTrue(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target/a.txt"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target2"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/2target"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/sub/target"))).isFalse(); + } + + @Test + public void absoluteAnywhereRuleForDirectory() { + VirtualPath rootPath = VirtualPath.parse("/home/jjlauer"); + + VirtualPathMatcher matcher = VirtualPathMatcher.compile("target"); + + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target"))).isTrue(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target/a.txt"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target2"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/2target"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/sub/target"))).isTrue(); + } + + @Test + public void absoluteAnywhereRuleForDirectoryWithSlash() { + VirtualPath rootPath = VirtualPath.parse("/home/jjlauer"); + + VirtualPathMatcher matcher = VirtualPathMatcher.compile("target/"); + + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target"))).isTrue(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target/a.txt"))).isTrue(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target2"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/2target"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/sub/target"))).isTrue(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/sub/target/a.txt"))).isTrue(); + } + + @Test + public void absoluteAnywhereRuleForFile() { + VirtualPath rootPath = VirtualPath.parse("/home/jjlauer"); + + VirtualPathMatcher matcher = VirtualPathMatcher.compile("*.log"); + + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/log"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/a.log"))).isTrue(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/sub/a.log"))).isTrue(); + } + + @Test + public void absoluteAnywhereRuleForDirectoryOnWindows() { + VirtualPath rootPath = VirtualPath.parse("C:\\Users\\jjlauer"); + + VirtualPathMatcher matcher = VirtualPathMatcher.compile("target"); + + assertThat(matcher.matches(rootPath, VirtualPath.parse("C:\\Users\\jjlauer\\target"))).isTrue(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("C:\\Users\\jjlauer\\target\\a.txt"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("C:\\Users\\jjlauer\\target2"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("C:\\Users\\jjlauer\\2target"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("C:\\Users\\jjlauer\\sub\\target"))).isTrue(); + } + + @Test + public void relativeDoubleStarRuleForFiles() { + VirtualPath rootPath = VirtualPath.parse("/home/jjlauer"); + + VirtualPathMatcher matcher = VirtualPathMatcher.compile("docs/**/*.md"); + + // this is where java's globbing kinda fails where it can't do zero or more with that match + //assertThat(matcher.matches(rootPath, VirtualPath.parse("docs"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("docs/src/a.md"))).isTrue(); + } + +} \ No newline at end of file From faf471b14e1b1aa6defd227217ffa99ebaf0e46b Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Sun, 30 Nov 2025 10:24:53 -0500 Subject: [PATCH 3/4] Updated matchers --- .../java/com/fizzed/jsync/engine/JsyncEngine.java | 4 ++++ .../fizzed/jsync/vfs/util/VirtualPathMatcher.java | 14 +++++++++++--- .../fizzed/jsync/vfs/util/VirtualPathMatchers.java | 11 ++++++++--- .../jsync/vfs/util/VirtualPathMatcherTest.java | 13 +++++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java b/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java index aa7a65a..0d9fd79 100644 --- a/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java +++ b/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java @@ -245,6 +245,9 @@ public JsyncResult sync(VirtualFileSystem sourceVfs, String sourcePath, VirtualF this.excludeMatchers = VirtualPathMatchers.compile(this.excludes); this.ignoreMatchers = VirtualPathMatchers.compile(this.ignores); + log.debug("excludeMatchers: {}", this.excludeMatchers); + log.debug("ignoreMatchers: {}", this.ignoreMatchers); + final long now = System.currentTimeMillis(); @@ -393,6 +396,7 @@ protected void syncDirectory(int level, JsyncResult result, List { + log.debug("Checking if should ignore: root={}, path={}", this.sourceRootPath, v); if (this.ignoreMatchers.matches(this.sourceRootPath, v)) { this.eventHandler.willIgnoreSourcePath(v); return false; diff --git a/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatcher.java b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatcher.java index f8369be..fc726d3 100644 --- a/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatcher.java +++ b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatcher.java @@ -8,9 +8,11 @@ public class VirtualPathMatcher { + private final String globRule; private final PathMatcher matcher; - public VirtualPathMatcher(PathMatcher matcher) { + public VirtualPathMatcher(String globRule, PathMatcher matcher) { + this.globRule = globRule; this.matcher = matcher; } @@ -35,6 +37,11 @@ public boolean matches(VirtualPath rootPath, VirtualPath currentPath) { return this.matcher.matches(Paths.get(relativePath)); } + @Override + public String toString() { + return this.globRule; + } + static public VirtualPathMatcher compile(String rule) { String glob = rule.trim(); boolean isDirectory = false; @@ -85,9 +92,10 @@ static public VirtualPathMatcher compile(String rule) { finalGlob.append("}"); - PathMatcher matcher = FileSystems.getDefault().getPathMatcher(finalGlob.toString()); + String globRule = finalGlob.toString(); + PathMatcher matcher = FileSystems.getDefault().getPathMatcher(globRule); - return new VirtualPathMatcher(matcher); + return new VirtualPathMatcher(globRule, matcher); } } \ No newline at end of file diff --git a/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java index 87bc1dd..dd505a2 100644 --- a/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java +++ b/jsync-vfs/src/main/java/com/fizzed/jsync/vfs/util/VirtualPathMatchers.java @@ -7,14 +7,14 @@ public class VirtualPathMatchers { - private final List matchers = new ArrayList<>(); + private final List matchers; public VirtualPathMatchers(List matchers) { - this.matchers.addAll(matchers); + this.matchers = matchers; } public boolean matches(VirtualPath rootPath, VirtualPath path) { - for (VirtualPathMatcher matcher : matchers) { + for (VirtualPathMatcher matcher : this.matchers) { if (matcher.matches(rootPath, path)) { return true; } @@ -22,6 +22,11 @@ public boolean matches(VirtualPath rootPath, VirtualPath path) { return false; } + @Override + public String toString() { + return this.matchers.toString(); + } + static public VirtualPathMatchers compile(List rules) { List matchers = new ArrayList<>(); if (rules != null) { diff --git a/jsync-vfs/src/test/java/com/fizzed/jsync/vfs/util/VirtualPathMatcherTest.java b/jsync-vfs/src/test/java/com/fizzed/jsync/vfs/util/VirtualPathMatcherTest.java index f9fd550..f94169a 100644 --- a/jsync-vfs/src/test/java/com/fizzed/jsync/vfs/util/VirtualPathMatcherTest.java +++ b/jsync-vfs/src/test/java/com/fizzed/jsync/vfs/util/VirtualPathMatcherTest.java @@ -47,6 +47,19 @@ public void absoluteAnywhereRuleForDirectoryWithSlash() { assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/sub/target/a.txt"))).isTrue(); } + @Test + public void absoluteAnywhereRuleForDirectoryWithLeadingDot() { + VirtualPath rootPath = VirtualPath.parse("/home/jjlauer"); + + VirtualPathMatcher matcher = VirtualPathMatcher.compile(".buildx"); + + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/.buildx"))).isTrue(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/.buildx/a.txt"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/target2"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/2target"))).isFalse(); + assertThat(matcher.matches(rootPath, VirtualPath.parse("/home/jjlauer/sub/.buildx"))).isTrue(); + } + @Test public void absoluteAnywhereRuleForFile() { VirtualPath rootPath = VirtualPath.parse("/home/jjlauer"); From 489056b1ca00d39cfdeec40bad9a3d5cf27b7ac5 Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Sun, 30 Nov 2025 10:29:36 -0500 Subject: [PATCH 4/4] Update logging statement --- .../src/main/java/com/fizzed/jsync/engine/JsyncEngine.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java b/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java index 0d9fd79..79f0589 100644 --- a/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java +++ b/jsync-engine/src/main/java/com/fizzed/jsync/engine/JsyncEngine.java @@ -245,8 +245,8 @@ public JsyncResult sync(VirtualFileSystem sourceVfs, String sourcePath, VirtualF this.excludeMatchers = VirtualPathMatchers.compile(this.excludes); this.ignoreMatchers = VirtualPathMatchers.compile(this.ignores); - log.debug("excludeMatchers: {}", this.excludeMatchers); - log.debug("ignoreMatchers: {}", this.ignoreMatchers); + log.debug("Using exclude matchers: {}", this.excludeMatchers); + log.debug("Using ignore matchers: {}", this.ignoreMatchers); final long now = System.currentTimeMillis();