View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2008-2011, Shawn O. Pearce <spearce@spearce.org>
4    * Copyright (C) 2008-2011, Robin Rosenberg <robin.rosenberg@dewire.com>
5    * Copyright (C) 2010, 2020 Christian Halstrick <christian.halstrick@sap.com> and others
6    *
7    * This program and the accompanying materials are made available under the
8    * terms of the Eclipse Distribution License v. 1.0 which is available at
9    * https://www.eclipse.org/org/documents/edl-v10.php.
10   *
11   * SPDX-License-Identifier: BSD-3-Clause
12   */
13  package org.eclipse.jgit.lib;
14  
15  import static java.nio.charset.StandardCharsets.UTF_8;
16  import static org.junit.Assert.assertArrayEquals;
17  import static org.junit.Assert.assertEquals;
18  import static org.junit.Assert.assertFalse;
19  import static org.junit.Assert.assertNotNull;
20  import static org.junit.Assert.assertTrue;
21  import static org.junit.Assert.fail;
22  
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.util.Arrays;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.eclipse.jgit.api.CheckoutCommand;
32  import org.eclipse.jgit.api.CheckoutResult;
33  import org.eclipse.jgit.api.Git;
34  import org.eclipse.jgit.api.MergeResult.MergeStatus;
35  import org.eclipse.jgit.api.ResetCommand.ResetType;
36  import org.eclipse.jgit.api.Status;
37  import org.eclipse.jgit.api.errors.GitAPIException;
38  import org.eclipse.jgit.api.errors.NoFilepatternException;
39  import org.eclipse.jgit.dircache.DirCache;
40  import org.eclipse.jgit.dircache.DirCacheCheckout;
41  import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
42  import org.eclipse.jgit.dircache.DirCacheEditor;
43  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
44  import org.eclipse.jgit.dircache.DirCacheEntry;
45  import org.eclipse.jgit.errors.CheckoutConflictException;
46  import org.eclipse.jgit.errors.CorruptObjectException;
47  import org.eclipse.jgit.errors.NoWorkTreeException;
48  import org.eclipse.jgit.events.ChangeRecorder;
49  import org.eclipse.jgit.events.ListenerHandle;
50  import org.eclipse.jgit.junit.RepositoryTestCase;
51  import org.eclipse.jgit.junit.TestRepository;
52  import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
53  import org.eclipse.jgit.revwalk.RevCommit;
54  import org.eclipse.jgit.treewalk.AbstractTreeIterator;
55  import org.eclipse.jgit.treewalk.FileTreeIterator;
56  import org.eclipse.jgit.treewalk.TreeWalk;
57  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
58  import org.eclipse.jgit.util.FS;
59  import org.eclipse.jgit.util.FileUtils;
60  import org.eclipse.jgit.util.StringUtils;
61  import org.junit.Assume;
62  import org.junit.Test;
63  
64  public class DirCacheCheckoutTest extends RepositoryTestCase {
65  	private DirCacheCheckout dco;
66  	protected ObjectId theHead;
67  	protected ObjectId theMerge;
68  	private DirCache dirCache;
69  
70  	private void prescanTwoTrees(ObjectId head, ObjectId merge)
71  			throws IllegalStateException, IOException {
72  		DirCache dc = db.lockDirCache();
73  		try {
74  			dco = new DirCacheCheckout(db, head, dc, merge);
75  			dco.preScanTwoTrees();
76  		} finally {
77  			dc.unlock();
78  		}
79  	}
80  
81  	private void checkout() throws IOException {
82  		DirCache dc = db.lockDirCache();
83  		try {
84  			dco = new DirCacheCheckout(db, theHead, dc, theMerge);
85  			dco.checkout();
86  		} finally {
87  			dc.unlock();
88  		}
89  	}
90  
91  	private List<String> getRemoved() {
92  		return dco.getRemoved();
93  	}
94  
95  	private Map<String, CheckoutMetadata> getUpdated() {
96  		return dco.getUpdated();
97  	}
98  
99  	private List<String> getConflicts() {
100 		return dco.getConflicts();
101 	}
102 
103 	private static HashMap<String, String> mk(String a) {
104 		return mkmap(a, a);
105 	}
106 
107 	private static HashMap<String, String> mkmap(String... args) {
108 		if ((args.length % 2) > 0)
109 			throw new IllegalArgumentException("needs to be pairs");
110 
111 		HashMap<String, String> map = new HashMap<>();
112 		for (int i = 0; i < args.length; i += 2) {
113 			map.put(args[i], args[i + 1]);
114 		}
115 
116 		return map;
117 	}
118 
119 	@Test
120 	public void testResetHard() throws IOException, NoFilepatternException,
121 			GitAPIException {
122 		ChangeRecorder recorder = new ChangeRecorder();
123 		ListenerHandle handle = null;
124 		try (Git git = new Git(db)) {
125 			handle = db.getListenerList()
126 					.addWorkingTreeModifiedListener(recorder);
127 			writeTrashFile("f", "f()");
128 			writeTrashFile("D/g", "g()");
129 			git.add().addFilepattern(".").call();
130 			git.commit().setMessage("inital").call();
131 			assertIndex(mkmap("f", "f()", "D/g", "g()"));
132 			recorder.assertNoEvent();
133 			git.branchCreate().setName("topic").call();
134 			recorder.assertNoEvent();
135 
136 			writeTrashFile("f", "f()\nmaster");
137 			writeTrashFile("D/g", "g()\ng2()");
138 			writeTrashFile("E/h", "h()");
139 			git.add().addFilepattern(".").call();
140 			RevCommit master = git.commit().setMessage("master-1").call();
141 			assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
142 			recorder.assertNoEvent();
143 
144 			checkoutBranch("refs/heads/topic");
145 			assertIndex(mkmap("f", "f()", "D/g", "g()"));
146 			recorder.assertEvent(new String[] { "f", "D/g" },
147 					new String[] { "E/h" });
148 
149 			writeTrashFile("f", "f()\nside");
150 			assertTrue(new File(db.getWorkTree(), "D/g").delete());
151 			writeTrashFile("G/i", "i()");
152 			git.add().addFilepattern(".").call();
153 			git.add().addFilepattern(".").setUpdate(true).call();
154 			RevCommit topic = git.commit().setMessage("topic-1").call();
155 			assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
156 			recorder.assertNoEvent();
157 
158 			writeTrashFile("untracked", "untracked");
159 
160 			resetHard(master);
161 			assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
162 			recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
163 					new String[] { "G", "G/i" });
164 
165 			resetHard(topic);
166 			assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
167 			assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked",
168 					"untracked"));
169 			recorder.assertEvent(new String[] { "f", "G/i" },
170 					new String[] { "D", "D/g", "E", "E/h" });
171 
172 			assertEquals(MergeStatus.CONFLICTING, git.merge().include(master)
173 					.call().getMergeStatus());
174 			assertEquals(
175 					"[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]",
176 					indexState(0));
177 			recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
178 					ChangeRecorder.EMPTY);
179 
180 			resetHard(master);
181 			assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
182 			assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h",
183 					"h()", "untracked", "untracked"));
184 			recorder.assertEvent(new String[] { "f", "D/g" },
185 					new String[] { "G", "G/i" });
186 
187 		} finally {
188 			if (handle != null) {
189 				handle.remove();
190 			}
191 		}
192 	}
193 
194 	/**
195 	 * Reset hard from unclean condition.
196 	 * <p>
197 	 * WorkDir: Empty <br>
198 	 * Index: f/g <br>
199 	 * Merge: x
200 	 *
201 	 * @throws Exception
202 	 */
203 	@Test
204 	public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
205 			throws Exception {
206 		ChangeRecorder recorder = new ChangeRecorder();
207 		ListenerHandle handle = null;
208 		try (Git git = new Git(db)) {
209 			handle = db.getListenerList()
210 					.addWorkingTreeModifiedListener(recorder);
211 			writeTrashFile("x", "x");
212 			git.add().addFilepattern("x").call();
213 			RevCommit id1 = git.commit().setMessage("c1").call();
214 
215 			writeTrashFile("f/g", "f/g");
216 			git.rm().addFilepattern("x").call();
217 			recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "x" });
218 			git.add().addFilepattern("f/g").call();
219 			git.commit().setMessage("c2").call();
220 			deleteTrashFile("f/g");
221 			deleteTrashFile("f");
222 
223 			// The actual test
224 			git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call();
225 			assertIndex(mkmap("x", "x"));
226 			recorder.assertEvent(new String[] { "x" }, ChangeRecorder.EMPTY);
227 		} finally {
228 			if (handle != null) {
229 				handle.remove();
230 			}
231 		}
232 	}
233 
234 	/**
235 	 * Test first checkout in a repo
236 	 *
237 	 * @throws Exception
238 	 */
239 	@Test
240 	public void testInitialCheckout() throws Exception {
241 		ChangeRecorder recorder = new ChangeRecorder();
242 		ListenerHandle handle = null;
243 		try (Git git = new Git(db);
244 				TestRepository<Repository> db_t = new TestRepository<>(db)) {
245 			handle = db.getListenerList()
246 					.addWorkingTreeModifiedListener(recorder);
247 			BranchBuilder master = db_t.branch("master");
248 			master.commit().add("f", "1").message("m0").create();
249 			assertFalse(new File(db.getWorkTree(), "f").exists());
250 			git.checkout().setName("master").call();
251 			assertTrue(new File(db.getWorkTree(), "f").exists());
252 			recorder.assertEvent(new String[] { "f" }, ChangeRecorder.EMPTY);
253 		} finally {
254 			if (handle != null) {
255 				handle.remove();
256 			}
257 		}
258 	}
259 
260 	private void checkoutLineEndings(String inIndex, String expected,
261 			String attributes) throws Exception {
262 		try (Git git = new Git(db);
263 				TestRepository<Repository> db_t = new TestRepository<>(db)) {
264 			BranchBuilder master = db_t.branch("master");
265 			master.commit().add("f", inIndex).message("m0").create();
266 			if (!StringUtils.isEmptyOrNull(attributes)) {
267 				master.commit().add(".gitattributes", attributes)
268 						.message("attributes").create();
269 			}
270 			File f = new File(db.getWorkTree(), "f");
271 			assertFalse(f.exists());
272 			git.checkout().setName("master").call();
273 			assertTrue(f.exists());
274 			checkFile(f, expected);
275 		}
276 	}
277 
278 	@Test
279 	public void testCheckoutWithCRLF() throws Exception {
280 		checkoutLineEndings("first line\r\nsecond line\r\n",
281 				"first line\r\nsecond line\r\n", null);
282 	}
283 
284 	@Test
285 	public void testCheckoutWithCRLFAuto() throws Exception {
286 		checkoutLineEndings("first line\r\nsecond line\r\n",
287 				"first line\r\nsecond line\r\n", "f text=auto");
288 	}
289 
290 	@Test
291 	public void testCheckoutWithCRLFAutoEolLf() throws Exception {
292 		checkoutLineEndings("first line\r\nsecond line\r\n",
293 				"first line\r\nsecond line\r\n", "f text=auto eol=lf");
294 	}
295 
296 	@Test
297 	public void testCheckoutWithCRLFAutoEolNative() throws Exception {
298 		checkoutLineEndings("first line\r\nsecond line\r\n",
299 				"first line\r\nsecond line\r\n", "f text=auto eol=native");
300 	}
301 
302 	@Test
303 	public void testCheckoutWithCRLFAutoEolCrLf() throws Exception {
304 		checkoutLineEndings("first line\r\nsecond line\r\n",
305 				"first line\r\nsecond line\r\n", "f text=auto eol=crlf");
306 	}
307 
308 	@Test
309 	public void testCheckoutWithLF() throws Exception {
310 		checkoutLineEndings("first line\nsecond line\n",
311 				"first line\nsecond line\n", null);
312 	}
313 
314 	@Test
315 	public void testCheckoutWithLFAuto() throws Exception {
316 		checkoutLineEndings("first line\nsecond line\n",
317 				"first line\nsecond line\n", "f text=auto");
318 	}
319 
320 	@Test
321 	public void testCheckoutWithLFAutoEolLf() throws Exception {
322 		checkoutLineEndings("first line\nsecond line\n",
323 				"first line\nsecond line\n", "f text=auto eol=lf");
324 	}
325 
326 	@Test
327 	public void testCheckoutWithLFAutoEolNative() throws Exception {
328 		checkoutLineEndings(
329 				"first line\nsecond line\n", "first line\nsecond line\n"
330 						.replaceAll("\n", System.lineSeparator()),
331 				"f text=auto eol=native");
332 	}
333 
334 	@Test
335 	public void testCheckoutWithLFAutoEolCrLf() throws Exception {
336 		checkoutLineEndings("first line\nsecond line\n",
337 				"first line\r\nsecond line\r\n", "f text=auto eol=crlf");
338 	}
339 
340 	@Test
341 	public void testCheckoutMixedAutoEolCrLf() throws Exception {
342 		checkoutLineEndings("first line\nsecond line\r\n",
343 				"first line\nsecond line\r\n", "f text=auto eol=crlf");
344 	}
345 
346 	@Test
347 	public void testCheckoutMixedAutoEolLf() throws Exception {
348 		checkoutLineEndings("first line\nsecond line\r\n",
349 				"first line\nsecond line\r\n", "f text=auto eol=lf");
350 	}
351 
352 	@Test
353 	public void testCheckoutMixedTextCrLf() throws Exception {
354 		// Huh? Is this a bug in git? Both git 2.18.0 and git 2.33.0 do
355 		// write the file with CRLF (and consequently report the file as
356 		// modified in "git status" after check-out), however the CRLF in the
357 		// repository is _not_ replaced by LF with eol=lf (see test below).
358 		checkoutLineEndings("first line\nsecond line\r\n",
359 				"first line\r\nsecond line\r\n", "f text eol=crlf");
360 	}
361 
362 	@Test
363 	public void testCheckoutMixedTextLf() throws Exception {
364 		checkoutLineEndings("first line\nsecond line\r\nfoo",
365 				"first line\nsecond line\r\nfoo", "f text eol=lf");
366 	}
367 
368 	private DirCacheCheckout resetHard(RevCommit commit)
369 			throws NoWorkTreeException,
370 			CorruptObjectException, IOException {
371 		DirCacheCheckout dc;
372 		dc = new DirCacheCheckout(db, null, db.lockDirCache(),
373 				commit.getTree());
374 		dc.setFailOnConflict(true);
375 		assertTrue(dc.checkout());
376 		return dc;
377 	}
378 
379 	private void assertIndex(HashMap<String, String> i)
380 			throws CorruptObjectException, IOException {
381 		String expectedValue;
382 		String path;
383 		DirCache read = DirCache.read(db.getIndexFile(), db.getFS());
384 
385 		assertEquals("Index has not the right size.", i.size(),
386 				read.getEntryCount());
387 		for (int j = 0; j < read.getEntryCount(); j++) {
388 			path = read.getEntry(j).getPathString();
389 			expectedValue = i.get(path);
390 			assertNotNull("found unexpected entry for path " + path
391 					+ " in index", expectedValue);
392 			assertTrue("unexpected content for path " + path
393 					+ " in index. Expected: <" + expectedValue + ">",
394 					Arrays.equals(db.open(read.getEntry(j).getObjectId())
395 							.getCachedBytes(), i.get(path).getBytes(UTF_8)));
396 		}
397 	}
398 
399 	@Test
400 	public void testRules1thru3_NoIndexEntry() throws IOException {
401 		ObjectId head = buildTree(mk("foo"));
402 		ObjectId merge = db.newObjectInserter().insert(Constants.OBJ_TREE,
403 				new byte[0]);
404 
405 		prescanTwoTrees(head, merge);
406 
407 		assertTrue(getRemoved().contains("foo"));
408 
409 		prescanTwoTrees(merge, head);
410 
411 		assertTrue(getUpdated().containsKey("foo"));
412 
413 		merge = buildTree(mkmap("foo", "a"));
414 
415 		prescanTwoTrees(head, merge);
416 
417 		assertConflict("foo");
418 	}
419 
420 	void setupCase(HashMap<String, String> headEntries, HashMap<String, String> mergeEntries, HashMap<String, String> indexEntries) throws IOException {
421 		theHead = buildTree(headEntries);
422 		theMerge = buildTree(mergeEntries);
423 		buildIndex(indexEntries);
424 	}
425 
426 	private void buildIndex(HashMap<String, String> indexEntries) throws IOException {
427 		dirCache = new DirCache(db.getIndexFile(), db.getFS());
428 		if (indexEntries != null) {
429 			assertTrue(dirCache.lock());
430 			DirCacheEditor editor = dirCache.editor();
431 			for (java.util.Map.Entry<String,String> e : indexEntries.entrySet()) {
432 				writeTrashFile(e.getKey(), e.getValue());
433 				ObjectId id;
434 				try (ObjectInserter inserter = db.newObjectInserter()) {
435 					id = inserter.insert(Constants.OBJ_BLOB,
436 						Constants.encode(e.getValue()));
437 				}
438 				editor.add(new DirCacheEditor.DeletePath(e.getKey()));
439 				editor.add(new DirCacheEditor.PathEdit(e.getKey()) {
440 					@Override
441 					public void apply(DirCacheEntry ent) {
442 						ent.setFileMode(FileMode.REGULAR_FILE);
443 						ent.setObjectId(id);
444 						ent.setUpdateNeeded(false);
445 					}
446 				});
447 			}
448 			assertTrue(editor.commit());
449 		}
450 
451 	}
452 
453 	static final class AddEdit extends PathEdit {
454 
455 		private final ObjectId data;
456 
457 		private final long length;
458 
459 		public AddEdit(String entryPath, ObjectId data, long length) {
460 			super(entryPath);
461 			this.data = data;
462 			this.length = length;
463 		}
464 
465 		@Override
466 		public void apply(DirCacheEntry ent) {
467 			ent.setFileMode(FileMode.REGULAR_FILE);
468 			ent.setLength(length);
469 			ent.setObjectId(data);
470 		}
471 
472 	}
473 
474 	private ObjectId buildTree(HashMap<String, String> headEntries)
475 			throws IOException {
476 		DirCache lockDirCache = DirCache.newInCore();
477 		// assertTrue(lockDirCache.lock());
478 		DirCacheEditor editor = lockDirCache.editor();
479 		if (headEntries != null) {
480 			for (java.util.Map.Entry<String, String> e : headEntries.entrySet()) {
481 				AddEdit addEdit = new AddEdit(e.getKey(),
482 						genSha1(e.getValue()), e.getValue().length());
483 				editor.add(addEdit);
484 			}
485 		}
486 		editor.finish();
487 		return lockDirCache.writeTree(db.newObjectInserter());
488 	}
489 
490 	ObjectId genSha1(String data) {
491 		try (ObjectInserter w = db.newObjectInserter()) {
492 			ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes(UTF_8));
493 			w.flush();
494 			return id;
495 		} catch (IOException e) {
496 			fail(e.toString());
497 		}
498 		return null;
499 	}
500 
501 	protected void go() throws IllegalStateException, IOException {
502 		prescanTwoTrees(theHead, theMerge);
503 	}
504 
505 	@Test
506 	public void testRules4thru13_IndexEntryNotInHead() throws IOException {
507 		// rules 4 and 5
508 		HashMap<String, String> idxMap;
509 
510 		idxMap = new HashMap<>();
511 		idxMap.put("foo", "foo");
512 		setupCase(null, null, idxMap);
513 		go();
514 
515 		assertTrue(getUpdated().isEmpty());
516 		assertTrue(getRemoved().isEmpty());
517 		assertTrue(getConflicts().isEmpty());
518 
519 		// rules 6 and 7
520 		idxMap = new HashMap<>();
521 		idxMap.put("foo", "foo");
522 		setupCase(null, idxMap, idxMap);
523 		go();
524 
525 		assertAllEmpty();
526 
527 		// rules 8 and 9
528 		HashMap<String, String> mergeMap;
529 		mergeMap = new HashMap<>();
530 
531 		mergeMap.put("foo", "merge");
532 		setupCase(null, mergeMap, idxMap);
533 		go();
534 
535 		assertTrue(getUpdated().isEmpty());
536 		assertTrue(getRemoved().isEmpty());
537 		assertTrue(getConflicts().contains("foo"));
538 
539 		// rule 10
540 
541 		HashMap<String, String> headMap = new HashMap<>();
542 		headMap.put("foo", "foo");
543 		setupCase(headMap, null, idxMap);
544 		go();
545 
546 		assertTrue(getRemoved().contains("foo"));
547 		assertTrue(getUpdated().isEmpty());
548 		assertTrue(getConflicts().isEmpty());
549 
550 		// rule 11
551 		setupCase(headMap, null, idxMap);
552 		assertTrue(new File(trash, "foo").delete());
553 		writeTrashFile("foo", "bar");
554 		db.readDirCache().getEntry(0).setUpdateNeeded(true);
555 		go();
556 
557 		assertTrue(getRemoved().isEmpty());
558 		assertTrue(getUpdated().isEmpty());
559 		assertTrue(getConflicts().contains("foo"));
560 
561 		// rule 12 & 13
562 		headMap.put("foo", "head");
563 		setupCase(headMap, null, idxMap);
564 		go();
565 
566 		assertTrue(getRemoved().isEmpty());
567 		assertTrue(getUpdated().isEmpty());
568 		assertTrue(getConflicts().contains("foo"));
569 
570 		// rules 14 & 15
571 		setupCase(headMap, headMap, idxMap);
572 		go();
573 
574 		assertAllEmpty();
575 
576 		// rules 16 & 17
577 		setupCase(headMap, mergeMap, idxMap); go();
578 		assertTrue(getConflicts().contains("foo"));
579 
580 		// rules 18 & 19
581 		setupCase(headMap, idxMap, idxMap); go();
582 		assertAllEmpty();
583 
584 		// rule 20
585 		setupCase(idxMap, mergeMap, idxMap); go();
586 		assertTrue(getUpdated().containsKey("foo"));
587 
588 		// rules 21
589 		setupCase(idxMap, mergeMap, idxMap);
590 		assertTrue(new File(trash, "foo").delete());
591 		writeTrashFile("foo", "bar");
592 		db.readDirCache().getEntry(0).setUpdateNeeded(true);
593 		go();
594 		assertTrue(getConflicts().contains("foo"));
595 	}
596 
597 	private void assertAllEmpty() {
598 		assertTrue(getRemoved().isEmpty());
599 		assertTrue(getUpdated().isEmpty());
600 		assertTrue(getConflicts().isEmpty());
601 	}
602 
603 	/*-
604 	 * Directory/File Conflict cases:
605 	 * It's entirely possible that in practice a number of these may be equivalent
606 	 * to the cases described in git-read-tree.txt. As long as it does the right thing,
607 	 * that's all I care about. These are basically reverse-engineered from
608 	 * what git currently does. If there are tests for these in git, it's kind of
609 	 * hard to track them all down...
610 	 *
611 	 *     H        I       M     Clean     H==M     H==I    I==M         Result
612 	 *     ------------------------------------------------------------------
613 	 *1    D        D       F       Y         N       Y       N           Update
614 	 *2    D        D       F       N         N       Y       N           Conflict
615 	 *3    D        F       D                 Y       N       N           Keep
616 	 *4    D        F       D                 N       N       N           Conflict
617 	 *5    D        F       F       Y         N       N       Y           Keep
618 	 *5b   D        F       F       Y         N       N       N           Conflict
619 	 *6    D        F       F       N         N       N       Y           Keep
620 	 *6b   D        F       F       N         N       N       N           Conflict
621 	 *7    F        D       F       Y         Y       N       N           Update
622 	 *8    F        D       F       N         Y       N       N           Conflict
623 	 *9    F        D       F       Y         N       N       N           Update
624 	 *10   F        D       D                 N       N       Y           Keep
625 	 *11   F        D       D                 N       N       N           Conflict
626 	 *12   F        F       D       Y         N       Y       N           Update
627 	 *13   F        F       D       N         N       Y       N           Conflict
628 	 *14   F        F       D                 N       N       N           Conflict
629 	 *15   0        F       D                 N       N       N           Conflict
630 	 *16   0        D       F       Y         N       N       N           Update
631 	 *17   0        D       F                 N       N       N           Conflict
632 	 *18   F        0       D                                             Update
633 	 *19   D        0       F                                             Update
634 	 */
635 	@Test
636 	public void testDirectoryFileSimple() throws IOException {
637 		ObjectId treeDF = buildTree(mkmap("DF", "DF"));
638 		ObjectId treeDFDF = buildTree(mkmap("DF/DF", "DF/DF"));
639 		buildIndex(mkmap("DF", "DF"));
640 
641 		prescanTwoTrees(treeDF, treeDFDF);
642 
643 		assertTrue(getRemoved().contains("DF"));
644 		assertTrue(getUpdated().containsKey("DF/DF"));
645 
646 		recursiveDelete(new File(trash, "DF"));
647 		buildIndex(mkmap("DF/DF", "DF/DF"));
648 
649 		prescanTwoTrees(treeDFDF, treeDF);
650 		assertTrue(getRemoved().contains("DF/DF"));
651 		assertTrue(getUpdated().containsKey("DF"));
652 	}
653 
654 	@Test
655 	public void testDirectoryFileConflicts_1() throws Exception {
656 		// 1
657 		doit(mk("DF/DF"), mk("DF"), mk("DF/DF"));
658 		assertNoConflicts();
659 		assertUpdated("DF");
660 		assertRemoved("DF/DF");
661 	}
662 
663 	@Test
664 	public void testDirectoryFileConflicts_2() throws Exception {
665 		// 2
666 		setupCase(mk("DF/DF"), mk("DF"), mk("DF/DF"));
667 		writeTrashFile("DF/DF", "different");
668 		go();
669 		assertConflict("DF/DF");
670 
671 	}
672 
673 	@Test
674 	public void testDirectoryFileConflicts_3() throws Exception {
675 		// 3
676 		doit(mk("DF/DF"), mk("DF/DF"), mk("DF"));
677 		assertNoConflicts();
678 	}
679 
680 	@Test
681 	public void testDirectoryFileConflicts_4() throws Exception {
682 		// 4 (basically same as 3, just with H and M different)
683 		doit(mk("DF/DF"), mkmap("DF/DF", "foo"), mk("DF"));
684 		assertConflict("DF/DF");
685 
686 	}
687 
688 	@Test
689 	public void testDirectoryFileConflicts_5() throws Exception {
690 		// 5
691 		doit(mk("DF/DF"), mk("DF"), mk("DF"));
692 		assertRemoved("DF/DF");
693 		assertEquals(0, dco.getConflicts().size());
694 		assertEquals(0, dco.getUpdated().size());
695 	}
696 
697 	@Test
698 	public void testDirectoryFileConflicts_5b() throws Exception {
699 		// 5
700 		doit(mk("DF/DF"), mkmap("DF", "different"), mk("DF"));
701 		assertRemoved("DF/DF");
702 		assertConflict("DF");
703 		assertEquals(0, dco.getUpdated().size());
704 	}
705 
706 	@Test
707 	public void testDirectoryFileConflicts_6() throws Exception {
708 		// 6
709 		setupCase(mk("DF/DF"), mk("DF"), mk("DF"));
710 		writeTrashFile("DF", "different");
711 		go();
712 		assertRemoved("DF/DF");
713 		assertEquals(0, dco.getConflicts().size());
714 		assertEquals(0, dco.getUpdated().size());
715 	}
716 
717 	@Test
718 	public void testDirectoryFileConflicts_6b() throws Exception {
719 		// 6
720 		setupCase(mk("DF/DF"), mk("DF"), mkmap("DF", "different"));
721 		writeTrashFile("DF", "again different");
722 		go();
723 		assertRemoved("DF/DF");
724 		assertConflict("DF");
725 		assertEquals(0, dco.getUpdated().size());
726 	}
727 
728 	@Test
729 	public void testDirectoryFileConflicts_7() throws Exception {
730 		// 7
731 		doit(mk("DF"), mk("DF"), mk("DF/DF"));
732 		assertUpdated("DF");
733 		assertRemoved("DF/DF");
734 
735 		cleanUpDF();
736 		setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF"));
737 		go();
738 		assertRemoved("DF/DF/DF/DF/DF");
739 		assertUpdated("DF/DF");
740 
741 		cleanUpDF();
742 		setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF"));
743 		writeTrashFile("DF/DF/DF/DF/DF", "diff");
744 		go();
745 		assertConflict("DF/DF/DF/DF/DF");
746 
747 		// assertUpdated("DF/DF");
748 		// Why do we expect an update on DF/DF. H==M,
749 		// H&M are files and index contains a dir, index
750 		// is dirty: that case is not in the table but
751 		// we cannot update DF/DF to a file, this would
752 		// require that we delete DF/DF/DF/DF/DF in workdir
753 		// throwing away unsaved contents.
754 		// This test would fail in DirCacheCheckoutTests.
755 	}
756 
757 	@Test
758 	public void testDirectoryFileConflicts_8() throws Exception {
759 		// 8
760 		setupCase(mk("DF"), mk("DF"), mk("DF/DF"));
761 		recursiveDelete(new File(db.getWorkTree(), "DF"));
762 		writeTrashFile("DF", "xy");
763 		go();
764 		assertConflict("DF/DF");
765 	}
766 
767 	@Test
768 	public void testDirectoryFileConflicts_9() throws Exception {
769 		// 9
770 		doit(mkmap("DF", "QP"), mkmap("DF", "QP"), mkmap("DF/DF", "DF/DF"));
771 		assertRemoved("DF/DF");
772 		assertUpdated("DF");
773 	}
774 
775 	@Test
776 	public void testDirectoryFileConflicts_10() throws Exception {
777 		// 10
778 		cleanUpDF();
779 		doit(mk("DF"), mk("DF/DF"), mk("DF/DF"));
780 		assertNoConflicts();
781 	}
782 
783 	@Test
784 	public void testDirectoryFileConflicts_11() throws Exception {
785 		// 11
786 		doit(mk("DF"), mk("DF/DF"), mkmap("DF/DF", "asdf"));
787 		assertConflict("DF/DF");
788 	}
789 
790 	@Test
791 	public void testDirectoryFileConflicts_12() throws Exception {
792 		// 12
793 		cleanUpDF();
794 		doit(mk("DF"), mk("DF/DF"), mk("DF"));
795 		assertRemoved("DF");
796 		assertUpdated("DF/DF");
797 	}
798 
799 	@Test
800 	public void testDirectoryFileConflicts_13() throws Exception {
801 		// 13
802 		cleanUpDF();
803 		setupCase(mk("DF"), mk("DF/DF"), mk("DF"));
804 		writeTrashFile("DF", "asdfsdf");
805 		go();
806 		assertConflict("DF");
807 		assertUpdated("DF/DF");
808 	}
809 
810 	@Test
811 	public void testDirectoryFileConflicts_14() throws Exception {
812 		// 14
813 		cleanUpDF();
814 		doit(mk("DF"), mk("DF/DF"), mkmap("DF", "Foo"));
815 		assertConflict("DF");
816 		assertUpdated("DF/DF");
817 	}
818 
819 	@Test
820 	public void testDirectoryFileConflicts_15() throws Exception {
821 		// 15
822 		doit(mkmap(), mk("DF/DF"), mk("DF"));
823 
824 		// This test would fail in DirCacheCheckoutTests. I think this test is wrong,
825 		// it should check for conflicts according to rule 15
826 		// assertRemoved("DF");
827 
828 		assertUpdated("DF/DF");
829 	}
830 
831 	@Test
832 	public void testDirectoryFileConflicts_15b() throws Exception {
833 		// 15, take 2, just to check multi-leveled
834 		doit(mkmap(), mk("DF/DF/DF/DF"), mk("DF"));
835 
836 		// I think this test is wrong, it should
837 		// check for conflicts according to rule 15
838 		// This test would fail in DirCacheCheckouts
839 		// assertRemoved("DF");
840 
841 		assertUpdated("DF/DF/DF/DF");
842 	}
843 
844 	@Test
845 	public void testDirectoryFileConflicts_16() throws Exception {
846 		// 16
847 		cleanUpDF();
848 		doit(mkmap(), mk("DF"), mk("DF/DF/DF"));
849 		assertRemoved("DF/DF/DF");
850 		assertUpdated("DF");
851 	}
852 
853 	@Test
854 	public void testDirectoryFileConflicts_17() throws Exception {
855 		// 17
856 		cleanUpDF();
857 		setupCase(mkmap(), mk("DF"), mk("DF/DF/DF"));
858 		writeTrashFile("DF/DF/DF", "asdf");
859 		go();
860 		assertConflict("DF/DF/DF");
861 
862 		// Why do we expect an update on DF. If we really update
863 		// DF and update also the working tree we would have to
864 		// overwrite a dirty file in the work-tree DF/DF/DF
865 		// This test would fail in DirCacheCheckout
866 		// assertUpdated("DF");
867 	}
868 
869 	@Test
870 	public void testDirectoryFileConflicts_18() throws Exception {
871 		// 18
872 		cleanUpDF();
873 		doit(mk("DF/DF"), mk("DF/DF/DF/DF"), null);
874 		assertRemoved("DF/DF");
875 		assertUpdated("DF/DF/DF/DF");
876 	}
877 
878 	@Test
879 	public void testDirectoryFileConflicts_19() throws Exception {
880 		// 19
881 		cleanUpDF();
882 		doit(mk("DF/DF/DF/DF"), mk("DF/DF/DF"), null);
883 		assertRemoved("DF/DF/DF/DF");
884 		assertUpdated("DF/DF/DF");
885 	}
886 
887 	protected void cleanUpDF() throws Exception {
888 		tearDown();
889 		setUp();
890 		recursiveDelete(new File(trash, "DF"));
891 	}
892 
893 	protected void assertConflict(String s) {
894 		assertTrue(getConflicts().contains(s));
895 	}
896 
897 	protected void assertUpdated(String s) {
898 		assertTrue(getUpdated().containsKey(s));
899 	}
900 
901 	protected void assertRemoved(String s) {
902 		assertTrue(getRemoved().contains(s));
903 	}
904 
905 	protected void assertNoConflicts() {
906 		assertTrue(getConflicts().isEmpty());
907 	}
908 
909 	protected void doit(HashMap<String, String> h, HashMap<String, String> m, HashMap<String, String> i)
910 			throws IOException {
911 				setupCase(h, m, i);
912 				go();
913 			}
914 
915 	@Test
916 	public void testUntrackedConflicts() throws IOException {
917 		setupCase(null, mk("foo"), null);
918 		writeTrashFile("foo", "foo");
919 		go();
920 
921 		// test that we don't overwrite untracked files when there is a HEAD
922 		recursiveDelete(new File(trash, "foo"));
923 		setupCase(mk("other"), mkmap("other", "other", "foo", "foo"),
924 				mk("other"));
925 		writeTrashFile("foo", "bar");
926 		try {
927 			checkout();
928 			fail("didn't get the expected exception");
929 		} catch (CheckoutConflictException e) {
930 			assertConflict("foo");
931 			assertEquals("foo", e.getConflictingFiles()[0]);
932 			assertWorkDir(mkmap("foo", "bar", "other", "other"));
933 			assertIndex(mk("other"));
934 		}
935 
936 		// test that we don't overwrite untracked files when there is no HEAD
937 		recursiveDelete(new File(trash, "other"));
938 		recursiveDelete(new File(trash, "foo"));
939 		setupCase(null, mk("foo"), null);
940 		writeTrashFile("foo", "bar");
941 		try {
942 			checkout();
943 			fail("didn't get the expected exception");
944 		} catch (CheckoutConflictException e) {
945 			assertConflict("foo");
946 			assertWorkDir(mkmap("foo", "bar"));
947 			assertIndex(mkmap("other", "other"));
948 		}
949 
950 		// TODO: Why should we expect conflicts here?
951 		// H and M are empty and according to rule #5 of
952 		// the carry-over rules a dirty index is no reason
953 		// for a conflict. (I also feel it should be a
954 		// conflict because we are going to overwrite
955 		// unsaved content in the working tree
956 		// This test would fail in DirCacheCheckoutTest
957 		// assertConflict("foo");
958 
959 		recursiveDelete(new File(trash, "foo"));
960 		recursiveDelete(new File(trash, "other"));
961 		setupCase(null, mk("foo"), null);
962 		writeTrashFile("foo/bar/baz", "");
963 		writeTrashFile("foo/blahblah", "");
964 		go();
965 
966 		assertConflict("foo");
967 		assertConflict("foo/bar/baz");
968 		assertConflict("foo/blahblah");
969 
970 		recursiveDelete(new File(trash, "foo"));
971 
972 		setupCase(mkmap("foo/bar", "", "foo/baz", ""),
973 				mk("foo"), mkmap("foo/bar", "", "foo/baz", ""));
974 		assertTrue(new File(trash, "foo/bar").exists());
975 		go();
976 
977 		assertNoConflicts();
978 	}
979 
980 	@Test
981 	public void testCloseNameConflictsX0() throws IOException {
982 		setupCase(mkmap("a/a", "a/a-c"), mkmap("a/a","a/a", "b.b/b.b","b.b/b.bs"), mkmap("a/a", "a/a-c") );
983 		checkout();
984 		assertIndex(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs"));
985 		assertWorkDir(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs"));
986 		go();
987 		assertIndex(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs"));
988 		assertWorkDir(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs"));
989 		assertNoConflicts();
990 	}
991 
992 	@Test
993 	public void testCloseNameConflicts1() throws IOException {
994 		setupCase(mkmap("a/a", "a/a-c"), mkmap("a/a","a/a", "a.a/a.a","a.a/a.a"), mkmap("a/a", "a/a-c") );
995 		checkout();
996 		assertIndex(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a"));
997 		assertWorkDir(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a"));
998 		go();
999 		assertIndex(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a"));
1000 		assertWorkDir(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a"));
1001 		assertNoConflicts();
1002 	}
1003 
1004 	@Test
1005 	public void testCheckoutHierarchy() throws IOException {
1006 		setupCase(
1007 				mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g",
1008 						"e/g"),
1009 				mkmap("a", "a2", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g",
1010 						"e/g2"),
1011 				mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g",
1012 						"e/g3"));
1013 		try {
1014 			checkout();
1015 			fail("did not throw CheckoutConflictException");
1016 		} catch (CheckoutConflictException e) {
1017 			assertWorkDir(mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f",
1018 					"e/f", "e/g", "e/g3"));
1019 			assertConflict("e/g");
1020 			assertEquals("e/g", e.getConflictingFiles()[0]);
1021 		}
1022 	}
1023 
1024 	@Test
1025 	public void testCheckoutOutChanges() throws IOException {
1026 		setupCase(mk("foo"), mk("foo/bar"), mk("foo"));
1027 		checkout();
1028 		assertIndex(mk("foo/bar"));
1029 		assertWorkDir(mk("foo/bar"));
1030 
1031 		assertFalse(new File(trash, "foo").isFile());
1032 		assertTrue(new File(trash, "foo/bar").isFile());
1033 		recursiveDelete(new File(trash, "foo"));
1034 
1035 		assertWorkDir(mkmap());
1036 
1037 		setupCase(mk("foo/bar"), mk("foo"), mk("foo/bar"));
1038 		checkout();
1039 
1040 		assertIndex(mk("foo"));
1041 		assertWorkDir(mk("foo"));
1042 
1043 		assertFalse(new File(trash, "foo/bar").isFile());
1044 		assertTrue(new File(trash, "foo").isFile());
1045 
1046 		setupCase(mk("foo"), mkmap("foo", "qux"), mkmap("foo", "bar"));
1047 
1048 		assertIndex(mkmap("foo", "bar"));
1049 		assertWorkDir(mkmap("foo", "bar"));
1050 
1051 		try {
1052 			checkout();
1053 			fail("did not throw exception");
1054 		} catch (CheckoutConflictException e) {
1055 			assertIndex(mkmap("foo", "bar"));
1056 			assertWorkDir(mkmap("foo", "bar"));
1057 		}
1058 	}
1059 
1060 	@Test
1061 	public void testCheckoutChangeLinkToEmptyDir() throws Exception {
1062 		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
1063 		String fname = "was_file";
1064 		ChangeRecorder recorder = new ChangeRecorder();
1065 		ListenerHandle handle = null;
1066 		try (Git git = new Git(db)) {
1067 			handle = db.getListenerList()
1068 					.addWorkingTreeModifiedListener(recorder);
1069 			// Add a file
1070 			writeTrashFile(fname, "a");
1071 			git.add().addFilepattern(fname).call();
1072 
1073 			// Add a link to file
1074 			String linkName = "link";
1075 			File link = writeLink(linkName, fname).toFile();
1076 			git.add().addFilepattern(linkName).call();
1077 			git.commit().setMessage("Added file and link").call();
1078 
1079 			assertWorkDir(mkmap(linkName, "a", fname, "a"));
1080 
1081 			// replace link with empty directory
1082 			FileUtils.delete(link);
1083 			FileUtils.mkdir(link);
1084 			assertTrue("Link must be a directory now", link.isDirectory());
1085 
1086 			// modify file
1087 			writeTrashFile(fname, "b");
1088 			assertWorkDir(mkmap(fname, "b", linkName, "/"));
1089 			recorder.assertNoEvent();
1090 
1091 			// revert both paths to HEAD state
1092 			git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
1093 					.addPath(linkName).call();
1094 
1095 			assertWorkDir(mkmap(fname, "a", linkName, "a"));
1096 			recorder.assertEvent(new String[] { fname, linkName },
1097 					ChangeRecorder.EMPTY);
1098 
1099 			Status st = git.status().call();
1100 			assertTrue(st.isClean());
1101 		} finally {
1102 			if (handle != null) {
1103 				handle.remove();
1104 			}
1105 		}
1106 	}
1107 
1108 	@Test
1109 	public void testCheckoutChangeLinkToEmptyDirs() throws Exception {
1110 		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
1111 		String fname = "was_file";
1112 		ChangeRecorder recorder = new ChangeRecorder();
1113 		ListenerHandle handle = null;
1114 		try (Git git = new Git(db)) {
1115 			handle = db.getListenerList()
1116 					.addWorkingTreeModifiedListener(recorder);
1117 			// Add a file
1118 			writeTrashFile(fname, "a");
1119 			git.add().addFilepattern(fname).call();
1120 
1121 			// Add a link to file
1122 			String linkName = "link";
1123 			File link = writeLink(linkName, fname).toFile();
1124 			git.add().addFilepattern(linkName).call();
1125 			git.commit().setMessage("Added file and link").call();
1126 
1127 			assertWorkDir(mkmap(linkName, "a", fname, "a"));
1128 
1129 			// replace link with directory containing only directories, no files
1130 			FileUtils.delete(link);
1131 			FileUtils.mkdirs(new File(link, "dummyDir"));
1132 			assertTrue("Link must be a directory now", link.isDirectory());
1133 
1134 			assertFalse("Must not delete non empty directory", link.delete());
1135 
1136 			// modify file
1137 			writeTrashFile(fname, "b");
1138 			assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/"));
1139 			recorder.assertNoEvent();
1140 
1141 			// revert both paths to HEAD state
1142 			git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
1143 					.addPath(linkName).call();
1144 
1145 			assertWorkDir(mkmap(fname, "a", linkName, "a"));
1146 			recorder.assertEvent(new String[] { fname, linkName },
1147 					ChangeRecorder.EMPTY);
1148 
1149 			Status st = git.status().call();
1150 			assertTrue(st.isClean());
1151 		} finally {
1152 			if (handle != null) {
1153 				handle.remove();
1154 			}
1155 		}
1156 	}
1157 
1158 	@Test
1159 	public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception {
1160 		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
1161 		String fname = "file";
1162 		ChangeRecorder recorder = new ChangeRecorder();
1163 		ListenerHandle handle = null;
1164 		try (Git git = new Git(db)) {
1165 			handle = db.getListenerList()
1166 					.addWorkingTreeModifiedListener(recorder);
1167 			// Add a file
1168 			writeTrashFile(fname, "a");
1169 			git.add().addFilepattern(fname).call();
1170 
1171 			// Add a link to file
1172 			String linkName = "link";
1173 			File link = writeLink(linkName, fname).toFile();
1174 			git.add().addFilepattern(linkName).call();
1175 			git.commit().setMessage("Added file and link").call();
1176 
1177 			assertWorkDir(mkmap(linkName, "a", fname, "a"));
1178 
1179 			// replace link with directory containing only directories, no files
1180 			FileUtils.delete(link);
1181 
1182 			// create but do not add a file in the new directory to the index
1183 			writeTrashFile(linkName + "/dir1", "file1", "c");
1184 
1185 			// create but do not add a file in the new directory to the index
1186 			writeTrashFile(linkName + "/dir2", "file2", "d");
1187 
1188 			assertTrue("File must be a directory now", link.isDirectory());
1189 			assertFalse("Must not delete non empty directory", link.delete());
1190 
1191 			// 2 extra files are created
1192 			assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
1193 					linkName + "/dir2/file2", "d"));
1194 			recorder.assertNoEvent();
1195 
1196 			// revert path to HEAD state
1197 			git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
1198 					.call();
1199 
1200 			// expect only the one added to the index
1201 			assertWorkDir(mkmap(linkName, "a", fname, "a"));
1202 			recorder.assertEvent(new String[] { linkName },
1203 					ChangeRecorder.EMPTY);
1204 
1205 			Status st = git.status().call();
1206 			assertTrue(st.isClean());
1207 		} finally {
1208 			if (handle != null) {
1209 				handle.remove();
1210 			}
1211 		}
1212 	}
1213 
1214 	@Test
1215 	public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry()
1216 			throws Exception {
1217 		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
1218 		String fname = "file";
1219 		ChangeRecorder recorder = new ChangeRecorder();
1220 		ListenerHandle handle = null;
1221 		try (Git git = new Git(db)) {
1222 			handle = db.getListenerList()
1223 					.addWorkingTreeModifiedListener(recorder);
1224 			// Add a file
1225 			writeTrashFile(fname, "a");
1226 			git.add().addFilepattern(fname).call();
1227 
1228 			// Add a link to file
1229 			String linkName = "link";
1230 			File link = writeLink(linkName, fname).toFile();
1231 			git.add().addFilepattern(linkName).call();
1232 			git.commit().setMessage("Added file and link").call();
1233 
1234 			assertWorkDir(mkmap(linkName, "a", fname, "a"));
1235 
1236 			// replace link with directory containing only directories, no files
1237 			FileUtils.delete(link);
1238 
1239 			// create and add a file in the new directory to the index
1240 			writeTrashFile(linkName + "/dir1", "file1", "c");
1241 			git.add().addFilepattern(linkName + "/dir1/file1").call();
1242 
1243 			// create but do not add a file in the new directory to the index
1244 			writeTrashFile(linkName + "/dir2", "file2", "d");
1245 
1246 			assertTrue("File must be a directory now", link.isDirectory());
1247 			assertFalse("Must not delete non empty directory", link.delete());
1248 
1249 			// 2 extra files are created
1250 			assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
1251 					linkName + "/dir2/file2", "d"));
1252 			recorder.assertNoEvent();
1253 
1254 			// revert path to HEAD state
1255 			git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
1256 					.call();
1257 
1258 			// original file and link
1259 			assertWorkDir(mkmap(linkName, "a", fname, "a"));
1260 			recorder.assertEvent(new String[] { linkName },
1261 					ChangeRecorder.EMPTY);
1262 
1263 			Status st = git.status().call();
1264 			assertTrue(st.isClean());
1265 		} finally {
1266 			if (handle != null) {
1267 				handle.remove();
1268 			}
1269 		}
1270 	}
1271 
1272 	@Test
1273 	public void testCheckoutChangeFileToEmptyDir() throws Exception {
1274 		String fname = "was_file";
1275 		ChangeRecorder recorder = new ChangeRecorder();
1276 		ListenerHandle handle = null;
1277 		try (Git git = new Git(db)) {
1278 			handle = db.getListenerList()
1279 					.addWorkingTreeModifiedListener(recorder);
1280 			// Add a file
1281 			File file = writeTrashFile(fname, "a");
1282 			git.add().addFilepattern(fname).call();
1283 			git.commit().setMessage("Added file").call();
1284 
1285 			// replace file with empty directory
1286 			FileUtils.delete(file);
1287 			FileUtils.mkdir(file);
1288 			assertTrue("File must be a directory now", file.isDirectory());
1289 			assertWorkDir(mkmap(fname, "/"));
1290 			recorder.assertNoEvent();
1291 
1292 			// revert path to HEAD state
1293 			git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
1294 			assertWorkDir(mkmap(fname, "a"));
1295 			recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
1296 
1297 			Status st = git.status().call();
1298 			assertTrue(st.isClean());
1299 		} finally {
1300 			if (handle != null) {
1301 				handle.remove();
1302 			}
1303 		}
1304 	}
1305 
1306 	@Test
1307 	public void testCheckoutChangeFileToEmptyDirs() throws Exception {
1308 		String fname = "was_file";
1309 		ChangeRecorder recorder = new ChangeRecorder();
1310 		ListenerHandle handle = null;
1311 		try (Git git = new Git(db)) {
1312 			handle = db.getListenerList()
1313 					.addWorkingTreeModifiedListener(recorder);
1314 			// Add a file
1315 			File file = writeTrashFile(fname, "a");
1316 			git.add().addFilepattern(fname).call();
1317 			git.commit().setMessage("Added file").call();
1318 
1319 			// replace file with directory containing only directories, no files
1320 			FileUtils.delete(file);
1321 			FileUtils.mkdirs(new File(file, "dummyDir"));
1322 			assertTrue("File must be a directory now", file.isDirectory());
1323 			assertFalse("Must not delete non empty directory", file.delete());
1324 
1325 			assertWorkDir(mkmap(fname + "/dummyDir", "/"));
1326 			recorder.assertNoEvent();
1327 
1328 			// revert path to HEAD state
1329 			git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
1330 			assertWorkDir(mkmap(fname, "a"));
1331 			recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
1332 
1333 			Status st = git.status().call();
1334 			assertTrue(st.isClean());
1335 		} finally {
1336 			if (handle != null) {
1337 				handle.remove();
1338 			}
1339 		}
1340 	}
1341 
1342 	@Test
1343 	public void testCheckoutChangeFileToNonEmptyDirs() throws Exception {
1344 		String fname = "was_file";
1345 		ChangeRecorder recorder = new ChangeRecorder();
1346 		ListenerHandle handle = null;
1347 		try (Git git = new Git(db)) {
1348 			handle = db.getListenerList()
1349 					.addWorkingTreeModifiedListener(recorder);
1350 			// Add a file
1351 			File file = writeTrashFile(fname, "a");
1352 			git.add().addFilepattern(fname).call();
1353 			git.commit().setMessage("Added file").call();
1354 
1355 			assertWorkDir(mkmap(fname, "a"));
1356 
1357 			// replace file with directory containing only directories, no files
1358 			FileUtils.delete(file);
1359 
1360 			// create but do not add a file in the new directory to the index
1361 			writeTrashFile(fname + "/dir1", "file1", "c");
1362 
1363 			// create but do not add a file in the new directory to the index
1364 			writeTrashFile(fname + "/dir2", "file2", "d");
1365 
1366 			assertTrue("File must be a directory now", file.isDirectory());
1367 			assertFalse("Must not delete non empty directory", file.delete());
1368 
1369 			// 2 extra files are created
1370 			assertWorkDir(mkmap(fname + "/dir1/file1", "c",
1371 					fname + "/dir2/file2", "d"));
1372 			recorder.assertNoEvent();
1373 
1374 			// revert path to HEAD state
1375 			git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
1376 
1377 			// expect only the one added to the index
1378 			assertWorkDir(mkmap(fname, "a"));
1379 			recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
1380 
1381 			Status st = git.status().call();
1382 			assertTrue(st.isClean());
1383 		} finally {
1384 			if (handle != null) {
1385 				handle.remove();
1386 			}
1387 		}
1388 	}
1389 
1390 	@Test
1391 	public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry()
1392 			throws Exception {
1393 		String fname = "was_file";
1394 		ChangeRecorder recorder = new ChangeRecorder();
1395 		ListenerHandle handle = null;
1396 		try (Git git = new Git(db)) {
1397 			handle = db.getListenerList()
1398 					.addWorkingTreeModifiedListener(recorder);
1399 			// Add a file
1400 			File file = writeTrashFile(fname, "a");
1401 			git.add().addFilepattern(fname).call();
1402 			git.commit().setMessage("Added file").call();
1403 
1404 			assertWorkDir(mkmap(fname, "a"));
1405 
1406 			// replace file with directory containing only directories, no files
1407 			FileUtils.delete(file);
1408 
1409 			// create and add a file in the new directory to the index
1410 			writeTrashFile(fname + "/dir", "file1", "c");
1411 			git.add().addFilepattern(fname + "/dir/file1").call();
1412 
1413 			// create but do not add a file in the new directory to the index
1414 			writeTrashFile(fname + "/dir", "file2", "d");
1415 
1416 			assertTrue("File must be a directory now", file.isDirectory());
1417 			assertFalse("Must not delete non empty directory", file.delete());
1418 
1419 			// 2 extra files are created
1420 			assertWorkDir(mkmap(fname + "/dir/file1", "c", fname + "/dir/file2",
1421 					"d"));
1422 			recorder.assertNoEvent();
1423 
1424 			// revert path to HEAD state
1425 			git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
1426 			assertWorkDir(mkmap(fname, "a"));
1427 			recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
1428 			Status st = git.status().call();
1429 			assertTrue(st.isClean());
1430 		} finally {
1431 			if (handle != null) {
1432 				handle.remove();
1433 			}
1434 		}
1435 	}
1436 
1437 	@Test
1438 	public void testCheckoutOutChangesAutoCRLFfalse() throws IOException {
1439 		setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo"));
1440 		checkout();
1441 		assertIndex(mkmap("foo/bar", "foo\nbar"));
1442 		assertWorkDir(mkmap("foo/bar", "foo\nbar"));
1443 	}
1444 
1445 	@Test
1446 	public void testCheckoutOutChangesAutoCRLFInput() throws IOException {
1447 		setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo"));
1448 		db.getConfig().setString("core", null, "autocrlf", "input");
1449 		checkout();
1450 		assertIndex(mkmap("foo/bar", "foo\nbar"));
1451 		assertWorkDir(mkmap("foo/bar", "foo\nbar"));
1452 	}
1453 
1454 	@Test
1455 	public void testCheckoutOutChangesAutoCRLFtrue() throws IOException {
1456 		setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo"));
1457 		db.getConfig().setString("core", null, "autocrlf", "true");
1458 		checkout();
1459 		assertIndex(mkmap("foo/bar", "foo\nbar"));
1460 		assertWorkDir(mkmap("foo/bar", "foo\r\nbar"));
1461 	}
1462 
1463 	@Test
1464 	public void testCheckoutOutChangesAutoCRLFtrueBinary() throws IOException {
1465 		setupCase(mk("foo"), mkmap("foo/bar", "foo\nb\u0000ar"), mk("foo"));
1466 		db.getConfig().setString("core", null, "autocrlf", "true");
1467 		checkout();
1468 		assertIndex(mkmap("foo/bar", "foo\nb\u0000ar"));
1469 		assertWorkDir(mkmap("foo/bar", "foo\nb\u0000ar"));
1470 	}
1471 
1472 	@Test
1473 	public void testCheckoutUncachedChanges() throws IOException {
1474 		setupCase(mk("foo"), mk("foo"), mk("foo"));
1475 		writeTrashFile("foo", "otherData");
1476 		checkout();
1477 		assertIndex(mk("foo"));
1478 		assertWorkDir(mkmap("foo", "otherData"));
1479 		assertTrue(new File(trash, "foo").isFile());
1480 	}
1481 
1482 	@Test
1483 	public void testDontOverwriteDirtyFile() throws IOException {
1484 		setupCase(mk("foo"), mk("other"), mk("foo"));
1485 		writeTrashFile("foo", "different");
1486 		try {
1487 			checkout();
1488 			fail("Didn't got the expected conflict");
1489 		} catch (CheckoutConflictException e) {
1490 			assertIndex(mk("foo"));
1491 			assertWorkDir(mkmap("foo", "different"));
1492 			assertEquals(Arrays.asList("foo"), getConflicts());
1493 			assertTrue(new File(trash, "foo").isFile());
1494 		}
1495 	}
1496 
1497 	@Test
1498 	public void testDontOverwriteEmptyFolder() throws IOException {
1499 		setupCase(mk("foo"), mk("foo"), mk("foo"));
1500 		FileUtils.mkdir(new File(db.getWorkTree(), "d"));
1501 		checkout();
1502 		assertWorkDir(mkmap("foo", "foo", "d", "/"));
1503 	}
1504 
1505 	@Test
1506 	public void testOverwriteUntrackedIgnoredFile() throws IOException,
1507 			GitAPIException {
1508 		String fname="file.txt";
1509 		ChangeRecorder recorder = new ChangeRecorder();
1510 		ListenerHandle handle = null;
1511 		try (Git git = new Git(db)) {
1512 			handle = db.getListenerList()
1513 					.addWorkingTreeModifiedListener(recorder);
1514 			// Add a file
1515 			writeTrashFile(fname, "a");
1516 			git.add().addFilepattern(fname).call();
1517 			git.commit().setMessage("create file").call();
1518 
1519 			// Create branch
1520 			git.branchCreate().setName("side").call();
1521 
1522 			// Modify file
1523 			writeTrashFile(fname, "b");
1524 			git.add().addFilepattern(fname).call();
1525 			git.commit().setMessage("modify file").call();
1526 			recorder.assertNoEvent();
1527 
1528 			// Switch branches
1529 			git.checkout().setName("side").call();
1530 			recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
1531 			git.rm().addFilepattern(fname).call();
1532 			recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { fname });
1533 			writeTrashFile(".gitignore", fname);
1534 			git.add().addFilepattern(".gitignore").call();
1535 			git.commit().setMessage("delete and ignore file").call();
1536 
1537 			writeTrashFile(fname, "Something different");
1538 			recorder.assertNoEvent();
1539 			git.checkout().setName("master").call();
1540 			assertWorkDir(mkmap(fname, "b"));
1541 			recorder.assertEvent(new String[] { fname },
1542 					new String[] { ".gitignore" });
1543 			assertTrue(git.status().call().isClean());
1544 		} finally {
1545 			if (handle != null) {
1546 				handle.remove();
1547 			}
1548 		}
1549 	}
1550 
1551 	@Test
1552 	public void testOverwriteUntrackedFileModeChange()
1553 			throws IOException, GitAPIException {
1554 		String fname = "file.txt";
1555 		ChangeRecorder recorder = new ChangeRecorder();
1556 		ListenerHandle handle = null;
1557 		try (Git git = new Git(db)) {
1558 			handle = db.getListenerList()
1559 					.addWorkingTreeModifiedListener(recorder);
1560 			// Add a file
1561 			File file = writeTrashFile(fname, "a");
1562 			git.add().addFilepattern(fname).call();
1563 			git.commit().setMessage("create file").call();
1564 			assertWorkDir(mkmap(fname, "a"));
1565 
1566 			// Create branch
1567 			git.branchCreate().setName("side").call();
1568 
1569 			// Switch branches
1570 			git.checkout().setName("side").call();
1571 			recorder.assertNoEvent();
1572 
1573 			// replace file with directory containing files
1574 			FileUtils.delete(file);
1575 
1576 			// create and add a file in the new directory to the index
1577 			writeTrashFile(fname + "/dir1", "file1", "c");
1578 			git.add().addFilepattern(fname + "/dir1/file1").call();
1579 
1580 			// create but do not add a file in the new directory to the index
1581 			writeTrashFile(fname + "/dir2", "file2", "d");
1582 
1583 			assertTrue("File must be a directory now", file.isDirectory());
1584 			assertFalse("Must not delete non empty directory", file.delete());
1585 
1586 			// 2 extra files are created
1587 			assertWorkDir(mkmap(fname + "/dir1/file1", "c",
1588 					fname + "/dir2/file2", "d"));
1589 
1590 			try {
1591 				git.checkout().setName("master").call();
1592 				fail("did not throw exception");
1593 			} catch (Exception e) {
1594 				// 2 extra files are still there
1595 				assertWorkDir(mkmap(fname + "/dir1/file1", "c",
1596 						fname + "/dir2/file2", "d"));
1597 			}
1598 			recorder.assertNoEvent();
1599 		} finally {
1600 			if (handle != null) {
1601 				handle.remove();
1602 			}
1603 		}
1604 	}
1605 
1606 	@Test
1607 	public void testOverwriteUntrackedLinkModeChange()
1608 			throws Exception {
1609 		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
1610 		String fname = "file.txt";
1611 		ChangeRecorder recorder = new ChangeRecorder();
1612 		ListenerHandle handle = null;
1613 		try (Git git = new Git(db)) {
1614 			handle = db.getListenerList()
1615 					.addWorkingTreeModifiedListener(recorder);
1616 			// Add a file
1617 			writeTrashFile(fname, "a");
1618 			git.add().addFilepattern(fname).call();
1619 
1620 			// Add a link to file
1621 			String linkName = "link";
1622 			File link = writeLink(linkName, fname).toFile();
1623 			git.add().addFilepattern(linkName).call();
1624 			git.commit().setMessage("Added file and link").call();
1625 
1626 			assertWorkDir(mkmap(linkName, "a", fname, "a"));
1627 
1628 			// Create branch
1629 			git.branchCreate().setName("side").call();
1630 
1631 			// Switch branches
1632 			git.checkout().setName("side").call();
1633 			recorder.assertNoEvent();
1634 
1635 			// replace link with directory containing files
1636 			FileUtils.delete(link);
1637 
1638 			// create and add a file in the new directory to the index
1639 			writeTrashFile(linkName + "/dir1", "file1", "c");
1640 			git.add().addFilepattern(linkName + "/dir1/file1").call();
1641 
1642 			// create but do not add a file in the new directory to the index
1643 			writeTrashFile(linkName + "/dir2", "file2", "d");
1644 
1645 			assertTrue("Link must be a directory now", link.isDirectory());
1646 			assertFalse("Must not delete non empty directory", link.delete());
1647 
1648 			// 2 extra files are created
1649 			assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
1650 					linkName + "/dir2/file2", "d"));
1651 
1652 			try {
1653 				git.checkout().setName("master").call();
1654 				fail("did not throw exception");
1655 			} catch (Exception e) {
1656 				// 2 extra files are still there
1657 				assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
1658 						linkName + "/dir2/file2", "d"));
1659 			}
1660 			recorder.assertNoEvent();
1661 		} finally {
1662 			if (handle != null) {
1663 				handle.remove();
1664 			}
1665 		}
1666 	}
1667 
1668 	@Test
1669 	public void testFileModeChangeWithNoContentChangeUpdate() throws Exception {
1670 		if (!FS.DETECTED.supportsExecute())
1671 			return;
1672 
1673 		ChangeRecorder recorder = new ChangeRecorder();
1674 		ListenerHandle handle = null;
1675 		try (Git git = new Git(db)) {
1676 			handle = db.getListenerList()
1677 					.addWorkingTreeModifiedListener(recorder);
1678 			// Add non-executable file
1679 			File file = writeTrashFile("file.txt", "a");
1680 			git.add().addFilepattern("file.txt").call();
1681 			git.commit().setMessage("commit1").call();
1682 			assertFalse(db.getFS().canExecute(file));
1683 
1684 			// Create branch
1685 			git.branchCreate().setName("b1").call();
1686 
1687 			// Make file executable
1688 			db.getFS().setExecute(file, true);
1689 			git.add().addFilepattern("file.txt").call();
1690 			git.commit().setMessage("commit2").call();
1691 			recorder.assertNoEvent();
1692 
1693 			// Verify executable and working directory is clean
1694 			Status status = git.status().call();
1695 			assertTrue(status.getModified().isEmpty());
1696 			assertTrue(status.getChanged().isEmpty());
1697 			assertTrue(db.getFS().canExecute(file));
1698 
1699 			// Switch branches
1700 			git.checkout().setName("b1").call();
1701 
1702 			// Verify not executable and working directory is clean
1703 			status = git.status().call();
1704 			assertTrue(status.getModified().isEmpty());
1705 			assertTrue(status.getChanged().isEmpty());
1706 			assertFalse(db.getFS().canExecute(file));
1707 			recorder.assertEvent(new String[] { "file.txt" },
1708 					ChangeRecorder.EMPTY);
1709 		} finally {
1710 			if (handle != null) {
1711 				handle.remove();
1712 			}
1713 		}
1714 	}
1715 
1716 	@Test
1717 	public void testFileModeChangeAndContentChangeConflict() throws Exception {
1718 		if (!FS.DETECTED.supportsExecute())
1719 			return;
1720 
1721 		ChangeRecorder recorder = new ChangeRecorder();
1722 		ListenerHandle handle = null;
1723 		try (Git git = new Git(db)) {
1724 			handle = db.getListenerList()
1725 					.addWorkingTreeModifiedListener(recorder);
1726 			// Add non-executable file
1727 			File file = writeTrashFile("file.txt", "a");
1728 			git.add().addFilepattern("file.txt").call();
1729 			git.commit().setMessage("commit1").call();
1730 			assertFalse(db.getFS().canExecute(file));
1731 
1732 			// Create branch
1733 			git.branchCreate().setName("b1").call();
1734 
1735 			// Make file executable
1736 			db.getFS().setExecute(file, true);
1737 			git.add().addFilepattern("file.txt").call();
1738 			git.commit().setMessage("commit2").call();
1739 
1740 			// Verify executable and working directory is clean
1741 			Status status = git.status().call();
1742 			assertTrue(status.getModified().isEmpty());
1743 			assertTrue(status.getChanged().isEmpty());
1744 			assertTrue(db.getFS().canExecute(file));
1745 
1746 			writeTrashFile("file.txt", "b");
1747 
1748 			// Switch branches
1749 			CheckoutCommand checkout = git.checkout().setName("b1");
1750 			try {
1751 				checkout.call();
1752 				fail("Checkout exception not thrown");
1753 			} catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) {
1754 				CheckoutResult result = checkout.getResult();
1755 				assertNotNull(result);
1756 				assertNotNull(result.getConflictList());
1757 				assertEquals(1, result.getConflictList().size());
1758 				assertTrue(result.getConflictList().contains("file.txt"));
1759 			}
1760 			recorder.assertNoEvent();
1761 		} finally {
1762 			if (handle != null) {
1763 				handle.remove();
1764 			}
1765 		}
1766 	}
1767 
1768 	@Test
1769 	public void testDirtyFileModeEqualHeadMerge()
1770 			throws Exception {
1771 		if (!FS.DETECTED.supportsExecute())
1772 			return;
1773 
1774 		ChangeRecorder recorder = new ChangeRecorder();
1775 		ListenerHandle handle = null;
1776 		try (Git git = new Git(db)) {
1777 			handle = db.getListenerList()
1778 					.addWorkingTreeModifiedListener(recorder);
1779 			// Add non-executable file
1780 			File file = writeTrashFile("file.txt", "a");
1781 			git.add().addFilepattern("file.txt").call();
1782 			git.commit().setMessage("commit1").call();
1783 			assertFalse(db.getFS().canExecute(file));
1784 
1785 			// Create branch
1786 			git.branchCreate().setName("b1").call();
1787 
1788 			// Create second commit and don't touch file
1789 			writeTrashFile("file2.txt", "");
1790 			git.add().addFilepattern("file2.txt").call();
1791 			git.commit().setMessage("commit2").call();
1792 
1793 			// stage a mode change
1794 			writeTrashFile("file.txt", "a");
1795 			db.getFS().setExecute(file, true);
1796 			git.add().addFilepattern("file.txt").call();
1797 
1798 			// dirty the file
1799 			writeTrashFile("file.txt", "b");
1800 
1801 			assertEquals(
1802 					"[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]",
1803 					indexState(CONTENT));
1804 			assertWorkDir(mkmap("file.txt", "b", "file2.txt", ""));
1805 			recorder.assertNoEvent();
1806 
1807 			// Switch branches and check that the dirty file survived in
1808 			// worktree and index
1809 			git.checkout().setName("b1").call();
1810 			assertEquals("[file.txt, mode:100755, content:a]",
1811 					indexState(CONTENT));
1812 			assertWorkDir(mkmap("file.txt", "b"));
1813 			recorder.assertEvent(ChangeRecorder.EMPTY,
1814 					new String[] { "file2.txt" });
1815 		} finally {
1816 			if (handle != null) {
1817 				handle.remove();
1818 			}
1819 		}
1820 	}
1821 
1822 	@Test
1823 	public void testDirtyFileModeEqualIndexMerge()
1824 			throws Exception {
1825 		if (!FS.DETECTED.supportsExecute())
1826 			return;
1827 
1828 		ChangeRecorder recorder = new ChangeRecorder();
1829 		ListenerHandle handle = null;
1830 		try (Git git = new Git(db)) {
1831 			handle = db.getListenerList()
1832 					.addWorkingTreeModifiedListener(recorder);
1833 			// Add non-executable file
1834 			File file = writeTrashFile("file.txt", "a");
1835 			git.add().addFilepattern("file.txt").call();
1836 			git.commit().setMessage("commit1").call();
1837 			assertFalse(db.getFS().canExecute(file));
1838 
1839 			// Create branch
1840 			git.branchCreate().setName("b1").call();
1841 
1842 			// Create second commit with executable file
1843 			file = writeTrashFile("file.txt", "b");
1844 			db.getFS().setExecute(file, true);
1845 			git.add().addFilepattern("file.txt").call();
1846 			git.commit().setMessage("commit2").call();
1847 
1848 			// stage the same content as in the branch we want to switch to
1849 			writeTrashFile("file.txt", "a");
1850 			db.getFS().setExecute(file, false);
1851 			git.add().addFilepattern("file.txt").call();
1852 
1853 			// dirty the file
1854 			writeTrashFile("file.txt", "c");
1855 			db.getFS().setExecute(file, true);
1856 
1857 			assertEquals("[file.txt, mode:100644, content:a]",
1858 					indexState(CONTENT));
1859 			assertWorkDir(mkmap("file.txt", "c"));
1860 			recorder.assertNoEvent();
1861 
1862 			// Switch branches and check that the dirty file survived in
1863 			// worktree
1864 			// and index
1865 			git.checkout().setName("b1").call();
1866 			assertEquals("[file.txt, mode:100644, content:a]",
1867 					indexState(CONTENT));
1868 			assertWorkDir(mkmap("file.txt", "c"));
1869 			recorder.assertNoEvent();
1870 		} finally {
1871 			if (handle != null) {
1872 				handle.remove();
1873 			}
1874 		}
1875 	}
1876 
1877 	@Test
1878 	public void testFileModeChangeAndContentChangeNoConflict() throws Exception {
1879 		if (!FS.DETECTED.supportsExecute())
1880 			return;
1881 
1882 		ChangeRecorder recorder = new ChangeRecorder();
1883 		ListenerHandle handle = null;
1884 		try (Git git = new Git(db)) {
1885 			handle = db.getListenerList()
1886 					.addWorkingTreeModifiedListener(recorder);
1887 			// Add first file
1888 			File file1 = writeTrashFile("file1.txt", "a");
1889 			git.add().addFilepattern("file1.txt").call();
1890 			git.commit().setMessage("commit1").call();
1891 			assertFalse(db.getFS().canExecute(file1));
1892 
1893 			// Add second file
1894 			File file2 = writeTrashFile("file2.txt", "b");
1895 			git.add().addFilepattern("file2.txt").call();
1896 			git.commit().setMessage("commit2").call();
1897 			assertFalse(db.getFS().canExecute(file2));
1898 			recorder.assertNoEvent();
1899 
1900 			// Create branch from first commit
1901 			assertNotNull(git.checkout().setCreateBranch(true).setName("b1")
1902 					.setStartPoint(Constants.HEAD + "~1").call());
1903 			recorder.assertEvent(ChangeRecorder.EMPTY,
1904 					new String[] { "file2.txt" });
1905 
1906 			// Change content and file mode in working directory and index
1907 			file1 = writeTrashFile("file1.txt", "c");
1908 			db.getFS().setExecute(file1, true);
1909 			git.add().addFilepattern("file1.txt").call();
1910 
1911 			// Switch back to 'master'
1912 			assertNotNull(git.checkout().setName(Constants.MASTER).call());
1913 			recorder.assertEvent(new String[] { "file2.txt" },
1914 					ChangeRecorder.EMPTY);
1915 		} finally {
1916 			if (handle != null) {
1917 				handle.remove();
1918 			}
1919 		}
1920 	}
1921 
1922 	@Test(expected = CheckoutConflictException.class)
1923 	public void testFolderFileConflict() throws Exception {
1924 		RevCommit headCommit = commitFile("f/a", "initial content", "master");
1925 		RevCommit checkoutCommit = commitFile("f/a", "side content", "side");
1926 		FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE);
1927 		writeTrashFile("f", "file instead of folder");
1928 		new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
1929 				checkoutCommit.getTree()).checkout();
1930 	}
1931 
1932 	@Test
1933 	public void testMultipleContentConflicts() throws Exception {
1934 		commitFile("a", "initial content", "master");
1935 		RevCommit headCommit = commitFile("b", "initial content", "master");
1936 		commitFile("a", "side content", "side");
1937 		RevCommit checkoutCommit = commitFile("b", "side content", "side");
1938 		writeTrashFile("a", "changed content");
1939 		writeTrashFile("b", "changed content");
1940 
1941 		try {
1942 			new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
1943 					checkoutCommit.getTree()).checkout();
1944 			fail();
1945 		} catch (CheckoutConflictException expected) {
1946 			assertEquals(2, expected.getConflictingFiles().length);
1947 			assertTrue(Arrays.asList(expected.getConflictingFiles())
1948 					.contains("a"));
1949 			assertTrue(Arrays.asList(expected.getConflictingFiles())
1950 					.contains("b"));
1951 			assertEquals("changed content", read("a"));
1952 			assertEquals("changed content", read("b"));
1953 		}
1954 	}
1955 
1956 	@Test
1957 	public void testFolderFileAndContentConflicts() throws Exception {
1958 		RevCommit headCommit = commitFile("f/a", "initial content", "master");
1959 		commitFile("b", "side content", "side");
1960 		RevCommit checkoutCommit = commitFile("f/a", "side content", "side");
1961 		FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE);
1962 		writeTrashFile("f", "file instead of a folder");
1963 		writeTrashFile("b", "changed content");
1964 
1965 		try {
1966 			new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
1967 					checkoutCommit.getTree()).checkout();
1968 			fail();
1969 		} catch (CheckoutConflictException expected) {
1970 			assertEquals(2, expected.getConflictingFiles().length);
1971 			assertTrue(Arrays.asList(expected.getConflictingFiles())
1972 					.contains("b"));
1973 			assertTrue(Arrays.asList(expected.getConflictingFiles())
1974 					.contains("f"));
1975 			assertEquals("file instead of a folder", read("f"));
1976 			assertEquals("changed content", read("b"));
1977 		}
1978 	}
1979 
1980 	@Test
1981 	public void testLongFilename() throws Exception {
1982 		char[] bytes = new char[253];
1983 		Arrays.fill(bytes, 'f');
1984 		String longFileName = new String(bytes);
1985 		// 1
1986 		doit(mkmap(longFileName, "a"), mkmap(longFileName, "b"),
1987 				mkmap(longFileName, "a"));
1988 		writeTrashFile(longFileName, "a");
1989 		checkout();
1990 		assertNoConflicts();
1991 		assertUpdated(longFileName);
1992 	}
1993 
1994 	@Test
1995 	public void testIgnoredDirectory() throws Exception {
1996 		writeTrashFile(".gitignore", "src/ignored");
1997 		writeTrashFile("src/ignored/sub/foo.txt", "1");
1998 		try (Git git = new Git(db)) {
1999 			git.add().addFilepattern(".").call();
2000 			RevCommit commit = git.commit().setMessage("adding .gitignore")
2001 					.call();
2002 			writeTrashFile("foo.txt", "2");
2003 			writeTrashFile("zzz.txt", "3");
2004 			git.add().addFilepattern("foo.txt").call();
2005 			git.commit().setMessage("add file").call();
2006 			assertEquals("Should not have entered ignored directory", 1,
2007 					resetHardAndCount(commit));
2008 		}
2009 	}
2010 
2011 	@Test
2012 	public void testIgnoredDirectoryWithTrackedContent() throws Exception {
2013 		writeTrashFile("src/ignored/sub/foo.txt", "1");
2014 		try (Git git = new Git(db)) {
2015 			git.add().addFilepattern(".").call();
2016 			git.commit().setMessage("adding foo.txt").call();
2017 			writeTrashFile(".gitignore", "src/ignored");
2018 			writeTrashFile("src/ignored/sub/foo.txt", "2");
2019 			writeTrashFile("src/ignored/other/bar.txt", "3");
2020 			git.add().addFilepattern(".").call();
2021 			RevCommit commit = git.commit().setMessage("adding .gitignore")
2022 					.call();
2023 			writeTrashFile("foo.txt", "2");
2024 			writeTrashFile("zzz.txt", "3");
2025 			git.add().addFilepattern("foo.txt").call();
2026 			git.commit().setMessage("add file").call();
2027 			File file = writeTrashFile("src/ignored/sub/foo.txt", "3");
2028 			assertEquals("Should have entered ignored directory", 3,
2029 					resetHardAndCount(commit));
2030 			checkFile(file, "2");
2031 		}
2032 	}
2033 
2034 	@Test
2035 	public void testResetWithChangeInGitignore() throws Exception {
2036 		writeTrashFile(".gitignore", "src/ignored");
2037 		writeTrashFile("src/ignored/sub/foo.txt", "1");
2038 		try (Git git = new Git(db)) {
2039 			git.add().addFilepattern(".").call();
2040 			RevCommit initial = git.commit().setMessage("initial").call();
2041 			writeTrashFile("src/newignored/foo.txt", "2");
2042 			writeTrashFile("src/.gitignore", "newignored");
2043 			git.add().addFilepattern(".").call();
2044 			RevCommit commit = git.commit().setMessage("newignored").call();
2045 			assertEquals("Should not have entered src/newignored directory", 1,
2046 					resetHardAndCount(initial));
2047 			assertEquals("Should have entered src/newignored directory", 2,
2048 					resetHardAndCount(commit));
2049 			deleteTrashFile("src/.gitignore");
2050 			git.rm().addFilepattern("src/.gitignore").call();
2051 			RevCommit top = git.commit().setMessage("Unignore newignore")
2052 					.call();
2053 			assertEquals("Should have entered src/newignored directory", 2,
2054 					resetHardAndCount(initial));
2055 			assertEquals("Should have entered src/newignored directory", 2,
2056 					resetHardAndCount(commit));
2057 			assertEquals("Should not have entered src/newignored directory", 1,
2058 					resetHardAndCount(top));
2059 
2060 		}
2061 	}
2062 
2063 	@Test
2064 	public void testCheckoutWithEmptyIndexDoesntOverwrite() throws Exception {
2065 		try (Git git = new Git(db);
2066 				TestRepository<Repository> db_t = new TestRepository<>(db)) {
2067 			// prepare the commits
2068 			BranchBuilder master = db_t.branch("master");
2069 			RevCommit mergeCommit = master.commit()
2070 					.add("p/x", "headContent")
2071 					.message("m0").create();
2072 			master.commit().add("p/x", "headContent").message("m1").create();
2073 			git.checkout().setName("master").call();
2074 
2075 			// empty index and write unsaved data in 'p'
2076 			git.rm().addFilepattern("p").call();
2077 			writeTrashFile("p", "important data");
2078 
2079 			git.checkout().setName(mergeCommit.getName()).call();
2080 
2081 			assertEquals("", indexState(CONTENT));
2082 			assertEquals("important data", read("p"));
2083 		}
2084 	}
2085 
2086 	private static class TestFileTreeIterator extends FileTreeIterator {
2087 
2088 		// For assertions only
2089 		private final int[] count;
2090 
2091 		public TestFileTreeIterator(Repository repo, int[] count) {
2092 			super(repo);
2093 			this.count = count;
2094 		}
2095 
2096 		protected TestFileTreeIterator(final WorkingTreeIterator p,
2097 				final File root, FS fs, FileModeStrategy fileModeStrategy,
2098 				int[] count) {
2099 			super(p, root, fs, fileModeStrategy);
2100 			this.count = count;
2101 		}
2102 
2103 		@Override
2104 		protected AbstractTreeIterator enterSubtree() {
2105 			count[0] += 1;
2106 			return new TestFileTreeIterator(this,
2107 					((FileEntry) current()).getFile(), fs, fileModeStrategy,
2108 					count);
2109 		}
2110 	}
2111 
2112 	private int resetHardAndCount(RevCommit commit) throws Exception {
2113 		int[] callCount = { 0 };
2114 		DirCache cache = db.lockDirCache();
2115 		FileTreeIterator workingTreeIterator = new TestFileTreeIterator(db,
2116 				callCount);
2117 		try {
2118 			DirCacheCheckout checkout = new DirCacheCheckout(db, null, cache,
2119 					commit.getTree().getId(), workingTreeIterator);
2120 			checkout.setFailOnConflict(false);
2121 			checkout.checkout();
2122 		} finally {
2123 			cache.unlock();
2124 		}
2125 		return callCount[0];
2126 	}
2127 
2128 	public void assertWorkDir(Map<String, String> i)
2129 			throws CorruptObjectException,
2130 			IOException {
2131 		try (TreeWalk walk = new TreeWalk(db)) {
2132 			walk.setRecursive(false);
2133 			walk.addTree(new FileTreeIterator(db));
2134 			String expectedValue;
2135 			String path;
2136 			int nrFiles = 0;
2137 			FileTreeIterator ft;
2138 			while (walk.next()) {
2139 				ft = walk.getTree(0, FileTreeIterator.class);
2140 				path = ft.getEntryPathString();
2141 				expectedValue = i.get(path);
2142 				File file = new File(db.getWorkTree(), path);
2143 				assertTrue(file.exists());
2144 				if (file.isFile()) {
2145 					assertNotNull("found unexpected file for path " + path
2146 							+ " in workdir", expectedValue);
2147 					try (FileInputStream is = new FileInputStream(file)) {
2148 						byte[] buffer = new byte[(int) file.length()];
2149 						int offset = 0;
2150 						int numRead = 0;
2151 						while (offset < buffer.length
2152 								&& (numRead = is.read(buffer, offset,
2153 										buffer.length - offset)) >= 0) {
2154 							offset += numRead;
2155 						}
2156 						assertArrayEquals(
2157 								"unexpected content for path " + path
2158 										+ " in workDir. ",
2159 								buffer, i.get(path).getBytes(UTF_8));
2160 					}
2161 					nrFiles++;
2162 				} else if (file.isDirectory()) {
2163 					String[] files = file.list();
2164 					if (files != null && files.length == 0) {
2165 						assertEquals("found unexpected empty folder for path "
2166 								+ path + " in workDir. ", "/", i.get(path));
2167 						nrFiles++;
2168 					}
2169 				}
2170 				if (walk.isSubtree()) {
2171 					walk.enterSubtree();
2172 				}
2173 			}
2174 			assertEquals("WorkDir has not the right size.", i.size(), nrFiles);
2175 		}
2176 	}
2177 }