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