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