View Javadoc
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  
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  	private static class ObjectReaderSource extends ContentSource {
95  		private final ObjectReader reader;
96  
97  		ObjectReaderSource(ObjectReader reader) {
98  			this.reader = reader;
99  		}
100 
101 		@Override
102 		public long size(String path, ObjectId id) throws IOException {
103 			try {
104 				return reader.getObjectSize(id, Constants.OBJ_BLOB);
105 			} catch (MissingObjectException ignore) {
106 				return 0;
107 			}
108 		}
109 
110 		@Override
111 		public ObjectLoader open(String path, ObjectId id) throws IOException {
112 			return reader.open(id, Constants.OBJ_BLOB);
113 		}
114 	}
115 
116 	private static class WorkingTreeSource extends ContentSource {
117 		private final TreeWalk tw;
118 
119 		private final WorkingTreeIterator iterator;
120 
121 		private String current;
122 
123 		WorkingTreeIterator ptr;
124 
125 		WorkingTreeSource(WorkingTreeIterator iterator) {
126 			this.tw = new TreeWalk(iterator.getRepository(),
127 					(ObjectReader) null);
128 			this.tw.setRecursive(true);
129 			this.iterator = iterator;
130 		}
131 
132 		@Override
133 		public long size(String path, ObjectId id) throws IOException {
134 			seek(path);
135 			return ptr.getEntryLength();
136 		}
137 
138 		@Override
139 		public ObjectLoader open(String path, ObjectId id) throws IOException {
140 			seek(path);
141 			long entrySize = ptr.getEntryContentLength();
142 			return new ObjectLoader() {
143 				@Override
144 				public long getSize() {
145 					return entrySize;
146 				}
147 
148 				@Override
149 				public int getType() {
150 					return ptr.getEntryFileMode().getObjectType();
151 				}
152 
153 				@Override
154 				public ObjectStream openStream() throws MissingObjectException,
155 						IOException {
156 					long contentLength = entrySize;
157 					InputStream in = ptr.openEntryStream();
158 					in = new BufferedInputStream(in);
159 					return new ObjectStream.Filter(getType(), contentLength, in);
160 				}
161 
162 				@Override
163 				public boolean isLarge() {
164 					return true;
165 				}
166 
167 				@Override
168 				public byte[] getCachedBytes() throws LargeObjectException {
169 					throw new LargeObjectException();
170 				}
171 			};
172 		}
173 
174 		private void seek(String path) throws IOException {
175 			if (!path.equals(current)) {
176 				iterator.reset();
177 				// Possibly this iterator had an associated DirCacheIterator,
178 				// but we have no access to it and thus don't know about it.
179 				// We have to reset this iterator here to work without
180 				// DirCacheIterator and to descend always into ignored
181 				// directories. Otherwise we might not find tracked files below
182 				// ignored folders. Since we're looking only for a single
183 				// specific path this is not a performance problem.
184 				iterator.setWalkIgnoredDirectories(true);
185 				iterator.setDirCacheIterator(null, -1);
186 				tw.reset();
187 				tw.addTree(iterator);
188 				tw.setFilter(PathFilter.create(path));
189 				current = path;
190 				if (!tw.next())
191 					throw new FileNotFoundException(path);
192 				ptr = tw.getTree(0, WorkingTreeIterator.class);
193 				if (ptr == null)
194 					throw new FileNotFoundException(path);
195 			}
196 		}
197 	}
198 
199 	/** A pair of sources to access the old and new sides of a DiffEntry. */
200 	public static final class Pair {
201 		private final ContentSource oldSource;
202 
203 		private final ContentSource newSource;
204 
205 		/**
206 		 * Construct a pair of sources.
207 		 *
208 		 * @param oldSource
209 		 *            source to read the old side of a DiffEntry.
210 		 * @param newSource
211 		 *            source to read the new side of a DiffEntry.
212 		 */
213 		public Pair(ContentSource/../org/eclipse/jgit/diff/ContentSource.html#ContentSource">ContentSource oldSource, ContentSource newSource) {
214 			this.oldSource = oldSource;
215 			this.newSource = newSource;
216 		}
217 
218 		/**
219 		 * Determine the size of the object.
220 		 *
221 		 * @param side
222 		 *            which side of the entry to read (OLD or NEW).
223 		 * @param ent
224 		 *            the entry to examine.
225 		 * @return the size in bytes.
226 		 * @throws IOException
227 		 *             the file cannot be accessed.
228 		 */
229 		public long size(DiffEntry.Side side, DiffEntry ent) throws IOException {
230 			switch (side) {
231 			case OLD:
232 				return oldSource.size(ent.oldPath, ent.oldId.toObjectId());
233 			case NEW:
234 				return newSource.size(ent.newPath, ent.newId.toObjectId());
235 			default:
236 				throw new IllegalArgumentException();
237 			}
238 		}
239 
240 		/**
241 		 * Open the object.
242 		 *
243 		 * @param side
244 		 *            which side of the entry to read (OLD or NEW).
245 		 * @param ent
246 		 *            the entry to examine.
247 		 * @return a loader that can supply the content of the file. The loader
248 		 *         must be used before another loader can be obtained from this
249 		 *         same source.
250 		 * @throws IOException
251 		 *             the file cannot be accessed.
252 		 */
253 		public ObjectLoader open(DiffEntry.Side side, DiffEntry ent)
254 				throws IOException {
255 			switch (side) {
256 			case OLD:
257 				return oldSource.open(ent.oldPath, ent.oldId.toObjectId());
258 			case NEW:
259 				return newSource.open(ent.newPath, ent.newId.toObjectId());
260 			default:
261 				throw new IllegalArgumentException();
262 			}
263 		}
264 	}
265 }