Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit.test/tst')
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java15
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java29
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java60
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java180
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java87
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java21
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java279
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java125
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java19
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java389
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java585
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java1
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java271
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java7
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java73
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java6
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java144
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java269
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java211
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java1054
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java28
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java106
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java585
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java170
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java125
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java26
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java392
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java726
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java16
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java24
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java1150
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java71
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java82
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java93
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java53
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java40
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java96
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java210
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java222
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java283
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java222
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java88
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java41
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java33
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java90
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java16
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java11
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java (renamed from org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java)2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java65
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java49
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java9
63 files changed, 8108 insertions, 878 deletions
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 ed3907e9b2..aafda0171c 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
@@ -303,6 +303,21 @@ public class AddCommandTest extends RepositoryTestCase {
}
@Test
+ public void testAttributesConflictingMatch() throws Exception {
+ writeTrashFile(".gitattributes", "foo/** crlf=input\n*.jar binary");
+ writeTrashFile("foo/bar.jar", "\r\n");
+ // We end up with attributes [binary -diff -merge -text crlf=input].
+ // crlf should have no effect when -text is present.
+ try (Git git = new Git(db)) {
+ git.add().addFilepattern(".").call();
+ assertEquals(
+ "[.gitattributes, mode:100644, content:foo/** crlf=input\n*.jar binary]"
+ + "[foo/bar.jar, mode:100644, content:\r\n]",
+ indexState(CONTENT));
+ }
+ }
+
+ @Test
public void testCleanFilterEnvironment()
throws IOException, GitAPIException {
writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index 3c196724a9..1201d9f391 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -85,7 +85,6 @@ import org.eclipse.jgit.lib.Sets;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FileUtils;
@@ -431,8 +430,8 @@ public class CheckoutCommandTest extends RepositoryTestCase {
config.save();
// fetch from first repository
- RefSpec spec = new RefSpec("+refs/heads/*:refs/remotes/origin/*");
- git2.fetch().setRemote("origin").setRefSpecs(spec).call();
+ git2.fetch().setRemote("origin")
+ .setRefSpecs("+refs/heads/*:refs/remotes/origin/*").call();
return db2;
}
}
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 ae0b8dd3c2..e687a6ca7f 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
@@ -76,6 +76,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.URIish;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Test;
@@ -145,16 +146,36 @@ public class CloneCommandTest extends RepositoryTestCase {
File directory = createTempDirectory("testCloneRepository");
CloneCommand command = Git.cloneRepository();
command.setDirectory(directory);
- command.setGitDir(new File(directory, ".git"));
+ command.setGitDir(new File(directory, Constants.DOT_GIT));
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertEquals(directory, git2.getRepository().getWorkTree());
- assertEquals(new File(directory, ".git"), git2.getRepository()
+ assertEquals(new File(directory, Constants.DOT_GIT), git2.getRepository()
.getDirectory());
}
@Test
+ public void testCloneRepositoryDefaultDirectory()
+ throws URISyntaxException, JGitInternalException {
+ CloneCommand command = Git.cloneRepository().setURI(fileUri());
+
+ command.verifyDirectories(new URIish(fileUri()));
+ File directory = command.getDirectory();
+ assertEquals(git.getRepository().getWorkTree().getName(), directory.getName());
+ }
+
+ @Test
+ public void testCloneBareRepositoryDefaultDirectory()
+ throws URISyntaxException, JGitInternalException {
+ CloneCommand command = Git.cloneRepository().setURI(fileUri()).setBare(true);
+
+ command.verifyDirectories(new URIish(fileUri()));
+ File directory = command.getDirectory();
+ assertEquals(git.getRepository().getWorkTree().getName() + Constants.DOT_GIT_EXT, directory.getName());
+ }
+
+ @Test
public void testCloneRepositoryExplicitGitDirNonStd() throws IOException,
JGitInternalException, GitAPIException {
File directory = createTempDirectory("testCloneRepository");
@@ -168,8 +189,8 @@ public class CloneCommandTest extends RepositoryTestCase {
assertEquals(directory, git2.getRepository().getWorkTree());
assertEquals(gDir, git2.getRepository()
.getDirectory());
- assertTrue(new File(directory, ".git").isFile());
- assertFalse(new File(gDir, ".git").exists());
+ assertTrue(new File(directory, Constants.DOT_GIT).isFile());
+ assertFalse(new File(gDir, Constants.DOT_GIT).exists());
}
@Test
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 37fee402e9..a0834e7e85 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
@@ -557,6 +557,11 @@ public class CommitCommandTest extends RepositoryTestCase {
} catch (EmtpyCommitException e) {
// expect this exception
}
+
+ // Allow empty commits also when setOnly was set
+ git.commit().setAuthor("New Author", "newauthor@example.org")
+ .setMessage("again no change").setOnly("file1")
+ .setAllowEmpty(true).call();
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
index 1e5d3bc30e..6a667830e8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
@@ -54,6 +54,7 @@ import java.util.Collection;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -92,26 +93,65 @@ public class DescribeCommandTest extends RepositoryTestCase {
ObjectId c1 = modify("aaa");
ObjectId c2 = modify("bbb");
- tag("t1");
+ tag("alice-t1");
ObjectId c3 = modify("ccc");
- tag("t2");
+ tag("bob-t2");
ObjectId c4 = modify("ddd");
assertNull(describe(c1));
assertNull(describe(c1, true));
- assertEquals("t1", describe(c2));
- assertEquals("t2", describe(c3));
- assertEquals("t2-0-g44579eb", describe(c3, true));
+ assertNull(describe(c1, "a*", "b*", "c*"));
+
+ assertEquals("alice-t1", describe(c2));
+ assertEquals("alice-t1", describe(c2, "alice*"));
+ assertNull(describe(c2, "bob*"));
+ assertNull(describe(c2, "?ob*"));
+ assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
+
+ assertEquals("bob-t2", describe(c3));
+ assertEquals("bob-t2-0-g44579eb", describe(c3, true));
+ assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
+ assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
+ assertEquals("bob-t2", describe(c3, "bob*"));
+ assertEquals("bob-t2", describe(c3, "?ob*"));
+ assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
assertNameStartsWith(c4, "3e563c5");
// the value verified with git-describe(1)
- assertEquals("t2-1-g3e563c5", describe(c4));
- assertEquals("t2-1-g3e563c5", describe(c4, true));
+ assertEquals("bob-t2-1-g3e563c5", describe(c4));
+ assertEquals("bob-t2-1-g3e563c5", describe(c4, true));
+ assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
+ assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
+ assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
// test default target
- assertEquals("t2-1-g3e563c5", git.describe().call());
+ assertEquals("bob-t2-1-g3e563c5", git.describe().call());
+ }
+
+ @Test
+ public void testDescribeMultiMatch() throws Exception {
+ ObjectId c1 = modify("aaa");
+ tag("v1.0.0");
+ tag("v1.1.1");
+ ObjectId c2 = modify("bbb");
+
+ // Ensure that if we're interested in any tags, we get the first match as per Git behaviour
+ assertEquals("v1.0.0", describe(c1));
+ assertEquals("v1.0.0-1-g3747db3", describe(c2));
+
+ // Ensure that if we're only interested in one of multiple tags, we get the right match
+ assertEquals("v1.0.0", describe(c1, "v1.0*"));
+ assertEquals("v1.1.1", describe(c1, "v1.1*"));
+ assertEquals("v1.0.0-1-g3747db3", describe(c2, "v1.0*"));
+ assertEquals("v1.1.1-1-g3747db3", describe(c2, "v1.1*"));
+
+ // Ensure that ordering of match precedence is preserved as per Git behaviour
+ assertEquals("v1.0.0", describe(c1, "v1.0*", "v1.1*"));
+ assertEquals("v1.1.1", describe(c1, "v1.1*", "v1.0*"));
+ assertEquals("v1.0.0-1-g3747db3", describe(c2, "v1.0*", "v1.1*"));
+ assertEquals("v1.1.1-1-g3747db3", describe(c2, "v1.1*", "v1.0*"));
}
/**
@@ -271,6 +311,10 @@ public class DescribeCommandTest extends RepositoryTestCase {
return describe(c1, false);
}
+ private String describe(ObjectId c1, String... patterns) throws GitAPIException, IOException, InvalidPatternException {
+ return git.describe().setTarget(c1).setMatch(patterns).call();
+ }
+
private static void assertNameStartsWith(ObjectId c4, String prefix) {
assertTrue(c4.name(), c4.name().startsWith(prefix));
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
index a36f6c551a..530fb1b2fe 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
@@ -45,7 +45,9 @@ package org.eclipse.jgit.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
@@ -93,9 +95,8 @@ public class FetchCommandTest extends RepositoryTestCase {
RevCommit commit = remoteGit.commit().setMessage("initial commit").call();
Ref tagRef = remoteGit.tag().setName("tag").call();
- RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
- git.fetch().setRemote("test").setRefSpecs(spec)
- .call();
+ git.fetch().setRemote("test")
+ .setRefSpecs("refs/heads/master:refs/heads/x").call();
assertEquals(commit.getId(),
db.resolve(commit.getId().getName() + "^{commit}"));
@@ -104,12 +105,97 @@ public class FetchCommandTest extends RepositoryTestCase {
}
@Test
+ public void fetchAddsBranches() throws Exception {
+ final String branch1 = "b1";
+ final String branch2 = "b2";
+ final String remoteBranch1 = "test/" + branch1;
+ final String remoteBranch2 = "test/" + branch2;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call();
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call();
+
+ String spec = "refs/heads/*:refs/remotes/test/*";
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+ }
+
+ @Test
+ public void fetchDoesntDeleteBranches() throws Exception {
+ final String branch1 = "b1";
+ final String branch2 = "b2";
+ final String remoteBranch1 = "test/" + branch1;
+ final String remoteBranch2 = "test/" + branch2;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call();
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call();
+
+ String spec = "refs/heads/*:refs/remotes/test/*";
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+
+ remoteGit.branchDelete().setBranchNames(branch1).call();
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+ }
+
+ @Test
+ public void fetchUpdatesBranches() throws Exception {
+ final String branch1 = "b1";
+ final String branch2 = "b2";
+ final String remoteBranch1 = "test/" + branch1;
+ final String remoteBranch2 = "test/" + branch2;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call();
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call();
+
+ String spec = "refs/heads/*:refs/remotes/test/*";
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+
+ remoteGit.commit().setMessage("commit").call();
+ branchRef2 = remoteGit.branchCreate().setName(branch2).setForce(true).call();
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+ }
+
+ @Test
+ public void fetchPrunesBranches() throws Exception {
+ final String branch1 = "b1";
+ final String branch2 = "b2";
+ final String remoteBranch1 = "test/" + branch1;
+ final String remoteBranch2 = "test/" + branch2;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call();
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call();
+
+ String spec = "refs/heads/*:refs/remotes/test/*";
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+
+ remoteGit.branchDelete().setBranchNames(branch1).call();
+ git.fetch().setRemote("test").setRefSpecs(spec)
+ .setRemoveDeletedRefs(true).call();
+ assertNull(db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+ }
+
+ @Test
public void fetchShouldAutoFollowTag() throws Exception {
remoteGit.commit().setMessage("commit").call();
Ref tagRef = remoteGit.tag().setName("foo").call();
- RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*");
- git.fetch().setRemote("test").setRefSpecs(spec)
+ git.fetch().setRemote("test")
+ .setRefSpecs("refs/heads/*:refs/remotes/origin/*")
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(tagRef.getObjectId(), db.resolve("foo"));
@@ -120,8 +206,8 @@ public class FetchCommandTest extends RepositoryTestCase {
remoteGit.commit().setMessage("commit").call();
Ref tagRef = remoteGit.tag().setName("foo").call();
remoteGit.commit().setMessage("commit2").call();
- RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*");
- git.fetch().setRemote("test").setRefSpecs(spec)
+ git.fetch().setRemote("test")
+ .setRefSpecs("refs/heads/*:refs/remotes/origin/*")
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(tagRef.getObjectId(), db.resolve("foo"));
}
@@ -132,9 +218,8 @@ public class FetchCommandTest extends RepositoryTestCase {
remoteGit.checkout().setName("other").setCreateBranch(true).call();
remoteGit.commit().setMessage("commit2").call();
remoteGit.tag().setName("foo").call();
- RefSpec spec = new RefSpec(
- "refs/heads/master:refs/remotes/origin/master");
- git.fetch().setRemote("test").setRefSpecs(spec)
+ git.fetch().setRemote("test")
+ .setRefSpecs("refs/heads/master:refs/remotes/origin/master")
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertNull(db.resolve("foo"));
}
@@ -146,7 +231,7 @@ public class FetchCommandTest extends RepositoryTestCase {
Ref tagRef = remoteGit.tag().setName(tagName).call();
ObjectId originalId = tagRef.getObjectId();
- RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*");
+ String spec = "refs/heads/*:refs/remotes/origin/*";
git.fetch().setRemote("test").setRefSpecs(spec)
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(originalId, db.resolve(tagName));
@@ -172,7 +257,7 @@ public class FetchCommandTest extends RepositoryTestCase {
remoteGit.commit().setMessage("commit").call();
Ref tagRef1 = remoteGit.tag().setName(tagName).call();
- RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*");
+ String spec = "refs/heads/*:refs/remotes/origin/*";
git.fetch().setRemote("test").setRefSpecs(spec)
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(tagRef1.getObjectId(), db.resolve(tagName));
@@ -188,4 +273,75 @@ public class FetchCommandTest extends RepositoryTestCase {
assertEquals(RefUpdate.Result.FORCED, update.getResult());
assertEquals(tagRef2.getObjectId(), db.resolve(tagName));
}
+
+ @Test
+ public void fetchAddRefsWithDuplicateRefspec() throws Exception {
+ final String branchName = "branch";
+ final String remoteBranchName = "test/" + branchName;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef = remoteGit.branchCreate().setName(branchName).call();
+
+ final String spec1 = "+refs/heads/*:refs/remotes/test/*";
+ final String spec2 = "refs/heads/*:refs/remotes/test/*";
+ final StoredConfig config = db.getConfig();
+ RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+ remoteConfig.addFetchRefSpec(new RefSpec(spec1));
+ remoteConfig.addFetchRefSpec(new RefSpec(spec2));
+ remoteConfig.update(config);
+
+ git.fetch().setRemote("test").setRefSpecs(spec1).call();
+ assertEquals(branchRef.getObjectId(), db.resolve(remoteBranchName));
+ }
+
+ @Test
+ public void fetchPruneRefsWithDuplicateRefspec()
+ throws Exception {
+ final String branchName = "branch";
+ final String remoteBranchName = "test/" + branchName;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef = remoteGit.branchCreate().setName(branchName).call();
+
+ final String spec1 = "+refs/heads/*:refs/remotes/test/*";
+ final String spec2 = "refs/heads/*:refs/remotes/test/*";
+ final StoredConfig config = db.getConfig();
+ RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+ remoteConfig.addFetchRefSpec(new RefSpec(spec1));
+ remoteConfig.addFetchRefSpec(new RefSpec(spec2));
+ remoteConfig.update(config);
+
+ git.fetch().setRemote("test").setRefSpecs(spec1).call();
+ assertEquals(branchRef.getObjectId(), db.resolve(remoteBranchName));
+
+ remoteGit.branchDelete().setBranchNames(branchName).call();
+ git.fetch().setRemote("test").setRefSpecs(spec1)
+ .setRemoveDeletedRefs(true).call();
+ assertNull(db.resolve(remoteBranchName));
+ }
+
+ @Test
+ public void fetchUpdateRefsWithDuplicateRefspec() throws Exception {
+ final String tagName = "foo";
+ remoteGit.commit().setMessage("commit").call();
+ Ref tagRef1 = remoteGit.tag().setName(tagName).call();
+ List<RefSpec> refSpecs = new ArrayList<>();
+ refSpecs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+ refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
+ // Updating tags via the RefSpecs and setting TagOpt.FETCH_TAGS (or
+ // AUTO_FOLLOW) will result internally in *two* updates for the same
+ // ref.
+ git.fetch().setRemote("test").setRefSpecs(refSpecs)
+ .setTagOpt(TagOpt.AUTO_FOLLOW).call();
+ assertEquals(tagRef1.getObjectId(), db.resolve(tagName));
+
+ remoteGit.commit().setMessage("commit 2").call();
+ Ref tagRef2 = remoteGit.tag().setName(tagName).setForceUpdate(true)
+ .call();
+ FetchResult result = git.fetch().setRemote("test").setRefSpecs(refSpecs)
+ .setTagOpt(TagOpt.FETCH_TAGS).call();
+ assertEquals(2, result.getTrackingRefUpdates().size());
+ TrackingRefUpdate update = result
+ .getTrackingRefUpdate(Constants.R_TAGS + tagName);
+ assertEquals(RefUpdate.Result.FORCED, update.getResult());
+ assertEquals(tagRef2.getObjectId(), db.resolve(tagName));
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
index 38178bfd0e..bd0efad016 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
@@ -289,4 +289,4 @@ public class LogCommandTest extends RepositoryTestCase {
.setMessage("merge s0 with m1").call();
}
-} \ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 823516b99b..a341284850 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -620,4 +620,4 @@ public class PullCommandTest extends RepositoryTestCase {
fis.close();
}
}
-} \ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index 8c613ec488..e0c1499030 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -83,6 +83,11 @@ public class PushCommandTest extends RepositoryTestCase {
// create other repository
Repository db2 = createWorkRepository();
+ final StoredConfig config2 = db2.getConfig();
+
+ // this tests that this config can be parsed properly
+ config2.setString("fsck", "", "missingEmail", "ignore");
+ config2.save();
// setup the first repository
final StoredConfig config = db.getConfig();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
index f2e4d5b3b3..ad3ab7fbdf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
@@ -55,12 +55,15 @@ import org.eclipse.jgit.api.errors.InvalidRefNameException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.StashApplyFailureException;
+import org.eclipse.jgit.events.ChangeRecorder;
+import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.FileUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -77,15 +80,31 @@ public class StashApplyCommandTest extends RepositoryTestCase {
private File committedFile;
+ private ChangeRecorder recorder;
+
+ private ListenerHandle handle;
+
@Override
@Before
public void setUp() throws Exception {
super.setUp();
git = Git.wrap(db);
+ recorder = new ChangeRecorder();
+ handle = db.getListenerList().addWorkingTreeModifiedListener(recorder);
committedFile = writeTrashFile(PATH, "content");
git.add().addFilepattern(PATH).call();
head = git.commit().setMessage("add file").call();
assertNotNull(head);
+ recorder.assertNoEvent();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ if (handle != null) {
+ handle.remove();
+ }
+ super.tearDown();
}
@Test
@@ -95,10 +114,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertFalse(committedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH });
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -121,11 +142,13 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertFalse(addedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { addedPath });
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertTrue(addedFile.exists());
assertEquals("content2", read(addedFile));
+ recorder.assertEvent(new String[] { addedPath }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getChanged().isEmpty());
@@ -142,14 +165,17 @@ public class StashApplyCommandTest extends RepositoryTestCase {
@Test
public void indexDelete() throws Exception {
git.rm().addFilepattern("file.txt").call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" });
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertFalse(committedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" });
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -170,10 +196,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content2", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -193,16 +221,21 @@ public class StashApplyCommandTest extends RepositoryTestCase {
File subfolderFile = writeTrashFile(path, "content");
git.add().addFilepattern(path).call();
head = git.commit().setMessage("add file").call();
+ recorder.assertNoEvent();
writeTrashFile(path, "content2");
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(subfolderFile));
+ recorder.assertEvent(new String[] { "d1/d2/f.txt" },
+ ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content2", read(subfolderFile));
+ recorder.assertEvent(new String[] { "d1/d2/f.txt", "d1/d2", "d1" },
+ ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -225,10 +258,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content3", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -252,10 +287,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content2", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -281,10 +318,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertFalse(added.exists());
+ recorder.assertNoEvent();
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content2", read(added));
+ recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getChanged().isEmpty());
@@ -308,10 +347,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertFalse(committedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH });
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -337,9 +378,13 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed);
assertTrue(committedFile.exists());
assertFalse(addedFile.exists());
+ recorder.assertEvent(new String[] { PATH },
+ new String[] { "file2.txt" });
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
+ recorder.assertEvent(new String[] { "file2.txt" },
+ new String[] { PATH });
Status status = git.status().call();
assertTrue(status.getChanged().isEmpty());
@@ -362,6 +407,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed);
assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content3");
@@ -372,6 +418,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
// expected
}
assertEquals("content3", read(PATH));
+ recorder.assertNoEvent();
}
@Test
@@ -391,10 +438,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals("content\nhead change\nmore content\n",
read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("committed change").call();
+ recorder.assertNoEvent();
try {
git.stashApply().call();
@@ -402,6 +451,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
} catch (StashApplyFailureException e) {
// expected
}
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = new StatusCommand(db).call();
assertEquals(1, status.getConflicting().size());
assertEquals(
@@ -426,12 +476,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
writeTrashFile(PATH, "master content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even content").call();
+ recorder.assertNoEvent();
git.checkout().setName(otherBranch).call();
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "otherBranch content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even more content").call();
+ recorder.assertNoEvent();
writeTrashFile(path2, "content\nstashed change\nmore content\n");
@@ -442,12 +495,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals("otherBranch content",
read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
git.checkout().setName("master").call();
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().call();
assertEquals("content\nstashed change\nmore content\n", read(file2));
assertEquals("master content",
read(committedFile));
+ recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
}
@Test
@@ -467,12 +523,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
writeTrashFile(PATH, "master content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even content").call();
+ recorder.assertNoEvent();
git.checkout().setName(otherBranch).call();
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "otherBranch content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even more content").call();
+ recorder.assertNoEvent();
writeTrashFile(path2,
"content\nstashed change in index\nmore content\n");
@@ -485,8 +544,10 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals("content\nmore content\n", read(file2));
assertEquals("otherBranch content", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
git.checkout().setName("master").call();
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().call();
assertEquals("content\nstashed change\nmore content\n", read(file2));
assertEquals(
@@ -494,6 +555,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
+ "[file2.txt, mode:100644, content:content\nstashed change in index\nmore content\n]",
indexState(CONTENT));
assertEquals("master content", read(committedFile));
+ recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
}
@Test
@@ -501,6 +563,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
writeTrashFile(PATH, "content\nmore content\n");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("more content").call();
+ recorder.assertNoEvent();
writeTrashFile(PATH, "content\nstashed change\nmore content\n");
@@ -508,15 +571,18 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed);
assertEquals("content\nmore content\n", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("committed change").call();
+ recorder.assertNoEvent();
git.stashApply().call();
assertEquals(
"content\nstashed change\nmore content\ncommitted change\n",
read(committedFile));
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
}
@Test
@@ -527,6 +593,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed);
assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content3");
git.add().addFilepattern(PATH).call();
@@ -538,6 +605,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
} catch (StashApplyFailureException e) {
// expected
}
+ recorder.assertNoEvent();
assertEquals("content2", read(PATH));
}
@@ -549,6 +617,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed);
assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
String path2 = "file2.txt";
writeTrashFile(path2, "content3");
@@ -557,6 +626,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -583,12 +653,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(ChangeRecorder.EMPTY,
+ new String[] { subdir, path });
git.branchCreate().setName(otherBranch).call();
git.checkout().setName(otherBranch).call();
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
+ recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getChanged().isEmpty());
@@ -643,12 +716,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
git.commit().setMessage("x").call();
file.delete();
git.rm().addFilepattern("file").call();
+ recorder.assertNoEvent();
git.stashCreate().call();
+ recorder.assertEvent(new String[] { "file" }, ChangeRecorder.EMPTY);
file.delete();
git.stashApply().setStashRef("stash@{0}").call();
assertFalse(file.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file" });
}
@Test
@@ -660,9 +736,11 @@ public class StashApplyCommandTest extends RepositoryTestCase {
git.add().addFilepattern(PATH).call();
git.stashCreate().call();
assertTrue(untrackedFile.exists());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().setStashRef("stash@{0}").call();
assertTrue(untrackedFile.exists());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertEquals(1, status.getUntracked().size());
@@ -684,11 +762,14 @@ public class StashApplyCommandTest extends RepositoryTestCase {
.call();
assertNotNull(stashedCommit);
assertFalse(untrackedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
+
deleteTrashFile("a/b"); // checkout should create parent dirs
git.stashApply().setStashRef("stash@{0}").call();
assertTrue(untrackedFile.exists());
assertEquals("content", read(path));
+ recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertEquals(1, status.getUntracked().size());
@@ -706,6 +787,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
String path = "untracked.txt";
writeTrashFile(path, "untracked");
git.stashCreate().setIncludeUntracked(true).call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
writeTrashFile(path, "committed");
head = git.commit().setMessage("add file").call();
@@ -719,6 +801,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals(e.getMessage(), JGitText.get().stashApplyConflict);
}
assertEquals("committed", read(path));
+ recorder.assertNoEvent();
}
@Test
@@ -727,6 +810,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
String path = "untracked.txt";
writeTrashFile(path, "untracked");
git.stashCreate().setIncludeUntracked(true).call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
writeTrashFile(path, "working-directory");
try {
@@ -736,6 +820,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals(e.getMessage(), JGitText.get().stashApplyConflict);
}
assertEquals("working-directory", read(path));
+ recorder.assertNoEvent();
}
@Test
@@ -747,11 +832,13 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertTrue(PATH + " should exist", check(PATH));
assertEquals(PATH + " should have been reset", "content", read(PATH));
assertFalse(path + " should not exist", check(path));
+ recorder.assertEvent(new String[] { PATH }, new String[] { path });
git.stashApply().setStashRef("stash@{0}").call();
assertTrue(PATH + " should exist", check(PATH));
assertEquals(PATH + " should have new content", "changed", read(PATH));
assertTrue(path + " should exist", check(path));
assertEquals(path + " should have new content", "untracked",
read(path));
+ recorder.assertEvent(new String[] { PATH, path }, ChangeRecorder.EMPTY);
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java
index a7e0ab9f51..d658a53942 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java
@@ -51,6 +51,7 @@ import java.util.Iterator;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -94,9 +95,7 @@ public class StashListCommandTest extends RepositoryTestCase {
git.add().addFilepattern("file.txt").call();
RevCommit commit = git.commit().setMessage("create file").call();
- RefUpdate update = db.updateRef(Constants.R_STASH);
- update.setNewObjectId(commit);
- assertEquals(Result.NEW, update.update());
+ assertEquals(Result.NEW, newStashUpdate(commit).update());
StashListCommand command = git.stashList();
Collection<RevCommit> stashed = command.call();
@@ -117,13 +116,8 @@ public class StashListCommandTest extends RepositoryTestCase {
git.add().addFilepattern("file.txt").call();
RevCommit commit2 = git.commit().setMessage("edit file").call();
- RefUpdate create = db.updateRef(Constants.R_STASH);
- create.setNewObjectId(commit1);
- assertEquals(Result.NEW, create.update());
-
- RefUpdate update = db.updateRef(Constants.R_STASH);
- update.setNewObjectId(commit2);
- assertEquals(Result.FAST_FORWARD, update.update());
+ assertEquals(Result.NEW, newStashUpdate(commit1).update());
+ assertEquals(Result.FAST_FORWARD, newStashUpdate(commit2).update());
StashListCommand command = git.stashList();
Collection<RevCommit> stashed = command.call();
@@ -133,4 +127,11 @@ public class StashListCommandTest extends RepositoryTestCase {
assertEquals(commit2, iter.next());
assertEquals(commit1, iter.next());
}
+
+ private RefUpdate newStashUpdate(ObjectId newId) throws Exception {
+ RefUpdate ru = db.updateRef(Constants.R_STASH);
+ ru.setNewObjectId(newId);
+ ru.setForceRefLog(true);
+ return ru;
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
index ca456b3c8a..5868482c88 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
@@ -1,4 +1,7 @@
/*
+ * Copyright (C) 2015, 2017 Ivan Motsch <ivan.motsch@bsiag.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
@@ -254,6 +257,282 @@ public class AttributesHandlerTest extends RepositoryTestCase {
endWalk();
}
+ @Test
+ public void testRelativePaths() throws Exception {
+ setupRepo("sub/ global", "sub/** init",
+ "sub/** top_sub\n*.txt top",
+ "sub/** subsub\nsub/ subsub2\n*.txt foo");
+ // The last sub/** is in sub/.gitattributes. It must not
+ // apply to any of the files here. It would match for a
+ // further subdirectory sub/sub. The sub/ rules must match
+ // only for directories.
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub", attrs("global"));
+ assertIteration(F, "sub/.gitattributes", attrs("init top_sub"));
+ assertIteration(F, "sub/a.txt", attrs("init foo top top_sub"));
+ endWalk();
+ // All right, let's see that they *do* apply in sub/sub:
+ writeTrashFile("sub/sub/b.txt", "b");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub", attrs("global"));
+ assertIteration(F, "sub/.gitattributes", attrs("init top_sub"));
+ assertIteration(F, "sub/a.txt", attrs("init foo top top_sub"));
+ assertIteration(D, "sub/sub", attrs("init subsub2 top_sub global"));
+ assertIteration(F, "sub/sub/b.txt",
+ attrs("init foo subsub top top_sub"));
+ endWalk();
+ }
+
+ @Test
+ public void testNestedMatchNot() throws Exception {
+ setupRepo(null, null, "*.xml xml\n*.jar jar", null);
+ writeTrashFile("foo.xml/bar.jar", "b");
+ writeTrashFile("foo.xml/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ // On foo.xml/bar.jar we must not have 'xml'
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo.xml", attrs("xml"));
+ assertIteration(F, "foo.xml/bar.jar", attrs("jar"));
+ assertIteration(F, "foo.xml/bar.xml", attrs("xml"));
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(F, "sub/b.jar", attrs("jar"));
+ assertIteration(F, "sub/b.xml", attrs("xml"));
+ endWalk();
+ }
+
+ @Test
+ public void testNestedMatch() throws Exception {
+ // See also CGitAttributeTest.testNestedMatch()
+ setupRepo(null, null, "foo/ xml\nsub/foo/ sub\n*.jar jar", null);
+ writeTrashFile("foo/bar.jar", "b");
+ writeTrashFile("foo/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ writeTrashFile("sub/foo/b.jar", "bf");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo", attrs("xml"));
+ assertIteration(F, "foo/bar.jar", attrs("jar"));
+ assertIteration(F, "foo/bar.xml");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(F, "sub/b.jar", attrs("jar"));
+ assertIteration(F, "sub/b.xml");
+ assertIteration(D, "sub/foo", attrs("sub xml"));
+ assertIteration(F, "sub/foo/b.jar", attrs("jar"));
+ endWalk();
+ }
+
+ @Test
+ public void testNestedMatchRecursive() throws Exception {
+ setupRepo(null, null, "foo/** xml\n*.jar jar", null);
+ writeTrashFile("foo/bar.jar", "b");
+ writeTrashFile("foo/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ writeTrashFile("sub/foo/b.jar", "bf");
+ // On foo.xml/bar.jar we must not have 'xml'
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(F, "foo/bar.jar", attrs("jar xml"));
+ assertIteration(F, "foo/bar.xml", attrs("xml"));
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(F, "sub/b.jar", attrs("jar"));
+ assertIteration(F, "sub/b.xml");
+ assertIteration(D, "sub/foo");
+ assertIteration(F, "sub/foo/b.jar", attrs("jar"));
+ endWalk();
+ }
+
+ @Test
+ public void testStarMatchOnSlashNot() throws Exception {
+ setupRepo(null, null, "s*xt bar", null);
+ writeTrashFile("sub/a.txt", "1");
+ writeTrashFile("foo/sext", "2");
+ writeTrashFile("foo/s.txt", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(F, "foo/s.txt", attrs("bar"));
+ assertIteration(F, "foo/sext", attrs("bar"));
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testPrefixMatchNot() throws Exception {
+ setupRepo(null, null, "sub/new bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testComplexPathMatch() throws Exception {
+ setupRepo(null, null, "s[t-v]b/n[de]w bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("sub/ndw", "2");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(F, "sub/ndw", attrs("bar"));
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testStarPathMatch() throws Exception {
+ setupRepo(null, null, "sub/new/* bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("sub/new/lower/foo.txt", "2");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new");
+ assertIteration(F, "sub/new/foo.txt", attrs("bar"));
+ assertIteration(D, "sub/new/lower", attrs("bar"));
+ assertIteration(F, "sub/new/lower/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubSimple() throws Exception {
+ setupRepo(null, null, "sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new");
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ assertIteration(D, "sub/sub");
+ assertIteration(D, "sub/sub/new");
+ assertIteration(F, "sub/sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursive() throws Exception {
+ setupRepo(null, null, "**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new", attrs("bar"));
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
+ setupRepo(null, null, "**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new", attrs("bar"));
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ assertIteration(D, "sub/sub");
+ assertIteration(D, "sub/sub/new", attrs("bar"));
+ assertIteration(F, "sub/sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
+ setupRepo(null, null, "**/**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new", attrs("bar"));
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ assertIteration(D, "sub/sub");
+ assertIteration(D, "sub/sub/new", attrs("bar"));
+ assertIteration(F, "sub/sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubComplex() throws Exception {
+ setupRepo(null, null, "s[uv]b/n*/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new");
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatch() throws Exception {
+ setupRepo(null, null, "new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("foo/new", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(F, "foo/new");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new", attrs("bar"));
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
private static Collection<Attribute> attrs(String s) {
return new AttributesRule("*", s).getAttributes();
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
index e8dd952324..72cc1d1814 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
@@ -109,16 +109,16 @@ public class AttributesMatcherTest {
pattern = "/src/ne?";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new.c");
//Test name-only fnmatcher matches
pattern = "ne?";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertMatched(pattern, "/neb");
assertNotMatched(pattern, "/src/new.c");
}
@@ -169,16 +169,16 @@ public class AttributesMatcherTest {
pattern = "/src/ne?";
assertMatched(pattern, "src/new/");
assertMatched(pattern, "src/new");
- assertMatched(pattern, "src/new/a.c");
- assertMatched(pattern, "src/new/a/a.c");
+ assertNotMatched(pattern, "src/new/a.c");
+ assertNotMatched(pattern, "src/new/a/a.c");
assertNotMatched(pattern, "src/new.c");
//Test name-only fnmatcher matches
pattern = "ne?";
assertMatched(pattern, "src/new/");
assertMatched(pattern, "src/new");
- assertMatched(pattern, "src/new/a.c");
- assertMatched(pattern, "src/new/a/a.c");
+ assertNotMatched(pattern, "src/new/a.c");
+ assertNotMatched(pattern, "src/new/a/a.c");
assertMatched(pattern, "neb");
assertNotMatched(pattern, "src/new.c");
}
@@ -197,35 +197,50 @@ public class AttributesMatcherTest {
pattern = "/src/new";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new.c");
//Test child directory is matched, slash after name
pattern = "/src/new/";
assertMatched(pattern, "/src/new/");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new");
assertNotMatched(pattern, "/src/new.c");
//Test directory is matched by name only
pattern = "b1";
- assertMatched(pattern, "/src/new/a/b1/a.c");
+ assertNotMatched(pattern, "/src/new/a/b1/a.c");
assertNotMatched(pattern, "/src/new/a/b2/file.c");
assertNotMatched(pattern, "/src/new/a/bb1/file.c");
assertNotMatched(pattern, "/src/new/a/file.c");
+ assertNotMatched(pattern, "/src/new/a/bb1");
+ assertMatched(pattern, "/src/new/a/b1");
}
@Test
public void testTrailingSlash() {
String pattern = "/src/";
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src");
assertNotMatched(pattern, "/srcA/");
+
+ pattern = "src/";
+ assertMatched(pattern, "src/");
+ assertMatched(pattern, "/src/");
+ assertNotMatched(pattern, "src");
+ assertNotMatched(pattern, "/src/new");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "foo/src/a.c");
+ assertNotMatched(pattern, "foo/src/bar/a.c");
+ assertNotMatched(pattern, "foo/src/bar/src");
+ assertMatched(pattern, "foo/src/");
+ assertMatched(pattern, "foo/src/bar/src/");
}
@Test
@@ -239,51 +254,58 @@ public class AttributesMatcherTest {
assertMatched(pattern, "/src/test.stp");
assertNotMatched(pattern, "/test.stp1");
assertNotMatched(pattern, "/test.astp");
+ assertNotMatched(pattern, "test.stp/foo.bar");
+ assertMatched(pattern, "test.stp");
+ assertMatched(pattern, "test.stp/");
+ assertMatched(pattern, "test.stp/test.stp");
//Test matches for name-only, applies to file name or folder name
pattern = "src";
assertMatched(pattern, "/src");
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/file/src");
//Test matches for name-only, applies only to folder names
pattern = "src/";
- assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertNotMatched(pattern, "/src");
assertNotMatched(pattern, "/file/src");
+ assertMatched(pattern, "/file/src/");
//Test matches for name-only, applies to file name or folder name
//With a small wildcard
pattern = "?rc";
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
+ assertMatched(pattern, "/new/src/");
assertMatched(pattern, "/file/src");
assertMatched(pattern, "/src/");
//Test matches for name-only, applies to file name or folder name
//With a small wildcard
pattern = "?r[a-c]";
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/file/src");
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/srb/a.c");
- assertMatched(pattern, "/grb/new/a.c");
- assertMatched(pattern, "/new/crb/a.c");
+ assertNotMatched(pattern, "/srb/a.c");
+ assertNotMatched(pattern, "/grb/new/a.c");
+ assertNotMatched(pattern, "/new/crb/a.c");
assertMatched(pattern, "/file/3rb");
assertMatched(pattern, "/xrb/");
- assertMatched(pattern, "/3ra/a.c");
- assertMatched(pattern, "/5ra/new/a.c");
- assertMatched(pattern, "/new/1ra/a.c");
+ assertNotMatched(pattern, "/3ra/a.c");
+ assertNotMatched(pattern, "/5ra/new/a.c");
+ assertNotMatched(pattern, "/new/1ra/a.c");
+ assertNotMatched(pattern, "/new/1ra/a.c/");
assertMatched(pattern, "/file/dra");
+ assertMatched(pattern, "/file/dra/");
assertMatched(pattern, "/era/");
assertNotMatched(pattern, "/crg");
assertNotMatched(pattern, "/cr3");
@@ -360,6 +382,39 @@ public class AttributesMatcherTest {
assertEquals(r.getAttributes().get(2).toString(), "attribute3=value");
}
+ @Test
+ public void testBracketsInGroup() {
+ //combinations of brackets in brackets, escaped and not
+
+ String[] patterns = new String[]{"[[\\]]", "[\\[\\]]"};
+ for (String pattern : patterns) {
+ assertNotMatched(pattern, "");
+ assertNotMatched(pattern, "[]");
+ assertNotMatched(pattern, "][");
+ assertNotMatched(pattern, "[\\[]");
+ assertNotMatched(pattern, "[[]");
+ assertNotMatched(pattern, "[[]]");
+ assertNotMatched(pattern, "[\\[\\]]");
+
+ assertMatched(pattern, "[");
+ assertMatched(pattern, "]");
+ }
+
+ patterns = new String[]{"[[]]", "[\\[]]"};
+ for (String pattern : patterns) {
+ assertNotMatched(pattern, "");
+ assertMatched(pattern, "[]");
+ assertNotMatched(pattern, "][");
+ assertNotMatched(pattern, "[\\[]");
+ assertNotMatched(pattern, "[[]");
+ assertNotMatched(pattern, "[[]]");
+ assertNotMatched(pattern, "[\\[\\]]");
+
+ assertNotMatched(pattern, "[");
+ assertNotMatched(pattern, "]");
+ }
+ }
+
/**
* Check for a match. If target ends with "/", match will assume that the
* target is meant to be a directory.
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index ec2370e67f..f0d3c3690f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
@@ -166,6 +166,25 @@ public class AttributesNodeTest {
assertAttribute("file.type3", node, asSet(A_UNSET_ATTR, B_SET_ATTR));
}
+ @Test
+ public void testDoubleAsteriskAtEnd() throws IOException {
+ String attributeFileContent = "dir/** \tA -B\tC=value";
+
+ is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ AttributesNode node = new AttributesNode();
+ node.parse(is);
+ assertAttribute("dir", node,
+ asSet(new Attribute[]{}));
+ assertAttribute("dir/", node,
+ asSet(new Attribute[]{}));
+ assertAttribute("dir/file.type1", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ assertAttribute("dir/sub/", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ assertAttribute("dir/sub/file.type1", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ }
+
private void assertAttribute(String path, AttributesNode node,
Attributes attrs) throws IOException {
Attributes attributes = new Attributes();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
new file mode 100644
index 0000000000..34838138e3
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch>
+ * 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.attributes;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests that verify that the attributes of files in a repository are the same
+ * in JGit and in C-git.
+ */
+public class CGitAttributesTest extends RepositoryTestCase {
+
+ @Before
+ public void initRepo() throws IOException {
+ // Because we run C-git, we must ensure that global or user exclude
+ // files cannot influence the tests. So we set core.excludesFile to an
+ // empty file inside the repository.
+ StoredConfig config = db.getConfig();
+ File fakeUserGitignore = writeTrashFile(".fake_user_gitignore", "");
+ config.setString("core", null, "excludesFile",
+ fakeUserGitignore.getAbsolutePath());
+ // Disable case-insensitivity -- JGit doesn't handle that yet.
+ config.setBoolean("core", null, "ignoreCase", false);
+ // And try to switch off the global attributes file, too.
+ config.setString("core", null, "attributesFile",
+ fakeUserGitignore.getAbsolutePath());
+ config.save();
+ }
+
+ private void createFiles(String... paths) throws IOException {
+ for (String path : paths) {
+ writeTrashFile(path, "x");
+ }
+ }
+
+ private String toString(TemporaryBuffer b) throws IOException {
+ return RawParseUtils.decode(b.toByteArray());
+ }
+
+ private Attribute fromString(String key, String value) {
+ if ("set".equals(value)) {
+ return new Attribute(key, Attribute.State.SET);
+ }
+ if ("unset".equals(value)) {
+ return new Attribute(key, Attribute.State.UNSET);
+ }
+ if ("unspecified".equals(value)) {
+ return new Attribute(key, Attribute.State.UNSPECIFIED);
+ }
+ return new Attribute(key, value);
+ }
+
+ private LinkedHashMap<String, Attributes> cgitAttributes(
+ Set<String> allFiles) throws Exception {
+ FS fs = db.getFS();
+ StringBuilder input = new StringBuilder();
+ for (String filename : allFiles) {
+ input.append(filename).append('\n');
+ }
+ ProcessBuilder builder = fs.runInShell("git",
+ new String[] { "check-attr", "--stdin", "--all" });
+ builder.directory(db.getWorkTree());
+ builder.environment().put("HOME", fs.userHome().getAbsolutePath());
+ ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
+ input.toString().getBytes(Constants.CHARSET)));
+ String errorOut = toString(result.getStderr());
+ assertEquals("External git failed", "exit 0\n",
+ "exit " + result.getRc() + '\n' + errorOut);
+ LinkedHashMap<String, Attributes> map = new LinkedHashMap<>();
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(
+ new BufferedInputStream(result.getStdout().openInputStream()),
+ Constants.CHARSET))) {
+ r.lines().forEach(line -> {
+ // Parse the line and add to result map
+ int start = 0;
+ int i = line.indexOf(':');
+ String path = line.substring(0, i).trim();
+ start = i + 1;
+ i = line.indexOf(':', start);
+ String key = line.substring(start, i).trim();
+ String value = line.substring(i + 1).trim();
+ Attribute attr = fromString(key, value);
+ Attributes attrs = map.get(path);
+ if (attrs == null) {
+ attrs = new Attributes(attr);
+ map.put(path, attrs);
+ } else {
+ attrs.put(attr);
+ }
+ });
+ }
+ return map;
+ }
+
+ private LinkedHashMap<String, Attributes> jgitAttributes()
+ throws IOException {
+ // Do a tree walk and return a list of all files and directories with
+ // their attributes
+ LinkedHashMap<String, Attributes> result = new LinkedHashMap<>();
+ try (TreeWalk walk = new TreeWalk(db)) {
+ walk.addTree(new FileTreeIterator(db));
+ walk.setFilter(new NotIgnoredFilter(0));
+ while (walk.next()) {
+ String path = walk.getPathString();
+ if (walk.isSubtree() && !path.endsWith("/")) {
+ // git check-attr expects directory paths to end with a
+ // slash
+ path += '/';
+ }
+ Attributes attrs = walk.getAttributes();
+ if (attrs != null && !attrs.isEmpty()) {
+ result.put(path, attrs);
+ } else {
+ result.put(path, null);
+ }
+ if (walk.isSubtree()) {
+ walk.enterSubtree();
+ }
+ }
+ }
+ return result;
+ }
+
+ private void assertSameAsCGit() throws Exception {
+ LinkedHashMap<String, Attributes> jgit = jgitAttributes();
+ LinkedHashMap<String, Attributes> cgit = cgitAttributes(jgit.keySet());
+ // remove all without attributes
+ Iterator<Map.Entry<String, Attributes>> iterator = jgit.entrySet()
+ .iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<String, Attributes> entry = iterator.next();
+ if (entry.getValue() == null) {
+ iterator.remove();
+ }
+ }
+ assertArrayEquals("JGit attributes differ from C git",
+ cgit.entrySet().toArray(), jgit.entrySet().toArray());
+ }
+
+ @Test
+ public void testBug508568() throws Exception {
+ createFiles("foo.xml/bar.jar", "sub/foo.xml/bar.jar");
+ writeTrashFile(".gitattributes", "*.xml xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testRelativePath() throws Exception {
+ createFiles("sub/foo.txt");
+ writeTrashFile("sub/.gitattributes", "sub/** sub\n" + "*.txt txt\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testRelativePaths() throws Exception {
+ createFiles("sub/foo.txt", "sub/sub/bar", "foo/sub/a.txt",
+ "foo/sub/bar/a.tmp");
+ writeTrashFile(".gitattributes", "sub/** sub\n" + "*.txt txt\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchNot() throws Exception {
+ createFiles("foo.xml/bar.jar", "foo.xml/bar.xml", "sub/b.jar",
+ "sub/b.xml");
+ writeTrashFile("sub/.gitattributes", "*.xml xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatch() throws Exception {
+ // This is an interesting test. At the time of this writing, the
+ // gitignore documentation says: "In other words, foo/ will match a
+ // directory foo AND PATHS UNDERNEATH IT, but will not match a regular
+ // file or a symbolic link foo". (Emphasis added.) And gitattributes is
+ // supposed to follow the same rules. But the documentation appears to
+ // lie: C-git will *not* apply the attribute "xml" to *any* files in
+ // any subfolder "foo" here. It will only apply the "jar" attribute
+ // to the three *.jar files.
+ //
+ // The point is probably that ignores are handled top-down, and once a
+ // directory "foo" is matched (here: on paths "foo" and "sub/foo" by
+ // pattern "foo/"), the directory is excluded and the gitignore
+ // documentation also says: "It is not possible to re-include a file if
+ // a parent directory of that file is excluded." So once the pattern
+ // "foo/" has matched, it appears as if everything beneath would also be
+ // matched.
+ //
+ // But not so for gitattributes! The foo/ rule only matches the
+ // directory itself, but not anything beneath.
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes",
+ "foo/ xml\n" + "sub/foo/ sub\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchWithWildcard() throws Exception {
+ // See above.
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes",
+ "**/foo/ xml\n" + "*/foo/ sub\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchRecursive() throws Exception {
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes", "foo/** xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testStarMatchOnSlashNot() throws Exception {
+ createFiles("sub/a.txt", "foo/sext", "foo/s.txt");
+ writeTrashFile(".gitattributes", "s*xt bar");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testPrefixMatchNot() throws Exception {
+ createFiles("src/new/foo.txt");
+ writeTrashFile(".gitattributes", "src/new bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testComplexPathMatchNot() throws Exception {
+ createFiles("src/new/foo.txt", "src/ndw");
+ writeTrashFile(".gitattributes", "s[p-s]c/n[de]w bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testStarPathMatchNot() throws Exception {
+ createFiles("src/new/foo.txt", "src/ndw");
+ writeTrashFile(".gitattributes", "src/* bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubSimple() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursive() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception {
+ createFiles("src/new/src/new/foo.txt",
+ "foo/src/new/bar/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception {
+ createFiles("src/src/src/new/foo.txt",
+ "foo/src/src/bar/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt",
+ "x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt");
+ writeTrashFile(".gitattributes", "**/*/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack6() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt");
+ writeTrashFile(".gitattributes", "**/*/**/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubComplex() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "s[rs]c/n*/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatch() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testBracketsInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitattributes", "[[]] bar1\n" + "[\\[]] bar2\n"
+ + "[[\\]] bar3\n" + "[\\[\\]] bar4\n");
+ assertSameAsCGit();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java
new file mode 100644
index 0000000000..a4f3d18d1f
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
+ * 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.attributes.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.function.Consumer;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeResult;
+import org.eclipse.jgit.api.MergeResult.MergeStatus;
+import org.eclipse.jgit.api.errors.CheckoutConflictException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.api.errors.NoMessageException;
+import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
+import org.eclipse.jgit.attributes.Attribute;
+import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class MergeGitAttributeTest extends RepositoryTestCase {
+
+ private static final String REFS_HEADS_RIGHT = "refs/heads/right";
+
+ private static final String REFS_HEADS_MASTER = "refs/heads/master";
+
+ private static final String REFS_HEADS_LEFT = "refs/heads/left";
+
+ private static final String DISABLE_CHECK_BRANCH = "refs/heads/disabled_checked";
+
+ private static final String ENABLE_CHECKED_BRANCH = "refs/heads/enabled_checked";
+
+ private static final String ENABLED_CHECKED_GIF = "enabled_checked.gif";
+
+ public Git createRepositoryBinaryConflict(Consumer<Git> initialCommit,
+ Consumer<Git> leftCommit, Consumer<Git> rightCommit)
+ throws NoFilepatternException, GitAPIException, NoWorkTreeException,
+ IOException {
+ // Set up a git whith conflict commits on images
+ Git git = new Git(db);
+
+ // First commit
+ initialCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ RevCommit firstCommit = git.commit().setAll(true)
+ .setMessage("initial commit adding git attribute file").call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, REFS_HEADS_LEFT);
+ checkoutBranch(REFS_HEADS_LEFT);
+ leftCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("Left").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, REFS_HEADS_RIGHT);
+ checkoutBranch(REFS_HEADS_RIGHT);
+ rightCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("Right").call();
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ return git;
+
+ }
+
+ @Test
+ public void mergeTextualFile_NoAttr() throws NoWorkTreeException,
+ NoFilepatternException, GitAPIException, IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ })) {
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
+
+ assertNull(mergeResult.getConflicts());
+
+ // Check that the image was not modified (not conflict marker added)
+ String result = read(
+ writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
+ assertEquals(result, read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_UnsetMerge_Conflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.cat -merge");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ })) {
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUnset(REFS_HEADS_LEFT, "main.cat");
+ assertAddMergeAttributeUnset(REFS_HEADS_RIGHT, "main.cat");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ String catContent = read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile());
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ assertEquals(catContent, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_UnsetMerge_NoConflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.txt -merge");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ })) {
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUndefined(REFS_HEADS_LEFT, "main.cat");
+ assertAddMergeAttributeUndefined(REFS_HEADS_RIGHT, "main.cat");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ String result = read(
+ writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
+ assertEquals(result, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_SetBinaryMerge_Conflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.cat merge=binary");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ })) {
+ // Check that the merge attribute is set to binary
+ assertAddMergeAttributeCustom(REFS_HEADS_LEFT, "main.cat",
+ "binary");
+ assertAddMergeAttributeCustom(REFS_HEADS_RIGHT, "main.cat",
+ "binary");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ String catContent = read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile());
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ assertEquals(catContent, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ /*
+ * This test is commented because JGit add conflict markers in binary files.
+ * cf. https://www.eclipse.org/forums/index.php/t/1086511/
+ */
+ @Test
+ @Ignore
+ public void mergeBinaryFile_NoAttr_Conflict() throws IllegalStateException,
+ IOException, NoHeadException, ConcurrentRefUpdateException,
+ CheckoutConflictException, InvalidMergeHeadsException,
+ WrongRepositoryStateException, NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ FileInputStream mergeResultFile = null;
+ // Set up a git with conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUndefined(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeUndefined(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (no conflict marker added)
+ mergeResultFile = new FileInputStream(
+ db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
+ .toFile());
+ assertTrue(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ } finally {
+ if (mergeResultFile != null) {
+ mergeResultFile.close();
+ }
+ }
+ }
+
+ @Test
+ public void mergeBinaryFile_UnsetMerge_Conflict()
+ throws IllegalStateException,
+ IOException, NoHeadException, ConcurrentRefUpdateException,
+ CheckoutConflictException, InvalidMergeHeadsException,
+ WrongRepositoryStateException, NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ FileInputStream mergeResultFile = null;
+ // Set up a git whith conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "*.gif -merge");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUnset(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeUnset(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ mergeResultFile = new FileInputStream(db.getWorkTree().toPath()
+ .resolve(ENABLED_CHECKED_GIF).toFile());
+ assertTrue(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ } finally {
+ if (mergeResultFile != null) {
+ mergeResultFile.close();
+ }
+ }
+ }
+
+ @Test
+ public void mergeBinaryFile_SetMerge_Conflict()
+ throws IllegalStateException, IOException, NoHeadException,
+ ConcurrentRefUpdateException, CheckoutConflictException,
+ InvalidMergeHeadsException, WrongRepositoryStateException,
+ NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ FileInputStream mergeResultFile = null;
+ // Set up a git whith conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "*.gif merge");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is set
+ assertAddMergeAttributeSet(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeSet(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ mergeResultFile = new FileInputStream(db.getWorkTree().toPath()
+ .resolve(ENABLED_CHECKED_GIF).toFile());
+ assertFalse(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ } finally {
+ if (mergeResultFile != null) {
+ mergeResultFile.close();
+ }
+ }
+ }
+
+ /*
+ * Copied from org.apache.commons.io.IOUtils
+ */
+ private boolean contentEquals(InputStream input1, InputStream input2)
+ throws IOException {
+ if (input1 == input2) {
+ return true;
+ }
+ if (!(input1 instanceof BufferedInputStream)) {
+ input1 = new BufferedInputStream(input1);
+ }
+ if (!(input2 instanceof BufferedInputStream)) {
+ input2 = new BufferedInputStream(input2);
+ }
+
+ int ch = input1.read();
+ while (-1 != ch) {
+ final int ch2 = input2.read();
+ if (ch != ch2) {
+ return false;
+ }
+ ch = input1.read();
+ }
+
+ final int ch2 = input2.read();
+ return ch2 == -1;
+ }
+
+ private void assertAddMergeAttributeUnset(String branch, String fileName)
+ throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.UNSET, mergeAttribute.getState());
+ }
+ }
+
+ private void assertAddMergeAttributeSet(String branch, String fileName)
+ throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.SET, mergeAttribute.getState());
+ }
+ }
+
+ private void assertAddMergeAttributeUndefined(String branch,
+ String fileName) throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNull(mergeAttribute);
+ }
+ }
+
+ private void assertAddMergeAttributeCustom(String branch, String fileName,
+ String value) throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.CUSTOM, mergeAttribute.getState());
+ assertEquals(value, mergeAttribute.getValue());
+ }
+ }
+
+ private void copy(String resourcePath, String resourceNewName,
+ String pathInRepo) throws IOException {
+ InputStream input = getClass().getResourceAsStream(resourcePath);
+ Files.copy(input, db.getWorkTree().toPath().resolve(pathInRepo)
+ .resolve(resourceNewName));
+ }
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java
index 12f4dcc0c7..341cc4f215 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java
@@ -47,6 +47,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
+
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java
new file mode 100644
index 0000000000..ee8191ffc5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch>
+ * 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.ignore;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests that verify that the set of ignore files in a repository is the same in
+ * JGit and in C-git.
+ */
+public class CGitIgnoreTest extends RepositoryTestCase {
+
+ @Before
+ public void initRepo() throws IOException {
+ // These tests focus on .gitignore files inside the repository. Because
+ // we run C-git, we must ensure that global or user exclude files cannot
+ // influence the tests. So we set core.excludesFile to an empty file
+ // inside the repository.
+ File fakeUserGitignore = writeTrashFile(".fake_user_gitignore", "");
+ StoredConfig config = db.getConfig();
+ config.setString("core", null, "excludesFile",
+ fakeUserGitignore.getAbsolutePath());
+ // Disable case-insensitivity -- JGit doesn't handle that yet.
+ config.setBoolean("core", null, "ignoreCase", false);
+ config.save();
+ }
+
+ private void createFiles(String... paths) throws IOException {
+ for (String path : paths) {
+ writeTrashFile(path, "x");
+ }
+ }
+
+ private String toString(TemporaryBuffer b) throws IOException {
+ return RawParseUtils.decode(b.toByteArray());
+ }
+
+ private String[] cgitIgnored() throws Exception {
+ FS fs = db.getFS();
+ ProcessBuilder builder = fs.runInShell("git", new String[] { "ls-files",
+ "--ignored", "--exclude-standard", "-o" });
+ builder.directory(db.getWorkTree());
+ builder.environment().put("HOME", fs.userHome().getAbsolutePath());
+ ExecutionResult result = fs.execute(builder,
+ new ByteArrayInputStream(new byte[0]));
+ String errorOut = toString(result.getStderr());
+ assertEquals("External git failed", "exit 0\n",
+ "exit " + result.getRc() + '\n' + errorOut);
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(
+ new BufferedInputStream(result.getStdout().openInputStream()),
+ Constants.CHARSET))) {
+ return r.lines().toArray(String[]::new);
+ }
+ }
+
+ private LinkedHashSet<String> jgitIgnored() throws IOException {
+ // Do a tree walk that does descend into ignored directories and return
+ // a list of all ignored files
+ LinkedHashSet<String> result = new LinkedHashSet<>();
+ try (TreeWalk walk = new TreeWalk(db)) {
+ walk.addTree(new FileTreeIterator(db));
+ walk.setRecursive(true);
+ while (walk.next()) {
+ if (walk.getTree(WorkingTreeIterator.class).isEntryIgnored()) {
+ result.add(walk.getPathString());
+ }
+ }
+ }
+ return result;
+ }
+
+ private void assertNoIgnoredVisited(Set<String> ignored) throws Exception {
+ // Do a recursive tree walk with a NotIgnoredFilter and verify that none
+ // of the files visited is in the ignored set
+ try (TreeWalk walk = new TreeWalk(db)) {
+ walk.addTree(new FileTreeIterator(db));
+ walk.setFilter(new NotIgnoredFilter(0));
+ walk.setRecursive(true);
+ while (walk.next()) {
+ String path = walk.getPathString();
+ assertFalse("File " + path + " is ignored, should not appear",
+ ignored.contains(path));
+ }
+ }
+ }
+
+ private void assertSameAsCGit(String... notIgnored) throws Exception {
+ LinkedHashSet<String> ignored = jgitIgnored();
+ String[] cgit = cgitIgnored();
+ assertArrayEquals(cgit, ignored.toArray());
+ for (String notExcluded : notIgnored) {
+ assertFalse("File " + notExcluded + " should not be ignored",
+ ignored.contains(notExcluded));
+ }
+ assertNoIgnoredVisited(ignored);
+ }
+
+ @Test
+ public void testSimpleIgnored() throws Exception {
+ createFiles("a.txt", "a.tmp", "src/sub/a.txt", "src/a.tmp",
+ "src/a.txt/b.tmp", "ignored/a.tmp", "ignored/not_ignored/a.tmp",
+ "ignored/other/a.tmp");
+ writeTrashFile(".gitignore",
+ "*.txt\n" + "/ignored/*\n" + "!/ignored/not_ignored");
+ assertSameAsCGit("ignored/not_ignored/a.tmp");
+ }
+
+ @Test
+ public void testDirOnlyMatch() throws Exception {
+ createFiles("a.txt", "src/foo/a.txt", "src/a.txt", "foo/a.txt");
+ writeTrashFile(".gitignore", "foo/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirOnlyMatchDeep() throws Exception {
+ createFiles("a.txt", "src/foo/a.txt", "src/a.txt", "foo/a.txt");
+ writeTrashFile(".gitignore", "**/foo/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testStarMatchOnSlashNot() throws Exception {
+ createFiles("sub/a.txt", "foo/sext", "foo/s.txt");
+ writeTrashFile(".gitignore", "s*xt");
+ assertSameAsCGit("sub/a.txt");
+ }
+
+ @Test
+ public void testPrefixMatch() throws Exception {
+ createFiles("src/new/foo.txt");
+ writeTrashFile(".gitignore", "src/new");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursive() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitignore", "**/src/new/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitignore", "**/src/new/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitignore", "**/**/src/new/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception {
+ createFiles("x/a/a/b/foo.txt");
+ writeTrashFile(".gitignore", "**/*/a/b/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt",
+ "x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt");
+ writeTrashFile(".gitignore", "**/*/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt");
+ writeTrashFile(".gitignore", "**/*/**/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testUnescapedBracketsInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitignore", "[[]]\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testEscapedFirstBracketInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitignore", "[\\[]]\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testEscapedSecondBracketInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitignore", "[[\\]]\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testEscapedBothBracketsInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitignore", "[\\[\\]]\n");
+ assertSameAsCGit();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
index 1863b80321..bcc8f7e47f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
@@ -391,7 +391,6 @@ public class FastIgnoreRuleTest {
assertMatched("/**/a/b", "c/d/a/b");
assertMatched("/**/**/a/b", "c/d/a/b");
- assertMatched("a/b/**", "a/b");
assertMatched("a/b/**", "a/b/c");
assertMatched("a/b/**", "a/b/c/d/");
assertMatched("a/b/**/**", "a/b/c/d");
@@ -415,6 +414,12 @@ public class FastIgnoreRuleTest {
@Test
public void testWildmatchDoNotMatch() {
+ assertNotMatched("a/**", "a/");
+ assertNotMatched("a/b/**", "a/b/");
+ assertNotMatched("a/**", "a");
+ assertNotMatched("a/b/**", "a/b");
+ assertNotMatched("a/b/**/", "a/b");
+ assertNotMatched("a/b/**/**", "a/b");
assertNotMatched("**/a/b", "a/c/b");
assertNotMatched("!/**/*.zip", "c/a/b.zip");
assertNotMatched("!**/*.zip", "c/a/b.zip");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java
new file mode 100644
index 0000000000..468989fe13
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * 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.ignore.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class StringsTest {
+
+ private void testString(String string, int n, int m) {
+ assertEquals(string, n, Strings.count(string, '/', false));
+ assertEquals(string, m, Strings.count(string, '/', true));
+ }
+
+ @Test
+ public void testCount() {
+ testString("", 0, 0);
+ testString("/", 1, 0);
+ testString("//", 2, 0);
+ testString("///", 3, 1);
+ testString("////", 4, 2);
+ testString("foo", 0, 0);
+ testString("/foo", 1, 0);
+ testString("foo/", 1, 0);
+ testString("/foo/", 2, 0);
+ testString("foo/bar", 1, 1);
+ testString("/foo/bar/", 3, 1);
+ testString("/foo/bar//", 4, 2);
+ testString("/foo//bar/", 4, 2);
+ testString(" /foo/ ", 2, 2);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
index 4f3b601d3c..4228c9dbec 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
@@ -142,7 +142,11 @@ public class IndexDiffWithSymlinkTest extends LocalDiskRepositoryTestCase {
String[] cmd = { "/bin/sh", "./" + name + ".sh" };
int exitCode;
String stdErr;
- Process process = Runtime.getRuntime().exec(cmd, null, testDir);
+ ProcessBuilder builder = new ProcessBuilder(cmd);
+ builder.environment().put("HOME",
+ FS.DETECTED.userHome().getAbsolutePath());
+ builder.directory(testDir);
+ Process process = builder.start();
try (InputStream stdOutStream = process.getInputStream();
InputStream stdErrStream = process.getErrorStream();
OutputStream stdInStream = process.getOutputStream()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java
index 5bef9fa29e..32d711f1f8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java
@@ -57,13 +57,14 @@ import org.junit.Test;
public class DeltaBaseCacheTest {
private static final int SZ = 512;
- private DfsPackKey key;
+ private DfsStreamKey key;
private DeltaBaseCache cache;
private TestRng rng;
@Before
public void setUp() {
- key = new DfsPackKey();
+ DfsRepositoryDescription repo = new DfsRepositoryDescription("test");
+ key = DfsStreamKey.of(repo, "test.key");
cache = new DeltaBaseCache(SZ);
rng = new TestRng(getClass().getSimpleName());
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
new file mode 100644
index 0000000000..2e3ee4526f
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.storage.dfs;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.junit.TestRng;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class DfsBlockCacheTest {
+ @Rule
+ public TestName testName = new TestName();
+ private TestRng rng;
+ private DfsBlockCache cache;
+
+ @Before
+ public void setUp() {
+ rng = new TestRng(testName.getMethodName());
+ resetCache();
+ }
+
+ @SuppressWarnings("resource")
+ @Test
+ public void streamKeyReusesBlocks() throws Exception {
+ DfsRepositoryDescription repo = new DfsRepositoryDescription("test");
+ InMemoryRepository r1 = new InMemoryRepository(repo);
+ byte[] content = rng.nextBytes(424242);
+ ObjectId id;
+ try (ObjectInserter ins = r1.newObjectInserter()) {
+ id = ins.insert(OBJ_BLOB, content);
+ ins.flush();
+ }
+
+ long oldSize = cache.getCurrentSize();
+ assertTrue(oldSize > 2000);
+ assertEquals(0, cache.getHitCount());
+
+ List<DfsPackDescription> packs = r1.getObjectDatabase().listPacks();
+ InMemoryRepository r2 = new InMemoryRepository(repo);
+ r2.getObjectDatabase().commitPack(packs, Collections.emptyList());
+ try (ObjectReader rdr = r2.newObjectReader()) {
+ byte[] actual = rdr.open(id, OBJ_BLOB).getBytes();
+ assertTrue(Arrays.equals(content, actual));
+ }
+ assertEquals(0, cache.getMissCount());
+ assertEquals(oldSize, cache.getCurrentSize());
+ }
+
+ @SuppressWarnings("resource")
+ @Test
+ public void weirdBlockSize() throws Exception {
+ DfsRepositoryDescription repo = new DfsRepositoryDescription("test");
+ InMemoryRepository r1 = new InMemoryRepository(repo);
+
+ byte[] content1 = rng.nextBytes(4);
+ byte[] content2 = rng.nextBytes(424242);
+ ObjectId id1;
+ ObjectId id2;
+ try (ObjectInserter ins = r1.newObjectInserter()) {
+ id1 = ins.insert(OBJ_BLOB, content1);
+ id2 = ins.insert(OBJ_BLOB, content2);
+ ins.flush();
+ }
+
+ resetCache();
+ List<DfsPackDescription> packs = r1.getObjectDatabase().listPacks();
+
+ InMemoryRepository r2 = new InMemoryRepository(repo);
+ r2.getObjectDatabase().setReadableChannelBlockSizeForTest(500);
+ r2.getObjectDatabase().commitPack(packs, Collections.emptyList());
+ try (ObjectReader rdr = r2.newObjectReader()) {
+ byte[] actual = rdr.open(id1, OBJ_BLOB).getBytes();
+ assertTrue(Arrays.equals(content1, actual));
+ }
+
+ InMemoryRepository r3 = new InMemoryRepository(repo);
+ r3.getObjectDatabase().setReadableChannelBlockSizeForTest(500);
+ r3.getObjectDatabase().commitPack(packs, Collections.emptyList());
+ try (ObjectReader rdr = r3.newObjectReader()) {
+ byte[] actual = rdr.open(id2, OBJ_BLOB).getBytes();
+ assertTrue(Arrays.equals(content2, actual));
+ }
+ }
+
+ private void resetCache() {
+ DfsBlockCache.reconfigure(new DfsBlockCacheConfig()
+ .setBlockSize(512)
+ .setBlockLimit(1 << 20));
+ cache = DfsBlockCache.getInstance();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
new file mode 100644
index 0000000000..804d744ae2
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.storage.dfs;
+
+import static org.eclipse.jgit.junit.JGitTestUtil.concat;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+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 java.io.IOException;
+
+import org.eclipse.jgit.internal.fsck.FsckError;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectChecker.ErrorType;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DfsFsckTest {
+ private TestRepository<InMemoryRepository> git;
+
+ private InMemoryRepository repo;
+
+ private ObjectInserter ins;
+
+ @Before
+ public void setUp() throws IOException {
+ DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
+ git = new TestRepository<>(new InMemoryRepository(desc));
+ repo = git.getRepository();
+ ins = repo.newObjectInserter();
+ }
+
+ @Test
+ public void testHealthyRepo() throws Exception {
+ RevCommit commit0 = git.commit().message("0").create();
+ RevCommit commit1 = git.commit().message("1").parent(commit0).create();
+ git.update("master", commit1);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 0);
+ assertEquals(errors.getMissingObjects().size(), 0);
+ assertEquals(errors.getCorruptIndices().size(), 0);
+ }
+
+ @Test
+ public void testCommitWithCorruptAuthor() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n");
+ b.append("author b <b@c> <b@c> 0 +0000\n");
+ b.append("committer <> 0 +0000\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_COMMIT, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.BAD_DATE);
+ }
+
+ @Test
+ public void testCommitWithoutTree() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("parent ");
+ b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
+ b.append('\n');
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_COMMIT, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.MISSING_TREE);
+ }
+
+ @Test
+ public void testTagWithoutObject() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("type commit\n");
+ b.append("tag test-tag\n");
+ b.append("tagger A. U. Thor <author@localhost> 1 +0000\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_TAG, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.MISSING_OBJECT);
+ }
+
+ @Test
+ public void testTreeWithNullSha() throws Exception {
+ byte[] data = concat(encodeASCII("100644 A"), new byte[] { '\0' },
+ new byte[OBJECT_ID_LENGTH]);
+ ObjectId id = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.NULL_SHA1);
+ }
+
+ @Test
+ public void testMultipleInvalidObjects() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("tree ");
+ b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
+ b.append('\n');
+ b.append("parent ");
+ b.append("\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id1 = ins.insert(Constants.OBJ_COMMIT, data);
+
+ b = new StringBuilder();
+ b.append("100644");
+ data = encodeASCII(b.toString());
+ ObjectId id2 = ins.insert(Constants.OBJ_TREE, data);
+
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 2);
+ for (CorruptObject o : errors.getCorruptObjects()) {
+ if (o.getId().equals(id1)) {
+ assertEquals(o.getErrorType(), ErrorType.BAD_PARENT_SHA1);
+ } else if (o.getId().equals(id2)) {
+ assertNull(o.getErrorType());
+ } else {
+ fail();
+ }
+ }
+ }
+
+ @Test
+ public void testValidConnectivity() throws Exception {
+ ObjectId blobId = ins
+ .insert(Constants.OBJ_BLOB, Constants.encode("foo"));
+
+ byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH];
+ blobId.copyRawTo(blobIdBytes, 0);
+ byte[] data = concat(encodeASCII("100644 regular-file\0"), blobIdBytes);
+ ObjectId treeId = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ RevCommit commit = git.commit().message("0").setTopLevelTree(treeId)
+ .create();
+
+ git.update("master", commit);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getMissingObjects().size(), 0);
+ }
+
+ @Test
+ public void testMissingObject() throws Exception {
+ ObjectId blobId = ObjectId
+ .fromString("19102815663d23f8b75a47e7a01965dcdc96468c");
+ byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH];
+ blobId.copyRawTo(blobIdBytes, 0);
+ byte[] data = concat(encodeASCII("100644 regular-file\0"), blobIdBytes);
+ ObjectId treeId = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ RevCommit commit = git.commit().message("0").setTopLevelTree(treeId)
+ .create();
+
+ git.update("master", commit);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getMissingObjects().size(), 1);
+ assertEquals(errors.getMissingObjects().iterator().next(), blobId);
+ }
+
+ @Test
+ public void testNonCommitHead() throws Exception {
+ RevCommit commit0 = git.commit().message("0").create();
+ StringBuilder b = new StringBuilder();
+ b.append("object ");
+ b.append(commit0.getName());
+ b.append('\n');
+ b.append("type commit\n");
+ b.append("tag test-tag\n");
+ b.append("tagger A. U. Thor <author@localhost> 1 +0000\n");
+
+ byte[] data = encodeASCII(b.toString());
+ ObjectId tagId = ins.insert(Constants.OBJ_TAG, data);
+ ins.flush();
+
+ git.update("master", tagId);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getCorruptObjects().size(), 0);
+ assertEquals(errors.getNonCommitHeads().size(), 1);
+ assertEquals(errors.getNonCommitHeads().iterator().next(),
+ "refs/heads/master");
+ }
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
index 17c1835bd0..55a5f726de 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
@@ -5,6 +5,7 @@ import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -13,24 +14,35 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
+import org.eclipse.jgit.internal.storage.reftable.RefCursor;
+import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
+import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
+import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
import org.eclipse.jgit.junit.MockSystemReader;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+/** Tests for pack creation and garbage expiration. */
public class DfsGarbageCollectorTest {
private TestRepository<InMemoryRepository> git;
private InMemoryRepository repo;
@@ -632,6 +644,205 @@ public class DfsGarbageCollectorTest {
}
}
+ @Test
+ public void testSinglePackForAllRefs() throws Exception {
+ RevCommit commit0 = commit().message("0").create();
+ git.update("head", commit0);
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update("refs/notes/note1", commit1);
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
+ gc.getPackConfig().setSinglePack(true);
+ run(gc);
+ assertEquals(1, odb.getPacks().length);
+
+ gc = new DfsGarbageCollector(repo);
+ gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
+ gc.getPackConfig().setSinglePack(false);
+ run(gc);
+ assertEquals(2, odb.getPacks().length);
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void producesNewReftable() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+
+ BatchRefUpdate bru = git.getRepository().getRefDatabase()
+ .newBatchUpdate();
+ bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit1, master));
+ for (int i = 1; i <= 5100; i++) {
+ bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit0,
+ String.format("refs/pulls/%04d", i)));
+ }
+ try (RevWalk rw = new RevWalk(git.getRepository())) {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+ assertEquals(GC, desc.getPackSource());
+ assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+ assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+
+ // Sibling REFTABLE is also present.
+ assertTrue(desc.hasFileExt(REFTABLE));
+ ReftableWriter.Stats stats = desc.getReftableStats();
+ assertNotNull(stats);
+ assertTrue(stats.totalBytes() > 0);
+ assertEquals(5101, stats.refCount());
+ assertEquals(1, stats.minUpdateIndex());
+ assertEquals(1, stats.maxUpdateIndex());
+
+ DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc);
+ try (DfsReader ctx = odb.newReader();
+ ReftableReader rr = table.open(ctx);
+ RefCursor rc = rr.seekRef("refs/pulls/5100")) {
+ assertTrue(rc.next());
+ assertEquals(commit0, rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void leavesNonGcReftablesIfNotConfigured() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update(master, commit1);
+
+ DfsPackDescription t1 = odb.newPack(INSERT);
+ try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
+ out.write("ignored".getBytes(StandardCharsets.UTF_8));
+ t1.addFileExt(REFTABLE);
+ }
+ odb.commitPack(Collections.singleton(t1), null);
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(null);
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+ assertEquals(GC, desc.getPackSource());
+ assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+ assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+
+ // Only INSERT REFTABLE above is present.
+ DfsReftable[] tables = odb.getReftables();
+ assertEquals(1, tables.length);
+ assertEquals(t1, tables[0].getPackDescription());
+ }
+
+ @Test
+ public void prunesNonGcReftables() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update(master, commit1);
+
+ DfsPackDescription t1 = odb.newPack(INSERT);
+ try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
+ out.write("ignored".getBytes(StandardCharsets.UTF_8));
+ t1.addFileExt(REFTABLE);
+ }
+ odb.commitPack(Collections.singleton(t1), null);
+ odb.clearCache();
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+ assertEquals(GC, desc.getPackSource());
+ assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+ assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+
+ // Only sibling GC REFTABLE is present.
+ DfsReftable[] tables = odb.getReftables();
+ assertEquals(1, tables.length);
+ assertEquals(desc, tables[0].getPackDescription());
+ assertTrue(desc.hasFileExt(REFTABLE));
+ }
+
+ @Test
+ public void compactsReftables() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update(master, commit1);
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+
+ DfsPackDescription t1 = odb.newPack(INSERT);
+ Ref next = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE,
+ "refs/heads/next", commit0.copy());
+ try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
+ ReftableWriter w = new ReftableWriter();
+ w.setMinUpdateIndex(42);
+ w.setMaxUpdateIndex(42);
+ w.begin(out);
+ w.sortAndWriteRefs(Collections.singleton(next));
+ w.finish();
+ t1.addFileExt(REFTABLE);
+ t1.setReftableStats(w.getStats());
+ }
+ odb.commitPack(Collections.singleton(t1), null);
+
+ gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+ assertEquals(GC, desc.getPackSource());
+ assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+ assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+
+ // Only sibling GC REFTABLE is present.
+ DfsReftable[] tables = odb.getReftables();
+ assertEquals(1, tables.length);
+ assertEquals(desc, tables[0].getPackDescription());
+ assertTrue(desc.hasFileExt(REFTABLE));
+
+ // GC reftable contains the compaction.
+ DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc);
+ try (DfsReader ctx = odb.newReader();
+ ReftableReader rr = table.open(ctx);
+ RefCursor rc = rr.allRefs()) {
+ assertEquals(1, rr.minUpdateIndex());
+ assertEquals(42, rr.maxUpdateIndex());
+
+ assertTrue(rc.next());
+ assertEquals(master, rc.getRef().getName());
+ assertEquals(commit1, rc.getRef().getObjectId());
+
+ assertTrue(rc.next());
+ assertEquals(next.getName(), rc.getRef().getName());
+ assertEquals(commit0, rc.getRef().getObjectId());
+
+ assertFalse(rc.next());
+ }
+ }
+
private TestRepository<InMemoryRepository>.CommitBuilder commit() {
return git.commit();
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
new file mode 100644
index 0000000000..3c4b8cf4bc
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ * 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.internal.storage.file;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.OK;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_MISSING_OBJECT;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_NONFASTFORWARD;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.TRANSACTION_ABORTED;
+import static org.eclipse.jgit.lib.ObjectId.zeroId;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Predicate;
+
+import org.eclipse.jgit.events.ListenerHandle;
+import org.eclipse.jgit.events.RefsChangedListener;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.StrictWorkMonitor;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.CheckoutEntry;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@SuppressWarnings("boxing")
+@RunWith(Parameterized.class)
+public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
+ @Parameter
+ public boolean atomic;
+
+ @Parameters(name = "atomic={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{ {Boolean.FALSE}, {Boolean.TRUE} });
+ }
+
+ private Repository diskRepo;
+ private TestRepository<Repository> repo;
+ private RefDirectory refdir;
+ private RevCommit A;
+ private RevCommit B;
+
+ /**
+ * When asserting the number of RefsChangedEvents you must account for one
+ * additional event due to the initial ref setup via a number of calls to
+ * {@link #writeLooseRef(String, AnyObjectId)} (will be fired in execute()
+ * when it is detected that the on-disk loose refs have changed), or for one
+ * additional event per {@link #writeRef(String, AnyObjectId)}.
+ */
+ private int refsChangedEvents;
+
+ private ListenerHandle handle;
+
+ private RefsChangedListener refsChangedListener = event -> {
+ refsChangedEvents++;
+ };
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ diskRepo = createBareRepository();
+ setLogAllRefUpdates(true);
+
+ refdir = (RefDirectory) diskRepo.getRefDatabase();
+ refdir.setRetrySleepMs(Arrays.asList(0, 0));
+
+ repo = new TestRepository<>(diskRepo);
+ A = repo.commit().create();
+ B = repo.commit(repo.getRevWalk().parseCommit(A));
+ refsChangedEvents = 0;
+ handle = diskRepo.getListenerList()
+ .addRefsChangedListener(refsChangedListener);
+ }
+
+ @After
+ public void removeListener() {
+ handle.remove();
+ refsChangedEvents = 0;
+ }
+
+ @Test
+ public void simpleNoForce() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD));
+ execute(newBatchUpdate(cmds));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, REJECTED_NONFASTFORWARD);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/masters", B);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, OK, REJECTED_NONFASTFORWARD);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void simpleForce() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", A);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ }
+
+ @Test
+ public void nonFastForwardDoesNotDoExpensiveMergeCheck() throws IOException {
+ writeLooseRef("refs/heads/master", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(B, A, "refs/heads/master", UPDATE_NONFASTFORWARD));
+ try (RevWalk rw = new RevWalk(diskRepo) {
+ @Override
+ public boolean isMergedInto(RevCommit base, RevCommit tip) {
+ throw new AssertionError("isMergedInto() should not be called");
+ }
+ }) {
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .execute(rw, new StrictWorkMonitor());
+ }
+
+ assertResults(cmds, OK);
+ assertRefs("refs/heads/master", A);
+ assertEquals(2, refsChangedEvents);
+ }
+
+ @Test
+ public void fileDirectoryConflict() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ // Atomic update sees that master and master/x are conflicting, then marks
+ // the first one in the list as LOCK_FAILURE and aborts the rest.
+ assertResults(cmds,
+ LOCK_FAILURE, TRANSACTION_ABORTED, TRANSACTION_ABORTED);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/masters", B);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ // Non-atomic updates are applied in order: master succeeds, then master/x
+ // fails due to conflict.
+ assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void conflictThanksToDelete() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE),
+ new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters/x", A);
+ if (atomic) {
+ assertEquals(2, refsChangedEvents);
+ } else {
+ // The non-atomic case actually produces 5 events, but that's an
+ // implementation detail. We expect at least 4 events, one for the
+ // initial read due to writeLooseRef(), and then one for each
+ // successful ref update.
+ assertTrue(refsChangedEvents >= 4);
+ }
+ }
+
+ @Test
+ public void updateToMissingObject() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, bad, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ assertResults(cmds, REJECTED_MISSING_OBJECT, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, REJECTED_MISSING_OBJECT, OK);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/foo2", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void addMissingObject() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, REJECTED_MISSING_OBJECT);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, OK, REJECTED_MISSING_OBJECT);
+ assertRefs("refs/heads/master", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void oneNonExistentRef() throws IOException {
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/foo1", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs();
+ assertEquals(0, refsChangedEvents);
+ } else {
+ assertResults(cmds, LOCK_FAILURE, OK);
+ assertRefs("refs/heads/foo2", B);
+ assertEquals(1, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void oneRefWrongOldValue() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(B, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, LOCK_FAILURE, OK);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/foo2", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void nonExistentRef() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertRefs("refs/heads/master", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void noRefLog() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+ assertEquals(Collections.singleton("refs/heads/master"), oldLogs.keySet());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ }
+
+ @Test
+ public void reflogDefaultIdent() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch2", A);
+
+ Map<String, ReflogEntry> oldLogs = getLastReflogs(
+ "refs/heads/master", "refs/heads/branch1", "refs/heads/branch2");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch1", CREATE));
+ execute(
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch1", B,
+ "refs/heads/branch2", A);
+ assertEquals(atomic ? 3 : 4, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch1"));
+ assertReflogUnchanged(oldLogs, "refs/heads/branch2");
+ }
+
+ @Test
+ public void reflogAppendStatusNoMessage() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch1", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/branch1", UPDATE_NONFASTFORWARD),
+ new ReceiveCommand(zeroId(), A, "refs/heads/branch2", CREATE));
+ execute(
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .setRefLogMessage(null, true));
+
+ assertResults(cmds, OK, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch1", A,
+ "refs/heads/branch2", A);
+ assertEquals(atomic ? 3 : 5, refsChangedEvents);
+ assertReflogEquals(
+ // Always forced; setAllowNonFastForwards(true) bypasses the check.
+ reflog(A, B, new PersonIdent(diskRepo), "forced-update"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(B, A, new PersonIdent(diskRepo), "forced-update"),
+ getLastReflog("refs/heads/branch1"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "created"),
+ getLastReflog("refs/heads/branch2"));
+ }
+
+ @Test
+ public void reflogAppendStatusFastForward() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage(null, true));
+
+ assertResults(cmds, OK);
+ assertRefs("refs/heads/master", B);
+ assertEquals(2, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "fast-forward"),
+ getLastReflog("refs/heads/master"));
+ }
+
+ @Test
+ public void reflogAppendStatusWithMessage() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/branch", CREATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", A);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog: fast-forward"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog: created"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void reflogCustomIdent() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ PersonIdent ident = new PersonIdent("A Reflog User", "reflog@example.com");
+ execute(
+ newBatchUpdate(cmds)
+ .setRefLogMessage("a reflog", false)
+ .setRefLogIdent(ident));
+
+ assertResults(cmds, OK, OK);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ assertReflogEquals(
+ reflog(A, B, ident, "a reflog"),
+ getLastReflog("refs/heads/master"),
+ true);
+ assertReflogEquals(
+ reflog(zeroId(), B, ident, "a reflog"),
+ getLastReflog("refs/heads/branch"),
+ true);
+ }
+
+ @Test
+ public void reflogDelete() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch", A);
+ assertEquals(
+ 2, getLastReflogs("refs/heads/master", "refs/heads/branch").size());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
+ new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs("refs/heads/branch", B);
+ assertEquals(atomic ? 3 : 4, refsChangedEvents);
+ assertNull(getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void reflogFileDirectoryConflict() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs("refs/heads/master/x", A);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertNull(getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master/x"));
+ }
+
+ @Test
+ public void reflogOnLockFailure() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertEquals(1, refsChangedEvents);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertEquals(2, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ }
+ }
+
+ @Test
+ public void overrideRefLogMessage() throws Exception {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ cmds.get(0).setRefLogMessage("custom log", false);
+ PersonIdent ident = new PersonIdent(diskRepo);
+ execute(
+ newBatchUpdate(cmds)
+ .setRefLogIdent(ident)
+ .setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, ident, "custom log"),
+ getLastReflog("refs/heads/master"),
+ true);
+ assertReflogEquals(
+ reflog(zeroId(), B, ident, "a reflog: created"),
+ getLastReflog("refs/heads/branch"),
+ true);
+ }
+
+ @Test
+ public void overrideDisableRefLog() throws Exception {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ cmds.get(0).disableRefLog();
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog: created"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void refLogNotWrittenWithoutConfigOption() throws Exception {
+ setLogAllRefUpdates(false);
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+ assertTrue(oldLogs.isEmpty());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ }
+
+ @Test
+ public void forceRefLogInUpdate() throws Exception {
+ setLogAllRefUpdates(false);
+ writeRef("refs/heads/master", A);
+ assertTrue(
+ getLastReflogs("refs/heads/master", "refs/heads/branch").isEmpty());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ execute(
+ newBatchUpdate(cmds)
+ .setRefLogMessage("a reflog", false)
+ .setForceRefLog(true));
+
+ assertResults(cmds, OK, OK);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void forceRefLogInCommand() throws Exception {
+ setLogAllRefUpdates(false);
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+ assertTrue(oldLogs.isEmpty());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ cmds.get(1).setForceRefLog(true);
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void packedRefsLockFailure() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(getLockFile("refs/heads/master").exists());
+ assertFalse(getLockFile("refs/heads/branch").exists());
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ // Only operates on loose refs, doesn't care that packed-refs is locked.
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ assertEquals(3, refsChangedEvents);
+ }
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void oneRefLockFailure() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE),
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+
+ LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master"));
+ assertTrue(myLock.lock());
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(LockFile.getLockFile(refdir.packedRefsFile).exists());
+ assertFalse(getLockFile("refs/heads/branch").exists());
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertRefs(
+ "refs/heads/branch", B,
+ "refs/heads/master", A);
+ assertEquals(2, refsChangedEvents);
+ }
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(getLockFile("refs/heads/master").exists());
+ assertResults(cmds, OK);
+ assertEquals(2, refsChangedEvents);
+ assertRefs("refs/heads/master", B);
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void atomicUpdateRespectsInProcessLock() throws Exception {
+ assumeTrue(atomic);
+
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+
+ Thread t = new Thread(() -> {
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ ReentrantLock l = refdir.inProcessPackedRefsLock;
+ l.lock();
+ try {
+ t.start();
+ long timeoutSecs = 10;
+ long startNanos = System.nanoTime();
+
+ // Hold onto the lock until we observe the worker thread has attempted to
+ // acquire it.
+ while (l.getQueueLength() == 0) {
+ long elapsedNanos = System.nanoTime() - startNanos;
+ assertTrue(
+ "timed out waiting for work thread to attempt to acquire lock",
+ NANOSECONDS.toSeconds(elapsedNanos) < timeoutSecs);
+ Thread.sleep(3);
+ }
+
+ // Once we unlock, the worker thread should finish the update promptly.
+ l.unlock();
+ t.join(SECONDS.toMillis(timeoutSecs));
+ assertFalse(t.isAlive());
+ } finally {
+ if (l.isHeldByCurrentThread()) {
+ l.unlock();
+ }
+ }
+
+ assertResults(cmds, OK, OK);
+ assertEquals(2, refsChangedEvents);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ }
+
+ private void setLogAllRefUpdates(boolean enable) throws Exception {
+ StoredConfig cfg = diskRepo.getConfig();
+ cfg.load();
+ cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, enable);
+ cfg.save();
+ }
+
+ private void writeLooseRef(String name, AnyObjectId id) throws IOException {
+ write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
+ }
+
+ private void writeRef(String name, AnyObjectId id) throws IOException {
+ RefUpdate u = diskRepo.updateRef(name);
+ u.setRefLogMessage(getClass().getSimpleName(), false);
+ u.setForceUpdate(true);
+ u.setNewObjectId(id);
+ RefUpdate.Result r = u.update();
+ switch (r) {
+ case NEW:
+ case FORCED:
+ return;
+ default:
+ throw new IOException("Got " + r + " while updating " + name);
+ }
+ }
+
+ private BatchRefUpdate newBatchUpdate(List<ReceiveCommand> cmds) {
+ BatchRefUpdate u = refdir.newBatchUpdate();
+ if (atomic) {
+ assertTrue(u.isAtomic());
+ } else {
+ u.setAtomic(false);
+ }
+ u.addCommand(cmds);
+ return u;
+ }
+
+ private void execute(BatchRefUpdate u) throws IOException {
+ execute(u, false);
+ }
+
+ private void execute(BatchRefUpdate u, boolean strictWork) throws IOException {
+ try (RevWalk rw = new RevWalk(diskRepo)) {
+ u.execute(rw,
+ strictWork ? new StrictWorkMonitor() : NullProgressMonitor.INSTANCE);
+ }
+ }
+
+ private void assertRefs(Object... args) throws IOException {
+ if (args.length % 2 != 0) {
+ throw new IllegalArgumentException(
+ "expected even number of args: " + Arrays.toString(args));
+ }
+
+ Map<String, AnyObjectId> expected = new LinkedHashMap<>();
+ for (int i = 0; i < args.length; i += 2) {
+ expected.put((String) args[i], (AnyObjectId) args[i + 1]);
+ }
+
+ Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
+ Ref actualHead = refs.remove(Constants.HEAD);
+ if (actualHead != null) {
+ String actualLeafName = actualHead.getLeaf().getName();
+ assertEquals(
+ "expected HEAD to point to refs/heads/master, got: " + actualLeafName,
+ "refs/heads/master", actualLeafName);
+ AnyObjectId expectedMaster = expected.get("refs/heads/master");
+ assertNotNull("expected master ref since HEAD exists", expectedMaster);
+ assertEquals(expectedMaster, actualHead.getObjectId());
+ }
+
+ Map<String, AnyObjectId> actual = new LinkedHashMap<>();
+ refs.forEach((n, r) -> actual.put(n, r.getObjectId()));
+
+ assertEquals(expected.keySet(), actual.keySet());
+ actual.forEach((n, a) -> assertEquals(n, expected.get(n), a));
+ }
+
+ enum Result {
+ OK(ReceiveCommand.Result.OK),
+ LOCK_FAILURE(ReceiveCommand.Result.LOCK_FAILURE),
+ REJECTED_NONFASTFORWARD(ReceiveCommand.Result.REJECTED_NONFASTFORWARD),
+ REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT),
+ TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted);
+
+ final Predicate<? super ReceiveCommand> p;
+
+ private Result(Predicate<? super ReceiveCommand> p) {
+ this.p = p;
+ }
+
+ private Result(ReceiveCommand.Result result) {
+ this(c -> c.getResult() == result);
+ }
+ }
+
+ private void assertResults(
+ List<ReceiveCommand> cmds, Result... expected) {
+ if (expected.length != cmds.size()) {
+ throw new IllegalArgumentException(
+ "expected " + cmds.size() + " result args");
+ }
+ for (int i = 0; i < cmds.size(); i++) {
+ ReceiveCommand c = cmds.get(i);
+ Result r = expected[i];
+ assertTrue(
+ String.format(
+ "result of command (%d) should be %s: %s %s%s",
+ Integer.valueOf(i), r, c,
+ c.getResult(),
+ c.getMessage() != null ? " (" + c.getMessage() + ")" : ""),
+ r.p.test(c));
+ }
+ }
+
+ private Map<String, ReflogEntry> getLastReflogs(String... names)
+ throws IOException {
+ Map<String, ReflogEntry> result = new LinkedHashMap<>();
+ for (String name : names) {
+ ReflogEntry e = getLastReflog(name);
+ if (e != null) {
+ result.put(name, e);
+ }
+ }
+ return result;
+ }
+
+ private ReflogEntry getLastReflog(String name) throws IOException {
+ ReflogReader r = diskRepo.getReflogReader(name);
+ if (r == null) {
+ return null;
+ }
+ return r.getLastEntry();
+ }
+
+ private File getLockFile(String refName) {
+ return LockFile.getLockFile(refdir.fileFor(refName));
+ }
+
+ private void assertReflogUnchanged(
+ Map<String, ReflogEntry> old, String name) throws IOException {
+ assertReflogEquals(old.get(name), getLastReflog(name), true);
+ }
+
+ private static void assertReflogEquals(
+ ReflogEntry expected, ReflogEntry actual) {
+ assertReflogEquals(expected, actual, false);
+ }
+
+ private static void assertReflogEquals(
+ ReflogEntry expected, ReflogEntry actual, boolean strictTime) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.getOldId(), actual.getOldId());
+ assertEquals(expected.getNewId(), actual.getNewId());
+ if (strictTime) {
+ assertEquals(expected.getWho(), actual.getWho());
+ } else {
+ assertEquals(expected.getWho().getName(), actual.getWho().getName());
+ assertEquals(
+ expected.getWho().getEmailAddress(),
+ actual.getWho().getEmailAddress());
+ }
+ assertEquals(expected.getComment(), actual.getComment());
+ }
+
+ private static ReflogEntry reflog(ObjectId oldId, ObjectId newId,
+ PersonIdent who, String comment) {
+ return new ReflogEntry() {
+ @Override
+ public ObjectId getOldId() {
+ return oldId;
+ }
+
+ @Override
+ public ObjectId getNewId() {
+ return newId;
+ }
+
+ @Override
+ public PersonIdent getWho() {
+ return who;
+ }
+
+ @Override
+ public String getComment() {
+ return comment;
+ }
+
+ @Override
+ public CheckoutEntry parseCheckout() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index 11a2a22edb..c43bdbd298 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -43,12 +43,13 @@
package org.eclipse.jgit.internal.storage.file;
-import static java.lang.Integer.valueOf;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.IOException;
@@ -74,6 +75,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.junit.Test;
+@SuppressWarnings("boxing")
public class GcPackRefsTest extends GcTestCase {
@Test
public void looseRefPacked() throws Exception {
@@ -100,27 +102,23 @@ public class GcPackRefsTest extends GcTestCase {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);
- final CyclicBarrier syncPoint = new CyclicBarrier(2);
+ CyclicBarrier syncPoint = new CyclicBarrier(2);
- Callable<Integer> packRefs = new Callable<Integer>() {
-
- /** @return 0 for success, 1 in case of error when writing pack */
- @Override
- public Integer call() throws Exception {
- syncPoint.await();
- try {
- gc.packRefs();
- return valueOf(0);
- } catch (IOException e) {
- return valueOf(1);
- }
+ // Returns 0 for success, 1 in case of error when writing pack.
+ Callable<Integer> packRefs = () -> {
+ syncPoint.await();
+ try {
+ gc.packRefs();
+ return 0;
+ } catch (IOException e) {
+ return 1;
}
};
ExecutorService pool = Executors.newFixedThreadPool(2);
try {
Future<Integer> p1 = pool.submit(packRefs);
Future<Integer> p2 = pool.submit(packRefs);
- assertEquals(1, p1.get().intValue() + p2.get().intValue());
+ assertThat(p1.get() + p2.get(), lessThanOrEqualTo(1));
} finally {
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java
new file mode 100644
index 0000000000..59d544e63f
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 Ericsson
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.time.Instant;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class GcTemporaryFilesTest extends GcTestCase {
+ private static final String TEMP_IDX = "gc_1234567890.idx_tmp";
+
+ private static final String TEMP_PACK = "gc_1234567890.pack_tmp";
+
+ private File packDir;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ packDir = Paths.get(repo.getObjectsDirectory().getAbsolutePath(),
+ "pack").toFile(); //$NON-NLS-1$
+ }
+
+ @Test
+ public void oldTempPacksAndIdxAreDeleted() throws Exception {
+ File tempIndex = new File(packDir, TEMP_IDX);
+ File tempPack = new File(packDir, TEMP_PACK);
+ if (!packDir.exists() || !packDir.isDirectory()) {
+ assertTrue(packDir.mkdirs());
+ }
+ assertTrue(tempPack.createNewFile());
+ assertTrue(tempIndex.createNewFile());
+ assertTrue(tempIndex.exists());
+ assertTrue(tempPack.exists());
+ long _24HoursBefore = Instant.now().toEpochMilli()
+ - 24 * 60 * 62 * 1000;
+ tempIndex.setLastModified(_24HoursBefore);
+ tempPack.setLastModified(_24HoursBefore);
+ gc.gc();
+ assertFalse(tempIndex.exists());
+ assertFalse(tempPack.exists());
+ }
+
+ @Test
+ public void recentTempPacksAndIdxAreNotDeleted() throws Exception {
+ File tempIndex = new File(packDir, TEMP_IDX);
+ File tempPack = new File(packDir, TEMP_PACK);
+ if (!packDir.exists() || !packDir.isDirectory()) {
+ assertTrue(packDir.mkdirs());
+ }
+ assertTrue(tempPack.createNewFile());
+ assertTrue(tempIndex.createNewFile());
+ assertTrue(tempIndex.exists());
+ assertTrue(tempPack.exists());
+ gc.gc();
+ assertTrue(tempIndex.exists());
+ assertTrue(tempPack.exists());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java
new file mode 100644
index 0000000000..8596f74f81
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.storage.file;
+
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.util.IO;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@SuppressWarnings("boxing")
+public class PackInserterTest extends RepositoryTestCase {
+ private WindowCacheConfig origWindowCacheConfig;
+
+ @Before
+ public void setWindowCacheConfig() {
+ origWindowCacheConfig = new WindowCacheConfig();
+ origWindowCacheConfig.install();
+ }
+
+ @After
+ public void resetWindowCacheConfig() {
+ origWindowCacheConfig.install();
+ }
+
+ @Before
+ public void emptyAtSetUp() throws Exception {
+ assertEquals(0, listPacks().size());
+ assertNoObjects();
+ }
+
+ @Test
+ public void noFlush() throws Exception {
+ try (PackInserter ins = newInserter()) {
+ ins.insert(OBJ_BLOB, Constants.encode("foo contents"));
+ // No flush.
+ }
+ assertNoObjects();
+ }
+
+ @Test
+ public void flushEmptyPack() throws Exception {
+ try (PackInserter ins = newInserter()) {
+ ins.flush();
+ }
+ assertNoObjects();
+ }
+
+ @Test
+ public void singlePack() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+ ObjectId treeId;
+ ObjectId commitId;
+ byte[] commit;
+ try (PackInserter ins = newInserter()) {
+ blobId = ins.insert(OBJ_BLOB, blob);
+
+ DirCache dc = DirCache.newInCore();
+ DirCacheBuilder b = dc.builder();
+ DirCacheEntry dce = new DirCacheEntry("foo");
+ dce.setFileMode(FileMode.REGULAR_FILE);
+ dce.setObjectId(blobId);
+ b.add(dce);
+ b.finish();
+ treeId = dc.writeTree(ins);
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(treeId);
+ cb.setAuthor(author);
+ cb.setCommitter(committer);
+ cb.setMessage("Commit message");
+ commit = cb.toByteArray();
+ commitId = ins.insert(cb);
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ List<PackFile> packs = listPacks();
+ assertEquals(1, packs.size());
+ assertEquals(3, packs.get(0).getObjectCount());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId, blob);
+
+ CanonicalTreeParser treeParser =
+ new CanonicalTreeParser(null, reader, treeId);
+ assertEquals("foo", treeParser.getEntryPathString());
+ assertEquals(blobId, treeParser.getEntryObjectId());
+
+ ObjectLoader commitLoader = reader.open(commitId);
+ assertEquals(OBJ_COMMIT, commitLoader.getType());
+ assertArrayEquals(commit, commitLoader.getBytes());
+ }
+ }
+
+ @Test
+ public void multiplePacks() throws Exception {
+ ObjectId blobId1;
+ ObjectId blobId2;
+ byte[] blob1 = Constants.encode("blob1");
+ byte[] blob2 = Constants.encode("blob2");
+
+ try (PackInserter ins = newInserter()) {
+ blobId1 = ins.insert(OBJ_BLOB, blob1);
+ ins.flush();
+ blobId2 = ins.insert(OBJ_BLOB, blob2);
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ List<PackFile> packs = listPacks();
+ assertEquals(2, packs.size());
+ assertEquals(1, packs.get(0).getObjectCount());
+ assertEquals(1, packs.get(1).getObjectCount());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId1, blob1);
+ assertBlob(reader, blobId2, blob2);
+ }
+ }
+
+ @Test
+ public void largeBlob() throws Exception {
+ ObjectId blobId;
+ byte[] blob = newLargeBlob();
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob.length, greaterThan(ins.getBufferSize()));
+ blobId =
+ ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ Collection<PackFile> packs = listPacks();
+ assertEquals(1, packs.size());
+ PackFile p = packs.iterator().next();
+ assertEquals(1, p.getObjectCount());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId, blob);
+ }
+ }
+
+ @Test
+ public void overwriteExistingPack() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+
+ try (PackInserter ins = newInserter()) {
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ List<PackFile> packs = listPacks();
+ assertEquals(1, packs.size());
+ PackFile pack = packs.get(0);
+ assertEquals(1, pack.getObjectCount());
+
+ String inode = getInode(pack.getPackFile());
+
+ try (PackInserter ins = newInserter()) {
+ ins.checkExisting(false);
+ assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ packs = listPacks();
+ assertEquals(1, packs.size());
+ pack = packs.get(0);
+ assertEquals(1, pack.getObjectCount());
+
+ if (inode != null) {
+ // Old file was overwritten with new file, although objects were
+ // equivalent.
+ assertNotEquals(inode, getInode(pack.getPackFile()));
+ }
+ }
+
+ @Test
+ public void checkExisting() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+
+ try (PackInserter ins = newInserter()) {
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.insert(OBJ_BLOB, Constants.encode("another blob"));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+
+ try (PackInserter ins = newInserter()) {
+ assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+
+ try (PackInserter ins = newInserter()) {
+ ins.checkExisting(false);
+ assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(2, listPacks().size());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId, blob);
+ }
+ }
+
+ @Test
+ public void insertSmallInputStreamRespectsCheckExisting() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob.length, lessThan(ins.getBufferSize()));
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.insert(OBJ_BLOB, Constants.encode("another blob"));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+
+ try (PackInserter ins = newInserter()) {
+ assertEquals(blobId,
+ ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob)));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+ }
+
+ @Test
+ public void insertLargeInputStreamBypassesCheckExisting() throws Exception {
+ ObjectId blobId;
+ byte[] blob = newLargeBlob();
+
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob.length, greaterThan(ins.getBufferSize()));
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.insert(OBJ_BLOB, Constants.encode("another blob"));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+
+ try (PackInserter ins = newInserter()) {
+ assertEquals(blobId,
+ ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob)));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(2, listPacks().size());
+ }
+
+ @Test
+ public void readBackSmallFiles() throws Exception {
+ ObjectId blobId1;
+ ObjectId blobId2;
+ ObjectId blobId3;
+ byte[] blob1 = Constants.encode("blob1");
+ byte[] blob2 = Constants.encode("blob2");
+ byte[] blob3 = Constants.encode("blob3");
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob1.length, lessThan(ins.getBufferSize()));
+ blobId1 = ins.insert(OBJ_BLOB, blob1);
+
+ try (ObjectReader reader = ins.newReader()) {
+ assertBlob(reader, blobId1, blob1);
+ }
+
+ // Read-back should not mess up the file pointer.
+ blobId2 = ins.insert(OBJ_BLOB, blob2);
+ ins.flush();
+
+ blobId3 = ins.insert(OBJ_BLOB, blob3);
+ }
+
+ assertPacksOnly();
+ List<PackFile> packs = listPacks();
+ assertEquals(1, packs.size());
+ assertEquals(2, packs.get(0).getObjectCount());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId1, blob1);
+ assertBlob(reader, blobId2, blob2);
+
+ try {
+ reader.open(blobId3);
+ fail("Expected MissingObjectException");
+ } catch (MissingObjectException expected) {
+ // Expected.
+ }
+ }
+ }
+
+ @Test
+ public void readBackLargeFile() throws Exception {
+ ObjectId blobId;
+ byte[] blob = newLargeBlob();
+
+ WindowCacheConfig wcc = new WindowCacheConfig();
+ wcc.setStreamFileThreshold(1024);
+ wcc.install();
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertThat(blob.length, greaterThan(reader.getStreamFileThreshold()));
+ }
+
+ try (PackInserter ins = newInserter()) {
+ blobId = ins.insert(OBJ_BLOB, blob);
+
+ try (ObjectReader reader = ins.newReader()) {
+ // Double-check threshold is propagated.
+ assertThat(blob.length, greaterThan(reader.getStreamFileThreshold()));
+ assertBlob(reader, blobId, blob);
+ }
+ }
+
+ assertPacksOnly();
+ // Pack was streamed out to disk and read back from the temp file, but
+ // ultimately rolled back and deleted.
+ assertEquals(0, listPacks().size());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ try {
+ reader.open(blobId);
+ fail("Expected MissingObjectException");
+ } catch (MissingObjectException expected) {
+ // Expected.
+ }
+ }
+ }
+
+ @Test
+ public void readBackFallsBackToRepo() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob.length, lessThan(ins.getBufferSize()));
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.flush();
+ }
+
+ try (PackInserter ins = newInserter();
+ ObjectReader reader = ins.newReader()) {
+ assertBlob(reader, blobId, blob);
+ }
+ }
+
+ @Test
+ public void readBackSmallObjectBeforeLargeObject() throws Exception {
+ WindowCacheConfig wcc = new WindowCacheConfig();
+ wcc.setStreamFileThreshold(1024);
+ wcc.install();
+
+ ObjectId blobId1;
+ ObjectId blobId2;
+ ObjectId largeId;
+ byte[] blob1 = Constants.encode("blob1");
+ byte[] blob2 = Constants.encode("blob2");
+ byte[] largeBlob = newLargeBlob();
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob1.length, lessThan(ins.getBufferSize()));
+ assertThat(largeBlob.length, greaterThan(ins.getBufferSize()));
+
+ blobId1 = ins.insert(OBJ_BLOB, blob1);
+ largeId = ins.insert(OBJ_BLOB, largeBlob);
+
+ try (ObjectReader reader = ins.newReader()) {
+ // A previous bug did not reset the file pointer to EOF after reading
+ // back. We need to seek to something further back than a full buffer,
+ // since the read-back code eagerly reads a full buffer's worth of data
+ // from the file to pass to the inflater. If we seeked back just a small
+ // amount, this step would consume the rest of the file, so the file
+ // pointer would coincidentally end up back at EOF, hiding the bug.
+ assertBlob(reader, blobId1, blob1);
+ }
+
+ blobId2 = ins.insert(OBJ_BLOB, blob2);
+
+ try (ObjectReader reader = ins.newReader()) {
+ assertBlob(reader, blobId1, blob1);
+ assertBlob(reader, blobId2, blob2);
+ assertBlob(reader, largeId, largeBlob);
+ }
+
+ ins.flush();
+ }
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId1, blob1);
+ assertBlob(reader, blobId2, blob2);
+ assertBlob(reader, largeId, largeBlob);
+ }
+ }
+
+ private List<PackFile> listPacks() throws Exception {
+ List<PackFile> fromOpenDb = listPacks(db);
+ List<PackFile> reopened;
+ try (FileRepository db2 = new FileRepository(db.getDirectory())) {
+ reopened = listPacks(db2);
+ }
+ assertEquals(fromOpenDb.size(), reopened.size());
+ for (int i = 0 ; i < fromOpenDb.size(); i++) {
+ PackFile a = fromOpenDb.get(i);
+ PackFile b = reopened.get(i);
+ assertEquals(a.getPackName(), b.getPackName());
+ assertEquals(
+ a.getPackFile().getAbsolutePath(), b.getPackFile().getAbsolutePath());
+ assertEquals(a.getObjectCount(), b.getObjectCount());
+ a.getObjectCount();
+ }
+ return fromOpenDb;
+ }
+
+ private static List<PackFile> listPacks(FileRepository db) throws Exception {
+ return db.getObjectDatabase().getPacks().stream()
+ .sorted(comparing(PackFile::getPackName)).collect(toList());
+ }
+
+ private PackInserter newInserter() {
+ return db.getObjectDatabase().newPackInserter();
+ }
+
+ private static byte[] newLargeBlob() {
+ byte[] blob = new byte[10240];
+ new Random(0).nextBytes(blob);
+ return blob;
+ }
+
+ private static String getInode(File f) throws Exception {
+ BasicFileAttributes attrs = Files.readAttributes(
+ f.toPath(), BasicFileAttributes.class);
+ Object k = attrs.fileKey();
+ if (k == null) {
+ return null;
+ }
+ Pattern p = Pattern.compile("^\\(dev=[^,]*,ino=(\\d+)\\)$");
+ Matcher m = p.matcher(k.toString());
+ return m.matches() ? m.group(1) : null;
+ }
+
+ private static void assertBlob(ObjectReader reader, ObjectId id,
+ byte[] expected) throws Exception {
+ ObjectLoader loader = reader.open(id);
+ assertEquals(OBJ_BLOB, loader.getType());
+ assertEquals(expected.length, loader.getSize());
+ try (ObjectStream s = loader.openStream()) {
+ int n = (int) s.getSize();
+ byte[] actual = new byte[n];
+ assertEquals(n, IO.readFully(s, actual, 0));
+ assertArrayEquals(expected, actual);
+ }
+ }
+
+ private void assertPacksOnly() throws Exception {
+ new BadFileCollector(f -> !f.endsWith(".pack") && !f.endsWith(".idx"))
+ .assertNoBadFiles(db.getObjectDatabase().getDirectory());
+ }
+
+ private void assertNoObjects() throws Exception {
+ new BadFileCollector(f -> true)
+ .assertNoBadFiles(db.getObjectDatabase().getDirectory());
+ }
+
+ private static class BadFileCollector extends SimpleFileVisitor<Path> {
+ private final Predicate<String> badName;
+ private List<String> bad;
+
+ BadFileCollector(Predicate<String> badName) {
+ this.badName = badName;
+ }
+
+ void assertNoBadFiles(File f) throws IOException {
+ bad = new ArrayList<>();
+ Files.walkFileTree(f.toPath(), this);
+ if (!bad.isEmpty()) {
+ fail("unexpected files in object directory: " + bad);
+ }
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ String name = file.getFileName().toString();
+ if (!attrs.isDirectory() && badName.test(name)) {
+ bad.add(name);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 53db123d34..fefccf314f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -61,32 +61,27 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceiveCommand.Type;
import org.junit.Before;
import org.junit.Test;
+@SuppressWarnings("boxing")
public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
private Repository diskRepo;
@@ -1293,125 +1288,20 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
}
@Test
- public void testBatchRefUpdateSimpleNoForce() throws IOException {
+ public void testPackedRefsLockFailure() throws Exception {
writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(B, A, "refs/heads/masters",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.REJECTED_NONFASTFORWARD, commands
- .get(1).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateSimpleForce() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(B, A, "refs/heads/masters",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheck()
- throws IOException {
- writeLooseRef("refs/heads/master", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(B, A, "refs/heads/master",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo) {
- @Override
- public boolean isMergedInto(RevCommit base, RevCommit tip) {
- throw new AssertionError("isMergedInto() should not be called");
- }
- }, new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateConflict() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(null, A, "refs/heads/master/x",
- ReceiveCommand.Type.CREATE),
- newCommand(null, A, "refs/heads", ReceiveCommand.Type.CREATE));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate
- .execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE);
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(1)
- .getResult());
- assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(2)
- .getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateConflictThanksToDelete() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(null, A, "refs/heads/masters/x",
- ReceiveCommand.Type.CREATE),
- newCommand(B, null, "refs/heads/masters",
- ReceiveCommand.Type.DELETE));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(2).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters/x]", refs
- .keySet().toString());
- assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId());
- }
-
- private static ReceiveCommand newCommand(RevCommit a, RevCommit b,
- String string, Type update) {
- return new ReceiveCommand(a != null ? a.getId() : null,
- b != null ? b.getId() : null, string, update);
+ refdir.setRetrySleepMs(Arrays.asList(0, 0));
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ refdir.pack(Arrays.asList("refs/heads/master"));
+ fail("expected LockFailedException");
+ } catch (LockFailedException e) {
+ assertEquals(refdir.packedRefsFile.getPath(), e.getFile().getPath());
+ } finally {
+ myLock.unlock();
+ }
+ Ref ref = refdir.getRef("refs/heads/master");
+ assertEquals(Storage.LOOSE, ref.getStorage());
}
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
@@ -1439,34 +1329,4 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
File path = new File(diskRepo.getDirectory(), name);
assertTrue("deleted " + name, path.delete());
}
-
- private static final class StrictWorkMonitor implements ProgressMonitor {
- private int lastWork, totalWork;
-
- @Override
- public void start(int totalTasks) {
- // empty
- }
-
- @Override
- public void beginTask(String title, int total) {
- this.totalWork = total;
- lastWork = 0;
- }
-
- @Override
- public void update(int completed) {
- lastWork += completed;
- }
-
- @Override
- public void endTask() {
- assertEquals("Units of work recorded", totalWork, lastWork);
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
- }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index 0f26b0fa61..d8d45a85b1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.internal.storage.file;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.junit.Assert.assertEquals;
import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
import static org.junit.Assert.assertEquals;
@@ -65,6 +66,7 @@ import java.util.Map.Entry;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefRename;
@@ -241,14 +243,73 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
@Test
public void testDeleteHeadInBareRepo() throws IOException {
Repository bareRepo = createBareRepository();
+ String master = "refs/heads/master";
+ Ref head = bareRepo.exactRef(Constants.HEAD);
+ assertNotNull(head);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertNull(head.getObjectId());
+ assertNull(bareRepo.exactRef(master));
+
+ ObjectId blobId;
+ try (ObjectInserter ins = bareRepo.newObjectInserter()) {
+ blobId = ins.insert(Constants.OBJ_BLOB, "contents".getBytes(UTF_8));
+ ins.flush();
+ }
+
+ // Create master via HEAD, so we delete it.
RefUpdate ref = bareRepo.updateRef(Constants.HEAD);
- ref.setNewObjectId(ObjectId
- .fromString("0123456789012345678901234567890123456789"));
- // Create the HEAD ref so we can delete it.
+ ref.setNewObjectId(blobId);
assertEquals(Result.NEW, ref.update());
+
+ head = bareRepo.exactRef(Constants.HEAD);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertEquals(blobId, head.getLeaf().getObjectId());
+ assertEquals(blobId, bareRepo.exactRef(master).getObjectId());
+
+ // Unlike in a non-bare repo, deleting the HEAD is allowed, and leaves HEAD
+ // back in a dangling state.
ref = bareRepo.updateRef(Constants.HEAD);
- delete(bareRepo, ref, Result.NO_CHANGE, true, true);
+ ref.setExpectedOldObjectId(blobId);
+ ref.setForceUpdate(true);
+ delete(bareRepo, ref, Result.FORCED, true, true);
+
+ head = bareRepo.exactRef(Constants.HEAD);
+ assertNotNull(head);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertNull(head.getObjectId());
+ assertNull(bareRepo.exactRef(master));
+ }
+
+ @Test
+ public void testDeleteSymref() throws IOException {
+ RefUpdate dst = updateRef("refs/heads/abc");
+ assertEquals(Result.NEW, dst.update());
+ ObjectId id = dst.getNewObjectId();
+
+ RefUpdate u = db.updateRef("refs/symref");
+ assertEquals(Result.NEW, u.link(dst.getName()));
+
+ Ref ref = db.exactRef(u.getName());
+ assertNotNull(ref);
+ assertTrue(ref.isSymbolic());
+ assertEquals(dst.getName(), ref.getLeaf().getName());
+ assertEquals(id, ref.getLeaf().getObjectId());
+
+ u = db.updateRef(u.getName());
+ u.setDetachingSymbolicRef();
+ u.setForceUpdate(true);
+ assertEquals(Result.FORCED, u.delete());
+
+ assertNull(db.exactRef(u.getName()));
+ ref = db.exactRef(dst.getName());
+ assertNotNull(ref);
+ assertFalse(ref.isSymbolic());
+ assertEquals(id, ref.getObjectId());
}
+
/**
* Delete a loose ref and make sure the directory in refs is deleted too,
* and the reflog dir too
@@ -899,12 +960,66 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
"HEAD").getReverseEntries().get(0).getComment());
}
+ @Test
+ public void testCreateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ RefUpdate ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ Result update = ru.update();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNull(ref);
+ }
+
+ @Test
+ public void testUpdateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ RefUpdate ru = updateRef(name);
+ Result update = ru.update();
+ assertEquals(Result.NEW, update);
+ ObjectId oldId = ru.getNewObjectId();
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ update = ru.update();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNotNull(ref);
+ assertEquals(oldId, ref.getObjectId());
+ }
+
+ @Test
+ public void testForceUpdateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ RefUpdate ru = updateRef(name);
+ Result update = ru.update();
+ assertEquals(Result.NEW, update);
+ ObjectId oldId = ru.getNewObjectId();
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ update = ru.forceUpdate();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNotNull(ref);
+ assertEquals(oldId, ref.getObjectId());
+ }
+
private static void writeReflog(Repository db, ObjectId newId, String msg,
String refName) throws IOException {
RefDirectory refs = (RefDirectory) db.getRefDatabase();
RefDirectoryUpdate update = refs.newUpdate(refName, true);
update.setNewObjectId(newId);
- refs.log(update, msg, true);
+ refs.log(false, update, msg, true);
}
private static class SubclassedId extends ObjectId {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
index 89b969e3f1..7f40bae556 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
@@ -61,7 +61,8 @@ public class ReflogWriterTest extends SampleDataRepositoryTestCase {
@Test
public void shouldFilterLineFeedFromMessage() throws Exception {
- ReflogWriter writer = new ReflogWriter(db);
+ ReflogWriter writer =
+ new ReflogWriter((RefDirectory) db.getRefDatabase());
PersonIdent ident = new PersonIdent("John Doe", "john@doe.com",
1243028200000L, 120);
ObjectId oldId = ObjectId
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
index ae1e531b83..9d23d8334c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
@@ -661,33 +661,39 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
@Test
public void test028_LockPackedRef() throws IOException {
+ ObjectId id1;
+ ObjectId id2;
+ try (ObjectInserter ins = db.newObjectInserter()) {
+ id1 = ins.insert(
+ Constants.OBJ_BLOB, "contents1".getBytes(Constants.CHARSET));
+ id2 = ins.insert(
+ Constants.OBJ_BLOB, "contents2".getBytes(Constants.CHARSET));
+ ins.flush();
+ }
+
writeTrashFile(".git/packed-refs",
- "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar");
+ id1.name() + " refs/heads/foobar");
writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n");
BUG_WorkAroundRacyGitIssues("packed-refs");
BUG_WorkAroundRacyGitIssues("HEAD");
ObjectId resolve = db.resolve("HEAD");
- assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name());
+ assertEquals(id1, resolve);
RefUpdate lockRef = db.updateRef("HEAD");
- ObjectId newId = ObjectId
- .fromString("07f822839a2fe9760f386cbbbcb3f92c5fe81def");
- lockRef.setNewObjectId(newId);
+ lockRef.setNewObjectId(id2);
assertEquals(RefUpdate.Result.FORCED, lockRef.forceUpdate());
assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists());
- assertEquals(newId, db.resolve("refs/heads/foobar"));
+ assertEquals(id2, db.resolve("refs/heads/foobar"));
// Again. The ref already exists
RefUpdate lockRef2 = db.updateRef("HEAD");
- ObjectId newId2 = ObjectId
- .fromString("7f822839a2fe9760f386cbbbcb3f92c5fe81def7");
- lockRef2.setNewObjectId(newId2);
+ lockRef2.setNewObjectId(id1);
assertEquals(RefUpdate.Result.FORCED, lockRef2.forceUpdate());
assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists());
- assertEquals(newId2, db.resolve("refs/heads/foobar"));
+ assertEquals(id1, db.resolve("refs/heads/foobar"));
}
@Test
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
new file mode 100644
index 0000000000..adba048e65
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.storage.reftable;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+import org.junit.Test;
+
+public class MergedReftableTest {
+ @Test
+ public void noTables() throws IOException {
+ MergedReftable mr = merge(new byte[0][]);
+ try (RefCursor rc = mr.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(R_HEADS)) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void oneEmptyTable() throws IOException {
+ MergedReftable mr = merge(write());
+ try (RefCursor rc = mr.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(R_HEADS)) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void twoEmptyTables() throws IOException {
+ MergedReftable mr = merge(write(), write());
+ try (RefCursor rc = mr.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(R_HEADS)) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void oneTableScan() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ refs.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ MergedReftable mr = merge(write(refs));
+ try (RefCursor rc = mr.allRefs()) {
+ for (Ref exp : refs) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ }
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void deleteIsHidden() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(delete("refs/heads/apple"));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.allRefs()) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void twoTableSeek() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(ref("refs/heads/banana", 3));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.seekRef("refs/heads/master")) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void twoTableById() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(ref("refs/heads/banana", 3));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.byObjectId(id(2))) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void fourTableScan() throws IOException {
+ List<Ref> base = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ base.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/next", 4),
+ ref(String.format("refs/heads/%03d", 55), 4096));
+ List<Ref> delta2 = Arrays.asList(
+ delete("refs/heads/next"),
+ ref(String.format("refs/heads/%03d", 55), 8192));
+ List<Ref> delta3 = Arrays.asList(
+ ref("refs/heads/master", 4242),
+ ref(String.format("refs/heads/%03d", 42), 5120),
+ ref(String.format("refs/heads/%03d", 98), 6120));
+
+ List<Ref> expected = merge(base, delta1, delta2, delta3);
+ MergedReftable mr = merge(
+ write(base),
+ write(delta1),
+ write(delta2),
+ write(delta3));
+ try (RefCursor rc = mr.allRefs()) {
+ for (Ref exp : expected) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ }
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void scanIncludeDeletes() throws IOException {
+ List<Ref> delta1 = Arrays.asList(ref("refs/heads/next", 4));
+ List<Ref> delta2 = Arrays.asList(delete("refs/heads/next"));
+ List<Ref> delta3 = Arrays.asList(ref("refs/heads/master", 8));
+
+ MergedReftable mr = merge(write(delta1), write(delta2), write(delta3));
+ mr.setIncludeDeletes(true);
+ try (RefCursor rc = mr.allRefs()) {
+ assertTrue(rc.next());
+ Ref r = rc.getRef();
+ assertEquals("refs/heads/master", r.getName());
+ assertEquals(id(8), r.getObjectId());
+
+ assertTrue(rc.next());
+ r = rc.getRef();
+ assertEquals("refs/heads/next", r.getName());
+ assertEquals(NEW, r.getStorage());
+ assertNull(r.getObjectId());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void oneTableSeek() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ refs.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ MergedReftable mr = merge(write(refs));
+ for (Ref exp : refs) {
+ try (RefCursor rc = mr.seekRef(exp.getName())) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+ }
+
+ @Test
+ public void missedUpdate() throws IOException {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter()
+ .setMinUpdateIndex(1)
+ .setMaxUpdateIndex(3)
+ .begin(buf);
+ writer.writeRef(ref("refs/heads/a", 1), 1);
+ writer.writeRef(ref("refs/heads/c", 3), 3);
+ writer.finish();
+ byte[] base = buf.toByteArray();
+
+ byte[] delta = write(Arrays.asList(
+ ref("refs/heads/b", 2),
+ ref("refs/heads/c", 4)),
+ 2);
+ MergedReftable mr = merge(base, delta);
+ try (RefCursor rc = mr.allRefs()) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/a", rc.getRef().getName());
+ assertEquals(id(1), rc.getRef().getObjectId());
+ assertEquals(1, rc.getUpdateIndex());
+
+ assertTrue(rc.next());
+ assertEquals("refs/heads/b", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertEquals(2, rc.getUpdateIndex());
+
+ assertTrue(rc.next());
+ assertEquals("refs/heads/c", rc.getRef().getName());
+ assertEquals(id(3), rc.getRef().getObjectId());
+ assertEquals(3, rc.getUpdateIndex());
+ }
+ }
+
+ @Test
+ public void compaction() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/next", 4),
+ ref("refs/heads/master", 1));
+ List<Ref> delta2 = Arrays.asList(delete("refs/heads/next"));
+ List<Ref> delta3 = Arrays.asList(ref("refs/heads/master", 8));
+
+ ReftableCompactor compactor = new ReftableCompactor();
+ compactor.addAll(Arrays.asList(
+ read(write(delta1)),
+ read(write(delta2)),
+ read(write(delta3))));
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ compactor.compact(out);
+ byte[] table = out.toByteArray();
+
+ ReftableReader reader = read(table);
+ try (RefCursor rc = reader.allRefs()) {
+ assertTrue(rc.next());
+ Ref r = rc.getRef();
+ assertEquals("refs/heads/master", r.getName());
+ assertEquals(id(8), r.getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ private static MergedReftable merge(byte[]... table) {
+ List<Reftable> stack = new ArrayList<>(table.length);
+ for (byte[] b : table) {
+ stack.add(read(b));
+ }
+ return new MergedReftable(stack);
+ }
+
+ private static ReftableReader read(byte[] table) {
+ return new ReftableReader(BlockSource.from(table));
+ }
+
+ private static Ref ref(String name, int id) {
+ return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
+ }
+
+ private static Ref delete(String name) {
+ return new ObjectIdRef.Unpeeled(NEW, name, null);
+ }
+
+ private static ObjectId id(int i) {
+ byte[] buf = new byte[OBJECT_ID_LENGTH];
+ buf[0] = (byte) (i & 0xff);
+ buf[1] = (byte) ((i >>> 8) & 0xff);
+ buf[2] = (byte) ((i >>> 16) & 0xff);
+ buf[3] = (byte) (i >>> 24);
+ return ObjectId.fromRaw(buf);
+ }
+
+ private byte[] write(Ref... refs) throws IOException {
+ return write(Arrays.asList(refs));
+ }
+
+ private byte[] write(Collection<Ref> refs) throws IOException {
+ return write(refs, 1);
+ }
+
+ private byte[] write(Collection<Ref> refs, long updateIndex)
+ throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ new ReftableWriter()
+ .setMinUpdateIndex(updateIndex)
+ .setMaxUpdateIndex(updateIndex)
+ .begin(buffer)
+ .sortAndWriteRefs(refs)
+ .finish();
+ return buffer.toByteArray();
+ }
+
+ @SafeVarargs
+ private static List<Ref> merge(List<Ref>... tables) {
+ Map<String, Ref> expect = new HashMap<>();
+ for (List<Ref> t : tables) {
+ for (Ref r : t) {
+ if (r.getStorage() == NEW && r.getObjectId() == null) {
+ expect.remove(r.getName());
+ } else {
+ expect.put(r.getName(), r);
+ }
+ }
+ }
+
+ List<Ref> expected = new ArrayList<>(expect.values());
+ Collections.sort(expected, RefComparator.INSTANCE);
+ return expected;
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
new file mode 100644
index 0000000000..3ea3061e38
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.storage.reftable;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.junit.Test;
+
+public class ReftableTest {
+ private static final String MASTER = "refs/heads/master";
+ private static final String NEXT = "refs/heads/next";
+ private static final String V1_0 = "refs/tags/v1.0";
+
+ private Stats stats;
+
+ @Test
+ public void emptyTable() throws IOException {
+ byte[] table = write();
+ assertEquals(92 /* header, footer */, table.length);
+ assertEquals('R', table[0]);
+ assertEquals('E', table[1]);
+ assertEquals('F', table[2]);
+ assertEquals('T', table[3]);
+ assertEquals(0x01, table[4]);
+ assertTrue(ReftableConstants.isFileHeaderMagic(table, 0, 8));
+ assertTrue(ReftableConstants.isFileHeaderMagic(table, 24, 92));
+
+ Reftable t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef(R_HEADS)) {
+ assertFalse(rc.next());
+ }
+ try (LogCursor rc = t.allLogs()) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void emptyVirtualTableFromRefs() throws IOException {
+ Reftable t = Reftable.from(Collections.emptyList());
+ try (RefCursor rc = t.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (LogCursor rc = t.allLogs()) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void estimateCurrentBytesOneRef() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ int expBytes = 24 + 4 + 5 + 4 + MASTER.length() + 20 + 68;
+
+ byte[] table;
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setIndexObjects(false);
+ ReftableWriter writer = new ReftableWriter().setConfig(cfg);
+ try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
+ writer.begin(buf);
+ assertEquals(92, writer.estimateTotalBytes());
+ writer.writeRef(exp);
+ assertEquals(expBytes, writer.estimateTotalBytes());
+ writer.finish();
+ table = buf.toByteArray();
+ }
+ assertEquals(expBytes, table.length);
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void estimateCurrentBytesWithIndex() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5670; i++) {
+ refs.add(ref(String.format("refs/heads/%04d", i), i));
+ }
+
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setIndexObjects(false);
+ cfg.setMaxIndexLevels(1);
+
+ int expBytes = 147860;
+ byte[] table;
+ ReftableWriter writer = new ReftableWriter().setConfig(cfg);
+ try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
+ writer.begin(buf);
+ writer.sortAndWriteRefs(refs);
+ assertEquals(expBytes, writer.estimateTotalBytes());
+ writer.finish();
+ stats = writer.getStats();
+ table = buf.toByteArray();
+ }
+ assertEquals(1, stats.refIndexLevels());
+ assertEquals(expBytes, table.length);
+ }
+
+ @Test
+ public void oneIdRef() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ byte[] table = write(exp);
+ assertEquals(24 + 4 + 5 + 4 + MASTER.length() + 20 + 68, table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertEquals(PACKED, act.getStorage());
+ assertTrue(act.isPeeled());
+ assertFalse(act.isSymbolic());
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ assertNull(act.getPeeledObjectId());
+ assertFalse(rc.wasDeleted());
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef(MASTER)) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertEquals(exp.getName(), act.getName());
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void oneTagRef() throws IOException {
+ Ref exp = tag(V1_0, 1, 2);
+ byte[] table = write(exp);
+ assertEquals(24 + 4 + 5 + 3 + V1_0.length() + 40 + 68, table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertEquals(PACKED, act.getStorage());
+ assertTrue(act.isPeeled());
+ assertFalse(act.isSymbolic());
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId());
+ }
+ }
+
+ @Test
+ public void oneSymbolicRef() throws IOException {
+ Ref exp = sym(HEAD, MASTER);
+ byte[] table = write(exp);
+ assertEquals(
+ 24 + 4 + 5 + 2 + HEAD.length() + 2 + MASTER.length() + 68,
+ table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertTrue(act.isSymbolic());
+ assertEquals(exp.getName(), act.getName());
+ assertNotNull(act.getLeaf());
+ assertEquals(MASTER, act.getTarget().getName());
+ assertNull(act.getObjectId());
+ }
+ }
+
+ @Test
+ public void resolveSymbolicRef() throws IOException {
+ Reftable t = read(write(
+ sym(HEAD, "refs/heads/tmp"),
+ sym("refs/heads/tmp", MASTER),
+ ref(MASTER, 1)));
+
+ Ref head = t.exactRef(HEAD);
+ assertNull(head.getObjectId());
+ assertEquals("refs/heads/tmp", head.getTarget().getName());
+
+ head = t.resolve(head);
+ assertNotNull(head);
+ assertEquals(id(1), head.getObjectId());
+
+ Ref master = t.exactRef(MASTER);
+ assertNotNull(master);
+ assertSame(master, t.resolve(master));
+ }
+
+ @Test
+ public void failDeepChainOfSymbolicRef() throws IOException {
+ Reftable t = read(write(
+ sym(HEAD, "refs/heads/1"),
+ sym("refs/heads/1", "refs/heads/2"),
+ sym("refs/heads/2", "refs/heads/3"),
+ sym("refs/heads/3", "refs/heads/4"),
+ sym("refs/heads/4", "refs/heads/5"),
+ sym("refs/heads/5", MASTER),
+ ref(MASTER, 1)));
+
+ Ref head = t.exactRef(HEAD);
+ assertNull(head.getObjectId());
+ assertNull(t.resolve(head));
+ }
+
+ @Test
+ public void oneDeletedRef() throws IOException {
+ String name = "refs/heads/gone";
+ Ref exp = newRef(name);
+ byte[] table = write(exp);
+ assertEquals(24 + 4 + 5 + 3 + name.length() + 68, table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertFalse(rc.next());
+ }
+
+ t.setIncludeDeletes(true);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertFalse(act.isSymbolic());
+ assertEquals(name, act.getName());
+ assertEquals(NEW, act.getStorage());
+ assertNull(act.getObjectId());
+ assertTrue(rc.wasDeleted());
+ }
+ }
+
+ @Test
+ public void seekNotFound() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ ReftableReader t = read(write(exp));
+ try (RefCursor rc = t.seekRef("refs/heads/a")) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef("refs/heads/n")) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void namespaceNotFound() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ ReftableReader t = read(write(exp));
+ try (RefCursor rc = t.seekRef("refs/changes/")) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef("refs/tags/")) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void namespaceHeads() throws IOException {
+ Ref master = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ Ref v1 = tag(V1_0, 3, 4);
+
+ ReftableReader t = read(write(master, next, v1));
+ try (RefCursor rc = t.seekRef("refs/tags/")) {
+ assertTrue(rc.next());
+ assertEquals(V1_0, rc.getRef().getName());
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef("refs/heads/")) {
+ assertTrue(rc.next());
+ assertEquals(MASTER, rc.getRef().getName());
+
+ assertTrue(rc.next());
+ assertEquals(NEXT, rc.getRef().getName());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void indexScan() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5670; i++) {
+ refs.add(ref(String.format("refs/heads/%04d", i), i));
+ }
+
+ byte[] table = write(refs);
+ assertTrue(stats.refIndexLevels() > 0);
+ assertTrue(stats.refIndexSize() > 0);
+ assertScan(refs, read(table));
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void indexSeek() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5670; i++) {
+ refs.add(ref(String.format("refs/heads/%04d", i), i));
+ }
+
+ byte[] table = write(refs);
+ assertTrue(stats.refIndexLevels() > 0);
+ assertTrue(stats.refIndexSize() > 0);
+ assertSeek(refs, read(table));
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void noIndexScan() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ refs.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ byte[] table = write(refs);
+ assertEquals(0, stats.refIndexLevels());
+ assertEquals(0, stats.refIndexSize());
+ assertEquals(table.length, stats.totalBytes());
+ assertScan(refs, read(table));
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void noIndexSeek() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ refs.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ byte[] table = write(refs);
+ assertEquals(0, stats.refIndexLevels());
+ assertSeek(refs, read(table));
+ }
+
+ @Test
+ public void withReflog() throws IOException {
+ Ref master = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+ String msg = "test";
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter()
+ .setMinUpdateIndex(1)
+ .setMaxUpdateIndex(1)
+ .begin(buffer);
+
+ writer.writeRef(master);
+ writer.writeRef(next);
+
+ writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
+ writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
+
+ writer.finish();
+ byte[] table = buffer.toByteArray();
+ assertEquals(247, table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ assertEquals(MASTER, rc.getRef().getName());
+ assertEquals(id(1), rc.getRef().getObjectId());
+ assertEquals(1, rc.getUpdateIndex());
+
+ assertTrue(rc.next());
+ assertEquals(NEXT, rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ try (LogCursor lc = t.allLogs()) {
+ assertTrue(lc.next());
+ assertEquals(MASTER, lc.getRefName());
+ assertEquals(1, lc.getUpdateIndex());
+ assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
+ assertEquals(id(1), lc.getReflogEntry().getNewId());
+ assertEquals(who, lc.getReflogEntry().getWho());
+ assertEquals(msg, lc.getReflogEntry().getComment());
+
+ assertTrue(lc.next());
+ assertEquals(NEXT, lc.getRefName());
+ assertEquals(1, lc.getUpdateIndex());
+ assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
+ assertEquals(id(2), lc.getReflogEntry().getNewId());
+ assertEquals(who, lc.getReflogEntry().getWho());
+ assertEquals(msg, lc.getReflogEntry().getComment());
+
+ assertFalse(lc.next());
+ }
+ }
+
+ @Test
+ public void onlyReflog() throws IOException {
+ PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+ String msg = "test";
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter()
+ .setMinUpdateIndex(1)
+ .setMaxUpdateIndex(1)
+ .begin(buffer);
+ writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
+ writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
+ writer.finish();
+ byte[] table = buffer.toByteArray();
+ stats = writer.getStats();
+ assertEquals(170, table.length);
+ assertEquals(0, stats.refCount());
+ assertEquals(0, stats.refBytes());
+ assertEquals(0, stats.refIndexLevels());
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef("refs/heads/")) {
+ assertFalse(rc.next());
+ }
+ try (LogCursor lc = t.allLogs()) {
+ assertTrue(lc.next());
+ assertEquals(MASTER, lc.getRefName());
+ assertEquals(1, lc.getUpdateIndex());
+ assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
+ assertEquals(id(1), lc.getReflogEntry().getNewId());
+ assertEquals(who, lc.getReflogEntry().getWho());
+ assertEquals(msg, lc.getReflogEntry().getComment());
+
+ assertTrue(lc.next());
+ assertEquals(NEXT, lc.getRefName());
+ assertEquals(1, lc.getUpdateIndex());
+ assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
+ assertEquals(id(2), lc.getReflogEntry().getNewId());
+ assertEquals(who, lc.getReflogEntry().getWho());
+ assertEquals(msg, lc.getReflogEntry().getComment());
+
+ assertFalse(lc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void logScan() throws IOException {
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setRefBlockSize(256);
+ cfg.setLogBlockSize(2048);
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter(cfg);
+ writer.setMinUpdateIndex(1).setMaxUpdateIndex(1).begin(buffer);
+
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5670; i++) {
+ Ref ref = ref(String.format("refs/heads/%03d", i), i);
+ refs.add(ref);
+ writer.writeRef(ref);
+ }
+
+ PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+ for (Ref ref : refs) {
+ writer.writeLog(ref.getName(), 1, who,
+ ObjectId.zeroId(), ref.getObjectId(),
+ "create " + ref.getName());
+ }
+ writer.finish();
+ stats = writer.getStats();
+ assertTrue(stats.logBytes() > 4096);
+ byte[] table = buffer.toByteArray();
+
+ ReftableReader t = read(table);
+ try (LogCursor lc = t.allLogs()) {
+ for (Ref exp : refs) {
+ assertTrue("has " + exp.getName(), lc.next());
+ assertEquals(exp.getName(), lc.getRefName());
+ ReflogEntry entry = lc.getReflogEntry();
+ assertNotNull(entry);
+ assertEquals(who, entry.getWho());
+ assertEquals(ObjectId.zeroId(), entry.getOldId());
+ assertEquals(exp.getObjectId(), entry.getNewId());
+ assertEquals("create " + exp.getName(), entry.getComment());
+ }
+ assertFalse(lc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void byObjectIdOneRefNoIndex() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 200; i++) {
+ refs.add(ref(String.format("refs/heads/%02d", i), i));
+ }
+ refs.add(ref("refs/heads/master", 100));
+
+ ReftableReader t = read(write(refs));
+ assertEquals(0, stats.objIndexSize());
+
+ try (RefCursor rc = t.byObjectId(id(42))) {
+ assertTrue("has 42", rc.next());
+ assertEquals("refs/heads/42", rc.getRef().getName());
+ assertEquals(id(42), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.byObjectId(id(100))) {
+ assertTrue("has 100", rc.next());
+ assertEquals("refs/heads/100", rc.getRef().getName());
+ assertEquals(id(100), rc.getRef().getObjectId());
+
+ assertTrue("has master", rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(100), rc.getRef().getObjectId());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void byObjectIdOneRefWithIndex() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5200; i++) {
+ refs.add(ref(String.format("refs/heads/%02d", i), i));
+ }
+ refs.add(ref("refs/heads/master", 100));
+
+ ReftableReader t = read(write(refs));
+ assertTrue(stats.objIndexSize() > 0);
+
+ try (RefCursor rc = t.byObjectId(id(42))) {
+ assertTrue("has 42", rc.next());
+ assertEquals("refs/heads/42", rc.getRef().getName());
+ assertEquals(id(42), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.byObjectId(id(100))) {
+ assertTrue("has 100", rc.next());
+ assertEquals("refs/heads/100", rc.getRef().getName());
+ assertEquals(id(100), rc.getRef().getObjectId());
+
+ assertTrue("has master", rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(100), rc.getRef().getObjectId());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void unpeeledDoesNotWrite() {
+ try {
+ write(new ObjectIdRef.Unpeeled(PACKED, MASTER, id(1)));
+ fail("expected IOException");
+ } catch (IOException e) {
+ assertEquals(JGitText.get().peeledRefIsRequired, e.getMessage());
+ }
+ }
+
+ @Test
+ public void nameTooLongDoesNotWrite() throws IOException {
+ try {
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setRefBlockSize(64);
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter(cfg).begin(buffer);
+ writer.writeRef(ref("refs/heads/i-am-not-a-teapot", 1));
+ writer.finish();
+ fail("expected BlockSizeTooSmallException");
+ } catch (BlockSizeTooSmallException e) {
+ assertEquals(85, e.getMinimumBlockSize());
+ }
+ }
+
+ @Test
+ public void badCrc32() throws IOException {
+ byte[] table = write();
+ table[table.length - 1] = 0x42;
+
+ try {
+ read(table).seekRef(HEAD);
+ fail("expected IOException");
+ } catch (IOException e) {
+ assertEquals(JGitText.get().invalidReftableCRC, e.getMessage());
+ }
+ }
+
+
+ private static void assertScan(List<Ref> refs, Reftable t)
+ throws IOException {
+ try (RefCursor rc = t.allRefs()) {
+ for (Ref exp : refs) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ }
+ assertFalse(rc.next());
+ }
+ }
+
+ private static void assertSeek(List<Ref> refs, Reftable t)
+ throws IOException {
+ for (Ref exp : refs) {
+ try (RefCursor rc = t.seekRef(exp.getName())) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+ }
+
+ private static Ref ref(String name, int id) {
+ return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
+ }
+
+ private static Ref tag(String name, int id1, int id2) {
+ return new ObjectIdRef.PeeledTag(PACKED, name, id(id1), id(id2));
+ }
+
+ private static Ref sym(String name, String target) {
+ return new SymbolicRef(name, newRef(target));
+ }
+
+ private static Ref newRef(String name) {
+ return new ObjectIdRef.Unpeeled(NEW, name, null);
+ }
+
+ private static ObjectId id(int i) {
+ byte[] buf = new byte[OBJECT_ID_LENGTH];
+ buf[0] = (byte) (i & 0xff);
+ buf[1] = (byte) ((i >>> 8) & 0xff);
+ buf[2] = (byte) ((i >>> 16) & 0xff);
+ buf[3] = (byte) (i >>> 24);
+ return ObjectId.fromRaw(buf);
+ }
+
+ private static ReftableReader read(byte[] table) {
+ return new ReftableReader(BlockSource.from(table));
+ }
+
+ private byte[] write(Ref... refs) throws IOException {
+ return write(Arrays.asList(refs));
+ }
+
+ private byte[] write(Collection<Ref> refs) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ stats = new ReftableWriter()
+ .begin(buffer)
+ .sortAndWriteRefs(refs)
+ .finish()
+ .getStats();
+ return buffer.toByteArray();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
index 67a7819900..d5a07e02fa 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
@@ -83,7 +83,7 @@ public class LocalDiskRefTreeDatabaseTest extends LocalDiskRepositoryTestCase {
FileRepository init = createWorkRepository();
FileBasedConfig cfg = init.getConfig();
cfg.setInt("core", null, "repositoryformatversion", 1);
- cfg.setString("extensions", null, "refsStorage", "reftree");
+ cfg.setString("extensions", null, "refStorage", "reftree");
cfg.save();
repo = (FileRepository) new FileRepositoryBuilder()
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java
index 6529d9ed57..30a9626b1f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java
@@ -86,7 +86,6 @@ public class AbbreviatedObjectIdTest {
final ObjectId f = i.toObjectId();
assertNotNull(f);
assertEquals(ObjectId.fromString(s), f);
- assertEquals(f.hashCode(), i.hashCode());
}
@Test
@@ -101,7 +100,6 @@ public class AbbreviatedObjectIdTest {
final ObjectId f = i.toObjectId();
assertNotNull(f);
assertEquals(ObjectId.fromString(s), f);
- assertEquals(f.hashCode(), i.hashCode());
}
@Test
@@ -215,7 +213,7 @@ public class AbbreviatedObjectIdTest {
}
@Test
- public void testEquals_Short() {
+ public void testEquals_Short8() {
final String s = "7b6e8067";
final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s);
final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(s);
@@ -226,6 +224,18 @@ public class AbbreviatedObjectIdTest {
}
@Test
+ public void testEquals_Short4() {
+ final String s = "7b6e";
+ final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s);
+ final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(s);
+ assertNotSame(a, b);
+ assertTrue(a.hashCode() != 0);
+ assertTrue(a.hashCode() == b.hashCode());
+ assertEquals(b, a);
+ assertEquals(a, b);
+ }
+
+ @Test
public void testEquals_Full() {
final String s = "7b6e8067ec96acef9a4184b43210d583b6d2f99a";
final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index e9505f67d0..a12831a149 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -80,6 +80,7 @@ import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -766,6 +767,7 @@ public class ConfigTest {
}
@Test
+ @Ignore
public void testIncludeInvalidName() throws ConfigInvalidException {
expectedEx.expect(ConfigInvalidException.class);
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
@@ -773,6 +775,7 @@ public class ConfigTest {
}
@Test
+ @Ignore
public void testIncludeNoValue() throws ConfigInvalidException {
expectedEx.expect(ConfigInvalidException.class);
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
@@ -780,6 +783,7 @@ public class ConfigTest {
}
@Test
+ @Ignore
public void testIncludeEmptyValue() throws ConfigInvalidException {
expectedEx.expect(ConfigInvalidException.class);
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
@@ -816,6 +820,7 @@ public class ConfigTest {
}
@Test
+ @Ignore
public void testIncludeTooManyRecursions() throws IOException {
File config = tmp.newFile("config");
String include = "[include]\npath=" + config.toPath() + "\n";
@@ -832,27 +837,14 @@ public class ConfigTest {
}
@Test
- public void testInclude() throws IOException, ConfigInvalidException {
+ public void testIncludeIsNoop() throws IOException, ConfigInvalidException {
File config = tmp.newFile("config");
- File more = tmp.newFile("config.more");
- File other = tmp.newFile("config.other");
String fooBar = "[foo]\nbar=true\n";
- String includeMore = "[include]\npath=" + more.toPath() + "\n";
- String includeOther = "path=" + other.toPath() + "\n";
- String fooPlus = fooBar + includeMore + includeOther;
- Files.write(config.toPath(), fooPlus.getBytes());
-
- String fooMore = "[foo]\nmore=bar\n";
- Files.write(more.toPath(), fooMore.getBytes());
-
- String otherMore = "[other]\nmore=bar\n";
- Files.write(other.toPath(), otherMore.getBytes());
+ Files.write(config.toPath(), fooBar.getBytes());
Config parsed = parse("[include]\npath=" + config.toPath() + "\n");
- assertTrue(parsed.getBoolean("foo", "bar", false));
- assertEquals("bar", parsed.getString("foo", null, "more"));
- assertEquals("bar", parsed.getString("other", null, "more"));
+ assertFalse(parsed.getBoolean("foo", "bar", false));
}
private static void assertReadLong(long exp) throws ConfigInvalidException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
index f8c2d4536d..05573b9468 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
@@ -72,6 +72,8 @@ import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.CheckoutConflictException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.events.ChangeRecorder;
+import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
@@ -141,14 +143,19 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
@Test
public void testResetHard() throws IOException, NoFilepatternException,
GitAPIException {
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
writeTrashFile("f", "f()");
writeTrashFile("D/g", "g()");
git.add().addFilepattern(".").call();
git.commit().setMessage("inital").call();
assertIndex(mkmap("f", "f()", "D/g", "g()"));
-
+ recorder.assertNoEvent();
git.branchCreate().setName("topic").call();
+ recorder.assertNoEvent();
writeTrashFile("f", "f()\nmaster");
writeTrashFile("D/g", "g()\ng2()");
@@ -156,9 +163,12 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
git.add().addFilepattern(".").call();
RevCommit master = git.commit().setMessage("master-1").call();
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
+ recorder.assertNoEvent();
checkoutBranch("refs/heads/topic");
assertIndex(mkmap("f", "f()", "D/g", "g()"));
+ recorder.assertEvent(new String[] { "f", "D/g" },
+ new String[] { "E/h" });
writeTrashFile("f", "f()\nside");
assertTrue(new File(db.getWorkTree(), "D/g").delete());
@@ -167,26 +177,41 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
git.add().addFilepattern(".").setUpdate(true).call();
RevCommit topic = git.commit().setMessage("topic-1").call();
assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
+ recorder.assertNoEvent();
writeTrashFile("untracked", "untracked");
resetHard(master);
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
+ recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
+ new String[] { "G", "G/i" });
+
resetHard(topic);
assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked",
"untracked"));
+ recorder.assertEvent(new String[] { "f", "G/i" },
+ new String[] { "D", "D/g", "E", "E/h" });
assertEquals(MergeStatus.CONFLICTING, git.merge().include(master)
.call().getMergeStatus());
assertEquals(
"[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]",
indexState(0));
+ recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
+ ChangeRecorder.EMPTY);
resetHard(master);
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h",
"h()", "untracked", "untracked"));
+ recorder.assertEvent(new String[] { "f", "D/g" },
+ new String[] { "G", "G/i" });
+
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -202,13 +227,18 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
@Test
public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
throws Exception {
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
writeTrashFile("x", "x");
git.add().addFilepattern("x").call();
RevCommit id1 = git.commit().setMessage("c1").call();
writeTrashFile("f/g", "f/g");
git.rm().addFilepattern("x").call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "x" });
git.add().addFilepattern("f/g").call();
git.commit().setMessage("c2").call();
deleteTrashFile("f/g");
@@ -217,6 +247,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// The actual test
git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call();
assertIndex(mkmap("x", "x"));
+ recorder.assertEvent(new String[] { "x" }, ChangeRecorder.EMPTY);
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -227,13 +262,22 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
*/
@Test
public void testInitialCheckout() throws Exception {
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
TestRepository<Repository> db_t = new TestRepository<>(db);
BranchBuilder master = db_t.branch("master");
master.commit().add("f", "1").message("m0").create();
assertFalse(new File(db.getWorkTree(), "f").exists());
git.checkout().setName("master").call();
assertTrue(new File(db.getWorkTree(), "f").exists());
+ recorder.assertEvent(new String[] { "f" }, ChangeRecorder.EMPTY);
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -930,120 +974,154 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
public void testCheckoutChangeLinkToEmptyDir() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "was_file";
- Git git = Git.wrap(db);
-
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
-
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
-
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
-
- // replace link with empty directory
- FileUtils.delete(link);
- FileUtils.mkdir(link);
- assertTrue("Link must be a directory now", link.isDirectory());
-
- // modify file
- writeTrashFile(fname, "b");
- assertWorkDir(mkmap(fname, "b", linkName, "/"));
-
- // revert both paths to HEAD state
- git.checkout().setStartPoint(Constants.HEAD)
- .addPath(fname).addPath(linkName).call();
-
- assertWorkDir(mkmap(fname, "a", linkName, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
+
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+ // replace link with empty directory
+ FileUtils.delete(link);
+ FileUtils.mkdir(link);
+ assertTrue("Link must be a directory now", link.isDirectory());
+
+ // modify file
+ writeTrashFile(fname, "b");
+ assertWorkDir(mkmap(fname, "b", linkName, "/"));
+ recorder.assertNoEvent();
+
+ // revert both paths to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
+ .addPath(linkName).call();
+
+ assertWorkDir(mkmap(fname, "a", linkName, "a"));
+ recorder.assertEvent(new String[] { fname, linkName },
+ ChangeRecorder.EMPTY);
+
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeLinkToEmptyDirs() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "was_file";
- Git git = Git.wrap(db);
-
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
-
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
-
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
-
- // replace link with directory containing only directories, no files
- FileUtils.delete(link);
- FileUtils.mkdirs(new File(link, "dummyDir"));
- assertTrue("Link must be a directory now", link.isDirectory());
-
- assertFalse("Must not delete non empty directory", link.delete());
-
- // modify file
- writeTrashFile(fname, "b");
- assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/"));
-
- // revert both paths to HEAD state
- git.checkout().setStartPoint(Constants.HEAD)
- .addPath(fname).addPath(linkName).call();
-
- assertWorkDir(mkmap(fname, "a", linkName, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
+
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+ // replace link with directory containing only directories, no files
+ FileUtils.delete(link);
+ FileUtils.mkdirs(new File(link, "dummyDir"));
+ assertTrue("Link must be a directory now", link.isDirectory());
+
+ assertFalse("Must not delete non empty directory", link.delete());
+
+ // modify file
+ writeTrashFile(fname, "b");
+ assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/"));
+ recorder.assertNoEvent();
+
+ // revert both paths to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
+ .addPath(linkName).call();
+
+ assertWorkDir(mkmap(fname, "a", linkName, "a"));
+ recorder.assertEvent(new String[] { fname, linkName },
+ ChangeRecorder.EMPTY);
+
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file";
- Git git = Git.wrap(db);
-
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
- // replace link with directory containing only directories, no files
- FileUtils.delete(link);
+ // replace link with directory containing only directories, no files
+ FileUtils.delete(link);
- // create but do not add a file in the new directory to the index
- writeTrashFile(linkName + "/dir1", "file1", "c");
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir1", "file1", "c");
- // create but do not add a file in the new directory to the index
- writeTrashFile(linkName + "/dir2", "file2", "d");
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir2", "file2", "d");
- assertTrue("File must be a directory now", link.isDirectory());
- assertFalse("Must not delete non empty directory", link.delete());
+ assertTrue("File must be a directory now", link.isDirectory());
+ assertFalse("Must not delete non empty directory", link.delete());
- // 2 extra files are created
- assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
- linkName + "/dir2/file2", "d"));
+ // 2 extra files are created
+ assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+ linkName + "/dir2/file2", "d"));
+ recorder.assertNoEvent();
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call();
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
+ .call();
- // expect only the one added to the index
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ // expect only the one added to the index
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ recorder.assertEvent(new String[] { linkName },
+ ChangeRecorder.EMPTY);
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1051,174 +1129,222 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file";
- Git git = Git.wrap(db);
-
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
- // replace link with directory containing only directories, no files
- FileUtils.delete(link);
+ // replace link with directory containing only directories, no files
+ FileUtils.delete(link);
- // create and add a file in the new directory to the index
- writeTrashFile(linkName + "/dir1", "file1", "c");
- git.add().addFilepattern(linkName + "/dir1/file1").call();
+ // create and add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir1", "file1", "c");
+ git.add().addFilepattern(linkName + "/dir1/file1").call();
- // create but do not add a file in the new directory to the index
- writeTrashFile(linkName + "/dir2", "file2", "d");
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir2", "file2", "d");
- assertTrue("File must be a directory now", link.isDirectory());
- assertFalse("Must not delete non empty directory", link.delete());
+ assertTrue("File must be a directory now", link.isDirectory());
+ assertFalse("Must not delete non empty directory", link.delete());
- // 2 extra files are created
- assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
- linkName + "/dir2/file2", "d"));
+ // 2 extra files are created
+ assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+ linkName + "/dir2/file2", "d"));
+ recorder.assertNoEvent();
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call();
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
+ .call();
- // original file and link
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ // original file and link
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ recorder.assertEvent(new String[] { linkName },
+ ChangeRecorder.EMPTY);
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeFileToEmptyDir() throws Exception {
String fname = "was_file";
- Git git = Git.wrap(db);
-
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("Added file").call();
-
- // replace file with empty directory
- FileUtils.delete(file);
- FileUtils.mkdir(file);
- assertTrue("File must be a directory now", file.isDirectory());
-
- assertWorkDir(mkmap(fname, "/"));
-
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
-
- assertWorkDir(mkmap(fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("Added file").call();
+
+ // replace file with empty directory
+ FileUtils.delete(file);
+ FileUtils.mkdir(file);
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertWorkDir(mkmap(fname, "/"));
+ recorder.assertNoEvent();
+
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+ assertWorkDir(mkmap(fname, "a"));
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
+
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeFileToEmptyDirs() throws Exception {
String fname = "was_file";
- Git git = Git.wrap(db);
-
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("Added file").call();
-
- // replace file with directory containing only directories, no files
- FileUtils.delete(file);
- FileUtils.mkdirs(new File(file, "dummyDir"));
- assertTrue("File must be a directory now", file.isDirectory());
- assertFalse("Must not delete non empty directory", file.delete());
-
- assertWorkDir(mkmap(fname + "/dummyDir", "/"));
-
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
-
- assertWorkDir(mkmap(fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("Added file").call();
+
+ // replace file with directory containing only directories, no files
+ FileUtils.delete(file);
+ FileUtils.mkdirs(new File(file, "dummyDir"));
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertFalse("Must not delete non empty directory", file.delete());
+
+ assertWorkDir(mkmap(fname + "/dummyDir", "/"));
+ recorder.assertNoEvent();
+
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+ assertWorkDir(mkmap(fname, "a"));
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
+
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeFileToNonEmptyDirs() throws Exception {
String fname = "was_file";
- Git git = Git.wrap(db);
-
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("Added file").call();
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("Added file").call();
- assertWorkDir(mkmap(fname, "a"));
+ assertWorkDir(mkmap(fname, "a"));
- // replace file with directory containing only directories, no files
- FileUtils.delete(file);
+ // replace file with directory containing only directories, no files
+ FileUtils.delete(file);
- // create but do not add a file in the new directory to the index
- writeTrashFile(fname + "/dir1", "file1", "c");
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(fname + "/dir1", "file1", "c");
- // create but do not add a file in the new directory to the index
- writeTrashFile(fname + "/dir2", "file2", "d");
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(fname + "/dir2", "file2", "d");
- assertTrue("File must be a directory now", file.isDirectory());
- assertFalse("Must not delete non empty directory", file.delete());
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertFalse("Must not delete non empty directory", file.delete());
- // 2 extra files are created
- assertWorkDir(
- mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d"));
+ // 2 extra files are created
+ assertWorkDir(mkmap(fname + "/dir1/file1", "c",
+ fname + "/dir2/file2", "d"));
+ recorder.assertNoEvent();
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
- // expect only the one added to the index
- assertWorkDir(mkmap(fname, "a"));
+ // expect only the one added to the index
+ assertWorkDir(mkmap(fname, "a"));
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry()
throws Exception {
String fname = "was_file";
- Git git = Git.wrap(db);
-
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("Added file").call();
-
- assertWorkDir(mkmap(fname, "a"));
-
- // replace file with directory containing only directories, no files
- FileUtils.delete(file);
-
- // create and add a file in the new directory to the index
- writeTrashFile(fname + "/dir", "file1", "c");
- git.add().addFilepattern(fname + "/dir/file1").call();
-
- // create but do not add a file in the new directory to the index
- writeTrashFile(fname + "/dir", "file2", "d");
-
- assertTrue("File must be a directory now", file.isDirectory());
- assertFalse("Must not delete non empty directory", file.delete());
-
- // 2 extra files are created
- assertWorkDir(
- mkmap(fname + "/dir/file1", "c", fname + "/dir/file2", "d"));
-
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
- assertWorkDir(mkmap(fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("Added file").call();
+
+ assertWorkDir(mkmap(fname, "a"));
+
+ // replace file with directory containing only directories, no files
+ FileUtils.delete(file);
+
+ // create and add a file in the new directory to the index
+ writeTrashFile(fname + "/dir", "file1", "c");
+ git.add().addFilepattern(fname + "/dir/file1").call();
+
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(fname + "/dir", "file2", "d");
+
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertFalse("Must not delete non empty directory", file.delete());
+
+ // 2 extra files are created
+ assertWorkDir(mkmap(fname + "/dir/file1", "c", fname + "/dir/file2",
+ "d"));
+ recorder.assertNoEvent();
+
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+ assertWorkDir(mkmap(fname, "a"));
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1293,76 +1419,100 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
public void testOverwriteUntrackedIgnoredFile() throws IOException,
GitAPIException {
String fname="file.txt";
- Git git = Git.wrap(db);
-
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("create file").call();
-
- // Create branch
- git.branchCreate().setName("side").call();
-
- // Modify file
- writeTrashFile(fname, "b");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("modify file").call();
-
- // Switch branches
- git.checkout().setName("side").call();
- git.rm().addFilepattern(fname).call();
- writeTrashFile(".gitignore", fname);
- git.add().addFilepattern(".gitignore").call();
- git.commit().setMessage("delete and ignore file").call();
-
- writeTrashFile(fname, "Something different");
- git.checkout().setName("master").call();
- assertWorkDir(mkmap(fname, "b"));
- assertTrue(git.status().call().isClean());
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("create file").call();
+
+ // Create branch
+ git.branchCreate().setName("side").call();
+
+ // Modify file
+ writeTrashFile(fname, "b");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("modify file").call();
+ recorder.assertNoEvent();
+
+ // Switch branches
+ git.checkout().setName("side").call();
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
+ git.rm().addFilepattern(fname).call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { fname });
+ writeTrashFile(".gitignore", fname);
+ git.add().addFilepattern(".gitignore").call();
+ git.commit().setMessage("delete and ignore file").call();
+
+ writeTrashFile(fname, "Something different");
+ recorder.assertNoEvent();
+ git.checkout().setName("master").call();
+ assertWorkDir(mkmap(fname, "b"));
+ recorder.assertEvent(new String[] { fname },
+ new String[] { ".gitignore" });
+ assertTrue(git.status().call().isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testOverwriteUntrackedFileModeChange()
throws IOException, GitAPIException {
String fname = "file.txt";
- Git git = Git.wrap(db);
-
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("create file").call();
- assertWorkDir(mkmap(fname, "a"));
-
- // Create branch
- git.branchCreate().setName("side").call();
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("create file").call();
+ assertWorkDir(mkmap(fname, "a"));
- // Switch branches
- git.checkout().setName("side").call();
+ // Create branch
+ git.branchCreate().setName("side").call();
- // replace file with directory containing files
- FileUtils.delete(file);
+ // Switch branches
+ git.checkout().setName("side").call();
+ recorder.assertNoEvent();
- // create and add a file in the new directory to the index
- writeTrashFile(fname + "/dir1", "file1", "c");
- git.add().addFilepattern(fname + "/dir1/file1").call();
+ // replace file with directory containing files
+ FileUtils.delete(file);
- // create but do not add a file in the new directory to the index
- writeTrashFile(fname + "/dir2", "file2", "d");
+ // create and add a file in the new directory to the index
+ writeTrashFile(fname + "/dir1", "file1", "c");
+ git.add().addFilepattern(fname + "/dir1/file1").call();
- assertTrue("File must be a directory now", file.isDirectory());
- assertFalse("Must not delete non empty directory", file.delete());
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(fname + "/dir2", "file2", "d");
- // 2 extra files are created
- assertWorkDir(
- mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d"));
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertFalse("Must not delete non empty directory", file.delete());
- try {
- git.checkout().setName("master").call();
- fail("did not throw exception");
- } catch (Exception e) {
- // 2 extra files are still there
+ // 2 extra files are created
assertWorkDir(mkmap(fname + "/dir1/file1", "c",
fname + "/dir2/file2", "d"));
+
+ try {
+ git.checkout().setName("master").call();
+ fail("did not throw exception");
+ } catch (Exception e) {
+ // 2 extra files are still there
+ assertWorkDir(mkmap(fname + "/dir1/file1", "c",
+ fname + "/dir2/file2", "d"));
+ }
+ recorder.assertNoEvent();
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -1371,50 +1521,60 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file.txt";
- Git git = Git.wrap(db);
-
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
-
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
- // Create branch
- git.branchCreate().setName("side").call();
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
- // Switch branches
- git.checkout().setName("side").call();
+ // Create branch
+ git.branchCreate().setName("side").call();
- // replace link with directory containing files
- FileUtils.delete(link);
+ // Switch branches
+ git.checkout().setName("side").call();
+ recorder.assertNoEvent();
- // create and add a file in the new directory to the index
- writeTrashFile(linkName + "/dir1", "file1", "c");
- git.add().addFilepattern(linkName + "/dir1/file1").call();
+ // replace link with directory containing files
+ FileUtils.delete(link);
- // create but do not add a file in the new directory to the index
- writeTrashFile(linkName + "/dir2", "file2", "d");
+ // create and add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir1", "file1", "c");
+ git.add().addFilepattern(linkName + "/dir1/file1").call();
- assertTrue("Link must be a directory now", link.isDirectory());
- assertFalse("Must not delete non empty directory", link.delete());
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir2", "file2", "d");
- // 2 extra files are created
- assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
- linkName + "/dir2/file2", "d"));
+ assertTrue("Link must be a directory now", link.isDirectory());
+ assertFalse("Must not delete non empty directory", link.delete());
- try {
- git.checkout().setName("master").call();
- fail("did not throw exception");
- } catch (Exception e) {
- // 2 extra files are still there
+ // 2 extra files are created
assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
linkName + "/dir2/file2", "d"));
+
+ try {
+ git.checkout().setName("master").call();
+ fail("did not throw exception");
+ } catch (Exception e) {
+ // 2 extra files are still there
+ assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+ linkName + "/dir2/file2", "d"));
+ }
+ recorder.assertNoEvent();
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -1423,36 +1583,47 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
-
- // Add non-executable file
- File file = writeTrashFile("file.txt", "a");
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file));
-
- // Create branch
- git.branchCreate().setName("b1").call();
-
- // Make file executable
- db.getFS().setExecute(file, true);
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit2").call();
-
- // Verify executable and working directory is clean
- Status status = git.status().call();
- assertTrue(status.getModified().isEmpty());
- assertTrue(status.getChanged().isEmpty());
- assertTrue(db.getFS().canExecute(file));
-
- // Switch branches
- git.checkout().setName("b1").call();
-
- // Verify not executable and working directory is clean
- status = git.status().call();
- assertTrue(status.getModified().isEmpty());
- assertTrue(status.getChanged().isEmpty());
- assertFalse(db.getFS().canExecute(file));
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add non-executable file
+ File file = writeTrashFile("file.txt", "a");
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file));
+
+ // Create branch
+ git.branchCreate().setName("b1").call();
+
+ // Make file executable
+ db.getFS().setExecute(file, true);
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit2").call();
+ recorder.assertNoEvent();
+
+ // Verify executable and working directory is clean
+ Status status = git.status().call();
+ assertTrue(status.getModified().isEmpty());
+ assertTrue(status.getChanged().isEmpty());
+ assertTrue(db.getFS().canExecute(file));
+
+ // Switch branches
+ git.checkout().setName("b1").call();
+
+ // Verify not executable and working directory is clean
+ status = git.status().call();
+ assertTrue(status.getModified().isEmpty());
+ assertTrue(status.getChanged().isEmpty());
+ assertFalse(db.getFS().canExecute(file));
+ recorder.assertEvent(new String[] { "file.txt" },
+ ChangeRecorder.EMPTY);
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1460,41 +1631,50 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
-
- // Add non-executable file
- File file = writeTrashFile("file.txt", "a");
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file));
-
- // Create branch
- git.branchCreate().setName("b1").call();
-
- // Make file executable
- db.getFS().setExecute(file, true);
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit2").call();
-
- // Verify executable and working directory is clean
- Status status = git.status().call();
- assertTrue(status.getModified().isEmpty());
- assertTrue(status.getChanged().isEmpty());
- assertTrue(db.getFS().canExecute(file));
-
- writeTrashFile("file.txt", "b");
-
- // Switch branches
- CheckoutCommand checkout = git.checkout().setName("b1");
- try {
- checkout.call();
- fail("Checkout exception not thrown");
- } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) {
- CheckoutResult result = checkout.getResult();
- assertNotNull(result);
- assertNotNull(result.getConflictList());
- assertEquals(1, result.getConflictList().size());
- assertTrue(result.getConflictList().contains("file.txt"));
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add non-executable file
+ File file = writeTrashFile("file.txt", "a");
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file));
+
+ // Create branch
+ git.branchCreate().setName("b1").call();
+
+ // Make file executable
+ db.getFS().setExecute(file, true);
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit2").call();
+
+ // Verify executable and working directory is clean
+ Status status = git.status().call();
+ assertTrue(status.getModified().isEmpty());
+ assertTrue(status.getChanged().isEmpty());
+ assertTrue(db.getFS().canExecute(file));
+
+ writeTrashFile("file.txt", "b");
+
+ // Switch branches
+ CheckoutCommand checkout = git.checkout().setName("b1");
+ try {
+ checkout.call();
+ fail("Checkout exception not thrown");
+ } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) {
+ CheckoutResult result = checkout.getResult();
+ assertNotNull(result);
+ assertNotNull(result.getConflictList());
+ assertEquals(1, result.getConflictList().size());
+ assertTrue(result.getConflictList().contains("file.txt"));
+ }
+ recorder.assertNoEvent();
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -1504,40 +1684,52 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
-
- // Add non-executable file
- File file = writeTrashFile("file.txt", "a");
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file));
-
- // Create branch
- git.branchCreate().setName("b1").call();
-
- // Create second commit and don't touch file
- writeTrashFile("file2.txt", "");
- git.add().addFilepattern("file2.txt").call();
- git.commit().setMessage("commit2").call();
-
- // stage a mode change
- writeTrashFile("file.txt", "a");
- db.getFS().setExecute(file, true);
- git.add().addFilepattern("file.txt").call();
-
- // dirty the file
- writeTrashFile("file.txt", "b");
-
- assertEquals(
- "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]",
- indexState(CONTENT));
- assertWorkDir(mkmap("file.txt", "b", "file2.txt", ""));
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add non-executable file
+ File file = writeTrashFile("file.txt", "a");
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file));
+
+ // Create branch
+ git.branchCreate().setName("b1").call();
+
+ // Create second commit and don't touch file
+ writeTrashFile("file2.txt", "");
+ git.add().addFilepattern("file2.txt").call();
+ git.commit().setMessage("commit2").call();
+
+ // stage a mode change
+ writeTrashFile("file.txt", "a");
+ db.getFS().setExecute(file, true);
+ git.add().addFilepattern("file.txt").call();
+
+ // dirty the file
+ writeTrashFile("file.txt", "b");
- // Switch branches and check that the dirty file survived in worktree
- // and index
- git.checkout().setName("b1").call();
- assertEquals("[file.txt, mode:100755, content:a]", indexState(CONTENT));
- assertWorkDir(mkmap("file.txt", "b"));
+ assertEquals(
+ "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]",
+ indexState(CONTENT));
+ assertWorkDir(mkmap("file.txt", "b", "file2.txt", ""));
+ recorder.assertNoEvent();
+
+ // Switch branches and check that the dirty file survived in
+ // worktree and index
+ git.checkout().setName("b1").call();
+ assertEquals("[file.txt, mode:100755, content:a]",
+ indexState(CONTENT));
+ assertWorkDir(mkmap("file.txt", "b"));
+ recorder.assertEvent(ChangeRecorder.EMPTY,
+ new String[] { "file2.txt" });
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1546,40 +1738,53 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
-
- // Add non-executable file
- File file = writeTrashFile("file.txt", "a");
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file));
-
- // Create branch
- git.branchCreate().setName("b1").call();
-
- // Create second commit with executable file
- file = writeTrashFile("file.txt", "b");
- db.getFS().setExecute(file, true);
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit2").call();
-
- // stage the same content as in the branch we want to switch to
- writeTrashFile("file.txt", "a");
- db.getFS().setExecute(file, false);
- git.add().addFilepattern("file.txt").call();
-
- // dirty the file
- writeTrashFile("file.txt", "c");
- db.getFS().setExecute(file, true);
-
- assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT));
- assertWorkDir(mkmap("file.txt", "c"));
-
- // Switch branches and check that the dirty file survived in worktree
- // and index
- git.checkout().setName("b1").call();
- assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT));
- assertWorkDir(mkmap("file.txt", "c"));
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add non-executable file
+ File file = writeTrashFile("file.txt", "a");
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file));
+
+ // Create branch
+ git.branchCreate().setName("b1").call();
+
+ // Create second commit with executable file
+ file = writeTrashFile("file.txt", "b");
+ db.getFS().setExecute(file, true);
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit2").call();
+
+ // stage the same content as in the branch we want to switch to
+ writeTrashFile("file.txt", "a");
+ db.getFS().setExecute(file, false);
+ git.add().addFilepattern("file.txt").call();
+
+ // dirty the file
+ writeTrashFile("file.txt", "c");
+ db.getFS().setExecute(file, true);
+
+ assertEquals("[file.txt, mode:100644, content:a]",
+ indexState(CONTENT));
+ assertWorkDir(mkmap("file.txt", "c"));
+ recorder.assertNoEvent();
+
+ // Switch branches and check that the dirty file survived in
+ // worktree
+ // and index
+ git.checkout().setName("b1").call();
+ assertEquals("[file.txt, mode:100644, content:a]",
+ indexState(CONTENT));
+ assertWorkDir(mkmap("file.txt", "c"));
+ recorder.assertNoEvent();
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1587,31 +1792,44 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
-
- // Add first file
- File file1 = writeTrashFile("file1.txt", "a");
- git.add().addFilepattern("file1.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file1));
-
- // Add second file
- File file2 = writeTrashFile("file2.txt", "b");
- git.add().addFilepattern("file2.txt").call();
- git.commit().setMessage("commit2").call();
- assertFalse(db.getFS().canExecute(file2));
-
- // Create branch from first commit
- assertNotNull(git.checkout().setCreateBranch(true).setName("b1")
- .setStartPoint(Constants.HEAD + "~1").call());
-
- // Change content and file mode in working directory and index
- file1 = writeTrashFile("file1.txt", "c");
- db.getFS().setExecute(file1, true);
- git.add().addFilepattern("file1.txt").call();
-
- // Switch back to 'master'
- assertNotNull(git.checkout().setName(Constants.MASTER).call());
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add first file
+ File file1 = writeTrashFile("file1.txt", "a");
+ git.add().addFilepattern("file1.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file1));
+
+ // Add second file
+ File file2 = writeTrashFile("file2.txt", "b");
+ git.add().addFilepattern("file2.txt").call();
+ git.commit().setMessage("commit2").call();
+ assertFalse(db.getFS().canExecute(file2));
+ recorder.assertNoEvent();
+
+ // Create branch from first commit
+ assertNotNull(git.checkout().setCreateBranch(true).setName("b1")
+ .setStartPoint(Constants.HEAD + "~1").call());
+ recorder.assertEvent(ChangeRecorder.EMPTY,
+ new String[] { "file2.txt" });
+
+ // Change content and file mode in working directory and index
+ file1 = writeTrashFile("file1.txt", "c");
+ db.getFS().setExecute(file1, true);
+ git.add().addFilepattern("file1.txt").call();
+
+ // Switch back to 'master'
+ assertNotNull(git.checkout().setName(Constants.MASTER).call());
+ recorder.assertEvent(new String[] { "file2.txt" },
+ ChangeRecorder.EMPTY);
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test(expected = CheckoutConflictException.class)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
index 0111b9411d..d89aabe75f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
@@ -43,11 +43,13 @@
package org.eclipse.jgit.lib;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
+import java.util.Set;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -118,6 +120,31 @@ public class IndexDiffSubmoduleTest extends RepositoryTestCase {
assertTrue(indexDiff.diff());
}
+ private void assertDiff(IndexDiff indexDiff, IgnoreSubmoduleMode mode,
+ IgnoreSubmoduleMode... expectedEmptyModes) throws IOException {
+ boolean diffResult = indexDiff.diff();
+ Set<String> submodulePaths = indexDiff
+ .getPathsWithIndexMode(FileMode.GITLINK);
+ boolean emptyExpected = false;
+ for (IgnoreSubmoduleMode empty : expectedEmptyModes) {
+ if (mode.equals(empty)) {
+ emptyExpected = true;
+ break;
+ }
+ }
+ if (emptyExpected) {
+ assertFalse("diff should be false with mode=" + mode,
+ diffResult);
+ assertEquals("should have no paths with FileMode.GITLINK", 0,
+ submodulePaths.size());
+ } else {
+ assertTrue("diff should be true with mode=" + mode,
+ diffResult);
+ assertTrue("submodule path should have FileMode.GITLINK",
+ submodulePaths.contains("modules/submodule"));
+ }
+ }
+
@Theory
public void testDirtySubmoduleWorktree(IgnoreSubmoduleMode mode)
throws IOException {
@@ -125,13 +152,8 @@ public class IndexDiffSubmoduleTest extends RepositoryTestCase {
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL)
- || mode.equals(IgnoreSubmoduleMode.DIRTY))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
+ IgnoreSubmoduleMode.DIRTY);
}
@Theory
@@ -145,12 +167,7 @@ public class IndexDiffSubmoduleTest extends RepositoryTestCase {
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL);
}
@Theory
@@ -163,13 +180,8 @@ public class IndexDiffSubmoduleTest extends RepositoryTestCase {
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL)
- || mode.equals(IgnoreSubmoduleMode.DIRTY))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
+ IgnoreSubmoduleMode.DIRTY);
}
@Theory
@@ -183,13 +195,8 @@ public class IndexDiffSubmoduleTest extends RepositoryTestCase {
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL)
- || mode.equals(IgnoreSubmoduleMode.DIRTY))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
+ IgnoreSubmoduleMode.DIRTY);
}
@Theory
@@ -200,13 +207,7 @@ public class IndexDiffSubmoduleTest extends RepositoryTestCase {
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL)
- || mode.equals(IgnoreSubmoduleMode.DIRTY)
- || mode.equals(IgnoreSubmoduleMode.UNTRACKED))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
+ IgnoreSubmoduleMode.DIRTY, IgnoreSubmoduleMode.UNTRACKED);
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index 43160fb115..7475d69f6c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.lib;
import static java.lang.Integer.valueOf;
+import static org.eclipse.jgit.junit.JGitTestUtil.concat;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
@@ -62,6 +63,7 @@ import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
+import static org.eclipse.jgit.util.RawParseUtils.decode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
@@ -72,11 +74,52 @@ import java.text.MessageFormat;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
public class ObjectCheckerTest {
+ private static final ObjectChecker SECRET_KEY_CHECKER = new ObjectChecker() {
+ @Override
+ public void checkBlob(byte[] raw) throws CorruptObjectException {
+ String in = decode(raw);
+ if (in.contains("secret_key")) {
+ throw new CorruptObjectException("don't add a secret key");
+ }
+ }
+ };
+
+ private static final ObjectChecker SECRET_KEY_BLOB_CHECKER = new ObjectChecker() {
+ @Override
+ public BlobObjectChecker newBlobObjectChecker() {
+ return new BlobObjectChecker() {
+ private boolean containSecretKey;
+
+ @Override
+ public void update(byte[] in, int offset, int len) {
+ String str = decode(in, offset, offset + len);
+ if (str.contains("secret_key")) {
+ containSecretKey = true;
+ }
+ }
+
+ @Override
+ public void endBlob(AnyObjectId id)
+ throws CorruptObjectException {
+ if (containSecretKey) {
+ throw new CorruptObjectException(
+ "don't add a secret key");
+ }
+ }
+ };
+ }
+ };
+
private ObjectChecker checker;
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
@Before
public void setUp() throws Exception {
checker = new ObjectChecker();
@@ -101,6 +144,32 @@ public class ObjectCheckerTest {
}
@Test
+ public void testCheckBlobNotCorrupt() throws CorruptObjectException {
+ SECRET_KEY_CHECKER.check(OBJ_BLOB, encodeASCII("key = \"public_key\""));
+ }
+
+ @Test
+ public void testCheckBlobCorrupt() throws CorruptObjectException {
+ thrown.expect(CorruptObjectException.class);
+ SECRET_KEY_CHECKER.check(OBJ_BLOB, encodeASCII("key = \"secret_key\""));
+ }
+
+ @Test
+ public void testCheckBlobWithBlobObjectCheckerNotCorrupt()
+ throws CorruptObjectException {
+ SECRET_KEY_BLOB_CHECKER.check(OBJ_BLOB,
+ encodeASCII("key = \"public_key\""));
+ }
+
+ @Test
+ public void testCheckBlobWithBlobObjectCheckerCorrupt()
+ throws CorruptObjectException {
+ thrown.expect(CorruptObjectException.class);
+ SECRET_KEY_BLOB_CHECKER.check(OBJ_BLOB,
+ encodeASCII("key = \"secret_key\""));
+ }
+
+ @Test
public void testValidCommitNoParent() throws CorruptObjectException {
StringBuilder b = new StringBuilder();
@@ -1054,20 +1123,7 @@ public class ObjectCheckerTest {
checker.checkTree(data);
}
- private static byte[] concat(byte[]... b) {
- int n = 0;
- for (byte[] a : b) {
- n += a.length;
- }
- byte[] data = new byte[n];
- n = 0;
- for (byte[] a : b) {
- System.arraycopy(a, 0, data, n, a.length);
- n += a.length;
- }
- return data;
- }
@Test
public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd()
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
index 7db9f60fd9..15f28afa6a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
@@ -179,4 +179,4 @@ public class ReflogResolveTest extends RepositoryTestCase {
}
}
}
-} \ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java
new file mode 100644
index 0000000000..fb8dec51d3
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
+import org.junit.Test;
+
+public class SubmoduleConfigTest {
+ @Test
+ public void fetchRecurseMatch() throws Exception {
+ assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("yes"));
+ assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("YES"));
+ assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("true"));
+ assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("TRUE"));
+
+ assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("on-demand"));
+ assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ON-DEMAND"));
+ assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("on_demand"));
+ assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ON_DEMAND"));
+
+ assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("no"));
+ assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("NO"));
+ assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("false"));
+ assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("FALSE"));
+ }
+
+ @Test
+ public void fetchRecurseNoMatch() throws Exception {
+ assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue("Y"));
+ assertFalse(FetchRecurseSubmodulesMode.NO.matchConfigValue("N"));
+ assertFalse(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ONDEMAND"));
+ assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue(""));
+ assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue(null));
+ }
+
+ @Test
+ public void fetchRecurseToConfigValue() {
+ assertEquals("on-demand",
+ FetchRecurseSubmodulesMode.ON_DEMAND.toConfigValue());
+ assertEquals("true", FetchRecurseSubmodulesMode.YES.toConfigValue());
+ assertEquals("false", FetchRecurseSubmodulesMode.NO.toConfigValue());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
index 2451c50f6f..077645e650 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
@@ -171,4 +171,4 @@ public class RevWalkMergeBaseTest extends RevWalkTestCase {
assertNull(rw.next());
}
-} \ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
index 353a487732..cf02aa84c6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
@@ -81,4 +81,4 @@ public class SkipRevFilterTest extends RevWalkTestCase {
public void testSkipRevFilterNegative() throws Exception {
SkipRevFilter.create(-1);
}
-} \ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
index 5c46659c0a..f42dd02814 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
@@ -269,4 +269,4 @@ public class SubmoduleAddTest extends RepositoryTestCase {
ConfigConstants.CONFIG_KEY_URL));
}
}
-} \ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
index 61df9d93f1..5832518f81 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
@@ -59,11 +59,11 @@ import org.eclipse.jgit.dircache.DirCacheEditor;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -256,11 +256,16 @@ public class SubmoduleStatusTest extends RepositoryTestCase {
}
@Test
- public void repositoryWithInitializedSubmodule() throws IOException,
- GitAPIException {
- final ObjectId id = ObjectId
- .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
- final String path = "sub";
+ public void repositoryWithInitializedSubmodule() throws Exception {
+ String path = "sub";
+ Repository subRepo = Git.init().setBare(false)
+ .setDirectory(new File(db.getWorkTree(), path)).call()
+ .getRepository();
+ assertNotNull(subRepo);
+
+ TestRepository<?> subTr = new TestRepository<>(subRepo);
+ ObjectId id = subTr.branch(Constants.HEAD).commit().create().copy();
+
DirCache cache = db.lockDirCache();
DirCacheEditor editor = cache.editor();
editor.add(new PathEdit(path) {
@@ -287,15 +292,6 @@ public class SubmoduleStatusTest extends RepositoryTestCase {
ConfigConstants.CONFIG_KEY_URL, url);
modulesConfig.save();
- Repository subRepo = Git.init().setBare(false)
- .setDirectory(new File(db.getWorkTree(), path)).call()
- .getRepository();
- assertNotNull(subRepo);
-
- RefUpdate update = subRepo.updateRef(Constants.HEAD, true);
- update.setNewObjectId(id);
- update.forceUpdate();
-
SubmoduleStatusCommand command = new SubmoduleStatusCommand(db);
Map<String, SubmoduleStatus> statuses = command.call();
assertNotNull(statuses);
@@ -312,11 +308,16 @@ public class SubmoduleStatusTest extends RepositoryTestCase {
}
@Test
- public void repositoryWithDifferentRevCheckedOutSubmodule()
- throws IOException, GitAPIException {
- final ObjectId id = ObjectId
- .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
- final String path = "sub";
+ public void repositoryWithDifferentRevCheckedOutSubmodule() throws Exception {
+ String path = "sub";
+ Repository subRepo = Git.init().setBare(false)
+ .setDirectory(new File(db.getWorkTree(), path)).call()
+ .getRepository();
+ assertNotNull(subRepo);
+
+ TestRepository<?> subTr = new TestRepository<>(subRepo);
+ ObjectId id = subTr.branch(Constants.HEAD).commit().create().copy();
+
DirCache cache = db.lockDirCache();
DirCacheEditor editor = cache.editor();
editor.add(new PathEdit(path) {
@@ -343,15 +344,7 @@ public class SubmoduleStatusTest extends RepositoryTestCase {
ConfigConstants.CONFIG_KEY_URL, url);
modulesConfig.save();
- Repository subRepo = Git.init().setBare(false)
- .setDirectory(new File(db.getWorkTree(), path)).call()
- .getRepository();
- assertNotNull(subRepo);
-
- RefUpdate update = subRepo.updateRef(Constants.HEAD, true);
- update.setNewObjectId(ObjectId
- .fromString("aaaa0000aaaa0000aaaa0000aaaa0000aaaa0000"));
- update.forceUpdate();
+ ObjectId newId = subTr.branch(Constants.HEAD).commit().create().copy();
SubmoduleStatusCommand command = new SubmoduleStatusCommand(db);
Map<String, SubmoduleStatus> statuses = command.call();
@@ -365,7 +358,7 @@ public class SubmoduleStatusTest extends RepositoryTestCase {
assertNotNull(status);
assertEquals(path, status.getPath());
assertEquals(id, status.getIndexId());
- assertEquals(update.getNewObjectId(), status.getHeadId());
+ assertEquals(newId, status.getHeadId());
assertEquals(SubmoduleStatusType.REV_CHECKED_OUT, status.getType());
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
index 8998a85462..fed22c0262 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
@@ -444,4 +444,44 @@ public class SubmoduleWalkTest extends RepositoryTestCase {
assertNull(gen.getRepository());
assertFalse(gen.next());
}
+
+ @Test
+ public void testTreeIteratorWithGitmodulesNameNotPath() throws Exception {
+ final ObjectId subId = ObjectId
+ .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ final String path = "sub";
+ final String arbitraryName = "x";
+
+ final Config gitmodules = new Config();
+ gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName,
+ CONFIG_KEY_PATH, "sub");
+ gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName,
+ CONFIG_KEY_URL, "git://example.com/sub");
+
+ RevCommit commit = testDb.getRevWalk()
+ .parseCommit(testDb.commit().noParents()
+ .add(DOT_GIT_MODULES, gitmodules.toText())
+ .edit(new PathEdit(path) {
+
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.GITLINK);
+ ent.setObjectId(subId);
+ }
+ }).create());
+
+ final CanonicalTreeParser p = new CanonicalTreeParser();
+ p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree());
+ SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub");
+ assertEquals(path, gen.getPath());
+ assertEquals(subId, gen.getObjectId());
+ assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
+ assertNull(gen.getConfigUpdate());
+ assertNull(gen.getConfigUrl());
+ assertEquals("sub", gen.getModulesPath());
+ assertNull(gen.getModulesUpdate());
+ assertEquals("git://example.com/sub", gen.getModulesUrl());
+ assertNull(gen.getRepository());
+ assertFalse(gen.next());
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java
new file mode 100644
index 0000000000..a5e5441405
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch>
+ * 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.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.net.InetSocketAddress;
+
+import org.junit.Test;
+
+/**
+ * Daemon tests.
+ */
+public class DaemonTest {
+
+ @Test
+ public void testDaemonStop() throws Exception {
+ Daemon d = new Daemon();
+ d.start();
+ InetSocketAddress address = d.getAddress();
+ assertTrue("Port should be allocated", address.getPort() > 0);
+ assertTrue("Daemon should be running", d.isRunning());
+ Thread.sleep(1000); // Give it time to enter accept()
+ d.stopAndWait();
+ // Try to start a new Daemon again on the same port
+ d = new Daemon(address);
+ d.start();
+ InetSocketAddress newAddress = d.getAddress();
+ assertEquals("New daemon should run on the same port", address,
+ newAddress);
+ assertTrue("Daemon should be running", d.isRunning());
+ Thread.sleep(1000);
+ d.stopAndWait();
+ }
+
+ @Test
+ public void testDaemonRestart() throws Exception {
+ Daemon d = new Daemon();
+ d.start();
+ InetSocketAddress address = d.getAddress();
+ assertTrue("Port should be allocated", address.getPort() > 0);
+ assertTrue("Daemon should be running", d.isRunning());
+ Thread.sleep(1000);
+ d.stopAndWait();
+ // Re-start the same daemon
+ d.start();
+ InetSocketAddress newAddress = d.getAddress();
+ assertEquals("Daemon should again run on the same port", address,
+ newAddress);
+ assertTrue("Daemon should be running", d.isRunning());
+ Thread.sleep(1000);
+ d.stopAndWait();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java
new file mode 100644
index 0000000000..c6b016a4cc
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * 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.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for correctly resolving URIs when reading http.* values from a
+ * {@link Config}.
+ */
+public class HttpConfigTest {
+
+ private static final String DEFAULT = "[http]\n" + "\tpostBuffer = 1\n"
+ + "\tsslVerify= true\n" + "\tfollowRedirects = true\n"
+ + "\tmaxRedirects = 5\n\n";
+
+ private Config config;
+
+ @Before
+ public void setUp() {
+ config = new Config();
+ }
+
+ @Test
+ public void testDefault() throws Exception {
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024 * 1024, http.getPostBuffer());
+ assertTrue(http.isSslVerify());
+ assertEquals(HttpConfig.HttpRedirectMode.INITIAL,
+ http.getFollowRedirects());
+ }
+
+ @Test
+ public void testMatchSuccess() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+ + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("https://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.org/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com:80/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com:8080/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithOnlySchemeInConfig() throws Exception {
+ config.fromText(
+ DEFAULT + "[http \"http://\"]\n" + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithPrefixUriInConfig() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example\"]\n"
+ + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchCaseSensitivity() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://exAMPle.com\"]\n"
+ + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithInvalidUriInConfig() throws Exception {
+ config.fromText(
+ DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithInvalidAndValidUriInConfig() throws Exception {
+ config.fromText(DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n"
+ + "[http \"http://example.com\"]\n" + "\tpostBuffer = 2048\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(2048, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithHostEndingInSlash() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example.com/\"]\n"
+ + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithUser() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example.com/path\"]\n"
+ + "\tpostBuffer = 1024\n"
+ + "[http \"http://example.com/path/repo\"]\n"
+ + "\tpostBuffer = 2048\n"
+ + "[http \"http://user@example.com/path\"]\n"
+ + "\tpostBuffer = 4096\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://user@example.com/path/repo.git"));
+ assertEquals(4096, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://user@example.com/path/repo/foo.git"));
+ assertEquals(2048, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://user@example.com/path/foo.git"));
+ assertEquals(4096, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com/path/foo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://User@example.com/path/repo/foo.git"));
+ assertEquals(2048, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://User@example.com/path/foo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchLonger() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example.com/path\"]\n"
+ + "\tpostBuffer = 1024\n"
+ + "[http \"http://example.com/path/repo\"]\n"
+ + "\tpostBuffer = 2048\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com/foo/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("https://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo/.git"));
+ assertEquals(2048, http.getPostBuffer());
+ http = new HttpConfig(config, new URIish("http://example.com/path"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://user@example.com/path"));
+ assertEquals(1024, http.getPostBuffer());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java
new file mode 100644
index 0000000000..94de2f211a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * 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.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+/**
+ * Basic URI path prefix match tests for {@link HttpConfig}.
+ */
+public class HttpConfigUriPathTest {
+
+ @Test
+ public void testNormalizationEmptyPaths() {
+ assertEquals("/", HttpConfig.normalize(""));
+ assertEquals("/", HttpConfig.normalize("/"));
+ }
+
+ @Test
+ public void testNormalization() {
+ assertEquals("/f", HttpConfig.normalize("f"));
+ assertEquals("/f", HttpConfig.normalize("/f"));
+ assertEquals("/f/", HttpConfig.normalize("/f/"));
+ assertEquals("/foo", HttpConfig.normalize("foo"));
+ assertEquals("/foo", HttpConfig.normalize("/foo"));
+ assertEquals("/foo/", HttpConfig.normalize("/foo/"));
+ assertEquals("/foo/bar", HttpConfig.normalize("foo/bar"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar"));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/"));
+ }
+
+ @Test
+ public void testNormalizationWithDot() {
+ assertEquals("/", HttpConfig.normalize("."));
+ assertEquals("/", HttpConfig.normalize("/."));
+ assertEquals("/", HttpConfig.normalize("/./"));
+ assertEquals("/foo", HttpConfig.normalize("foo/."));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/./bar"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/."));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/./"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/./././bar"));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/./././bar/"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/././."));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/./././"));
+ assertEquals("/foo/bar/.baz/bam",
+ HttpConfig.normalize("/foo/bar/.baz/bam"));
+ assertEquals("/foo/bar/.baz/bam/",
+ HttpConfig.normalize("/foo/bar/.baz/bam/"));
+ }
+
+ @Test
+ public void testNormalizationWithDotDot() {
+ assertEquals("/", HttpConfig.normalize("foo/.."));
+ assertEquals("/", HttpConfig.normalize("/foo/.."));
+ assertEquals("/", HttpConfig.normalize("/foo/../bar/.."));
+ assertEquals("/", HttpConfig.normalize("/foo/.././bar/.."));
+ assertEquals("/bar", HttpConfig.normalize("foo/../bar"));
+ assertEquals("/bar", HttpConfig.normalize("/foo/../bar"));
+ assertEquals("/bar", HttpConfig.normalize("/foo/./.././bar"));
+ assertEquals("/bar/", HttpConfig.normalize("/foo/../bar/"));
+ assertEquals("/bar/", HttpConfig.normalize("/foo/./.././bar/"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/baz/.."));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/baz/../"));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../.."));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../.."));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/.././.."));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../././.."));
+ assertEquals("/foo/baz", HttpConfig.normalize("/foo/bar/../baz"));
+ assertEquals("/foo/baz/", HttpConfig.normalize("/foo/bar/../baz/"));
+ assertEquals("/foo/baz", HttpConfig.normalize("/foo/bar/../baz/."));
+ assertEquals("/foo/baz/", HttpConfig.normalize("/foo/bar/../baz/./"));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/../baz/.."));
+ assertEquals("/foo/", HttpConfig.normalize("/foo/bar/../baz/../"));
+ assertEquals("/baz", HttpConfig.normalize("/foo/bar/../../baz"));
+ assertEquals("/baz/", HttpConfig.normalize("/foo/bar/../../baz/"));
+ assertEquals("/foo/.b/bar", HttpConfig.normalize("/foo/.b/bar"));
+ assertEquals("/.f/foo/.b/bar/", HttpConfig.normalize(".f/foo/.b/bar/"));
+ assertEquals("/foo/bar/..baz/bam",
+ HttpConfig.normalize("/foo/bar/..baz/bam"));
+ assertEquals("/foo/bar/..baz/bam/",
+ HttpConfig.normalize("/foo/bar/..baz/bam/"));
+ assertEquals("/foo/bar/.../baz/bam",
+ HttpConfig.normalize("/foo/bar/.../baz/bam"));
+ assertEquals("/foo/bar/.../baz/bam/",
+ HttpConfig.normalize("/foo/bar/.../baz/bam/"));
+ }
+
+ @Test
+ public void testNormalizationWithDoubleSlash() {
+ assertEquals("/", HttpConfig.normalize("//"));
+ assertEquals("/foo/", HttpConfig.normalize("///foo//"));
+ assertEquals("/foo", HttpConfig.normalize("///foo//."));
+ assertEquals("/foo/", HttpConfig.normalize("///foo//.////"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo//bar"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo//bar//."));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo//bar//./"));
+ }
+
+ @Test
+ public void testNormalizationWithDotDotFailing() {
+ assertNull(HttpConfig.normalize(".."));
+ assertNull(HttpConfig.normalize("/.."));
+ assertNull(HttpConfig.normalize("/../"));
+ assertNull(HttpConfig.normalize("/../foo"));
+ assertNull(HttpConfig.normalize("./../foo"));
+ assertNull(HttpConfig.normalize("/./../foo"));
+ assertNull(HttpConfig.normalize("/foo/./.././.."));
+ assertNull(HttpConfig.normalize("/foo/../bar/../.."));
+ assertNull(HttpConfig.normalize("/foo/../bar/../../baz"));
+ }
+
+ @Test
+ public void testSegmentCompare() {
+ // 2nd parameter is the match, will be normalized
+ assertSuccess("/foo", "");
+ assertSuccess("/foo", "/");
+ assertSuccess("/foo", "//");
+ assertSuccess("/foo", "foo");
+ assertSuccess("/foo", "/foo");
+ assertSuccess("/foo/", "foo");
+ assertSuccess("/foo/", "/foo");
+ assertSuccess("/foo/", "foo/");
+ assertSuccess("/foo/", "/foo/");
+ assertSuccess("/foo/bar", "foo");
+ assertSuccess("/foo/bar", "foo/");
+ assertSuccess("/foo/bar", "foo/bar");
+ assertSuccess("/foo/bar/", "foo/bar");
+ assertSuccess("/foo/bar/", "foo/bar/");
+ assertSuccess("/foo/bar", "/foo/bar");
+ assertSuccess("/foo/bar/", "/foo/bar");
+ assertSuccess("/foo/bar/", "/foo/bar/");
+ assertSuccess("/foo/bar", "/foo/bar/..");
+ assertSuccess("/foo/bar/", "/foo/bar/..");
+ assertSuccess("/foo/bar/", "/foo/bar/../");
+ assertSuccess("/foo/bar", "/foo/./bar");
+ assertSuccess("/foo/bar/", "/foo/./bar/");
+ assertSuccess("/some/repo/.git", "/some/repo");
+ assertSuccess("/some/repo/bare.git", "/some/repo");
+ assertSuccess("/some/repo/.git", "/some/repo/.git");
+ assertSuccess("/some/repo/bare.git", "/some/repo/bare.git");
+ }
+
+ @Test
+ public void testSegmentCompareFailing() {
+ // 2nd parameter is the match, will be normalized
+ assertEquals(-1, HttpConfig.segmentCompare("/foo", "foo/"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo", "/foo/"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foobar", "foo"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foobar", "/foo"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/foo/barbar/baz", "foo/bar"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo/barbar", "/foo/bar"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/some/repo.git", "/some/repo"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/some/repo.git", "/some/repo.g"));
+ assertEquals(-1, HttpConfig.segmentCompare("/some/repo/bare.git",
+ "/some/repo/bar"));
+ assertSuccess("/some/repo/bare.git", "/some/repo");
+ // Just to make sure we don't use the PathMatchers...
+ assertEquals(-1, HttpConfig.segmentCompare("/foo/barbar/baz", "**"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/foo/barbar/baz", "**/foo"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/foo/barbar/baz", "/*/barbar/**"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo", "/*"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo", "/???"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo/bar/baz", "bar"));
+ // Failing to normalize
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/foo/bar/baz", "bar/../.."));
+ }
+
+ private void assertSuccess(String uri, String match) {
+ String normalized = HttpConfig.normalize(match);
+ assertEquals(normalized.length(),
+ HttpConfig.segmentCompare(uri, match));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java
new file mode 100644
index 0000000000..1e65a20d7f
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * 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.transport;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.util.FS;
+import org.junit.After;
+import org.junit.Test;
+
+import com.jcraft.jsch.Session;
+
+/**
+ * Tests for correctly interpreting ssh config values when Jsch sessions are
+ * used.
+ */
+public class JschConfigSessionFactoryTest {
+
+ File tmpConfigFile;
+
+ OpenSshConfig tmpConfig;
+
+ DefaultSshSessionFactory factory = new DefaultSshSessionFactory();
+
+ @After
+ public void removeTmpConfig() {
+ if (tmpConfigFile == null) {
+ return;
+ }
+ if (tmpConfigFile.exists() && !tmpConfigFile.delete()) {
+ tmpConfigFile.deleteOnExit();
+ }
+ tmpConfigFile = null;
+ }
+
+ @Test
+ public void testNoConfigEntry() throws Exception {
+ tmpConfigFile = File.createTempFile("jsch", "test");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://egit/egit/egit");
+ assertEquals("egit", session.getHost());
+ // No user in URI, none in ssh config: default is OS user name
+ assertEquals(System.getProperty("user.name"), session.getUserName());
+ assertEquals(22, session.getPort());
+ }
+
+ @Test
+ public void testAlias() throws Exception {
+ tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org",
+ "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://egit/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ }
+
+ @Test
+ public void testAliasWithUser() throws Exception {
+ tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org",
+ "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bar@egit/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(29418, session.getPort());
+ }
+
+ @Test
+ public void testAliasWithPort() throws Exception {
+ tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org",
+ "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bar@egit:22/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(22, session.getPort());
+ }
+
+ @Test
+ public void testAliasIdentical() throws Exception {
+ tmpConfigFile = createConfig("Host git.eclipse.org",
+ "Hostname git.eclipse.org", "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://git.eclipse.org/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ }
+
+ @Test
+ public void testAliasIdenticalWithUser() throws Exception {
+ tmpConfigFile = createConfig("Host git.eclipse.org",
+ "Hostname git.eclipse.org", "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bar@git.eclipse.org/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(29418, session.getPort());
+ }
+
+ @Test
+ public void testAliasIdenticalWithPort() throws Exception {
+ tmpConfigFile = createConfig("Host git.eclipse.org",
+ "Hostname git.eclipse.org", "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession(
+ "ssh://bar@git.eclipse.org:300/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(300, session.getPort());
+ }
+
+ @Test
+ public void testConnectTimout() throws Exception {
+ tmpConfigFile = createConfig("Host git.eclipse.org",
+ "Hostname git.eclipse.org", "User foo", "Port 29418",
+ "ConnectTimeout 10");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://git.eclipse.org/something");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout());
+ }
+
+ @Test
+ public void testAliasCaseDifferenceUpcase() throws Exception {
+ tmpConfigFile = createConfig("Host Bitbucket.org",
+ "Hostname bitbucket.org", "User foo", "Port 29418",
+ "ConnectTimeout 10", //
+ "Host bitbucket.org", "Hostname bitbucket.org", "User bar",
+ "Port 22", "ConnectTimeout 5");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://Bitbucket.org/something");
+ assertEquals("bitbucket.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout());
+ }
+
+ @Test
+ public void testAliasCaseDifferenceLowcase() throws Exception {
+ tmpConfigFile = createConfig("Host Bitbucket.org",
+ "Hostname bitbucket.org", "User foo", "Port 29418",
+ "ConnectTimeout 10", //
+ "Host bitbucket.org", "Hostname bitbucket.org", "User bar",
+ "Port 22", "ConnectTimeout 5");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bitbucket.org/something");
+ assertEquals("bitbucket.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(22, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(5), session.getTimeout());
+ }
+
+ @Test
+ public void testAliasCaseDifferenceUpcaseInverted() throws Exception {
+ tmpConfigFile = createConfig("Host bitbucket.org",
+ "Hostname bitbucket.org", "User bar", "Port 22",
+ "ConnectTimeout 5", //
+ "Host Bitbucket.org", "Hostname bitbucket.org", "User foo",
+ "Port 29418", "ConnectTimeout 10");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://Bitbucket.org/something");
+ assertEquals("bitbucket.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout());
+ }
+
+ @Test
+ public void testAliasCaseDifferenceLowcaseInverted() throws Exception {
+ tmpConfigFile = createConfig("Host bitbucket.org",
+ "Hostname bitbucket.org", "User bar", "Port 22",
+ "ConnectTimeout 5", //
+ "Host Bitbucket.org", "Hostname bitbucket.org", "User foo",
+ "Port 29418", "ConnectTimeout 10");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bitbucket.org/something");
+ assertEquals("bitbucket.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(22, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(5), session.getTimeout());
+ }
+
+ private File createConfig(String... lines) throws Exception {
+ File f = File.createTempFile("jsch", "test");
+ Files.write(f.toPath(), Arrays.asList(lines));
+ return f;
+ }
+
+ private Session createSession(String uriText) throws Exception {
+ // For this test to make sense, these few lines must correspond to the
+ // code in JschConfigSessionFactory.getSession(). Because of
+ // side-effects we cannot encapsulate that there properly and so we have
+ // to duplicate this bit here. We also can't test getSession() itself
+ // since it would try to actually connect to a server.
+ URIish uri = new URIish(uriText);
+ String host = uri.getHost();
+ String user = uri.getUser();
+ String password = uri.getPass();
+ int port = uri.getPort();
+ OpenSshConfig.Host hostConfig = tmpConfig.lookup(host);
+ if (port <= 0) {
+ port = hostConfig.getPort();
+ }
+ if (user == null) {
+ user = hostConfig.getUser();
+ }
+ return factory.createSession(null, FS.DETECTED, user, password, host,
+ port, hostConfig);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
index fc520ab17f..d604751fef 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2014 Google Inc.
+ * Copyright (C) 2008, 2017 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -43,10 +43,13 @@
package org.eclipse.jgit.transport;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.File;
@@ -58,9 +61,12 @@ import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.OpenSshConfig.Host;
import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.SystemReader;
import org.junit.Before;
import org.junit.Test;
+import com.jcraft.jsch.ConfigRepository;
+
public class OpenSshConfigTest extends RepositoryTestCase {
private File home;
@@ -79,15 +85,18 @@ public class OpenSshConfigTest extends RepositoryTestCase {
configFile = new File(new File(home, ".ssh"), Constants.CONFIG);
FileUtils.mkdir(configFile.getParentFile());
- System.setProperty("user.name", "jex_junit");
+ mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "jex_junit");
osc = new OpenSshConfig(home, configFile);
}
private void config(final String data) throws IOException {
- final OutputStreamWriter fw = new OutputStreamWriter(
- new FileOutputStream(configFile), "UTF-8");
- fw.write(data);
- fw.close();
+ long lastMtime = configFile.lastModified();
+ do {
+ try (final OutputStreamWriter fw = new OutputStreamWriter(
+ new FileOutputStream(configFile), "UTF-8")) {
+ fw.write(data);
+ }
+ } while (lastMtime == configFile.lastModified());
}
@Test
@@ -155,13 +164,18 @@ public class OpenSshConfigTest extends RepositoryTestCase {
@Test
public void testAlias_DoesNotMatch() throws Exception {
- config("Host orcz\n" + "\tHostName repo.or.cz\n");
+ config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n");
final Host h = osc.lookup("repo.or.cz");
assertNotNull(h);
assertEquals("repo.or.cz", h.getHostName());
assertEquals("jex_junit", h.getUser());
assertEquals(22, h.getPort());
assertNull(h.getIdentityFile());
+ final Host h2 = osc.lookup("orcz");
+ assertEquals("repo.or.cz", h.getHostName());
+ assertEquals("jex_junit", h.getUser());
+ assertEquals(29418, h2.getPort());
+ assertNull(h.getIdentityFile());
}
@Test
@@ -282,4 +296,198 @@ public class OpenSshConfigTest extends RepositoryTestCase {
assertNotNull(h);
assertEquals(1, h.getConnectionAttempts());
}
+
+ @Test
+ public void testDefaultBlock() throws Exception {
+ config("ConnectionAttempts 5\n\nHost orcz\nConnectionAttempts 3\n");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(5, h.getConnectionAttempts());
+ }
+
+ @Test
+ public void testHostCaseInsensitive() throws Exception {
+ config("hOsT orcz\nConnectionAttempts 3\n");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(3, h.getConnectionAttempts());
+ }
+
+ @Test
+ public void testListValueSingle() throws Exception {
+ config("Host orcz\nUserKnownHostsFile /foo/bar\n");
+ final ConfigRepository.Config c = osc.getConfig("orcz");
+ assertNotNull(c);
+ assertEquals("/foo/bar", c.getValue("UserKnownHostsFile"));
+ }
+
+ @Test
+ public void testListValueMultiple() throws Exception {
+ // Tilde expansion occurs within the parser
+ config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" /foo/bar \n");
+ final ConfigRepository.Config c = osc.getConfig("orcz");
+ assertNotNull(c);
+ assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
+ "/foo/bar" },
+ c.getValues("UserKnownHostsFile"));
+ }
+
+ @Test
+ public void testRepeatedLookups() throws Exception {
+ config("Host orcz\n" + "\tConnectionAttempts 5\n");
+ final Host h1 = osc.lookup("orcz");
+ final Host h2 = osc.lookup("orcz");
+ assertNotNull(h1);
+ assertSame(h1, h2);
+ assertEquals(5, h1.getConnectionAttempts());
+ assertEquals(h1.getConnectionAttempts(), h2.getConnectionAttempts());
+ final ConfigRepository.Config c = osc.getConfig("orcz");
+ assertNotNull(c);
+ assertSame(c, h1.getConfig());
+ assertSame(c, h2.getConfig());
+ }
+
+ @Test
+ public void testRepeatedLookupsWithModification() throws Exception {
+ config("Host orcz\n" + "\tConnectionAttempts -1\n");
+ final Host h1 = osc.lookup("orcz");
+ assertNotNull(h1);
+ assertEquals(1, h1.getConnectionAttempts());
+ config("Host orcz\n" + "\tConnectionAttempts 5\n");
+ final Host h2 = osc.lookup("orcz");
+ assertNotNull(h2);
+ assertNotSame(h1, h2);
+ assertEquals(5, h2.getConnectionAttempts());
+ assertEquals(1, h1.getConnectionAttempts());
+ assertNotSame(h1.getConfig(), h2.getConfig());
+ }
+
+ @Test
+ public void testIdentityFile() throws Exception {
+ config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile /foo/bar");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ File f = h.getIdentityFile();
+ assertNotNull(f);
+ // Host does tilde replacement
+ assertEquals(new File(home, "foo/ba z"), f);
+ final ConfigRepository.Config c = h.getConfig();
+ // Config does tilde replacement, too
+ assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
+ "/foo/bar" },
+ c.getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testMultiIdentityFile() throws Exception {
+ config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile /foo/bar\nHOST *\nIdentityFile /foo/baz");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ File f = h.getIdentityFile();
+ assertNotNull(f);
+ // Host does tilde replacement
+ assertEquals(new File(home, "foo/ba z"), f);
+ final ConfigRepository.Config c = h.getConfig();
+ // Config does tilde replacement, too
+ assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
+ "/foo/bar", "/foo/baz" },
+ c.getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testNegatedPattern() throws Exception {
+ config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST !*.or.cz\nIdentityFile /foo/baz");
+ final Host h = osc.lookup("repo.or.cz");
+ assertNotNull(h);
+ assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
+ assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
+ h.getConfig().getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testPattern() throws Exception {
+ config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
+ final Host h = osc.lookup("repo.or.cz");
+ assertNotNull(h);
+ assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
+ assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
+ "/foo/baz" },
+ h.getConfig().getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testMultiHost() throws Exception {
+ config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
+ final Host h1 = osc.lookup("repo.or.cz");
+ assertNotNull(h1);
+ assertEquals(new File(home, "foo/bar"), h1.getIdentityFile());
+ assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
+ "/foo/baz" },
+ h1.getConfig().getValues("IdentityFile"));
+ final Host h2 = osc.lookup("orcz");
+ assertNotNull(h2);
+ assertEquals(new File(home, "foo/bar"), h2.getIdentityFile());
+ assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
+ h2.getConfig().getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testEqualsSign() throws Exception {
+ config("Host=orcz\n\tConnectionAttempts = 5\n\tUser=\t foobar\t\n");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(5, h.getConnectionAttempts());
+ assertEquals("foobar", h.getUser());
+ }
+
+ @Test
+ public void testMissingArgument() throws Exception {
+ config("Host=orcz\n\tSendEnv\nIdentityFile\t\nForwardX11\n\tUser=\t foobar\t\n");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals("foobar", h.getUser());
+ assertArrayEquals(new String[0], h.getConfig().getValues("SendEnv"));
+ assertNull(h.getIdentityFile());
+ assertNull(h.getConfig().getValue("ForwardX11"));
+ }
+
+ @Test
+ public void testHomeDirUserReplacement() throws Exception {
+ config("Host=orcz\n\tIdentityFile %d/.ssh/%u_id_dsa");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(new File(new File(home, ".ssh"), "jex_junit_id_dsa"),
+ h.getIdentityFile());
+ }
+
+ @Test
+ public void testHostnameReplacement() throws Exception {
+ config("Host=orcz\nHost *.*\n\tHostname %h\nHost *\n\tHostname %h.example.org");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals("orcz.example.org", h.getHostName());
+ }
+
+ @Test
+ public void testRemoteUserReplacement() throws Exception {
+ config("Host=orcz\n\tUser foo\n" + "Host *.*\n\tHostname %h\n"
+ + "Host *\n\tHostname %h.ex%%20ample.org\n\tIdentityFile ~/.ssh/%h_%r_id_dsa");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(
+ new File(new File(home, ".ssh"),
+ "orcz.ex%20ample.org_foo_id_dsa"),
+ h.getIdentityFile());
+ }
+
+ @Test
+ public void testLocalhostFQDNReplacement() throws Exception {
+ String localhost = SystemReader.getInstance().getHostname();
+ config("Host=orcz\n\tIdentityFile ~/.ssh/%l_id_dsa");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(
+ new File(new File(home, ".ssh"), localhost + "_id_dsa"),
+ h.getIdentityFile());
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java
new file mode 100644
index 0000000000..9610fbd6f1
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017, David Pursehouse <david.pursehouse@gmail.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.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.transport.PushConfig.PushRecurseSubmodulesMode;
+import org.junit.Test;
+
+public class PushConfigTest {
+ @Test
+ public void pushRecurseSubmoduleMatch() throws Exception {
+ assertTrue(PushRecurseSubmodulesMode.CHECK.matchConfigValue("check"));
+ assertTrue(PushRecurseSubmodulesMode.CHECK.matchConfigValue("CHECK"));
+
+ assertTrue(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("on-demand"));
+ assertTrue(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ON-DEMAND"));
+ assertTrue(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("on_demand"));
+ assertTrue(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ON_DEMAND"));
+
+ assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("no"));
+ assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("NO"));
+ assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("false"));
+ assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("FALSE"));
+ }
+
+ @Test
+ public void pushRecurseSubmoduleNoMatch() throws Exception {
+ assertFalse(PushRecurseSubmodulesMode.NO.matchConfigValue("N"));
+ assertFalse(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ONDEMAND"));
+ }
+
+ @Test
+ public void pushRecurseSubmoduleToConfigValue() {
+ assertEquals("on-demand",
+ PushRecurseSubmodulesMode.ON_DEMAND.toConfigValue());
+ assertEquals("check", PushRecurseSubmodulesMode.CHECK.toConfigValue());
+ assertEquals("false", PushRecurseSubmodulesMode.NO.toConfigValue());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
index 3411122888..8ef87cb3c1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
@@ -58,6 +58,8 @@ import java.security.MessageDigest;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.Deflater;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -159,6 +161,45 @@ public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCas
}
@Test
+ public void resetsHaves() throws Exception {
+ AtomicReference<Set<ObjectId>> haves = new AtomicReference<>();
+ try (TransportLocal t = new TransportLocal(src, uriOf(dst),
+ dst.getDirectory()) {
+ @Override
+ ReceivePack createReceivePack(Repository db) {
+ dst.incrementOpen();
+
+ ReceivePack rp = super.createReceivePack(dst);
+ rp.setAdvertiseRefsHook(new AdvertiseRefsHook() {
+ @Override
+ public void advertiseRefs(BaseReceivePack rp2)
+ throws ServiceMayNotContinueException {
+ rp.setAdvertisedRefs(rp.getRepository().getAllRefs(),
+ null);
+ new HidePrivateHook().advertiseRefs(rp);
+ haves.set(rp.getAdvertisedObjects());
+ }
+
+ @Override
+ public void advertiseRefs(UploadPack uploadPack)
+ throws ServiceMayNotContinueException {
+ throw new UnsupportedOperationException();
+ }
+ });
+ return rp;
+ }
+ }) {
+ try (PushConnection c = t.openPush()) {
+ // Just has to open/close for advertisement.
+ }
+ }
+
+ assertEquals(1, haves.get().size());
+ assertTrue(haves.get().contains(B));
+ assertFalse(haves.get().contains(P));
+ }
+
+ @Test
public void testSuccess() throws Exception {
// Manually force a delta of an object so we reuse it later.
//
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
index 0cada5c7ee..a0cf0d2dbc 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
@@ -51,6 +51,7 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -498,19 +499,30 @@ public class RemoteConfigTest {
}
@Test
- public void singlePushInsteadOf() throws Exception {
+ public void pushInsteadOfNotAppliedToPushUri() throws Exception {
config.setString("remote", "origin", "pushurl", "short:project.git");
config.setString("url", "https://server/repos/", "pushInsteadOf",
"short:");
RemoteConfig rc = new RemoteConfig(config, "origin");
assertFalse(rc.getPushURIs().isEmpty());
- assertEquals("https://server/repos/project.git", rc.getPushURIs()
- .get(0).toASCIIString());
+ assertEquals("short:project.git",
+ rc.getPushURIs().get(0).toASCIIString());
+ }
+
+ @Test
+ public void pushInsteadOfAppliedToUri() throws Exception {
+ config.setString("remote", "origin", "url", "short:project.git");
+ config.setString("url", "https://server/repos/", "pushInsteadOf",
+ "short:");
+ RemoteConfig rc = new RemoteConfig(config, "origin");
+ assertFalse(rc.getPushURIs().isEmpty());
+ assertEquals("https://server/repos/project.git",
+ rc.getPushURIs().get(0).toASCIIString());
}
@Test
public void multiplePushInsteadOf() throws Exception {
- config.setString("remote", "origin", "pushurl", "prefixproject.git");
+ config.setString("remote", "origin", "url", "prefixproject.git");
config.setStringList("url", "https://server/repos/", "pushInsteadOf",
Arrays.asList("pre", "prefix", "pref", "perf"));
RemoteConfig rc = new RemoteConfig(config, "origin");
@@ -518,4 +530,17 @@ public class RemoteConfigTest {
assertEquals("https://server/repos/project.git", rc.getPushURIs()
.get(0).toASCIIString());
}
+
+ @Test
+ public void pushInsteadOfNoPushUrl() throws Exception {
+ config.setString("remote", "origin", "url",
+ "http://git.eclipse.org/gitroot/jgit/jgit");
+ config.setStringList("url", "ssh://someone@git.eclipse.org:29418/",
+ "pushInsteadOf",
+ Collections.singletonList("http://git.eclipse.org/gitroot/"));
+ RemoteConfig rc = new RemoteConfig(config, "origin");
+ assertFalse(rc.getPushURIs().isEmpty());
+ assertEquals("ssh://someone@git.eclipse.org:29418/jgit/jgit",
+ rc.getPushURIs().get(0).toASCIIString());
+ }
}
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
new file mode 100644
index 0000000000..27c7674e9c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
@@ -0,0 +1,90 @@
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.UploadPack.RequestPolicy;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for server upload-pack utilities.
+ */
+public class UploadPackTest {
+ private URIish uri;
+
+ private TestProtocol<Object> testProtocol;
+
+ private Object ctx = new Object();
+
+ private InMemoryRepository server;
+
+ private InMemoryRepository client;
+
+ private RevCommit commit0;
+
+ private RevCommit commit1;
+
+ private RevCommit tip;
+
+ @Before
+ public void setUp() throws Exception {
+ server = newRepo("server");
+ client = newRepo("client");
+
+ TestRepository<InMemoryRepository> remote =
+ new TestRepository<>(server);
+ commit0 = remote.commit().message("0").create();
+ commit1 = remote.commit().message("1").parent(commit0).create();
+ tip = remote.commit().message("2").parent(commit1).create();
+ remote.update("master", tip);
+ }
+
+ @After
+ public void tearDown() {
+ Transport.unregister(testProtocol);
+ }
+
+ private static InMemoryRepository newRepo(String name) {
+ return new InMemoryRepository(new DfsRepositoryDescription(name));
+ }
+
+ @Test
+ public void testFetchParentOfShallowCommit() throws Exception {
+ testProtocol = new TestProtocol<>(
+ new UploadPackFactory<Object>() {
+ @Override
+ public UploadPack create(Object req, Repository db)
+ throws ServiceNotEnabledException,
+ ServiceNotAuthorizedException {
+ UploadPack up = new UploadPack(db);
+ up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);
+ // assume client has a shallow commit
+ up.getRevWalk().assumeShallow(
+ Collections.singleton(commit1.getId()));
+ return up;
+ }
+ }, null);
+ uri = testProtocol.register(ctx, server);
+
+ assertFalse(client.hasObject(commit0.toObjectId()));
+
+ // Fetch of the parent of the shallow commit
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
+ tn.fetch(NullProgressMonitor.INSTANCE,
+ Collections.singletonList(new RefSpec(commit0.name())));
+ assertTrue(client.hasObject(commit0.toObjectId()));
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
index 7c819c5eea..0394f68162 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
@@ -42,6 +42,14 @@
*/
package org.eclipse.jgit.treewalk.filter;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -53,14 +61,6 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-
public class PathFilterLogicTest extends RepositoryTestCase {
private ObjectId treeId;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java
index c6eca9d5e7..d6ea8c604c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java
@@ -44,6 +44,7 @@
package org.eclipse.jgit.util;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -186,6 +187,16 @@ public class IntListTest {
}
@Test
+ public void testContains() {
+ IntList i = new IntList();
+ i.add(1);
+ i.add(4);
+ assertTrue(i.contains(1));
+ assertTrue(i.contains(4));
+ assertFalse(i.contains(2));
+ }
+
+ @Test
public void testToString() {
final IntList i = new IntList();
i.add(1);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java
index 1a86aaff33..054c61e2be 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java
@@ -41,7 +41,7 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.transport;
+package org.eclipse.jgit.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java
index 7e11a61035..d2d44ffdc8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java
@@ -90,6 +90,24 @@ public class NBTest {
}
@Test
+ public void testDecodeUInt24() {
+ assertEquals(0, NB.decodeUInt24(b(0, 0, 0), 0));
+ assertEquals(0, NB.decodeUInt24(padb(3, 0, 0, 0), 3));
+
+ assertEquals(3, NB.decodeUInt24(b(0, 0, 3), 0));
+ assertEquals(3, NB.decodeUInt24(padb(3, 0, 0, 3), 3));
+
+ assertEquals(0xcede03, NB.decodeUInt24(b(0xce, 0xde, 3), 0));
+ assertEquals(0xbade03, NB.decodeUInt24(padb(3, 0xba, 0xde, 3), 3));
+
+ assertEquals(0x03bade, NB.decodeUInt24(b(3, 0xba, 0xde), 0));
+ assertEquals(0x03bade, NB.decodeUInt24(padb(3, 3, 0xba, 0xde), 3));
+
+ assertEquals(0xffffff, NB.decodeUInt24(b(0xff, 0xff, 0xff), 0));
+ assertEquals(0xffffff, NB.decodeUInt24(padb(3, 0xff, 0xff, 0xff), 3));
+ }
+
+ @Test
public void testDecodeInt32() {
assertEquals(0, NB.decodeInt32(b(0, 0, 0, 0), 0));
assertEquals(0, NB.decodeInt32(padb(3, 0, 0, 0, 0), 3));
@@ -198,6 +216,39 @@ public class NBTest {
}
@Test
+ public void testEncodeInt24() {
+ byte[] out = new byte[16];
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 0, 0);
+ assertOutput(b(0, 0, 0), out, 0);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 3, 0);
+ assertOutput(b(0, 0, 0), out, 3);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 0, 3);
+ assertOutput(b(0, 0, 3), out, 0);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 3, 3);
+ assertOutput(b(0, 0, 3), out, 3);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 0, 0xc0deac);
+ assertOutput(b(0xc0, 0xde, 0xac), out, 0);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 3, 0xbadeac);
+ assertOutput(b(0xba, 0xde, 0xac), out, 3);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 3, -1);
+ assertOutput(b(0xff, 0xff, 0xff), out, 3);
+ }
+
+ @Test
public void testEncodeInt32() {
final byte[] out = new byte[16];
@@ -315,10 +366,24 @@ public class NBTest {
return r;
}
+ private static byte[] b(int a, int b, int c) {
+ return new byte[] { (byte) a, (byte) b, (byte) c };
+ }
+
private static byte[] b(final int a, final int b, final int c, final int d) {
return new byte[] { (byte) a, (byte) b, (byte) c, (byte) d };
}
+ private static byte[] padb(int len, int a, int b, int c) {
+ final byte[] r = new byte[len + 4];
+ for (int i = 0; i < len; i++)
+ r[i] = (byte) 0xaf;
+ r[len] = (byte) a;
+ r[len + 1] = (byte) b;
+ r[len + 2] = (byte) c;
+ return r;
+ }
+
private static byte[] padb(final int len, final int a, final int b,
final int c, final int d) {
final byte[] r = new byte[len + 4];
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
index 593971478d..6efdce6d77 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
@@ -43,7 +43,7 @@
package org.eclipse.jgit.util;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotNull;
import java.io.UnsupportedEncodingException;
@@ -55,52 +55,51 @@ public class RawParseUtils_LineMapTest {
public void testEmpty() {
final IntList map = RawParseUtils.lineMap(new byte[] {}, 0, 0);
assertNotNull(map);
- assertEquals(2, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 0}, asInts(map));
}
@Test
public void testOneBlankLine() {
final IntList map = RawParseUtils.lineMap(new byte[] { '\n' }, 0, 1);
- assertEquals(3, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
- assertEquals(1, map.get(2));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 1}, asInts(map));
}
@Test
public void testTwoLineFooBar() throws UnsupportedEncodingException {
final byte[] buf = "foo\nbar\n".getBytes("ISO-8859-1");
final IntList map = RawParseUtils.lineMap(buf, 0, buf.length);
- assertEquals(4, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
- assertEquals(4, map.get(2));
- assertEquals(buf.length, map.get(3));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 4, buf.length}, asInts(map));
}
@Test
public void testTwoLineNoLF() throws UnsupportedEncodingException {
final byte[] buf = "foo\nbar".getBytes("ISO-8859-1");
final IntList map = RawParseUtils.lineMap(buf, 0, buf.length);
- assertEquals(4, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
- assertEquals(4, map.get(2));
- assertEquals(buf.length, map.get(3));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 4, buf.length}, asInts(map));
+ }
+
+ @Test
+ public void testBinary() throws UnsupportedEncodingException {
+ final byte[] buf = "xxxfoo\nb\0ar".getBytes("ISO-8859-1");
+ final IntList map = RawParseUtils.lineMap(buf, 3, buf.length);
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 3, buf.length}, asInts(map));
}
@Test
public void testFourLineBlanks() throws UnsupportedEncodingException {
final byte[] buf = "foo\n\n\nbar\n".getBytes("ISO-8859-1");
final IntList map = RawParseUtils.lineMap(buf, 0, buf.length);
- assertEquals(6, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
- assertEquals(4, map.get(2));
- assertEquals(5, map.get(3));
- assertEquals(6, map.get(4));
- assertEquals(buf.length, map.get(5));
+
+ assertArrayEquals(new int[]{
+ Integer.MIN_VALUE, 0, 4, 5, 6, buf.length
+ }, asInts(map));
+ }
+
+ private int[] asInts(IntList l) {
+ int[] result = new int[l.size()];
+ for (int i = 0; i < l.size(); i++) {
+ result[i] = l.get(i);
+ }
+ return result;
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
index 76687d15a5..fb76ec4e59 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
@@ -128,7 +128,14 @@ public class RelativeDateFormatterTest {
assertFormat(380, DAY_IN_MILLIS, "1 year, 1 month ago");
assertFormat(410, DAY_IN_MILLIS, "1 year, 2 months ago");
assertFormat(2, YEAR_IN_MILLIS, "2 years ago");
- assertFormat(1824, DAY_IN_MILLIS, "4 years, 12 months ago");
+ }
+
+ @Test
+ public void testFullYearMissingSomeDays() {
+ // avoid "x year(s), 12 months", as humans would always round this up to
+ // "x+1 years"
+ assertFormat(5 * 365 + 1, DAY_IN_MILLIS, "5 years ago");
+ assertFormat(2 * 365 - 10, DAY_IN_MILLIS, "2 years ago");
}
@Test

Back to the top