Browse Source

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
stable-3.1
Dave Borowitz 12 years ago committed by Gerrit Code Review @ Eclipse.org
parent
commit
4af82ed04c
  1. 7
      org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
  2. 54
      org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
  3. 281
      org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java

7
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$ 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 { static enum MultiAck {
OFF, CONTINUE, DETAILED; OFF, CONTINUE, DETAILED;
} }

54
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java

@ -43,11 +43,17 @@
package org.eclipse.jgit.transport; 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;
import org.eclipse.jgit.lib.Config.SectionParser; 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 { public class TransferConfig {
/** Key for {@link Config#get(SectionParser)}. */ /** Key for {@link Config#get(SectionParser)}. */
@ -58,9 +64,18 @@ public class TransferConfig {
}; };
private final boolean fsckObjects; private final boolean fsckObjects;
private final boolean allowTipSha1InWant;
private final String[] hideRefs;
TransferConfig(final Repository db) {
this(db.getConfig());
}
private TransferConfig(final Config rc) { private TransferConfig(final Config rc) {
fsckObjects = rc.getBoolean("receive", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$ 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() { public boolean isFsckObjects() {
return fsckObjects; 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);
}
};
}
} }

281
org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java

@ -49,6 +49,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; 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. * Implements the server side of a fetch connection, transmitting objects.
*/ */
public class UploadPack { 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_INCLUDE_TAG = BasePackFetchConnection.OPTION_INCLUDE_TAG;
static final String OPTION_MULTI_ACK = BasePackFetchConnection.OPTION_MULTI_ACK; static final String OPTION_MULTI_ACK = BasePackFetchConnection.OPTION_MULTI_ACK;
@ -113,12 +116,55 @@ public class UploadPack {
public static enum RequestPolicy { public static enum RequestPolicy {
/** Client may only ask for objects the server advertised a reference for. */ /** Client may only ask for objects the server advertised a reference for. */
ADVERTISED, 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, 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. */ /** Client may ask for any SHA-1 in the repository. */
ANY; 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. */ /** Data in the first line of a request, the line itself plus options. */
public static class FirstLine { public static class FirstLine {
private final String line; private final String line;
@ -166,6 +212,9 @@ public class UploadPack {
/** Configuration to pass into the PackWriter. */ /** Configuration to pass into the PackWriter. */
private PackConfig packConfig; private PackConfig packConfig;
/** Configuration for various transfer options. */
private TransferConfig transferConfig;
/** Timeout in seconds to wait for client interaction. */ /** Timeout in seconds to wait for client interaction. */
private int timeout; private int timeout;
@ -253,7 +302,7 @@ public class UploadPack {
private final RevFlagSet SAVE; private final RevFlagSet SAVE;
private RequestPolicy requestPolicy = RequestPolicy.ADVERTISED; private RequestValidator requestValidator = new AdvertisedRequestValidator();
private MultiAck multiAck = MultiAck.OFF; private MultiAck multiAck = MultiAck.OFF;
@ -285,6 +334,8 @@ public class UploadPack {
SAVE.add(PEER_HAS); SAVE.add(PEER_HAS);
SAVE.add(COMMON); SAVE.add(COMMON);
SAVE.add(SATISFIED); SAVE.add(SATISFIED);
setTransferConfig(null);
} }
/** @return the repository this upload is reading from. */ /** @return the repository this upload is reading from. */
@ -324,6 +375,9 @@ public class UploadPack {
refs = allRefs; refs = allRefs;
else else
refs = db.getAllRefs(); refs = db.getAllRefs();
if (refFilter == RefFilter.DEFAULT)
refs = transferConfig.getRefFilter().filter(refs);
else
refs = refFilter.filter(refs); refs = refFilter.filter(refs);
} }
@ -363,13 +417,24 @@ public class UploadPack {
*/ */
public void setBiDirectionalPipe(final boolean twoWay) { public void setBiDirectionalPipe(final boolean twoWay) {
biDirectionalPipe = 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() { 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}, * By default the policy is {@link RequestPolicy#ADVERTISED},
* which is the Git default requiring clients to only ask for an * which is the Git default requiring clients to only ask for an
* object that a reference directly points to. This may be relaxed * object that a reference directly points to. This may be relaxed
* to {@link RequestPolicy#REACHABLE_COMMIT} when callers * to {@link RequestPolicy#REACHABLE_COMMIT} or
* have {@link #setBiDirectionalPipe(boolean)} set to false. * {@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) { 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 */ /** @return the hook used while advertising the refs to the client */
@ -417,7 +511,8 @@ public class UploadPack {
* <p> * <p>
* Only refs allowed by this filter will be sent to the client. * Only refs allowed by this filter will be sent to the client.
* The filter is run against the refs specified by the * 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 * @param refFilter
* the filter; may be null to show all refs. * the filter; may be null to show all refs.
@ -452,6 +547,17 @@ public class UploadPack {
this.packConfig = pc; 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. */ /** @return the configured logger. */
public UploadPackLogger getLogger() { public UploadPackLogger getLogger() {
return logger; return logger;
@ -558,15 +664,10 @@ public class UploadPack {
private void service() throws IOException { private void service() throws IOException {
if (biDirectionalPipe) if (biDirectionalPipe)
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
else if (requestPolicy == RequestPolicy.ANY) else if (requestValidator instanceof AnyRequestValidator)
advertised = Collections.emptySet(); advertised = Collections.emptySet();
else { else
advertised = new HashSet<ObjectId>(); advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
for (Ref ref : getAdvertisedOrDefaultRefs().values()) {
if (ref.getObjectId() != null)
advertised.add(ref.getObjectId());
}
}
boolean sendPack; boolean sendPack;
try { try {
@ -618,6 +719,15 @@ public class UploadPack {
sendPack(); 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) { private void reportErrorDuringNegotiate(String msg) {
try { try {
pckOut.writeString("ERR " + msg + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ pckOut.writeString("ERR " + msg + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
@ -693,6 +803,11 @@ public class UploadPack {
adv.advertiseCapability(OPTION_SHALLOW); adv.advertiseCapability(OPTION_SHALLOW);
if (!biDirectionalPipe) if (!biDirectionalPipe)
adv.advertiseCapability(OPTION_NO_DONE); 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); adv.setDerefTags(true);
advertised = adv.send(getAdvertisedOrDefaultRefs()); advertised = adv.send(getAdvertisedOrDefaultRefs());
adv.end(); adv.end();
@ -921,27 +1036,13 @@ public class UploadPack {
private void parseWants() throws IOException { private void parseWants() throws IOException {
AsyncRevObjectQueue q = walk.parseAny(wantIds, true); AsyncRevObjectQueue q = walk.parseAny(wantIds, true);
try { try {
List<RevCommit> checkReachable = null; List<RevObject> notAdvertisedWants = null;
RevObject obj; RevObject obj;
while ((obj = q.next()) != null) { while ((obj = q.next()) != null) {
if (!advertised.contains(obj)) { if (!advertised.contains(obj)) {
switch (requestPolicy) { if (notAdvertisedWants == null)
case ADVERTISED: notAdvertisedWants = new ArrayList<RevObject>();
default: notAdvertisedWants.add(obj);
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;
}
} }
want(obj); want(obj);
@ -953,8 +1054,8 @@ public class UploadPack {
want(obj); want(obj);
} }
} }
if (checkReachable != null) if (notAdvertisedWants != null)
checkNotAdvertisedWants(checkReachable); requestValidator.checkWants(this, notAdvertisedWants);
wantIds.clear(); wantIds.clear();
} catch (MissingObjectException notFound) { } catch (MissingObjectException notFound) {
ObjectId id = notFound.getObjectId(); 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 { throws MissingObjectException, IncorrectObjectTypeException, IOException {
// Walk the requested commits back to the advertised commits. // Walk the requested commits back to the provided set of commits. If any
// If any commit exists, a branch was deleted or rewound and // commit exists, a branch was deleted or rewound and the repository owner
// the repository owner no longer exports that requested item. // no longer exports that requested item. If the requested commit is merged
// If the requested commit is merged into an advertised branch // into an advertised branch it will be marked UNINTERESTING and no commits
// it will be marked UNINTERESTING and no commits return. // return.
for (RevCommit c : notAdvertisedWants) for (RevObject obj : notAdvertisedWants) {
walk.markStart(c); if (!(obj instanceof RevCommit))
for (ObjectId id : advertised) { throw new PackProtocolException(MessageFormat.format(
JGitText.get().wantNotValid, obj.name()));
walk.markStart((RevCommit) obj);
}
for (ObjectId id : reachableFrom) {
try { try {
walk.markUninteresting(walk.parseCommit(id)); walk.markUninteresting(walk.parseCommit(id));
} catch (IncorrectObjectTypeException notCommit) { } catch (IncorrectObjectTypeException notCommit) {

Loading…
Cancel
Save