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