Add RequestPolicy.TIP to allow fetching non-advertised ref tips
Users of UploadPack may set a custom RefFilter or AdvertisedRefsHook that limits which refs are advertised, but clients may learn of a SHA-1 that the server should have as a ref tip through some alternative means. Support serving such objects from the server side with a new RequestPolicy. As with ADVERTISED, we need a special relaxed RequestPolicy to allow commits reachable from the set of valid tips for unidirectional connections. Change-Id: I0d0cc4f8ee04d265e5be8221b9384afb1b374315
This commit is contained in:
parent
21b3a16ab7
commit
fee679b587
|
@ -49,6 +49,7 @@
|
||||||
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;
|
||||||
|
@ -113,8 +114,27 @@ 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 some reference, even if
|
||||||
|
* that reference wasn't 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;
|
||||||
}
|
}
|
||||||
|
@ -363,8 +383,12 @@ public boolean isBiDirectionalPipe() {
|
||||||
*/
|
*/
|
||||||
public void setBiDirectionalPipe(final boolean twoWay) {
|
public void setBiDirectionalPipe(final boolean twoWay) {
|
||||||
biDirectionalPipe = twoWay;
|
biDirectionalPipe = twoWay;
|
||||||
if (!biDirectionalPipe && requestPolicy == RequestPolicy.ADVERTISED)
|
if (!biDirectionalPipe) {
|
||||||
requestPolicy = RequestPolicy.REACHABLE_COMMIT;
|
if (requestPolicy == RequestPolicy.ADVERTISED)
|
||||||
|
requestPolicy = RequestPolicy.REACHABLE_COMMIT;
|
||||||
|
else if (requestPolicy == RequestPolicy.TIP)
|
||||||
|
requestPolicy = RequestPolicy.REACHABLE_COMMIT_TIP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return policy used by the service to validate client requests. */
|
/** @return policy used by the service to validate client requests. */
|
||||||
|
@ -378,8 +402,9 @@ public RequestPolicy getRequestPolicy() {
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
public void setRequestPolicy(RequestPolicy policy) {
|
public void setRequestPolicy(RequestPolicy policy) {
|
||||||
requestPolicy = policy != null ? policy : RequestPolicy.ADVERTISED;
|
requestPolicy = policy != null ? policy : RequestPolicy.ADVERTISED;
|
||||||
|
@ -560,13 +585,8 @@ private void service() throws IOException {
|
||||||
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
|
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
|
||||||
else if (requestPolicy == RequestPolicy.ANY)
|
else if (requestPolicy == RequestPolicy.ANY)
|
||||||
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 +638,15 @@ else if (requestPolicy == RequestPolicy.ANY)
|
||||||
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$
|
||||||
|
@ -922,6 +951,8 @@ private void parseWants() throws IOException {
|
||||||
AsyncRevObjectQueue q = walk.parseAny(wantIds, true);
|
AsyncRevObjectQueue q = walk.parseAny(wantIds, true);
|
||||||
try {
|
try {
|
||||||
List<RevCommit> checkReachable = null;
|
List<RevCommit> checkReachable = null;
|
||||||
|
Set<ObjectId> reachableFrom = null;
|
||||||
|
Set<ObjectId> tips = null;
|
||||||
RevObject obj;
|
RevObject obj;
|
||||||
while ((obj = q.next()) != null) {
|
while ((obj = q.next()) != null) {
|
||||||
if (!advertised.contains(obj)) {
|
if (!advertised.contains(obj)) {
|
||||||
|
@ -935,8 +966,28 @@ private void parseWants() throws IOException {
|
||||||
throw new PackProtocolException(MessageFormat.format(
|
throw new PackProtocolException(MessageFormat.format(
|
||||||
JGitText.get().wantNotValid, obj));
|
JGitText.get().wantNotValid, obj));
|
||||||
}
|
}
|
||||||
if (checkReachable == null)
|
if (checkReachable == null) {
|
||||||
checkReachable = new ArrayList<RevCommit>();
|
checkReachable = new ArrayList<RevCommit>();
|
||||||
|
reachableFrom = advertised;
|
||||||
|
}
|
||||||
|
checkReachable.add((RevCommit) obj);
|
||||||
|
break;
|
||||||
|
case TIP:
|
||||||
|
if (tips == null)
|
||||||
|
tips = refIdSet(db.getAllRefs().values());
|
||||||
|
if (!tips.contains(obj))
|
||||||
|
throw new PackProtocolException(MessageFormat.format(
|
||||||
|
JGitText.get().wantNotValid, obj));
|
||||||
|
break;
|
||||||
|
case REACHABLE_COMMIT_TIP:
|
||||||
|
if (!(obj instanceof RevCommit)) {
|
||||||
|
throw new PackProtocolException(MessageFormat.format(
|
||||||
|
JGitText.get().wantNotValid, obj));
|
||||||
|
}
|
||||||
|
if (checkReachable == null) {
|
||||||
|
checkReachable = new ArrayList<RevCommit>();
|
||||||
|
reachableFrom = refIdSet(db.getAllRefs().values());
|
||||||
|
}
|
||||||
checkReachable.add((RevCommit) obj);
|
checkReachable.add((RevCommit) obj);
|
||||||
break;
|
break;
|
||||||
case ANY:
|
case ANY:
|
||||||
|
@ -954,7 +1005,7 @@ private void parseWants() throws IOException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (checkReachable != null)
|
if (checkReachable != null)
|
||||||
checkNotAdvertisedWants(checkReachable);
|
checkNotAdvertisedWants(checkReachable, reachableFrom);
|
||||||
wantIds.clear();
|
wantIds.clear();
|
||||||
} catch (MissingObjectException notFound) {
|
} catch (MissingObjectException notFound) {
|
||||||
ObjectId id = notFound.getObjectId();
|
ObjectId id = notFound.getObjectId();
|
||||||
|
@ -972,17 +1023,18 @@ private void want(RevObject obj) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkNotAdvertisedWants(List<RevCommit> notAdvertisedWants)
|
private void checkNotAdvertisedWants(List<RevCommit> 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 (RevCommit c : notAdvertisedWants)
|
||||||
walk.markStart(c);
|
walk.markStart(c);
|
||||||
for (ObjectId id : advertised) {
|
for (ObjectId id : reachableFrom) {
|
||||||
try {
|
try {
|
||||||
walk.markUninteresting(walk.parseCommit(id));
|
walk.markUninteresting(walk.parseCommit(id));
|
||||||
} catch (IncorrectObjectTypeException notCommit) {
|
} catch (IncorrectObjectTypeException notCommit) {
|
||||||
|
|
Loading…
Reference in New Issue