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