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