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 org.eclipse.jgit.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertTrue;
48  import static org.junit.Assert.assertFalse;
49  import static org.junit.Assert.assertNotNull;
50  
51  import java.io.File;
52  import java.io.IOException;
53  import java.util.Arrays;
54  
55  import org.eclipse.jgit.errors.CorruptObjectException;
56  import org.eclipse.jgit.ignore.IgnoreNode.MatchResult;
57  import org.eclipse.jgit.junit.RepositoryTestCase;
58  import org.eclipse.jgit.lib.FileMode;
59  import org.eclipse.jgit.treewalk.FileTreeIterator;
60  import org.eclipse.jgit.treewalk.TreeWalk;
61  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
62  import org.eclipse.jgit.util.FileUtils;
63  import org.junit.Test;
64  
65  /**
66   * Tests ignore node behavior on the local filesystem.
67   */
68  public class IgnoreNodeTest extends RepositoryTestCase {
69  	private static final FileMode D = FileMode.TREE;
70  
71  	private static final FileMode F = FileMode.REGULAR_FILE;
72  
73  	private static final boolean ignored = true;
74  
75  	private static final boolean tracked = false;
76  
77  	private TreeWalk walk;
78  
79  	@Test
80  	public void testRules() throws IOException {
81  		writeIgnoreFile(".git/info/exclude", "*~", "/out");
82  
83  		writeIgnoreFile(".gitignore", "*.o", "/config");
84  		writeTrashFile("config/secret", "");
85  		writeTrashFile("mylib.c", "");
86  		writeTrashFile("mylib.c~", "");
87  		writeTrashFile("mylib.o", "");
88  
89  		writeTrashFile("out/object/foo.exe", "");
90  		writeIgnoreFile("src/config/.gitignore", "lex.out");
91  		writeTrashFile("src/config/lex.out", "");
92  		writeTrashFile("src/config/config.c", "");
93  		writeTrashFile("src/config/config.c~", "");
94  		writeTrashFile("src/config/old/lex.out", "");
95  
96  		beginWalk();
97  		assertEntry(F, tracked, ".gitignore");
98  		assertEntry(D, ignored, "config");
99  		assertEntry(F, ignored, "config/secret");
100 		assertEntry(F, tracked, "mylib.c");
101 		assertEntry(F, ignored, "mylib.c~");
102 		assertEntry(F, ignored, "mylib.o");
103 
104 		assertEntry(D, ignored, "out");
105 		assertEntry(D, ignored, "out/object");
106 		assertEntry(F, ignored, "out/object/foo.exe");
107 
108 		assertEntry(D, tracked, "src");
109 		assertEntry(D, tracked, "src/config");
110 		assertEntry(F, tracked, "src/config/.gitignore");
111 		assertEntry(F, tracked, "src/config/config.c");
112 		assertEntry(F, ignored, "src/config/config.c~");
113 		assertEntry(F, ignored, "src/config/lex.out");
114 		assertEntry(D, tracked, "src/config/old");
115 		assertEntry(F, ignored, "src/config/old/lex.out");
116 		endWalk();
117 	}
118 
119 	@Test
120 	public void testNegation() throws IOException {
121 		// ignore all *.o files and ignore all "d" directories
122 		writeIgnoreFile(".gitignore", "*.o", "d");
123 
124 		// negate "ignore" for a/b/keep.o file only
125 		writeIgnoreFile("src/a/b/.gitignore", "!keep.o");
126 		writeTrashFile("src/a/b/keep.o", "");
127 		writeTrashFile("src/a/b/nothere.o", "");
128 
129 		// negate "ignore" for "d"
130 		writeIgnoreFile("src/c/.gitignore", "!d");
131 		// negate "ignore" for c/d/keep.o file only
132 		writeIgnoreFile("src/c/d/.gitignore", "!keep.o");
133 		writeTrashFile("src/c/d/keep.o", "");
134 		writeTrashFile("src/c/d/nothere.o", "");
135 
136 		beginWalk();
137 		assertEntry(F, tracked, ".gitignore");
138 		assertEntry(D, tracked, "src");
139 		assertEntry(D, tracked, "src/a");
140 		assertEntry(D, tracked, "src/a/b");
141 		assertEntry(F, tracked, "src/a/b/.gitignore");
142 		assertEntry(F, tracked, "src/a/b/keep.o");
143 		assertEntry(F, ignored, "src/a/b/nothere.o");
144 
145 		assertEntry(D, tracked, "src/c");
146 		assertEntry(F, tracked, "src/c/.gitignore");
147 		assertEntry(D, tracked, "src/c/d");
148 		assertEntry(F, tracked, "src/c/d/.gitignore");
149 		assertEntry(F, tracked, "src/c/d/keep.o");
150 		// must be ignored: "!d" should not negate *both* "d" and *.o rules!
151 		assertEntry(F, ignored, "src/c/d/nothere.o");
152 		endWalk();
153 	}
154 
155 	/*
156 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=407475
157 	 */
158 	@Test
159 	public void testNegateAllExceptJavaInSrc() throws IOException {
160 		// ignore all files except from src directory
161 		writeIgnoreFile(".gitignore", "/*", "!/src/");
162 		writeTrashFile("nothere.o", "");
163 
164 		// ignore all files except java
165 		writeIgnoreFile("src/.gitignore", "*", "!*.java");
166 
167 		writeTrashFile("src/keep.java", "");
168 		writeTrashFile("src/nothere.o", "");
169 		writeTrashFile("src/a/nothere.o", "");
170 
171 		beginWalk();
172 		assertEntry(F, ignored, ".gitignore");
173 		assertEntry(F, ignored, "nothere.o");
174 		assertEntry(D, tracked, "src");
175 		assertEntry(F, ignored, "src/.gitignore");
176 		assertEntry(D, ignored, "src/a");
177 		assertEntry(F, ignored, "src/a/nothere.o");
178 		assertEntry(F, tracked, "src/keep.java");
179 		assertEntry(F, ignored, "src/nothere.o");
180 		endWalk();
181 	}
182 
183 	/*
184 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=407475
185 	 */
186 	@Test
187 	public void testNegationAllExceptJavaInSrcAndExceptChildDirInSrc()
188 			throws IOException {
189 		// ignore all files except from src directory
190 		writeIgnoreFile(".gitignore", "/*", "!/src/");
191 		writeTrashFile("nothere.o", "");
192 
193 		// ignore all files except java in src folder and all children folders.
194 		// Last ignore rule breaks old jgit via bug 407475
195 		writeIgnoreFile("src/.gitignore", "*", "!*.java", "!*/");
196 
197 		writeTrashFile("src/keep.java", "");
198 		writeTrashFile("src/nothere.o", "");
199 		writeTrashFile("src/a/keep.java", "");
200 		writeTrashFile("src/a/keep.o", "");
201 
202 		beginWalk();
203 		assertEntry(F, ignored, ".gitignore");
204 		assertEntry(F, ignored, "nothere.o");
205 		assertEntry(D, tracked, "src");
206 		assertEntry(F, ignored, "src/.gitignore");
207 		assertEntry(D, tracked, "src/a");
208 		assertEntry(F, tracked, "src/a/keep.java");
209 		assertEntry(F, tracked, "src/a/keep.o");
210 		assertEntry(F, tracked, "src/keep.java");
211 		assertEntry(F, ignored, "src/nothere.o");
212 		endWalk();
213 	}
214 
215 	/*
216 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=448094
217 	 */
218 	@Test
219 	public void testRepeatedNegation() throws IOException {
220 		writeIgnoreFile(".gitignore", "e", "!e", "e", "!e", "e");
221 
222 		writeTrashFile("e/nothere.o", "");
223 
224 		beginWalk();
225 		assertEntry(F, tracked, ".gitignore");
226 		assertEntry(D, ignored, "e");
227 		assertEntry(F, ignored, "e/nothere.o");
228 		endWalk();
229 	}
230 
231 	/*
232 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=448094
233 	 */
234 	@Test
235 	public void testRepeatedNegationInDifferentFiles1() throws IOException {
236 		writeIgnoreFile(".gitignore", "*.o", "e");
237 
238 		writeIgnoreFile("e/.gitignore", "!e");
239 		writeTrashFile("e/nothere.o", "");
240 
241 		beginWalk();
242 		assertEntry(F, tracked, ".gitignore");
243 		assertEntry(D, ignored, "e");
244 		assertEntry(F, ignored, "e/.gitignore");
245 		assertEntry(F, ignored, "e/nothere.o");
246 		endWalk();
247 	}
248 
249 	/*
250 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=448094
251 	 */
252 	@Test
253 	public void testRepeatedNegationInDifferentFiles2() throws IOException {
254 		writeIgnoreFile(".gitignore", "*.o", "e");
255 
256 		writeIgnoreFile("a/.gitignore", "!e");
257 		writeTrashFile("a/e/nothere.o", "");
258 
259 		beginWalk();
260 		assertEntry(F, tracked, ".gitignore");
261 		assertEntry(D, tracked, "a");
262 		assertEntry(F, tracked, "a/.gitignore");
263 		assertEntry(D, tracked, "a/e");
264 		assertEntry(F, ignored, "a/e/nothere.o");
265 		endWalk();
266 	}
267 
268 	/*
269 	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=448094
270 	 */
271 	@Test
272 	public void testRepeatedNegationInDifferentFiles3() throws IOException {
273 		writeIgnoreFile(".gitignore", "*.o");
274 
275 		writeIgnoreFile("a/.gitignore", "e");
276 		writeIgnoreFile("a/b/.gitignore", "!e");
277 		writeTrashFile("a/b/e/nothere.o", "");
278 
279 		beginWalk();
280 		assertEntry(F, tracked, ".gitignore");
281 		assertEntry(D, tracked, "a");
282 		assertEntry(F, tracked, "a/.gitignore");
283 		assertEntry(D, tracked, "a/b");
284 		assertEntry(F, tracked, "a/b/.gitignore");
285 		assertEntry(D, tracked, "a/b/e");
286 		assertEntry(F, ignored, "a/b/e/nothere.o");
287 		endWalk();
288 	}
289 
290 	@Test
291 	public void testRepeatedNegationInDifferentFiles4() throws IOException {
292 		writeIgnoreFile(".gitignore", "*.o");
293 
294 		writeIgnoreFile("a/.gitignore", "e");
295 		// Rules are never empty: WorkingTreeIterator optimizes empty rules away
296 		// paranoia check in case this optimization will be removed
297 		writeIgnoreFile("a/b/.gitignore", "#");
298 		writeIgnoreFile("a/b/c/.gitignore", "!e");
299 		writeTrashFile("a/b/c/e/nothere.o", "");
300 
301 		beginWalk();
302 		assertEntry(F, tracked, ".gitignore");
303 		assertEntry(D, tracked, "a");
304 		assertEntry(F, tracked, "a/.gitignore");
305 		assertEntry(D, tracked, "a/b");
306 		assertEntry(F, tracked, "a/b/.gitignore");
307 		assertEntry(D, tracked, "a/b/c");
308 		assertEntry(F, tracked, "a/b/c/.gitignore");
309 		assertEntry(D, tracked, "a/b/c/e");
310 		assertEntry(F, ignored, "a/b/c/e/nothere.o");
311 		endWalk();
312 	}
313 
314 	@Test
315 	public void testEmptyIgnoreNode() {
316 		// Rules are never empty: WorkingTreeIterator optimizes empty files away
317 		// So we have to test it manually in case third party clients use
318 		// IgnoreNode directly.
319 		IgnoreNode node = new IgnoreNode();
320 		assertEquals(MatchResult.CHECK_PARENT, node.isIgnored("", false));
321 		assertEquals(MatchResult.CHECK_PARENT, node.isIgnored("", false, false));
322 		assertEquals(MatchResult.CHECK_PARENT_NEGATE_FIRST_MATCH,
323 				node.isIgnored("", false, true));
324 	}
325 
326 	@Test
327 	public void testSlashOnlyMatchesDirectory() throws IOException {
328 		writeIgnoreFile(".gitignore", "out/");
329 		writeTrashFile("out", "");
330 
331 		beginWalk();
332 		assertEntry(F, tracked, ".gitignore");
333 		assertEntry(F, tracked, "out");
334 
335 		FileUtils.delete(new File(trash, "out"));
336 		writeTrashFile("out/foo", "");
337 
338 		beginWalk();
339 		assertEntry(F, tracked, ".gitignore");
340 		assertEntry(D, ignored, "out");
341 		assertEntry(F, ignored, "out/foo");
342 		endWalk();
343 	}
344 
345 	@Test
346 	public void testWithSlashDoesNotMatchInSubDirectory() throws IOException {
347 		writeIgnoreFile(".gitignore", "a/b");
348 		writeTrashFile("a/a", "");
349 		writeTrashFile("a/b", "");
350 		writeTrashFile("src/a/a", "");
351 		writeTrashFile("src/a/b", "");
352 
353 		beginWalk();
354 		assertEntry(F, tracked, ".gitignore");
355 		assertEntry(D, tracked, "a");
356 		assertEntry(F, tracked, "a/a");
357 		assertEntry(F, ignored, "a/b");
358 		assertEntry(D, tracked, "src");
359 		assertEntry(D, tracked, "src/a");
360 		assertEntry(F, tracked, "src/a/a");
361 		assertEntry(F, tracked, "src/a/b");
362 		endWalk();
363 	}
364 
365 	@Test
366 	public void testNoPatterns() throws IOException {
367 		writeIgnoreFile(".gitignore", "", " ", "# comment", "/");
368 		writeTrashFile("a/a", "");
369 
370 		beginWalk();
371 		assertEntry(F, tracked, ".gitignore");
372 		assertEntry(D, tracked, "a");
373 		assertEntry(F, tracked, "a/a");
374 		endWalk();
375 	}
376 
377 	@Test
378 	public void testToString() throws Exception {
379 		assertEquals(Arrays.asList("").toString(), new IgnoreNode().toString());
380 		assertEquals(Arrays.asList("hello").toString(),
381 				new IgnoreNode(Arrays.asList(new FastIgnoreRule("hello")))
382 						.toString());
383 	}
384 
385 	private void beginWalk() throws CorruptObjectException {
386 		walk = new TreeWalk(db);
387 		walk.addTree(new FileTreeIterator(db));
388 	}
389 
390 	private void endWalk() throws IOException {
391 		assertFalse("Not all files tested", walk.next());
392 	}
393 
394 	private void assertEntry(FileMode type, boolean entryIgnored,
395 			String pathName) throws IOException {
396 		assertTrue("walk has entry", walk.next());
397 		assertEquals(pathName, walk.getPathString());
398 		assertEquals(type, walk.getFileMode(0));
399 
400 		WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class);
401 		assertNotNull("has tree", itr);
402 		assertEquals("is ignored", entryIgnored, itr.isEntryIgnored());
403 		if (D.equals(type))
404 			walk.enterSubtree();
405 	}
406 
407 	private void writeIgnoreFile(String name, String... rules)
408 			throws IOException {
409 		StringBuilder data = new StringBuilder();
410 		for (String line : rules)
411 			data.append(line + "\n");
412 		writeTrashFile(name, data.toString());
413 	}
414 }