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 }