You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WalkFetchConnection.java 26KB

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