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