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