Skip to content

Commit b1ed66b

Browse files
tejksatKostyaSha
authored andcommitted
* Fix for docker-java#643 .dockerignore rules handling refactored. A rule is translated to a regexp pattern and then a path is checked against it. * Fix for docker-java#643 Failing tests on Windows fixed: Windows file separator escaped.
1 parent 6195eb8 commit b1ed66b

File tree

20 files changed

+231
-185
lines changed

20 files changed

+231
-185
lines changed

src/main/java/com/github/dockerjava/core/GoLangFileMatch.java

Lines changed: 153 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.io.File;
77
import java.util.ArrayList;
88
import java.util.List;
9+
import java.util.regex.Pattern;
910

1011
import org.apache.commons.lang.StringUtils;
1112

@@ -49,6 +50,8 @@ private GoLangFileMatch() {
4950

5051
public static final boolean IS_WINDOWS = File.separatorChar == '\\';
5152

53+
private static final String PATTERN_CHARS_TO_ESCAPE = "\\.[]{}()*+-?^$|";
54+
5255
public static boolean match(List<String> patterns, File file) {
5356
return !match(patterns, file.getPath()).isEmpty();
5457
}
@@ -71,196 +74,194 @@ public static List<String> match(List<String> patterns, String name) {
7174
}
7275

7376
public static boolean match(String pattern, String name) {
74-
Pattern: while (!pattern.isEmpty()) {
75-
ScanResult scanResult = scanChunk(pattern);
76-
pattern = scanResult.pattern;
77-
if (scanResult.star && StringUtils.isEmpty(scanResult.chunk)) {
78-
// Trailing * matches rest of string unless it has a /.
79-
return name.indexOf(File.separatorChar) < 0;
80-
}
81-
// Look for match at current position.
82-
String matchResult = matchChunk(scanResult.chunk, name);
77+
return buildPattern(pattern).matcher(name).matches();
78+
}
8379

84-
// if we're the last chunk, make sure we've exhausted the name
85-
// otherwise we'll give a false result even if we could still match
86-
// using the star
87-
if (matchResult != null && (matchResult.isEmpty() || !pattern.isEmpty())) {
88-
name = matchResult;
89-
continue;
90-
}
91-
if (scanResult.star) {
92-
for (int i = 0; i < name.length() && name.charAt(i) != File.separatorChar; i++) {
93-
matchResult = matchChunk(scanResult.chunk, name.substring(i + 1));
94-
if (matchResult != null) {
95-
// if we're the last chunk, make sure we exhausted the name
96-
if (pattern.isEmpty() && !matchResult.isEmpty()) {
97-
continue;
98-
}
99-
name = matchResult;
100-
continue Pattern;
101-
}
102-
}
80+
private static Pattern buildPattern(String pattern) {
81+
StringBuilder patternStringBuilder = new StringBuilder("^");
82+
while (!pattern.isEmpty()) {
83+
pattern = appendChunkPattern(patternStringBuilder, pattern);
84+
85+
if (!pattern.isEmpty()) {
86+
patternStringBuilder.append(quote(File.separatorChar));
10387
}
104-
return false;
10588
}
106-
return name.isEmpty();
89+
patternStringBuilder.append("(").append(quote(File.separatorChar)).append(".*").append(")?");
90+
return Pattern.compile(patternStringBuilder.toString());
10791
}
10892

109-
static ScanResult scanChunk(String pattern) {
110-
boolean star = false;
111-
if (!pattern.isEmpty() && pattern.charAt(0) == '*') {
112-
pattern = pattern.substring(1);
113-
star = true;
93+
private static String quote(char separatorChar) {
94+
if (StringUtils.contains(PATTERN_CHARS_TO_ESCAPE, separatorChar)) {
95+
return "\\" + separatorChar;
96+
} else {
97+
return String.valueOf(separatorChar);
11498
}
99+
}
100+
101+
private static String appendChunkPattern(StringBuilder patternStringBuilder, String pattern) {
102+
if (pattern.equals("**") || pattern.startsWith("**" + File.separator)) {
103+
patternStringBuilder.append("(")
104+
.append("[^").append(quote(File.separatorChar)).append("]*")
105+
.append("(")
106+
.append(quote(File.separatorChar)).append("[^").append(quote(File.separatorChar)).append("]*")
107+
.append(")*").append(")?");
108+
return pattern.substring(pattern.length() == 2 ? 2 : 3);
109+
}
110+
115111
boolean inRange = false;
112+
int rangeFrom = 0;
113+
RangeParseState rangeParseState = RangeParseState.CHAR_EXPECTED;
114+
boolean isEsc = false;
116115
int i;
117-
Scan: for (i = 0; i < pattern.length(); i++) {
118-
switch (pattern.charAt(i)) {
119-
case '\\': {
120-
if (!IS_WINDOWS && i + 1 < pattern.length()) {
121-
i++;
116+
for (i = 0; i < pattern.length(); i++) {
117+
char c = pattern.charAt(i);
118+
switch (c) {
119+
case '/':
120+
if (!inRange) {
121+
if (!IS_WINDOWS && !isEsc) {
122+
// end of chunk
123+
return pattern.substring(i + 1);
124+
} else {
125+
patternStringBuilder.append(quote(c));
126+
}
127+
} else {
128+
rangeParseState = nextStateAfterChar(rangeParseState);
122129
}
130+
isEsc = false;
123131
break;
124-
}
125-
case '[':
126-
inRange = true;
127-
break;
128-
case ']':
129-
inRange = false;
130-
break;
131-
case '*':
132+
case '\\':
132133
if (!inRange) {
133-
break Scan;
134+
if (!IS_WINDOWS) {
135+
if (isEsc) {
136+
patternStringBuilder.append(quote(c));
137+
isEsc = false;
138+
} else {
139+
isEsc = true;
140+
}
141+
} else {
142+
// end of chunk
143+
return pattern.substring(i + 1);
144+
}
145+
} else {
146+
if (IS_WINDOWS || isEsc) {
147+
rangeParseState = nextStateAfterChar(rangeParseState);
148+
isEsc = false;
149+
} else {
150+
isEsc = true;
151+
}
134152
}
135-
}
136-
}
137-
return new ScanResult(star, pattern.substring(0, i), pattern.substring(i));
138-
}
139-
140-
static String matchChunk(String chunk, String s) {
141-
int chunkLength = chunk.length();
142-
int chunkOffset = 0;
143-
int sLength = s.length();
144-
int sOffset = 0;
145-
char r;
146-
while (chunkOffset < chunkLength) {
147-
if (sOffset == sLength) {
148-
return null;
149-
}
150-
switch (chunk.charAt(chunkOffset)) {
153+
break;
151154
case '[':
152-
r = s.charAt(sOffset);
153-
sOffset++;
154-
chunkOffset++;
155-
// We can't end right after '[', we're expecting at least
156-
// a closing bracket and possibly a caret.
157-
if (chunkOffset == chunkLength) {
158-
throw new GoLangFileMatchException();
159-
}
160-
// possibly negated
161-
boolean negated = chunk.charAt(chunkOffset) == '^';
162-
if (negated) {
163-
chunkOffset++;
155+
if (!isEsc) {
156+
if (inRange) {
157+
//"[ is not expected, ] had not reached"
158+
throw new GoLangFileMatchException();
159+
}
160+
rangeFrom = i;
161+
rangeParseState = RangeParseState.CHAR_EXPECTED;
162+
inRange = true;
163+
} else {
164+
if (!inRange) {
165+
patternStringBuilder.append(c);
166+
} else {
167+
rangeParseState = nextStateAfterChar(rangeParseState);
168+
}
164169
}
165-
// parse all ranges
166-
boolean match = false;
167-
int nrange = 0;
168-
while (true) {
169-
if (chunkOffset < chunkLength && chunk.charAt(chunkOffset) == ']' && nrange > 0) {
170-
chunkOffset++;
171-
break;
170+
isEsc = false;
171+
break;
172+
case ']':
173+
if (!isEsc) {
174+
if (!inRange) {
175+
//"] is not expected, [ was not met"
176+
throw new GoLangFileMatchException();
172177
}
173-
GetEscResult result = getEsc(chunk, chunkOffset, chunkLength);
174-
char lo = result.lo;
175-
char hi = lo;
176-
chunkOffset = result.chunkOffset;
177-
if (chunk.charAt(chunkOffset) == '-') {
178-
result = getEsc(chunk, ++chunkOffset, chunkLength);
179-
chunkOffset = result.chunkOffset;
180-
hi = result.lo;
178+
if (rangeParseState == RangeParseState.CHAR_EXPECTED_AFTER_DASH) {
179+
// character range not finished
180+
throw new GoLangFileMatchException();
181181
}
182-
if (lo <= r && r <= hi) {
183-
match = true;
182+
patternStringBuilder.append(pattern.substring(rangeFrom, i + 1));
183+
inRange = false;
184+
} else {
185+
if (!inRange) {
186+
patternStringBuilder.append(c);
187+
} else {
188+
rangeParseState = nextStateAfterChar(rangeParseState);
184189
}
185-
nrange++;
186190
}
187-
if (match == negated) {
188-
return null;
191+
isEsc = false;
192+
break;
193+
case '*':
194+
if (!inRange) {
195+
if (!isEsc) {
196+
patternStringBuilder.append("[^").append(quote(File.separatorChar)).append("]*");
197+
} else {
198+
patternStringBuilder.append(quote(c));
199+
}
200+
} else {
201+
rangeParseState = nextStateAfterChar(rangeParseState);
189202
}
203+
isEsc = false;
190204
break;
191-
192205
case '?':
193-
if (s.charAt(sOffset) == File.separatorChar) {
194-
return null;
206+
if (!inRange) {
207+
if (!isEsc) {
208+
patternStringBuilder.append("[^").append(quote(File.separatorChar)).append("]");
209+
} else {
210+
patternStringBuilder.append(quote(c));
211+
}
212+
} else {
213+
rangeParseState = nextStateAfterChar(rangeParseState);
195214
}
196-
sOffset++;
197-
chunkOffset++;
215+
isEsc = false;
198216
break;
199-
case '\\':
200-
if (!IS_WINDOWS) {
201-
chunkOffset++;
202-
if (chunkOffset == chunkLength) {
203-
throw new GoLangFileMatchException();
217+
case '-':
218+
if (!inRange) {
219+
patternStringBuilder.append(quote(c));
220+
} else {
221+
if (!isEsc) {
222+
if (rangeParseState != RangeParseState.CHAR_OR_DASH_EXPECTED) {
223+
// - not expected
224+
throw new GoLangFileMatchException();
225+
}
226+
rangeParseState = RangeParseState.CHAR_EXPECTED_AFTER_DASH;
227+
} else {
228+
rangeParseState = nextStateAfterChar(rangeParseState);
204229
}
205230
}
206-
// fallthrough
231+
isEsc = false;
232+
break;
207233
default:
208-
if (chunk.charAt(chunkOffset) != s.charAt(sOffset)) {
209-
return null;
234+
if (!inRange) {
235+
patternStringBuilder.append(quote(c));
236+
} else {
237+
rangeParseState = nextStateAfterChar(rangeParseState);
210238
}
211-
sOffset++;
212-
chunkOffset++;
239+
isEsc = false;
213240
}
214241
}
215-
return s.substring(sOffset);
216-
}
217-
218-
static GetEscResult getEsc(String chunk, int chunkOffset, int chunkLength) {
219-
if (chunkOffset == chunkLength) {
242+
if (isEsc) {
243+
// "Escaped character missing"
220244
throw new GoLangFileMatchException();
221245
}
222-
char r = chunk.charAt(chunkOffset);
223-
if (r == '-' || r == ']') {
246+
if (inRange) {
247+
// "Character range not finished"
224248
throw new GoLangFileMatchException();
225249
}
226-
if (r == '\\' && !IS_WINDOWS) {
227-
chunkOffset++;
228-
if (chunkOffset == chunkLength) {
229-
throw new GoLangFileMatchException();
230-
}
231-
232-
}
233-
r = chunk.charAt(chunkOffset);
234-
chunkOffset++;
235-
if (chunkOffset == chunkLength) {
236-
throw new GoLangFileMatchException();
237-
}
238-
return new GetEscResult(r, chunkOffset);
250+
return "";
239251
}
240252

241-
private static final class ScanResult {
242-
public boolean star;
243-
244-
public String chunk;
245-
246-
public String pattern;
247-
248-
ScanResult(boolean star, String chunk, String pattern) {
249-
this.star = star;
250-
this.chunk = chunk;
251-
this.pattern = pattern;
253+
private static RangeParseState nextStateAfterChar(RangeParseState currentState) {
254+
if (currentState == RangeParseState.CHAR_EXPECTED_AFTER_DASH) {
255+
return RangeParseState.CHAR_EXPECTED;
256+
} else {
257+
return RangeParseState.CHAR_OR_DASH_EXPECTED;
252258
}
253259
}
254260

255-
private static final class GetEscResult {
256-
public char lo;
257-
258-
public int chunkOffset;
259-
260-
GetEscResult(char lo, int chunkOffset) {
261-
this.lo = lo;
262-
this.chunkOffset = chunkOffset;
263-
}
261+
private enum RangeParseState {
262+
CHAR_EXPECTED,
263+
CHAR_OR_DASH_EXPECTED,
264+
CHAR_EXPECTED_AFTER_DASH
264265
}
265266

266267
}

src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,10 @@ private List<String> matchingIgnorePatterns(String fileName) {
204204

205205
int lineNumber = 0;
206206
for (String pattern : ignores) {
207+
String goLangPattern = pattern.startsWith("!") ? pattern.substring(1) : pattern;
207208
lineNumber++;
208209
try {
209-
if (GoLangFileMatch.match(pattern, fileName)) {
210+
if (GoLangFileMatch.match(goLangPattern, fileName)) {
210211
matches.add(pattern);
211212
}
212213
} catch (GoLangFileMatchException e) {
@@ -233,21 +234,7 @@ private String effectiveMatchingIgnorePattern(File file) {
233234

234235
String lastMatchingPattern = matchingPattern.get(matchingPattern.size() - 1);
235236

236-
int lastMatchingPatternIndex = ignores.lastIndexOf(lastMatchingPattern);
237-
238-
if (lastMatchingPatternIndex == ignores.size() - 1) {
239-
return lastMatchingPattern;
240-
}
241-
242-
List<String> remainingIgnorePattern = ignores.subList(lastMatchingPatternIndex + 1, ignores.size());
243-
244-
for (String ignorePattern : remainingIgnorePattern) {
245-
if (ignorePattern.equals("!" + relativeFilename)) {
246-
return null;
247-
}
248-
}
249-
250-
return lastMatchingPattern;
237+
return !lastMatchingPattern.startsWith("!") ? lastMatchingPattern : null;
251238
}
252239
}
253240
}

0 commit comments

Comments
 (0)