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.Test;
52  
53  public class FastIgnoreRuleTest {
54  
55  	@Test
56  	public void testSimpleCharClass() {
57  		assertMatched("][a]", "]a");
58  		assertMatched("[a]", "a");
59  		assertMatched("][a]", "]a");
60  		assertMatched("[a]", "a/");
61  		assertMatched("[a]", "a/b");
62  
63  		assertMatched("[a]", "b/a");
64  		assertMatched("[a]", "b/a/");
65  		assertMatched("[a]", "b/a/b");
66  
67  		assertMatched("[a]", "/a/");
68  		assertMatched("[a]", "/a/b");
69  
70  		assertMatched("[a]", "c/a/b");
71  		assertMatched("[a]", "c/b/a");
72  
73  		assertMatched("/[a]", "a");
74  		assertMatched("/[a]", "a/");
75  		assertMatched("/[a]", "a/b");
76  		assertMatched("/[a]", "/a");
77  		assertMatched("/[a]", "/a/");
78  		assertMatched("/[a]", "/a/b");
79  
80  		assertMatched("[a]/", "a/");
81  		assertMatched("[a]/", "a/b");
82  		assertMatched("[a]/", "/a/");
83  		assertMatched("[a]/", "/a/b");
84  
85  		assertMatched("/[a]/", "a/");
86  		assertMatched("/[a]/", "a/b");
87  		assertMatched("/[a]/", "/a/");
88  		assertMatched("/[a]/", "/a/b");
89  	}
90  
91  	@Test
92  	public void testCharClass() {
93  		assertMatched("[v-z]", "x");
94  		assertMatched("[v-z]", "x/");
95  		assertMatched("[v-z]", "x/b");
96  
97  		assertMatched("[v-z]", "b/x");
98  		assertMatched("[v-z]", "b/x/");
99  		assertMatched("[v-z]", "b/x/b");
100 
101 		assertMatched("[v-z]", "/x/");
102 		assertMatched("[v-z]", "/x/b");
103 
104 		assertMatched("[v-z]", "c/x/b");
105 		assertMatched("[v-z]", "c/b/x");
106 
107 		assertMatched("/[v-z]", "x");
108 		assertMatched("/[v-z]", "x/");
109 		assertMatched("/[v-z]", "x/b");
110 		assertMatched("/[v-z]", "/x");
111 		assertMatched("/[v-z]", "/x/");
112 		assertMatched("/[v-z]", "/x/b");
113 
114 		assertMatched("[v-z]/", "x/");
115 		assertMatched("[v-z]/", "x/b");
116 		assertMatched("[v-z]/", "/x/");
117 		assertMatched("[v-z]/", "/x/b");
118 
119 		assertMatched("/[v-z]/", "x/");
120 		assertMatched("/[v-z]/", "x/b");
121 		assertMatched("/[v-z]/", "/x/");
122 		assertMatched("/[v-z]/", "/x/b");
123 	}
124 
125 	@Test
126 	public void testTrailingSpaces() {
127 		assertMatched("a ", "a");
128 		assertMatched("a/ ", "a/");
129 		assertMatched("a/ ", "a/b");
130 		assertMatched("a/\\ ", "a/ ");
131 		assertNotMatched("a/\\ ", "a/");
132 		assertNotMatched("a/\\ ", "a/b");
133 		assertNotMatched("/ ", "a");
134 	}
135 
136 	@Test
137 	public void testAsteriskDot() {
138 		assertMatched("*.a", ".a");
139 		assertMatched("*.a", "/.a");
140 		assertMatched("*.a", "a.a");
141 		assertMatched("*.a", "/b.a");
142 		assertMatched("*.a", "b.a");
143 		assertMatched("*.a", "/a/b.a");
144 		assertMatched("*.a", "/b/.a");
145 	}
146 
147 	@Test
148 	public void testAsteriskDotDoNotMatch() {
149 		assertNotMatched("*.a", ".ab");
150 		assertNotMatched("*.a", "/.ab");
151 		assertNotMatched("*.a", "/b.ba");
152 		assertNotMatched("*.a", "a.ab");
153 		assertNotMatched("*.a", "/b.ab");
154 		assertNotMatched("*.a", "b.ab");
155 		assertNotMatched("*.a", "/a/b.ab");
156 		assertNotMatched("*.a", "/b/.ab");
157 	}
158 
159 	@Test
160 	public void testDotAsteriskMatch() {
161 		assertMatched("a.*", "a.");
162 		assertMatched("a.*", "a./");
163 		assertMatched("a.*", "a.b");
164 
165 		assertMatched("a.*", "b/a.b");
166 		assertMatched("a.*", "b/a.b/");
167 		assertMatched("a.*", "b/a.b/b");
168 
169 		assertMatched("a.*", "/a.b/");
170 		assertMatched("a.*", "/a.b/b");
171 
172 		assertMatched("a.*", "c/a.b/b");
173 		assertMatched("a.*", "c/b/a.b");
174 
175 		assertMatched("/a.*", "a.b");
176 		assertMatched("/a.*", "a.b/");
177 		assertMatched("/a.*", "a.b/b");
178 		assertMatched("/a.*", "/a.b");
179 		assertMatched("/a.*", "/a.b/");
180 		assertMatched("/a.*", "/a.b/b");
181 
182 		assertMatched("/a.*/b", "a.b/b");
183 		assertMatched("/a.*/b", "/a.b/b");
184 		assertMatched("/a.*/b", "/a.bc/b");
185 		assertMatched("/a.*/b", "/a./b");
186 	}
187 
188 	@Test
189 	public void testAsterisk() {
190 		assertMatched("a*", "a");
191 		assertMatched("a*", "a/");
192 		assertMatched("a*", "ab");
193 
194 		assertMatched("a*", "b/ab");
195 		assertMatched("a*", "b/ab/");
196 		assertMatched("a*", "b/ab/b");
197 
198 		assertMatched("a*", "b/abc");
199 		assertMatched("a*", "b/abc/");
200 		assertMatched("a*", "b/abc/b");
201 
202 		assertMatched("a*", "/abc/");
203 		assertMatched("a*", "/abc/b");
204 
205 		assertMatched("a*", "c/abc/b");
206 		assertMatched("a*", "c/b/abc");
207 
208 		assertMatched("/a*", "abc");
209 		assertMatched("/a*", "abc/");
210 		assertMatched("/a*", "abc/b");
211 		assertMatched("/a*", "/abc");
212 		assertMatched("/a*", "/abc/");
213 		assertMatched("/a*", "/abc/b");
214 
215 		assertMatched("/a*/b", "abc/b");
216 		assertMatched("/a*/b", "/abc/b");
217 		assertMatched("/a*/b", "/abcd/b");
218 		assertMatched("/a*/b", "/a/b");
219 	}
220 
221 	@Test
222 	public void testQuestionmark() {
223 		assertMatched("a?", "ab");
224 		assertMatched("a?", "ab/");
225 
226 		assertMatched("a?", "b/ab");
227 		assertMatched("a?", "b/ab/");
228 		assertMatched("a?", "b/ab/b");
229 
230 		assertMatched("a?", "/ab/");
231 		assertMatched("a?", "/ab/b");
232 
233 		assertMatched("a?", "c/ab/b");
234 		assertMatched("a?", "c/b/ab");
235 
236 		assertMatched("/a?", "ab");
237 		assertMatched("/a?", "ab/");
238 		assertMatched("/a?", "ab/b");
239 		assertMatched("/a?", "/ab");
240 		assertMatched("/a?", "/ab/");
241 		assertMatched("/a?", "/ab/b");
242 
243 		assertMatched("/a?/b", "ab/b");
244 		assertMatched("/a?/b", "/ab/b");
245 	}
246 
247 	@Test
248 	public void testQuestionmarkDoNotMatch() {
249 		assertNotMatched("a?", "a/");
250 		assertNotMatched("a?", "abc");
251 		assertNotMatched("a?", "abc/");
252 
253 		assertNotMatched("a?", "b/abc");
254 		assertNotMatched("a?", "b/abc/");
255 
256 		assertNotMatched("a?", "/abc/");
257 		assertNotMatched("a?", "/abc/b");
258 
259 		assertNotMatched("a?", "c/abc/b");
260 		assertNotMatched("a?", "c/b/abc");
261 
262 		assertNotMatched("/a?", "abc");
263 		assertNotMatched("/a?", "abc/");
264 		assertNotMatched("/a?", "abc/b");
265 		assertNotMatched("/a?", "/abc");
266 		assertNotMatched("/a?", "/abc/");
267 		assertNotMatched("/a?", "/abc/b");
268 
269 		assertNotMatched("/a?/b", "abc/b");
270 		assertNotMatched("/a?/b", "/abc/b");
271 		assertNotMatched("/a?/b", "/a/b");
272 	}
273 
274 	@Test
275 	public void testSimplePatterns() {
276 		assertMatched("a", "a");
277 		assertMatched("a", "a/");
278 		assertMatched("a", "a/b");
279 
280 		assertMatched("a", "b/a");
281 		assertMatched("a", "b/a/");
282 		assertMatched("a", "b/a/b");
283 
284 		assertMatched("a", "/a/");
285 		assertMatched("a", "/a/b");
286 
287 		assertMatched("a", "c/a/b");
288 		assertMatched("a", "c/b/a");
289 
290 		assertMatched("/a", "a");
291 		assertMatched("/a", "a/");
292 		assertMatched("/a", "a/b");
293 		assertMatched("/a", "/a");
294 		assertMatched("/a", "/a/");
295 		assertMatched("/a", "/a/b");
296 
297 		assertMatched("a/", "a/");
298 		assertMatched("a/", "a/b");
299 		assertMatched("a/", "/a/");
300 		assertMatched("a/", "/a/b");
301 
302 		assertMatched("/a/", "a/");
303 		assertMatched("/a/", "a/b");
304 		assertMatched("/a/", "/a/");
305 		assertMatched("/a/", "/a/b");
306 
307 	}
308 
309 	@Test
310 	public void testSimplePatternsDoNotMatch() {
311 		assertNotMatched("ab", "a");
312 		assertNotMatched("abc", "a/");
313 		assertNotMatched("abc", "a/b");
314 
315 		assertNotMatched("a", "ab");
316 		assertNotMatched("a", "ba");
317 		assertNotMatched("a", "aa");
318 
319 		assertNotMatched("a", "b/ab");
320 		assertNotMatched("a", "b/ba");
321 
322 		assertNotMatched("a", "b/ba");
323 		assertNotMatched("a", "b/ab");
324 
325 		assertNotMatched("a", "b/ba/");
326 		assertNotMatched("a", "b/ba/b");
327 
328 		assertNotMatched("a", "/aa");
329 		assertNotMatched("a", "aa/");
330 		assertNotMatched("a", "/aa/");
331 
332 		assertNotMatched("/a", "b/a");
333 		assertNotMatched("/a", "/b/a/");
334 
335 		assertNotMatched("a/", "a");
336 		assertNotMatched("a/", "b/a");
337 
338 		assertNotMatched("/a/", "a");
339 		assertNotMatched("/a/", "/a");
340 		assertNotMatched("/a/", "b/a");
341 	}
342 
343 	@Test
344 	public void testSegments() {
345 		assertMatched("/a/b", "a/b");
346 		assertMatched("/a/b", "/a/b");
347 		assertMatched("/a/b", "/a/b/");
348 		assertMatched("/a/b", "/a/b/c");
349 
350 		assertMatched("a/b", "a/b");
351 		assertMatched("a/b", "/a/b");
352 		assertMatched("a/b", "/a/b/");
353 		assertMatched("a/b", "/a/b/c");
354 
355 		assertMatched("a/b/", "a/b/");
356 		assertMatched("a/b/", "/a/b/");
357 		assertMatched("a/b/", "/a/b/c");
358 	}
359 
360 	@Test
361 	public void testSegmentsDoNotMatch() {
362 		assertNotMatched("a/b", "/a/bb");
363 		assertNotMatched("a/b", "/aa/b");
364 		assertNotMatched("a/b", "a/bb");
365 		assertNotMatched("a/b", "aa/b");
366 		assertNotMatched("a/b", "c/aa/b");
367 		assertNotMatched("a/b", "c/a/bb");
368 		assertNotMatched("a/b/", "/a/b");
369 		assertNotMatched("/a/b/", "/a/b");
370 		assertNotMatched("/a/b", "c/a/b");
371 		assertNotMatched("/a/b/", "c/a/b");
372 		assertNotMatched("/a/b/", "c/a/b/");
373 
374 		// XXX why is it like this????
375 		assertNotMatched("a/b", "c/a/b");
376 		assertNotMatched("a/b", "c/a/b/");
377 		assertNotMatched("a/b", "c/a/b/c");
378 		assertNotMatched("a/b/", "c/a/b/");
379 		assertNotMatched("a/b/", "c/a/b/c");
380 	}
381 
382 	@Test
383 	public void testWildmatch() {
384 		assertMatched("**/a/b", "a/b");
385 		assertMatched("**/a/b", "c/a/b");
386 		assertMatched("**/a/b", "c/d/a/b");
387 		assertMatched("**/**/a/b", "c/d/a/b");
388 
389 		assertMatched("/**/a/b", "a/b");
390 		assertMatched("/**/a/b", "c/a/b");
391 		assertMatched("/**/a/b", "c/d/a/b");
392 		assertMatched("/**/**/a/b", "c/d/a/b");
393 
394 		assertMatched("a/b/**", "a/b/c");
395 		assertMatched("a/b/**", "a/b/c/d/");
396 		assertMatched("a/b/**/**", "a/b/c/d");
397 
398 		assertMatched("**/a/**/b", "c/d/a/b");
399 		assertMatched("**/a/**/b", "c/d/a/e/b");
400 		assertMatched("**/**/a/**/**/b", "c/d/a/e/b");
401 
402 		assertMatched("/**/a/**/b", "c/d/a/b");
403 		assertMatched("/**/a/**/b", "c/d/a/e/b");
404 		assertMatched("/**/**/a/**/**/b", "c/d/a/e/b");
405 
406 		assertMatched("a/**/b", "a/b");
407 		assertMatched("a/**/b", "a/c/b");
408 		assertMatched("a/**/b", "a/c/d/b");
409 		assertMatched("a/**/**/b", "a/c/d/b");
410 
411 		assertMatched("a/**/b/**/c", "a/c/b/d/c");
412 		assertMatched("a/**/**/b/**/**/c", "a/c/b/d/c");
413 	}
414 
415 	@Test
416 	public void testWildmatchDoNotMatch() {
417 		assertNotMatched("a/**", "a/");
418 		assertNotMatched("a/b/**", "a/b/");
419 		assertNotMatched("a/**", "a");
420 		assertNotMatched("a/b/**", "a/b");
421 		assertNotMatched("a/b/**/", "a/b");
422 		assertNotMatched("a/b/**/**", "a/b");
423 		assertNotMatched("**/a/b", "a/c/b");
424 		assertNotMatched("!/**/*.zip", "c/a/b.zip");
425 		assertNotMatched("!**/*.zip", "c/a/b.zip");
426 		assertNotMatched("a/**/b", "a/c/bb");
427 	}
428 
429 	@SuppressWarnings("unused")
430 	@Test
431 	public void testSimpleRules() {
432 		try {
433 			new FastIgnoreRule(null);
434 			fail("Illegal input allowed!");
435 		} catch (IllegalArgumentException e) {
436 			// expected
437 		}
438 		assertFalse(new FastIgnoreRule("/").isMatch("/", false));
439 		assertFalse(new FastIgnoreRule("//").isMatch("//", false));
440 		assertFalse(new FastIgnoreRule("#").isMatch("#", false));
441 		assertFalse(new FastIgnoreRule("").isMatch("", false));
442 		assertFalse(new FastIgnoreRule(" ").isMatch(" ", false));
443 	}
444 
445 	@Test
446 	public void testSplit() {
447 		try {
448 			split("/", '/').toArray();
449 			fail("should not allow single slash");
450 		} catch (IllegalStateException e) {
451 			// expected
452 		}
453 
454 		assertArrayEquals(new String[] { "a", "b" }, split("a/b", '/')
455 				.toArray());
456 		assertArrayEquals(new String[] { "a", "b/" }, split("a/b/", '/')
457 				.toArray());
458 		assertArrayEquals(new String[] { "/a", "b" }, split("/a/b", '/')
459 				.toArray());
460 		assertArrayEquals(new String[] { "/a", "b/" }, split("/a/b/", '/')
461 				.toArray());
462 		assertArrayEquals(new String[] { "/a", "b", "c" }, split("/a/b/c", '/')
463 				.toArray());
464 		assertArrayEquals(new String[] { "/a", "b", "c/" },
465 				split("/a/b/c/", '/').toArray());
466 	}
467 
468 	public void assertMatched(String pattern, String path) {
469 		boolean match = match(pattern, path);
470 		String result = path + " is " + (match ? "ignored" : "not ignored")
471 				+ " via '" + pattern + "' rule";
472 		if (!match) {
473 			System.err.println(result);
474 		}
475 		assertTrue("Expected a match for: " + pattern + " with: " + path,
476 					match);
477 
478 		if (pattern.startsWith("!")) {
479 			pattern = pattern.substring(1);
480 		} else {
481 			pattern = "!" + pattern;
482 		}
483 		match = match(pattern, path);
484 		assertFalse("Expected no match for: " + pattern + " with: " + path,
485 				match);
486 	}
487 
488 	public void assertNotMatched(String pattern, String path) {
489 		boolean match = match(pattern, path);
490 		String result = path + " is " + (match ? "ignored" : "not ignored")
491 				+ " via '" + pattern + "' rule";
492 		if (match) {
493 			System.err.println(result);
494 		}
495 		assertFalse("Expected no match for: " + pattern + " with: " + path,
496 					match);
497 
498 		if (pattern.startsWith("!")) {
499 			pattern = pattern.substring(1);
500 		} else {
501 			pattern = "!" + pattern;
502 		}
503 		match = match(pattern, path);
504 		assertTrue("Expected a match for: " + pattern + " with: " + path,
505 					match);
506 	}
507 
508 	/**
509 	 * Check for a match. If target ends with "/", match will assume that the
510 	 * target is meant to be a directory.
511 	 *
512 	 * @param pattern
513 	 *            Pattern as it would appear in a .gitignore file
514 	 * @param target
515 	 *            Target file path relative to repository's GIT_DIR
516 	 * @return Result of {@link FastIgnoreRule#isMatch(String, boolean)}
517 	 */
518 	private boolean match(String pattern, String target) {
519 		boolean isDirectory = target.endsWith("/");
520 		FastIgnoreRule r = new FastIgnoreRule(pattern);
521 		// If speed of this test is ever an issue, we can use a presetRule field
522 		// to avoid recompiling a pattern each time.
523 		boolean match = r.isMatch(target, isDirectory);
524 		if (r.getNegation())
525 			match = !match;
526 		return match;
527 	}
528 }