/* * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com> * Copyright (C) 2008-2011, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008-2011, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2010-2011, Christian Halstrick <christian.halstrick@sap.com> * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v1.0 which accompanies this * distribution, is reproduced below, and is available at * http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.lib; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jgit.api.CheckoutCommand; import org.eclipse.jgit.api.CheckoutResult; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.junit.Assume; import org.junit.Test; public class DirCacheCheckoutTest extends RepositoryTestCase { private DirCacheCheckout dco; protected ObjectId theHead; protected ObjectId theMerge; private DirCache dirCache; private void prescanTwoTrees(ObjectId head, ObjectId merge) throws IllegalStateException, IOException { DirCache dc = db.lockDirCache(); try { dco = new DirCacheCheckout(db, head, dc, merge); dco.preScanTwoTrees(); } finally { dc.unlock(); } } private void checkout() throws IOException { DirCache dc = db.lockDirCache(); try { dco = new DirCacheCheckout(db, theHead, dc, theMerge); dco.checkout(); } finally { dc.unlock(); } } private List<String> getRemoved() { return dco.getRemoved(); } private Map<String, CheckoutMetadata> getUpdated() { return dco.getUpdated(); } private List<String> getConflicts() { return dco.getConflicts(); } private static HashMap<String, String> mk(String a) { return mkmap(a, a); } private static HashMap<String, String> mkmap(String... args) { if ((args.length % 2) > 0) throw new IllegalArgumentException("needs to be pairs"); HashMap<String, String> map = new HashMap<String, String>(); for (int i = 0; i < args.length; i += 2) { map.put(args[i], args[i + 1]); } return map; } @Test public void testResetHard() throws IOException, NoFilepatternException, GitAPIException { try (Git git = new Git(db)) { writeTrashFile("f", "f()"); writeTrashFile("D/g", "g()"); git.add().addFilepattern(".").call(); git.commit().setMessage("inital").call(); assertIndex(mkmap("f", "f()", "D/g", "g()")); git.branchCreate().setName("topic").call(); writeTrashFile("f", "f()\nmaster"); writeTrashFile("D/g", "g()\ng2()"); writeTrashFile("E/h", "h()"); git.add().addFilepattern(".").call(); RevCommit master = git.commit().setMessage("master-1").call(); assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); checkoutBranch("refs/heads/topic"); assertIndex(mkmap("f", "f()", "D/g", "g()")); writeTrashFile("f", "f()\nside"); assertTrue(new File(db.getWorkTree(), "D/g").delete()); writeTrashFile("G/i", "i()"); git.add().addFilepattern(".").call(); git.add().addFilepattern(".").setUpdate(true).call(); RevCommit topic = git.commit().setMessage("topic-1").call(); assertIndex(mkmap("f", "f()\nside", "G/i", "i()")); writeTrashFile("untracked", "untracked"); resetHard(master); assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); resetHard(topic); assertIndex(mkmap("f", "f()\nside", "G/i", "i()")); assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked", "untracked")); assertEquals(MergeStatus.CONFLICTING, git.merge().include(master) .call().getMergeStatus()); assertEquals( "[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]", indexState(0)); resetHard(master); assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()", "untracked", "untracked")); } } /** * Reset hard from unclean condition. * <p> * WorkDir: Empty <br/> * Index: f/g <br/> * Merge: x * * @throws Exception */ @Test public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile() throws Exception { try (Git git = new Git(db)) { writeTrashFile("x", "x"); git.add().addFilepattern("x").call(); RevCommit id1 = git.commit().setMessage("c1").call(); writeTrashFile("f/g", "f/g"); git.rm().addFilepattern("x").call(); git.add().addFilepattern("f/g").call(); git.commit().setMessage("c2").call(); deleteTrashFile("f/g"); deleteTrashFile("f"); // The actual test git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call(); assertIndex(mkmap("x", "x")); } } /** * Test first checkout in a repo * * @throws Exception */ @Test public void testInitialCheckout() throws Exception { try (Git git = new Git(db)) { TestRepository<Repository> db_t = new TestRepository<Repository>(db); BranchBuilder master = db_t.branch("master"); master.commit().add("f", "1").message("m0").create(); assertFalse(new File(db.getWorkTree(), "f").exists()); git.checkout().setName("master").call(); assertTrue(new File(db.getWorkTree(), "f").exists()); } } private DirCacheCheckout resetHard(RevCommit commit) throws NoWorkTreeException, CorruptObjectException, IOException { DirCacheCheckout dc; dc = new DirCacheCheckout(db, null, db.lockDirCache(), commit.getTree()); dc.setFailOnConflict(true); assertTrue(dc.checkout()); return dc; } private void assertIndex(HashMap<String, String> i) throws CorruptObjectException, IOException { String expectedValue; String path; DirCache read = DirCache.read(db.getIndexFile(), db.getFS()); assertEquals("Index has not the right size.", i.size(), read.getEntryCount()); for (int j = 0; j < read.getEntryCount(); j++) { path = read.getEntry(j).getPathString(); expectedValue = i.get(path); assertNotNull("found unexpected entry for path " + path + " in index", expectedValue); assertTrue("unexpected content for path " + path + " in index. Expected: <" + expectedValue + ">", Arrays.equals(db.open(read.getEntry(j).getObjectId()) .getCachedBytes(), i.get(path).getBytes())); } } @Test public void testRules1thru3_NoIndexEntry() throws IOException { ObjectId head = buildTree(mk("foo")); ObjectId merge = db.newObjectInserter().insert(Constants.OBJ_TREE, new byte[0]); prescanTwoTrees(head, merge); assertTrue(getRemoved().contains("foo")); prescanTwoTrees(merge, head); assertTrue(getUpdated().containsKey("foo")); merge = buildTree(mkmap("foo", "a")); prescanTwoTrees(head, merge); assertConflict("foo"); } void setupCase(HashMap<String, String> headEntries, HashMap<String, String> mergeEntries, HashMap<String, String> indexEntries) throws IOException { theHead = buildTree(headEntries); theMerge = buildTree(mergeEntries); buildIndex(indexEntries); } private void buildIndex(HashMap<String, String> indexEntries) throws IOException { dirCache = new DirCache(db.getIndexFile(), db.getFS()); if (indexEntries != null) { assertTrue(dirCache.lock()); DirCacheEditor editor = dirCache.editor(); for (java.util.Map.Entry<String,String> e : indexEntries.entrySet()) { writeTrashFile(e.getKey(), e.getValue()); ObjectInserter inserter = db.newObjectInserter(); final ObjectId id = inserter.insert(Constants.OBJ_BLOB, Constants.encode(e.getValue())); editor.add(new DirCacheEditor.DeletePath(e.getKey())); editor.add(new DirCacheEditor.PathEdit(e.getKey()) { @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.REGULAR_FILE); ent.setObjectId(id); ent.setUpdateNeeded(false); } }); } assertTrue(editor.commit()); } } static final class AddEdit extends PathEdit { private final ObjectId data; private final long length; public AddEdit(String entryPath, ObjectId data, long length) { super(entryPath); this.data = data; this.length = length; } @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.REGULAR_FILE); ent.setLength(length); ent.setObjectId(data); } } private ObjectId buildTree(HashMap<String, String> headEntries) throws IOException { DirCache lockDirCache = DirCache.newInCore(); // assertTrue(lockDirCache.lock()); DirCacheEditor editor = lockDirCache.editor(); if (headEntries != null) { for (java.util.Map.Entry<String, String> e : headEntries.entrySet()) { AddEdit addEdit = new AddEdit(e.getKey(), genSha1(e.getValue()), e.getValue().length()); editor.add(addEdit); } } editor.finish(); return lockDirCache.writeTree(db.newObjectInserter()); } ObjectId genSha1(String data) { try (ObjectInserter w = db.newObjectInserter()) { ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes()); w.flush(); return id; } catch (IOException e) { fail(e.toString()); } return null; } protected void go() throws IllegalStateException, IOException { prescanTwoTrees(theHead, theMerge); } @Test public void testRules4thru13_IndexEntryNotInHead() throws IOException { // rules 4 and 5 HashMap<String, String> idxMap; idxMap = new HashMap<String, String>(); idxMap.put("foo", "foo"); setupCase(null, null, idxMap); go(); assertTrue(getUpdated().isEmpty()); assertTrue(getRemoved().isEmpty()); assertTrue(getConflicts().isEmpty()); // rules 6 and 7 idxMap = new HashMap<String, String>(); idxMap.put("foo", "foo"); setupCase(null, idxMap, idxMap); go(); assertAllEmpty(); // rules 8 and 9 HashMap<String, String> mergeMap; mergeMap = new HashMap<String, String>(); mergeMap.put("foo", "merge"); setupCase(null, mergeMap, idxMap); go(); assertTrue(getUpdated().isEmpty()); assertTrue(getRemoved().isEmpty()); assertTrue(getConflicts().contains("foo")); // rule 10 HashMap<String, String> headMap = new HashMap<String, String>(); headMap.put("foo", "foo"); setupCase(headMap, null, idxMap); go(); assertTrue(getRemoved().contains("foo")); assertTrue(getUpdated().isEmpty()); assertTrue(getConflicts().isEmpty()); // rule 11 setupCase(headMap, null, idxMap); assertTrue(new File(trash, "foo").delete()); writeTrashFile("foo", "bar"); db.readDirCache().getEntry(0).setUpdateNeeded(true); go(); assertTrue(getRemoved().isEmpty()); assertTrue(getUpdated().isEmpty()); assertTrue(getConflicts().contains("foo")); // rule 12 & 13 headMap.put("foo", "head"); setupCase(headMap, null, idxMap); go(); assertTrue(getRemoved().isEmpty()); assertTrue(getUpdated().isEmpty()); assertTrue(getConflicts().contains("foo")); // rules 14 & 15 setupCase(headMap, headMap, idxMap); go(); assertAllEmpty(); // rules 16 & 17 setupCase(headMap, mergeMap, idxMap); go(); assertTrue(getConflicts().contains("foo")); // rules 18 & 19 setupCase(headMap, idxMap, idxMap); go(); assertAllEmpty(); // rule 20 setupCase(idxMap, mergeMap, idxMap); go(); assertTrue(getUpdated().containsKey("foo")); // rules 21 setupCase(idxMap, mergeMap, idxMap); assertTrue(new File(trash, "foo").delete()); writeTrashFile("foo", "bar"); db.readDirCache().getEntry(0).setUpdateNeeded(true); go(); assertTrue(getConflicts().contains("foo")); } private void assertAllEmpty() { assertTrue(getRemoved().isEmpty()); assertTrue(getUpdated().isEmpty()); assertTrue(getConflicts().isEmpty()); } /*- * Directory/File Conflict cases: * It's entirely possible that in practice a number of these may be equivalent * to the cases described in git-read-tree.txt. As long as it does the right thing, * that's all I care about. These are basically reverse-engineered from * what git currently does. If there are tests for these in git, it's kind of * hard to track them all down... * * H I M Clean H==M H==I I==M Result * ------------------------------------------------------------------ *1 D D F Y N Y N Update *2 D D F N N Y N Conflict *3 D F D Y N N Keep *4 D F D N N N Conflict *5 D F F Y N N Y Keep *5b D F F Y N N N Conflict *6 D F F N N N Y Keep *6b D F F N N N N Conflict *7 F D F Y Y N N Update *8 F D F N Y N N Conflict *9 F D F Y N N N Update *10 F D D N N Y Keep *11 F D D N N N Conflict *12 F F D Y N Y N Update *13 F F D N N Y N Conflict *14 F F D N N N Conflict *15 0 F D N N N Conflict *16 0 D F Y N N N Update *17 0 D F N N N Conflict *18 F 0 D Update *19 D 0 F Update */ @Test public void testDirectoryFileSimple() throws IOException { ObjectId treeDF = buildTree(mkmap("DF", "DF")); ObjectId treeDFDF = buildTree(mkmap("DF/DF", "DF/DF")); buildIndex(mkmap("DF", "DF")); prescanTwoTrees(treeDF, treeDFDF); assertTrue(getRemoved().contains("DF")); assertTrue(getUpdated().containsKey("DF/DF")); recursiveDelete(new File(trash, "DF")); buildIndex(mkmap("DF/DF", "DF/DF")); prescanTwoTrees(treeDFDF, treeDF); assertTrue(getRemoved().contains("DF/DF")); assertTrue(getUpdated().containsKey("DF")); } @Test public void testDirectoryFileConflicts_1() throws Exception { // 1 doit(mk("DF/DF"), mk("DF"), mk("DF/DF")); assertNoConflicts(); assertUpdated("DF"); assertRemoved("DF/DF"); } @Test public void testDirectoryFileConflicts_2() throws Exception { // 2 setupCase(mk("DF/DF"), mk("DF"), mk("DF/DF")); writeTrashFile("DF/DF", "different"); go(); assertConflict("DF/DF"); } @Test public void testDirectoryFileConflicts_3() throws Exception { // 3 doit(mk("DF/DF"), mk("DF/DF"), mk("DF")); assertNoConflicts(); } @Test public void testDirectoryFileConflicts_4() throws Exception { // 4 (basically same as 3, just with H and M different) doit(mk("DF/DF"), mkmap("DF/DF", "foo"), mk("DF")); assertConflict("DF/DF"); } @Test public void testDirectoryFileConflicts_5() throws Exception { // 5 doit(mk("DF/DF"), mk("DF"), mk("DF")); assertRemoved("DF/DF"); assertEquals(0, dco.getConflicts().size()); assertEquals(0, dco.getUpdated().size()); } @Test public void testDirectoryFileConflicts_5b() throws Exception { // 5 doit(mk("DF/DF"), mkmap("DF", "different"), mk("DF")); assertRemoved("DF/DF"); assertConflict("DF"); assertEquals(0, dco.getUpdated().size()); } @Test public void testDirectoryFileConflicts_6() throws Exception { // 6 setupCase(mk("DF/DF"), mk("DF"), mk("DF")); writeTrashFile("DF", "different"); go(); assertRemoved("DF/DF"); assertEquals(0, dco.getConflicts().size()); assertEquals(0, dco.getUpdated().size()); } @Test public void testDirectoryFileConflicts_6b() throws Exception { // 6 setupCase(mk("DF/DF"), mk("DF"), mkmap("DF", "different")); writeTrashFile("DF", "again different"); go(); assertRemoved("DF/DF"); assertConflict("DF"); assertEquals(0, dco.getUpdated().size()); } @Test public void testDirectoryFileConflicts_7() throws Exception { // 7 doit(mk("DF"), mk("DF"), mk("DF/DF")); assertUpdated("DF"); assertRemoved("DF/DF"); cleanUpDF(); setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF")); go(); assertRemoved("DF/DF/DF/DF/DF"); assertUpdated("DF/DF"); cleanUpDF(); setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF")); writeTrashFile("DF/DF/DF/DF/DF", "diff"); go(); assertConflict("DF/DF/DF/DF/DF"); // assertUpdated("DF/DF"); // Why do we expect an update on DF/DF. H==M, // H&M are files and index contains a dir, index // is dirty: that case is not in the table but // we cannot update DF/DF to a file, this would // require that we delete DF/DF/DF/DF/DF in workdir // throwing away unsaved contents. // This test would fail in DirCacheCheckoutTests. } @Test public void testDirectoryFileConflicts_8() throws Exception { // 8 setupCase(mk("DF"), mk("DF"), mk("DF/DF")); recursiveDelete(new File(db.getWorkTree(), "DF")); writeTrashFile("DF", "xy"); go(); assertConflict("DF/DF"); } @Test public void testDirectoryFileConflicts_9() throws Exception { // 9 doit(mkmap("DF", "QP"), mkmap("DF", "QP"), mkmap("DF/DF", "DF/DF")); assertRemoved("DF/DF"); assertUpdated("DF"); } @Test public void testDirectoryFileConflicts_10() throws Exception { // 10 cleanUpDF(); doit(mk("DF"), mk("DF/DF"), mk("DF/DF")); assertNoConflicts(); } @Test public void testDirectoryFileConflicts_11() throws Exception { // 11 doit(mk("DF"), mk("DF/DF"), mkmap("DF/DF", "asdf")); assertConflict("DF/DF"); } @Test public void testDirectoryFileConflicts_12() throws Exception { // 12 cleanUpDF(); doit(mk("DF"), mk("DF/DF"), mk("DF")); assertRemoved("DF"); assertUpdated("DF/DF"); } @Test public void testDirectoryFileConflicts_13() throws Exception { // 13 cleanUpDF(); setupCase(mk("DF"), mk("DF/DF"), mk("DF")); writeTrashFile("DF", "asdfsdf"); go(); assertConflict("DF"); assertUpdated("DF/DF"); } @Test public void testDirectoryFileConflicts_14() throws Exception { // 14 cleanUpDF(); doit(mk("DF"), mk("DF/DF"), mkmap("DF", "Foo")); assertConflict("DF"); assertUpdated("DF/DF"); } @Test public void testDirectoryFileConflicts_15() throws Exception { // 15 doit(mkmap(), mk("DF/DF"), mk("DF")); // This test would fail in DirCacheCheckoutTests. I think this test is wrong, // it should check for conflicts according to rule 15 // assertRemoved("DF"); assertUpdated("DF/DF"); } @Test public void testDirectoryFileConflicts_15b() throws Exception { // 15, take 2, just to check multi-leveled doit(mkmap(), mk("DF/DF/DF/DF"), mk("DF")); // I think this test is wrong, it should // check for conflicts according to rule 15 // This test would fail in DirCacheCheckouts // assertRemoved("DF"); assertUpdated("DF/DF/DF/DF"); } @Test public void testDirectoryFileConflicts_16() throws Exception { // 16 cleanUpDF(); doit(mkmap(), mk("DF"), mk("DF/DF/DF")); assertRemoved("DF/DF/DF"); assertUpdated("DF"); } @Test public void testDirectoryFileConflicts_17() throws Exception { // 17 cleanUpDF(); setupCase(mkmap(), mk("DF"), mk("DF/DF/DF")); writeTrashFile("DF/DF/DF", "asdf"); go(); assertConflict("DF/DF/DF"); // Why do we expect an update on DF. If we really update // DF and update also the working tree we would have to // overwrite a dirty file in the work-tree DF/DF/DF // This test would fail in DirCacheCheckout // assertUpdated("DF"); } @Test public void testDirectoryFileConflicts_18() throws Exception { // 18 cleanUpDF(); doit(mk("DF/DF"), mk("DF/DF/DF/DF"), null); assertRemoved("DF/DF"); assertUpdated("DF/DF/DF/DF"); } @Test public void testDirectoryFileConflicts_19() throws Exception { // 19 cleanUpDF(); doit(mk("DF/DF/DF/DF"), mk("DF/DF/DF"), null); assertRemoved("DF/DF/DF/DF"); assertUpdated("DF/DF/DF"); } protected void cleanUpDF() throws Exception { tearDown(); setUp(); recursiveDelete(new File(trash, "DF")); } protected void assertConflict(String s) { assertTrue(getConflicts().contains(s)); } protected void assertUpdated(String s) { assertTrue(getUpdated().containsKey(s)); } protected void assertRemoved(String s) { assertTrue(getRemoved().contains(s)); } protected void assertNoConflicts() { assertTrue(getConflicts().isEmpty()); } protected void doit(HashMap<String, String> h, HashMap<String, String> m, HashMap<String, String> i) throws IOException { setupCase(h, m, i); go(); } @Test public void testUntrackedConflicts() throws IOException { setupCase(null, mk("foo"), null); writeTrashFile("foo", "foo"); go(); // test that we don't overwrite untracked files when there is a HEAD recursiveDelete(new File(trash, "foo")); setupCase(mk("other"), mkmap("other", "other", "foo", "foo"), mk("other")); writeTrashFile("foo", "bar"); try { checkout(); fail("didn't get the expected exception"); } catch (CheckoutConflictException e) { assertConflict("foo"); assertEquals("foo", e.getConflictingFiles()[0]); assertWorkDir(mkmap("foo", "bar", "other", "other")); assertIndex(mk("other")); } // test that we don't overwrite untracked files when there is no HEAD recursiveDelete(new File(trash, "other")); recursiveDelete(new File(trash, "foo")); setupCase(null, mk("foo"), null); writeTrashFile("foo", "bar"); try { checkout(); fail("didn't get the expected exception"); } catch (CheckoutConflictException e) { assertConflict("foo"); assertWorkDir(mkmap("foo", "bar")); assertIndex(mkmap("other", "other")); } // TODO: Why should we expect conflicts here? // H and M are empty and according to rule #5 of // the carry-over rules a dirty index is no reason // for a conflict. (I also feel it should be a // conflict because we are going to overwrite // unsaved content in the working tree // This test would fail in DirCacheCheckoutTest // assertConflict("foo"); recursiveDelete(new File(trash, "foo")); recursiveDelete(new File(trash, "other")); setupCase(null, mk("foo"), null); writeTrashFile("foo/bar/baz", ""); writeTrashFile("foo/blahblah", ""); go(); assertConflict("foo"); assertConflict("foo/bar/baz"); assertConflict("foo/blahblah"); recursiveDelete(new File(trash, "foo")); setupCase(mkmap("foo/bar", "", "foo/baz", ""), mk("foo"), mkmap("foo/bar", "", "foo/baz", "")); assertTrue(new File(trash, "foo/bar").exists()); go(); assertNoConflicts(); } @Test public void testCloseNameConflictsX0() throws IOException { 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") ); checkout(); assertIndex(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs")); assertWorkDir(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs")); go(); assertIndex(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs")); assertWorkDir(mkmap("a/a", "a/a", "b.b/b.b", "b.b/b.bs")); assertNoConflicts(); } @Test public void testCloseNameConflicts1() throws IOException { 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") ); checkout(); assertIndex(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a")); assertWorkDir(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a")); go(); assertIndex(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a")); assertWorkDir(mkmap("a/a", "a/a", "a.a/a.a", "a.a/a.a")); assertNoConflicts(); } @Test public void testCheckoutHierarchy() throws IOException { setupCase( mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g", "e/g"), mkmap("a", "a2", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g", "e/g2"), mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g", "e/g3")); try { checkout(); } catch (CheckoutConflictException e) { assertWorkDir(mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f", "e/f", "e/g", "e/g3")); assertConflict("e/g"); assertEquals("e/g", e.getConflictingFiles()[0]); } } @Test public void testCheckoutOutChanges() throws IOException { setupCase(mk("foo"), mk("foo/bar"), mk("foo")); checkout(); assertIndex(mk("foo/bar")); assertWorkDir(mk("foo/bar")); assertFalse(new File(trash, "foo").isFile()); assertTrue(new File(trash, "foo/bar").isFile()); recursiveDelete(new File(trash, "foo")); assertWorkDir(mkmap()); setupCase(mk("foo/bar"), mk("foo"), mk("foo/bar")); checkout(); assertIndex(mk("foo")); assertWorkDir(mk("foo")); assertFalse(new File(trash, "foo/bar").isFile()); assertTrue(new File(trash, "foo").isFile()); setupCase(mk("foo"), mkmap("foo", "qux"), mkmap("foo", "bar")); assertIndex(mkmap("foo", "bar")); assertWorkDir(mkmap("foo", "bar")); try { checkout(); fail("did not throw exception"); } catch (CheckoutConflictException e) { assertIndex(mkmap("foo", "bar")); assertWorkDir(mkmap("foo", "bar")); } } @Test public void testCheckoutChangeLinkToEmptyDir() throws Exception { Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "was_file"; Git git = Git.wrap(db); // Add a file writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); // Add a link to file String linkName = "link"; File link = writeLink(linkName, fname).toFile(); git.add().addFilepattern(linkName).call(); git.commit().setMessage("Added file and link").call(); assertWorkDir(mkmap(linkName, "a", fname, "a")); // replace link with empty directory FileUtils.delete(link); FileUtils.mkdir(link); assertTrue("Link must be a directory now", link.isDirectory()); // modify file writeTrashFile(fname, "b"); assertWorkDir(mkmap(fname, "b", linkName, "/")); // revert both paths to HEAD state git.checkout().setStartPoint(Constants.HEAD) .addPath(fname).addPath(linkName).call(); assertWorkDir(mkmap(fname, "a", linkName, "a")); Status st = git.status().call(); assertTrue(st.isClean()); } @Test public void testCheckoutChangeLinkToEmptyDirs() throws Exception { Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "was_file"; Git git = Git.wrap(db); // Add a file writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); // Add a link to file String linkName = "link"; File link = writeLink(linkName, fname).toFile(); git.add().addFilepattern(linkName).call(); git.commit().setMessage("Added file and link").call(); assertWorkDir(mkmap(linkName, "a", fname, "a")); // replace link with directory containing only directories, no files FileUtils.delete(link); FileUtils.mkdirs(new File(link, "dummyDir")); assertTrue("Link must be a directory now", link.isDirectory()); assertFalse("Must not delete non empty directory", link.delete()); // modify file writeTrashFile(fname, "b"); assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/")); // revert both paths to HEAD state git.checkout().setStartPoint(Constants.HEAD) .addPath(fname).addPath(linkName).call(); assertWorkDir(mkmap(fname, "a", linkName, "a")); Status st = git.status().call(); assertTrue(st.isClean()); } @Test public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception { Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "file"; Git git = Git.wrap(db); // Add a file writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); // Add a link to file String linkName = "link"; File link = writeLink(linkName, fname).toFile(); git.add().addFilepattern(linkName).call(); git.commit().setMessage("Added file and link").call(); assertWorkDir(mkmap(linkName, "a", fname, "a")); // replace link with directory containing only directories, no files FileUtils.delete(link); // create but do not add a file in the new directory to the index writeTrashFile(linkName + "/dir1", "file1", "c"); // create but do not add a file in the new directory to the index writeTrashFile(linkName + "/dir2", "file2", "d"); assertTrue("File must be a directory now", link.isDirectory()); assertFalse("Must not delete non empty directory", link.delete()); // 2 extra files are created assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", linkName + "/dir2/file2", "d")); // revert path to HEAD state git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call(); // expect only the one added to the index assertWorkDir(mkmap(linkName, "a", fname, "a")); Status st = git.status().call(); assertTrue(st.isClean()); } @Test public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry() throws Exception { Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "file"; Git git = Git.wrap(db); // Add a file writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); // Add a link to file String linkName = "link"; File link = writeLink(linkName, fname).toFile(); git.add().addFilepattern(linkName).call(); git.commit().setMessage("Added file and link").call(); assertWorkDir(mkmap(linkName, "a", fname, "a")); // replace link with directory containing only directories, no files FileUtils.delete(link); // create and add a file in the new directory to the index writeTrashFile(linkName + "/dir1", "file1", "c"); git.add().addFilepattern(linkName + "/dir1/file1").call(); // create but do not add a file in the new directory to the index writeTrashFile(linkName + "/dir2", "file2", "d"); assertTrue("File must be a directory now", link.isDirectory()); assertFalse("Must not delete non empty directory", link.delete()); // 2 extra files are created assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", linkName + "/dir2/file2", "d")); // revert path to HEAD state git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call(); // original file and link assertWorkDir(mkmap(linkName, "a", fname, "a")); Status st = git.status().call(); assertTrue(st.isClean()); } @Test public void testCheckoutChangeFileToEmptyDir() throws Exception { String fname = "was_file"; Git git = Git.wrap(db); // Add a file File file = writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); git.commit().setMessage("Added file").call(); // replace file with empty directory FileUtils.delete(file); FileUtils.mkdir(file); assertTrue("File must be a directory now", file.isDirectory()); assertWorkDir(mkmap(fname, "/")); // revert path to HEAD state git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); assertWorkDir(mkmap(fname, "a")); Status st = git.status().call(); assertTrue(st.isClean()); } @Test public void testCheckoutChangeFileToEmptyDirs() throws Exception { String fname = "was_file"; Git git = Git.wrap(db); // Add a file File file = writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); git.commit().setMessage("Added file").call(); // replace file with directory containing only directories, no files FileUtils.delete(file); FileUtils.mkdirs(new File(file, "dummyDir")); assertTrue("File must be a directory now", file.isDirectory()); assertFalse("Must not delete non empty directory", file.delete()); assertWorkDir(mkmap(fname + "/dummyDir", "/")); // revert path to HEAD state git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); assertWorkDir(mkmap(fname, "a")); Status st = git.status().call(); assertTrue(st.isClean()); } @Test public void testCheckoutChangeFileToNonEmptyDirs() throws Exception { String fname = "was_file"; Git git = Git.wrap(db); // Add a file File file = writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); git.commit().setMessage("Added file").call(); assertWorkDir(mkmap(fname, "a")); // replace file with directory containing only directories, no files FileUtils.delete(file); // create but do not add a file in the new directory to the index writeTrashFile(fname + "/dir1", "file1", "c"); // create but do not add a file in the new directory to the index writeTrashFile(fname + "/dir2", "file2", "d"); assertTrue("File must be a directory now", file.isDirectory()); assertFalse("Must not delete non empty directory", file.delete()); // 2 extra files are created assertWorkDir( mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d")); // revert path to HEAD state git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); // expect only the one added to the index assertWorkDir(mkmap(fname, "a")); Status st = git.status().call(); assertTrue(st.isClean()); } @Test public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry() throws Exception { String fname = "was_file"; Git git = Git.wrap(db); // Add a file File file = writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); git.commit().setMessage("Added file").call(); assertWorkDir(mkmap(fname, "a")); // replace file with directory containing only directories, no files FileUtils.delete(file); // create and add a file in the new directory to the index writeTrashFile(fname + "/dir", "file1", "c"); git.add().addFilepattern(fname + "/dir/file1").call(); // create but do not add a file in the new directory to the index writeTrashFile(fname + "/dir", "file2", "d"); assertTrue("File must be a directory now", file.isDirectory()); assertFalse("Must not delete non empty directory", file.delete()); // 2 extra files are created assertWorkDir( mkmap(fname + "/dir/file1", "c", fname + "/dir/file2", "d")); // revert path to HEAD state git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); assertWorkDir(mkmap(fname, "a")); Status st = git.status().call(); assertTrue(st.isClean()); } @Test public void testCheckoutOutChangesAutoCRLFfalse() throws IOException { setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo")); checkout(); assertIndex(mkmap("foo/bar", "foo\nbar")); assertWorkDir(mkmap("foo/bar", "foo\nbar")); } @Test public void testCheckoutOutChangesAutoCRLFInput() throws IOException { setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo")); db.getConfig().setString("core", null, "autocrlf", "input"); checkout(); assertIndex(mkmap("foo/bar", "foo\nbar")); assertWorkDir(mkmap("foo/bar", "foo\nbar")); } @Test public void testCheckoutOutChangesAutoCRLFtrue() throws IOException { setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo")); db.getConfig().setString("core", null, "autocrlf", "true"); checkout(); assertIndex(mkmap("foo/bar", "foo\nbar")); assertWorkDir(mkmap("foo/bar", "foo\r\nbar")); } @Test public void testCheckoutOutChangesAutoCRLFtrueBinary() throws IOException { setupCase(mk("foo"), mkmap("foo/bar", "foo\nb\u0000ar"), mk("foo")); db.getConfig().setString("core", null, "autocrlf", "true"); checkout(); assertIndex(mkmap("foo/bar", "foo\nb\u0000ar")); assertWorkDir(mkmap("foo/bar", "foo\nb\u0000ar")); } @Test public void testCheckoutUncachedChanges() throws IOException { setupCase(mk("foo"), mk("foo"), mk("foo")); writeTrashFile("foo", "otherData"); checkout(); assertIndex(mk("foo")); assertWorkDir(mkmap("foo", "otherData")); assertTrue(new File(trash, "foo").isFile()); } @Test public void testDontOverwriteDirtyFile() throws IOException { setupCase(mk("foo"), mk("other"), mk("foo")); writeTrashFile("foo", "different"); try { checkout(); fail("Didn't got the expected conflict"); } catch (CheckoutConflictException e) { assertIndex(mk("foo")); assertWorkDir(mkmap("foo", "different")); assertEquals(Arrays.asList("foo"), getConflicts()); assertTrue(new File(trash, "foo").isFile()); } } @Test public void testDontOverwriteEmptyFolder() throws IOException { setupCase(mk("foo"), mk("foo"), mk("foo")); FileUtils.mkdir(new File(db.getWorkTree(), "d")); checkout(); assertWorkDir(mkmap("foo", "foo", "d", "/")); } @Test public void testOverwriteUntrackedIgnoredFile() throws IOException, GitAPIException { String fname="file.txt"; Git git = Git.wrap(db); // Add a file writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); git.commit().setMessage("create file").call(); // Create branch git.branchCreate().setName("side").call(); // Modify file writeTrashFile(fname, "b"); git.add().addFilepattern(fname).call(); git.commit().setMessage("modify file").call(); // Switch branches git.checkout().setName("side").call(); git.rm().addFilepattern(fname).call(); writeTrashFile(".gitignore", fname); git.add().addFilepattern(".gitignore").call(); git.commit().setMessage("delete and ignore file").call(); writeTrashFile(fname, "Something different"); git.checkout().setName("master").call(); assertWorkDir(mkmap(fname, "b")); assertTrue(git.status().call().isClean()); } @Test public void testOverwriteUntrackedFileModeChange() throws IOException, GitAPIException { String fname = "file.txt"; Git git = Git.wrap(db); // Add a file File file = writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); git.commit().setMessage("create file").call(); assertWorkDir(mkmap(fname, "a")); // Create branch git.branchCreate().setName("side").call(); // Switch branches git.checkout().setName("side").call(); // replace file with directory containing files FileUtils.delete(file); // create and add a file in the new directory to the index writeTrashFile(fname + "/dir1", "file1", "c"); git.add().addFilepattern(fname + "/dir1/file1").call(); // create but do not add a file in the new directory to the index writeTrashFile(fname + "/dir2", "file2", "d"); assertTrue("File must be a directory now", file.isDirectory()); assertFalse("Must not delete non empty directory", file.delete()); // 2 extra files are created assertWorkDir( mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d")); try { git.checkout().setName("master").call(); fail("did not throw exception"); } catch (Exception e) { // 2 extra files are still there assertWorkDir(mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d")); } } @Test public void testOverwriteUntrackedLinkModeChange() throws Exception { Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "file.txt"; Git git = Git.wrap(db); // Add a file writeTrashFile(fname, "a"); git.add().addFilepattern(fname).call(); // Add a link to file String linkName = "link"; File link = writeLink(linkName, fname).toFile(); git.add().addFilepattern(linkName).call(); git.commit().setMessage("Added file and link").call(); assertWorkDir(mkmap(linkName, "a", fname, "a")); // Create branch git.branchCreate().setName("side").call(); // Switch branches git.checkout().setName("side").call(); // replace link with directory containing files FileUtils.delete(link); // create and add a file in the new directory to the index writeTrashFile(linkName + "/dir1", "file1", "c"); git.add().addFilepattern(linkName + "/dir1/file1").call(); // create but do not add a file in the new directory to the index writeTrashFile(linkName + "/dir2", "file2", "d"); assertTrue("Link must be a directory now", link.isDirectory()); assertFalse("Must not delete non empty directory", link.delete()); // 2 extra files are created assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", linkName + "/dir2/file2", "d")); try { git.checkout().setName("master").call(); fail("did not throw exception"); } catch (Exception e) { // 2 extra files are still there assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", linkName + "/dir2/file2", "d")); } } @Test public void testFileModeChangeWithNoContentChangeUpdate() throws Exception { if (!FS.DETECTED.supportsExecute()) return; Git git = Git.wrap(db); // Add non-executable file File file = writeTrashFile("file.txt", "a"); git.add().addFilepattern("file.txt").call(); git.commit().setMessage("commit1").call(); assertFalse(db.getFS().canExecute(file)); // Create branch git.branchCreate().setName("b1").call(); // Make file executable db.getFS().setExecute(file, true); git.add().addFilepattern("file.txt").call(); git.commit().setMessage("commit2").call(); // Verify executable and working directory is clean Status status = git.status().call(); assertTrue(status.getModified().isEmpty()); assertTrue(status.getChanged().isEmpty()); assertTrue(db.getFS().canExecute(file)); // Switch branches git.checkout().setName("b1").call(); // Verify not executable and working directory is clean status = git.status().call(); assertTrue(status.getModified().isEmpty()); assertTrue(status.getChanged().isEmpty()); assertFalse(db.getFS().canExecute(file)); } @Test public void testFileModeChangeAndContentChangeConflict() throws Exception { if (!FS.DETECTED.supportsExecute()) return; Git git = Git.wrap(db); // Add non-executable file File file = writeTrashFile("file.txt", "a"); git.add().addFilepattern("file.txt").call(); git.commit().setMessage("commit1").call(); assertFalse(db.getFS().canExecute(file)); // Create branch git.branchCreate().setName("b1").call(); // Make file executable db.getFS().setExecute(file, true); git.add().addFilepattern("file.txt").call(); git.commit().setMessage("commit2").call(); // Verify executable and working directory is clean Status status = git.status().call(); assertTrue(status.getModified().isEmpty()); assertTrue(status.getChanged().isEmpty()); assertTrue(db.getFS().canExecute(file)); writeTrashFile("file.txt", "b"); // Switch branches CheckoutCommand checkout = git.checkout().setName("b1"); try { checkout.call(); fail("Checkout exception not thrown"); } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) { CheckoutResult result = checkout.getResult(); assertNotNull(result); assertNotNull(result.getConflictList()); assertEquals(1, result.getConflictList().size()); assertTrue(result.getConflictList().contains("file.txt")); } } @Test public void testDirtyFileModeEqualHeadMerge() throws Exception { if (!FS.DETECTED.supportsExecute()) return; Git git = Git.wrap(db); // Add non-executable file File file = writeTrashFile("file.txt", "a"); git.add().addFilepattern("file.txt").call(); git.commit().setMessage("commit1").call(); assertFalse(db.getFS().canExecute(file)); // Create branch git.branchCreate().setName("b1").call(); // Create second commit and don't touch file writeTrashFile("file2.txt", ""); git.add().addFilepattern("file2.txt").call(); git.commit().setMessage("commit2").call(); // stage a mode change writeTrashFile("file.txt", "a"); db.getFS().setExecute(file, true); git.add().addFilepattern("file.txt").call(); // dirty the file writeTrashFile("file.txt", "b"); assertEquals( "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]", indexState(CONTENT)); assertWorkDir(mkmap("file.txt", "b", "file2.txt", "")); // Switch branches and check that the dirty file survived in worktree // and index git.checkout().setName("b1").call(); assertEquals("[file.txt, mode:100755, content:a]", indexState(CONTENT)); assertWorkDir(mkmap("file.txt", "b")); } @Test public void testDirtyFileModeEqualIndexMerge() throws Exception { if (!FS.DETECTED.supportsExecute()) return; Git git = Git.wrap(db); // Add non-executable file File file = writeTrashFile("file.txt", "a"); git.add().addFilepattern("file.txt").call(); git.commit().setMessage("commit1").call(); assertFalse(db.getFS().canExecute(file)); // Create branch git.branchCreate().setName("b1").call(); // Create second commit with executable file file = writeTrashFile("file.txt", "b"); db.getFS().setExecute(file, true); git.add().addFilepattern("file.txt").call(); git.commit().setMessage("commit2").call(); // stage the same content as in the branch we want to switch to writeTrashFile("file.txt", "a"); db.getFS().setExecute(file, false); git.add().addFilepattern("file.txt").call(); // dirty the file writeTrashFile("file.txt", "c"); db.getFS().setExecute(file, true); assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT)); assertWorkDir(mkmap("file.txt", "c")); // Switch branches and check that the dirty file survived in worktree // and index git.checkout().setName("b1").call(); assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT)); assertWorkDir(mkmap("file.txt", "c")); } @Test public void testFileModeChangeAndContentChangeNoConflict() throws Exception { if (!FS.DETECTED.supportsExecute()) return; Git git = Git.wrap(db); // Add first file File file1 = writeTrashFile("file1.txt", "a"); git.add().addFilepattern("file1.txt").call(); git.commit().setMessage("commit1").call(); assertFalse(db.getFS().canExecute(file1)); // Add second file File file2 = writeTrashFile("file2.txt", "b"); git.add().addFilepattern("file2.txt").call(); git.commit().setMessage("commit2").call(); assertFalse(db.getFS().canExecute(file2)); // Create branch from first commit assertNotNull(git.checkout().setCreateBranch(true).setName("b1") .setStartPoint(Constants.HEAD + "~1").call()); // Change content and file mode in working directory and index file1 = writeTrashFile("file1.txt", "c"); db.getFS().setExecute(file1, true); git.add().addFilepattern("file1.txt").call(); // Switch back to 'master' assertNotNull(git.checkout().setName(Constants.MASTER).call()); } @Test(expected = CheckoutConflictException.class) public void testFolderFileConflict() throws Exception { RevCommit headCommit = commitFile("f/a", "initial content", "master"); RevCommit checkoutCommit = commitFile("f/a", "side content", "side"); FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE); writeTrashFile("f", "file instead of folder"); new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(), checkoutCommit.getTree()).checkout(); } @Test public void testMultipleContentConflicts() throws Exception { commitFile("a", "initial content", "master"); RevCommit headCommit = commitFile("b", "initial content", "master"); commitFile("a", "side content", "side"); RevCommit checkoutCommit = commitFile("b", "side content", "side"); writeTrashFile("a", "changed content"); writeTrashFile("b", "changed content"); try { new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(), checkoutCommit.getTree()).checkout(); fail(); } catch (CheckoutConflictException expected) { assertEquals(2, expected.getConflictingFiles().length); assertTrue(Arrays.asList(expected.getConflictingFiles()) .contains("a")); assertTrue(Arrays.asList(expected.getConflictingFiles()) .contains("b")); assertEquals("changed content", read("a")); assertEquals("changed content", read("b")); } } @Test public void testFolderFileAndContentConflicts() throws Exception { RevCommit headCommit = commitFile("f/a", "initial content", "master"); commitFile("b", "side content", "side"); RevCommit checkoutCommit = commitFile("f/a", "side content", "side"); FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE); writeTrashFile("f", "file instead of a folder"); writeTrashFile("b", "changed content"); try { new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(), checkoutCommit.getTree()).checkout(); fail(); } catch (CheckoutConflictException expected) { assertEquals(2, expected.getConflictingFiles().length); assertTrue(Arrays.asList(expected.getConflictingFiles()) .contains("b")); assertTrue(Arrays.asList(expected.getConflictingFiles()) .contains("f")); assertEquals("file instead of a folder", read("f")); assertEquals("changed content", read("b")); } } public void assertWorkDir(Map<String, String> i) throws CorruptObjectException, IOException { try (TreeWalk walk = new TreeWalk(db)) { walk.setRecursive(false); walk.addTree(new FileTreeIterator(db)); String expectedValue; String path; int nrFiles = 0; FileTreeIterator ft; while (walk.next()) { ft = walk.getTree(0, FileTreeIterator.class); path = ft.getEntryPathString(); expectedValue = i.get(path); File file = new File(db.getWorkTree(), path); assertTrue(file.exists()); if (file.isFile()) { assertNotNull("found unexpected file for path " + path + " in workdir", expectedValue); FileInputStream is = new FileInputStream(file); byte[] buffer = new byte[(int) file.length()]; int offset = 0; int numRead = 0; while (offset < buffer.length && (numRead = is.read(buffer, offset, buffer.length - offset)) >= 0) { offset += numRead; } is.close(); assertArrayEquals("unexpected content for path " + path + " in workDir. ", buffer, i.get(path).getBytes()); nrFiles++; } else if (file.isDirectory()) { if (file.list().length == 0) { assertEquals("found unexpected empty folder for path " + path + " in workDir. ", "/", i.get(path)); nrFiles++; } } if (walk.isSubtree()) { walk.enterSubtree(); } } assertEquals("WorkDir has not the right size.", i.size(), nrFiles); } } }