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 	final Repository local;
119 
120 	/** If not null the validator for received objects. */
121 	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 	String lockMessage;
184 
185 	final List<PackLock> packLocks;
186 
187 	/** Inserter to write objects onto {@link #local}. */
188 	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 = inserter.newReader();
199 
200 		remotes = new ArrayList<>();
201 		remotes.add(w);
202 
203 		unfetchedPacks = new LinkedList<>();
204 		packsConsidered = new HashSet<>();
205 
206 		noPacksYet = new LinkedList<>();
207 		noPacksYet.add(w);
208 
209 		noAlternatesYet = new LinkedList<>();
210 		noAlternatesYet.add(w);
211 
212 		fetchErrors = new HashMap<>();
213 		packLocks = new ArrayList<>(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<>();
224 	}
225 
226 	/** {@inheritDoc} */
227 	@Override
228 	public boolean didFetchTestConnectivity() {
229 		return true;
230 	}
231 
232 	/** {@inheritDoc} */
233 	@Override
234 	protected void doFetch(final ProgressMonitor monitor,
235 			final Collection<Ref> want, final Set<ObjectId> have)
236 			throws TransportException {
237 		markLocalRefsComplete(have);
238 		queueWants(want);
239 
240 		while (!monitor.isCancelled() && !workQueue.isEmpty()) {
241 			final ObjectId id = workQueue.removeFirst();
242 			if (!(id instanceof RevObject) || !((RevObject) id).has(COMPLETE))
243 				downloadObject(monitor, id);
244 			process(id);
245 		}
246 
247 		try {
248 			inserter.flush();
249 		} catch (IOException e) {
250 			throw new TransportException(e.getMessage(), e);
251 		}
252 	}
253 
254 	/** {@inheritDoc} */
255 	@Override
256 	public Collection<PackLock> getPackLocks() {
257 		return packLocks;
258 	}
259 
260 	/** {@inheritDoc} */
261 	@Override
262 	public void setPackLockMessage(final String message) {
263 		lockMessage = message;
264 	}
265 
266 	/** {@inheritDoc} */
267 	@Override
268 	public void close() {
269 		inserter.close();
270 		reader.close();
271 		for (final RemotePack p : unfetchedPacks) {
272 			if (p.tmpIdx != null)
273 				p.tmpIdx.delete();
274 		}
275 		for (final WalkRemoteObjectDatabase r : remotes)
276 			r.close();
277 	}
278 
279 	private void queueWants(final Collection<Ref> want)
280 			throws TransportException {
281 		final HashSet<ObjectId> inWorkQueue = new HashSet<>();
282 		for (final Ref r : want) {
283 			final ObjectId id = r.getObjectId();
284 			if (id == null) {
285 				throw new NullPointerException(MessageFormat.format(
286 						JGitText.get().transportProvidedRefWithNoObjectId, r.getName()));
287 			}
288 			try {
289 				final RevObject obj = revWalk.parseAny(id);
290 				if (obj.has(COMPLETE))
291 					continue;
292 				if (inWorkQueue.add(id)) {
293 					obj.add(IN_WORK_QUEUE);
294 					workQueue.add(obj);
295 				}
296 			} catch (MissingObjectException e) {
297 				if (inWorkQueue.add(id))
298 					workQueue.add(id);
299 			} catch (IOException e) {
300 				throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e);
301 			}
302 		}
303 	}
304 
305 	private void process(final ObjectId id) throws TransportException {
306 		final RevObject obj;
307 		try {
308 			if (id instanceof RevObject) {
309 				obj = (RevObject) id;
310 				if (obj.has(COMPLETE))
311 					return;
312 				revWalk.parseHeaders(obj);
313 			} else {
314 				obj = revWalk.parseAny(id);
315 				if (obj.has(COMPLETE))
316 					return;
317 			}
318 		} catch (IOException e) {
319 			throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e);
320 		}
321 
322 		switch (obj.getType()) {
323 		case Constants.OBJ_BLOB:
324 			processBlob(obj);
325 			break;
326 		case Constants.OBJ_TREE:
327 			processTree(obj);
328 			break;
329 		case Constants.OBJ_COMMIT:
330 			processCommit(obj);
331 			break;
332 		case Constants.OBJ_TAG:
333 			processTag(obj);
334 			break;
335 		default:
336 			throw new TransportException(MessageFormat.format(JGitText.get().unknownObjectType, id.name()));
337 		}
338 
339 		// If we had any prior errors fetching this object they are
340 		// now resolved, as the object was parsed successfully.
341 		//
342 		fetchErrors.remove(id);
343 	}
344 
345 	private void processBlob(final RevObject obj) throws TransportException {
346 		try {
347 			if (reader.has(obj, Constants.OBJ_BLOB))
348 				obj.add(COMPLETE);
349 			else
350 				throw new TransportException(MessageFormat.format(JGitText
351 						.get().cannotReadBlob, obj.name()),
352 						new MissingObjectException(obj, Constants.TYPE_BLOB));
353 		} catch (IOException error) {
354 			throw new TransportException(MessageFormat.format(
355 					JGitText.get().cannotReadBlob, obj.name()), error);
356 		}
357 	}
358 
359 	private void processTree(final RevObject obj) throws TransportException {
360 		try {
361 			treeWalk.reset(obj);
362 			while (treeWalk.next()) {
363 				final FileMode mode = treeWalk.getFileMode(0);
364 				final int sType = mode.getObjectType();
365 
366 				switch (sType) {
367 				case Constants.OBJ_BLOB:
368 				case Constants.OBJ_TREE:
369 					treeWalk.getObjectId(idBuffer, 0);
370 					needs(revWalk.lookupAny(idBuffer, sType));
371 					continue;
372 
373 				default:
374 					if (FileMode.GITLINK.equals(mode))
375 						continue;
376 					treeWalk.getObjectId(idBuffer, 0);
377 					throw new CorruptObjectException(MessageFormat.format(JGitText.get().invalidModeFor
378 							, mode, idBuffer.name(), treeWalk.getPathString(), obj.getId().name()));
379 				}
380 			}
381 		} catch (IOException ioe) {
382 			throw new TransportException(MessageFormat.format(JGitText.get().cannotReadTree, obj.name()), ioe);
383 		}
384 		obj.add(COMPLETE);
385 	}
386 
387 	private void processCommit(final RevObject obj) throws TransportException {
388 		final RevCommit commit = (RevCommit) obj;
389 		markLocalCommitsComplete(commit.getCommitTime());
390 		needs(commit.getTree());
391 		for (final RevCommit p : commit.getParents())
392 			needs(p);
393 		obj.add(COMPLETE);
394 	}
395 
396 	private void processTag(final RevObject obj) {
397 		final RevTag tag = (RevTag) obj;
398 		needs(tag.getObject());
399 		obj.add(COMPLETE);
400 	}
401 
402 	private void needs(final RevObject obj) {
403 		if (obj.has(COMPLETE))
404 			return;
405 		if (!obj.has(IN_WORK_QUEUE)) {
406 			obj.add(IN_WORK_QUEUE);
407 			workQueue.add(obj);
408 		}
409 	}
410 
411 	private void downloadObject(final ProgressMonitor pm, final AnyObjectId id)
412 			throws TransportException {
413 		if (alreadyHave(id))
414 			return;
415 
416 		for (;;) {
417 			// Try a pack file we know about, but don't have yet. Odds are
418 			// that if it has this object, it has others related to it so
419 			// getting the pack is a good bet.
420 			//
421 			if (downloadPackedObject(pm, id))
422 				return;
423 
424 			// Search for a loose object over all alternates, starting
425 			// from the one we last successfully located an object through.
426 			//
427 			final String idStr = id.name();
428 			final String subdir = idStr.substring(0, 2);
429 			final String file = idStr.substring(2);
430 			final String looseName = subdir + "/" + file; //$NON-NLS-1$
431 
432 			for (int i = lastRemoteIdx; i < remotes.size(); i++) {
433 				if (downloadLooseObject(id, looseName, remotes.get(i))) {
434 					lastRemoteIdx = i;
435 					return;
436 				}
437 			}
438 			for (int i = 0; i < lastRemoteIdx; i++) {
439 				if (downloadLooseObject(id, looseName, remotes.get(i))) {
440 					lastRemoteIdx = i;
441 					return;
442 				}
443 			}
444 
445 			// Try to obtain more pack information and search those.
446 			//
447 			while (!noPacksYet.isEmpty()) {
448 				final WalkRemoteObjectDatabase wrr = noPacksYet.removeFirst();
449 				final Collection<String> packNameList;
450 				try {
451 					pm.beginTask(JGitText.get().listingPacks,
452 							ProgressMonitor.UNKNOWN);
453 					packNameList = wrr.getPackNames();
454 				} catch (IOException e) {
455 					// Try another repository.
456 					//
457 					recordError(id, e);
458 					continue;
459 				} finally {
460 					pm.endTask();
461 				}
462 
463 				if (packNameList == null || packNameList.isEmpty())
464 					continue;
465 				for (final String packName : packNameList) {
466 					if (packsConsidered.add(packName))
467 						unfetchedPacks.add(new RemotePack(wrr, packName));
468 				}
469 				if (downloadPackedObject(pm, id))
470 					return;
471 			}
472 
473 			// Try to expand the first alternate we haven't expanded yet.
474 			//
475 			Collection<WalkRemoteObjectDatabase> al = expandOneAlternate(id, pm);
476 			if (al != null && !al.isEmpty()) {
477 				for (final WalkRemoteObjectDatabase alt : al) {
478 					remotes.add(alt);
479 					noPacksYet.add(alt);
480 					noAlternatesYet.add(alt);
481 				}
482 				continue;
483 			}
484 
485 			// We could not obtain the object. There may be reasons why.
486 			//
487 			List<Throwable> failures = fetchErrors.get(id);
488 			final TransportException te;
489 
490 			te = new TransportException(MessageFormat.format(JGitText.get().cannotGet, id.name()));
491 			if (failures != null && !failures.isEmpty()) {
492 				if (failures.size() == 1)
493 					te.initCause(failures.get(0));
494 				else
495 					te.initCause(new CompoundException(failures));
496 			}
497 			throw te;
498 		}
499 	}
500 
501 	private boolean alreadyHave(final AnyObjectId id) throws TransportException {
502 		try {
503 			return reader.has(id);
504 		} catch (IOException error) {
505 			throw new TransportException(MessageFormat.format(
506 					JGitText.get().cannotReadObject, id.name()), error);
507 		}
508 	}
509 
510 	private boolean downloadPackedObject(final ProgressMonitor monitor,
511 			final AnyObjectId id) throws TransportException {
512 		// Search for the object in a remote pack whose index we have,
513 		// but whose pack we do not yet have.
514 		//
515 		final Iterator<RemotePack> packItr = unfetchedPacks.iterator();
516 		while (packItr.hasNext() && !monitor.isCancelled()) {
517 			final RemotePack pack = packItr.next();
518 			try {
519 				pack.openIndex(monitor);
520 			} catch (IOException err) {
521 				// If the index won't open its either not found or
522 				// its a format we don't recognize. In either case
523 				// we may still be able to obtain the object from
524 				// another source, so don't consider it a failure.
525 				//
526 				recordError(id, err);
527 				packItr.remove();
528 				continue;
529 			}
530 
531 			if (monitor.isCancelled()) {
532 				// If we were cancelled while the index was opening
533 				// the open may have aborted. We can't search an
534 				// unopen index.
535 				//
536 				return false;
537 			}
538 
539 			if (!pack.index.hasObject(id)) {
540 				// Not in this pack? Try another.
541 				//
542 				continue;
543 			}
544 
545 			// It should be in the associated pack. Download that
546 			// and attach it to the local repository so we can use
547 			// all of the contained objects.
548 			//
549 			try {
550 				pack.downloadPack(monitor);
551 			} catch (IOException err) {
552 				// If the pack failed to download, index correctly,
553 				// or open in the local repository we may still be
554 				// able to obtain this object from another pack or
555 				// an alternate.
556 				//
557 				recordError(id, err);
558 				continue;
559 			} finally {
560 				// If the pack was good its in the local repository
561 				// and Repository.hasObject(id) will succeed in the
562 				// future, so we do not need this data anymore. If
563 				// it failed the index and pack are unusable and we
564 				// shouldn't consult them again.
565 				//
566 				try {
567 					if (pack.tmpIdx != null)
568 						FileUtils.delete(pack.tmpIdx);
569 				} catch (IOException e) {
570 					throw new TransportException(e.getMessage(), e);
571 				}
572 				packItr.remove();
573 			}
574 
575 			if (!alreadyHave(id)) {
576 				// What the hell? This pack claimed to have
577 				// the object, but after indexing we didn't
578 				// actually find it in the pack.
579 				//
580 				recordError(id, new FileNotFoundException(MessageFormat.format(
581 						JGitText.get().objectNotFoundIn, id.name(), pack.packName)));
582 				continue;
583 			}
584 
585 			// Complete any other objects that we can.
586 			//
587 			final Iterator<ObjectId> pending = swapFetchQueue();
588 			while (pending.hasNext()) {
589 				final ObjectId p = pending.next();
590 				if (pack.index.hasObject(p)) {
591 					pending.remove();
592 					process(p);
593 				} else {
594 					workQueue.add(p);
595 				}
596 			}
597 			return true;
598 
599 		}
600 		return false;
601 	}
602 
603 	private Iterator<ObjectId> swapFetchQueue() {
604 		final Iterator<ObjectId> r = workQueue.iterator();
605 		workQueue = new LinkedList<>();
606 		return r;
607 	}
608 
609 	private boolean downloadLooseObject(final AnyObjectId id,
610 			final String looseName, final WalkRemoteObjectDatabase remote)
611 			throws TransportException {
612 		try {
613 			final byte[] compressed = remote.open(looseName).toArray();
614 			verifyAndInsertLooseObject(id, compressed);
615 			return true;
616 		} catch (FileNotFoundException e) {
617 			// Not available in a loose format from this alternate?
618 			// Try another strategy to get the object.
619 			//
620 			recordError(id, e);
621 			return false;
622 		} catch (IOException e) {
623 			throw new TransportException(MessageFormat.format(JGitText.get().cannotDownload, id.name()), e);
624 		}
625 	}
626 
627 	private void verifyAndInsertLooseObject(final AnyObjectId id,
628 			final byte[] compressed) throws IOException {
629 		final ObjectLoader uol;
630 		try {
631 			uol = UnpackedObject.parse(compressed, id);
632 		} catch (CorruptObjectException parsingError) {
633 			// Some HTTP servers send back a "200 OK" status with an HTML
634 			// page that explains the requested file could not be found.
635 			// These servers are most certainly misconfigured, but many
636 			// of them exist in the world, and many of those are hosting
637 			// Git repositories.
638 			//
639 			// Since an HTML page is unlikely to hash to one of our loose
640 			// objects we treat this condition as a FileNotFoundException
641 			// and attempt to recover by getting the object from another
642 			// source.
643 			//
644 			final FileNotFoundException e;
645 			e = new FileNotFoundException(id.name());
646 			e.initCause(parsingError);
647 			throw e;
648 		}
649 
650 		final int type = uol.getType();
651 		final byte[] raw = uol.getCachedBytes();
652 		if (objCheck != null) {
653 			try {
654 				objCheck.check(id, type, raw);
655 			} catch (CorruptObjectException e) {
656 				throw new TransportException(MessageFormat.format(
657 						JGitText.get().transportExceptionInvalid,
658 						Constants.typeString(type), id.name(), e.getMessage()));
659 			}
660 		}
661 
662 		ObjectId act = inserter.insert(type, raw);
663 		if (!AnyObjectId.equals(id, act)) {
664 			throw new TransportException(MessageFormat.format(
665 					JGitText.get().incorrectHashFor, id.name(), act.name(),
666 					Constants.typeString(type),
667 					Integer.valueOf(compressed.length)));
668 		}
669 	}
670 
671 	private Collection<WalkRemoteObjectDatabase> expandOneAlternate(
672 			final AnyObjectId id, final ProgressMonitor pm) {
673 		while (!noAlternatesYet.isEmpty()) {
674 			final WalkRemoteObjectDatabase wrr = noAlternatesYet.removeFirst();
675 			try {
676 				pm.beginTask(JGitText.get().listingAlternates, ProgressMonitor.UNKNOWN);
677 				Collection<WalkRemoteObjectDatabase> altList = wrr
678 						.getAlternates();
679 				if (altList != null && !altList.isEmpty())
680 					return altList;
681 			} catch (IOException e) {
682 				// Try another repository.
683 				//
684 				recordError(id, e);
685 			} finally {
686 				pm.endTask();
687 			}
688 		}
689 		return null;
690 	}
691 
692 	private void markLocalRefsComplete(final Set<ObjectId> have) throws TransportException {
693 		Map<String, Ref> refs;
694 		try {
695 			refs = local.getRefDatabase().getRefs(ALL);
696 		} catch (IOException e) {
697 			throw new TransportException(e.getMessage(), e);
698 		}
699 		for (final Ref r : refs.values()) {
700 			try {
701 				markLocalObjComplete(revWalk.parseAny(r.getObjectId()));
702 			} catch (IOException readError) {
703 				throw new TransportException(MessageFormat.format(JGitText.get().localRefIsMissingObjects, r.getName()), readError);
704 			}
705 		}
706 		for (final ObjectId id : have) {
707 			try {
708 				markLocalObjComplete(revWalk.parseAny(id));
709 			} catch (IOException readError) {
710 				throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionMissingAssumed, id.name()), readError);
711 			}
712 		}
713 	}
714 
715 	private void markLocalObjComplete(RevObject obj) throws IOException {
716 		while (obj.getType() == Constants.OBJ_TAG) {
717 			obj.add(COMPLETE);
718 			obj = ((RevTag) obj).getObject();
719 			revWalk.parseHeaders(obj);
720 		}
721 
722 		switch (obj.getType()) {
723 		case Constants.OBJ_BLOB:
724 			obj.add(COMPLETE);
725 			break;
726 		case Constants.OBJ_COMMIT:
727 			pushLocalCommit((RevCommit) obj);
728 			break;
729 		case Constants.OBJ_TREE:
730 			markTreeComplete((RevTree) obj);
731 			break;
732 		}
733 	}
734 
735 	private void markLocalCommitsComplete(final int until)
736 			throws TransportException {
737 		try {
738 			for (;;) {
739 				final RevCommit c = localCommitQueue.peek();
740 				if (c == null || c.getCommitTime() < until)
741 					return;
742 				localCommitQueue.next();
743 
744 				markTreeComplete(c.getTree());
745 				for (final RevCommit p : c.getParents())
746 					pushLocalCommit(p);
747 			}
748 		} catch (IOException err) {
749 			throw new TransportException(JGitText.get().localObjectsIncomplete, err);
750 		}
751 	}
752 
753 	private void pushLocalCommit(final RevCommit p)
754 			throws MissingObjectException, IOException {
755 		if (p.has(LOCALLY_SEEN))
756 			return;
757 		revWalk.parseHeaders(p);
758 		p.add(LOCALLY_SEEN);
759 		p.add(COMPLETE);
760 		p.carry(COMPLETE);
761 		localCommitQueue.add(p);
762 	}
763 
764 	private void markTreeComplete(final RevTree tree) throws IOException {
765 		if (tree.has(COMPLETE))
766 			return;
767 		tree.add(COMPLETE);
768 		treeWalk.reset(tree);
769 		while (treeWalk.next()) {
770 			final FileMode mode = treeWalk.getFileMode(0);
771 			final int sType = mode.getObjectType();
772 
773 			switch (sType) {
774 			case Constants.OBJ_BLOB:
775 				treeWalk.getObjectId(idBuffer, 0);
776 				revWalk.lookupAny(idBuffer, sType).add(COMPLETE);
777 				continue;
778 
779 			case Constants.OBJ_TREE: {
780 				treeWalk.getObjectId(idBuffer, 0);
781 				final RevObject o = revWalk.lookupAny(idBuffer, sType);
782 				if (!o.has(COMPLETE)) {
783 					o.add(COMPLETE);
784 					treeWalk.enterSubtree();
785 				}
786 				continue;
787 			}
788 			default:
789 				if (FileMode.GITLINK.equals(mode))
790 					continue;
791 				treeWalk.getObjectId(idBuffer, 0);
792 				throw new CorruptObjectException(MessageFormat.format(JGitText.get().corruptObjectInvalidMode3
793 						, mode, idBuffer.name(), treeWalk.getPathString(), tree.name()));
794 			}
795 		}
796 	}
797 
798 	private void recordError(final AnyObjectId id, final Throwable what) {
799 		final ObjectId objId = id.copy();
800 		List<Throwable> errors = fetchErrors.get(objId);
801 		if (errors == null) {
802 			errors = new ArrayList<>(2);
803 			fetchErrors.put(objId, errors);
804 		}
805 		errors.add(what);
806 	}
807 
808 	private class RemotePack {
809 		final WalkRemoteObjectDatabase connection;
810 
811 		final String packName;
812 
813 		final String idxName;
814 
815 		File tmpIdx;
816 
817 		PackIndex index;
818 
819 		RemotePack(final WalkRemoteObjectDatabase c, final String pn) {
820 			connection = c;
821 			packName = pn;
822 			idxName = packName.substring(0, packName.length() - 5) + ".idx"; //$NON-NLS-1$
823 
824 			String tn = idxName;
825 			if (tn.startsWith("pack-")) //$NON-NLS-1$
826 				tn = tn.substring(5);
827 			if (tn.endsWith(".idx")) //$NON-NLS-1$
828 				tn = tn.substring(0, tn.length() - 4);
829 
830 			if (local.getObjectDatabase() instanceof ObjectDirectory) {
831 				tmpIdx = new File(((ObjectDirectory) local.getObjectDatabase())
832 								.getDirectory(),
833 						"walk-" + tn + ".walkidx"); //$NON-NLS-1$ //$NON-NLS-2$
834 			}
835 		}
836 
837 		void openIndex(final ProgressMonitor pm) throws IOException {
838 			if (index != null)
839 				return;
840 			if (tmpIdx == null)
841 				tmpIdx = File.createTempFile("jgit-walk-", ".idx"); //$NON-NLS-1$ //$NON-NLS-2$
842 			else if (tmpIdx.isFile()) {
843 				try {
844 					index = PackIndex.open(tmpIdx);
845 					return;
846 				} catch (FileNotFoundException err) {
847 					// Fall through and get the file.
848 				}
849 			}
850 
851 			final WalkRemoteObjectDatabase.FileStream s;
852 			s = connection.open("pack/" + idxName); //$NON-NLS-1$
853 			pm.beginTask("Get " + idxName.substring(0, 12) + "..idx", //$NON-NLS-1$ //$NON-NLS-2$
854 					s.length < 0 ? ProgressMonitor.UNKNOWN
855 							: (int) (s.length / 1024));
856 			try {
857 				final FileOutputStream fos = new FileOutputStream(tmpIdx);
858 				try {
859 					final byte[] buf = new byte[2048];
860 					int cnt;
861 					while (!pm.isCancelled() && (cnt = s.in.read(buf)) >= 0) {
862 						fos.write(buf, 0, cnt);
863 						pm.update(cnt / 1024);
864 					}
865 				} finally {
866 					fos.close();
867 				}
868 			} catch (IOException err) {
869 				FileUtils.delete(tmpIdx);
870 				throw err;
871 			} finally {
872 				s.in.close();
873 			}
874 			pm.endTask();
875 
876 			if (pm.isCancelled()) {
877 				FileUtils.delete(tmpIdx);
878 				return;
879 			}
880 
881 			try {
882 				index = PackIndex.open(tmpIdx);
883 			} catch (IOException e) {
884 				FileUtils.delete(tmpIdx);
885 				throw e;
886 			}
887 		}
888 
889 		void downloadPack(final ProgressMonitor monitor) throws IOException {
890 			String name = "pack/" + packName; //$NON-NLS-1$
891 			WalkRemoteObjectDatabase.FileStream s = connection.open(name);
892 			try {
893 				PackParser parser = inserter.newPackParser(s.in);
894 				parser.setAllowThin(false);
895 				parser.setObjectChecker(objCheck);
896 				parser.setLockMessage(lockMessage);
897 				PackLock lock = parser.parse(monitor);
898 				if (lock != null)
899 					packLocks.add(lock);
900 			} finally {
901 				s.in.close();
902 			}
903 		}
904 	}
905 }