View Javadoc
1   /*
2    * Copyright (C) 2014, Obeo.
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.attributes;
44  
45  import static org.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertFalse;
47  import static org.junit.Assert.assertTrue;
48  
49  import java.io.File;
50  import java.io.IOException;
51  import java.util.ArrayList;
52  import java.util.Arrays;
53  import java.util.Collection;
54  import java.util.Collections;
55  import java.util.HashSet;
56  import java.util.List;
57  import java.util.Set;
58  
59  import org.eclipse.jgit.api.Git;
60  import org.eclipse.jgit.api.errors.GitAPIException;
61  import org.eclipse.jgit.api.errors.NoFilepatternException;
62  import org.eclipse.jgit.attributes.Attribute.State;
63  import org.eclipse.jgit.dircache.DirCacheIterator;
64  import org.eclipse.jgit.errors.NoWorkTreeException;
65  import org.eclipse.jgit.junit.JGitTestUtil;
66  import org.eclipse.jgit.junit.RepositoryTestCase;
67  import org.eclipse.jgit.lib.FileMode;
68  import org.eclipse.jgit.lib.Repository;
69  import org.eclipse.jgit.treewalk.FileTreeIterator;
70  import org.eclipse.jgit.treewalk.TreeWalk;
71  import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
72  import org.junit.After;
73  import org.junit.Before;
74  import org.junit.Test;
75  
76  /**
77   * Tests the attributes are correctly computed in a {@link TreeWalk}.
78   *
79   * @see TreeWalk#getAttributes()
80   */
81  public class TreeWalkAttributeTest extends RepositoryTestCase {
82  
83  	private static final FileMode M = FileMode.MISSING;
84  
85  	private static final FileMode D = FileMode.TREE;
86  
87  	private static final FileMode F = FileMode.REGULAR_FILE;
88  
89  	private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
90  
91  	private static Attribute EOL_LF = new Attribute("eol", "lf");
92  
93  	private static Attribute TEXT_SET = new Attribute("text", State.SET);
94  
95  	private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET);
96  
97  	private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
98  
99  	private static Attribute DELTA_SET = new Attribute("delta", State.SET);
100 
101 	private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global");
102 
103 	private static Attribute CUSTOM_INFO = new Attribute("custom", "info");
104 
105 	private static Attribute CUSTOM_ROOT = new Attribute("custom", "root");
106 
107 	private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent");
108 
109 	private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current");
110 
111 	private static Attribute CUSTOM2_UNSET = new Attribute("custom2",
112 			State.UNSET);
113 
114 	private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET);
115 
116 	private TreeWalk walk;
117 
118 	private TreeWalk ci_walk;
119 
120 	private Git git;
121 
122 	private File customAttributeFile;
123 
124 	@Override
125 	@Before
126 	public void setUp() throws Exception {
127 		super.setUp();
128 		git = new Git(db);
129 	}
130 
131 	@Override
132 	@After
133 	public void tearDown() throws Exception {
134 		super.tearDown();
135 		if (customAttributeFile != null)
136 			customAttributeFile.delete();
137 	}
138 
139 	/**
140 	 * Checks that the attributes are computed correctly depending on the
141 	 * operation type.
142 	 * <p>
143 	 * In this test we changed the content of the attribute files in the working
144 	 * tree compared to the one in the index.
145 	 * </p>
146 	 *
147 	 * @throws IOException
148 	 * @throws NoFilepatternException
149 	 * @throws GitAPIException
150 	 */
151 	@Test
152 	public void testCheckinCheckoutDifferences() throws IOException,
153 			NoFilepatternException, GitAPIException {
154 
155 		writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
156 		writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
157 		writeAttributesFile(".gitattributes", "*.txt custom=root");
158 		writeAttributesFile("level1/.gitattributes", "*.txt text");
159 		writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
160 
161 		writeTrashFile("l0.txt", "");
162 
163 		writeTrashFile("level1/l1.txt", "");
164 
165 		writeTrashFile("level1/level2/l2.txt", "");
166 
167 		git.add().addFilepattern(".").call();
168 
169 		beginWalk();
170 
171 		// Modify all attributes
172 		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2");
173 		writeAttributesFile(".git/info/attributes", "*.txt eol=lf");
174 		writeAttributesFile(".gitattributes", "*.txt custom=info");
175 		writeAttributesFile("level1/.gitattributes", "*.txt -text");
176 		writeAttributesFile("level1/level2/.gitattributes", "*.txt delta");
177 
178 		assertEntry(F, ".gitattributes");
179 		assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET),
180 				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET));
181 
182 		assertEntry(D, "level1");
183 		assertEntry(F, "level1/.gitattributes");
184 		assertEntry(F, "level1/l1.txt",
185 				asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET),
186 				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET));
187 
188 		assertEntry(D, "level1/level2");
189 		assertEntry(F, "level1/level2/.gitattributes");
190 		assertEntry(F, "level1/level2/l2.txt",
191 				asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET),
192 				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET));
193 
194 		endWalk();
195 	}
196 
197 	/**
198 	 * Checks that the index is used as fallback when the git attributes file
199 	 * are missing in the working tree.
200 	 *
201 	 * @throws IOException
202 	 * @throws NoFilepatternException
203 	 * @throws GitAPIException
204 	 */
205 	@Test
206 	public void testIndexOnly() throws IOException, NoFilepatternException,
207 			GitAPIException {
208 		List<File> attrFiles = new ArrayList<>();
209 		attrFiles.add(writeGlobalAttributeFile("globalAttributesFile",
210 				"*.txt -custom2"));
211 		attrFiles.add(writeAttributesFile(".git/info/attributes",
212 				"*.txt eol=crlf"));
213 		attrFiles
214 				.add(writeAttributesFile(".gitattributes", "*.txt custom=root"));
215 		attrFiles
216 				.add(writeAttributesFile("level1/.gitattributes", "*.txt text"));
217 		attrFiles.add(writeAttributesFile("level1/level2/.gitattributes",
218 				"*.txt -delta"));
219 
220 		writeTrashFile("l0.txt", "");
221 
222 		writeTrashFile("level1/l1.txt", "");
223 
224 		writeTrashFile("level1/level2/l2.txt", "");
225 
226 		git.add().addFilepattern(".").call();
227 
228 		// Modify all attributes
229 		for (File attrFile : attrFiles)
230 			attrFile.delete();
231 
232 		beginWalk();
233 
234 		assertEntry(M, ".gitattributes");
235 		assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT));
236 
237 		assertEntry(D, "level1");
238 		assertEntry(M, "level1/.gitattributes");
239 		assertEntry(F, "level1/l1.txt",
240 
241 		asSet(CUSTOM_ROOT, TEXT_SET));
242 
243 		assertEntry(D, "level1/level2");
244 		assertEntry(M, "level1/level2/.gitattributes");
245 		assertEntry(F, "level1/level2/l2.txt",
246 
247 		asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET));
248 
249 		endWalk();
250 	}
251 
252 	/**
253 	 * Check that we search in the working tree for attributes although the file
254 	 * we are currently inspecting does not exist anymore in the working tree.
255 	 *
256 	 * @throws IOException
257 	 * @throws NoFilepatternException
258 	 * @throws GitAPIException
259 	 */
260 	@Test
261 	public void testIndexOnly2()
262 			throws IOException, NoFilepatternException, GitAPIException {
263 		File l2 = writeTrashFile("level1/level2/l2.txt", "");
264 		writeTrashFile("level1/level2/l1.txt", "");
265 
266 		git.add().addFilepattern(".").call();
267 
268 		writeAttributesFile(".gitattributes", "*.txt custom=root");
269 		assertTrue(l2.delete());
270 
271 		beginWalk();
272 
273 		assertEntry(F, ".gitattributes");
274 		assertEntry(D, "level1");
275 		assertEntry(D, "level1/level2");
276 		assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT));
277 		assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT));
278 
279 		endWalk();
280 	}
281 
282 	/**
283 	 * Basic test for git attributes.
284 	 * <p>
285 	 * In this use case files are present in both the working tree and the index
286 	 * </p>
287 	 *
288 	 * @throws IOException
289 	 * @throws NoFilepatternException
290 	 * @throws GitAPIException
291 	 */
292 	@Test
293 	public void testRules() throws IOException, NoFilepatternException,
294 			GitAPIException {
295 		writeAttributesFile(".git/info/attributes", "windows* eol=crlf");
296 
297 		writeAttributesFile(".gitattributes", "*.txt eol=lf");
298 		writeTrashFile("windows.file", "");
299 		writeTrashFile("windows.txt", "");
300 		writeTrashFile("readme.txt", "");
301 
302 		writeAttributesFile("src/config/.gitattributes", "*.txt -delta");
303 		writeTrashFile("src/config/readme.txt", "");
304 		writeTrashFile("src/config/windows.file", "");
305 		writeTrashFile("src/config/windows.txt", "");
306 
307 		beginWalk();
308 
309 		git.add().addFilepattern(".").call();
310 
311 		assertEntry(F, ".gitattributes");
312 		assertEntry(F, "readme.txt", asSet(EOL_LF));
313 
314 		assertEntry(D, "src");
315 		assertEntry(D, "src/config");
316 		assertEntry(F, "src/config/.gitattributes");
317 		assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF));
318 		assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF));
319 		assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF));
320 
321 		assertEntry(F, "windows.file", asSet(EOL_CRLF));
322 		assertEntry(F, "windows.txt", asSet(EOL_CRLF));
323 
324 		endWalk();
325 	}
326 
327 	/**
328 	 * Checks that if there is no .gitattributes file in the repository
329 	 * everything still work fine.
330 	 *
331 	 * @throws IOException
332 	 */
333 	@Test
334 	public void testNoAttributes() throws IOException {
335 		writeTrashFile("l0.txt", "");
336 		writeTrashFile("level1/l1.txt", "");
337 		writeTrashFile("level1/level2/l2.txt", "");
338 
339 		beginWalk();
340 
341 		assertEntry(F, "l0.txt");
342 
343 		assertEntry(D, "level1");
344 		assertEntry(F, "level1/l1.txt");
345 
346 		assertEntry(D, "level1/level2");
347 		assertEntry(F, "level1/level2/l2.txt");
348 
349 		endWalk();
350 	}
351 
352 	/**
353 	 * Checks that an empty .gitattribute file does not return incorrect value.
354 	 *
355 	 * @throws IOException
356 	 */
357 	@Test
358 	public void testEmptyGitAttributeFile() throws IOException {
359 		writeAttributesFile(".git/info/attributes", "");
360 		writeTrashFile("l0.txt", "");
361 		writeAttributesFile(".gitattributes", "");
362 		writeTrashFile("level1/l1.txt", "");
363 		writeTrashFile("level1/level2/l2.txt", "");
364 
365 		beginWalk();
366 
367 		assertEntry(F, ".gitattributes");
368 		assertEntry(F, "l0.txt");
369 
370 		assertEntry(D, "level1");
371 		assertEntry(F, "level1/l1.txt");
372 
373 		assertEntry(D, "level1/level2");
374 		assertEntry(F, "level1/level2/l2.txt");
375 
376 		endWalk();
377 	}
378 
379 	@Test
380 	public void testNoMatchingAttributes() throws IOException {
381 		writeAttributesFile(".git/info/attributes", "*.java delta");
382 		writeAttributesFile(".gitattributes", "*.java -delta");
383 		writeAttributesFile("levelA/.gitattributes", "*.java eol=lf");
384 		writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf");
385 
386 		writeTrashFile("levelA/lA.txt", "");
387 
388 		beginWalk();
389 
390 		assertEntry(F, ".gitattributes");
391 
392 		assertEntry(D, "levelA");
393 		assertEntry(F, "levelA/.gitattributes");
394 		assertEntry(F, "levelA/lA.txt");
395 
396 		assertEntry(D, "levelB");
397 		assertEntry(F, "levelB/.gitattributes");
398 
399 		endWalk();
400 	}
401 
402 	/**
403 	 * Checks that $GIT_DIR/info/attributes file has the highest precedence.
404 	 *
405 	 * @throws IOException
406 	 */
407 	@Test
408 	public void testPrecedenceInfo() throws IOException {
409 		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
410 		writeAttributesFile(".git/info/attributes", "*.txt custom=info");
411 		writeAttributesFile(".gitattributes", "*.txt custom=root");
412 		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
413 		writeAttributesFile("level1/level2/.gitattributes",
414 				"*.txt custom=current");
415 
416 		writeTrashFile("level1/level2/file.txt", "");
417 
418 		beginWalk();
419 
420 		assertEntry(F, ".gitattributes");
421 
422 		assertEntry(D, "level1");
423 		assertEntry(F, "level1/.gitattributes");
424 
425 		assertEntry(D, "level1/level2");
426 		assertEntry(F, "level1/level2/.gitattributes");
427 		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO));
428 
429 		endWalk();
430 	}
431 
432 	/**
433 	 * Checks that a subfolder ".gitattributes" file has precedence over its
434 	 * parent.
435 	 *
436 	 * @throws IOException
437 	 */
438 	@Test
439 	public void testPrecedenceCurrent() throws IOException {
440 		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
441 		writeAttributesFile(".gitattributes", "*.txt custom=root");
442 		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
443 		writeAttributesFile("level1/level2/.gitattributes",
444 				"*.txt custom=current");
445 
446 		writeTrashFile("level1/level2/file.txt", "");
447 
448 		beginWalk();
449 
450 		assertEntry(F, ".gitattributes");
451 
452 		assertEntry(D, "level1");
453 		assertEntry(F, "level1/.gitattributes");
454 
455 		assertEntry(D, "level1/level2");
456 		assertEntry(F, "level1/level2/.gitattributes");
457 		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT));
458 
459 		endWalk();
460 	}
461 
462 	/**
463 	 * Checks that the parent ".gitattributes" file is used as fallback.
464 	 *
465 	 * @throws IOException
466 	 */
467 	@Test
468 	public void testPrecedenceParent() throws IOException {
469 		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
470 		writeAttributesFile(".gitattributes", "*.txt custom=root");
471 		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
472 
473 		writeTrashFile("level1/level2/file.txt", "");
474 
475 		beginWalk();
476 
477 		assertEntry(F, ".gitattributes");
478 
479 		assertEntry(D, "level1");
480 		assertEntry(F, "level1/.gitattributes");
481 
482 		assertEntry(D, "level1/level2");
483 		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT));
484 
485 		endWalk();
486 	}
487 
488 	/**
489 	 * Checks that the grand parent ".gitattributes" file is used as fallback.
490 	 *
491 	 * @throws IOException
492 	 */
493 	@Test
494 	public void testPrecedenceRoot() throws IOException {
495 		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
496 		writeAttributesFile(".gitattributes", "*.txt custom=root");
497 
498 		writeTrashFile("level1/level2/file.txt", "");
499 
500 		beginWalk();
501 
502 		assertEntry(F, ".gitattributes");
503 
504 		assertEntry(D, "level1");
505 
506 		assertEntry(D, "level1/level2");
507 		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT));
508 
509 		endWalk();
510 	}
511 
512 	/**
513 	 * Checks that the global attribute file is used as fallback.
514 	 *
515 	 * @throws IOException
516 	 */
517 	@Test
518 	public void testPrecedenceGlobal() throws IOException {
519 		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
520 
521 		writeTrashFile("level1/level2/file.txt", "");
522 
523 		beginWalk();
524 
525 		assertEntry(D, "level1");
526 
527 		assertEntry(D, "level1/level2");
528 		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL));
529 
530 		endWalk();
531 	}
532 
533 	/**
534 	 * Checks the precedence on a hierarchy with multiple attributes.
535 	 * <p>
536 	 * In this test all file are present in both the working tree and the index.
537 	 * </p>
538 	 *
539 	 * @throws IOException
540 	 * @throws GitAPIException
541 	 * @throws NoFilepatternException
542 	 */
543 	@Test
544 	public void testHierarchyBothIterator() throws IOException,
545 			NoFilepatternException, GitAPIException {
546 		writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
547 		writeAttributesFile(".gitattributes", "*.local eol=lf");
548 		writeAttributesFile("level1/.gitattributes", "*.local text");
549 		writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
550 
551 		writeTrashFile("l0.global", "");
552 		writeTrashFile("l0.local", "");
553 
554 		writeTrashFile("level1/l1.global", "");
555 		writeTrashFile("level1/l1.local", "");
556 
557 		writeTrashFile("level1/level2/l2.global", "");
558 		writeTrashFile("level1/level2/l2.local", "");
559 
560 		beginWalk();
561 
562 		git.add().addFilepattern(".").call();
563 
564 		assertEntry(F, ".gitattributes");
565 		assertEntry(F, "l0.global", asSet(EOL_CRLF));
566 		assertEntry(F, "l0.local", asSet(EOL_LF));
567 
568 		assertEntry(D, "level1");
569 		assertEntry(F, "level1/.gitattributes");
570 		assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
571 		assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
572 
573 		assertEntry(D, "level1/level2");
574 		assertEntry(F, "level1/level2/.gitattributes");
575 		assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
576 		assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
577 
578 		endWalk();
579 
580 	}
581 
582 	/**
583 	 * Checks the precedence on a hierarchy with multiple attributes.
584 	 * <p>
585 	 * In this test all file are present only in the working tree.
586 	 * </p>
587 	 *
588 	 * @throws IOException
589 	 * @throws GitAPIException
590 	 * @throws NoFilepatternException
591 	 */
592 	@Test
593 	public void testHierarchyWorktreeOnly()
594 			throws IOException, NoFilepatternException, GitAPIException {
595 		writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
596 		writeAttributesFile(".gitattributes", "*.local eol=lf");
597 		writeAttributesFile("level1/.gitattributes", "*.local text");
598 		writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
599 
600 		writeTrashFile("l0.global", "");
601 		writeTrashFile("l0.local", "");
602 
603 		writeTrashFile("level1/l1.global", "");
604 		writeTrashFile("level1/l1.local", "");
605 
606 		writeTrashFile("level1/level2/l2.global", "");
607 		writeTrashFile("level1/level2/l2.local", "");
608 
609 		beginWalk();
610 
611 		assertEntry(F, ".gitattributes");
612 		assertEntry(F, "l0.global", asSet(EOL_CRLF));
613 		assertEntry(F, "l0.local", asSet(EOL_LF));
614 
615 		assertEntry(D, "level1");
616 		assertEntry(F, "level1/.gitattributes");
617 		assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
618 		assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
619 
620 		assertEntry(D, "level1/level2");
621 		assertEntry(F, "level1/level2/.gitattributes");
622 		assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
623 		assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
624 
625 		endWalk();
626 
627 	}
628 
629 	/**
630 	 * Checks that the list of attributes is an aggregation of all the
631 	 * attributes from the attributes files hierarchy.
632 	 *
633 	 * @throws IOException
634 	 */
635 	@Test
636 	public void testAggregation() throws IOException {
637 		writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
638 		writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
639 		writeAttributesFile(".gitattributes", "*.txt custom=root");
640 		writeAttributesFile("level1/.gitattributes", "*.txt text");
641 		writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
642 
643 		writeTrashFile("l0.txt", "");
644 
645 		writeTrashFile("level1/l1.txt", "");
646 
647 		writeTrashFile("level1/level2/l2.txt", "");
648 
649 		beginWalk();
650 
651 		assertEntry(F, ".gitattributes");
652 		assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET));
653 
654 		assertEntry(D, "level1");
655 		assertEntry(F, "level1/.gitattributes");
656 		assertEntry(F, "level1/l1.txt",
657 				asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET));
658 
659 		assertEntry(D, "level1/level2");
660 		assertEntry(F, "level1/level2/.gitattributes");
661 		assertEntry(
662 				F,
663 				"level1/level2/l2.txt",
664 				asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET,
665 						CUSTOM2_UNSET));
666 
667 		endWalk();
668 
669 	}
670 
671 	/**
672 	 * Checks that the last entry in .gitattributes is used if 2 lines match the
673 	 * same attribute
674 	 *
675 	 * @throws IOException
676 	 */
677 	@Test
678 	public void testOverriding() throws IOException {
679 		writeAttributesFile(".git/info/attributes",//
680 				//
681 				"*.txt custom=current",//
682 				"*.txt custom=parent",//
683 				"*.txt custom=root",//
684 				"*.txt custom=info",
685 				//
686 				"*.txt delta",//
687 				"*.txt -delta",
688 				//
689 				"*.txt eol=lf",//
690 				"*.txt eol=crlf",
691 				//
692 				"*.txt text",//
693 				"*.txt -text");
694 
695 		writeTrashFile("l0.txt", "");
696 		beginWalk();
697 
698 		assertEntry(F, "l0.txt",
699 				asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
700 
701 		endWalk();
702 	}
703 
704 	/**
705 	 * Checks that the last value of an attribute is used if in the same line an
706 	 * attribute is defined several time.
707 	 *
708 	 * @throws IOException
709 	 */
710 	@Test
711 	public void testOverriding2() throws IOException {
712 		writeAttributesFile(".git/info/attributes",
713 				"*.txt custom=current custom=parent custom=root custom=info",//
714 				"*.txt delta -delta",//
715 				"*.txt eol=lf eol=crlf",//
716 				"*.txt text -text");
717 		writeTrashFile("l0.txt", "");
718 		beginWalk();
719 
720 		assertEntry(F, "l0.txt",
721 				asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
722 
723 		endWalk();
724 	}
725 
726 	@Test
727 	public void testRulesInherited() throws Exception {
728 		writeAttributesFile(".gitattributes", "**/*.txt eol=lf");
729 		writeTrashFile("src/config/readme.txt", "");
730 		writeTrashFile("src/config/windows.file", "");
731 
732 		beginWalk();
733 
734 		assertEntry(F, ".gitattributes");
735 		assertEntry(D, "src");
736 		assertEntry(D, "src/config");
737 
738 		assertEntry(F, "src/config/readme.txt", asSet(EOL_LF));
739 		assertEntry(F, "src/config/windows.file",
740 				Collections.<Attribute> emptySet());
741 
742 		endWalk();
743 	}
744 
745 	private void beginWalk() throws NoWorkTreeException, IOException {
746 		walk = new TreeWalk(db);
747 		walk.addTree(new FileTreeIterator(db));
748 		walk.addTree(new DirCacheIterator(db.readDirCache()));
749 
750 		ci_walk = new TreeWalk(db);
751 		ci_walk.setOperationType(OperationType.CHECKIN_OP);
752 		ci_walk.addTree(new FileTreeIterator(db));
753 		ci_walk.addTree(new DirCacheIterator(db.readDirCache()));
754 	}
755 
756 	/**
757 	 * Assert an entry in which checkin and checkout attributes are expected to
758 	 * be the same.
759 	 *
760 	 * @param type
761 	 * @param pathName
762 	 * @param forBothOperaiton
763 	 * @throws IOException
764 	 */
765 	private void assertEntry(FileMode type, String pathName,
766 			Set<Attribute> forBothOperaiton) throws IOException {
767 		assertEntry(type, pathName, forBothOperaiton, forBothOperaiton);
768 	}
769 
770 	/**
771 	 * Assert an entry with no attribute expected.
772 	 *
773 	 * @param type
774 	 * @param pathName
775 	 * @throws IOException
776 	 */
777 	private void assertEntry(FileMode type, String pathName) throws IOException {
778 		assertEntry(type, pathName, Collections.<Attribute> emptySet(),
779 				Collections.<Attribute> emptySet());
780 	}
781 
782 	/**
783 	 * Assert that an entry;
784 	 * <ul>
785 	 * <li>Has the correct type</li>
786 	 * <li>Exist in the tree walk</li>
787 	 * <li>Has the expected attributes on a checkin operation</li>
788 	 * <li>Has the expected attributes on a checkout operation</li>
789 	 * </ul>
790 	 *
791 	 * @param type
792 	 * @param pathName
793 	 * @param checkinAttributes
794 	 * @param checkoutAttributes
795 	 * @throws IOException
796 	 */
797 	private void assertEntry(FileMode type, String pathName,
798 			Set<Attribute> checkinAttributes, Set<Attribute> checkoutAttributes)
799 			throws IOException {
800 		assertTrue("walk has entry", walk.next());
801 		assertTrue("walk has entry", ci_walk.next());
802 		assertEquals(pathName, walk.getPathString());
803 		assertEquals(type, walk.getFileMode(0));
804 
805 		assertEquals(checkinAttributes,
806 				asSet(ci_walk.getAttributes().getAll()));
807 		assertEquals(checkoutAttributes,
808 				asSet(walk.getAttributes().getAll()));
809 
810 		if (D.equals(type)) {
811 			walk.enterSubtree();
812 			ci_walk.enterSubtree();
813 		}
814 	}
815 
816 	private static Set<Attribute> asSet(Collection<Attribute> attributes) {
817 		Set<Attribute> ret = new HashSet<>();
818 		for (Attribute a : attributes) {
819 			ret.add(a);
820 		}
821 		return (ret);
822 	}
823 
824 	private File writeAttributesFile(String name, String... rules)
825 			throws IOException {
826 		StringBuilder data = new StringBuilder();
827 		for (String line : rules)
828 			data.append(line + "\n");
829 		return writeTrashFile(name, data.toString());
830 	}
831 
832 	/**
833 	 * Creates an attributes file and set its locationĀ in the git configuration.
834 	 *
835 	 * @param fileName
836 	 * @param attributes
837 	 * @return The attribute file
838 	 * @throws IOException
839 	 * @see Repository#getConfig()
840 	 */
841 	private File writeGlobalAttributeFile(String fileName, String... attributes)
842 			throws IOException {
843 		customAttributeFile = File.createTempFile("tmp_", fileName, null);
844 		customAttributeFile.deleteOnExit();
845 		StringBuilder attributesFileContent = new StringBuilder();
846 		for (String attr : attributes) {
847 			attributesFileContent.append(attr).append("\n");
848 		}
849 		JGitTestUtil.write(customAttributeFile,
850 				attributesFileContent.toString());
851 		db.getConfig().setString("core", null, "attributesfile",
852 				customAttributeFile.getAbsolutePath());
853 		return customAttributeFile;
854 	}
855 
856 	static Set<Attribute> asSet(Attribute... attrs) {
857 		HashSet<Attribute> result = new HashSet<>();
858 		result.addAll(Arrays.asList(attrs));
859 		return result;
860 	}
861 
862 	private void endWalk() throws IOException {
863 		assertFalse("Not all files tested", walk.next());
864 		assertFalse("Not all files tested", ci_walk.next());
865 	}
866 }