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