WalkFetchConnection.java

  1. /*
  2.  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  3.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.transport;

  12. import java.io.File;
  13. import java.io.FileNotFoundException;
  14. import java.io.FileOutputStream;
  15. import java.io.IOException;
  16. import java.text.MessageFormat;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.HashMap;
  20. import java.util.HashSet;
  21. import java.util.Iterator;
  22. import java.util.LinkedList;
  23. import java.util.List;
  24. import java.util.Set;

  25. import org.eclipse.jgit.errors.CompoundException;
  26. import org.eclipse.jgit.errors.CorruptObjectException;
  27. import org.eclipse.jgit.errors.MissingObjectException;
  28. import org.eclipse.jgit.errors.TransportException;
  29. import org.eclipse.jgit.internal.JGitText;
  30. import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
  31. import org.eclipse.jgit.internal.storage.file.PackIndex;
  32. import org.eclipse.jgit.internal.storage.file.PackLock;
  33. import org.eclipse.jgit.internal.storage.file.UnpackedObject;
  34. import org.eclipse.jgit.lib.AnyObjectId;
  35. import org.eclipse.jgit.lib.Constants;
  36. import org.eclipse.jgit.lib.FileMode;
  37. import org.eclipse.jgit.lib.MutableObjectId;
  38. import org.eclipse.jgit.lib.ObjectChecker;
  39. import org.eclipse.jgit.lib.ObjectId;
  40. import org.eclipse.jgit.lib.ObjectInserter;
  41. import org.eclipse.jgit.lib.ObjectLoader;
  42. import org.eclipse.jgit.lib.ObjectReader;
  43. import org.eclipse.jgit.lib.ProgressMonitor;
  44. import org.eclipse.jgit.lib.Ref;
  45. import org.eclipse.jgit.lib.Repository;
  46. import org.eclipse.jgit.revwalk.DateRevQueue;
  47. import org.eclipse.jgit.revwalk.RevCommit;
  48. import org.eclipse.jgit.revwalk.RevFlag;
  49. import org.eclipse.jgit.revwalk.RevObject;
  50. import org.eclipse.jgit.revwalk.RevTag;
  51. import org.eclipse.jgit.revwalk.RevTree;
  52. import org.eclipse.jgit.revwalk.RevWalk;
  53. import org.eclipse.jgit.treewalk.TreeWalk;
  54. import org.eclipse.jgit.util.FileUtils;

  55. /**
  56.  * Generic fetch support for dumb transport protocols.
  57.  * <p>
  58.  * Since there are no Git-specific smarts on the remote side of the connection
  59.  * the client side must determine which objects it needs to copy in order to
  60.  * completely fetch the requested refs and their history. The generic walk
  61.  * support in this class parses each individual object (once it has been copied
  62.  * to the local repository) and examines the list of objects that must also be
  63.  * copied to create a complete history. Objects which are already available
  64.  * locally are retained (and not copied), saving bandwidth for incremental
  65.  * fetches. Pack files are copied from the remote repository only as a last
  66.  * resort, as the entire pack must be copied locally in order to access any
  67.  * single object.
  68.  * <p>
  69.  * This fetch connection does not actually perform the object data transfer.
  70.  * Instead it delegates the transfer to a {@link WalkRemoteObjectDatabase},
  71.  * which knows how to read individual files from the remote repository and
  72.  * supply the data as a standard Java InputStream.
  73.  *
  74.  * @see WalkRemoteObjectDatabase
  75.  */
  76. class WalkFetchConnection extends BaseFetchConnection {
  77.     /** The repository this transport fetches into, or pushes out of. */
  78.     final Repository local;

  79.     /** If not null the validator for received objects. */
  80.     final ObjectChecker objCheck;

  81.     /**
  82.      * List of all remote repositories we may need to get objects out of.
  83.      * <p>
  84.      * The first repository in the list is the one we were asked to fetch from;
  85.      * the remaining repositories point to the alternate locations we can fetch
  86.      * objects through.
  87.      */
  88.     private final List<WalkRemoteObjectDatabase> remotes;

  89.     /** Most recently used item in {@link #remotes}. */
  90.     private int lastRemoteIdx;

  91.     private final RevWalk revWalk;

  92.     private final TreeWalk treeWalk;

  93.     /** Objects whose direct dependents we know we have (or will have). */
  94.     private final RevFlag COMPLETE;

  95.     /** Objects that have already entered {@link #workQueue}. */
  96.     private final RevFlag IN_WORK_QUEUE;

  97.     /** Commits that have already entered {@link #localCommitQueue}. */
  98.     private final RevFlag LOCALLY_SEEN;

  99.     /** Commits already reachable from all local refs. */
  100.     private final DateRevQueue localCommitQueue;

  101.     /** Objects we need to copy from the remote repository. */
  102.     private LinkedList<ObjectId> workQueue;

  103.     /** Databases we have not yet obtained the list of packs from. */
  104.     private final LinkedList<WalkRemoteObjectDatabase> noPacksYet;

  105.     /** Databases we have not yet obtained the alternates from. */
  106.     private final LinkedList<WalkRemoteObjectDatabase> noAlternatesYet;

  107.     /** Packs we have discovered, but have not yet fetched locally. */
  108.     private final LinkedList<RemotePack> unfetchedPacks;

  109.     /**
  110.      * Packs whose indexes we have looked at in {@link #unfetchedPacks}.
  111.      * <p>
  112.      * We try to avoid getting duplicate copies of the same pack through
  113.      * multiple alternates by only looking at packs whose names are not yet in
  114.      * this collection.
  115.      */
  116.     private final Set<String> packsConsidered;

  117.     private final MutableObjectId idBuffer = new MutableObjectId();

  118.     /**
  119.      * Errors received while trying to obtain an object.
  120.      * <p>
  121.      * If the fetch winds up failing because we cannot locate a specific object
  122.      * then we need to report all errors related to that object back to the
  123.      * caller as there may be cascading failures.
  124.      */
  125.     private final HashMap<ObjectId, List<Throwable>> fetchErrors;

  126.     String lockMessage;

  127.     final List<PackLock> packLocks;

  128.     /** Inserter to write objects onto {@link #local}. */
  129.     final ObjectInserter inserter;

  130.     /** Inserter to read objects from {@link #local}. */
  131.     private final ObjectReader reader;

  132.     WalkFetchConnection(WalkTransport t, WalkRemoteObjectDatabase w) {
  133.         Transport wt = (Transport)t;
  134.         local = wt.local;
  135.         objCheck = wt.getObjectChecker();
  136.         inserter = local.newObjectInserter();
  137.         reader = inserter.newReader();

  138.         remotes = new ArrayList<>();
  139.         remotes.add(w);

  140.         unfetchedPacks = new LinkedList<>();
  141.         packsConsidered = new HashSet<>();

  142.         noPacksYet = new LinkedList<>();
  143.         noPacksYet.add(w);

  144.         noAlternatesYet = new LinkedList<>();
  145.         noAlternatesYet.add(w);

  146.         fetchErrors = new HashMap<>();
  147.         packLocks = new ArrayList<>(4);

  148.         revWalk = new RevWalk(reader);
  149.         revWalk.setRetainBody(false);
  150.         treeWalk = new TreeWalk(reader);
  151.         COMPLETE = revWalk.newFlag("COMPLETE"); //$NON-NLS-1$
  152.         IN_WORK_QUEUE = revWalk.newFlag("IN_WORK_QUEUE"); //$NON-NLS-1$
  153.         LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN"); //$NON-NLS-1$

  154.         localCommitQueue = new DateRevQueue();
  155.         workQueue = new LinkedList<>();
  156.     }

  157.     /** {@inheritDoc} */
  158.     @Override
  159.     public boolean didFetchTestConnectivity() {
  160.         return true;
  161.     }

  162.     /** {@inheritDoc} */
  163.     @Override
  164.     protected void doFetch(final ProgressMonitor monitor,
  165.             final Collection<Ref> want, final Set<ObjectId> have)
  166.             throws TransportException {
  167.         markLocalRefsComplete(have);
  168.         queueWants(want);

  169.         while (!monitor.isCancelled() && !workQueue.isEmpty()) {
  170.             final ObjectId id = workQueue.removeFirst();
  171.             if (!(id instanceof RevObject) || !((RevObject) id).has(COMPLETE))
  172.                 downloadObject(monitor, id);
  173.             process(id);
  174.         }

  175.         try {
  176.             inserter.flush();
  177.         } catch (IOException e) {
  178.             throw new TransportException(e.getMessage(), e);
  179.         }
  180.     }

  181.     /** {@inheritDoc} */
  182.     @Override
  183.     public Collection<PackLock> getPackLocks() {
  184.         return packLocks;
  185.     }

  186.     /** {@inheritDoc} */
  187.     @Override
  188.     public void setPackLockMessage(String message) {
  189.         lockMessage = message;
  190.     }

  191.     /** {@inheritDoc} */
  192.     @Override
  193.     public void close() {
  194.         inserter.close();
  195.         reader.close();
  196.         for (RemotePack p : unfetchedPacks) {
  197.             if (p.tmpIdx != null)
  198.                 p.tmpIdx.delete();
  199.         }
  200.         for (WalkRemoteObjectDatabase r : remotes)
  201.             r.close();
  202.     }

  203.     private void queueWants(Collection<Ref> want)
  204.             throws TransportException {
  205.         final HashSet<ObjectId> inWorkQueue = new HashSet<>();
  206.         for (Ref r : want) {
  207.             final ObjectId id = r.getObjectId();
  208.             if (id == null) {
  209.                 throw new NullPointerException(MessageFormat.format(
  210.                         JGitText.get().transportProvidedRefWithNoObjectId, r.getName()));
  211.             }
  212.             try {
  213.                 final RevObject obj = revWalk.parseAny(id);
  214.                 if (obj.has(COMPLETE))
  215.                     continue;
  216.                 if (inWorkQueue.add(id)) {
  217.                     obj.add(IN_WORK_QUEUE);
  218.                     workQueue.add(obj);
  219.                 }
  220.             } catch (MissingObjectException e) {
  221.                 if (inWorkQueue.add(id))
  222.                     workQueue.add(id);
  223.             } catch (IOException e) {
  224.                 throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e);
  225.             }
  226.         }
  227.     }

  228.     private void process(ObjectId id) throws TransportException {
  229.         final RevObject obj;
  230.         try {
  231.             if (id instanceof RevObject) {
  232.                 obj = (RevObject) id;
  233.                 if (obj.has(COMPLETE))
  234.                     return;
  235.                 revWalk.parseHeaders(obj);
  236.             } else {
  237.                 obj = revWalk.parseAny(id);
  238.                 if (obj.has(COMPLETE))
  239.                     return;
  240.             }
  241.         } catch (IOException e) {
  242.             throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e);
  243.         }

  244.         switch (obj.getType()) {
  245.         case Constants.OBJ_BLOB:
  246.             processBlob(obj);
  247.             break;
  248.         case Constants.OBJ_TREE:
  249.             processTree(obj);
  250.             break;
  251.         case Constants.OBJ_COMMIT:
  252.             processCommit(obj);
  253.             break;
  254.         case Constants.OBJ_TAG:
  255.             processTag(obj);
  256.             break;
  257.         default:
  258.             throw new TransportException(MessageFormat.format(JGitText.get().unknownObjectType, id.name()));
  259.         }

  260.         // If we had any prior errors fetching this object they are
  261.         // now resolved, as the object was parsed successfully.
  262.         //
  263.         fetchErrors.remove(id);
  264.     }

  265.     private void processBlob(RevObject obj) throws TransportException {
  266.         try {
  267.             if (reader.has(obj, Constants.OBJ_BLOB))
  268.                 obj.add(COMPLETE);
  269.             else
  270.                 throw new TransportException(MessageFormat.format(JGitText
  271.                         .get().cannotReadBlob, obj.name()),
  272.                         new MissingObjectException(obj, Constants.TYPE_BLOB));
  273.         } catch (IOException error) {
  274.             throw new TransportException(MessageFormat.format(
  275.                     JGitText.get().cannotReadBlob, obj.name()), error);
  276.         }
  277.     }

  278.     private void processTree(RevObject obj) throws TransportException {
  279.         try {
  280.             treeWalk.reset(obj);
  281.             while (treeWalk.next()) {
  282.                 final FileMode mode = treeWalk.getFileMode(0);
  283.                 final int sType = mode.getObjectType();

  284.                 switch (sType) {
  285.                 case Constants.OBJ_BLOB:
  286.                 case Constants.OBJ_TREE:
  287.                     treeWalk.getObjectId(idBuffer, 0);
  288.                     needs(revWalk.lookupAny(idBuffer, sType));
  289.                     continue;

  290.                 default:
  291.                     if (FileMode.GITLINK.equals(mode))
  292.                         continue;
  293.                     treeWalk.getObjectId(idBuffer, 0);
  294.                     throw new CorruptObjectException(MessageFormat.format(JGitText.get().invalidModeFor
  295.                             , mode, idBuffer.name(), treeWalk.getPathString(), obj.getId().name()));
  296.                 }
  297.             }
  298.         } catch (IOException ioe) {
  299.             throw new TransportException(MessageFormat.format(JGitText.get().cannotReadTree, obj.name()), ioe);
  300.         }
  301.         obj.add(COMPLETE);
  302.     }

  303.     private void processCommit(RevObject obj) throws TransportException {
  304.         final RevCommit commit = (RevCommit) obj;
  305.         markLocalCommitsComplete(commit.getCommitTime());
  306.         needs(commit.getTree());
  307.         for (RevCommit p : commit.getParents())
  308.             needs(p);
  309.         obj.add(COMPLETE);
  310.     }

  311.     private void processTag(RevObject obj) {
  312.         final RevTag tag = (RevTag) obj;
  313.         needs(tag.getObject());
  314.         obj.add(COMPLETE);
  315.     }

  316.     private void needs(RevObject obj) {
  317.         if (obj.has(COMPLETE))
  318.             return;
  319.         if (!obj.has(IN_WORK_QUEUE)) {
  320.             obj.add(IN_WORK_QUEUE);
  321.             workQueue.add(obj);
  322.         }
  323.     }

  324.     private void downloadObject(ProgressMonitor pm, AnyObjectId id)
  325.             throws TransportException {
  326.         if (alreadyHave(id))
  327.             return;

  328.         for (;;) {
  329.             // Try a pack file we know about, but don't have yet. Odds are
  330.             // that if it has this object, it has others related to it so
  331.             // getting the pack is a good bet.
  332.             //
  333.             if (downloadPackedObject(pm, id))
  334.                 return;

  335.             // Search for a loose object over all alternates, starting
  336.             // from the one we last successfully located an object through.
  337.             //
  338.             final String idStr = id.name();
  339.             final String subdir = idStr.substring(0, 2);
  340.             final String file = idStr.substring(2);
  341.             final String looseName = subdir + "/" + file; //$NON-NLS-1$

  342.             for (int i = lastRemoteIdx; i < remotes.size(); i++) {
  343.                 if (downloadLooseObject(id, looseName, remotes.get(i))) {
  344.                     lastRemoteIdx = i;
  345.                     return;
  346.                 }
  347.             }
  348.             for (int i = 0; i < lastRemoteIdx; i++) {
  349.                 if (downloadLooseObject(id, looseName, remotes.get(i))) {
  350.                     lastRemoteIdx = i;
  351.                     return;
  352.                 }
  353.             }

  354.             // Try to obtain more pack information and search those.
  355.             //
  356.             while (!noPacksYet.isEmpty()) {
  357.                 final WalkRemoteObjectDatabase wrr = noPacksYet.removeFirst();
  358.                 final Collection<String> packNameList;
  359.                 try {
  360.                     pm.beginTask(JGitText.get().listingPacks,
  361.                             ProgressMonitor.UNKNOWN);
  362.                     packNameList = wrr.getPackNames();
  363.                 } catch (IOException e) {
  364.                     // Try another repository.
  365.                     //
  366.                     recordError(id, e);
  367.                     continue;
  368.                 } finally {
  369.                     pm.endTask();
  370.                 }

  371.                 if (packNameList == null || packNameList.isEmpty())
  372.                     continue;
  373.                 for (String packName : packNameList) {
  374.                     if (packsConsidered.add(packName))
  375.                         unfetchedPacks.add(new RemotePack(wrr, packName));
  376.                 }
  377.                 if (downloadPackedObject(pm, id))
  378.                     return;
  379.             }

  380.             // Try to expand the first alternate we haven't expanded yet.
  381.             //
  382.             Collection<WalkRemoteObjectDatabase> al = expandOneAlternate(id, pm);
  383.             if (al != null && !al.isEmpty()) {
  384.                 for (WalkRemoteObjectDatabase alt : al) {
  385.                     remotes.add(alt);
  386.                     noPacksYet.add(alt);
  387.                     noAlternatesYet.add(alt);
  388.                 }
  389.                 continue;
  390.             }

  391.             // We could not obtain the object. There may be reasons why.
  392.             //
  393.             List<Throwable> failures = fetchErrors.get(id);
  394.             final TransportException te;

  395.             te = new TransportException(MessageFormat.format(JGitText.get().cannotGet, id.name()));
  396.             if (failures != null && !failures.isEmpty()) {
  397.                 if (failures.size() == 1)
  398.                     te.initCause(failures.get(0));
  399.                 else
  400.                     te.initCause(new CompoundException(failures));
  401.             }
  402.             throw te;
  403.         }
  404.     }

  405.     private boolean alreadyHave(AnyObjectId id) throws TransportException {
  406.         try {
  407.             return reader.has(id);
  408.         } catch (IOException error) {
  409.             throw new TransportException(MessageFormat.format(
  410.                     JGitText.get().cannotReadObject, id.name()), error);
  411.         }
  412.     }

  413.     private boolean downloadPackedObject(final ProgressMonitor monitor,
  414.             final AnyObjectId id) throws TransportException {
  415.         // Search for the object in a remote pack whose index we have,
  416.         // but whose pack we do not yet have.
  417.         //
  418.         final Iterator<RemotePack> packItr = unfetchedPacks.iterator();
  419.         while (packItr.hasNext() && !monitor.isCancelled()) {
  420.             final RemotePack pack = packItr.next();
  421.             try {
  422.                 pack.openIndex(monitor);
  423.             } catch (IOException err) {
  424.                 // If the index won't open its either not found or
  425.                 // its a format we don't recognize. In either case
  426.                 // we may still be able to obtain the object from
  427.                 // another source, so don't consider it a failure.
  428.                 //
  429.                 recordError(id, err);
  430.                 packItr.remove();
  431.                 continue;
  432.             }

  433.             if (monitor.isCancelled()) {
  434.                 // If we were cancelled while the index was opening
  435.                 // the open may have aborted. We can't search an
  436.                 // unopen index.
  437.                 //
  438.                 return false;
  439.             }

  440.             if (!pack.index.hasObject(id)) {
  441.                 // Not in this pack? Try another.
  442.                 //
  443.                 continue;
  444.             }

  445.             // It should be in the associated pack. Download that
  446.             // and attach it to the local repository so we can use
  447.             // all of the contained objects.
  448.             //
  449.             Throwable e1 = null;
  450.             try {
  451.                 pack.downloadPack(monitor);
  452.             } catch (IOException err) {
  453.                 // If the pack failed to download, index correctly,
  454.                 // or open in the local repository we may still be
  455.                 // able to obtain this object from another pack or
  456.                 // an alternate.
  457.                 //
  458.                 recordError(id, err);
  459.                 e1 = err;
  460.                 continue;
  461.             } finally {
  462.                 // If the pack was good its in the local repository
  463.                 // and Repository.getObjectDatabase().has(id) will
  464.                 // succeed in the future, so we do not need this
  465.                 // data any more. If it failed the index and pack
  466.                 // are unusable and we shouldn't consult them again.
  467.                 //
  468.                 try {
  469.                     if (pack.tmpIdx != null)
  470.                         FileUtils.delete(pack.tmpIdx);
  471.                 } catch (IOException e) {
  472.                     if (e1 != null) {
  473.                         e.addSuppressed(e1);
  474.                     }
  475.                     throw new TransportException(e.getMessage(), e);
  476.                 }
  477.                 packItr.remove();
  478.             }

  479.             if (!alreadyHave(id)) {
  480.                 // What the hell? This pack claimed to have
  481.                 // the object, but after indexing we didn't
  482.                 // actually find it in the pack.
  483.                 //
  484.                 recordError(id, new FileNotFoundException(MessageFormat.format(
  485.                         JGitText.get().objectNotFoundIn, id.name(), pack.packName)));
  486.                 continue;
  487.             }

  488.             // Complete any other objects that we can.
  489.             //
  490.             final Iterator<ObjectId> pending = swapFetchQueue();
  491.             while (pending.hasNext()) {
  492.                 final ObjectId p = pending.next();
  493.                 if (pack.index.hasObject(p)) {
  494.                     pending.remove();
  495.                     process(p);
  496.                 } else {
  497.                     workQueue.add(p);
  498.                 }
  499.             }
  500.             return true;

  501.         }
  502.         return false;
  503.     }

  504.     private Iterator<ObjectId> swapFetchQueue() {
  505.         final Iterator<ObjectId> r = workQueue.iterator();
  506.         workQueue = new LinkedList<>();
  507.         return r;
  508.     }

  509.     private boolean downloadLooseObject(final AnyObjectId id,
  510.             final String looseName, final WalkRemoteObjectDatabase remote)
  511.             throws TransportException {
  512.         try {
  513.             final byte[] compressed = remote.open(looseName).toArray();
  514.             verifyAndInsertLooseObject(id, compressed);
  515.             return true;
  516.         } catch (FileNotFoundException e) {
  517.             // Not available in a loose format from this alternate?
  518.             // Try another strategy to get the object.
  519.             //
  520.             recordError(id, e);
  521.             return false;
  522.         } catch (IOException e) {
  523.             throw new TransportException(MessageFormat.format(JGitText.get().cannotDownload, id.name()), e);
  524.         }
  525.     }

  526.     private void verifyAndInsertLooseObject(final AnyObjectId id,
  527.             final byte[] compressed) throws IOException {
  528.         final ObjectLoader uol;
  529.         try {
  530.             uol = UnpackedObject.parse(compressed, id);
  531.         } catch (CorruptObjectException parsingError) {
  532.             // Some HTTP servers send back a "200 OK" status with an HTML
  533.             // page that explains the requested file could not be found.
  534.             // These servers are most certainly misconfigured, but many
  535.             // of them exist in the world, and many of those are hosting
  536.             // Git repositories.
  537.             //
  538.             // Since an HTML page is unlikely to hash to one of our loose
  539.             // objects we treat this condition as a FileNotFoundException
  540.             // and attempt to recover by getting the object from another
  541.             // source.
  542.             //
  543.             final FileNotFoundException e;
  544.             e = new FileNotFoundException(id.name());
  545.             e.initCause(parsingError);
  546.             throw e;
  547.         }

  548.         final int type = uol.getType();
  549.         final byte[] raw = uol.getCachedBytes();
  550.         if (objCheck != null) {
  551.             try {
  552.                 objCheck.check(id, type, raw);
  553.             } catch (CorruptObjectException e) {
  554.                 throw new TransportException(MessageFormat.format(
  555.                         JGitText.get().transportExceptionInvalid,
  556.                         Constants.typeString(type), id.name(), e.getMessage()));
  557.             }
  558.         }

  559.         ObjectId act = inserter.insert(type, raw);
  560.         if (!AnyObjectId.isEqual(id, act)) {
  561.             throw new TransportException(MessageFormat.format(
  562.                     JGitText.get().incorrectHashFor, id.name(), act.name(),
  563.                     Constants.typeString(type),
  564.                     Integer.valueOf(compressed.length)));
  565.         }
  566.     }

  567.     private Collection<WalkRemoteObjectDatabase> expandOneAlternate(
  568.             final AnyObjectId id, final ProgressMonitor pm) {
  569.         while (!noAlternatesYet.isEmpty()) {
  570.             final WalkRemoteObjectDatabase wrr = noAlternatesYet.removeFirst();
  571.             try {
  572.                 pm.beginTask(JGitText.get().listingAlternates, ProgressMonitor.UNKNOWN);
  573.                 Collection<WalkRemoteObjectDatabase> altList = wrr
  574.                         .getAlternates();
  575.                 if (altList != null && !altList.isEmpty())
  576.                     return altList;
  577.             } catch (IOException e) {
  578.                 // Try another repository.
  579.                 //
  580.                 recordError(id, e);
  581.             } finally {
  582.                 pm.endTask();
  583.             }
  584.         }
  585.         return null;
  586.     }

  587.     private void markLocalRefsComplete(Set<ObjectId> have) throws TransportException {
  588.         List<Ref> refs;
  589.         try {
  590.             refs = local.getRefDatabase().getRefs();
  591.         } catch (IOException e) {
  592.             throw new TransportException(e.getMessage(), e);
  593.         }
  594.         for (Ref r : refs) {
  595.             try {
  596.                 markLocalObjComplete(revWalk.parseAny(r.getObjectId()));
  597.             } catch (IOException readError) {
  598.                 throw new TransportException(MessageFormat.format(JGitText.get().localRefIsMissingObjects, r.getName()), readError);
  599.             }
  600.         }
  601.         for (ObjectId id : have) {
  602.             try {
  603.                 markLocalObjComplete(revWalk.parseAny(id));
  604.             } catch (IOException readError) {
  605.                 throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionMissingAssumed, id.name()), readError);
  606.             }
  607.         }
  608.     }

  609.     private void markLocalObjComplete(RevObject obj) throws IOException {
  610.         while (obj.getType() == Constants.OBJ_TAG) {
  611.             obj.add(COMPLETE);
  612.             obj = ((RevTag) obj).getObject();
  613.             revWalk.parseHeaders(obj);
  614.         }

  615.         switch (obj.getType()) {
  616.         case Constants.OBJ_BLOB:
  617.             obj.add(COMPLETE);
  618.             break;
  619.         case Constants.OBJ_COMMIT:
  620.             pushLocalCommit((RevCommit) obj);
  621.             break;
  622.         case Constants.OBJ_TREE:
  623.             markTreeComplete((RevTree) obj);
  624.             break;
  625.         }
  626.     }

  627.     private void markLocalCommitsComplete(int until)
  628.             throws TransportException {
  629.         try {
  630.             for (;;) {
  631.                 final RevCommit c = localCommitQueue.peek();
  632.                 if (c == null || c.getCommitTime() < until)
  633.                     return;
  634.                 localCommitQueue.next();

  635.                 markTreeComplete(c.getTree());
  636.                 for (RevCommit p : c.getParents())
  637.                     pushLocalCommit(p);
  638.             }
  639.         } catch (IOException err) {
  640.             throw new TransportException(JGitText.get().localObjectsIncomplete, err);
  641.         }
  642.     }

  643.     private void pushLocalCommit(RevCommit p)
  644.             throws MissingObjectException, IOException {
  645.         if (p.has(LOCALLY_SEEN))
  646.             return;
  647.         revWalk.parseHeaders(p);
  648.         p.add(LOCALLY_SEEN);
  649.         p.add(COMPLETE);
  650.         p.carry(COMPLETE);
  651.         localCommitQueue.add(p);
  652.     }

  653.     private void markTreeComplete(RevTree tree) throws IOException {
  654.         if (tree.has(COMPLETE))
  655.             return;
  656.         tree.add(COMPLETE);
  657.         treeWalk.reset(tree);
  658.         while (treeWalk.next()) {
  659.             final FileMode mode = treeWalk.getFileMode(0);
  660.             final int sType = mode.getObjectType();

  661.             switch (sType) {
  662.             case Constants.OBJ_BLOB:
  663.                 treeWalk.getObjectId(idBuffer, 0);
  664.                 revWalk.lookupAny(idBuffer, sType).add(COMPLETE);
  665.                 continue;

  666.             case Constants.OBJ_TREE: {
  667.                 treeWalk.getObjectId(idBuffer, 0);
  668.                 final RevObject o = revWalk.lookupAny(idBuffer, sType);
  669.                 if (!o.has(COMPLETE)) {
  670.                     o.add(COMPLETE);
  671.                     treeWalk.enterSubtree();
  672.                 }
  673.                 continue;
  674.             }
  675.             default:
  676.                 if (FileMode.GITLINK.equals(mode))
  677.                     continue;
  678.                 treeWalk.getObjectId(idBuffer, 0);
  679.                 throw new CorruptObjectException(MessageFormat.format(JGitText.get().corruptObjectInvalidMode3
  680.                         , mode, idBuffer.name(), treeWalk.getPathString(), tree.name()));
  681.             }
  682.         }
  683.     }

  684.     private void recordError(AnyObjectId id, Throwable what) {
  685.         final ObjectId objId = id.copy();
  686.         List<Throwable> errors = fetchErrors.get(objId);
  687.         if (errors == null) {
  688.             errors = new ArrayList<>(2);
  689.             fetchErrors.put(objId, errors);
  690.         }
  691.         errors.add(what);
  692.     }

  693.     private class RemotePack {
  694.         final WalkRemoteObjectDatabase connection;

  695.         final String packName;

  696.         final String idxName;

  697.         File tmpIdx;

  698.         PackIndex index;

  699.         RemotePack(WalkRemoteObjectDatabase c, String pn) {
  700.             connection = c;
  701.             packName = pn;
  702.             idxName = packName.substring(0, packName.length() - 5) + ".idx"; //$NON-NLS-1$

  703.             String tn = idxName;
  704.             if (tn.startsWith("pack-")) //$NON-NLS-1$
  705.                 tn = tn.substring(5);
  706.             if (tn.endsWith(".idx")) //$NON-NLS-1$
  707.                 tn = tn.substring(0, tn.length() - 4);

  708.             if (local.getObjectDatabase() instanceof ObjectDirectory) {
  709.                 tmpIdx = new File(((ObjectDirectory) local.getObjectDatabase())
  710.                                 .getDirectory(),
  711.                         "walk-" + tn + ".walkidx"); //$NON-NLS-1$ //$NON-NLS-2$
  712.             }
  713.         }

  714.         void openIndex(ProgressMonitor pm) throws IOException {
  715.             if (index != null)
  716.                 return;
  717.             if (tmpIdx == null)
  718.                 tmpIdx = File.createTempFile("jgit-walk-", ".idx"); //$NON-NLS-1$ //$NON-NLS-2$
  719.             else if (tmpIdx.isFile()) {
  720.                 try {
  721.                     index = PackIndex.open(tmpIdx);
  722.                     return;
  723.                 } catch (FileNotFoundException err) {
  724.                     // Fall through and get the file.
  725.                 }
  726.             }

  727.             final WalkRemoteObjectDatabase.FileStream s;
  728.             s = connection.open("pack/" + idxName); //$NON-NLS-1$
  729.             pm.beginTask("Get " + idxName.substring(0, 12) + "..idx", //$NON-NLS-1$ //$NON-NLS-2$
  730.                     s.length < 0 ? ProgressMonitor.UNKNOWN
  731.                             : (int) (s.length / 1024));
  732.             try (FileOutputStream fos = new FileOutputStream(tmpIdx)) {
  733.                 final byte[] buf = new byte[2048];
  734.                 int cnt;
  735.                 while (!pm.isCancelled() && (cnt = s.in.read(buf)) >= 0) {
  736.                     fos.write(buf, 0, cnt);
  737.                     pm.update(cnt / 1024);
  738.                 }
  739.             } catch (IOException err) {
  740.                 FileUtils.delete(tmpIdx);
  741.                 throw err;
  742.             } finally {
  743.                 s.in.close();
  744.             }
  745.             pm.endTask();

  746.             if (pm.isCancelled()) {
  747.                 FileUtils.delete(tmpIdx);
  748.                 return;
  749.             }

  750.             try {
  751.                 index = PackIndex.open(tmpIdx);
  752.             } catch (IOException e) {
  753.                 FileUtils.delete(tmpIdx);
  754.                 throw e;
  755.             }
  756.         }

  757.         void downloadPack(ProgressMonitor monitor) throws IOException {
  758.             String name = "pack/" + packName; //$NON-NLS-1$
  759.             WalkRemoteObjectDatabase.FileStream s = connection.open(name);
  760.             try {
  761.                 PackParser parser = inserter.newPackParser(s.in);
  762.                 parser.setAllowThin(false);
  763.                 parser.setObjectChecker(objCheck);
  764.                 parser.setLockMessage(lockMessage);
  765.                 PackLock lock = parser.parse(monitor);
  766.                 if (lock != null)
  767.                     packLocks.add(lock);
  768.             } finally {
  769.                 s.in.close();
  770.             }
  771.         }
  772.     }
  773. }