|
|
@@ -49,6 +49,7 @@ import java.io.InputStream; |
|
|
|
import java.io.OutputStream; |
|
|
|
import java.text.MessageFormat; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
@@ -107,6 +108,16 @@ public class UploadPack { |
|
|
|
|
|
|
|
static final String OPTION_SHALLOW = BasePackFetchConnection.OPTION_SHALLOW; |
|
|
|
|
|
|
|
/** Policy the server uses to validate client requests */ |
|
|
|
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. */ |
|
|
|
REACHABLE_COMMIT, |
|
|
|
/** Client may ask for any SHA-1 in the repository. */ |
|
|
|
ANY; |
|
|
|
} |
|
|
|
|
|
|
|
/** Database we read the objects from. */ |
|
|
|
private final Repository db; |
|
|
|
|
|
|
@@ -198,6 +209,8 @@ public class UploadPack { |
|
|
|
|
|
|
|
private final RevFlagSet SAVE; |
|
|
|
|
|
|
|
private RequestPolicy requestPolicy = RequestPolicy.ADVERTISED; |
|
|
|
|
|
|
|
private MultiAck multiAck = MultiAck.OFF; |
|
|
|
|
|
|
|
private boolean noDone; |
|
|
@@ -243,12 +256,22 @@ public class UploadPack { |
|
|
|
|
|
|
|
/** @return all refs which were advertised to the client. */ |
|
|
|
public final Map<String, Ref> getAdvertisedRefs() { |
|
|
|
if (refs == null) { |
|
|
|
refs = refFilter.filter(db.getAllRefs()); |
|
|
|
} |
|
|
|
if (refs == null) |
|
|
|
setAdvertisedRefs(db.getAllRefs()); |
|
|
|
return refs; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @param allRefs |
|
|
|
* explicit set of references to claim as advertised by this |
|
|
|
* UploadPack instance. This overrides any references that |
|
|
|
* may exist in the source repository. The map is passed |
|
|
|
* to the configured {@link #getRefFilter()}. |
|
|
|
*/ |
|
|
|
public void setAdvertisedRefs(Map<String, Ref> allRefs) { |
|
|
|
refs = refFilter.filter(allRefs); |
|
|
|
} |
|
|
|
|
|
|
|
/** @return timeout (in seconds) before aborting an IO operation. */ |
|
|
|
public int getTimeout() { |
|
|
|
return timeout; |
|
|
@@ -285,6 +308,26 @@ 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. */ |
|
|
|
public RequestPolicy getRequestPolicy() { |
|
|
|
return requestPolicy; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @param policy |
|
|
|
* the policy used to enforce validation of a client's want list. |
|
|
|
* 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. |
|
|
|
*/ |
|
|
|
public void setRequestPolicy(RequestPolicy policy) { |
|
|
|
requestPolicy = policy != null ? policy : RequestPolicy.ADVERTISED; |
|
|
|
} |
|
|
|
|
|
|
|
/** @return the filter used while advertising the refs to the client */ |
|
|
@@ -406,6 +449,8 @@ public class UploadPack { |
|
|
|
private void service() throws IOException { |
|
|
|
if (biDirectionalPipe) |
|
|
|
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); |
|
|
|
else if (requestPolicy == RequestPolicy.ANY) |
|
|
|
advertised = Collections.emptySet(); |
|
|
|
else { |
|
|
|
advertised = new HashSet<ObjectId>(); |
|
|
|
for (Ref ref : getAdvertisedRefs().values()) { |
|
|
@@ -659,6 +704,7 @@ public class UploadPack { |
|
|
|
needMissing = true; |
|
|
|
} |
|
|
|
|
|
|
|
Set<RevObject> notAdvertisedWants = null; |
|
|
|
int haveCnt = 0; |
|
|
|
AsyncRevObjectQueue q = walk.parseAny(toParse, needMissing); |
|
|
|
try { |
|
|
@@ -682,10 +728,10 @@ public class UploadPack { |
|
|
|
// list wasn't parsed earlier, and was done in this batch. |
|
|
|
// |
|
|
|
if (wantIds.remove(obj)) { |
|
|
|
if (!advertised.contains(obj)) { |
|
|
|
String msg = MessageFormat.format( |
|
|
|
JGitText.get().wantNotValid, obj.name()); |
|
|
|
throw new PackProtocolException(msg); |
|
|
|
if (!advertised.contains(obj) && requestPolicy != RequestPolicy.ANY) { |
|
|
|
if (notAdvertisedWants == null) |
|
|
|
notAdvertisedWants = new HashSet<RevObject>(); |
|
|
|
notAdvertisedWants.add(obj); |
|
|
|
} |
|
|
|
|
|
|
|
if (!obj.has(WANT)) { |
|
|
@@ -745,6 +791,26 @@ public class UploadPack { |
|
|
|
} finally { |
|
|
|
q.release(); |
|
|
|
} |
|
|
|
|
|
|
|
// If the client asked for non advertised object, check our policy. |
|
|
|
if (notAdvertisedWants != null && !notAdvertisedWants.isEmpty()) { |
|
|
|
switch (requestPolicy) { |
|
|
|
case ADVERTISED: |
|
|
|
default: |
|
|
|
throw new PackProtocolException(MessageFormat.format( |
|
|
|
JGitText.get().wantNotValid, |
|
|
|
notAdvertisedWants.iterator().next().name())); |
|
|
|
|
|
|
|
case REACHABLE_COMMIT: |
|
|
|
checkNotAdvertisedWants(notAdvertisedWants); |
|
|
|
break; |
|
|
|
|
|
|
|
case ANY: |
|
|
|
// Allow whatever was asked for. |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
int missCnt = peerHas.size() - haveCnt; |
|
|
|
|
|
|
|
// If we don't have one of the objects but we're also willing to |
|
|
@@ -787,6 +853,40 @@ public class UploadPack { |
|
|
|
return last; |
|
|
|
} |
|
|
|
|
|
|
|
private void checkNotAdvertisedWants(Set<RevObject> notAdvertisedWants) |
|
|
|
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 (RevObject o : notAdvertisedWants) { |
|
|
|
if (!(o instanceof RevCommit)) { |
|
|
|
throw new PackProtocolException(MessageFormat.format( |
|
|
|
JGitText.get().wantNotValid, |
|
|
|
notAdvertisedWants.iterator().next().name())); |
|
|
|
} |
|
|
|
walk.markStart((RevCommit) o); |
|
|
|
} |
|
|
|
|
|
|
|
for (ObjectId id : advertised) { |
|
|
|
try { |
|
|
|
walk.markUninteresting(walk.parseCommit(id)); |
|
|
|
} catch (IncorrectObjectTypeException notCommit) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
RevCommit bad = walk.next(); |
|
|
|
if (bad != null) { |
|
|
|
throw new PackProtocolException(MessageFormat.format( |
|
|
|
JGitText.get().wantNotValid, |
|
|
|
bad.name())); |
|
|
|
} |
|
|
|
walk.reset(); |
|
|
|
} |
|
|
|
|
|
|
|
private void addCommonBase(final RevObject o) { |
|
|
|
if (!o.has(COMMON)) { |
|
|
|
o.add(COMMON); |
|
|
@@ -941,7 +1041,7 @@ public class UploadPack { |
|
|
|
pw.setThin(options.contains(OPTION_THIN_PACK)); |
|
|
|
pw.setReuseValidatingObjects(false); |
|
|
|
|
|
|
|
if (commonBase.isEmpty()) { |
|
|
|
if (commonBase.isEmpty() && refs != null) { |
|
|
|
Set<ObjectId> tagTargets = new HashSet<ObjectId>(); |
|
|
|
for (Ref ref : refs.values()) { |
|
|
|
if (ref.getPeeledObjectId() != null) |
|
|
@@ -968,7 +1068,7 @@ public class UploadPack { |
|
|
|
rw = ow; |
|
|
|
} |
|
|
|
|
|
|
|
if (options.contains(OPTION_INCLUDE_TAG)) { |
|
|
|
if (options.contains(OPTION_INCLUDE_TAG) && refs != null) { |
|
|
|
for (Ref ref : refs.values()) { |
|
|
|
ObjectId objectId = ref.getObjectId(); |
|
|
|
|