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 }