diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java index 8c3f6f37c..3001880d2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java @@ -1,6 +1,9 @@ /* - * Copyright (C) 2010, Christian Halstrick and - * other copyright owners as documented in the project's IP log. + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008-2011, Shawn O. Pearce + * Copyright (C) 2008-2011, Robin Rosenberg + * Copyright (C) 2010-2011, Christian Halstrick + * 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 @@ -38,10 +41,16 @@ package org.eclipse.jgit.lib; 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; @@ -52,15 +61,23 @@ 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.DirCacheEditor; +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.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; import org.junit.Test; -public class DirCacheCheckoutTest extends ReadTreeTest { +public class DirCacheCheckoutTest extends RepositoryTestCase { private DirCacheCheckout dco; - @Override - public void prescanTwoTrees(Tree head, Tree merge) + protected Tree theHead; + protected Tree theMerge; + private DirCache dirCache; + + private void prescanTwoTrees(Tree head, Tree merge) throws IllegalStateException, IOException { DirCache dc = db.lockDirCache(); try { @@ -71,8 +88,7 @@ public class DirCacheCheckoutTest extends ReadTreeTest { } } - @Override - public void checkout() throws IOException { + private void checkout() throws IOException { DirCache dc = db.lockDirCache(); try { dco = new DirCacheCheckout(db, theHead.getId(), dc, theMerge.getId()); @@ -82,21 +98,34 @@ public class DirCacheCheckoutTest extends ReadTreeTest { } } - @Override - public List getRemoved() { + private List getRemoved() { return dco.getRemoved(); } - @Override - public Map getUpdated() { + private Map getUpdated() { return dco.getUpdated(); } - @Override - public List getConflicts() { + private List getConflicts() { return dco.getConflicts(); } + private static HashMap mk(String a) { + return mkmap(a, a); + } + + private static HashMap mkmap(String... args) { + if ((args.length % 2) > 0) + throw new IllegalArgumentException("needs to be pairs"); + + HashMap map = new HashMap(); + 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 { @@ -187,4 +216,711 @@ public class DirCacheCheckoutTest extends ReadTreeTest { assertTrue(dc.checkout()); return dc; } + + private void assertIndex(HashMap 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 { + Tree head = new Tree(db); + head = buildTree(mk("foo")); + ObjectId objectId = head.findBlobMember("foo").getId(); + Tree merge = new Tree(db); + + prescanTwoTrees(head, merge); + + assertTrue(getRemoved().contains("foo")); + + prescanTwoTrees(merge, head); + + assertEquals(objectId, getUpdated().get("foo")); + + merge = buildTree(mkmap("foo", "a")); + ObjectId anotherId = merge.findBlobMember("foo").getId(); + + prescanTwoTrees(head, merge); + + assertEquals(anotherId, getUpdated().get("foo")); + } + + void setupCase(HashMap headEntries, HashMap mergeEntries, HashMap indexEntries) throws IOException { + theHead = buildTree(headEntries); + theMerge = buildTree(mergeEntries); + buildIndex(indexEntries); + } + + private void buildIndex(HashMap 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 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()); + } + + } + + private Tree buildTree(HashMap headEntries) throws IOException { + Tree tree = new Tree(db); + if (headEntries == null) + return tree; + FileTreeEntry fileEntry; + Tree parent; + ObjectInserter oi = db.newObjectInserter(); + try { + for (java.util.Map.Entry e : headEntries.entrySet()) { + fileEntry = tree.addFile(e.getKey()); + fileEntry.setId(genSha1(e.getValue())); + parent = fileEntry.getParent(); + while (parent != null) { + parent.setId(oi.insert(Constants.OBJ_TREE, parent.format())); + parent = parent.getParent(); + } + } + oi.flush(); + } finally { + oi.release(); + } + return tree; + } + + ObjectId genSha1(String data) { + ObjectInserter w = db.newObjectInserter(); + try { + ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes()); + w.flush(); + return id; + } catch (IOException e) { + fail(e.toString()); + } finally { + w.release(); + } + return null; + } + + protected void go() throws IllegalStateException, IOException { + prescanTwoTrees(theHead, theMerge); + } + + @Test + public void testRules4thru13_IndexEntryNotInHead() throws IOException { + // rules 4 and 5 + HashMap idxMap; + + idxMap = new HashMap(); + 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(); + idxMap.put("foo", "foo"); + setupCase(null, idxMap, idxMap); + go(); + + assertAllEmpty(); + + // rules 8 and 9 + HashMap mergeMap; + mergeMap = new HashMap(); + + mergeMap.put("foo", "merge"); + setupCase(null, mergeMap, idxMap); + go(); + + assertTrue(getUpdated().isEmpty()); + assertTrue(getRemoved().isEmpty()); + assertTrue(getConflicts().contains("foo")); + + // rule 10 + + HashMap headMap = new HashMap(); + 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 Update + *4 D F D N N N Update + *5 D F F Y N N Y Keep + *6 D F F N N N Y Keep + *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 { + Tree treeDF = buildTree(mkmap("DF", "DF")); + Tree 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 - the first to break! + doit(mk("DF/DF"), mk("DF/DF"), mk("DF")); + assertUpdated("DF/DF"); + assertRemoved("DF"); + } + + @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")); + assertUpdated("DF/DF"); + assertRemoved("DF"); + + } + + @Test + public void testDirectoryFileConflicts_5() throws Exception { + // 5 + doit(mk("DF/DF"), mk("DF"), mk("DF")); + assertRemoved("DF/DF"); + + } + + @Test + public void testDirectoryFileConflicts_6() throws Exception { + // 6 + setupCase(mk("DF/DF"), mk("DF"), mk("DF")); + writeTrashFile("DF", "different"); + go(); + assertRemoved("DF/DF"); + } + + @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(mk("DF"), mkmap("DF", "QP"), mk("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 h, HashMap m, HashMap 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"); + 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"); + } + } + + @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 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")); + assertTrue(getConflicts().equals(Arrays.asList("foo"))); + assertTrue(new File(trash, "foo").isFile()); + } + } + + public void assertWorkDir(HashMap i) throws CorruptObjectException, + IOException { + TreeWalk walk = new TreeWalk(db); + walk.setRecursive(true); + 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); + assertNotNull("found unexpected file for path " + path + + " in workdir", expectedValue); + File file = new File(db.getWorkTree(), path); + assertTrue(file.exists()); + if (file.isFile()) { + 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(); + assertTrue("unexpected content for path " + path + + " in workDir. Expected: <" + expectedValue + ">", + Arrays.equals(buffer, i.get(path).getBytes())); + nrFiles++; + } + } + assertEquals("WorkDir has not the right size.", i.size(), nrFiles); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java index 77f123854..e350fea32 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java @@ -49,32 +49,53 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.TreeSet; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.MergeResult; import org.eclipse.jgit.api.MergeResult.MergeStatus; +import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.util.IO; import org.junit.Test; public class IndexDiffTest extends RepositoryTestCase { + + static PathEdit add(final Repository db, final File workdir, + final String path) throws FileNotFoundException, IOException { + ObjectInserter inserter = db.newObjectInserter(); + final File f = new File(workdir, path); + final ObjectId id = inserter.insert(Constants.OBJ_BLOB, + IO.readFully(f)); + return new PathEdit(path) { + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.REGULAR_FILE); + ent.setLength(f.length()); + ent.setObjectId(id); + } + }; + } + @Test public void testAdded() throws IOException { - GitIndex index = new GitIndex(db); writeTrashFile("file1", "file1"); writeTrashFile("dir/subfile", "dir/subfile"); Tree tree = new Tree(db); tree.setId(insertTree(tree)); - index.add(trash, new File(trash, "file1")); - index.add(trash, new File(trash, "dir/subfile")); - index.write(); + DirCache index = db.lockDirCache(); + DirCacheEditor editor = index.editor(); + editor.add(add(db, trash, "file1")); + editor.add(add(db, trash, "dir/subfile")); + editor.commit(); FileTreeIterator iterator = new FileTreeIterator(db); IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); @@ -113,13 +134,13 @@ public class IndexDiffTest extends RepositoryTestCase { } @Test - public void testModified() throws IOException { - GitIndex index = new GitIndex(db); + public void testModified() throws IOException, NoFilepatternException { + writeTrashFile("file2", "file2"); + writeTrashFile("dir/file3", "dir/file3"); - index.add(trash, writeTrashFile("file2", "file2")); - index.add(trash, writeTrashFile("dir/file3", "dir/file3")); - index.write(); + Git git = new Git(db); + git.add().addFilepattern("file2").addFilepattern("dir/file3").call(); writeTrashFile("dir/file3", "changed"); @@ -261,14 +282,17 @@ public class IndexDiffTest extends RepositoryTestCase { } @Test - public void testUnchangedSimple() throws IOException { - GitIndex index = new GitIndex(db); - - index.add(trash, writeTrashFile("a.b", "a.b")); - index.add(trash, writeTrashFile("a.c", "a.c")); - index.add(trash, writeTrashFile("a=c", "a=c")); - index.add(trash, writeTrashFile("a=d", "a=d")); - index.write(); + public void testUnchangedSimple() throws IOException, + NoFilepatternException { + writeTrashFile("a.b", "a.b"); + writeTrashFile("a.c", "a.c"); + writeTrashFile("a=c", "a=c"); + writeTrashFile("a=d", "a=d"); + Git git = new Git(db); + git.add().addFilepattern("a.b").call(); + git.add().addFilepattern("a.c").call(); + git.add().addFilepattern("a=c").call(); + git.add().addFilepattern("a=d").call(); Tree tree = new Tree(db); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin @@ -290,23 +314,27 @@ public class IndexDiffTest extends RepositoryTestCase { } /** - * This test has both files and directories that involve - * the tricky ordering used by Git. + * This test has both files and directories that involve the tricky ordering + * used by Git. * * @throws IOException + * @throws NoFilepatternException */ @Test - public void testUnchangedComplex() throws IOException { - GitIndex index = new GitIndex(db); - - index.add(trash, writeTrashFile("a.b", "a.b")); - index.add(trash, writeTrashFile("a.c", "a.c")); - index.add(trash, writeTrashFile("a/b.b/b", "a/b.b/b")); - index.add(trash, writeTrashFile("a/b", "a/b")); - index.add(trash, writeTrashFile("a/c", "a/c")); - index.add(trash, writeTrashFile("a=c", "a=c")); - index.add(trash, writeTrashFile("a=d", "a=d")); - index.write(); + public void testUnchangedComplex() throws IOException, + NoFilepatternException { + Git git = new Git(db); + writeTrashFile("a.b", "a.b"); + writeTrashFile("a.c", "a.c"); + writeTrashFile("a/b.b/b", "a/b.b/b"); + writeTrashFile("a/b", "a/b"); + writeTrashFile("a/c", "a/c"); + writeTrashFile("a=c", "a=c"); + writeTrashFile("a=d", "a=d"); + git.add().addFilepattern("a.b").addFilepattern("a.c") + .addFilepattern("a/b.b/b").addFilepattern("a/b") + .addFilepattern("a/c").addFilepattern("a=c") + .addFilepattern("a=d").call(); Tree tree = new Tree(db); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java deleted file mode 100644 index f76822ea5..000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java +++ /dev/null @@ -1,802 +0,0 @@ -/* - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2008-2009, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * Copyright (C) 2010, Christian Halstrick - * 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.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.errors.CheckoutConflictException; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.treewalk.FileTreeIterator; -import org.eclipse.jgit.treewalk.TreeWalk; -import org.junit.Test; - -public abstract class ReadTreeTest extends RepositoryTestCase { - protected Tree theHead; - protected Tree theMerge; - - // Each of these rules are from the read-tree manpage - // go there to see what they mean. - // Rule 0 is left out for obvious reasons :) - @Test - public void testRules1thru3_NoIndexEntry() throws IOException { - Tree head = new Tree(db); - head = buildTree(mk("foo")); - ObjectId objectId = head.findBlobMember("foo").getId(); - Tree merge = new Tree(db); - - prescanTwoTrees(head, merge); - - assertTrue(getRemoved().contains("foo")); - - prescanTwoTrees(merge, head); - - assertEquals(objectId, getUpdated().get("foo")); - - merge = buildTree(mkmap("foo", "a")); - ObjectId anotherId = merge.findBlobMember("foo").getId(); - - prescanTwoTrees(head, merge); - - assertEquals(anotherId, getUpdated().get("foo")); - } - - void setupCase(HashMap headEntries, - HashMap mergeEntries, - HashMap indexEntries) throws IOException { - theHead = buildTree(headEntries); - theMerge = buildTree(mergeEntries); - buildIndex(indexEntries); - } - - private void buildIndex(HashMap indexEntries) throws IOException { - GitIndex index = new GitIndex(db); - - if (indexEntries != null) { - for (java.util.Map.Entry e : indexEntries.entrySet()) { - index.add(trash, writeTrashFile(e.getKey(), e.getValue())).forceRecheck(); - } - } - - index.write(); - db.getIndex().read(); - } - - private Tree buildTree(HashMap headEntries) throws IOException { - Tree tree = new Tree(db); - if (headEntries == null) - return tree; - FileTreeEntry fileEntry; - Tree parent; - ObjectInserter oi = db.newObjectInserter(); - try { - for (java.util.Map.Entry e : headEntries.entrySet()) { - fileEntry = tree.addFile(e.getKey()); - fileEntry.setId(genSha1(e.getValue())); - parent = fileEntry.getParent(); - while (parent != null) { - parent.setId(oi.insert(Constants.OBJ_TREE, parent.format())); - parent = parent.getParent(); - } - } - oi.flush(); - } finally { - oi.release(); - } - return tree; - } - - ObjectId genSha1(String data) { - ObjectInserter w = db.newObjectInserter(); - try { - ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes()); - w.flush(); - return id; - } catch (IOException e) { - fail(e.toString()); - } finally { - w.release(); - } - return null; - } - - protected void go() throws IllegalStateException, IOException { - prescanTwoTrees(theHead, theMerge); - } - - // for these rules, they all have clean yes/no options - // but it doesn't matter if the entry is clean or not - // so we can just ignore the state in the filesystem entirely - @Test - public void testRules4thru13_IndexEntryNotInHead() throws IOException { - // rules 4 and 5 - HashMap idxMap; - - idxMap = new HashMap(); - 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(); - idxMap.put("foo", "foo"); - setupCase(null, idxMap, idxMap); - go(); - - assertAllEmpty(); - - // rules 8 and 9 - HashMap mergeMap; - mergeMap = new HashMap(); - - mergeMap.put("foo", "merge"); - setupCase(null, mergeMap, idxMap); - go(); - - assertTrue(getUpdated().isEmpty()); - assertTrue(getRemoved().isEmpty()); - assertTrue(getConflicts().contains("foo")); - - // rule 10 - - HashMap headMap = new HashMap(); - 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.getIndex().getMembers()[0].forceRecheck(); - 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.getIndex().getMembers()[0].forceRecheck(); - go(); - assertTrue(getConflicts().contains("foo")); - } - - private void assertAllEmpty() { - assertTrue(getRemoved().isEmpty()); - assertTrue(getUpdated().isEmpty()); - assertTrue(getConflicts().isEmpty()); - } - - @Test - public void testDirectoryFileSimple() throws IOException { - Tree treeDF = buildTree(mkmap("DF", "DF")); - Tree 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")); - } - - /* - * 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 Update - *4 D F D N N N Update - *5 D F F Y N N Y Keep - *6 D F F N N N Y Keep - *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 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 - the first to break! - doit(mk("DF/DF"), mk("DF/DF"), mk("DF")); - assertUpdated("DF/DF"); - assertRemoved("DF"); - } - - @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")); - assertUpdated("DF/DF"); - assertRemoved("DF"); - - } - - @Test - public void testDirectoryFileConflicts_5() throws Exception { - // 5 - doit(mk("DF/DF"), mk("DF"), mk("DF")); - assertRemoved("DF/DF"); - - } - - @Test - public void testDirectoryFileConflicts_6() throws Exception { - // 6 - setupCase(mk("DF/DF"), mk("DF"), mk("DF")); - writeTrashFile("DF", "different"); - go(); - assertRemoved("DF/DF"); - } - - @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(mk("DF"), mkmap("DF", "QP"), mk("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 h, HashMap m, - HashMap i) throws IOException { - setupCase(h, m, i); - go(); - } - - protected static HashMap mk(String a) { - return mkmap(a, a); - } - - protected static HashMap mkmap(String... args) { - if ((args.length % 2) > 0) - throw new IllegalArgumentException("needs to be pairs"); - - HashMap map = new HashMap(); - for (int i = 0; i < args.length; i += 2) { - map.put(args[i], args[i+1]); - } - - return map; - } - - @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"); - 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()); - } - - // TODO: Why should we expect conflicts here? - // H and M are emtpy 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"); - } - } - - @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 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")); - assertTrue(getConflicts().equals(Arrays.asList("foo"))); - assertTrue(new File(trash, "foo").isFile()); - } - } - - public void assertWorkDir(HashMap i) - throws CorruptObjectException, IOException { - TreeWalk walk = new TreeWalk(db); - walk.setRecursive(true); - 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); - assertNotNull("found unexpected file for path " - + path + " in workdir", expectedValue); - File file = new File(db.getWorkTree(), path); - assertTrue(file.exists()); - if (file.isFile()) { - 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(); - assertTrue("unexpected content for path " + path - + " in workDir. Expected: <" + expectedValue + ">", - Arrays.equals(buffer, i.get(path).getBytes())); - nrFiles++; - } - } - assertEquals("WorkDir has not the right size.", i.size(), nrFiles); - } - - - public void assertIndex(HashMap i) - throws CorruptObjectException, IOException { - String expectedValue; - String path; - GitIndex theIndex=db.getIndex(); - // Without an explicit refresh we might miss index updates. If the index - // is updated multiple times inside a FileSystemTimer tick db.getIndex will - // not reload the index and return a cached (stale) index. - theIndex.read(); - assertEquals("Index has not the right size.", i.size(), - theIndex.getMembers().length); - for (int j = 0; j < theIndex.getMembers().length; j++) { - path = theIndex.getMembers()[j].getName(); - 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(theIndex.getMembers()[j].getObjectId()) - .getCachedBytes(), i.get(path).getBytes())); - } - } - - public abstract void prescanTwoTrees(Tree head, Tree merge) throws IllegalStateException, IOException; - public abstract void checkout() throws IOException; - public abstract List getRemoved(); - public abstract Map getUpdated(); - public abstract List getConflicts(); -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java index 1781c3d62..8affb01f2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java @@ -165,7 +165,7 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { public void testExceptionThrown_BareRepoGetIndex() throws Exception { File gitDir = getFile("workdir"); try { - new FileRepository(gitDir).getIndex(); + new FileRepository(gitDir).readDirCache(); fail("Expected NoWorkTreeException missing"); } catch (NoWorkTreeException e) { // expected diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 4806ab922..b1c4c78e6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -816,6 +816,7 @@ public abstract class Repository { * @throws NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. + * @deprecated Use {@link #readDirCache()} instead */ public GitIndex getIndex() throws IOException, NoWorkTreeException { if (isBare()) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java index 38c33f311..6f47db675 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java @@ -241,6 +241,7 @@ public abstract class TreeEntry implements Comparable { * * @param i * @return '/' for Tree entries and NUL for non-treeish objects + * @deprecated since it depends on deprecated GitIndex, and internal */ final public static int lastChar(Entry i) { // FIXME, gitlink etc. Currently Trees cannot appear in the