summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java25
-rw-r--r--org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java118
-rw-r--r--org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java58
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java3
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java20
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java589
-rw-r--r--org.eclipse.jgit.pgm/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java16
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java2
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java47
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java2
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java20
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java4
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java6
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java20
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java4
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java4
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java8
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java12
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java4
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java16
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java10
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java66
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java97
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java31
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java56
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java25
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java54
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java91
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java319
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java45
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java15
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java6
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java18
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java73
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java48
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java77
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java3
-rw-r--r--org.eclipse.jgit/.settings/.api_filters40
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/ketch/KetchText.properties13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java44
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java42
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java62
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ElectionRound.java141
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchConstants.java82
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java624
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeaderCache.java126
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchPreReceive.java155
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java755
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java301
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchText.java (renamed from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java)61
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java183
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LeaderSnapshot.java164
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java229
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LogIndex.java119
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Proposal.java391
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java299
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java316
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaConfig.java233
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaFetchRequest.java (renamed from org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java)77
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaPushRequest.java168
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaSnapshot.java109
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Round.java116
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java270
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/package-info.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java115
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java601
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java256
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java8
-rwxr-xr-xtools/version.sh11
89 files changed, 5956 insertions, 2379 deletions
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
index 362a09d64f..677132d732 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
@@ -140,8 +140,7 @@ public class DumbClientDumbServerTest extends HttpTestCase {
assertEquals("http", remoteURI.getScheme());
Map<String, Ref> map;
- Transport t = Transport.open(dst, remoteURI);
- try {
+ try (Transport t = Transport.open(dst, remoteURI)) {
// I didn't make up these public interface names, I just
// approved them for inclusion into the code base. Sorry.
// --spearce
@@ -149,14 +148,9 @@ public class DumbClientDumbServerTest extends HttpTestCase {
assertTrue("isa TransportHttp", t instanceof TransportHttp);
assertTrue("isa HttpTransport", t instanceof HttpTransport);
- FetchConnection c = t.openFetch();
- try {
+ try (FetchConnection c = t.openFetch()) {
map = c.getRefsMap();
- } finally {
- c.close();
}
- } finally {
- t.close();
}
assertNotNull("have map of refs", map);
@@ -201,11 +195,8 @@ public class DumbClientDumbServerTest extends HttpTestCase {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));
- Transport t = Transport.open(dst, remoteURI);
- try {
+ try (Transport t = Transport.open(dst, remoteURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
- } finally {
- t.close();
}
assertTrue(dst.hasObject(A_txt));
@@ -226,11 +217,8 @@ public class DumbClientDumbServerTest extends HttpTestCase {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));
- Transport t = Transport.open(dst, remoteURI);
- try {
+ try (Transport t = Transport.open(dst, remoteURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
- } finally {
- t.close();
}
assertTrue(dst.hasObject(A_txt));
@@ -265,8 +253,7 @@ public class DumbClientDumbServerTest extends HttpTestCase {
final RevCommit Q = src.commit().create();
final Repository db = src.getRepository();
- Transport t = Transport.open(db, remoteURI);
- try {
+ try (Transport t = Transport.open(db, remoteURI)) {
try {
t.push(NullProgressMonitor.INSTANCE, push(src, Q));
fail("push incorrectly completed against a dumb server");
@@ -274,8 +261,6 @@ public class DumbClientDumbServerTest extends HttpTestCase {
String exp = "remote does not support smart HTTP push";
assertEquals(exp, nse.getMessage());
}
- } finally {
- t.close();
}
}
}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
index cf20898b37..ce78442785 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
@@ -157,8 +157,7 @@ public class HttpClientTests extends HttpTestCase {
public void testRepositoryNotFound_Dumb() throws Exception {
URIish uri = toURIish("/dumb.none/not-found");
Repository dst = createBareRepository();
- Transport t = Transport.open(dst, uri);
- try {
+ try (Transport t = Transport.open(dst, uri)) {
try {
t.openFetch();
fail("connection opened to not found repository");
@@ -167,8 +166,6 @@ public class HttpClientTests extends HttpTestCase {
+ "/info/refs?service=git-upload-pack not found";
assertEquals(exp, err.getMessage());
}
- } finally {
- t.close();
}
}
@@ -176,8 +173,7 @@ public class HttpClientTests extends HttpTestCase {
public void testRepositoryNotFound_Smart() throws Exception {
URIish uri = toURIish("/smart.none/not-found");
Repository dst = createBareRepository();
- Transport t = Transport.open(dst, uri);
- try {
+ try (Transport t = Transport.open(dst, uri)) {
try {
t.openFetch();
fail("connection opened to not found repository");
@@ -186,8 +182,6 @@ public class HttpClientTests extends HttpTestCase {
+ "/info/refs?service=git-upload-pack not found";
assertEquals(exp, err.getMessage());
}
- } finally {
- t.close();
}
}
@@ -201,16 +195,9 @@ public class HttpClientTests extends HttpTestCase {
Repository dst = createBareRepository();
Ref head;
- Transport t = Transport.open(dst, dumbAuthNoneURI);
- try {
- FetchConnection c = t.openFetch();
- try {
- head = c.getRef(Constants.HEAD);
- } finally {
- c.close();
- }
- } finally {
- t.close();
+ try (Transport t = Transport.open(dst, dumbAuthNoneURI);
+ FetchConnection c = t.openFetch()) {
+ head = c.getRef(Constants.HEAD);
}
assertNotNull("has " + Constants.HEAD, head);
assertEquals(Q, head.getObjectId());
@@ -225,16 +212,9 @@ public class HttpClientTests extends HttpTestCase {
Repository dst = createBareRepository();
Ref head;
- Transport t = Transport.open(dst, dumbAuthNoneURI);
- try {
- FetchConnection c = t.openFetch();
- try {
- head = c.getRef(Constants.HEAD);
- } finally {
- c.close();
- }
- } finally {
- t.close();
+ try (Transport t = Transport.open(dst, dumbAuthNoneURI);
+ FetchConnection c = t.openFetch()) {
+ head = c.getRef(Constants.HEAD);
}
assertNull("has no " + Constants.HEAD, head);
}
@@ -249,16 +229,9 @@ public class HttpClientTests extends HttpTestCase {
Repository dst = createBareRepository();
Ref head;
- Transport t = Transport.open(dst, smartAuthNoneURI);
- try {
- FetchConnection c = t.openFetch();
- try {
- head = c.getRef(Constants.HEAD);
- } finally {
- c.close();
- }
- } finally {
- t.close();
+ try (Transport t = Transport.open(dst, smartAuthNoneURI);
+ FetchConnection c = t.openFetch()) {
+ head = c.getRef(Constants.HEAD);
}
assertNotNull("has " + Constants.HEAD, head);
assertEquals(Q, head.getObjectId());
@@ -268,16 +241,13 @@ public class HttpClientTests extends HttpTestCase {
public void testListRemote_Smart_WithQueryParameters() throws Exception {
URIish myURI = toURIish("/snone/do?r=1&p=test.git");
Repository dst = createBareRepository();
- Transport t = Transport.open(dst, myURI);
- try {
+ try (Transport t = Transport.open(dst, myURI)) {
try {
t.openFetch();
fail("test did not fail to find repository as expected");
} catch (NoRemoteRepositoryException err) {
// expected
}
- } finally {
- t.close();
}
List<AccessEvent> requests = getRequests();
@@ -296,8 +266,7 @@ public class HttpClientTests extends HttpTestCase {
@Test
public void testListRemote_Dumb_NeedsAuth() throws Exception {
Repository dst = createBareRepository();
- Transport t = Transport.open(dst, dumbAuthBasicURI);
- try {
+ try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
try {
t.openFetch();
fail("connection opened even info/refs needs auth basic");
@@ -306,42 +275,35 @@ public class HttpClientTests extends HttpTestCase {
+ JGitText.get().noCredentialsProvider;
assertEquals(exp, err.getMessage());
}
- } finally {
- t.close();
}
}
@Test
public void testListRemote_Dumb_Auth() throws Exception {
Repository dst = createBareRepository();
- Transport t = Transport.open(dst, dumbAuthBasicURI);
- t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
- AppServer.username, AppServer.password));
- try {
- t.openFetch();
- } finally {
- t.close();
+ try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
+ t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
+ AppServer.username, AppServer.password));
+ t.openFetch().close();
}
- t = Transport.open(dst, dumbAuthBasicURI);
- t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
- AppServer.username, ""));
- try {
- t.openFetch();
- fail("connection opened even info/refs needs auth basic and we provide wrong password");
- } catch (TransportException err) {
- String exp = dumbAuthBasicURI + ": "
- + JGitText.get().notAuthorized;
- assertEquals(exp, err.getMessage());
- } finally {
- t.close();
+ try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
+ t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
+ AppServer.username, ""));
+ try {
+ t.openFetch();
+ fail("connection opened even info/refs needs auth basic and we provide wrong password");
+ } catch (TransportException err) {
+ String exp = dumbAuthBasicURI + ": "
+ + JGitText.get().notAuthorized;
+ assertEquals(exp, err.getMessage());
+ }
}
}
@Test
public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception {
Repository dst = createBareRepository();
- Transport t = Transport.open(dst, smartAuthBasicURI);
- try {
+ try (Transport t = Transport.open(dst, smartAuthBasicURI)) {
try {
t.openFetch();
fail("connection opened even though service disabled");
@@ -350,8 +312,6 @@ public class HttpClientTests extends HttpTestCase {
+ JGitText.get().noCredentialsProvider;
assertEquals(exp, err.getMessage());
}
- } finally {
- t.close();
}
}
@@ -363,8 +323,7 @@ public class HttpClientTests extends HttpTestCase {
cfg.save();
Repository dst = createBareRepository();
- Transport t = Transport.open(dst, smartAuthNoneURI);
- try {
+ try (Transport t = Transport.open(dst, smartAuthNoneURI)) {
try {
t.openFetch();
fail("connection opened even though service disabled");
@@ -373,24 +332,15 @@ public class HttpClientTests extends HttpTestCase {
+ JGitText.get().serviceNotEnabledNoName;
assertEquals(exp, err.getMessage());
}
- } finally {
- t.close();
}
}
@Test
public void testListRemoteWithoutLocalRepository() throws Exception {
- Transport t = Transport.open(smartAuthNoneURI);
- try {
- FetchConnection c = t.openFetch();
- try {
- Ref head = c.getRef(Constants.HEAD);
- assertNotNull(head);
- } finally {
- c.close();
- }
- } finally {
- t.close();
+ try (Transport t = Transport.open(smartAuthNoneURI);
+ FetchConnection c = t.openFetch()) {
+ Ref head = c.getRef(Constants.HEAD);
+ assertNotNull(head);
}
}
}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 9ca0789e29..82861ed9b7 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -211,8 +211,7 @@ public class SmartClientSmartServerTest extends HttpTestCase {
assertEquals("http", remoteURI.getScheme());
Map<String, Ref> map;
- Transport t = Transport.open(dst, remoteURI);
- try {
+ try (Transport t = Transport.open(dst, remoteURI)) {
// I didn't make up these public interface names, I just
// approved them for inclusion into the code base. Sorry.
// --spearce
@@ -226,8 +225,6 @@ public class SmartClientSmartServerTest extends HttpTestCase {
} finally {
c.close();
}
- } finally {
- t.close();
}
assertNotNull("have map of refs", map);
@@ -257,8 +254,7 @@ public class SmartClientSmartServerTest extends HttpTestCase {
public void testListRemote_BadName() throws IOException, URISyntaxException {
Repository dst = createBareRepository();
URIish uri = new URIish(this.remoteURI.toString() + ".invalid");
- Transport t = Transport.open(dst, uri);
- try {
+ try (Transport t = Transport.open(dst, uri)) {
try {
t.openFetch();
fail("fetch connection opened");
@@ -266,8 +262,6 @@ public class SmartClientSmartServerTest extends HttpTestCase {
assertEquals(uri + ": Git repository not found",
notFound.getMessage());
}
- } finally {
- t.close();
}
List<AccessEvent> requests = getRequests();
@@ -288,11 +282,8 @@ public class SmartClientSmartServerTest extends HttpTestCase {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));
- Transport t = Transport.open(dst, remoteURI);
- try {
+ try (Transport t = Transport.open(dst, remoteURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
- } finally {
- t.close();
}
assertTrue(dst.hasObject(A_txt));
@@ -331,11 +322,8 @@ public class SmartClientSmartServerTest extends HttpTestCase {
// Bootstrap by doing the clone.
//
TestRepository dst = createTestRepository();
- Transport t = Transport.open(dst.getRepository(), remoteURI);
- try {
+ try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
- } finally {
- t.close();
}
assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
List<AccessEvent> cloneRequests = getRequests();
@@ -352,11 +340,8 @@ public class SmartClientSmartServerTest extends HttpTestCase {
// Now incrementally update.
//
- t = Transport.open(dst.getRepository(), remoteURI);
- try {
+ try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
- } finally {
- t.close();
}
assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
@@ -394,11 +379,8 @@ public class SmartClientSmartServerTest extends HttpTestCase {
// Bootstrap by doing the clone.
//
TestRepository dst = createTestRepository();
- Transport t = Transport.open(dst.getRepository(), remoteURI);
- try {
+ try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
- } finally {
- t.close();
}
assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
List<AccessEvent> cloneRequests = getRequests();
@@ -418,11 +400,8 @@ public class SmartClientSmartServerTest extends HttpTestCase {
// Now incrementally update.
//
- t = Transport.open(dst.getRepository(), remoteURI);
- try {
+ try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
- } finally {
- t.close();
}
assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
@@ -474,8 +453,7 @@ public class SmartClientSmartServerTest extends HttpTestCase {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));
- Transport t = Transport.open(dst, brokenURI);
- try {
+ try (Transport t = Transport.open(dst, brokenURI)) {
try {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
fail("fetch completed despite upload-pack being broken");
@@ -485,8 +463,6 @@ public class SmartClientSmartServerTest extends HttpTestCase {
+ " received Content-Type text/plain; charset=UTF-8";
assertEquals(exp, err.getMessage());
}
- } finally {
- t.close();
}
List<AccessEvent> requests = getRequests();
@@ -517,12 +493,10 @@ public class SmartClientSmartServerTest extends HttpTestCase {
final RevCommit Q = src.commit().add("Q", Q_txt).create();
final Repository db = src.getRepository();
final String dstName = Constants.R_HEADS + "new.branch";
- Transport t;
// push anonymous shouldn't be allowed.
//
- t = Transport.open(db, remoteURI);
- try {
+ try (Transport t = Transport.open(db, remoteURI)) {
final String srcExpr = Q.name();
final boolean forceUpdate = false;
final String localName = null;
@@ -538,8 +512,6 @@ public class SmartClientSmartServerTest extends HttpTestCase {
+ JGitText.get().authenticationNotSupported;
assertEquals(exp, e.getMessage());
}
- } finally {
- t.close();
}
List<AccessEvent> requests = getRequests();
@@ -560,12 +532,10 @@ public class SmartClientSmartServerTest extends HttpTestCase {
final RevCommit Q = src.commit().add("Q", Q_txt).create();
final Repository db = src.getRepository();
final String dstName = Constants.R_HEADS + "new.branch";
- Transport t;
enableReceivePack();
- t = Transport.open(db, remoteURI);
- try {
+ try (Transport t = Transport.open(db, remoteURI)) {
final String srcExpr = Q.name();
final boolean forceUpdate = false;
final String localName = null;
@@ -574,8 +544,6 @@ public class SmartClientSmartServerTest extends HttpTestCase {
RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
srcExpr, dstName, forceUpdate, localName, oldId);
t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
- } finally {
- t.close();
}
assertTrue(remoteRepository.hasObject(Q_txt));
@@ -633,7 +601,6 @@ public class SmartClientSmartServerTest extends HttpTestCase {
final RevCommit Q = src.commit().add("Q", Q_bin).create();
final Repository db = src.getRepository();
final String dstName = Constants.R_HEADS + "new.branch";
- Transport t;
enableReceivePack();
@@ -642,8 +609,7 @@ public class SmartClientSmartServerTest extends HttpTestCase {
cfg.setInt("http", null, "postbuffer", 8 * 1024);
cfg.save();
- t = Transport.open(db, remoteURI);
- try {
+ try (Transport t = Transport.open(db, remoteURI)) {
final String srcExpr = Q.name();
final boolean forceUpdate = false;
final String localName = null;
@@ -652,8 +618,6 @@ public class SmartClientSmartServerTest extends HttpTestCase {
RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
srcExpr, dstName, forceUpdate, localName, oldId);
t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
- } finally {
- t.close();
}
assertTrue(remoteRepository.hasObject(Q_bin));
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index 3a4f9c7884..8439c39c8b 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -1155,8 +1155,7 @@ public class TestRepository<R extends Repository> {
return self;
}
- private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c)
- throws IOException {
+ private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) {
if (changeId == null)
return;
int idx = ChangeIdUtil.indexOfChangeId(message, "\n");
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java
index f1a53d7dcc..55f4d8b1b8 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java
@@ -63,7 +63,9 @@ public class BranchTest extends CLIRepositoryTestCase {
@Before
public void setUp() throws Exception {
super.setUp();
- new Git(db).commit().setMessage("initial commit").call();
+ try (Git git = new Git(db)) {
+ git.commit().setMessage("initial commit").call();
+ }
}
@Test
@@ -95,13 +97,15 @@ public class BranchTest extends CLIRepositoryTestCase {
@Test
public void testListContains() throws Exception {
- new Git(db).branchCreate().setName("initial").call();
- RevCommit second = new Git(db).commit().setMessage("second commit")
- .call();
- assertEquals(toString(" initial", "* master"),
- toString(execute("git branch --contains 6fd41be")));
- assertEquals("* master",
- toString(execute("git branch --contains " + second.name())));
+ try (Git git = new Git(db)) {
+ git.branchCreate().setName("initial").call();
+ RevCommit second = git.commit().setMessage("second commit")
+ .call();
+ assertEquals(toString(" initial", "* master"),
+ toString(execute("git branch --contains 6fd41be")));
+ assertEquals("* master",
+ toString(execute("git branch --contains " + second.name())));
+ }
}
@Test
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
index 6175b6c06a..e690ad6964 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
@@ -73,27 +73,33 @@ public class CheckoutTest extends CLIRepositoryTestCase {
@Test
public void testCheckoutSelf() throws Exception {
- new Git(db).commit().setMessage("initial commit").call();
+ try (Git git = new Git(db)) {
+ git.commit().setMessage("initial commit").call();
- assertStringArrayEquals("Already on 'master'",
- execute("git checkout master"));
+ assertStringArrayEquals("Already on 'master'",
+ execute("git checkout master"));
+ }
}
@Test
public void testCheckoutBranch() throws Exception {
- new Git(db).commit().setMessage("initial commit").call();
- new Git(db).branchCreate().setName("side").call();
+ try (Git git = new Git(db)) {
+ git.commit().setMessage("initial commit").call();
+ git.branchCreate().setName("side").call();
- assertStringArrayEquals("Switched to branch 'side'",
- execute("git checkout side"));
+ assertStringArrayEquals("Switched to branch 'side'",
+ execute("git checkout side"));
+ }
}
@Test
public void testCheckoutNewBranch() throws Exception {
- new Git(db).commit().setMessage("initial commit").call();
+ try (Git git = new Git(db)) {
+ git.commit().setMessage("initial commit").call();
- assertStringArrayEquals("Switched to a new branch 'side'",
- execute("git checkout -b side"));
+ assertStringArrayEquals("Switched to a new branch 'side'",
+ execute("git checkout -b side"));
+ }
}
@Test
@@ -105,11 +111,13 @@ public class CheckoutTest extends CLIRepositoryTestCase {
@Test
public void testCheckoutNewBranchThatAlreadyExists() throws Exception {
- new Git(db).commit().setMessage("initial commit").call();
+ try (Git git = new Git(db)) {
+ git.commit().setMessage("initial commit").call();
- assertStringArrayEquals(
- "fatal: A branch named 'master' already exists.",
+ assertStringArrayEquals(
+ "fatal: A branch named 'master' already exists.",
executeUnchecked("git checkout -b master"));
+ }
}
@Test
@@ -127,32 +135,35 @@ public class CheckoutTest extends CLIRepositoryTestCase {
@Test
public void testCheckoutHead() throws Exception {
- new Git(db).commit().setMessage("initial commit").call();
+ try (Git git = new Git(db)) {
+ git.commit().setMessage("initial commit").call();
- assertStringArrayEquals("", execute("git checkout HEAD"));
+ assertStringArrayEquals("", execute("git checkout HEAD"));
+ }
}
@Test
public void testCheckoutExistingBranchWithConflict() throws Exception {
- Git git = new Git(db);
- writeTrashFile("a", "Hello world a");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("commit file a").call();
- git.branchCreate().setName("branch_1").call();
- git.rm().addFilepattern("a").call();
- FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
- writeTrashFile("a/b", "Hello world b");
- git.add().addFilepattern("a/b").call();
- git.commit().setMessage("commit folder a").call();
- git.rm().addFilepattern("a").call();
- writeTrashFile("a", "New Hello world a");
- git.add().addFilepattern(".").call();
-
- String[] execute = execute("git checkout branch_1");
- assertEquals(
- "error: Your local changes to the following files would be overwritten by checkout:",
- execute[0]);
- assertEquals("\ta", execute[1]);
+ try (Git git = new Git(db)) {
+ writeTrashFile("a", "Hello world a");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("commit file a").call();
+ git.branchCreate().setName("branch_1").call();
+ git.rm().addFilepattern("a").call();
+ FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+ writeTrashFile("a/b", "Hello world b");
+ git.add().addFilepattern("a/b").call();
+ git.commit().setMessage("commit folder a").call();
+ git.rm().addFilepattern("a").call();
+ writeTrashFile("a", "New Hello world a");
+ git.add().addFilepattern(".").call();
+
+ String[] execute = execute("git checkout branch_1");
+ assertEquals(
+ "error: Your local changes to the following files would be overwritten by checkout:",
+ execute[0]);
+ assertEquals("\ta", execute[1]);
+ }
}
/**
@@ -174,41 +185,43 @@ public class CheckoutTest extends CLIRepositoryTestCase {
*/
@Test
public void testCheckoutWithMissingWorkingTreeFile() throws Exception {
- Git git = new Git(db);
- File fileA = writeTrashFile("a", "Hello world a");
- writeTrashFile("b", "Hello world b");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add files a & b").call();
- Ref branch_1 = git.branchCreate().setName("branch_1").call();
- writeTrashFile("a", "b");
- git.add().addFilepattern("a").call();
- git.commit().setMessage("modify file a").call();
-
- FileEntry entry = new FileTreeIterator.FileEntry(new File(
- db.getWorkTree(), "a"), db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
-
- FileUtils.delete(fileA);
-
- git.checkout().setName(branch_1.getName()).call();
-
- entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
- db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
- assertEquals("Hello world a", read(fileA));
+ try (Git git = new Git(db)) {
+ File fileA = writeTrashFile("a", "Hello world a");
+ writeTrashFile("b", "Hello world b");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add files a & b").call();
+ Ref branch_1 = git.branchCreate().setName("branch_1").call();
+ writeTrashFile("a", "b");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("modify file a").call();
+
+ FileEntry entry = new FileTreeIterator.FileEntry(new File(
+ db.getWorkTree(), "a"), db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+ FileUtils.delete(fileA);
+
+ git.checkout().setName(branch_1.getName()).call();
+
+ entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+ db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+ assertEquals("Hello world a", read(fileA));
+ }
}
@Test
public void testCheckoutOrphan() throws Exception {
- Git git = new Git(db);
- git.commit().setMessage("initial commit").call();
-
- assertStringArrayEquals("Switched to a new branch 'new_branch'",
- execute("git checkout --orphan new_branch"));
- assertEquals("refs/heads/new_branch",
- db.exactRef("HEAD").getTarget().getName());
- RevCommit commit = git.commit().setMessage("orphan commit").call();
- assertEquals(0, commit.getParentCount());
+ try (Git git = new Git(db)) {
+ git.commit().setMessage("initial commit").call();
+
+ assertStringArrayEquals("Switched to a new branch 'new_branch'",
+ execute("git checkout --orphan new_branch"));
+ assertEquals("refs/heads/new_branch",
+ db.exactRef("HEAD").getTarget().getName());
+ RevCommit commit = git.commit().setMessage("orphan commit").call();
+ assertEquals(0, commit.getParentCount());
+ }
}
/**
@@ -231,33 +244,34 @@ public class CheckoutTest extends CLIRepositoryTestCase {
@Test
public void fileModeTestMissingThenFolderWithFileInWorkingTree()
throws Exception {
- Git git = new Git(db);
- writeTrashFile("b", "Hello world b");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add file b").call();
- Ref branch_1 = git.branchCreate().setName("branch_1").call();
- File folderA = new File(db.getWorkTree(), "a");
- FileUtils.mkdirs(folderA);
- writeTrashFile("a/c", "Hello world c");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add folder a").call();
-
- FileEntry entry = new FileTreeIterator.FileEntry(new File(
- db.getWorkTree(), "a"), db.getFS());
- assertEquals(FileMode.TREE, entry.getMode());
-
- FileUtils.delete(folderA, FileUtils.RECURSIVE);
- writeTrashFile("a", "b");
-
- entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
- db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
-
- git.checkout().setName(branch_1.getName()).call();
-
- entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
- db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+ try (Git git = new Git(db)) {
+ writeTrashFile("b", "Hello world b");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add file b").call();
+ Ref branch_1 = git.branchCreate().setName("branch_1").call();
+ File folderA = new File(db.getWorkTree(), "a");
+ FileUtils.mkdirs(folderA);
+ writeTrashFile("a/c", "Hello world c");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add folder a").call();
+
+ FileEntry entry = new FileTreeIterator.FileEntry(new File(
+ db.getWorkTree(), "a"), db.getFS());
+ assertEquals(FileMode.TREE, entry.getMode());
+
+ FileUtils.delete(folderA, FileUtils.RECURSIVE);
+ writeTrashFile("a", "b");
+
+ entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+ db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+ git.checkout().setName(branch_1.getName()).call();
+
+ entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+ db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+ }
}
/**
@@ -279,30 +293,31 @@ public class CheckoutTest extends CLIRepositoryTestCase {
*/
@Test
public void fileModeTestFolderWithMissingInWorkingTree() throws Exception {
- Git git = new Git(db);
- writeTrashFile("b", "Hello world b");
- writeTrashFile("a", "b");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add file b & file a").call();
- Ref branch_1 = git.branchCreate().setName("branch_1").call();
- git.rm().addFilepattern("a").call();
- File folderA = new File(db.getWorkTree(), "a");
- FileUtils.mkdirs(folderA);
- writeTrashFile("a/c", "Hello world c");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add folder a").call();
-
- FileEntry entry = new FileTreeIterator.FileEntry(new File(
- db.getWorkTree(), "a"), db.getFS());
- assertEquals(FileMode.TREE, entry.getMode());
-
- FileUtils.delete(folderA, FileUtils.RECURSIVE);
-
- git.checkout().setName(branch_1.getName()).call();
-
- entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
- db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+ try (Git git = new Git(db)) {
+ writeTrashFile("b", "Hello world b");
+ writeTrashFile("a", "b");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add file b & file a").call();
+ Ref branch_1 = git.branchCreate().setName("branch_1").call();
+ git.rm().addFilepattern("a").call();
+ File folderA = new File(db.getWorkTree(), "a");
+ FileUtils.mkdirs(folderA);
+ writeTrashFile("a/c", "Hello world c");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add folder a").call();
+
+ FileEntry entry = new FileTreeIterator.FileEntry(new File(
+ db.getWorkTree(), "a"), db.getFS());
+ assertEquals(FileMode.TREE, entry.getMode());
+
+ FileUtils.delete(folderA, FileUtils.RECURSIVE);
+
+ git.checkout().setName(branch_1.getName()).call();
+
+ entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+ db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+ }
}
/**
@@ -324,32 +339,33 @@ public class CheckoutTest extends CLIRepositoryTestCase {
*/
@Test
public void fileModeTestMissingWithFolderInWorkingTree() throws Exception {
- Git git = new Git(db);
- writeTrashFile("b", "Hello world b");
- writeTrashFile("a", "b");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add file b & file a").call();
- Ref branch_1 = git.branchCreate().setName("branch_1").call();
- git.rm().addFilepattern("a").call();
- git.commit().setMessage("delete file a").call();
-
- FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
- writeTrashFile("a/c", "Hello world c");
-
- FileEntry entry = new FileTreeIterator.FileEntry(new File(
- db.getWorkTree(), "a"), db.getFS());
- assertEquals(FileMode.TREE, entry.getMode());
-
- CheckoutConflictException exception = null;
- try {
- git.checkout().setName(branch_1.getName()).call();
- } catch (CheckoutConflictException e) {
- exception = e;
+ try (Git git = new Git(db)) {
+ writeTrashFile("b", "Hello world b");
+ writeTrashFile("a", "b");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add file b & file a").call();
+ Ref branch_1 = git.branchCreate().setName("branch_1").call();
+ git.rm().addFilepattern("a").call();
+ git.commit().setMessage("delete file a").call();
+
+ FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+ writeTrashFile("a/c", "Hello world c");
+
+ FileEntry entry = new FileTreeIterator.FileEntry(new File(
+ db.getWorkTree(), "a"), db.getFS());
+ assertEquals(FileMode.TREE, entry.getMode());
+
+ CheckoutConflictException exception = null;
+ try {
+ git.checkout().setName(branch_1.getName()).call();
+ } catch (CheckoutConflictException e) {
+ exception = e;
+ }
+ assertNotNull(exception);
+ assertEquals(2, exception.getConflictingPaths().size());
+ assertEquals("a", exception.getConflictingPaths().get(0));
+ assertEquals("a/c", exception.getConflictingPaths().get(1));
}
- assertNotNull(exception);
- assertEquals(2, exception.getConflictingPaths().size());
- assertEquals("a", exception.getConflictingPaths().get(0));
- assertEquals("a/c", exception.getConflictingPaths().get(1));
}
/**
@@ -371,40 +387,41 @@ public class CheckoutTest extends CLIRepositoryTestCase {
@Test
public void fileModeTestFolderThenMissingWithFileInWorkingTree()
throws Exception {
- Git git = new Git(db);
- FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
- writeTrashFile("a/c", "Hello world c");
- writeTrashFile("b", "Hello world b");
- git.add().addFilepattern(".").call();
- RevCommit commit1 = git.commit().setMessage("add folder a & file b")
- .call();
- Ref branch_1 = git.branchCreate().setName("branch_1").call();
- git.rm().addFilepattern("a").call();
- RevCommit commit2 = git.commit().setMessage("delete folder a").call();
-
- TreeWalk tw = new TreeWalk(db);
- tw.addTree(commit1.getTree());
- tw.addTree(commit2.getTree());
- List<DiffEntry> scan = DiffEntry.scan(tw);
- assertEquals(1, scan.size());
- assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
- assertEquals(FileMode.TREE, scan.get(0).getOldMode());
-
- writeTrashFile("a", "b");
-
- FileEntry entry = new FileTreeIterator.FileEntry(new File(
- db.getWorkTree(), "a"), db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
-
- CheckoutConflictException exception = null;
- try {
- git.checkout().setName(branch_1.getName()).call();
- } catch (CheckoutConflictException e) {
- exception = e;
+ try (Git git = new Git(db)) {
+ FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+ writeTrashFile("a/c", "Hello world c");
+ writeTrashFile("b", "Hello world b");
+ git.add().addFilepattern(".").call();
+ RevCommit commit1 = git.commit().setMessage("add folder a & file b")
+ .call();
+ Ref branch_1 = git.branchCreate().setName("branch_1").call();
+ git.rm().addFilepattern("a").call();
+ RevCommit commit2 = git.commit().setMessage("delete folder a").call();
+
+ TreeWalk tw = new TreeWalk(db);
+ tw.addTree(commit1.getTree());
+ tw.addTree(commit2.getTree());
+ List<DiffEntry> scan = DiffEntry.scan(tw);
+ assertEquals(1, scan.size());
+ assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
+ assertEquals(FileMode.TREE, scan.get(0).getOldMode());
+
+ writeTrashFile("a", "b");
+
+ FileEntry entry = new FileTreeIterator.FileEntry(new File(
+ db.getWorkTree(), "a"), db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+ CheckoutConflictException exception = null;
+ try {
+ git.checkout().setName(branch_1.getName()).call();
+ } catch (CheckoutConflictException e) {
+ exception = e;
+ }
+ assertNotNull(exception);
+ assertEquals(1, exception.getConflictingPaths().size());
+ assertEquals("a", exception.getConflictingPaths().get(0));
}
- assertNotNull(exception);
- assertEquals(1, exception.getConflictingPaths().size());
- assertEquals("a", exception.getConflictingPaths().get(0));
}
/**
@@ -427,30 +444,31 @@ public class CheckoutTest extends CLIRepositoryTestCase {
@Test
public void fileModeTestFolderThenFileWithMissingInWorkingTree()
throws Exception {
- Git git = new Git(db);
- FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
- writeTrashFile("a/c", "Hello world c");
- writeTrashFile("b", "Hello world b");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add folder a & file b").call();
- Ref branch_1 = git.branchCreate().setName("branch_1").call();
- git.rm().addFilepattern("a").call();
- File fileA = new File(db.getWorkTree(), "a");
- writeTrashFile("a", "b");
- git.add().addFilepattern("a").call();
- git.commit().setMessage("add file a").call();
-
- FileEntry entry = new FileTreeIterator.FileEntry(new File(
- db.getWorkTree(), "a"), db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
-
- FileUtils.delete(fileA);
-
- git.checkout().setName(branch_1.getName()).call();
-
- entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
- db.getFS());
- assertEquals(FileMode.TREE, entry.getMode());
+ try (Git git = new Git(db)) {
+ FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+ writeTrashFile("a/c", "Hello world c");
+ writeTrashFile("b", "Hello world b");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add folder a & file b").call();
+ Ref branch_1 = git.branchCreate().setName("branch_1").call();
+ git.rm().addFilepattern("a").call();
+ File fileA = new File(db.getWorkTree(), "a");
+ writeTrashFile("a", "b");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("add file a").call();
+
+ FileEntry entry = new FileTreeIterator.FileEntry(new File(
+ db.getWorkTree(), "a"), db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+ FileUtils.delete(fileA);
+
+ git.checkout().setName(branch_1.getName()).call();
+
+ entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+ db.getFS());
+ assertEquals(FileMode.TREE, entry.getMode());
+ }
}
/**
@@ -471,38 +489,39 @@ public class CheckoutTest extends CLIRepositoryTestCase {
*/
@Test
public void fileModeTestFileThenFileWithFolderInIndex() throws Exception {
- Git git = new Git(db);
- writeTrashFile("a", "Hello world a");
- writeTrashFile("b", "Hello world b");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add files a & b").call();
- Ref branch_1 = git.branchCreate().setName("branch_1").call();
- writeTrashFile("a", "b");
- git.add().addFilepattern("a").call();
- git.commit().setMessage("add file a").call();
-
- FileEntry entry = new FileTreeIterator.FileEntry(new File(
- db.getWorkTree(), "a"), db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
-
- git.rm().addFilepattern("a").call();
- FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
- writeTrashFile("a/c", "Hello world c");
- git.add().addFilepattern(".").call();
-
- entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
- db.getFS());
- assertEquals(FileMode.TREE, entry.getMode());
-
- CheckoutConflictException exception = null;
- try {
- git.checkout().setName(branch_1.getName()).call();
- } catch (CheckoutConflictException e) {
- exception = e;
+ try (Git git = new Git(db)) {
+ writeTrashFile("a", "Hello world a");
+ writeTrashFile("b", "Hello world b");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add files a & b").call();
+ Ref branch_1 = git.branchCreate().setName("branch_1").call();
+ writeTrashFile("a", "b");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("add file a").call();
+
+ FileEntry entry = new FileTreeIterator.FileEntry(new File(
+ db.getWorkTree(), "a"), db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+ git.rm().addFilepattern("a").call();
+ FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+ writeTrashFile("a/c", "Hello world c");
+ git.add().addFilepattern(".").call();
+
+ entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+ db.getFS());
+ assertEquals(FileMode.TREE, entry.getMode());
+
+ CheckoutConflictException exception = null;
+ try {
+ git.checkout().setName(branch_1.getName()).call();
+ } catch (CheckoutConflictException e) {
+ exception = e;
+ }
+ assertNotNull(exception);
+ assertEquals(1, exception.getConflictingPaths().size());
+ assertEquals("a", exception.getConflictingPaths().get(0));
}
- assertNotNull(exception);
- assertEquals(1, exception.getConflictingPaths().size());
- assertEquals("a", exception.getConflictingPaths().get(0));
}
/**
@@ -524,66 +543,68 @@ public class CheckoutTest extends CLIRepositoryTestCase {
*/
@Test
public void fileModeTestFileWithFolderInIndex() throws Exception {
- Git git = new Git(db);
- writeTrashFile("b", "Hello world b");
- writeTrashFile("a", "b");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("add file b & file a").call();
- Ref branch_1 = git.branchCreate().setName("branch_1").call();
- git.rm().addFilepattern("a").call();
- writeTrashFile("a", "Hello world a");
- git.add().addFilepattern("a").call();
- git.commit().setMessage("add file a").call();
-
- FileEntry entry = new FileTreeIterator.FileEntry(new File(
- db.getWorkTree(), "a"), db.getFS());
- assertEquals(FileMode.REGULAR_FILE, entry.getMode());
-
- git.rm().addFilepattern("a").call();
- FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
- writeTrashFile("a/c", "Hello world c");
- git.add().addFilepattern(".").call();
-
- entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
- db.getFS());
- assertEquals(FileMode.TREE, entry.getMode());
-
- CheckoutConflictException exception = null;
- try {
- git.checkout().setName(branch_1.getName()).call();
- } catch (CheckoutConflictException e) {
- exception = e;
+ try (Git git = new Git(db)) {
+ writeTrashFile("b", "Hello world b");
+ writeTrashFile("a", "b");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("add file b & file a").call();
+ Ref branch_1 = git.branchCreate().setName("branch_1").call();
+ git.rm().addFilepattern("a").call();
+ writeTrashFile("a", "Hello world a");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("add file a").call();
+
+ FileEntry entry = new FileTreeIterator.FileEntry(new File(
+ db.getWorkTree(), "a"), db.getFS());
+ assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+ git.rm().addFilepattern("a").call();
+ FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+ writeTrashFile("a/c", "Hello world c");
+ git.add().addFilepattern(".").call();
+
+ entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+ db.getFS());
+ assertEquals(FileMode.TREE, entry.getMode());
+
+ CheckoutConflictException exception = null;
+ try {
+ git.checkout().setName(branch_1.getName()).call();
+ } catch (CheckoutConflictException e) {
+ exception = e;
+ }
+ assertNotNull(exception);
+ assertEquals(1, exception.getConflictingPaths().size());
+ assertEquals("a", exception.getConflictingPaths().get(0));
+
+ // TODO: ideally we'd like to get two paths from this exception
+ // assertEquals(2, exception.getConflictingPaths().size());
+ // assertEquals("a", exception.getConflictingPaths().get(0));
+ // assertEquals("a/c", exception.getConflictingPaths().get(1));
}
- assertNotNull(exception);
- assertEquals(1, exception.getConflictingPaths().size());
- assertEquals("a", exception.getConflictingPaths().get(0));
-
- // TODO: ideally we'd like to get two paths from this exception
- // assertEquals(2, exception.getConflictingPaths().size());
- // assertEquals("a", exception.getConflictingPaths().get(0));
- // assertEquals("a/c", exception.getConflictingPaths().get(1));
}
@Test
public void testCheckoutPath() throws Exception {
- Git git = new Git(db);
- writeTrashFile("a", "Hello world a");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("commit file a").call();
- git.branchCreate().setName("branch_1").call();
- git.checkout().setName("branch_1").call();
- File b = writeTrashFile("b", "Hello world b");
- git.add().addFilepattern("b").call();
- git.commit().setMessage("commit file b").call();
- File a = writeTrashFile("a", "New Hello world a");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("modified a").call();
- assertArrayEquals(new String[] { "" },
- execute("git checkout HEAD~2 -- a"));
- assertEquals("Hello world a", read(a));
- assertArrayEquals(new String[] { "* branch_1", " master", "" },
- execute("git branch"));
- assertEquals("Hello world b", read(b));
+ try (Git git = new Git(db)) {
+ writeTrashFile("a", "Hello world a");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("commit file a").call();
+ git.branchCreate().setName("branch_1").call();
+ git.checkout().setName("branch_1").call();
+ File b = writeTrashFile("b", "Hello world b");
+ git.add().addFilepattern("b").call();
+ git.commit().setMessage("commit file b").call();
+ File a = writeTrashFile("a", "New Hello world a");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("modified a").call();
+ assertArrayEquals(new String[] { "" },
+ execute("git checkout HEAD~2 -- a"));
+ assertEquals("Hello world a", read(a));
+ assertArrayEquals(new String[] { "* branch_1", " master", "" },
+ execute("git branch"));
+ assertEquals("Hello world b", read(b));
+ }
}
@Test
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 9dc6aea16f..bc9205c7ce 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -19,6 +19,7 @@ Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)",
org.eclipse.jgit.dircache;version="[4.2.0,4.3.0)",
org.eclipse.jgit.errors;version="[4.2.0,4.3.0)",
org.eclipse.jgit.gitrepo;version="[4.2.0,4.3.0)",
+ org.eclipse.jgit.internal.ketch;version="[4.2.0,4.3.0)",
org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)",
org.eclipse.jgit.internal.storage.pack;version="[4.2.0,4.3.0)",
org.eclipse.jgit.internal.storage.reftree;version="[4.2.0,4.3.0)",
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
index fde0a78bf6..2cee2cb007 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
@@ -129,20 +129,20 @@ abstract class AbstractFetchCommand extends TextBuiltin {
final TrackingRefUpdate u) {
final RefUpdate.Result r = u.getResult();
if (r == RefUpdate.Result.LOCK_FAILURE)
- return "[lock fail]";
+ return "[lock fail]"; //$NON-NLS-1$
if (r == RefUpdate.Result.IO_FAILURE)
- return "[i/o error]";
+ return "[i/o error]"; //$NON-NLS-1$
if (r == RefUpdate.Result.REJECTED)
- return "[rejected]";
+ return "[rejected]"; //$NON-NLS-1$
if (ObjectId.zeroId().equals(u.getNewObjectId()))
- return "[deleted]";
+ return "[deleted]"; //$NON-NLS-1$
if (r == RefUpdate.Result.NEW) {
if (u.getRemoteName().startsWith(Constants.R_HEADS))
- return "[new branch]";
+ return "[new branch]"; //$NON-NLS-1$
else if (u.getLocalName().startsWith(Constants.R_TAGS))
- return "[new tag]";
- return "[new]";
+ return "[new tag]"; //$NON-NLS-1$
+ return "[new]"; //$NON-NLS-1$
}
if (r == RefUpdate.Result.FORCED) {
@@ -158,7 +158,7 @@ abstract class AbstractFetchCommand extends TextBuiltin {
}
if (r == RefUpdate.Result.NO_CHANGE)
- return "[up to date]";
+ return "[up to date]"; //$NON-NLS-1$
return "[" + r.name() + "]"; //$NON-NLS-1$//$NON-NLS-2$
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
index 8569e9278e..faae13a4d7 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
@@ -74,7 +74,7 @@ class Config extends TextBuiltin {
list();
else
throw new NotSupportedException(
- "only --list option is currently supported");
+ "only --list option is currently supported"); //$NON-NLS-1$
}
private void list() throws IOException, ConfigInvalidException {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
index 04182d6dbe..03f3fac0b6 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
@@ -45,18 +45,29 @@ package org.eclipse.jgit.pgm;
import java.io.File;
import java.net.InetSocketAddress;
+import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
+import org.eclipse.jgit.internal.ketch.KetchLeader;
+import org.eclipse.jgit.internal.ketch.KetchLeaderCache;
+import org.eclipse.jgit.internal.ketch.KetchPreReceive;
+import org.eclipse.jgit.internal.ketch.KetchSystem;
+import org.eclipse.jgit.internal.ketch.KetchText;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.transport.DaemonClient;
import org.eclipse.jgit.transport.DaemonService;
+import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.resolver.FileResolver;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.util.FS;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -90,6 +101,13 @@ class Daemon extends TextBuiltin {
@Option(name = "--export-all", usage = "usage_exportWithoutGitDaemonExportOk")
boolean exportAll;
+ @Option(name = "--ketch")
+ KetchServerType ketchServerType;
+
+ enum KetchServerType {
+ LEADER;
+ }
+
@Argument(required = true, metaVar = "metaVar_directory", usage = "usage_directoriesToExport")
final List<File> directory = new ArrayList<File>();
@@ -146,7 +164,9 @@ class Daemon extends TextBuiltin {
service(d, n).setOverridable(true);
for (final String n : forbidOverride)
service(d, n).setOverridable(false);
-
+ if (ketchServerType == KetchServerType.LEADER) {
+ startKetchLeader(d);
+ }
d.start();
outw.println(MessageFormat.format(CLIText.get().listeningOn, d.getAddress()));
}
@@ -159,4 +179,29 @@ class Daemon extends TextBuiltin {
throw die(MessageFormat.format(CLIText.get().serviceNotSupported, n));
return svc;
}
+
+ private void startKetchLeader(org.eclipse.jgit.transport.Daemon daemon) {
+ KetchSystem system = new KetchSystem();
+ final KetchLeaderCache leaders = new KetchLeaderCache(system);
+ final ReceivePackFactory<DaemonClient> factory;
+
+ factory = daemon.getReceivePackFactory();
+ daemon.setReceivePackFactory(new ReceivePackFactory<DaemonClient>() {
+ @Override
+ public ReceivePack create(DaemonClient req, Repository repo)
+ throws ServiceNotEnabledException,
+ ServiceNotAuthorizedException {
+ ReceivePack rp = factory.create(req, repo);
+ KetchLeader leader;
+ try {
+ leader = leaders.get(repo);
+ } catch (URISyntaxException err) {
+ throw new ServiceNotEnabledException(
+ KetchText.get().invalidFollowerUri, err);
+ }
+ rp.setPreReceiveHook(new KetchPreReceive(leader));
+ return rp;
+ }
+ });
+ }
} \ No newline at end of file
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
index d43424c7ce..6947cdd100 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
@@ -324,7 +324,7 @@ class Log extends RevWalkTextBuiltin {
return false;
if (emptyLine)
outw.println();
- outw.print("Notes");
+ outw.print("Notes"); //$NON-NLS-1$
if (label != null) {
outw.print(" ("); //$NON-NLS-1$
outw.print(label);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
index 9098c1263d..33ea1deb8e 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
@@ -182,15 +182,15 @@ class Push extends TextBuiltin {
switch (rru.getStatus()) {
case OK:
if (rru.isDelete())
- printUpdateLine('-', "[deleted]", null, remoteName, null);
+ printUpdateLine('-', "[deleted]", null, remoteName, null); //$NON-NLS-1$
else {
final Ref oldRef = result.getAdvertisedRef(remoteName);
if (oldRef == null) {
final String summary;
if (remoteName.startsWith(Constants.R_TAGS))
- summary = "[new tag]";
+ summary = "[new tag]"; //$NON-NLS-1$
else
- summary = "[new branch]";
+ summary = "[new branch]"; //$NON-NLS-1$
printUpdateLine('*', summary, srcRef, remoteName, null);
} else {
boolean fastForward = rru.isFastForward();
@@ -206,16 +206,16 @@ class Push extends TextBuiltin {
break;
case NON_EXISTING:
- printUpdateLine('X', "[no match]", null, remoteName, null);
+ printUpdateLine('X', "[no match]", null, remoteName, null); //$NON-NLS-1$
break;
case REJECTED_NODELETE:
- printUpdateLine('!', "[rejected]", null, remoteName,
+ printUpdateLine('!', "[rejected]", null, remoteName, //$NON-NLS-1$
CLIText.get().remoteSideDoesNotSupportDeletingRefs);
break;
case REJECTED_NONFASTFORWARD:
- printUpdateLine('!', "[rejected]", srcRef, remoteName,
+ printUpdateLine('!', "[rejected]", srcRef, remoteName, //$NON-NLS-1$
CLIText.get().nonFastForward);
break;
@@ -223,22 +223,22 @@ class Push extends TextBuiltin {
final String message = MessageFormat.format(
CLIText.get().remoteRefObjectChangedIsNotExpectedOne,
safeAbbreviate(reader, rru.getExpectedOldObjectId()));
- printUpdateLine('!', "[rejected]", srcRef, remoteName, message);
+ printUpdateLine('!', "[rejected]", srcRef, remoteName, message); //$NON-NLS-1$
break;
case REJECTED_OTHER_REASON:
- printUpdateLine('!', "[remote rejected]", srcRef, remoteName, rru
+ printUpdateLine('!', "[remote rejected]", srcRef, remoteName, rru //$NON-NLS-1$
.getMessage());
break;
case UP_TO_DATE:
if (verbose)
- printUpdateLine('=', "[up to date]", srcRef, remoteName, null);
+ printUpdateLine('=', "[up to date]", srcRef, remoteName, null); //$NON-NLS-1$
break;
case NOT_ATTEMPTED:
case AWAITING_REPORT:
- printUpdateLine('?', "[unexpected push-process behavior]", srcRef,
+ printUpdateLine('?', "[unexpected push-process behavior]", srcRef, //$NON-NLS-1$
remoteName, rru.getMessage());
break;
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
index eb222747d3..9cee37b791 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
@@ -89,7 +89,7 @@ class Reset extends TextBuiltin {
if (hard)
mode = selectMode(mode, ResetType.HARD);
if (mode == null)
- throw die("no reset mode set");
+ throw die("no reset mode set"); //$NON-NLS-1$
command.setMode(mode);
}
command.call();
@@ -98,7 +98,7 @@ class Reset extends TextBuiltin {
private static ResetType selectMode(ResetType mode, ResetType want) {
if (mode != null)
- throw die("reset modes are mutually exclusive, select one");
+ throw die("reset modes are mutually exclusive, select one"); //$NON-NLS-1$
return want;
}
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
index 939f5836f3..c5ecb8496e 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
@@ -56,7 +56,8 @@ import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.Option;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.pgm.internal.CLIText;;
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.opt.CmdLineParser;
@Command(usage = "usage_RevParse")
class RevParse extends TextBuiltin {
@@ -84,7 +85,8 @@ class RevParse extends TextBuiltin {
}
} else {
if (verify && commits.size() > 1) {
- throw new CmdLineException(CLIText.get().needSingleRevision);
+ final CmdLineParser clp = new CmdLineParser(this);
+ throw new CmdLineException(clp, CLIText.get().needSingleRevision);
}
for (final ObjectId o : commits) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
index d856989011..c96f2c1ba4 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
@@ -136,7 +136,7 @@ class DiffAlgorithms extends TextBuiltin {
protected void run() throws Exception {
mxBean = ManagementFactory.getThreadMXBean();
if (!mxBean.isCurrentThreadCpuTimeSupported())
- throw die("Current thread CPU time not supported on this JRE");
+ throw die("Current thread CPU time not supported on this JRE"); //$NON-NLS-1$
if (gitDirs.isEmpty()) {
RepositoryBuilder rb = new RepositoryBuilder() //
@@ -248,18 +248,18 @@ class DiffAlgorithms extends TextBuiltin {
File parent = directory.getParentFile();
if (name.equals(Constants.DOT_GIT) && parent != null)
name = parent.getName();
- outw.println(name + ": start at " + startId.name());
+ outw.println(name + ": start at " + startId.name()); //$NON-NLS-1$
}
- outw.format(" %12d files, %8d commits\n", valueOf(files),
+ outw.format(" %12d files, %8d commits\n", valueOf(files), //$NON-NLS-1$
valueOf(commits));
- outw.format(" N=%10d min lines, %8d max lines\n", valueOf(minN),
+ outw.format(" N=%10d min lines, %8d max lines\n", valueOf(minN), //$NON-NLS-1$
valueOf(maxN));
- outw.format("%-25s %12s ( %12s %12s )\n", //
- "Algorithm", "Time(ns)", "Time(ns) on", "Time(ns) on");
- outw.format("%-25s %12s ( %12s %12s )\n", //
- "", "", "N=" + minN, "N=" + maxN);
+ outw.format("%-25s %12s ( %12s %12s )\n", //$NON-NLS-1$
+ "Algorithm", "Time(ns)", "Time(ns) on", "Time(ns) on"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ outw.format("%-25s %12s ( %12s %12s )\n", //$NON-NLS-1$
+ "", "", "N=" + minN, "N=" + maxN); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
outw.println("-----------------------------------------------------" //$NON-NLS-1$
+ "----------------"); //$NON-NLS-1$
@@ -335,9 +335,9 @@ class DiffAlgorithms extends TextBuiltin {
}
}
} catch (IllegalArgumentException e) {
- throw die("Cannot determine names", e);
+ throw die("Cannot determine names", e); //$NON-NLS-1$
} catch (IllegalAccessException e) {
- throw die("Cannot determine names", e);
+ throw die("Cannot determine names", e); //$NON-NLS-1$
}
return all;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
index 6260cd99fa..e2f93f4814 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
@@ -167,7 +167,7 @@ class RebuildCommitGraph extends TextBuiltin {
}
}
- pm.beginTask("Rewriting commits", queue.size());
+ pm.beginTask("Rewriting commits", queue.size()); //$NON-NLS-1$
try (ObjectInserter oi = db.newObjectInserter()) {
final ObjectId emptyTree = oi.insert(Constants.OBJ_TREE,
new byte[] {});
@@ -203,7 +203,7 @@ class RebuildCommitGraph extends TextBuiltin {
newc.setAuthor(new PersonIdent(me, new Date(t.commitTime)));
newc.setCommitter(newc.getAuthor());
newc.setParentIds(newParents);
- newc.setMessage("ORIGINAL " + t.oldId.name() + "\n"); //$NON-NLS-2$
+ newc.setMessage("ORIGINAL " + t.oldId.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
t.newId = oi.insert(newc);
rewrites.put(t.oldId, t.newId);
pm.update(1);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
index d3eb245cd9..150fe6eecb 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
@@ -87,9 +87,9 @@ class ShowPackDelta extends TextBuiltin {
long size = reader.getObjectSize(obj, obj.getType());
try {
if (BinaryDelta.getResultSize(delta) != size)
- throw die("Object " + obj.name() + " is not a delta");
+ throw die("Object " + obj.name() + " is not a delta"); //$NON-NLS-1$ //$NON-NLS-2$
} catch (ArrayIndexOutOfBoundsException bad) {
- throw die("Object " + obj.name() + " is not a delta");
+ throw die("Object " + obj.name() + " is not a delta"); //$NON-NLS-1$ //$NON-NLS-2$
}
outw.println(BinaryDelta.format(delta));
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
index 887ad08af7..062f4e7a1f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
@@ -349,10 +349,10 @@ class TextHashFunctions extends TextBuiltin {
name = parent.getName();
outw.println(name + ":"); //$NON-NLS-1$
}
- outw.format(" %6d files; %5d avg. unique lines/file\n", //
+ outw.format(" %6d files; %5d avg. unique lines/file\n", //$NON-NLS-1$
valueOf(fileCnt), //
valueOf(lineCnt / fileCnt));
- outw.format("%-20s %-15s %9s\n", "Hash", "Fold", "Max Len");
+ outw.format("%-20s %-15s %9s\n", "Hash", "Fold", "Max Len"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
outw.println("-----------------------------------------------"); //$NON-NLS-1$
String lastHashName = null;
for (Function fun : all) {
@@ -405,9 +405,9 @@ class TextHashFunctions extends TextBuiltin {
}
}
} catch (IllegalArgumentException e) {
- throw new RuntimeException("Cannot determine names", e);
+ throw new RuntimeException("Cannot determine names", e); //$NON-NLS-1$
} catch (IllegalAccessException e) {
- throw new RuntimeException("Cannot determine names", e);
+ throw new RuntimeException("Cannot determine names", e); //$NON-NLS-1$
}
List<Function> all = new ArrayList<Function>();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
index 229fb67b0c..6b8a61d4d1 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
@@ -109,7 +109,7 @@ public class AbstractTreeIteratorHandler extends
try {
dirc = DirCache.read(new File(name), FS.DETECTED);
} catch (IOException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().notAnIndexFile, name), e);
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notAnIndexFile, name), e);
}
setter.addValue(new DirCacheIterator(dirc));
return 1;
@@ -119,20 +119,20 @@ public class AbstractTreeIteratorHandler extends
try {
id = clp.getRepository().resolve(name);
} catch (IOException e) {
- throw new CmdLineException(e.getMessage());
+ throw new CmdLineException(clp, e.getMessage());
}
if (id == null)
- throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
final CanonicalTreeParser p = new CanonicalTreeParser();
try (ObjectReader curs = clp.getRepository().newObjectReader()) {
p.reset(curs, clp.getRevWalk().parseTree(id));
} catch (MissingObjectException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
} catch (IncorrectObjectTypeException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
} catch (IOException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
}
setter.addValue(p);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
index fa24d4b02f..364809d835 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
@@ -86,14 +86,14 @@ public class ObjectIdHandler extends OptionHandler<ObjectId> {
try {
id = clp.getRepository().resolve(name);
} catch (IOException e) {
- throw new CmdLineException(e.getMessage());
+ throw new CmdLineException(clp, e.getMessage());
}
if (id != null) {
setter.addValue(id);
return 1;
}
- throw new CmdLineException(MessageFormat.format(CLIText.get().notAnObject, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notAnObject, name));
}
@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
index b1be128db1..9ae56e4eaf 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
@@ -96,8 +96,10 @@ public class RevCommitHandler extends OptionHandler<RevCommit> {
final int dot2 = name.indexOf(".."); //$NON-NLS-1$
if (dot2 != -1) {
if (!option.isMultiValued())
- throw new CmdLineException(MessageFormat.format(CLIText.get().onlyOneMetaVarExpectedIn
- , option.metaVar(), name));
+ throw new CmdLineException(clp,
+ MessageFormat.format(
+ CLIText.get().onlyOneMetaVarExpectedIn,
+ option.metaVar(), name));
final String left = name.substring(0, dot2);
final String right = name.substring(dot2 + 2);
@@ -116,20 +118,20 @@ public class RevCommitHandler extends OptionHandler<RevCommit> {
try {
id = clp.getRepository().resolve(name);
} catch (IOException e) {
- throw new CmdLineException(e.getMessage());
+ throw new CmdLineException(clp, e.getMessage());
}
if (id == null)
- throw new CmdLineException(MessageFormat.format(CLIText.get().notACommit, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
final RevCommit c;
try {
c = clp.getRevWalk().parseCommit(id);
} catch (MissingObjectException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().notACommit, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
} catch (IncorrectObjectTypeException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().notACommit, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
} catch (IOException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
}
if (interesting)
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
index eb155af9f4..e2879e076a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
@@ -89,20 +89,20 @@ public class RevTreeHandler extends OptionHandler<RevTree> {
try {
id = clp.getRepository().resolve(name);
} catch (IOException e) {
- throw new CmdLineException(e.getMessage());
+ throw new CmdLineException(clp, e.getMessage());
}
if (id == null)
- throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
final RevTree c;
try {
c = clp.getRevWalk().parseTree(id);
} catch (MissingObjectException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
} catch (IncorrectObjectTypeException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
} catch (IOException e) {
- throw new CmdLineException(MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+ throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
}
setter.addValue(c);
return 1;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
index c62ef0d2b8..96f3ed0afa 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
@@ -63,6 +63,8 @@ import org.eclipse.jgit.pgm.internal.CLIText;
* we can execute at runtime with the remaining arguments of the parser.
*/
public class SubcommandHandler extends OptionHandler<TextBuiltin> {
+ private final org.eclipse.jgit.pgm.opt.CmdLineParser clp;
+
/**
* Create a new handler for the command name.
* <p>
@@ -75,6 +77,7 @@ public class SubcommandHandler extends OptionHandler<TextBuiltin> {
public SubcommandHandler(final CmdLineParser parser,
final OptionDef option, final Setter<? super TextBuiltin> setter) {
super(parser, option, setter);
+ clp = (org.eclipse.jgit.pgm.opt.CmdLineParser) parser;
}
@Override
@@ -82,7 +85,7 @@ public class SubcommandHandler extends OptionHandler<TextBuiltin> {
final String name = params.getParameter(0);
final CommandRef cr = CommandCatalog.get(name);
if (cr == null)
- throw new CmdLineException(MessageFormat.format(
+ throw new CmdLineException(clp, MessageFormat.format(
CLIText.get().notAJgitCommand, name));
// Force option parsing to stop. Everything after us should
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
index 910a645e2b..2fe40b99ed 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
@@ -104,37 +104,38 @@ public class BranchCommandTest extends RepositoryTestCase {
private Git setUpRepoWithRemote() throws Exception {
Repository remoteRepository = createWorkRepository();
- Git remoteGit = new Git(remoteRepository);
- // commit something
- writeTrashFile("Test.txt", "Hello world");
- remoteGit.add().addFilepattern("Test.txt").call();
- initialCommit = remoteGit.commit().setMessage("Initial commit").call();
- writeTrashFile("Test.txt", "Some change");
- remoteGit.add().addFilepattern("Test.txt").call();
- secondCommit = remoteGit.commit().setMessage("Second commit").call();
- // create a master branch
- RefUpdate rup = remoteRepository.updateRef("refs/heads/master");
- rup.setNewObjectId(initialCommit.getId());
- rup.forceUpdate();
-
- Repository localRepository = createWorkRepository();
- Git localGit = new Git(localRepository);
- StoredConfig config = localRepository.getConfig();
- RemoteConfig rc = new RemoteConfig(config, "origin");
- rc.addURI(new URIish(remoteRepository.getDirectory().getAbsolutePath()));
- rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
- rc.update(config);
- config.save();
- FetchResult res = localGit.fetch().setRemote("origin").call();
- assertFalse(res.getTrackingRefUpdates().isEmpty());
- rup = localRepository.updateRef("refs/heads/master");
- rup.setNewObjectId(initialCommit.getId());
- rup.forceUpdate();
- rup = localRepository.updateRef(Constants.HEAD);
- rup.link("refs/heads/master");
- rup.setNewObjectId(initialCommit.getId());
- rup.update();
- return localGit;
+ try (Git remoteGit = new Git(remoteRepository)) {
+ // commit something
+ writeTrashFile("Test.txt", "Hello world");
+ remoteGit.add().addFilepattern("Test.txt").call();
+ initialCommit = remoteGit.commit().setMessage("Initial commit").call();
+ writeTrashFile("Test.txt", "Some change");
+ remoteGit.add().addFilepattern("Test.txt").call();
+ secondCommit = remoteGit.commit().setMessage("Second commit").call();
+ // create a master branch
+ RefUpdate rup = remoteRepository.updateRef("refs/heads/master");
+ rup.setNewObjectId(initialCommit.getId());
+ rup.forceUpdate();
+
+ Repository localRepository = createWorkRepository();
+ Git localGit = new Git(localRepository);
+ StoredConfig config = localRepository.getConfig();
+ RemoteConfig rc = new RemoteConfig(config, "origin");
+ rc.addURI(new URIish(remoteRepository.getDirectory().getAbsolutePath()));
+ rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+ rc.update(config);
+ config.save();
+ FetchResult res = localGit.fetch().setRemote("origin").call();
+ assertFalse(res.getTrackingRefUpdates().isEmpty());
+ rup = localRepository.updateRef("refs/heads/master");
+ rup.setNewObjectId(initialCommit.getId());
+ rup.forceUpdate();
+ rup = localRepository.updateRef(Constants.HEAD);
+ rup.link("refs/heads/master");
+ rup.setNewObjectId(initialCommit.getId());
+ rup.update();
+ return localGit;
+ }
}
@Test
@@ -192,8 +193,7 @@ public class BranchCommandTest extends RepositoryTestCase {
@Test
public void testListAllBranchesShouldNotDie() throws Exception {
- Git git = setUpRepoWithRemote();
- git.branchList().setListMode(ListMode.ALL).call();
+ setUpRepoWithRemote().branchList().setListMode(ListMode.ALL).call();
}
@Test
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 4bfb128cbc..362d7ac9c9 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
@@ -417,20 +417,20 @@ public class CheckoutCommandTest extends RepositoryTestCase {
InvalidRemoteException, TransportException {
// create second repository
Repository db2 = createWorkRepository();
- Git git2 = new Git(db2);
-
- // setup the second repository to fetch from the first repository
- final StoredConfig config = db2.getConfig();
- RemoteConfig remoteConfig = new RemoteConfig(config, "origin");
- URIish uri = new URIish(db.getDirectory().toURI().toURL());
- remoteConfig.addURI(uri);
- remoteConfig.update(config);
- config.save();
-
- // fetch from first repository
- RefSpec spec = new RefSpec("+refs/heads/*:refs/remotes/origin/*");
- git2.fetch().setRemote("origin").setRefSpecs(spec).call();
- return db2;
+ try (Git git2 = new Git(db2)) {
+ // setup the second repository to fetch from the first repository
+ final StoredConfig config = db2.getConfig();
+ RemoteConfig remoteConfig = new RemoteConfig(config, "origin");
+ URIish uri = new URIish(db.getDirectory().toURI().toURL());
+ remoteConfig.addURI(uri);
+ remoteConfig.update(config);
+ config.save();
+
+ // fetch from first repository
+ RefSpec spec = new RefSpec("+refs/heads/*:refs/remotes/origin/*");
+ git2.fetch().setRemote("origin").setRefSpecs(spec).call();
+ return db2;
+ }
}
private CheckoutCommand newOrphanBranchCommand() {
@@ -639,40 +639,41 @@ public class CheckoutCommandTest extends RepositoryTestCase {
File clean_filter = writeTempFile("sed s/V1/@version/g -");
File smudge_filter = writeTempFile("sed s/@version/V1/g -");
- Git git = new Git(db);
- StoredConfig config = git.getRepository().getConfig();
- config.setString("filter", "tstFilter", "smudge",
- "sh " + slashify(smudge_filter.getPath()));
- config.setString("filter", "tstFilter", "clean",
- "sh " + slashify(clean_filter.getPath()));
- config.save();
- writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
- git.add().addFilepattern(".gitattributes").call();
- git.commit().setMessage("add attributes").call();
-
- writeTrashFile("filterTest.txt", "hello world, V1");
- git.add().addFilepattern("filterTest.txt").call();
- git.commit().setMessage("add filterText.txt").call();
- assertEquals(
- "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]",
- indexState(CONTENT));
-
- git.checkout().setCreateBranch(true).setName("test2").call();
- writeTrashFile("filterTest.txt", "bon giorno world, V1");
- git.add().addFilepattern("filterTest.txt").call();
- git.commit().setMessage("modified filterText.txt").call();
-
- assertTrue(git.status().call().isClean());
- assertEquals(
- "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:bon giorno world, @version]",
- indexState(CONTENT));
-
- git.checkout().setName("refs/heads/test").call();
- assertTrue(git.status().call().isClean());
- assertEquals(
- "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]",
- indexState(CONTENT));
- assertEquals("hello world, V1", read("filterTest.txt"));
+ try (Git git2 = new Git(db)) {
+ StoredConfig config = git.getRepository().getConfig();
+ config.setString("filter", "tstFilter", "smudge",
+ "sh " + slashify(smudge_filter.getPath()));
+ config.setString("filter", "tstFilter", "clean",
+ "sh " + slashify(clean_filter.getPath()));
+ config.save();
+ writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+ git2.add().addFilepattern(".gitattributes").call();
+ git2.commit().setMessage("add attributes").call();
+
+ writeTrashFile("filterTest.txt", "hello world, V1");
+ git2.add().addFilepattern("filterTest.txt").call();
+ git2.commit().setMessage("add filterText.txt").call();
+ assertEquals(
+ "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]",
+ indexState(CONTENT));
+
+ git2.checkout().setCreateBranch(true).setName("test2").call();
+ writeTrashFile("filterTest.txt", "bon giorno world, V1");
+ git2.add().addFilepattern("filterTest.txt").call();
+ git2.commit().setMessage("modified filterText.txt").call();
+
+ assertTrue(git2.status().call().isClean());
+ assertEquals(
+ "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:bon giorno world, @version]",
+ indexState(CONTENT));
+
+ git2.checkout().setName("refs/heads/test").call();
+ assertTrue(git2.status().call().isClean());
+ assertEquals(
+ "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]",
+ indexState(CONTENT));
+ assertEquals("hello world, V1", read("filterTest.txt"));
+ }
}
private File writeTempFile(String body) throws IOException {
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 0d03047d53..b39a68a22e 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
@@ -46,12 +46,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
+import org.eclipse.jgit.api.errors.EmtpyCommitException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.dircache.DirCache;
@@ -477,6 +480,34 @@ public class CommitCommandTest extends RepositoryTestCase {
}
@Test
+ public void commitEmptyCommits() throws Exception {
+ try (Git git = new Git(db)) {
+
+ writeTrashFile("file1", "file1");
+ git.add().addFilepattern("file1").call();
+ RevCommit initial = git.commit().setMessage("initial commit")
+ .call();
+
+ RevCommit emptyFollowUp = git.commit()
+ .setAuthor("New Author", "newauthor@example.org")
+ .setMessage("no change").call();
+
+ assertNotEquals(initial.getId(), emptyFollowUp.getId());
+ assertEquals(initial.getTree().getId(),
+ emptyFollowUp.getTree().getId());
+
+ try {
+ git.commit().setAuthor("New Author", "newauthor@example.org")
+ .setMessage("again no change").setAllowEmpty(false)
+ .call();
+ fail("Didn't get the expected EmtpyCommitException");
+ } catch (EmtpyCommitException e) {
+ // expect this exception
+ }
+ }
+ }
+
+ @Test
public void commitOnlyShouldCommitUnmergedPathAndNotAffectOthers()
throws Exception {
DirCache index = db.lockDirCache();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
index a67f2b912a..66f25e8e51 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
@@ -65,6 +65,7 @@ import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
@@ -139,8 +140,8 @@ public class ResetCommandTest extends RepositoryTestCase {
AmbiguousObjectException, IOException, GitAPIException {
setupRepository();
ObjectId prevHead = db.resolve(Constants.HEAD);
- git.reset().setMode(ResetType.HARD).setRef(initialCommit.getName())
- .call();
+ assertSameAsHead(git.reset().setMode(ResetType.HARD)
+ .setRef(initialCommit.getName()).call());
// check if HEAD points to initial commit now
ObjectId head = db.resolve(Constants.HEAD);
assertEquals(initialCommit, head);
@@ -176,8 +177,8 @@ public class ResetCommandTest extends RepositoryTestCase {
AmbiguousObjectException, IOException, GitAPIException {
setupRepository();
ObjectId prevHead = db.resolve(Constants.HEAD);
- git.reset().setMode(ResetType.SOFT).setRef(initialCommit.getName())
- .call();
+ assertSameAsHead(git.reset().setMode(ResetType.SOFT)
+ .setRef(initialCommit.getName()).call());
// check if HEAD points to initial commit now
ObjectId head = db.resolve(Constants.HEAD);
assertEquals(initialCommit, head);
@@ -197,8 +198,8 @@ public class ResetCommandTest extends RepositoryTestCase {
AmbiguousObjectException, IOException, GitAPIException {
setupRepository();
ObjectId prevHead = db.resolve(Constants.HEAD);
- git.reset().setMode(ResetType.MIXED).setRef(initialCommit.getName())
- .call();
+ assertSameAsHead(git.reset().setMode(ResetType.MIXED)
+ .setRef(initialCommit.getName()).call());
// check if HEAD points to initial commit now
ObjectId head = db.resolve(Constants.HEAD);
assertEquals(initialCommit, head);
@@ -241,7 +242,8 @@ public class ResetCommandTest extends RepositoryTestCase {
assertTrue(bEntry.getLength() > 0);
assertTrue(bEntry.getLastModified() > 0);
- git.reset().setMode(ResetType.MIXED).setRef(commit2.getName()).call();
+ assertSameAsHead(git.reset().setMode(ResetType.MIXED)
+ .setRef(commit2.getName()).call());
cache = db.readDirCache();
@@ -280,7 +282,7 @@ public class ResetCommandTest extends RepositoryTestCase {
+ "[a.txt, mode:100644, stage:3]",
indexState(0));
- git.reset().setMode(ResetType.MIXED).call();
+ assertSameAsHead(git.reset().setMode(ResetType.MIXED).call());
assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]",
indexState(0));
@@ -298,8 +300,8 @@ public class ResetCommandTest extends RepositoryTestCase {
// 'a.txt' has already been modified in setupRepository
// 'notAddedToIndex.txt' has been added to repository
- git.reset().addPath(indexFile.getName())
- .addPath(untrackedFile.getName()).call();
+ assertSameAsHead(git.reset().addPath(indexFile.getName())
+ .addPath(untrackedFile.getName()).call());
DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS())
.getEntry(indexFile.getName());
@@ -329,7 +331,7 @@ public class ResetCommandTest extends RepositoryTestCase {
git.add().addFilepattern(untrackedFile.getName()).call();
// 'dir/b.txt' has already been modified in setupRepository
- git.reset().addPath("dir").call();
+ assertSameAsHead(git.reset().addPath("dir").call());
DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS())
.getEntry("dir/b.txt");
@@ -358,9 +360,9 @@ public class ResetCommandTest extends RepositoryTestCase {
// 'a.txt' has already been modified in setupRepository
// 'notAddedToIndex.txt' has been added to repository
// reset to the inital commit
- git.reset().setRef(initialCommit.getName())
- .addPath(indexFile.getName())
- .addPath(untrackedFile.getName()).call();
+ assertSameAsHead(git.reset().setRef(initialCommit.getName())
+ .addPath(indexFile.getName()).addPath(untrackedFile.getName())
+ .call());
// check that HEAD hasn't moved
ObjectId head = db.resolve(Constants.HEAD);
@@ -397,7 +399,7 @@ public class ResetCommandTest extends RepositoryTestCase {
+ "[b.txt, mode:100644]",
indexState(0));
- git.reset().addPath(file).call();
+ assertSameAsHead(git.reset().addPath(file).call());
assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]",
indexState(0));
@@ -409,7 +411,7 @@ public class ResetCommandTest extends RepositoryTestCase {
writeTrashFile("a.txt", "content");
git.add().addFilepattern("a.txt").call();
// Should assume an empty tree, like in C Git 1.8.2
- git.reset().addPath("a.txt").call();
+ assertSameAsHead(git.reset().addPath("a.txt").call());
DirCache cache = db.readDirCache();
DirCacheEntry aEntry = cache.getEntry("a.txt");
@@ -421,7 +423,8 @@ public class ResetCommandTest extends RepositoryTestCase {
git = new Git(db);
writeTrashFile("a.txt", "content");
git.add().addFilepattern("a.txt").call();
- git.reset().setRef("doesnotexist").addPath("a.txt").call();
+ assertSameAsHead(
+ git.reset().setRef("doesnotexist").addPath("a.txt").call());
}
@Test
@@ -431,7 +434,7 @@ public class ResetCommandTest extends RepositoryTestCase {
git.add().addFilepattern("a.txt").call();
writeTrashFile("a.txt", "modified");
// should use default mode MIXED
- git.reset().call();
+ assertSameAsHead(git.reset().call());
DirCache cache = db.readDirCache();
DirCacheEntry aEntry = cache.getEntry("a.txt");
@@ -452,7 +455,7 @@ public class ResetCommandTest extends RepositoryTestCase {
git.add().addFilepattern(untrackedFile.getName()).call();
- git.reset().setRef(tagName).setMode(HARD).call();
+ assertSameAsHead(git.reset().setRef(tagName).setMode(HARD).call());
ObjectId head = db.resolve(Constants.HEAD);
assertEquals(secondCommit, head);
@@ -486,7 +489,8 @@ public class ResetCommandTest extends RepositoryTestCase {
result.getMergeStatus());
assertNotNull(db.readSquashCommitMsg());
- g.reset().setMode(ResetType.HARD).setRef(first.getName()).call();
+ assertSameAsHead(g.reset().setMode(ResetType.HARD)
+ .setRef(first.getName()).call());
assertNull(db.readSquashCommitMsg());
}
@@ -497,7 +501,7 @@ public class ResetCommandTest extends RepositoryTestCase {
File fileA = writeTrashFile("a.txt", "content");
git.add().addFilepattern("a.txt").call();
// Should assume an empty tree, like in C Git 1.8.2
- git.reset().setMode(ResetType.HARD).call();
+ assertSameAsHead(git.reset().setMode(ResetType.HARD).call());
DirCache cache = db.readDirCache();
DirCacheEntry aEntry = cache.getEntry("a.txt");
@@ -558,4 +562,14 @@ public class ResetCommandTest extends RepositoryTestCase {
return dc.getEntry(path) != null;
}
+ /**
+ * Asserts that a certain ref is similar to repos HEAD.
+ * @param ref
+ * @throws IOException
+ */
+ private void assertSameAsHead(Ref ref) throws IOException {
+ Ref headRef = db.getRef(Constants.HEAD);
+ assertEquals(headRef.getName(), ref.getName());
+ assertEquals(headRef.getObjectId(), ref.getObjectId());
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
index dc7181aece..05fa007904 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
@@ -69,6 +69,7 @@ public class DirCacheEntryTest {
assertFalse(isValidPath("a\u0000b"));
}
+ @SuppressWarnings("unused")
private static boolean isValidPath(String path) {
try {
new DirCacheEntry(path);
@@ -78,6 +79,7 @@ public class DirCacheEntryTest {
}
}
+ @SuppressWarnings("unused")
@Test
public void testCreate_ByStringPath() {
assertEquals("a", new DirCacheEntry("a").getPathString());
@@ -91,6 +93,7 @@ public class DirCacheEntryTest {
}
}
+ @SuppressWarnings("unused")
@Test
public void testCreate_ByStringPathAndStage() {
DirCacheEntry e;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
index 6c8ee737d2..dca356434b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
@@ -72,6 +72,7 @@ public class FileRepositoryBuilderTest extends LocalDiskRepositoryTestCase {
.findGitDir(d).getGitDir());
}
+ @SuppressWarnings("unused")
@Test
public void emptyRepositoryFormatVersion() throws Exception {
Repository r = createWorkRepository();
@@ -83,6 +84,7 @@ public class FileRepositoryBuilderTest extends LocalDiskRepositoryTestCase {
new FileRepository(r.getDirectory());
}
+ @SuppressWarnings("unused")
@Test
public void invalidRepositoryFormatVersion() throws Exception {
Repository r = createWorkRepository();
@@ -99,6 +101,7 @@ public class FileRepositoryBuilderTest extends LocalDiskRepositoryTestCase {
}
}
+ @SuppressWarnings("unused")
@Test
public void unknownRepositoryFormatVersion() throws Exception {
Repository r = createWorkRepository();
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 8744ed1bd6..48434189e4 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
@@ -558,13 +558,15 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(ppid, db.resolve("refs/heads/master"));
// real test
- RevCommit old = new RevWalk(db).parseCommit(ppid);
- RefUpdate updateRef2 = db.updateRef("refs/heads/master");
- updateRef2.setExpectedOldObjectId(old);
- updateRef2.setNewObjectId(pid);
- Result update2 = updateRef2.update();
- assertEquals(Result.FAST_FORWARD, update2);
- assertEquals(pid, db.resolve("refs/heads/master"));
+ try (RevWalk rw = new RevWalk(db)) {
+ RevCommit old = rw.parseCommit(ppid);
+ RefUpdate updateRef2 = db.updateRef("refs/heads/master");
+ updateRef2.setExpectedOldObjectId(old);
+ updateRef2.setNewObjectId(pid);
+ Result update2 = updateRef2.update();
+ assertEquals(Result.FAST_FORWARD, update2);
+ assertEquals(pid, db.resolve("refs/heads/master"));
+ }
}
/**
@@ -707,9 +709,10 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
// Create new Repository instance, to reread caches and make sure our
// assumptions are persistent.
- Repository ndb = new FileRepository(db.getDirectory());
- assertEquals(rb2, ndb.resolve("refs/heads/new/name"));
- assertNull(ndb.resolve("refs/heads/b"));
+ try (Repository ndb = new FileRepository(db.getDirectory())) {
+ assertEquals(rb2, ndb.resolve("refs/heads/new/name"));
+ assertNull(ndb.resolve("refs/heads/b"));
+ }
}
public void tryRenameWhenLocked(String toLock, String fromName,
@@ -751,7 +754,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertNull(db.resolve(toName));
assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
.getReverseEntries().toString());
- if (oldHeadId != null)
+ if (oldHeadId != null && oldHeadLog != null)
assertEquals(oldHeadLog.toString(), db.getReflogReader(
Constants.HEAD).getReverseEntries().toString());
} finally {
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 5670a9634d..f4d655f86b 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
@@ -67,7 +67,6 @@ import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.FileTreeEntry;
import org.eclipse.jgit.lib.ObjectDatabase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -75,7 +74,6 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TagBuilder;
-import org.eclipse.jgit.lib.Tree;
import org.eclipse.jgit.lib.TreeFormatter;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag;
@@ -364,6 +362,7 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
assertNotSame(db.getConfig(), db2.getConfig());
}
+ @SuppressWarnings("unused")
@Test
public void test008_FailOnWrongVersion() throws IOException {
final File cfg = new File(db.getDirectory(), Constants.CONFIG);
@@ -419,29 +418,6 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
}
@Test
- public void test012_SubtreeExternalSorting() throws IOException {
- final ObjectId emptyBlob = insertEmptyBlob();
- final Tree t = new Tree(db);
- final FileTreeEntry e0 = t.addFile("a-");
- final FileTreeEntry e1 = t.addFile("a-b");
- final FileTreeEntry e2 = t.addFile("a/b");
- final FileTreeEntry e3 = t.addFile("a=");
- final FileTreeEntry e4 = t.addFile("a=b");
-
- e0.setId(emptyBlob);
- e1.setId(emptyBlob);
- e2.setId(emptyBlob);
- e3.setId(emptyBlob);
- e4.setId(emptyBlob);
-
- final Tree a = (Tree) t.findTreeMember("a");
- a.setId(insertTree(a));
- assertEquals(ObjectId
- .fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"),
- insertTree(t));
- }
-
- @Test
public void test020_createBlobTag() throws IOException {
final ObjectId emptyId = insertEmptyBlob();
final TagBuilder t = new TagBuilder();
@@ -464,9 +440,8 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
@Test
public void test021_createTreeTag() throws IOException {
final ObjectId emptyId = insertEmptyBlob();
- final Tree almostEmptyTree = new Tree(db);
- almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId,
- "empty".getBytes(), false));
+ TreeFormatter almostEmptyTree = new TreeFormatter();
+ almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
final TagBuilder t = new TagBuilder();
t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE);
@@ -488,9 +463,8 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
@Test
public void test022_createCommitTag() throws IOException {
final ObjectId emptyId = insertEmptyBlob();
- final Tree almostEmptyTree = new Tree(db);
- almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId,
- "empty".getBytes(), false));
+ TreeFormatter almostEmptyTree = new TreeFormatter();
+ almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
final CommitBuilder almostEmptyCommit = new CommitBuilder();
almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L,
@@ -520,9 +494,8 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
@Test
public void test023_createCommitNonAnullii() throws IOException {
final ObjectId emptyId = insertEmptyBlob();
- final Tree almostEmptyTree = new Tree(db);
- almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId,
- "empty".getBytes(), false));
+ TreeFormatter almostEmptyTree = new TreeFormatter();
+ almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
CommitBuilder commit = new CommitBuilder();
commit.setTreeId(almostEmptyTreeId);
@@ -542,9 +515,8 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
@Test
public void test024_createCommitNonAscii() throws IOException {
final ObjectId emptyId = insertEmptyBlob();
- final Tree almostEmptyTree = new Tree(db);
- almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId,
- "empty".getBytes(), false));
+ TreeFormatter almostEmptyTree = new TreeFormatter();
+ almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
CommitBuilder commit = new CommitBuilder();
commit.setTreeId(almostEmptyTreeId);
@@ -746,14 +718,6 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
return emptyId;
}
- private ObjectId insertTree(Tree tree) throws IOException {
- try (ObjectInserter oi = db.newObjectInserter()) {
- ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format());
- oi.flush();
- return id;
- }
- }
-
private ObjectId insertTree(TreeFormatter tree) throws IOException {
try (ObjectInserter oi = db.newObjectInserter()) {
ObjectId id = oi.insert(tree);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
index a5cd7b5c0b..7fcee3dc82 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
@@ -99,8 +99,7 @@ public class IndexDiffTest extends RepositoryTestCase {
public void testAdded() throws IOException {
writeTrashFile("file1", "file1");
writeTrashFile("dir/subfile", "dir/subfile");
- Tree tree = new Tree(db);
- tree.setId(insertTree(tree));
+ ObjectId tree = insertTree(new TreeFormatter());
DirCache index = db.lockDirCache();
DirCacheEditor editor = index.editor();
@@ -108,7 +107,7 @@ public class IndexDiffTest extends RepositoryTestCase {
editor.add(add(db, trash, "dir/subfile"));
editor.commit();
FileTreeIterator iterator = new FileTreeIterator(db);
- IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+ IndexDiff diff = new IndexDiff(db, tree, iterator);
diff.diff();
assertEquals(2, diff.getAdded().size());
assertTrue(diff.getAdded().contains("file1"));
@@ -124,18 +123,16 @@ public class IndexDiffTest extends RepositoryTestCase {
writeTrashFile("file2", "file2");
writeTrashFile("dir/file3", "dir/file3");
- Tree tree = new Tree(db);
- tree.addFile("file2");
- tree.addFile("dir/file3");
- assertEquals(2, tree.memberCount());
- tree.findBlobMember("file2").setId(ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad"));
- Tree tree2 = (Tree) tree.findTreeMember("dir");
- tree2.findBlobMember("file3").setId(ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b"));
- tree2.setId(insertTree(tree2));
- tree.setId(insertTree(tree));
+ TreeFormatter dir = new TreeFormatter();
+ dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b"));
+
+ TreeFormatter tree = new TreeFormatter();
+ tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad"));
+ tree.append("dir", FileMode.TREE, insertTree(dir));
+ ObjectId treeId = insertTree(tree);
FileTreeIterator iterator = new FileTreeIterator(db);
- IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+ IndexDiff diff = new IndexDiff(db, treeId, iterator);
diff.diff();
assertEquals(2, diff.getRemoved().size());
assertTrue(diff.getRemoved().contains("file2"));
@@ -157,16 +154,16 @@ public class IndexDiffTest extends RepositoryTestCase {
writeTrashFile("dir/file3", "changed");
- Tree tree = new Tree(db);
- tree.addFile("file2").setId(ObjectId.fromString("0123456789012345678901234567890123456789"));
- tree.addFile("dir/file3").setId(ObjectId.fromString("0123456789012345678901234567890123456789"));
- assertEquals(2, tree.memberCount());
+ TreeFormatter dir = new TreeFormatter();
+ dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789"));
+
+ TreeFormatter tree = new TreeFormatter();
+ tree.append("dir", FileMode.TREE, insertTree(dir));
+ tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789"));
+ ObjectId treeId = insertTree(tree);
- Tree tree2 = (Tree) tree.findTreeMember("dir");
- tree2.setId(insertTree(tree2));
- tree.setId(insertTree(tree));
FileTreeIterator iterator = new FileTreeIterator(db);
- IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+ IndexDiff diff = new IndexDiff(db, treeId, iterator);
diff.diff();
assertEquals(2, diff.getChanged().size());
assertTrue(diff.getChanged().contains("file2"));
@@ -314,17 +311,16 @@ public class IndexDiffTest extends RepositoryTestCase {
git.add().addFilepattern("a=c").call();
git.add().addFilepattern("a=d").call();
- Tree tree = new Tree(db);
+ TreeFormatter tree = new TreeFormatter();
// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
- tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
- tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
- tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
- tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
-
- tree.setId(insertTree(tree));
+ tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
+ tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
+ tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
+ tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
+ ObjectId treeId = insertTree(tree);
FileTreeIterator iterator = new FileTreeIterator(db);
- IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+ IndexDiff diff = new IndexDiff(db, treeId, iterator);
diff.diff();
assertEquals(0, diff.getChanged().size());
assertEquals(0, diff.getAdded().size());
@@ -356,24 +352,27 @@ public class IndexDiffTest extends RepositoryTestCase {
.addFilepattern("a/c").addFilepattern("a=c")
.addFilepattern("a=d").call();
- Tree tree = new Tree(db);
+
// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
- tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
- tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
- tree.addFile("a/b.b/b").setId(ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd"));
- tree.addFile("a/b").setId(ObjectId.fromString("db89c972fc57862eae378f45b74aca228037d415"));
- tree.addFile("a/c").setId(ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007"));
- tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
- tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
-
- Tree tree3 = (Tree) tree.findTreeMember("a/b.b");
- tree3.setId(insertTree(tree3));
- Tree tree2 = (Tree) tree.findTreeMember("a");
- tree2.setId(insertTree(tree2));
- tree.setId(insertTree(tree));
+ TreeFormatter bb = new TreeFormatter();
+ bb.append("b", FileMode.REGULAR_FILE, ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd"));
+
+ TreeFormatter a = new TreeFormatter();
+ a.append("b", FileMode.REGULAR_FILE, ObjectId
+ .fromString("db89c972fc57862eae378f45b74aca228037d415"));
+ a.append("b.b", FileMode.TREE, insertTree(bb));
+ a.append("c", FileMode.REGULAR_FILE, ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007"));
+
+ TreeFormatter tree = new TreeFormatter();
+ tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
+ tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
+ tree.append("a", FileMode.TREE, insertTree(a));
+ tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
+ tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
+ ObjectId treeId = insertTree(tree);
FileTreeIterator iterator = new FileTreeIterator(db);
- IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+ IndexDiff diff = new IndexDiff(db, treeId, iterator);
diff.diff();
assertEquals(0, diff.getChanged().size());
assertEquals(0, diff.getAdded().size());
@@ -383,9 +382,9 @@ public class IndexDiffTest extends RepositoryTestCase {
assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
}
- private ObjectId insertTree(Tree tree) throws IOException {
+ private ObjectId insertTree(TreeFormatter tree) throws IOException {
try (ObjectInserter oi = db.newObjectInserter()) {
- ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format());
+ ObjectId id = oi.insert(tree);
oi.flush();
return id;
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java
index 315c495606..1515a07ac4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java
@@ -75,11 +75,13 @@ public class T0001_PersonIdentTest {
p.toExternalString());
}
+ @SuppressWarnings("unused")
@Test(expected = IllegalArgumentException.class)
public void nullForNameShouldThrowIllegalArgumentException() {
new PersonIdent(null, "author@example.com");
}
+ @SuppressWarnings("unused")
@Test(expected = IllegalArgumentException.class)
public void nullForEmailShouldThrowIllegalArgumentException() {
new PersonIdent("A U Thor", null);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java
deleted file mode 100644
index 651e62c9ca..0000000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
-import org.junit.Test;
-
-@SuppressWarnings("deprecation")
-public class T0002_TreeTest extends SampleDataRepositoryTestCase {
- private static final ObjectId SOME_FAKE_ID = ObjectId.fromString(
- "0123456789abcdef0123456789abcdef01234567");
-
- private static int compareNamesUsingSpecialCompare(String a, String b)
- throws UnsupportedEncodingException {
- char lasta = '\0';
- byte[] abytes;
- if (a.length() > 0 && a.charAt(a.length()-1) == '/') {
- lasta = '/';
- a = a.substring(0, a.length() - 1);
- }
- abytes = a.getBytes("ISO-8859-1");
- char lastb = '\0';
- byte[] bbytes;
- if (b.length() > 0 && b.charAt(b.length()-1) == '/') {
- lastb = '/';
- b = b.substring(0, b.length() - 1);
- }
- bbytes = b.getBytes("ISO-8859-1");
- return Tree.compareNames(abytes, bbytes, lasta, lastb);
- }
-
- @Test
- public void test000_sort_01() throws UnsupportedEncodingException {
- assertEquals(0, compareNamesUsingSpecialCompare("a","a"));
- }
-
- @Test
- public void test000_sort_02() throws UnsupportedEncodingException {
- assertEquals(-1, compareNamesUsingSpecialCompare("a","b"));
- assertEquals(1, compareNamesUsingSpecialCompare("b","a"));
- }
-
- @Test
- public void test000_sort_03() throws UnsupportedEncodingException {
- assertEquals(1, compareNamesUsingSpecialCompare("a:","a"));
- assertEquals(1, compareNamesUsingSpecialCompare("a/","a"));
- assertEquals(-1, compareNamesUsingSpecialCompare("a","a/"));
- assertEquals(-1, compareNamesUsingSpecialCompare("a","a:"));
- assertEquals(1, compareNamesUsingSpecialCompare("a:","a/"));
- assertEquals(-1, compareNamesUsingSpecialCompare("a/","a:"));
- }
-
- @Test
- public void test000_sort_04() throws UnsupportedEncodingException {
- assertEquals(-1, compareNamesUsingSpecialCompare("a.a","a/a"));
- assertEquals(1, compareNamesUsingSpecialCompare("a/a","a.a"));
- }
-
- @Test
- public void test000_sort_05() throws UnsupportedEncodingException {
- assertEquals(-1, compareNamesUsingSpecialCompare("a.","a/"));
- assertEquals(1, compareNamesUsingSpecialCompare("a/","a."));
-
- }
-
- @Test
- public void test001_createEmpty() throws IOException {
- final Tree t = new Tree(db);
- assertTrue("isLoaded", t.isLoaded());
- assertTrue("isModified", t.isModified());
- assertTrue("no parent", t.getParent() == null);
- assertTrue("isRoot", t.isRoot());
- assertTrue("no name", t.getName() == null);
- assertTrue("no nameUTF8", t.getNameUTF8() == null);
- assertTrue("has entries array", t.members() != null);
- assertEquals("entries is empty", 0, t.members().length);
- assertEquals("full name is empty", "", t.getFullName());
- assertTrue("no id", t.getId() == null);
- assertTrue("database is r", t.getRepository() == db);
- assertTrue("no foo child", t.findTreeMember("foo") == null);
- assertTrue("no foo child", t.findBlobMember("foo") == null);
- }
-
- @Test
- public void test002_addFile() throws IOException {
- final Tree t = new Tree(db);
- t.setId(SOME_FAKE_ID);
- assertTrue("has id", t.getId() != null);
- assertFalse("not modified", t.isModified());
-
- final String n = "bob";
- final FileTreeEntry f = t.addFile(n);
- assertNotNull("have file", f);
- assertEquals("name matches", n, f.getName());
- assertEquals("name matches", f.getName(), new String(f.getNameUTF8(),
- "UTF-8"));
- assertEquals("full name matches", n, f.getFullName());
- assertTrue("no id", f.getId() == null);
- assertTrue("is modified", t.isModified());
- assertTrue("has no id", t.getId() == null);
- assertTrue("found bob", t.findBlobMember(f.getName()) == f);
-
- final TreeEntry[] i = t.members();
- assertNotNull("members array not null", i);
- assertTrue("iterator is not empty", i != null && i.length > 0);
- assertTrue("iterator returns file", i != null && i[0] == f);
- assertTrue("iterator is empty", i != null && i.length == 1);
- }
-
- @Test
- public void test004_addTree() throws IOException {
- final Tree t = new Tree(db);
- t.setId(SOME_FAKE_ID);
- assertTrue("has id", t.getId() != null);
- assertFalse("not modified", t.isModified());
-
- final String n = "bob";
- final Tree f = t.addTree(n);
- assertNotNull("have tree", f);
- assertEquals("name matches", n, f.getName());
- assertEquals("name matches", f.getName(), new String(f.getNameUTF8(),
- "UTF-8"));
- assertEquals("full name matches", n, f.getFullName());
- assertTrue("no id", f.getId() == null);
- assertTrue("parent matches", f.getParent() == t);
- assertTrue("repository matches", f.getRepository() == db);
- assertTrue("isLoaded", f.isLoaded());
- assertFalse("has items", f.members().length > 0);
- assertFalse("is root", f.isRoot());
- assertTrue("parent is modified", t.isModified());
- assertTrue("parent has no id", t.getId() == null);
- assertTrue("found bob child", t.findTreeMember(f.getName()) == f);
-
- final TreeEntry[] i = t.members();
- assertTrue("iterator is not empty", i.length > 0);
- assertTrue("iterator returns file", i[0] == f);
- assertEquals("iterator is empty", 1, i.length);
- }
-
- @Test
- public void test005_addRecursiveFile() throws IOException {
- final Tree t = new Tree(db);
- final FileTreeEntry f = t.addFile("a/b/c");
- assertNotNull("created f", f);
- assertEquals("c", f.getName());
- assertEquals("b", f.getParent().getName());
- assertEquals("a", f.getParent().getParent().getName());
- assertTrue("t is great-grandparent", t == f.getParent().getParent()
- .getParent());
- }
-
- @Test
- public void test005_addRecursiveTree() throws IOException {
- final Tree t = new Tree(db);
- final Tree f = t.addTree("a/b/c");
- assertNotNull("created f", f);
- assertEquals("c", f.getName());
- assertEquals("b", f.getParent().getName());
- assertEquals("a", f.getParent().getParent().getName());
- assertTrue("t is great-grandparent", t == f.getParent().getParent()
- .getParent());
- }
-
- @Test
- public void test006_addDeepTree() throws IOException {
- final Tree t = new Tree(db);
-
- final Tree e = t.addTree("e");
- assertNotNull("have e", e);
- assertTrue("e.parent == t", e.getParent() == t);
- final Tree f = t.addTree("f");
- assertNotNull("have f", f);
- assertTrue("f.parent == t", f.getParent() == t);
- final Tree g = f.addTree("g");
- assertNotNull("have g", g);
- assertTrue("g.parent == f", g.getParent() == f);
- final Tree h = g.addTree("h");
- assertNotNull("have h", h);
- assertTrue("h.parent = g", h.getParent() == g);
-
- h.setId(SOME_FAKE_ID);
- assertTrue("h not modified", !h.isModified());
- g.setId(SOME_FAKE_ID);
- assertTrue("g not modified", !g.isModified());
- f.setId(SOME_FAKE_ID);
- assertTrue("f not modified", !f.isModified());
- e.setId(SOME_FAKE_ID);
- assertTrue("e not modified", !e.isModified());
- t.setId(SOME_FAKE_ID);
- assertTrue("t not modified.", !t.isModified());
-
- assertEquals("full path of h ok", "f/g/h", h.getFullName());
- assertTrue("Can find h", t.findTreeMember(h.getFullName()) == h);
- assertTrue("Can't find f/z", t.findBlobMember("f/z") == null);
- assertTrue("Can't find y/z", t.findBlobMember("y/z") == null);
-
- final FileTreeEntry i = h.addFile("i");
- assertNotNull(i);
- assertEquals("full path of i ok", "f/g/h/i", i.getFullName());
- assertTrue("Can find i", t.findBlobMember(i.getFullName()) == i);
- assertTrue("h modified", h.isModified());
- assertTrue("g modified", g.isModified());
- assertTrue("f modified", f.isModified());
- assertTrue("e not modified", !e.isModified());
- assertTrue("t modified", t.isModified());
-
- assertTrue("h no id", h.getId() == null);
- assertTrue("g no id", g.getId() == null);
- assertTrue("f no id", f.getId() == null);
- assertTrue("e has id", e.getId() != null);
- assertTrue("t no id", t.getId() == null);
- }
-
- @Test
- public void test007_manyFileLookup() throws IOException {
- final Tree t = new Tree(db);
- final List<FileTreeEntry> files = new ArrayList<FileTreeEntry>(26 * 26);
- for (char level1 = 'a'; level1 <= 'z'; level1++) {
- for (char level2 = 'a'; level2 <= 'z'; level2++) {
- final String n = "." + level1 + level2 + "9";
- final FileTreeEntry f = t.addFile(n);
- assertNotNull("File " + n + " added.", f);
- assertEquals(n, f.getName());
- files.add(f);
- }
- }
- assertEquals(files.size(), t.memberCount());
- final TreeEntry[] ents = t.members();
- assertNotNull(ents);
- assertEquals(files.size(), ents.length);
- for (int k = 0; k < ents.length; k++) {
- assertTrue("File " + files.get(k).getName()
- + " is at " + k + ".", files.get(k) == ents[k]);
- }
- }
-
- @Test
- public void test008_SubtreeInternalSorting() throws IOException {
- final Tree t = new Tree(db);
- final FileTreeEntry e0 = t.addFile("a-b");
- final FileTreeEntry e1 = t.addFile("a-");
- final FileTreeEntry e2 = t.addFile("a=b");
- final Tree e3 = t.addTree("a");
- final FileTreeEntry e4 = t.addFile("a=");
-
- final TreeEntry[] ents = t.members();
- assertSame(e1, ents[0]);
- assertSame(e0, ents[1]);
- assertSame(e3, ents[2]);
- assertSame(e4, ents[3]);
- assertSame(e2, ents[4]);
- }
-
- @Test
- public void test009_SymlinkAndGitlink() throws IOException {
- final Tree symlinkTree = mapTree("symlink");
- assertTrue("Symlink entry exists", symlinkTree.existsBlob("symlink.txt"));
- final Tree gitlinkTree = mapTree("gitlink");
- assertTrue("Gitlink entry exists", gitlinkTree.existsBlob("submodule"));
- }
-
- private Tree mapTree(String name) throws IOException {
- ObjectId id = db.resolve(name + "^{tree}");
- return new Tree(db, id, db.open(id).getCachedBytes());
- }
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java
index 2a59f58c66..9c9edc1476 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java
@@ -47,11 +47,10 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileTreeEntry;
+import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.Tree;
+import org.eclipse.jgit.lib.TreeFormatter;
import org.junit.Test;
@SuppressWarnings("deprecation")
@@ -220,28 +219,24 @@ public class ObjectWalkTest extends RevWalkTestCase {
.fromString("abbbfafe3129f85747aba7bfac992af77134c607");
final RevTree tree_root, tree_A, tree_AB;
final RevCommit b;
- {
- Tree root = new Tree(db);
- Tree A = root.addTree("A");
- FileTreeEntry B = root.addFile("B");
- B.setId(bId);
-
- Tree A_A = A.addTree("A");
- Tree A_B = A.addTree("B");
-
- try (final ObjectInserter inserter = db.newObjectInserter()) {
- A_A.setId(inserter.insert(Constants.OBJ_TREE, A_A.format()));
- A_B.setId(inserter.insert(Constants.OBJ_TREE, A_B.format()));
- A.setId(inserter.insert(Constants.OBJ_TREE, A.format()));
- root.setId(inserter.insert(Constants.OBJ_TREE, root.format()));
- inserter.flush();
- }
-
- tree_root = rw.parseTree(root.getId());
- tree_A = rw.parseTree(A.getId());
- tree_AB = rw.parseTree(A_A.getId());
- assertSame(tree_AB, rw.parseTree(A_B.getId()));
- b = commit(rw.parseTree(root.getId()));
+ try (ObjectInserter inserter = db.newObjectInserter()) {
+ ObjectId empty = inserter.insert(new TreeFormatter());
+
+ TreeFormatter A = new TreeFormatter();
+ A.append("A", FileMode.TREE, empty);
+ A.append("B", FileMode.TREE, empty);
+ ObjectId idA = inserter.insert(A);
+
+ TreeFormatter root = new TreeFormatter();
+ root.append("A", FileMode.TREE, idA);
+ root.append("B", FileMode.REGULAR_FILE, bId);
+ ObjectId idRoot = inserter.insert(root);
+ inserter.flush();
+
+ tree_root = objw.parseTree(idRoot);
+ tree_A = objw.parseTree(idA);
+ tree_AB = objw.parseTree(empty);
+ b = commit(tree_root);
}
markStart(b);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java
index 782e414b62..c1e078d10d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java
@@ -112,12 +112,9 @@ public class AtomicPushTest {
public void pushNonAtomic() throws Exception {
PushResult r;
server.setPerformsAtomicTransactions(false);
- Transport tn = testProtocol.open(uri, client, "server");
- try {
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
tn.setPushAtomic(false);
r = tn.push(NullProgressMonitor.INSTANCE, commands());
- } finally {
- tn.close();
}
RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
@@ -131,12 +128,9 @@ public class AtomicPushTest {
@Test
public void pushAtomicClientGivesUpEarly() throws Exception {
PushResult r;
- Transport tn = testProtocol.open(uri, client, "server");
- try {
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
tn.setPushAtomic(true);
r = tn.push(NullProgressMonitor.INSTANCE, commands());
- } finally {
- tn.close();
}
RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
@@ -167,8 +161,7 @@ public class AtomicPushTest {
ObjectId.zeroId()));
server.setPerformsAtomicTransactions(false);
- Transport tn = testProtocol.open(uri, client, "server");
- try {
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
tn.setPushAtomic(true);
tn.push(NullProgressMonitor.INSTANCE, cmds);
fail("did not throw TransportException");
@@ -176,8 +169,6 @@ public class AtomicPushTest {
assertEquals(
uri + ": " + JGitText.get().atomicPushNotSupported,
e.getMessage());
- } finally {
- tn.close();
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
index ba89d2d616..f94f707258 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
@@ -166,8 +166,10 @@ public class BundleWriterTest extends SampleDataRepositoryTestCase {
final ByteArrayInputStream in = new ByteArrayInputStream(bundle);
final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*");
final Set<RefSpec> refs = Collections.singleton(rs);
- return new TransportBundleStream(newRepo, uri, in).fetch(
- NullProgressMonitor.INSTANCE, refs);
+ try (TransportBundleStream transport = new TransportBundleStream(
+ newRepo, uri, in)) {
+ return transport.fetch(NullProgressMonitor.INSTANCE, refs);
+ }
}
private byte[] makeBundle(final String name,
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 aa5914fe03..94bc383db7 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
@@ -116,12 +116,9 @@ public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCas
// Clone from dst into src
//
- Transport t = Transport.open(src, uriOf(dst));
- try {
+ try (Transport t = Transport.open(src, uriOf(dst))) {
t.fetch(PM, Collections.singleton(new RefSpec("+refs/*:refs/*")));
assertEquals(B, src.resolve(R_MASTER));
- } finally {
- t.close();
}
// Now put private stuff into dst.
@@ -144,7 +141,8 @@ public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCas
@Test
public void testFilterHidesPrivate() throws Exception {
Map<String, Ref> refs;
- TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
+ try (TransportLocal t = new TransportLocal(src, uriOf(dst),
+ dst.getDirectory()) {
@Override
ReceivePack createReceivePack(final Repository db) {
db.close();
@@ -154,16 +152,10 @@ public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCas
rp.setAdvertiseRefsHook(new HidePrivateHook());
return rp;
}
- };
- try {
- PushConnection c = t.openPush();
- try {
+ }) {
+ try (PushConnection c = t.openPush()) {
refs = c.getRefsMap();
- } finally {
- c.close();
}
- } finally {
- t.close();
}
assertNotNull(refs);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
index e9ae1909ea..3661677cd9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
@@ -195,6 +195,7 @@ public class SideBandOutputStreamTest {
assertEquals(1, flushCnt[0]);
}
+ @SuppressWarnings("unused")
@Test
public void testConstructor_RejectsBadChannel() {
try {
@@ -220,6 +221,7 @@ public class SideBandOutputStreamTest {
}
}
+ @SuppressWarnings("unused")
@Test
public void testConstructor_RejectsBadBufferSize() {
try {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
index 55e1e44206..5519f61ac2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
@@ -61,13 +61,10 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class TransportTest extends SampleDataRepositoryTestCase {
- private Transport transport;
-
private RemoteConfig remoteConfig;
@Override
@@ -77,17 +74,6 @@ public class TransportTest extends SampleDataRepositoryTestCase {
final Config config = db.getConfig();
remoteConfig = new RemoteConfig(config, "test");
remoteConfig.addURI(new URIish("http://everyones.loves.git/u/2"));
- transport = null;
- }
-
- @Override
- @After
- public void tearDown() throws Exception {
- if (transport != null) {
- transport.close();
- transport = null;
- }
- super.tearDown();
}
/**
@@ -99,10 +85,11 @@ public class TransportTest extends SampleDataRepositoryTestCase {
@Test
public void testFindRemoteRefUpdatesNoWildcardNoTracking()
throws IOException {
- transport = Transport.open(db, remoteConfig);
- final Collection<RemoteRefUpdate> result = transport
- .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec(
- "refs/heads/master:refs/heads/x")));
+ Collection<RemoteRefUpdate> result;
+ try (Transport transport = Transport.open(db, remoteConfig)) {
+ result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1,
+ new RefSpec("refs/heads/master:refs/heads/x")));
+ }
assertEquals(1, result.size());
final RemoteRefUpdate rru = result.iterator().next();
@@ -122,10 +109,11 @@ public class TransportTest extends SampleDataRepositoryTestCase {
@Test
public void testFindRemoteRefUpdatesNoWildcardNoDestination()
throws IOException {
- transport = Transport.open(db, remoteConfig);
- final Collection<RemoteRefUpdate> result = transport
- .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec(
- "+refs/heads/master")));
+ Collection<RemoteRefUpdate> result;
+ try (Transport transport = Transport.open(db, remoteConfig)) {
+ result = transport.findRemoteRefUpdatesFor(
+ Collections.nCopies(1, new RefSpec("+refs/heads/master")));
+ }
assertEquals(1, result.size());
final RemoteRefUpdate rru = result.iterator().next();
@@ -143,10 +131,11 @@ public class TransportTest extends SampleDataRepositoryTestCase {
*/
@Test
public void testFindRemoteRefUpdatesWildcardNoTracking() throws IOException {
- transport = Transport.open(db, remoteConfig);
- final Collection<RemoteRefUpdate> result = transport
- .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec(
- "+refs/heads/*:refs/heads/test/*")));
+ Collection<RemoteRefUpdate> result;
+ try (Transport transport = Transport.open(db, remoteConfig)) {
+ result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1,
+ new RefSpec("+refs/heads/*:refs/heads/test/*")));
+ }
assertEquals(12, result.size());
boolean foundA = false;
@@ -171,12 +160,14 @@ public class TransportTest extends SampleDataRepositoryTestCase {
*/
@Test
public void testFindRemoteRefUpdatesTwoRefSpecs() throws IOException {
- transport = Transport.open(db, remoteConfig);
final RefSpec specA = new RefSpec("+refs/heads/a:refs/heads/b");
final RefSpec specC = new RefSpec("+refs/heads/c:refs/heads/d");
final Collection<RefSpec> specs = Arrays.asList(specA, specC);
- final Collection<RemoteRefUpdate> result = transport
- .findRemoteRefUpdatesFor(specs);
+
+ Collection<RemoteRefUpdate> result;
+ try (Transport transport = Transport.open(db, remoteConfig)) {
+ result = transport.findRemoteRefUpdatesFor(specs);
+ }
assertEquals(2, result.size());
boolean foundA = false;
@@ -202,10 +193,12 @@ public class TransportTest extends SampleDataRepositoryTestCase {
public void testFindRemoteRefUpdatesTrackingRef() throws IOException {
remoteConfig.addFetchRefSpec(new RefSpec(
"refs/heads/*:refs/remotes/test/*"));
- transport = Transport.open(db, remoteConfig);
- final Collection<RemoteRefUpdate> result = transport
- .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec(
- "+refs/heads/a:refs/heads/a")));
+
+ Collection<RemoteRefUpdate> result;
+ try (Transport transport = Transport.open(db, remoteConfig)) {
+ result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1,
+ new RefSpec("+refs/heads/a:refs/heads/a")));
+ }
assertEquals(1, result.size());
final TrackingRefUpdate tru = result.iterator().next()
@@ -225,20 +218,18 @@ public class TransportTest extends SampleDataRepositoryTestCase {
config.addURI(new URIish("../" + otherDir));
// Should not throw NoRemoteRepositoryException
- transport = Transport.open(db, config);
+ Transport.open(db, config).close();
}
@Test
public void testLocalTransportFetchWithoutLocalRepository()
throws Exception {
URIish uri = new URIish("file://" + db.getWorkTree().getAbsolutePath());
- transport = Transport.open(uri);
- FetchConnection fetchConnection = transport.openFetch();
- try {
- Ref head = fetchConnection.getRef(Constants.HEAD);
- assertNotNull(head);
- } finally {
- fetchConnection.close();
+ try (Transport transport = Transport.open(uri)) {
+ try (FetchConnection fetchConnection = transport.openFetch()) {
+ Ref head = fetchConnection.getRef(Constants.HEAD);
+ assertNotNull(head);
+ }
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
index 5e4ba35079..e55d373347 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
@@ -51,7 +51,6 @@ 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 static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
@@ -64,24 +63,16 @@ public class URIishTest {
private static final String GIT_SCHEME = "git://";
- @Test
+ @SuppressWarnings("unused")
+ @Test(expected = URISyntaxException.class)
public void shouldRaiseErrorOnEmptyURI() throws Exception {
- try {
- new URIish("");
- fail("expecting an exception");
- } catch (URISyntaxException e) {
- // expected
- }
+ new URIish("");
}
- @Test
+ @SuppressWarnings("unused")
+ @Test(expected = URISyntaxException.class)
public void shouldRaiseErrorOnNullURI() throws Exception {
- try {
- new URIish((String) null);
- fail("expecting an exception");
- } catch (URISyntaxException e) {
- // expected
- }
+ new URIish((String) null);
}
@Test
@@ -634,34 +625,19 @@ public class URIishTest {
assertEquals(u, new URIish(str));
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void testGetNullHumanishName() {
- try {
- new URIish().getHumanishName();
- fail("path must be not null");
- } catch (IllegalArgumentException e) {
- // expected
- }
+ new URIish().getHumanishName();
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void testGetEmptyHumanishName() throws URISyntaxException {
- try {
- new URIish(GIT_SCHEME).getHumanishName();
- fail("empty path is useless");
- } catch (IllegalArgumentException e) {
- // expected
- }
+ new URIish(GIT_SCHEME).getHumanishName();
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void testGetAbsEmptyHumanishName() {
- try {
- new URIish().getHumanishName();
- fail("empty path is useless");
- } catch (IllegalArgumentException e) {
- // expected
- }
+ new URIish().getHumanishName();
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java
index aca7c80fd7..c3ff7df8f2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java
@@ -44,7 +44,6 @@
package org.eclipse.jgit.treewalk;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
import static org.eclipse.jgit.lib.Constants.encode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -54,11 +53,10 @@ import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.Tree;
+import org.eclipse.jgit.lib.TreeFormatter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.junit.Test;
-@SuppressWarnings("deprecation")
public class TreeWalkBasicDiffTest extends RepositoryTestCase {
@Test
public void testMissingSubtree_DetectFileAdded_FileModified()
@@ -72,62 +70,63 @@ public class TreeWalkBasicDiffTest extends RepositoryTestCase {
// Create sub-a/empty, sub-c/empty = hello.
{
- final Tree root = new Tree(db);
+ TreeFormatter root = new TreeFormatter();
{
- final Tree subA = root.addTree("sub-a");
- subA.addFile("empty").setId(aFileId);
- subA.setId(inserter.insert(OBJ_TREE, subA.format()));
+ TreeFormatter subA = new TreeFormatter();
+ subA.append("empty", FileMode.REGULAR_FILE, aFileId);
+ root.append("sub-a", FileMode.TREE, inserter.insert(subA));
}
{
- final Tree subC = root.addTree("sub-c");
- subC.addFile("empty").setId(cFileId1);
- subC.setId(inserter.insert(OBJ_TREE, subC.format()));
+ TreeFormatter subC = new TreeFormatter();
+ subC.append("empty", FileMode.REGULAR_FILE, cFileId1);
+ root.append("sub-c", FileMode.TREE, inserter.insert(subC));
}
- oldTree = inserter.insert(OBJ_TREE, root.format());
+ oldTree = inserter.insert(root);
}
// Create sub-a/empty, sub-b/empty, sub-c/empty.
{
- final Tree root = new Tree(db);
+ TreeFormatter root = new TreeFormatter();
{
- final Tree subA = root.addTree("sub-a");
- subA.addFile("empty").setId(aFileId);
- subA.setId(inserter.insert(OBJ_TREE, subA.format()));
+ TreeFormatter subA = new TreeFormatter();
+ subA.append("empty", FileMode.REGULAR_FILE, aFileId);
+ root.append("sub-a", FileMode.TREE, inserter.insert(subA));
}
{
- final Tree subB = root.addTree("sub-b");
- subB.addFile("empty").setId(bFileId);
- subB.setId(inserter.insert(OBJ_TREE, subB.format()));
+ TreeFormatter subB = new TreeFormatter();
+ subB.append("empty", FileMode.REGULAR_FILE, bFileId);
+ root.append("sub-b", FileMode.TREE, inserter.insert(subB));
}
{
- final Tree subC = root.addTree("sub-c");
- subC.addFile("empty").setId(cFileId2);
- subC.setId(inserter.insert(OBJ_TREE, subC.format()));
+ TreeFormatter subC = new TreeFormatter();
+ subC.append("empty", FileMode.REGULAR_FILE, cFileId2);
+ root.append("sub-c", FileMode.TREE, inserter.insert(subC));
}
- newTree = inserter.insert(OBJ_TREE, root.format());
+ newTree = inserter.insert(root);
}
inserter.flush();
}
- final TreeWalk tw = new TreeWalk(db);
- tw.reset(oldTree, newTree);
- tw.setRecursive(true);
- tw.setFilter(TreeFilter.ANY_DIFF);
+ try (TreeWalk tw = new TreeWalk(db)) {
+ tw.reset(oldTree, newTree);
+ tw.setRecursive(true);
+ tw.setFilter(TreeFilter.ANY_DIFF);
- assertTrue(tw.next());
- assertEquals("sub-b/empty", tw.getPathString());
- assertEquals(FileMode.MISSING, tw.getFileMode(0));
- assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1));
- assertEquals(ObjectId.zeroId(), tw.getObjectId(0));
- assertEquals(bFileId, tw.getObjectId(1));
+ assertTrue(tw.next());
+ assertEquals("sub-b/empty", tw.getPathString());
+ assertEquals(FileMode.MISSING, tw.getFileMode(0));
+ assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1));
+ assertEquals(ObjectId.zeroId(), tw.getObjectId(0));
+ assertEquals(bFileId, tw.getObjectId(1));
- assertTrue(tw.next());
- assertEquals("sub-c/empty", tw.getPathString());
- assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0));
- assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1));
- assertEquals(cFileId1, tw.getObjectId(0));
- assertEquals(cFileId2, tw.getObjectId(1));
+ assertTrue(tw.next());
+ assertEquals("sub-c/empty", tw.getPathString());
+ assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0));
+ assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1));
+ assertEquals(cFileId1, tw.getObjectId(0));
+ assertEquals(cFileId2, tw.getObjectId(1));
- assertFalse(tw.next());
+ assertFalse(tw.next());
+ }
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
index 7273cdbabc..aaeb79c64a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
@@ -45,7 +45,6 @@ package org.eclipse.jgit.util;
import static org.junit.Assert.assertEquals;
-import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.junit.MockSystemReader;
@@ -113,7 +112,7 @@ public class ChangeIdUtilTest {
}
@Test
- public void testId() throws IOException {
+ public void testId() {
String msg = "A\nMessage\n";
ObjectId id = ChangeIdUtil.computeChangeId(treeId, parentId, p, q, msg);
assertEquals("73f3751208ac92cbb76f9a26ac4a0d9d472e381b", ObjectId
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index b2a8f677f3..36041f8144 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,5 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.jgit" version="2">
+ <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.FileTreeEntry">
+ <filter id="305324134">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.FileTreeEntry"/>
+ <message_argument value="org.eclipse.jgit_4.2.0"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.GitlinkTreeEntry">
+ <filter id="305324134">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.GitlinkTreeEntry"/>
+ <message_argument value="org.eclipse.jgit_4.2.0"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.SymlinkTreeEntry">
+ <filter id="305324134">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.SymlinkTreeEntry"/>
+ <message_argument value="org.eclipse.jgit_4.2.0"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.Tree">
+ <filter id="305324134">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.Tree"/>
+ <message_argument value="org.eclipse.jgit_4.2.0"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.TreeEntry">
+ <filter id="305324134">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.TreeEntry"/>
+ <message_argument value="org.eclipse.jgit_4.2.0"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode">
<filter comment="attributes weren't really usable in earlier versions" id="338792546">
<message_arguments>
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 25d0be6ec7..3d3e74f5fd 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -59,6 +59,7 @@ Export-Package: org.eclipse.jgit.annotations;version="4.2.0",
org.eclipse.jgit.ignore;version="4.2.0",
org.eclipse.jgit.ignore.internal;version="4.2.0";x-friends:="org.eclipse.jgit.test",
org.eclipse.jgit.internal;version="4.2.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.ketch;version="4.2.0";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
org.eclipse.jgit.internal.storage.dfs;version="4.2.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.server",
org.eclipse.jgit.internal.storage.file;version="4.2.0";
x-friends:="org.eclipse.jgit.test,
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/ketch/KetchText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/ketch/KetchText.properties
new file mode 100644
index 0000000000..1fbb7cb3b5
--- /dev/null
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/ketch/KetchText.properties
@@ -0,0 +1,13 @@
+accepted=accepted.
+cannotFetchFromLocalReplica=cannot fetch from LocalReplica
+failed=failed!
+invalidFollowerUri=invalid follower URI
+leaderFailedToStore=leader failed to store
+localReplicaRequired=LocalReplica instance is required
+mismatchedTxnNamespace=mismatched txnNamespace; expected {0} found {1}
+outsideTxnNamespace=ref {0} is outside of txnNamespace {1}
+proposingUpdates=Proposing updates
+queuedProposalFailedToApply=queued proposal failed to apply
+starting=starting!
+unsupportedVoterCount=unsupported voter count {0}, expected one of {1}
+waitingForQueue=Waiting for queue
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 5b7d84f732..b5057ad282 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -53,6 +53,7 @@ import java.util.List;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.EmtpyCommitException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
@@ -130,6 +131,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
private PrintStream hookOutRedirect;
+ private Boolean allowEmpty;
+
/**
* @param repo
*/
@@ -179,7 +182,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
processOptions(state, rw);
- if (all && !repo.isBare() && repo.getWorkTree() != null) {
+ if (all && !repo.isBare()) {
try (Git git = new Git(repo)) {
git.add()
.addFilepattern(".") //$NON-NLS-1$
@@ -231,6 +234,16 @@ public class CommitCommand extends GitCommand<RevCommit> {
if (insertChangeId)
insertChangeId(indexTreeId);
+ // Check for empty commits
+ if (headId != null && !allowEmpty.booleanValue()) {
+ RevCommit headCommit = rw.parseCommit(headId);
+ headCommit.getTree();
+ if (indexTreeId.equals(headCommit.getTree())) {
+ throw new EmtpyCommitException(
+ JGitText.get().emptyCommit);
+ }
+ }
+
// Create a Commit object, populate it and write it
CommitBuilder commit = new CommitBuilder();
commit.setCommitter(committer);
@@ -457,6 +470,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
// there must be at least one change
if (emptyCommit)
+ // Would like to throw a EmptyCommitException. But this would break the API
+ // TODO(ch): Change this in the next release
throw new JGitInternalException(JGitText.get().emptyCommit);
// update index
@@ -510,6 +525,12 @@ public class CommitCommand extends GitCommand<RevCommit> {
committer = new PersonIdent(repo);
if (author == null && !amend)
author = committer;
+ if (allowEmpty == null)
+ // JGit allows empty commits by default. Only when pathes are
+ // specified the commit should not be empty. This behaviour differs
+ // from native git but can only be adapted in the next release.
+ // TODO(ch) align the defaults with native git
+ allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE;
// when doing a merge commit parse MERGE_HEAD and MERGE_MSG files
if (state == RepositoryState.MERGING_RESOLVED
@@ -579,6 +600,27 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
/**
+ * @param allowEmpty
+ * whether it should be allowed to create a commit which has the
+ * same tree as it's sole predecessor (a commit which doesn't
+ * change anything). By default when creating standard commits
+ * (without specifying paths) JGit allows to create such commits.
+ * When this flag is set to false an attempt to create an "empty"
+ * standard commit will lead to an EmptyCommitException.
+ * <p>
+ * By default when creating a commit containing only specified
+ * paths an attempt to create an empty commit leads to a
+ * {@link JGitInternalException}. By setting this flag to
+ * <code>true</code> this exception will not be thrown.
+ * @return {@code this}
+ * @since 4.2
+ */
+ public CommitCommand setAllowEmpty(boolean allowEmpty) {
+ this.allowEmpty = Boolean.valueOf(allowEmpty);
+ return this;
+ }
+
+ /**
* @return the commit message used for the <code>commit</code>
*/
public String getMessage() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index 9620089b08..de512761a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -116,22 +116,17 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
org.eclipse.jgit.api.errors.TransportException {
checkCallable();
- try {
- Transport transport = Transport.open(repo, remote);
- try {
- transport.setCheckFetchedObjects(checkFetchedObjects);
- transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
- transport.setDryRun(dryRun);
- if (tagOption != null)
- transport.setTagOpt(tagOption);
- transport.setFetchThin(thin);
- configure(transport);
-
- FetchResult result = transport.fetch(monitor, refSpecs);
- return result;
- } finally {
- transport.close();
- }
+ try (Transport transport = Transport.open(repo, remote)) {
+ transport.setCheckFetchedObjects(checkFetchedObjects);
+ transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
+ transport.setDryRun(dryRun);
+ if (tagOption != null)
+ transport.setTagOpt(tagOption);
+ transport.setFetchThin(thin);
+ configure(transport);
+
+ FetchResult result = transport.fetch(monitor, refSpecs);
+ return result;
} catch (NoRemoteRepositoryException e) {
throw new InvalidRemoteException(MessageFormat.format(
JGitText.get().invalidRemote, remote), e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
index 3363a0fc8f..f3527fd805 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
@@ -182,13 +182,9 @@ public class LsRemoteCommand extends
org.eclipse.jgit.api.errors.TransportException {
checkCallable();
- Transport transport = null;
- FetchConnection fc = null;
- try {
- if (repo != null)
- transport = Transport.open(repo, remote);
- else
- transport = Transport.open(new URIish(remote));
+ try (Transport transport = repo != null
+ ? Transport.open(repo, remote)
+ : Transport.open(new URIish(remote))) {
transport.setOptionUploadPack(uploadPack);
configure(transport);
Collection<RefSpec> refSpecs = new ArrayList<RefSpec>(1);
@@ -199,19 +195,20 @@ public class LsRemoteCommand extends
refSpecs.add(new RefSpec("refs/heads/*:refs/remotes/origin/*")); //$NON-NLS-1$
Collection<Ref> refs;
Map<String, Ref> refmap = new HashMap<String, Ref>();
- fc = transport.openFetch();
- refs = fc.getRefs();
- if (refSpecs.isEmpty())
- for (Ref r : refs)
- refmap.put(r.getName(), r);
- else
- for (Ref r : refs)
- for (RefSpec rs : refSpecs)
- if (rs.matchSource(r)) {
- refmap.put(r.getName(), r);
- break;
- }
- return refmap;
+ try (FetchConnection fc = transport.openFetch()) {
+ refs = fc.getRefs();
+ if (refSpecs.isEmpty())
+ for (Ref r : refs)
+ refmap.put(r.getName(), r);
+ else
+ for (Ref r : refs)
+ for (RefSpec rs : refSpecs)
+ if (rs.matchSource(r)) {
+ refmap.put(r.getName(), r);
+ break;
+ }
+ return refmap;
+ }
} catch (URISyntaxException e) {
throw new InvalidRemoteException(MessageFormat.format(
JGitText.get().invalidRemote, remote));
@@ -223,11 +220,6 @@ public class LsRemoteCommand extends
throw new org.eclipse.jgit.api.errors.TransportException(
e.getMessage(),
e);
- } finally {
- if (fc != null)
- fc.close();
- if (transport != null)
- transport.close();
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
index 8f4bc4f26c..4c91e6c17f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -190,10 +190,8 @@ public class ResetCommand extends GitCommand<Ref> {
ObjectId origHead = ru.getOldObjectId();
if (origHead != null)
repo.writeOrigHead(origHead);
- result = ru.getRef();
- } else {
- result = repo.getRef(Constants.HEAD);
}
+ result = repo.exactRef(Constants.HEAD);
if (mode == null)
mode = ResetType.MIXED;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java
new file mode 100644
index 0000000000..b3cc1bfcf2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v1.0 which accompanies this
+ * distribution, is reproduced below, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.api.errors;
+
+/**
+ * Exception thrown when a newly created commit does not contain any changes
+ *
+ * @since 4.2
+ */
+public class EmtpyCommitException extends GitAPIException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public EmtpyCommitException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * @param message
+ */
+ public EmtpyCommitException(String message) {
+ super(message);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ElectionRound.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ElectionRound.java
new file mode 100644
index 0000000000..014eab2b45
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ElectionRound.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.KetchConstants.TERM;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TreeFormatter;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The initial {@link Round} for a leaderless repository, used to establish a
+ * leader.
+ */
+class ElectionRound extends Round {
+ private static final Logger log = LoggerFactory.getLogger(ElectionRound.class);
+
+ private long term;
+
+ ElectionRound(KetchLeader leader, LogIndex head) {
+ super(leader, head);
+ }
+
+ @Override
+ void start() throws IOException {
+ ObjectId id;
+ try (Repository git = leader.openRepository();
+ ObjectInserter inserter = git.newObjectInserter()) {
+ id = bumpTerm(git, inserter);
+ inserter.flush();
+ }
+ runAsync(id);
+ }
+
+ @Override
+ void success() {
+ // Do nothing upon election, KetchLeader will copy the term.
+ }
+
+ long getTerm() {
+ return term;
+ }
+
+ private ObjectId bumpTerm(Repository git, ObjectInserter inserter)
+ throws IOException {
+ CommitBuilder b = new CommitBuilder();
+ if (!ObjectId.zeroId().equals(acceptedOldIndex)) {
+ try (RevWalk rw = new RevWalk(git)) {
+ RevCommit c = rw.parseCommit(acceptedOldIndex);
+ b.setTreeId(c.getTree());
+ b.setParentId(acceptedOldIndex);
+ term = parseTerm(c.getFooterLines(TERM)) + 1;
+ }
+ } else {
+ term = 1;
+ b.setTreeId(inserter.insert(new TreeFormatter()));
+ }
+
+ StringBuilder msg = new StringBuilder();
+ msg.append(KetchConstants.TERM.getName())
+ .append(": ") //$NON-NLS-1$
+ .append(term);
+
+ String tag = leader.getSystem().newLeaderTag();
+ if (tag != null && !tag.isEmpty()) {
+ msg.append(' ').append(tag);
+ }
+
+ b.setAuthor(leader.getSystem().newCommitter());
+ b.setCommitter(b.getAuthor());
+ b.setMessage(msg.toString());
+
+ if (log.isDebugEnabled()) {
+ log.debug("Trying to elect myself " + b.getMessage()); //$NON-NLS-1$
+ }
+ return inserter.insert(b);
+ }
+
+ private static long parseTerm(List<String> footer) {
+ if (footer.isEmpty()) {
+ return 0;
+ }
+
+ String s = footer.get(0);
+ int p = s.indexOf(' ');
+ if (p > 0) {
+ s = s.substring(0, p);
+ }
+ return Long.parseLong(s, 10);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchConstants.java
new file mode 100644
index 0000000000..171c059db1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchConstants.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import org.eclipse.jgit.revwalk.FooterKey;
+
+/** Frequently used constants in a Ketch system. */
+public class KetchConstants {
+ /**
+ * Default reference namespace holding {@link #ACCEPTED} and
+ * {@link #COMMITTED} references and the {@link #STAGE} sub-namespace.
+ */
+ public static final String DEFAULT_TXN_NAMESPACE = "refs/txn/"; //$NON-NLS-1$
+
+ /** Reference name holding the RefTree accepted by a follower. */
+ public static final String ACCEPTED = "accepted"; //$NON-NLS-1$
+
+ /** Reference name holding the RefTree known to be committed. */
+ public static final String COMMITTED = "committed"; //$NON-NLS-1$
+
+ /** Reference subdirectory holding proposed heads. */
+ public static final String STAGE = "stage/"; //$NON-NLS-1$
+
+ /** Footer containing the current term. */
+ public static final FooterKey TERM = new FooterKey("Term"); //$NON-NLS-1$
+
+ /** Section for Ketch configuration ({@code ketch}). */
+ public static final String CONFIG_SECTION_KETCH = "ketch"; //$NON-NLS-1$
+
+ /** Behavior for a replica ({@code remote.$name.ketch-type}) */
+ public static final String CONFIG_KEY_TYPE = "ketch-type"; //$NON-NLS-1$
+
+ /** Behavior for a replica ({@code remote.$name.ketch-commit}) */
+ public static final String CONFIG_KEY_COMMIT = "ketch-commit"; //$NON-NLS-1$
+
+ /** Behavior for a replica ({@code remote.$name.ketch-speed}) */
+ public static final String CONFIG_KEY_SPEED = "ketch-speed"; //$NON-NLS-1$
+
+ private KetchConstants() {
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java
new file mode 100644
index 0000000000..3bcd6bcb24
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.KetchLeader.State.CANDIDATE;
+import static org.eclipse.jgit.internal.ketch.KetchLeader.State.LEADER;
+import static org.eclipse.jgit.internal.ketch.KetchLeader.State.SHUTDOWN;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.Participation.FOLLOWER_ONLY;
+import static org.eclipse.jgit.internal.ketch.Proposal.State.QUEUED;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jgit.internal.storage.reftree.RefTree;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A leader managing consensus across remote followers.
+ * <p>
+ * A leader instance starts up in {@link State#CANDIDATE} and tries to begin a
+ * new term by sending an {@link ElectionRound} to all replicas. Its term starts
+ * if a majority of replicas have accepted this leader instance for the term.
+ * <p>
+ * Once elected by a majority the instance enters {@link State#LEADER} and runs
+ * proposals offered to {@link #queueProposal(Proposal)}. This continues until
+ * the leader is timed out for inactivity, or is deposed by a competing leader
+ * gaining its own majority.
+ * <p>
+ * Once timed out or deposed this {@code KetchLeader} instance should be
+ * discarded, and a new instance takes over.
+ * <p>
+ * Each leader instance coordinates a group of {@link KetchReplica}s. Replica
+ * instances are owned by the leader instance and must be discarded when the
+ * leader is discarded.
+ * <p>
+ * In Ketch all push requests are issued through the leader. The steps are as
+ * follows (see {@link KetchPreReceive} for an example):
+ * <ul>
+ * <li>Create a {@link Proposal} with the
+ * {@link org.eclipse.jgit.transport.ReceiveCommand}s that represent the push.
+ * <li>Invoke {@link #queueProposal(Proposal)} on the leader instance.
+ * <li>Wait for consensus with {@link Proposal#await()}.
+ * <li>To examine the status of the push, check {@link Proposal#getCommands()},
+ * looking at
+ * {@link org.eclipse.jgit.internal.storage.reftree.Command#getResult()}.
+ * </ul>
+ * <p>
+ * The leader gains consensus by first pushing the needed objects and a
+ * {@link RefTree} representing the desired target repository state to the
+ * {@code refs/txn/accepted} branch on each of the replicas. Once a majority has
+ * succeeded, the leader commits the state by either pushing the
+ * {@code refs/txn/accepted} value to {@code refs/txn/committed} (for
+ * Ketch-aware replicas) or by pushing updates to {@code refs/heads/master},
+ * etc. for stock Git replicas.
+ * <p>
+ * Internally, the actual transport to replicas is performed on background
+ * threads via the {@link KetchSystem}'s executor service. For performance, the
+ * {@link KetchLeader}, {@link KetchReplica} and {@link Proposal} objects share
+ * some state, and may invoke each other's methods on different threads. This
+ * access is protected by the leader's {@link #lock} object. Care must be taken
+ * to prevent concurrent access by correctly obtaining the leader's lock.
+ */
+public abstract class KetchLeader {
+ private static final Logger log = LoggerFactory.getLogger(KetchLeader.class);
+
+ /** Current state of the leader instance. */
+ public static enum State {
+ /** Newly created instance trying to elect itself leader. */
+ CANDIDATE,
+
+ /** Leader instance elected by a majority. */
+ LEADER,
+
+ /** Instance has been deposed by another with a more recent term. */
+ DEPOSED,
+
+ /** Leader has been gracefully shutdown, e.g. due to inactivity. */
+ SHUTDOWN;
+ }
+
+ private final KetchSystem system;
+
+ /** Leader's knowledge of replicas for this repository. */
+ private KetchReplica[] voters;
+ private KetchReplica[] followers;
+ private LocalReplica self;
+
+ /**
+ * Lock protecting all data within this leader instance.
+ * <p>
+ * This lock extends into the {@link KetchReplica} instances used by the
+ * leader. They share the same lock instance to simplify concurrency.
+ */
+ final Lock lock;
+
+ private State state = CANDIDATE;
+
+ /** Term of this leader, once elected. */
+ private long term;
+
+ /**
+ * Pending proposals accepted into the queue in FIFO order.
+ * <p>
+ * These proposals were preflighted and do not contain any conflicts with
+ * each other and their expectations matched the leader's local view of the
+ * agreed upon {@code refs/txn/accepted} tree.
+ */
+ private final List<Proposal> queued;
+
+ /**
+ * State of the repository's RefTree after applying all entries in
+ * {@link #queued}. New proposals must be consistent with this tree to be
+ * appended to the end of {@link #queued}.
+ * <p>
+ * Must be deep-copied with {@link RefTree#copy()} if
+ * {@link #roundHoldsReferenceToRefTree} is {@code true}.
+ */
+ private RefTree refTree;
+
+ /**
+ * If {@code true} {@link #refTree} must be duplicated before queuing the
+ * next proposal. The {@link #refTree} was passed into the constructor of a
+ * {@link ProposalRound}, and that external reference to the {@link RefTree}
+ * object is held by the proposal until it materializes the tree object in
+ * the object store. This field is set {@code true} when the proposal begins
+ * execution and set {@code false} once tree objects are persisted in the
+ * local repository's object store or {@link #refTree} is replaced with a
+ * copy to isolate it from any running rounds.
+ * <p>
+ * If proposals arrive less frequently than the {@code RefTree} is written
+ * out to the repository the {@link #roundHoldsReferenceToRefTree} behavior
+ * avoids duplicating {@link #refTree}, reducing both time and memory used.
+ * However if proposals arrive more frequently {@link #refTree} must be
+ * duplicated to prevent newly queued proposals from corrupting the
+ * {@link #runningRound}.
+ */
+ volatile boolean roundHoldsReferenceToRefTree;
+
+ /** End of the leader's log. */
+ private LogIndex headIndex;
+
+ /** Leader knows this (and all prior) states are committed. */
+ private LogIndex committedIndex;
+
+ /**
+ * Is the leader idle with no work pending? If {@code true} there is no work
+ * for the leader (normal state). This field is {@code false} when the
+ * leader thread is scheduled for execution, or while {@link #runningRound}
+ * defines a round in progress.
+ */
+ private boolean idle;
+
+ /** Current round the leader is preparing and waiting for a vote on. */
+ private Round runningRound;
+
+ /**
+ * Construct a leader for a Ketch instance.
+ *
+ * @param system
+ * Ketch system configuration the leader must adhere to.
+ */
+ protected KetchLeader(KetchSystem system) {
+ this.system = system;
+ this.lock = new ReentrantLock(true /* fair */);
+ this.queued = new ArrayList<>(4);
+ this.idle = true;
+ }
+
+ /** @return system configuration. */
+ KetchSystem getSystem() {
+ return system;
+ }
+
+ /**
+ * Configure the replicas used by this Ketch instance.
+ * <p>
+ * Replicas should be configured once at creation before any proposals are
+ * executed. Once elections happen, <b>reconfiguration is a complicated
+ * concept that is not currently supported</b>.
+ *
+ * @param replicas
+ * members participating with the same repository.
+ */
+ public void setReplicas(Collection<KetchReplica> replicas) {
+ List<KetchReplica> v = new ArrayList<>(5);
+ List<KetchReplica> f = new ArrayList<>(5);
+ for (KetchReplica r : replicas) {
+ switch (r.getParticipation()) {
+ case FULL:
+ v.add(r);
+ break;
+
+ case FOLLOWER_ONLY:
+ f.add(r);
+ break;
+ }
+ }
+
+ Collection<Integer> validVoters = validVoterCounts();
+ if (!validVoters.contains(Integer.valueOf(v.size()))) {
+ throw new IllegalArgumentException(MessageFormat.format(
+ KetchText.get().unsupportedVoterCount,
+ Integer.valueOf(v.size()),
+ validVoters));
+ }
+
+ LocalReplica me = findLocal(v);
+ if (me == null) {
+ throw new IllegalArgumentException(
+ KetchText.get().localReplicaRequired);
+ }
+
+ lock.lock();
+ try {
+ voters = v.toArray(new KetchReplica[v.size()]);
+ followers = f.toArray(new KetchReplica[f.size()]);
+ self = me;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private static Collection<Integer> validVoterCounts() {
+ @SuppressWarnings("boxing")
+ Integer[] valid = {
+ // An odd number of voting replicas is required.
+ 1, 3, 5, 7, 9 };
+ return Arrays.asList(valid);
+ }
+
+ private static LocalReplica findLocal(Collection<KetchReplica> voters) {
+ for (KetchReplica r : voters) {
+ if (r instanceof LocalReplica) {
+ return (LocalReplica) r;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get an instance of the repository for use by a leader thread.
+ * <p>
+ * The caller will close the repository.
+ *
+ * @return opened repository for use by the leader thread.
+ * @throws IOException
+ * cannot reopen the repository for the leader.
+ */
+ protected abstract Repository openRepository() throws IOException;
+
+ /**
+ * Queue a reference update proposal for consensus.
+ * <p>
+ * This method does not wait for consensus to be reached. The proposal is
+ * checked to look for risks of conflicts, and then submitted into the queue
+ * for distribution as soon as possible.
+ * <p>
+ * Callers must use {@link Proposal#await()} to see if the proposal is done.
+ *
+ * @param proposal
+ * the proposed reference updates to queue for consideration.
+ * Once execution is complete the individual reference result
+ * fields will be populated with the outcome.
+ * @throws InterruptedException
+ * current thread was interrupted. The proposal may have been
+ * aborted if it was not yet queued for execution.
+ * @throws IOException
+ * unrecoverable error preventing proposals from being attempted
+ * by this leader.
+ */
+ public void queueProposal(Proposal proposal)
+ throws InterruptedException, IOException {
+ try {
+ lock.lockInterruptibly();
+ } catch (InterruptedException e) {
+ proposal.abort();
+ throw e;
+ }
+ try {
+ if (refTree == null) {
+ initialize();
+ for (Proposal p : queued) {
+ refTree.apply(p.getCommands());
+ }
+ } else if (roundHoldsReferenceToRefTree) {
+ refTree = refTree.copy();
+ roundHoldsReferenceToRefTree = false;
+ }
+
+ if (!refTree.apply(proposal.getCommands())) {
+ // A conflict exists so abort the proposal.
+ proposal.abort();
+ return;
+ }
+
+ queued.add(proposal);
+ proposal.notifyState(QUEUED);
+
+ if (idle) {
+ scheduleLeader();
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void initialize() throws IOException {
+ try (Repository git = openRepository(); RevWalk rw = new RevWalk(git)) {
+ self.initialize(git);
+
+ ObjectId accepted = self.getTxnAccepted();
+ if (!ObjectId.zeroId().equals(accepted)) {
+ RevCommit c = rw.parseCommit(accepted);
+ headIndex = LogIndex.unknown(accepted);
+ refTree = RefTree.read(rw.getObjectReader(), c.getTree());
+ } else {
+ headIndex = LogIndex.unknown(ObjectId.zeroId());
+ refTree = RefTree.newEmptyTree();
+ }
+ }
+ }
+
+ private void scheduleLeader() {
+ idle = false;
+ system.getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ runLeader();
+ }
+ });
+ }
+
+ private void runLeader() {
+ Round round;
+ lock.lock();
+ try {
+ switch (state) {
+ case CANDIDATE:
+ round = new ElectionRound(this, headIndex);
+ break;
+
+ case LEADER:
+ round = newProposalRound();
+ break;
+
+ case DEPOSED:
+ case SHUTDOWN:
+ default:
+ log.warn("Leader cannot run {}", state); //$NON-NLS-1$
+ // TODO(sop): Redirect proposals.
+ return;
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ try {
+ round.start();
+ } catch (IOException e) {
+ // TODO(sop) Depose leader if it cannot use its repository.
+ log.error(KetchText.get().leaderFailedToStore, e);
+ lock.lock();
+ try {
+ nextRound();
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ private ProposalRound newProposalRound() {
+ List<Proposal> todo = new ArrayList<>(queued);
+ queued.clear();
+ roundHoldsReferenceToRefTree = true;
+ return new ProposalRound(this, headIndex, todo, refTree);
+ }
+
+ /** @return term of this leader's reign. */
+ long getTerm() {
+ return term;
+ }
+
+ /** @return end of the leader's log. */
+ LogIndex getHead() {
+ return headIndex;
+ }
+
+ /**
+ * @return state leader knows it has committed across a quorum of replicas.
+ */
+ LogIndex getCommitted() {
+ return committedIndex;
+ }
+
+ boolean isIdle() {
+ return idle;
+ }
+
+ void runAsync(Round round) {
+ lock.lock();
+ try {
+ // End of the log is this round. Once transport begins it is
+ // reasonable to assume at least one replica will eventually get
+ // this, and there is reasonable probability it commits.
+ headIndex = round.acceptedNewIndex;
+ runningRound = round;
+
+ for (KetchReplica replica : voters) {
+ replica.pushTxnAcceptedAsync(round);
+ }
+ for (KetchReplica replica : followers) {
+ replica.pushTxnAcceptedAsync(round);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Asynchronous signal from a replica after completion.
+ * <p>
+ * Must be called while {@link #lock} is held by the replica.
+ *
+ * @param replica
+ * replica posting a completion event.
+ */
+ void onReplicaUpdate(KetchReplica replica) {
+ if (log.isDebugEnabled()) {
+ log.debug("Replica {} finished:\n{}", //$NON-NLS-1$
+ replica.describeForLog(), snapshot());
+ }
+
+ if (replica.getParticipation() == FOLLOWER_ONLY) {
+ // Followers cannot vote, so votes haven't changed.
+ return;
+ } else if (runningRound == null) {
+ // No round running, no need to tally votes.
+ return;
+ }
+
+ assert headIndex.equals(runningRound.acceptedNewIndex);
+ int matching = 0;
+ for (KetchReplica r : voters) {
+ if (r.hasAccepted(headIndex)) {
+ matching++;
+ }
+ }
+
+ int quorum = voters.length / 2 + 1;
+ boolean success = matching >= quorum;
+ if (!success) {
+ return;
+ }
+
+ switch (state) {
+ case CANDIDATE:
+ term = ((ElectionRound) runningRound).getTerm();
+ state = LEADER;
+ if (log.isDebugEnabled()) {
+ log.debug("Won election, running term " + term); //$NON-NLS-1$
+ }
+
+ //$FALL-THROUGH$
+ case LEADER:
+ committedIndex = headIndex;
+ if (log.isDebugEnabled()) {
+ log.debug("Committed {} in term {}", //$NON-NLS-1$
+ committedIndex.describeForLog(),
+ Long.valueOf(term));
+ }
+ nextRound();
+ commitAsync(replica);
+ notifySuccess(runningRound);
+ if (log.isDebugEnabled()) {
+ log.debug("Leader state:\n{}", snapshot()); //$NON-NLS-1$
+ }
+ break;
+
+ default:
+ log.debug("Leader ignoring replica while in {}", state); //$NON-NLS-1$
+ break;
+ }
+ }
+
+ private void notifySuccess(Round round) {
+ // Drop the leader lock while notifying Proposal listeners.
+ lock.unlock();
+ try {
+ round.success();
+ } finally {
+ lock.lock();
+ }
+ }
+
+ private void commitAsync(KetchReplica caller) {
+ for (KetchReplica r : voters) {
+ if (r == caller) {
+ continue;
+ }
+ if (r.shouldPushUnbatchedCommit(committedIndex, isIdle())) {
+ r.pushCommitAsync(committedIndex);
+ }
+ }
+ for (KetchReplica r : followers) {
+ if (r == caller) {
+ continue;
+ }
+ if (r.shouldPushUnbatchedCommit(committedIndex, isIdle())) {
+ r.pushCommitAsync(committedIndex);
+ }
+ }
+ }
+
+ /** Schedule the next round; invoked while {@link #lock} is held. */
+ void nextRound() {
+ runningRound = null;
+
+ if (queued.isEmpty()) {
+ idle = true;
+ } else {
+ // Caller holds lock. Reschedule leader on a new thread so
+ // the call stack can unwind and lock is not held unexpectedly
+ // during prepare for the next round.
+ scheduleLeader();
+ }
+ }
+
+ /** @return snapshot this leader. */
+ public LeaderSnapshot snapshot() {
+ lock.lock();
+ try {
+ LeaderSnapshot s = new LeaderSnapshot();
+ s.state = state;
+ s.term = term;
+ s.headIndex = headIndex;
+ s.committedIndex = committedIndex;
+ s.idle = isIdle();
+ for (KetchReplica r : voters) {
+ s.replicas.add(r.snapshot());
+ }
+ for (KetchReplica r : followers) {
+ s.replicas.add(r.snapshot());
+ }
+ return s;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /** Gracefully shutdown this leader and cancel outstanding operations. */
+ public void shutdown() {
+ lock.lock();
+ try {
+ if (state != SHUTDOWN) {
+ state = SHUTDOWN;
+ for (KetchReplica r : voters) {
+ r.shutdown();
+ }
+ for (KetchReplica r : followers) {
+ r.shutdown();
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return snapshot().toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeaderCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeaderCache.java
new file mode 100644
index 0000000000..ba033c1a42
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeaderCache.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import java.net.URISyntaxException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * A cache of live leader instances, keyed by repository.
+ * <p>
+ * Ketch only assigns a leader to a repository when needed. If
+ * {@link #get(Repository)} is called for a repository that does not have a
+ * leader, the leader is created and added to the cache.
+ */
+public class KetchLeaderCache {
+ private final KetchSystem system;
+ private final ConcurrentMap<String, KetchLeader> leaders;
+ private final Lock startLock;
+
+ /**
+ * Initialize a new leader cache.
+ *
+ * @param system
+ * system configuration for the leaders
+ */
+ public KetchLeaderCache(KetchSystem system) {
+ this.system = system;
+ leaders = new ConcurrentHashMap<>();
+ startLock = new ReentrantLock(true /* fair */);
+ }
+
+ /**
+ * Lookup the leader instance for a given repository.
+ *
+ * @param repo
+ * repository to get the leader for.
+ * @return the leader instance for the repository.
+ * @throws URISyntaxException
+ * remote configuration contains an invalid URL.
+ */
+ public KetchLeader get(Repository repo)
+ throws URISyntaxException {
+ String key = computeKey(repo);
+ KetchLeader leader = leaders.get(key);
+ if (leader != null) {
+ return leader;
+ }
+ return startLeader(key, repo);
+ }
+
+ private KetchLeader startLeader(String key, Repository repo)
+ throws URISyntaxException {
+ startLock.lock();
+ try {
+ KetchLeader leader = leaders.get(key);
+ if (leader != null) {
+ return leader;
+ }
+ leader = system.createLeader(repo);
+ leaders.put(key, leader);
+ return leader;
+ } finally {
+ startLock.unlock();
+ }
+ }
+
+ private static String computeKey(Repository repo) {
+ if (repo instanceof DfsRepository) {
+ DfsRepository dfs = (DfsRepository) repo;
+ return dfs.getDescription().getRepositoryName();
+ }
+
+ if (repo.getDirectory() != null) {
+ return repo.getDirectory().toURI().toString();
+ }
+
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchPreReceive.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchPreReceive.java
new file mode 100644
index 0000000000..1b4307f3fb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchPreReceive.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.eclipse.jgit.internal.ketch.Proposal.State.EXECUTED;
+import static org.eclipse.jgit.internal.ketch.Proposal.State.QUEUED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.transport.PreReceiveHook;
+import org.eclipse.jgit.transport.ProgressSpinner;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * PreReceiveHook for handling push traffic in a Ketch system.
+ * <p>
+ * Install an instance on {@link ReceivePack} to capture the commands and other
+ * connection state and relay them through the {@link KetchLeader}, allowing the
+ * leader to gain consensus about the new reference state.
+ */
+public class KetchPreReceive implements PreReceiveHook {
+ private static final Logger log = LoggerFactory.getLogger(KetchPreReceive.class);
+
+ private final KetchLeader leader;
+
+ /**
+ * Construct a hook executing updates through a {@link KetchLeader}.
+ *
+ * @param leader
+ * leader for this repository.
+ */
+ public KetchPreReceive(KetchLeader leader) {
+ this.leader = leader;
+ }
+
+ @Override
+ public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> cmds) {
+ cmds = ReceiveCommand.filter(cmds, NOT_ATTEMPTED);
+ if (cmds.isEmpty()) {
+ return;
+ }
+
+ try {
+ Proposal proposal = new Proposal(rp.getRevWalk(), cmds)
+ .setPushCertificate(rp.getPushCertificate())
+ .setAuthor(rp.getRefLogIdent())
+ .setMessage("push"); //$NON-NLS-1$
+ leader.queueProposal(proposal);
+ if (proposal.isDone()) {
+ // This failed fast, e.g. conflict or bad precondition.
+ return;
+ }
+
+ ProgressSpinner spinner = new ProgressSpinner(
+ rp.getMessageOutputStream());
+ if (proposal.getState() == QUEUED) {
+ waitForQueue(proposal, spinner);
+ }
+ if (!proposal.isDone()) {
+ waitForPropose(proposal, spinner);
+ }
+ } catch (IOException | InterruptedException e) {
+ String msg = JGitText.get().transactionAborted;
+ for (ReceiveCommand cmd : cmds) {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ cmd.setResult(REJECTED_OTHER_REASON, msg);
+ }
+ }
+ log.error(msg, e);
+ }
+ }
+
+ private void waitForQueue(Proposal proposal, ProgressSpinner spinner)
+ throws InterruptedException {
+ spinner.beginTask(KetchText.get().waitingForQueue, 1, SECONDS);
+ while (!proposal.awaitStateChange(QUEUED, 250, MILLISECONDS)) {
+ spinner.update();
+ }
+ switch (proposal.getState()) {
+ case RUNNING:
+ default:
+ spinner.endTask(KetchText.get().starting);
+ break;
+
+ case EXECUTED:
+ spinner.endTask(KetchText.get().accepted);
+ break;
+
+ case ABORTED:
+ spinner.endTask(KetchText.get().failed);
+ break;
+ }
+ }
+
+ private void waitForPropose(Proposal proposal, ProgressSpinner spinner)
+ throws InterruptedException {
+ spinner.beginTask(KetchText.get().proposingUpdates, 2, SECONDS);
+ while (!proposal.await(250, MILLISECONDS)) {
+ spinner.update();
+ }
+ spinner.endTask(proposal.getState() == EXECUTED
+ ? KetchText.get().accepted
+ : KetchText.get().failed);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java
new file mode 100644
index 0000000000..a30bbb260a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java
@@ -0,0 +1,755 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitSpeed.BATCHED;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitSpeed.FAST;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.CURRENT;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.LAGGING;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.OFFLINE;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.UNKNOWN;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.reftree.RefTree;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.SystemReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Ketch replica, either {@link LocalReplica} or {@link RemoteGitReplica}.
+ * <p>
+ * Replicas can be either a stock Git replica, or a Ketch-aware replica.
+ * <p>
+ * A stock Git replica has no special knowledge of Ketch and simply stores
+ * objects and references. Ketch communicates with the stock Git replica using
+ * the Git push wire protocol. The {@link KetchLeader} commits an agreed upon
+ * state by pushing all references to the Git replica, for example
+ * {@code "refs/heads/master"} is pushed during commit. Stock Git replicas use
+ * {@link CommitMethod#ALL_REFS} to record the final state.
+ * <p>
+ * Ketch-aware replicas understand the {@code RefTree} sent during the proposal
+ * and during commit are able to update their own reference space to match the
+ * state represented by the {@code RefTree}. Ketch-aware replicas typically use
+ * a {@link org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase} and
+ * {@link CommitMethod#TXN_COMMITTED} to record the final state.
+ * <p>
+ * KetchReplica instances are tightly coupled with a single {@link KetchLeader}.
+ * Some state may be accessed by the leader thread and uses the leader's own
+ * {@link KetchLeader#lock} to protect shared data.
+ */
+public abstract class KetchReplica {
+ static final Logger log = LoggerFactory.getLogger(KetchReplica.class);
+ private static final byte[] PEEL = { ' ', '^' };
+
+ /** Participation of a replica in establishing consensus. */
+ public enum Participation {
+ /** Replica can vote. */
+ FULL,
+
+ /** Replica does not vote, but tracks leader. */
+ FOLLOWER_ONLY;
+ }
+
+ /** How this replica wants to receive Ketch commit operations. */
+ public enum CommitMethod {
+ /** All references are pushed to the peer as standard Git. */
+ ALL_REFS,
+
+ /** Only {@code refs/txn/committed} is written/updated. */
+ TXN_COMMITTED;
+ }
+
+ /** Delay before committing to a replica. */
+ public enum CommitSpeed {
+ /**
+ * Send the commit immediately, even if it could be batched with the
+ * next proposal.
+ */
+ FAST,
+
+ /**
+ * If the next proposal is available, batch the commit with it,
+ * otherwise just send the commit. This generates less network use, but
+ * may provide slower consistency on the replica.
+ */
+ BATCHED;
+ }
+
+ /** Current state of a replica. */
+ public enum State {
+ /** Leader has not yet contacted the replica. */
+ UNKNOWN,
+
+ /** Replica is behind the consensus. */
+ LAGGING,
+
+ /** Replica matches the consensus. */
+ CURRENT,
+
+ /** Replica has a different (or unknown) history. */
+ DIVERGENT,
+
+ /** Replica's history contains the leader's history. */
+ AHEAD,
+
+ /** Replica can not be contacted. */
+ OFFLINE;
+ }
+
+ private final KetchLeader leader;
+ private final String replicaName;
+ private final Participation participation;
+ private final CommitMethod commitMethod;
+ private final CommitSpeed commitSpeed;
+ private final long minRetryMillis;
+ private final long maxRetryMillis;
+ private final Map<ObjectId, List<ReceiveCommand>> staged;
+ private final Map<String, ReceiveCommand> running;
+ private final Map<String, ReceiveCommand> waiting;
+ private final List<ReplicaPushRequest> queued;
+
+ /**
+ * Value known for {@code "refs/txn/accepted"}.
+ * <p>
+ * Raft literature refers to this as {@code matchIndex}.
+ */
+ private ObjectId txnAccepted;
+
+ /**
+ * Value known for {@code "refs/txn/committed"}.
+ * <p>
+ * Raft literature refers to this as {@code commitIndex}. In traditional
+ * Raft this is a state variable inside the follower implementation, but
+ * Ketch keeps it in the leader.
+ */
+ private ObjectId txnCommitted;
+
+ /** What is happening with this replica. */
+ private State state = UNKNOWN;
+ private String error;
+
+ /** Scheduled retry due to communication failure. */
+ private Future<?> retryFuture;
+ private long lastRetryMillis;
+ private long retryAtMillis;
+
+ /**
+ * Configure a replica representation.
+ *
+ * @param leader
+ * instance this replica follows.
+ * @param name
+ * unique-ish name identifying this replica for debugging.
+ * @param cfg
+ * how Ketch should treat the replica.
+ */
+ protected KetchReplica(KetchLeader leader, String name, ReplicaConfig cfg) {
+ this.leader = leader;
+ this.replicaName = name;
+ this.participation = cfg.getParticipation();
+ this.commitMethod = cfg.getCommitMethod();
+ this.commitSpeed = cfg.getCommitSpeed();
+ this.minRetryMillis = cfg.getMinRetry(MILLISECONDS);
+ this.maxRetryMillis = cfg.getMaxRetry(MILLISECONDS);
+ this.staged = new HashMap<>();
+ this.running = new HashMap<>();
+ this.waiting = new HashMap<>();
+ this.queued = new ArrayList<>(4);
+ }
+
+ /** @return system configuration. */
+ public KetchSystem getSystem() {
+ return getLeader().getSystem();
+ }
+
+ /** @return leader instance this replica follows. */
+ public KetchLeader getLeader() {
+ return leader;
+ }
+
+ /** @return unique-ish name for debugging. */
+ public String getName() {
+ return replicaName;
+ }
+
+ /** @return description of this replica for error/debug logging purposes. */
+ protected String describeForLog() {
+ return getName();
+ }
+
+ /** @return how the replica participates in this Ketch system. */
+ public Participation getParticipation() {
+ return participation;
+ }
+
+ /** @return how Ketch will commit to the repository. */
+ public CommitMethod getCommitMethod() {
+ return commitMethod;
+ }
+
+ /** @return when Ketch will commit to the repository. */
+ public CommitSpeed getCommitSpeed() {
+ return commitSpeed;
+ }
+
+ /**
+ * Called by leader to perform graceful shutdown.
+ * <p>
+ * Default implementation cancels any scheduled retry. Subclasses may add
+ * additional logic before or after calling {@code super.shutdown()}.
+ * <p>
+ * Called with {@link KetchLeader#lock} held by caller.
+ */
+ protected void shutdown() {
+ Future<?> f = retryFuture;
+ if (f != null) {
+ retryFuture = null;
+ f.cancel(true);
+ }
+ }
+
+ ReplicaSnapshot snapshot() {
+ ReplicaSnapshot s = new ReplicaSnapshot(this);
+ s.accepted = txnAccepted;
+ s.committed = txnCommitted;
+ s.state = state;
+ s.error = error;
+ s.retryAtMillis = waitingForRetry() ? retryAtMillis : 0;
+ return s;
+ }
+
+ /**
+ * Update the leader's view of the replica after a poll.
+ * <p>
+ * Called with {@link KetchLeader#lock} held by caller.
+ *
+ * @param refs
+ * map of refs from the replica.
+ */
+ void initialize(Map<String, Ref> refs) {
+ if (txnAccepted == null) {
+ txnAccepted = getId(refs.get(getSystem().getTxnAccepted()));
+ }
+ if (txnCommitted == null) {
+ txnCommitted = getId(refs.get(getSystem().getTxnCommitted()));
+ }
+ }
+
+ ObjectId getTxnAccepted() {
+ return txnAccepted;
+ }
+
+ boolean hasAccepted(LogIndex id) {
+ return equals(txnAccepted, id);
+ }
+
+ private static boolean equals(@Nullable ObjectId a, LogIndex b) {
+ return a != null && b != null && AnyObjectId.equals(a, b);
+ }
+
+ /**
+ * Schedule a proposal round with the replica.
+ * <p>
+ * Called with {@link KetchLeader#lock} held by caller.
+ *
+ * @param round
+ * current round being run by the leader.
+ */
+ void pushTxnAcceptedAsync(Round round) {
+ List<ReceiveCommand> cmds = new ArrayList<>();
+ if (commitSpeed == BATCHED) {
+ LogIndex committedIndex = leader.getCommitted();
+ if (equals(txnAccepted, committedIndex)
+ && !equals(txnCommitted, committedIndex)) {
+ prepareTxnCommitted(cmds, committedIndex);
+ }
+ }
+
+ // TODO(sop) Lagging replicas should build accept on the fly.
+ if (round.stageCommands != null) {
+ for (ReceiveCommand cmd : round.stageCommands) {
+ // TODO(sop): Do not send certain object graphs to replica.
+ cmds.add(copy(cmd));
+ }
+ }
+ cmds.add(new ReceiveCommand(
+ round.acceptedOldIndex, round.acceptedNewIndex,
+ getSystem().getTxnAccepted()));
+ pushAsync(new ReplicaPushRequest(this, cmds));
+ }
+
+ private static ReceiveCommand copy(ReceiveCommand c) {
+ return new ReceiveCommand(c.getOldId(), c.getNewId(), c.getRefName());
+ }
+
+ boolean shouldPushUnbatchedCommit(LogIndex committed, boolean leaderIdle) {
+ return (leaderIdle || commitSpeed == FAST) && hasAccepted(committed);
+ }
+
+ void pushCommitAsync(LogIndex committed) {
+ List<ReceiveCommand> cmds = new ArrayList<>();
+ prepareTxnCommitted(cmds, committed);
+ pushAsync(new ReplicaPushRequest(this, cmds));
+ }
+
+ private void prepareTxnCommitted(List<ReceiveCommand> cmds,
+ ObjectId committed) {
+ removeStaged(cmds, committed);
+ cmds.add(new ReceiveCommand(
+ txnCommitted, committed,
+ getSystem().getTxnCommitted()));
+ }
+
+ private void removeStaged(List<ReceiveCommand> cmds, ObjectId committed) {
+ List<ReceiveCommand> a = staged.remove(committed);
+ if (a != null) {
+ delete(cmds, a);
+ }
+ if (staged.isEmpty() || !(committed instanceof LogIndex)) {
+ return;
+ }
+
+ LogIndex committedIndex = (LogIndex) committed;
+ Iterator<Map.Entry<ObjectId, List<ReceiveCommand>>> itr = staged
+ .entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry<ObjectId, List<ReceiveCommand>> e = itr.next();
+ if (e.getKey() instanceof LogIndex) {
+ LogIndex stagedIndex = (LogIndex) e.getKey();
+ if (stagedIndex.isBefore(committedIndex)) {
+ delete(cmds, e.getValue());
+ itr.remove();
+ }
+ }
+ }
+ }
+
+ private static void delete(List<ReceiveCommand> cmds,
+ List<ReceiveCommand> createCmds) {
+ for (ReceiveCommand cmd : createCmds) {
+ ObjectId id = cmd.getNewId();
+ String name = cmd.getRefName();
+ cmds.add(new ReceiveCommand(id, ObjectId.zeroId(), name));
+ }
+ }
+
+ /**
+ * Determine the next push for this replica (if any) and start it.
+ * <p>
+ * If the replica has successfully accepted the committed state of the
+ * leader, this method will push all references to the replica using the
+ * configured {@link CommitMethod}.
+ * <p>
+ * If the replica is {@link State#LAGGING} this method will begin catch up
+ * by sending a more recent {@code refs/txn/accepted}.
+ * <p>
+ * Must be invoked with {@link KetchLeader#lock} held by caller.
+ */
+ private void runNextPushRequest() {
+ LogIndex committed = leader.getCommitted();
+ if (!equals(txnCommitted, committed)
+ && shouldPushUnbatchedCommit(committed, leader.isIdle())) {
+ pushCommitAsync(committed);
+ }
+
+ if (queued.isEmpty() || !running.isEmpty() || waitingForRetry()) {
+ return;
+ }
+
+ // Collapse all queued requests into a single request.
+ Map<String, ReceiveCommand> cmdMap = new HashMap<>();
+ for (ReplicaPushRequest req : queued) {
+ for (ReceiveCommand cmd : req.getCommands()) {
+ String name = cmd.getRefName();
+ ReceiveCommand old = cmdMap.remove(name);
+ if (old != null) {
+ cmd = new ReceiveCommand(
+ old.getOldId(), cmd.getNewId(),
+ name);
+ }
+ cmdMap.put(name, cmd);
+ }
+ }
+ queued.clear();
+ waiting.clear();
+
+ List<ReceiveCommand> next = new ArrayList<>(cmdMap.values());
+ for (ReceiveCommand cmd : next) {
+ running.put(cmd.getRefName(), cmd);
+ }
+ startPush(new ReplicaPushRequest(this, next));
+ }
+
+ private void pushAsync(ReplicaPushRequest req) {
+ if (defer(req)) {
+ // TODO(sop) Collapse during long retry outage.
+ for (ReceiveCommand cmd : req.getCommands()) {
+ waiting.put(cmd.getRefName(), cmd);
+ }
+ queued.add(req);
+ } else {
+ for (ReceiveCommand cmd : req.getCommands()) {
+ running.put(cmd.getRefName(), cmd);
+ }
+ startPush(req);
+ }
+ }
+
+ private boolean defer(ReplicaPushRequest req) {
+ if (waitingForRetry()) {
+ // Prior communication failure; everything is deferred.
+ return true;
+ }
+
+ for (ReceiveCommand nextCmd : req.getCommands()) {
+ ReceiveCommand priorCmd = waiting.get(nextCmd.getRefName());
+ if (priorCmd == null) {
+ priorCmd = running.get(nextCmd.getRefName());
+ }
+ if (priorCmd != null) {
+ // Another request pending on same ref; that must go first.
+ // Verify priorCmd.newId == nextCmd.oldId?
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean waitingForRetry() {
+ Future<?> f = retryFuture;
+ return f != null && !f.isDone();
+ }
+
+ private void retryLater(ReplicaPushRequest req) {
+ Collection<ReceiveCommand> cmds = req.getCommands();
+ for (ReceiveCommand cmd : cmds) {
+ cmd.setResult(NOT_ATTEMPTED, null);
+ if (!waiting.containsKey(cmd.getRefName())) {
+ waiting.put(cmd.getRefName(), cmd);
+ }
+ }
+ queued.add(0, new ReplicaPushRequest(this, cmds));
+
+ if (!waitingForRetry()) {
+ long delay = KetchSystem.delay(
+ lastRetryMillis,
+ minRetryMillis, maxRetryMillis);
+ if (log.isDebugEnabled()) {
+ log.debug("Retrying {} after {} ms", //$NON-NLS-1$
+ describeForLog(), Long.valueOf(delay));
+ }
+ lastRetryMillis = delay;
+ retryAtMillis = SystemReader.getInstance().getCurrentTime() + delay;
+ retryFuture = getSystem().getExecutor()
+ .schedule(new WeakRetryPush(this), delay, MILLISECONDS);
+ }
+ }
+
+ /** Weakly holds a retrying replica, allowing it to garbage collect. */
+ static class WeakRetryPush extends WeakReference<KetchReplica>
+ implements Callable<Void> {
+ WeakRetryPush(KetchReplica r) {
+ super(r);
+ }
+
+ @Override
+ public Void call() throws Exception {
+ KetchReplica r = get();
+ if (r != null) {
+ r.doRetryPush();
+ }
+ return null;
+ }
+ }
+
+ private void doRetryPush() {
+ leader.lock.lock();
+ try {
+ retryFuture = null;
+ runNextPushRequest();
+ } finally {
+ leader.lock.unlock();
+ }
+ }
+
+ /**
+ * Begin executing a single push.
+ * <p>
+ * This method must move processing onto another thread.
+ * Called with {@link KetchLeader#lock} held by caller.
+ *
+ * @param req
+ * the request to send to the replica.
+ */
+ protected abstract void startPush(ReplicaPushRequest req);
+
+ /**
+ * Callback from {@link ReplicaPushRequest} upon success or failure.
+ * <p>
+ * Acquires the {@link KetchLeader#lock} and updates the leader's internal
+ * knowledge about this replica to reflect what has been learned during a
+ * push to the replica. In some cases of divergence this method may take
+ * some time to determine how the replica has diverged; to reduce contention
+ * this is evaluated before acquiring the leader lock.
+ *
+ * @param repo
+ * local repository instance used by the push thread.
+ * @param req
+ * push request just attempted.
+ */
+ void afterPush(@Nullable Repository repo, ReplicaPushRequest req) {
+ ReceiveCommand acceptCmd = null;
+ ReceiveCommand commitCmd = null;
+ List<ReceiveCommand> stages = null;
+
+ for (ReceiveCommand cmd : req.getCommands()) {
+ String name = cmd.getRefName();
+ if (name.equals(getSystem().getTxnAccepted())) {
+ acceptCmd = cmd;
+ } else if (name.equals(getSystem().getTxnCommitted())) {
+ commitCmd = cmd;
+ } else if (cmd.getResult() == OK && cmd.getType() == CREATE
+ && name.startsWith(getSystem().getTxnStage())) {
+ if (stages == null) {
+ stages = new ArrayList<>();
+ }
+ stages.add(cmd);
+ }
+ }
+
+ State newState = null;
+ ObjectId acceptId = readId(req, acceptCmd);
+ if (repo != null && acceptCmd != null && acceptCmd.getResult() != OK
+ && req.getException() == null) {
+ try (LagCheck lag = new LagCheck(this, repo)) {
+ newState = lag.check(acceptId, acceptCmd);
+ acceptId = lag.getRemoteId();
+ }
+ }
+
+ leader.lock.lock();
+ try {
+ for (ReceiveCommand cmd : req.getCommands()) {
+ running.remove(cmd.getRefName());
+ }
+
+ Throwable err = req.getException();
+ if (err != null) {
+ state = OFFLINE;
+ error = err.toString();
+ retryLater(req);
+ leader.onReplicaUpdate(this);
+ return;
+ }
+
+ lastRetryMillis = 0;
+ error = null;
+ updateView(req, acceptId, commitCmd);
+
+ if (acceptCmd != null && acceptCmd.getResult() == OK) {
+ state = hasAccepted(leader.getHead()) ? CURRENT : LAGGING;
+ if (stages != null) {
+ staged.put(acceptCmd.getNewId(), stages);
+ }
+ } else if (newState != null) {
+ state = newState;
+ }
+
+ leader.onReplicaUpdate(this);
+ runNextPushRequest();
+ } finally {
+ leader.lock.unlock();
+ }
+ }
+
+ private void updateView(ReplicaPushRequest req, @Nullable ObjectId acceptId,
+ ReceiveCommand commitCmd) {
+ if (acceptId != null) {
+ txnAccepted = acceptId;
+ }
+
+ ObjectId committed = readId(req, commitCmd);
+ if (committed != null) {
+ txnCommitted = committed;
+ } else if (acceptId != null && txnCommitted == null) {
+ // Initialize during first conversation.
+ Map<String, Ref> adv = req.getRefs();
+ if (adv != null) {
+ Ref refs = adv.get(getSystem().getTxnCommitted());
+ txnCommitted = getId(refs);
+ }
+ }
+ }
+
+ @Nullable
+ private static ObjectId readId(ReplicaPushRequest req,
+ @Nullable ReceiveCommand cmd) {
+ if (cmd == null) {
+ // Ref was not in the command list, do not trust advertisement.
+ return null;
+
+ } else if (cmd.getResult() == OK) {
+ // Currently at newId.
+ return cmd.getNewId();
+ }
+
+ Map<String, Ref> refs = req.getRefs();
+ return refs != null ? getId(refs.get(cmd.getRefName())) : null;
+ }
+
+ /**
+ * Fetch objects from the remote using the calling thread.
+ * <p>
+ * Called without {@link KetchLeader#lock}.
+ *
+ * @param repo
+ * local repository to fetch objects into.
+ * @param req
+ * the request to fetch from a replica.
+ * @throws IOException
+ * communication with the replica was not possible.
+ */
+ protected abstract void blockingFetch(Repository repo,
+ ReplicaFetchRequest req) throws IOException;
+
+ /**
+ * Build a list of commands to commit {@link CommitMethod#ALL_REFS}.
+ *
+ * @param git
+ * local leader repository to read committed state from.
+ * @param current
+ * all known references in the replica's repository. Typically
+ * this comes from a push advertisement.
+ * @param committed
+ * state being pushed to {@code refs/txn/committed}.
+ * @return commands to update during commit.
+ * @throws IOException
+ * cannot read the committed state.
+ */
+ protected Collection<ReceiveCommand> prepareCommit(Repository git,
+ Map<String, Ref> current, ObjectId committed) throws IOException {
+ List<ReceiveCommand> delta = new ArrayList<>();
+ Map<String, Ref> remote = new HashMap<>(current);
+ try (RevWalk rw = new RevWalk(git);
+ TreeWalk tw = new TreeWalk(rw.getObjectReader())) {
+ tw.setRecursive(true);
+ tw.addTree(rw.parseCommit(committed).getTree());
+ while (tw.next()) {
+ if (tw.getRawMode(0) != TYPE_GITLINK
+ || tw.isPathSuffix(PEEL, 2)) {
+ // Symbolic references cannot be pushed.
+ // Caching peeled values is handled remotely.
+ continue;
+ }
+
+ // TODO(sop) Do not send certain ref names to replica.
+ String name = RefTree.refName(tw.getPathString());
+ Ref oldRef = remote.remove(name);
+ ObjectId oldId = getId(oldRef);
+ ObjectId newId = tw.getObjectId(0);
+ if (!AnyObjectId.equals(oldId, newId)) {
+ delta.add(new ReceiveCommand(oldId, newId, name));
+ }
+ }
+ }
+
+ // Delete any extra references not in the committed state.
+ for (Ref ref : remote.values()) {
+ if (canDelete(ref)) {
+ delta.add(new ReceiveCommand(
+ ref.getObjectId(), ObjectId.zeroId(),
+ ref.getName()));
+ }
+ }
+ return delta;
+ }
+
+ boolean canDelete(Ref ref) {
+ String name = ref.getName();
+ if (HEAD.equals(name)) {
+ return false;
+ }
+ if (name.startsWith(getSystem().getTxnNamespace())) {
+ return false;
+ }
+ // TODO(sop) Do not delete precious names from replica.
+ return true;
+ }
+
+ @NonNull
+ static ObjectId getId(@Nullable Ref ref) {
+ if (ref != null) {
+ ObjectId id = ref.getObjectId();
+ if (id != null) {
+ return id;
+ }
+ }
+ return ObjectId.zeroId();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java
new file mode 100644
index 0000000000..71e872e3fa
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.KetchConstants.ACCEPTED;
+import static org.eclipse.jgit.internal.ketch.KetchConstants.COMMITTED;
+import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_KEY_TYPE;
+import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_SECTION_KETCH;
+import static org.eclipse.jgit.internal.ketch.KetchConstants.DEFAULT_TXN_NAMESPACE;
+import static org.eclipse.jgit.internal.ketch.KetchConstants.STAGE;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_NAME;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REMOTE;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Ketch system-wide configuration.
+ * <p>
+ * This class provides useful defaults for testing and small proof of concepts.
+ * Full scale installations are expected to subclass and override methods to
+ * provide consistent configuration across all managed repositories.
+ * <p>
+ * Servers should configure their own {@link ScheduledExecutorService}.
+ */
+public class KetchSystem {
+ private static final Random RNG = new Random();
+
+ /** @return default executor, one thread per available processor. */
+ public static ScheduledExecutorService defaultExecutor() {
+ return DefaultExecutorHolder.I;
+ }
+
+ private final ScheduledExecutorService executor;
+ private final String txnNamespace;
+ private final String txnAccepted;
+ private final String txnCommitted;
+ private final String txnStage;
+
+ /** Create a default system with a thread pool of 1 thread per CPU. */
+ public KetchSystem() {
+ this(defaultExecutor(), DEFAULT_TXN_NAMESPACE);
+ }
+
+ /**
+ * Create a Ketch system with the provided executor service.
+ *
+ * @param executor
+ * thread pool to run background operations.
+ * @param txnNamespace
+ * reference namespace for the RefTree graph and associated
+ * transaction state. Must begin with {@code "refs/"} and end
+ * with {@code '/'}, for example {@code "refs/txn/"}.
+ */
+ public KetchSystem(ScheduledExecutorService executor, String txnNamespace) {
+ this.executor = executor;
+ this.txnNamespace = txnNamespace;
+ this.txnAccepted = txnNamespace + ACCEPTED;
+ this.txnCommitted = txnNamespace + COMMITTED;
+ this.txnStage = txnNamespace + STAGE;
+ }
+
+ /** @return executor to perform background operations. */
+ public ScheduledExecutorService getExecutor() {
+ return executor;
+ }
+
+ /**
+ * Get the namespace used for the RefTree graph and transaction management.
+ *
+ * @return reference namespace such as {@code "refs/txn/"}.
+ */
+ public String getTxnNamespace() {
+ return txnNamespace;
+ }
+
+ /** @return name of the accepted RefTree graph. */
+ public String getTxnAccepted() {
+ return txnAccepted;
+ }
+
+ /** @return name of the committed RefTree graph. */
+ public String getTxnCommitted() {
+ return txnCommitted;
+ }
+
+ /** @return prefix for staged objects, e.g. {@code "refs/txn/stage/"}. */
+ public String getTxnStage() {
+ return txnStage;
+ }
+
+ /** @return identity line for the committer header of a RefTreeGraph. */
+ public PersonIdent newCommitter() {
+ String name = "ketch"; //$NON-NLS-1$
+ String email = "ketch@system"; //$NON-NLS-1$
+ return new PersonIdent(name, email);
+ }
+
+ /**
+ * Construct a random tag to identify a candidate during leader election.
+ * <p>
+ * Multiple processes trying to elect themselves leaders at exactly the same
+ * time (rounded to seconds) using the same {@link #newCommitter()} identity
+ * strings, for the same term, may generate the same ObjectId for the
+ * election commit and falsely assume they have both won.
+ * <p>
+ * Candidates add this tag to their election ballot commit to disambiguate
+ * the election. The tag only needs to be unique for a given triplet of
+ * {@link #newCommitter()}, system time (rounded to seconds), and term. If
+ * every replica in the system uses a unique {@code newCommitter} (such as
+ * including the host name after the {@code "@"} in the email address) the
+ * tag could be the empty string.
+ * <p>
+ * The default implementation generates a few bytes of random data.
+ *
+ * @return unique tag; null or empty string if {@code newCommitter()} is
+ * sufficiently unique to identify the leader.
+ */
+ @Nullable
+ public String newLeaderTag() {
+ int n = RNG.nextInt(1 << (6 * 4));
+ return String.format("%06x", Integer.valueOf(n)); //$NON-NLS-1$
+ }
+
+ /**
+ * Construct the KetchLeader instance of a repository.
+ *
+ * @param repo
+ * local repository stored by the leader.
+ * @return leader instance.
+ * @throws URISyntaxException
+ * a follower configuration contains an unsupported URI.
+ */
+ public KetchLeader createLeader(final Repository repo)
+ throws URISyntaxException {
+ KetchLeader leader = new KetchLeader(this) {
+ @Override
+ protected Repository openRepository() {
+ repo.incrementOpen();
+ return repo;
+ }
+ };
+ leader.setReplicas(createReplicas(leader, repo));
+ return leader;
+ }
+
+ /**
+ * Get the collection of replicas for a repository.
+ * <p>
+ * The collection of replicas must include the local repository.
+ *
+ * @param leader
+ * the leader driving these replicas.
+ * @param repo
+ * repository to get the replicas of.
+ * @return collection of replicas for the specified repository.
+ * @throws URISyntaxException
+ * a configured URI is invalid.
+ */
+ protected List<KetchReplica> createReplicas(KetchLeader leader,
+ Repository repo) throws URISyntaxException {
+ List<KetchReplica> replicas = new ArrayList<>();
+ Config cfg = repo.getConfig();
+ String localName = getLocalName(cfg);
+ for (String name : cfg.getSubsections(CONFIG_KEY_REMOTE)) {
+ if (!hasParticipation(cfg, name)) {
+ continue;
+ }
+
+ ReplicaConfig kc = ReplicaConfig.newFromConfig(cfg, name);
+ if (name.equals(localName)) {
+ replicas.add(new LocalReplica(leader, name, kc));
+ continue;
+ }
+
+ RemoteConfig rc = new RemoteConfig(cfg, name);
+ List<URIish> uris = rc.getPushURIs();
+ if (uris.isEmpty()) {
+ uris = rc.getURIs();
+ }
+ for (URIish uri : uris) {
+ String n = uris.size() == 1 ? name : uri.getHost();
+ replicas.add(new RemoteGitReplica(leader, n, uri, kc, rc));
+ }
+ }
+ return replicas;
+ }
+
+ private static boolean hasParticipation(Config cfg, String name) {
+ return cfg.getString(CONFIG_KEY_REMOTE, name, CONFIG_KEY_TYPE) != null;
+ }
+
+ private static String getLocalName(Config cfg) {
+ return cfg.getString(CONFIG_SECTION_KETCH, null, CONFIG_KEY_NAME);
+ }
+
+ static class DefaultExecutorHolder {
+ private static final Logger log = LoggerFactory.getLogger(KetchSystem.class);
+ static final ScheduledExecutorService I = create();
+
+ private static ScheduledExecutorService create() {
+ int cores = Runtime.getRuntime().availableProcessors();
+ int threads = Math.max(5, cores);
+ log.info("Using {} threads", Integer.valueOf(threads)); //$NON-NLS-1$
+ return Executors.newScheduledThreadPool(
+ threads,
+ new ThreadFactory() {
+ private final AtomicInteger threadCnt = new AtomicInteger();
+
+ @Override
+ public Thread newThread(Runnable r) {
+ int id = threadCnt.incrementAndGet();
+ Thread thr = new Thread(r);
+ thr.setName("KetchExecutor-" + id); //$NON-NLS-1$
+ return thr;
+ }
+ });
+ }
+
+ private DefaultExecutorHolder() {
+ }
+ }
+
+ /**
+ * Compute a delay in a {@code min..max} interval with random jitter.
+ *
+ * @param last
+ * amount of delay waited before the last attempt. This is used
+ * to seed the next delay interval. Should be 0 if there was no
+ * prior delay.
+ * @param min
+ * shortest amount of allowable delay between attempts.
+ * @param max
+ * longest amount of allowable delay between attempts.
+ * @return new amount of delay to wait before the next attempt.
+ */
+ static long delay(long last, long min, long max) {
+ long r = Math.max(0, last * 3 - min);
+ if (r > 0) {
+ int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
+ r = RNG.nextInt(c);
+ }
+ return Math.max(Math.min(min + r, max), min);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchText.java
index c7e41bce04..b6c3bc92c5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchText.java
@@ -1,6 +1,5 @@
/*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2016, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -42,44 +41,30 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.internal.ketch;
-/**
- * A tree entry representing a symbolic link.
- *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
+import org.eclipse.jgit.nls.NLS;
+import org.eclipse.jgit.nls.TranslationBundle;
- /**
- * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
- * the specified parent
- *
- * @param parent
- * @param id
- * @param nameUTF8
- */
- public SymlinkTreeEntry(final Tree parent, final ObjectId id,
- final byte[] nameUTF8) {
- super(parent, id, nameUTF8);
+/** Translation bundle for the Ketch implementation. */
+public class KetchText extends TranslationBundle {
+ /** @return instance of this translation bundle. */
+ public static KetchText get() {
+ return NLS.getBundleFor(KetchText.class);
}
- public FileMode getMode() {
- return FileMode.SYMLINK;
- }
-
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(getId()));
- r.append(" S "); //$NON-NLS-1$
- r.append(getFullName());
- return r.toString();
- }
+ // @formatter:off
+ /***/ public String accepted;
+ /***/ public String cannotFetchFromLocalReplica;
+ /***/ public String failed;
+ /***/ public String invalidFollowerUri;
+ /***/ public String leaderFailedToStore;
+ /***/ public String localReplicaRequired;
+ /***/ public String mismatchedTxnNamespace;
+ /***/ public String outsideTxnNamespace;
+ /***/ public String proposingUpdates;
+ /***/ public String queuedProposalFailedToApply;
+ /***/ public String starting;
+ /***/ public String unsupportedVoterCount;
+ /***/ public String waitingForQueue;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
new file mode 100644
index 0000000000..35327ea0b3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.AHEAD;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.DIVERGENT;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.LAGGING;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.UNKNOWN;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * A helper to check if a {@link KetchReplica} is ahead or behind the leader.
+ */
+class LagCheck implements AutoCloseable {
+ private final KetchReplica replica;
+ private final Repository repo;
+ private RevWalk rw;
+ private ObjectId remoteId;
+
+ LagCheck(KetchReplica replica, Repository repo) {
+ this.replica = replica;
+ this.repo = repo;
+ initRevWalk();
+ }
+
+ private void initRevWalk() {
+ if (rw != null) {
+ rw.close();
+ }
+
+ rw = new RevWalk(repo);
+ rw.setRetainBody(false);
+ }
+
+ public void close() {
+ if (rw != null) {
+ rw.close();
+ rw = null;
+ }
+ }
+
+ ObjectId getRemoteId() {
+ return remoteId;
+ }
+
+ KetchReplica.State check(ObjectId acceptId, ReceiveCommand acceptCmd) {
+ remoteId = acceptId;
+ if (remoteId == null) {
+ // Nothing advertised by the replica, value is unknown.
+ return UNKNOWN;
+ }
+
+ if (AnyObjectId.equals(remoteId, ObjectId.zeroId())) {
+ // Replica does not have the txnAccepted reference.
+ return LAGGING;
+ }
+
+ try {
+ RevCommit remote;
+ try {
+ remote = parseRemoteCommit(acceptCmd.getRefName());
+ } catch (RefGoneException gone) {
+ // Replica does not have the txnAccepted reference.
+ return LAGGING;
+ } catch (MissingObjectException notFound) {
+ // Local repository does not know this commit so it cannot
+ // be including the replica's log.
+ return DIVERGENT;
+ }
+
+ RevCommit head = rw.parseCommit(acceptCmd.getNewId());
+ if (rw.isMergedInto(remote, head)) {
+ return LAGGING;
+ }
+
+ // TODO(sop) Check term to see if my leader was deposed.
+ if (rw.isMergedInto(head, remote)) {
+ return AHEAD;
+ } else {
+ return DIVERGENT;
+ }
+ } catch (IOException err) {
+ KetchReplica.log.error(String.format(
+ "Cannot compare %s", //$NON-NLS-1$
+ acceptCmd.getRefName()), err);
+ return UNKNOWN;
+ }
+ }
+
+ private RevCommit parseRemoteCommit(String refName)
+ throws IOException, MissingObjectException, RefGoneException {
+ try {
+ return rw.parseCommit(remoteId);
+ } catch (MissingObjectException notLocal) {
+ // Fall through and try to acquire the object by fetching it.
+ }
+
+ ReplicaFetchRequest fetch = new ReplicaFetchRequest(
+ Collections.singleton(refName),
+ Collections.<ObjectId> emptySet());
+ try {
+ replica.blockingFetch(repo, fetch);
+ } catch (IOException fetchErr) {
+ KetchReplica.log.error(String.format(
+ "Cannot fetch %s (%s) from %s", //$NON-NLS-1$
+ remoteId.abbreviate(8).name(), refName,
+ replica.describeForLog()), fetchErr);
+ throw new MissingObjectException(remoteId, OBJ_COMMIT);
+ }
+
+ Map<String, Ref> adv = fetch.getRefs();
+ if (adv == null) {
+ throw new MissingObjectException(remoteId, OBJ_COMMIT);
+ }
+
+ Ref ref = adv.get(refName);
+ if (ref == null || ref.getObjectId() == null) {
+ throw new RefGoneException();
+ }
+
+ initRevWalk();
+ remoteId = ref.getObjectId();
+ return rw.parseCommit(remoteId);
+ }
+
+ private static class RefGoneException extends Exception {
+ private static final long serialVersionUID = 1L;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LeaderSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LeaderSnapshot.java
new file mode 100644
index 0000000000..28a49df97a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LeaderSnapshot.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.KetchReplica.State.OFFLINE;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+
+/** A snapshot of a leader and its view of the world. */
+public class LeaderSnapshot {
+ final List<ReplicaSnapshot> replicas = new ArrayList<>();
+ KetchLeader.State state;
+ long term;
+ LogIndex headIndex;
+ LogIndex committedIndex;
+ boolean idle;
+
+ LeaderSnapshot() {
+ }
+
+ /** @return unmodifiable view of configured replicas. */
+ public Collection<ReplicaSnapshot> getReplicas() {
+ return Collections.unmodifiableList(replicas);
+ }
+
+ /** @return current state of the leader. */
+ public KetchLeader.State getState() {
+ return state;
+ }
+
+ /**
+ * @return {@code true} if the leader is not running a round to reach
+ * consensus, and has no rounds queued.
+ */
+ public boolean isIdle() {
+ return idle;
+ }
+
+ /**
+ * @return term of this leader. Valid only if {@link #getState()} is
+ * currently {@link KetchLeader.State#LEADER}.
+ */
+ public long getTerm() {
+ return term;
+ }
+
+ /**
+ * @return end of the leader's log; null if leader hasn't started up enough
+ * to begin its own election.
+ */
+ @Nullable
+ public LogIndex getHead() {
+ return headIndex;
+ }
+
+ /**
+ * @return state the leader knows is committed on a majority of participant
+ * replicas. Null until the leader instance has committed a log
+ * index within its own term.
+ */
+ @Nullable
+ public LogIndex getCommitted() {
+ return committedIndex;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ s.append(isIdle() ? "IDLE" : "RUNNING"); //$NON-NLS-1$ //$NON-NLS-2$
+ s.append(" state ").append(getState()); //$NON-NLS-1$
+ if (getTerm() > 0) {
+ s.append(" term ").append(getTerm()); //$NON-NLS-1$
+ }
+ s.append('\n');
+ s.append(String.format(
+ "%-10s %12s %12s\n", //$NON-NLS-1$
+ "Replica", "Accepted", "Committed")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ s.append("------------------------------------\n"); //$NON-NLS-1$
+ debug(s, "(leader)", getHead(), getCommitted()); //$NON-NLS-1$
+ s.append('\n');
+ for (ReplicaSnapshot r : getReplicas()) {
+ debug(s, r);
+ s.append('\n');
+ }
+ s.append('\n');
+ return s.toString();
+ }
+
+ private static void debug(StringBuilder b, ReplicaSnapshot s) {
+ KetchReplica replica = s.getReplica();
+ debug(b, replica.getName(), s.getAccepted(), s.getCommitted());
+ b.append(String.format(" %-8s %s", //$NON-NLS-1$
+ replica.getParticipation(), s.getState()));
+ if (s.getState() == OFFLINE) {
+ String err = s.getErrorMessage();
+ if (err != null) {
+ b.append(" (").append(err).append(')'); //$NON-NLS-1$
+ }
+ }
+ }
+
+ private static void debug(StringBuilder s, String name,
+ ObjectId accepted, ObjectId committed) {
+ s.append(String.format(
+ "%-10s %-12s %-12s", //$NON-NLS-1$
+ name, str(accepted), str(committed)));
+ }
+
+ static String str(ObjectId c) {
+ if (c instanceof LogIndex) {
+ return ((LogIndex) c).describeForLog();
+ } else if (c != null) {
+ return c.abbreviate(8).name();
+ }
+ return "-"; //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java
new file mode 100644
index 0000000000..e297bca45e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod.ALL_REFS;
+import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod.TXN_COMMITTED;
+import static org.eclipse.jgit.lib.RefDatabase.ALL;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/** Ketch replica running on the same system as the {@link KetchLeader}. */
+public class LocalReplica extends KetchReplica {
+ /**
+ * Configure a local replica.
+ *
+ * @param leader
+ * instance this replica follows.
+ * @param name
+ * unique-ish name identifying this replica for debugging.
+ * @param cfg
+ * how Ketch should treat the local system.
+ */
+ public LocalReplica(KetchLeader leader, String name, ReplicaConfig cfg) {
+ super(leader, name, cfg);
+ }
+
+ @Override
+ protected String describeForLog() {
+ return String.format("%s (leader)", getName()); //$NON-NLS-1$
+ }
+
+ /**
+ * Initializes local replica by reading accepted and committed references.
+ * <p>
+ * Loads accepted and committed references from the reference database of
+ * the local replica and stores their current ObjectIds in memory.
+ *
+ * @param repo
+ * repository to initialize state from.
+ * @throws IOException
+ * cannot read repository state.
+ */
+ void initialize(Repository repo) throws IOException {
+ RefDatabase refdb = repo.getRefDatabase();
+ if (refdb instanceof RefTreeDatabase) {
+ RefTreeDatabase treeDb = (RefTreeDatabase) refdb;
+ String txnNamespace = getSystem().getTxnNamespace();
+ if (!txnNamespace.equals(treeDb.getTxnNamespace())) {
+ throw new IOException(MessageFormat.format(
+ KetchText.get().mismatchedTxnNamespace,
+ txnNamespace, treeDb.getTxnNamespace()));
+ }
+ refdb = treeDb.getBootstrap();
+ }
+ initialize(refdb.exactRef(
+ getSystem().getTxnAccepted(),
+ getSystem().getTxnCommitted()));
+ }
+
+ @Override
+ protected void startPush(final ReplicaPushRequest req) {
+ getSystem().getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ try (Repository git = getLeader().openRepository()) {
+ try {
+ update(git, req);
+ req.done(git);
+ } catch (Throwable err) {
+ req.setException(git, err);
+ }
+ } catch (IOException err) {
+ req.setException(null, err);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void blockingFetch(Repository repo, ReplicaFetchRequest req)
+ throws IOException {
+ throw new IOException(KetchText.get().cannotFetchFromLocalReplica);
+ }
+
+ private void update(Repository git, ReplicaPushRequest req)
+ throws IOException {
+ RefDatabase refdb = git.getRefDatabase();
+ CommitMethod method = getCommitMethod();
+
+ // Local replica probably uses RefTreeDatabase, the request should
+ // be only for the txnNamespace, so drop to the bootstrap layer.
+ if (refdb instanceof RefTreeDatabase) {
+ if (!isOnlyTxnNamespace(req.getCommands())) {
+ return;
+ }
+
+ refdb = ((RefTreeDatabase) refdb).getBootstrap();
+ method = TXN_COMMITTED;
+ }
+
+ BatchRefUpdate batch = refdb.newBatchUpdate();
+ batch.setRefLogIdent(getSystem().newCommitter());
+ batch.setRefLogMessage("ketch", false); //$NON-NLS-1$
+ batch.setAllowNonFastForwards(true);
+
+ // RefDirectory updates multiple references sequentially.
+ // Run everything else first, then accepted (if present),
+ // then committed (if present). This ensures an earlier
+ // failure will not update these critical references.
+ ReceiveCommand accepted = null;
+ ReceiveCommand committed = null;
+ for (ReceiveCommand cmd : req.getCommands()) {
+ String name = cmd.getRefName();
+ if (name.equals(getSystem().getTxnAccepted())) {
+ accepted = cmd;
+ } else if (name.equals(getSystem().getTxnCommitted())) {
+ committed = cmd;
+ } else {
+ batch.addCommand(cmd);
+ }
+ }
+ if (committed != null && method == ALL_REFS) {
+ Map<String, Ref> refs = refdb.getRefs(ALL);
+ batch.addCommand(prepareCommit(git, refs, committed.getNewId()));
+ }
+ if (accepted != null) {
+ batch.addCommand(accepted);
+ }
+ if (committed != null) {
+ batch.addCommand(committed);
+ }
+
+ try (RevWalk rw = new RevWalk(git)) {
+ batch.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+
+ // KetchReplica only cares about accepted and committed in
+ // advertisement. If they failed, store the current values
+ // back in the ReplicaPushRequest.
+ List<String> failed = new ArrayList<>(2);
+ checkFailed(failed, accepted);
+ checkFailed(failed, committed);
+ if (!failed.isEmpty()) {
+ String[] arr = failed.toArray(new String[failed.size()]);
+ req.setRefs(refdb.exactRef(arr));
+ }
+ }
+
+ private static void checkFailed(List<String> failed, ReceiveCommand cmd) {
+ if (cmd != null && cmd.getResult() != OK) {
+ failed.add(cmd.getRefName());
+ }
+ }
+
+ private boolean isOnlyTxnNamespace(Collection<ReceiveCommand> cmdList) {
+ // Be paranoid and reject non txnNamespace names, this
+ // is a programming error in Ketch that should not occur.
+
+ String txnNamespace = getSystem().getTxnNamespace();
+ for (ReceiveCommand cmd : cmdList) {
+ if (!cmd.getRefName().startsWith(txnNamespace)) {
+ cmd.setResult(REJECTED_OTHER_REASON,
+ MessageFormat.format(
+ KetchText.get().outsideTxnNamespace,
+ cmd.getRefName(), txnNamespace));
+ ReceiveCommand.abort(cmdList);
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LogIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LogIndex.java
new file mode 100644
index 0000000000..350c8ed62e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LogIndex.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * An ObjectId for a commit extended with incrementing log index.
+ * <p>
+ * For any two LogIndex instances, {@code A} is an ancestor of {@code C}
+ * reachable through parent edges in the graph if {@code A.index < C.index}.
+ * LogIndex provides a performance optimization for Ketch, the same information
+ * can be obtained from {@link org.eclipse.jgit.revwalk.RevWalk}.
+ * <p>
+ * Index values are only valid within a single {@link KetchLeader} instance
+ * after it has won an election. By restricting scope to a single leader new
+ * leaders do not need to traverse the entire history to determine the next
+ * {@code index} for new proposals. This differs from Raft, where leader
+ * election uses the log index and the term number to determine which replica
+ * holds a sufficiently up-to-date log. Since Ketch uses Git objects for storage
+ * of its replicated log, it keeps the term number as Raft does but uses
+ * standard Git operations to imply the log index.
+ * <p>
+ * {@link Round#runAsync(AnyObjectId)} bumps the index as each new round is
+ * constructed.
+ */
+public class LogIndex extends ObjectId {
+ static LogIndex unknown(AnyObjectId id) {
+ return new LogIndex(id, 0);
+ }
+
+ private final long index;
+
+ private LogIndex(AnyObjectId id, long index) {
+ super(id);
+ this.index = index;
+ }
+
+ LogIndex nextIndex(AnyObjectId id) {
+ return new LogIndex(id, index + 1);
+ }
+
+ /** @return index provided by the current leader instance. */
+ public long getIndex() {
+ return index;
+ }
+
+ /**
+ * Check if this log position committed before another log position.
+ * <p>
+ * Only valid for log positions in memory for the current leader.
+ *
+ * @param c
+ * other (more recent) log position.
+ * @return true if this log position was before {@code c} or equal to c and
+ * therefore any agreement of {@code c} implies agreement on this
+ * log position.
+ */
+ boolean isBefore(LogIndex c) {
+ return index <= c.index;
+ }
+
+ /**
+ * @return string suitable for debug logging containing the log index and
+ * abbreviated ObjectId.
+ */
+ @SuppressWarnings("boxing")
+ public String describeForLog() {
+ return String.format("%5d/%s", index, abbreviate(6).name()); //$NON-NLS-1$
+ }
+
+ @SuppressWarnings("boxing")
+ @Override
+ public String toString() {
+ return String.format("LogId[%5d/%s]", index, name()); //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Proposal.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Proposal.java
new file mode 100644
index 0000000000..0876eb5dbd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Proposal.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.Proposal.State.ABORTED;
+import static org.eclipse.jgit.internal.ketch.Proposal.State.EXECUTED;
+import static org.eclipse.jgit.internal.ketch.Proposal.State.NEW;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.storage.reftree.Command;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.PushCertificate;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * A proposal to be applied in a Ketch system.
+ * <p>
+ * Pushing to a Ketch leader results in the leader making a proposal. The
+ * proposal includes the list of reference updates. The leader attempts to send
+ * the proposal to a quorum of replicas by pushing the proposal to a "staging"
+ * area under the {@code refs/txn/stage/} namespace. If the proposal succeeds
+ * then the changes are durable and the leader can commit the proposal.
+ * <p>
+ * Proposals are executed by {@link KetchLeader#queueProposal(Proposal)}, which
+ * runs them asynchronously in the background. Proposals are thread-safe futures
+ * allowing callers to {@link #await()} for results or be notified by callback
+ * using {@link #addListener(Runnable)}.
+ */
+public class Proposal {
+ /** Current state of the proposal. */
+ public enum State {
+ /** Proposal has not yet been given to a {@link KetchLeader}. */
+ NEW(false),
+
+ /**
+ * Proposal was validated and has entered the queue, but a round
+ * containing this proposal has not started yet.
+ */
+ QUEUED(false),
+
+ /** Round containing the proposal has begun and is in progress. */
+ RUNNING(false),
+
+ /**
+ * Proposal was executed through a round. Individual results from
+ * {@link Proposal#getCommands()}, {@link Command#getResult()} explain
+ * the success or failure outcome.
+ */
+ EXECUTED(true),
+
+ /** Proposal was aborted and did not reach consensus. */
+ ABORTED(true);
+
+ private final boolean done;
+
+ private State(boolean done) {
+ this.done = done;
+ }
+
+ /** @return true if this is a terminal state. */
+ public boolean isDone() {
+ return done;
+ }
+ }
+
+ private final List<Command> commands;
+ private PersonIdent author;
+ private String message;
+ private PushCertificate pushCert;
+ private final List<Runnable> listeners = new CopyOnWriteArrayList<>();
+ private final AtomicReference<State> state = new AtomicReference<>(NEW);
+
+ /**
+ * Create a proposal from a list of Ketch commands.
+ *
+ * @param cmds
+ * prepared list of commands.
+ */
+ public Proposal(List<Command> cmds) {
+ commands = Collections.unmodifiableList(new ArrayList<>(cmds));
+ }
+
+ /**
+ * Create a proposal from a collection of received commands.
+ *
+ * @param rw
+ * walker to assist in preparing commands.
+ * @param cmds
+ * list of pending commands.
+ * @throws MissingObjectException
+ * newId of a command is not found locally.
+ * @throws IOException
+ * local objects cannot be accessed.
+ */
+ public Proposal(RevWalk rw, Collection<ReceiveCommand> cmds)
+ throws MissingObjectException, IOException {
+ commands = asCommandList(rw, cmds);
+ }
+
+ private static List<Command> asCommandList(RevWalk rw,
+ Collection<ReceiveCommand> cmds)
+ throws MissingObjectException, IOException {
+ List<Command> commands = new ArrayList<>(cmds.size());
+ for (ReceiveCommand cmd : cmds) {
+ commands.add(new Command(rw, cmd));
+ }
+ return Collections.unmodifiableList(commands);
+ }
+
+ /** @return commands from this proposal. */
+ public Collection<Command> getCommands() {
+ return commands;
+ }
+
+ /** @return optional author of the proposal. */
+ @Nullable
+ public PersonIdent getAuthor() {
+ return author;
+ }
+
+ /**
+ * Set the author for the proposal.
+ *
+ * @param who
+ * optional identity of the author of the proposal.
+ * @return {@code this}
+ */
+ public Proposal setAuthor(@Nullable PersonIdent who) {
+ author = who;
+ return this;
+ }
+
+ /** @return optional message for the commit log of the RefTree. */
+ @Nullable
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Set the message to appear in the commit log of the RefTree.
+ *
+ * @param msg
+ * message text for the commit.
+ * @return {@code this}
+ */
+ public Proposal setMessage(@Nullable String msg) {
+ message = msg != null && !msg.isEmpty() ? msg : null;
+ return this;
+ }
+
+ /** @return optional certificate signing the references. */
+ @Nullable
+ public PushCertificate getPushCertificate() {
+ return pushCert;
+ }
+
+ /**
+ * Set the push certificate signing the references.
+ *
+ * @param cert
+ * certificate, may be null.
+ * @return {@code this}
+ */
+ public Proposal setPushCertificate(@Nullable PushCertificate cert) {
+ pushCert = cert;
+ return this;
+ }
+
+ /**
+ * Add a callback to be invoked when the proposal is done.
+ * <p>
+ * A proposal is done when it has entered either {@link State#EXECUTED} or
+ * {@link State#ABORTED} state. If the proposal is already done
+ * {@code callback.run()} is immediately invoked on the caller's thread.
+ *
+ * @param callback
+ * method to run after the proposal is done. The callback may be
+ * run on a Ketch system thread and should be completed quickly.
+ */
+ public void addListener(Runnable callback) {
+ boolean runNow = false;
+ synchronized (state) {
+ if (state.get().isDone()) {
+ runNow = true;
+ } else {
+ listeners.add(callback);
+ }
+ }
+ if (runNow) {
+ callback.run();
+ }
+ }
+
+ /** Set command result as OK. */
+ void success() {
+ for (Command c : commands) {
+ if (c.getResult() == NOT_ATTEMPTED) {
+ c.setResult(OK);
+ }
+ }
+ notifyState(EXECUTED);
+ }
+
+ /** Mark commands as "transaction aborted". */
+ void abort() {
+ Command.abort(commands, null);
+ notifyState(ABORTED);
+ }
+
+ /** @return read the current state of the proposal. */
+ public State getState() {
+ return state.get();
+ }
+
+ /**
+ * @return {@code true} if the proposal was attempted. A true value does not
+ * mean consensus was reached, only that the proposal was considered
+ * and will not be making any more progress beyond its current
+ * state.
+ */
+ public boolean isDone() {
+ return state.get().isDone();
+ }
+
+ /**
+ * Wait for the proposal to be attempted and {@link #isDone()} to be true.
+ *
+ * @throws InterruptedException
+ * caller was interrupted before proposal executed.
+ */
+ public void await() throws InterruptedException {
+ synchronized (state) {
+ while (!state.get().isDone()) {
+ state.wait();
+ }
+ }
+ }
+
+ /**
+ * Wait for the proposal to be attempted and {@link #isDone()} to be true.
+ *
+ * @param wait
+ * how long to wait.
+ * @param unit
+ * unit describing the wait time.
+ * @return true if the proposal is done; false if the method timed out.
+ * @throws InterruptedException
+ * caller was interrupted before proposal executed.
+ */
+ public boolean await(long wait, TimeUnit unit) throws InterruptedException {
+ synchronized (state) {
+ if (state.get().isDone()) {
+ return true;
+ }
+ state.wait(unit.toMillis(wait));
+ return state.get().isDone();
+ }
+ }
+
+ /**
+ * Wait for the proposal to exit a state.
+ *
+ * @param notIn
+ * state the proposal should not be in to return.
+ * @param wait
+ * how long to wait.
+ * @param unit
+ * unit describing the wait time.
+ * @return true if the proposal exited the state; false on time out.
+ * @throws InterruptedException
+ * caller was interrupted before proposal executed.
+ */
+ public boolean awaitStateChange(State notIn, long wait, TimeUnit unit)
+ throws InterruptedException {
+ synchronized (state) {
+ if (state.get() != notIn) {
+ return true;
+ }
+ state.wait(unit.toMillis(wait));
+ return state.get() != notIn;
+ }
+ }
+
+ void notifyState(State s) {
+ synchronized (state) {
+ state.set(s);
+ state.notifyAll();
+ }
+ if (s.isDone()) {
+ for (Runnable callback : listeners) {
+ callback.run();
+ }
+ listeners.clear();
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ s.append("Ketch Proposal {\n"); //$NON-NLS-1$
+ s.append(" ").append(state.get()).append('\n'); //$NON-NLS-1$
+ if (author != null) {
+ s.append(" author ").append(author).append('\n'); //$NON-NLS-1$
+ }
+ if (message != null) {
+ s.append(" message ").append(message).append('\n'); //$NON-NLS-1$
+ }
+ for (Command c : commands) {
+ s.append(" "); //$NON-NLS-1$
+ format(s, c.getOldRef(), "CREATE"); //$NON-NLS-1$
+ s.append(' ');
+ format(s, c.getNewRef(), "DELETE"); //$NON-NLS-1$
+ s.append(' ').append(c.getRefName());
+ if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
+ s.append(' ').append(c.getResult()); // $NON-NLS-1$
+ }
+ s.append('\n');
+ }
+ s.append('}');
+ return s.toString();
+ }
+
+ private static void format(StringBuilder s, @Nullable Ref r, String n) {
+ if (r == null) {
+ s.append(n);
+ } else if (r.isSymbolic()) {
+ s.append(r.getTarget().getName());
+ } else {
+ ObjectId id = r.getObjectId();
+ if (id != null) {
+ s.append(id.abbreviate(8).name());
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java
new file mode 100644
index 0000000000..d34477ab26
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ProposalRound.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.Proposal.State.RUNNING;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.reftree.Command;
+import org.eclipse.jgit.internal.storage.reftree.RefTree;
+import org.eclipse.jgit.lib.CommitBuilder;
+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.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/** A {@link Round} that aggregates and sends user {@link Proposal}s. */
+class ProposalRound extends Round {
+ private final List<Proposal> todo;
+ private RefTree queuedTree;
+
+ ProposalRound(KetchLeader leader, LogIndex head, List<Proposal> todo,
+ @Nullable RefTree tree) {
+ super(leader, head);
+ this.todo = todo;
+
+ if (tree != null && canCombine(todo)) {
+ this.queuedTree = tree;
+ } else {
+ leader.roundHoldsReferenceToRefTree = false;
+ }
+ }
+
+ private static boolean canCombine(List<Proposal> todo) {
+ Proposal first = todo.get(0);
+ for (int i = 1; i < todo.size(); i++) {
+ if (!canCombine(first, todo.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean canCombine(Proposal a, Proposal b) {
+ String aMsg = nullToEmpty(a.getMessage());
+ String bMsg = nullToEmpty(b.getMessage());
+ return aMsg.equals(bMsg) && canCombine(a.getAuthor(), b.getAuthor());
+ }
+
+ private static String nullToEmpty(@Nullable String str) {
+ return str != null ? str : ""; //$NON-NLS-1$
+ }
+
+ private static boolean canCombine(@Nullable PersonIdent a,
+ @Nullable PersonIdent b) {
+ if (a != null && b != null) {
+ // Same name and email address. Combine timestamp as the two
+ // proposals are running concurrently and appear together or
+ // not at all from the point of view of an outside reader.
+ return a.getName().equals(b.getName())
+ && a.getEmailAddress().equals(b.getEmailAddress());
+ }
+
+ // If a and b are null, both will be the system identity.
+ return a == null && b == null;
+ }
+
+ void start() throws IOException {
+ for (Proposal p : todo) {
+ p.notifyState(RUNNING);
+ }
+ try {
+ ObjectId id;
+ try (Repository git = leader.openRepository()) {
+ id = insertProposals(git);
+ }
+ runAsync(id);
+ } catch (NoOp e) {
+ for (Proposal p : todo) {
+ p.success();
+ }
+ leader.lock.lock();
+ try {
+ leader.nextRound();
+ } finally {
+ leader.lock.unlock();
+ }
+ } catch (IOException e) {
+ abort();
+ throw e;
+ }
+ }
+
+ private ObjectId insertProposals(Repository git)
+ throws IOException, NoOp {
+ ObjectId id;
+ try (ObjectInserter inserter = git.newObjectInserter()) {
+ // TODO(sop) Process signed push certificates.
+
+ if (queuedTree != null) {
+ id = insertSingleProposal(git, inserter);
+ } else {
+ id = insertMultiProposal(git, inserter);
+ }
+
+ stageCommands = makeStageList(git, inserter);
+ inserter.flush();
+ }
+ return id;
+ }
+
+ private ObjectId insertSingleProposal(Repository git,
+ ObjectInserter inserter) throws IOException, NoOp {
+ // Fast path: tree is passed in with all proposals applied.
+ ObjectId treeId = queuedTree.writeTree(inserter);
+ queuedTree = null;
+ leader.roundHoldsReferenceToRefTree = false;
+
+ if (!ObjectId.zeroId().equals(acceptedOldIndex)) {
+ try (RevWalk rw = new RevWalk(git)) {
+ RevCommit c = rw.parseCommit(acceptedOldIndex);
+ if (treeId.equals(c.getTree())) {
+ throw new NoOp();
+ }
+ }
+ }
+
+ Proposal p = todo.get(0);
+ CommitBuilder b = new CommitBuilder();
+ b.setTreeId(treeId);
+ if (!ObjectId.zeroId().equals(acceptedOldIndex)) {
+ b.setParentId(acceptedOldIndex);
+ }
+ b.setCommitter(leader.getSystem().newCommitter());
+ b.setAuthor(p.getAuthor() != null ? p.getAuthor() : b.getCommitter());
+ b.setMessage(message(p));
+ return inserter.insert(b);
+ }
+
+ private ObjectId insertMultiProposal(Repository git,
+ ObjectInserter inserter) throws IOException, NoOp {
+ // The tree was not passed in, or there are multiple proposals
+ // each needing their own commit. Reset the tree and replay each
+ // proposal in order as individual commits.
+ ObjectId lastIndex = acceptedOldIndex;
+ ObjectId oldTreeId;
+ RefTree tree;
+ if (ObjectId.zeroId().equals(lastIndex)) {
+ oldTreeId = ObjectId.zeroId();
+ tree = RefTree.newEmptyTree();
+ } else {
+ try (RevWalk rw = new RevWalk(git)) {
+ RevCommit c = rw.parseCommit(lastIndex);
+ oldTreeId = c.getTree();
+ tree = RefTree.read(rw.getObjectReader(), c.getTree());
+ }
+ }
+
+ PersonIdent committer = leader.getSystem().newCommitter();
+ for (Proposal p : todo) {
+ if (!tree.apply(p.getCommands())) {
+ // This should not occur, previously during queuing the
+ // commands were successfully applied to the pending tree.
+ // Abort the entire round.
+ throw new IOException(
+ KetchText.get().queuedProposalFailedToApply);
+ }
+
+ ObjectId treeId = tree.writeTree(inserter);
+ if (treeId.equals(oldTreeId)) {
+ continue;
+ }
+
+ CommitBuilder b = new CommitBuilder();
+ b.setTreeId(treeId);
+ if (!ObjectId.zeroId().equals(lastIndex)) {
+ b.setParentId(lastIndex);
+ }
+ b.setAuthor(p.getAuthor() != null ? p.getAuthor() : committer);
+ b.setCommitter(committer);
+ b.setMessage(message(p));
+ lastIndex = inserter.insert(b);
+ }
+ if (lastIndex.equals(acceptedOldIndex)) {
+ throw new NoOp();
+ }
+ return lastIndex;
+ }
+
+ private String message(Proposal p) {
+ StringBuilder m = new StringBuilder();
+ String msg = p.getMessage();
+ if (msg != null && !msg.isEmpty()) {
+ m.append(msg);
+ while (m.length() < 2 || m.charAt(m.length() - 2) != '\n'
+ || m.charAt(m.length() - 1) != '\n') {
+ m.append('\n');
+ }
+ }
+ m.append(KetchConstants.TERM.getName())
+ .append(": ") //$NON-NLS-1$
+ .append(leader.getTerm());
+ return m.toString();
+ }
+
+ void abort() {
+ for (Proposal p : todo) {
+ p.abort();
+ }
+ }
+
+ void success() {
+ for (Proposal p : todo) {
+ p.success();
+ }
+ }
+
+ private List<ReceiveCommand> makeStageList(Repository git,
+ ObjectInserter inserter) throws IOException {
+ // For each branch, collapse consecutive updates to only most recent,
+ // avoiding sending multiple objects in a rapid fast-forward chain, or
+ // rewritten content.
+ Map<String, ObjectId> byRef = new HashMap<>();
+ for (Proposal p : todo) {
+ for (Command c : p.getCommands()) {
+ Ref n = c.getNewRef();
+ if (n != null && !n.isSymbolic()) {
+ byRef.put(n.getName(), n.getObjectId());
+ }
+ }
+ }
+ if (byRef.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ Set<ObjectId> newObjs = new HashSet<>(byRef.values());
+ StageBuilder b = new StageBuilder(
+ leader.getSystem().getTxnStage(),
+ acceptedNewIndex);
+ return b.makeStageList(newObjs, git, inserter);
+ }
+
+
+ private static class NoOp extends Exception {
+ private static final long serialVersionUID = 1L;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java
new file mode 100644
index 0000000000..6f4a178673
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod.ALL_REFS;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NODELETE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+import static org.eclipse.jgit.lib.Ref.Storage.NETWORK;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.AnyObjectId;
+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.transport.FetchConnection;
+import org.eclipse.jgit.transport.PushConnection;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.Transport;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Representation of a Git repository on a remote replica system.
+ * <p>
+ * {@link KetchLeader} will contact the replica using the Git wire protocol.
+ * <p>
+ * The remote replica may be fully Ketch-aware, or a standard Git server.
+ */
+public class RemoteGitReplica extends KetchReplica {
+ private final URIish uri;
+ private final RemoteConfig remoteConfig;
+
+ /**
+ * Configure a new remote.
+ *
+ * @param leader
+ * instance this replica follows.
+ * @param name
+ * unique-ish name identifying this remote for debugging.
+ * @param uri
+ * URI to connect to the follower's repository.
+ * @param cfg
+ * how Ketch should treat the remote system.
+ * @param rc
+ * optional remote configuration describing how to contact the
+ * peer repository.
+ */
+ public RemoteGitReplica(KetchLeader leader, String name, URIish uri,
+ ReplicaConfig cfg, @Nullable RemoteConfig rc) {
+ super(leader, name, cfg);
+ this.uri = uri;
+ this.remoteConfig = rc;
+ }
+
+ /** @return URI to contact the remote peer repository. */
+ public URIish getURI() {
+ return uri;
+ }
+
+ /** @return optional configuration describing how to contact the peer. */
+ @Nullable
+ protected RemoteConfig getRemoteConfig() {
+ return remoteConfig;
+ }
+
+ @Override
+ protected String describeForLog() {
+ return String.format("%s @ %s", getName(), getURI()); //$NON-NLS-1$
+ }
+
+ @Override
+ protected void startPush(final ReplicaPushRequest req) {
+ getSystem().getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ try (Repository git = getLeader().openRepository()) {
+ try {
+ push(git, req);
+ req.done(git);
+ } catch (Throwable err) {
+ req.setException(git, err);
+ }
+ } catch (IOException err) {
+ req.setException(null, err);
+ }
+ }
+ });
+ }
+
+ private void push(Repository repo, ReplicaPushRequest req)
+ throws NotSupportedException, TransportException, IOException {
+ Map<String, Ref> adv;
+ List<RemoteCommand> cmds = asUpdateList(req.getCommands());
+ try (Transport transport = Transport.open(repo, uri)) {
+ RemoteConfig rc = getRemoteConfig();
+ if (rc != null) {
+ transport.applyConfig(rc);
+ }
+ transport.setPushAtomic(true);
+ adv = push(repo, transport, cmds);
+ }
+ for (RemoteCommand c : cmds) {
+ c.copyStatusToResult();
+ }
+ req.setRefs(adv);
+ }
+
+ private Map<String, Ref> push(Repository git, Transport transport,
+ List<RemoteCommand> cmds) throws IOException {
+ Map<String, RemoteRefUpdate> updates = asUpdateMap(cmds);
+ try (PushConnection connection = transport.openPush()) {
+ Map<String, Ref> adv = connection.getRefsMap();
+ RemoteRefUpdate accepted = updates.get(getSystem().getTxnAccepted());
+ if (accepted != null && !isExpectedValue(adv, accepted)) {
+ abort(cmds);
+ return adv;
+ }
+
+ RemoteRefUpdate committed = updates.get(getSystem().getTxnCommitted());
+ if (committed != null && !isExpectedValue(adv, committed)) {
+ abort(cmds);
+ return adv;
+ }
+ if (committed != null && getCommitMethod() == ALL_REFS) {
+ prepareCommit(git, cmds, updates, adv,
+ committed.getNewObjectId());
+ }
+
+ connection.push(NullProgressMonitor.INSTANCE, updates);
+ return adv;
+ }
+ }
+
+ private static boolean isExpectedValue(Map<String, Ref> adv,
+ RemoteRefUpdate u) {
+ Ref r = adv.get(u.getRemoteName());
+ if (!AnyObjectId.equals(getId(r), u.getExpectedOldObjectId())) {
+ ((RemoteCommand) u).cmd.setResult(LOCK_FAILURE);
+ return false;
+ }
+ return true;
+ }
+
+ private void prepareCommit(Repository git, List<RemoteCommand> cmds,
+ Map<String, RemoteRefUpdate> updates, Map<String, Ref> adv,
+ ObjectId committed) throws IOException {
+ for (ReceiveCommand cmd : prepareCommit(git, adv, committed)) {
+ RemoteCommand c = new RemoteCommand(cmd);
+ cmds.add(c);
+ updates.put(c.getRemoteName(), c);
+ }
+ }
+
+ private static List<RemoteCommand> asUpdateList(
+ Collection<ReceiveCommand> cmds) {
+ try {
+ List<RemoteCommand> toPush = new ArrayList<>(cmds.size());
+ for (ReceiveCommand cmd : cmds) {
+ toPush.add(new RemoteCommand(cmd));
+ }
+ return toPush;
+ } catch (IOException e) {
+ // Cannot occur as no IO was required to build the command.
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static Map<String, RemoteRefUpdate> asUpdateMap(
+ List<RemoteCommand> cmds) {
+ Map<String, RemoteRefUpdate> m = new LinkedHashMap<>();
+ for (RemoteCommand cmd : cmds) {
+ m.put(cmd.getRemoteName(), cmd);
+ }
+ return m;
+ }
+
+ private static void abort(List<RemoteCommand> cmds) {
+ List<ReceiveCommand> tmp = new ArrayList<>(cmds.size());
+ for (RemoteCommand cmd : cmds) {
+ tmp.add(cmd.cmd);
+ }
+ ReceiveCommand.abort(tmp);
+ }
+
+ protected void blockingFetch(Repository repo, ReplicaFetchRequest req)
+ throws NotSupportedException, TransportException {
+ try (Transport transport = Transport.open(repo, uri)) {
+ RemoteConfig rc = getRemoteConfig();
+ if (rc != null) {
+ transport.applyConfig(rc);
+ }
+ fetch(transport, req);
+ }
+ }
+
+ private void fetch(Transport transport, ReplicaFetchRequest req)
+ throws NotSupportedException, TransportException {
+ try (FetchConnection conn = transport.openFetch()) {
+ Map<String, Ref> remoteRefs = conn.getRefsMap();
+ req.setRefs(remoteRefs);
+
+ List<Ref> want = new ArrayList<>();
+ for (String name : req.getWantRefs()) {
+ Ref ref = remoteRefs.get(name);
+ if (ref != null && ref.getObjectId() != null) {
+ want.add(ref);
+ }
+ }
+ for (ObjectId id : req.getWantObjects()) {
+ want.add(new ObjectIdRef.Unpeeled(NETWORK, id.name(), id));
+ }
+
+ conn.fetch(NullProgressMonitor.INSTANCE, want,
+ Collections.<ObjectId> emptySet());
+ }
+ }
+
+ static class RemoteCommand extends RemoteRefUpdate {
+ final ReceiveCommand cmd;
+
+ RemoteCommand(ReceiveCommand cmd) throws IOException {
+ super(null, null,
+ cmd.getNewId(), cmd.getRefName(),
+ true /* force update */,
+ null /* no local tracking ref */,
+ cmd.getOldId());
+ this.cmd = cmd;
+ }
+
+ void copyStatusToResult() {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ switch (getStatus()) {
+ case OK:
+ case UP_TO_DATE:
+ case NON_EXISTING:
+ cmd.setResult(OK);
+ break;
+
+ case REJECTED_NODELETE:
+ cmd.setResult(REJECTED_NODELETE);
+ break;
+
+ case REJECTED_NONFASTFORWARD:
+ cmd.setResult(REJECTED_NONFASTFORWARD);
+ break;
+
+ case REJECTED_OTHER_REASON:
+ cmd.setResult(REJECTED_OTHER_REASON, getMessage());
+ break;
+
+ default:
+ cmd.setResult(REJECTED_OTHER_REASON, getStatus().name());
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaConfig.java
new file mode 100644
index 0000000000..e16e63aa7e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaConfig.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_KEY_COMMIT;
+import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_KEY_SPEED;
+import static org.eclipse.jgit.internal.ketch.KetchConstants.CONFIG_KEY_TYPE;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REMOTE;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod;
+import org.eclipse.jgit.internal.ketch.KetchReplica.CommitSpeed;
+import org.eclipse.jgit.internal.ketch.KetchReplica.Participation;
+import org.eclipse.jgit.lib.Config;
+
+/** Configures a {@link KetchReplica}. */
+public class ReplicaConfig {
+ /**
+ * Read a configuration from a config block.
+ *
+ * @param cfg
+ * configuration to read.
+ * @param name
+ * of the replica being configured.
+ * @return replica configuration for {@code name}.
+ */
+ public static ReplicaConfig newFromConfig(Config cfg, String name) {
+ return new ReplicaConfig().fromConfig(cfg, name);
+ }
+
+ private Participation participation = Participation.FULL;
+ private CommitMethod commitMethod = CommitMethod.ALL_REFS;
+ private CommitSpeed commitSpeed = CommitSpeed.BATCHED;
+ private long minRetry = SECONDS.toMillis(5);
+ private long maxRetry = MINUTES.toMillis(1);
+
+ /** @return participation of the replica in the system. */
+ public Participation getParticipation() {
+ return participation;
+ }
+
+ /** @return how Ketch should apply committed changes. */
+ public CommitMethod getCommitMethod() {
+ return commitMethod;
+ }
+
+ /** @return how quickly should Ketch commit. */
+ public CommitSpeed getCommitSpeed() {
+ return commitSpeed;
+ }
+
+ /**
+ * Returns the minimum wait delay before retrying a failure.
+ *
+ * @param unit
+ * to get retry delay in.
+ * @return minimum delay before retrying a failure.
+ */
+ public long getMinRetry(TimeUnit unit) {
+ return unit.convert(minRetry, MILLISECONDS);
+ }
+
+ /**
+ * Returns the maximum wait delay before retrying a failure.
+ *
+ * @param unit
+ * to get retry delay in.
+ * @return maximum delay before retrying a failure.
+ */
+ public long getMaxRetry(TimeUnit unit) {
+ return unit.convert(maxRetry, MILLISECONDS);
+ }
+
+ /**
+ * Update the configuration from a config block.
+ *
+ * @param cfg
+ * configuration to read.
+ * @param name
+ * of the replica being configured.
+ * @return {@code this}
+ */
+ public ReplicaConfig fromConfig(Config cfg, String name) {
+ participation = cfg.getEnum(
+ CONFIG_KEY_REMOTE, name, CONFIG_KEY_TYPE,
+ participation);
+ commitMethod = cfg.getEnum(
+ CONFIG_KEY_REMOTE, name, CONFIG_KEY_COMMIT,
+ commitMethod);
+ commitSpeed = cfg.getEnum(
+ CONFIG_KEY_REMOTE, name, CONFIG_KEY_SPEED,
+ commitSpeed);
+ minRetry = getMillis(cfg, name, "ketch-minRetry", minRetry); //$NON-NLS-1$
+ maxRetry = getMillis(cfg, name, "ketch-maxRetry", maxRetry); //$NON-NLS-1$
+ return this;
+ }
+
+ private static long getMillis(Config cfg, String name, String key,
+ long defaultValue) {
+ String valStr = cfg.getString(CONFIG_KEY_REMOTE, name, key);
+ if (valStr == null) {
+ return defaultValue;
+ }
+
+ valStr = valStr.trim();
+ if (valStr.isEmpty()) {
+ return defaultValue;
+ }
+
+ Matcher m = UnitMap.PATTERN.matcher(valStr);
+ if (!m.matches()) {
+ return defaultValue;
+ }
+
+ String digits = m.group(1);
+ String unitName = m.group(2).trim();
+ TimeUnit unit = UnitMap.UNITS.get(unitName);
+ if (unit == null) {
+ return defaultValue;
+ }
+
+ try {
+ if (digits.indexOf('.') == -1) {
+ return unit.toMillis(Long.parseLong(digits));
+ }
+
+ double val = Double.parseDouble(digits);
+ return (long) (val * unit.toMillis(1));
+ } catch (NumberFormatException nfe) {
+ return defaultValue;
+ }
+ }
+
+ static class UnitMap {
+ static final Pattern PATTERN = Pattern
+ .compile("^([1-9][0-9]*(?:\\.[0-9]*)?)\\s*(.*)$"); //$NON-NLS-1$
+
+ static final Map<String, TimeUnit> UNITS;
+
+ static {
+ Map<String, TimeUnit> m = new HashMap<>();
+ TimeUnit u = MILLISECONDS;
+ m.put("", u); //$NON-NLS-1$
+ m.put("ms", u); //$NON-NLS-1$
+ m.put("millis", u); //$NON-NLS-1$
+ m.put("millisecond", u); //$NON-NLS-1$
+ m.put("milliseconds", u); //$NON-NLS-1$
+
+ u = SECONDS;
+ m.put("s", u); //$NON-NLS-1$
+ m.put("sec", u); //$NON-NLS-1$
+ m.put("secs", u); //$NON-NLS-1$
+ m.put("second", u); //$NON-NLS-1$
+ m.put("seconds", u); //$NON-NLS-1$
+
+ u = MINUTES;
+ m.put("m", u); //$NON-NLS-1$
+ m.put("min", u); //$NON-NLS-1$
+ m.put("mins", u); //$NON-NLS-1$
+ m.put("minute", u); //$NON-NLS-1$
+ m.put("minutes", u); //$NON-NLS-1$
+
+ u = HOURS;
+ m.put("h", u); //$NON-NLS-1$
+ m.put("hr", u); //$NON-NLS-1$
+ m.put("hrs", u); //$NON-NLS-1$
+ m.put("hour", u); //$NON-NLS-1$
+ m.put("hours", u); //$NON-NLS-1$
+
+ u = DAYS;
+ m.put("d", u); //$NON-NLS-1$
+ m.put("day", u); //$NON-NLS-1$
+ m.put("days", u); //$NON-NLS-1$
+
+ UNITS = Collections.unmodifiableMap(m);
+ }
+
+ private UnitMap() {
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaFetchRequest.java
index 936fd82bfd..201d9e9743 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaFetchRequest.java
@@ -1,7 +1,5 @@
/*
- * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2016, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -43,45 +41,56 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.internal.ketch;
-/**
- * A tree entry representing a gitlink entry used for submodules.
- *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class GitlinkTreeEntry extends TreeEntry {
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+
+/** A fetch request to obtain objects from a replica, and its result. */
+public class ReplicaFetchRequest {
+ private final Set<String> wantRefs;
+ private final Set<ObjectId> wantObjects;
+ private Map<String, Ref> refs;
/**
- * Construct a {@link GitlinkTreeEntry} with the specified name and SHA-1 in
- * the specified parent
+ * Construct a new fetch request for a replica.
*
- * @param parent
- * @param id
- * @param nameUTF8
+ * @param wantRefs
+ * named references to be fetched.
+ * @param wantObjects
+ * specific objects to be fetched.
*/
- public GitlinkTreeEntry(final Tree parent, final ObjectId id,
- final byte[] nameUTF8) {
- super(parent, id, nameUTF8);
+ public ReplicaFetchRequest(Set<String> wantRefs,
+ Set<ObjectId> wantObjects) {
+ this.wantRefs = wantRefs;
+ this.wantObjects = wantObjects;
}
- public FileMode getMode() {
- return FileMode.GITLINK;
+ /** @return references to be fetched. */
+ public Set<String> getWantRefs() {
+ return wantRefs;
}
- @Override
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(getId()));
- r.append(" G "); //$NON-NLS-1$
- r.append(getFullName());
- return r.toString();
+ /** @return objects to be fetched. */
+ public Set<ObjectId> getWantObjects() {
+ return wantObjects;
+ }
+
+ /** @return remote references, usually from the advertisement. */
+ @Nullable
+ public Map<String, Ref> getRefs() {
+ return refs;
+ }
+
+ /**
+ * @param refs
+ * references observed from the replica.
+ */
+ public void setRefs(Map<String, Ref> refs) {
+ this.refs = refs;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaPushRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaPushRequest.java
new file mode 100644
index 0000000000..691b1424f4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaPushRequest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * A push request sending objects to a replica, and its result.
+ * <p>
+ * Implementors of {@link KetchReplica} must populate the command result fields,
+ * {@link #setRefs(Map)}, and call one of
+ * {@link #setException(Repository, Throwable)} or {@link #done(Repository)} to
+ * finish processing.
+ */
+public class ReplicaPushRequest {
+ private final KetchReplica replica;
+ private final Collection<ReceiveCommand> commands;
+ private Map<String, Ref> refs;
+ private Throwable exception;
+ private boolean notified;
+
+ /**
+ * Construct a new push request for a replica.
+ *
+ * @param replica
+ * the replica being pushed to.
+ * @param commands
+ * commands to be executed.
+ */
+ public ReplicaPushRequest(KetchReplica replica,
+ Collection<ReceiveCommand> commands) {
+ this.replica = replica;
+ this.commands = commands;
+ }
+
+ /** @return commands to be executed, and their results. */
+ public Collection<ReceiveCommand> getCommands() {
+ return commands;
+ }
+
+ /** @return remote references, usually from the advertisement. */
+ @Nullable
+ public Map<String, Ref> getRefs() {
+ return refs;
+ }
+
+ /**
+ * @param refs
+ * references observed from the replica.
+ */
+ public void setRefs(Map<String, Ref> refs) {
+ this.refs = refs;
+ }
+
+ /** @return exception thrown, if any. */
+ @Nullable
+ public Throwable getException() {
+ return exception;
+ }
+
+ /**
+ * Mark the request as crashing with a communication error.
+ * <p>
+ * This method may take significant time acquiring the leader lock and
+ * updating the Ketch state machine with the failure.
+ *
+ * @param repo
+ * local repository reference used by the push attempt.
+ * @param err
+ * exception thrown during communication.
+ */
+ public void setException(@Nullable Repository repo, Throwable err) {
+ if (KetchReplica.log.isErrorEnabled()) {
+ KetchReplica.log.error(describe("failed"), err); //$NON-NLS-1$
+ }
+ if (!notified) {
+ notified = true;
+ exception = err;
+ replica.afterPush(repo, this);
+ }
+ }
+
+ /**
+ * Mark the request as completed without exception.
+ * <p>
+ * This method may take significant time acquiring the leader lock and
+ * updating the Ketch state machine with results from this replica.
+ *
+ * @param repo
+ * local repository reference used by the push attempt.
+ */
+ public void done(Repository repo) {
+ if (KetchReplica.log.isDebugEnabled()) {
+ KetchReplica.log.debug(describe("completed")); //$NON-NLS-1$
+ }
+ if (!notified) {
+ notified = true;
+ replica.afterPush(repo, this);
+ }
+ }
+
+ private String describe(String heading) {
+ StringBuilder b = new StringBuilder();
+ b.append("push to "); //$NON-NLS-1$
+ b.append(replica.describeForLog());
+ b.append(' ').append(heading).append(":\n"); //$NON-NLS-1$
+ for (ReceiveCommand cmd : commands) {
+ b.append(String.format(
+ " %-12s %-12s %s %s", //$NON-NLS-1$
+ LeaderSnapshot.str(cmd.getOldId()),
+ LeaderSnapshot.str(cmd.getNewId()),
+ cmd.getRefName(),
+ cmd.getResult()));
+ if (cmd.getMessage() != null) {
+ b.append(' ').append(cmd.getMessage());
+ }
+ b.append('\n');
+ }
+ return b.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaSnapshot.java
new file mode 100644
index 0000000000..8c3de027d2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/ReplicaSnapshot.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import java.util.Date;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * A snapshot of a replica.
+ *
+ * @see LeaderSnapshot
+ */
+public class ReplicaSnapshot {
+ final KetchReplica replica;
+ ObjectId accepted;
+ ObjectId committed;
+ KetchReplica.State state;
+ String error;
+ long retryAtMillis;
+
+ ReplicaSnapshot(KetchReplica replica) {
+ this.replica = replica;
+ }
+
+ /** @return the replica this snapshot describes the state of. */
+ public KetchReplica getReplica() {
+ return replica;
+ }
+
+ /** @return current state of the replica. */
+ public KetchReplica.State getState() {
+ return state;
+ }
+
+ /** @return last known Git commit at {@code refs/txn/accepted}. */
+ @Nullable
+ public ObjectId getAccepted() {
+ return accepted;
+ }
+
+ /** @return last known Git commit at {@code refs/txn/committed}. */
+ @Nullable
+ public ObjectId getCommitted() {
+ return committed;
+ }
+
+ /**
+ * @return if {@link #getState()} == {@link KetchReplica.State#OFFLINE} an
+ * optional human-readable message from the transport system
+ * explaining the failure.
+ */
+ @Nullable
+ public String getErrorMessage() {
+ return error;
+ }
+
+ /**
+ * @return time (usually in the future) when the leader will retry
+ * communication with the offline or lagging replica; null if no
+ * retry is scheduled or necessary.
+ */
+ @Nullable
+ public Date getRetryAt() {
+ return retryAtMillis > 0 ? new Date(retryAtMillis) : null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Round.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Round.java
new file mode 100644
index 0000000000..1335b85cca
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/Round.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * One round-trip to all replicas proposing a log entry.
+ * <p>
+ * In Raft a log entry represents a state transition at a specific index in the
+ * replicated log. The leader can only append log entries to the log.
+ * <p>
+ * In Ketch a log entry is recorded under the {@code refs/txn} namespace. This
+ * occurs when:
+ * <ul>
+ * <li>a replica wants to establish itself as a new leader by proposing a new
+ * term (see {@link ElectionRound})
+ * <li>an established leader wants to gain consensus on new {@link Proposal}s
+ * (see {@link ProposalRound})
+ * </ul>
+ */
+abstract class Round {
+ final KetchLeader leader;
+ final LogIndex acceptedOldIndex;
+ LogIndex acceptedNewIndex;
+ List<ReceiveCommand> stageCommands;
+
+ Round(KetchLeader leader, LogIndex head) {
+ this.leader = leader;
+ this.acceptedOldIndex = head;
+ }
+
+ /**
+ * Creates a commit for {@code refs/txn/accepted} and calls
+ * {@link #runAsync(AnyObjectId)} to begin execution of the round across
+ * the system.
+ * <p>
+ * If references are being updated (such as in a {@link ProposalRound}) the
+ * RefTree may be modified.
+ * <p>
+ * Invoked without {@link KetchLeader#lock} to build objects.
+ *
+ * @throws IOException
+ * the round cannot build new objects within the leader's
+ * repository. The leader may be unable to execute.
+ */
+ abstract void start() throws IOException;
+
+ /**
+ * Asynchronously distribute the round's new value for
+ * {@code refs/txn/accepted} to all replicas.
+ * <p>
+ * Invoked by {@link #start()} after new commits have been created for the
+ * log. The method passes {@code newId} to {@link KetchLeader} to be
+ * distributed to all known replicas.
+ *
+ * @param newId
+ * new value for {@code refs/txn/accepted}.
+ */
+ void runAsync(AnyObjectId newId) {
+ acceptedNewIndex = acceptedOldIndex.nextIndex(newId);
+ leader.runAsync(this);
+ }
+
+ /**
+ * Notify the round it was accepted by a majority of the system.
+ * <p>
+ * Invoked by the leader with {@link KetchLeader#lock} held by the caller.
+ */
+ abstract void success();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java
new file mode 100644
index 0000000000..61871a494f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2016, 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.ketch;
+
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.treewalk.EmptyTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+/** Constructs a set of commands to stage content during a proposal. */
+public class StageBuilder {
+ /**
+ * Acceptable number of references to send in a single stage transaction.
+ * <p>
+ * If the number of unique objects exceeds this amount the builder will
+ * attempt to decrease the reference count by chaining commits..
+ */
+ private static final int SMALL_BATCH_SIZE = 5;
+
+ /**
+ * Acceptable number of commits to chain together using parent pointers.
+ * <p>
+ * When staging many unique commits the {@link StageBuilder} batches
+ * together unrelated commits as parents of a temporary commit. After the
+ * proposal completes the temporary commit is discarded and can be garbage
+ * collected by all replicas.
+ */
+ private static final int TEMP_PARENT_BATCH_SIZE = 128;
+
+ private static final byte[] PEEL = { ' ', '^' };
+
+ private final String txnStage;
+ private final String txnId;
+
+ /**
+ * Construct a stage builder for a transaction.
+ *
+ * @param txnStageNamespace
+ * namespace for transaction references to build
+ * {@code "txnStageNamespace/txnId.n"} style names.
+ * @param txnId
+ * identifier used to name temporary staging refs.
+ */
+ public StageBuilder(String txnStageNamespace, ObjectId txnId) {
+ this.txnStage = txnStageNamespace;
+ this.txnId = txnId.name();
+ }
+
+ /**
+ * Compare two RefTrees and return commands to stage new objects.
+ * <p>
+ * This method ignores the lineage between the two RefTrees and does a
+ * straight diff on the two trees. New objects will be staged. The diff
+ * strategy is useful to catch-up a lagging replica, without sending every
+ * intermediate step. This may mean the replica does not have the same
+ * object set as other replicas if there are rewinds or branch deletes.
+ *
+ * @param git
+ * source repository to read {@code oldTree} and {@code newTree}
+ * from.
+ * @param oldTree
+ * accepted RefTree on the replica ({@code refs/txn/accepted}).
+ * Use {@link ObjectId#zeroId()} if the remote does not have any
+ * ref tree, e.g. a new replica catching up.
+ * @param newTree
+ * RefTree being sent to the replica. The trees will be compared.
+ * @return list of commands to create {@code "refs/txn/stage/..."}
+ * references on replicas anchoring new objects into the repository
+ * while a transaction gains consensus.
+ * @throws IOException
+ * {@code git} cannot be accessed to compare {@code oldTree} and
+ * {@code newTree} to build the object set.
+ */
+ public List<ReceiveCommand> makeStageList(Repository git, ObjectId oldTree,
+ ObjectId newTree) throws IOException {
+ try (RevWalk rw = new RevWalk(git);
+ TreeWalk tw = new TreeWalk(rw.getObjectReader());
+ ObjectInserter ins = git.newObjectInserter()) {
+ if (AnyObjectId.equals(oldTree, ObjectId.zeroId())) {
+ tw.addTree(new EmptyTreeIterator());
+ } else {
+ tw.addTree(rw.parseTree(oldTree));
+ }
+ tw.addTree(rw.parseTree(newTree));
+ tw.setFilter(TreeFilter.ANY_DIFF);
+ tw.setRecursive(true);
+
+ Set<ObjectId> newObjs = new HashSet<>();
+ while (tw.next()) {
+ if (tw.getRawMode(1) == TYPE_GITLINK
+ && !tw.isPathSuffix(PEEL, 2)) {
+ newObjs.add(tw.getObjectId(1));
+ }
+ }
+
+ List<ReceiveCommand> cmds = makeStageList(newObjs, git, ins);
+ ins.flush();
+ return cmds;
+ }
+ }
+
+ /**
+ * Construct a set of commands to stage objects on a replica.
+ *
+ * @param newObjs
+ * objects to send to a replica.
+ * @param git
+ * local repository to read source objects from. Required to
+ * perform minification of {@code newObjs}.
+ * @param inserter
+ * inserter to write temporary commit objects during minification
+ * if many new branches are created by {@code newObjs}.
+ * @return list of commands to create {@code "refs/txn/stage/..."}
+ * references on replicas anchoring {@code newObjs} into the
+ * repository while a transaction gains consensus.
+ * @throws IOException
+ * {@code git} cannot be accessed to perform minification of
+ * {@code newObjs}.
+ */
+ public List<ReceiveCommand> makeStageList(Set<ObjectId> newObjs,
+ @Nullable Repository git, @Nullable ObjectInserter inserter)
+ throws IOException {
+ if (git == null || newObjs.size() <= SMALL_BATCH_SIZE) {
+ // Without a source repository can only construct unique set.
+ List<ReceiveCommand> cmds = new ArrayList<>(newObjs.size());
+ for (ObjectId id : newObjs) {
+ stage(cmds, id);
+ }
+ return cmds;
+ }
+
+ List<ReceiveCommand> cmds = new ArrayList<>();
+ List<RevCommit> commits = new ArrayList<>();
+ reduceObjects(cmds, commits, git, newObjs);
+
+ if (inserter == null || commits.size() <= 1
+ || (cmds.size() + commits.size()) <= SMALL_BATCH_SIZE) {
+ // Without an inserter to aggregate commits, or for a small set of
+ // commits just send one stage ref per commit.
+ for (RevCommit c : commits) {
+ stage(cmds, c.copy());
+ }
+ return cmds;
+ }
+
+ // 'commits' is sorted most recent to least recent commit.
+ // Group batches of commits and build a chain.
+ // TODO(sop) Cluster by restricted graphs to support filtering.
+ ObjectId tip = null;
+ for (int end = commits.size(); end > 0;) {
+ int start = Math.max(0, end - TEMP_PARENT_BATCH_SIZE);
+ List<RevCommit> batch = commits.subList(start, end);
+ List<ObjectId> parents = new ArrayList<>(1 + batch.size());
+ if (tip != null) {
+ parents.add(tip);
+ }
+ parents.addAll(batch);
+
+ CommitBuilder b = new CommitBuilder();
+ b.setTreeId(batch.get(0).getTree());
+ b.setParentIds(parents);
+ b.setAuthor(tmpAuthor(batch));
+ b.setCommitter(b.getAuthor());
+ tip = inserter.insert(b);
+ end = start;
+ }
+ stage(cmds, tip);
+ return cmds;
+ }
+
+ private static PersonIdent tmpAuthor(List<RevCommit> commits) {
+ // Construct a predictable author using most recent commit time.
+ int t = 0;
+ for (int i = 0; i < commits.size();) {
+ t = Math.max(t, commits.get(i).getCommitTime());
+ }
+ String name = "Ketch Stage"; //$NON-NLS-1$
+ String email = "tmp@tmp"; //$NON-NLS-1$
+ return new PersonIdent(name, email, t * 1000L, 0);
+ }
+
+ private void reduceObjects(List<ReceiveCommand> cmds,
+ List<RevCommit> commits, Repository git,
+ Set<ObjectId> newObjs) throws IOException {
+ try (RevWalk rw = new RevWalk(git)) {
+ rw.setRetainBody(false);
+
+ for (ObjectId id : newObjs) {
+ RevObject obj = rw.parseAny(id);
+ if (obj instanceof RevCommit) {
+ rw.markStart((RevCommit) obj);
+ } else {
+ stage(cmds, id);
+ }
+ }
+
+ for (RevCommit c; (c = rw.next()) != null;) {
+ commits.add(c);
+ rw.markUninteresting(c);
+ }
+ }
+ }
+
+ private void stage(List<ReceiveCommand> cmds, ObjectId id) {
+ int estLen = txnStage.length() + txnId.length() + 5;
+ StringBuilder n = new StringBuilder(estLen);
+ n.append(txnStage).append(txnId).append('.');
+ n.append(Integer.toHexString(cmds.size()));
+ cmds.add(new ReceiveCommand(ObjectId.zeroId(), id, n.toString()));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/package-info.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/package-info.java
new file mode 100644
index 0000000000..dfe03752ca
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Distributed consensus system built on Git.
+ */
+package org.eclipse.jgit.internal.ketch;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index a050e1a5bd..5e246b47b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectId;
@@ -312,7 +311,7 @@ public class InMemoryRepository extends DfsRepository {
try (RevWalk rw = new RevWalk(getRepository())) {
for (ReceiveCommand c : cmds) {
if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
- reject(cmds);
+ ReceiveCommand.abort(cmds);
return;
}
@@ -324,7 +323,7 @@ public class InMemoryRepository extends DfsRepository {
}
} catch (IOException e) {
c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
- reject(cmds);
+ ReceiveCommand.abort(cmds);
return;
}
}
@@ -337,7 +336,7 @@ public class InMemoryRepository extends DfsRepository {
if (r == null) {
if (c.getType() != ReceiveCommand.Type.CREATE) {
c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
- reject(cmds);
+ ReceiveCommand.abort(cmds);
return;
}
} else {
@@ -345,7 +344,7 @@ public class InMemoryRepository extends DfsRepository {
if (r.isSymbolic() || objectId == null
|| !objectId.equals(c.getOldId())) {
c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
- reject(cmds);
+ ReceiveCommand.abort(cmds);
return;
}
}
@@ -374,15 +373,6 @@ public class InMemoryRepository extends DfsRepository {
clearCache();
}
- private void reject(List<ReceiveCommand> cmds) {
- for (ReceiveCommand c : cmds) {
- if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
- c.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON,
- JGitText.get().transactionAborted);
- }
- }
- }
-
@Override
protected boolean compareAndPut(Ref oldRef, Ref newRef)
throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java
index 540c4384a7..dd08375f21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java
@@ -49,12 +49,14 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
import static org.eclipse.jgit.lib.Ref.Storage.NETWORK;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
import java.io.IOException;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -79,6 +81,30 @@ import org.eclipse.jgit.transport.ReceiveCommand.Result;
* for processing.
*/
public class Command {
+ /**
+ * Set unprocessed commands as failed due to transaction aborted.
+ * <p>
+ * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to
+ * {@link Result#REJECTED_OTHER_REASON}. If {@code why} is non-null its
+ * contents will be used as the message for the first command status.
+ *
+ * @param commands
+ * commands to mark as failed.
+ * @param why
+ * optional message to set on the first aborted command.
+ */
+ public static void abort(Iterable<Command> commands, @Nullable String why) {
+ if (why == null || why.isEmpty()) {
+ why = JGitText.get().transactionAborted;
+ }
+ for (Command c : commands) {
+ if (c.getResult() == NOT_ATTEMPTED) {
+ c.setResult(REJECTED_OTHER_REASON, why);
+ why = JGitText.get().transactionAborted;
+ }
+ }
+ }
+
private final Ref oldRef;
private final Ref newRef;
private final ReceiveCommand cmd;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java
index 43eec5185b..85690c8ca5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java
@@ -55,7 +55,6 @@ import static org.eclipse.jgit.lib.Ref.Storage.NEW;
import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
-import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
import java.io.IOException;
@@ -248,7 +247,8 @@ public class RefTree {
if (!isValidRef(cmd)) {
cmd.setResult(REJECTED_OTHER_REASON,
JGitText.get().funnyRefname);
- return abort(cmdList);
+ Command.abort(cmdList, null);
+ return false;
}
apply(ed, cmd);
}
@@ -264,9 +264,11 @@ public class RefTree {
break;
}
}
- return abort(cmdList);
+ Command.abort(cmdList, null);
+ return false;
} catch (LockFailureException e) {
- return abort(cmdList);
+ Command.abort(cmdList, null);
+ return false;
}
}
@@ -342,19 +344,6 @@ public class RefTree {
}
}
- private static boolean abort(Iterable<Command> cmdList) {
- for (Command cmd : cmdList) {
- if (cmd.getResult() == NOT_ATTEMPTED) {
- reject(cmd, JGitText.get().transactionAborted);
- }
- }
- return false;
- }
-
- private static void reject(Command cmd, String msg) {
- cmd.setResult(REJECTED_OTHER_REASON, msg);
- }
-
/**
* Convert a path name in a RefTree to the reference name known by Git.
*
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
index 0cedea94d0..a55a9f51e7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
@@ -98,7 +98,7 @@ class RefTreeBatch extends BatchRefUpdate {
}
if (c.getType() == UPDATE_NONFASTFORWARD) {
c.setResult(REJECTED_NONFASTFORWARD);
- reject();
+ ReceiveCommand.abort(getCommands());
return;
}
}
@@ -108,15 +108,6 @@ class RefTreeBatch extends BatchRefUpdate {
execute(rw, todo);
}
- private void reject() {
- String aborted = JGitText.get().transactionAborted;
- for (ReceiveCommand c : getCommands()) {
- if (c.getResult() == NOT_ATTEMPTED) {
- c.setResult(REJECTED_OTHER_REASON, aborted);
- }
- }
- }
-
void init(RevWalk rw) throws IOException {
src = refdb.getBootstrap().exactRef(refdb.getTxnCommitted());
if (src != null && src.getObjectId() != null) {
@@ -150,13 +141,13 @@ class RefTreeBatch extends BatchRefUpdate {
void execute(RevWalk rw, List<Command> todo) throws IOException {
for (Command c : todo) {
if (c.getResult() != NOT_ATTEMPTED) {
- reject(todo, JGitText.get().transactionAborted);
+ Command.abort(todo, null);
return;
}
if (refdb.conflictsWithBootstrap(c.getRefName())) {
c.setResult(REJECTED_OTHER_REASON, MessageFormat
.format(JGitText.get().invalidRefName, c.getRefName()));
- reject(todo, JGitText.get().transactionAborted);
+ Command.abort(todo, null);
return;
}
}
@@ -210,7 +201,7 @@ class RefTreeBatch extends BatchRefUpdate {
c.setResult(OK);
}
} else {
- reject(todo, commit.getResult().name());
+ Command.abort(todo, commit.getResult().name());
}
}
@@ -228,13 +219,4 @@ class RefTreeBatch extends BatchRefUpdate {
u.addCommand(commit);
u.execute(rw, NullProgressMonitor.INSTANCE);
}
-
- private static void reject(List<Command> todo, String msg) {
- for (Command c : todo) {
- if (c.getResult() == NOT_ATTEMPTED) {
- c.setResult(REJECTED_OTHER_REASON, msg);
- msg = JGitText.get().transactionAborted;
- }
- }
- }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
deleted file mode 100644
index 6811417ee0..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-
-/**
- * A representation of a file (blob) object in a {@link Tree}.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class FileTreeEntry extends TreeEntry {
- private FileMode mode;
-
- /**
- * Constructor for a File (blob) object.
- *
- * @param parent
- * The {@link Tree} holding this object (or null)
- * @param id
- * the SHA-1 of the blob (or null for a yet unhashed file)
- * @param nameUTF8
- * raw object name in the parent tree
- * @param execute
- * true if the executable flag is set
- */
- public FileTreeEntry(final Tree parent, final ObjectId id,
- final byte[] nameUTF8, final boolean execute) {
- super(parent, id, nameUTF8);
- setExecutable(execute);
- }
-
- public FileMode getMode() {
- return mode;
- }
-
- /**
- * @return true if this file is executable
- */
- public boolean isExecutable() {
- return getMode().equals(FileMode.EXECUTABLE_FILE);
- }
-
- /**
- * @param execute set/reset the executable flag
- */
- public void setExecutable(final boolean execute) {
- mode = execute ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE;
- }
-
- /**
- * @return an {@link ObjectLoader} that will return the data
- * @throws IOException
- */
- public ObjectLoader openReader() throws IOException {
- return getRepository().open(getId(), Constants.OBJ_BLOB);
- }
-
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(getId()));
- r.append(' ');
- r.append(isExecutable() ? 'X' : 'F');
- r.append(' ');
- r.append(getFullName());
- return r.toString();
- }
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
deleted file mode 100644
index 43bd489dc0..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
- * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.EntryExistsException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.ObjectWritingException;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * A representation of a Git tree entry. A Tree is a directory in Git.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class Tree extends TreeEntry {
- private static final TreeEntry[] EMPTY_TREE = {};
-
- /**
- * Compare two names represented as bytes. Since git treats names of trees and
- * blobs differently we have one parameter that represents a '/' for trees. For
- * other objects the value should be NUL. The names are compare by their positive
- * byte value (0..255).
- *
- * A blob and a tree with the same name will not compare equal.
- *
- * @param a name
- * @param b name
- * @param lasta '/' if a is a tree, else NUL
- * @param lastb '/' if b is a tree, else NUL
- *
- * @return &lt; 0 if a is sorted before b, 0 if they are the same, else b
- */
- public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) {
- return compareNames(a, b, 0, b.length, lasta, lastb);
- }
-
- private static final int compareNames(final byte[] a, final byte[] nameUTF8,
- final int nameStart, final int nameEnd, final int lasta, int lastb) {
- int j,k;
- for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) {
- final int aj = a[j] & 0xff;
- final int bk = nameUTF8[k] & 0xff;
- if (aj < bk)
- return -1;
- else if (aj > bk)
- return 1;
- }
- if (j < a.length) {
- int aj = a[j]&0xff;
- if (aj < lastb)
- return -1;
- else if (aj > lastb)
- return 1;
- else
- if (j == a.length - 1)
- return 0;
- else
- return -1;
- }
- if (k < nameEnd) {
- int bk = nameUTF8[k] & 0xff;
- if (lasta < bk)
- return -1;
- else if (lasta > bk)
- return 1;
- else
- if (k == nameEnd - 1)
- return 0;
- else
- return 1;
- }
- if (lasta < lastb)
- return -1;
- else if (lasta > lastb)
- return 1;
-
- final int namelength = nameEnd - nameStart;
- if (a.length == namelength)
- return 0;
- else if (a.length < namelength)
- return -1;
- else
- return 1;
- }
-
- private static final byte[] substring(final byte[] s, final int nameStart,
- final int nameEnd) {
- if (nameStart == 0 && nameStart == s.length)
- return s;
- final byte[] n = new byte[nameEnd - nameStart];
- System.arraycopy(s, nameStart, n, 0, n.length);
- return n;
- }
-
- private static final int binarySearch(final TreeEntry[] entries,
- final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) {
- if (entries.length == 0)
- return -1;
- int high = entries.length;
- int low = 0;
- do {
- final int mid = (low + high) >>> 1;
- final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8,
- nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last);
- if (cmp < 0)
- low = mid + 1;
- else if (cmp == 0)
- return mid;
- else
- high = mid;
- } while (low < high);
- return -(low + 1);
- }
-
- private final Repository db;
-
- private TreeEntry[] contents;
-
- /**
- * Constructor for a new Tree
- *
- * @param repo The repository that owns the Tree.
- */
- public Tree(final Repository repo) {
- super(null, null, null);
- db = repo;
- contents = EMPTY_TREE;
- }
-
- /**
- * Construct a Tree object with known content and hash value
- *
- * @param repo
- * @param myId
- * @param raw
- * @throws IOException
- */
- public Tree(final Repository repo, final ObjectId myId, final byte[] raw)
- throws IOException {
- super(null, myId, null);
- db = repo;
- readTree(raw);
- }
-
- /**
- * Construct a new Tree under another Tree
- *
- * @param parent
- * @param nameUTF8
- */
- public Tree(final Tree parent, final byte[] nameUTF8) {
- super(parent, null, nameUTF8);
- db = parent.getRepository();
- contents = EMPTY_TREE;
- }
-
- /**
- * Construct a Tree with a known SHA-1 under another tree. Data is not yet
- * specified and will have to be loaded on demand.
- *
- * @param parent
- * @param id
- * @param nameUTF8
- */
- public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) {
- super(parent, id, nameUTF8);
- db = parent.getRepository();
- }
-
- public FileMode getMode() {
- return FileMode.TREE;
- }
-
- /**
- * @return true if this Tree is the top level Tree.
- */
- public boolean isRoot() {
- return getParent() == null;
- }
-
- public Repository getRepository() {
- return db;
- }
-
- /**
- * @return true of the data of this Tree is loaded
- */
- public boolean isLoaded() {
- return contents != null;
- }
-
- /**
- * Forget the in-memory data for this tree.
- */
- public void unload() {
- if (isModified())
- throw new IllegalStateException(JGitText.get().cannotUnloadAModifiedTree);
- contents = null;
- }
-
- /**
- * Adds a new or existing file with the specified name to this tree.
- * Trees are added if necessary as the name may contain '/':s.
- *
- * @param name Name
- * @return a {@link FileTreeEntry} for the added file.
- * @throws IOException
- */
- public FileTreeEntry addFile(final String name) throws IOException {
- return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0);
- }
-
- /**
- * Adds a new or existing file with the specified name to this tree.
- * Trees are added if necessary as the name may contain '/':s.
- *
- * @param s an array containing the name
- * @param offset when the name starts in the tree.
- *
- * @return a {@link FileTreeEntry} for the added file.
- * @throws IOException
- */
- public FileTreeEntry addFile(final byte[] s, final int offset)
- throws IOException {
- int slash;
- int p;
-
- for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
- // search for path component terminator
- }
-
- ensureLoaded();
- byte xlast = slash<s.length ? (byte)'/' : 0;
- p = binarySearch(contents, s, xlast, offset, slash);
- if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
- return ((Tree) contents[p]).addFile(s, slash + 1);
-
- final byte[] newName = substring(s, offset, slash);
- if (p >= 0)
- throw new EntryExistsException(RawParseUtils.decode(newName));
- else if (slash < s.length) {
- final Tree t = new Tree(this, newName);
- insertEntry(p, t);
- return t.addFile(s, slash + 1);
- } else {
- final FileTreeEntry f = new FileTreeEntry(this, null, newName,
- false);
- insertEntry(p, f);
- return f;
- }
- }
-
- /**
- * Adds a new or existing Tree with the specified name to this tree.
- * Trees are added if necessary as the name may contain '/':s.
- *
- * @param name Name
- * @return a {@link FileTreeEntry} for the added tree.
- * @throws IOException
- */
- public Tree addTree(final String name) throws IOException {
- return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0);
- }
-
- /**
- * Adds a new or existing Tree with the specified name to this tree.
- * Trees are added if necessary as the name may contain '/':s.
- *
- * @param s an array containing the name
- * @param offset when the name starts in the tree.
- *
- * @return a {@link FileTreeEntry} for the added tree.
- * @throws IOException
- */
- public Tree addTree(final byte[] s, final int offset) throws IOException {
- int slash;
- int p;
-
- for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
- // search for path component terminator
- }
-
- ensureLoaded();
- p = binarySearch(contents, s, (byte)'/', offset, slash);
- if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
- return ((Tree) contents[p]).addTree(s, slash + 1);
-
- final byte[] newName = substring(s, offset, slash);
- if (p >= 0)
- throw new EntryExistsException(RawParseUtils.decode(newName));
-
- final Tree t = new Tree(this, newName);
- insertEntry(p, t);
- return slash == s.length ? t : t.addTree(s, slash + 1);
- }
-
- /**
- * Add the specified tree entry to this tree.
- *
- * @param e
- * @throws IOException
- */
- public void addEntry(final TreeEntry e) throws IOException {
- final int p;
-
- ensureLoaded();
- p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length);
- if (p < 0) {
- e.attachParent(this);
- insertEntry(p, e);
- } else {
- throw new EntryExistsException(e.getName());
- }
- }
-
- private void insertEntry(int p, final TreeEntry e) {
- final TreeEntry[] c = contents;
- final TreeEntry[] n = new TreeEntry[c.length + 1];
- p = -(p + 1);
- for (int k = c.length - 1; k >= p; k--)
- n[k + 1] = c[k];
- n[p] = e;
- for (int k = p - 1; k >= 0; k--)
- n[k] = c[k];
- contents = n;
- setModified();
- }
-
- void removeEntry(final TreeEntry e) {
- final TreeEntry[] c = contents;
- final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0,
- e.getNameUTF8().length);
- if (p >= 0) {
- final TreeEntry[] n = new TreeEntry[c.length - 1];
- for (int k = c.length - 1; k > p; k--)
- n[k - 1] = c[k];
- for (int k = p - 1; k >= 0; k--)
- n[k] = c[k];
- contents = n;
- setModified();
- }
- }
-
- /**
- * @return number of members in this tree
- * @throws IOException
- */
- public int memberCount() throws IOException {
- ensureLoaded();
- return contents.length;
- }
-
- /**
- * Return all members of the tree sorted in Git order.
- *
- * Entries are sorted by the numerical unsigned byte
- * values with (sub)trees having an implicit '/'. An
- * example of a tree with three entries. a:b is an
- * actual file name here.
- *
- * <p>
- * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b
- * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a
- * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b
- *
- * @return all entries in this Tree, sorted.
- * @throws IOException
- */
- public TreeEntry[] members() throws IOException {
- ensureLoaded();
- final TreeEntry[] c = contents;
- if (c.length != 0) {
- final TreeEntry[] r = new TreeEntry[c.length];
- for (int k = c.length - 1; k >= 0; k--)
- r[k] = c[k];
- return r;
- } else
- return c;
- }
-
- private boolean exists(final String s, byte slast) throws IOException {
- return findMember(s, slast) != null;
- }
-
- /**
- * @param path to the tree.
- * @return true if a tree with the specified path can be found under this
- * tree.
- * @throws IOException
- */
- public boolean existsTree(String path) throws IOException {
- return exists(path,(byte)'/');
- }
-
- /**
- * @param path of the non-tree entry.
- * @return true if a blob, symlink, or gitlink with the specified name
- * can be found under this tree.
- * @throws IOException
- */
- public boolean existsBlob(String path) throws IOException {
- return exists(path,(byte)0);
- }
-
- private TreeEntry findMember(final String s, byte slast) throws IOException {
- return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0);
- }
-
- private TreeEntry findMember(final byte[] s, final byte slast, final int offset)
- throws IOException {
- int slash;
- int p;
-
- for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
- // search for path component terminator
- }
-
- ensureLoaded();
- byte xlast = slash<s.length ? (byte)'/' : slast;
- p = binarySearch(contents, s, xlast, offset, slash);
- if (p >= 0) {
- final TreeEntry r = contents[p];
- if (slash < s.length-1)
- return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1)
- : null;
- return r;
- }
- return null;
- }
-
- /**
- * @param s
- * blob name
- * @return a {@link TreeEntry} representing an object with the specified
- * relative path.
- * @throws IOException
- */
- public TreeEntry findBlobMember(String s) throws IOException {
- return findMember(s,(byte)0);
- }
-
- /**
- * @param s Tree Name
- * @return a Tree with the name s or null
- * @throws IOException
- */
- public TreeEntry findTreeMember(String s) throws IOException {
- return findMember(s,(byte)'/');
- }
-
- private void ensureLoaded() throws IOException, MissingObjectException {
- if (!isLoaded()) {
- ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE);
- readTree(ldr.getCachedBytes());
- }
- }
-
- private void readTree(final byte[] raw) throws IOException {
- final int rawSize = raw.length;
- int rawPtr = 0;
- TreeEntry[] temp;
- int nextIndex = 0;
-
- while (rawPtr < rawSize) {
- while (rawPtr < rawSize && raw[rawPtr] != 0)
- rawPtr++;
- rawPtr++;
- rawPtr += Constants.OBJECT_ID_LENGTH;
- nextIndex++;
- }
-
- temp = new TreeEntry[nextIndex];
- rawPtr = 0;
- nextIndex = 0;
- while (rawPtr < rawSize) {
- int c = raw[rawPtr++];
- if (c < '0' || c > '7')
- throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidEntryMode);
- int mode = c - '0';
- for (;;) {
- c = raw[rawPtr++];
- if (' ' == c)
- break;
- else if (c < '0' || c > '7')
- throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidMode);
- mode <<= 3;
- mode += c - '0';
- }
-
- int nameLen = 0;
- while (raw[rawPtr + nameLen] != 0)
- nameLen++;
- final byte[] name = new byte[nameLen];
- System.arraycopy(raw, rawPtr, name, 0, nameLen);
- rawPtr += nameLen + 1;
-
- final ObjectId id = ObjectId.fromRaw(raw, rawPtr);
- rawPtr += Constants.OBJECT_ID_LENGTH;
-
- final TreeEntry ent;
- if (FileMode.REGULAR_FILE.equals(mode))
- ent = new FileTreeEntry(this, id, name, false);
- else if (FileMode.EXECUTABLE_FILE.equals(mode))
- ent = new FileTreeEntry(this, id, name, true);
- else if (FileMode.TREE.equals(mode))
- ent = new Tree(this, id, name);
- else if (FileMode.SYMLINK.equals(mode))
- ent = new SymlinkTreeEntry(this, id, name);
- else if (FileMode.GITLINK.equals(mode))
- ent = new GitlinkTreeEntry(this, id, name);
- else
- throw new CorruptObjectException(getId(), MessageFormat.format(
- JGitText.get().corruptObjectInvalidMode2, Integer.toOctalString(mode)));
- temp[nextIndex++] = ent;
- }
-
- contents = temp;
- }
-
- /**
- * Format this Tree in canonical format.
- *
- * @return canonical encoding of the tree object.
- * @throws IOException
- * the tree cannot be loaded, or its not in a writable state.
- */
- public byte[] format() throws IOException {
- TreeFormatter fmt = new TreeFormatter();
- for (TreeEntry e : members()) {
- ObjectId id = e.getId();
- if (id == null)
- throw new ObjectWritingException(MessageFormat.format(JGitText
- .get().objectAtPathDoesNotHaveId, e.getFullName()));
-
- fmt.append(e.getNameUTF8(), e.getMode(), id);
- }
- return fmt.toByteArray();
- }
-
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(getId()));
- r.append(" T "); //$NON-NLS-1$
- r.append(getFullName());
- return r.toString();
- }
-
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java
deleted file mode 100644
index a1ffa68056..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * This class represents an entry in a tree, like a blob or another tree.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public abstract class TreeEntry implements Comparable {
- private byte[] nameUTF8;
-
- private Tree parent;
-
- private ObjectId id;
-
- /**
- * Construct a named tree entry.
- *
- * @param myParent
- * @param myId
- * @param myNameUTF8
- */
- protected TreeEntry(final Tree myParent, final ObjectId myId,
- final byte[] myNameUTF8) {
- nameUTF8 = myNameUTF8;
- parent = myParent;
- id = myId;
- }
-
- /**
- * @return parent of this tree.
- */
- public Tree getParent() {
- return parent;
- }
-
- /**
- * Delete this entry.
- */
- public void delete() {
- getParent().removeEntry(this);
- detachParent();
- }
-
- /**
- * Detach this entry from it's parent.
- */
- public void detachParent() {
- parent = null;
- }
-
- void attachParent(final Tree p) {
- parent = p;
- }
-
- /**
- * @return the repository owning this entry.
- */
- public Repository getRepository() {
- return getParent().getRepository();
- }
-
- /**
- * @return the raw byte name of this entry.
- */
- public byte[] getNameUTF8() {
- return nameUTF8;
- }
-
- /**
- * @return the name of this entry.
- */
- public String getName() {
- if (nameUTF8 != null)
- return RawParseUtils.decode(nameUTF8);
- return null;
- }
-
- /**
- * Rename this entry.
- *
- * @param n The new name
- * @throws IOException
- */
- public void rename(final String n) throws IOException {
- rename(Constants.encode(n));
- }
-
- /**
- * Rename this entry.
- *
- * @param n The new name
- * @throws IOException
- */
- public void rename(final byte[] n) throws IOException {
- final Tree t = getParent();
- if (t != null) {
- delete();
- }
- nameUTF8 = n;
- if (t != null) {
- t.addEntry(this);
- }
- }
-
- /**
- * @return true if this entry is new or modified since being loaded.
- */
- public boolean isModified() {
- return getId() == null;
- }
-
- /**
- * Mark this entry as modified.
- */
- public void setModified() {
- setId(null);
- }
-
- /**
- * @return SHA-1 of this tree entry (null for new unhashed entries)
- */
- public ObjectId getId() {
- return id;
- }
-
- /**
- * Set (update) the SHA-1 of this entry. Invalidates the id's of all
- * entries above this entry as they will have to be recomputed.
- *
- * @param n SHA-1 for this entry.
- */
- public void setId(final ObjectId n) {
- // If we have a parent and our id is being cleared or changed then force
- // the parent's id to become unset as it depends on our id.
- //
- final Tree p = getParent();
- if (p != null && id != n) {
- if ((id == null && n != null) || (id != null && n == null)
- || !id.equals(n)) {
- p.setId(null);
- }
- }
-
- id = n;
- }
-
- /**
- * @return repository relative name of this entry
- */
- public String getFullName() {
- final StringBuilder r = new StringBuilder();
- appendFullName(r);
- return r.toString();
- }
-
- /**
- * @return repository relative name of the entry
- * FIXME better encoding
- */
- public byte[] getFullNameUTF8() {
- return getFullName().getBytes();
- }
-
- public int compareTo(final Object o) {
- if (this == o)
- return 0;
- if (o instanceof TreeEntry)
- return Tree.compareNames(nameUTF8, ((TreeEntry) o).nameUTF8, lastChar(this), lastChar((TreeEntry)o));
- return -1;
- }
-
- /**
- * Helper for accessing tree/blob methods.
- *
- * @param treeEntry
- * @return '/' for Tree entries and NUL for non-treeish objects.
- */
- final public static int lastChar(TreeEntry treeEntry) {
- if (!(treeEntry instanceof Tree))
- return '\0';
- else
- return '/';
- }
-
- /**
- * @return mode (type of object)
- */
- public abstract FileMode getMode();
-
- private void appendFullName(final StringBuilder r) {
- final TreeEntry p = getParent();
- final String n = getName();
- if (p != null) {
- p.appendFullName(r);
- if (r.length() > 0) {
- r.append('/');
- }
- }
- if (n != null) {
- r.append(n);
- }
- }
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index 728643e925..a20e652553 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -1453,10 +1453,7 @@ public abstract class BaseReceivePack {
* @since 3.6
*/
protected void failPendingCommands() {
- for (ReceiveCommand cmd : commands) {
- if (cmd.getResult() == Result.NOT_ATTEMPTED)
- cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted);
- }
+ ReceiveCommand.abort(commands);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
index 0ff9fcea74..da288ec31e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -59,8 +59,7 @@ import org.eclipse.jgit.lib.Ref;
*
* @see Transport
*/
-public interface Connection {
-
+public interface Connection extends AutoCloseable {
/**
* Get the complete map of refs advertised as available for fetching or
* pushing.
@@ -108,6 +107,10 @@ public interface Connection {
* <p>
* If additional messages were produced by the remote peer, these should
* still be retained in the connection instance for {@link #getMessages()}.
+ * <p>
+ * {@code AutoClosable.close()} declares that it throws {@link Exception}.
+ * Implementers shouldn't throw checked exceptions. This override narrows
+ * the signature to prevent them from doing so.
*/
public void close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
index d9e0b937e8..2593ba556d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
@@ -256,6 +256,16 @@ public class Daemon {
}
/**
+ * Get the factory used to construct per-request ReceivePack.
+ *
+ * @return the factory.
+ * @since 4.2
+ */
+ public ReceivePackFactory<DaemonClient> getReceivePackFactory() {
+ return receivePackFactory;
+ }
+
+ /**
* Set the factory to construct and configure per-request ReceivePack.
*
* @param factory
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
index 5702b6d7b9..2b21c4a8fe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -43,6 +43,9 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -168,6 +171,25 @@ public class ReceiveCommand {
return filter((Iterable<ReceiveCommand>) commands, want);
}
+ /**
+ * Set unprocessed commands as failed due to transaction aborted.
+ * <p>
+ * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to
+ * {@link Result#REJECTED_OTHER_REASON}.
+ *
+ * @param commands
+ * commands to mark as failed.
+ * @since 4.2
+ */
+ public static void abort(Iterable<ReceiveCommand> commands) {
+ for (ReceiveCommand c : commands) {
+ if (c.getResult() == NOT_ATTEMPTED) {
+ c.setResult(REJECTED_OTHER_REASON,
+ JGitText.get().transactionAborted);
+ }
+ }
+ }
+
private final ObjectId oldId;
private final ObjectId newId;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index 6af153cbc9..9e6d1f68f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -98,7 +98,7 @@ import org.eclipse.jgit.storage.pack.PackConfig;
* Transport instances and the connections they create are not thread-safe.
* Callers must ensure a transport is accessed by only one thread at a time.
*/
-public abstract class Transport {
+public abstract class Transport implements AutoCloseable {
/** Type of operation a Transport is being opened for. */
public enum Operation {
/** Transport is to fetch objects locally. */
@@ -1353,6 +1353,10 @@ public abstract class Transport {
* must close that network socket, disconnecting the two peers. If the
* remote repository is actually local (same system) this method must close
* any open file handles used to read the "remote" repository.
+ * <p>
+ * {@code AutoClosable.close()} declares that it throws {@link Exception}.
+ * Implementers shouldn't throw checked exceptions. This override narrows
+ * the signature to prevent them from doing so.
*/
public abstract void close();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index 5ce39c2e03..3ee2feb140 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -715,7 +715,7 @@ public class URIish implements Serializable {
*/
public String getHumanishName() throws IllegalArgumentException {
String s = getPath();
- if ("/".equals(s) || "".equals(s)) //$NON-NLS-1$
+ if ("/".equals(s) || "".equals(s)) //$NON-NLS-1$ //$NON-NLS-2$
s = getHost();
if (s == null) // $NON-NLS-1$
throw new IllegalArgumentException();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 83fada4f95..5cd713da78 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -574,18 +574,13 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* @param p
* an iterator to walk over. The iterator should be new, with no
* parent, and should still be positioned before the first entry.
- * The tree which the iterator operates on must have the same root
- * as other trees in the walk.
- *
+ * The tree which the iterator operates on must have the same
+ * root as other trees in the walk.
* @return position of this tree within the walker.
- * @throws CorruptObjectException
- * the iterator was unable to obtain its first entry, due to
- * possible data corruption within the backing data store.
*/
- public int addTree(final AbstractTreeIterator p)
- throws CorruptObjectException {
- final int n = trees.length;
- final AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1];
+ public int addTree(AbstractTreeIterator p) {
+ int n = trees.length;
+ AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1];
System.arraycopy(trees, 0, newTrees, 0, n);
newTrees[n] = p;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
index 35fc99e54e..e14096e598 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -42,7 +42,6 @@
*/
package org.eclipse.jgit.util;
-import java.io.IOException;
import java.util.regex.Pattern;
import org.eclipse.jgit.lib.Constants;
@@ -90,12 +89,10 @@ public class ChangeIdUtil {
* The commit message
* @return the change id SHA1 string (without the 'I') or null if the
* message is not complete enough
- * @throws IOException
*/
public static ObjectId computeChangeId(final ObjectId treeId,
final ObjectId firstParentId, final PersonIdent author,
- final PersonIdent committer, final String message)
- throws IOException {
+ final PersonIdent committer, final String message) {
String cleanMessage = clean(message);
if (cleanMessage.length() == 0)
return null;
@@ -116,8 +113,7 @@ public class ChangeIdUtil {
b.append("\n\n"); //$NON-NLS-1$
b.append(cleanMessage);
try (ObjectInserter f = new ObjectInserter.Formatter()) {
- return f.idFor(Constants.OBJ_COMMIT, //
- b.toString().getBytes(Constants.CHARACTER_ENCODING));
+ return f.idFor(Constants.OBJ_COMMIT, Constants.encode(b.toString()));
}
}
diff --git a/tools/version.sh b/tools/version.sh
index a2f81290d6..81ffe06b25 100755
--- a/tools/version.sh
+++ b/tools/version.sh
@@ -149,17 +149,6 @@ perl -pi~ -e '
$seen_version = 0;
$old_argv = $ARGV;
}
- if ($seen_version < 5) {
- $seen_version++ if
- s{<(version)>.*</\1>}{<${1}>'"$POM_V"'</${1}>};
- }
- ' org.eclipse.jgit.packaging/org.eclipse.jgit.updatesite/pom.xml
-
-perl -pi~ -e '
- if ($ARGV ne $old_argv) {
- $seen_version = 0;
- $old_argv = $ARGV;
- }
if (!$seen_version) {
$seen_version = 1 if
s{<(version)>.*</\1>}{<${1}>'"$POM_V"'</${1}>};