diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java new file mode 100644 index 000000000..4d1150844 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2018, Google LLC. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItems; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class ProtocolV2ParserTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private TestRepository testRepo; + + @Before + public void setUp() throws Exception { + testRepo = new TestRepository<>(newRepo("protocol-v2-parser-test")); + } + + private static InMemoryRepository newRepo(String name) { + return new InMemoryRepository(new DfsRepositoryDescription(name)); + } + + private static class ConfigBuilder { + + private boolean allowRefInWant; + + private boolean allowFilter; + + private ConfigBuilder() { + } + + static ConfigBuilder start() { + return new ConfigBuilder(); + } + + static TransferConfig getDefault() { + return start().done(); + } + + ConfigBuilder allowRefInWant() { + allowRefInWant = true; + return this; + } + + ConfigBuilder allowFilter() { + allowFilter = true; + return this; + } + + TransferConfig done() { + Config rc = new Config(); + rc.setBoolean("uploadpack", null, "allowrefinwant", allowRefInWant); + rc.setBoolean("uploadpack", null, "allowfilter", allowFilter); + return new TransferConfig(rc); + } + } + + /* + * Convert the input lines to the PacketLine that the parser reads. + */ + private static PacketLineIn formatAsPacketLine(String... inputLines) + throws IOException { + ByteArrayOutputStream send = new ByteArrayOutputStream(); + PacketLineOut pckOut = new PacketLineOut(send); + for (String line : inputLines) { + if (line == PacketLineIn.END) { + pckOut.end(); + } else if (line == PacketLineIn.DELIM) { + pckOut.writeDelim(); + } else { + pckOut.writeString(line); + } + } + + return new PacketLineIn(new ByteArrayInputStream(send.toByteArray())); + } + + private static List objIdsAsStrings(Collection objIds) { + // TODO(ifrade) Translate this to a matcher, so it would read as + // assertThat(req.wantsIds(), hasObjectIds("...", "...")) + return objIds.stream().map(ObjectId::name).collect(Collectors.toList()); + } + + /* + * Succesful fetch with the basic core commands of the protocol. + */ + @Test + public void testFetchBasicArguments() + throws PackProtocolException, IOException { + PacketLineIn pckIn = formatAsPacketLine( + PacketLineIn.DELIM, + "thin-pack", "no-progress", "include-tag", "ofs-delta", + "want 4624442d68ee402a94364191085b77137618633e", + "want f900c8326a43303685c46b279b9f70411bff1a4b", + "have 554f6e41067b9e3e565b6988a8294fac1cb78f4b", + "have abc760ab9ad72f08209943251b36cb886a578f87", "done", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.getDefault()); + FetchV2Request request = parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + assertTrue(request.getOptions() + .contains(GitProtocolConstants.OPTION_THIN_PACK)); + assertTrue(request.getOptions() + .contains(GitProtocolConstants.OPTION_NO_PROGRESS)); + assertTrue(request.getOptions() + .contains(GitProtocolConstants.OPTION_INCLUDE_TAG)); + assertTrue(request.getOptions() + .contains(GitProtocolConstants.CAPABILITY_OFS_DELTA)); + assertThat(objIdsAsStrings(request.getWantsIds()), + hasItems("4624442d68ee402a94364191085b77137618633e", + "f900c8326a43303685c46b279b9f70411bff1a4b")); + assertThat(objIdsAsStrings(request.getPeerHas()), + hasItems("554f6e41067b9e3e565b6988a8294fac1cb78f4b", + "abc760ab9ad72f08209943251b36cb886a578f87")); + assertTrue(request.getWantedRefs().isEmpty()); + assertTrue(request.wasDoneReceived()); + } + + @Test + public void testFetchWithShallow_deepen() throws IOException { + PacketLineIn pckIn = formatAsPacketLine( + PacketLineIn.DELIM, + "deepen 15", + "deepen-relative", + "shallow 28274d02c489f4c7e68153056e9061a46f62d7a0", + "shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.getDefault()); + FetchV2Request request = parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + assertThat(objIdsAsStrings(request.getClientShallowCommits()), + hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0", + "145e683b229dcab9d0e2ccb01b386f9ecc17d29d")); + assertTrue(request.getDeepenNotRefs().isEmpty()); + assertEquals(15, request.getDepth()); + assertTrue(request.getOptions() + .contains(GitProtocolConstants.OPTION_DEEPEN_RELATIVE)); + } + + @Test + public void testFetchWithShallow_deepenNot() throws IOException { + PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, + "shallow 28274d02c489f4c7e68153056e9061a46f62d7a0", + "shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d", + "deepen-not a08595f76159b09d57553e37a5123f1091bb13e7", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.getDefault()); + FetchV2Request request = parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + assertThat(objIdsAsStrings(request.getClientShallowCommits()), + hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0", + "145e683b229dcab9d0e2ccb01b386f9ecc17d29d")); + assertThat(request.getDeepenNotRefs(), + hasItems("a08595f76159b09d57553e37a5123f1091bb13e7")); + } + + @Test + public void testFetchWithShallow_deepenSince() throws IOException { + PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, + "shallow 28274d02c489f4c7e68153056e9061a46f62d7a0", + "shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d", + "deepen-since 123123123", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.getDefault()); + FetchV2Request request = parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + assertThat(objIdsAsStrings(request.getClientShallowCommits()), + hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0", + "145e683b229dcab9d0e2ccb01b386f9ecc17d29d")); + assertEquals(123123123, request.getDeepenSince()); + } + + @Test + public void testFetchWithNoneFilter() throws IOException { + PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, + "filter blob:none", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.start().allowFilter().done()); + FetchV2Request request = parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + assertEquals(0, request.getFilterBlobLimit()); + } + + @Test + public void testFetchWithBlobSizeFilter() throws IOException { + PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, + "filter blob:limit=15", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.start().allowFilter().done()); + FetchV2Request request = parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + assertEquals(15, request.getFilterBlobLimit()); + } + + @Test + public void testFetchMustNotHaveMultipleFilters() throws IOException { + thrown.expect(PackProtocolException.class); + PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, + "filter blob:none", + "filter blob:limit=12", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.start().allowFilter().done()); + FetchV2Request request = parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + assertEquals(0, request.getFilterBlobLimit()); + } + + @Test + public void testFetchFilterWithoutAllowFilter() throws IOException { + thrown.expect(PackProtocolException.class); + PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, + "filter blob:limit=12", PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.getDefault()); + parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + } + + @Test + public void testFetchWithRefInWant() throws Exception { + RevCommit one = testRepo.commit().message("1").create(); + RevCommit two = testRepo.commit().message("2").create(); + testRepo.update("branchA", one); + testRepo.update("branchB", two); + + PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, + "want e4980cdc48cfa1301493ca94eb70523f6788b819", + "want-ref refs/heads/branchA", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.start().allowRefInWant().done()); + + + FetchV2Request request = parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + assertEquals(1, request.getWantedRefs().size()); + assertThat(request.getWantedRefs().keySet(), + hasItems("refs/heads/branchA")); + assertEquals(2, request.getWantsIds().size()); + assertThat(objIdsAsStrings(request.getWantsIds()), + hasItems("e4980cdc48cfa1301493ca94eb70523f6788b819", + one.getName())); + } + + @Test + public void testFetchWithRefInWantUnknownRef() throws Exception { + thrown.expect(PackProtocolException.class); + thrown.expectMessage(containsString("refs/heads/branchC")); + + PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, + "want e4980cdc48cfa1301493ca94eb70523f6788b819", + "want-ref refs/heads/branchC", + PacketLineIn.END); + ProtocolV2Parser parser = new ProtocolV2Parser( + ConfigBuilder.start().allowRefInWant().done()); + + RevCommit one = testRepo.commit().message("1").create(); + RevCommit two = testRepo.commit().message("2").create(); + testRepo.update("branchA", one); + testRepo.update("branchB", two); + + parser.parseFetchRequest(pckIn, + testRepo.getRepository().getRefDatabase()); + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java new file mode 100644 index 000000000..eae2c6edb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2018, Google LLC. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport; + +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WANT_REF; + +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; + +/** + * Parse the incoming git protocol lines from the wire and translate them into a + * Request object. + * + * It requires a transferConfig object to know what the server supports (e.g. + * ref-in-want and/or filters). + */ +final class ProtocolV2Parser { + + private final TransferConfig transferConfig; + + ProtocolV2Parser(TransferConfig transferConfig) { + this.transferConfig = transferConfig; + } + + /** + * Parse the incoming fetch request arguments from the wire. The caller must + * be sure that what is comings is a fetch request before coming here. + * + * This operation requires the reference database to validate incoming + * references. + * + * @param pckIn + * incoming lines + * @param refdb + * reference database (to validate that received references exist + * and point to valid objects) + * @return A FetchV2Request populated with information received from the + * wire. + * @throws PackProtocolException + * incompatible options, wrong type of arguments or other issues + * where the request breaks the protocol. + * @throws IOException + * an IO error prevented reading the incoming message or + * accessing the ref database. + */ + FetchV2Request parseFetchRequest(PacketLineIn pckIn, RefDatabase refdb) + throws PackProtocolException, IOException { + FetchV2Request.Builder reqBuilder = FetchV2Request.builder(); + + // Packs are always sent multiplexed and using full 64K + // lengths. + reqBuilder.addOption(OPTION_SIDE_BAND_64K); + + String line; + + // Currently, we do not support any capabilities, so the next + // line is DELIM. + if ((line = pckIn.readString()) != PacketLineIn.DELIM) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().unexpectedPacketLine, line)); + } + + boolean filterReceived = false; + while ((line = pckIn.readString()) != PacketLineIn.END) { + if (line.startsWith("want ")) { //$NON-NLS-1$ + reqBuilder.addWantsIds(ObjectId.fromString(line.substring(5))); + } else if (transferConfig.isAllowRefInWant() + && line.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$ + String refName = line.substring(OPTION_WANT_REF.length() + 1); + // TODO(ifrade): This validation should be done after the + // protocol parsing. It is not a protocol problem asking for an + // unexisting ref and we wouldn't need the ref database here + Ref ref = refdb.exactRef(refName); + if (ref == null) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidRefName, refName)); + } + ObjectId oid = ref.getObjectId(); + if (oid == null) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidRefName, refName)); + } + reqBuilder.addWantedRef(refName, oid); + reqBuilder.addWantsIds(oid); + } else if (line.startsWith("have ")) { //$NON-NLS-1$ + reqBuilder.addPeerHas(ObjectId.fromString(line.substring(5))); + } else if (line.equals("done")) { //$NON-NLS-1$ + reqBuilder.setDoneReceived(); + } else if (line.equals(OPTION_THIN_PACK)) { + reqBuilder.addOption(OPTION_THIN_PACK); + } else if (line.equals(OPTION_NO_PROGRESS)) { + reqBuilder.addOption(OPTION_NO_PROGRESS); + } else if (line.equals(OPTION_INCLUDE_TAG)) { + reqBuilder.addOption(OPTION_INCLUDE_TAG); + } else if (line.equals(OPTION_OFS_DELTA)) { + reqBuilder.addOption(OPTION_OFS_DELTA); + } else if (line.startsWith("shallow ")) { //$NON-NLS-1$ + reqBuilder.addClientShallowCommit( + ObjectId.fromString(line.substring(8))); + } else if (line.startsWith("deepen ")) { //$NON-NLS-1$ + int parsedDepth = Integer.parseInt(line.substring(7)); + if (parsedDepth <= 0) { + throw new PackProtocolException( + MessageFormat.format(JGitText.get().invalidDepth, + Integer.valueOf(parsedDepth))); + } + if (reqBuilder.getDeepenSince() != 0) { + throw new PackProtocolException( + JGitText.get().deepenSinceWithDeepen); + } + if (reqBuilder.hasDeepenNotRefs()) { + throw new PackProtocolException( + JGitText.get().deepenNotWithDeepen); + } + reqBuilder.setDepth(parsedDepth); + } else if (line.startsWith("deepen-not ")) { //$NON-NLS-1$ + reqBuilder.addDeepenNotRef(line.substring(11)); + if (reqBuilder.getDepth() != 0) { + throw new PackProtocolException( + JGitText.get().deepenNotWithDeepen); + } + } else if (line.equals(OPTION_DEEPEN_RELATIVE)) { + reqBuilder.addOption(OPTION_DEEPEN_RELATIVE); + } else if (line.startsWith("deepen-since ")) { //$NON-NLS-1$ + int ts = Integer.parseInt(line.substring(13)); + if (ts <= 0) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidTimestamp, line)); + } + if (reqBuilder.getDepth() != 0) { + throw new PackProtocolException( + JGitText.get().deepenSinceWithDeepen); + } + reqBuilder.setDeepenSince(ts); + } else if (transferConfig.isAllowFilter() + && line.startsWith(OPTION_FILTER + ' ')) { + if (filterReceived) { + throw new PackProtocolException( + JGitText.get().tooManyFilters); + } + filterReceived = true; + reqBuilder.setFilterBlobLimit(filterLine( + line.substring(OPTION_FILTER.length() + 1))); + } else { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().unexpectedPacketLine, line)); + } + } + + return reqBuilder.build(); + } + + /** + * Process the content of "filter" line from the protocol. It has a shape + * like "blob:none" or "blob:limit=N", with limit a positive number. + * + * @param blobLine + * the content of the "filter" line in the protocol + * @return N, the limit, defaulting to 0 if "none" + * @throws PackProtocolException + * invalid filter because due to unrecognized format or + * negative/non-numeric filter. + */ + static long filterLine(String blobLine) throws PackProtocolException { + long blobLimit = -1; + + if (blobLine.equals("blob:none")) { //$NON-NLS-1$ + blobLimit = 0; + } else if (blobLine.startsWith("blob:limit=")) { //$NON-NLS-1$ + try { + blobLimit = Long + .parseLong(blobLine.substring("blob:limit=".length())); //$NON-NLS-1$ + } catch (NumberFormatException e) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidFilter, blobLine)); + } + } + /* + * We must have (1) either "blob:none" or "blob:limit=" set (because we + * only support blob size limits for now), and (2) if the latter, then + * it must be nonnegative. Throw if (1) or (2) is not met. + */ + if (blobLimit < 0) { + throw new PackProtocolException( + MessageFormat.format(JGitText.get().invalidFilter, blobLine)); + } + + return blobLimit; + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index b1d7e5447..cd7290edc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -63,7 +63,6 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; -import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WANT_REF; import java.io.ByteArrayOutputStream; import java.io.EOFException; @@ -944,12 +943,6 @@ public class UploadPack { } private void fetchV2() throws IOException { - FetchV2Request.Builder reqBuilder = FetchV2Request.builder(); - - // Packs are always sent multiplexed and using full 64K - // lengths. - reqBuilder.addOption(OPTION_SIDE_BAND_64K); - // Depending on the requestValidator, #processHaveLines may // require that advertised be set. Set it only in the required // circumstances (to avoid a full ref lookup in the case that @@ -962,105 +955,11 @@ public class UploadPack { advertised = refIdSet(getAdvertisedOrDefaultRefs().values()); } - String line; - - // Currently, we do not support any capabilities, so the next - // line is DELIM. - if ((line = pckIn.readString()) != PacketLineIn.DELIM) { - throw new PackProtocolException(MessageFormat - .format(JGitText.get().unexpectedPacketLine, line)); - } - - boolean includeTag = false; - boolean filterReceived = false; - while ((line = pckIn.readString()) != PacketLineIn.END) { - if (line.startsWith("want ")) { //$NON-NLS-1$ - reqBuilder.addWantsIds(ObjectId.fromString(line.substring(5))); - } else if (transferConfig.isAllowRefInWant() && - line.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$ - String refName = line.substring(OPTION_WANT_REF.length() + 1); - Ref ref = db.getRefDatabase().exactRef(refName); - if (ref == null) { - throw new PackProtocolException( - MessageFormat.format(JGitText.get().invalidRefName, - refName)); - } - ObjectId oid = ref.getObjectId(); - if (oid == null) { - throw new PackProtocolException( - MessageFormat.format(JGitText.get().invalidRefName, - refName)); - } - reqBuilder.addWantedRef(refName, oid); - reqBuilder.addWantsIds(oid); - } else if (line.startsWith("have ")) { //$NON-NLS-1$ - reqBuilder.addPeerHas(ObjectId.fromString(line.substring(5))); - } else if (line.equals("done")) { //$NON-NLS-1$ - reqBuilder.setDoneReceived(); - } else if (line.equals(OPTION_THIN_PACK)) { - reqBuilder.addOption(OPTION_THIN_PACK); - } else if (line.equals(OPTION_NO_PROGRESS)) { - reqBuilder.addOption(OPTION_NO_PROGRESS); - } else if (line.equals(OPTION_INCLUDE_TAG)) { - reqBuilder.addOption(OPTION_INCLUDE_TAG); - includeTag = true; - } else if (line.equals(OPTION_OFS_DELTA)) { - reqBuilder.addOption(OPTION_OFS_DELTA); - } else if (line.startsWith("shallow ")) { //$NON-NLS-1$ - reqBuilder.addClientShallowCommit( - ObjectId.fromString(line.substring(8))); - } else if (line.startsWith("deepen ")) { //$NON-NLS-1$ - int parsedDepth = Integer.parseInt(line.substring(7)); - if (parsedDepth <= 0) { - throw new PackProtocolException( - MessageFormat.format(JGitText.get().invalidDepth, - Integer.valueOf(parsedDepth))); - } - if (reqBuilder.getDeepenSince() != 0) { - throw new PackProtocolException( - JGitText.get().deepenSinceWithDeepen); - } - if (reqBuilder.hasDeepenNotRefs()) { - throw new PackProtocolException( - JGitText.get().deepenNotWithDeepen); - } - reqBuilder.setDepth(parsedDepth); - } else if (line.startsWith("deepen-not ")) { //$NON-NLS-1$ - reqBuilder.addDeepenNotRef(line.substring(11)); - if (reqBuilder.getDepth() != 0) { - throw new PackProtocolException( - JGitText.get().deepenNotWithDeepen); - } - } else if (line.equals(OPTION_DEEPEN_RELATIVE)) { - reqBuilder.addOption(OPTION_DEEPEN_RELATIVE); - } else if (line.startsWith("deepen-since ")) { //$NON-NLS-1$ - int ts = Integer.parseInt(line.substring(13)); - if (ts <= 0) { - throw new PackProtocolException( - MessageFormat.format( - JGitText.get().invalidTimestamp, line)); - } - if (reqBuilder.getDepth() != 0) { - throw new PackProtocolException( - JGitText.get().deepenSinceWithDeepen); - } - reqBuilder.setDeepenSince(ts); - } else if (transferConfig.isAllowFilter() - && line.startsWith(OPTION_FILTER + ' ')) { - if (filterReceived) { - throw new PackProtocolException(JGitText.get().tooManyFilters); - } - filterReceived = true; - reqBuilder.setFilterBlobLimit(parseFilter( - line.substring(OPTION_FILTER.length() + 1))); - } else { - throw new PackProtocolException(MessageFormat - .format(JGitText.get().unexpectedPacketLine, line)); - } - } + ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig); + FetchV2Request req = parser.parseFetchRequest(pckIn, + db.getRefDatabase()); rawOut.stopBuffering(); - FetchV2Request req = reqBuilder.build(); protocolV2Hook.onFetch(req); // TODO(ifrade): Refactor to pass around the Request object, instead of @@ -1139,7 +1038,7 @@ public class UploadPack { pckOut.writeDelim(); pckOut.writeString("packfile\n"); //$NON-NLS-1$ sendPack(new PackStatistics.Accumulator(), - includeTag + req.getOptions().contains(OPTION_INCLUDE_TAG) ? db.getRefDatabase().getRefsByPrefix(R_TAGS) : null, unshallowCommits); @@ -1459,37 +1358,6 @@ public class UploadPack { return msgOut; } - private long parseFilter(String arg) throws PackProtocolException { - long blobLimit = -1; - - if (arg.equals("blob:none")) { //$NON-NLS-1$ - blobLimit = 0; - } else if (arg.startsWith("blob:limit=")) { //$NON-NLS-1$ - try { - blobLimit = Long.parseLong( - arg.substring("blob:limit=".length())); //$NON-NLS-1$ - } catch (NumberFormatException e) { - throw new PackProtocolException( - MessageFormat.format(JGitText.get().invalidFilter, - arg)); - } - } - /* - * We must have (1) either "blob:none" or - * "blob:limit=" set (because we only support - * blob size limits for now), and (2) if the - * latter, then it must be nonnegative. Throw - * if (1) or (2) is not met. - */ - if (blobLimit < 0) { - throw new PackProtocolException( - MessageFormat.format(JGitText.get().invalidFilter, - arg)); - } - - return blobLimit; - } - private void recvWants() throws IOException { boolean isFirst = true; boolean filterReceived = false; @@ -1530,7 +1398,7 @@ public class UploadPack { } filterReceived = true; - filterBlobLimit = parseFilter(arg); + filterBlobLimit = ProtocolV2Parser.filterLine(arg); continue; }