View Javadoc
1   /*
2    * Copyright (C) 2010, Red Hat Inc.
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 java.nio.charset.StandardCharsets.UTF_8;
46  import static org.eclipse.jgit.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertEquals;
48  import static org.junit.Assert.assertFalse;
49  import static org.junit.Assert.assertNotNull;
50  import static org.junit.Assert.assertTrue;
51  
52  import java.io.ByteArrayInputStream;
53  import java.io.File;
54  import java.io.IOException;
55  import java.io.InputStream;
56  import java.util.ArrayList;
57  import java.util.Arrays;
58  
59  import org.eclipse.jgit.junit.RepositoryTestCase;
60  import org.eclipse.jgit.lib.FileMode;
61  import org.eclipse.jgit.treewalk.FileTreeIterator;
62  import org.eclipse.jgit.treewalk.TreeWalk;
63  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
64  import org.eclipse.jgit.util.FileUtils;
65  import org.eclipse.jgit.util.SystemReader;
66  import org.junit.Test;
67  
68  /**
69   * Tests ignore node behavior on the local filesystem.
70   */
71  public class IgnoreNodeTest extends RepositoryTestCase {
72  	private static final FileMode D = FileMode.TREE;
73  
74  	private static final FileMode F = FileMode.REGULAR_FILE;
75  
76  	private static final boolean ignored = true;
77  
78  	private static final boolean tracked = false;
79  
80  	private TreeWalk walk;
81  
82  	@Test
83  	public void testSimpleRootGitIgnoreGlobalIgnore() throws IOException {
84  		writeIgnoreFile(".gitignore", "x");
85  
86  		writeTrashFile("a/x/file", "");
87  		writeTrashFile("b/x", "");
88  		writeTrashFile("x/file", "");
89  
90  		beginWalk();
91  		assertEntry(F, tracked, ".gitignore");
92  		assertEntry(D, tracked, "a");
93  		assertEntry(D, ignored, "a/x");
94  		assertEntry(F, ignored, "a/x/file");
95  		assertEntry(D, tracked, "b");
96  		assertEntry(F, ignored, "b/x");
97  		assertEntry(D, ignored, "x");
98  		assertEntry(F, ignored, "x/file");
99  		endWalk();
100 	}
101 
102 	@Test
103 	public void testSimpleRootGitIgnoreGlobalDirIgnore() throws IOException {
104 		writeIgnoreFile(".gitignore", "x/");
105 
106 		writeTrashFile("a/x/file", "");
107 		writeTrashFile("x/file", "");
108 
109 		beginWalk();
110 		assertEntry(F, tracked, ".gitignore");
111 		assertEntry(D, tracked, "a");
112 		assertEntry(D, ignored, "a/x");
113 		assertEntry(F, ignored, "a/x/file");
114 		assertEntry(D, ignored, "x");
115 		assertEntry(F, ignored, "x/file");
116 		endWalk();
117 	}
118 
119 	@Test
120 	public void testSimpleRootGitIgnoreWildMatcher() throws IOException {
121 		writeIgnoreFile(".gitignore", "**");
122 
123 		writeTrashFile("a/x", "");
124 		writeTrashFile("y", "");
125 
126 		beginWalk();
127 		assertEntry(F, ignored, ".gitignore");
128 		assertEntry(D, ignored, "a");
129 		assertEntry(F, ignored, "a/x");
130 		assertEntry(F, ignored, "y");
131 		endWalk();
132 	}
133 
134 	@Test
135 	public void testSimpleRootGitIgnoreWildMatcherDirOnly() throws IOException {
136 		writeIgnoreFile(".gitignore", "**/");
137 
138 		writeTrashFile("a/x", "");
139 		writeTrashFile("y", "");
140 
141 		beginWalk();
142 		assertEntry(F, tracked, ".gitignore");
143 		assertEntry(D, ignored, "a");
144 		assertEntry(F, ignored, "a/x");
145 		assertEntry(F, tracked, "y");
146 		endWalk();
147 	}
148 
149 	@Test
150 	public void testSimpleRootGitIgnoreGlobalNegation1() throws IOException {
151 		writeIgnoreFile(".gitignore", "*", "!x*");
152 		writeTrashFile("x1", "");
153 		writeTrashFile("a/x2", "");
154 		writeTrashFile("x3/y", "");
155 
156 		beginWalk();
157 		assertEntry(F, ignored, ".gitignore");
158 		assertEntry(D, ignored, "a");
159 		assertEntry(F, ignored, "a/x2");
160 		assertEntry(F, tracked, "x1");
161 		assertEntry(D, tracked, "x3");
162 		assertEntry(F, ignored, "x3/y");
163 		endWalk();
164 	}
165 
166 	@Test
167 	public void testSimpleRootGitIgnoreGlobalNegation2() throws IOException {
168 		writeIgnoreFile(".gitignore", "*", "!x*", "!/a");
169 		writeTrashFile("x1", "");
170 		writeTrashFile("a/x2", "");
171 		writeTrashFile("x3/y", "");
172 
173 		beginWalk();
174 		assertEntry(F, ignored, ".gitignore");
175 		assertEntry(D, tracked, "a");
176 		assertEntry(F, tracked, "a/x2");
177 		assertEntry(F, tracked, "x1");
178 		assertEntry(D, tracked, "x3");
179 		assertEntry(F, ignored, "x3/y");
180 		endWalk();
181 	}
182 
183 	@Test
184 	public void testSimpleRootGitIgnoreGlobalNegation3() throws IOException {
185 		writeIgnoreFile(".gitignore", "*", "!x*", "!x*/**");
186 		writeTrashFile("x1", "");
187 		writeTrashFile("a/x2", "");
188 		writeTrashFile("x3/y", "");
189 
190 		beginWalk();
191 		assertEntry(F, ignored, ".gitignore");
192 		assertEntry(D, ignored, "a");
193 		assertEntry(F, ignored, "a/x2");
194 		assertEntry(F, tracked, "x1");
195 		assertEntry(D, tracked, "x3");
196 		assertEntry(F, tracked, "x3/y");
197 		endWalk();
198 	}
199 
200 	@Test
201 	public void testSimpleRootGitIgnoreGlobalNegation4() throws IOException {
202 		writeIgnoreFile(".gitignore", "*", "!**/");
203 		writeTrashFile("x1", "");
204 		writeTrashFile("a/x2", "");
205 		writeTrashFile("x3/y", "");
206 
207 		beginWalk();
208 		assertEntry(F, ignored, ".gitignore");
209 		assertEntry(D, tracked, "a");
210 		assertEntry(F, ignored, "a/x2");
211 		assertEntry(F, ignored, "x1");
212 		assertEntry(D, tracked, "x3");
213 		assertEntry(F, ignored, "x3/y");
214 		endWalk();
215 	}
216 
217 	@Test
218 	public void testRules() throws IOException {
219 		writeIgnoreFile(".git/info/exclude", "*~", "/out");
220 
221 		writeIgnoreFile(".gitignore", "*.o", "/config");
222 		writeTrashFile("config/secret", "");
223 		writeTrashFile("mylib.c", "");
224 		writeTrashFile("mylib.c~", "");
225 		writeTrashFile("mylib.o", "");
226 
227 		writeTrashFile("out/object/foo.exe", "");
228 		writeIgnoreFile("src/config/.gitignore", "lex.out");
229 		writeTrashFile("src/config/lex.out", "");
230 		writeTrashFile("src/config/config.c", "");
231 		writeTrashFile("src/config/config.c~", "");
232 		writeTrashFile("src/config/old/lex.out", "");
233 
234 		beginWalk();
235 		assertEntry(F, tracked, ".gitignore");
236 		assertEntry(D, ignored, "config");
237 		assertEntry(F, ignored, "config/secret");
238 		assertEntry(F, tracked, "mylib.c");
239 		assertEntry(F, ignored, "mylib.c~");
240 		assertEntry(F, ignored, "mylib.o");
241 
242 		assertEntry(D, ignored, "out");
243 		assertEntry(D, ignored, "out/object");
244 		assertEntry(F, ignored, "out/object/foo.exe");
245 
246 		assertEntry(D, tracked, "src");
247 		assertEntry(D, tracked, "src/config");
248 		assertEntry(F, tracked, "src/config/.gitignore");
249 		assertEntry(F, tracked, "src/config/config.c");
250 		assertEntry(F, ignored, "src/config/config.c~");
251 		assertEntry(F, ignored, "src/config/lex.out");
252 		assertEntry(D, tracked, "src/config/old");
253 		assertEntry(F, ignored, "src/config/old/lex.out");
254 		endWalk();
255 	}
256 
257 	@Test
258 	public void testNegation() throws IOException {
259 		// ignore all *.o files and ignore all "d" directories
260 		writeIgnoreFile(".gitignore", "*.o", "d");
261 
262 		// negate "ignore" for a/b/keep.o file only
263 		writeIgnoreFile("src/a/b/.gitignore", "!keep.o");
264 		writeTrashFile("src/a/b/keep.o", "");
265 		writeTrashFile("src/a/b/nothere.o", "");
266 
267 		// negate "ignore" for "d"
268 		writeIgnoreFile("src/c/.gitignore", "!d");
269 		// negate "ignore" for c/d/keep.o file only
270 		writeIgnoreFile("src/c/d/.gitignore", "!keep.o");
271 		writeTrashFile("src/c/d/keep.o", "");
272 		writeTrashFile("src/c/d/nothere.o", "");
273 
274 		beginWalk();
275 		assertEntry(F, tracked, ".gitignore");
276 		assertEntry(D, tracked, "src");
277 		assertEntry(D, tracked, "src/a");
278 		assertEntry(D, tracked, "src/a/b");
279 		assertEntry(F, tracked, "src/a/b/.gitignore");
280 		assertEntry(F, tracked, "src/a/b/keep.o");
281 		assertEntry(F, ignored, "src/a/b/nothere.o");
282 
283 		assertEntry(D, tracked, "src/c");
284 		assertEntry(F, tracked, "src/c/.gitignore");
285 		assertEntry(D, tracked, "src/c/d");
286 		assertEntry(F, tracked, "src/c/d/.gitignore");
287 		assertEntry(F, tracked, "src/c/d/keep.o");
288 		// must be ignored: "!d" should not negate *both* "d" and *.o rules!
289 		assertEntry(F, ignored, "src/c/d/nothere.o");
290 		endWalk();
291 	}
292 
293 	/*
294 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=407475
295 	 */
296 	@Test
297 	public void testNegateAllExceptJavaInSrc() throws IOException {
298 		// ignore all files except from src directory
299 		writeIgnoreFile(".gitignore", "/*", "!/src/");
300 		writeTrashFile("nothere.o", "");
301 
302 		// ignore all files except java
303 		writeIgnoreFile("src/.gitignore", "*", "!*.java");
304 
305 		writeTrashFile("src/keep.java", "");
306 		writeTrashFile("src/nothere.o", "");
307 		writeTrashFile("src/a/nothere.o", "");
308 
309 		beginWalk();
310 		assertEntry(F, ignored, ".gitignore");
311 		assertEntry(F, ignored, "nothere.o");
312 		assertEntry(D, tracked, "src");
313 		assertEntry(F, ignored, "src/.gitignore");
314 		assertEntry(D, ignored, "src/a");
315 		assertEntry(F, ignored, "src/a/nothere.o");
316 		assertEntry(F, tracked, "src/keep.java");
317 		assertEntry(F, ignored, "src/nothere.o");
318 		endWalk();
319 	}
320 
321 	/*
322 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=407475
323 	 */
324 	@Test
325 	public void testNegationAllExceptJavaInSrcAndExceptChildDirInSrc()
326 			throws IOException {
327 		// ignore all files except from src directory
328 		writeIgnoreFile(".gitignore", "/*", "!/src/");
329 		writeTrashFile("nothere.o", "");
330 
331 		// ignore all files except java in src folder and all children folders.
332 		// Last ignore rule breaks old jgit via bug 407475
333 		writeIgnoreFile("src/.gitignore", "*", "!*.java", "!*/");
334 
335 		writeTrashFile("src/keep.java", "");
336 		writeTrashFile("src/nothere.o", "");
337 		writeTrashFile("src/a/keep.java", "");
338 		writeTrashFile("src/a/keep.o", "");
339 
340 		beginWalk();
341 		assertEntry(F, ignored, ".gitignore");
342 		assertEntry(F, ignored, "nothere.o");
343 		assertEntry(D, tracked, "src");
344 		assertEntry(F, ignored, "src/.gitignore");
345 		assertEntry(D, tracked, "src/a");
346 		assertEntry(F, tracked, "src/a/keep.java");
347 		assertEntry(F, ignored, "src/a/keep.o");
348 		assertEntry(F, tracked, "src/keep.java");
349 		assertEntry(F, ignored, "src/nothere.o");
350 		endWalk();
351 	}
352 
353 	/*
354 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=448094
355 	 */
356 	@Test
357 	public void testRepeatedNegation() throws IOException {
358 		writeIgnoreFile(".gitignore", "e", "!e", "e", "!e", "e");
359 
360 		writeTrashFile("e/nothere.o", "");
361 
362 		beginWalk();
363 		assertEntry(F, tracked, ".gitignore");
364 		assertEntry(D, ignored, "e");
365 		assertEntry(F, ignored, "e/nothere.o");
366 		endWalk();
367 	}
368 
369 	/*
370 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=448094
371 	 */
372 	@Test
373 	public void testRepeatedNegationInDifferentFiles1() throws IOException {
374 		writeIgnoreFile(".gitignore", "*.o", "e");
375 
376 		writeIgnoreFile("e/.gitignore", "!e");
377 		writeTrashFile("e/nothere.o", "");
378 
379 		beginWalk();
380 		assertEntry(F, tracked, ".gitignore");
381 		assertEntry(D, ignored, "e");
382 		assertEntry(F, ignored, "e/.gitignore");
383 		assertEntry(F, ignored, "e/nothere.o");
384 		endWalk();
385 	}
386 
387 	/*
388 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=448094
389 	 */
390 	@Test
391 	public void testRepeatedNegationInDifferentFiles2() throws IOException {
392 		writeIgnoreFile(".gitignore", "*.o", "e");
393 
394 		writeIgnoreFile("a/.gitignore", "!e");
395 		writeTrashFile("a/e/nothere.o", "");
396 
397 		beginWalk();
398 		assertEntry(F, tracked, ".gitignore");
399 		assertEntry(D, tracked, "a");
400 		assertEntry(F, tracked, "a/.gitignore");
401 		assertEntry(D, tracked, "a/e");
402 		assertEntry(F, ignored, "a/e/nothere.o");
403 		endWalk();
404 	}
405 
406 	/*
407 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=448094
408 	 */
409 	@Test
410 	public void testRepeatedNegationInDifferentFiles3() throws IOException {
411 		writeIgnoreFile(".gitignore", "*.o");
412 
413 		writeIgnoreFile("a/.gitignore", "e");
414 		writeIgnoreFile("a/b/.gitignore", "!e");
415 		writeTrashFile("a/b/e/nothere.o", "");
416 
417 		beginWalk();
418 		assertEntry(F, tracked, ".gitignore");
419 		assertEntry(D, tracked, "a");
420 		assertEntry(F, tracked, "a/.gitignore");
421 		assertEntry(D, tracked, "a/b");
422 		assertEntry(F, tracked, "a/b/.gitignore");
423 		assertEntry(D, tracked, "a/b/e");
424 		assertEntry(F, ignored, "a/b/e/nothere.o");
425 		endWalk();
426 	}
427 
428 	@Test
429 	public void testRepeatedNegationInDifferentFiles4() throws IOException {
430 		writeIgnoreFile(".gitignore", "*.o");
431 
432 		writeIgnoreFile("a/.gitignore", "e");
433 		// Rules are never empty: WorkingTreeIterator optimizes empty rules away
434 		// paranoia check in case this optimization will be removed
435 		writeIgnoreFile("a/b/.gitignore", "#");
436 		writeIgnoreFile("a/b/c/.gitignore", "!e");
437 		writeTrashFile("a/b/c/e/nothere.o", "");
438 
439 		beginWalk();
440 		assertEntry(F, tracked, ".gitignore");
441 		assertEntry(D, tracked, "a");
442 		assertEntry(F, tracked, "a/.gitignore");
443 		assertEntry(D, tracked, "a/b");
444 		assertEntry(F, tracked, "a/b/.gitignore");
445 		assertEntry(D, tracked, "a/b/c");
446 		assertEntry(F, tracked, "a/b/c/.gitignore");
447 		assertEntry(D, tracked, "a/b/c/e");
448 		assertEntry(F, ignored, "a/b/c/e/nothere.o");
449 		endWalk();
450 	}
451 
452 	@Test
453 	public void testRepeatedNegationInDifferentFiles5() throws IOException {
454 		writeIgnoreFile(".gitignore", "e");
455 		writeIgnoreFile("a/.gitignore", "e");
456 		writeIgnoreFile("a/b/.gitignore", "!e");
457 		writeTrashFile("a/b/e/nothere.o", "");
458 
459 		beginWalk();
460 		assertEntry(F, tracked, ".gitignore");
461 		assertEntry(D, tracked, "a");
462 		assertEntry(F, tracked, "a/.gitignore");
463 		assertEntry(D, tracked, "a/b");
464 		assertEntry(F, tracked, "a/b/.gitignore");
465 		assertEntry(D, tracked, "a/b/e");
466 		assertEntry(F, tracked, "a/b/e/nothere.o");
467 		endWalk();
468 	}
469 
470 	@Test
471 	public void testIneffectiveNegationDifferentLevels1() throws IOException {
472 		writeIgnoreFile(".gitignore", "a/b/e/", "!a/b/e/*");
473 		writeTrashFile("a/b/e/nothere.o", "");
474 
475 		beginWalk();
476 		assertEntry(F, tracked, ".gitignore");
477 		assertEntry(D, tracked, "a");
478 		assertEntry(D, tracked, "a/b");
479 		assertEntry(D, ignored, "a/b/e");
480 		assertEntry(F, ignored, "a/b/e/nothere.o");
481 		endWalk();
482 	}
483 
484 	@Test
485 	public void testIneffectiveNegationDifferentLevels2() throws IOException {
486 		writeIgnoreFile(".gitignore", "a/b/e/");
487 		writeIgnoreFile("a/.gitignore", "!b/e/*");
488 		writeTrashFile("a/b/e/nothere.o", "");
489 
490 		beginWalk();
491 		assertEntry(F, tracked, ".gitignore");
492 		assertEntry(D, tracked, "a");
493 		assertEntry(F, tracked, "a/.gitignore");
494 		assertEntry(D, tracked, "a/b");
495 		assertEntry(D, ignored, "a/b/e");
496 		assertEntry(F, ignored, "a/b/e/nothere.o");
497 		endWalk();
498 	}
499 
500 	@Test
501 	public void testIneffectiveNegationDifferentLevels3() throws IOException {
502 		writeIgnoreFile(".gitignore", "a/b/e/");
503 		writeIgnoreFile("a/b/.gitignore", "!e/*");
504 		writeTrashFile("a/b/e/nothere.o", "");
505 
506 		beginWalk();
507 		assertEntry(F, tracked, ".gitignore");
508 		assertEntry(D, tracked, "a");
509 		assertEntry(D, tracked, "a/b");
510 		assertEntry(F, tracked, "a/b/.gitignore");
511 		assertEntry(D, ignored, "a/b/e");
512 		assertEntry(F, ignored, "a/b/e/nothere.o");
513 		endWalk();
514 	}
515 
516 	@Test
517 	public void testIneffectiveNegationDifferentLevels4() throws IOException {
518 		writeIgnoreFile(".gitignore", "a/b/e/");
519 		writeIgnoreFile("a/b/e/.gitignore", "!*");
520 		writeTrashFile("a/b/e/nothere.o", "");
521 
522 		beginWalk();
523 		assertEntry(F, tracked, ".gitignore");
524 		assertEntry(D, tracked, "a");
525 		assertEntry(D, tracked, "a/b");
526 		assertEntry(D, ignored, "a/b/e");
527 		assertEntry(F, ignored, "a/b/e/.gitignore");
528 		assertEntry(F, ignored, "a/b/e/nothere.o");
529 		endWalk();
530 	}
531 
532 	@Test
533 	public void testIneffectiveNegationDifferentLevels5() throws IOException {
534 		writeIgnoreFile("a/.gitignore", "b/e/");
535 		writeIgnoreFile("a/b/.gitignore", "!e/*");
536 		writeTrashFile("a/b/e/nothere.o", "");
537 
538 		beginWalk();
539 		assertEntry(D, tracked, "a");
540 		assertEntry(F, tracked, "a/.gitignore");
541 		assertEntry(D, tracked, "a/b");
542 		assertEntry(F, tracked, "a/b/.gitignore");
543 		assertEntry(D, ignored, "a/b/e");
544 		assertEntry(F, ignored, "a/b/e/nothere.o");
545 		endWalk();
546 	}
547 
548 	@Test
549 	public void testEmptyIgnoreRules() throws IOException {
550 		IgnoreNode node = new IgnoreNode();
551 		node.parse(writeToString("", "#", "!", "[[=a=]]"));
552 		assertEquals(new ArrayList<>(), node.getRules());
553 		node.parse(writeToString(" ", " / "));
554 		assertEquals(2, node.getRules().size());
555 	}
556 
557 	@Test
558 	public void testSlashOnlyMatchesDirectory() throws IOException {
559 		writeIgnoreFile(".gitignore", "out/");
560 		writeTrashFile("out", "");
561 
562 		beginWalk();
563 		assertEntry(F, tracked, ".gitignore");
564 		assertEntry(F, tracked, "out");
565 
566 		FileUtils.delete(new File(trash, "out"));
567 		writeTrashFile("out/foo", "");
568 
569 		beginWalk();
570 		assertEntry(F, tracked, ".gitignore");
571 		assertEntry(D, ignored, "out");
572 		assertEntry(F, ignored, "out/foo");
573 		endWalk();
574 	}
575 
576 	@Test
577 	public void testSlashMatchesDirectory() throws IOException {
578 		writeIgnoreFile(".gitignore", "out2/");
579 
580 		writeTrashFile("out1/out1", "");
581 		writeTrashFile("out1/out2", "");
582 		writeTrashFile("out2/out1", "");
583 		writeTrashFile("out2/out2", "");
584 
585 		beginWalk();
586 		assertEntry(F, tracked, ".gitignore");
587 		assertEntry(D, tracked, "out1");
588 		assertEntry(F, tracked, "out1/out1");
589 		assertEntry(F, tracked, "out1/out2");
590 		assertEntry(D, ignored, "out2");
591 		assertEntry(F, ignored, "out2/out1");
592 		assertEntry(F, ignored, "out2/out2");
593 		endWalk();
594 	}
595 
596 	@Test
597 	public void testWildcardWithSlashMatchesDirectory() throws IOException {
598 		writeIgnoreFile(".gitignore", "out2*/");
599 
600 		writeTrashFile("out1/out1.txt", "");
601 		writeTrashFile("out1/out2", "");
602 		writeTrashFile("out1/out2.txt", "");
603 		writeTrashFile("out1/out2x/a", "");
604 		writeTrashFile("out2/out1.txt", "");
605 		writeTrashFile("out2/out2.txt", "");
606 		writeTrashFile("out2x/out1.txt", "");
607 		writeTrashFile("out2x/out2.txt", "");
608 
609 		beginWalk();
610 		assertEntry(F, tracked, ".gitignore");
611 		assertEntry(D, tracked, "out1");
612 		assertEntry(F, tracked, "out1/out1.txt");
613 		assertEntry(F, tracked, "out1/out2");
614 		assertEntry(F, tracked, "out1/out2.txt");
615 		assertEntry(D, ignored, "out1/out2x");
616 		assertEntry(F, ignored, "out1/out2x/a");
617 		assertEntry(D, ignored, "out2");
618 		assertEntry(F, ignored, "out2/out1.txt");
619 		assertEntry(F, ignored, "out2/out2.txt");
620 		assertEntry(D, ignored, "out2x");
621 		assertEntry(F, ignored, "out2x/out1.txt");
622 		assertEntry(F, ignored, "out2x/out2.txt");
623 		endWalk();
624 	}
625 
626 	@Test
627 	public void testWithSlashDoesNotMatchInSubDirectory() throws IOException {
628 		writeIgnoreFile(".gitignore", "a/b");
629 		writeTrashFile("a/a", "");
630 		writeTrashFile("a/b", "");
631 		writeTrashFile("src/a/a", "");
632 		writeTrashFile("src/a/b", "");
633 
634 		beginWalk();
635 		assertEntry(F, tracked, ".gitignore");
636 		assertEntry(D, tracked, "a");
637 		assertEntry(F, tracked, "a/a");
638 		assertEntry(F, ignored, "a/b");
639 		assertEntry(D, tracked, "src");
640 		assertEntry(D, tracked, "src/a");
641 		assertEntry(F, tracked, "src/a/a");
642 		assertEntry(F, tracked, "src/a/b");
643 		endWalk();
644 	}
645 
646 	@Test
647 	public void testNoPatterns() throws IOException {
648 		writeIgnoreFile(".gitignore", "", " ", "# comment", "/");
649 		writeTrashFile("a/a", "");
650 
651 		beginWalk();
652 		assertEntry(F, tracked, ".gitignore");
653 		assertEntry(D, tracked, "a");
654 		assertEntry(F, tracked, "a/a");
655 		endWalk();
656 	}
657 
658 	@Test
659 	public void testLeadingSpaces() throws IOException {
660 		writeTrashFile("  a/  a", "");
661 		writeTrashFile("  a/ a", "");
662 		writeTrashFile("  a/a", "");
663 		writeTrashFile(" a/  a", "");
664 		writeTrashFile(" a/ a", "");
665 		writeTrashFile(" a/a", "");
666 		writeIgnoreFile(".gitignore", " a", "  a");
667 		writeTrashFile("a/  a", "");
668 		writeTrashFile("a/ a", "");
669 		writeTrashFile("a/a", "");
670 
671 		beginWalk();
672 		assertEntry(D, ignored, "  a");
673 		assertEntry(F, ignored, "  a/  a");
674 		assertEntry(F, ignored, "  a/ a");
675 		assertEntry(F, ignored, "  a/a");
676 		assertEntry(D, ignored, " a");
677 		assertEntry(F, ignored, " a/  a");
678 		assertEntry(F, ignored, " a/ a");
679 		assertEntry(F, ignored, " a/a");
680 		assertEntry(F, tracked, ".gitignore");
681 		assertEntry(D, tracked, "a");
682 		assertEntry(F, ignored, "a/  a");
683 		assertEntry(F, ignored, "a/ a");
684 		assertEntry(F, tracked, "a/a");
685 		endWalk();
686 	}
687 
688 	@Test
689 	public void testTrailingSpaces() throws IOException {
690 		// Windows can't create files with trailing spaces
691 		// If this assumption fails the test is halted and ignored.
692 		org.junit.Assume.assumeFalse(SystemReader.getInstance().isWindows());
693 		writeTrashFile("a  /a", "");
694 		writeTrashFile("a  /a ", "");
695 		writeTrashFile("a  /a  ", "");
696 		writeTrashFile("a /a", "");
697 		writeTrashFile("a /a ", "");
698 		writeTrashFile("a /a  ", "");
699 		writeTrashFile("a/a", "");
700 		writeTrashFile("a/a ", "");
701 		writeTrashFile("a/a  ", "");
702 		writeTrashFile("b/c", "");
703 
704 		writeIgnoreFile(".gitignore", "a\\ ", "a \\ ", "b/ ");
705 
706 		beginWalk();
707 		assertEntry(F, tracked, ".gitignore");
708 		assertEntry(D, ignored, "a  ");
709 		assertEntry(F, ignored, "a  /a");
710 		assertEntry(F, ignored, "a  /a ");
711 		assertEntry(F, ignored, "a  /a  ");
712 		assertEntry(D, ignored, "a ");
713 		assertEntry(F, ignored, "a /a");
714 		assertEntry(F, ignored, "a /a ");
715 		assertEntry(F, ignored, "a /a  ");
716 		assertEntry(D, tracked, "a");
717 		assertEntry(F, tracked, "a/a");
718 		assertEntry(F, ignored, "a/a ");
719 		assertEntry(F, ignored, "a/a  ");
720 		assertEntry(D, ignored, "b");
721 		assertEntry(F, ignored, "b/c");
722 		endWalk();
723 	}
724 
725 	@Test
726 	public void testToString() throws Exception {
727 		assertEquals(Arrays.asList("").toString(), new IgnoreNode().toString());
728 		assertEquals(Arrays.asList("hello").toString(),
729 				new IgnoreNode(Arrays.asList(new FastIgnoreRule("hello")))
730 						.toString());
731 	}
732 
733 	private void beginWalk() {
734 		walk = new TreeWalk(db);
735 		FileTreeIterator iter = new FileTreeIterator(db);
736 		iter.setWalkIgnoredDirectories(true);
737 		walk.addTree(iter);
738 	}
739 
740 	private void endWalk() throws IOException {
741 		assertFalse("Not all files tested", walk.next());
742 	}
743 
744 	private void assertEntry(FileMode type, boolean entryIgnored,
745 			String pathName) throws IOException {
746 		assertTrue("walk has entry", walk.next());
747 		assertEquals(pathName, walk.getPathString());
748 		assertEquals(type, walk.getFileMode(0));
749 
750 		WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class);
751 		assertNotNull("has tree", itr);
752 		assertEquals("is ignored", entryIgnored, itr.isEntryIgnored());
753 		if (D.equals(type))
754 			walk.enterSubtree();
755 	}
756 
757 	private void writeIgnoreFile(String name, String... rules)
758 			throws IOException {
759 		StringBuilder data = new StringBuilder();
760 		for (String line : rules)
761 			data.append(line + "\n");
762 		writeTrashFile(name, data.toString());
763 	}
764 
765 	private InputStream writeToString(String... rules) {
766 		StringBuilder data = new StringBuilder();
767 		for (String line : rules) {
768 			data.append(line + "\n");
769 		}
770 		return new ByteArrayInputStream(data.toString().getBytes(UTF_8));
771 	}
772 }