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