summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java44
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java17
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java28
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java53
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java175
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java116
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java122
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java67
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java241
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java69
-rw-r--r--pom.xml2
19 files changed, 885 insertions, 197 deletions
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
index 41217d9971..c88670ec97 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
@@ -76,6 +76,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.errors.UnpackException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.InternalHttpServerGlue;
@@ -200,7 +201,7 @@ class ReceivePackServlet extends HttpServlet {
consumeRequestBody(req);
out.close();
- } catch (UnpackException e) {
+ } catch (UnpackException | PackProtocolException e) {
// This should be already reported to the client.
log(rp.getRepository(), e.getCause());
consumeRequestBody(req);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
index 0742504d23..bbd41237e2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
@@ -45,13 +45,16 @@ package org.eclipse.jgit.internal.storage.file;
import static org.junit.Assert.assertEquals;
+import java.io.File;
import java.io.IOException;
import java.util.Collection;
+import java.util.Date;
import java.util.Iterator;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.pack.PackConfig;
+import org.junit.Test;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
@@ -176,6 +179,47 @@ public class GcBasicPackingTest extends GcTestCase {
}
}
+ @Test
+ public void testDonePruneTooYoungPacks() throws Exception {
+ BranchBuilder bb = tr.branch("refs/heads/master");
+ bb.commit().message("M").add("M", "M").create();
+
+ gc.setExpireAgeMillis(0);
+ gc.gc();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(3, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+ File oldPackfile = tr.getRepository().getObjectDatabase().getPacks()
+ .iterator().next().getPackFile();
+
+ fsTick();
+ bb.commit().message("B").add("B", "Q").create();
+
+ // The old packfile is too young to be deleted. We should end up with
+ // two pack files
+ gc.setExpire(new Date(oldPackfile.lastModified() - 1));
+ gc.gc();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ // if objects exist in multiple packFiles then they are counted multiple
+ // times
+ assertEquals(9, stats.numberOfPackedObjects);
+ assertEquals(2, stats.numberOfPackFiles);
+
+ // repack again but now without a grace period for packfiles. We should
+ // end up with one packfile
+ gc.setExpireAgeMillis(0);
+ gc.gc();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ // if objects exist in multiple packFiles then they are counted multiple
+ // times
+ assertEquals(6, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+
+ }
+
private void configureGc(GC myGc, boolean aggressive) {
PackConfig pconfig = new PackConfig(repo);
if (aggressive) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java
index abf57d6cd3..2198b87aa5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java
@@ -49,6 +49,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
+
import org.junit.Test;
public class ObjectIdTest {
@@ -124,6 +126,21 @@ public class ObjectIdTest {
assertEquals(x.toLowerCase(), oid.name());
}
+ @Test(expected = InvalidObjectIdException.class)
+ public void testFromString_short() {
+ ObjectId.fromString("cafe1234");
+ }
+
+ @Test(expected = InvalidObjectIdException.class)
+ public void testFromString_nonHex() {
+ ObjectId.fromString("0123456789abcdefghij0123456789abcdefghij");
+ }
+
+ @Test(expected = InvalidObjectIdException.class)
+ public void testFromString_shortNonHex() {
+ ObjectId.fromString("6789ghij");
+ }
+
@Test
public void testGetByte() {
byte[] raw = new byte[20];
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
index 0cab987e6a..df9e0294e8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
@@ -43,11 +43,14 @@
package org.eclipse.jgit.lib;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -59,6 +62,7 @@ import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.junit.Test;
+@SuppressWarnings("boxing")
public class RepositoryCacheTest extends RepositoryTestCase {
@Test
public void testNonBareFileKey() throws IOException {
@@ -147,4 +151,28 @@ public class RepositoryCacheTest extends RepositoryTestCase {
d2.close();
d2.close();
}
+
+ @Test
+ public void testGetRegisteredWhenEmpty() {
+ assertThat(RepositoryCache.getRegisteredKeys().size(), is(0));
+ }
+
+ @Test
+ public void testGetRegistered() {
+ RepositoryCache.register(db);
+
+ assertThat(RepositoryCache.getRegisteredKeys(),
+ hasItem(FileKey.exact(db.getDirectory(), db.getFS())));
+ assertThat(RepositoryCache.getRegisteredKeys().size(), is(1));
+ }
+
+ @Test
+ public void testUnregister() {
+ RepositoryCache.register(db);
+ RepositoryCache
+ .unregister(FileKey.exact(db.getDirectory(), db.getFS()));
+
+ assertThat(RepositoryCache.getRegisteredKeys().size(), is(0));
+ }
+
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java
index 98164d9335..7578c6e3eb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java
@@ -13,17 +13,17 @@
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * 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.
+ * 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,
@@ -43,28 +43,43 @@
package org.eclipse.jgit.transport;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
/** Tests for base receive-pack utilities. */
public class BaseReceivePackTest {
@Test
- public void chomp() {
- assertEquals("foo", BaseReceivePack.chomp("foo"));
- assertEquals("foo", BaseReceivePack.chomp("foo\n"));
- assertEquals("foo\n", BaseReceivePack.chomp("foo\n\n"));
- }
-
- @Test
- public void parseCommand() {
- String input = "0000000000000000000000000000000000000000"
- + " deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
- + " refs/heads/master";
- ReceiveCommand cmd = BaseReceivePack.parseCommand(input);
+ public void parseCommand() throws Exception {
+ String o = "0000000000000000000000000000000000000000";
+ String n = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ String r = "refs/heads/master";
+ ReceiveCommand cmd = BaseReceivePack.parseCommand(o + " " + n + " " + r);
assertEquals(ObjectId.zeroId(), cmd.getOldId());
assertEquals("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
cmd.getNewId().name());
assertEquals("refs/heads/master", cmd.getRefName());
+
+ assertParseCommandFails(null);
+ assertParseCommandFails("");
+ assertParseCommandFails(o.substring(35) + " " + n.substring(35)
+ + " " + r + "\n");
+ assertParseCommandFails(o + " " + n + " " + r + "\n");
+ assertParseCommandFails(o + " " + n + " " + "refs^foo");
+ assertParseCommandFails(o + " " + n.substring(10) + " " + r);
+ assertParseCommandFails(o.substring(10) + " " + n + " " + r);
+ assertParseCommandFails("X" + o.substring(1) + " " + n + " " + r);
+ assertParseCommandFails(o + " " + "X" + n.substring(1) + " " + r);
+ }
+
+ private void assertParseCommandFails(String input) {
+ try {
+ BaseReceivePack.parseCommand(input);
+ fail();
+ } catch (PackProtocolException e) {
+ // Expected.
+ }
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java
index 9c157c3379..3a4b00d876 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java
@@ -45,6 +45,7 @@ package org.eclipse.jgit.transport;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -52,6 +53,8 @@ import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
@@ -60,6 +63,7 @@ import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
import org.junit.Before;
import org.junit.Test;
@@ -87,6 +91,30 @@ public class PushCertificateParserTest {
+ "0020-----END PGP SIGNATURE-----\n"
+ "0012push-cert-end\n";
+ // Same push certificate, with all trailing newlines stripped.
+ // (Note that the canonical signed payload is the same, so the same signature
+ // is still valid.)
+ private static final String INPUT_NO_NEWLINES = "001bcertificate version 0.1"
+ + "0040pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700"
+ + "0023pushee git://localhost/repo.git"
+ + "0029nonce 1433954361-bde756572d665bba81d8"
+ + "0004"
+ + "00670000000000000000000000000000000000000000"
+ + " 6c2b981a177396fb47345b7df3e4d3f854c6bea7"
+ + " refs/heads/master"
+ + "0021-----BEGIN PGP SIGNATURE-----"
+ + "0015Version: GnuPG v1"
+ + "0004"
+ + "0044iQEcBAABAgAGBQJVeGg5AAoJEPfTicJkUdPkUggH/RKAeI9/i/LduuiqrL/SSdIa"
+ + "00449tYaSqJKLbXz63M/AW4Sp+4u+dVCQvnAt/a35CVEnpZz6hN4Kn/tiswOWVJf4CO7"
+ + "0044htNubGs5ZMwvD6sLYqKAnrM3WxV/2TbbjzjZW6Jkidz3jz/WRT4SmjGYiEO7aA+V"
+ + "00444ZdIS9f7sW5VsHHYlNThCA7vH8Uu48bUovFXyQlPTX0pToSgrWV3JnTxDNxfn3iG"
+ + "0044IL0zTY/qwVCdXgFownLcs6J050xrrBWIKqfcWr3u4D2aCLyR0v+S/KArr7ulZygY"
+ + "0044+SOklImn8TAZiNxhWtA6ens66IiammUkZYFv7SSzoPLFZT4dC84SmGPWgf94NoQ="
+ + "0009=XFeC"
+ + "001f-----END PGP SIGNATURE-----"
+ + "0011push-cert-end";
+
private Repository db;
@Before
@@ -114,12 +142,11 @@ public class PushCertificateParserTest {
ObjectId oldId = ObjectId.zeroId();
ObjectId newId =
ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
- String rawLine =
- oldId.name() + " " + newId.name() + " refs/heads/master";
- ReceiveCommand cmd = BaseReceivePack.parseCommand(rawLine);
+ String line = oldId.name() + " " + newId.name() + " refs/heads/master";
+ ReceiveCommand cmd = BaseReceivePack.parseCommand(line);
- parser.addCommand(cmd, rawLine);
- parser.addCommand(rawLine);
+ parser.addCommand(cmd);
+ parser.addCommand(line);
assertNull(parser.build());
}
@@ -132,8 +159,8 @@ public class PushCertificateParserTest {
assertNull(parser.build());
parser.receiveHeader(pckIn, false);
- parser.addCommand(pckIn.readStringRaw());
- assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readStringRaw());
+ parser.addCommand(pckIn.readString());
+ assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
parser.receiveSignature(pckIn);
assertNull(parser.build());
}
@@ -162,8 +189,8 @@ public class PushCertificateParserTest {
PushCertificateParser parser =
new PushCertificateParser(db, newEnabledConfig());
parser.receiveHeader(pckIn, false);
- parser.addCommand(pckIn.readStringRaw());
- assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readStringRaw());
+ parser.addCommand(pckIn.readString());
+ assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
parser.receiveSignature(pckIn);
PushCertificate cert = parser.build();
@@ -190,7 +217,46 @@ public class PushCertificateParserTest {
String signature = concatPacketLines(INPUT, 6, 17);
assertTrue(signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE));
- assertTrue(signature.endsWith(PushCertificateParser.END_SIGNATURE));
+ assertTrue(signature.endsWith(PushCertificateParser.END_SIGNATURE + "\n"));
+ assertEquals(signature, cert.getSignature());
+ }
+
+ @Test
+ public void parseCertFromPktLineNoNewlines() throws Exception {
+ PacketLineIn pckIn = newPacketLineIn(INPUT_NO_NEWLINES);
+ PushCertificateParser parser =
+ new PushCertificateParser(db, newEnabledConfig());
+ parser.receiveHeader(pckIn, false);
+ parser.addCommand(pckIn.readString());
+ assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
+ parser.receiveSignature(pckIn);
+
+ PushCertificate cert = parser.build();
+ assertEquals("0.1", cert.getVersion());
+ assertEquals("Dave Borowitz", cert.getPusherIdent().getName());
+ assertEquals("dborowitz@google.com",
+ cert.getPusherIdent().getEmailAddress());
+ assertEquals(1433954361000L, cert.getPusherIdent().getWhen().getTime());
+ assertEquals(-7 * 60, cert.getPusherIdent().getTimeZoneOffset());
+ assertEquals("git://localhost/repo.git", cert.getPushee());
+ assertEquals("1433954361-bde756572d665bba81d8", cert.getNonce());
+
+ assertNotEquals(cert.getNonce(), parser.getAdvertiseNonce());
+ assertEquals(PushCertificate.NonceStatus.BAD, cert.getNonceStatus());
+
+ assertEquals(1, cert.getCommands().size());
+ ReceiveCommand cmd = cert.getCommands().get(0);
+ assertEquals("refs/heads/master", cmd.getRefName());
+ assertEquals(ObjectId.zeroId(), cmd.getOldId());
+ assertEquals("6c2b981a177396fb47345b7df3e4d3f854c6bea7",
+ cmd.getNewId().name());
+
+ // Canonical signed payload has reinserted newlines.
+ assertEquals(concatPacketLines(INPUT, 0, 6), cert.toText());
+
+ String signature = concatPacketLines(INPUT, 6, 17);
+ assertTrue(signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE));
+ assertTrue(signature.endsWith(PushCertificateParser.END_SIGNATURE + "\n"));
assertEquals(signature, cert.getSignature());
}
@@ -203,6 +269,91 @@ public class PushCertificateParserTest {
assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 4));
}
+ @Test
+ public void testConcatPacketLinesInsertsNewlines() throws Exception {
+ String input = "000bline 1\n000aline 2000bline 3\n";
+ assertEquals("line 1\n", concatPacketLines(input, 0, 1));
+ assertEquals("line 1\nline 2\n", concatPacketLines(input, 0, 2));
+ assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 3));
+ assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 4));
+ }
+
+ @Test
+ public void testParseReader() throws Exception {
+ Reader reader = new InputStreamReader(
+ new ByteArrayInputStream(
+ Constants.encode(concatPacketLines(INPUT, 0, 18))));
+ PushCertificate streamCert = PushCertificateParser.fromReader(reader);
+
+ PacketLineIn pckIn = newPacketLineIn(INPUT);
+ PushCertificateParser pckParser =
+ new PushCertificateParser(db, newEnabledConfig());
+ pckParser.receiveHeader(pckIn, false);
+ pckParser.addCommand(pckIn.readString());
+ assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
+ pckParser.receiveSignature(pckIn);
+ PushCertificate pckCert = pckParser.build();
+
+ // Nonce status is unsolicited since this was not parsed in the context of
+ // the wire protocol; as a result, certs are not actually equal.
+ assertEquals(NonceStatus.UNSOLICITED, streamCert.getNonceStatus());
+
+ assertEquals(pckCert.getVersion(), streamCert.getVersion());
+ assertEquals(pckCert.getPusherIdent().getName(),
+ streamCert.getPusherIdent().getName());
+ assertEquals(pckCert.getPusherIdent().getEmailAddress(),
+ streamCert.getPusherIdent().getEmailAddress());
+ assertEquals(pckCert.getPusherIdent().getWhen().getTime(),
+ streamCert.getPusherIdent().getWhen().getTime());
+ assertEquals(pckCert.getPusherIdent().getTimeZoneOffset(),
+ streamCert.getPusherIdent().getTimeZoneOffset());
+ assertEquals(pckCert.getPushee(), streamCert.getPushee());
+ assertEquals(pckCert.getNonce(), streamCert.getNonce());
+ assertEquals(pckCert.getSignature(), streamCert.getSignature());
+ assertEquals(pckCert.toText(), streamCert.toText());
+
+ assertEquals(pckCert.getCommands().size(), streamCert.getCommands().size());
+ ReceiveCommand pckCmd = pckCert.getCommands().get(0);
+ ReceiveCommand streamCmd = streamCert.getCommands().get(0);
+ assertEquals(pckCmd.getRefName(), streamCmd.getRefName());
+ assertEquals(pckCmd.getOldId(), streamCmd.getOldId());
+ assertEquals(pckCmd.getNewId().name(), streamCmd.getNewId().name());
+ }
+
+ @Test
+ public void testParseMultipleFromStream() throws Exception {
+ String input = concatPacketLines(INPUT, 0, 17);
+ assertFalse(input.contains(PushCertificateParser.END_CERT));
+ input += input;
+ Reader reader = new InputStreamReader(
+ new ByteArrayInputStream(Constants.encode(input)));
+
+ assertNotNull(PushCertificateParser.fromReader(reader));
+ assertNotNull(PushCertificateParser.fromReader(reader));
+ assertEquals(-1, reader.read());
+ assertNull(PushCertificateParser.fromReader(reader));
+ }
+
+ @Test
+ public void testMissingPusheeField() throws Exception {
+ // Omit pushee line from existing cert. (This means the signature would not
+ // match, but we're not verifying it here.)
+ String input = INPUT.replace("0024pushee git://localhost/repo.git\n", "");
+ assertFalse(input.contains(PushCertificateParser.PUSHEE));
+
+ PacketLineIn pckIn = newPacketLineIn(input);
+ PushCertificateParser parser =
+ new PushCertificateParser(db, newEnabledConfig());
+ parser.receiveHeader(pckIn, false);
+ parser.addCommand(pckIn.readString());
+ assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
+ parser.receiveSignature(pckIn);
+
+ PushCertificate cert = parser.build();
+ assertEquals("0.1", cert.getVersion());
+ assertNull(cert.getPushee());
+ }
+
private static String concatPacketLines(String input, int begin, int end)
throws IOException {
StringBuilder result = new StringBuilder();
@@ -211,12 +362,12 @@ public class PushCertificateParserTest {
while (i < end) {
String line;
try {
- line = pckIn.readStringRaw();
+ line = pckIn.readString();
} catch (EOFException e) {
break;
}
if (++i > begin) {
- result.append(line);
+ result.append(line).append('\n');
}
}
return result.toString();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java
new file mode 100644
index 0000000000..928fb2ed9a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015, 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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.eclipse.jgit.lib.Constants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IOReadLineTest {
+ @Parameter(0)
+ public boolean buffered;
+
+ @Parameter(1)
+ public int sizeHint;
+
+ @SuppressWarnings("boxing")
+ @Parameters(name="buffered={0}, sizeHint={1}")
+ public static Collection<Object[]> getParameters() {
+ Boolean[] bv = {false, true};
+ Integer[] sv = {-1, 0, 1, 2, 3, 4, 64};
+ Collection<Object[]> params = new ArrayList<>(bv.length * sv.length);
+ for (boolean b : bv) {
+ for (Integer s : sv) {
+ params.add(new Object[]{b, s});
+ }
+ }
+ return params;
+ }
+
+ @Test
+ public void testReadLine() throws Exception {
+ Reader r = newReader("foo\nbar\nbaz\n");
+ assertEquals("foo\n", readLine(r));
+ assertEquals("bar\n", readLine(r));
+ assertEquals("baz\n", readLine(r));
+ assertEquals("", readLine(r));
+ }
+
+ @Test
+ public void testReadLineNoTrailingNewline() throws Exception {
+ Reader r = newReader("foo\nbar\nbaz");
+ assertEquals("foo\n", readLine(r));
+ assertEquals("bar\n", readLine(r));
+ assertEquals("baz", readLine(r));
+ assertEquals("", readLine(r));
+ }
+
+ private String readLine(Reader r) throws Exception {
+ return IO.readLine(r, sizeHint);
+ }
+
+ private Reader newReader(String in) {
+ Reader r = new InputStreamReader(
+ new ByteArrayInputStream(Constants.encode(in)));
+ if (buffered) {
+ r = new BufferedReader(r);
+ }
+ assertEquals(Boolean.valueOf(buffered),
+ Boolean.valueOf(r.markSupported()));
+ return r;
+ }
+}
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 509027dafc..bc8b8bfc05 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -59,23 +59,23 @@ cannotCreateIndexfile=Cannot create an index file with name {0}
cannotCreateTempDir=Cannot create a temp dir
cannotDeleteCheckedOutBranch=Branch {0} is checked out and can not be deleted
cannotDeleteFile=Cannot delete file: {0}
-cannotDeleteObjectsPath="Can't delete {0}/{1}: {2}
+cannotDeleteObjectsPath=Cannot delete {0}/{1}: {2}
cannotDeleteStaleTrackingRef=Cannot delete stale tracking ref {0}
cannotDeleteStaleTrackingRef2=Cannot delete stale tracking ref {0}: {1}
cannotDetermineProxyFor=Cannot determine proxy for {0}
cannotDownload=Cannot download {0}
-cannotEnterObjectsPath=Can't enter {0}/objects: {1}
-cannotEnterPathFromParent=Can't enter {0} from {1}: {2}
+cannotEnterObjectsPath=Cannot enter {0}/objects: {1}
+cannotEnterPathFromParent=Cannot enter {0} from {1}: {2}
cannotExecute=cannot execute: {0}
cannotGet=Cannot get {0}
-cannotGetObjectsPath=Can't get {0}/{1}: {2}
-cannotListObjectsPath=Can't ls {0}/{1}: {2}
-cannotListPackPath=Can't ls {0}/pack: {1}
+cannotGetObjectsPath=Cannot get {0}/{1}: {2}
+cannotListObjectsPath=Cannot ls {0}/{1}: {2}
+cannotListPackPath=Cannot ls {0}/pack: {1}
cannotListRefs=cannot list refs
cannotLock=Cannot lock {0}
cannotLockPackIn=Cannot lock pack in {0}
cannotMatchOnEmptyString=Cannot match on empty string.
-cannotMkdirObjectPath=Can't mkdir {0}/{1}: {2}
+cannotMkdirObjectPath=Cannot mkdir {0}/{1}: {2}
cannotMoveIndexTo=Cannot move index to {0}
cannotMovePackTo=Cannot move pack to {0}
cannotOpenService=cannot open {0}
@@ -97,7 +97,7 @@ cannotStoreObjects=cannot store objects
cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID
cannotUnloadAModifiedTree=Cannot unload a modified tree.
cannotWorkWithOtherStagesThanZeroRightNow=Cannot work with other stages than zero right now. Won't write corrupt index.
-cannotWriteObjectsPath="Can't write {0}/{1}: {2}
+cannotWriteObjectsPath=Cannot write {0}/{1}: {2}
canOnlyCherryPickCommitsWithOneParent=Cannot cherry-pick commit ''{0}'' because it has {1} parents, only commits with exactly one parent are supported.
canOnlyRevertCommitsWithOneParent=Cannot revert commit ''{0}'' because it has {1} parents, only commits with exactly one parent are supported
commitDoesNotHaveGivenParent=The commit ''{0}'' does not have a parent number {1}.
@@ -325,6 +325,7 @@ invalidEncryption=Invalid encryption
invalidGitdirRef = Invalid .git reference in file ''{0}''
invalidGitType=invalid git type: {0}
invalidId=Invalid id: {0}
+invalidId0=Invalid id
invalidIdLength=Invalid id length {0}; should be {1}
invalidIgnoreParamSubmodule=Found invalid ignore param for submodule {0}.
invalidIntegerValue=Invalid integer value: {0}.{1}={2}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java
index b545312ae7..390545ffaf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java
@@ -45,7 +45,8 @@
package org.eclipse.jgit.errors;
-import java.io.UnsupportedEncodingException;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
import java.text.MessageFormat;
import org.eclipse.jgit.internal.JGitText;
@@ -64,16 +65,25 @@ public class InvalidObjectIdException extends IllegalArgumentException {
* @param length of the sequence of invalid bytes.
*/
public InvalidObjectIdException(byte[] bytes, int offset, int length) {
- super(MessageFormat.format(JGitText.get().invalidId, asAscii(bytes, offset, length)));
+ super(msg(bytes, offset, length));
+ }
+
+ /**
+ * @param id the invalid id.
+ *
+ * @since 4.1
+ */
+ public InvalidObjectIdException(String id) {
+ super(MessageFormat.format(JGitText.get().invalidId, id));
}
- private static String asAscii(byte[] bytes, int offset, int length) {
+ private static String msg(byte[] bytes, int offset, int length) {
try {
- return ": " + new String(bytes, offset, length, "US-ASCII"); //$NON-NLS-1$ //$NON-NLS-2$
- } catch (UnsupportedEncodingException e2) {
- return ""; //$NON-NLS-1$
- } catch (StringIndexOutOfBoundsException e2) {
- return ""; //$NON-NLS-1$
+ return MessageFormat.format(
+ JGitText.get().invalidId,
+ new String(bytes, offset, length, US_ASCII));
+ } catch (StringIndexOutOfBoundsException e) {
+ return JGitText.get().invalidId0;
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java
index 5503bd19e2..44bc16492d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java
@@ -60,7 +60,7 @@ public class PackProtocolException extends TransportException {
* @param uri
* URI used for transport
* @param s
- * message
+ * message, which may be shown to an end-user.
*/
public PackProtocolException(final URIish uri, final String s) {
super(uri + ": " + s); //$NON-NLS-1$
@@ -73,7 +73,7 @@ public class PackProtocolException extends TransportException {
* @param uri
* URI used for transport
* @param s
- * message
+ * message, which may be shown to an end-user.
* @param cause
* root cause exception
*/
@@ -86,7 +86,7 @@ public class PackProtocolException extends TransportException {
* Constructs an PackProtocolException with the specified detail message.
*
* @param s
- * message
+ * message, which may be shown to an end-user.
*/
public PackProtocolException(final String s) {
super(s);
@@ -96,7 +96,7 @@ public class PackProtocolException extends TransportException {
* Constructs an PackProtocolException with the specified detail message.
*
* @param s
- * message
+ * message, which may be shown to an end-user.
* @param cause
* root cause exception
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index f903f23377..86f277ac39 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -384,6 +384,7 @@ public class JGitText extends TranslationBundle {
/***/ public String invalidGitdirRef;
/***/ public String invalidGitType;
/***/ public String invalidId;
+ /***/ public String invalidId0;
/***/ public String invalidIdLength;
/***/ public String invalidIgnoreParamSubmodule;
/***/ public String invalidIntegerValue;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 338106f8e7..c5723c0594 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -175,13 +175,17 @@ public class GC {
/**
* Delete old pack files. What is 'old' is defined by specifying a set of
* old pack files and a set of new pack files. Each pack file contained in
- * old pack files but not contained in new pack files will be deleted.
+ * old pack files but not contained in new pack files will be deleted. If an
+ * expirationDate is set then pack files which are younger than the
+ * expirationDate will not be deleted.
*
* @param oldPacks
* @param newPacks
+ * @throws ParseException
*/
private void deleteOldPacks(Collection<PackFile> oldPacks,
- Collection<PackFile> newPacks) {
+ Collection<PackFile> newPacks) throws ParseException {
+ long expireDate = getExpireDate();
oldPackLoop: for (PackFile oldPack : oldPacks) {
String oldName = oldPack.getPackName();
// check whether an old pack file is also among the list of new
@@ -190,7 +194,8 @@ public class GC {
if (oldName.equals(newPack.getPackName()))
continue oldPackLoop;
- if (!oldPack.shouldBeKept()) {
+ if (!oldPack.shouldBeKept()
+ && oldPack.getPackFile().lastModified() < expireDate) {
oldPack.close();
prunePack(oldName);
}
@@ -303,22 +308,7 @@ public class GC {
*/
public void prune(Set<ObjectId> objectsToKeep) throws IOException,
ParseException {
- long expireDate = Long.MAX_VALUE;
-
- if (expire == null && expireAgeMillis == -1) {
- String pruneExpireStr = repo.getConfig().getString(
- ConfigConstants.CONFIG_GC_SECTION, null,
- ConfigConstants.CONFIG_KEY_PRUNEEXPIRE);
- if (pruneExpireStr == null)
- pruneExpireStr = PRUNE_EXPIRE_DEFAULT;
- expire = GitDateParser.parse(pruneExpireStr, null, SystemReader
- .getInstance().getLocale());
- expireAgeMillis = -1;
- }
- if (expire != null)
- expireDate = expire.getTime();
- if (expireAgeMillis != -1)
- expireDate = System.currentTimeMillis() - expireAgeMillis;
+ long expireDate = getExpireDate();
// Collect all loose objects which are old enough, not referenced from
// the index and not in objectsToKeep
@@ -435,6 +425,26 @@ public class GC {
repo.getObjectDatabase().close();
}
+ private long getExpireDate() throws ParseException {
+ long expireDate = Long.MAX_VALUE;
+
+ if (expire == null && expireAgeMillis == -1) {
+ String pruneExpireStr = repo.getConfig().getString(
+ ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_PRUNEEXPIRE);
+ if (pruneExpireStr == null)
+ pruneExpireStr = PRUNE_EXPIRE_DEFAULT;
+ expire = GitDateParser.parse(pruneExpireStr, null, SystemReader
+ .getInstance().getLocale());
+ expireAgeMillis = -1;
+ }
+ if (expire != null)
+ expireDate = expire.getTime();
+ if (expireAgeMillis != -1)
+ expireDate = System.currentTimeMillis() - expireAgeMillis;
+ return expireDate;
+ }
+
/**
* Remove all entries from a map which key is the id of an object referenced
* by the given ObjectWalk
@@ -559,7 +569,14 @@ public class GC {
if (rest != null)
ret.add(rest);
}
- deleteOldPacks(toBeDeleted, ret);
+ try {
+ deleteOldPacks(toBeDeleted, ret);
+ } catch (ParseException e) {
+ // TODO: the exception has to be wrapped into an IOException because
+ // throwing the ParseException directly would break the API, instead
+ // we should throw a ConfigInvalidException
+ throw new IOException(e);
+ }
prunePacked();
lastPackedRefs = refsBefore;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
index bdbffee490..de7e207ac4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
@@ -45,7 +45,6 @@
package org.eclipse.jgit.lib;
import org.eclipse.jgit.errors.InvalidObjectIdException;
-import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.NB;
import org.eclipse.jgit.util.RawParseUtils;
@@ -53,7 +52,6 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
-import java.text.MessageFormat;
/**
* A SHA-1 abstraction.
@@ -230,9 +228,9 @@ public class ObjectId extends AnyObjectId implements Serializable {
* @return the converted object id.
*/
public static ObjectId fromString(final String str) {
- if (str.length() != Constants.OBJECT_ID_STRING_LENGTH)
- throw new IllegalArgumentException(
- MessageFormat.format(JGitText.get().invalidId, str));
+ if (str.length() != Constants.OBJECT_ID_STRING_LENGTH) {
+ throw new InvalidObjectIdException(str);
+ }
return fromHexString(Constants.encodeASCII(str), 0);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index 0c58a0bea4..23cc264c1c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -47,6 +47,8 @@ import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -143,6 +145,28 @@ public class RepositoryCache {
}
}
+ /**
+ * Remove a repository from the cache.
+ * <p>
+ * Removes a repository from the cache, if it is still registered here,
+ * permitting it to close.
+ *
+ * @param location
+ * location of the repository to remove.
+ * @since 4.1
+ */
+ public static void unregister(Key location) {
+ cache.unregisterRepository(location);
+ }
+
+ /**
+ * @return the locations of all repositories registered in the cache.
+ * @since 4.1
+ */
+ public static Collection<Key> getRegisteredKeys() {
+ return cache.getKeys();
+ }
+
/** Unregister all repositories from the cache. */
public static void clear() {
cache.clearAll();
@@ -195,6 +219,10 @@ public class RepositoryCache {
oldDb.close();
}
+ private Collection<Key> getKeys() {
+ return new ArrayList<Key>(cacheMap.keySet());
+ }
+
private void clearAll() {
for (int stage = 0; stage < 2; stage++) {
for (Iterator<Map.Entry<Key, Reference<Repository>>> i = cacheMap
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 37e5d3cd3c..518a3178f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -1065,74 +1065,84 @@ public abstract class BaseReceivePack {
protected void recvCommands() throws IOException {
PushCertificateParser certParser = getPushCertificateParser();
FirstLine firstLine = null;
- for (;;) {
- String rawLine;
- try {
- rawLine = pckIn.readStringRaw();
- } catch (EOFException eof) {
- if (commands.isEmpty())
- return;
- throw eof;
- }
- if (rawLine == PacketLineIn.END) {
- break;
- }
- String line = chomp(rawLine);
-
- if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$
- clientShallowCommits.add(ObjectId.fromString(line.substring(8, 48)));
- continue;
- }
-
- if (firstLine == null) {
- firstLine = new FirstLine(line);
- enabledCapabilities = firstLine.getCapabilities();
- line = firstLine.getLine();
+ try {
+ for (;;) {
+ String line;
+ try {
+ line = pckIn.readString();
+ } catch (EOFException eof) {
+ if (commands.isEmpty())
+ return;
+ throw eof;
+ }
+ if (line == PacketLineIn.END) {
+ break;
+ }
- if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) {
- certParser.receiveHeader(pckIn, !isBiDirectionalPipe());
+ if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$
+ clientShallowCommits.add(ObjectId.fromString(line.substring(8, 48)));
continue;
}
- }
- if (rawLine.equals(PushCertificateParser.BEGIN_SIGNATURE)) {
- certParser.receiveSignature(pckIn);
- continue;
- }
+ if (firstLine == null) {
+ firstLine = new FirstLine(line);
+ enabledCapabilities = firstLine.getCapabilities();
+ line = firstLine.getLine();
- if (line.length() < 83) {
- final String m = JGitText.get().errorInvalidProtocolWantedOldNewRef;
- sendError(m);
- throw new PackProtocolException(m);
- }
+ if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) {
+ certParser.receiveHeader(pckIn, !isBiDirectionalPipe());
+ continue;
+ }
+ }
- final ReceiveCommand cmd = parseCommand(line);
- if (cmd.getRefName().equals(Constants.HEAD)) {
- cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
- } else {
- cmd.setRef(refs.get(cmd.getRefName()));
- }
- commands.add(cmd);
- if (certParser.enabled()) {
- // Must use raw line with optional newline so signed payload can be
- // reconstructed.
- certParser.addCommand(cmd, rawLine);
+ if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) {
+ certParser.receiveSignature(pckIn);
+ continue;
+ }
+
+ ReceiveCommand cmd;
+ try {
+ cmd = parseCommand(line);
+ } catch (PackProtocolException e) {
+ sendError(e.getMessage());
+ throw e;
+ }
+ if (cmd.getRefName().equals(Constants.HEAD)) {
+ cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
+ } else {
+ cmd.setRef(refs.get(cmd.getRefName()));
+ }
+ commands.add(cmd);
+ if (certParser.enabled()) {
+ certParser.addCommand(cmd);
+ }
}
+ } catch (PackProtocolException e) {
+ sendError(e.getMessage());
+ throw e;
}
}
- static String chomp(String line) {
- if (line != null && !line.isEmpty()
- && line.charAt(line.length() - 1) == '\n') {
- return line.substring(0, line.length() - 1);
+ static ReceiveCommand parseCommand(String line) throws PackProtocolException {
+ if (line == null || line.length() < 83) {
+ throw new PackProtocolException(
+ JGitText.get().errorInvalidProtocolWantedOldNewRef);
+ }
+ String oldStr = line.substring(0, 40);
+ String newStr = line.substring(41, 81);
+ ObjectId oldId, newId;
+ try {
+ oldId = ObjectId.fromString(oldStr);
+ newId = ObjectId.fromString(newStr);
+ } catch (IllegalArgumentException e) {
+ throw new PackProtocolException(
+ JGitText.get().errorInvalidProtocolWantedOldNewRef, e);
}
- return line;
- }
-
- static ReceiveCommand parseCommand(String line) {
- ObjectId oldId = ObjectId.fromString(line.substring(0, 40));
- ObjectId newId = ObjectId.fromString(line.substring(41, 81));
String name = line.substring(82);
+ if (!Repository.isValidRefName(name)) {
+ throw new PackProtocolException(
+ JGitText.get().errorInvalidProtocolWantedOldNewRef);
+ }
return new ReceiveCommand(oldId, newId, name);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java
index 6dc4153d1e..413b02b1c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java
@@ -50,6 +50,7 @@ import static org.eclipse.jgit.transport.PushCertificateParser.VERSION;
import java.text.MessageFormat;
import java.util.List;
+import java.util.Objects;
import org.eclipse.jgit.internal.JGitText;
@@ -85,12 +86,11 @@ public class PushCertificate {
private final String nonce;
private final NonceStatus nonceStatus;
private final List<ReceiveCommand> commands;
- private final String rawCommands;
private final String signature;
PushCertificate(String version, PushCertificateIdent pusher, String pushee,
String nonce, NonceStatus nonceStatus, List<ReceiveCommand> commands,
- String rawCommands, String signature) {
+ String signature) {
if (version == null || version.isEmpty()) {
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().pushCertificateInvalidField, VERSION));
@@ -99,10 +99,6 @@ public class PushCertificate {
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().pushCertificateInvalidField, PUSHER));
}
- if (pushee == null || pushee.isEmpty()) {
- throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().pushCertificateInvalidField, PUSHEE));
- }
if (nonce == null || nonce.isEmpty()) {
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().pushCertificateInvalidField, NONCE));
@@ -112,8 +108,7 @@ public class PushCertificate {
JGitText.get().pushCertificateInvalidField,
"nonce status")); //$NON-NLS-1$
}
- if (commands == null || commands.isEmpty()
- || rawCommands == null || rawCommands.isEmpty()) {
+ if (commands == null || commands.isEmpty()) {
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().pushCertificateInvalidField,
"command")); //$NON-NLS-1$
@@ -123,7 +118,7 @@ public class PushCertificate {
JGitText.get().pushCertificateInvalidSignature);
}
if (!signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE)
- || !signature.endsWith(PushCertificateParser.END_SIGNATURE)) {
+ || !signature.endsWith(PushCertificateParser.END_SIGNATURE + '\n')) {
throw new IllegalArgumentException(
JGitText.get().pushCertificateInvalidSignature);
}
@@ -133,7 +128,6 @@ public class PushCertificate {
this.nonce = nonce;
this.nonceStatus = nonceStatus;
this.commands = commands;
- this.rawCommands = rawCommands;
this.signature = signature;
}
@@ -209,14 +203,59 @@ public class PushCertificate {
* @since 4.1
*/
public String toText() {
- return new StringBuilder()
+ StringBuilder sb = new StringBuilder()
.append(VERSION).append(' ').append(version).append('\n')
.append(PUSHER).append(' ').append(getPusher())
.append('\n')
.append(PUSHEE).append(' ').append(pushee).append('\n')
.append(NONCE).append(' ').append(nonce).append('\n')
- .append('\n')
- .append(rawCommands)
- .toString();
+ .append('\n');
+ for (ReceiveCommand cmd : commands) {
+ sb.append(cmd.getOldId().name())
+ .append(' ').append(cmd.getNewId().name())
+ .append(' ').append(cmd.getRefName()).append('\n');
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return signature.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PushCertificate)) {
+ return false;
+ }
+ PushCertificate p = (PushCertificate) o;
+ return version.equals(p.version)
+ && pusher.equals(p.pusher)
+ && Objects.equals(pushee, p.pushee)
+ && nonceStatus == p.nonceStatus
+ && signature.equals(p.signature)
+ && commandsEqual(this, p);
+ }
+
+ private static boolean commandsEqual(PushCertificate c1, PushCertificate c2) {
+ if (c1.commands.size() != c2.commands.size()) {
+ return false;
+ }
+ for (int i = 0; i < c1.commands.size(); i++) {
+ ReceiveCommand cmd1 = c1.commands.get(i);
+ ReceiveCommand cmd2 = c2.commands.get(i);
+ if (!cmd1.getOldId().equals(cmd2.getOldId())
+ || !cmd1.getNewId().equals(cmd2.getNewId())
+ || !cmd1.getRefName().equals(cmd2.getRefName())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + '['
+ + toText() + signature + ']';
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java
index fea8f125e1..fd49e40c76 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java
@@ -40,14 +40,15 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+
package org.eclipse.jgit.transport;
-import static org.eclipse.jgit.transport.BaseReceivePack.chomp;
import static org.eclipse.jgit.transport.BaseReceivePack.parseCommand;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT;
import java.io.EOFException;
import java.io.IOException;
+import java.io.Reader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
@@ -58,6 +59,7 @@ import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
+import org.eclipse.jgit.util.IO;
/**
* Parser for signed push certificates.
@@ -66,9 +68,9 @@ import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
*/
public class PushCertificateParser {
static final String BEGIN_SIGNATURE =
- "-----BEGIN PGP SIGNATURE-----\n"; //$NON-NLS-1$
+ "-----BEGIN PGP SIGNATURE-----"; //$NON-NLS-1$
static final String END_SIGNATURE =
- "-----END PGP SIGNATURE-----\n"; //$NON-NLS-1$
+ "-----END PGP SIGNATURE-----"; //$NON-NLS-1$
static final String VERSION = "certificate version"; //$NON-NLS-1$
@@ -78,9 +80,81 @@ public class PushCertificateParser {
static final String NONCE = "nonce"; //$NON-NLS-1$
+ static final String END_CERT = "push-cert-end"; //$NON-NLS-1$
+
private static final String VERSION_0_1 = "0.1"; //$NON-NLS-1$
- private static final String END_CERT = "push-cert-end\n"; //$NON-NLS-1$
+ private static interface StringReader {
+ /**
+ * @return the next string from the input, up to an optional newline, with
+ * newline stripped if present
+ *
+ * @throws EOFException
+ * if EOF was reached.
+ * @throws IOException
+ * if an error occurred during reading.
+ */
+ String read() throws EOFException, IOException;
+ }
+
+ private static class PacketLineReader implements StringReader {
+ private final PacketLineIn pckIn;
+
+ private PacketLineReader(PacketLineIn pckIn) {
+ this.pckIn = pckIn;
+ }
+
+ @Override
+ public String read() throws IOException {
+ return pckIn.readString();
+ }
+ }
+
+ private static class StreamReader implements StringReader {
+ private final Reader reader;
+
+ private StreamReader(Reader reader) {
+ this.reader = reader;
+ }
+
+ @Override
+ public String read() throws IOException {
+ // Presize for a command containing 2 SHA-1s and some refname.
+ String line = IO.readLine(reader, 41 * 2 + 64);
+ if (line.isEmpty()) {
+ throw new EOFException();
+ } else if (line.charAt(line.length() - 1) == '\n') {
+ line = line.substring(0, line.length() - 1);
+ }
+ return line;
+ }
+ }
+
+ /**
+ * Parse a push certificate from a reader.
+ * <p>
+ * Differences from the {@link PacketLineIn} receiver methods:
+ * <ul>
+ * <li>Does not use pkt-line framing.</li>
+ * <li>Reads an entire cert in one call rather than depending on a loop in
+ * the caller.</li>
+ * <li>Does not assume a {@code "push-cert-end"} line.</li>
+ * </ul>
+ *
+ * @param r
+ * input reader; consumed only up until the end of the next
+ * signature in the input.
+ * @return the parsed certificate, or null if the reader was at EOF.
+ * @throws PackProtocolException
+ * if the certificate is malformed.
+ * @throws IOException
+ * if there was an error reading from the input.
+ * @since 4.1
+ */
+ public static PushCertificate fromReader(Reader r)
+ throws PackProtocolException, IOException {
+ return new PushCertificateParser().parse(r);
+ }
private boolean received;
private String version;
@@ -110,11 +184,17 @@ public class PushCertificateParser {
*/
private final int nonceSlopLimit;
+ private final boolean enabled;
private final NonceGenerator nonceGenerator;
- private final List<ReceiveCommand> commands;
- private final StringBuilder rawCommands;
+ private final List<ReceiveCommand> commands = new ArrayList<>();
- PushCertificateParser(Repository into, SignedPushConfig cfg) {
+ /**
+ * @param into
+ * destination repository for the push.
+ * @param cfg
+ * configuration for signed push.
+ */
+ public PushCertificateParser(Repository into, SignedPushConfig cfg) {
if (cfg != null) {
nonceSlopLimit = cfg.getCertNonceSlopLimit();
nonceGenerator = cfg.getNonceGenerator();
@@ -123,8 +203,48 @@ public class PushCertificateParser {
nonceGenerator = null;
}
db = into;
- commands = new ArrayList<>();
- rawCommands = new StringBuilder();
+ enabled = nonceGenerator != null;
+ }
+
+ private PushCertificateParser() {
+ db = null;
+ nonceSlopLimit = 0;
+ nonceGenerator = null;
+ enabled = true;
+ }
+
+ /**
+ * Parse a push certificate from a reader.
+ *
+ * @see #fromReader(Reader)
+ * @param r
+ * input reader; consumed only up until the end of the next
+ * signature in the input.
+ * @return the parsed certificate, or null if the reader was at EOF.
+ * @throws PackProtocolException
+ * if the certificate is malformed.
+ * @throws IOException
+ * if there was an error reading from the input.
+ * @since 4.1
+ */
+ public PushCertificate parse(Reader r)
+ throws PackProtocolException, IOException {
+ StreamReader reader = new StreamReader(r);
+ receiveHeader(reader, true);
+ String line;
+ try {
+ while (!(line = reader.read()).isEmpty()) {
+ if (line.equals(BEGIN_SIGNATURE)) {
+ receiveSignature(reader);
+ break;
+ }
+ addCommand(line);
+ }
+ } catch (EOFException e) {
+ // EOF reached, but might have been at a valid state. Let build call below
+ // sort it out.
+ }
+ return build();
}
/**
@@ -134,24 +254,24 @@ public class PushCertificateParser {
* @since 4.1
*/
public PushCertificate build() throws IOException {
- if (!received || nonceGenerator == null) {
+ if (!received || !enabled) {
return null;
}
try {
return new PushCertificate(version, pusher, pushee, receivedNonce,
- nonceStatus, Collections.unmodifiableList(commands),
- rawCommands.toString(), signature);
+ nonceStatus, Collections.unmodifiableList(commands), signature);
} catch (IllegalArgumentException e) {
throw new IOException(e.getMessage(), e);
}
}
/**
- * @return if the server is configured to use signed pushes.
+ * @return if the repository is configured to use signed pushes in this
+ * context.
* @since 4.0
*/
public boolean enabled() {
- return nonceGenerator != null;
+ return enabled;
}
/**
@@ -175,14 +295,21 @@ public class PushCertificateParser {
return sentNonce;
}
- private static String parseHeader(PacketLineIn pckIn, String header)
+ private static String parseHeader(StringReader reader, String header)
+ throws IOException {
+ return parseHeader(reader.read(), header);
+ }
+
+ private static String parseHeader(String s, String header)
throws IOException {
- String s = pckIn.readString();
+ if (s.isEmpty()) {
+ throw new EOFException();
+ }
if (s.length() <= header.length()
|| !s.startsWith(header)
|| s.charAt(header.length()) != ' ') {
throw new PackProtocolException(MessageFormat.format(
- JGitText.get().pushCertificateInvalidHeader, header));
+ JGitText.get().pushCertificateInvalidField, header));
}
return s.substring(header.length() + 1);
}
@@ -209,24 +336,42 @@ public class PushCertificateParser {
*/
public void receiveHeader(PacketLineIn pckIn, boolean stateless)
throws IOException {
- received = true;
+ receiveHeader(new PacketLineReader(pckIn), stateless);
+ }
+
+ private void receiveHeader(StringReader reader, boolean stateless)
+ throws IOException {
try {
- version = parseHeader(pckIn, VERSION);
+ try {
+ version = parseHeader(reader, VERSION);
+ } catch (EOFException e) {
+ return;
+ }
+ received = true;
if (!version.equals(VERSION_0_1)) {
throw new PackProtocolException(MessageFormat.format(
JGitText.get().pushCertificateInvalidFieldValue, VERSION, version));
}
- String rawPusher = parseHeader(pckIn, PUSHER);
+ String rawPusher = parseHeader(reader, PUSHER);
pusher = PushCertificateIdent.parse(rawPusher);
if (pusher == null) {
throw new PackProtocolException(MessageFormat.format(
JGitText.get().pushCertificateInvalidFieldValue,
PUSHER, rawPusher));
}
- pushee = parseHeader(pckIn, PUSHEE);
- receivedNonce = parseHeader(pckIn, NONCE);
+ String next = reader.read();
+ if (next.startsWith(PUSHEE)) {
+ pushee = parseHeader(next, PUSHEE);
+ receivedNonce = parseHeader(reader, NONCE);
+ } else {
+ receivedNonce = parseHeader(next, NONCE);
+ }
+ nonceStatus = nonceGenerator != null
+ ? nonceGenerator.verify(
+ receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
+ : NonceStatus.UNSOLICITED;
// An empty line.
- if (!pckIn.readString().isEmpty()) {
+ if (!reader.read().isEmpty()) {
throw new PackProtocolException(
JGitText.get().pushCertificateInvalidHeader);
}
@@ -234,19 +379,15 @@ public class PushCertificateParser {
throw new PackProtocolException(
JGitText.get().pushCertificateInvalidHeader, eof);
}
- nonceStatus = nonceGenerator != null
- ? nonceGenerator.verify(
- receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
- : NonceStatus.UNSOLICITED;
}
/**
* Read the PGP signature.
* <p>
* This method assumes the line
- * {@code "-----BEGIN PGP SIGNATURE-----\n"} has already been parsed,
- * and continues parsing until an {@code "-----END PGP SIGNATURE-----\n"} is
- * found, followed by {@code "push-cert-end\n"}.
+ * {@code "-----BEGIN PGP SIGNATURE-----"} has already been parsed,
+ * and continues parsing until an {@code "-----END PGP SIGNATURE-----"} is
+ * found, followed by {@code "push-cert-end"}.
*
* @param pckIn
* where we read the signature from.
@@ -255,18 +396,23 @@ public class PushCertificateParser {
* @since 4.0
*/
public void receiveSignature(PacketLineIn pckIn) throws IOException {
+ StringReader reader = new PacketLineReader(pckIn);
+ receiveSignature(reader);
+ if (!reader.read().equals(END_CERT)) {
+ throw new PackProtocolException(
+ JGitText.get().pushCertificateInvalidSignature);
+ }
+ }
+
+ private void receiveSignature(StringReader reader) throws IOException {
received = true;
try {
- StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE);
+ StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n');
String line;
- while (!(line = pckIn.readStringRaw()).equals(END_SIGNATURE)) {
- sig.append(line);
- }
- signature = sig.append(END_SIGNATURE).toString();
- if (!pckIn.readStringRaw().equals(END_CERT)) {
- throw new PackProtocolException(
- JGitText.get().pushCertificateInvalidSignature);
+ while (!(line = reader.read()).equals(END_SIGNATURE)) {
+ sig.append(line).append('\n');
}
+ signature = sig.append(END_SIGNATURE).append('\n').toString();
} catch (EOFException eof) {
throw new PackProtocolException(
JGitText.get().pushCertificateInvalidSignature, eof);
@@ -278,26 +424,23 @@ public class PushCertificateParser {
*
* @param cmd
* the command.
- * @param rawLine
- * the exact line read from the wire that produced this
- * command, including trailing newline if present.
* @since 4.1
*/
- public void addCommand(ReceiveCommand cmd, String rawLine) {
+ public void addCommand(ReceiveCommand cmd) {
commands.add(cmd);
- rawCommands.append(rawLine);
}
/**
* Add a command to the signature.
*
- * @param rawLine
- * the exact line read from the wire that produced this
- * command, including trailing newline if present.
+ * @param line
+ * the line read from the wire that produced this
+ * command, with optional trailing newline already trimmed.
+ * @throws PackProtocolException
+ * if the raw line cannot be parsed to a command.
* @since 4.0
*/
- public void addCommand(String rawLine) {
- commands.add(parseCommand(chomp(rawLine)));
- rawCommands.append(rawLine);
+ public void addCommand(String line) throws PackProtocolException {
+ commands.add(parseCommand(line));
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
index c817c475aa..a9f5c9bf65 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
@@ -51,6 +51,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.text.MessageFormat;
@@ -371,6 +372,74 @@ public class IO {
return l;
}
+ /**
+ * Read the next line from a reader.
+ * <p>
+ * Like {@link java.io.BufferedReader#readLine()}, but only treats {@code \n}
+ * as end-of-line, and includes the trailing newline.
+ *
+ * @param in
+ * the reader to read from.
+ * @param sizeHint
+ * hint for buffer sizing; 0 or negative for default.
+ * @return the next line from the input, always ending in {@code \n} unless
+ * EOF was reached.
+ * @throws IOException
+ * there was an error reading from the stream.
+ */
+ public static String readLine(Reader in, int sizeHint) throws IOException {
+ if (in.markSupported()) {
+ if (sizeHint <= 0) {
+ sizeHint = 1024;
+ }
+ StringBuilder sb = new StringBuilder(sizeHint);
+ char[] buf = new char[sizeHint];
+ while (true) {
+ in.mark(sizeHint);
+ int n = in.read(buf);
+ if (n < 0) {
+ in.reset();
+ return sb.toString();
+ }
+ for (int i = 0; i < n; i++) {
+ if (buf[i] == '\n') {
+ resetAndSkipFully(in, ++i);
+ sb.append(buf, 0, i);
+ return sb.toString();
+ }
+ }
+ if (n > 0) {
+ sb.append(buf, 0, n);
+ }
+ resetAndSkipFully(in, n);
+ }
+ } else {
+ StringBuilder buf = sizeHint > 0
+ ? new StringBuilder(sizeHint)
+ : new StringBuilder();
+ int i;
+ while ((i = in.read()) != -1) {
+ char c = (char) i;
+ buf.append(c);
+ if (c == '\n') {
+ break;
+ }
+ }
+ return buf.toString();
+ }
+ }
+
+ private static void resetAndSkipFully(Reader fd, long toSkip) throws IOException {
+ fd.reset();
+ while (toSkip > 0) {
+ long r = fd.skip(toSkip);
+ if (r <= 0) {
+ throw new EOFException(JGitText.get().shortSkipOfBlock);
+ }
+ toSkip -= r;
+ }
+ }
+
private IO() {
// Don't create instances of a static only utility.
}
diff --git a/pom.xml b/pom.xml
index 91f279b788..10dfe8cf4b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -192,7 +192,7 @@
<maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
<bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
- <jgit-last-release-version>3.7.0.201502260915-r</jgit-last-release-version>
+ <jgit-last-release-version>4.0.0.201506090130-r</jgit-last-release-version>
<jsch-version>0.1.51</jsch-version>
<javaewah-version>0.7.9</javaewah-version>
<junit-version>4.11</junit-version>