Skip to content

Commit

Permalink
Handle Windows file separators in dockerignore patterns.
Browse files Browse the repository at this point in the history
  • Loading branch information
gesellix committed Nov 11, 2018
1 parent 4994811 commit 80fea51
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.gesellix.docker.client.builder

import groovy.util.logging.Slf4j

import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.PathMatcher
Expand All @@ -10,32 +11,35 @@ import java.nio.file.PathMatcher
class GlobsMatcher {

File base
List<String> globs
List<Matcher> matchers

GlobsMatcher(File base, List<String> globs) {
this.base = base
def fileSystem = FileSystems.getDefault()
this.matchers = globs.collectMany {
if (it.contains("\\")) {
// if .dockerignore contains backslashes (for windows) something nasty will happen.
// Caught: java.util.regex.PatternSyntaxException: No character to escape near index 3
// bin\
it = it.replaceAll('\\\\', '/')
}
if (it.endsWith("/")) {
[new Matcher(fileSystem, it.replaceAll("/\$", "")),
new Matcher(fileSystem, it.replaceAll("/\$", "/**"))]
}
else {
[new Matcher(fileSystem, it)]
this.globs = globs
}

void initMatchers() {
if (this.matchers == null) {
def fileSystem = FileSystems.getDefault()
this.matchers = globs.collectMany {
if (it.endsWith("/")) {
return [new Matcher(fileSystem, it.replaceAll("/\$", "")),
new Matcher(fileSystem, it.replaceAll("/\$", "/**"))]
}
else {
return [new Matcher(fileSystem, it)]
}
}.reverse()
matchers.each {
log.debug("pattern: ${it.pattern}")
}
}.reverse()
matchers.each {
log.debug("pattern: ${it.pattern}")
}
}

def matches(File path) {
boolean matches(File path) {
initMatchers()

def relativePath = base.absoluteFile.toPath().relativize(path.absoluteFile.toPath())
def match = matchers.find {
it.matches(relativePath)
Expand All @@ -54,18 +58,27 @@ class GlobsMatcher {
PathMatcher matcher
boolean negate

Matcher(fileSystem, String pattern) {
this.negate = pattern.startsWith("!")
this.pattern = pattern
static String separator = File.separatorChar

Matcher(FileSystem fileSystem, String pattern) {
this.pattern = pattern.replaceAll("/", "\\${separator}")
.split("\\${separator}")
.join("\\${separator}")
String negation = "!"
this.negate = pattern.startsWith(negation)
if (this.negate) {
def invertedPattern = pattern.substring("!".length())
this.matcher = fileSystem.getPathMatcher("glob:${invertedPattern}")
String invertedPattern = this.pattern.substring(negation.length())
this.matcher = createGlob(fileSystem, invertedPattern)
}
else {
this.matcher = fileSystem.getPathMatcher("glob:${pattern}")
this.matcher = createGlob(fileSystem, this.pattern)
}
}

static PathMatcher createGlob(FileSystem fileSystem, String glob) {
return fileSystem.getPathMatcher("glob:${glob}")
}

@Override
boolean matches(Path path) {
return matcher.matches(path)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package de.gesellix.docker.client.builder

import de.gesellix.util.IOUtils
import org.apache.commons.lang.SystemUtils
import spock.lang.Requires
import spock.lang.Specification

class DockerignoreFileFilterSpec extends Specification {
Expand Down Expand Up @@ -39,8 +41,8 @@ class DockerignoreFileFilterSpec extends Specification {
new File("${baseDir}/keepme/subdir/keep-me.txt")].sort()
}


def "handles backslashes in patterns (for windows)"() {
@Requires({ SystemUtils.IS_OS_WINDOWS })
def "handles trailing backslashes in patterns (windows only)"() {
given:
def baseDir = IOUtils.getResource("/dockerignore_windows").file
def base = new File(baseDir)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package de.gesellix.docker.client.builder

import org.apache.commons.lang.SystemUtils
import spock.lang.IgnoreIf
import spock.lang.Requires
import spock.lang.Specification
import spock.lang.Unroll

Expand All @@ -12,6 +12,7 @@ class GlobsMatcherSpec extends Specification {
def "matches all patterns"() {
given:
def matcher = new GlobsMatcher(new File(""), ["abc", "cde"])
matcher.initMatchers()

expect:
matcher.matchers.size() == 2
Expand Down Expand Up @@ -48,7 +49,7 @@ class GlobsMatcherSpec extends Specification {
"[-]" | new File("") | new File("-")
}

@IgnoreIf({ !SystemUtils.IS_OS_LINUX && !SystemUtils.IS_OS_MAC })
@Requires({ SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC })
@Unroll
"#pattern should match #path on unix systems"(String pattern, File base, File path) {
expect:
Expand All @@ -63,6 +64,17 @@ class GlobsMatcherSpec extends Specification {
// "[\\]a]" | new File("") | new File("]")
}

@Requires({ SystemUtils.IS_OS_WINDOWS })
@Unroll
"#pattern should match #path on windows systems"(String pattern, File base, File path) {
expect:
new GlobsMatcher(base, [pattern]).matches(path)

where:
pattern | base | path
"bin\\" | new File("") | new File("bin\\foo")
}

@Unroll
"#pattern should not match #path"(String pattern, File path) {
expect:
Expand All @@ -83,7 +95,7 @@ class GlobsMatcherSpec extends Specification {
"a*b" | new File("a/b")
}

@IgnoreIf({ !SystemUtils.IS_OS_LINUX && !SystemUtils.IS_OS_MAC })
@Requires({ SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC })
@Unroll
"#pattern should not match #path on unix systems"(String pattern, File base, File path) {
expect:
Expand All @@ -109,6 +121,23 @@ class GlobsMatcherSpec extends Specification {
"[]a]" | new File("") | new File("]")
}

@Requires({ SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC })
@Unroll
"#pattern should throw exception on unix systems"(String pattern, File base, File path) {
when:
new GlobsMatcher(base, [pattern]).matches(path)

then:
thrown(PatternSyntaxException)

where:
pattern | base | path
// On *nix, the backslash is the escape character, so it's invalid to be used at the end of a pattern
// A trailing backslash is only valid on Windows.
// We actually differ from the official client's behaviour, which silently ignores invalid patterns.
"bin\\" | new File("") | new File("bin/foo")
}

def "allows pattern exclusions"() {
expect:
new GlobsMatcher(new File(""), patterns).matches(path) == shouldMatch
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
bin\
Dockerfile
3 changes: 3 additions & 0 deletions client/src/test/resources/dockerignore_windows/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM microsoft/aspnetcore:2.0
COPY . /example
RUN ls -lisah /example

0 comments on commit 80fea51

Please sign in to comment.