summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Borowitz <dborowitz@google.com>2013-07-10 14:50:41 -0400
committerGerrit Code Review @ Eclipse.org <gerrit@eclipse.org>2013-07-10 14:50:41 -0400
commit4af82ed04c9806293662d3a7460f11f14c5fdb3a (patch)
tree5704121bb6775ff37528d073f5cc04d4f434c499
parent4b1f368033a6978e3cadd68f840586ab9bc4c538 (diff)
parent360d8624ca42ed13ec99fb5f30c887118e581080 (diff)
downloadjgit-4af82ed04c9806293662d3a7460f11f14c5fdb3a.tar.gz
jgit-4af82ed04c9806293662d3a7460f11f14c5fdb3a.zip
Merge changes If386fe25,I52a17499,Id12e7f00,I264e028a,I0d52af8a,I0d0cc4f8
* changes: UploadPack: allow custom RequestValidator instances UploadPack: refactor want validation UploadPack: set RefFilter from TransportConfig UploadPack: configure RequestPolicy with TransportConfig UploadPack: advertise allow-tip-sha1-in-want Add RequestPolicy.TIP to allow fetching non-advertised ref tips
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java54
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java283
3 files changed, 294 insertions, 50 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index c82a0cde81..1b13d0ba2a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -181,6 +181,13 @@ public abstract class BasePackFetchConnection extends BasePackConnection
*/
public static final String OPTION_NO_DONE = "no-done"; //$NON-NLS-1$
+ /**
+ * The client supports fetching objects at the tip of any ref, even if not
+ * advertised.
+ * @since 3.1
+ */
+ public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = "allow-tip-sha1-in-want"; //$NON-NLS-1$
+
static enum MultiAck {
OFF, CONTINUE, DETAILED;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index c3e39868dd..1286718de2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -43,11 +43,17 @@
package org.eclipse.jgit.transport;
+import java.util.HashMap;
+import java.util.Map;
+
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
/**
- * The standard "transfer", "fetch" and "receive" configuration parameters.
+ * The standard "transfer", "fetch", "receive", and "uploadpack" configuration
+ * parameters.
*/
public class TransferConfig {
/** Key for {@link Config#get(SectionParser)}. */
@@ -58,9 +64,18 @@ public class TransferConfig {
};
private final boolean fsckObjects;
+ private final boolean allowTipSha1InWant;
+ private final String[] hideRefs;
+
+ TransferConfig(final Repository db) {
+ this(db.getConfig());
+ }
private TransferConfig(final Config rc) {
fsckObjects = rc.getBoolean("receive", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$
+ allowTipSha1InWant = rc.getBoolean(
+ "uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$
+ hideRefs = rc.getStringList("uploadpack", null, "hiderefs"); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
@@ -69,4 +84,41 @@ public class TransferConfig {
public boolean isFsckObjects() {
return fsckObjects;
}
+
+ /**
+ * @return allow clients to request non-advertised tip SHA-1s?
+ */
+ public boolean isAllowTipSha1InWant() {
+ return allowTipSha1InWant;
+ }
+
+ /**
+ * @return {@link RefFilter} respecting configured hidden refs.
+ */
+ public RefFilter getRefFilter() {
+ if (hideRefs.length == 0)
+ return RefFilter.DEFAULT;
+
+ return new RefFilter() {
+ public Map<String, Ref> filter(Map<String, Ref> refs) {
+ Map<String, Ref> result = new HashMap<String, Ref>();
+ for (Map.Entry<String, Ref> e : refs.entrySet()) {
+ boolean add = true;
+ for (String hide : hideRefs) {
+ if (e.getKey().equals(hide) || prefixMatch(hide, e.getKey())) {
+ add = false;
+ break;
+ }
+ }
+ if (add)
+ result.put(e.getKey(), e.getValue());
+ }
+ return result;
+ }
+
+ private boolean prefixMatch(String p, String s) {
+ return p.charAt(p.length() - 1) == '/' && s.startsWith(p);
+ }
+ };
+ }
}
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 e5e04d014e..0d23cf7929 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -49,6 +49,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -89,6 +90,8 @@ import org.eclipse.jgit.util.io.TimeoutOutputStream;
* Implements the server side of a fetch connection, transmitting objects.
*/
public class UploadPack {
+ static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = BasePackFetchConnection.OPTION_ALLOW_TIP_SHA1_IN_WANT;
+
static final String OPTION_INCLUDE_TAG = BasePackFetchConnection.OPTION_INCLUDE_TAG;
static final String OPTION_MULTI_ACK = BasePackFetchConnection.OPTION_MULTI_ACK;
@@ -113,12 +116,55 @@ public class UploadPack {
public static enum RequestPolicy {
/** Client may only ask for objects the server advertised a reference for. */
ADVERTISED,
- /** Client may ask for any commit reachable from a reference. */
+
+ /**
+ * Client may ask for any commit reachable from a reference advertised by
+ * the server.
+ */
REACHABLE_COMMIT,
+
+ /**
+ * Client may ask for objects that are the tip of any reference, even if not
+ * advertised.
+ * <p>
+ * This may happen, for example, when a custom {@link RefFilter} is set.
+ */
+ TIP,
+
+ /**
+ * Client may ask for any commit reachable from any reference, even if that
+ * reference wasn't advertised.
+ */
+ REACHABLE_COMMIT_TIP,
+
/** Client may ask for any SHA-1 in the repository. */
ANY;
}
+ /**
+ * Validator for client requests.
+ *
+ * @since 3.1
+ */
+ public interface RequestValidator {
+ /**
+ * Check a list of client wants against the request policy.
+ *
+ * @param up
+ * {@link UploadPack} instance.
+ * @param wants
+ * objects the client requested that were not advertised.
+ *
+ * @throws PackProtocolException
+ * if one or more wants is not valid.
+ * @throws IOException
+ * if a low-level exception occurred.
+ * @since 3.1
+ */
+ void checkWants(UploadPack up, List<RevObject> wants)
+ throws PackProtocolException, IOException;
+ }
+
/** Data in the first line of a request, the line itself plus options. */
public static class FirstLine {
private final String line;
@@ -166,6 +212,9 @@ public class UploadPack {
/** Configuration to pass into the PackWriter. */
private PackConfig packConfig;
+ /** Configuration for various transfer options. */
+ private TransferConfig transferConfig;
+
/** Timeout in seconds to wait for client interaction. */
private int timeout;
@@ -253,7 +302,7 @@ public class UploadPack {
private final RevFlagSet SAVE;
- private RequestPolicy requestPolicy = RequestPolicy.ADVERTISED;
+ private RequestValidator requestValidator = new AdvertisedRequestValidator();
private MultiAck multiAck = MultiAck.OFF;
@@ -285,6 +334,8 @@ public class UploadPack {
SAVE.add(PEER_HAS);
SAVE.add(COMMON);
SAVE.add(SATISFIED);
+
+ setTransferConfig(null);
}
/** @return the repository this upload is reading from. */
@@ -324,7 +375,10 @@ public class UploadPack {
refs = allRefs;
else
refs = db.getAllRefs();
- refs = refFilter.filter(refs);
+ if (refFilter == RefFilter.DEFAULT)
+ refs = transferConfig.getRefFilter().filter(refs);
+ else
+ refs = refFilter.filter(refs);
}
/** @return timeout (in seconds) before aborting an IO operation. */
@@ -363,13 +417,24 @@ public class UploadPack {
*/
public void setBiDirectionalPipe(final boolean twoWay) {
biDirectionalPipe = twoWay;
- if (!biDirectionalPipe && requestPolicy == RequestPolicy.ADVERTISED)
- requestPolicy = RequestPolicy.REACHABLE_COMMIT;
}
- /** @return policy used by the service to validate client requests. */
+ /**
+ * @return policy used by the service to validate client requests, or null for
+ * a custom request validator.
+ */
public RequestPolicy getRequestPolicy() {
- return requestPolicy;
+ if (requestValidator instanceof AdvertisedRequestValidator)
+ return RequestPolicy.ADVERTISED;
+ if (requestValidator instanceof ReachableCommitRequestValidator)
+ return RequestPolicy.REACHABLE_COMMIT;
+ if (requestValidator instanceof TipRequestValidator)
+ return RequestPolicy.TIP;
+ if (requestValidator instanceof ReachableCommitTipRequestValidator)
+ return RequestPolicy.REACHABLE_COMMIT_TIP;
+ if (requestValidator instanceof AnyRequestValidator)
+ return RequestPolicy.ANY;
+ return null;
}
/**
@@ -378,11 +443,40 @@ public class UploadPack {
* By default the policy is {@link RequestPolicy#ADVERTISED},
* which is the Git default requiring clients to only ask for an
* object that a reference directly points to. This may be relaxed
- * to {@link RequestPolicy#REACHABLE_COMMIT} when callers
- * have {@link #setBiDirectionalPipe(boolean)} set to false.
+ * to {@link RequestPolicy#REACHABLE_COMMIT} or
+ * {@link RequestPolicy#REACHABLE_COMMIT_TIP} when callers have
+ * {@link #setBiDirectionalPipe(boolean)} set to false.
+ * Overrides any policy specified in a {@link TransferConfig}.
*/
public void setRequestPolicy(RequestPolicy policy) {
- requestPolicy = policy != null ? policy : RequestPolicy.ADVERTISED;
+ switch (policy) {
+ case ADVERTISED:
+ default:
+ requestValidator = new AdvertisedRequestValidator();
+ break;
+ case REACHABLE_COMMIT:
+ requestValidator = new ReachableCommitRequestValidator();
+ break;
+ case TIP:
+ requestValidator = new TipRequestValidator();
+ break;
+ case REACHABLE_COMMIT_TIP:
+ requestValidator = new ReachableCommitTipRequestValidator();
+ break;
+ case ANY:
+ requestValidator = new AnyRequestValidator();
+ break;
+ }
+ }
+
+ /**
+ * @param validator
+ * custom validator for client want list.
+ * @since 3.1
+ */
+ public void setRequestValidator(RequestValidator validator) {
+ requestValidator = validator != null ? validator
+ : new AdvertisedRequestValidator();
}
/** @return the hook used while advertising the refs to the client */
@@ -417,7 +511,8 @@ public class UploadPack {
* <p>
* Only refs allowed by this filter will be sent to the client.
* The filter is run against the refs specified by the
- * {@link AdvertiseRefsHook} (if applicable).
+ * {@link AdvertiseRefsHook} (if applicable). If null or not set, uses the
+ * filter implied by the {@link TransferConfig}.
*
* @param refFilter
* the filter; may be null to show all refs.
@@ -452,6 +547,17 @@ public class UploadPack {
this.packConfig = pc;
}
+ /**
+ * @param tc
+ * configuration controlling transfer options. If null the source
+ * repository's settings will be used.
+ */
+ public void setTransferConfig(TransferConfig tc) {
+ this.transferConfig = tc != null ? tc : new TransferConfig(db);
+ setRequestPolicy(transferConfig.isAllowTipSha1InWant()
+ ? RequestPolicy.TIP : RequestPolicy.ADVERTISED);
+ }
+
/** @return the configured logger. */
public UploadPackLogger getLogger() {
return logger;
@@ -558,15 +664,10 @@ public class UploadPack {
private void service() throws IOException {
if (biDirectionalPipe)
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
- else if (requestPolicy == RequestPolicy.ANY)
+ else if (requestValidator instanceof AnyRequestValidator)
advertised = Collections.emptySet();
- else {
- advertised = new HashSet<ObjectId>();
- for (Ref ref : getAdvertisedOrDefaultRefs().values()) {
- if (ref.getObjectId() != null)
- advertised.add(ref.getObjectId());
- }
- }
+ else
+ advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
boolean sendPack;
try {
@@ -618,6 +719,15 @@ public class UploadPack {
sendPack();
}
+ private static Set<ObjectId> refIdSet(Collection<Ref> refs) {
+ Set<ObjectId> ids = new HashSet<ObjectId>(refs.size());
+ for (Ref ref : refs) {
+ if (ref.getObjectId() != null)
+ ids.add(ref.getObjectId());
+ }
+ return ids;
+ }
+
private void reportErrorDuringNegotiate(String msg) {
try {
pckOut.writeString("ERR " + msg + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -693,6 +803,11 @@ public class UploadPack {
adv.advertiseCapability(OPTION_SHALLOW);
if (!biDirectionalPipe)
adv.advertiseCapability(OPTION_NO_DONE);
+ RequestPolicy policy = getRequestPolicy();
+ if (policy == RequestPolicy.TIP
+ || policy == RequestPolicy.REACHABLE_COMMIT_TIP
+ || policy == null)
+ adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT);
adv.setDerefTags(true);
advertised = adv.send(getAdvertisedOrDefaultRefs());
adv.end();
@@ -921,27 +1036,13 @@ public class UploadPack {
private void parseWants() throws IOException {
AsyncRevObjectQueue q = walk.parseAny(wantIds, true);
try {
- List<RevCommit> checkReachable = null;
+ List<RevObject> notAdvertisedWants = null;
RevObject obj;
while ((obj = q.next()) != null) {
if (!advertised.contains(obj)) {
- switch (requestPolicy) {
- case ADVERTISED:
- default:
- throw new PackProtocolException(MessageFormat.format(
- JGitText.get().wantNotValid, obj));
- case REACHABLE_COMMIT:
- if (!(obj instanceof RevCommit)) {
- throw new PackProtocolException(MessageFormat.format(
- JGitText.get().wantNotValid, obj));
- }
- if (checkReachable == null)
- checkReachable = new ArrayList<RevCommit>();
- checkReachable.add((RevCommit) obj);
- break;
- case ANY:
- break;
- }
+ if (notAdvertisedWants == null)
+ notAdvertisedWants = new ArrayList<RevObject>();
+ notAdvertisedWants.add(obj);
}
want(obj);
@@ -953,8 +1054,8 @@ public class UploadPack {
want(obj);
}
}
- if (checkReachable != null)
- checkNotAdvertisedWants(checkReachable);
+ if (notAdvertisedWants != null)
+ requestValidator.checkWants(this, notAdvertisedWants);
wantIds.clear();
} catch (MissingObjectException notFound) {
ObjectId id = notFound.getObjectId();
@@ -972,17 +1073,101 @@ public class UploadPack {
}
}
- private void checkNotAdvertisedWants(List<RevCommit> notAdvertisedWants)
+ /**
+ * Validator corresponding to {@link RequestPolicy#ADVERTISED}.
+ *
+ * @since 3.1
+ */
+ public static final class AdvertisedRequestValidator
+ implements RequestValidator {
+ public void checkWants(UploadPack up, List<RevObject> wants)
+ throws PackProtocolException, IOException {
+ if (!up.isBiDirectionalPipe())
+ new ReachableCommitRequestValidator().checkWants(up, wants);
+ else if (!wants.isEmpty())
+ throw new PackProtocolException(MessageFormat.format(
+ JGitText.get().wantNotValid, wants.iterator().next().name()));
+ }
+ }
+
+ /**
+ * Validator corresponding to {@link RequestPolicy#REACHABLE_COMMIT}.
+ *
+ * @since 3.1
+ */
+ public static final class ReachableCommitRequestValidator
+ implements RequestValidator {
+ public void checkWants(UploadPack up, List<RevObject> wants)
+ throws PackProtocolException, IOException {
+ checkNotAdvertisedWants(up.getRevWalk(), wants,
+ refIdSet(up.getAdvertisedRefs().values()));
+ }
+ }
+
+ /**
+ * Validator corresponding to {@link RequestPolicy#TIP}.
+ *
+ * @since 3.1
+ */
+ public static final class TipRequestValidator implements RequestValidator {
+ public void checkWants(UploadPack up, List<RevObject> wants)
+ throws PackProtocolException, IOException {
+ if (!up.isBiDirectionalPipe())
+ new ReachableCommitTipRequestValidator().checkWants(up, wants);
+ else if (!wants.isEmpty()) {
+ Set<ObjectId> refIds =
+ refIdSet(up.getRepository().getAllRefs().values());
+ for (RevObject obj : wants) {
+ if (!refIds.contains(obj))
+ throw new PackProtocolException(MessageFormat.format(
+ JGitText.get().wantNotValid, obj.name()));
+ }
+ }
+ }
+ }
+
+ /**
+ * Validator corresponding to {@link RequestPolicy#REACHABLE_COMMIT_TIP}.
+ *
+ * @since 3.1
+ */
+ public static final class ReachableCommitTipRequestValidator
+ implements RequestValidator {
+ public void checkWants(UploadPack up, List<RevObject> wants)
+ throws PackProtocolException, IOException {
+ checkNotAdvertisedWants(up.getRevWalk(), wants,
+ refIdSet(up.getRepository().getAllRefs().values()));
+ }
+ }
+
+ /**
+ * Validator corresponding to {@link RequestPolicy#ANY}.
+ *
+ * @since 3.1
+ */
+ public static final class AnyRequestValidator implements RequestValidator {
+ public void checkWants(UploadPack up, List<RevObject> wants)
+ throws PackProtocolException, IOException {
+ // All requests are valid.
+ }
+ }
+
+ private static void checkNotAdvertisedWants(RevWalk walk,
+ List<RevObject> notAdvertisedWants, Set<ObjectId> reachableFrom)
throws MissingObjectException, IncorrectObjectTypeException, IOException {
- // Walk the requested commits back to the advertised commits.
- // If any commit exists, a branch was deleted or rewound and
- // the repository owner no longer exports that requested item.
- // If the requested commit is merged into an advertised branch
- // it will be marked UNINTERESTING and no commits return.
-
- for (RevCommit c : notAdvertisedWants)
- walk.markStart(c);
- for (ObjectId id : advertised) {
+ // Walk the requested commits back to the provided set of commits. If any
+ // commit exists, a branch was deleted or rewound and the repository owner
+ // no longer exports that requested item. If the requested commit is merged
+ // into an advertised branch it will be marked UNINTERESTING and no commits
+ // return.
+
+ for (RevObject obj : notAdvertisedWants) {
+ if (!(obj instanceof RevCommit))
+ throw new PackProtocolException(MessageFormat.format(
+ JGitText.get().wantNotValid, obj.name()));
+ walk.markStart((RevCommit) obj);
+ }
+ for (ObjectId id : reachableFrom) {
try {
walk.markUninteresting(walk.parseCommit(id));
} catch (IncorrectObjectTypeException notCommit) {