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