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.

WalkRemoteObjectDatabase.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.transport;
  44. import java.io.BufferedReader;
  45. import java.io.ByteArrayOutputStream;
  46. import java.io.FileNotFoundException;
  47. import java.io.IOException;
  48. import java.io.InputStream;
  49. import java.io.InputStreamReader;
  50. import java.io.OutputStream;
  51. import java.text.MessageFormat;
  52. import java.util.ArrayList;
  53. import java.util.Collection;
  54. import java.util.Map;
  55. import org.eclipse.jgit.errors.TransportException;
  56. import org.eclipse.jgit.internal.JGitText;
  57. import org.eclipse.jgit.internal.storage.file.RefDirectory;
  58. import org.eclipse.jgit.lib.Constants;
  59. import org.eclipse.jgit.lib.ObjectId;
  60. import org.eclipse.jgit.lib.ObjectIdRef;
  61. import org.eclipse.jgit.lib.ProgressMonitor;
  62. import org.eclipse.jgit.lib.Ref;
  63. import org.eclipse.jgit.util.IO;
  64. /**
  65. * Transfers object data through a dumb transport.
  66. * <p>
  67. * Implementations are responsible for resolving path names relative to the
  68. * <code>objects/</code> subdirectory of a single remote Git repository or
  69. * naked object database and make the content available as a Java input stream
  70. * for reading during fetch. The actual object traversal logic to determine the
  71. * names of files to retrieve is handled through the generic, protocol
  72. * independent {@link WalkFetchConnection}.
  73. */
  74. abstract class WalkRemoteObjectDatabase {
  75. static final String ROOT_DIR = "../"; //$NON-NLS-1$
  76. static final String INFO_PACKS = "info/packs"; //$NON-NLS-1$
  77. static final String INFO_ALTERNATES = "info/alternates"; //$NON-NLS-1$
  78. static final String INFO_HTTP_ALTERNATES = "info/http-alternates"; //$NON-NLS-1$
  79. static final String INFO_REFS = ROOT_DIR + Constants.INFO_REFS;
  80. abstract URIish getURI();
  81. /**
  82. * Obtain the list of available packs (if any).
  83. * <p>
  84. * Pack names should be the file name in the packs directory, that is
  85. * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>. Index
  86. * names should not be included in the returned collection.
  87. *
  88. * @return list of pack names; null or empty list if none are available.
  89. * @throws IOException
  90. * The connection is unable to read the remote repository's list
  91. * of available pack files.
  92. */
  93. abstract Collection<String> getPackNames() throws IOException;
  94. /**
  95. * Obtain alternate connections to alternate object databases (if any).
  96. * <p>
  97. * Alternates are typically read from the file {@link #INFO_ALTERNATES} or
  98. * {@link #INFO_HTTP_ALTERNATES}. The content of each line must be resolved
  99. * by the implementation and a new database reference should be returned to
  100. * represent the additional location.
  101. * <p>
  102. * Alternates may reuse the same network connection handle, however the
  103. * fetch connection will {@link #close()} each created alternate.
  104. *
  105. * @return list of additional object databases the caller could fetch from;
  106. * null or empty list if none are configured.
  107. * @throws IOException
  108. * The connection is unable to read the remote repository's list
  109. * of configured alternates.
  110. */
  111. abstract Collection<WalkRemoteObjectDatabase> getAlternates()
  112. throws IOException;
  113. /**
  114. * Open a single file for reading.
  115. * <p>
  116. * Implementors should make every attempt possible to ensure
  117. * {@link FileNotFoundException} is used when the remote object does not
  118. * exist. However when fetching over HTTP some misconfigured servers may
  119. * generate a 200 OK status message (rather than a 404 Not Found) with an
  120. * HTML formatted message explaining the requested resource does not exist.
  121. * Callers such as {@link WalkFetchConnection} are prepared to handle this
  122. * by validating the content received, and assuming content that fails to
  123. * match its hash is an incorrectly phrased FileNotFoundException.
  124. *
  125. * @param path
  126. * location of the file to read, relative to this objects
  127. * directory (e.g.
  128. * <code>cb/95df6ab7ae9e57571511ef451cf33767c26dd2</code> or
  129. * <code>pack/pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>).
  130. * @return a stream to read from the file. Never null.
  131. * @throws FileNotFoundException
  132. * the requested file does not exist at the given location.
  133. * @throws IOException
  134. * The connection is unable to read the remote's file, and the
  135. * failure occurred prior to being able to determine if the file
  136. * exists, or after it was determined to exist but before the
  137. * stream could be created.
  138. */
  139. abstract FileStream open(String path) throws FileNotFoundException,
  140. IOException;
  141. /**
  142. * Create a new connection for a discovered alternate object database
  143. * <p>
  144. * This method is typically called by {@link #readAlternates(String)} when
  145. * subclasses us the generic alternate parsing logic for their
  146. * implementation of {@link #getAlternates()}.
  147. *
  148. * @param location
  149. * the location of the new alternate, relative to the current
  150. * object database.
  151. * @return a new database connection that can read from the specified
  152. * alternate.
  153. * @throws IOException
  154. * The database connection cannot be established with the
  155. * alternate, such as if the alternate location does not
  156. * actually exist and the connection's constructor attempts to
  157. * verify that.
  158. */
  159. abstract WalkRemoteObjectDatabase openAlternate(String location)
  160. throws IOException;
  161. /**
  162. * Close any resources used by this connection.
  163. * <p>
  164. * If the remote repository is contacted by a network socket this method
  165. * must close that network socket, disconnecting the two peers. If the
  166. * remote repository is actually local (same system) this method must close
  167. * any open file handles used to read the "remote" repository.
  168. */
  169. abstract void close();
  170. /**
  171. * Delete a file from the object database.
  172. * <p>
  173. * Path may start with <code>../</code> to request deletion of a file that
  174. * resides in the repository itself.
  175. * <p>
  176. * When possible empty directories must be removed, up to but not including
  177. * the current object database directory itself.
  178. * <p>
  179. * This method does not support deletion of directories.
  180. *
  181. * @param path
  182. * name of the item to be removed, relative to the current object
  183. * database.
  184. * @throws IOException
  185. * deletion is not supported, or deletion failed.
  186. */
  187. void deleteFile(final String path) throws IOException {
  188. throw new IOException(MessageFormat.format(JGitText.get().deletingNotSupported, path));
  189. }
  190. /**
  191. * Open a remote file for writing.
  192. * <p>
  193. * Path may start with <code>../</code> to request writing of a file that
  194. * resides in the repository itself.
  195. * <p>
  196. * The requested path may or may not exist. If the path already exists as a
  197. * file the file should be truncated and completely replaced.
  198. * <p>
  199. * This method creates any missing parent directories, if necessary.
  200. *
  201. * @param path
  202. * name of the file to write, relative to the current object
  203. * database.
  204. * @return stream to write into this file. Caller must close the stream to
  205. * complete the write request. The stream is not buffered and each
  206. * write may cause a network request/response so callers should
  207. * buffer to smooth out small writes.
  208. * @param monitor
  209. * (optional) progress monitor to post write completion to during
  210. * the stream's close method.
  211. * @param monitorTask
  212. * (optional) task name to display during the close method.
  213. * @throws IOException
  214. * writing is not supported, or attempting to write the file
  215. * failed, possibly due to permissions or remote disk full, etc.
  216. */
  217. OutputStream writeFile(final String path, final ProgressMonitor monitor,
  218. final String monitorTask) throws IOException {
  219. throw new IOException(MessageFormat.format(JGitText.get().writingNotSupported, path));
  220. }
  221. /**
  222. * Atomically write a remote file.
  223. * <p>
  224. * This method attempts to perform as atomic of an update as it can,
  225. * reducing (or eliminating) the time that clients might be able to see
  226. * partial file content. This method is not suitable for very large
  227. * transfers as the complete content must be passed as an argument.
  228. * <p>
  229. * Path may start with <code>../</code> to request writing of a file that
  230. * resides in the repository itself.
  231. * <p>
  232. * The requested path may or may not exist. If the path already exists as a
  233. * file the file should be truncated and completely replaced.
  234. * <p>
  235. * This method creates any missing parent directories, if necessary.
  236. *
  237. * @param path
  238. * name of the file to write, relative to the current object
  239. * database.
  240. * @param data
  241. * complete new content of the file.
  242. * @throws IOException
  243. * writing is not supported, or attempting to write the file
  244. * failed, possibly due to permissions or remote disk full, etc.
  245. */
  246. void writeFile(final String path, final byte[] data) throws IOException {
  247. final OutputStream os = writeFile(path, null, null);
  248. try {
  249. os.write(data);
  250. } finally {
  251. os.close();
  252. }
  253. }
  254. /**
  255. * Delete a loose ref from the remote repository.
  256. *
  257. * @param name
  258. * name of the ref within the ref space, for example
  259. * <code>refs/heads/pu</code>.
  260. * @throws IOException
  261. * deletion is not supported, or deletion failed.
  262. */
  263. void deleteRef(final String name) throws IOException {
  264. deleteFile(ROOT_DIR + name);
  265. }
  266. /**
  267. * Delete a reflog from the remote repository.
  268. *
  269. * @param name
  270. * name of the ref within the ref space, for example
  271. * <code>refs/heads/pu</code>.
  272. * @throws IOException
  273. * deletion is not supported, or deletion failed.
  274. */
  275. void deleteRefLog(final String name) throws IOException {
  276. deleteFile(ROOT_DIR + Constants.LOGS + "/" + name); //$NON-NLS-1$
  277. }
  278. /**
  279. * Overwrite (or create) a loose ref in the remote repository.
  280. * <p>
  281. * This method creates any missing parent directories, if necessary.
  282. *
  283. * @param name
  284. * name of the ref within the ref space, for example
  285. * <code>refs/heads/pu</code>.
  286. * @param value
  287. * new value to store in this ref. Must not be null.
  288. * @throws IOException
  289. * writing is not supported, or attempting to write the file
  290. * failed, possibly due to permissions or remote disk full, etc.
  291. */
  292. void writeRef(final String name, final ObjectId value) throws IOException {
  293. final ByteArrayOutputStream b;
  294. b = new ByteArrayOutputStream(Constants.OBJECT_ID_STRING_LENGTH + 1);
  295. value.copyTo(b);
  296. b.write('\n');
  297. writeFile(ROOT_DIR + name, b.toByteArray());
  298. }
  299. /**
  300. * Rebuild the {@link #INFO_PACKS} for dumb transport clients.
  301. * <p>
  302. * This method rebuilds the contents of the {@link #INFO_PACKS} file to
  303. * match the passed list of pack names.
  304. *
  305. * @param packNames
  306. * names of available pack files, in the order they should appear
  307. * in the file. Valid pack name strings are of the form
  308. * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>.
  309. * @throws IOException
  310. * writing is not supported, or attempting to write the file
  311. * failed, possibly due to permissions or remote disk full, etc.
  312. */
  313. void writeInfoPacks(final Collection<String> packNames) throws IOException {
  314. final StringBuilder w = new StringBuilder();
  315. for (final String n : packNames) {
  316. w.append("P "); //$NON-NLS-1$
  317. w.append(n);
  318. w.append('\n');
  319. }
  320. writeFile(INFO_PACKS, Constants.encodeASCII(w.toString()));
  321. }
  322. /**
  323. * Open a buffered reader around a file.
  324. * <p>
  325. * This is shorthand for calling {@link #open(String)} and then wrapping it
  326. * in a reader suitable for line oriented files like the alternates list.
  327. *
  328. * @return a stream to read from the file. Never null.
  329. * @param path
  330. * location of the file to read, relative to this objects
  331. * directory (e.g. <code>info/packs</code>).
  332. * @throws FileNotFoundException
  333. * the requested file does not exist at the given location.
  334. * @throws IOException
  335. * The connection is unable to read the remote's file, and the
  336. * failure occurred prior to being able to determine if the file
  337. * exists, or after it was determined to exist but before the
  338. * stream could be created.
  339. */
  340. BufferedReader openReader(final String path) throws IOException {
  341. final InputStream is = open(path).in;
  342. return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
  343. }
  344. /**
  345. * Read a standard Git alternates file to discover other object databases.
  346. * <p>
  347. * This method is suitable for reading the standard formats of the
  348. * alternates file, such as found in <code>objects/info/alternates</code>
  349. * or <code>objects/info/http-alternates</code> within a Git repository.
  350. * <p>
  351. * Alternates appear one per line, with paths expressed relative to this
  352. * object database.
  353. *
  354. * @param listPath
  355. * location of the alternate file to read, relative to this
  356. * object database (e.g. <code>info/alternates</code>).
  357. * @return the list of discovered alternates. Empty list if the file exists,
  358. * but no entries were discovered.
  359. * @throws FileNotFoundException
  360. * the requested file does not exist at the given location.
  361. * @throws IOException
  362. * The connection is unable to read the remote's file, and the
  363. * failure occurred prior to being able to determine if the file
  364. * exists, or after it was determined to exist but before the
  365. * stream could be created.
  366. */
  367. Collection<WalkRemoteObjectDatabase> readAlternates(final String listPath)
  368. throws IOException {
  369. final BufferedReader br = openReader(listPath);
  370. try {
  371. final Collection<WalkRemoteObjectDatabase> alts = new ArrayList<WalkRemoteObjectDatabase>();
  372. for (;;) {
  373. String line = br.readLine();
  374. if (line == null)
  375. break;
  376. if (!line.endsWith("/")) //$NON-NLS-1$
  377. line += "/"; //$NON-NLS-1$
  378. alts.add(openAlternate(line));
  379. }
  380. return alts;
  381. } finally {
  382. br.close();
  383. }
  384. }
  385. /**
  386. * Read a standard Git packed-refs file to discover known references.
  387. *
  388. * @param avail
  389. * return collection of references. Any existing entries will be
  390. * replaced if they are found in the packed-refs file.
  391. * @throws TransportException
  392. * an error occurred reading from the packed refs file.
  393. */
  394. protected void readPackedRefs(final Map<String, Ref> avail)
  395. throws TransportException {
  396. try {
  397. final BufferedReader br = openReader(ROOT_DIR
  398. + Constants.PACKED_REFS);
  399. try {
  400. readPackedRefsImpl(avail, br);
  401. } finally {
  402. br.close();
  403. }
  404. } catch (FileNotFoundException notPacked) {
  405. // Perhaps it wasn't worthwhile, or is just an older repository.
  406. } catch (IOException e) {
  407. throw new TransportException(getURI(), JGitText.get().errorInPackedRefs, e);
  408. }
  409. }
  410. private void readPackedRefsImpl(final Map<String, Ref> avail,
  411. final BufferedReader br) throws IOException {
  412. Ref last = null;
  413. boolean peeled = false;
  414. for (;;) {
  415. String line = br.readLine();
  416. if (line == null)
  417. break;
  418. if (line.charAt(0) == '#') {
  419. if (line.startsWith(RefDirectory.PACKED_REFS_HEADER)) {
  420. line = line.substring(RefDirectory.PACKED_REFS_HEADER.length());
  421. peeled = line.contains(RefDirectory.PACKED_REFS_PEELED);
  422. }
  423. continue;
  424. }
  425. if (line.charAt(0) == '^') {
  426. if (last == null)
  427. throw new TransportException(JGitText.get().peeledLineBeforeRef);
  428. final ObjectId id = ObjectId.fromString(line.substring(1));
  429. last = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED, last
  430. .getName(), last.getObjectId(), id);
  431. avail.put(last.getName(), last);
  432. continue;
  433. }
  434. final int sp = line.indexOf(' ');
  435. if (sp < 0)
  436. throw new TransportException(MessageFormat.format(JGitText.get().unrecognizedRef, line));
  437. final ObjectId id = ObjectId.fromString(line.substring(0, sp));
  438. final String name = line.substring(sp + 1);
  439. if (peeled)
  440. last = new ObjectIdRef.PeeledNonTag(Ref.Storage.PACKED, name, id);
  441. else
  442. last = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, name, id);
  443. avail.put(last.getName(), last);
  444. }
  445. }
  446. static final class FileStream {
  447. final InputStream in;
  448. final long length;
  449. /**
  450. * Create a new stream of unknown length.
  451. *
  452. * @param i
  453. * stream containing the file data. This stream will be
  454. * closed by the caller when reading is complete.
  455. */
  456. FileStream(final InputStream i) {
  457. in = i;
  458. length = -1;
  459. }
  460. /**
  461. * Create a new stream of known length.
  462. *
  463. * @param i
  464. * stream containing the file data. This stream will be
  465. * closed by the caller when reading is complete.
  466. * @param n
  467. * total number of bytes available for reading through
  468. * <code>i</code>.
  469. */
  470. FileStream(final InputStream i, final long n) {
  471. in = i;
  472. length = n;
  473. }
  474. byte[] toArray() throws IOException {
  475. try {
  476. if (length >= 0) {
  477. final byte[] r = new byte[(int) length];
  478. IO.readFully(in, r, 0, r.length);
  479. return r;
  480. }
  481. final ByteArrayOutputStream r = new ByteArrayOutputStream();
  482. final byte[] buf = new byte[2048];
  483. int n;
  484. while ((n = in.read(buf)) >= 0)
  485. r.write(buf, 0, n);
  486. return r.toByteArray();
  487. } finally {
  488. in.close();
  489. }
  490. }
  491. }
  492. }