Skip to content

Commit 2ab4883

Browse files
robsymeclaude
andcommitted
Fix leading **/ glob to match zero-or-more directories
Java's glob pattern `**/foo` requires at least one directory component before `foo`, but users expect it to also match `foo` at the root level. This change transforms leading `**/` patterns to `{,**/}` which means "either nothing OR any number of directories", correctly matching: - `foo` (zero directories) - `bar/foo` (one directory) - `bar/baz/foo` (multiple directories) This fixes issues where `files("path/**/subdir/*.txt")` would fail to find files in `path/subdir/` while finding files in `path/x/subdir/`. Fixes #5948 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> Signed-off-by: Rob Syme <[email protected]>
1 parent 03da64e commit 2ab4883

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

modules/nf-commons/src/main/nextflow/file/FileHelper.groovy

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,13 @@ class FileHelper {
859859
final syntax = options.syntax ?: 'glob'
860860
final relative = options.relative == true
861861

862-
final matcher = getPathMatcherFor("$syntax:${filePattern}", folder.fileSystem)
862+
// Transform leading ** to match zero-or-more directories (not just one-or-more)
863+
// Java's glob ** at pattern start requires at least one directory component,
864+
// but users expect **/foo to also match foo at the root level
865+
final adjustedPattern = filePattern.startsWith('**/')
866+
? "{,**/}" + filePattern.substring(3)
867+
: filePattern
868+
final matcher = getPathMatcherFor("$syntax:${adjustedPattern}", folder.fileSystem)
863869
final singleParam = action.getMaximumNumberOfParameters() == 1
864870

865871
Files.walkFileTree(folder, walkOptions, Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {

modules/nf-commons/src/test/nextflow/file/FileHelperTest.groovy

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,48 @@ class FileHelperTest extends Specification {
624624

625625
}
626626

627+
def 'should match files at root and subdirectories with double-star glob'() {
628+
// Tests that **/ pattern matches files at the root level (zero directories)
629+
// as well as files in subdirectories (one or more directories)
630+
// See: https://github.com/nextflow-io/nextflow/issues/5948
631+
632+
given:
633+
def folder = Files.createTempDirectory('test')
634+
635+
// Create files at root level
636+
folder.resolve('data.bin').text = 'root data'
637+
folder.resolve('other.txt').text = 'other file'
638+
639+
// Create files in subdirectory
640+
folder.resolve('subdir').mkdir()
641+
folder.resolve('subdir').resolve('data.bin').text = 'subdir data'
642+
643+
// Create files in nested subdirectory
644+
folder.resolve('subdir').resolve('nested').mkdir()
645+
folder.resolve('subdir').resolve('nested').resolve('data.bin').text = 'nested data'
646+
647+
when: 'using **/ pattern should match files at all levels including root'
648+
def result = []
649+
FileHelper.visitFiles(folder, '**/data.bin', relative: true) { result << it.toString() }
650+
651+
then: 'files at root AND in subdirectories are matched'
652+
result.sort() == ['data.bin', 'subdir/data.bin', 'subdir/nested/data.bin']
653+
654+
when: 'using **/ with intermediate directory should also match at all levels'
655+
result = []
656+
folder.resolve('InterOp').mkdir()
657+
folder.resolve('InterOp').resolve('metrics.bin').text = 'root interop'
658+
folder.resolve('subdir').resolve('InterOp').mkdir()
659+
folder.resolve('subdir').resolve('InterOp').resolve('metrics.bin').text = 'subdir interop'
660+
FileHelper.visitFiles(folder, '**/InterOp/*.bin', relative: true) { result << it.toString() }
661+
662+
then: 'InterOp directories at root AND in subdirectories are matched'
663+
result.sort() == ['InterOp/metrics.bin', 'subdir/InterOp/metrics.bin']
664+
665+
cleanup:
666+
folder?.deleteDir()
667+
}
668+
627669

628670
def 'should copy file path to foreign file system' () {
629671

0 commit comments

Comments
 (0)