1 /*
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3 * and other copyright owners as documented in the project's IP log.
4 *
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
9 *
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
23 *
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43
44 package org.eclipse.jgit.revwalk;
45
46 import java.io.IOException;
47
48 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
49 import org.eclipse.jgit.errors.MissingObjectException;
50 import org.eclipse.jgit.revwalk.filter.RevFilter;
51
52 /**
53 * An ordered list of {@link RevCommit} subclasses.
54 *
55 * @param <E>
56 * type of subclass of RevCommit the list is storing.
57 */
58 public class RevCommitList<E extends RevCommit> extends RevObjectList<E> {
59 private RevWalk walker;
60
61 @Override
62 public void clear() {
63 super.clear();
64 walker = null;
65 }
66
67 /**
68 * Apply a flag to all commits matching the specified filter.
69 * <p>
70 * Same as <code>applyFlag(matching, flag, 0, size())</code>, but without
71 * the incremental behavior.
72 *
73 * @param matching
74 * the filter to test commits with. If the filter includes a
75 * commit it will have the flag set; if the filter does not
76 * include the commit the flag will be unset.
77 * @param flag
78 * the flag to apply (or remove). Applications are responsible
79 * for allocating this flag from the source RevWalk.
80 * @throws IOException
81 * revision filter needed to read additional objects, but an
82 * error occurred while reading the pack files or loose objects
83 * of the repository.
84 * @throws IncorrectObjectTypeException
85 * revision filter needed to read additional objects, but an
86 * object was not of the correct type. Repository corruption may
87 * have occurred.
88 * @throws MissingObjectException
89 * revision filter needed to read additional objects, but an
90 * object that should be present was not found. Repository
91 * corruption may have occurred.
92 */
93 public void applyFlag(final RevFilter matching, final RevFlag flag)
94 throws MissingObjectException, IncorrectObjectTypeException,
95 IOException {
96 applyFlag(matching, flag, 0, size());
97 }
98
99 /**
100 * Apply a flag to all commits matching the specified filter.
101 * <p>
102 * This version allows incremental testing and application, such as from a
103 * background thread that needs to periodically halt processing and send
104 * updates to the UI.
105 *
106 * @param matching
107 * the filter to test commits with. If the filter includes a
108 * commit it will have the flag set; if the filter does not
109 * include the commit the flag will be unset.
110 * @param flag
111 * the flag to apply (or remove). Applications are responsible
112 * for allocating this flag from the source RevWalk.
113 * @param rangeBegin
114 * first commit within the list to begin testing at, inclusive.
115 * Must not be negative, but may be beyond the end of the list.
116 * @param rangeEnd
117 * last commit within the list to end testing at, exclusive. If
118 * smaller than or equal to <code>rangeBegin</code> then no
119 * commits will be tested.
120 * @throws IOException
121 * revision filter needed to read additional objects, but an
122 * error occurred while reading the pack files or loose objects
123 * of the repository.
124 * @throws IncorrectObjectTypeException
125 * revision filter needed to read additional objects, but an
126 * object was not of the correct type. Repository corruption may
127 * have occurred.
128 * @throws MissingObjectException
129 * revision filter needed to read additional objects, but an
130 * object that should be present was not found. Repository
131 * corruption may have occurred.
132 */
133 public void applyFlag(final RevFilter matching, final RevFlag flag,
134 int rangeBegin, int rangeEnd) throws MissingObjectException,
135 IncorrectObjectTypeException, IOException {
136 final RevWalk w = flag.getRevWalk();
137 rangeEnd = Math.min(rangeEnd, size());
138 while (rangeBegin < rangeEnd) {
139 int index = rangeBegin;
140 Block s = contents;
141 while (s.shift > 0) {
142 final int i = index >> s.shift;
143 index -= i << s.shift;
144 s = (Block) s.contents[i];
145 }
146
147 while (rangeBegin++ < rangeEnd && index < BLOCK_SIZE) {
148 final RevCommit c = (RevCommit) s.contents[index++];
149 if (matching.include(w, c))
150 c.add(flag);
151 else
152 c.remove(flag);
153 }
154 }
155 }
156
157 /**
158 * Remove the given flag from all commits.
159 * <p>
160 * Same as <code>clearFlag(flag, 0, size())</code>, but without the
161 * incremental behavior.
162 *
163 * @param flag
164 * the flag to remove. Applications are responsible for
165 * allocating this flag from the source RevWalk.
166 */
167 public void clearFlag(final RevFlag flag) {
168 clearFlag(flag, 0, size());
169 }
170
171 /**
172 * Remove the given flag from all commits.
173 * <p>
174 * This method is actually implemented in terms of:
175 * <code>applyFlag(RevFilter.NONE, flag, rangeBegin, rangeEnd)</code>.
176 *
177 * @param flag
178 * the flag to remove. Applications are responsible for
179 * allocating this flag from the source RevWalk.
180 * @param rangeBegin
181 * first commit within the list to begin testing at, inclusive.
182 * Must not be negative, but may be beyond the end of the list.
183 * @param rangeEnd
184 * last commit within the list to end testing at, exclusive. If
185 * smaller than or equal to <code>rangeBegin</code> then no
186 * commits will be tested.
187 */
188 public void clearFlag(final RevFlag flag, final int rangeBegin,
189 final int rangeEnd) {
190 try {
191 applyFlag(RevFilter.NONE, flag, rangeBegin, rangeEnd);
192 } catch (IOException e) {
193 // Never happen. The filter we use does not throw any
194 // exceptions, for any reason.
195 }
196 }
197
198 /**
199 * Find the next commit that has the given flag set.
200 *
201 * @param flag
202 * the flag to test commits against.
203 * @param begin
204 * first commit index to test at. Applications may wish to begin
205 * at 0, to test the first commit in the list.
206 * @return index of the first commit at or after index <code>begin</code>
207 * that has the specified flag set on it; -1 if no match is found.
208 */
209 public int indexOf(final RevFlag flag, int begin) {
210 while (begin < size()) {
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++ < size() && index < BLOCK_SIZE) {
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 * Find the next commit that has the given flag set.
230 *
231 * @param flag
232 * the flag to test commits against.
233 * @param begin
234 * first commit index to test at. Applications may wish to begin
235 * at <code>size()-1</code>, to test the last commit in the
236 * list.
237 * @return index of the first commit at or before index <code>begin</code>
238 * that has the specified flag set on it; -1 if no match is found.
239 */
240 public int lastIndexOf(final RevFlag flag, int begin) {
241 begin = Math.min(begin, size() - 1);
242 while (begin >= 0) {
243 int index = begin;
244 Block s = contents;
245 while (s.shift > 0) {
246 final int i = index >> s.shift;
247 index -= i << s.shift;
248 s = (Block) s.contents[i];
249 }
250
251 while (begin-- >= 0 && index >= 0) {
252 final RevCommit c = (RevCommit) s.contents[index--];
253 if (c.has(flag))
254 return begin;
255 }
256 }
257 return -1;
258 }
259
260 /**
261 * Set the revision walker this list populates itself from.
262 *
263 * @param w
264 * the walker to populate from.
265 * @see #fillTo(int)
266 */
267 public void source(final RevWalk w) {
268 walker = w;
269 }
270
271 /**
272 * Is this list still pending more items?
273 *
274 * @return true if {@link #fillTo(int)} might be able to extend the list
275 * size when called.
276 */
277 public boolean isPending() {
278 return walker != null;
279 }
280
281 /**
282 * Ensure this list contains at least a specified number of commits.
283 * <p>
284 * The revision walker specified by {@link #source(RevWalk)} is pumped until
285 * the given number of commits are contained in this list. If there are
286 * fewer total commits available from the walk then the method will return
287 * early. Callers can test the final size of the list by {@link #size()} to
288 * determine if the high water mark specified was met.
289 *
290 * @param highMark
291 * number of commits the caller wants this list to contain when
292 * the fill operation is complete.
293 * @throws IOException
294 * see {@link RevWalk#next()}
295 * @throws IncorrectObjectTypeException
296 * see {@link RevWalk#next()}
297 * @throws MissingObjectException
298 * see {@link RevWalk#next()}
299 */
300 @SuppressWarnings("unchecked")
301 public void fillTo(final int highMark) throws MissingObjectException,
302 IncorrectObjectTypeException, IOException {
303 if (walker == null || size > highMark)
304 return;
305
306 RevCommit c = walker.next();
307 if (c == null) {
308 walker = null;
309 return;
310 }
311 enter(size, (E) c);
312 add((E) c);
313
314 while (size <= highMark) {
315 int index = size;
316 Block s = contents;
317 while (index >> s.shift >= BLOCK_SIZE) {
318 s = new Block(s.shift + BLOCK_SHIFT);
319 s.contents[0] = contents;
320 contents = s;
321 }
322 while (s.shift > 0) {
323 final int i = index >> s.shift;
324 index -= i << s.shift;
325 if (s.contents[i] == null)
326 s.contents[i] = new Block(s.shift - BLOCK_SHIFT);
327 s = (Block) s.contents[i];
328 }
329
330 final Object[] dst = s.contents;
331 while (size <= highMark && index < BLOCK_SIZE) {
332 c = walker.next();
333 if (c == null) {
334 walker = null;
335 return;
336 }
337 enter(size++, (E) c);
338 dst[index++] = c;
339 }
340 }
341 }
342
343 /**
344 * Ensures all commits until the given commit are loaded. The revision
345 * walker specified by {@link #source(RevWalk)} is pumped until the
346 * specified commit is loaded. Callers can test the final size of the list
347 * by {@link #size()} to determine if the high water mark specified was met.
348 * <p>
349 *
350 * @param commitToLoad
351 * commit the caller wants this list to contain when the fill
352 * operation is complete.
353 * @param highMark
354 * maximum number of commits the caller wants this list to
355 * contain when the fill operation is complete. If highMark is 0
356 * the walk is pumped until the specified commit or the end of
357 * the walk is reached.
358 * @throws IOException
359 * see {@link RevWalk#next()}
360 * @throws IncorrectObjectTypeException
361 * see {@link RevWalk#next()}
362 * @throws MissingObjectException
363 * see {@link RevWalk#next()}
364 */
365 @SuppressWarnings("unchecked")
366 public void fillTo(final RevCommit commitToLoad, int highMark)
367 throws MissingObjectException, IncorrectObjectTypeException,
368 IOException {
369 if (walker == null || commitToLoad == null
370 || (highMark > 0 && size > highMark))
371 return;
372
373 RevCommit c = walker.next();
374 if (c == null) {
375 walker = null;
376 return;
377 }
378 enter(size, (E) c);
379 add((E) c);
380
381 while ((highMark == 0 || size <= highMark) && !c.equals(commitToLoad)) {
382 int index = size;
383 Block s = contents;
384 while (index >> s.shift >= BLOCK_SIZE) {
385 s = new Block(s.shift + BLOCK_SHIFT);
386 s.contents[0] = contents;
387 contents = s;
388 }
389 while (s.shift > 0) {
390 final int i = index >> s.shift;
391 index -= i << s.shift;
392 if (s.contents[i] == null)
393 s.contents[i] = new Block(s.shift - BLOCK_SHIFT);
394 s = (Block) s.contents[i];
395 }
396
397 final Object[] dst = s.contents;
398 while ((highMark == 0 || size <= highMark) && index < BLOCK_SIZE
399 && !c.equals(commitToLoad)) {
400 c = walker.next();
401 if (c == null) {
402 walker = null;
403 return;
404 }
405 enter(size++, (E) c);
406 dst[index++] = c;
407 }
408 }
409 }
410
411 /**
412 * Optional callback invoked when commits enter the list by fillTo.
413 * <p>
414 * This method is only called during {@link #fillTo(int)}.
415 *
416 * @param index
417 * the list position this object will appear at.
418 * @param e
419 * the object being added (or set) into the list.
420 */
421 protected void enter(final int index, final E e) {
422 // Do nothing by default.
423 }
424 }