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