View Javadoc
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  
12  import java.io.IOException;
13  import java.util.ArrayList;
14  import java.util.Collection;
15  import java.util.Iterator;
16  import java.util.List;
17  import java.util.Optional;
18  import java.util.stream.Stream;
19  
20  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
21  import org.eclipse.jgit.errors.MissingObjectException;
22  import org.eclipse.jgit.lib.BitmapIndex;
23  import org.eclipse.jgit.lib.BitmapIndex.Bitmap;
24  import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
25  import org.eclipse.jgit.lib.Constants;
26  import org.eclipse.jgit.revwalk.ReachabilityChecker;
27  import org.eclipse.jgit.revwalk.RevCommit;
28  import org.eclipse.jgit.revwalk.RevFlag;
29  import org.eclipse.jgit.revwalk.RevSort;
30  import org.eclipse.jgit.revwalk.RevWalk;
31  import org.eclipse.jgit.revwalk.filter.RevFilter;
32  
33  /**
34   * Checks the reachability using bitmaps.
35   */
36  public class BitmappedReachabilityChecker implements ReachabilityChecker {
37  
38  	private final RevWalk walk;
39  
40  	/**
41  	 * @param walk
42  	 *            walk on the repository to get or create the bitmaps for the
43  	 *            commits. It must have bitmaps.
44  	 * @throws AssertionError
45  	 *             runtime exception if walk is over a repository without
46  	 *             bitmaps
47  	 * @throws IOException
48  	 *             if the index or the object reader cannot be opened.
49  	 */
50  	public BitmappedReachabilityChecker(RevWalk walk)
51  			throws IOException {
52  		this.walk = walk;
53  		if (walk.getObjectReader().getBitmapIndex() == null) {
54  			throw new AssertionError(
55  					"Trying to use bitmapped reachability check " //$NON-NLS-1$
56  							+ "on a repository without bitmaps"); //$NON-NLS-1$
57  		}
58  	}
59  
60  	/**
61  	 * Check all targets are reachable from the starters.
62  	 * <p>
63  	 * In this implementation, it is recommended to put the most popular
64  	 * starters (e.g. refs/heads tips) at the beginning.
65  	 */
66  	@Override
67  	public Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
68  			Stream<RevCommit> starters) throws MissingObjectException,
69  			IncorrectObjectTypeException, IOException {
70  
71  		List<RevCommit> remainingTargets = new ArrayList<>(targets);
72  
73  		walk.reset();
74  		walk.sort(RevSort.TOPO);
75  
76  		// Filter emits only commits that are unreachable from previously
77  		// visited commits. Internally it keeps a bitmap of everything
78  		// reachable so far, which we use to discard reachable targets.
79  		BitmapIndex repoBitmaps = walk.getObjectReader().getBitmapIndex();
80  		ReachedFilter reachedFilter = new ReachedFilter(repoBitmaps);
81  		walk.setRevFilter(reachedFilter);
82  
83  		Iterator<RevCommit> startersIter = starters.iterator();
84  		while (startersIter.hasNext()) {
85  			walk.markStart(startersIter.next());
86  			while (walk.next() != null) {
87  				remainingTargets.removeIf(reachedFilter::isReachable);
88  
89  				if (remainingTargets.isEmpty()) {
90  					return Optional.empty();
91  				}
92  			}
93  			walk.reset();
94  		}
95  
96  		return Optional.of(remainingTargets.get(0));
97  	}
98  
99  	/**
100 	 * This filter emits commits that were not bitmap-reachable from anything
101 	 * visited before. Or in other words, commits that add something (themselves
102 	 * or their bitmap) to the "reached" bitmap.
103 	 *
104 	 * Current progress can be queried via {@link #isReachable(RevCommit)}.
105 	 */
106 	private static class ReachedFilter extends RevFilter {
107 
108 		private final BitmapIndex repoBitmaps;
109 		private final BitmapBuilder reached;
110 
111 		/**
112 		 * Create a filter that emits only previously unreachable commits.
113 		 *
114 		 * @param repoBitmaps
115 		 *            bitmap index of the repo
116 		 */
117 		public ReachedFilter(BitmapIndex repoBitmaps) {
118 			this.repoBitmaps = repoBitmaps;
119 			this.reached = repoBitmaps.newBitmapBuilder();
120 		}
121 
122 		/** {@inheritDoc} */
123 		@Override
124 		public final boolean include(RevWalk walker, RevCommit cmit) {
125 			Bitmap commitBitmap;
126 
127 			if (reached.contains(cmit)) {
128 				// already seen or included
129 				dontFollow(cmit);
130 				return false;
131 			}
132 
133 			if ((commitBitmap = repoBitmaps.getBitmap(cmit)) != null) {
134 				reached.or(commitBitmap);
135 				// Emit the commit because there are new contents in the bitmap
136 				// but don't follow parents (they are already in the bitmap)
137 				dontFollow(cmit);
138 				return true;
139 			}
140 
141 			// No bitmaps, keep going
142 			reached.addObject(cmit, Constants.OBJ_COMMIT);
143 			return true;
144 		}
145 
146 		private static final void dontFollow(RevCommit cmit) {
147 			for (RevCommit p : cmit.getParents()) {
148 				p.add(RevFlag.SEEN);
149 			}
150 		}
151 
152 		/** {@inheritDoc} */
153 		@Override
154 		public final RevFilter clone() {
155 			throw new UnsupportedOperationException();
156 		}
157 
158 		/** {@inheritDoc} */
159 		@Override
160 		public final boolean requiresCommitBody() {
161 			return false;
162 		}
163 
164 		boolean isReachable(RevCommit commit) {
165 			return reached.contains(commit);
166 		}
167 	}
168 }