--- /dev/null
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * fetch protocol v2 request.
+ *
+ * <p>
+ * This is used as an input to {@link ProtocolV2Hook}.
+ *
+ * @since 5.1
+ */
+public final class FetchV2Request {
+ private final List<ObjectId> peerHas;
+
+ private final TreeMap<String, ObjectId> wantedRefs;
+
+ private final Set<ObjectId> wantsIds;
+
+ private final Set<ObjectId> clientShallowCommits;
+
+ private final int shallowSince;
+
+ private final List<String> shallowExcludeRefs;
+
+ private final int depth;
+
+ private final long filterBlobLimit;
+
+ private final Set<String> options;
+
+ private FetchV2Request(List<ObjectId> peerHas,
+ TreeMap<String, ObjectId> wantedRefs, Set<ObjectId> wantsIds,
+ Set<ObjectId> clientShallowCommits, int shallowSince,
+ List<String> shallowExcludeRefs, int depth, long filterBlobLimit,
+ Set<String> options) {
+ this.peerHas = peerHas;
+ this.wantedRefs = wantedRefs;
+ this.wantsIds = wantsIds;
+ this.clientShallowCommits = clientShallowCommits;
+ this.shallowSince = shallowSince;
+ this.shallowExcludeRefs = shallowExcludeRefs;
+ this.depth = depth;
+ this.filterBlobLimit = filterBlobLimit;
+ this.options = options;
+ }
+
+ /**
+ * @return object ids in the "have" lines of the request
+ */
+ @NonNull
+ List<ObjectId> getPeerHas() {
+ return this.peerHas;
+ }
+
+ /**
+ * @return list of references in the "want-ref" lines of the request
+ */
+ @NonNull
+ Map<String, ObjectId> getWantedRefs() {
+ return this.wantedRefs;
+ }
+
+ /**
+ * @return object ids in the "want" (and "want-ref") lines of the request
+ */
+ @NonNull
+ Set<ObjectId> getWantsIds() {
+ return wantsIds;
+ }
+
+ /**
+ * Shallow commits the client already has.
+ *
+ * These are sent by the client in "shallow" request lines.
+ *
+ * @return set of commits the client has declared as shallow.
+ */
+ @NonNull
+ Set<ObjectId> getClientShallowCommits() {
+ return clientShallowCommits;
+ }
+
+ /**
+ * The value in a "deepen-since" line in the request, indicating the
+ * timestamp where to stop fetching/cloning.
+ *
+ * @return timestamp where to stop the shallow fetch/clone. Defaults to 0 if
+ * not set in the request
+ */
+ int getShallowSince() {
+ return shallowSince;
+ }
+
+ /**
+ * @return the refs in "deepen-not" lines in the request.
+ */
+ @NonNull
+ List<String> getShallowExcludeRefs() {
+ return shallowExcludeRefs;
+ }
+
+ /**
+ * @return the depth set in a "deepen" line. 0 by default.
+ */
+ int getDepth() {
+ return depth;
+ }
+
+ /**
+ * @return the blob limit set in a "filter" line (-1 if not set)
+ */
+ long getFilterBlobLimit() {
+ return filterBlobLimit;
+ }
+
+ /**
+ * Options that tune the expected response from the server, like
+ * "thin-pack", "no-progress" or "ofs-delta"
+ *
+ * These are options listed and well-defined in the git protocol
+ * specification
+ *
+ * @return options found in the request lines
+ */
+ @NonNull
+ Set<String> getOptions() {
+ return options;
+ }
+
+ /** @return A builder of {@link FetchV2Request}. */
+ static Builder builder() {
+ return new Builder();
+ }
+
+
+ /** A builder for {@link FetchV2Request}. */
+ static final class Builder {
+ List<ObjectId> peerHas = new ArrayList<>();
+
+ TreeMap<String, ObjectId> wantedRefs = new TreeMap<>();
+
+ Set<ObjectId> wantsIds = new HashSet<>();
+
+ Set<ObjectId> clientShallowCommits = new HashSet<>();
+
+ List<String> shallowExcludeRefs = new ArrayList<>();
+
+ Set<String> options = new HashSet<>();
+
+ int depth;
+
+ int shallowSince;
+
+ long filterBlobLimit = -1;
+
+ private Builder() {
+ }
+
+ /**
+ * @param objectId
+ * from a "have" line in a fetch request
+ * @return the builder
+ */
+ Builder addPeerHas(ObjectId objectId) {
+ peerHas.add(objectId);
+ return this;
+ }
+
+ /**
+ * From a "want-ref" line in a fetch request
+ *
+ * @param refName
+ * reference name
+ * @param oid
+ * object id
+ * @return the builder
+ */
+ Builder addWantedRef(String refName, ObjectId oid) {
+ wantedRefs.put(refName, oid);
+ return this;
+ }
+
+ /**
+ * @param option
+ * fetch request lines acting as options
+ * @return the builder
+ */
+ Builder addOption(String option) {
+ options.add(option);
+ return this;
+ }
+
+ /**
+ * @param objectId
+ * from a "want" line in a fetch request
+ * @return the builder
+ */
+ Builder addWantsIds(ObjectId objectId) {
+ wantsIds.add(objectId);
+ return this;
+ }
+
+ /**
+ * @param shallowOid
+ * from a "shallow" line in the fetch request
+ * @return the builder
+ */
+ Builder addClientShallowCommit(ObjectId shallowOid) {
+ this.clientShallowCommits.add(shallowOid);
+ return this;
+ }
+
+ /**
+ * @param d
+ * from a "deepen" line in the fetch request
+ * @return the builder
+ */
+ Builder setDepth(int d) {
+ this.depth = d;
+ return this;
+ }
+
+ /**
+ * @return depth set in the request (via a "deepen" line). Defaulting to
+ * 0 if not set.
+ */
+ int getDepth() {
+ return this.depth;
+ }
+
+ /**
+ * @return if there has been any "deepen not" line in the request
+ */
+ boolean hasShallowExcludeRefs() {
+ return shallowExcludeRefs.size() > 0;
+ }
+
+ /**
+ * @param shallowExcludeRef reference in a "deepen not" line
+ * @return the builder
+ */
+ Builder addShallowExcludeRefs(String shallowExcludeRef) {
+ this.shallowExcludeRefs.add(shallowExcludeRef);
+ return this;
+ }
+
+ /**
+ * @param value
+ * shallow since value received in a "deepen since" line
+ * @return the builder
+ */
+ Builder setShallowSince(int value) {
+ this.shallowSince = value;
+ return this;
+ }
+
+ /**
+ * @return shallow since value, sent before in a "deepen since" line. 0
+ * by default.
+ */
+ int getShallowSince() {
+ return this.shallowSince;
+ }
+
+ /**
+ * @param filterBlobLimit
+ * set in a "filter" line
+ * @return the builder
+ */
+ Builder setFilterBlobLimit(long filterBlobLimit) {
+ this.filterBlobLimit = filterBlobLimit;
+ return this;
+ }
+
+ /**
+ * @return Initialized fetch request
+ */
+ FetchV2Request build() {
+ return new FetchV2Request(peerHas, wantedRefs, wantsIds,
+ clientShallowCommits, shallowSince, shallowExcludeRefs,
+ depth, filterBlobLimit, options);
+ }
+ }
+}
throws ServiceMayNotContinueException {
// Do nothing by default.
}
+
+ /**
+ * @param req the fetch request
+ * @throws ServiceMayNotContinueException abort; the message will be sent to the user
+ */
+ default void onFetch(FetchV2Request req)
+ throws ServiceMayNotContinueException {
+ // Do nothing by default
+ }
}
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TreeMap;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.CorruptObjectException;
}
private void fetchV2() throws IOException {
- options = new HashSet<>();
+ FetchV2Request.Builder reqBuilder = FetchV2Request.builder();
// Packs are always sent multiplexed and using full 64K
// lengths.
- options.add(OPTION_SIDE_BAND_64K);
+ reqBuilder.addOption(OPTION_SIDE_BAND_64K);
// Depending on the requestValidator, #processHaveLines may
// require that advertised be set. Set it only in the required
}
String line;
- List<ObjectId> peerHas = new ArrayList<>();
boolean doneReceived = false;
// Currently, we do not support any capabilities, so the next
boolean includeTag = false;
boolean filterReceived = false;
- TreeMap<String, ObjectId> wantedRefs = new TreeMap<>();
while ((line = pckIn.readString()) != PacketLineIn.END) {
if (line.startsWith("want ")) { //$NON-NLS-1$
- wantIds.add(ObjectId.fromString(line.substring(5)));
+ 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);
MessageFormat.format(JGitText.get().invalidRefName,
refName));
}
- wantedRefs.put(refName, oid);
- wantIds.add(oid);
+ reqBuilder.addWantedRef(refName, oid);
+ reqBuilder.addWantsIds(oid);
} else if (line.startsWith("have ")) { //$NON-NLS-1$
- peerHas.add(ObjectId.fromString(line.substring(5)));
+ reqBuilder.addPeerHas(ObjectId.fromString(line.substring(5)));
} else if (line.equals("done")) { //$NON-NLS-1$
doneReceived = true;
} else if (line.equals(OPTION_THIN_PACK)) {
- options.add(OPTION_THIN_PACK);
+ reqBuilder.addOption(OPTION_THIN_PACK);
} else if (line.equals(OPTION_NO_PROGRESS)) {
- options.add(OPTION_NO_PROGRESS);
+ reqBuilder.addOption(OPTION_NO_PROGRESS);
} else if (line.equals(OPTION_INCLUDE_TAG)) {
- options.add(OPTION_INCLUDE_TAG);
+ reqBuilder.addOption(OPTION_INCLUDE_TAG);
includeTag = true;
} else if (line.equals(OPTION_OFS_DELTA)) {
- options.add(OPTION_OFS_DELTA);
+ reqBuilder.addOption(OPTION_OFS_DELTA);
} else if (line.startsWith("shallow ")) { //$NON-NLS-1$
- clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
+ reqBuilder.addClientShallowCommit(
+ ObjectId.fromString(line.substring(8)));
} else if (line.startsWith("deepen ")) { //$NON-NLS-1$
- depth = Integer.parseInt(line.substring(7));
- if (depth <= 0) {
+ int parsedDepth = Integer.parseInt(line.substring(7));
+ if (parsedDepth <= 0) {
throw new PackProtocolException(
MessageFormat.format(JGitText.get().invalidDepth,
Integer.valueOf(depth)));
}
- if (shallowSince != 0) {
+ if (reqBuilder.getShallowSince() != 0) {
throw new PackProtocolException(
JGitText.get().deepenSinceWithDeepen);
}
- if (!shallowExcludeRefs.isEmpty()) {
+ if (reqBuilder.hasShallowExcludeRefs()) {
throw new PackProtocolException(
JGitText.get().deepenNotWithDeepen);
}
+ reqBuilder.setDepth(parsedDepth);
} else if (line.startsWith("deepen-not ")) { //$NON-NLS-1$
- shallowExcludeRefs.add(line.substring(11));
- if (depth != 0) {
+ reqBuilder.addShallowExcludeRefs(line.substring(11));
+ if (reqBuilder.getDepth() != 0) {
throw new PackProtocolException(
JGitText.get().deepenNotWithDeepen);
}
} else if (line.equals(OPTION_DEEPEN_RELATIVE)) {
- options.add(OPTION_DEEPEN_RELATIVE);
+ reqBuilder.addOption(OPTION_DEEPEN_RELATIVE);
} else if (line.startsWith("deepen-since ")) { //$NON-NLS-1$
- shallowSince = Integer.parseInt(line.substring(13));
- if (shallowSince <= 0) {
+ int parsedShallowSince = Integer.parseInt(line.substring(13));
+ if (parsedShallowSince <= 0) {
throw new PackProtocolException(
MessageFormat.format(
JGitText.get().invalidTimestamp, line));
}
- if (depth != 0) {
+ if (reqBuilder.getDepth() != 0) {
throw new PackProtocolException(
JGitText.get().deepenSinceWithDeepen);
}
+ reqBuilder.setShallowSince(parsedShallowSince);
} else if (transferConfig.isAllowFilter()
&& line.startsWith(OPTION_FILTER + ' ')) {
if (filterReceived) {
throw new PackProtocolException(JGitText.get().tooManyFilters);
}
filterReceived = true;
- parseFilter(line.substring(OPTION_FILTER.length() + 1));
+ reqBuilder.setFilterBlobLimit(parseFilter(
+ line.substring(OPTION_FILTER.length() + 1)));
} else {
throw new PackProtocolException(MessageFormat
.format(JGitText.get().unexpectedPacketLine, line));
}
rawOut.stopBuffering();
+ FetchV2Request req = reqBuilder.build();
+ protocolV2Hook.onFetch(req);
+
+ // TODO(ifrade): Refactor to pass around the Request object, instead of
+ // copying data back to class fields
+ options = req.getOptions();
+ wantIds.addAll(req.getWantsIds());
+ clientShallowCommits.addAll(req.getClientShallowCommits());
+ depth = req.getDepth();
+ shallowSince = req.getShallowSince();
+ filterBlobLimit = req.getFilterBlobLimit();
+ shallowExcludeRefs = req.getShallowExcludeRefs();
+
boolean sectionSent = false;
@Nullable List<ObjectId> shallowCommits = null;
List<ObjectId> unshallowCommits = new ArrayList<>();
- if (!clientShallowCommits.isEmpty()) {
+ if (!req.getClientShallowCommits().isEmpty()) {
verifyClientShallow();
}
- if (depth != 0 || shallowSince != 0 || !shallowExcludeRefs.isEmpty()) {
+ if (req.getDepth() != 0 || req.getShallowSince() != 0
+ || !req.getShallowExcludeRefs().isEmpty()) {
shallowCommits = new ArrayList<>();
processShallow(shallowCommits, unshallowCommits, false);
}
- if (!clientShallowCommits.isEmpty())
- walk.assumeShallow(clientShallowCommits);
+ if (!req.getClientShallowCommits().isEmpty())
+ walk.assumeShallow(req.getClientShallowCommits());
if (doneReceived) {
- processHaveLines(peerHas, ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE));
+ processHaveLines(req.getPeerHas(), ObjectId.zeroId(),
+ new PacketLineOut(NullOutputStream.INSTANCE));
} else {
pckOut.writeString("acknowledgments\n"); //$NON-NLS-1$
- for (ObjectId id : peerHas) {
+ for (ObjectId id : req.getPeerHas()) {
if (walk.getObjectReader().has(id)) {
pckOut.writeString("ACK " + id.getName() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
- processHaveLines(peerHas, ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE));
+ processHaveLines(req.getPeerHas(), ObjectId.zeroId(),
+ new PacketLineOut(NullOutputStream.INSTANCE));
if (okToGiveUp()) {
pckOut.writeString("ready\n"); //$NON-NLS-1$
} else if (commonBase.isEmpty()) {
sectionSent = true;
}
- if (!wantedRefs.isEmpty()) {
+ if (!req.getWantedRefs().isEmpty()) {
if (sectionSent) {
pckOut.writeDelim();
}
pckOut.writeString("wanted-refs\n"); //$NON-NLS-1$
- for (Map.Entry<String, ObjectId> entry : wantedRefs.entrySet()) {
+ for (Map.Entry<String, ObjectId> entry : req.getWantedRefs()
+ .entrySet()) {
pckOut.writeString(entry.getValue().getName() + ' ' +
entry.getKey() + '\n');
}
return msgOut;
}
- private void parseFilter(String arg) throws PackProtocolException {
+ private long parseFilter(String arg) throws PackProtocolException {
+ long blobLimit = -1;
+
if (arg.equals("blob:none")) { //$NON-NLS-1$
- filterBlobLimit = 0;
+ blobLimit = 0;
} else if (arg.startsWith("blob:limit=")) { //$NON-NLS-1$
try {
- filterBlobLimit = Long.parseLong(
+ blobLimit = Long.parseLong(
arg.substring("blob:limit=".length())); //$NON-NLS-1$
} catch (NumberFormatException e) {
throw new PackProtocolException(
* latter, then it must be nonnegative. Throw
* if (1) or (2) is not met.
*/
- if (filterBlobLimit < 0) {
+ if (blobLimit < 0) {
throw new PackProtocolException(
MessageFormat.format(JGitText.get().invalidFilter,
arg));
}
+
+ return blobLimit;
}
private void recvWants() throws IOException {
}
filterReceived = true;
- parseFilter(arg);
+ filterBlobLimit = parseFilter(arg);
continue;
}