BitmappedReachabilityChecker.java

  1. /*
  2.  * Copyright (C) 2019, Google LLC 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. package org.eclipse.jgit.internal.revwalk;

  11. import java.io.IOException;
  12. import java.util.ArrayList;
  13. import java.util.Collection;
  14. import java.util.Iterator;
  15. import java.util.List;
  16. import java.util.Optional;
  17. import java.util.stream.Stream;

  18. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  19. import org.eclipse.jgit.errors.MissingObjectException;
  20. import org.eclipse.jgit.lib.BitmapIndex;
  21. import org.eclipse.jgit.lib.BitmapIndex.Bitmap;
  22. import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
  23. import org.eclipse.jgit.lib.Constants;
  24. import org.eclipse.jgit.revwalk.ReachabilityChecker;
  25. import org.eclipse.jgit.revwalk.RevCommit;
  26. import org.eclipse.jgit.revwalk.RevFlag;
  27. import org.eclipse.jgit.revwalk.RevSort;
  28. import org.eclipse.jgit.revwalk.RevWalk;
  29. import org.eclipse.jgit.revwalk.filter.RevFilter;

  30. /**
  31.  * Checks the reachability using bitmaps.
  32.  */
  33. public class BitmappedReachabilityChecker implements ReachabilityChecker {

  34.     private final RevWalk walk;

  35.     /**
  36.      * @param walk
  37.      *            walk on the repository to get or create the bitmaps for the
  38.      *            commits. It must have bitmaps.
  39.      * @throws AssertionError
  40.      *             runtime exception if walk is over a repository without
  41.      *             bitmaps
  42.      * @throws IOException
  43.      *             if the index or the object reader cannot be opened.
  44.      */
  45.     public BitmappedReachabilityChecker(RevWalk walk)
  46.             throws IOException {
  47.         this.walk = walk;
  48.         if (walk.getObjectReader().getBitmapIndex() == null) {
  49.             throw new AssertionError(
  50.                     "Trying to use bitmapped reachability check " //$NON-NLS-1$
  51.                             + "on a repository without bitmaps"); //$NON-NLS-1$
  52.         }
  53.     }

  54.     /**
  55.      * Check all targets are reachable from the starters.
  56.      * <p>
  57.      * In this implementation, it is recommended to put the most popular
  58.      * starters (e.g. refs/heads tips) at the beginning.
  59.      */
  60.     @Override
  61.     public Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
  62.             Stream<RevCommit> starters) throws MissingObjectException,
  63.             IncorrectObjectTypeException, IOException {

  64.         List<RevCommit> remainingTargets = new ArrayList<>(targets);

  65.         walk.reset();
  66.         walk.sort(RevSort.TOPO);

  67.         // Filter emits only commits that are unreachable from previously
  68.         // visited commits. Internally it keeps a bitmap of everything
  69.         // reachable so far, which we use to discard reachable targets.
  70.         BitmapIndex repoBitmaps = walk.getObjectReader().getBitmapIndex();
  71.         ReachedFilter reachedFilter = new ReachedFilter(repoBitmaps);
  72.         walk.setRevFilter(reachedFilter);

  73.         Iterator<RevCommit> startersIter = starters.iterator();
  74.         while (startersIter.hasNext()) {
  75.             walk.markStart(startersIter.next());
  76.             while (walk.next() != null) {
  77.                 remainingTargets.removeIf(reachedFilter::isReachable);

  78.                 if (remainingTargets.isEmpty()) {
  79.                     return Optional.empty();
  80.                 }
  81.             }
  82.             walk.reset();
  83.         }

  84.         return Optional.of(remainingTargets.get(0));
  85.     }

  86.     /**
  87.      * This filter emits commits that were not bitmap-reachable from anything
  88.      * visited before. Or in other words, commits that add something (themselves
  89.      * or their bitmap) to the "reached" bitmap.
  90.      *
  91.      * Current progress can be queried via {@link #isReachable(RevCommit)}.
  92.      */
  93.     private static class ReachedFilter extends RevFilter {

  94.         private final BitmapIndex repoBitmaps;
  95.         private final BitmapBuilder reached;

  96.         /**
  97.          * Create a filter that emits only previously unreachable commits.
  98.          *
  99.          * @param repoBitmaps
  100.          *            bitmap index of the repo
  101.          */
  102.         public ReachedFilter(BitmapIndex repoBitmaps) {
  103.             this.repoBitmaps = repoBitmaps;
  104.             this.reached = repoBitmaps.newBitmapBuilder();
  105.         }

  106.         /** {@inheritDoc} */
  107.         @Override
  108.         public final boolean include(RevWalk walker, RevCommit cmit) {
  109.             Bitmap commitBitmap;

  110.             if (reached.contains(cmit)) {
  111.                 // already seen or included
  112.                 dontFollow(cmit);
  113.                 return false;
  114.             }

  115.             if ((commitBitmap = repoBitmaps.getBitmap(cmit)) != null) {
  116.                 reached.or(commitBitmap);
  117.                 // Emit the commit because there are new contents in the bitmap
  118.                 // but don't follow parents (they are already in the bitmap)
  119.                 dontFollow(cmit);
  120.                 return true;
  121.             }

  122.             // No bitmaps, keep going
  123.             reached.addObject(cmit, Constants.OBJ_COMMIT);
  124.             return true;
  125.         }

  126.         private static final void dontFollow(RevCommit cmit) {
  127.             for (RevCommit p : cmit.getParents()) {
  128.                 p.add(RevFlag.SEEN);
  129.             }
  130.         }

  131.         /** {@inheritDoc} */
  132.         @Override
  133.         public final RevFilter clone() {
  134.             throw new UnsupportedOperationException();
  135.         }

  136.         /** {@inheritDoc} */
  137.         @Override
  138.         public final boolean requiresCommitBody() {
  139.             return false;
  140.         }

  141.         boolean isReachable(RevCommit commit) {
  142.             return reached.contains(commit);
  143.         }
  144.     }
  145. }