View Javadoc
1   /*
2    * Copyright (C) 2012, Google Inc.
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.internal.storage.pack;
45  
46  import static org.eclipse.jgit.internal.storage.file.PackBitmapIndex.FLAG_REUSE;
47  import static org.eclipse.jgit.revwalk.RevFlag.SEEN;
48  
49  import java.io.IOException;
50  import java.util.ArrayList;
51  import java.util.Collection;
52  import java.util.Collections;
53  import java.util.Comparator;
54  import java.util.HashSet;
55  import java.util.Iterator;
56  import java.util.List;
57  import java.util.Set;
58  
59  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
60  import org.eclipse.jgit.errors.MissingObjectException;
61  import org.eclipse.jgit.internal.JGitText;
62  import org.eclipse.jgit.internal.revwalk.AddUnseenToBitmapFilter;
63  import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
64  import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap;
65  import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
66  import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
67  import org.eclipse.jgit.internal.storage.file.PackBitmapIndexRemapper;
68  import org.eclipse.jgit.lib.AnyObjectId;
69  import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
70  import org.eclipse.jgit.lib.Constants;
71  import org.eclipse.jgit.lib.ObjectId;
72  import org.eclipse.jgit.lib.ObjectReader;
73  import org.eclipse.jgit.lib.ProgressMonitor;
74  import org.eclipse.jgit.revwalk.BitmapWalker;
75  import org.eclipse.jgit.revwalk.ObjectWalk;
76  import org.eclipse.jgit.revwalk.RevCommit;
77  import org.eclipse.jgit.revwalk.RevObject;
78  import org.eclipse.jgit.revwalk.RevWalk;
79  import org.eclipse.jgit.revwalk.filter.RevFilter;
80  import org.eclipse.jgit.storage.pack.PackConfig;
81  import org.eclipse.jgit.util.BlockList;
82  import org.eclipse.jgit.util.SystemReader;
83  
84  import com.googlecode.javaewah.EWAHCompressedBitmap;
85  
86  /**
87   * Helper class for the {@link PackWriter} to select commits for which to build
88   * pack index bitmaps.
89   */
90  class PackWriterBitmapPreparer {
91  
92  	private static final int DAY_IN_SECONDS = 24 * 60 * 60;
93  
94  	private static final Comparator<RevCommit> ORDER_BY_REVERSE_TIMESTAMP = new Comparator<RevCommit>() {
95  		@Override
96  		public int compare(RevCommit a, RevCommit b) {
97  			return Integer.signum(b.getCommitTime() - a.getCommitTime());
98  		}
99  	};
100 
101 	private final ObjectReader reader;
102 	private final ProgressMonitor pm;
103 	private final Set<? extends ObjectId> want;
104 	private final PackBitmapIndexBuilder writeBitmaps;
105 	private final BitmapIndexImpl commitBitmapIndex;
106 	private final PackBitmapIndexRemapper bitmapRemapper;
107 	private final BitmapIndexImpl bitmapIndex;
108 
109 	private final int contiguousCommitCount;
110 	private final int recentCommitCount;
111 	private final int recentCommitSpan;
112 	private final int distantCommitSpan;
113 	private final int excessiveBranchCount;
114 	private final long inactiveBranchTimestamp;
115 
116 	PackWriterBitmapPreparer(ObjectReader reader,
117 			PackBitmapIndexBuilder writeBitmaps, ProgressMonitor pm,
118 			Set<? extends ObjectId> want, PackConfig config)
119 					throws IOException {
120 		this.reader = reader;
121 		this.writeBitmaps = writeBitmaps;
122 		this.pm = pm;
123 		this.want = want;
124 		this.commitBitmapIndex = new BitmapIndexImpl(writeBitmaps);
125 		this.bitmapRemapper = PackBitmapIndexRemapper.newPackBitmapIndex(
126 				reader.getBitmapIndex(), writeBitmaps);
127 		this.bitmapIndex = new BitmapIndexImpl(bitmapRemapper);
128 		this.contiguousCommitCount = config.getBitmapContiguousCommitCount();
129 		this.recentCommitCount = config.getBitmapRecentCommitCount();
130 		this.recentCommitSpan = config.getBitmapRecentCommitSpan();
131 		this.distantCommitSpan = config.getBitmapDistantCommitSpan();
132 		this.excessiveBranchCount = config.getBitmapExcessiveBranchCount();
133 		long now = SystemReader.getInstance().getCurrentTime();
134 		long ageInSeconds = config.getBitmapInactiveBranchAgeInDays()
135 				* DAY_IN_SECONDS;
136 		this.inactiveBranchTimestamp = (now / 1000) - ageInSeconds;
137 	}
138 
139 	/**
140 	 * Returns the commit objects for which bitmap indices should be built.
141 	 *
142 	 * @param expectedCommitCount
143 	 *            count of commits in the pack
144 	 * @param excludeFromBitmapSelection
145 	 *            commits that should be excluded from bitmap selection
146 	 * @return commit objects for which bitmap indices should be built
147 	 * @throws IncorrectObjectTypeException
148 	 *             if any of the processed objects is not a commit
149 	 * @throws IOException
150 	 *             on errors reading pack or index files
151 	 * @throws MissingObjectException
152 	 *             if an expected object is missing
153 	 */
154 	Collection<BitmapCommit> selectCommits(int expectedCommitCount,
155 			Set<? extends ObjectId> excludeFromBitmapSelection)
156 			throws IncorrectObjectTypeException, IOException,
157 			MissingObjectException {
158 		/*
159 		 * Thinking of bitmap indices as a cache, if we find bitmaps at or at a
160 		 * close ancestor to 'old' and 'new' when calculating old..new, then all
161 		 * objects can be calculated with minimal graph walking. A distribution
162 		 * that favors creating bitmaps for the most recent commits maximizes
163 		 * the cache hits for clients that are close to HEAD, which is the
164 		 * majority of calculations performed.
165 		 */
166 		try (RevWalk rw = new RevWalk(reader);
167 				RevWalk rw2 = new RevWalk(reader)) {
168 			pm.beginTask(JGitText.get().selectingCommits,
169 					ProgressMonitor.UNKNOWN);
170 			rw.setRetainBody(false);
171 			CommitSelectionHelper selectionHelper = captureOldAndNewCommits(rw,
172 					expectedCommitCount, excludeFromBitmapSelection);
173 			pm.endTask();
174 
175 			// Add reused bitmaps from the previous GC pack's bitmap indices.
176 			// Currently they are always fully reused, even if their spans don't
177 			// match this run's PackConfig values.
178 			int newCommits = selectionHelper.getCommitCount();
179 			BlockList<BitmapCommit> selections = new BlockList<>(
180 					selectionHelper.reusedCommits.size()
181 							+ newCommits / recentCommitSpan + 1);
182 			for (BitmapCommit reuse : selectionHelper.reusedCommits) {
183 				selections.add(reuse);
184 			}
185 
186 			if (newCommits == 0) {
187 				for (AnyObjectId id : selectionHelper.newWants) {
188 					selections.add(new BitmapCommit(id, false, 0));
189 				}
190 				return selections;
191 			}
192 
193 			pm.beginTask(JGitText.get().selectingCommits, newCommits);
194 			int totalWants = want.size();
195 			BitmapBuilder seen = commitBitmapIndex.newBitmapBuilder();
196 			seen.or(selectionHelper.reusedCommitsBitmap);
197 			rw2.setRetainBody(false);
198 			rw2.setRevFilter(new NotInBitmapFilter(seen));
199 
200 			// For each branch, do a revwalk to enumerate its commits. Exclude
201 			// both reused commits and any commits seen in a previous branch.
202 			// Then iterate through all new commits from oldest to newest,
203 			// selecting well-spaced commits in this branch.
204 			for (RevCommit rc : selectionHelper.newWantsByNewest) {
205 				BitmapBuilder tipBitmap = commitBitmapIndex.newBitmapBuilder();
206 				rw2.markStart((RevCommit) rw2.peel(rw2.parseAny(rc)));
207 				RevCommit rc2;
208 				while ((rc2 = rw2.next()) != null) {
209 					tipBitmap.addObject(rc2, Constants.OBJ_COMMIT);
210 				}
211 				int cardinality = tipBitmap.cardinality();
212 				seen.or(tipBitmap);
213 
214 				// Within this branch, keep ordered lists of commits
215 				// representing chains in its history, where each chain is a
216 				// "sub-branch". Ordering commits by these chains makes for
217 				// fewer differences between consecutive selected commits, which
218 				// in turn provides better compression/on the run-length
219 				// encoding of the XORs between them.
220 				List<List<BitmapCommit>> chains = new ArrayList<>();
221 
222 				// Mark the current branch as inactive if its tip commit isn't
223 				// recent and there are an excessive number of branches, to
224 				// prevent memory bloat of computing too many bitmaps for stale
225 				// branches.
226 				boolean isActiveBranch = true;
227 				if (totalWants > excessiveBranchCount && !isRecentCommit(rc)) {
228 					isActiveBranch = false;
229 				}
230 
231 				// Insert bitmaps at the offsets suggested by the
232 				// nextSelectionDistance() heuristic. Only reuse bitmaps created
233 				// for more distant commits.
234 				int index = -1;
235 				int nextIn = nextSpan(cardinality);
236 				int nextFlg = nextIn == distantCommitSpan
237 						? PackBitmapIndex.FLAG_REUSE
238 						: 0;
239 
240 				// For the current branch, iterate through all commits from
241 				// oldest to newest.
242 				for (RevCommit c : selectionHelper) {
243 					// Optimization: if we have found all the commits for this
244 					// branch, stop searching
245 					int distanceFromTip = cardinality - index - 1;
246 					if (distanceFromTip == 0) {
247 						break;
248 					}
249 
250 					// Ignore commits that are not in this branch
251 					if (!tipBitmap.contains(c)) {
252 						continue;
253 					}
254 
255 					index++;
256 					nextIn--;
257 					pm.update(1);
258 
259 					// Always pick the items in wants, prefer merge commits.
260 					if (selectionHelper.newWants.remove(c)) {
261 						if (nextIn > 0) {
262 							nextFlg = 0;
263 						}
264 					} else {
265 						boolean stillInSpan = nextIn >= 0;
266 						boolean isMergeCommit = c.getParentCount() > 1;
267 						// Force selection if:
268 						// a) we have exhausted the window looking for merges
269 						// b) we are in the top commits of an active branch
270 						// c) we are at a branch tip
271 						boolean mustPick = (nextIn <= -recentCommitSpan)
272 								|| (isActiveBranch
273 										&& (distanceFromTip <= contiguousCommitCount))
274 								|| (distanceFromTip == 1); // most recent commit
275 						if (!mustPick && (stillInSpan || !isMergeCommit)) {
276 							continue;
277 						}
278 					}
279 
280 					// This commit is selected.
281 					// Calculate where to look for the next one.
282 					int flags = nextFlg;
283 					nextIn = nextSpan(distanceFromTip);
284 					nextFlg = nextIn == distantCommitSpan
285 							? PackBitmapIndex.FLAG_REUSE
286 							: 0;
287 
288 					// Create the commit bitmap for the current commit
289 					BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder();
290 					rw.reset();
291 					rw.markStart(c);
292 					rw.setRevFilter(new AddUnseenToBitmapFilter(
293 							selectionHelper.reusedCommitsBitmap, bitmap));
294 					while (rw.next() != null) {
295 						// The filter adds the reachable commits to bitmap.
296 					}
297 
298 					// Sort the commits by independent chains in this branch's
299 					// history, yielding better compression when building
300 					// bitmaps.
301 					List<BitmapCommit> longestAncestorChain = null;
302 					for (List<BitmapCommit> chain : chains) {
303 						BitmapCommit mostRecentCommit = chain
304 								.get(chain.size() - 1);
305 						if (bitmap.contains(mostRecentCommit)) {
306 							if (longestAncestorChain == null
307 									|| longestAncestorChain.size() < chain
308 											.size()) {
309 								longestAncestorChain = chain;
310 							}
311 						}
312 					}
313 
314 					if (longestAncestorChain == null) {
315 						longestAncestorChain = new ArrayList<>();
316 						chains.add(longestAncestorChain);
317 					}
318 					longestAncestorChain.add(new BitmapCommit(c,
319 							!longestAncestorChain.isEmpty(), flags));
320 					writeBitmaps.addBitmap(c, bitmap, 0);
321 				}
322 
323 				for (List<BitmapCommit> chain : chains) {
324 					selections.addAll(chain);
325 				}
326 			}
327 			writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps.
328 
329 			// Add the remaining peeledWant
330 			for (AnyObjectId remainingWant : selectionHelper.newWants) {
331 				selections.add(new BitmapCommit(remainingWant, false, 0));
332 			}
333 
334 			pm.endTask();
335 			return selections;
336 		}
337 	}
338 
339 	private boolean isRecentCommit(RevCommit revCommit) {
340 		return revCommit.getCommitTime() > inactiveBranchTimestamp;
341 	}
342 
343 	/**
344 	 * A RevFilter that excludes the commits named in a bitmap from the walk.
345 	 * <p>
346 	 * If a commit is in {@code bitmap} then that commit is not emitted by the
347 	 * walk and its parents are marked as SEEN so the walk can skip them.  The
348 	 * bitmaps passed in have the property that the parents of any commit in
349 	 * {@code bitmap} are also in {@code bitmap}, so marking the parents as
350 	 * SEEN speeds up the RevWalk by saving it from walking down blind alleys
351 	 * and does not change the commits emitted.
352 	 */
353 	private static class NotInBitmapFilter extends RevFilter {
354 		private final BitmapBuilder bitmap;
355 
356 		NotInBitmapFilter(BitmapBuilder bitmap) {
357 			this.bitmap = bitmap;
358 		}
359 
360 		@Override
361 		public final boolean include(RevWalk rw, RevCommit c) {
362 			if (!bitmap.contains(c)) {
363 				return true;
364 			}
365 			for (RevCommit p : c.getParents()) {
366 				p.add(SEEN);
367 			}
368 			return false;
369 		}
370 
371 		@Override
372 		public final NotInBitmapFilter clone() {
373 			throw new UnsupportedOperationException();
374 		}
375 
376 		@Override
377 		public final boolean requiresCommitBody() {
378 			return false;
379 		}
380 	}
381 
382 	/**
383 	 * Records which of the {@code wants} can be found in the previous GC pack's
384 	 * bitmap indices and which are new.
385 	 *
386 	 * @param rw
387 	 *            a {@link RevWalk} to find reachable objects in this repository
388 	 * @param expectedCommitCount
389 	 *            expected count of commits. The actual count may be less due to
390 	 *            unreachable garbage.
391 	 * @param excludeFromBitmapSelection
392 	 *            commits that should be excluded from bitmap selection
393 	 * @return a {@link CommitSelectionHelper} capturing which commits are
394 	 *         covered by a previous pack's bitmaps and which new commits need
395 	 *         bitmap coverage
396 	 * @throws IncorrectObjectTypeException
397 	 *             if any of the processed objects is not a commit
398 	 * @throws IOException
399 	 *             on errors reading pack or index files
400 	 * @throws MissingObjectException
401 	 *             if an expected object is missing
402 	 */
403 	private CommitSelectionHelper captureOldAndNewCommits(RevWalk rw,
404 			int expectedCommitCount,
405 			Set<? extends ObjectId> excludeFromBitmapSelection)
406 			throws IncorrectObjectTypeException, IOException,
407 			MissingObjectException {
408 		// Track bitmaps and commits from the previous GC pack bitmap indices.
409 		BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder();
410 		List<BitmapCommit> reuseCommits = new ArrayList<>();
411 		for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) {
412 			// More recent commits did not have the reuse flag set, so skip them
413 			if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) {
414 				continue;
415 			}
416 			RevObject ro = rw.peel(rw.parseAny(entry));
417 			if (!(ro instanceof RevCommit)) {
418 				continue;
419 			}
420 
421 			RevCommit rc = (RevCommit) ro;
422 			reuseCommits.add(new BitmapCommit(rc, false, entry.getFlags()));
423 			if (!reuse.contains(rc)) {
424 				EWAHCompressedBitmap bitmap = bitmapRemapper.ofObjectType(
425 						bitmapRemapper.getBitmap(rc), Constants.OBJ_COMMIT);
426 				reuse.or(new CompressedBitmap(bitmap, commitBitmapIndex));
427 			}
428 		}
429 
430 		// Add branch tips that are not represented in a previous pack's bitmap
431 		// indices. Set up a RevWalk to find new commits not in the old packs.
432 		List<RevCommit> newWantsByNewest = new ArrayList<>(want.size());
433 		Set<RevCommit> newWants = new HashSet<>(want.size());
434 		for (AnyObjectId objectId : want) {
435 			RevObject ro = rw.peel(rw.parseAny(objectId));
436 			if (!(ro instanceof RevCommit) || reuse.contains(ro)
437 					|| excludeFromBitmapSelection.contains(ro)) {
438 				continue;
439 			}
440 
441 			RevCommit rc = (RevCommit) ro;
442 			rw.markStart(rc);
443 			newWants.add(rc);
444 			newWantsByNewest.add(rc);
445 		}
446 
447 		// Create a list of commits in reverse order (older to newer) that are
448 		// not in the previous bitmap indices and are reachable.
449 		rw.setRevFilter(new NotInBitmapFilter(reuse));
450 		RevCommit[] commits = new RevCommit[expectedCommitCount];
451 		int pos = commits.length;
452 		RevCommit rc;
453 		while ((rc = rw.next()) != null && pos > 0) {
454 			commits[--pos] = rc;
455 			pm.update(1);
456 		}
457 
458 		// Sort the new wants by reverse commit time.
459 		Collections.sort(newWantsByNewest, ORDER_BY_REVERSE_TIMESTAMP);
460 		return new CommitSelectionHelper(newWants, commits, pos,
461 				newWantsByNewest, reuse, reuseCommits);
462 	}
463 
464 	/*-
465 	 * Returns the desired distance to the next bitmap based on the distance
466 	 * from the tip commit. Only differentiates recent from distant spans,
467 	 * selectCommits() handles the contiguous commits at the tip for active
468 	 * or inactive branches.
469 	 *
470 	 * A graph of this function looks like this, where
471 	 * the X axis is the distance from the tip commit and the Y axis is the
472 	 * bitmap selection distance.
473 	 *
474 	 * 5000                ____...
475 	 *                    /
476 	 *                  /
477 	 *                /
478 	 *              /
479 	 *  100  _____/
480 	 *       0  20100  25000
481 	 *
482 	 * Linear scaling between 20100 and 25000 prevents spans >100 for distances
483 	 * <20000 (otherwise, a span of 5000 would be returned for a distance of
484 	 * 21000, and the range 16000-20000 would have no selections).
485 	 */
486 	int nextSpan(int distanceFromTip) {
487 		if (distanceFromTip < 0) {
488 			throw new IllegalArgumentException();
489 		}
490 
491 		// Commits more toward the start will have more bitmaps.
492 		if (distanceFromTip <= recentCommitCount) {
493 			return recentCommitSpan;
494 		}
495 
496 		int next = Math.min(distanceFromTip - recentCommitCount,
497 				distantCommitSpan);
498 		return Math.max(next, recentCommitSpan);
499 	}
500 
501 	BitmapWalker newBitmapWalker() {
502 		return new BitmapWalker(
503 				new ObjectWalk(reader), bitmapIndex, null);
504 	}
505 
506 	/**
507 	 * A commit object for which a bitmap index should be built.
508 	 */
509 	static final class BitmapCommit extends ObjectId {
510 		private final boolean reuseWalker;
511 		private final int flags;
512 
513 		BitmapCommit(AnyObjectId objectId, boolean reuseWalker, int flags) {
514 			super(objectId);
515 			this.reuseWalker = reuseWalker;
516 			this.flags = flags;
517 		}
518 
519 		boolean isReuseWalker() {
520 			return reuseWalker;
521 		}
522 
523 		int getFlags() {
524 			return flags;
525 		}
526 	}
527 
528 	/**
529 	 * Container for state used in the first phase of selecting commits, which
530 	 * walks all of the reachable commits via the branch tips that are not
531 	 * covered by a previous pack's bitmaps ({@code newWants}) and stores them
532 	 * in {@code newCommitsByOldest}. {@code newCommitsByOldest} is initialized
533 	 * with an expected size of all commits, but may be smaller if some commits
534 	 * are unreachable and/or some commits are covered by a previous pack's
535 	 * bitmaps. {@code commitStartPos} will contain a positive offset to either
536 	 * the root commit or the oldest commit not covered by previous bitmaps.
537 	 */
538 	private static final class CommitSelectionHelper implements Iterable<RevCommit> {
539 		final Set<? extends ObjectId> newWants;
540 
541 		final List<RevCommit> newWantsByNewest;
542 		final BitmapBuilder reusedCommitsBitmap;
543 
544 		final List<BitmapCommit> reusedCommits;
545 
546 		final RevCommit[] newCommitsByOldest;
547 
548 		final int newCommitStartPos;
549 
550 		CommitSelectionHelper(Set<? extends ObjectId> newWants,
551 				RevCommit[] commitsByOldest, int commitStartPos,
552 				List<RevCommit> newWantsByNewest,
553 				BitmapBuilder reusedCommitsBitmap,
554 				List<BitmapCommit> reuse) {
555 			this.newWants = newWants;
556 			this.newCommitsByOldest = commitsByOldest;
557 			this.newCommitStartPos = commitStartPos;
558 			this.newWantsByNewest = newWantsByNewest;
559 			this.reusedCommitsBitmap = reusedCommitsBitmap;
560 			this.reusedCommits = reuse;
561 		}
562 
563 		@Override
564 		public Iterator<RevCommit> iterator() {
565 			// Member variables referenced by this iterator will have synthetic
566 			// accessors generated for them if they are made private.
567 			return new Iterator<RevCommit>() {
568 				int pos = newCommitStartPos;
569 
570 				@Override
571 				public boolean hasNext() {
572 					return pos < newCommitsByOldest.length;
573 				}
574 
575 				@Override
576 				public RevCommit next() {
577 					return newCommitsByOldest[pos++];
578 				}
579 
580 				@Override
581 				public void remove() {
582 					throw new UnsupportedOperationException();
583 				}
584 			};
585 		}
586 
587 		int getCommitCount() {
588 			return newCommitsByOldest.length - newCommitStartPos;
589 		}
590 	}
591 }