View Javadoc
1   /*
2    * Copyright (C) 2010, 2021 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  
11  package org.eclipse.jgit.diff;
12  
13  import java.io.BufferedInputStream;
14  import java.io.FileNotFoundException;
15  import java.io.IOException;
16  import java.io.InputStream;
17  
18  import org.eclipse.jgit.errors.LargeObjectException;
19  import org.eclipse.jgit.errors.MissingObjectException;
20  import org.eclipse.jgit.lib.Constants;
21  import org.eclipse.jgit.lib.ObjectId;
22  import org.eclipse.jgit.lib.ObjectLoader;
23  import org.eclipse.jgit.lib.ObjectReader;
24  import org.eclipse.jgit.lib.ObjectStream;
25  import org.eclipse.jgit.treewalk.TreeWalk;
26  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
27  import org.eclipse.jgit.treewalk.filter.PathFilter;
28  
29  /**
30   * Supplies the content of a file for
31   * {@link org.eclipse.jgit.diff.DiffFormatter}.
32   * <p>
33   * A content source is not thread-safe. Sources may contain state, including
34   * information about the last ObjectLoader they returned. Callers must be
35   * careful to ensure there is no more than one ObjectLoader pending on any
36   * source, at any time.
37   */
38  public abstract class ContentSource {
39  	/**
40  	 * Construct a content source for an ObjectReader.
41  	 *
42  	 * @param reader
43  	 *            the reader to obtain blobs from.
44  	 * @return a source wrapping the reader.
45  	 */
46  	public static ContentSource create(ObjectReader reader) {
47  		return new ObjectReaderSource(reader);
48  	}
49  
50  	/**
51  	 * Construct a content source for a working directory.
52  	 *
53  	 * If the iterator is a {@link org.eclipse.jgit.treewalk.FileTreeIterator}
54  	 * an optimized version is used that doesn't require seeking through a
55  	 * TreeWalk.
56  	 *
57  	 * @param iterator
58  	 *            the iterator to obtain source files through.
59  	 * @return a content source wrapping the iterator.
60  	 */
61  	public static ContentSource create(WorkingTreeIterator iterator) {
62  		return new WorkingTreeSource(iterator);
63  	}
64  
65  	/**
66  	 * Determine the size of the object.
67  	 *
68  	 * @param path
69  	 *            the path of the file, relative to the root of the repository.
70  	 * @param id
71  	 *            blob id of the file, if known.
72  	 * @return the size in bytes.
73  	 * @throws java.io.IOException
74  	 *             the file cannot be accessed.
75  	 */
76  	public abstract long size(String path, ObjectId id) throws IOException;
77  
78  	/**
79  	 * Open the object.
80  	 *
81  	 * @param path
82  	 *            the path of the file, relative to the root of the repository.
83  	 * @param id
84  	 *            blob id of the file, if known.
85  	 * @return a loader that can supply the content of the file. The loader must
86  	 *         be used before another loader can be obtained from this same
87  	 *         source.
88  	 * @throws java.io.IOException
89  	 *             the file cannot be accessed.
90  	 */
91  	public abstract ObjectLoader open(String path, ObjectId id)
92  			throws IOException;
93  
94  	/**
95  	 * Closes the used resources like ObjectReader, TreeWalk etc. Default
96  	 * implementation does nothing.
97  	 *
98  	 * @since 6.2
99  	 */
100 	public void close() {
101 		// Do nothing
102 	}
103 
104 	/**
105 	 * Checks if the source is from "working tree", so it can be accessed as a
106 	 * file directly.
107 	 *
108 	 * @since 6.2
109 	 *
110 	 * @return true if working tree source and false otherwise (loader must be
111 	 *         used)
112 	 */
113 	public boolean isWorkingTreeSource() {
114 		return false;
115 	}
116 
117 	private static class ObjectReaderSource extends ContentSource {
118 		private final ObjectReader reader;
119 
120 		ObjectReaderSource(ObjectReader reader) {
121 			this.reader = reader;
122 		}
123 
124 		@Override
125 		public long size(String path, ObjectId id) throws IOException {
126 			try {
127 				return reader.getObjectSize(id, Constants.OBJ_BLOB);
128 			} catch (MissingObjectException ignore) {
129 				return 0;
130 			}
131 		}
132 
133 		@Override
134 		public ObjectLoader open(String path, ObjectId id) throws IOException {
135 			return reader.open(id, Constants.OBJ_BLOB);
136 		}
137 
138 		@Override
139 		public void close() {
140 			reader.close();
141 		}
142 
143 		@Override
144 		public boolean isWorkingTreeSource() {
145 			return false;
146 		}
147 	}
148 
149 	private static class WorkingTreeSource extends ContentSource {
150 		private final TreeWalk tw;
151 
152 		private final WorkingTreeIterator iterator;
153 
154 		private String current;
155 
156 		WorkingTreeIterator ptr;
157 
158 		WorkingTreeSource(WorkingTreeIterator iterator) {
159 			this.tw = new TreeWalk(iterator.getRepository(),
160 					(ObjectReader) null);
161 			this.tw.setRecursive(true);
162 			this.iterator = iterator;
163 		}
164 
165 		@Override
166 		public long size(String path, ObjectId id) throws IOException {
167 			seek(path);
168 			return ptr.getEntryLength();
169 		}
170 
171 		@Override
172 		public ObjectLoader open(String path, ObjectId id) throws IOException {
173 			seek(path);
174 			long entrySize = ptr.getEntryContentLength();
175 			return new ObjectLoader() {
176 				@Override
177 				public long getSize() {
178 					return entrySize;
179 				}
180 
181 				@Override
182 				public int getType() {
183 					return ptr.getEntryFileMode().getObjectType();
184 				}
185 
186 				@Override
187 				public ObjectStream openStream() throws MissingObjectException,
188 						IOException {
189 					long contentLength = entrySize;
190 					InputStream in = ptr.openEntryStream();
191 					in = new BufferedInputStream(in);
192 					return new ObjectStream.Filter(getType(), contentLength, in);
193 				}
194 
195 				@Override
196 				public boolean isLarge() {
197 					return true;
198 				}
199 
200 				@Override
201 				public byte[] getCachedBytes() throws LargeObjectException {
202 					throw new LargeObjectException();
203 				}
204 			};
205 		}
206 
207 		private void seek(String path) throws IOException {
208 			if (!path.equals(current)) {
209 				iterator.reset();
210 				// Possibly this iterator had an associated DirCacheIterator,
211 				// but we have no access to it and thus don't know about it.
212 				// We have to reset this iterator here to work without
213 				// DirCacheIterator and to descend always into ignored
214 				// directories. Otherwise we might not find tracked files below
215 				// ignored folders. Since we're looking only for a single
216 				// specific path this is not a performance problem.
217 				iterator.setWalkIgnoredDirectories(true);
218 				iterator.setDirCacheIterator(null, -1);
219 				tw.reset();
220 				tw.addTree(iterator);
221 				tw.setFilter(PathFilter.create(path));
222 				current = path;
223 				if (!tw.next())
224 					throw new FileNotFoundException(path);
225 				ptr = tw.getTree(0, WorkingTreeIterator.class);
226 				if (ptr == null)
227 					throw new FileNotFoundException(path);
228 			}
229 		}
230 
231 		@Override
232 		public void close() {
233 			tw.close();
234 		}
235 
236 		@Override
237 		public boolean isWorkingTreeSource() {
238 			return true;
239 		}
240 	}
241 
242 	/** A pair of sources to access the old and new sides of a DiffEntry. */
243 	public static final class Pair {
244 		private final ContentSource oldSource;
245 
246 		private final ContentSource newSource;
247 
248 		/**
249 		 * Construct a pair of sources.
250 		 *
251 		 * @param oldSource
252 		 *            source to read the old side of a DiffEntry.
253 		 * @param newSource
254 		 *            source to read the new side of a DiffEntry.
255 		 */
256 		public Pair(ContentSource oldSource, ContentSource newSource) {
257 			this.oldSource = oldSource;
258 			this.newSource = newSource;
259 		}
260 
261 		/**
262 		 * Determine the size of the object.
263 		 *
264 		 * @param side
265 		 *            which side of the entry to read (OLD or NEW).
266 		 * @param ent
267 		 *            the entry to examine.
268 		 * @return the size in bytes.
269 		 * @throws IOException
270 		 *             the file cannot be accessed.
271 		 */
272 		public long size(DiffEntry.Side side, DiffEntry ent) throws IOException {
273 			switch (side) {
274 			case OLD:
275 				return oldSource.size(ent.oldPath, ent.oldId.toObjectId());
276 			case NEW:
277 				return newSource.size(ent.newPath, ent.newId.toObjectId());
278 			default:
279 				throw new IllegalArgumentException();
280 			}
281 		}
282 
283 		/**
284 		 * Open the object.
285 		 *
286 		 * @param side
287 		 *            which side of the entry to read (OLD or NEW).
288 		 * @param ent
289 		 *            the entry to examine.
290 		 * @return a loader that can supply the content of the file. The loader
291 		 *         must be used before another loader can be obtained from this
292 		 *         same source.
293 		 * @throws IOException
294 		 *             the file cannot be accessed.
295 		 */
296 		public ObjectLoader open(DiffEntry.Side side, DiffEntry ent)
297 				throws IOException {
298 			switch (side) {
299 			case OLD:
300 				return oldSource.open(ent.oldPath, ent.oldId.toObjectId());
301 			case NEW:
302 				return newSource.open(ent.newPath, ent.newId.toObjectId());
303 			default:
304 				throw new IllegalArgumentException();
305 			}
306 		}
307 
308 		/**
309 		 * Closes used resources.
310 		 *
311 		 * @since 6.2
312 		 */
313 		public void close() {
314 			oldSource.close();
315 			newSource.close();
316 		}
317 
318 		/**
319 		 * Checks if source (side) is a "working tree".
320 		 *
321 		 * @since 6.2
322 		 *
323 		 * @param side
324 		 *            which side of the entry to read (OLD or NEW).
325 		 * @return is the source a "working tree"
326 		 *
327 		 */
328 		public boolean isWorkingTreeSource(DiffEntry.Side side) {
329 			switch (side) {
330 			case OLD:
331 				return oldSource.isWorkingTreeSource();
332 			case NEW:
333 				return newSource.isWorkingTreeSource();
334 			default:
335 				throw new IllegalArgumentException();
336 			}
337 		}
338 
339 	}
340 }