View Javadoc
1   /*
2    * Copyright (C) 2008-2010, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.transport;
45  
46  import static org.eclipse.jgit.lib.RefDatabase.ALL;
47  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
48  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
49  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT;
50  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
51  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK;
52  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK_DETAILED;
53  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_DONE;
54  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
55  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
56  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SHALLOW;
57  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND;
58  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
59  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
60  
61  import java.io.EOFException;
62  import java.io.IOException;
63  import java.io.InputStream;
64  import java.io.OutputStream;
65  import java.text.MessageFormat;
66  import java.util.ArrayList;
67  import java.util.Collection;
68  import java.util.Collections;
69  import java.util.HashSet;
70  import java.util.List;
71  import java.util.Map;
72  import java.util.Set;
73  
74  import org.eclipse.jgit.errors.CorruptObjectException;
75  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
76  import org.eclipse.jgit.errors.MissingObjectException;
77  import org.eclipse.jgit.errors.PackProtocolException;
78  import org.eclipse.jgit.internal.JGitText;
79  import org.eclipse.jgit.internal.storage.pack.PackWriter;
80  import org.eclipse.jgit.lib.Constants;
81  import org.eclipse.jgit.lib.NullProgressMonitor;
82  import org.eclipse.jgit.lib.ObjectId;
83  import org.eclipse.jgit.lib.ProgressMonitor;
84  import org.eclipse.jgit.lib.Ref;
85  import org.eclipse.jgit.lib.Repository;
86  import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
87  import org.eclipse.jgit.revwalk.DepthWalk;
88  import org.eclipse.jgit.revwalk.ObjectWalk;
89  import org.eclipse.jgit.revwalk.RevCommit;
90  import org.eclipse.jgit.revwalk.RevFlag;
91  import org.eclipse.jgit.revwalk.RevFlagSet;
92  import org.eclipse.jgit.revwalk.RevObject;
93  import org.eclipse.jgit.revwalk.RevTag;
94  import org.eclipse.jgit.revwalk.RevWalk;
95  import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
96  import org.eclipse.jgit.storage.pack.PackConfig;
97  import org.eclipse.jgit.storage.pack.PackStatistics;
98  import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck;
99  import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
100 import org.eclipse.jgit.util.io.InterruptTimer;
101 import org.eclipse.jgit.util.io.NullOutputStream;
102 import org.eclipse.jgit.util.io.TimeoutInputStream;
103 import org.eclipse.jgit.util.io.TimeoutOutputStream;
104 
105 /**
106  * Implements the server side of a fetch connection, transmitting objects.
107  */
108 public class UploadPack {
109 	/** Policy the server uses to validate client requests */
110 	public static enum RequestPolicy {
111 		/** Client may only ask for objects the server advertised a reference for. */
112 		ADVERTISED,
113 
114 		/**
115 		 * Client may ask for any commit reachable from a reference advertised by
116 		 * the server.
117 		 */
118 		REACHABLE_COMMIT,
119 
120 		/**
121 		 * Client may ask for objects that are the tip of any reference, even if not
122 		 * advertised.
123 		 * <p>
124 		 * This may happen, for example, when a custom {@link RefFilter} is set.
125 		 *
126 		 * @since 3.1
127 		 */
128 		TIP,
129 
130 		/**
131 		 * Client may ask for any commit reachable from any reference, even if that
132 		 * reference wasn't advertised.
133 		 *
134 		 * @since 3.1
135 		 */
136 		REACHABLE_COMMIT_TIP,
137 
138 		/** Client may ask for any SHA-1 in the repository. */
139 		ANY;
140 	}
141 
142 	/**
143 	 * Validator for client requests.
144 	 *
145 	 * @since 3.1
146 	 */
147 	public interface RequestValidator {
148 		/**
149 		 * Check a list of client wants against the request policy.
150 		 *
151 		 * @param up
152 		 *            {@link UploadPack} instance.
153 		 * @param wants
154 		 *            objects the client requested that were not advertised.
155 		 *
156 		 * @throws PackProtocolException
157 		 *            if one or more wants is not valid.
158 		 * @throws IOException
159 		 *            if a low-level exception occurred.
160 		 * @since 3.1
161 		 */
162 		void checkWants(UploadPack up, List<ObjectId> wants)
163 				throws PackProtocolException, IOException;
164 	}
165 
166 	/** Data in the first line of a request, the line itself plus options. */
167 	public static class FirstLine {
168 		private final String line;
169 		private final Set<String> options;
170 
171 		/**
172 		 * Parse the first line of a receive-pack request.
173 		 *
174 		 * @param line
175 		 *            line from the client.
176 		 */
177 		public FirstLine(String line) {
178 			if (line.length() > 45) {
179 				final HashSet<String> opts = new HashSet<String>();
180 				String opt = line.substring(45);
181 				if (opt.startsWith(" ")) //$NON-NLS-1$
182 					opt = opt.substring(1);
183 				for (String c : opt.split(" ")) //$NON-NLS-1$
184 					opts.add(c);
185 				this.line = line.substring(0, 45);
186 				this.options = Collections.unmodifiableSet(opts);
187 			} else {
188 				this.line = line;
189 				this.options = Collections.emptySet();
190 			}
191 		}
192 
193 		/** @return non-capabilities part of the line. */
194 		public String getLine() {
195 			return line;
196 		}
197 
198 		/** @return options parsed from the line. */
199 		public Set<String> getOptions() {
200 			return options;
201 		}
202 	}
203 
204 	/** Database we read the objects from. */
205 	private final Repository db;
206 
207 	/** Revision traversal support over {@link #db}. */
208 	private final RevWalk walk;
209 
210 	/** Configuration to pass into the PackWriter. */
211 	private PackConfig packConfig;
212 
213 	/** Configuration for various transfer options. */
214 	private TransferConfig transferConfig;
215 
216 	/** Timeout in seconds to wait for client interaction. */
217 	private int timeout;
218 
219 	/**
220 	 * Is the client connection a bi-directional socket or pipe?
221 	 * <p>
222 	 * If true, this class assumes it can perform multiple read and write cycles
223 	 * with the client over the input and output streams. This matches the
224 	 * functionality available with a standard TCP/IP connection, or a local
225 	 * operating system or in-memory pipe.
226 	 * <p>
227 	 * If false, this class runs in a read everything then output results mode,
228 	 * making it suitable for single round-trip systems RPCs such as HTTP.
229 	 */
230 	private boolean biDirectionalPipe = true;
231 
232 	/** Timer to manage {@link #timeout}. */
233 	private InterruptTimer timer;
234 
235 	private InputStream rawIn;
236 
237 	private OutputStream rawOut;
238 
239 	private PacketLineIn pckIn;
240 
241 	private PacketLineOut pckOut;
242 
243 	private OutputStream msgOut = NullOutputStream.INSTANCE;
244 
245 	/** The refs we advertised as existing at the start of the connection. */
246 	private Map<String, Ref> refs;
247 
248 	/** Hook used while advertising the refs to the client. */
249 	private AdvertiseRefsHook advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
250 
251 	/** Filter used while advertising the refs to the client. */
252 	private RefFilter refFilter = RefFilter.DEFAULT;
253 
254 	/** Hook handling the various upload phases. */
255 	private PreUploadHook preUploadHook = PreUploadHook.NULL;
256 
257 	/** Hook for taking post upload actions. */
258 	private PostUploadHook postUploadHook = PostUploadHook.NULL;
259 
260 	/** Capabilities requested by the client. */
261 	private Set<String> options;
262 	String userAgent;
263 
264 	/** Raw ObjectIds the client has asked for, before validating them. */
265 	private final Set<ObjectId> wantIds = new HashSet<ObjectId>();
266 
267 	/** Objects the client wants to obtain. */
268 	private final Set<RevObject> wantAll = new HashSet<RevObject>();
269 
270 	/** Objects on both sides, these don't have to be sent. */
271 	private final Set<RevObject> commonBase = new HashSet<RevObject>();
272 
273 	/** Shallow commits the client already has. */
274 	private final Set<ObjectId> clientShallowCommits = new HashSet<ObjectId>();
275 
276 	/** Shallow commits on the client which are now becoming unshallow */
277 	private final List<ObjectId> unshallowCommits = new ArrayList<ObjectId>();
278 
279 	/** Desired depth from the client on a shallow request. */
280 	private int depth;
281 
282 	/** Commit time of the oldest common commit, in seconds. */
283 	private int oldestTime;
284 
285 	/** null if {@link #commonBase} should be examined again. */
286 	private Boolean okToGiveUp;
287 
288 	private boolean sentReady;
289 
290 	/** Objects we sent in our advertisement list, clients can ask for these. */
291 	private Set<ObjectId> advertised;
292 
293 	/** Marked on objects the client has asked us to give them. */
294 	private final RevFlag WANT;
295 
296 	/** Marked on objects both we and the client have. */
297 	private final RevFlag PEER_HAS;
298 
299 	/** Marked on objects in {@link #commonBase}. */
300 	private final RevFlag COMMON;
301 
302 	/** Objects where we found a path from the want list to a common base. */
303 	private final RevFlag SATISFIED;
304 
305 	private final RevFlagSet SAVE;
306 
307 	private RequestValidator requestValidator = new AdvertisedRequestValidator();
308 
309 	private MultiAck multiAck = MultiAck.OFF;
310 
311 	private boolean noDone;
312 
313 	private PackStatistics statistics;
314 
315 	private UploadPackLogger logger = UploadPackLogger.NULL;
316 
317 	/**
318 	 * Create a new pack upload for an open repository.
319 	 *
320 	 * @param copyFrom
321 	 *            the source repository.
322 	 */
323 	public UploadPack(final Repository copyFrom) {
324 		db = copyFrom;
325 		walk = new RevWalk(db);
326 		walk.setRetainBody(false);
327 
328 		WANT = walk.newFlag("WANT"); //$NON-NLS-1$
329 		PEER_HAS = walk.newFlag("PEER_HAS"); //$NON-NLS-1$
330 		COMMON = walk.newFlag("COMMON"); //$NON-NLS-1$
331 		SATISFIED = walk.newFlag("SATISFIED"); //$NON-NLS-1$
332 		walk.carry(PEER_HAS);
333 
334 		SAVE = new RevFlagSet();
335 		SAVE.add(WANT);
336 		SAVE.add(PEER_HAS);
337 		SAVE.add(COMMON);
338 		SAVE.add(SATISFIED);
339 
340 		setTransferConfig(null);
341 	}
342 
343 	/** @return the repository this upload is reading from. */
344 	public final Repository getRepository() {
345 		return db;
346 	}
347 
348 	/** @return the RevWalk instance used by this connection. */
349 	public final RevWalk getRevWalk() {
350 		return walk;
351 	}
352 
353 	/**
354 	 * Get refs which were advertised to the client.
355 	 *
356 	 * @return all refs which were advertised to the client, or null if
357 	 *         {@link #setAdvertisedRefs(Map)} has not been called yet.
358 	 */
359 	public final Map<String, Ref> getAdvertisedRefs() {
360 		return refs;
361 	}
362 
363 	/**
364 	 * Set the refs advertised by this UploadPack.
365 	 * <p>
366 	 * Intended to be called from a {@link PreUploadHook}.
367 	 *
368 	 * @param allRefs
369 	 *            explicit set of references to claim as advertised by this
370 	 *            UploadPack instance. This overrides any references that
371 	 *            may exist in the source repository. The map is passed
372 	 *            to the configured {@link #getRefFilter()}. If null, assumes
373 	 *            all refs were advertised.
374 	 */
375 	public void setAdvertisedRefs(Map<String, Ref> allRefs) {
376 		if (allRefs != null)
377 			refs = allRefs;
378 		else
379 			refs = db.getAllRefs();
380 		if (refFilter == RefFilter.DEFAULT)
381 			refs = transferConfig.getRefFilter().filter(refs);
382 		else
383 			refs = refFilter.filter(refs);
384 	}
385 
386 	/** @return timeout (in seconds) before aborting an IO operation. */
387 	public int getTimeout() {
388 		return timeout;
389 	}
390 
391 	/**
392 	 * Set the timeout before willing to abort an IO call.
393 	 *
394 	 * @param seconds
395 	 *            number of seconds to wait (with no data transfer occurring)
396 	 *            before aborting an IO read or write operation with the
397 	 *            connected client.
398 	 */
399 	public void setTimeout(final int seconds) {
400 		timeout = seconds;
401 	}
402 
403 	/**
404 	 * @return true if this class expects a bi-directional pipe opened between
405 	 *         the client and itself. The default is true.
406 	 */
407 	public boolean isBiDirectionalPipe() {
408 		return biDirectionalPipe;
409 	}
410 
411 	/**
412 	 * @param twoWay
413 	 *            if true, this class will assume the socket is a fully
414 	 *            bidirectional pipe between the two peers and takes advantage
415 	 *            of that by first transmitting the known refs, then waiting to
416 	 *            read commands. If false, this class assumes it must read the
417 	 *            commands before writing output and does not perform the
418 	 *            initial advertising.
419 	 */
420 	public void setBiDirectionalPipe(final boolean twoWay) {
421 		biDirectionalPipe = twoWay;
422 	}
423 
424 	/**
425 	 * @return policy used by the service to validate client requests, or null for
426 	 *         a custom request validator.
427 	 */
428 	public RequestPolicy getRequestPolicy() {
429 		if (requestValidator instanceof AdvertisedRequestValidator)
430 			return RequestPolicy.ADVERTISED;
431 		if (requestValidator instanceof ReachableCommitRequestValidator)
432 			return RequestPolicy.REACHABLE_COMMIT;
433 		if (requestValidator instanceof TipRequestValidator)
434 			return RequestPolicy.TIP;
435 		if (requestValidator instanceof ReachableCommitTipRequestValidator)
436 			return RequestPolicy.REACHABLE_COMMIT_TIP;
437 		if (requestValidator instanceof AnyRequestValidator)
438 			return RequestPolicy.ANY;
439 		return null;
440 	}
441 
442 	/**
443 	 * @param policy
444 	 *            the policy used to enforce validation of a client's want list.
445 	 *            By default the policy is {@link RequestPolicy#ADVERTISED},
446 	 *            which is the Git default requiring clients to only ask for an
447 	 *            object that a reference directly points to. This may be relaxed
448 	 *            to {@link RequestPolicy#REACHABLE_COMMIT} or
449 	 *            {@link RequestPolicy#REACHABLE_COMMIT_TIP} when callers have
450 	 *            {@link #setBiDirectionalPipe(boolean)} set to false.
451 	 *            Overrides any policy specified in a {@link TransferConfig}.
452 	 */
453 	public void setRequestPolicy(RequestPolicy policy) {
454 		switch (policy) {
455 			case ADVERTISED:
456 			default:
457 				requestValidator = new AdvertisedRequestValidator();
458 				break;
459 			case REACHABLE_COMMIT:
460 				requestValidator = new ReachableCommitRequestValidator();
461 				break;
462 			case TIP:
463 				requestValidator = new TipRequestValidator();
464 				break;
465 			case REACHABLE_COMMIT_TIP:
466 				requestValidator = new ReachableCommitTipRequestValidator();
467 				break;
468 			case ANY:
469 				requestValidator = new AnyRequestValidator();
470 				break;
471 		}
472 	}
473 
474 	/**
475 	 * @param validator
476 	 *            custom validator for client want list.
477 	 * @since 3.1
478 	 */
479 	public void setRequestValidator(RequestValidator validator) {
480 		requestValidator = validator != null ? validator
481 				: new AdvertisedRequestValidator();
482 	}
483 
484 	/** @return the hook used while advertising the refs to the client */
485 	public AdvertiseRefsHook getAdvertiseRefsHook() {
486 		return advertiseRefsHook;
487 	}
488 
489 	/** @return the filter used while advertising the refs to the client */
490 	public RefFilter getRefFilter() {
491 		return refFilter;
492 	}
493 
494 	/**
495 	 * Set the hook used while advertising the refs to the client.
496 	 * <p>
497 	 * If the {@link AdvertiseRefsHook} chooses to call
498 	 * {@link #setAdvertisedRefs(Map)}, only refs set by this hook <em>and</em>
499 	 * selected by the {@link RefFilter} will be shown to the client.
500 	 *
501 	 * @param advertiseRefsHook
502 	 *            the hook; may be null to show all refs.
503 	 */
504 	public void setAdvertiseRefsHook(final AdvertiseRefsHook advertiseRefsHook) {
505 		if (advertiseRefsHook != null)
506 			this.advertiseRefsHook = advertiseRefsHook;
507 		else
508 			this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
509 	}
510 
511 	/**
512 	 * Set the filter used while advertising the refs to the client.
513 	 * <p>
514 	 * Only refs allowed by this filter will be sent to the client.
515 	 * The filter is run against the refs specified by the
516 	 * {@link AdvertiseRefsHook} (if applicable). If null or not set, uses the
517 	 * filter implied by the {@link TransferConfig}.
518 	 *
519 	 * @param refFilter
520 	 *            the filter; may be null to show all refs.
521 	 */
522 	public void setRefFilter(final RefFilter refFilter) {
523 		this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT;
524 	}
525 
526 	/** @return the configured pre upload hook. */
527 	public PreUploadHook getPreUploadHook() {
528 		return preUploadHook;
529 	}
530 
531 	/**
532 	 * Set the hook that controls how this instance will behave.
533 	 *
534 	 * @param hook
535 	 *            the hook; if null no special actions are taken.
536 	 */
537 	public void setPreUploadHook(PreUploadHook hook) {
538 		preUploadHook = hook != null ? hook : PreUploadHook.NULL;
539 	}
540 
541 	/**
542 	 * @return the configured post upload hook.
543 	 * @since 4.1
544 	 */
545 	public PostUploadHook getPostUploadHook() {
546 		return postUploadHook;
547 	}
548 
549 	/**
550 	 * Set the hook for post upload actions (logging, repacking).
551 	 *
552 	 * @param hook
553 	 *            the hook; if null no special actions are taken.
554 	 * @since 4.1
555 	 */
556 	public void setPostUploadHook(PostUploadHook hook) {
557 		postUploadHook = hook != null ? hook : PostUploadHook.NULL;
558 	}
559 
560 	/**
561 	 * Set the configuration used by the pack generator.
562 	 *
563 	 * @param pc
564 	 *            configuration controlling packing parameters. If null the
565 	 *            source repository's settings will be used.
566 	 */
567 	public void setPackConfig(PackConfig pc) {
568 		this.packConfig = pc;
569 	}
570 
571 	/**
572 	 * @param tc
573 	 *            configuration controlling transfer options. If null the source
574 	 *            repository's settings will be used.
575 	 * @since 3.1
576 	 */
577 	public void setTransferConfig(TransferConfig tc) {
578 		this.transferConfig = tc != null ? tc : new TransferConfig(db);
579 		if (transferConfig.isAllowTipSha1InWant()) {
580 			setRequestPolicy(transferConfig.isAllowReachableSha1InWant()
581 				? RequestPolicy.REACHABLE_COMMIT_TIP : RequestPolicy.TIP);
582 		} else {
583 			setRequestPolicy(transferConfig.isAllowReachableSha1InWant()
584 				? RequestPolicy.REACHABLE_COMMIT : RequestPolicy.ADVERTISED);
585 		}
586 	}
587 
588 	/**
589 	 * @return the configured logger.
590 	 *
591 	 * @deprecated Use {@link #getPreUploadHook()}.
592 	 */
593 	@Deprecated
594 	public UploadPackLogger getLogger() {
595 		return logger;
596 	}
597 
598 	/**
599 	 * Set the logger.
600 	 *
601 	 * @param logger
602 	 *            the logger instance. If null, no logging occurs.
603 	 * @deprecated Use {@link #setPreUploadHook(PreUploadHook)}.
604 	 */
605 	@Deprecated
606 	public void setLogger(UploadPackLogger logger) {
607 		this.logger = logger;
608 	}
609 
610 	/**
611 	 * Check whether the client expects a side-band stream.
612 	 *
613 	 * @return true if the client has advertised a side-band capability, false
614 	 *     otherwise.
615 	 * @throws RequestNotYetReadException
616 	 *             if the client's request has not yet been read from the wire, so
617 	 *             we do not know if they expect side-band. Note that the client
618 	 *             may have already written the request, it just has not been
619 	 *             read.
620 	 */
621 	public boolean isSideBand() throws RequestNotYetReadException {
622 		if (options == null)
623 			throw new RequestNotYetReadException();
624 		return (options.contains(OPTION_SIDE_BAND)
625 				|| options.contains(OPTION_SIDE_BAND_64K));
626 	}
627 
628 	/**
629 	 * Execute the upload task on the socket.
630 	 *
631 	 * @param input
632 	 *            raw input to read client commands from. Caller must ensure the
633 	 *            input is buffered, otherwise read performance may suffer.
634 	 * @param output
635 	 *            response back to the Git network client, to write the pack
636 	 *            data onto. Caller must ensure the output is buffered,
637 	 *            otherwise write performance may suffer.
638 	 * @param messages
639 	 *            secondary "notice" channel to send additional messages out
640 	 *            through. When run over SSH this should be tied back to the
641 	 *            standard error channel of the command execution. For most
642 	 *            other network connections this should be null.
643 	 * @throws IOException
644 	 */
645 	public void upload(final InputStream input, final OutputStream output,
646 			final OutputStream messages) throws IOException {
647 		try {
648 			rawIn = input;
649 			rawOut = output;
650 			if (messages != null)
651 				msgOut = messages;
652 
653 			if (timeout > 0) {
654 				final Thread caller = Thread.currentThread();
655 				timer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
656 				TimeoutInputStream i = new TimeoutInputStream(rawIn, timer);
657 				TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
658 				i.setTimeout(timeout * 1000);
659 				o.setTimeout(timeout * 1000);
660 				rawIn = i;
661 				rawOut = o;
662 			}
663 
664 			pckIn = new PacketLineIn(rawIn);
665 			pckOut = new PacketLineOut(rawOut);
666 			service();
667 		} finally {
668 			msgOut = NullOutputStream.INSTANCE;
669 			walk.close();
670 			if (timer != null) {
671 				try {
672 					timer.terminate();
673 				} finally {
674 					timer = null;
675 				}
676 			}
677 		}
678 	}
679 
680 	/**
681 	 * Get the PackWriter's statistics if a pack was sent to the client.
682 	 *
683 	 * @return statistics about pack output, if a pack was sent. Null if no pack
684 	 *         was sent, such as during the negotation phase of a smart HTTP
685 	 *         connection, or if the client was already up-to-date.
686 	 * @since 3.0
687 	 * @deprecated Use {@link #getStatistics()}.
688 	 */
689 	@Deprecated
690 	public PackWriter.Statistics getPackStatistics() {
691 		return statistics == null ? null
692 				: new PackWriter.Statistics(statistics);
693 	}
694 
695 	/**
696 	 * Get the PackWriter's statistics if a pack was sent to the client.
697 	 *
698 	 * @return statistics about pack output, if a pack was sent. Null if no pack
699 	 *         was sent, such as during the negotation phase of a smart HTTP
700 	 *         connection, or if the client was already up-to-date.
701 	 * @since 4.1
702 	 */
703 	public PackStatistics getStatistics() {
704 		return statistics;
705 	}
706 
707 	private Map<String, Ref> getAdvertisedOrDefaultRefs() {
708 		if (refs == null)
709 			setAdvertisedRefs(null);
710 		return refs;
711 	}
712 
713 	private void service() throws IOException {
714 		if (biDirectionalPipe)
715 			sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
716 		else if (requestValidator instanceof AnyRequestValidator)
717 			advertised = Collections.emptySet();
718 		else
719 			advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
720 
721 		boolean sendPack;
722 		try {
723 			recvWants();
724 			if (wantIds.isEmpty()) {
725 				preUploadHook.onBeginNegotiateRound(this, wantIds, 0);
726 				preUploadHook.onEndNegotiateRound(this, wantIds, 0, 0, false);
727 				return;
728 			}
729 
730 			if (options.contains(OPTION_MULTI_ACK_DETAILED)) {
731 				multiAck = MultiAck.DETAILED;
732 				noDone = options.contains(OPTION_NO_DONE);
733 			} else if (options.contains(OPTION_MULTI_ACK))
734 				multiAck = MultiAck.CONTINUE;
735 			else
736 				multiAck = MultiAck.OFF;
737 
738 			if (!clientShallowCommits.isEmpty())
739 				verifyClientShallow();
740 			if (depth != 0)
741 				processShallow();
742 			if (!clientShallowCommits.isEmpty())
743 				walk.assumeShallow(clientShallowCommits);
744 			sendPack = negotiate();
745 		} catch (PackProtocolException err) {
746 			reportErrorDuringNegotiate(err.getMessage());
747 			throw err;
748 
749 		} catch (ServiceMayNotContinueException err) {
750 			if (!err.isOutput() && err.getMessage() != null) {
751 				try {
752 					pckOut.writeString("ERR " + err.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
753 					err.setOutput();
754 				} catch (Throwable err2) {
755 					// Ignore this secondary failure (and not mark output).
756 				}
757 			}
758 			throw err;
759 
760 		} catch (IOException err) {
761 			reportErrorDuringNegotiate(JGitText.get().internalServerError);
762 			throw err;
763 		} catch (RuntimeException err) {
764 			reportErrorDuringNegotiate(JGitText.get().internalServerError);
765 			throw err;
766 		} catch (Error err) {
767 			reportErrorDuringNegotiate(JGitText.get().internalServerError);
768 			throw err;
769 		}
770 
771 		if (sendPack)
772 			sendPack();
773 	}
774 
775 	private static Set<ObjectId> refIdSet(Collection<Ref> refs) {
776 		Set<ObjectId> ids = new HashSet<ObjectId>(refs.size());
777 		for (Ref ref : refs) {
778 			if (ref.getObjectId() != null)
779 				ids.add(ref.getObjectId());
780 		}
781 		return ids;
782 	}
783 
784 	private void reportErrorDuringNegotiate(String msg) {
785 		try {
786 			pckOut.writeString("ERR " + msg + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
787 		} catch (Throwable err) {
788 			// Ignore this secondary failure.
789 		}
790 	}
791 
792 	private void processShallow() throws IOException {
793 		try (DepthWalk.RevWalk depthWalk = new DepthWalk.RevWalk(
794 				walk.getObjectReader(), depth)) {
795 
796 			// Find all the commits which will be shallow
797 			for (ObjectId o : wantIds) {
798 				try {
799 					depthWalk.markRoot(depthWalk.parseCommit(o));
800 				} catch (IncorrectObjectTypeException notCommit) {
801 					// Ignore non-commits in this loop.
802 				}
803 			}
804 
805 			RevCommit o;
806 			while ((o = depthWalk.next()) != null) {
807 				DepthWalk.Commit c = (DepthWalk.Commit) o;
808 
809 				// Commits at the boundary which aren't already shallow in
810 				// the client need to be marked as such
811 				if (c.getDepth() == depth && !clientShallowCommits.contains(c))
812 					pckOut.writeString("shallow " + o.name()); //$NON-NLS-1$
813 
814 				// Commits not on the boundary which are shallow in the client
815 				// need to become unshallowed
816 				if (c.getDepth() < depth && clientShallowCommits.remove(c)) {
817 					unshallowCommits.add(c.copy());
818 					pckOut.writeString("unshallow " + c.name()); //$NON-NLS-1$
819 				}
820 			}
821 		}
822 		pckOut.end();
823 	}
824 
825 	private void verifyClientShallow()
826 			throws IOException, PackProtocolException {
827 		AsyncRevObjectQueue q = walk.parseAny(clientShallowCommits, true);
828 		try {
829 			for (;;) {
830 				try {
831 					// Shallow objects named by the client must be commits.
832 					RevObject o = q.next();
833 					if (o == null) {
834 						break;
835 					}
836 					if (!(o instanceof RevCommit)) {
837 						throw new PackProtocolException(
838 							MessageFormat.format(
839 								JGitText.get().invalidShallowObject,
840 								o.name()));
841 					}
842 				} catch (MissingObjectException notCommit) {
843 					// shallow objects not known at the server are ignored
844 					// by git-core upload-pack, match that behavior.
845 					clientShallowCommits.remove(notCommit.getObjectId());
846 					continue;
847 				}
848 			}
849 		} finally {
850 			q.release();
851 		}
852 	}
853 
854 	/**
855 	 * Generate an advertisement of available refs and capabilities.
856 	 *
857 	 * @param adv
858 	 *            the advertisement formatter.
859 	 * @throws IOException
860 	 *             the formatter failed to write an advertisement.
861 	 * @throws ServiceMayNotContinueException
862 	 *             the hook denied advertisement.
863 	 */
864 	public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException,
865 			ServiceMayNotContinueException {
866 		try {
867 			advertiseRefsHook.advertiseRefs(this);
868 		} catch (ServiceMayNotContinueException fail) {
869 			if (fail.getMessage() != null) {
870 				adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$
871 				fail.setOutput();
872 			}
873 			throw fail;
874 		}
875 
876 		adv.init(db);
877 		adv.advertiseCapability(OPTION_INCLUDE_TAG);
878 		adv.advertiseCapability(OPTION_MULTI_ACK_DETAILED);
879 		adv.advertiseCapability(OPTION_MULTI_ACK);
880 		adv.advertiseCapability(OPTION_OFS_DELTA);
881 		adv.advertiseCapability(OPTION_SIDE_BAND);
882 		adv.advertiseCapability(OPTION_SIDE_BAND_64K);
883 		adv.advertiseCapability(OPTION_THIN_PACK);
884 		adv.advertiseCapability(OPTION_NO_PROGRESS);
885 		adv.advertiseCapability(OPTION_SHALLOW);
886 		if (!biDirectionalPipe)
887 			adv.advertiseCapability(OPTION_NO_DONE);
888 		RequestPolicy policy = getRequestPolicy();
889 		if (policy == RequestPolicy.TIP
890 				|| policy == RequestPolicy.REACHABLE_COMMIT_TIP
891 				|| policy == null)
892 			adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT);
893 		if (policy == RequestPolicy.REACHABLE_COMMIT
894 				|| policy == RequestPolicy.REACHABLE_COMMIT_TIP
895 				|| policy == null)
896 			adv.advertiseCapability(OPTION_ALLOW_REACHABLE_SHA1_IN_WANT);
897 		adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
898 		adv.setDerefTags(true);
899 		Map<String, Ref> advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs();
900 		findSymrefs(adv, advertisedOrDefaultRefs);
901 		advertised = adv.send(advertisedOrDefaultRefs);
902 		if (adv.isEmpty())
903 			adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$
904 		adv.end();
905 	}
906 
907 	/**
908 	 * Send a message to the client, if it supports receiving them.
909 	 * <p>
910 	 * If the client doesn't support receiving messages, the message will be
911 	 * discarded, with no other indication to the caller or to the client.
912 	 *
913 	 * @param what
914 	 *            string describing the problem identified by the hook. The
915 	 *            string must not end with an LF, and must not contain an LF.
916 	 * @since 3.1
917 	 */
918 	public void sendMessage(String what) {
919 		try {
920 			msgOut.write(Constants.encode(what + "\n")); //$NON-NLS-1$
921 		} catch (IOException e) {
922 			// Ignore write failures.
923 		}
924 	}
925 
926 	/**
927 	 * @return an underlying stream for sending messages to the client, or null.
928 	 * @since 3.1
929 	 */
930 	public OutputStream getMessageOutputStream() {
931 		return msgOut;
932 	}
933 
934 	private void recvWants() throws IOException {
935 		boolean isFirst = true;
936 		for (;;) {
937 			String line;
938 			try {
939 				line = pckIn.readString();
940 			} catch (EOFException eof) {
941 				if (isFirst)
942 					break;
943 				throw eof;
944 			}
945 
946 			if (line == PacketLineIn.END)
947 				break;
948 
949 			if (line.startsWith("deepen ")) { //$NON-NLS-1$
950 				depth = Integer.parseInt(line.substring(7));
951 				continue;
952 			}
953 
954 			if (line.startsWith("shallow ")) { //$NON-NLS-1$
955 				clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
956 				continue;
957 			}
958 
959 			if (!line.startsWith("want ") || line.length() < 45) //$NON-NLS-1$
960 				throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line)); //$NON-NLS-1$
961 
962 			if (isFirst) {
963 				if (line.length() > 45) {
964 					FirstLine firstLine = new FirstLine(line);
965 					options = firstLine.getOptions();
966 					line = firstLine.getLine();
967 				} else
968 					options = Collections.emptySet();
969 			}
970 
971 			wantIds.add(ObjectId.fromString(line.substring(5)));
972 			isFirst = false;
973 		}
974 	}
975 
976 	/**
977 	 * Returns the clone/fetch depth. Valid only after calling recvWants().
978 	 *
979 	 * @return the depth requested by the client, or 0 if unbounded.
980 	 * @since 4.0
981 	 */
982 	public int getDepth() {
983 		if (options == null)
984 			throw new RequestNotYetReadException();
985 		return depth;
986 	}
987 
988 	/**
989 	 * Get the user agent of the client.
990 	 * <p>
991 	 * If the client is new enough to use {@code agent=} capability that value
992 	 * will be returned. Older HTTP clients may also supply their version using
993 	 * the HTTP {@code User-Agent} header. The capability overrides the HTTP
994 	 * header if both are available.
995 	 * <p>
996 	 * When an HTTP request has been received this method returns the HTTP
997 	 * {@code User-Agent} header value until capabilities have been parsed.
998 	 *
999 	 * @return user agent supplied by the client. Available only if the client
1000 	 *         is new enough to advertise its user agent.
1001 	 * @since 4.0
1002 	 */
1003 	public String getPeerUserAgent() {
1004 		return UserAgent.getAgent(options, userAgent);
1005 	}
1006 
1007 	private boolean negotiate() throws IOException {
1008 		okToGiveUp = Boolean.FALSE;
1009 
1010 		ObjectId last = ObjectId.zeroId();
1011 		List<ObjectId> peerHas = new ArrayList<ObjectId>(64);
1012 		for (;;) {
1013 			String line;
1014 			try {
1015 				line = pckIn.readString();
1016 			} catch (EOFException eof) {
1017 				// EOF on stateless RPC (aka smart HTTP) and non-shallow request
1018 				// means the client asked for the updated shallow/unshallow data,
1019 				// disconnected, and will try another request with actual want/have.
1020 				// Don't report the EOF here, its a bug in the protocol that the client
1021 				// just disconnects without sending an END.
1022 				if (!biDirectionalPipe && depth > 0)
1023 					return false;
1024 				throw eof;
1025 			}
1026 
1027 			if (line == PacketLineIn.END) {
1028 				last = processHaveLines(peerHas, last);
1029 				if (commonBase.isEmpty() || multiAck != MultiAck.OFF)
1030 					pckOut.writeString("NAK\n"); //$NON-NLS-1$
1031 				if (noDone && sentReady) {
1032 					pckOut.writeString("ACK " + last.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
1033 					return true;
1034 				}
1035 				if (!biDirectionalPipe)
1036 					return false;
1037 				pckOut.flush();
1038 
1039 			} else if (line.startsWith("have ") && line.length() == 45) { //$NON-NLS-1$
1040 				peerHas.add(ObjectId.fromString(line.substring(5)));
1041 
1042 			} else if (line.equals("done")) { //$NON-NLS-1$
1043 				last = processHaveLines(peerHas, last);
1044 
1045 				if (commonBase.isEmpty())
1046 					pckOut.writeString("NAK\n"); //$NON-NLS-1$
1047 
1048 				else if (multiAck != MultiAck.OFF)
1049 					pckOut.writeString("ACK " + last.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
1050 
1051 				return true;
1052 
1053 			} else {
1054 				throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "have", line)); //$NON-NLS-1$
1055 			}
1056 		}
1057 	}
1058 
1059 	private ObjectId processHaveLines(List<ObjectId> peerHas, ObjectId last)
1060 			throws IOException {
1061 		preUploadHook.onBeginNegotiateRound(this, wantIds, peerHas.size());
1062 		if (wantAll.isEmpty() && !wantIds.isEmpty())
1063 			parseWants();
1064 		if (peerHas.isEmpty())
1065 			return last;
1066 
1067 		sentReady = false;
1068 		int haveCnt = 0;
1069 		walk.getObjectReader().setAvoidUnreachableObjects(true);
1070 		AsyncRevObjectQueue q = walk.parseAny(peerHas, false);
1071 		try {
1072 			for (;;) {
1073 				RevObject obj;
1074 				try {
1075 					obj = q.next();
1076 				} catch (MissingObjectException notFound) {
1077 					continue;
1078 				}
1079 				if (obj == null)
1080 					break;
1081 
1082 				last = obj;
1083 				haveCnt++;
1084 
1085 				if (obj instanceof RevCommit) {
1086 					RevCommit c = (RevCommit) obj;
1087 					if (oldestTime == 0 || c.getCommitTime() < oldestTime)
1088 						oldestTime = c.getCommitTime();
1089 				}
1090 
1091 				if (obj.has(PEER_HAS))
1092 					continue;
1093 
1094 				obj.add(PEER_HAS);
1095 				if (obj instanceof RevCommit)
1096 					((RevCommit) obj).carry(PEER_HAS);
1097 				addCommonBase(obj);
1098 
1099 				// If both sides have the same object; let the client know.
1100 				//
1101 				switch (multiAck) {
1102 				case OFF:
1103 					if (commonBase.size() == 1)
1104 						pckOut.writeString("ACK " + obj.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
1105 					break;
1106 				case CONTINUE:
1107 					pckOut.writeString("ACK " + obj.name() + " continue\n"); //$NON-NLS-1$ //$NON-NLS-2$
1108 					break;
1109 				case DETAILED:
1110 					pckOut.writeString("ACK " + obj.name() + " common\n"); //$NON-NLS-1$ //$NON-NLS-2$
1111 					break;
1112 				}
1113 			}
1114 		} finally {
1115 			q.release();
1116 			walk.getObjectReader().setAvoidUnreachableObjects(false);
1117 		}
1118 
1119 		int missCnt = peerHas.size() - haveCnt;
1120 
1121 		// If we don't have one of the objects but we're also willing to
1122 		// create a pack at this point, let the client know so it stops
1123 		// telling us about its history.
1124 		//
1125 		boolean didOkToGiveUp = false;
1126 		if (0 < missCnt) {
1127 			for (int i = peerHas.size() - 1; i >= 0; i--) {
1128 				ObjectId id = peerHas.get(i);
1129 				if (walk.lookupOrNull(id) == null) {
1130 					didOkToGiveUp = true;
1131 					if (okToGiveUp()) {
1132 						switch (multiAck) {
1133 						case OFF:
1134 							break;
1135 						case CONTINUE:
1136 							pckOut.writeString("ACK " + id.name() + " continue\n"); //$NON-NLS-1$ //$NON-NLS-2$
1137 							break;
1138 						case DETAILED:
1139 							pckOut.writeString("ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$
1140 							sentReady = true;
1141 							break;
1142 						}
1143 					}
1144 					break;
1145 				}
1146 			}
1147 		}
1148 
1149 		if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) {
1150 			ObjectId id = peerHas.get(peerHas.size() - 1);
1151 			sentReady = true;
1152 			pckOut.writeString("ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$
1153 			sentReady = true;
1154 		}
1155 
1156 		preUploadHook.onEndNegotiateRound(this, wantAll, haveCnt, missCnt, sentReady);
1157 		peerHas.clear();
1158 		return last;
1159 	}
1160 
1161 	private void parseWants() throws IOException {
1162 		List<ObjectId> notAdvertisedWants = null;
1163 		for (ObjectId obj : wantIds) {
1164 			if (!advertised.contains(obj)) {
1165 				if (notAdvertisedWants == null)
1166 					notAdvertisedWants = new ArrayList<ObjectId>();
1167 				notAdvertisedWants.add(obj);
1168 			}
1169 		}
1170 		if (notAdvertisedWants != null)
1171 			requestValidator.checkWants(this, notAdvertisedWants);
1172 
1173 		AsyncRevObjectQueue q = walk.parseAny(wantIds, true);
1174 		try {
1175 			RevObject obj;
1176 			while ((obj = q.next()) != null) {
1177 				want(obj);
1178 
1179 				if (!(obj instanceof RevCommit))
1180 					obj.add(SATISFIED);
1181 				if (obj instanceof RevTag) {
1182 					obj = walk.peel(obj);
1183 					if (obj instanceof RevCommit)
1184 						want(obj);
1185 				}
1186 			}
1187 			wantIds.clear();
1188 		} catch (MissingObjectException notFound) {
1189 			ObjectId id = notFound.getObjectId();
1190 			throw new PackProtocolException(MessageFormat.format(
1191 					JGitText.get().wantNotValid, id.name()), notFound);
1192 		} finally {
1193 			q.release();
1194 		}
1195 	}
1196 
1197 	private void want(RevObject obj) {
1198 		if (!obj.has(WANT)) {
1199 			obj.add(WANT);
1200 			wantAll.add(obj);
1201 		}
1202 	}
1203 
1204 	/**
1205 	 * Validator corresponding to {@link RequestPolicy#ADVERTISED}.
1206 	 *
1207 	 * @since 3.1
1208 	 */
1209 	public static final class AdvertisedRequestValidator
1210 			implements RequestValidator {
1211 		public void checkWants(UploadPack up, List<ObjectId> wants)
1212 				throws PackProtocolException, IOException {
1213 			if (!up.isBiDirectionalPipe())
1214 				new ReachableCommitRequestValidator().checkWants(up, wants);
1215 			else if (!wants.isEmpty())
1216 				throw new PackProtocolException(MessageFormat.format(
1217 						JGitText.get().wantNotValid, wants.iterator().next().name()));
1218 		}
1219 	}
1220 
1221 	/**
1222 	 * Validator corresponding to {@link RequestPolicy#REACHABLE_COMMIT}.
1223 	 *
1224 	 * @since 3.1
1225 	 */
1226 	public static final class ReachableCommitRequestValidator
1227 			implements RequestValidator {
1228 		public void checkWants(UploadPack up, List<ObjectId> wants)
1229 				throws PackProtocolException, IOException {
1230 			checkNotAdvertisedWants(up.getRevWalk(), wants,
1231 					refIdSet(up.getAdvertisedRefs().values()));
1232 		}
1233 	}
1234 
1235 	/**
1236 	 * Validator corresponding to {@link RequestPolicy#TIP}.
1237 	 *
1238 	 * @since 3.1
1239 	 */
1240 	public static final class TipRequestValidator implements RequestValidator {
1241 		public void checkWants(UploadPack up, List<ObjectId> wants)
1242 				throws PackProtocolException, IOException {
1243 			if (!up.isBiDirectionalPipe())
1244 				new ReachableCommitTipRequestValidator().checkWants(up, wants);
1245 			else if (!wants.isEmpty()) {
1246 				Set<ObjectId> refIds =
1247 					refIdSet(up.getRepository().getRefDatabase().getRefs(ALL).values());
1248 				for (ObjectId obj : wants) {
1249 					if (!refIds.contains(obj))
1250 						throw new PackProtocolException(MessageFormat.format(
1251 								JGitText.get().wantNotValid, obj.name()));
1252 				}
1253 			}
1254 		}
1255 	}
1256 
1257 	/**
1258 	 * Validator corresponding to {@link RequestPolicy#REACHABLE_COMMIT_TIP}.
1259 	 *
1260 	 * @since 3.1
1261 	 */
1262 	public static final class ReachableCommitTipRequestValidator
1263 			implements RequestValidator {
1264 		public void checkWants(UploadPack up, List<ObjectId> wants)
1265 				throws PackProtocolException, IOException {
1266 			checkNotAdvertisedWants(up.getRevWalk(), wants,
1267 					refIdSet(up.getRepository().getRefDatabase().getRefs(ALL).values()));
1268 		}
1269 	}
1270 
1271 	/**
1272 	 * Validator corresponding to {@link RequestPolicy#ANY}.
1273 	 *
1274 	 * @since 3.1
1275 	 */
1276 	public static final class AnyRequestValidator implements RequestValidator {
1277 		public void checkWants(UploadPack up, List<ObjectId> wants)
1278 				throws PackProtocolException, IOException {
1279 			// All requests are valid.
1280 		}
1281 	}
1282 
1283 	private static void checkNotAdvertisedWants(RevWalk walk,
1284 			List<ObjectId> notAdvertisedWants, Set<ObjectId> reachableFrom)
1285 			throws MissingObjectException, IncorrectObjectTypeException, IOException {
1286 		// Walk the requested commits back to the provided set of commits. If any
1287 		// commit exists, a branch was deleted or rewound and the repository owner
1288 		// no longer exports that requested item. If the requested commit is merged
1289 		// into an advertised branch it will be marked UNINTERESTING and no commits
1290 		// return.
1291 
1292 		AsyncRevObjectQueue q = walk.parseAny(notAdvertisedWants, true);
1293 		try {
1294 			RevObject obj;
1295 			while ((obj = q.next()) != null) {
1296 				if (!(obj instanceof RevCommit))
1297 					throw new PackProtocolException(MessageFormat.format(
1298 						JGitText.get().wantNotValid, obj.name()));
1299 				walk.markStart((RevCommit) obj);
1300 			}
1301 		} catch (MissingObjectException notFound) {
1302 			ObjectId id = notFound.getObjectId();
1303 			throw new PackProtocolException(MessageFormat.format(
1304 					JGitText.get().wantNotValid, id.name()), notFound);
1305 		} finally {
1306 			q.release();
1307 		}
1308 		for (ObjectId id : reachableFrom) {
1309 			try {
1310 				walk.markUninteresting(walk.parseCommit(id));
1311 			} catch (IncorrectObjectTypeException notCommit) {
1312 				continue;
1313 			}
1314 		}
1315 
1316 		RevCommit bad = walk.next();
1317 		if (bad != null) {
1318 			throw new PackProtocolException(MessageFormat.format(
1319 					JGitText.get().wantNotValid,
1320 					bad.name()));
1321 		}
1322 		walk.reset();
1323 	}
1324 
1325 	private void addCommonBase(final RevObject o) {
1326 		if (!o.has(COMMON)) {
1327 			o.add(COMMON);
1328 			commonBase.add(o);
1329 			okToGiveUp = null;
1330 		}
1331 	}
1332 
1333 	private boolean okToGiveUp() throws PackProtocolException {
1334 		if (okToGiveUp == null)
1335 			okToGiveUp = Boolean.valueOf(okToGiveUpImp());
1336 		return okToGiveUp.booleanValue();
1337 	}
1338 
1339 	private boolean okToGiveUpImp() throws PackProtocolException {
1340 		if (commonBase.isEmpty())
1341 			return false;
1342 
1343 		try {
1344 			for (RevObject obj : wantAll) {
1345 				if (!wantSatisfied(obj))
1346 					return false;
1347 			}
1348 			return true;
1349 		} catch (IOException e) {
1350 			throw new PackProtocolException(JGitText.get().internalRevisionError, e);
1351 		}
1352 	}
1353 
1354 	private boolean wantSatisfied(final RevObject want) throws IOException {
1355 		if (want.has(SATISFIED))
1356 			return true;
1357 
1358 		walk.resetRetain(SAVE);
1359 		walk.markStart((RevCommit) want);
1360 		if (oldestTime != 0)
1361 			walk.setRevFilter(CommitTimeRevFilter.after(oldestTime * 1000L));
1362 		for (;;) {
1363 			final RevCommit c = walk.next();
1364 			if (c == null)
1365 				break;
1366 			if (c.has(PEER_HAS)) {
1367 				addCommonBase(c);
1368 				want.add(SATISFIED);
1369 				return true;
1370 			}
1371 		}
1372 		return false;
1373 	}
1374 
1375 	private void sendPack() throws IOException {
1376 		final boolean sideband = options.contains(OPTION_SIDE_BAND)
1377 				|| options.contains(OPTION_SIDE_BAND_64K);
1378 
1379 		if (!biDirectionalPipe) {
1380 			// Ensure the request was fully consumed. Any remaining input must
1381 			// be a protocol error. If we aren't at EOF the implementation is broken.
1382 			int eof = rawIn.read();
1383 			if (0 <= eof)
1384 				throw new CorruptObjectException(MessageFormat.format(
1385 						JGitText.get().expectedEOFReceived,
1386 						"\\x" + Integer.toHexString(eof))); //$NON-NLS-1$
1387 		}
1388 
1389 		if (sideband) {
1390 			try {
1391 				sendPack(true);
1392 			} catch (ServiceMayNotContinueException noPack) {
1393 				// This was already reported on (below).
1394 				throw noPack;
1395 			} catch (IOException err) {
1396 				if (reportInternalServerErrorOverSideband())
1397 					throw new UploadPackInternalServerErrorException(err);
1398 				else
1399 					throw err;
1400 			} catch (RuntimeException err) {
1401 				if (reportInternalServerErrorOverSideband())
1402 					throw new UploadPackInternalServerErrorException(err);
1403 				else
1404 					throw err;
1405 			} catch (Error err) {
1406 				if (reportInternalServerErrorOverSideband())
1407 					throw new UploadPackInternalServerErrorException(err);
1408 				else
1409 					throw err;
1410 			}
1411 		} else {
1412 			sendPack(false);
1413 		}
1414 	}
1415 
1416 	private boolean reportInternalServerErrorOverSideband() {
1417 		try {
1418 			@SuppressWarnings("resource" /* java 7 */)
1419 			SideBandOutputStream err = new SideBandOutputStream(
1420 					SideBandOutputStream.CH_ERROR,
1421 					SideBandOutputStream.SMALL_BUF,
1422 					rawOut);
1423 			err.write(Constants.encode(JGitText.get().internalServerError));
1424 			err.flush();
1425 			return true;
1426 		} catch (Throwable cannotReport) {
1427 			// Ignore the reason. This is a secondary failure.
1428 			return false;
1429 		}
1430 	}
1431 
1432 	private void sendPack(final boolean sideband) throws IOException {
1433 		ProgressMonitor pm = NullProgressMonitor.INSTANCE;
1434 		OutputStream packOut = rawOut;
1435 
1436 		if (sideband) {
1437 			int bufsz = SideBandOutputStream.SMALL_BUF;
1438 			if (options.contains(OPTION_SIDE_BAND_64K))
1439 				bufsz = SideBandOutputStream.MAX_BUF;
1440 
1441 			packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
1442 					bufsz, rawOut);
1443 			if (!options.contains(OPTION_NO_PROGRESS)) {
1444 				msgOut = new SideBandOutputStream(
1445 						SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
1446 				pm = new SideBandProgressMonitor(msgOut);
1447 			}
1448 		}
1449 
1450 		try {
1451 			if (wantAll.isEmpty()) {
1452 				preUploadHook.onSendPack(this, wantIds, commonBase);
1453 			} else {
1454 				preUploadHook.onSendPack(this, wantAll, commonBase);
1455 			}
1456 			msgOut.flush();
1457 		} catch (ServiceMayNotContinueException noPack) {
1458 			if (sideband && noPack.getMessage() != null) {
1459 				noPack.setOutput();
1460 				@SuppressWarnings("resource" /* java 7 */)
1461 				SideBandOutputStream err = new SideBandOutputStream(
1462 						SideBandOutputStream.CH_ERROR,
1463 						SideBandOutputStream.SMALL_BUF, rawOut);
1464 				err.write(Constants.encode(noPack.getMessage()));
1465 				err.flush();
1466 			}
1467 			throw noPack;
1468 		}
1469 
1470 		PackConfig cfg = packConfig;
1471 		if (cfg == null)
1472 			cfg = new PackConfig(db);
1473 		final PackWriter pw = new PackWriter(cfg, walk.getObjectReader());
1474 		try {
1475 			pw.setIndexDisabled(true);
1476 			pw.setUseCachedPacks(true);
1477 			pw.setUseBitmaps(depth == 0 && clientShallowCommits.isEmpty());
1478 			pw.setClientShallowCommits(clientShallowCommits);
1479 			pw.setReuseDeltaCommits(true);
1480 			pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
1481 			pw.setThin(options.contains(OPTION_THIN_PACK));
1482 			pw.setReuseValidatingObjects(false);
1483 
1484 			if (commonBase.isEmpty() && refs != null) {
1485 				Set<ObjectId> tagTargets = new HashSet<ObjectId>();
1486 				for (Ref ref : refs.values()) {
1487 					if (ref.getPeeledObjectId() != null)
1488 						tagTargets.add(ref.getPeeledObjectId());
1489 					else if (ref.getObjectId() == null)
1490 						continue;
1491 					else if (ref.getName().startsWith(Constants.R_HEADS))
1492 						tagTargets.add(ref.getObjectId());
1493 				}
1494 				pw.setTagTargets(tagTargets);
1495 			}
1496 
1497 			if (depth > 0)
1498 				pw.setShallowPack(depth, unshallowCommits);
1499 
1500 			RevWalk rw = walk;
1501 			if (wantAll.isEmpty()) {
1502 				pw.preparePack(pm, wantIds, commonBase);
1503 			} else {
1504 				walk.reset();
1505 
1506 				ObjectWalk ow = walk.toObjectWalkWithSameObjects();
1507 				pw.preparePack(pm, ow, wantAll, commonBase);
1508 				rw = ow;
1509 			}
1510 
1511 			if (options.contains(OPTION_INCLUDE_TAG) && refs != null) {
1512 				for (Ref ref : refs.values()) {
1513 					ObjectId objectId = ref.getObjectId();
1514 
1515 					// If the object was already requested, skip it.
1516 					if (wantAll.isEmpty()) {
1517 						if (wantIds.contains(objectId))
1518 							continue;
1519 					} else {
1520 						RevObject obj = rw.lookupOrNull(objectId);
1521 						if (obj != null && obj.has(WANT))
1522 							continue;
1523 					}
1524 
1525 					if (!ref.isPeeled())
1526 						ref = db.peel(ref);
1527 
1528 					ObjectId peeledId = ref.getPeeledObjectId();
1529 					if (peeledId == null)
1530 						continue;
1531 
1532 					objectId = ref.getObjectId();
1533 					if (pw.willInclude(peeledId) && !pw.willInclude(objectId))
1534 						pw.addObject(rw.parseAny(objectId));
1535 				}
1536 			}
1537 
1538 			pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut);
1539 
1540 			if (msgOut != NullOutputStream.INSTANCE) {
1541 				String msg = pw.getStatistics().getMessage() + '\n';
1542 				msgOut.write(Constants.encode(msg));
1543 				msgOut.flush();
1544 			}
1545 
1546 		} finally {
1547 			statistics = pw.getStatistics();
1548 			if (statistics != null) {
1549 				postUploadHook.onPostUpload(statistics);
1550 				logger.onPackStatistics(new PackWriter.Statistics(statistics));
1551 			}
1552 			pw.close();
1553 		}
1554 
1555 		if (sideband)
1556 			pckOut.end();
1557 	}
1558 
1559 	private static void findSymrefs(
1560 			final RefAdvertiser adv, final Map<String, Ref> refs) {
1561 		Ref head = refs.get(Constants.HEAD);
1562 		if (head != null && head.isSymbolic()) {
1563 			adv.addSymref(Constants.HEAD, head.getLeaf().getName());
1564 		}
1565 	}
1566 }