View Javadoc
1   /*
2    * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.ignore;
44  
45  import static org.eclipse.jgit.ignore.internal.Strings.split;
46  import static org.junit.Assert.assertArrayEquals;
47  import static org.junit.Assert.assertFalse;
48  import static org.junit.Assert.assertTrue;
49  import static org.junit.Assert.fail;
50  
51  import org.junit.Before;
52  import org.junit.Test;
53  
54  public class FastIgnoreRuleTest {
55  
56  	private boolean pathMatch;
57  
58  	@Before
59  	public void setup() {
60  		pathMatch = false;
61  	}
62  
63  	@Test
64  	public void testSimpleCharClass() {
65  		assertMatched("][a]", "]a");
66  		assertMatched("[a]", "a");
67  		assertMatched("][a]", "]a");
68  		assertMatched("[a]", "a/");
69  		assertMatched("[a]", "a/b");
70  
71  		assertMatched("[a]", "b/a");
72  		assertMatched("[a]", "b/a/");
73  		assertMatched("[a]", "b/a/b");
74  
75  		assertMatched("[a]", "/a/");
76  		assertMatched("[a]", "/a/b");
77  
78  		assertMatched("[a]", "c/a/b");
79  		assertMatched("[a]", "c/b/a");
80  
81  		assertMatched("/[a]", "a");
82  		assertMatched("/[a]", "a/");
83  		assertMatched("/[a]", "a/b");
84  		assertMatched("/[a]", "/a");
85  		assertMatched("/[a]", "/a/");
86  		assertMatched("/[a]", "/a/b");
87  
88  		assertMatched("[a]/", "a/");
89  		assertMatched("[a]/", "a/b");
90  		assertMatched("[a]/", "/a/");
91  		assertMatched("[a]/", "/a/b");
92  
93  		assertMatched("/[a]/", "a/");
94  		assertMatched("/[a]/", "a/b");
95  		assertMatched("/[a]/", "/a/");
96  		assertMatched("/[a]/", "/a/b");
97  	}
98  
99  	@Test
100 	public void testCharClass() {
101 		assertMatched("[v-z]", "x");
102 		assertMatched("[v-z]", "x/");
103 		assertMatched("[v-z]", "x/b");
104 
105 		assertMatched("[v-z]", "b/x");
106 		assertMatched("[v-z]", "b/x/");
107 		assertMatched("[v-z]", "b/x/b");
108 
109 		assertMatched("[v-z]", "/x/");
110 		assertMatched("[v-z]", "/x/b");
111 
112 		assertMatched("[v-z]", "c/x/b");
113 		assertMatched("[v-z]", "c/b/x");
114 
115 		assertMatched("/[v-z]", "x");
116 		assertMatched("/[v-z]", "x/");
117 		assertMatched("/[v-z]", "x/b");
118 		assertMatched("/[v-z]", "/x");
119 		assertMatched("/[v-z]", "/x/");
120 		assertMatched("/[v-z]", "/x/b");
121 
122 		assertMatched("[v-z]/", "x/");
123 		assertMatched("[v-z]/", "x/b");
124 		assertMatched("[v-z]/", "/x/");
125 		assertMatched("[v-z]/", "/x/b");
126 
127 		assertMatched("/[v-z]/", "x/");
128 		assertMatched("/[v-z]/", "x/b");
129 		assertMatched("/[v-z]/", "/x/");
130 		assertMatched("/[v-z]/", "/x/b");
131 	}
132 
133 	@Test
134 	public void testTrailingSpaces() {
135 		assertMatched("a ", "a");
136 		assertMatched("a/ ", "a/");
137 		assertMatched("a/ ", "a/b");
138 		assertMatched("a/\\ ", "a/ ");
139 		assertNotMatched("a/\\ ", "a/");
140 		assertNotMatched("a/\\ ", "a/b");
141 		assertNotMatched("/ ", "a");
142 	}
143 
144 	@Test
145 	public void testAsteriskDot() {
146 		assertMatched("*.a", ".a");
147 		assertMatched("*.a", "/.a");
148 		assertMatched("*.a", "a.a");
149 		assertMatched("*.a", "/b.a");
150 		assertMatched("*.a", "b.a");
151 		assertMatched("*.a", "/a/b.a");
152 		assertMatched("*.a", "/b/.a");
153 	}
154 
155 	@Test
156 	public void testAsteriskDotDoNotMatch() {
157 		assertNotMatched("*.a", ".ab");
158 		assertNotMatched("*.a", "/.ab");
159 		assertNotMatched("*.a", "/b.ba");
160 		assertNotMatched("*.a", "a.ab");
161 		assertNotMatched("*.a", "/b.ab");
162 		assertNotMatched("*.a", "b.ab");
163 		assertNotMatched("*.a", "/a/b.ab");
164 		assertNotMatched("*.a", "/b/.ab");
165 	}
166 
167 	@Test
168 	public void testDotAsteriskMatch() {
169 		assertMatched("a.*", "a.");
170 		assertMatched("a.*", "a./");
171 		assertMatched("a.*", "a.b");
172 
173 		assertMatched("a.*", "b/a.b");
174 		assertMatched("a.*", "b/a.b/");
175 		assertMatched("a.*", "b/a.b/b");
176 
177 		assertMatched("a.*", "/a.b/");
178 		assertMatched("a.*", "/a.b/b");
179 
180 		assertMatched("a.*", "c/a.b/b");
181 		assertMatched("a.*", "c/b/a.b");
182 
183 		assertMatched("/a.*", "a.b");
184 		assertMatched("/a.*", "a.b/");
185 		assertMatched("/a.*", "a.b/b");
186 		assertMatched("/a.*", "/a.b");
187 		assertMatched("/a.*", "/a.b/");
188 		assertMatched("/a.*", "/a.b/b");
189 
190 		assertMatched("/a.*/b", "a.b/b");
191 		assertMatched("/a.*/b", "/a.b/b");
192 		assertMatched("/a.*/b", "/a.bc/b");
193 		assertMatched("/a.*/b", "/a./b");
194 	}
195 
196 	@Test
197 	public void testAsterisk() {
198 		assertMatched("a*", "a");
199 		assertMatched("a*", "a/");
200 		assertMatched("a*", "ab");
201 
202 		assertMatched("a*", "b/ab");
203 		assertMatched("a*", "b/ab/");
204 		assertMatched("a*", "b/ab/b");
205 
206 		assertMatched("a*", "b/abc");
207 		assertMatched("a*", "b/abc/");
208 		assertMatched("a*", "b/abc/b");
209 
210 		assertMatched("a*", "/abc/");
211 		assertMatched("a*", "/abc/b");
212 
213 		assertMatched("a*", "c/abc/b");
214 		assertMatched("a*", "c/b/abc");
215 
216 		assertMatched("/a*", "abc");
217 		assertMatched("/a*", "abc/");
218 		assertMatched("/a*", "abc/b");
219 		assertMatched("/a*", "/abc");
220 		assertMatched("/a*", "/abc/");
221 		assertMatched("/a*", "/abc/b");
222 
223 		assertMatched("/a*/b", "abc/b");
224 		assertMatched("/a*/b", "/abc/b");
225 		assertMatched("/a*/b", "/abcd/b");
226 		assertMatched("/a*/b", "/a/b");
227 	}
228 
229 	@Test
230 	public void testQuestionmark() {
231 		assertMatched("a?", "ab");
232 		assertMatched("a?", "ab/");
233 
234 		assertMatched("a?", "b/ab");
235 		assertMatched("a?", "b/ab/");
236 		assertMatched("a?", "b/ab/b");
237 
238 		assertMatched("a?", "/ab/");
239 		assertMatched("a?", "/ab/b");
240 
241 		assertMatched("a?", "c/ab/b");
242 		assertMatched("a?", "c/b/ab");
243 
244 		assertMatched("/a?", "ab");
245 		assertMatched("/a?", "ab/");
246 		assertMatched("/a?", "ab/b");
247 		assertMatched("/a?", "/ab");
248 		assertMatched("/a?", "/ab/");
249 		assertMatched("/a?", "/ab/b");
250 
251 		assertMatched("/a?/b", "ab/b");
252 		assertMatched("/a?/b", "/ab/b");
253 	}
254 
255 	@Test
256 	public void testQuestionmarkDoNotMatch() {
257 		assertNotMatched("a?", "a/");
258 		assertNotMatched("a?", "abc");
259 		assertNotMatched("a?", "abc/");
260 
261 		assertNotMatched("a?", "b/abc");
262 		assertNotMatched("a?", "b/abc/");
263 
264 		assertNotMatched("a?", "/abc/");
265 		assertNotMatched("a?", "/abc/b");
266 
267 		assertNotMatched("a?", "c/abc/b");
268 		assertNotMatched("a?", "c/b/abc");
269 
270 		assertNotMatched("/a?", "abc");
271 		assertNotMatched("/a?", "abc/");
272 		assertNotMatched("/a?", "abc/b");
273 		assertNotMatched("/a?", "/abc");
274 		assertNotMatched("/a?", "/abc/");
275 		assertNotMatched("/a?", "/abc/b");
276 
277 		assertNotMatched("/a?/b", "abc/b");
278 		assertNotMatched("/a?/b", "/abc/b");
279 		assertNotMatched("/a?/b", "/a/b");
280 	}
281 
282 	@Test
283 	public void testSimplePatterns() {
284 		assertMatched("a", "a");
285 		assertMatched("a", "a/");
286 		assertMatched("a", "a/b");
287 
288 		assertMatched("a", "b/a");
289 		assertMatched("a", "b/a/");
290 		assertMatched("a", "b/a/b");
291 
292 		assertMatched("a", "/a/");
293 		assertMatched("a", "/a/b");
294 
295 		assertMatched("a", "c/a/b");
296 		assertMatched("a", "c/b/a");
297 
298 		assertMatched("/a", "a");
299 		assertMatched("/a", "a/");
300 		assertMatched("/a", "a/b");
301 		assertMatched("/a", "/a");
302 		assertMatched("/a", "/a/");
303 		assertMatched("/a", "/a/b");
304 
305 		assertMatched("a/", "a/");
306 		assertMatched("a/", "a/b");
307 		assertMatched("a/", "/a/");
308 		assertMatched("a/", "/a/b");
309 
310 		assertMatched("/a/", "a/");
311 		assertMatched("/a/", "a/b");
312 		assertMatched("/a/", "/a/");
313 		assertMatched("/a/", "/a/b");
314 
315 	}
316 
317 	@Test
318 	public void testSimplePatternsDoNotMatch() {
319 		assertNotMatched("ab", "a");
320 		assertNotMatched("abc", "a/");
321 		assertNotMatched("abc", "a/b");
322 
323 		assertNotMatched("a", "ab");
324 		assertNotMatched("a", "ba");
325 		assertNotMatched("a", "aa");
326 
327 		assertNotMatched("a", "b/ab");
328 		assertNotMatched("a", "b/ba");
329 
330 		assertNotMatched("a", "b/ba");
331 		assertNotMatched("a", "b/ab");
332 
333 		assertNotMatched("a", "b/ba/");
334 		assertNotMatched("a", "b/ba/b");
335 
336 		assertNotMatched("a", "/aa");
337 		assertNotMatched("a", "aa/");
338 		assertNotMatched("a", "/aa/");
339 
340 		assertNotMatched("/a", "b/a");
341 		assertNotMatched("/a", "/b/a/");
342 
343 		assertNotMatched("a/", "a");
344 		assertNotMatched("a/", "b/a");
345 
346 		assertNotMatched("/a/", "a");
347 		assertNotMatched("/a/", "/a");
348 		assertNotMatched("/a/", "b/a");
349 	}
350 
351 	@Test
352 	public void testSegments() {
353 		assertMatched("/a/b", "a/b");
354 		assertMatched("/a/b", "/a/b");
355 		assertMatched("/a/b", "/a/b/");
356 		assertMatched("/a/b", "/a/b/c");
357 
358 		assertMatched("a/b", "a/b");
359 		assertMatched("a/b", "/a/b");
360 		assertMatched("a/b", "/a/b/");
361 		assertMatched("a/b", "/a/b/c");
362 
363 		assertMatched("a/b/", "a/b/");
364 		assertMatched("a/b/", "/a/b/");
365 		assertMatched("a/b/", "/a/b/c");
366 	}
367 
368 	@Test
369 	public void testSegmentsDoNotMatch() {
370 		assertNotMatched("a/b", "/a/bb");
371 		assertNotMatched("a/b", "/aa/b");
372 		assertNotMatched("a/b", "a/bb");
373 		assertNotMatched("a/b", "aa/b");
374 		assertNotMatched("a/b", "c/aa/b");
375 		assertNotMatched("a/b", "c/a/bb");
376 		assertNotMatched("a/b/", "/a/b");
377 		assertNotMatched("/a/b/", "/a/b");
378 		assertNotMatched("/a/b", "c/a/b");
379 		assertNotMatched("/a/b/", "c/a/b");
380 		assertNotMatched("/a/b/", "c/a/b/");
381 
382 		// XXX why is it like this????
383 		assertNotMatched("a/b", "c/a/b");
384 		assertNotMatched("a/b", "c/a/b/");
385 		assertNotMatched("a/b", "c/a/b/c");
386 		assertNotMatched("a/b/", "c/a/b/");
387 		assertNotMatched("a/b/", "c/a/b/c");
388 	}
389 
390 	@Test
391 	public void testWildmatch() {
392 		assertMatched("**/a/b", "a/b");
393 		assertMatched("**/a/b", "c/a/b");
394 		assertMatched("**/a/b", "c/d/a/b");
395 		assertMatched("**/**/a/b", "c/d/a/b");
396 
397 		assertMatched("/**/a/b", "a/b");
398 		assertMatched("/**/a/b", "c/a/b");
399 		assertMatched("/**/a/b", "c/d/a/b");
400 		assertMatched("/**/**/a/b", "c/d/a/b");
401 
402 		assertMatched("a/b/**", "a/b/c");
403 		assertMatched("a/b/**", "a/b/c/d/");
404 		assertMatched("a/b/**/**", "a/b/c/d");
405 
406 		assertMatched("**/a/**/b", "c/d/a/b");
407 		assertMatched("**/a/**/b", "c/d/a/e/b");
408 		assertMatched("**/**/a/**/**/b", "c/d/a/e/b");
409 
410 		assertMatched("/**/a/**/b", "c/d/a/b");
411 		assertMatched("/**/a/**/b", "c/d/a/e/b");
412 		assertMatched("/**/**/a/**/**/b", "c/d/a/e/b");
413 
414 		assertMatched("a/**/b", "a/b");
415 		assertMatched("a/**/b", "a/c/b");
416 		assertMatched("a/**/b", "a/c/d/b");
417 		assertMatched("a/**/**/b", "a/c/d/b");
418 
419 		assertMatched("a/**/b/**/c", "a/c/b/d/c");
420 		assertMatched("a/**/**/b/**/**/c", "a/c/b/d/c");
421 
422 		assertMatched("**/", "a/");
423 		assertMatched("**/", "a/b");
424 		assertMatched("**/", "a/b/c");
425 		assertMatched("**/**/", "a/");
426 		assertMatched("**/**/", "a/b");
427 		assertMatched("**/**/", "a/b/");
428 		assertMatched("**/**/", "a/b/c");
429 		assertMatched("x/**/", "x/a/");
430 		assertMatched("x/**/", "x/a/b");
431 		assertMatched("x/**/", "x/a/b/");
432 		assertMatched("**/x/", "a/x/");
433 		assertMatched("**/x/", "a/b/x/");
434 	}
435 
436 	@Test
437 	public void testWildmatchDoNotMatch() {
438 		assertNotMatched("a/**", "a/");
439 		assertNotMatched("a/b/**", "a/b/");
440 		assertNotMatched("a/**", "a");
441 		assertNotMatched("a/b/**", "a/b");
442 		assertNotMatched("a/b/**/", "a/b");
443 		assertNotMatched("a/b/**/**", "a/b");
444 		assertNotMatched("**/a/b", "a/c/b");
445 		assertNotMatched("!/**/*.zip", "c/a/b.zip");
446 		assertNotMatched("!**/*.zip", "c/a/b.zip");
447 		assertNotMatched("a/**/b", "a/c/bb");
448 
449 		assertNotMatched("**/", "a");
450 		assertNotMatched("**/**/", "a");
451 		assertNotMatched("**/x/", "a/b/x");
452 	}
453 
454 	@SuppressWarnings("unused")
455 	@Test
456 	public void testSimpleRules() {
457 		try {
458 			new FastIgnoreRule(null);
459 			fail("Illegal input allowed!");
460 		} catch (IllegalArgumentException e) {
461 			// expected
462 		}
463 		assertFalse(new FastIgnoreRule("/").isMatch("/", false));
464 		assertFalse(new FastIgnoreRule("//").isMatch("//", false));
465 		assertFalse(new FastIgnoreRule("#").isMatch("#", false));
466 		assertFalse(new FastIgnoreRule("").isMatch("", false));
467 		assertFalse(new FastIgnoreRule(" ").isMatch(" ", false));
468 	}
469 
470 	@Test
471 	public void testSplit() {
472 		try {
473 			split("/", '/').toArray();
474 			fail("should not allow single slash");
475 		} catch (IllegalStateException e) {
476 			// expected
477 		}
478 
479 		assertArrayEquals(new String[] { "a", "b" }, split("a/b", '/')
480 				.toArray());
481 		assertArrayEquals(new String[] { "a", "b/" }, split("a/b/", '/')
482 				.toArray());
483 		assertArrayEquals(new String[] { "/a", "b" }, split("/a/b", '/')
484 				.toArray());
485 		assertArrayEquals(new String[] { "/a", "b/" }, split("/a/b/", '/')
486 				.toArray());
487 		assertArrayEquals(new String[] { "/a", "b", "c" }, split("/a/b/c", '/')
488 				.toArray());
489 		assertArrayEquals(new String[] { "/a", "b", "c/" },
490 				split("/a/b/c/", '/').toArray());
491 	}
492 
493 	@Test
494 	public void testPathMatch() {
495 		pathMatch = true;
496 		assertMatched("a", "a");
497 		assertMatched("a/", "a/");
498 		assertNotMatched("a/", "a/b");
499 
500 		assertMatched("**", "a");
501 		assertMatched("**", "a/");
502 		assertMatched("**", "a/b");
503 
504 		assertNotMatched("**/", "a");
505 		assertNotMatched("**/", "a/b");
506 		assertMatched("**/", "a/");
507 		assertMatched("**/", "a/b/");
508 
509 		assertNotMatched("x/**/", "x/a");
510 		assertNotMatched("x/**/", "x/a/b");
511 		assertMatched("x/**/", "x/a/");
512 		assertMatched("x/**/", "x/y/a/");
513 	}
514 
515 	private void assertMatched(String pattern, String path) {
516 		boolean match = match(pattern, path);
517 		String result = path + " is " + (match ? "ignored" : "not ignored")
518 				+ " via '" + pattern + "' rule";
519 		if (!match) {
520 			System.err.println(result);
521 		}
522 		assertTrue("Expected a match for: " + pattern + " with: " + path,
523 					match);
524 
525 		if (pattern.startsWith("!")) {
526 			pattern = pattern.substring(1);
527 		} else {
528 			pattern = "!" + pattern;
529 		}
530 		match = match(pattern, path);
531 		assertFalse("Expected no match for: " + pattern + " with: " + path,
532 				match);
533 	}
534 
535 	private void assertNotMatched(String pattern, String path) {
536 		boolean match = match(pattern, path);
537 		String result = path + " is " + (match ? "ignored" : "not ignored")
538 				+ " via '" + pattern + "' rule";
539 		if (match) {
540 			System.err.println(result);
541 		}
542 		assertFalse("Expected no match for: " + pattern + " with: " + path,
543 					match);
544 
545 		if (pattern.startsWith("!")) {
546 			pattern = pattern.substring(1);
547 		} else {
548 			pattern = "!" + pattern;
549 		}
550 		match = match(pattern, path);
551 		assertTrue("Expected a match for: " + pattern + " with: " + path,
552 					match);
553 	}
554 
555 	/**
556 	 * Check for a match. If target ends with "/", match will assume that the
557 	 * target is meant to be a directory.
558 	 *
559 	 * @param pattern
560 	 *            Pattern as it would appear in a .gitignore file
561 	 * @param target
562 	 *            Target file path relative to repository's GIT_DIR
563 	 * @return Result of {@link FastIgnoreRule#isMatch(String, boolean)}
564 	 */
565 	private boolean match(String pattern, String target) {
566 		boolean isDirectory = target.endsWith("/");
567 		FastIgnoreRule r = new FastIgnoreRule(pattern);
568 		// If speed of this test is ever an issue, we can use a presetRule field
569 		// to avoid recompiling a pattern each time.
570 		boolean match = r.isMatch(target, isDirectory, pathMatch);
571 		if (r.getNegation())
572 			match = !match;
573 		return match;
574 	}
575 }