diff options
Diffstat (limited to 'org.eclipse.jgit.test/tst')
15 files changed, 918 insertions, 9 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java index 80bcb19d5e..63cd21f59d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java @@ -250,6 +250,36 @@ public class ApplyCommandTest extends RepositoryTestCase { assertFalse(new File(db.getWorkTree(), "NonASCIIDel").exists()); } + @Test + public void testRenameNoHunks() throws Exception { + ApplyResult result = init("RenameNoHunks", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "RenameNoHunks"), result.getUpdatedFiles() + .get(0)); + checkFile(new File(db.getWorkTree(), "nested/subdir/Renamed"), + b.getString(0, b.size(), false)); + } + + @Test + public void testRenameWithHunks() throws Exception { + ApplyResult result = init("RenameWithHunks", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "RenameWithHunks"), result.getUpdatedFiles() + .get(0)); + checkFile(new File(db.getWorkTree(), "nested/subdir/Renamed"), + b.getString(0, b.size(), false)); + } + + @Test + public void testCopyWithHunks() throws Exception { + ApplyResult result = init("CopyWithHunks", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "CopyWithHunks"), result.getUpdatedFiles() + .get(0)); + checkFile(new File(db.getWorkTree(), "CopyResult"), + b.getString(0, b.size(), false)); + } + private static byte[] readFile(String patchFile) throws IOException { final InputStream in = getTestResource(patchFile); if (in == null) { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java index 3d0dacab3d..b737bbec0e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java @@ -44,6 +44,7 @@ import org.eclipse.jgit.submodule.SubmoduleStatusType; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.TagOpt; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.SystemReader; import org.junit.Test; @@ -111,6 +112,7 @@ public class CloneCommandTest extends RepositoryTestCase { .size()); assertEquals(new RefSpec("+refs/heads/*:refs/remotes/origin/*"), fetchRefSpec(git2.getRepository())); + assertTagOption(git2.getRepository(), TagOpt.AUTO_FOLLOW); } @Test @@ -801,6 +803,50 @@ public class CloneCommandTest extends RepositoryTestCase { } } + @Test + public void testCloneNoTags() throws IOException, JGitInternalException, + GitAPIException, URISyntaxException { + File directory = createTempDirectory("testCloneRepository"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setNoTags(); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + assertNotNull(git2); + assertNotNull(git2.getRepository().resolve("refs/heads/test")); + assertNull(git2.getRepository().resolve("tag-initial")); + assertNull(git2.getRepository().resolve("tag-for-blob")); + assertTagOption(git2.getRepository(), TagOpt.NO_TAGS); + } + + @Test + public void testCloneFollowTags() throws IOException, JGitInternalException, + GitAPIException, URISyntaxException { + File directory = createTempDirectory("testCloneRepository"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setBranch("refs/heads/master"); + command.setBranchesToClone( + Collections.singletonList("refs/heads/master")); + command.setTagOption(TagOpt.FETCH_TAGS); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + assertNotNull(git2); + assertNull(git2.getRepository().resolve("refs/heads/test")); + assertNotNull(git2.getRepository().resolve("tag-initial")); + assertNotNull(git2.getRepository().resolve("tag-for-blob")); + assertTagOption(git2.getRepository(), TagOpt.FETCH_TAGS); + } + + private void assertTagOption(Repository repo, TagOpt expectedTagOption) + throws URISyntaxException { + RemoteConfig remoteConfig = new RemoteConfig( + repo.getConfig(), "origin"); + assertEquals(expectedTagOption, remoteConfig.getTagOpt()); + } + private String fileUri() { return "file://" + git.getRepository().getWorkTree().getAbsolutePath(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java index 4cc3ca0a53..8084505c10 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import java.io.File; import java.util.Date; @@ -600,41 +601,64 @@ public class CommitCommandTest extends RepositoryTestCase { } } - @Test - public void commitWithAutoCrlfAndNonNormalizedIndex() throws Exception { + private void nonNormalizedIndexTest(boolean executable) throws Exception { + String mode = executable ? "100755" : "100644"; try (Git git = new Git(db)) { // Commit a file with CR/LF into the index FileBasedConfig config = db.getConfig(); config.setString("core", null, "autocrlf", "false"); config.save(); - writeTrashFile("file.txt", "line 1\r\nline 2\r\n"); + File testFile = writeTrashFile("file.txt", "line 1\r\nline 2\r\n"); + if (executable) { + FS.DETECTED.setExecute(testFile, true); + } git.add().addFilepattern("file.txt").call(); git.commit().setMessage("Initial").call(); assertEquals( - "[file.txt, mode:100644, content:line 1\r\nline 2\r\n]", + "[file.txt, mode:" + mode + + ", content:line 1\r\nline 2\r\n]", indexState(CONTENT)); config.setString("core", null, "autocrlf", "true"); config.save(); writeTrashFile("file.txt", "line 1\r\nline 1.5\r\nline 2\r\n"); - writeTrashFile("file2.txt", "new\r\nfile\r\n"); + testFile = writeTrashFile("file2.txt", "new\r\nfile\r\n"); + if (executable) { + FS.DETECTED.setExecute(testFile, true); + } git.add().addFilepattern("file.txt").addFilepattern("file2.txt") .call(); git.commit().setMessage("Second").call(); assertEquals( - "[file.txt, mode:100644, content:line 1\r\nline 1.5\r\nline 2\r\n]" - + "[file2.txt, mode:100644, content:new\nfile\n]", + "[file.txt, mode:" + mode + + ", content:line 1\r\nline 1.5\r\nline 2\r\n]" + + "[file2.txt, mode:" + mode + + ", content:new\nfile\n]", indexState(CONTENT)); writeTrashFile("file2.txt", "new\r\nfile\r\ncontent\r\n"); git.add().addFilepattern("file2.txt").call(); git.commit().setMessage("Third").call(); assertEquals( - "[file.txt, mode:100644, content:line 1\r\nline 1.5\r\nline 2\r\n]" - + "[file2.txt, mode:100644, content:new\nfile\ncontent\n]", + "[file.txt, mode:" + mode + + ", content:line 1\r\nline 1.5\r\nline 2\r\n]" + + "[file2.txt, mode:" + mode + + ", content:new\nfile\ncontent\n]", indexState(CONTENT)); } } @Test + public void commitWithAutoCrlfAndNonNormalizedIndex() throws Exception { + nonNormalizedIndexTest(false); + } + + @Test + public void commitExecutableWithAutoCrlfAndNonNormalizedIndex() + throws Exception { + assumeTrue(FS.DETECTED.supportsExecute()); + nonNormalizedIndexTest(true); + } + + @Test public void testDeletionConflictWithAutoCrlf() throws Exception { try (Git git = new Git(db)) { // Commit a file with CR/LF into the index diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java index e0d9cbc197..7e0de82d82 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java @@ -12,6 +12,7 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import java.io.File; import java.io.IOException; @@ -21,6 +22,8 @@ import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Sets; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; import org.junit.Test; public class StatusCommandTest extends RepositoryTestCase { @@ -135,4 +138,26 @@ public class StatusCommandTest extends RepositoryTestCase { assertEquals(Sets.of("a", "D/b", "D/D/d"), stat.getModified()); } } + + @Test + public void testExecutableWithNonNormalizedIndex() throws Exception { + assumeTrue(FS.DETECTED.supportsExecute()); + try (Git git = new Git(db)) { + // Commit a file with CR/LF into the index + FileBasedConfig config = db.getConfig(); + config.setString("core", null, "autocrlf", "false"); + config.save(); + File testFile = writeTrashFile("file.txt", "line 1\r\nline 2\r\n"); + FS.DETECTED.setExecute(testFile, true); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("Initial").call(); + assertEquals( + "[file.txt, mode:100755, content:line 1\r\nline 2\r\n]", + indexState(CONTENT)); + config.setString("core", null, "autocrlf", "true"); + config.save(); + Status status = git.status().call(); + assertTrue("Expected no differences", status.isClean()); + } + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java index 2a2aeb592f..a246ac9e9c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java @@ -287,6 +287,123 @@ public class MergedReftableTest { } @Test + public void nonOverlappedUpdateIndices() throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter(buf) + .setMinUpdateIndex(1) + .setMaxUpdateIndex(2) + .begin(); + writer.writeRef(ref("refs/heads/a", 1), 1); + writer.writeRef(ref("refs/heads/b", 2), 2); + writer.finish(); + byte[] base = buf.toByteArray(); + + buf = new ByteArrayOutputStream(); + writer = new ReftableWriter(buf) + .setMinUpdateIndex(3) + .setMaxUpdateIndex(4) + .begin(); + writer.writeRef(ref("refs/heads/a", 10), 3); + writer.writeRef(ref("refs/heads/b", 20), 4); + writer.finish(); + byte[] delta = buf.toByteArray(); + + MergedReftable mr = merge(base, delta); + assertEquals(1, mr.minUpdateIndex()); + assertEquals(4, mr.maxUpdateIndex()); + + try (RefCursor rc = mr.allRefs()) { + assertTrue(rc.next()); + assertEquals("refs/heads/a", rc.getRef().getName()); + assertEquals(id(10), rc.getRef().getObjectId()); + assertEquals(3, rc.getRef().getUpdateIndex()); + + assertTrue(rc.next()); + assertEquals("refs/heads/b", rc.getRef().getName()); + assertEquals(id(20), rc.getRef().getObjectId()); + assertEquals(4, rc.getRef().getUpdateIndex()); + } + } + + @Test + public void overlappedUpdateIndices() throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter(buf) + .setMinUpdateIndex(1) + .setMaxUpdateIndex(3) + .begin(); + writer.writeRef(ref("refs/heads/a", 1), 1); + writer.writeRef(ref("refs/heads/b", 2), 3); + writer.finish(); + byte[] base = buf.toByteArray(); + + buf = new ByteArrayOutputStream(); + writer = new ReftableWriter(buf) + .setMinUpdateIndex(2) + .setMaxUpdateIndex(4) + .begin(); + writer.writeRef(ref("refs/heads/a", 10), 2); + writer.writeRef(ref("refs/heads/b", 20), 4); + writer.finish(); + byte[] delta = buf.toByteArray(); + + MergedReftable mr = merge(base, delta); + assertEquals(1, mr.minUpdateIndex()); + assertEquals(4, mr.maxUpdateIndex()); + + try (RefCursor rc = mr.allRefs()) { + assertTrue(rc.next()); + assertEquals("refs/heads/a", rc.getRef().getName()); + assertEquals(id(10), rc.getRef().getObjectId()); + assertEquals(2, rc.getRef().getUpdateIndex()); + + assertTrue(rc.next()); + assertEquals("refs/heads/b", rc.getRef().getName()); + assertEquals(id(20), rc.getRef().getObjectId()); + assertEquals(4, rc.getRef().getUpdateIndex()); + } + } + + @Test + public void enclosedUpdateIndices() throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter(buf) + .setMinUpdateIndex(1) + .setMaxUpdateIndex(4) + .begin(); + writer.writeRef(ref("refs/heads/a", 1), 1); + writer.writeRef(ref("refs/heads/b", 20), 4); + writer.finish(); + byte[] base = buf.toByteArray(); + + buf = new ByteArrayOutputStream(); + writer = new ReftableWriter(buf) + .setMinUpdateIndex(2) + .setMaxUpdateIndex(3) + .begin(); + writer.writeRef(ref("refs/heads/a", 10), 2); + writer.writeRef(ref("refs/heads/b", 2), 3); + writer.finish(); + byte[] delta = buf.toByteArray(); + + MergedReftable mr = merge(base, delta); + assertEquals(1, mr.minUpdateIndex()); + assertEquals(4, mr.maxUpdateIndex()); + + try (RefCursor rc = mr.allRefs()) { + assertTrue(rc.next()); + assertEquals("refs/heads/a", rc.getRef().getName()); + assertEquals(id(10), rc.getRef().getObjectId()); + assertEquals(2, rc.getRef().getUpdateIndex()); + + assertTrue(rc.next()); + assertEquals("refs/heads/b", rc.getRef().getName()); + assertEquals(id(20), rc.getRef().getObjectId()); + assertEquals(4, rc.getRef().getUpdateIndex()); + } + } + + @Test public void compaction() throws IOException { List<Ref> delta1 = Arrays.asList( ref("refs/heads/next", 4), diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java index 032349d5f8..7a244e1d8b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java @@ -1254,6 +1254,94 @@ public class MergerTest extends RepositoryTestCase { } } + /** + * Merging two commits with a conflict in the virtual ancestor. + * + * Content conflicts while merging the virtual ancestor must be ignored. + * + * In the following tree, while merging A and B, the recursive algorithm + * finds as base commits X and Y and tries to merge them: X deletes file "a" + * and Y modifies it. + * + * Note: we delete "a" in (master) and (second-branch) to make avoid manual + * merges. The situation is the same without those deletions and fixing + * manually the merge of (merge-both-sides) on both branches. + * + * <pre> + * A (second-branch) Merge branch 'merge-both-sides' into second-branch + * |\ + * o | Delete modified a + * | | + * | | B (master) Merge branch 'merge-both-sides' (into master) + * | |/| + * | X | (merge-both-sides) Delete original a + * | | | + * | | o Delete modified a + * | |/ + * |/| + * Y | Modify a + * |/ + * o Initial commit + * </pre> + * + * @param strategy + * @throws Exception + */ + @Theory + public void checkMergeConflictInVirtualAncestor( + MergeStrategy strategy) throws Exception { + if (!strategy.equals(MergeStrategy.RECURSIVE)) { + return; + } + + Git git = Git.wrap(db); + + // master + writeTrashFile("a", "aaaaaaaa"); + writeTrashFile("b", "bbbbbbbb"); + git.add().addFilepattern("a").addFilepattern("b").call(); + RevCommit first = git.commit().setMessage("Initial commit").call(); + + writeTrashFile("a", "aaaaaaaaaaaaaaa"); + git.add().addFilepattern("a").call(); + RevCommit commitY = git.commit().setMessage("Modify a").call(); + + git.rm().addFilepattern("a").call(); + // Do more in this commits, so it is not identical to the deletion in + // second-branch + writeTrashFile("c", "cccccccc"); + git.add().addFilepattern("c").call(); + git.commit().setMessage("Delete modified a").call(); + + // merge-both-sides: starts before "a" is modified and deletes it + git.checkout().setCreateBranch(true).setStartPoint(first) + .setName("merge-both-sides").call(); + git.rm().addFilepattern("a").call(); + RevCommit commitX = git.commit().setMessage("Delete original a").call(); + + // second branch + git.checkout().setCreateBranch(true).setStartPoint(commitY) + .setName("second-branch").call(); + git.rm().addFilepattern("a").call(); + git.commit().setMessage("Delete modified a").call(); + + // Merge merge-both-sides into second-branch + MergeResult mergeResult = git.merge().include(commitX) + .setStrategy(strategy) + .call(); + ObjectId commitB = mergeResult.getNewHead(); + + // Merge merge-both-sides into master + git.checkout().setName("master").call(); + mergeResult = git.merge().include(commitX).setStrategy(strategy) + .call(); + + // Now, merge commit A and B (i.e. "master" and "second-branch"). + // None of them have the file "a", so there is no conflict, BUT while + // building the virtual ancestor it will find a conflict between Y and X + git.merge().include(commitB).call(); + } + private void writeSubmodule(String path, ObjectId commit) throws IOException, ConfigInvalidException { addSubmoduleToIndex(path, commit); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmappedObjectReachabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmappedObjectReachabilityTest.java new file mode 100644 index 0000000000..d2b6e89168 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmappedObjectReachabilityTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020, Google LLC and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.revwalk; + +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.internal.storage.file.GC; +import org.eclipse.jgit.junit.TestRepository; + +public class BitmappedObjectReachabilityTest + extends ObjectReachabilityTestCase { + + @Override + ObjectReachabilityChecker getChecker( + TestRepository<FileRepository> repository) throws Exception { + // GC generates the bitmaps + GC gc = new GC(repository.getRepository()); + gc.setAuto(false); + gc.gc(); + + return new BitmappedObjectReachabilityChecker( + repository.getRevWalk().toObjectWalkWithSameObjects()); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FirstParentRevWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FirstParentRevWalkTest.java index 4a3b04d4e2..c8256b89c0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FirstParentRevWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FirstParentRevWalkTest.java @@ -164,6 +164,23 @@ public class FirstParentRevWalkTest extends RevWalkTestCase { } @Test + public void testTopoNonIntermixSort() throws Exception { + RevCommit a = commit(); + RevCommit b1 = commit(a); + RevCommit b2 = commit(a); + RevCommit c = commit(b1, b2); + + rw.reset(); + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + rw.setFirstParent(true); + markStart(c); + assertCommit(c, rw.next()); + assertCommit(b1, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + @Test public void testCommitTimeSort() throws Exception { RevCommit a = commit(); RevCommit b1 = commit(a); @@ -428,4 +445,39 @@ public class FirstParentRevWalkTest extends RevWalkTestCase { assertCommit(c, rw.next()); assertNull(rw.next()); } + + @Test + public void testWithTopoNonIntermixSortAndTreeFilter() throws Exception { + RevCommit a = commit(); + RevCommit b = commit(tree(file("0", blob("b"))), a); + RevCommit c = commit(tree(file("0", blob("c"))), b, a); + RevCommit d = commit(tree(file("0", blob("d"))), c); + + rw.reset(); + rw.setFirstParent(true); + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true); + rw.setTreeFilter(PathFilterGroup.createFromStrings("0")); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testWithTopoNonIntermixSortAndTreeFilter2() throws Exception { + RevCommit a = commit(); + RevCommit b = commit(tree(file("0", blob("b"))), a); + RevCommit c = commit(tree(file("0", blob("c"))), a, b); + RevCommit d = commit(tree(file("0", blob("d"))), c); + + rw.reset(); + rw.setFirstParent(true); + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true); + rw.setTreeFilter(PathFilterGroup.createFromStrings("0")); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c, rw.next()); + assertNull(rw.next()); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectReachabilityTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectReachabilityTestCase.java new file mode 100644 index 0000000000..267b163f43 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectReachabilityTestCase.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020, Google LLC and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.revwalk; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Stream; + +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.junit.TestRepository.CommitBuilder; +import org.junit.Before; +import org.junit.Test; + +public abstract class ObjectReachabilityTestCase + extends LocalDiskRepositoryTestCase { + + private TestRepository<FileRepository> repo; + private AddressableRevCommit baseCommit; + private AddressableRevCommit branchACommit; + private AddressableRevCommit branchBCommit; + private AddressableRevCommit mergeCommit; + + abstract ObjectReachabilityChecker getChecker( + TestRepository<FileRepository> repository) throws Exception; + + // Pair of commit and blob inside it + protected static class AddressableRevCommit { + RevCommit commit; + + RevBlob blob; + + AddressableRevCommit(RevCommit commit, RevBlob blob) { + this.commit = commit; + this.blob = blob; + } + } + + /** {@inheritDoc} */ + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + FileRepository db = createWorkRepository(); + repo = new TestRepository<>(db); + prepareRepo(); + } + + @Test + public void blob_in_base_reachable_from_branches() throws Exception { + ObjectReachabilityChecker checker = getChecker(repo); + + RevObject baseBlob = baseCommit.blob; + assertReachable("reachable from one branch", checker.areAllReachable( + Arrays.asList(baseBlob), Stream.of(branchACommit.commit))); + assertReachable("reachable from another branch", + checker.areAllReachable( + Arrays.asList(baseBlob), + Stream.of(branchBCommit.commit))); + } + + @Test + public void blob_reachable_from_owning_commit() throws Exception { + ObjectReachabilityChecker checker = getChecker(repo); + + RevObject branchABlob = branchACommit.blob; + assertReachable("reachable from itself", + checker.areAllReachable(Arrays.asList(branchABlob), + Stream.of(branchACommit.commit))); + } + + @Test + public void blob_in_branch_reachable_from_merge() throws Exception { + ObjectReachabilityChecker checker = getChecker(repo); + + RevObject branchABlob = branchACommit.blob; + assertReachable("reachable from merge", checker.areAllReachable( + Arrays.asList(branchABlob), Stream.of(mergeCommit.commit))); + } + + @Test + public void blob_unreachable_from_earlier_commit() throws Exception { + ObjectReachabilityChecker checker = getChecker(repo); + + RevObject branchABlob = branchACommit.blob; + assertUnreachable("unreachable from earlier commit", + checker.areAllReachable(Arrays.asList(branchABlob), + Stream.of(baseCommit.commit))); + } + + @Test + public void blob_unreachable_from_parallel_branch() throws Exception { + ObjectReachabilityChecker checker = getChecker(repo); + + RevObject branchABlob = branchACommit.blob; + assertUnreachable("unreachable from another branch", + checker.areAllReachable(Arrays.asList(branchABlob), + Stream.of(branchBCommit.commit))); + } + + private void prepareRepo() throws Exception { + baseCommit = createCommit("base"); + branchACommit = createCommit("branchA", baseCommit); + branchBCommit = createCommit("branchB", baseCommit); + mergeCommit = createCommit("merge", branchACommit, branchBCommit); + + // Bitmaps are generated from references + repo.update("refs/heads/a", branchACommit.commit); + repo.update("refs/heads/b", branchBCommit.commit); + repo.update("refs/heads/merge", mergeCommit.commit); + } + + private AddressableRevCommit createCommit(String blobPath, AddressableRevCommit... parents) throws Exception { + RevBlob blob = repo.blob(blobPath + " content"); + CommitBuilder commitBuilder = repo.commit(); + for (int i = 0; i < parents.length; i++) { + commitBuilder.parent(parents[i].commit); + } + commitBuilder.add(blobPath, blob); + + RevCommit commit = commitBuilder.create(); + return new AddressableRevCommit(commit, blob); + } + + private static void assertReachable(String msg, Optional<RevObject> result) { + assertFalse(msg, result.isPresent()); + } + + private static void assertUnreachable(String msg, Optional<RevObject> result) { + assertTrue(msg, result.isPresent()); + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/PedestrianObjectReachabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/PedestrianObjectReachabilityTest.java new file mode 100644 index 0000000000..b1c9556df8 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/PedestrianObjectReachabilityTest.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020, Google LLC and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.revwalk; + +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.junit.TestRepository; + +public class PedestrianObjectReachabilityTest + extends ObjectReachabilityTestCase { + + @Override + ObjectReachabilityChecker getChecker( + TestRepository<FileRepository> repository) + throws Exception { + return new PedestrianObjectReachabilityChecker( + repository.getRevWalk().toObjectWalkWithSameObjects()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkSortTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkSortTest.java index 6f110fa317..8af6747739 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkSortTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkSortTest.java @@ -10,9 +10,12 @@ package org.eclipse.jgit.revwalk; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.eclipse.jgit.internal.JGitText; import org.junit.Test; public class RevWalkSortTest extends RevWalkTestCase { @@ -144,4 +147,171 @@ public class RevWalkSortTest extends RevWalkTestCase { assertCommit(d, rw.next()); assertNull(rw.next()); } + + @Test + public void testSort_TOPO_NON_INTERMIX() throws Exception { + // c1 is back dated before its parent. + // + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(-5, b); + final RevCommit c2 = commit(10, b); + final RevCommit d = commit(c1, c2); + + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c2, rw.next()); + assertCommit(c1, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testSort_TOPO_NON_INTERMIX_OutOfOrderCommitTimes() + throws Exception { + // b is committed before c2 in a different line of history. + // + final RevCommit a = commit(); + final RevCommit c1 = commit(a); + final RevCommit b = commit(a); + final RevCommit c2 = commit(c1); + final RevCommit d = commit(b, c2); + + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c2, rw.next()); + assertCommit(c1, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testSort_TOPO_NON_INTERMIX_MultipleLinesOfHistory() + throws Exception { + final RevCommit a1 = commit(); + final RevCommit b1 = commit(a1); + final RevCommit a2 = commit(a1, b1); + final RevCommit b2 = commit(b1); + final RevCommit b3 = commit(b1); + final RevCommit a3 = commit(a2, b2); + final RevCommit a4 = commit(a3, b3); + + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + markStart(a4); + assertCommit(a4, rw.next()); + assertCommit(b3, rw.next()); + assertCommit(a3, rw.next()); + assertCommit(b2, rw.next()); + assertCommit(a2, rw.next()); + assertCommit(b1, rw.next()); + assertCommit(a1, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testSort_TOPO_NON_INTERMIX_REVERSE() throws Exception { + // c1 is back dated before its parent. + // + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(-5, b); + final RevCommit c2 = commit(10, b); + final RevCommit d = commit(c1, c2); + + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + rw.sort(RevSort.REVERSE, true); + markStart(d); + assertCommit(a, rw.next()); + assertCommit(b, rw.next()); + assertCommit(c1, rw.next()); + assertCommit(c2, rw.next()); + assertCommit(d, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testSort_TOPO_NON_INTERMIX_REVERSE_MultipleLinesOfHistory() + throws Exception { + final RevCommit a1 = commit(); + final RevCommit b1 = commit(a1); + final RevCommit a2 = commit(a1, b1); + final RevCommit b2 = commit(b1); + final RevCommit b3 = commit(b1); + final RevCommit a3 = commit(a2, b2); + final RevCommit a4 = commit(a3, b3); + + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + rw.sort(RevSort.REVERSE, true); + markStart(a4); + assertCommit(a1, rw.next()); + assertCommit(b1, rw.next()); + assertCommit(a2, rw.next()); + assertCommit(b2, rw.next()); + assertCommit(a3, rw.next()); + assertCommit(b3, rw.next()); + assertCommit(a4, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testSort_TOPO_NON_INTERMIX_ParentOfMultipleStartChildren() + throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(a); + final RevCommit d1 = commit(a); + final RevCommit d2 = commit(d1); + final RevCommit e = commit(a); + + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + markStart(b); + markStart(c); + markStart(d2); + markStart(e); + assertCommit(e, rw.next()); + assertCommit(d2, rw.next()); + assertCommit(d1, rw.next()); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testSort_TOPO_NON_INTERMIX_Uninteresting() throws Exception { + final RevCommit a1 = commit(); + final RevCommit a2 = commit(a1); + final RevCommit a3 = commit(a2); + final RevCommit b = commit(a1); + final RevCommit a4 = commit(a3, b); + + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + markStart(a4); + markUninteresting(a2); + assertCommit(a4, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a3, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testSort_TOPO_NON_INTERMIX_and_TOPO_throws() throws Exception { + final RevCommit a = commit(); + + rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER); + rw.sort(RevSort.TOPO, true); + markStart(a); + try { + rw.next(); + fail("did not throw IllegalStateException"); + } catch (IllegalStateException e) { + assertEquals( + JGitText.get().cannotCombineTopoSortWithTopoKeepBranchTogetherSort, + e.getMessage()); + } + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java index 6bf8b4c7d8..d403624b71 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java @@ -953,6 +953,17 @@ public class URIishTest { assertEquals(-1, u.getPort()); assertNull(u.getUser()); assertEquals("b.txt", u.getHumanishName()); + + u = new URIish("file:/a/test.bundle"); + assertEquals("file", u.getScheme()); + assertFalse(u.isRemote()); + assertNull(u.getHost()); + assertNull(u.getPass()); + assertEquals("/a/test.bundle", u.getRawPath()); + assertEquals("/a/test.bundle", u.getPath()); + assertEquals(-1, u.getPort()); + assertNull(u.getUser()); + assertEquals("test", u.getHumanishName()); } @Test diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java index ea86563da8..d58e576984 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java @@ -44,6 +44,7 @@ import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Sets; import org.eclipse.jgit.lib.TextProgressMonitor; @@ -2238,4 +2239,81 @@ public class UploadPackTest { } } + @Test + public void testSafeToClearRefsInFetchV0() throws Exception { + server = + new RefCallsCountingRepository( + new DfsRepositoryDescription("server")); + remote = new TestRepository<>(server); + RevCommit one = remote.commit().message("1").create(); + remote.update("one", one); + testProtocol = new TestProtocol<>((Object req, Repository db) -> { + UploadPack up = new UploadPack(db); + return up; + }, null); + uri = testProtocol.register(ctx, server); + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(one.name()))); + } + assertTrue(client.getObjectDatabase().has(one.toObjectId())); + assertEquals(1, ((RefCallsCountingRepository)server).numRefCalls()); + } + + @Test + public void testSafeToClearRefsInFetchV2() throws Exception { + server = + new RefCallsCountingRepository( + new DfsRepositoryDescription("server")); + remote = new TestRepository<>(server); + RevCommit one = remote.commit().message("1").create(); + RevCommit two = remote.commit().message("2").create(); + remote.update("one", one); + remote.update("two", two); + server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true); + ByteArrayInputStream recvStream = uploadPackV2( + "command=fetch\n", + PacketLineIn.delimiter(), + "want-ref refs/heads/one\n", + "want-ref refs/heads/two\n", + "done\n", + PacketLineIn.end()); + PacketLineIn pckIn = new PacketLineIn(recvStream); + assertThat(pckIn.readString(), is("wanted-refs")); + assertThat( + Arrays.asList(pckIn.readString(), pckIn.readString()), + hasItems( + one.toObjectId().getName() + " refs/heads/one", + two.toObjectId().getName() + " refs/heads/two")); + assertTrue(PacketLineIn.isDelimiter(pckIn.readString())); + assertThat(pckIn.readString(), is("packfile")); + parsePack(recvStream); + assertTrue(client.getObjectDatabase().has(one.toObjectId())); + assertEquals(1, ((RefCallsCountingRepository)server).numRefCalls()); + } + + private class RefCallsCountingRepository extends InMemoryRepository { + private final InMemoryRepository.MemRefDatabase refdb; + private int numRefCalls; + + public RefCallsCountingRepository(DfsRepositoryDescription repoDesc) { + super(repoDesc); + refdb = new InMemoryRepository.MemRefDatabase() { + @Override + public List<Ref> getRefs() throws IOException { + numRefCalls++; + return super.getRefs(); + } + }; + } + + public int numRefCalls() { + return numRefCalls; + } + + @Override + public RefDatabase getRefDatabase() { + return refdb; + } + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java index f9ec5d8d6f..2b1fb2ef04 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java @@ -79,6 +79,15 @@ public class FileUtilsTest { } @Test + public void testDeleteReadOnlyFile() throws IOException { + File f = new File(trash, "f"); + FileUtils.createNewFile(f); + assertTrue(f.setReadOnly()); + FileUtils.delete(f); + assertFalse(f.exists()); + } + + @Test public void testDeleteRecursive() throws IOException { File f1 = new File(trash, "test/test/a"); FileUtils.mkdirs(f1.getParentFile()); @@ -339,6 +348,34 @@ public class FileUtilsTest { } @Test + public void testDeleteNonRecursiveTreeNotOk() throws IOException { + File t = new File(trash, "t"); + FileUtils.mkdir(t); + File f = new File(t, "f"); + FileUtils.createNewFile(f); + try { + FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY); + fail("expected failure to delete f"); + } catch (IOException e) { + assertTrue(e.getMessage().endsWith(t.getAbsolutePath())); + } + assertTrue(f.exists()); + assertTrue(t.exists()); + } + + @Test + public void testDeleteNonRecursiveTreeIgnoreError() throws IOException { + File t = new File(trash, "t"); + FileUtils.mkdir(t); + File f = new File(t, "f"); + FileUtils.createNewFile(f); + FileUtils.delete(t, + FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.IGNORE_ERRORS); + assertTrue(f.exists()); + assertTrue(t.exists()); + } + + @Test public void testRenameOverNonExistingFile() throws IOException { File d = new File(trash, "d"); FileUtils.mkdirs(d); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java index 254878ae0b..33ed360efd 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java @@ -259,6 +259,38 @@ public class HookTest extends RepositoryTestCase { } @Test + public void testHookPathWithBlank() throws Exception { + assumeSupportedPlatform(); + + File file = writeHookFile("../../a directory/" + PreCommitHook.NAME, + "#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n" + + "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\""); + StoredConfig cfg = db.getConfig(); + cfg.load(); + cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_HOOKS_PATH, + file.getParentFile().getAbsolutePath()); + cfg.save(); + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream()) { + ProcessResult res = FS.DETECTED.runHookIfPresent(db, + PreCommitHook.NAME, new String[] { "arg1", "arg2" }, + new PrintStream(out), new PrintStream(err), "stdin"); + + assertEquals("unexpected hook output", + "test arg1 arg2\nstdin\n" + + db.getDirectory().getAbsolutePath() + '\n' + + db.getWorkTree().getAbsolutePath() + '\n', + out.toString("UTF-8")); + assertEquals("unexpected output on stderr stream", "stderr\n", + err.toString("UTF-8")); + assertEquals("unexpected exit code", 0, res.getExitCode()); + assertEquals("unexpected process status", ProcessResult.Status.OK, + res.getStatus()); + } + } + + @Test public void testFailedPreCommitHookBlockCommit() throws Exception { assumeSupportedPlatform(); |