Fix "fetch pulled too many objects" when auto-following tags
[egit/qmx.git] / org.spearce.jgit / src / org / spearce / jgit / transport / WalkFetchConnection.java
blob91c5ea8e433b386fbb011fb10e94f42e7011598a
1 /*
2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
9 * conditions are met:
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
22 * written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org.spearce.jgit.transport;
41 import java.io.File;
42 import java.io.FileNotFoundException;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.Iterator;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Set;
54 import org.spearce.jgit.errors.CompoundException;
55 import org.spearce.jgit.errors.CorruptObjectException;
56 import org.spearce.jgit.errors.MissingObjectException;
57 import org.spearce.jgit.errors.ObjectWritingException;
58 import org.spearce.jgit.errors.TransportException;
59 import org.spearce.jgit.lib.AnyObjectId;
60 import org.spearce.jgit.lib.Constants;
61 import org.spearce.jgit.lib.FileMode;
62 import org.spearce.jgit.lib.ObjectChecker;
63 import org.spearce.jgit.lib.ObjectId;
64 import org.spearce.jgit.lib.PackIndex;
65 import org.spearce.jgit.lib.ProgressMonitor;
66 import org.spearce.jgit.lib.Ref;
67 import org.spearce.jgit.lib.Repository;
68 import org.spearce.jgit.lib.UnpackedObjectLoader;
69 import org.spearce.jgit.revwalk.DateRevQueue;
70 import org.spearce.jgit.revwalk.RevCommit;
71 import org.spearce.jgit.revwalk.RevFlag;
72 import org.spearce.jgit.revwalk.RevObject;
73 import org.spearce.jgit.revwalk.RevTag;
74 import org.spearce.jgit.revwalk.RevTree;
75 import org.spearce.jgit.revwalk.RevWalk;
76 import org.spearce.jgit.treewalk.TreeWalk;
78 /**
79 * Generic fetch support for dumb transport protocols.
80 * <p>
81 * Since there are no Git-specific smarts on the remote side of the connection
82 * the client side must determine which objects it needs to copy in order to
83 * completely fetch the requested refs and their history. The generic walk
84 * support in this class parses each individual object (once it has been copied
85 * to the local repository) and examines the list of objects that must also be
86 * copied to create a complete history. Objects which are already available
87 * locally are retained (and not copied), saving bandwidth for incremental
88 * fetches. Pack files are copied from the remote repository only as a last
89 * resort, as the entire pack must be copied locally in order to access any
90 * single object.
91 * <p>
92 * This fetch connection does not actually perform the object data transfer.
93 * Instead it delegates the transfer to a {@link WalkRemoteObjectDatabase},
94 * which knows how to read individual files from the remote repository and
95 * supply the data as a standard Java InputStream.
97 * @see WalkRemoteObjectDatabase
99 class WalkFetchConnection extends BaseFetchConnection {
100 /** The repository this transport fetches into, or pushes out of. */
101 private final Repository local;
103 /** If not null the validator for received objects. */
104 private final ObjectChecker objCheck;
107 * List of all remote repositories we may need to get objects out of.
108 * <p>
109 * The first repository in the list is the one we were asked to fetch from;
110 * the remaining repositories point to the alternate locations we can fetch
111 * objects through.
113 private final List<WalkRemoteObjectDatabase> remotes;
115 /** Most recently used item in {@link #remotes}. */
116 private int lastRemoteIdx;
118 private final RevWalk revWalk;
120 private final TreeWalk treeWalk;
122 /** Objects whose direct dependents we know we have (or will have). */
123 private final RevFlag COMPLETE;
125 /** Objects that have already entered {@link #workQueue}. */
126 private final RevFlag IN_WORK_QUEUE;
128 /** Commits that have already entered {@link #localCommitQueue}. */
129 private final RevFlag LOCALLY_SEEN;
131 /** Commits already reachable from all local refs. */
132 private final DateRevQueue localCommitQueue;
134 /** Objects we need to copy from the remote repository. */
135 private LinkedList<ObjectId> workQueue;
137 /** Databases we have not yet obtained the list of packs from. */
138 private final LinkedList<WalkRemoteObjectDatabase> noPacksYet;
140 /** Databases we have not yet obtained the alternates from. */
141 private final LinkedList<WalkRemoteObjectDatabase> noAlternatesYet;
143 /** Packs we have discovered, but have not yet fetched locally. */
144 private final LinkedList<RemotePack> unfetchedPacks;
147 * Packs whose indexes we have looked at in {@link #unfetchedPacks}.
148 * <p>
149 * We try to avoid getting duplicate copies of the same pack through
150 * multiple alternates by only looking at packs whose names are not yet in
151 * this collection.
153 private final Set<String> packsConsidered;
156 * Errors received while trying to obtain an object.
157 * <p>
158 * If the fetch winds up failing because we cannot locate a specific object
159 * then we need to report all errors related to that object back to the
160 * caller as there may be cascading failures.
162 private final HashMap<ObjectId, List<Throwable>> fetchErrors;
164 WalkFetchConnection(final WalkTransport wt, final WalkRemoteObjectDatabase w) {
165 local = wt.local;
166 objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null;
168 remotes = new ArrayList<WalkRemoteObjectDatabase>();
169 remotes.add(w);
171 unfetchedPacks = new LinkedList<RemotePack>();
172 packsConsidered = new HashSet<String>();
174 noPacksYet = new LinkedList<WalkRemoteObjectDatabase>();
175 noPacksYet.add(w);
177 noAlternatesYet = new LinkedList<WalkRemoteObjectDatabase>();
178 noAlternatesYet.add(w);
180 fetchErrors = new HashMap<ObjectId, List<Throwable>>();
182 revWalk = new RevWalk(local);
183 treeWalk = new TreeWalk(local);
184 COMPLETE = revWalk.newFlag("COMPLETE");
185 IN_WORK_QUEUE = revWalk.newFlag("IN_WORK_QUEUE");
186 LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN");
188 localCommitQueue = new DateRevQueue();
189 workQueue = new LinkedList<ObjectId>();
192 public boolean didFetchTestConnectivity() {
193 return true;
196 @Override
197 protected void doFetch(final ProgressMonitor monitor,
198 final Collection<Ref> want, final Set<ObjectId> have)
199 throws TransportException {
200 markLocalRefsComplete(have);
201 queueWants(want);
203 while (!monitor.isCancelled() && !workQueue.isEmpty()) {
204 final ObjectId id = workQueue.removeFirst();
205 if (!(id instanceof RevObject) || !((RevObject) id).has(COMPLETE))
206 downloadObject(monitor, id);
207 process(id);
211 @Override
212 public void close() {
213 for (final RemotePack p : unfetchedPacks)
214 p.tmpIdx.delete();
215 for (final WalkRemoteObjectDatabase r : remotes)
216 r.close();
219 private void queueWants(final Collection<Ref> want)
220 throws TransportException {
221 final HashSet<ObjectId> inWorkQueue = new HashSet<ObjectId>();
222 for (final Ref r : want) {
223 final ObjectId id = r.getObjectId();
224 try {
225 final RevObject obj = revWalk.parseAny(id);
226 if (obj.has(COMPLETE))
227 continue;
228 if (inWorkQueue.add(id)) {
229 obj.add(IN_WORK_QUEUE);
230 workQueue.add(obj);
232 } catch (MissingObjectException e) {
233 if (inWorkQueue.add(id))
234 workQueue.add(id);
235 } catch (IOException e) {
236 throw new TransportException("Cannot read " + id.name(), e);
241 private void process(final ObjectId id) throws TransportException {
242 final RevObject obj;
243 try {
244 if (id instanceof RevObject) {
245 obj = (RevObject) id;
246 if (obj.has(COMPLETE))
247 return;
248 revWalk.parse(obj);
249 } else {
250 obj = revWalk.parseAny(id);
251 if (obj.has(COMPLETE))
252 return;
254 } catch (IOException e) {
255 throw new TransportException("Cannot read " + id.name(), e);
258 // We only care about traversal; disposing an object throws its
259 // message buffer (if any) away but retains the links so we can
260 // continue to navigate, but use less memory long-term.
262 obj.dispose();
264 switch (obj.getType()) {
265 case Constants.OBJ_BLOB:
266 processBlob(obj);
267 break;
268 case Constants.OBJ_TREE:
269 processTree(obj);
270 break;
271 case Constants.OBJ_COMMIT:
272 processCommit(obj);
273 break;
274 case Constants.OBJ_TAG:
275 processTag(obj);
276 break;
277 default:
278 throw new TransportException("Unknown object type " + id.name());
281 // If we had any prior errors fetching this object they are
282 // now resolved, as the object was parsed successfully.
284 fetchErrors.remove(id.copy());
287 private void processBlob(final RevObject obj) throws TransportException {
288 if (!local.hasObject(obj))
289 throw new TransportException("Cannot read blob " + obj.name(),
290 new MissingObjectException(obj, Constants.TYPE_BLOB));
291 obj.add(COMPLETE);
294 private void processTree(final RevObject obj) throws TransportException {
295 try {
296 treeWalk.reset(new ObjectId[] { obj });
297 while (treeWalk.next()) {
298 final FileMode mode = treeWalk.getFileMode(0);
299 final int sType = mode.getObjectType();
301 switch (sType) {
302 case Constants.OBJ_BLOB:
303 case Constants.OBJ_TREE: {
304 final ObjectId sId = treeWalk.getObjectId(0);
305 needs(revWalk.lookupAny(sId, sType));
306 continue;
308 default:
309 if (FileMode.GITLINK.equals(mode))
310 continue;
311 throw new CorruptObjectException("Invalid mode " + mode
312 + " for " + treeWalk.getObjectId(0).name() + " "
313 + treeWalk.getPathString() + " in "
314 + obj.getId().name() + ".");
317 } catch (IOException ioe) {
318 throw new TransportException("Cannot read tree " + obj.name(), ioe);
320 obj.add(COMPLETE);
323 private void processCommit(final RevObject obj) throws TransportException {
324 final RevCommit commit = (RevCommit) obj;
325 markLocalCommitsComplete(commit.getCommitTime());
326 needs(commit.getTree());
327 for (final RevCommit p : commit.getParents())
328 needs(p);
329 obj.add(COMPLETE);
332 private void processTag(final RevObject obj) {
333 final RevTag tag = (RevTag) obj;
334 needs(tag.getObject());
335 obj.add(COMPLETE);
338 private void needs(final RevObject obj) {
339 if (obj.has(COMPLETE))
340 return;
341 if (!obj.has(IN_WORK_QUEUE)) {
342 obj.add(IN_WORK_QUEUE);
343 workQueue.add(obj);
347 private void downloadObject(final ProgressMonitor pm, final AnyObjectId id)
348 throws TransportException {
349 if (local.hasObject(id))
350 return;
352 for (;;) {
353 // Try a pack file we know about, but don't have yet. Odds are
354 // that if it has this object, it has others related to it so
355 // getting the pack is a good bet.
357 if (downloadPackedObject(pm, id))
358 return;
360 // Search for a loose object over all alternates, starting
361 // from the one we last successfully located an object through.
363 final String idStr = id.name();
364 final String subdir = idStr.substring(0, 2);
365 final String file = idStr.substring(2);
366 final String looseName = subdir + "/" + file;
368 for (int i = lastRemoteIdx; i < remotes.size(); i++) {
369 if (downloadLooseObject(id, looseName, remotes.get(i))) {
370 lastRemoteIdx = i;
371 return;
374 for (int i = 0; i < lastRemoteIdx; i++) {
375 if (downloadLooseObject(id, looseName, remotes.get(i))) {
376 lastRemoteIdx = i;
377 return;
381 // Try to obtain more pack information and search those.
383 while (!noPacksYet.isEmpty()) {
384 final WalkRemoteObjectDatabase wrr = noPacksYet.removeFirst();
385 final Collection<String> packNameList;
386 try {
387 pm.beginTask("Listing packs", ProgressMonitor.UNKNOWN);
388 packNameList = wrr.getPackNames();
389 } catch (IOException e) {
390 // Try another repository.
392 recordError(id, e);
393 continue;
394 } finally {
395 pm.endTask();
398 if (packNameList == null || packNameList.isEmpty())
399 continue;
400 for (final String packName : packNameList) {
401 if (packsConsidered.add(packName))
402 unfetchedPacks.add(new RemotePack(wrr, packName));
404 if (downloadPackedObject(pm, id))
405 return;
408 // Try to expand the first alternate we haven't expanded yet.
410 Collection<WalkRemoteObjectDatabase> al = expandOneAlternate(id, pm);
411 if (al != null && !al.isEmpty()) {
412 for (final WalkRemoteObjectDatabase alt : al) {
413 remotes.add(alt);
414 noPacksYet.add(alt);
415 noAlternatesYet.add(alt);
417 continue;
420 // We could not obtain the object. There may be reasons why.
422 List<Throwable> failures = fetchErrors.get(id.copy());
423 final TransportException te;
425 te = new TransportException("Cannot get " + id.name() + ".");
426 if (failures != null && !failures.isEmpty()) {
427 if (failures.size() == 1)
428 te.initCause(failures.get(0));
429 else
430 te.initCause(new CompoundException(failures));
432 throw te;
436 private boolean downloadPackedObject(final ProgressMonitor monitor,
437 final AnyObjectId id) throws TransportException {
438 // Search for the object in a remote pack whose index we have,
439 // but whose pack we do not yet have.
441 final Iterator<RemotePack> packItr = unfetchedPacks.iterator();
442 while (packItr.hasNext() && !monitor.isCancelled()) {
443 final RemotePack pack = packItr.next();
444 try {
445 pack.openIndex(monitor);
446 } catch (IOException err) {
447 // If the index won't open its either not found or
448 // its a format we don't recognize. In either case
449 // we may still be able to obtain the object from
450 // another source, so don't consider it a failure.
452 recordError(id, err);
453 packItr.remove();
454 continue;
457 if (monitor.isCancelled()) {
458 // If we were cancelled while the index was opening
459 // the open may have aborted. We can't search an
460 // unopen index.
462 return false;
465 if (!pack.index.hasObject(id)) {
466 // Not in this pack? Try another.
468 continue;
471 // It should be in the associated pack. Download that
472 // and attach it to the local repository so we can use
473 // all of the contained objects.
475 try {
476 pack.downloadPack(monitor);
477 } catch (IOException err) {
478 // If the pack failed to download, index correctly,
479 // or open in the local repository we may still be
480 // able to obtain this object from another pack or
481 // an alternate.
483 recordError(id, err);
484 continue;
485 } finally {
486 // If the pack was good its in the local repository
487 // and Repository.hasObject(id) will succeed in the
488 // future, so we do not need this data anymore. If
489 // it failed the index and pack are unusable and we
490 // shouldn't consult them again.
492 pack.tmpIdx.delete();
493 packItr.remove();
496 if (!local.hasObject(id)) {
497 // What the hell? This pack claimed to have
498 // the object, but after indexing we didn't
499 // actually find it in the pack.
501 recordError(id, new FileNotFoundException("Object " + id.name()
502 + " not found in " + pack.packName + "."));
503 continue;
506 // Complete any other objects that we can.
508 final Iterator<ObjectId> pending = swapFetchQueue();
509 while (pending.hasNext()) {
510 final ObjectId p = pending.next();
511 if (pack.index.hasObject(p)) {
512 pending.remove();
513 process(p);
514 } else {
515 workQueue.add(p);
518 return true;
521 return false;
524 private Iterator<ObjectId> swapFetchQueue() {
525 final Iterator<ObjectId> r = workQueue.iterator();
526 workQueue = new LinkedList<ObjectId>();
527 return r;
530 private boolean downloadLooseObject(final AnyObjectId id,
531 final String looseName, final WalkRemoteObjectDatabase remote)
532 throws TransportException {
533 try {
534 final byte[] compressed = remote.open(looseName).toArray();
535 verifyLooseObject(id, compressed);
536 saveLooseObject(id, compressed);
537 return true;
538 } catch (FileNotFoundException e) {
539 // Not available in a loose format from this alternate?
540 // Try another strategy to get the object.
542 recordError(id, e);
543 return false;
544 } catch (IOException e) {
545 throw new TransportException("Cannot download " + id.name(), e);
549 private void verifyLooseObject(final AnyObjectId id, final byte[] compressed)
550 throws IOException {
551 final UnpackedObjectLoader uol;
552 try {
553 uol = new UnpackedObjectLoader(compressed);
554 } catch (CorruptObjectException parsingError) {
555 // Some HTTP servers send back a "200 OK" status with an HTML
556 // page that explains the requested file could not be found.
557 // These servers are most certainly misconfigured, but many
558 // of them exist in the world, and many of those are hosting
559 // Git repositories.
561 // Since an HTML page is unlikely to hash to one of our loose
562 // objects we treat this condition as a FileNotFoundException
563 // and attempt to recover by getting the object from another
564 // source.
566 final FileNotFoundException e;
567 e = new FileNotFoundException(id.name());
568 e.initCause(parsingError);
569 throw e;
572 if (!AnyObjectId.equals(id, uol.getId())) {
573 throw new TransportException("Incorrect hash for " + id.name()
574 + "; computed " + uol.getId().name() + " as a "
575 + Constants.encodedTypeString(uol.getType()) + " from "
576 + compressed.length + " bytes.");
578 if (objCheck != null) {
579 try {
580 objCheck.check(uol.getType(), uol.getCachedBytes());
581 } catch (CorruptObjectException e) {
582 throw new TransportException("Invalid "
583 + Constants.encodedTypeString(uol.getType()) + " "
584 + id.name() + ":" + e.getMessage());
589 private void saveLooseObject(final AnyObjectId id, final byte[] compressed)
590 throws IOException, ObjectWritingException {
591 final File tmp;
593 tmp = File.createTempFile("noz", null, local.getObjectsDirectory());
594 try {
595 final FileOutputStream out = new FileOutputStream(tmp);
596 try {
597 out.write(compressed);
598 } finally {
599 out.close();
601 tmp.setReadOnly();
602 } catch (IOException e) {
603 tmp.delete();
604 throw e;
607 final File o = local.toFile(id);
608 if (tmp.renameTo(o))
609 return;
611 // Maybe the directory doesn't exist yet as the object
612 // directories are always lazily created. Note that we
613 // try the rename first as the directory likely does exist.
615 o.getParentFile().mkdir();
616 if (tmp.renameTo(o))
617 return;
619 tmp.delete();
620 if (local.hasObject(id))
621 return;
622 throw new ObjectWritingException("Unable to store " + id.name() + ".");
625 private Collection<WalkRemoteObjectDatabase> expandOneAlternate(
626 final AnyObjectId id, final ProgressMonitor pm) {
627 while (!noAlternatesYet.isEmpty()) {
628 final WalkRemoteObjectDatabase wrr = noAlternatesYet.removeFirst();
629 try {
630 pm.beginTask("Listing alternates", ProgressMonitor.UNKNOWN);
631 Collection<WalkRemoteObjectDatabase> altList = wrr
632 .getAlternates();
633 if (altList != null && !altList.isEmpty())
634 return altList;
635 } catch (IOException e) {
636 // Try another repository.
638 recordError(id, e);
639 } finally {
640 pm.endTask();
643 return null;
646 private void markLocalRefsComplete(final Set<ObjectId> have) throws TransportException {
647 for (final Ref r : local.getAllRefs().values()) {
648 try {
649 markLocalObjComplete(revWalk.parseAny(r.getObjectId()));
650 } catch (IOException readError) {
651 throw new TransportException("Local ref " + r.getName()
652 + " is missing object(s).", readError);
655 for (final ObjectId id : have) {
656 try {
657 markLocalObjComplete(revWalk.parseAny(id));
658 } catch (IOException readError) {
659 throw new TransportException("Missing assumed "+id.name(), readError);
664 private void markLocalObjComplete(RevObject obj) throws IOException {
665 while (obj.getType() == Constants.OBJ_TAG) {
666 obj.add(COMPLETE);
667 obj.dispose();
668 obj = ((RevTag) obj).getObject();
669 revWalk.parse(obj);
672 switch (obj.getType()) {
673 case Constants.OBJ_BLOB:
674 obj.add(COMPLETE);
675 break;
676 case Constants.OBJ_COMMIT:
677 pushLocalCommit((RevCommit) obj);
678 break;
679 case Constants.OBJ_TREE:
680 markTreeComplete((RevTree) obj);
681 break;
685 private void markLocalCommitsComplete(final int until)
686 throws TransportException {
687 try {
688 for (;;) {
689 final RevCommit c = localCommitQueue.peek();
690 if (c == null || c.getCommitTime() < until)
691 return;
692 localCommitQueue.next();
694 markTreeComplete(c.getTree());
695 for (final RevCommit p : c.getParents())
696 pushLocalCommit(p);
698 } catch (IOException err) {
699 throw new TransportException("Local objects incomplete.", err);
703 private void pushLocalCommit(final RevCommit p)
704 throws MissingObjectException, IOException {
705 if (p.has(LOCALLY_SEEN))
706 return;
707 revWalk.parse(p);
708 p.add(LOCALLY_SEEN);
709 p.add(COMPLETE);
710 p.carry(COMPLETE);
711 p.dispose();
712 localCommitQueue.add(p);
715 private void markTreeComplete(final RevTree tree) throws IOException {
716 if (tree.has(COMPLETE))
717 return;
718 tree.add(COMPLETE);
719 treeWalk.reset(new ObjectId[] { tree });
720 while (treeWalk.next()) {
721 final FileMode mode = treeWalk.getFileMode(0);
722 final int sType = mode.getObjectType();
724 switch (sType) {
725 case Constants.OBJ_BLOB: {
726 final ObjectId sid = treeWalk.getObjectId(0);
727 revWalk.lookupAny(sid, sType).add(COMPLETE);
728 continue;
730 case Constants.OBJ_TREE: {
731 final ObjectId sid = treeWalk.getObjectId(0);
732 final RevObject o = revWalk.lookupAny(sid, sType);
733 if (!o.has(COMPLETE)) {
734 o.add(COMPLETE);
735 treeWalk.enterSubtree();
737 continue;
739 default:
740 if (FileMode.GITLINK.equals(mode))
741 continue;
742 throw new CorruptObjectException("Invalid mode " + mode
743 + " for " + treeWalk.getObjectId(0).name() + " "
744 + treeWalk.getPathString() + " in " + tree.name() + ".");
749 private void recordError(final AnyObjectId id, final Throwable what) {
750 final ObjectId objId = id.copy();
751 List<Throwable> errors = fetchErrors.get(objId);
752 if (errors == null) {
753 errors = new ArrayList<Throwable>(2);
754 fetchErrors.put(objId, errors);
756 errors.add(what);
759 private class RemotePack {
760 final WalkRemoteObjectDatabase connection;
762 final String packName;
764 final String idxName;
766 final File tmpIdx;
768 PackIndex index;
770 RemotePack(final WalkRemoteObjectDatabase c, final String pn) {
771 final File objdir = local.getObjectsDirectory();
772 connection = c;
773 packName = pn;
774 idxName = packName.substring(0, packName.length() - 5) + ".idx";
776 String tn = idxName;
777 if (tn.startsWith("pack-"))
778 tn = tn.substring(5);
779 if (tn.endsWith(".idx"))
780 tn = tn.substring(0, tn.length() - 4);
781 tmpIdx = new File(objdir, "walk-" + tn + ".walkidx");
784 void openIndex(final ProgressMonitor pm) throws IOException {
785 if (index != null)
786 return;
787 if (tmpIdx.isFile()) {
788 try {
789 index = PackIndex.open(tmpIdx);
790 return;
791 } catch (FileNotFoundException err) {
792 // Fall through and get the file.
796 final WalkRemoteObjectDatabase.FileStream s;
797 s = connection.open("pack/" + idxName);
798 pm.beginTask("Get " + idxName.substring(0, 12) + "..idx",
799 s.length < 0 ? ProgressMonitor.UNKNOWN
800 : (int) (s.length / 1024));
801 try {
802 final FileOutputStream fos = new FileOutputStream(tmpIdx);
803 try {
804 final byte[] buf = new byte[2048];
805 int cnt;
806 while (!pm.isCancelled() && (cnt = s.in.read(buf)) >= 0) {
807 fos.write(buf, 0, cnt);
808 pm.update(cnt / 1024);
810 } finally {
811 fos.close();
813 } catch (IOException err) {
814 tmpIdx.delete();
815 throw err;
816 } finally {
817 s.in.close();
819 pm.endTask();
821 if (pm.isCancelled()) {
822 tmpIdx.delete();
823 return;
826 try {
827 index = PackIndex.open(tmpIdx);
828 } catch (IOException e) {
829 tmpIdx.delete();
830 throw e;
834 void downloadPack(final ProgressMonitor monitor) throws IOException {
835 final WalkRemoteObjectDatabase.FileStream s;
836 final IndexPack ip;
838 s = connection.open("pack/" + packName);
839 ip = IndexPack.create(local, s.in);
840 ip.setFixThin(false);
841 ip.setObjectChecker(objCheck);
842 ip.index(monitor);
843 ip.renameAndOpenPack();