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