View Javadoc
1   /*
2    * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  
45  package org.eclipse.jgit.transport;
46  
47  import static org.eclipse.jgit.lib.RefDatabase.ALL;
48  
49  import java.io.File;
50  import java.io.FileNotFoundException;
51  import java.io.FileOutputStream;
52  import java.io.IOException;
53  import java.text.MessageFormat;
54  import java.util.ArrayList;
55  import java.util.Collection;
56  import java.util.HashMap;
57  import java.util.HashSet;
58  import java.util.Iterator;
59  import java.util.LinkedList;
60  import java.util.List;
61  import java.util.Map;
62  import java.util.Set;
63  
64  import org.eclipse.jgit.errors.CompoundException;
65  import org.eclipse.jgit.errors.CorruptObjectException;
66  import org.eclipse.jgit.errors.MissingObjectException;
67  import org.eclipse.jgit.errors.TransportException;
68  import org.eclipse.jgit.internal.JGitText;
69  import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
70  import org.eclipse.jgit.internal.storage.file.PackIndex;
71  import org.eclipse.jgit.internal.storage.file.PackLock;
72  import org.eclipse.jgit.internal.storage.file.UnpackedObject;
73  import org.eclipse.jgit.lib.AnyObjectId;
74  import org.eclipse.jgit.lib.Constants;
75  import org.eclipse.jgit.lib.FileMode;
76  import org.eclipse.jgit.lib.MutableObjectId;
77  import org.eclipse.jgit.lib.ObjectChecker;
78  import org.eclipse.jgit.lib.ObjectId;
79  import org.eclipse.jgit.lib.ObjectInserter;
80  import org.eclipse.jgit.lib.ObjectLoader;
81  import org.eclipse.jgit.lib.ObjectReader;
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.DateRevQueue;
86  import org.eclipse.jgit.revwalk.RevCommit;
87  import org.eclipse.jgit.revwalk.RevFlag;
88  import org.eclipse.jgit.revwalk.RevObject;
89  import org.eclipse.jgit.revwalk.RevTag;
90  import org.eclipse.jgit.revwalk.RevTree;
91  import org.eclipse.jgit.revwalk.RevWalk;
92  import org.eclipse.jgit.treewalk.TreeWalk;
93  import org.eclipse.jgit.util.FileUtils;
94  
95  /**
96   * Generic fetch support for dumb transport protocols.
97   * <p>
98   * Since there are no Git-specific smarts on the remote side of the connection
99   * the client side must determine which objects it needs to copy in order to
100  * completely fetch the requested refs and their history. The generic walk
101  * support in this class parses each individual object (once it has been copied
102  * to the local repository) and examines the list of objects that must also be
103  * copied to create a complete history. Objects which are already available
104  * locally are retained (and not copied), saving bandwidth for incremental
105  * fetches. Pack files are copied from the remote repository only as a last
106  * resort, as the entire pack must be copied locally in order to access any
107  * single object.
108  * <p>
109  * This fetch connection does not actually perform the object data transfer.
110  * Instead it delegates the transfer to a {@link WalkRemoteObjectDatabase},
111  * which knows how to read individual files from the remote repository and
112  * supply the data as a standard Java InputStream.
113  *
114  * @see WalkRemoteObjectDatabase
115  */
116 class WalkFetchConnection extends BaseFetchConnection {
117 	/** The repository this transport fetches into, or pushes out of. */
118 	private final Repository local;
119 
120 	/** If not null the validator for received objects. */
121 	private final ObjectChecker objCheck;
122 
123 	/**
124 	 * List of all remote repositories we may need to get objects out of.
125 	 * <p>
126 	 * The first repository in the list is the one we were asked to fetch from;
127 	 * the remaining repositories point to the alternate locations we can fetch
128 	 * objects through.
129 	 */
130 	private final List<WalkRemoteObjectDatabase> remotes;
131 
132 	/** Most recently used item in {@link #remotes}. */
133 	private int lastRemoteIdx;
134 
135 	private final RevWalk revWalk;
136 
137 	private final TreeWalk treeWalk;
138 
139 	/** Objects whose direct dependents we know we have (or will have). */
140 	private final RevFlag COMPLETE;
141 
142 	/** Objects that have already entered {@link #workQueue}. */
143 	private final RevFlag IN_WORK_QUEUE;
144 
145 	/** Commits that have already entered {@link #localCommitQueue}. */
146 	private final RevFlag LOCALLY_SEEN;
147 
148 	/** Commits already reachable from all local refs. */
149 	private final DateRevQueue localCommitQueue;
150 
151 	/** Objects we need to copy from the remote repository. */
152 	private LinkedList<ObjectId> workQueue;
153 
154 	/** Databases we have not yet obtained the list of packs from. */
155 	private final LinkedList<WalkRemoteObjectDatabase> noPacksYet;
156 
157 	/** Databases we have not yet obtained the alternates from. */
158 	private final LinkedList<WalkRemoteObjectDatabase> noAlternatesYet;
159 
160 	/** Packs we have discovered, but have not yet fetched locally. */
161 	private final LinkedList<RemotePack> unfetchedPacks;
162 
163 	/**
164 	 * Packs whose indexes we have looked at in {@link #unfetchedPacks}.
165 	 * <p>
166 	 * We try to avoid getting duplicate copies of the same pack through
167 	 * multiple alternates by only looking at packs whose names are not yet in
168 	 * this collection.
169 	 */
170 	private final Set<String> packsConsidered;
171 
172 	private final MutableObjectId idBuffer = new MutableObjectId();
173 
174 	/**
175 	 * Errors received while trying to obtain an object.
176 	 * <p>
177 	 * If the fetch winds up failing because we cannot locate a specific object
178 	 * then we need to report all errors related to that object back to the
179 	 * caller as there may be cascading failures.
180 	 */
181 	private final HashMap<ObjectId, List<Throwable>> fetchErrors;
182 
183 	private String lockMessage;
184 
185 	private final List<PackLock> packLocks;
186 
187 	/** Inserter to write objects onto {@link #local}. */
188 	private final ObjectInserter inserter;
189 
190 	/** Inserter to read objects from {@link #local}. */
191 	private final ObjectReader reader;
192 
193 	WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) {
194 		Transport wt = (Transport)t;
195 		local = wt.local;
196 		objCheck = wt.getObjectChecker();
197 		inserter = local.newObjectInserter();
198 		reader = local.newObjectReader();
199 
200 		remotes = new ArrayList<WalkRemoteObjectDatabase>();
201 		remotes.add(w);
202 
203 		unfetchedPacks = new LinkedList<RemotePack>();
204 		packsConsidered = new HashSet<String>();
205 
206 		noPacksYet = new LinkedList<WalkRemoteObjectDatabase>();
207 		noPacksYet.add(w);
208 
209 		noAlternatesYet = new LinkedList<WalkRemoteObjectDatabase>();
210 		noAlternatesYet.add(w);
211 
212 		fetchErrors = new HashMap<ObjectId, List<Throwable>>();
213 		packLocks = new ArrayList<PackLock>(4);
214 
215 		revWalk = new RevWalk(reader);
216 		revWalk.setRetainBody(false);
217 		treeWalk = new TreeWalk(reader);
218 		COMPLETE = revWalk.newFlag("COMPLETE"); //$NON-NLS-1$
219 		IN_WORK_QUEUE = revWalk.newFlag("IN_WORK_QUEUE"); //$NON-NLS-1$
220 		LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN"); //$NON-NLS-1$
221 
222 		localCommitQueue = new DateRevQueue();
223 		workQueue = new LinkedList<ObjectId>();
224 	}
225 
226 	public boolean didFetchTestConnectivity() {
227 		return true;
228 	}
229 
230 	@Override
231 	protected void doFetch(final ProgressMonitor monitor,
232 			final Collection<Ref> want, final Set<ObjectId> have)
233 			throws TransportException {
234 		markLocalRefsComplete(have);
235 		queueWants(want);
236 
237 		while (!monitor.isCancelled() && !workQueue.isEmpty()) {
238 			final ObjectId id = workQueue.removeFirst();
239 			if (!(id instanceof RevObject) || !((RevObject) id).has(COMPLETE))
240 				downloadObject(monitor, id);
241 			process(id);
242 		}
243 	}
244 
245 	public Collection<PackLock> getPackLocks() {
246 		return packLocks;
247 	}
248 
249 	public void setPackLockMessage(final String message) {
250 		lockMessage = message;
251 	}
252 
253 	@Override
254 	public void close() {
255 		inserter.close();
256 		reader.close();
257 		for (final RemotePack p : unfetchedPacks) {
258 			if (p.tmpIdx != null)
259 				p.tmpIdx.delete();
260 		}
261 		for (final WalkRemoteObjectDatabase r : remotes)
262 			r.close();
263 	}
264 
265 	private void queueWants(final Collection<Ref> want)
266 			throws TransportException {
267 		final HashSet<ObjectId> inWorkQueue = new HashSet<ObjectId>();
268 		for (final Ref r : want) {
269 			final ObjectId id = r.getObjectId();
270 			try {
271 				final RevObject obj = revWalk.parseAny(id);
272 				if (obj.has(COMPLETE))
273 					continue;
274 				if (inWorkQueue.add(id)) {
275 					obj.add(IN_WORK_QUEUE);
276 					workQueue.add(obj);
277 				}
278 			} catch (MissingObjectException e) {
279 				if (inWorkQueue.add(id))
280 					workQueue.add(id);
281 			} catch (IOException e) {
282 				throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e);
283 			}
284 		}
285 	}
286 
287 	private void process(final ObjectId id) throws TransportException {
288 		final RevObject obj;
289 		try {
290 			if (id instanceof RevObject) {
291 				obj = (RevObject) id;
292 				if (obj.has(COMPLETE))
293 					return;
294 				revWalk.parseHeaders(obj);
295 			} else {
296 				obj = revWalk.parseAny(id);
297 				if (obj.has(COMPLETE))
298 					return;
299 			}
300 		} catch (IOException e) {
301 			throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e);
302 		}
303 
304 		switch (obj.getType()) {
305 		case Constants.OBJ_BLOB:
306 			processBlob(obj);
307 			break;
308 		case Constants.OBJ_TREE:
309 			processTree(obj);
310 			break;
311 		case Constants.OBJ_COMMIT:
312 			processCommit(obj);
313 			break;
314 		case Constants.OBJ_TAG:
315 			processTag(obj);
316 			break;
317 		default:
318 			throw new TransportException(MessageFormat.format(JGitText.get().unknownObjectType, id.name()));
319 		}
320 
321 		// If we had any prior errors fetching this object they are
322 		// now resolved, as the object was parsed successfully.
323 		//
324 		fetchErrors.remove(id);
325 	}
326 
327 	private void processBlob(final RevObject obj) throws TransportException {
328 		try {
329 			if (reader.has(obj, Constants.OBJ_BLOB))
330 				obj.add(COMPLETE);
331 			else
332 				throw new TransportException(MessageFormat.format(JGitText
333 						.get().cannotReadBlob, obj.name()),
334 						new MissingObjectException(obj, Constants.TYPE_BLOB));
335 		} catch (IOException error) {
336 			throw new TransportException(MessageFormat.format(
337 					JGitText.get().cannotReadBlob, obj.name()), error);
338 		}
339 	}
340 
341 	private void processTree(final RevObject obj) throws TransportException {
342 		try {
343 			treeWalk.reset(obj);
344 			while (treeWalk.next()) {
345 				final FileMode mode = treeWalk.getFileMode(0);
346 				final int sType = mode.getObjectType();
347 
348 				switch (sType) {
349 				case Constants.OBJ_BLOB:
350 				case Constants.OBJ_TREE:
351 					treeWalk.getObjectId(idBuffer, 0);
352 					needs(revWalk.lookupAny(idBuffer, sType));
353 					continue;
354 
355 				default:
356 					if (FileMode.GITLINK.equals(mode))
357 						continue;
358 					treeWalk.getObjectId(idBuffer, 0);
359 					throw new CorruptObjectException(MessageFormat.format(JGitText.get().invalidModeFor
360 							, mode, idBuffer.name(), treeWalk.getPathString(), obj.getId().name()));
361 				}
362 			}
363 		} catch (IOException ioe) {
364 			throw new TransportException(MessageFormat.format(JGitText.get().cannotReadTree, obj.name()), ioe);
365 		}
366 		obj.add(COMPLETE);
367 	}
368 
369 	private void processCommit(final RevObject obj) throws TransportException {
370 		final RevCommit commit = (RevCommit) obj;
371 		markLocalCommitsComplete(commit.getCommitTime());
372 		needs(commit.getTree());
373 		for (final RevCommit p : commit.getParents())
374 			needs(p);
375 		obj.add(COMPLETE);
376 	}
377 
378 	private void processTag(final RevObject obj) {
379 		final RevTag tag = (RevTag) obj;
380 		needs(tag.getObject());
381 		obj.add(COMPLETE);
382 	}
383 
384 	private void needs(final RevObject obj) {
385 		if (obj.has(COMPLETE))
386 			return;
387 		if (!obj.has(IN_WORK_QUEUE)) {
388 			obj.add(IN_WORK_QUEUE);
389 			workQueue.add(obj);
390 		}
391 	}
392 
393 	private void downloadObject(final ProgressMonitor pm, final AnyObjectId id)
394 			throws TransportException {
395 		if (alreadyHave(id))
396 			return;
397 
398 		for (;;) {
399 			// Try a pack file we know about, but don't have yet. Odds are
400 			// that if it has this object, it has others related to it so
401 			// getting the pack is a good bet.
402 			//
403 			if (downloadPackedObject(pm, id))
404 				return;
405 
406 			// Search for a loose object over all alternates, starting
407 			// from the one we last successfully located an object through.
408 			//
409 			final String idStr = id.name();
410 			final String subdir = idStr.substring(0, 2);
411 			final String file = idStr.substring(2);
412 			final String looseName = subdir + "/" + file; //$NON-NLS-1$
413 
414 			for (int i = lastRemoteIdx; i < remotes.size(); i++) {
415 				if (downloadLooseObject(id, looseName, remotes.get(i))) {
416 					lastRemoteIdx = i;
417 					return;
418 				}
419 			}
420 			for (int i = 0; i < lastRemoteIdx; i++) {
421 				if (downloadLooseObject(id, looseName, remotes.get(i))) {
422 					lastRemoteIdx = i;
423 					return;
424 				}
425 			}
426 
427 			// Try to obtain more pack information and search those.
428 			//
429 			while (!noPacksYet.isEmpty()) {
430 				final WalkRemoteObjectDatabase wrr = noPacksYet.removeFirst();
431 				final Collection<String> packNameList;
432 				try {
433 					pm.beginTask(JGitText.get().listingPacks,
434 							ProgressMonitor.UNKNOWN);
435 					packNameList = wrr.getPackNames();
436 				} catch (IOException e) {
437 					// Try another repository.
438 					//
439 					recordError(id, e);
440 					continue;
441 				} finally {
442 					pm.endTask();
443 				}
444 
445 				if (packNameList == null || packNameList.isEmpty())
446 					continue;
447 				for (final String packName : packNameList) {
448 					if (packsConsidered.add(packName))
449 						unfetchedPacks.add(new RemotePack(wrr, packName));
450 				}
451 				if (downloadPackedObject(pm, id))
452 					return;
453 			}
454 
455 			// Try to expand the first alternate we haven't expanded yet.
456 			//
457 			Collection<WalkRemoteObjectDatabase> al = expandOneAlternate(id, pm);
458 			if (al != null && !al.isEmpty()) {
459 				for (final WalkRemoteObjectDatabase alt : al) {
460 					remotes.add(alt);
461 					noPacksYet.add(alt);
462 					noAlternatesYet.add(alt);
463 				}
464 				continue;
465 			}
466 
467 			// We could not obtain the object. There may be reasons why.
468 			//
469 			List<Throwable> failures = fetchErrors.get(id);
470 			final TransportException te;
471 
472 			te = new TransportException(MessageFormat.format(JGitText.get().cannotGet, id.name()));
473 			if (failures != null && !failures.isEmpty()) {
474 				if (failures.size() == 1)
475 					te.initCause(failures.get(0));
476 				else
477 					te.initCause(new CompoundException(failures));
478 			}
479 			throw te;
480 		}
481 	}
482 
483 	private boolean alreadyHave(final AnyObjectId id) throws TransportException {
484 		try {
485 			return reader.has(id);
486 		} catch (IOException error) {
487 			throw new TransportException(MessageFormat.format(
488 					JGitText.get().cannotReadObject, id.name()), error);
489 		}
490 	}
491 
492 	private boolean downloadPackedObject(final ProgressMonitor monitor,
493 			final AnyObjectId id) throws TransportException {
494 		// Search for the object in a remote pack whose index we have,
495 		// but whose pack we do not yet have.
496 		//
497 		final Iterator<RemotePack> packItr = unfetchedPacks.iterator();
498 		while (packItr.hasNext() && !monitor.isCancelled()) {
499 			final RemotePack pack = packItr.next();
500 			try {
501 				pack.openIndex(monitor);
502 			} catch (IOException err) {
503 				// If the index won't open its either not found or
504 				// its a format we don't recognize. In either case
505 				// we may still be able to obtain the object from
506 				// another source, so don't consider it a failure.
507 				//
508 				recordError(id, err);
509 				packItr.remove();
510 				continue;
511 			}
512 
513 			if (monitor.isCancelled()) {
514 				// If we were cancelled while the index was opening
515 				// the open may have aborted. We can't search an
516 				// unopen index.
517 				//
518 				return false;
519 			}
520 
521 			if (!pack.index.hasObject(id)) {
522 				// Not in this pack? Try another.
523 				//
524 				continue;
525 			}
526 
527 			// It should be in the associated pack. Download that
528 			// and attach it to the local repository so we can use
529 			// all of the contained objects.
530 			//
531 			try {
532 				pack.downloadPack(monitor);
533 			} catch (IOException err) {
534 				// If the pack failed to download, index correctly,
535 				// or open in the local repository we may still be
536 				// able to obtain this object from another pack or
537 				// an alternate.
538 				//
539 				recordError(id, err);
540 				continue;
541 			} finally {
542 				// If the pack was good its in the local repository
543 				// and Repository.hasObject(id) will succeed in the
544 				// future, so we do not need this data anymore. If
545 				// it failed the index and pack are unusable and we
546 				// shouldn't consult them again.
547 				//
548 				try {
549 					if (pack.tmpIdx != null)
550 						FileUtils.delete(pack.tmpIdx);
551 				} catch (IOException e) {
552 					throw new TransportException(e.getMessage(), e);
553 				}
554 				packItr.remove();
555 			}
556 
557 			if (!alreadyHave(id)) {
558 				// What the hell? This pack claimed to have
559 				// the object, but after indexing we didn't
560 				// actually find it in the pack.
561 				//
562 				recordError(id, new FileNotFoundException(MessageFormat.format(
563 						JGitText.get().objectNotFoundIn, id.name(), pack.packName)));
564 				continue;
565 			}
566 
567 			// Complete any other objects that we can.
568 			//
569 			final Iterator<ObjectId> pending = swapFetchQueue();
570 			while (pending.hasNext()) {
571 				final ObjectId p = pending.next();
572 				if (pack.index.hasObject(p)) {
573 					pending.remove();
574 					process(p);
575 				} else {
576 					workQueue.add(p);
577 				}
578 			}
579 			return true;
580 
581 		}
582 		return false;
583 	}
584 
585 	private Iterator<ObjectId> swapFetchQueue() {
586 		final Iterator<ObjectId> r = workQueue.iterator();
587 		workQueue = new LinkedList<ObjectId>();
588 		return r;
589 	}
590 
591 	private boolean downloadLooseObject(final AnyObjectId id,
592 			final String looseName, final WalkRemoteObjectDatabase remote)
593 			throws TransportException {
594 		try {
595 			final byte[] compressed = remote.open(looseName).toArray();
596 			verifyAndInsertLooseObject(id, compressed);
597 			return true;
598 		} catch (FileNotFoundException e) {
599 			// Not available in a loose format from this alternate?
600 			// Try another strategy to get the object.
601 			//
602 			recordError(id, e);
603 			return false;
604 		} catch (IOException e) {
605 			throw new TransportException(MessageFormat.format(JGitText.get().cannotDownload, id.name()), e);
606 		}
607 	}
608 
609 	private void verifyAndInsertLooseObject(final AnyObjectId id,
610 			final byte[] compressed) throws IOException {
611 		final ObjectLoader uol;
612 		try {
613 			uol = UnpackedObject.parse(compressed, id);
614 		} catch (CorruptObjectException parsingError) {
615 			// Some HTTP servers send back a "200 OK" status with an HTML
616 			// page that explains the requested file could not be found.
617 			// These servers are most certainly misconfigured, but many
618 			// of them exist in the world, and many of those are hosting
619 			// Git repositories.
620 			//
621 			// Since an HTML page is unlikely to hash to one of our loose
622 			// objects we treat this condition as a FileNotFoundException
623 			// and attempt to recover by getting the object from another
624 			// source.
625 			//
626 			final FileNotFoundException e;
627 			e = new FileNotFoundException(id.name());
628 			e.initCause(parsingError);
629 			throw e;
630 		}
631 
632 		final int type = uol.getType();
633 		final byte[] raw = uol.getCachedBytes();
634 		if (objCheck != null) {
635 			try {
636 				objCheck.check(type, raw);
637 			} catch (CorruptObjectException e) {
638 				throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid
639 						, Constants.typeString(type), id.name(), e.getMessage()));
640 			}
641 		}
642 
643 		ObjectId act = inserter.insert(type, raw);
644 		if (!AnyObjectId.equals(id, act)) {
645 			throw new TransportException(MessageFormat.format(
646 					JGitText.get().incorrectHashFor, id.name(), act.name(),
647 					Constants.typeString(type),
648 					Integer.valueOf(compressed.length)));
649 		}
650 		inserter.flush();
651 	}
652 
653 	private Collection<WalkRemoteObjectDatabase> expandOneAlternate(
654 			final AnyObjectId id, final ProgressMonitor pm) {
655 		while (!noAlternatesYet.isEmpty()) {
656 			final WalkRemoteObjectDatabase wrr = noAlternatesYet.removeFirst();
657 			try {
658 				pm.beginTask(JGitText.get().listingAlternates, ProgressMonitor.UNKNOWN);
659 				Collection<WalkRemoteObjectDatabase> altList = wrr
660 						.getAlternates();
661 				if (altList != null && !altList.isEmpty())
662 					return altList;
663 			} catch (IOException e) {
664 				// Try another repository.
665 				//
666 				recordError(id, e);
667 			} finally {
668 				pm.endTask();
669 			}
670 		}
671 		return null;
672 	}
673 
674 	private void markLocalRefsComplete(final Set<ObjectId> have) throws TransportException {
675 		Map<String, Ref> refs;
676 		try {
677 			refs = local.getRefDatabase().getRefs(ALL);
678 		} catch (IOException e) {
679 			throw new TransportException(e.getMessage(), e);
680 		}
681 		for (final Ref r : refs.values()) {
682 			try {
683 				markLocalObjComplete(revWalk.parseAny(r.getObjectId()));
684 			} catch (IOException readError) {
685 				throw new TransportException(MessageFormat.format(JGitText.get().localRefIsMissingObjects, r.getName()), readError);
686 			}
687 		}
688 		for (final ObjectId id : have) {
689 			try {
690 				markLocalObjComplete(revWalk.parseAny(id));
691 			} catch (IOException readError) {
692 				throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionMissingAssumed, id.name()), readError);
693 			}
694 		}
695 	}
696 
697 	private void markLocalObjComplete(RevObject obj) throws IOException {
698 		while (obj.getType() == Constants.OBJ_TAG) {
699 			obj.add(COMPLETE);
700 			obj = ((RevTag) obj).getObject();
701 			revWalk.parseHeaders(obj);
702 		}
703 
704 		switch (obj.getType()) {
705 		case Constants.OBJ_BLOB:
706 			obj.add(COMPLETE);
707 			break;
708 		case Constants.OBJ_COMMIT:
709 			pushLocalCommit((RevCommit) obj);
710 			break;
711 		case Constants.OBJ_TREE:
712 			markTreeComplete((RevTree) obj);
713 			break;
714 		}
715 	}
716 
717 	private void markLocalCommitsComplete(final int until)
718 			throws TransportException {
719 		try {
720 			for (;;) {
721 				final RevCommit c = localCommitQueue.peek();
722 				if (c == null || c.getCommitTime() < until)
723 					return;
724 				localCommitQueue.next();
725 
726 				markTreeComplete(c.getTree());
727 				for (final RevCommit p : c.getParents())
728 					pushLocalCommit(p);
729 			}
730 		} catch (IOException err) {
731 			throw new TransportException(JGitText.get().localObjectsIncomplete, err);
732 		}
733 	}
734 
735 	private void pushLocalCommit(final RevCommit p)
736 			throws MissingObjectException, IOException {
737 		if (p.has(LOCALLY_SEEN))
738 			return;
739 		revWalk.parseHeaders(p);
740 		p.add(LOCALLY_SEEN);
741 		p.add(COMPLETE);
742 		p.carry(COMPLETE);
743 		localCommitQueue.add(p);
744 	}
745 
746 	private void markTreeComplete(final RevTree tree) throws IOException {
747 		if (tree.has(COMPLETE))
748 			return;
749 		tree.add(COMPLETE);
750 		treeWalk.reset(tree);
751 		while (treeWalk.next()) {
752 			final FileMode mode = treeWalk.getFileMode(0);
753 			final int sType = mode.getObjectType();
754 
755 			switch (sType) {
756 			case Constants.OBJ_BLOB:
757 				treeWalk.getObjectId(idBuffer, 0);
758 				revWalk.lookupAny(idBuffer, sType).add(COMPLETE);
759 				continue;
760 
761 			case Constants.OBJ_TREE: {
762 				treeWalk.getObjectId(idBuffer, 0);
763 				final RevObject o = revWalk.lookupAny(idBuffer, sType);
764 				if (!o.has(COMPLETE)) {
765 					o.add(COMPLETE);
766 					treeWalk.enterSubtree();
767 				}
768 				continue;
769 			}
770 			default:
771 				if (FileMode.GITLINK.equals(mode))
772 					continue;
773 				treeWalk.getObjectId(idBuffer, 0);
774 				throw new CorruptObjectException(MessageFormat.format(JGitText.get().corruptObjectInvalidMode3
775 						, mode, idBuffer.name(), treeWalk.getPathString(), tree.name()));
776 			}
777 		}
778 	}
779 
780 	private void recordError(final AnyObjectId id, final Throwable what) {
781 		final ObjectId objId = id.copy();
782 		List<Throwable> errors = fetchErrors.get(objId);
783 		if (errors == null) {
784 			errors = new ArrayList<Throwable>(2);
785 			fetchErrors.put(objId, errors);
786 		}
787 		errors.add(what);
788 	}
789 
790 	private class RemotePack {
791 		final WalkRemoteObjectDatabase connection;
792 
793 		final String packName;
794 
795 		final String idxName;
796 
797 		File tmpIdx;
798 
799 		PackIndex index;
800 
801 		RemotePack(final WalkRemoteObjectDatabase c, final String pn) {
802 			connection = c;
803 			packName = pn;
804 			idxName = packName.substring(0, packName.length() - 5) + ".idx"; //$NON-NLS-1$
805 
806 			String tn = idxName;
807 			if (tn.startsWith("pack-")) //$NON-NLS-1$
808 				tn = tn.substring(5);
809 			if (tn.endsWith(".idx")) //$NON-NLS-1$
810 				tn = tn.substring(0, tn.length() - 4);
811 
812 			if (local.getObjectDatabase() instanceof ObjectDirectory) {
813 				tmpIdx = new File(((ObjectDirectory) local.getObjectDatabase())
814 								.getDirectory(),
815 						"walk-" + tn + ".walkidx"); //$NON-NLS-1$ //$NON-NLS-2$
816 			}
817 		}
818 
819 		void openIndex(final ProgressMonitor pm) throws IOException {
820 			if (index != null)
821 				return;
822 			if (tmpIdx == null)
823 				tmpIdx = File.createTempFile("jgit-walk-", ".idx"); //$NON-NLS-1$ //$NON-NLS-2$
824 			else if (tmpIdx.isFile()) {
825 				try {
826 					index = PackIndex.open(tmpIdx);
827 					return;
828 				} catch (FileNotFoundException err) {
829 					// Fall through and get the file.
830 				}
831 			}
832 
833 			final WalkRemoteObjectDatabase.FileStream s;
834 			s = connection.open("pack/" + idxName); //$NON-NLS-1$
835 			pm.beginTask("Get " + idxName.substring(0, 12) + "..idx", //$NON-NLS-1$ //$NON-NLS-2$
836 					s.length < 0 ? ProgressMonitor.UNKNOWN
837 							: (int) (s.length / 1024));
838 			try {
839 				final FileOutputStream fos = new FileOutputStream(tmpIdx);
840 				try {
841 					final byte[] buf = new byte[2048];
842 					int cnt;
843 					while (!pm.isCancelled() && (cnt = s.in.read(buf)) >= 0) {
844 						fos.write(buf, 0, cnt);
845 						pm.update(cnt / 1024);
846 					}
847 				} finally {
848 					fos.close();
849 				}
850 			} catch (IOException err) {
851 				FileUtils.delete(tmpIdx);
852 				throw err;
853 			} finally {
854 				s.in.close();
855 			}
856 			pm.endTask();
857 
858 			if (pm.isCancelled()) {
859 				FileUtils.delete(tmpIdx);
860 				return;
861 			}
862 
863 			try {
864 				index = PackIndex.open(tmpIdx);
865 			} catch (IOException e) {
866 				FileUtils.delete(tmpIdx);
867 				throw e;
868 			}
869 		}
870 
871 		void downloadPack(final ProgressMonitor monitor) throws IOException {
872 			String name = "pack/" + packName; //$NON-NLS-1$
873 			WalkRemoteObjectDatabase.FileStream s = connection.open(name);
874 			PackParser parser = inserter.newPackParser(s.in);
875 			parser.setAllowThin(false);
876 			parser.setObjectChecker(objCheck);
877 			parser.setLockMessage(lockMessage);
878 			PackLock lock = parser.parse(monitor);
879 			if (lock != null)
880 				packLocks.add(lock);
881 			inserter.flush();
882 		}
883 	}
884 }