diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 4ba446edb..f85c22ddb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -76,6 +76,7 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand.Result; +import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; import org.eclipse.jgit.util.io.TimeoutInputStream; import org.eclipse.jgit.util.io.TimeoutOutputStream; @@ -519,7 +520,7 @@ public void println() { private void service() throws IOException { if (biDirectionalPipe) - sendAdvertisedRefs(); + sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); else refs = db.getAllRefs(); recvCommands(); @@ -574,9 +575,17 @@ private void unlockPack() { } } - private void sendAdvertisedRefs() throws IOException { + /** + * Generate an advertisement of available refs and capabilities. + * + * @param adv + * the advertisement formatter. + * @throws IOException + * the formatter failed to write an advertisement. + */ + public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException { final RevFlag advertised = walk.newFlag("ADVERTISED"); - final RefAdvertiser adv = new RefAdvertiser(pckOut, walk, advertised); + adv.init(walk, advertised); adv.advertiseCapability(CAPABILITY_DELETE_REFS); adv.advertiseCapability(CAPABILITY_REPORT_STATUS); if (allowOfsDelta) @@ -589,7 +598,7 @@ private void sendAdvertisedRefs() throws IOException { adv.includeAdditionalHaves(); if (adv.isEmpty()) adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); - pckOut.end(); + adv.end(); } private void recvCommands() throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java index 6a6053d94..2a06ed889 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java @@ -61,12 +61,28 @@ import org.eclipse.jgit.revwalk.RevWalk; /** Support for the start of {@link UploadPack} and {@link ReceivePack}. */ -class RefAdvertiser { - private final PacketLineOut pckOut; +public abstract class RefAdvertiser { + static class PacketLineOutRefAdvertiser extends RefAdvertiser { + private final PacketLineOut pckOut; - private final RevWalk walk; + PacketLineOutRefAdvertiser(PacketLineOut out) { + pckOut = out; + } - private final RevFlag ADVERTISED; + @Override + protected void writeOne(final CharSequence line) throws IOException { + pckOut.writeString(line.toString()); + } + + @Override + protected void end() throws IOException { + pckOut.end(); + } + } + + private RevWalk walk; + + private RevFlag ADVERTISED; private final StringBuilder tmpLine = new StringBuilder(100); @@ -78,22 +94,72 @@ class RefAdvertiser { private boolean first = true; - RefAdvertiser(final PacketLineOut out, final RevWalk protoWalk, - final RevFlag advertisedFlag) { - pckOut = out; + /** + * Initialize a new advertisement formatter. + * + * @param protoWalk + * the RevWalk used to parse objects that are advertised. + * @param advertisedFlag + * flag marked on any advertised objects parsed out of the + * {@code protoWalk}'s object pool, permitting the caller to + * later quickly determine if an object was advertised (or not). + */ + public void init(final RevWalk protoWalk, final RevFlag advertisedFlag) { walk = protoWalk; ADVERTISED = advertisedFlag; } - void setDerefTags(final boolean deref) { + /** + * Toggle tag peeling. + *

+ *

+ * This method must be invoked prior to any of the following: + *

+ * + * @param deref + * true to show the dereferenced value of a tag as the special + * ref $tag^{} ; false to omit it from the output. + */ + public void setDerefTags(final boolean deref) { derefTags = deref; } - void advertiseCapability(String name) { + /** + * Add one protocol capability to the initial advertisement. + *

+ * This method must be invoked prior to any of the following: + *

+ * + * @param name + * the name of a single protocol capability supported by the + * caller. The set of capabilities are sent to the client in the + * advertisement, allowing the client to later selectively enable + * features it recognizes. + */ + public void advertiseCapability(String name) { capablities.add(name); } - void send(final Collection refs) throws IOException { + /** + * Format an advertisement for the supplied refs. + * + * @param refs + * zero or more refs to format for the client. The collection is + * copied and sorted before display and therefore may appear in + * any order. + * @throws IOException + * the underlying output stream failed to write out an + * advertisement record. + */ + public void send(final Collection refs) throws IOException { for (final Ref r : RefComparator.sort(refs)) { final RevObject obj = parseAnyOrNull(r.getObjectId()); if (obj != null) { @@ -104,7 +170,21 @@ void send(final Collection refs) throws IOException { } } - void advertiseHave(AnyObjectId id) throws IOException { + /** + * Advertise one object is available using the magic {@code .have}. + *

+ * The magic {@code .have} advertisement is not available for fetching by a + * client, but can be used by a client when considering a delta base + * candidate before transferring data in a push. Within the record created + * by this method the ref name is simply the invalid string {@code .have}. + * + * @param id + * identity of the object that is assumed to exist. + * @throws IOException + * the underlying output stream failed to write out an + * advertisement record. + */ + public void advertiseHave(AnyObjectId id) throws IOException { RevObject obj = parseAnyOrNull(id); if (obj != null) { advertiseAnyOnce(obj, ".have"); @@ -113,7 +193,14 @@ void advertiseHave(AnyObjectId id) throws IOException { } } - void includeAdditionalHaves() throws IOException { + /** + * Include references of alternate repositories as {@code .have} lines. + * + * @throws IOException + * the underlying output stream failed to write out an + * advertisement record. + */ + public void includeAdditionalHaves() throws IOException { additionalHaves(walk.getRepository().getObjectDatabase()); } @@ -129,7 +216,8 @@ private void additionalHaves(final Repository alt) throws IOException { advertiseHave(r.getObjectId()); } - boolean isEmpty() { + /** @return true if no advertisements have been sent yet. */ + public boolean isEmpty() { return first; } @@ -172,7 +260,22 @@ private void advertiseTag(final RevTag tag, final String refName) advertiseAny(tag.getObject(), refName); } - void advertiseId(final AnyObjectId id, final String refName) + /** + * Advertise one object under a specific name. + *

+ * If the advertised object is a tag, this method does not advertise the + * peeled version of it. + * + * @param id + * the object to advertise. + * @param refName + * name of the reference to advertise the object as, can be any + * string not including the NUL byte. + * @throws IOException + * the underlying output stream failed to write out an + * advertisement record. + */ + public void advertiseId(final AnyObjectId id, final String refName) throws IOException { tmpLine.setLength(0); id.copyTo(tmpId, tmpLine); @@ -190,6 +293,27 @@ void advertiseId(final AnyObjectId id, final String refName) } } tmpLine.append('\n'); - pckOut.writeString(tmpLine.toString()); + writeOne(tmpLine); } + + /** + * Write a single advertisement line. + * + * @param line + * the advertisement line to be written. The line always ends + * with LF. Never null or the empty string. + * @throws IOException + * the underlying output stream failed to write out an + * advertisement record. + */ + protected abstract void writeOne(CharSequence line) throws IOException; + + /** + * Mark the end of the advertisements. + * + * @throws IOException + * the underlying output stream failed to write out an + * advertisement record. + */ + protected abstract void end() throws IOException; } 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 fe2afbe9b..6b81bc492 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -70,6 +70,7 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; import org.eclipse.jgit.util.io.TimeoutInputStream; import org.eclipse.jgit.util.io.TimeoutOutputStream; @@ -282,7 +283,7 @@ public void upload(final InputStream input, final OutputStream output, private void service() throws IOException { if (biDirectionalPipe) - sendAdvertisedRefs(); + sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); else { refs = db.getAllRefs(); for (Ref r : refs.values()) { @@ -309,8 +310,16 @@ else if (options.contains(OPTION_MULTI_ACK)) sendPack(); } - private void sendAdvertisedRefs() throws IOException { - final RefAdvertiser adv = new RefAdvertiser(pckOut, walk, ADVERTISED); + /** + * Generate an advertisement of available refs and capabilities. + * + * @param adv + * the advertisement formatter. + * @throws IOException + * the formatter failed to write an advertisement. + */ + public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException { + adv.init(walk, ADVERTISED); adv.advertiseCapability(OPTION_INCLUDE_TAG); adv.advertiseCapability(OPTION_MULTI_ACK_DETAILED); adv.advertiseCapability(OPTION_MULTI_ACK); @@ -322,7 +331,7 @@ private void sendAdvertisedRefs() throws IOException { adv.setDerefTags(true); refs = db.getAllRefs(); adv.send(refs.values()); - pckOut.end(); + adv.end(); } private void recvWants() throws IOException {