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