View Javadoc
1   /*
2    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 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.revwalk;
12  
13  import java.io.IOException;
14  
15  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
16  import org.eclipse.jgit.errors.MissingObjectException;
17  import org.eclipse.jgit.revwalk.filter.RevFilter;
18  
19  /**
20   * An ordered list of {@link org.eclipse.jgit.revwalk.RevCommit} subclasses.
21   *
22   * @param <E>
23   *            type of subclass of RevCommit the list is storing.
24   */
25  public class RevCommitList<E extends RevCommit> extends RevObjectList<E> {
26  	private RevWalk walker;
27  
28  	/** {@inheritDoc} */
29  	@Override
30  	public void clear() {
31  		super.clear();
32  		walker = null;
33  	}
34  
35  	/**
36  	 * Apply a flag to all commits matching the specified filter.
37  	 * <p>
38  	 * Same as <code>applyFlag(matching, flag, 0, size())</code>, but without
39  	 * the incremental behavior.
40  	 *
41  	 * @param matching
42  	 *            the filter to test commits with. If the filter includes a
43  	 *            commit it will have the flag set; if the filter does not
44  	 *            include the commit the flag will be unset.
45  	 * @param flag
46  	 *            the flag to apply (or remove). Applications are responsible
47  	 *            for allocating this flag from the source RevWalk.
48  	 * @throws java.io.IOException
49  	 *             revision filter needed to read additional objects, but an
50  	 *             error occurred while reading the pack files or loose objects
51  	 *             of the repository.
52  	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
53  	 *             revision filter needed to read additional objects, but an
54  	 *             object was not of the correct type. Repository corruption may
55  	 *             have occurred.
56  	 * @throws org.eclipse.jgit.errors.MissingObjectException
57  	 *             revision filter needed to read additional objects, but an
58  	 *             object that should be present was not found. Repository
59  	 *             corruption may have occurred.
60  	 */
61  	public void applyFlag(RevFilter matching, RevFlag flag)
62  			throws MissingObjectException, IncorrectObjectTypeException,
63  			IOException {
64  		applyFlag(matching, flag, 0, size());
65  	}
66  
67  	/**
68  	 * Apply a flag to all commits matching the specified filter.
69  	 * <p>
70  	 * This version allows incremental testing and application, such as from a
71  	 * background thread that needs to periodically halt processing and send
72  	 * updates to the UI.
73  	 *
74  	 * @param matching
75  	 *            the filter to test commits with. If the filter includes a
76  	 *            commit it will have the flag set; if the filter does not
77  	 *            include the commit the flag will be unset.
78  	 * @param flag
79  	 *            the flag to apply (or remove). Applications are responsible
80  	 *            for allocating this flag from the source RevWalk.
81  	 * @param rangeBegin
82  	 *            first commit within the list to begin testing at, inclusive.
83  	 *            Must not be negative, but may be beyond the end of the list.
84  	 * @param rangeEnd
85  	 *            last commit within the list to end testing at, exclusive. If
86  	 *            smaller than or equal to <code>rangeBegin</code> then no
87  	 *            commits will be tested.
88  	 * @throws java.io.IOException
89  	 *             revision filter needed to read additional objects, but an
90  	 *             error occurred while reading the pack files or loose objects
91  	 *             of the repository.
92  	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
93  	 *             revision filter needed to read additional objects, but an
94  	 *             object was not of the correct type. Repository corruption may
95  	 *             have occurred.
96  	 * @throws org.eclipse.jgit.errors.MissingObjectException
97  	 *             revision filter needed to read additional objects, but an
98  	 *             object that should be present was not found. Repository
99  	 *             corruption may have occurred.
100 	 */
101 	public void applyFlag(final RevFilter matching, final RevFlag flag,
102 			int rangeBegin, int rangeEnd) throws MissingObjectException,
103 			IncorrectObjectTypeException, IOException {
104 		final RevWalk w = flag.getRevWalk();
105 		rangeEnd = Math.min(rangeEnd, size());
106 		while (rangeBegin < rangeEnd) {
107 			int index = rangeBegin;
108 			Block s = contents;
109 			while (s.shift > 0) {
110 				final int i = index >> s.shift;
111 				index -= i << s.shift;
112 				s = (Block) s.contents[i];
113 			}
114 
115 			while (rangeBegin++ < rangeEnd && index < BLOCK_SIZE) {
116 				final RevCommit c = (RevCommit) s.contents[index++];
117 				if (matching.include(w, c))
118 					c.add(flag);
119 				else
120 					c.remove(flag);
121 			}
122 		}
123 	}
124 
125 	/**
126 	 * Remove the given flag from all commits.
127 	 * <p>
128 	 * Same as <code>clearFlag(flag, 0, size())</code>, but without the
129 	 * incremental behavior.
130 	 *
131 	 * @param flag
132 	 *            the flag to remove. Applications are responsible for
133 	 *            allocating this flag from the source RevWalk.
134 	 */
135 	public void clearFlag(RevFlag flag) {
136 		clearFlag(flag, 0, size());
137 	}
138 
139 	/**
140 	 * Remove the given flag from all commits.
141 	 * <p>
142 	 * This method is actually implemented in terms of:
143 	 * <code>applyFlag(RevFilter.NONE, flag, rangeBegin, rangeEnd)</code>.
144 	 *
145 	 * @param flag
146 	 *            the flag to remove. Applications are responsible for
147 	 *            allocating this flag from the source RevWalk.
148 	 * @param rangeBegin
149 	 *            first commit within the list to begin testing at, inclusive.
150 	 *            Must not be negative, but may be beyond the end of the list.
151 	 * @param rangeEnd
152 	 *            last commit within the list to end testing at, exclusive. If
153 	 *            smaller than or equal to <code>rangeBegin</code> then no
154 	 *            commits will be tested.
155 	 */
156 	public void clearFlag(final RevFlag flag, final int rangeBegin,
157 			final int rangeEnd) {
158 		try {
159 			applyFlag(RevFilter.NONE, flag, rangeBegin, rangeEnd);
160 		} catch (IOException e) {
161 			// Never happen. The filter we use does not throw any
162 			// exceptions, for any reason.
163 		}
164 	}
165 
166 	/**
167 	 * Find the next commit that has the given flag set.
168 	 *
169 	 * @param flag
170 	 *            the flag to test commits against.
171 	 * @param begin
172 	 *            first commit index to test at. Applications may wish to begin
173 	 *            at 0, to test the first commit in the list.
174 	 * @return index of the first commit at or after index <code>begin</code>
175 	 *         that has the specified flag set on it; -1 if no match is found.
176 	 */
177 	public int indexOf(RevFlag flag, int begin) {
178 		while (begin < size()) {
179 			int index = begin;
180 			Block s = contents;
181 			while (s.shift > 0) {
182 				final int i = index >> s.shift;
183 				index -= i << s.shift;
184 				s = (Block) s.contents[i];
185 			}
186 
187 			while (begin++ < size() && index < BLOCK_SIZE) {
188 				final RevCommit c = (RevCommit) s.contents[index++];
189 				if (c.has(flag))
190 					return begin;
191 			}
192 		}
193 		return -1;
194 	}
195 
196 	/**
197 	 * Find the next commit that has the given flag set.
198 	 *
199 	 * @param flag
200 	 *            the flag to test commits against.
201 	 * @param begin
202 	 *            first commit index to test at. Applications may wish to begin
203 	 *            at <code>size()-1</code>, to test the last commit in the
204 	 *            list.
205 	 * @return index of the first commit at or before index <code>begin</code>
206 	 *         that has the specified flag set on it; -1 if no match is found.
207 	 */
208 	public int lastIndexOf(RevFlag flag, int begin) {
209 		begin = Math.min(begin, size() - 1);
210 		while (begin >= 0) {
211 			int index = begin;
212 			Block s = contents;
213 			while (s.shift > 0) {
214 				final int i = index >> s.shift;
215 				index -= i << s.shift;
216 				s = (Block) s.contents[i];
217 			}
218 
219 			while (begin-- >= 0 && index >= 0) {
220 				final RevCommit c = (RevCommit) s.contents[index--];
221 				if (c.has(flag))
222 					return begin;
223 			}
224 		}
225 		return -1;
226 	}
227 
228 	/**
229 	 * Set the revision walker this list populates itself from.
230 	 *
231 	 * @param w
232 	 *            the walker to populate from.
233 	 * @see #fillTo(int)
234 	 */
235 	public void source(RevWalk w) {
236 		walker = w;
237 	}
238 
239 	/**
240 	 * Is this list still pending more items?
241 	 *
242 	 * @return true if {@link #fillTo(int)} might be able to extend the list
243 	 *         size when called.
244 	 */
245 	public boolean isPending() {
246 		return walker != null;
247 	}
248 
249 	/**
250 	 * Ensure this list contains at least a specified number of commits.
251 	 * <p>
252 	 * The revision walker specified by {@link #source(RevWalk)} is pumped until
253 	 * the given number of commits are contained in this list. If there are
254 	 * fewer total commits available from the walk then the method will return
255 	 * early. Callers can test the final size of the list by {@link #size()} to
256 	 * determine if the high water mark specified was met.
257 	 *
258 	 * @param highMark
259 	 *            number of commits the caller wants this list to contain when
260 	 *            the fill operation is complete.
261 	 * @throws java.io.IOException
262 	 *             see {@link org.eclipse.jgit.revwalk.RevWalk#next()}
263 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
264 	 *             see {@link org.eclipse.jgit.revwalk.RevWalk#next()}
265 	 * @throws org.eclipse.jgit.errors.MissingObjectException
266 	 *             see {@link org.eclipse.jgit.revwalk.RevWalk#next()}
267 	 */
268 	@SuppressWarnings("unchecked")
269 	public void fillTo(int highMark) throws MissingObjectException,
270 			IncorrectObjectTypeException, IOException {
271 		if (walker == null || size > highMark)
272 			return;
273 
274 		RevCommit c = walker.next();
275 		if (c == null) {
276 			walker = null;
277 			return;
278 		}
279 		enter(size, (E) c);
280 		add((E) c);
281 
282 		while (size <= highMark) {
283 			int index = size;
284 			Block s = contents;
285 			while (index >> s.shift >= BLOCK_SIZE) {
286 				s = new Block(s.shift + BLOCK_SHIFT);
287 				s.contents[0] = contents;
288 				contents = s;
289 			}
290 			while (s.shift > 0) {
291 				final int i = index >> s.shift;
292 				index -= i << s.shift;
293 				if (s.contents[i] == null)
294 					s.contents[i] = new Block(s.shift - BLOCK_SHIFT);
295 				s = (Block) s.contents[i];
296 			}
297 
298 			final Object[] dst = s.contents;
299 			while (size <= highMark && index < BLOCK_SIZE) {
300 				c = walker.next();
301 				if (c == null) {
302 					walker = null;
303 					return;
304 				}
305 				enter(size++, (E) c);
306 				dst[index++] = c;
307 			}
308 		}
309 	}
310 
311 	/**
312 	 * Ensures all commits until the given commit are loaded. The revision
313 	 * walker specified by {@link #source(RevWalk)} is pumped until the
314 	 * specified commit is loaded. Callers can test the final size of the list
315 	 * by {@link #size()} to determine if the high water mark specified was met.
316 	 * <p>
317 	 *
318 	 * @param commitToLoad
319 	 *            commit the caller wants this list to contain when the fill
320 	 *            operation is complete.
321 	 * @param highMark
322 	 *            maximum number of commits the caller wants this list to
323 	 *            contain when the fill operation is complete. If highMark is 0
324 	 *            the walk is pumped until the specified commit or the end of
325 	 *            the walk is reached.
326 	 * @throws java.io.IOException
327 	 *             see {@link org.eclipse.jgit.revwalk.RevWalk#next()}
328 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
329 	 *             see {@link org.eclipse.jgit.revwalk.RevWalk#next()}
330 	 * @throws org.eclipse.jgit.errors.MissingObjectException
331 	 *             see {@link org.eclipse.jgit.revwalk.RevWalk#next()}
332 	 */
333 	@SuppressWarnings("unchecked")
334 	public void fillTo(RevCommit commitToLoad, int highMark)
335 			throws MissingObjectException, IncorrectObjectTypeException,
336 			IOException {
337 		if (walker == null || commitToLoad == null
338 				|| (highMark > 0 && size > highMark))
339 			return;
340 
341 		RevCommit c = walker.next();
342 		if (c == null) {
343 			walker = null;
344 			return;
345 		}
346 		enter(size, (E) c);
347 		add((E) c);
348 
349 		while ((highMark == 0 || size <= highMark) && !c.equals(commitToLoad)) {
350 			int index = size;
351 			Block s = contents;
352 			while (index >> s.shift >= BLOCK_SIZE) {
353 				s = new Block(s.shift + BLOCK_SHIFT);
354 				s.contents[0] = contents;
355 				contents = s;
356 			}
357 			while (s.shift > 0) {
358 				final int i = index >> s.shift;
359 				index -= i << s.shift;
360 				if (s.contents[i] == null)
361 					s.contents[i] = new Block(s.shift - BLOCK_SHIFT);
362 				s = (Block) s.contents[i];
363 			}
364 
365 			final Object[] dst = s.contents;
366 			while ((highMark == 0 || size <= highMark) && index < BLOCK_SIZE
367 					&& !c.equals(commitToLoad)) {
368 				c = walker.next();
369 				if (c == null) {
370 					walker = null;
371 					return;
372 				}
373 				enter(size++, (E) c);
374 				dst[index++] = c;
375 			}
376 		}
377 	}
378 
379 	/**
380 	 * Optional callback invoked when commits enter the list by fillTo.
381 	 * <p>
382 	 * This method is only called during {@link #fillTo(int)}.
383 	 *
384 	 * @param index
385 	 *            the list position this object will appear at.
386 	 * @param e
387 	 *            the object being added (or set) into the list.
388 	 */
389 	protected void enter(int index, E e) {
390 		// Do nothing by default.
391 	}
392 }