| /* |
| * Copyright (C) 2012, Google Inc. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.eclipse.jgit.internal.storage.pack; |
| |
| import static org.eclipse.jgit.internal.storage.file.PackBitmapIndex.FLAG_REUSE; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import com.googlecode.javaewah.EWAHCompressedBitmap; |
| |
| import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
| import org.eclipse.jgit.errors.MissingObjectException; |
| import org.eclipse.jgit.internal.JGitText; |
| import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl; |
| import org.eclipse.jgit.internal.storage.file.PackBitmapIndex; |
| import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; |
| import org.eclipse.jgit.internal.storage.file.PackBitmapIndexRemapper; |
| import org.eclipse.jgit.lib.AnyObjectId; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectReader; |
| import org.eclipse.jgit.lib.ProgressMonitor; |
| import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; |
| import org.eclipse.jgit.revwalk.ObjectWalk; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevObject; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.util.BlockList; |
| |
| /** Helper class for the PackWriter to select commits for pack index bitmaps. */ |
| class PackWriterBitmapPreparer { |
| |
| private static final Comparator<BitmapBuilder> BUILDER_BY_CARDINALITY_DSC = |
| new Comparator<BitmapBuilder>() { |
| public int compare(BitmapBuilder a, BitmapBuilder b) { |
| return Integer.signum(b.cardinality() - a.cardinality()); |
| } |
| }; |
| |
| private final ObjectReader reader; |
| private final ProgressMonitor pm; |
| private final Set<? extends ObjectId> want; |
| private final PackBitmapIndexBuilder writeBitmaps; |
| private final BitmapIndexImpl commitBitmapIndex; |
| private final PackBitmapIndexRemapper bitmapRemapper; |
| private final BitmapIndexImpl bitmapIndex; |
| private final int minCommits = 100; |
| private final int maxCommits = 5000; |
| |
| PackWriterBitmapPreparer(ObjectReader reader, |
| PackBitmapIndexBuilder writeBitmaps, ProgressMonitor pm, |
| Set<? extends ObjectId> want) throws IOException { |
| this.reader = reader; |
| this.writeBitmaps = writeBitmaps; |
| this.pm = pm; |
| this.want = want; |
| this.commitBitmapIndex = new BitmapIndexImpl(writeBitmaps); |
| this.bitmapRemapper = PackBitmapIndexRemapper.newPackBitmapIndex( |
| reader.getBitmapIndex(), writeBitmaps); |
| this.bitmapIndex = new BitmapIndexImpl(bitmapRemapper); |
| } |
| |
| Collection<BitmapCommit> doCommitSelection(int expectedNumCommits) |
| throws MissingObjectException, IncorrectObjectTypeException, |
| IOException { |
| pm.beginTask(JGitText.get().selectingCommits, ProgressMonitor.UNKNOWN); |
| RevWalk rw = new RevWalk(reader); |
| WalkResult result = findPaths(rw, expectedNumCommits); |
| pm.endTask(); |
| |
| int totCommits = result.commitsByOldest.length - result.commitStartPos; |
| BlockList<BitmapCommit> selections = new BlockList<BitmapCommit>( |
| totCommits / minCommits + 1); |
| for (BitmapCommit reuse : result.reuse) |
| selections.add(reuse); |
| |
| if (totCommits == 0) { |
| for (AnyObjectId id : result.peeledWant) |
| selections.add(new BitmapCommit(id, false, 0)); |
| return selections; |
| } |
| |
| pm.beginTask(JGitText.get().selectingCommits, totCommits); |
| |
| for (BitmapBuilder bitmapableCommits : result.paths) { |
| int cardinality = bitmapableCommits.cardinality(); |
| |
| List<List<BitmapCommit>> running = new ArrayList< |
| List<BitmapCommit>>(); |
| |
| // Insert bitmaps at the offsets suggested by the |
| // nextSelectionDistance() heuristic. |
| int index = -1; |
| int nextIn = nextSelectionDistance(0, cardinality); |
| int nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0; |
| boolean mustPick = nextIn == 0; |
| for (RevCommit c : result) { |
| if (!bitmapableCommits.contains(c)) |
| continue; |
| |
| index++; |
| nextIn--; |
| pm.update(1); |
| |
| // Always pick the items in want and prefer merge commits. |
| if (result.peeledWant.remove(c)) { |
| if (nextIn > 0) |
| nextFlg = 0; |
| } else if (!mustPick && ((nextIn > 0) |
| || (c.getParentCount() <= 1 && nextIn > -minCommits))) { |
| continue; |
| } |
| |
| int flags = nextFlg; |
| nextIn = nextSelectionDistance(index, cardinality); |
| nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0; |
| mustPick = nextIn == 0; |
| |
| BitmapBuilder fullBitmap = commitBitmapIndex.newBitmapBuilder(); |
| rw.reset(); |
| rw.markStart(c); |
| for (AnyObjectId objectId : result.reuse) |
| rw.markUninteresting(rw.parseCommit(objectId)); |
| rw.setRevFilter( |
| PackWriterBitmapWalker.newRevFilter(null, fullBitmap)); |
| |
| while (rw.next() != null) { |
| // Work is done in the RevFilter. |
| } |
| |
| List<List<BitmapCommit>> matches = new ArrayList< |
| List<BitmapCommit>>(); |
| for (List<BitmapCommit> list : running) { |
| BitmapCommit last = list.get(list.size() - 1); |
| if (fullBitmap.contains(last)) |
| matches.add(list); |
| } |
| |
| List<BitmapCommit> match; |
| if (matches.isEmpty()) { |
| match = new ArrayList<BitmapCommit>(); |
| running.add(match); |
| } else { |
| match = matches.get(0); |
| // Append to longest |
| for (List<BitmapCommit> list : matches) { |
| if (list.size() > match.size()) |
| match = list; |
| } |
| } |
| match.add(new BitmapCommit(c, !match.isEmpty(), flags)); |
| writeBitmaps.addBitmap(c, fullBitmap, 0); |
| } |
| |
| for (List<BitmapCommit> list : running) |
| selections.addAll(list); |
| } |
| writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps. |
| |
| // Add the remaining peeledWant |
| for (AnyObjectId remainingWant : result.peeledWant) |
| selections.add(new BitmapCommit(remainingWant, false, 0)); |
| |
| pm.endTask(); |
| return selections; |
| } |
| |
| private WalkResult findPaths(RevWalk rw, int expectedNumCommits) |
| throws MissingObjectException, IOException { |
| BitmapBuilder reuseBitmap = commitBitmapIndex.newBitmapBuilder(); |
| List<BitmapCommit> reuse = new ArrayList<BitmapCommit>(); |
| for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) { |
| if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) |
| continue; |
| |
| RevObject ro = rw.peel(rw.parseAny(entry)); |
| if (ro instanceof RevCommit) { |
| RevCommit rc = (RevCommit) ro; |
| reuse.add(new BitmapCommit(rc, false, entry.getFlags())); |
| rw.markUninteresting(rc); |
| |
| EWAHCompressedBitmap bitmap = bitmapRemapper.ofObjectType( |
| bitmapRemapper.getBitmap(rc), Constants.OBJ_COMMIT); |
| writeBitmaps.addBitmap(rc, bitmap, 0); |
| reuseBitmap.add(rc, Constants.OBJ_COMMIT); |
| } |
| } |
| writeBitmaps.clearBitmaps(); // Remove temporary bitmaps |
| |
| // Do a RevWalk by commit time descending. Keep track of all the paths |
| // from the wants. |
| List<BitmapBuilder> paths = new ArrayList<BitmapBuilder>(want.size()); |
| Set<RevCommit> peeledWant = new HashSet<RevCommit>(want.size()); |
| for (AnyObjectId objectId : want) { |
| RevObject ro = rw.peel(rw.parseAny(objectId)); |
| if (ro instanceof RevCommit && !reuseBitmap.contains(ro)) { |
| RevCommit rc = (RevCommit) ro; |
| peeledWant.add(rc); |
| rw.markStart(rc); |
| |
| BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder(); |
| bitmap.or(reuseBitmap); |
| bitmap.add(rc, Constants.OBJ_COMMIT); |
| paths.add(bitmap); |
| } |
| } |
| |
| // Update the paths from the wants and create a list of commits in |
| // reverse iteration order. |
| RevCommit[] commits = new RevCommit[expectedNumCommits]; |
| int pos = commits.length; |
| RevCommit rc; |
| while ((rc = rw.next()) != null) { |
| commits[--pos] = rc; |
| for (BitmapBuilder path : paths) { |
| if (path.contains(rc)) { |
| for (RevCommit c : rc.getParents()) |
| path.add(c, Constants.OBJ_COMMIT); |
| } |
| } |
| |
| pm.update(1); |
| } |
| |
| // Remove the reused bitmaps from the paths |
| if (!reuse.isEmpty()) |
| for (BitmapBuilder bitmap : paths) |
| bitmap.andNot(reuseBitmap); |
| |
| // Sort the paths |
| List<BitmapBuilder> distinctPaths = new ArrayList<BitmapBuilder>(paths.size()); |
| while (!paths.isEmpty()) { |
| Collections.sort(paths, BUILDER_BY_CARDINALITY_DSC); |
| BitmapBuilder largest = paths.remove(0); |
| distinctPaths.add(largest); |
| |
| // Update the remaining paths, by removing the objects from |
| // the path that was just added. |
| for (int i = paths.size() - 1; i >= 0; i--) |
| paths.get(i).andNot(largest); |
| } |
| |
| return new WalkResult(peeledWant, commits, pos, distinctPaths, reuse); |
| } |
| |
| private int nextSelectionDistance(int idx, int cardinality) { |
| if (idx > cardinality) |
| throw new IllegalArgumentException(); |
| int idxFromStart = cardinality - idx; |
| int mustRegionEnd = 100; |
| if (idxFromStart <= mustRegionEnd) |
| return 0; |
| |
| // Commits more toward the start will have more bitmaps. |
| int minRegionEnd = 20000; |
| if (idxFromStart <= minRegionEnd) |
| return Math.min(idxFromStart - mustRegionEnd, minCommits); |
| |
| // Commits more toward the end will have fewer. |
| int next = Math.min(idxFromStart - minRegionEnd, maxCommits); |
| return Math.max(next, minCommits); |
| } |
| |
| PackWriterBitmapWalker newBitmapWalker() { |
| return new PackWriterBitmapWalker( |
| new ObjectWalk(reader), bitmapIndex, null); |
| } |
| |
| static final class BitmapCommit extends ObjectId { |
| private final boolean reuseWalker; |
| private final int flags; |
| |
| private BitmapCommit( |
| AnyObjectId objectId, boolean reuseWalker, int flags) { |
| super(objectId); |
| this.reuseWalker = reuseWalker; |
| this.flags = flags; |
| } |
| |
| boolean isReuseWalker() { |
| return reuseWalker; |
| } |
| |
| int getFlags() { |
| return flags; |
| } |
| } |
| |
| private static final class WalkResult implements Iterable<RevCommit> { |
| private final Set<? extends ObjectId> peeledWant; |
| private final RevCommit[] commitsByOldest; |
| private final int commitStartPos; |
| private final List<BitmapBuilder> paths; |
| private final Iterable<BitmapCommit> reuse; |
| |
| private WalkResult(Set<? extends ObjectId> peeledWant, |
| RevCommit[] commitsByOldest, int commitStartPos, |
| List<BitmapBuilder> paths, Iterable<BitmapCommit> reuse) { |
| this.peeledWant = peeledWant; |
| this.commitsByOldest = commitsByOldest; |
| this.commitStartPos = commitStartPos; |
| this.paths = paths; |
| this.reuse = reuse; |
| } |
| |
| public Iterator<RevCommit> iterator() { |
| return new Iterator<RevCommit>() { |
| int pos = commitStartPos; |
| |
| public boolean hasNext() { |
| return pos < commitsByOldest.length; |
| } |
| |
| public RevCommit next() { |
| return commitsByOldest[pos++]; |
| } |
| |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| } |
| } |