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
tags/v3.1.0.201309270735-rc1
Dave Borowitz 10 years ago
parent
commit
4af82ed04c

+ 7
- 0
org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java View File

@@ -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;
}

+ 53
- 1
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java View File

@@ -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);
}
};
}
}

+ 234
- 49
org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java View File

@@ -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) {

Loading…
Cancel
Save