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 }