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.

ContentSource.java 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /*
  2. * Copyright (C) 2010, 2020 Google Inc. and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.diff;
  11. import java.io.BufferedInputStream;
  12. import java.io.FileNotFoundException;
  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import org.eclipse.jgit.errors.LargeObjectException;
  16. import org.eclipse.jgit.errors.MissingObjectException;
  17. import org.eclipse.jgit.lib.Constants;
  18. import org.eclipse.jgit.lib.ObjectId;
  19. import org.eclipse.jgit.lib.ObjectLoader;
  20. import org.eclipse.jgit.lib.ObjectReader;
  21. import org.eclipse.jgit.lib.ObjectStream;
  22. import org.eclipse.jgit.treewalk.TreeWalk;
  23. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  24. import org.eclipse.jgit.treewalk.filter.PathFilter;
  25. /**
  26. * Supplies the content of a file for
  27. * {@link org.eclipse.jgit.diff.DiffFormatter}.
  28. * <p>
  29. * A content source is not thread-safe. Sources may contain state, including
  30. * information about the last ObjectLoader they returned. Callers must be
  31. * careful to ensure there is no more than one ObjectLoader pending on any
  32. * source, at any time.
  33. */
  34. public abstract class ContentSource {
  35. /**
  36. * Construct a content source for an ObjectReader.
  37. *
  38. * @param reader
  39. * the reader to obtain blobs from.
  40. * @return a source wrapping the reader.
  41. */
  42. public static ContentSource create(ObjectReader reader) {
  43. return new ObjectReaderSource(reader);
  44. }
  45. /**
  46. * Construct a content source for a working directory.
  47. *
  48. * If the iterator is a {@link org.eclipse.jgit.treewalk.FileTreeIterator}
  49. * an optimized version is used that doesn't require seeking through a
  50. * TreeWalk.
  51. *
  52. * @param iterator
  53. * the iterator to obtain source files through.
  54. * @return a content source wrapping the iterator.
  55. */
  56. public static ContentSource create(WorkingTreeIterator iterator) {
  57. return new WorkingTreeSource(iterator);
  58. }
  59. /**
  60. * Determine the size of the object.
  61. *
  62. * @param path
  63. * the path of the file, relative to the root of the repository.
  64. * @param id
  65. * blob id of the file, if known.
  66. * @return the size in bytes.
  67. * @throws java.io.IOException
  68. * the file cannot be accessed.
  69. */
  70. public abstract long size(String path, ObjectId id) throws IOException;
  71. /**
  72. * Open the object.
  73. *
  74. * @param path
  75. * the path of the file, relative to the root of the repository.
  76. * @param id
  77. * blob id of the file, if known.
  78. * @return a loader that can supply the content of the file. The loader must
  79. * be used before another loader can be obtained from this same
  80. * source.
  81. * @throws java.io.IOException
  82. * the file cannot be accessed.
  83. */
  84. public abstract ObjectLoader open(String path, ObjectId id)
  85. throws IOException;
  86. private static class ObjectReaderSource extends ContentSource {
  87. private final ObjectReader reader;
  88. ObjectReaderSource(ObjectReader reader) {
  89. this.reader = reader;
  90. }
  91. @Override
  92. public long size(String path, ObjectId id) throws IOException {
  93. try {
  94. return reader.getObjectSize(id, Constants.OBJ_BLOB);
  95. } catch (MissingObjectException ignore) {
  96. return 0;
  97. }
  98. }
  99. @Override
  100. public ObjectLoader open(String path, ObjectId id) throws IOException {
  101. return reader.open(id, Constants.OBJ_BLOB);
  102. }
  103. }
  104. private static class WorkingTreeSource extends ContentSource {
  105. private final TreeWalk tw;
  106. private final WorkingTreeIterator iterator;
  107. private String current;
  108. WorkingTreeIterator ptr;
  109. WorkingTreeSource(WorkingTreeIterator iterator) {
  110. this.tw = new TreeWalk(iterator.getRepository(),
  111. (ObjectReader) null);
  112. this.tw.setRecursive(true);
  113. this.iterator = iterator;
  114. }
  115. @Override
  116. public long size(String path, ObjectId id) throws IOException {
  117. seek(path);
  118. return ptr.getEntryLength();
  119. }
  120. @Override
  121. public ObjectLoader open(String path, ObjectId id) throws IOException {
  122. seek(path);
  123. long entrySize = ptr.getEntryContentLength();
  124. return new ObjectLoader() {
  125. @Override
  126. public long getSize() {
  127. return entrySize;
  128. }
  129. @Override
  130. public int getType() {
  131. return ptr.getEntryFileMode().getObjectType();
  132. }
  133. @Override
  134. public ObjectStream openStream() throws MissingObjectException,
  135. IOException {
  136. long contentLength = entrySize;
  137. InputStream in = ptr.openEntryStream();
  138. in = new BufferedInputStream(in);
  139. return new ObjectStream.Filter(getType(), contentLength, in);
  140. }
  141. @Override
  142. public boolean isLarge() {
  143. return true;
  144. }
  145. @Override
  146. public byte[] getCachedBytes() throws LargeObjectException {
  147. throw new LargeObjectException();
  148. }
  149. };
  150. }
  151. private void seek(String path) throws IOException {
  152. if (!path.equals(current)) {
  153. iterator.reset();
  154. // Possibly this iterator had an associated DirCacheIterator,
  155. // but we have no access to it and thus don't know about it.
  156. // We have to reset this iterator here to work without
  157. // DirCacheIterator and to descend always into ignored
  158. // directories. Otherwise we might not find tracked files below
  159. // ignored folders. Since we're looking only for a single
  160. // specific path this is not a performance problem.
  161. iterator.setWalkIgnoredDirectories(true);
  162. iterator.setDirCacheIterator(null, -1);
  163. tw.reset();
  164. tw.addTree(iterator);
  165. tw.setFilter(PathFilter.create(path));
  166. current = path;
  167. if (!tw.next())
  168. throw new FileNotFoundException(path);
  169. ptr = tw.getTree(0, WorkingTreeIterator.class);
  170. if (ptr == null)
  171. throw new FileNotFoundException(path);
  172. }
  173. }
  174. }
  175. /** A pair of sources to access the old and new sides of a DiffEntry. */
  176. public static final class Pair {
  177. private final ContentSource oldSource;
  178. private final ContentSource newSource;
  179. /**
  180. * Construct a pair of sources.
  181. *
  182. * @param oldSource
  183. * source to read the old side of a DiffEntry.
  184. * @param newSource
  185. * source to read the new side of a DiffEntry.
  186. */
  187. public Pair(ContentSource oldSource, ContentSource newSource) {
  188. this.oldSource = oldSource;
  189. this.newSource = newSource;
  190. }
  191. /**
  192. * Determine the size of the object.
  193. *
  194. * @param side
  195. * which side of the entry to read (OLD or NEW).
  196. * @param ent
  197. * the entry to examine.
  198. * @return the size in bytes.
  199. * @throws IOException
  200. * the file cannot be accessed.
  201. */
  202. public long size(DiffEntry.Side side, DiffEntry ent) throws IOException {
  203. switch (side) {
  204. case OLD:
  205. return oldSource.size(ent.oldPath, ent.oldId.toObjectId());
  206. case NEW:
  207. return newSource.size(ent.newPath, ent.newId.toObjectId());
  208. default:
  209. throw new IllegalArgumentException();
  210. }
  211. }
  212. /**
  213. * Open the object.
  214. *
  215. * @param side
  216. * which side of the entry to read (OLD or NEW).
  217. * @param ent
  218. * the entry to examine.
  219. * @return a loader that can supply the content of the file. The loader
  220. * must be used before another loader can be obtained from this
  221. * same source.
  222. * @throws IOException
  223. * the file cannot be accessed.
  224. */
  225. public ObjectLoader open(DiffEntry.Side side, DiffEntry ent)
  226. throws IOException {
  227. switch (side) {
  228. case OLD:
  229. return oldSource.open(ent.oldPath, ent.oldId.toObjectId());
  230. case NEW:
  231. return newSource.open(ent.newPath, ent.newId.toObjectId());
  232. default:
  233. throw new IllegalArgumentException();
  234. }
  235. }
  236. }
  237. }