Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion modules/nf-commons/src/main/nextflow/file/FileHelper.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,13 @@ class FileHelper {
final syntax = options.syntax ?: 'glob'
final relative = options.relative == true

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

Files.walkFileTree(folder, walkOptions, Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
Expand Down
42 changes: 42 additions & 0 deletions modules/nf-commons/src/test/nextflow/file/FileHelperTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,48 @@ class FileHelperTest extends Specification {

}

def 'should match files at root and subdirectories with double-star glob'() {
// Tests that **/ pattern matches files at the root level (zero directories)
// as well as files in subdirectories (one or more directories)
// See: https://github.com/nextflow-io/nextflow/issues/5948

given:
def folder = Files.createTempDirectory('test')

// Create files at root level
folder.resolve('data.bin').text = 'root data'
folder.resolve('other.txt').text = 'other file'

// Create files in subdirectory
folder.resolve('subdir').mkdir()
folder.resolve('subdir').resolve('data.bin').text = 'subdir data'

// Create files in nested subdirectory
folder.resolve('subdir').resolve('nested').mkdir()
folder.resolve('subdir').resolve('nested').resolve('data.bin').text = 'nested data'

when: 'using **/ pattern should match files at all levels including root'
def result = []
FileHelper.visitFiles(folder, '**/data.bin', relative: true) { result << it.toString() }

then: 'files at root AND in subdirectories are matched'
result.sort() == ['data.bin', 'subdir/data.bin', 'subdir/nested/data.bin']

when: 'using **/ with intermediate directory should also match at all levels'
result = []
folder.resolve('InterOp').mkdir()
folder.resolve('InterOp').resolve('metrics.bin').text = 'root interop'
folder.resolve('subdir').resolve('InterOp').mkdir()
folder.resolve('subdir').resolve('InterOp').resolve('metrics.bin').text = 'subdir interop'
FileHelper.visitFiles(folder, '**/InterOp/*.bin', relative: true) { result << it.toString() }

then: 'InterOp directories at root AND in subdirectories are matched'
result.sort() == ['InterOp/metrics.bin', 'subdir/InterOp/metrics.bin']

cleanup:
folder?.deleteDir()
}


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

Expand Down
Loading