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