Support for fetchThin and pushThin options in Transport
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / transport / PackFetchConnection.java
blob6209030218829fdde625a970b48d86762370e982
1 /*
2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
9 * conditions are met:
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
22 * written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org.spearce.jgit.transport;
41 import java.io.BufferedInputStream;
42 import java.io.BufferedOutputStream;
43 import java.io.EOFException;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.OutputStream;
47 import java.util.Collection;
48 import java.util.Date;
49 import java.util.HashSet;
50 import java.util.LinkedHashMap;
51 import java.util.Set;
53 import org.spearce.jgit.errors.PackProtocolException;
54 import org.spearce.jgit.errors.TransportException;
55 import org.spearce.jgit.lib.AnyObjectId;
56 import org.spearce.jgit.lib.MutableObjectId;
57 import org.spearce.jgit.lib.ObjectId;
58 import org.spearce.jgit.lib.ProgressMonitor;
59 import org.spearce.jgit.lib.Ref;
60 import org.spearce.jgit.lib.Repository;
61 import org.spearce.jgit.revwalk.RevCommit;
62 import org.spearce.jgit.revwalk.RevCommitList;
63 import org.spearce.jgit.revwalk.RevFlag;
64 import org.spearce.jgit.revwalk.RevObject;
65 import org.spearce.jgit.revwalk.RevSort;
66 import org.spearce.jgit.revwalk.RevWalk;
67 import org.spearce.jgit.revwalk.filter.CommitTimeRevFilter;
68 import org.spearce.jgit.revwalk.filter.RevFilter;
70 /**
71 * Fetch implementation using the native Git pack transfer service.
72 * <p>
73 * This is the canonical implementation for transferring objects from the remote
74 * repository to the local repository by talking to the 'git-upload-pack'
75 * service. Objects are packed on the remote side into a pack file and then sent
76 * down the pipe to us.
77 * <p>
78 * This connection requires only a bi-directional pipe or socket, and thus is
79 * easily wrapped up into a local process pipe, anonymous TCP socket, or a
80 * command executed through an SSH tunnel.
82 abstract class PackFetchConnection extends FetchConnection {
83 /**
84 * Maximum number of 'have' lines to send before giving up.
85 * <p>
86 * During {@link #negotiate(ProgressMonitor)} we send at most this many
87 * commits to the remote peer as 'have' lines without an ACK response before
88 * we give up.
90 private static final int MAX_HAVES = 256;
92 static final String OPTION_INCLUDE_TAG = "include-tag";
94 static final String OPTION_MULTI_ACK = "multi_ack";
96 static final String OPTION_THIN_PACK = "thin-pack";
98 static final String OPTION_SIDE_BAND = "side-band";
100 static final String OPTION_SIDE_BAND_64K = "side-band-64k";
102 static final String OPTION_OFS_DELTA = "ofs-delta";
104 static final String OPTION_SHALLOW = "shallow";
106 /** The repository this transport fetches into, or pushes out of. */
107 protected final Repository local;
109 /** Remote repository location. */
110 protected final URIish uri;
112 /** Capability tokens advertised by the remote side. */
113 protected final Set<String> remoteCapablities = new HashSet<String>();
115 /** Buffered input stream reading from the remote. */
116 protected InputStream in;
118 /** Buffered output stream sending to the remote. */
119 protected OutputStream out;
121 /** Packet line decoder around {@link #in}. */
122 protected PacketLineIn pckIn;
124 /** Packet line encoder around {@link #out}. */
125 protected PacketLineOut pckOut;
127 private final RevWalk walk;
129 /** All commits that are immediately reachable by a local ref. */
130 private RevCommitList<RevCommit> reachableCommits;
132 /** Marks an object as having all its dependencies. */
133 final RevFlag REACHABLE;
135 /** Marks a commit known to both sides of the connection. */
136 final RevFlag COMMON;
138 /** Marks a commit listed in the advertised refs. */
139 final RevFlag ADVERTISED;
141 private boolean multiAck;
143 private boolean thinPack;
145 private boolean sideband;
147 private boolean includeTags;
149 PackFetchConnection(final PackTransport packTransport) {
150 local = packTransport.local;
151 uri = packTransport.uri;
152 includeTags = packTransport.getTagOpt() != TagOpt.NO_TAGS;
153 thinPack = packTransport.isFetchThin();
155 walk = new RevWalk(local);
156 reachableCommits = new RevCommitList<RevCommit>();
157 REACHABLE = walk.newFlag("REACHABLE");
158 COMMON = walk.newFlag("COMMON");
159 ADVERTISED = walk.newFlag("ADVERTISED");
161 walk.carry(COMMON);
162 walk.carry(REACHABLE);
163 walk.carry(ADVERTISED);
166 protected void init(final InputStream myIn, final OutputStream myOut) {
167 in = myIn instanceof BufferedInputStream ? myIn
168 : new BufferedInputStream(myIn);
169 out = myOut instanceof BufferedOutputStream ? myOut
170 : new BufferedOutputStream(myOut);
172 pckIn = new PacketLineIn(in);
173 pckOut = new PacketLineOut(out);
176 @Override
177 public boolean didFetchIncludeTags() {
178 return includeTags;
181 protected void readAdvertisedRefs() throws TransportException {
182 try {
183 readAdvertisedRefsImpl();
184 } catch (TransportException err) {
185 close();
186 throw err;
187 } catch (IOException err) {
188 close();
189 throw new TransportException(err.getMessage(), err);
190 } catch (RuntimeException err) {
191 close();
192 throw new TransportException(err.getMessage(), err);
196 private void readAdvertisedRefsImpl() throws IOException {
197 final LinkedHashMap<String, Ref> avail = new LinkedHashMap<String, Ref>();
198 for (;;) {
199 String line;
201 try {
202 line = pckIn.readString();
203 } catch (EOFException eof) {
204 if (avail.isEmpty())
205 throw new TransportException(uri + " not found.");
206 throw eof;
209 if (avail.isEmpty()) {
210 // The first line (if any) may contain "hidden"
211 // capability values after a NUL byte.
213 final int nul = line.indexOf('\0');
214 if (nul >= 0) {
215 for (String c : line.substring(nul + 1).split(" "))
216 remoteCapablities.add(c);
217 line = line.substring(0, nul);
221 if (line.length() == 0)
222 break;
224 String name = line.substring(41, line.length());
225 final ObjectId id = ObjectId.fromString(line.substring(0, 40));
226 if (name.endsWith("^{}")) {
227 name = name.substring(0, name.length() - 3);
228 final Ref prior = avail.get(name);
229 if (prior == null)
230 throw new PackProtocolException("advertisement of " + name
231 + "^{} came before " + name);
233 if (prior.getPeeledObjectId() != null)
234 throw duplicateAdvertisement(name + "^{}");
236 avail.put(name, new Ref(name, prior.getObjectId(), id));
237 } else {
238 final Ref prior = avail.put(name, new Ref(name, id));
239 if (prior != null)
240 throw duplicateAdvertisement(name);
243 available(avail);
246 private PackProtocolException duplicateAdvertisement(final String name) {
247 return new PackProtocolException("duplicate advertisements of " + name);
250 @Override
251 protected void doFetch(final ProgressMonitor monitor,
252 final Collection<Ref> want) throws TransportException {
253 try {
254 markRefsAdvertised();
255 markReachable(maxTimeWanted(want));
257 if (sendWants(want)) {
258 negotiate(monitor);
260 walk.dispose();
261 reachableCommits = null;
263 receivePack(monitor);
265 } catch (CancelledException ce) {
266 close();
267 return; // Caller should test (or just know) this themselves.
268 } catch (IOException err) {
269 close();
270 throw new TransportException(err.getMessage(), err);
271 } catch (RuntimeException err) {
272 close();
273 throw new TransportException(err.getMessage(), err);
277 private int maxTimeWanted(final Collection<Ref> wants) {
278 int maxTime = 0;
279 for (final Ref r : wants) {
280 try {
281 final RevObject obj = walk.parseAny(r.getObjectId());
282 if (obj instanceof RevCommit) {
283 final int cTime = ((RevCommit) obj).getCommitTime();
284 if (maxTime < cTime)
285 maxTime = cTime;
287 } catch (IOException error) {
288 // We don't have it, but we want to fetch (thus fixing error).
291 return maxTime;
294 private void markReachable(final int maxTime) throws IOException {
295 for (final Ref r : local.getAllRefs().values()) {
296 try {
297 final RevCommit o = walk.parseCommit(r.getObjectId());
298 o.add(REACHABLE);
299 reachableCommits.add(o);
300 } catch (IOException readError) {
301 // If we cannot read the value of the ref skip it.
302 } catch (ClassCastException cce) {
303 // Not a commit type.
307 if (maxTime > 0) {
308 // Mark reachable commits until we reach maxTime. These may
309 // wind up later matching up against things we want and we
310 // can avoid asking for something we already happen to have.
312 final Date maxWhen = new Date(maxTime * 1000L);
313 walk.sort(RevSort.COMMIT_TIME_DESC);
314 walk.markStart(reachableCommits);
315 walk.setRevFilter(CommitTimeRevFilter.after(maxWhen));
316 for (;;) {
317 final RevCommit c = walk.next();
318 if (c == null)
319 break;
320 if (c.has(ADVERTISED) && !c.has(COMMON)) {
321 // This is actually going to be a common commit, but
322 // our peer doesn't know that fact yet.
324 c.add(COMMON);
325 c.carry(COMMON);
326 reachableCommits.add(c);
332 private boolean sendWants(final Collection<Ref> want) throws IOException {
333 boolean first = true;
334 for (final Ref r : want) {
335 try {
336 if (walk.parseAny(r.getObjectId()).has(REACHABLE)) {
337 // We already have this object. Asking for it is
338 // not a very good idea.
340 continue;
342 } catch (IOException err) {
343 // Its OK, we don't have it, but we want to fix that
344 // by fetching the object from the other side.
347 final StringBuilder line = new StringBuilder(46);
348 line.append("want ");
349 line.append(r.getObjectId());
350 if (first) {
351 line.append(enableCapabilities());
352 first = false;
354 line.append('\n');
355 pckOut.writeString(line.toString());
357 pckOut.end();
358 return !first;
361 private String enableCapabilities() {
362 final StringBuilder line = new StringBuilder();
363 if (includeTags)
364 includeTags = wantCapability(line, OPTION_INCLUDE_TAG);
365 wantCapability(line, OPTION_OFS_DELTA);
366 multiAck = wantCapability(line, OPTION_MULTI_ACK);
367 if (thinPack)
368 thinPack = wantCapability(line, OPTION_THIN_PACK);
369 if (wantCapability(line, OPTION_SIDE_BAND_64K))
370 sideband = true;
371 else if (wantCapability(line, OPTION_SIDE_BAND))
372 sideband = true;
373 return line.toString();
376 private boolean wantCapability(final StringBuilder b, final String option) {
377 if (!remoteCapablities.contains(option))
378 return false;
379 if (b.length() > 0)
380 b.append(' ');
381 b.append(option);
382 return true;
385 private void negotiate(final ProgressMonitor monitor) throws IOException,
386 CancelledException {
387 final MutableObjectId ackId = new MutableObjectId();
388 int resultsPending = 0;
389 int havesSent = 0;
390 int havesSinceLastContinue = 0;
391 boolean receivedContinue = false;
392 boolean receivedAck = false;
393 boolean sendHaves = true;
395 negotiateBegin();
396 while (sendHaves) {
397 final RevCommit c = walk.next();
398 if (c == null)
399 break;
401 pckOut.writeString("have " + c.getId() + "\n");
402 havesSent++;
403 havesSinceLastContinue++;
405 if ((31 & havesSent) != 0) {
406 // We group the have lines into blocks of 32, each marked
407 // with a flush (aka end). This one is within a block so
408 // continue with another have line.
410 continue;
413 if (monitor.isCancelled())
414 throw new CancelledException();
416 pckOut.end();
417 resultsPending++; // Each end will cause a result to come back.
419 if (havesSent == 32) {
420 // On the first block we race ahead and try to send
421 // more of the second block while waiting for the
422 // remote to respond to our first block request.
423 // This keeps us one block ahead of the peer.
425 continue;
428 while (resultsPending > 0) {
429 final PacketLineIn.AckNackResult anr;
431 anr = pckIn.readACK(ackId);
432 resultsPending--;
433 if (anr == PacketLineIn.AckNackResult.NAK) {
434 // More have lines are necessary to compute the
435 // pack on the remote side. Keep doing that.
437 break;
440 if (anr == PacketLineIn.AckNackResult.ACK) {
441 // The remote side is happy and knows exactly what
442 // to send us. There is no further negotiation and
443 // we can break out immediately.
445 multiAck = false;
446 resultsPending = 0;
447 receivedAck = true;
448 sendHaves = false;
449 break;
452 if (anr == PacketLineIn.AckNackResult.ACK_CONTINUE) {
453 // The server knows this commit (ackId). We don't
454 // need to send any further along its ancestry, but
455 // we need to continue to talk about other parts of
456 // our local history.
458 markCommon(walk.parseAny(ackId));
459 receivedAck = true;
460 receivedContinue = true;
461 havesSinceLastContinue = 0;
464 if (monitor.isCancelled())
465 throw new CancelledException();
468 if (receivedContinue && havesSinceLastContinue > MAX_HAVES) {
469 // Our history must be really different from the remote's.
470 // We just sent a whole slew of have lines, and it did not
471 // recognize any of them. Avoid sending our entire history
472 // to them by giving up early.
474 break;
478 // Tell the remote side we have run out of things to talk about.
480 if (monitor.isCancelled())
481 throw new CancelledException();
482 pckOut.writeString("done\n");
483 pckOut.flush();
485 if (!receivedAck) {
486 // Apparently if we have never received an ACK earlier
487 // there is one more result expected from the done we
488 // just sent to the remote.
490 multiAck = false;
491 resultsPending++;
494 while (resultsPending > 0 || multiAck) {
495 final PacketLineIn.AckNackResult anr;
497 anr = pckIn.readACK(ackId);
498 resultsPending--;
500 if (anr == PacketLineIn.AckNackResult.ACK)
501 break; // commit negotiation is finished.
503 if (anr == PacketLineIn.AckNackResult.ACK_CONTINUE) {
504 // There must be a normal ACK following this.
506 multiAck = true;
509 if (monitor.isCancelled())
510 throw new CancelledException();
514 private void negotiateBegin() throws IOException {
515 walk.resetRetain(REACHABLE, ADVERTISED);
516 walk.markStart(reachableCommits);
517 walk.sort(RevSort.COMMIT_TIME_DESC);
518 walk.setRevFilter(new RevFilter() {
519 @Override
520 public RevFilter clone() {
521 return this;
524 @Override
525 public boolean include(final RevWalk walker, final RevCommit c) {
526 final boolean remoteKnowsIsCommon = c.has(COMMON);
527 if (c.has(ADVERTISED)) {
528 // Remote advertised this, and we have it, hence common.
529 // Whether or not the remote knows that fact is tested
530 // before we added the flag. If the remote doesn't know
531 // we have to still send them this object.
533 c.add(COMMON);
535 return !remoteKnowsIsCommon;
540 private void markRefsAdvertised() {
541 for (final Ref r : getRefs()) {
542 markAdvertised(r.getObjectId());
543 if (r.getPeeledObjectId() != null)
544 markAdvertised(r.getPeeledObjectId());
548 private void markAdvertised(final AnyObjectId id) {
549 try {
550 walk.parseAny(id).add(ADVERTISED);
551 } catch (IOException readError) {
552 // We probably just do not have this object locally.
556 private void markCommon(final RevObject obj) {
557 obj.add(COMMON);
558 if (obj instanceof RevCommit)
559 ((RevCommit) obj).carry(COMMON);
562 private void receivePack(final ProgressMonitor monitor) throws IOException {
563 final IndexPack ip;
565 ip = IndexPack.create(local, sideband ? pckIn.sideband(monitor) : in);
566 ip.setFixThin(thinPack);
567 ip.index(monitor);
568 ip.renameAndOpenPack();
571 @Override
572 public void close() {
573 if (out != null) {
574 try {
575 pckOut.end();
576 out.close();
577 } catch (IOException err) {
578 // Ignore any close errors.
579 } finally {
580 out = null;
581 pckOut = null;
585 if (in != null) {
586 try {
587 in.close();
588 } catch (IOException err) {
589 // Ignore any close errors.
590 } finally {
591 in = null;
592 pckIn = null;
597 private static class CancelledException extends Exception {
598 private static final long serialVersionUID = 1L;