diff options
35 files changed, 1245 insertions, 310 deletions
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java index 77ed73048d..ebdb74f3c7 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java @@ -153,22 +153,22 @@ class Diff extends TextBuiltin { for (DiffEntry ent : files) { switch (ent.getChangeType()) { case ADD: - out.println("A\t" + ent.getNewName()); + out.println("A\t" + ent.getNewPath()); break; case DELETE: - out.println("D\t" + ent.getOldName()); + out.println("D\t" + ent.getOldPath()); break; case MODIFY: - out.println("M\t" + ent.getNewName()); + out.println("M\t" + ent.getNewPath()); break; case COPY: out.format("C%1$03d\t%2$s\t%3$s", ent.getScore(), // - ent.getOldName(), ent.getNewName()); + ent.getOldPath(), ent.getNewPath()); out.println(); break; case RENAME: out.format("R%1$03d\t%2$s\t%3$s", ent.getScore(), // - ent.getOldName(), ent.getNewName()); + ent.getOldPath(), ent.getNewPath()); out.println(); break; } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java index 48a05915e4..fbc019fa24 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java @@ -242,7 +242,7 @@ class Log extends RevWalkTextBuiltin { String oldPath = ((FollowFilter) pathFilter).getPath(); for (DiffEntry ent : files) { if (ent.getChangeType() == ChangeType.ADD - && ent.getNewName().equals(oldPath)) + && ent.getNewPath().equals(oldPath)) return true; } return false; @@ -251,8 +251,8 @@ class Log extends RevWalkTextBuiltin { private List<DiffEntry> updateFollowFilter(List<DiffEntry> files) { String oldPath = ((FollowFilter) pathFilter).getPath(); for (DiffEntry ent : files) { - if (isRename(ent) && ent.getNewName().equals(oldPath)) { - pathFilter = FollowFilter.create(ent.getOldName()); + if (isRename(ent) && ent.getNewPath().equals(oldPath)) { + pathFilter = FollowFilter.create(ent.getOldPath()); return Collections.singletonList(ent); } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java index 5bad4ef98c..9b51b871fc 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java @@ -55,4 +55,9 @@ class Version extends TextBuiltin { out.println(MessageFormat.format(CLIText.get().jgitVersion, pkg.getImplementationVersion())); } + + @Override + protected final boolean requiresRepository() { + return false; + } } diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java index ae9fb0ef50..ff442b6188 100644 --- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java +++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java @@ -108,9 +108,9 @@ public class EGitPatchHistoryTest extends TestCase { for (final FileHeader fh : p.getFiles()) { final String fileName; if (fh.getChangeType() != FileHeader.ChangeType.DELETE) - fileName = fh.getNewName(); + fileName = fh.getNewPath(); else - fileName = fh.getOldName(); + fileName = fh.getOldPath(); final StatInfo s = files.remove(fileName); final String nid = fileName + " in " + cid; assertNotNull("No " + nid, s); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java index 9e195b4974..a7d0984689 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java @@ -44,15 +44,17 @@ package org.eclipse.jgit.api; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.RepositoryTestCase; public class AddCommandTest extends RepositoryTestCase { @@ -86,15 +88,11 @@ public class AddCommandTest extends RepositoryTestCase { Git git = new Git(db); - DirCache dc = git.add().addFilepattern("a.txt").call(); + git.add().addFilepattern("a.txt").call(); - assertEquals(1, dc.getEntryCount()); - assertEquals("a.txt", dc.getEntry(0).getPathString()); - assertNotNull(dc.getEntry(0).getObjectId()); - assertEquals(file.lastModified(), dc.getEntry(0).getLastModified()); - assertEquals(file.length(), dc.getEntry(0).getLength()); - assertEquals(FileMode.REGULAR_FILE, dc.getEntry(0).getFileMode()); - assertEquals(0, dc.getEntry(0).getStage()); + assertEquals( + "[a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]", + indexState(CONTENT_ID)); } public void testAddExistingSingleFileInSubDir() throws IOException, NoFilepatternException { @@ -107,15 +105,11 @@ public class AddCommandTest extends RepositoryTestCase { Git git = new Git(db); - DirCache dc = git.add().addFilepattern("sub/a.txt").call(); + git.add().addFilepattern("sub/a.txt").call(); - assertEquals(1, dc.getEntryCount()); - assertEquals("sub/a.txt", dc.getEntry(0).getPathString()); - assertNotNull(dc.getEntry(0).getObjectId()); - assertEquals(file.lastModified(), dc.getEntry(0).getLastModified()); - assertEquals(file.length(), dc.getEntry(0).getLength()); - assertEquals(FileMode.REGULAR_FILE, dc.getEntry(0).getFileMode()); - assertEquals(0, dc.getEntry(0).getStage()); + assertEquals( + "[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]", + indexState(CONTENT_ID)); } public void testAddExistingSingleFileTwice() throws IOException, NoFilepatternException { @@ -128,7 +122,7 @@ public class AddCommandTest extends RepositoryTestCase { Git git = new Git(db); DirCache dc = git.add().addFilepattern("a.txt").call(); - ObjectId id1 = dc.getEntry(0).getObjectId(); + dc.getEntry(0).getObjectId(); writer = new PrintWriter(file); writer.print("other content"); @@ -136,10 +130,9 @@ public class AddCommandTest extends RepositoryTestCase { dc = git.add().addFilepattern("a.txt").call(); - assertEquals(1, dc.getEntryCount()); - assertEquals("a.txt", dc.getEntry(0).getPathString()); - assertNotSame(id1, dc.getEntry(0).getObjectId()); - assertEquals(0, dc.getEntry(0).getStage()); + assertEquals( + "[a.txt, mode:100644, sha1:4f41554f6e0045ef53848fc0c3f33b6a9abc24a9]", + indexState(CONTENT_ID)); } public void testAddExistingSingleFileTwiceWithCommit() throws Exception { @@ -152,7 +145,7 @@ public class AddCommandTest extends RepositoryTestCase { Git git = new Git(db); DirCache dc = git.add().addFilepattern("a.txt").call(); - ObjectId id1 = dc.getEntry(0).getObjectId(); + dc.getEntry(0).getObjectId(); git.commit().setMessage("commit a.txt").call(); @@ -162,10 +155,9 @@ public class AddCommandTest extends RepositoryTestCase { dc = git.add().addFilepattern("a.txt").call(); - assertEquals(1, dc.getEntryCount()); - assertEquals("a.txt", dc.getEntry(0).getPathString()); - assertNotSame(id1, dc.getEntry(0).getObjectId()); - assertEquals(0, dc.getEntry(0).getStage()); + assertEquals( + "[a.txt, mode:100644, sha1:4f41554f6e0045ef53848fc0c3f33b6a9abc24a9]", + indexState(CONTENT_ID)); } public void testAddRemovedFile() throws Exception { @@ -178,16 +170,15 @@ public class AddCommandTest extends RepositoryTestCase { Git git = new Git(db); DirCache dc = git.add().addFilepattern("a.txt").call(); - ObjectId id1 = dc.getEntry(0).getObjectId(); + dc.getEntry(0).getObjectId(); file.delete(); // is supposed to do nothing dc = git.add().addFilepattern("a.txt").call(); - assertEquals(1, dc.getEntryCount()); - assertEquals("a.txt", dc.getEntry(0).getPathString()); - assertEquals(id1, dc.getEntry(0).getObjectId()); - assertEquals(0, dc.getEntry(0).getStage()); + assertEquals( + "[a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]", + indexState(CONTENT_ID)); } public void testAddRemovedCommittedFile() throws Exception { @@ -202,16 +193,15 @@ public class AddCommandTest extends RepositoryTestCase { git.commit().setMessage("commit a.txt").call(); - ObjectId id1 = dc.getEntry(0).getObjectId(); + dc.getEntry(0).getObjectId(); file.delete(); // is supposed to do nothing dc = git.add().addFilepattern("a.txt").call(); - assertEquals(1, dc.getEntryCount()); - assertEquals("a.txt", dc.getEntry(0).getPathString()); - assertEquals(id1, dc.getEntry(0).getObjectId()); - assertEquals(0, dc.getEntry(0).getStage()); + assertEquals( + "[a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]", + indexState(CONTENT_ID)); } public void testAddWithConflicts() throws Exception { @@ -229,38 +219,42 @@ public class AddCommandTest extends RepositoryTestCase { writer.print("content b"); writer.close(); - ObjectWriter ow = new ObjectWriter(db); + ObjectInserter newObjectInserter = db.newObjectInserter(); DirCache dc = db.lockDirCache(); DirCacheBuilder builder = dc.builder(); - addEntryToBuilder("b.txt", file2, ow, builder, 0); - addEntryToBuilder("a.txt", file, ow, builder, 1); + addEntryToBuilder("b.txt", file2, newObjectInserter, builder, 0); + addEntryToBuilder("a.txt", file, newObjectInserter, builder, 1); writer = new PrintWriter(file); writer.print("other content"); writer.close(); - addEntryToBuilder("a.txt", file, ow, builder, 3); + addEntryToBuilder("a.txt", file, newObjectInserter, builder, 3); writer = new PrintWriter(file); writer.print("our content"); writer.close(); - ObjectId id1 = addEntryToBuilder("a.txt", file, ow, builder, 2) + addEntryToBuilder("a.txt", file, newObjectInserter, builder, 2) .getObjectId(); builder.commit(); - assertEquals(4, dc.getEntryCount()); + assertEquals( + "[a.txt, mode:100644, stage:1, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" + + "[a.txt, mode:100644, stage:2, sha1:b9f89ff733bdaf49e02711535867bb821f9db55e]" + + "[a.txt, mode:100644, stage:3, sha1:4f41554f6e0045ef53848fc0c3f33b6a9abc24a9]" + + "[b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]", + indexState(CONTENT_ID)); // now the test begins Git git = new Git(db); dc = git.add().addFilepattern("a.txt").call(); - assertEquals(2, dc.getEntryCount()); - assertEquals("a.txt", dc.getEntry("a.txt").getPathString()); - assertEquals(id1, dc.getEntry("a.txt").getObjectId()); - assertEquals(0, dc.getEntry("a.txt").getStage()); - assertEquals(0, dc.getEntry("b.txt").getStage()); + assertEquals( + "[a.txt, mode:100644, sha1:b9f89ff733bdaf49e02711535867bb821f9db55e]" + + "[b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]", + indexState(CONTENT_ID)); } public void testAddTwoFiles() throws Exception { @@ -277,13 +271,11 @@ public class AddCommandTest extends RepositoryTestCase { writer.close(); Git git = new Git(db); - DirCache dc = git.add().addFilepattern("a.txt").addFilepattern("b.txt").call(); - assertEquals("a.txt", dc.getEntry("a.txt").getPathString()); - assertEquals("b.txt", dc.getEntry("b.txt").getPathString()); - assertNotNull(dc.getEntry("a.txt").getObjectId()); - assertNotNull(dc.getEntry("b.txt").getObjectId()); - assertEquals(0, dc.getEntry("a.txt").getStage()); - assertEquals(0, dc.getEntry("b.txt").getStage()); + git.add().addFilepattern("a.txt").addFilepattern("b.txt").call(); + assertEquals( + "[a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" + + "[b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]", + indexState(CONTENT_ID)); } public void testAddFolder() throws Exception { @@ -301,13 +293,11 @@ public class AddCommandTest extends RepositoryTestCase { writer.close(); Git git = new Git(db); - DirCache dc = git.add().addFilepattern("sub").call(); - assertEquals("sub/a.txt", dc.getEntry("sub/a.txt").getPathString()); - assertEquals("sub/b.txt", dc.getEntry("sub/b.txt").getPathString()); - assertNotNull(dc.getEntry("sub/a.txt").getObjectId()); - assertNotNull(dc.getEntry("sub/b.txt").getObjectId()); - assertEquals(0, dc.getEntry("sub/a.txt").getStage()); - assertEquals(0, dc.getEntry("sub/b.txt").getStage()); + git.add().addFilepattern("sub").call(); + assertEquals( + "[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" + + "[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]", + indexState(CONTENT_ID)); } public void testAddIgnoredFile() throws Exception { @@ -331,11 +321,11 @@ public class AddCommandTest extends RepositoryTestCase { writer.close(); Git git = new Git(db); - DirCache dc = git.add().addFilepattern("sub").call(); - assertEquals("sub/a.txt", dc.getEntry("sub/a.txt").getPathString()); - assertNull(dc.getEntry("sub/b.txt")); - assertNotNull(dc.getEntry("sub/a.txt").getObjectId()); - assertEquals(0, dc.getEntry("sub/a.txt").getStage()); + git.add().addFilepattern("sub").call(); + + assertEquals( + "[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]", + indexState(CONTENT_ID)); } public void testAddWholeRepo() throws Exception { @@ -353,15 +343,125 @@ public class AddCommandTest extends RepositoryTestCase { writer.close(); Git git = new Git(db); - DirCache dc = git.add().addFilepattern(".").call(); - assertEquals("sub/a.txt", dc.getEntry("sub/a.txt").getPathString()); - assertEquals("sub/b.txt", dc.getEntry("sub/b.txt").getPathString()); + git.add().addFilepattern(".").call(); + assertEquals( + "[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" + + "[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]", + indexState(CONTENT_ID)); + } + + // the same three cases as in testAddWithParameterUpdate + // file a exists in workdir and in index -> added + // file b exists not in workdir but in index -> unchanged + // file c exists in workdir but not in index -> added + public void testAddWithoutParameterUpdate() throws Exception { + new File(db.getWorkTree(), "sub").mkdir(); + File file = new File(db.getWorkTree(), "sub/a.txt"); + file.createNewFile(); + PrintWriter writer = new PrintWriter(file); + writer.print("content"); + writer.close(); + + File file2 = new File(db.getWorkTree(), "sub/b.txt"); + file2.createNewFile(); + writer = new PrintWriter(file2); + writer.print("content b"); + writer.close(); + + Git git = new Git(db); + git.add().addFilepattern("sub").call(); + + assertEquals( + "[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" + + "[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]", + indexState(CONTENT_ID)); + + git.commit().setMessage("commit").call(); + + // new unstaged file sub/c.txt + File file3 = new File(db.getWorkTree(), "sub/c.txt"); + file3.createNewFile(); + writer = new PrintWriter(file3); + writer.print("content c"); + writer.close(); + + // file sub/a.txt is modified + writer = new PrintWriter(file); + writer.print("modified content"); + writer.close(); + + // file sub/b.txt is deleted + file2.delete(); + + git.add().addFilepattern("sub").call(); + // change in sub/a.txt is staged + // deletion of sub/b.txt is not staged + // sub/c.txt is staged + assertEquals( + "[sub/a.txt, mode:100644, sha1:268af4e306cfcf6e79edd50fed9c553d211f68e3]" + + "[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]" + + "[sub/c.txt, mode:100644, sha1:fa08654474ae2ddc4f61ee3a43d017ba65a439c3]", + indexState(CONTENT_ID)); + } + + // file a exists in workdir and in index -> added + // file b exists not in workdir but in index -> deleted + // file c exists in workdir but not in index -> unchanged + public void testAddWithParameterUpdate() throws Exception { + new File(db.getWorkTree(), "sub").mkdir(); + File file = new File(db.getWorkTree(), "sub/a.txt"); + file.createNewFile(); + PrintWriter writer = new PrintWriter(file); + writer.print("content"); + writer.close(); + + File file2 = new File(db.getWorkTree(), "sub/b.txt"); + file2.createNewFile(); + writer = new PrintWriter(file2); + writer.print("content b"); + writer.close(); + + Git git = new Git(db); + git.add().addFilepattern("sub").call(); + + assertEquals( + "[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" + + "[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]", + indexState(CONTENT_ID)); + + git.commit().setMessage("commit").call(); + + // new unstaged file sub/c.txt + File file3 = new File(db.getWorkTree(), "sub/c.txt"); + file3.createNewFile(); + writer = new PrintWriter(file3); + writer.print("content c"); + writer.close(); + + // file sub/a.txt is modified + writer = new PrintWriter(file); + writer.print("modified content"); + writer.close(); + + file2.delete(); + + // change in sub/a.txt is staged + // deletion of sub/b.txt is staged + // sub/c.txt is not staged + git.add().addFilepattern("sub").setUpdate(true).call(); + // change in sub/a.txt is staged + assertEquals( + "[sub/a.txt, mode:100644, sha1:268af4e306cfcf6e79edd50fed9c553d211f68e3]", + indexState(CONTENT_ID)); } private DirCacheEntry addEntryToBuilder(String path, File file, - ObjectWriter ow, DirCacheBuilder builder, int stage) + ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage) throws IOException { - ObjectId id = ow.writeBlob(file); + FileInputStream inputStream = new FileInputStream(file); + ObjectId id = newObjectInserter.insert( + Constants.OBJ_BLOB, file.length(), inputStream); + inputStream.close(); DirCacheEntry entry = new DirCacheEntry(path, stage); entry.setObjectId(id); entry.setFileMode(FileMode.REGULAR_FILE); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java index a62045dc9f..cf30039ab7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java @@ -45,6 +45,7 @@ package org.eclipse.jgit.api; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.io.PrintWriter; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.Constants; @@ -53,6 +54,7 @@ import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.TreeWalk; public class CommitAndLogCommandTests extends RepositoryTestCase { public void testSomeCommits() throws NoHeadException, NoMessageException, @@ -151,4 +153,36 @@ public class CommitAndLogCommandTests extends RepositoryTestCase { assertEquals(parents[1], second); assertTrue(parents.length==2); } + + public void testAddUnstagedChanges() throws IOException, NoHeadException, + NoMessageException, ConcurrentRefUpdateException, + JGitInternalException, WrongRepositoryStateException, + NoFilepatternException { + File file = new File(db.getWorkTree(), "a.txt"); + file.createNewFile(); + PrintWriter writer = new PrintWriter(file); + writer.print("content"); + writer.close(); + + Git git = new Git(db); + git.add().addFilepattern("a.txt").call(); + RevCommit commit = git.commit().setMessage("initial commit").call(); + TreeWalk tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); + assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea", + tw.getObjectId(0).getName()); + + writer = new PrintWriter(file); + writer.print("content2"); + writer.close(); + commit = git.commit().setMessage("second commit").call(); + tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); + assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea", + tw.getObjectId(0).getName()); + + commit = git.commit().setAll(true).setMessage("third commit") + .setAll(true).call(); + tw = TreeWalk.forPath(db, "a.txt", commit.getTree()); + assertEquals("db00fd65b218578127ea51f3dffac701f12f486a", + tw.getObjectId(0).getName()); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java index eb965cf601..26116d2504 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java @@ -275,6 +275,21 @@ public class RenameDetectorTest extends RepositoryTestCase { assertRename(b, a, 74, entries.get(0)); } + public void testInexactRename_SameContentMultipleTimes() throws Exception { + ObjectId aId = blob("a\na\na\na\n"); + ObjectId bId = blob("a\na\na\n"); + + DiffEntry a = DiffEntry.add(PATH_A, aId); + DiffEntry b = DiffEntry.delete(PATH_Q, bId); + + rd.add(a); + rd.add(b); + + List<DiffEntry> entries = rd.compute(); + assertEquals(1, entries.size()); + assertRename(b, a, 74, entries.get(0)); + } + public void testInexactRenames_OnePair2() throws Exception { ObjectId aId = blob("ab\nab\nab\nac\nad\nae\n"); ObjectId bId = blob("ac\nab\nab\nab\naa\na0\na1\n"); @@ -483,10 +498,10 @@ public class RenameDetectorTest extends RepositoryTestCase { assertEquals(1, entries.size()); DiffEntry modify = entries.get(0); - assertEquals(m.oldName, modify.oldName); + assertEquals(m.oldPath, modify.oldPath); assertEquals(m.oldId, modify.oldId); assertEquals(m.oldMode, modify.oldMode); - assertEquals(m.newName, modify.newName); + assertEquals(m.newPath, modify.newPath); assertEquals(m.newId, modify.newId); assertEquals(m.newMode, modify.newMode); assertEquals(m.changeType, modify.changeType); @@ -545,8 +560,8 @@ public class RenameDetectorTest extends RepositoryTestCase { DiffEntry rename) { assertEquals(ChangeType.RENAME, rename.getChangeType()); - assertEquals(o.getOldName(), rename.getOldName()); - assertEquals(n.getNewName(), rename.getNewName()); + assertEquals(o.getOldPath(), rename.getOldPath()); + assertEquals(n.getNewPath(), rename.getNewPath()); assertEquals(o.getOldMode(), rename.getOldMode()); assertEquals(n.getNewMode(), rename.getNewMode()); @@ -561,8 +576,8 @@ public class RenameDetectorTest extends RepositoryTestCase { DiffEntry copy) { assertEquals(ChangeType.COPY, copy.getChangeType()); - assertEquals(o.getOldName(), copy.getOldName()); - assertEquals(n.getNewName(), copy.getNewName()); + assertEquals(o.getOldPath(), copy.getOldPath()); + assertEquals(n.getNewPath(), copy.getNewPath()); assertEquals(o.getOldMode(), copy.getOldMode()); assertEquals(n.getNewMode(), copy.getNewMode()); @@ -575,11 +590,11 @@ public class RenameDetectorTest extends RepositoryTestCase { private static void assertAdd(String newName, ObjectId newId, FileMode newMode, DiffEntry add) { - assertEquals(DiffEntry.DEV_NULL, add.oldName); + assertEquals(DiffEntry.DEV_NULL, add.oldPath); assertEquals(DiffEntry.A_ZERO, add.oldId); assertEquals(FileMode.MISSING, add.oldMode); assertEquals(ChangeType.ADD, add.changeType); - assertEquals(newName, add.newName); + assertEquals(newName, add.newPath); assertEquals(AbbreviatedObjectId.fromObjectId(newId), add.newId); assertEquals(newMode, add.newMode); } 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 ac7ce5bd48..c439baccf6 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 @@ -48,16 +48,21 @@ package org.eclipse.jgit.lib; import java.io.File; import java.io.IOException; +import org.eclipse.jgit.treewalk.FileTreeIterator; + public class IndexDiffTest extends RepositoryTestCase { 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(new ObjectWriter(db).writeTree(tree)); index.add(trash, new File(trash, "file1")); index.add(trash, new File(trash, "dir/subfile")); - IndexDiff diff = new IndexDiff(tree, index); + index.write(); + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(2, diff.getAdded().size()); assertTrue(diff.getAdded().contains("file1")); @@ -68,7 +73,6 @@ public class IndexDiffTest extends RepositoryTestCase { } public void testRemoved() throws IOException { - GitIndex index = new GitIndex(db); writeTrashFile("file2", "file2"); writeTrashFile("dir/file3", "dir/file3"); @@ -82,7 +86,8 @@ public class IndexDiffTest extends RepositoryTestCase { tree2.setId(new ObjectWriter(db).writeTree(tree2)); tree.setId(new ObjectWriter(db).writeTree(tree)); - IndexDiff diff = new IndexDiff(tree, index); + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(2, diff.getRemoved().size()); assertTrue(diff.getRemoved().contains("file2")); @@ -98,6 +103,7 @@ public class IndexDiffTest extends RepositoryTestCase { index.add(trash, writeTrashFile("file2", "file2")); index.add(trash, writeTrashFile("dir/file3", "dir/file3")); + index.write(); writeTrashFile("dir/file3", "changed"); @@ -109,7 +115,8 @@ public class IndexDiffTest extends RepositoryTestCase { Tree tree2 = (Tree) tree.findTreeMember("dir"); tree2.setId(new ObjectWriter(db).writeTree(tree2)); tree.setId(new ObjectWriter(db).writeTree(tree)); - IndexDiff diff = new IndexDiff(tree, index); + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(2, diff.getChanged().size()); assertTrue(diff.getChanged().contains("file2")); @@ -128,6 +135,7 @@ public class IndexDiffTest extends RepositoryTestCase { 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(); Tree tree = new Tree(db); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin @@ -138,7 +146,8 @@ public class IndexDiffTest extends RepositoryTestCase { tree.setId(new ObjectWriter(db).writeTree(tree)); - IndexDiff diff = new IndexDiff(tree, index); + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -163,6 +172,7 @@ public class IndexDiffTest extends RepositoryTestCase { 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(); Tree tree = new Tree(db); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin @@ -180,7 +190,8 @@ public class IndexDiffTest extends RepositoryTestCase { tree2.setId(new ObjectWriter(db).writeTree(tree2)); tree.setId(new ObjectWriter(db).writeTree(tree)); - IndexDiff diff = new IndexDiff(tree, index); + FileTreeIterator iterator = new FileTreeIterator(db); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java new file mode 100644 index 0000000000..e208b27e6b --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2010, 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 java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.TreeSet; + +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.FileTreeIteratorWithTimeControl; +import org.eclipse.jgit.treewalk.NameConflictTreeWalk; + +public class RacyGitTests extends RepositoryTestCase { + public void testIterator() throws IllegalStateException, IOException, + InterruptedException { + TreeSet<Long> modTimes = new TreeSet<Long>(); + File lastFile = null; + for (int i = 0; i < 10; i++) { + lastFile = new File(db.getWorkTree(), "0." + i); + lastFile.createNewFile(); + if (i == 5) + fsTick(lastFile); + } + modTimes.add(fsTick(lastFile)); + for (int i = 0; i < 10; i++) { + lastFile = new File(db.getWorkTree(), "1." + i); + lastFile.createNewFile(); + } + modTimes.add(fsTick(lastFile)); + for (int i = 0; i < 10; i++) { + lastFile = new File(db.getWorkTree(), "2." + i); + lastFile.createNewFile(); + if (i % 4 == 0) + fsTick(lastFile); + } + FileTreeIteratorWithTimeControl fileIt = new FileTreeIteratorWithTimeControl( + db, modTimes); + NameConflictTreeWalk tw = new NameConflictTreeWalk(db); + tw.reset(); + tw.addTree(fileIt); + tw.setRecursive(true); + FileTreeIterator t; + long t0 = 0; + for (int i = 0; i < 10; i++) { + assertTrue(tw.next()); + t = tw.getTree(0, FileTreeIterator.class); + if (i == 0) + t0 = t.getEntryLastModified(); + else + assertEquals(t0, t.getEntryLastModified()); + } + long t1 = 0; + for (int i = 0; i < 10; i++) { + assertTrue(tw.next()); + t = tw.getTree(0, FileTreeIterator.class); + if (i == 0) { + t1 = t.getEntryLastModified(); + assertTrue(t1 > t0); + } else + assertEquals(t1, t.getEntryLastModified()); + } + long t2 = 0; + for (int i = 0; i < 10; i++) { + assertTrue(tw.next()); + t = tw.getTree(0, FileTreeIterator.class); + if (i == 0) { + t2 = t.getEntryLastModified(); + assertTrue(t2 > t1); + } else + assertEquals(t2, t.getEntryLastModified()); + } + } + + public void testRacyGitDetection() throws IOException, + IllegalStateException, InterruptedException { + TreeSet<Long> modTimes = new TreeSet<Long>(); + File lastFile; + + // wait to ensure that modtimes of the file doesn't match last index + // file modtime + modTimes.add(fsTick(db.getIndexFile())); + + // create two files + addToWorkDir("a", "a"); + lastFile = addToWorkDir("b", "b"); + + // wait to ensure that file-modTimes and therefore index entry modTime + // doesn't match the modtime of index-file after next persistance + modTimes.add(fsTick(lastFile)); + + // now add both files to the index. No racy git expected + addToIndex(modTimes); + + assertEquals( + "[a, mode:100644, time:t0, length:1, sha1:2e65efe2a145dda7ee51d1741299f848e5bf752e]" + + "[b, mode:100644, time:t0, length:1, sha1:63d8dbd40c23542e740659a7168a0ce3138ea748]", + indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT_ID)); + + // Remember the last modTime of index file. All modifications times of + // further modification are translated to this value so it looks that + // files have been modified in the same time slot as the index file + modTimes.add(Long.valueOf(db.getIndexFile().lastModified())); + + // modify one file + addToWorkDir("a", "a2"); + // now update the index the index. 'a' has to be racily clean -- because + // it's modification time is exactly the same as the previous index file + // mod time. + addToIndex(modTimes); + + db.readDirCache(); + // although racily clean a should not be reported as being dirty + assertEquals( + "[a, mode:100644, time:t1, smudged, length:0]" + + "[b, mode:100644, time:t0, length:1]", + indexState(SMUDGE|MOD_TIME|LENGTH)); + } + + private void addToIndex(TreeSet<Long> modTimes) + throws FileNotFoundException, IOException { + DirCacheBuilder builder = db.lockDirCache().builder(); + FileTreeIterator fIt = new FileTreeIteratorWithTimeControl( + db, modTimes); + DirCacheEntry dce; + while (!fIt.eof()) { + dce = new DirCacheEntry(fIt.getEntryPathString()); + dce.setFileMode(fIt.getEntryFileMode()); + dce.setLastModified(fIt.getEntryLastModified()); + dce.setLength((int) fIt.getEntryLength()); + dce.setObjectId(fIt.getEntryObjectId()); + builder.add(dce); + fIt.next(1); + } + builder.commit(); + } + + private File addToWorkDir(String path, String content) throws IOException { + File f = new File(db.getWorkTree(), path); + FileOutputStream fos = new FileOutputStream(f); + try { + fos.write(content.getBytes(Constants.CHARACTER_ENCODING)); + return f; + } finally { + fos.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java index e78f8512a2..963c9d039d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java @@ -52,9 +52,14 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.util.Map; +import java.util.TreeSet; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.treewalk.NameConflictTreeWalk; /** * Base class for most JGit unit tests. @@ -114,4 +119,154 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { db = createWorkRepository(); trash = db.getWorkTree(); } + + public static final int MOD_TIME = 1; + + public static final int SMUDGE = 2; + + public static final int LENGTH = 4; + + public static final int CONTENT_ID = 8; + + /** + * Represent the state of the index in one String. This representation is + * useful when writing tests which do assertions on the state of the index. + * By default information about path, mode, stage (if different from 0) is + * included. A bitmask controls which additional info about + * modificationTimes, smudge state and length is included. + * <p> + * The format of the returned string is described with this BNF: + * + * <pre> + * result = ( "[" path mode stage? time? smudge? length? sha1? "]" )* . + * mode = ", mode:" number . + * stage = ", stage:" number . + * time = ", time:t" timestamp-index . + * smudge = "" | ", smudged" . + * length = ", length:" number . + * sha1 = ", sha1:" hex-sha1 . + * + * 'stage' is only presented when the stage is different from 0. All + * reported time stamps are mapped to strings like "t0", "t1", ... "tn". The + * smallest reported time-stamp will be called "t0". This allows to write + * assertions against the string although the concrete value of the + * time stamps is unknown. + * + * @param includedOptions + * a bitmask constructed out of the constants {@link #MOD_TIME}, + * {@link #SMUDGE}, {@link #LENGTH} and {@link #CONTENT_ID} + * controlling which info is present in the resulting string. + * @return a string encoding the index state + * @throws IllegalStateException + * @throws IOException + */ + public String indexState(int includedOptions) + throws IllegalStateException, IOException { + DirCache dc = db.readDirCache(); + StringBuilder sb = new StringBuilder(); + TreeSet<Long> timeStamps = null; + + // iterate once over the dircache just to collect all time stamps + if (0 != (includedOptions & MOD_TIME)) { + timeStamps = new TreeSet<Long>(); + for (int i=0; i<dc.getEntryCount(); ++i) + timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); + } + + // iterate again, now produce the result string + NameConflictTreeWalk tw = new NameConflictTreeWalk(db); + tw.setRecursive(true); + tw.reset(); + tw.addTree(new DirCacheIterator(dc)); + while (tw.next()) { + DirCacheIterator dcIt = tw.getTree(0, DirCacheIterator.class); + sb.append("["+tw.getPathString()+", mode:" + dcIt.getEntryFileMode()); + int stage = dcIt.getDirCacheEntry().getStage(); + if (stage != 0) + sb.append(", stage:" + stage); + if (0 != (includedOptions & MOD_TIME)) { + sb.append(", time:t"+ + timeStamps.headSet(Long.valueOf(dcIt.getDirCacheEntry().getLastModified())).size()); + } + if (0 != (includedOptions & SMUDGE)) + if (dcIt.getDirCacheEntry().isSmudged()) + sb.append(", smudged"); + if (0 != (includedOptions & LENGTH)) + sb.append(", length:" + + Integer.toString(dcIt.getDirCacheEntry().getLength())); + if (0 != (includedOptions & CONTENT_ID)) + sb.append(", sha1:" + ObjectId.toString(dcIt + .getEntryObjectId())); + sb.append("]"); + } + return sb.toString(); + } + + /** + * Helper method to map arbitrary objects to user-defined names. This can be + * used create short names for objects to produce small and stable debug + * output. It is guaranteed that when you lookup the same object multiple + * times even with different nameTemplates this method will always return + * the same name which was derived from the first nameTemplate. + * nameTemplates can contain "%n" which will be replaced by a running number + * before used as a name. + * + * @param l + * the object to lookup + * @param nameTemplate + * the name for that object. Can contain "%n" which will be + * replaced by a running number before used as a name. If the + * lookup table already contains the object this parameter will + * be ignored + * @param lookupTable + * a table storing object-name mappings. + * @return a name of that object. Is not guaranteed to be unique. Use + * nameTemplates containing "%n" to always have unique names + */ + public static String lookup(Object l, String nameTemplate, + Map<Object, String> lookupTable) { + String name = lookupTable.get(l); + if (name == null) { + name = nameTemplate.replaceAll("%n", + Integer.toString(lookupTable.size())); + lookupTable.put(l, name); + } + return name; + } + + /** + * Waits until it is guaranteed that a subsequent file modification has a + * younger modification timestamp than the modification timestamp of the + * given file. This is done by touching a temporary file, reading the + * lastmodified attribute and, if needed, sleeping. After sleeping this loop + * starts again until the filesystem timer has advanced enough. + * + * @param lastFile + * the file on which we want to wait until the filesystem timer + * has advanced more than the lastmodification timestamp of this + * file + * @return return the last measured value of the filesystem timer which is + * greater than then the lastmodification time of lastfile. + * @throws InterruptedException + * @throws IOException + */ + public static long fsTick(File lastFile) throws InterruptedException, + IOException { + long sleepTime = 1; + File tmp = File.createTempFile("FileTreeIteratorWithTimeControl", null); + try { + long startTime = (lastFile == null) ? tmp.lastModified() : lastFile + .lastModified(); + long actTime = tmp.lastModified(); + while (actTime <= startTime) { + Thread.sleep(sleepTime); + sleepTime *= 5; + tmp.setLastModified(System.currentTimeMillis()); + actTime = tmp.lastModified(); + } + return actTime; + } finally { + tmp.delete(); + } + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java index 17e99779cf..813a701eaf 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java @@ -79,16 +79,16 @@ public class FileHeaderTest extends TestCase { final FileHeader fh = header(name); assertEquals(gitLine(name).length(), fh.parseGitFileName(0, fh.buf.length)); - assertEquals(name, fh.getOldName()); - assertSame(fh.getOldName(), fh.getNewName()); + assertEquals(name, fh.getOldPath()); + assertSame(fh.getOldPath(), fh.getNewPath()); assertFalse(fh.hasMetaDataChanges()); } public void testParseGitFileName_FailFooBar() { final FileHeader fh = data("a/foo b/bar\n-"); assertTrue(fh.parseGitFileName(0, fh.buf.length) > 0); - assertNull(fh.getOldName()); - assertNull(fh.getNewName()); + assertNull(fh.getOldPath()); + assertNull(fh.getNewPath()); assertFalse(fh.hasMetaDataChanges()); } @@ -97,8 +97,8 @@ public class FileHeaderTest extends TestCase { final FileHeader fh = header(name); assertEquals(gitLine(name).length(), fh.parseGitFileName(0, fh.buf.length)); - assertEquals(name, fh.getOldName()); - assertSame(fh.getOldName(), fh.getNewName()); + assertEquals(name, fh.getOldPath()); + assertSame(fh.getOldPath(), fh.getNewPath()); assertFalse(fh.hasMetaDataChanges()); } @@ -108,8 +108,8 @@ public class FileHeaderTest extends TestCase { final FileHeader fh = dqHeader(dqName); assertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0, fh.buf.length)); - assertEquals(name, fh.getOldName()); - assertSame(fh.getOldName(), fh.getNewName()); + assertEquals(name, fh.getOldPath()); + assertSame(fh.getOldPath(), fh.getNewPath()); assertFalse(fh.hasMetaDataChanges()); } @@ -119,8 +119,8 @@ public class FileHeaderTest extends TestCase { final FileHeader fh = dqHeader(dqName); assertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0, fh.buf.length)); - assertEquals(name, fh.getOldName()); - assertSame(fh.getOldName(), fh.getNewName()); + assertEquals(name, fh.getOldPath()); + assertSame(fh.getOldPath(), fh.getNewPath()); assertFalse(fh.hasMetaDataChanges()); } @@ -129,8 +129,8 @@ public class FileHeaderTest extends TestCase { final FileHeader fh = header(name); assertEquals(gitLine(name).length(), fh.parseGitFileName(0, fh.buf.length)); - assertEquals(name, fh.getOldName()); - assertSame(fh.getOldName(), fh.getNewName()); + assertEquals(name, fh.getOldPath()); + assertSame(fh.getOldPath(), fh.getNewPath()); assertFalse(fh.hasMetaDataChanges()); } @@ -139,8 +139,8 @@ public class FileHeaderTest extends TestCase { final String header = "project-v-1.0/" + name + " mydev/" + name + "\n"; final FileHeader fh = data(header + "-"); assertEquals(header.length(), fh.parseGitFileName(0, fh.buf.length)); - assertEquals(name, fh.getOldName()); - assertSame(fh.getOldName(), fh.getNewName()); + assertEquals(name, fh.getOldPath()); + assertSame(fh.getOldPath(), fh.getNewPath()); assertFalse(fh.hasMetaDataChanges()); } @@ -153,9 +153,9 @@ public class FileHeaderTest extends TestCase { + "@@ -0,0 +1 @@\n" + "+a\n"); assertParse(fh); - assertEquals("/dev/null", fh.getOldName()); - assertSame(DiffEntry.DEV_NULL, fh.getOldName()); - assertEquals("\u00c5ngstr\u00f6m", fh.getNewName()); + assertEquals("/dev/null", fh.getOldPath()); + assertSame(DiffEntry.DEV_NULL, fh.getOldPath()); + assertEquals("\u00c5ngstr\u00f6m", fh.getNewPath()); assertSame(FileHeader.ChangeType.ADD, fh.getChangeType()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); @@ -178,9 +178,9 @@ public class FileHeaderTest extends TestCase { + "@@ -1 +0,0 @@\n" + "-a\n"); assertParse(fh); - assertEquals("\u00c5ngstr\u00f6m", fh.getOldName()); - assertEquals("/dev/null", fh.getNewName()); - assertSame(DiffEntry.DEV_NULL, fh.getNewName()); + assertEquals("\u00c5ngstr\u00f6m", fh.getOldPath()); + assertEquals("/dev/null", fh.getNewPath()); + assertSame(DiffEntry.DEV_NULL, fh.getNewPath()); assertSame(FileHeader.ChangeType.DELETE, fh.getChangeType()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); @@ -198,8 +198,8 @@ public class FileHeaderTest extends TestCase { final FileHeader fh = data("diff --git a/a b b/a b\n" + "old mode 100644\n" + "new mode 100755\n"); assertParse(fh); - assertEquals("a b", fh.getOldName()); - assertEquals("a b", fh.getNewName()); + assertEquals("a b", fh.getOldPath()); + assertEquals("a b", fh.getNewPath()); assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); @@ -220,14 +220,14 @@ public class FileHeaderTest extends TestCase { + "rename to \" c/\\303\\205ngstr\\303\\266m\"\n"); int ptr = fh.parseGitFileName(0, fh.buf.length); assertTrue(ptr > 0); - assertNull(fh.getOldName()); // can't parse names on a rename - assertNull(fh.getNewName()); + assertNull(fh.getOldPath()); // can't parse names on a rename + assertNull(fh.getNewPath()); ptr = fh.parseGitHeaders(ptr, fh.buf.length); assertTrue(ptr > 0); - assertEquals("a", fh.getOldName()); - assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName()); + assertEquals("a", fh.getOldPath()); + assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewPath()); assertSame(FileHeader.ChangeType.RENAME, fh.getChangeType()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); @@ -249,14 +249,14 @@ public class FileHeaderTest extends TestCase { + "rename new \" c/\\303\\205ngstr\\303\\266m\"\n"); int ptr = fh.parseGitFileName(0, fh.buf.length); assertTrue(ptr > 0); - assertNull(fh.getOldName()); // can't parse names on a rename - assertNull(fh.getNewName()); + assertNull(fh.getOldPath()); // can't parse names on a rename + assertNull(fh.getNewPath()); ptr = fh.parseGitHeaders(ptr, fh.buf.length); assertTrue(ptr > 0); - assertEquals("a", fh.getOldName()); - assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName()); + assertEquals("a", fh.getOldPath()); + assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewPath()); assertSame(FileHeader.ChangeType.RENAME, fh.getChangeType()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); @@ -278,14 +278,14 @@ public class FileHeaderTest extends TestCase { + "copy to \" c/\\303\\205ngstr\\303\\266m\"\n"); int ptr = fh.parseGitFileName(0, fh.buf.length); assertTrue(ptr > 0); - assertNull(fh.getOldName()); // can't parse names on a copy - assertNull(fh.getNewName()); + assertNull(fh.getOldPath()); // can't parse names on a copy + assertNull(fh.getNewPath()); ptr = fh.parseGitHeaders(ptr, fh.buf.length); assertTrue(ptr > 0); - assertEquals("a", fh.getOldName()); - assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName()); + assertEquals("a", fh.getOldPath()); + assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewPath()); assertSame(FileHeader.ChangeType.COPY, fh.getChangeType()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); @@ -307,8 +307,8 @@ public class FileHeaderTest extends TestCase { + ".." + nid + " 100644\n" + "--- a/a\n" + "+++ b/a\n"); assertParse(fh); - assertEquals("a", fh.getOldName()); - assertEquals("a", fh.getNewName()); + assertEquals("a", fh.getOldPath()); + assertEquals("a", fh.getNewPath()); assertSame(FileMode.REGULAR_FILE, fh.getOldMode()); assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); @@ -331,8 +331,8 @@ public class FileHeaderTest extends TestCase { + ".." + nid + "\n" + "--- a/a\n" + "+++ b/a\n"); assertParse(fh); - assertEquals("a", fh.getOldName()); - assertEquals("a", fh.getNewName()); + assertEquals("a", fh.getOldPath()); + assertEquals("a", fh.getNewPath()); assertFalse(fh.hasMetaDataChanges()); assertNull(fh.getOldMode()); @@ -357,8 +357,8 @@ public class FileHeaderTest extends TestCase { + " 100644\n" + "--- a/a\n" + "+++ b/a\n"); assertParse(fh); - assertEquals("a", fh.getOldName()); - assertEquals("a", fh.getNewName()); + assertEquals("a", fh.getOldPath()); + assertEquals("a", fh.getNewPath()); assertSame(FileMode.REGULAR_FILE, fh.getOldMode()); assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); @@ -386,8 +386,8 @@ public class FileHeaderTest extends TestCase { + "\n" + "--- a/a\n" + "+++ b/a\n"); assertParse(fh); - assertEquals("a", fh.getOldName()); - assertEquals("a", fh.getNewName()); + assertEquals("a", fh.getOldPath()); + assertEquals("a", fh.getNewPath()); assertNull(fh.getOldMode()); assertNull(fh.getNewMode()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java index 1d879cba67..cef13f5f16 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java @@ -60,8 +60,8 @@ public class PatchCcTest extends TestCase { final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); assertEquals("org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java", - cfh.getNewName()); - assertEquals(cfh.getNewName(), cfh.getOldName()); + cfh.getNewPath()); + assertEquals(cfh.getNewPath(), cfh.getOldPath()); assertEquals(98, cfh.startOffset); @@ -114,8 +114,8 @@ public class PatchCcTest extends TestCase { final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); - assertSame(DiffEntry.DEV_NULL, cfh.getOldName()); - assertEquals("d", cfh.getNewName()); + assertSame(DiffEntry.DEV_NULL, cfh.getOldPath()); + assertEquals("d", cfh.getNewPath()); assertEquals(187, cfh.startOffset); @@ -168,8 +168,8 @@ public class PatchCcTest extends TestCase { final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); - assertEquals("a", cfh.getOldName()); - assertSame(DiffEntry.DEV_NULL, cfh.getNewName()); + assertEquals("a", cfh.getOldPath()); + assertSame(DiffEntry.DEV_NULL, cfh.getNewPath()); assertEquals(187, cfh.startOffset); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java index 62a107130e..67b3f5c589 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java @@ -56,7 +56,7 @@ public class PatchErrorTest extends TestCase { final FileHeader fh = p.getFiles().get(0); assertEquals( "org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java", - fh.getNewName()); + fh.getNewPath()); assertEquals(1, fh.getHunks().size()); } @@ -114,14 +114,14 @@ public class PatchErrorTest extends TestCase { final FileHeader fh = p.getFiles().get(0); assertEquals( "org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java", - fh.getNewName()); + fh.getNewPath()); assertEquals(1, fh.getHunks().size()); } { final FileHeader fh = p.getFiles().get(1); assertEquals( "org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java", - fh.getNewName()); + fh.getNewPath()); assertEquals(1, fh.getHunks().size()); } @@ -139,7 +139,7 @@ public class PatchErrorTest extends TestCase { { final FileHeader fh = p.getFiles().get(0); assertEquals("org.spearce.egit.ui/icons/toolbar/fetchd.png", fh - .getNewName()); + .getNewPath()); assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType()); assertTrue(fh.getHunks().isEmpty()); assertNull(fh.getForwardBinaryHunk()); @@ -147,7 +147,7 @@ public class PatchErrorTest extends TestCase { { final FileHeader fh = p.getFiles().get(1); assertEquals("org.spearce.egit.ui/icons/toolbar/fetche.png", fh - .getNewName()); + .getNewPath()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); assertTrue(fh.getHunks().isEmpty()); assertNull(fh.getForwardBinaryHunk()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java index 52d6e27ca8..dd76251385 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java @@ -68,11 +68,11 @@ public class PatchTest extends TestCase { assertEquals( "org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java", - fRepositoryConfigTest.getNewName()); + fRepositoryConfigTest.getNewPath()); assertEquals( "org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java", - fRepositoryConfig.getNewName()); + fRepositoryConfig.getNewPath()); assertEquals(572, fRepositoryConfigTest.startOffset); assertEquals(1490, fRepositoryConfig.startOffset); @@ -168,7 +168,7 @@ public class PatchTest extends TestCase { assertEquals("0000000", fh.getOldId().name()); assertSame(FileMode.MISSING, fh.getOldMode()); assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); - assertTrue(fh.getNewName().startsWith( + assertTrue(fh.getNewPath().startsWith( "org.spearce.egit.ui/icons/toolbar/")); assertSame(FileHeader.PatchType.BINARY, fh.getPatchType()); assertTrue(fh.getHunks().isEmpty()); @@ -179,7 +179,7 @@ public class PatchTest extends TestCase { } final FileHeader fh = p.getFiles().get(4); - assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewName()); + assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewPath()); assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); assertFalse(fh.hasMetaDataChanges()); @@ -203,7 +203,7 @@ public class PatchTest extends TestCase { assertNotNull(fh.getNewId()); assertEquals(ObjectId.zeroId().name(), fh.getOldId().name()); assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); - assertTrue(fh.getNewName().startsWith( + assertTrue(fh.getNewPath().startsWith( "org.spearce.egit.ui/icons/toolbar/")); assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType()); assertTrue(fh.getHunks().isEmpty()); @@ -224,7 +224,7 @@ public class PatchTest extends TestCase { } final FileHeader fh = p.getFiles().get(4); - assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewName()); + assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewPath()); assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); assertFalse(fh.hasMetaDataChanges()); @@ -241,7 +241,7 @@ public class PatchTest extends TestCase { assertTrue(p.getErrors().isEmpty()); final FileHeader fh = p.getFiles().get(0); - assertTrue(fh.getNewName().startsWith("zero.bin")); + assertTrue(fh.getNewPath().startsWith("zero.bin")); assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType()); assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); @@ -279,7 +279,7 @@ public class PatchTest extends TestCase { final FileHeader f = p.getFiles().get(0); - assertEquals("a", f.getNewName()); + assertEquals("a", f.getNewPath()); assertEquals(252, f.startOffset); assertEquals("2e65efe", f.getOldId().name()); @@ -313,7 +313,7 @@ public class PatchTest extends TestCase { final FileHeader f = p.getFiles().get(0); - assertEquals("a", f.getNewName()); + assertEquals("a", f.getNewPath()); assertEquals(256, f.startOffset); assertEquals("f2ad6c7", f.getOldId().name()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java new file mode 100644 index 0000000000..bb76d0075d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010, 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.treewalk; + +import java.io.File; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FS; + +/** + * A {@link FileTreeIterator} used in tests which allows to specify explicitly + * what will be returned by {@link #getEntryLastModified()}. This allows to + * write tests where certain files have to have the same modification time. + * <p> + * This iterator is configured by a list of strictly increasing long values + * t(0), t(1), ..., t(n). For each file with a modification between t(x) and + * t(x+1) [ t(x) <= time < t(x+1) ] this iterator will report t(x). For files + * with a modification time smaller t(0) a modification time of 0 is returned. + * For files with a modification time greater or equal t(n) t(n) will be + * returned. + * <p> + * This class was written especially to test racy-git problems + */ +public class FileTreeIteratorWithTimeControl extends FileTreeIterator { + private TreeSet<Long> modTimes; + + public FileTreeIteratorWithTimeControl(FileTreeIterator p, Repository repo, + TreeSet<Long> modTimes) { + super(p, repo.getWorkTree(), repo.getFS()); + this.modTimes = modTimes; + } + + public FileTreeIteratorWithTimeControl(FileTreeIterator p, File f, FS fs, + TreeSet<Long> modTimes) { + super(p, f, fs); + this.modTimes = modTimes; + } + + public FileTreeIteratorWithTimeControl(Repository repo, + TreeSet<Long> modTimes) { + super(repo); + this.modTimes = modTimes; + } + + public FileTreeIteratorWithTimeControl(File f, FS fs, + TreeSet<Long> modTimes) { + super(f, fs); + this.modTimes = modTimes; + } + + @Override + public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) { + return new FileTreeIteratorWithTimeControl(this, + ((FileEntry) current()).file, fs, modTimes); + } + + @Override + public long getEntryLastModified() { + if (modTimes == null) + return 0; + Long cutOff = Long.valueOf(super.getEntryLastModified() + 1); + SortedSet<Long> head = modTimes.headSet(cutOff); + return head.isEmpty() ? 0 : head.last().longValue(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java index 675331baf4..e59b7c18d3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java @@ -120,11 +120,15 @@ public class NameConflictTreeWalkTest extends RepositoryTestCase { tw.addTree(new DirCacheIterator(tree1)); assertModes("a", REGULAR_FILE, TREE, tw); + assertTrue(tw.isDirectoryFileConflict()); assertTrue(tw.isSubtree()); tw.enterSubtree(); assertModes("a/b", MISSING, REGULAR_FILE, tw); + assertTrue(tw.isDirectoryFileConflict()); assertModes("a.b", EXECUTABLE_FILE, MISSING, tw); + assertFalse(tw.isDirectoryFileConflict()); assertModes("a0b", SYMLINK, MISSING, tw); + assertFalse(tw.isDirectoryFileConflict()); } public void testDF_GapByOne() throws Exception { @@ -153,10 +157,14 @@ public class NameConflictTreeWalkTest extends RepositoryTestCase { assertModes("a", REGULAR_FILE, TREE, tw); assertTrue(tw.isSubtree()); + assertTrue(tw.isDirectoryFileConflict()); tw.enterSubtree(); assertModes("a/b", MISSING, REGULAR_FILE, tw); + assertTrue(tw.isDirectoryFileConflict()); assertModes("a.b", EXECUTABLE_FILE, EXECUTABLE_FILE, tw); + assertFalse(tw.isDirectoryFileConflict()); assertModes("a0b", SYMLINK, MISSING, tw); + assertFalse(tw.isDirectoryFileConflict()); } public void testDF_SkipsSeenSubtree() throws Exception { @@ -185,10 +193,57 @@ public class NameConflictTreeWalkTest extends RepositoryTestCase { assertModes("a", REGULAR_FILE, TREE, tw); assertTrue(tw.isSubtree()); + assertTrue(tw.isDirectoryFileConflict()); tw.enterSubtree(); assertModes("a/b", MISSING, REGULAR_FILE, tw); + assertTrue(tw.isDirectoryFileConflict()); assertModes("a.b", MISSING, EXECUTABLE_FILE, tw); + assertFalse(tw.isDirectoryFileConflict()); assertModes("a0b", SYMLINK, SYMLINK, tw); + assertFalse(tw.isDirectoryFileConflict()); + } + + public void testDF_DetectConflict() throws Exception { + final DirCache tree0 = db.readDirCache(); + final DirCache tree1 = db.readDirCache(); + { + final DirCacheBuilder b0 = tree0.builder(); + final DirCacheBuilder b1 = tree1.builder(); + + b0.add(makeEntry("0", REGULAR_FILE)); + b0.add(makeEntry("a", REGULAR_FILE)); + b1.add(makeEntry("0", REGULAR_FILE)); + b1.add(makeEntry("a.b", REGULAR_FILE)); + b1.add(makeEntry("a/b", REGULAR_FILE)); + b1.add(makeEntry("a/c/e", REGULAR_FILE)); + + b0.finish(); + b1.finish(); + assertEquals(2, tree0.getEntryCount()); + assertEquals(4, tree1.getEntryCount()); + } + + final NameConflictTreeWalk tw = new NameConflictTreeWalk(db); + tw.reset(); + tw.addTree(new DirCacheIterator(tree0)); + tw.addTree(new DirCacheIterator(tree1)); + + assertModes("0", REGULAR_FILE, REGULAR_FILE, tw); + assertFalse(tw.isDirectoryFileConflict()); + assertModes("a", REGULAR_FILE, TREE, tw); + assertTrue(tw.isSubtree()); + assertTrue(tw.isDirectoryFileConflict()); + tw.enterSubtree(); + assertModes("a/b", MISSING, REGULAR_FILE, tw); + assertTrue(tw.isDirectoryFileConflict()); + assertModes("a/c", MISSING, TREE, tw); + assertTrue(tw.isDirectoryFileConflict()); + tw.enterSubtree(); + assertModes("a/c/e", MISSING, REGULAR_FILE, tw); + assertTrue(tw.isDirectoryFileConflict()); + + assertModes("a.b", MISSING, REGULAR_FILE, tw); + assertFalse(tw.isDirectoryFileConflict()); } private DirCacheEntry makeEntry(final String path, final FileMode mode) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java index 3b1584612d..a15cadfbda 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java @@ -123,6 +123,13 @@ public class ChangeIdUtilTest extends TestCase { call("has changeid\n\nBug: 33\nmore text\nSigned-off-by: me@you.too\nChange-Id: I0123456789012345678901234567890123456789\nAnd then some\n")); } + public void testHasChangeidWithReplacement() throws Exception { + assertEquals( + "has changeid\n\nBug: 33\nmore text\nSigned-off-by: me@you.too\nChange-Id: I988d2d7a6f2c0578fccabd4ebd3cec0768bc7f9f\nAnd then some\n", + call("has changeid\n\nBug: 33\nmore text\nSigned-off-by: me@you.too\nChange-Id: I0123456789012345678901234567890123456789\nAnd then some\n", + true)); + } + public void testOneliner() throws Exception { assertEquals( "oneliner\n\nChange-Id: I3a98091ce4470de88d52ae317fcd297e2339f063\n", @@ -236,6 +243,47 @@ public class ChangeIdUtilTest extends TestCase { "Change-Id: I4f4e2e1e8568ddc1509baecb8c1270a1fb4b6da7\n"); } + public void testChangeIdAlreadySetWithReplacement() throws Exception { + // If a Change-Id is already present in the footer, the hook + // replaces the Change-Id with the new value.. + // + assertEquals("a\n" + // + "\n" + // + "Change-Id: Ifa324efa85bfb3c8696a46a0f67fa70c35be5f5f\n", + call("a\n" + // + "\n" + // + "Change-Id: Iaeac9b4149291060228ef0154db2985a31111335\n", + true)); + assertEquals("fix: this thing\n" + // + "\n" + // + "Change-Id: Ib63e4990a06412a3f24bd93bb160e98ac1bd412b\n", + call("fix: this thing\n" + // + "\n" + // + "Change-Id: I388bdaf52ed05b55e62a22d0a20d2c1ae0d33e7e\n", + true)); + assertEquals("fix-a-widget: this thing\n" + // + "\n" + // + "Change-Id: If0444e4d0cabcf41b3d3b46b7e9a7a64a82117af\n", + call("fix-a-widget: this thing\n" + // + "\n" + // + "Change-Id: Id3bc5359d768a6400450283e12bdfb6cd135ea4b\n", + true)); + assertEquals("FIX: this thing\n" + // + "\n" + // + "Change-Id: Iba5a3b2d5e5df46448f6daf362b6bfa775c6491d\n", + call("FIX: this thing\n" + // + "\n" + // + "Change-Id: I1b55098b5a2cce0b3f3da783dda50d5f79f873fa\n", + true)); + assertEquals("Fix-A-Widget: this thing\n" + // + "\n" + // + "Change-Id: I2573d47c62c42429fbe424d70cfba931f8f87848\n", + call("Fix-A-Widget: this thing\n" + // + "\n" + // + "Change-Id: I4f4e2e1e8568ddc1509baecb8c1270a1fb4b6da7\n", + true)); + } + public void testTimeAltersId() throws Exception { assertEquals("a\n" + // "\n" + // @@ -513,11 +561,15 @@ public class ChangeIdUtilTest extends TestCase { } private String call(final String body) throws Exception { + return call(body, false); + } + + private String call(final String body, boolean replaceExisting) throws Exception { ObjectId computeChangeId = ChangeIdUtil.computeChangeId(treeId1, parentId1, author, committer, body); if (computeChangeId == null) return body; - return ChangeIdUtil.insertId(body, computeChangeId); + return ChangeIdUtil.insertId(body, computeChangeId, replaceExisting); } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index 1b2b81fce3..a9878f8d29 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -335,6 +335,7 @@ sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0} staleRevFlagsOn=Stale RevFlags on {0} startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported statelessRPCRequiresOptionToBeEnabled=stateless RPC requires {0} to be enabled +submodulesNotSupported=Submodules are not supported symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java. tSizeMustBeGreaterOrEqual1=tSize must be >= 1 theFactoryMustNotBeNull=The factory must not be null diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 9d1e2cd808..461242cb27 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -394,6 +394,7 @@ public class JGitText extends TranslationBundle { /***/ public String staleRevFlagsOn; /***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported; /***/ public String statelessRPCRequiresOptionToBeEnabled; + /***/ public String submodulesNotSupported; /***/ public String symlinkCannotBeWrittenAsTheLinkTarget; /***/ public String tSizeMustBeGreaterOrEqual1; /***/ public String theFactoryMustNotBeNull; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index e41ab580bb..f7d4da4d5a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -76,6 +76,8 @@ public class AddCommand extends GitCommand<DirCache> { private WorkingTreeIterator workingTreeIterator; + private boolean update = false; + /** * * @param repo @@ -158,18 +160,20 @@ public class AddCommand extends GitCommand<DirCache> { // this path, we however want to add only one // new DirCacheEntry per path. else if (!(path.equals(lastAddedFile))) { - if (f != null) { // the file exists - DirCacheEntry entry = new DirCacheEntry(path); - entry.setLength((int)f.getEntryLength()); - entry.setLastModified(f.getEntryLastModified()); - entry.setFileMode(f.getEntryFileMode()); - entry.setObjectId(ow.writeBlob(file)); - - builder.add(entry); - lastAddedFile = path; - } else { - c = tw.getTree(0, DirCacheIterator.class); - builder.add(c.getDirCacheEntry()); + if (!(update && tw.getTree(0, DirCacheIterator.class) == null)) { + if (f != null) { // the file exists + DirCacheEntry entry = new DirCacheEntry(path); + entry.setLength((int)f.getEntryLength()); + entry.setLastModified(f.getEntryLastModified()); + entry.setFileMode(f.getEntryFileMode()); + entry.setObjectId(ow.writeBlob(file)); + + builder.add(entry); + lastAddedFile = path; + } else if (!update){ + c = tw.getTree(0, DirCacheIterator.class); + builder.add(c.getDirCacheEntry()); + } } } } @@ -186,4 +190,29 @@ public class AddCommand extends GitCommand<DirCache> { return dc; } + /** + * @param update + * If set to true, the command only matches {@code filepattern} + * against already tracked files in the index rather than the + * working tree. That means that it will never stage new files, + * but that it will stage modified new contents of tracked files + * and that it will remove files from the index if the + * corresponding files in the working tree have been removed. + * In contrast to the git command line a {@code filepattern} must + * exist also if update is set to true as there is no + * concept of a working directory here. + * + * @return {@code this} + */ + public AddCommand setUpdate(boolean update) { + this.update = update; + return this; + } + + /** + * @return is the parameter update is set + */ + public boolean isUpdate() { + return update; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 17b7113470..ae4b33477b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -42,7 +42,6 @@ */ package org.eclipse.jgit.api; -import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.LinkedList; @@ -80,6 +79,8 @@ public class CommitCommand extends GitCommand<RevCommit> { private String message; + private boolean all; + /** * parents this commit should have. The current HEAD will be in this list * and also all commits mentioned in .git/MERGE_HEAD @@ -128,6 +129,18 @@ public class CommitCommand extends GitCommand<RevCommit> { processOptions(state); try { + if (all && !repo.isBare() && repo.getWorkTree() != null) { + Git git = new Git(repo); + try { + git.add() + .addFilepattern(".") + .setUpdate(true).call(); + } catch (NoFilepatternException e) { + // should really not happen + throw new JGitInternalException(e.getMessage(), e); + } + } + Ref head = repo.getRef(Constants.HEAD); if (head == null) throw new NoHeadException( @@ -174,13 +187,11 @@ public class CommitCommand extends GitCommand<RevCommit> { case NEW: case FAST_FORWARD: { setCallable(false); - File meta = repo.getDirectory(); - if (state == RepositoryState.MERGING_RESOLVED - && meta != null) { + if (state == RepositoryState.MERGING_RESOLVED) { // Commit was successful. Now delete the files // used for merge commits - new File(meta, Constants.MERGE_HEAD).delete(); - new File(meta, Constants.MERGE_MSG).delete(); + repo.writeMergeCommitMsg(null); + repo.writeMergeHeads(null); } return revCommit; } @@ -356,4 +367,19 @@ public class CommitCommand extends GitCommand<RevCommit> { public PersonIdent getAuthor() { return author; } + + /** + * If set to true the Commit command automatically stages files that have + * been modified and deleted, but new files you not known by the repository + * are not affected. This corresponds to the parameter -a on the command + * line. + * + * @param all + * @return {@code this} + */ + public CommitCommand setAll(boolean all) { + this.all = all; + return this; + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java index 7dbcdaa6d9..55ecc4e22a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java @@ -112,15 +112,15 @@ public class DiffEntry { entry.oldMode = walk.getFileMode(0); entry.newMode = walk.getFileMode(1); - entry.newName = entry.oldName = walk.getPathString(); + entry.newPath = entry.oldPath = walk.getPathString(); if (entry.oldMode == FileMode.MISSING) { - entry.oldName = DiffEntry.DEV_NULL; + entry.oldPath = DiffEntry.DEV_NULL; entry.changeType = ChangeType.ADD; r.add(entry); } else if (entry.newMode == FileMode.MISSING) { - entry.newName = DiffEntry.DEV_NULL; + entry.newPath = DiffEntry.DEV_NULL; entry.changeType = ChangeType.DELETE; r.add(entry); @@ -139,11 +139,11 @@ public class DiffEntry { DiffEntry e = new DiffEntry(); e.oldId = A_ZERO; e.oldMode = FileMode.MISSING; - e.oldName = DEV_NULL; + e.oldPath = DEV_NULL; e.newId = AbbreviatedObjectId.fromObjectId(id); e.newMode = FileMode.REGULAR_FILE; - e.newName = path; + e.newPath = path; e.changeType = ChangeType.ADD; return e; } @@ -152,11 +152,11 @@ public class DiffEntry { DiffEntry e = new DiffEntry(); e.oldId = AbbreviatedObjectId.fromObjectId(id); e.oldMode = FileMode.REGULAR_FILE; - e.oldName = path; + e.oldPath = path; e.newId = A_ZERO; e.newMode = FileMode.MISSING; - e.newName = DEV_NULL; + e.newPath = DEV_NULL; e.changeType = ChangeType.DELETE; return e; } @@ -164,10 +164,10 @@ public class DiffEntry { static DiffEntry modify(String path) { DiffEntry e = new DiffEntry(); e.oldMode = FileMode.REGULAR_FILE; - e.oldName = path; + e.oldPath = path; e.newMode = FileMode.REGULAR_FILE; - e.newName = path; + e.newPath = path; e.changeType = ChangeType.MODIFY; return e; } @@ -185,21 +185,21 @@ public class DiffEntry { DiffEntry del = new DiffEntry(); del.oldId = entry.getOldId(); del.oldMode = entry.getOldMode(); - del.oldName = entry.getOldName(); + del.oldPath = entry.getOldPath(); del.newId = A_ZERO; del.newMode = FileMode.MISSING; - del.newName = DiffEntry.DEV_NULL; + del.newPath = DiffEntry.DEV_NULL; del.changeType = ChangeType.DELETE; DiffEntry add = new DiffEntry(); add.oldId = A_ZERO; add.oldMode = FileMode.MISSING; - add.oldName = DiffEntry.DEV_NULL; + add.oldPath = DiffEntry.DEV_NULL; add.newId = entry.getNewId(); add.newMode = entry.getNewMode(); - add.newName = entry.getNewName(); + add.newPath = entry.getNewPath(); add.changeType = ChangeType.ADD; return Arrays.asList(del, add); } @@ -210,11 +210,11 @@ public class DiffEntry { r.oldId = src.oldId; r.oldMode = src.oldMode; - r.oldName = src.oldName; + r.oldPath = src.oldPath; r.newId = dst.newId; r.newMode = dst.newMode; - r.newName = dst.newName; + r.newPath = dst.newPath; r.changeType = changeType; r.score = score; @@ -223,10 +223,10 @@ public class DiffEntry { } /** File name of the old (pre-image). */ - protected String oldName; + protected String oldPath; /** File name of the new (post-image). */ - protected String newName; + protected String newPath; /** Old mode of the file, if described by the patch, else null. */ protected FileMode oldMode; @@ -253,7 +253,7 @@ public class DiffEntry { * of this patch: * <ul> * <li><i>file add</i>: always <code>/dev/null</code></li> - * <li><i>file modify</i>: always {@link #getNewName()}</li> + * <li><i>file modify</i>: always {@link #getNewPath()}</li> * <li><i>file delete</i>: always the file being deleted</li> * <li><i>file copy</i>: source file the copy originates from</li> * <li><i>file rename</i>: source file the rename originates from</li> @@ -261,8 +261,8 @@ public class DiffEntry { * * @return old name for this file. */ - public String getOldName() { - return oldName; + public String getOldPath() { + return oldPath; } /** @@ -272,7 +272,7 @@ public class DiffEntry { * of this patch: * <ul> * <li><i>file add</i>: always the file being created</li> - * <li><i>file modify</i>: always {@link #getOldName()}</li> + * <li><i>file modify</i>: always {@link #getOldPath()}</li> * <li><i>file delete</i>: always <code>/dev/null</code></li> * <li><i>file copy</i>: destination file the copy ends up at</li> * <li><i>file rename</i>: destination file the rename ends up at/li> @@ -280,8 +280,8 @@ public class DiffEntry { * * @return new name for this file. */ - public String getNewName() { - return newName; + public String getNewPath() { + return newPath; } /** @return the old file mode, if described in the patch */ @@ -294,14 +294,14 @@ public class DiffEntry { return newMode; } - /** @return the type of change this patch makes on {@link #getNewName()} */ + /** @return the type of change this patch makes on {@link #getNewPath()} */ public ChangeType getChangeType() { return changeType; } /** - * @return similarity score between {@link #getOldName()} and - * {@link #getNewName()} if {@link #getChangeType()} is + * @return similarity score between {@link #getOldPath()} and + * {@link #getNewPath()} if {@link #getChangeType()} is * {@link ChangeType#COPY} or {@link ChangeType#RENAME}. */ public int getScore() { @@ -334,19 +334,19 @@ public class DiffEntry { buf.append(" "); switch (changeType) { case ADD: - buf.append(newName); + buf.append(newPath); break; case COPY: - buf.append(oldName + "->" + newName); + buf.append(oldPath + "->" + newPath); break; case DELETE: - buf.append(oldName); + buf.append(oldPath); break; case MODIFY: - buf.append(oldName); + buf.append(oldPath); break; case RENAME: - buf.append(oldName + "->" + newName); + buf.append(oldPath + "->" + newPath); break; } buf.append("]"); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java index 4ee742ae16..bb4a77c427 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -246,8 +246,8 @@ public class DiffFormatter { private void writeDiffHeader(OutputStream o, DiffEntry ent) throws IOException { - String oldName = quotePath("a/" + ent.getOldName()); - String newName = quotePath("b/" + ent.getNewName()); + String oldName = quotePath("a/" + ent.getOldPath()); + String newName = quotePath("b/" + ent.getNewPath()); o.write(encode("diff --git " + oldName + " " + newName + "\n")); switch (ent.getChangeType()) { @@ -267,10 +267,10 @@ public class DiffFormatter { o.write(encodeASCII("similarity index " + ent.getScore() + "%")); o.write('\n'); - o.write(encode("rename from " + quotePath(ent.getOldName()))); + o.write(encode("rename from " + quotePath(ent.getOldPath()))); o.write('\n'); - o.write(encode("rename to " + quotePath(ent.getNewName()))); + o.write(encode("rename to " + quotePath(ent.getNewPath()))); o.write('\n'); break; @@ -278,10 +278,10 @@ public class DiffFormatter { o.write(encodeASCII("similarity index " + ent.getScore() + "%")); o.write('\n'); - o.write(encode("copy from " + quotePath(ent.getOldName()))); + o.write(encode("copy from " + quotePath(ent.getOldPath()))); o.write('\n'); - o.write(encode("copy to " + quotePath(ent.getNewName()))); + o.write(encode("copy to " + quotePath(ent.getNewPath()))); o.write('\n'); if (!ent.getOldMode().equals(ent.getNewMode())) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java index fedd7cda15..3ae3dc422c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java @@ -80,8 +80,8 @@ public class RenameDetector { // the old name. // if (ent.changeType == ChangeType.DELETE) - return ent.oldName; - return ent.newName; + return ent.oldPath; + return ent.newPath; } private int sortOf(ChangeType changeType) { @@ -369,18 +369,18 @@ public class RenameDetector { + deleted.size()); for (DiffEntry src : deleted) { - nameMap.put(src.oldName, src); + nameMap.put(src.oldPath, src); pm.update(1); } for (DiffEntry dst : added) { - DiffEntry src = nameMap.remove(dst.newName); + DiffEntry src = nameMap.remove(dst.newPath); if (src != null) { if (sameType(src.oldMode, dst.newMode)) { entries.add(DiffEntry.pair(ChangeType.MODIFY, src, dst, src.score)); } else { - nameMap.put(src.oldName, src); + nameMap.put(src.oldPath, src); newAdded.add(dst); } } else { @@ -509,10 +509,10 @@ public class RenameDetector { long[] matrix = new long[dels.size() * adds.size()]; int mNext = 0; for (int addIdx = 0; addIdx < adds.size(); addIdx++) { - String addedName = adds.get(addIdx).newName; + String addedName = adds.get(addIdx).newPath; for (int delIdx = 0; delIdx < dels.size(); delIdx++) { - String deletedName = dels.get(delIdx).oldName; + String deletedName = dels.get(delIdx).oldPath; int score = SimilarityRenameDetector.nameScore(addedName, deletedName); matrix[mNext] = SimilarityRenameDetector.encode(score, addIdx, delIdx); @@ -625,7 +625,7 @@ public class RenameDetector { } private static String path(DiffEntry de) { - return de.changeType == ChangeType.DELETE ? de.oldName : de.newName; + return de.changeType == ChangeType.DELETE ? de.oldPath : de.newPath; } private static FileMode mode(DiffEntry de) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java index 22b74f461c..39bcebb4c3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java @@ -216,7 +216,8 @@ class SimilarityIndex { for (;;) { if (srcKey == dstKey) { - common += countOf(dstHash[dstIdx]); + common += Math.min(countOf(srcHash[srcIdx]), + countOf(dstHash[dstIdx])); if (++srcIdx == srcHash.length) break; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java index d05fc2a313..643ac01525 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java @@ -266,7 +266,7 @@ class SimilarityRenameDetector { // nameScore returns a value between 0 and 100, but we want it // to be in the same range as the content score. This allows it // to be dropped into the pretty formula for the final score. - int nameScore = nameScore(srcEnt.oldName, dstEnt.newName) * 100; + int nameScore = nameScore(srcEnt.oldPath, dstEnt.newPath) * 100; int score = (contentScore * 99 + nameScore * 1) / 10000; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java index db0f942b07..d4e6ac9824 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java @@ -1,6 +1,7 @@ /* * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com> * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com> + * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com> * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -44,97 +45,163 @@ package org.eclipse.jgit.lib; -import java.io.File; import java.io.IOException; import java.util.HashSet; -import org.eclipse.jgit.lib.GitIndex.Entry; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; +import org.eclipse.jgit.treewalk.filter.TreeFilter; /** - * Compares the Index, a Tree, and the working directory - * - * @deprecated Use {@link org.eclipse.jgit.treewalk.TreeWalk} instead, with at - * least the {@link org.eclipse.jgit.dircache.DirCacheIterator} and - * {@link org.eclipse.jgit.treewalk.FileTreeIterator} iterators, and setting - * the filter {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF}. + * Compares the index, a tree, and the working directory + * Ignored files are not taken into account. + * The following information is retrieved: + * <li> added files + * <li> changed files + * <li> removed files + * <li> missing files + * <li> modified files + * <li> untracked files */ -@Deprecated public class IndexDiff { - private GitIndex index; - private Tree tree; + + private final static int TREE = 0; + + private final static int INDEX = 1; + + private final static int WORKDIR = 2; + + private final Repository repository; + + private final RevTree tree; + + private final WorkingTreeIterator initialWorkingTreeIterator; + + private HashSet<String> added = new HashSet<String>(); + + private HashSet<String> changed = new HashSet<String>(); + + private HashSet<String> removed = new HashSet<String>(); + + private HashSet<String> missing = new HashSet<String>(); + + private HashSet<String> modified = new HashSet<String>(); + + private HashSet<String> untracked = new HashSet<String>(); /** - * Construct an indexdiff for diffing the workdir against - * the index. + * Construct an IndexDiff * * @param repository + * @param revstr + * symbolic name e.g. HEAD + * @param workingTreeIterator + * iterator for working directory * @throws IOException */ - public IndexDiff(Repository repository) throws IOException { - this.tree = repository.mapTree(Constants.HEAD); - this.index = repository.getIndex(); + public IndexDiff(Repository repository, String revstr, + WorkingTreeIterator workingTreeIterator) throws IOException { + this.repository = repository; + ObjectId objectId = repository.resolve(revstr); + tree = new RevWalk(repository).parseTree(objectId); + this.initialWorkingTreeIterator = workingTreeIterator; } /** - * Construct an indexdiff for diffing the workdir against both - * the index and a tree. + * Construct an Indexdiff * - * @param tree - * @param index + * @param repository + * @param objectId + * tree id + * @param workingTreeIterator + * iterator for working directory + * @throws IOException */ - public IndexDiff(Tree tree, GitIndex index) { - this.tree = tree; - this.index = index; + public IndexDiff(Repository repository, ObjectId objectId, + WorkingTreeIterator workingTreeIterator) throws IOException { + this.repository = repository; + tree = new RevWalk(repository).parseTree(objectId); + this.initialWorkingTreeIterator = workingTreeIterator; } - boolean anyChanges = false; - /** * Run the diff operation. Until this is called, all lists will be empty + * * @return if anything is different between index, tree, and workdir * @throws IOException */ public boolean diff() throws IOException { - final File root = index.getRepository().getWorkTree(); - new IndexTreeWalker(index, tree, root, new AbstractIndexTreeVisitor() { - public void visitEntry(TreeEntry treeEntry, Entry indexEntry, File file) { - if (treeEntry == null) { - added.add(indexEntry.getName()); - anyChanges = true; - } else if (indexEntry == null) { - if (!(treeEntry instanceof Tree)) - removed.add(treeEntry.getFullName()); - anyChanges = true; + boolean changesExist = false; + DirCache dirCache = repository.readDirCache(); + TreeWalk treeWalk = new TreeWalk(repository); + treeWalk.reset(); + treeWalk.setRecursive(true); + // add the trees (tree, dirchache, workdir) + treeWalk.addTree(tree); + treeWalk.addTree(new DirCacheIterator(dirCache)); + treeWalk.addTree(initialWorkingTreeIterator); + treeWalk.setFilter(TreeFilter.ANY_DIFF); + while (treeWalk.next()) { + AbstractTreeIterator treeIterator = treeWalk.getTree(TREE, + AbstractTreeIterator.class); + DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX, + DirCacheIterator.class); + WorkingTreeIterator workingTreeIterator = treeWalk.getTree(WORKDIR, + WorkingTreeIterator.class); + FileMode fileModeTree = treeWalk.getFileMode(TREE); + + if (treeIterator != null) { + if (dirCacheIterator != null) { + if (!treeIterator.getEntryObjectId().equals( + dirCacheIterator.getEntryObjectId())) { + // in repo, in index, content diff => changed + changed.add(dirCacheIterator.getEntryPathString()); + changesExist = true; + } } else { - if (!treeEntry.getId().equals(indexEntry.getObjectId())) { - changed.add(indexEntry.getName()); - anyChanges = true; + // in repo, not in index => removed + if (!fileModeTree.equals(FileMode.TYPE_TREE)) { + removed.add(treeIterator.getEntryPathString()); + changesExist = true; } } - - if (indexEntry != null) { - if (!file.exists()) { - missing.add(indexEntry.getName()); - anyChanges = true; - } else { - if (indexEntry.isModified(root, true)) { - modified.add(indexEntry.getName()); - anyChanges = true; - } + } else { + if (dirCacheIterator != null) { + // not in repo, in index => added + added.add(dirCacheIterator.getEntryPathString()); + changesExist = true; + } else { + // not in repo, not in index => untracked + if (workingTreeIterator != null + && !workingTreeIterator.isEntryIgnored()) { + untracked.add(workingTreeIterator.getEntryPathString()); + changesExist = true; } } } - }).walk(); - return anyChanges; + if (dirCacheIterator != null) { + if (workingTreeIterator == null) { + // in index, not in workdir => missing + missing.add(dirCacheIterator.getEntryPathString()); + changesExist = true; + } else { + if (!dirCacheIterator.idEqual(workingTreeIterator)) { + // in index, in workdir, content differs => modified + modified.add(dirCacheIterator.getEntryPathString()); + changesExist = true; + } + } + } + } + return changesExist; } - HashSet<String> added = new HashSet<String>(); - HashSet<String> changed = new HashSet<String>(); - HashSet<String> removed = new HashSet<String>(); - HashSet<String> missing = new HashSet<String>(); - HashSet<String> modified = new HashSet<String>(); - /** * @return list of files added to the index, not in the tree */ @@ -169,4 +236,11 @@ public class IndexDiff { public HashSet<String> getModified() { return modified; } + + /** + * @return list of files on modified on disk relative to the index + */ + public HashSet<String> getUntracked() { + return untracked; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java index ef3d7840f7..beab61abe2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java @@ -165,6 +165,10 @@ public class WorkDirCheckout { private void checkoutOutIndexNoHead() throws IOException { new IndexTreeWalker(index, merge, root, new AbstractIndexTreeVisitor() { public void visitEntry(TreeEntry m, Entry i, File f) throws IOException { + // TODO remove this once we support submodules + if (f.getName().equals(".gitmodules")) + throw new UnsupportedOperationException( + JGitText.get().submodulesNotSupported); if (m == null) { index.remove(root, f); return; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java index 0c24fc6be2..eae1040f09 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java @@ -385,12 +385,12 @@ public class FileHeader extends DiffEntry { if (buf[sp - 2] != '"') { return eol; } - oldName = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1); - oldName = p1(oldName); + oldPath = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1); + oldPath = p1(oldPath); } else { - oldName = decode(Constants.CHARSET, buf, aStart, sp - 1); + oldPath = decode(Constants.CHARSET, buf, aStart, sp - 1); } - newName = oldName; + newPath = oldPath; return eol; } @@ -431,27 +431,27 @@ public class FileHeader extends DiffEntry { parseNewFileMode(ptr, eol); } else if (match(buf, ptr, COPY_FROM) >= 0) { - oldName = parseName(oldName, ptr + COPY_FROM.length, eol); + oldPath = parseName(oldPath, ptr + COPY_FROM.length, eol); changeType = ChangeType.COPY; } else if (match(buf, ptr, COPY_TO) >= 0) { - newName = parseName(newName, ptr + COPY_TO.length, eol); + newPath = parseName(newPath, ptr + COPY_TO.length, eol); changeType = ChangeType.COPY; } else if (match(buf, ptr, RENAME_OLD) >= 0) { - oldName = parseName(oldName, ptr + RENAME_OLD.length, eol); + oldPath = parseName(oldPath, ptr + RENAME_OLD.length, eol); changeType = ChangeType.RENAME; } else if (match(buf, ptr, RENAME_NEW) >= 0) { - newName = parseName(newName, ptr + RENAME_NEW.length, eol); + newPath = parseName(newPath, ptr + RENAME_NEW.length, eol); changeType = ChangeType.RENAME; } else if (match(buf, ptr, RENAME_FROM) >= 0) { - oldName = parseName(oldName, ptr + RENAME_FROM.length, eol); + oldPath = parseName(oldPath, ptr + RENAME_FROM.length, eol); changeType = ChangeType.RENAME; } else if (match(buf, ptr, RENAME_TO) >= 0) { - newName = parseName(newName, ptr + RENAME_TO.length, eol); + newPath = parseName(newPath, ptr + RENAME_TO.length, eol); changeType = ChangeType.RENAME; } else if (match(buf, ptr, SIMILARITY_INDEX) >= 0) { @@ -474,14 +474,14 @@ public class FileHeader extends DiffEntry { } void parseOldName(int ptr, final int eol) { - oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol)); - if (oldName == DEV_NULL) + oldPath = p1(parseName(oldPath, ptr + OLD_NAME.length, eol)); + if (oldPath == DEV_NULL) changeType = ChangeType.ADD; } void parseNewName(int ptr, final int eol) { - newName = p1(parseName(newName, ptr + NEW_NAME.length, eol)); - if (newName == DEV_NULL) + newPath = p1(parseName(newPath, ptr + NEW_NAME.length, eol)); + if (newPath == DEV_NULL) changeType = ChangeType.DELETE; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java index a068943fbc..dc9e0321f5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java @@ -65,19 +65,16 @@ public class PlotCommit<L extends PlotLane> extends RevCommit { PlotCommit[] children; - final Ref[] refs; + Ref[] refs; /** * Create a new commit. * * @param id * the identity of this commit. - * @param tags - * the tags associated with this commit, null for no tags */ - protected PlotCommit(final AnyObjectId id, final Ref[] tags) { + protected PlotCommit(final AnyObjectId id) { super(id); - this.refs = tags; passingLanes = NO_LANES; children = NO_CHILDREN; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java index 476592f5d9..c69e66cb8c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java @@ -52,6 +52,8 @@ import java.util.Map; import java.util.Set; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; @@ -94,14 +96,19 @@ public class PlotWalk extends RevWalk { @Override protected RevCommit createCommit(final AnyObjectId id) { - return new PlotCommit(id, getTags(id)); + return new PlotCommit(id); } - /** - * @param commitId - * @return return the list of knows tags referring to this commit - */ - protected Ref[] getTags(final AnyObjectId commitId) { + @Override + public RevCommit next() throws MissingObjectException, + IncorrectObjectTypeException, IOException { + PlotCommit<?> pc = (PlotCommit) super.next(); + if (pc != null) + pc.refs = getTags(pc); + return pc; + } + + private Ref[] getTags(final AnyObjectId commitId) { Collection<Ref> list = reverseRefMap.get(commitId); Ref[] tags; if (list == null) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java index 8ec2d2b091..41cfcf8691 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java @@ -245,8 +245,8 @@ class RewriteTreeFilter extends RevFilter { TreeFilter newFilter = oldFilter; for (DiffEntry ent : files) { - if (isRename(ent) && ent.getNewName().equals(oldFilter.getPath())) { - newFilter = FollowFilter.create(ent.getOldName()); + if (isRename(ent) && ent.getNewPath().equals(oldFilter.getPath())) { + newFilter = FollowFilter.create(ent.getOldPath()); break; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java index 59f9c82670..78e7b10a7d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java @@ -296,9 +296,9 @@ public class UnpackedObject { try { if (remaining <= 0) checkValidEndOfStream(in, inf, id, new byte[64]); - super.close(); } finally { InflaterCache.release(inf); + super.close(); } } }; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java index 99126e8615..2d6acbddf0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java @@ -87,6 +87,8 @@ public class NameConflictTreeWalk extends TreeWalk { private boolean fastMinHasMatch; + private AbstractTreeIterator dfConflict; + /** * Create a new tree walker for a given repository. * @@ -141,6 +143,7 @@ public class NameConflictTreeWalk extends TreeWalk { if (minRef.eof()) return minRef; + boolean hasConflict = false; minRef.matches = minRef; while (++i < trees.length) { final AbstractTreeIterator t = trees[i]; @@ -156,6 +159,7 @@ public class NameConflictTreeWalk extends TreeWalk { // tree anyway. // t.matches = minRef; + hasConflict = true; } else { fastMinHasMatch = false; t.matches = t; @@ -182,10 +186,13 @@ public class NameConflictTreeWalk extends TreeWalk { } t.matches = t; minRef = t; + hasConflict = true; } else fastMinHasMatch = false; } + if (hasConflict && fastMinHasMatch && dfConflict == null) + dfConflict = minRef; return minRef; } @@ -281,6 +288,10 @@ public class NameConflictTreeWalk extends TreeWalk { for (final AbstractTreeIterator t : trees) if (t.matches == minRef) t.matches = treeMatch; + + if (dfConflict == null) + dfConflict = treeMatch; + return treeMatch; } @@ -302,6 +313,9 @@ public class NameConflictTreeWalk extends TreeWalk { t.matches = null; } } + + if (ch == dfConflict) + dfConflict = null; } @Override @@ -319,5 +333,26 @@ public class NameConflictTreeWalk extends TreeWalk { t.matches = null; } } + + if (ch == dfConflict) + dfConflict = null; + } + + /** + * True if the current entry is covered by a directory/file conflict. + * + * This means that for some prefix of the current entry's path, this walk + * has detected a directory/file conflict. Also true if the current entry + * itself is a directory/file conflict. + * + * Example: If this TreeWalk points to foo/bar/a.txt and this method returns + * true then you know that either for path foo or for path foo/bar files and + * folders were detected. + * + * @return <code>true</code> if the current entry is covered by a + * directory/file conflict, <code>false</code> otherwise + */ + public boolean isDirectoryFileConflict() { + return dfConflict != null; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java index 2ffcbed9f1..a8e505d6e5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java @@ -61,6 +61,8 @@ import org.eclipse.jgit.lib.PersonIdent; */ public class ChangeIdUtil { + static final String CHANGE_ID = "Change-Id:"; + // package-private so the unit test can test this part only static String clean(String msg) { return msg.// @@ -136,8 +138,39 @@ public class ChangeIdUtil { * @return a commit message with an inserted Change-Id line */ public static String insertId(String message, ObjectId changeId) { - if (message.indexOf("\nChange-Id:") > 0) + return insertId(message, changeId, false); + } + + /** + * Find the right place to insert a Change-Id and return it. + * <p> + * If no Change-Id is found the Change-Id is inserted before + * the first footer line but after a Bug line. + * + * If Change-Id is found and replaceExisting is set to false, + * the message is unchanged. + * + * If Change-Id is found and replaceExisting is set to true, + * the Change-Id is replaced with {@code changeId}. + * + * @param message + * @param changeId + * @param replaceExisting + * @return a commit message with an inserted Change-Id line + */ + public static String insertId(String message, ObjectId changeId, + boolean replaceExisting) { + if (message.indexOf(CHANGE_ID) > 0) { + if (replaceExisting) { + int i = message.indexOf(CHANGE_ID) + 10; + while (message.charAt(i) == ' ') + i++; + String oldId = message.length() == (i + 40) ? + message.substring(i) : message.substring(i, i + 41); + message = message.replace(oldId, "I" + changeId.getName()); + } return message; + } String[] lines = message.split("\n"); int footerFirstLine = lines.length; @@ -173,7 +206,8 @@ public class ChangeIdUtil { } if (insertAfter == lines.length && insertAfter == footerFirstLine) ret.append("\n"); - ret.append("Change-Id: I"); + ret.append(CHANGE_ID); + ret.append(" I"); ret.append(ObjectId.toString(changeId)); ret.append("\n"); for (; i < lines.length; ++i) { |