View Javadoc
1   /*
2    * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
3    *
4    * This program and the accompanying materials are made available
5    * under the terms of the Eclipse Distribution License v1.0 which
6    * accompanies this distribution, is reproduced below, and is
7    * available at http://www.eclipse.org/org/documents/edl-v10.php
8    *
9    * All rights reserved.
10   *
11   * Redistribution and use in source and binary forms, with or
12   * without modification, are permitted provided that the following
13   * conditions are met:
14   *
15   * - Redistributions of source code must retain the above copyright
16   *   notice, this list of conditions and the following disclaimer.
17   *
18   * - Redistributions in binary form must reproduce the above
19   *   copyright notice, this list of conditions and the following
20   *   disclaimer in the documentation and/or other materials provided
21   *   with the distribution.
22   *
23   * - Neither the name of the Eclipse Foundation, Inc. nor the
24   *   names of its contributors may be used to endorse or promote
25   *   products derived from this software without specific prior
26   *   written permission.
27   *
28   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
29   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
30   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
32   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
33   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38   */
39  package org.eclipse.jgit.api;
40  
41  import static java.nio.charset.StandardCharsets.UTF_8;
42  import static org.junit.Assert.assertEquals;
43  import static org.junit.Assert.assertFalse;
44  import static org.junit.Assert.assertTrue;
45  
46  import java.io.File;
47  import java.io.IOException;
48  
49  import org.eclipse.jgit.api.ResetCommand.ResetType;
50  import org.eclipse.jgit.api.errors.CheckoutConflictException;
51  import org.eclipse.jgit.api.errors.GitAPIException;
52  import org.eclipse.jgit.api.errors.NoFilepatternException;
53  import org.eclipse.jgit.attributes.Attribute;
54  import org.eclipse.jgit.dircache.DirCache;
55  import org.eclipse.jgit.dircache.DirCacheEditor;
56  import org.eclipse.jgit.dircache.DirCacheEntry;
57  import org.eclipse.jgit.dircache.DirCacheIterator;
58  import org.eclipse.jgit.errors.RevisionSyntaxException;
59  import org.eclipse.jgit.junit.RepositoryTestCase;
60  import org.eclipse.jgit.lib.ConfigConstants;
61  import org.eclipse.jgit.lib.Constants;
62  import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
63  import org.eclipse.jgit.lib.CoreConfig.EOL;
64  import org.eclipse.jgit.lib.FileMode;
65  import org.eclipse.jgit.lib.ObjectLoader;
66  import org.eclipse.jgit.revwalk.RevCommit;
67  import org.eclipse.jgit.storage.file.FileBasedConfig;
68  import org.eclipse.jgit.treewalk.FileTreeIterator;
69  import org.eclipse.jgit.treewalk.TreeWalk;
70  import org.eclipse.jgit.util.FS;
71  import org.eclipse.jgit.util.IO;
72  import org.junit.Assert;
73  import org.junit.Test;
74  import org.junit.experimental.theories.DataPoint;
75  import org.junit.experimental.theories.Theories;
76  import org.junit.runner.RunWith;
77  
78  /**
79   * Unit tests for end-of-line conversion and settings using core.autocrlf, *
80   * core.eol and the .gitattributes eol, text, binary (macro for -diff -merge
81   * -text)
82   */
83  @RunWith(Theories.class)
84  public class EolRepositoryTest extends RepositoryTestCase {
85  	private static final FileMode D = FileMode.TREE;
86  
87  	private static final FileMode F = FileMode.REGULAR_FILE;
88  
89  	@DataPoint
90  	public static boolean doSmudgeEntries = true;
91  
92  	@DataPoint
93  	public static boolean dontSmudgeEntries = false;
94  
95  	private boolean smudge;
96  
97  	@DataPoint
98  	public static String smallContents[] = {
99  			generateTestData(3, 1, true, false),
100 			generateTestData(3, 1, false, true),
101 			generateTestData(3, 1, true, true) };
102 
103 	@DataPoint
104 	public static String hugeContents[] = {
105 			generateTestData(1000000, 17, true, false),
106 			generateTestData(1000000, 17, false, true),
107 			generateTestData(1000000, 17, true, true) };
108 
109 	static String generateTestData(int size, int lineSize, boolean withCRLF,
110 			boolean withLF) {
111 		StringBuilder sb = new StringBuilder();
112 		for (int i = 0; i < size; i++) {
113 			if (i > 0 && i % lineSize == 0) {
114 				// newline
115 				if (withCRLF && withLF) {
116 					// mixed
117 					if (i % 2 == 0)
118 						sb.append("\r\n");
119 					else
120 						sb.append("\n");
121 				} else if (withCRLF) {
122 					sb.append("\r\n");
123 				} else if (withLF) {
124 					sb.append("\n");
125 				}
126 			}
127 			sb.append("A");
128 		}
129 		return sb.toString();
130 	}
131 
132 	public EolRepositoryTest(String[] testContent, boolean smudgeEntries) {
133 		CONTENT_CRLF = testContent[0];
134 		CONTENT_LF = testContent[1];
135 		CONTENT_MIXED = testContent[2];
136 		this.smudge = smudgeEntries;
137 	}
138 
139 	protected String CONTENT_CRLF;
140 
141 	protected String CONTENT_LF;
142 
143 	protected String CONTENT_MIXED;
144 
145 	/** work tree root .gitattributes */
146 	private File dotGitattributes;
147 
148 	/** file containing CRLF */
149 	private File fileCRLF;
150 
151 	/** file containing LF */
152 	private File fileLF;
153 
154 	/** file containing mixed CRLF and LF */
155 	private File fileMixed;
156 
157 	/** this values are set in {@link #collectRepositoryState()} */
158 	private static class ActualEntry {
159 		private String attrs;
160 
161 		private String file;
162 
163 		private String index;
164 
165 		private int indexContentLength;
166 	}
167 
168 	private ActualEntry entryCRLF = new ActualEntry();
169 
170 	private ActualEntry entryLF = new ActualEntry();
171 
172 	private ActualEntry entryMixed = new ActualEntry();
173 
174 	private DirCache dirCache;
175 
176 	@Test
177 	public void testDefaultSetup() throws Exception {
178 		// for EOL to work, the text attribute must be set
179 		setupGitAndDoHardReset(null, null, null, null, "* text=auto");
180 		collectRepositoryState();
181 		assertEquals("text=auto", entryCRLF.attrs);
182 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
183 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
184 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
185 	}
186 
187 	public void checkEntryContent(ActualEntry entry, String fileContent,
188 			String indexContent) {
189 		assertEquals(fileContent, entry.file);
190 		assertEquals(indexContent, entry.index);
191 		if (entry.indexContentLength != 0) {
192 			assertEquals(fileContent.length(), entry.indexContentLength);
193 		}
194 	}
195 
196 	@Test
197 	public void test_ConfigAutoCRLF_false() throws Exception {
198 		// for EOL to work, the text attribute must be set
199 		setupGitAndDoHardReset(AutoCRLF.FALSE, null, null, null, "* text=auto");
200 		collectRepositoryState();
201 		assertEquals("text=auto", entryCRLF.attrs);
202 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
203 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
204 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
205 	}
206 
207 	@Test
208 	public void test_ConfigAutoCRLF_true() throws Exception {
209 		// for EOL to work, the text attribute must be set
210 		setupGitAndDoHardReset(AutoCRLF.TRUE, null, null, null, "* text=auto");
211 		collectRepositoryState();
212 		assertEquals("text=auto", entryCRLF.attrs);
213 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
214 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
215 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
216 	}
217 
218 	@Test
219 	public void test_ConfigAutoCRLF_input() throws Exception {
220 		// for EOL to work, the text attribute must be set
221 		setupGitAndDoHardReset(AutoCRLF.INPUT, null, null, null, "* text=auto");
222 		collectRepositoryState();
223 		assertEquals("text=auto", entryCRLF.attrs);
224 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
225 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
226 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
227 	}
228 
229 	@Test
230 	public void test_ConfigEOL_lf() throws Exception {
231 		// for EOL to work, the text attribute must be set
232 		setupGitAndDoHardReset(null, EOL.LF, "*.txt text", null, null);
233 		collectRepositoryState();
234 		assertEquals("text", entryCRLF.attrs);
235 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
236 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
237 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
238 	}
239 
240 	@Test
241 	public void test_ConfigEOL_crlf() throws Exception {
242 		// for EOL to work, the text attribute must be set
243 		setupGitAndDoHardReset(null, EOL.CRLF, "*.txt text", null, null);
244 		collectRepositoryState();
245 		assertEquals("text", entryCRLF.attrs);
246 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
247 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
248 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
249 	}
250 
251 	@Test
252 	public void test_ConfigEOL_native_windows() throws Exception {
253 		String origLineSeparator = System.getProperty("line.separator", "\n");
254 		System.setProperty("line.separator", "\r\n");
255 		try {
256 			// for EOL to work, the text attribute must be set
257 			setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
258 			collectRepositoryState();
259 			assertEquals("text", entryCRLF.attrs);
260 			checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
261 			checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
262 		} finally {
263 			System.setProperty("line.separator", origLineSeparator);
264 		}
265 	}
266 
267 	@Test
268 	public void test_ConfigEOL_native_xnix() throws Exception {
269 		String origLineSeparator = System.getProperty("line.separator", "\n");
270 		System.setProperty("line.separator", "\n");
271 		try {
272 			// for EOL to work, the text attribute must be set
273 			setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
274 			collectRepositoryState();
275 			assertEquals("text", entryCRLF.attrs);
276 			checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
277 			checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
278 		} finally {
279 			System.setProperty("line.separator", origLineSeparator);
280 		}
281 	}
282 
283 	@Test
284 	public void test_ConfigAutoCRLF_false_ConfigEOL_lf() throws Exception {
285 		// for EOL to work, the text attribute must be set
286 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt text", null, null);
287 		collectRepositoryState();
288 		assertEquals("text", entryCRLF.attrs);
289 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
290 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
291 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
292 	}
293 
294 	@Test
295 	public void test_ConfigAutoCRLF_false_ConfigEOL_native() throws Exception {
296 		// for EOL to work, the text attribute must be set
297 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.NATIVE, "*.txt text", null, null);
298 		collectRepositoryState();
299 		assertEquals("text", entryCRLF.attrs);
300 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
301 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
302 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
303 	}
304 
305 	@Test
306 	public void test_ConfigAutoCRLF_true_ConfigEOL_lf() throws Exception {
307 		// for EOL to work, the text attribute must be set
308 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt text", null, null);
309 		collectRepositoryState();
310 		assertEquals("text", entryCRLF.attrs);
311 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
312 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
313 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
314 	}
315 
316 	@Test
317 	public void test_switchToBranchWithTextAttributes()
318 			throws Exception {
319 		Git git = Git.wrap(db);
320 
321 		// for EOL to work, the text attribute must be set
322 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.CRLF, null, null,
323 				"file1.txt text\nfile2.txt text\nfile3.txt text");
324 		collectRepositoryState();
325 		assertEquals("text", entryCRLF.attrs);
326 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
327 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
328 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
329 
330 		// switch to binary for file1
331 		dotGitattributes = createAndAddFile(git, Constants.DOT_GIT_ATTRIBUTES,
332 				"file1.txt binary\nfile2.txt text\nfile3.txt text");
333 		gitCommit(git, "switchedToBinaryFor1");
334 		recreateWorktree(git);
335 		collectRepositoryState();
336 		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
337 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
338 		assertEquals("text", entryLF.attrs);
339 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
340 		assertEquals("text", entryMixed.attrs);
341 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
342 
343 		// checkout the commit which has text for file1
344 		gitCheckout(git, "HEAD^");
345 		recreateWorktree(git);
346 		collectRepositoryState();
347 		assertEquals("text", entryCRLF.attrs);
348 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
349 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
350 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
351 	}
352 
353 	@Test
354 	public void test_switchToBranchWithBinaryAttributes() throws Exception {
355 		Git git = Git.wrap(db);
356 
357 		// for EOL to work, the text attribute must be set
358 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, null, null,
359 				"file1.txt binary\nfile2.txt binary\nfile3.txt binary");
360 		collectRepositoryState();
361 		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
362 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
363 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
364 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
365 
366 		// switch to text for file1
367 		dotGitattributes = createAndAddFile(git, Constants.DOT_GIT_ATTRIBUTES,
368 				"file1.txt text\nfile2.txt binary\nfile3.txt binary");
369 		gitCommit(git, "switchedToTextFor1");
370 		recreateWorktree(git);
371 		collectRepositoryState();
372 		assertEquals("text", entryCRLF.attrs);
373 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
374 		assertEquals("binary -diff -merge -text", entryLF.attrs);
375 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
376 		assertEquals("binary -diff -merge -text", entryMixed.attrs);
377 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
378 
379 		// checkout the commit which has text for file1
380 		gitCheckout(git, "HEAD^");
381 		recreateWorktree(git);
382 		collectRepositoryState();
383 		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
384 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
385 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
386 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
387 	}
388 
389 	@Test
390 	public void test_ConfigAutoCRLF_input_ConfigEOL_lf() throws Exception {
391 		// for EOL to work, the text attribute must be set
392 		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt text", null, null);
393 		collectRepositoryState();
394 		assertEquals("text", entryCRLF.attrs);
395 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
396 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
397 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
398 	}
399 
400 	@Test
401 	public void test_ConfigAutoCRLF_true_GlobalEOL_lf() throws Exception {
402 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt eol=lf", null, null);
403 		collectRepositoryState();
404 		assertEquals("eol=lf", entryCRLF.attrs);
405 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
406 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
407 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
408 	}
409 
410 	@Test
411 	public void test_ConfigAutoCRLF_false_GlobalEOL_lf() throws Exception {
412 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt eol=lf", null, null);
413 		collectRepositoryState();
414 		assertEquals("eol=lf", entryCRLF.attrs);
415 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
416 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
417 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
418 	}
419 
420 	@Test
421 	public void test_ConfigAutoCRLF_input_GlobalEOL_lf() throws Exception {
422 		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt eol=lf", null, null);
423 		collectRepositoryState();
424 		assertEquals("eol=lf", entryCRLF.attrs);
425 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
426 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
427 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
428 	}
429 
430 	@Test
431 	public void test_ConfigAutoCRLF_true_GlobalEOL_crlf() throws Exception {
432 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt eol=crlf", null, null);
433 		collectRepositoryState();
434 		assertEquals("eol=crlf", entryCRLF.attrs);
435 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
436 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
437 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
438 	}
439 
440 	@Test
441 	public void test_ConfigAutoCRLF_false_GlobalEOL_crlf() throws Exception {
442 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt eol=crlf", null, null);
443 		collectRepositoryState();
444 		assertEquals("eol=crlf", entryCRLF.attrs);
445 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
446 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
447 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
448 	}
449 
450 	@Test
451 	public void test_ConfigAutoCRLF_input_GlobalEOL_crlf() throws Exception {
452 		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt eol=crlf", null, null);
453 		collectRepositoryState();
454 		assertEquals("eol=crlf", entryCRLF.attrs);
455 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
456 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
457 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
458 	}
459 
460 	@Test
461 	public void test_ConfigAutoCRLF_true_GlobalEOL_lf_InfoEOL_crlf()
462 			throws Exception {
463 		setupGitAndDoHardReset(AutoCRLF.TRUE, null, "*.txt eol=lf", "*.txt eol=crlf", null);
464 		// info decides
465 		collectRepositoryState();
466 		assertEquals("eol=crlf", entryCRLF.attrs);
467 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
468 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
469 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
470 	}
471 
472 	@Test
473 	public void test_ConfigAutoCRLF_false_GlobalEOL_crlf_InfoEOL_lf()
474 			throws Exception {
475 		setupGitAndDoHardReset(AutoCRLF.FALSE, null, "*.txt eol=crlf", "*.txt eol=lf", null);
476 		// info decides
477 		collectRepositoryState();
478 		assertEquals("eol=lf", entryCRLF.attrs);
479 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
480 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
481 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
482 	}
483 
484 	@Test
485 	public void test_GlobalEOL_lf_RootEOL_crlf() throws Exception {
486 		setupGitAndDoHardReset(null, null, "*.txt eol=lf", null, "*.txt eol=crlf");
487 		// root over global
488 		collectRepositoryState();
489 		assertEquals("eol=crlf", entryCRLF.attrs);
490 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
491 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
492 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
493 	}
494 
495 	@Test
496 	public void test_GlobalEOL_lf_InfoEOL_crlf_RootEOL_lf() throws Exception {
497 		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt eol=crlf", "*.txt eol=lf");
498 		// info overrides all
499 		collectRepositoryState();
500 		assertEquals("eol=crlf", entryCRLF.attrs);
501 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
502 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
503 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
504 	}
505 
506 	@Test
507 	public void test_GlobalEOL_lf_InfoEOL_crlf_RootEOL_unspec()
508 			throws Exception {
509 		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt eol=crlf",
510 				"*.txt text !eol");
511 		// info overrides all
512 		collectRepositoryState();
513 		assertEquals("eol=crlf text", entryCRLF.attrs);
514 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
515 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
516 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
517 	}
518 
519 	@Test
520 	public void test_GlobalEOL_lf_InfoEOL_unspec_RootEOL_crlf()
521 			throws Exception {
522 		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt !eol",
523 				"*.txt text eol=crlf");
524 		// info overrides all
525 		collectRepositoryState();
526 		assertEquals("text", entryCRLF.attrs);
527 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
528 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
529 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
530 	}
531 
532 	@Test
533 	public void testBinary1() throws Exception {
534 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.CRLF, "*.txt text", "*.txt binary",
535 				"*.txt eol=crlf");
536 		// info overrides all
537 		collectRepositoryState();
538 		assertEquals("binary -diff -merge -text eol=crlf", entryCRLF.attrs);
539 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
540 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
541 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
542 	}
543 
544 	@Test
545 	public void testBinary2() throws Exception {
546 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.CRLF, "*.txt text eol=crlf", null,
547 				"*.txt binary");
548 		// root over global
549 		collectRepositoryState();
550 		assertEquals("binary -diff -merge -text eol=crlf", entryCRLF.attrs);
551 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
552 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
553 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
554 	}
555 
556 	// create new repo with
557 	// global .gitattributes
558 	// info .git/config/info/.gitattributes
559 	// workdir root .gitattributes
560 	// text file lf.txt CONTENT_LF
561 	// text file crlf.txt CONTENT_CRLF
562 	//
563 	// commit files (checkin)
564 	// delete working dir files
565 	// reset hard (checkout)
566 	private void setupGitAndDoHardReset(AutoCRLF autoCRLF, EOL eol,
567 			String globalAttributesContent, String infoAttributesContent,
568 			String workDirRootAttributesContent) throws Exception {
569 		Git git = new Git(db);
570 		FileBasedConfig config = db.getConfig();
571 		if (autoCRLF != null) {
572 			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
573 					ConfigConstants.CONFIG_KEY_AUTOCRLF, autoCRLF);
574 		}
575 		if (eol != null) {
576 			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
577 					ConfigConstants.CONFIG_KEY_EOL, eol);
578 		}
579 		if (globalAttributesContent != null) {
580 			File f = new File(db.getDirectory(), "global/attrs");
581 			write(f, globalAttributesContent);
582 			config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
583 					ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE,
584 					f.getAbsolutePath());
585 
586 		}
587 		if (infoAttributesContent != null) {
588 			File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES);
589 			write(f, infoAttributesContent);
590 		}
591 		config.save();
592 
593 		if (workDirRootAttributesContent != null) {
594 			dotGitattributes = createAndAddFile(git,
595 					Constants.DOT_GIT_ATTRIBUTES, workDirRootAttributesContent);
596 		} else {
597 			dotGitattributes = null;
598 		}
599 
600 		fileCRLF = createAndAddFile(git, "file1.txt", "a");
601 
602 		fileLF = createAndAddFile(git, "file2.txt", "a");
603 
604 		fileMixed = createAndAddFile(git, "file3.txt", "a");
605 
606 		RevCommit c = gitCommit(git, "create files");
607 
608 		fileCRLF = createAndAddFile(git, "file1.txt", CONTENT_CRLF);
609 
610 		fileLF = createAndAddFile(git, "file2.txt", CONTENT_LF);
611 
612 		fileMixed = createAndAddFile(git, "file3.txt", CONTENT_MIXED);
613 
614 		gitCommit(git, "addFiles");
615 
616 		recreateWorktree(git);
617 
618 		if (smudge) {
619 			DirCache dc = DirCache.lock(git.getRepository().getIndexFile(),
620 					FS.detect());
621 			DirCacheEditor editor = dc.editor();
622 			for (int i = 0; i < dc.getEntryCount(); i++) {
623 				editor.add(new DirCacheEditor.PathEdit(
624 						dc.getEntry(i).getPathString()) {
625 					@Override
626 					public void apply(DirCacheEntry ent) {
627 						ent.smudgeRacilyClean();
628 					}
629 				});
630 			}
631 			editor.commit();
632 		}
633 
634 		// @TODO: find out why the following assertion would break the tests
635 		// assertTrue(git.status().call().isClean());
636 		git.checkout().setName(c.getName()).call();
637 		git.checkout().setName("master").call();
638 	}
639 
640 	private void recreateWorktree(Git git)
641 			throws GitAPIException, CheckoutConflictException,
642 			InterruptedException, IOException, NoFilepatternException {
643 		// re-create file from the repo
644 		for (File f : new File[] { dotGitattributes, fileCRLF, fileLF, fileMixed }) {
645 			if (f == null)
646 				continue;
647 			f.delete();
648 			Assert.assertFalse(f.exists());
649 		}
650 		gitResetHard(git);
651 		fsTick(db.getIndexFile());
652 		gitAdd(git, ".");
653 	}
654 
655 	protected RevCommit gitCommit(Git git, String msg) throws GitAPIException {
656 		return git.commit().setMessage(msg).call();
657 	}
658 
659 	protected void gitAdd(Git git, String path) throws GitAPIException {
660 		git.add().addFilepattern(path).call();
661 	}
662 
663 	protected void gitResetHard(Git git) throws GitAPIException {
664 		git.reset().setMode(ResetType.HARD).call();
665 	}
666 
667 	protected void gitCheckout(Git git, String revstr)
668 			throws GitAPIException, RevisionSyntaxException, IOException {
669 		git.checkout().setName(db.resolve(revstr).getName()).call();
670 	}
671 
672 	// create a file and add it to the repo
673 	private File createAndAddFile(Git git, String path, String content)
674 			throws Exception {
675 		File f;
676 		int pos = path.lastIndexOf('/');
677 		if (pos < 0) {
678 			f = writeTrashFile(path, content);
679 		} else {
680 			f = writeTrashFile(path.substring(0, pos), path.substring(pos + 1),
681 					content);
682 		}
683 		gitAdd(git, path);
684 		Assert.assertTrue(f.exists());
685 		return f;
686 	}
687 
688 	private void collectRepositoryState() throws Exception {
689 		dirCache = db.readDirCache();
690 		try (TreeWalk walk = new TreeWalk(db)) {
691 			walk.addTree(new FileTreeIterator(db));
692 			walk.addTree(new DirCacheIterator(db.readDirCache()));
693 			if (dotGitattributes != null) {
694 				collectEntryContentAndAttributes(walk, F, ".gitattributes",
695 						null);
696 			}
697 			collectEntryContentAndAttributes(walk, F, fileCRLF.getName(),
698 					entryCRLF);
699 			collectEntryContentAndAttributes(walk, F, fileLF.getName(),
700 					entryLF);
701 			collectEntryContentAndAttributes(walk, F, fileMixed.getName(),
702 					entryMixed);
703 			assertFalse("Not all files tested", walk.next());
704 		}
705 	}
706 
707 	private void collectEntryContentAndAttributes(TreeWalk walk, FileMode type,
708 			String pathName,
709 			ActualEntry e) throws IOException {
710 		assertTrue("walk has entry", walk.next());
711 
712 		assertEquals(pathName, walk.getPathString());
713 		assertEquals(type, walk.getFileMode(0));
714 
715 		if (e != null) {
716 			e.attrs = "";
717 			for (Attribute a : walk.getAttributes().getAll()) {
718 				e.attrs += " " + a.toString();
719 			}
720 			e.attrs = e.attrs.trim();
721 			e.file = new String(
722 					IO.readFully(new File(db.getWorkTree(), pathName)), UTF_8);
723 			DirCacheEntry dce = dirCache.getEntry(pathName);
724 			ObjectLoader open = walk.getObjectReader().open(dce.getObjectId());
725 			e.index = new String(open.getBytes(), UTF_8);
726 			e.indexContentLength = dce.getLength();
727 		}
728 
729 		if (D.equals(type))
730 			walk.enterSubtree();
731 	}
732 }