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 	private TreeWalk walk;
146 
147 	/** work tree root .gitattributes */
148 	private File dotGitattributes;
149 
150 	/** file containing CRLF */
151 	private File fileCRLF;
152 
153 	/** file containing LF */
154 	private File fileLF;
155 
156 	/** file containing mixed CRLF and LF */
157 	private File fileMixed;
158 
159 	/** this values are set in {@link #collectRepositoryState()} */
160 	private static class ActualEntry {
161 		private String attrs;
162 
163 		private String file;
164 
165 		private String index;
166 
167 		private int indexContentLength;
168 	}
169 
170 	private ActualEntry entryCRLF = new ActualEntry();
171 
172 	private ActualEntry entryLF = new ActualEntry();
173 
174 	private ActualEntry entryMixed = new ActualEntry();
175 
176 	private DirCache dirCache;
177 
178 	@Test
179 	public void testDefaultSetup() throws Exception {
180 		// for EOL to work, the text attribute must be set
181 		setupGitAndDoHardReset(null, null, null, null, "* text=auto");
182 		collectRepositoryState();
183 		assertEquals("text=auto", entryCRLF.attrs);
184 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
185 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
186 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
187 	}
188 
189 	public void checkEntryContent(ActualEntry entry, String fileContent,
190 			String indexContent) {
191 		assertEquals(fileContent, entry.file);
192 		assertEquals(indexContent, entry.index);
193 		if (entry.indexContentLength != 0) {
194 			assertEquals(fileContent.length(), entry.indexContentLength);
195 		}
196 	}
197 
198 	@Test
199 	public void test_ConfigAutoCRLF_false() throws Exception {
200 		// for EOL to work, the text attribute must be set
201 		setupGitAndDoHardReset(AutoCRLF.FALSE, 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_ConfigAutoCRLF_true() throws Exception {
211 		// for EOL to work, the text attribute must be set
212 		setupGitAndDoHardReset(AutoCRLF.TRUE, null, null, null, "* text=auto");
213 		collectRepositoryState();
214 		assertEquals("text=auto", entryCRLF.attrs);
215 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
216 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
217 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
218 	}
219 
220 	@Test
221 	public void test_ConfigAutoCRLF_input() throws Exception {
222 		// for EOL to work, the text attribute must be set
223 		setupGitAndDoHardReset(AutoCRLF.INPUT, null, null, null, "* text=auto");
224 		collectRepositoryState();
225 		assertEquals("text=auto", entryCRLF.attrs);
226 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
227 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
228 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
229 	}
230 
231 	@Test
232 	public void test_ConfigEOL_lf() throws Exception {
233 		// for EOL to work, the text attribute must be set
234 		setupGitAndDoHardReset(null, EOL.LF, "*.txt text", null, null);
235 		collectRepositoryState();
236 		assertEquals("text", entryCRLF.attrs);
237 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
238 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
239 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
240 	}
241 
242 	@Test
243 	public void test_ConfigEOL_crlf() throws Exception {
244 		// for EOL to work, the text attribute must be set
245 		setupGitAndDoHardReset(null, EOL.CRLF, "*.txt text", null, null);
246 		collectRepositoryState();
247 		assertEquals("text", entryCRLF.attrs);
248 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
249 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
250 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
251 	}
252 
253 	@Test
254 	public void test_ConfigEOL_native_windows() throws Exception {
255 		String origLineSeparator = System.getProperty("line.separator", "\n");
256 		System.setProperty("line.separator", "\r\n");
257 		try {
258 			// for EOL to work, the text attribute must be set
259 			setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
260 			collectRepositoryState();
261 			assertEquals("text", entryCRLF.attrs);
262 			checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
263 			checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
264 		} finally {
265 			System.setProperty("line.separator", origLineSeparator);
266 		}
267 	}
268 
269 	@Test
270 	public void test_ConfigEOL_native_xnix() throws Exception {
271 		String origLineSeparator = System.getProperty("line.separator", "\n");
272 		System.setProperty("line.separator", "\n");
273 		try {
274 			// for EOL to work, the text attribute must be set
275 			setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
276 			collectRepositoryState();
277 			assertEquals("text", entryCRLF.attrs);
278 			checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
279 			checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
280 		} finally {
281 			System.setProperty("line.separator", origLineSeparator);
282 		}
283 	}
284 
285 	@Test
286 	public void test_ConfigAutoCRLF_false_ConfigEOL_lf() throws Exception {
287 		// for EOL to work, the text attribute must be set
288 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt text", null, null);
289 		collectRepositoryState();
290 		assertEquals("text", entryCRLF.attrs);
291 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
292 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
293 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
294 	}
295 
296 	@Test
297 	public void test_ConfigAutoCRLF_false_ConfigEOL_native() throws Exception {
298 		// for EOL to work, the text attribute must be set
299 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.NATIVE, "*.txt text", null, null);
300 		collectRepositoryState();
301 		assertEquals("text", entryCRLF.attrs);
302 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
303 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
304 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
305 	}
306 
307 	@Test
308 	public void test_ConfigAutoCRLF_true_ConfigEOL_lf() throws Exception {
309 		// for EOL to work, the text attribute must be set
310 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt text", null, null);
311 		collectRepositoryState();
312 		assertEquals("text", entryCRLF.attrs);
313 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
314 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
315 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
316 	}
317 
318 	@Test
319 	public void test_switchToBranchWithTextAttributes()
320 			throws Exception {
321 		Git git = Git.wrap(db);
322 
323 		// for EOL to work, the text attribute must be set
324 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.CRLF, null, null,
325 				"file1.txt text\nfile2.txt text\nfile3.txt text");
326 		collectRepositoryState();
327 		assertEquals("text", entryCRLF.attrs);
328 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
329 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
330 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
331 
332 		// switch to binary for file1
333 		dotGitattributes = createAndAddFile(git, Constants.DOT_GIT_ATTRIBUTES,
334 				"file1.txt binary\nfile2.txt text\nfile3.txt text");
335 		gitCommit(git, "switchedToBinaryFor1");
336 		recreateWorktree(git);
337 		collectRepositoryState();
338 		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
339 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
340 		assertEquals("text", entryLF.attrs);
341 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
342 		assertEquals("text", entryMixed.attrs);
343 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
344 
345 		// checkout the commit which has text for file1
346 		gitCheckout(git, "HEAD^");
347 		recreateWorktree(git);
348 		collectRepositoryState();
349 		assertEquals("text", entryCRLF.attrs);
350 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
351 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
352 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
353 	}
354 
355 	@Test
356 	public void test_switchToBranchWithBinaryAttributes() throws Exception {
357 		Git git = Git.wrap(db);
358 
359 		// for EOL to work, the text attribute must be set
360 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, null, null,
361 				"file1.txt binary\nfile2.txt binary\nfile3.txt binary");
362 		collectRepositoryState();
363 		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
364 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
365 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
366 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
367 
368 		// switch to text for file1
369 		dotGitattributes = createAndAddFile(git, Constants.DOT_GIT_ATTRIBUTES,
370 				"file1.txt text\nfile2.txt binary\nfile3.txt binary");
371 		gitCommit(git, "switchedToTextFor1");
372 		recreateWorktree(git);
373 		collectRepositoryState();
374 		assertEquals("text", entryCRLF.attrs);
375 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
376 		assertEquals("binary -diff -merge -text", entryLF.attrs);
377 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
378 		assertEquals("binary -diff -merge -text", entryMixed.attrs);
379 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
380 
381 		// checkout the commit which has text for file1
382 		gitCheckout(git, "HEAD^");
383 		recreateWorktree(git);
384 		collectRepositoryState();
385 		assertEquals("binary -diff -merge -text", entryCRLF.attrs);
386 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
387 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
388 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
389 	}
390 
391 	@Test
392 	public void test_ConfigAutoCRLF_input_ConfigEOL_lf() throws Exception {
393 		// for EOL to work, the text attribute must be set
394 		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt text", null, null);
395 		collectRepositoryState();
396 		assertEquals("text", entryCRLF.attrs);
397 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
398 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
399 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
400 	}
401 
402 	@Test
403 	public void test_ConfigAutoCRLF_true_GlobalEOL_lf() throws Exception {
404 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt eol=lf", null, null);
405 		collectRepositoryState();
406 		assertEquals("eol=lf", entryCRLF.attrs);
407 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
408 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
409 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
410 	}
411 
412 	@Test
413 	public void test_ConfigAutoCRLF_false_GlobalEOL_lf() throws Exception {
414 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt eol=lf", null, null);
415 		collectRepositoryState();
416 		assertEquals("eol=lf", entryCRLF.attrs);
417 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
418 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
419 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
420 	}
421 
422 	@Test
423 	public void test_ConfigAutoCRLF_input_GlobalEOL_lf() throws Exception {
424 		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt eol=lf", null, null);
425 		collectRepositoryState();
426 		assertEquals("eol=lf", entryCRLF.attrs);
427 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
428 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
429 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
430 	}
431 
432 	@Test
433 	public void test_ConfigAutoCRLF_true_GlobalEOL_crlf() throws Exception {
434 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.LF, "*.txt eol=crlf", null, null);
435 		collectRepositoryState();
436 		assertEquals("eol=crlf", entryCRLF.attrs);
437 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
438 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
439 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
440 	}
441 
442 	@Test
443 	public void test_ConfigAutoCRLF_false_GlobalEOL_crlf() throws Exception {
444 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.LF, "*.txt eol=crlf", null, null);
445 		collectRepositoryState();
446 		assertEquals("eol=crlf", entryCRLF.attrs);
447 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
448 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
449 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
450 	}
451 
452 	@Test
453 	public void test_ConfigAutoCRLF_input_GlobalEOL_crlf() throws Exception {
454 		setupGitAndDoHardReset(AutoCRLF.INPUT, EOL.LF, "*.txt eol=crlf", null, null);
455 		collectRepositoryState();
456 		assertEquals("eol=crlf", entryCRLF.attrs);
457 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
458 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
459 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
460 	}
461 
462 	@Test
463 	public void test_ConfigAutoCRLF_true_GlobalEOL_lf_InfoEOL_crlf()
464 			throws Exception {
465 		setupGitAndDoHardReset(AutoCRLF.TRUE, null, "*.txt eol=lf", "*.txt eol=crlf", null);
466 		// info decides
467 		collectRepositoryState();
468 		assertEquals("eol=crlf", entryCRLF.attrs);
469 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
470 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
471 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
472 	}
473 
474 	@Test
475 	public void test_ConfigAutoCRLF_false_GlobalEOL_crlf_InfoEOL_lf()
476 			throws Exception {
477 		setupGitAndDoHardReset(AutoCRLF.FALSE, null, "*.txt eol=crlf", "*.txt eol=lf", null);
478 		// info decides
479 		collectRepositoryState();
480 		assertEquals("eol=lf", entryCRLF.attrs);
481 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
482 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
483 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
484 	}
485 
486 	@Test
487 	public void test_GlobalEOL_lf_RootEOL_crlf() throws Exception {
488 		setupGitAndDoHardReset(null, null, "*.txt eol=lf", null, "*.txt eol=crlf");
489 		// root over global
490 		collectRepositoryState();
491 		assertEquals("eol=crlf", entryCRLF.attrs);
492 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
493 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
494 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
495 	}
496 
497 	@Test
498 	public void test_GlobalEOL_lf_InfoEOL_crlf_RootEOL_lf() throws Exception {
499 		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt eol=crlf", "*.txt eol=lf");
500 		// info overrides all
501 		collectRepositoryState();
502 		assertEquals("eol=crlf", entryCRLF.attrs);
503 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
504 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
505 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
506 	}
507 
508 	@Test
509 	public void test_GlobalEOL_lf_InfoEOL_crlf_RootEOL_unspec()
510 			throws Exception {
511 		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt eol=crlf",
512 				"*.txt text !eol");
513 		// info overrides all
514 		collectRepositoryState();
515 		assertEquals("eol=crlf text", entryCRLF.attrs);
516 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
517 		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
518 		checkEntryContent(entryMixed, CONTENT_CRLF, CONTENT_LF);
519 	}
520 
521 	@Test
522 	public void test_GlobalEOL_lf_InfoEOL_unspec_RootEOL_crlf()
523 			throws Exception {
524 		setupGitAndDoHardReset(null, null, "*.txt eol=lf", "*.txt !eol",
525 				"*.txt text eol=crlf");
526 		// info overrides all
527 		collectRepositoryState();
528 		assertEquals("text", entryCRLF.attrs);
529 		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
530 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
531 		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
532 	}
533 
534 	@Test
535 	public void testBinary1() throws Exception {
536 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.CRLF, "*.txt text", "*.txt binary",
537 				"*.txt eol=crlf");
538 		// info overrides all
539 		collectRepositoryState();
540 		assertEquals("binary -diff -merge -text eol=crlf", entryCRLF.attrs);
541 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
542 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
543 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
544 	}
545 
546 	@Test
547 	public void testBinary2() throws Exception {
548 		setupGitAndDoHardReset(AutoCRLF.TRUE, EOL.CRLF, "*.txt text eol=crlf", null,
549 				"*.txt binary");
550 		// root over global
551 		collectRepositoryState();
552 		assertEquals("binary -diff -merge -text eol=crlf", entryCRLF.attrs);
553 		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_CRLF);
554 		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
555 		checkEntryContent(entryMixed, CONTENT_MIXED, CONTENT_MIXED);
556 	}
557 
558 	// create new repo with
559 	// global .gitattributes
560 	// info .git/config/info/.gitattributes
561 	// workdir root .gitattributes
562 	// text file lf.txt CONTENT_LF
563 	// text file crlf.txt CONTENT_CRLF
564 	//
565 	// commit files (checkin)
566 	// delete working dir files
567 	// reset hard (checkout)
568 	private void setupGitAndDoHardReset(AutoCRLF autoCRLF, EOL eol,
569 			String globalAttributesContent, String infoAttributesContent,
570 			String workDirRootAttributesContent) throws Exception {
571 		Git git = new Git(db);
572 		FileBasedConfig config = db.getConfig();
573 		if (autoCRLF != null) {
574 			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
575 					ConfigConstants.CONFIG_KEY_AUTOCRLF, autoCRLF);
576 		}
577 		if (eol != null) {
578 			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
579 					ConfigConstants.CONFIG_KEY_EOL, eol);
580 		}
581 		if (globalAttributesContent != null) {
582 			File f = new File(db.getDirectory(), "global/attrs");
583 			write(f, globalAttributesContent);
584 			config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
585 					ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE,
586 					f.getAbsolutePath());
587 
588 		}
589 		if (infoAttributesContent != null) {
590 			File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES);
591 			write(f, infoAttributesContent);
592 		}
593 		config.save();
594 
595 		if (workDirRootAttributesContent != null) {
596 			dotGitattributes = createAndAddFile(git,
597 					Constants.DOT_GIT_ATTRIBUTES, workDirRootAttributesContent);
598 		} else {
599 			dotGitattributes = null;
600 		}
601 
602 		fileCRLF = createAndAddFile(git, "file1.txt", "a");
603 
604 		fileLF = createAndAddFile(git, "file2.txt", "a");
605 
606 		fileMixed = createAndAddFile(git, "file3.txt", "a");
607 
608 		RevCommit c = gitCommit(git, "create files");
609 
610 		fileCRLF = createAndAddFile(git, "file1.txt", CONTENT_CRLF);
611 
612 		fileLF = createAndAddFile(git, "file2.txt", CONTENT_LF);
613 
614 		fileMixed = createAndAddFile(git, "file3.txt", CONTENT_MIXED);
615 
616 		gitCommit(git, "addFiles");
617 
618 		recreateWorktree(git);
619 
620 		if (smudge) {
621 			DirCache dc = DirCache.lock(git.getRepository().getIndexFile(),
622 					FS.detect());
623 			DirCacheEditor editor = dc.editor();
624 			for (int i = 0; i < dc.getEntryCount(); i++) {
625 				editor.add(new DirCacheEditor.PathEdit(
626 						dc.getEntry(i).getPathString()) {
627 					@Override
628 					public void apply(DirCacheEntry ent) {
629 						ent.smudgeRacilyClean();
630 					}
631 				});
632 			}
633 			editor.commit();
634 		}
635 
636 		// @TODO: find out why the following assertion would break the tests
637 		// assertTrue(git.status().call().isClean());
638 		git.checkout().setName(c.getName()).call();
639 		git.checkout().setName("master").call();
640 	}
641 
642 	private void recreateWorktree(Git git)
643 			throws GitAPIException, CheckoutConflictException,
644 			InterruptedException, IOException, NoFilepatternException {
645 		// re-create file from the repo
646 		for (File f : new File[] { dotGitattributes, fileCRLF, fileLF, fileMixed }) {
647 			if (f == null)
648 				continue;
649 			f.delete();
650 			Assert.assertFalse(f.exists());
651 		}
652 		gitResetHard(git);
653 		fsTick(db.getIndexFile());
654 		gitAdd(git, ".");
655 	}
656 
657 	protected RevCommit gitCommit(Git git, String msg) throws GitAPIException {
658 		return git.commit().setMessage(msg).call();
659 	}
660 
661 	protected void gitAdd(Git git, String path) throws GitAPIException {
662 		git.add().addFilepattern(path).call();
663 	}
664 
665 	protected void gitResetHard(Git git) throws GitAPIException {
666 		git.reset().setMode(ResetType.HARD).call();
667 	}
668 
669 	protected void gitCheckout(Git git, String revstr)
670 			throws GitAPIException, RevisionSyntaxException, IOException {
671 		git.checkout().setName(db.resolve(revstr).getName()).call();
672 	}
673 
674 	// create a file and add it to the repo
675 	private File createAndAddFile(Git git, String path, String content)
676 			throws Exception {
677 		File f;
678 		int pos = path.lastIndexOf('/');
679 		if (pos < 0) {
680 			f = writeTrashFile(path, content);
681 		} else {
682 			f = writeTrashFile(path.substring(0, pos), path.substring(pos + 1),
683 					content);
684 		}
685 		gitAdd(git, path);
686 		Assert.assertTrue(f.exists());
687 		return f;
688 	}
689 
690 	private void collectRepositoryState() throws Exception {
691 		dirCache = db.readDirCache();
692 		walk = beginWalk();
693 		if (dotGitattributes != null)
694 			collectEntryContentAndAttributes(F, ".gitattributes", null);
695 		collectEntryContentAndAttributes(F, fileCRLF.getName(), entryCRLF);
696 		collectEntryContentAndAttributes(F, fileLF.getName(), entryLF);
697 		collectEntryContentAndAttributes(F, fileMixed.getName(), entryMixed);
698 		endWalk();
699 	}
700 
701 	private TreeWalk beginWalk() throws Exception {
702 		TreeWalk newWalk = new TreeWalk(db);
703 		newWalk.addTree(new FileTreeIterator(db));
704 		newWalk.addTree(new DirCacheIterator(db.readDirCache()));
705 		return newWalk;
706 	}
707 
708 	private void endWalk() throws IOException {
709 		assertFalse("Not all files tested", walk.next());
710 	}
711 
712 	private void collectEntryContentAndAttributes(FileMode type, String pathName,
713 			ActualEntry e) throws IOException {
714 		assertTrue("walk has entry", walk.next());
715 
716 		assertEquals(pathName, walk.getPathString());
717 		assertEquals(type, walk.getFileMode(0));
718 
719 		if (e != null) {
720 			e.attrs = "";
721 			for (Attribute a : walk.getAttributes().getAll()) {
722 				e.attrs += " " + a.toString();
723 			}
724 			e.attrs = e.attrs.trim();
725 			e.file = new String(
726 					IO.readFully(new File(db.getWorkTree(), pathName)), UTF_8);
727 			DirCacheEntry dce = dirCache.getEntry(pathName);
728 			ObjectLoader open = walk.getObjectReader().open(dce.getObjectId());
729 			e.index = new String(open.getBytes(), UTF_8);
730 			e.indexContentLength = dce.getLength();
731 		}
732 
733 		if (D.equals(type))
734 			walk.enterSubtree();
735 	}
736 }