View Javadoc
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 }