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