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  
48  import java.io.IOException;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.Collections;
52  import java.util.Comparator;
53  import java.util.HashSet;
54  import java.util.Iterator;
55  import java.util.List;
56  import java.util.Set;
57  
58  import com.googlecode.javaewah.EWAHCompressedBitmap;
59  
60  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
61  import org.eclipse.jgit.errors.MissingObjectException;
62  import org.eclipse.jgit.internal.JGitText;
63  import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
64  import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
65  import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
66  import org.eclipse.jgit.internal.storage.file.PackBitmapIndexRemapper;
67  import org.eclipse.jgit.lib.AnyObjectId;
68  import org.eclipse.jgit.lib.Constants;
69  import org.eclipse.jgit.lib.ObjectId;
70  import org.eclipse.jgit.lib.ObjectReader;
71  import org.eclipse.jgit.lib.ProgressMonitor;
72  import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
73  import org.eclipse.jgit.revwalk.ObjectWalk;
74  import org.eclipse.jgit.revwalk.RevCommit;
75  import org.eclipse.jgit.revwalk.RevObject;
76  import org.eclipse.jgit.revwalk.RevWalk;
77  import org.eclipse.jgit.util.BlockList;
78  
79  /** Helper class for the PackWriter to select commits for pack index bitmaps. */
80  class PackWriterBitmapPreparer {
81  
82  	private static final Comparator<BitmapBuilder> BUILDER_BY_CARDINALITY_DSC =
83  			new Comparator<BitmapBuilder>() {
84  		public int compare(BitmapBuilder a, BitmapBuilder b) {
85  			return Integer.signum(b.cardinality() - a.cardinality());
86  		}
87  	};
88  
89  	private final ObjectReader reader;
90  	private final ProgressMonitor pm;
91  	private final Set<? extends ObjectId> want;
92  	private final PackBitmapIndexBuilder writeBitmaps;
93  	private final BitmapIndexImpl commitBitmapIndex;
94  	private final PackBitmapIndexRemapper bitmapRemapper;
95  	private final BitmapIndexImpl bitmapIndex;
96  	private final int minCommits = 100;
97  	private final int maxCommits = 5000;
98  
99  	PackWriterBitmapPreparer(ObjectReader reader,
100 			PackBitmapIndexBuilder writeBitmaps, ProgressMonitor pm,
101 			Set<? extends ObjectId> want) throws IOException {
102 		this.reader = reader;
103 		this.writeBitmaps = writeBitmaps;
104 		this.pm = pm;
105 		this.want = want;
106 		this.commitBitmapIndex = new BitmapIndexImpl(writeBitmaps);
107 		this.bitmapRemapper = PackBitmapIndexRemapper.newPackBitmapIndex(
108 				reader.getBitmapIndex(), writeBitmaps);
109 		this.bitmapIndex = new BitmapIndexImpl(bitmapRemapper);
110 	}
111 
112 	Collection<BitmapCommit> doCommitSelection(int expectedNumCommits)
113 			throws MissingObjectException, IncorrectObjectTypeException,
114 			IOException {
115 		pm.beginTask(JGitText.get().selectingCommits, ProgressMonitor.UNKNOWN);
116 		RevWalk rw = new RevWalk(reader);
117 		WalkResult result = findPaths(rw, expectedNumCommits);
118 		pm.endTask();
119 
120 		int totCommits = result.commitsByOldest.length - result.commitStartPos;
121 		BlockList<BitmapCommit> selections = new BlockList<BitmapCommit>(
122 				totCommits / minCommits + 1);
123 		for (BitmapCommit reuse : result.reuse)
124 			selections.add(reuse);
125 
126 		if (totCommits == 0) {
127 			for (AnyObjectId id : result.peeledWant)
128 				selections.add(new BitmapCommit(id, false, 0));
129 			return selections;
130 		}
131 
132 		pm.beginTask(JGitText.get().selectingCommits, totCommits);
133 
134 		for (BitmapBuilder bitmapableCommits : result.paths) {
135 			int cardinality = bitmapableCommits.cardinality();
136 
137 			List<List<BitmapCommit>> running = new ArrayList<
138 					List<BitmapCommit>>();
139 
140 			// Insert bitmaps at the offsets suggested by the
141 			// nextSelectionDistance() heuristic.
142 			int index = -1;
143 			int nextIn = nextSelectionDistance(0, cardinality);
144 			int nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0;
145 			boolean mustPick = nextIn == 0;
146 			for (RevCommit c : result) {
147 				if (!bitmapableCommits.contains(c))
148 					continue;
149 
150 				index++;
151 				nextIn--;
152 				pm.update(1);
153 
154 				// Always pick the items in want and prefer merge commits.
155 				if (result.peeledWant.remove(c)) {
156 					if (nextIn > 0)
157 						nextFlg = 0;
158 				} else if (!mustPick && ((nextIn > 0)
159 						|| (c.getParentCount() <= 1 && nextIn > -minCommits))) {
160 					continue;
161 				}
162 
163 				int flags = nextFlg;
164 				nextIn = nextSelectionDistance(index, cardinality);
165 				nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0;
166 				mustPick = nextIn == 0;
167 
168 				BitmapBuilder fullBitmap = commitBitmapIndex.newBitmapBuilder();
169 				rw.reset();
170 				rw.markStart(c);
171 				for (AnyObjectId objectId : result.reuse)
172 					rw.markUninteresting(rw.parseCommit(objectId));
173 				rw.setRevFilter(
174 						PackWriterBitmapWalker.newRevFilter(null, fullBitmap));
175 
176 				while (rw.next() != null) {
177 					// Work is done in the RevFilter.
178 				}
179 
180 				List<List<BitmapCommit>> matches = new ArrayList<
181 						List<BitmapCommit>>();
182 				for (List<BitmapCommit> list : running) {
183 					BitmapCommit last = list.get(list.size() - 1);
184 					if (fullBitmap.contains(last))
185 						matches.add(list);
186 				}
187 
188 				List<BitmapCommit> match;
189 				if (matches.isEmpty()) {
190 					match = new ArrayList<BitmapCommit>();
191 					running.add(match);
192 				} else {
193 					match = matches.get(0);
194 					// Append to longest
195 					for (List<BitmapCommit> list : matches) {
196 						if (list.size() > match.size())
197 							match = list;
198 					}
199 				}
200 				match.add(new BitmapCommit(c, !match.isEmpty(), flags));
201 				writeBitmaps.addBitmap(c, fullBitmap, 0);
202 			}
203 
204 			for (List<BitmapCommit> list : running)
205 				selections.addAll(list);
206 		}
207 		writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps.
208 
209 		// Add the remaining peeledWant
210 		for (AnyObjectId remainingWant : result.peeledWant)
211 			selections.add(new BitmapCommit(remainingWant, false, 0));
212 
213 		pm.endTask();
214 		return selections;
215 	}
216 
217 	private WalkResult findPaths(RevWalk rw, int expectedNumCommits)
218 			throws MissingObjectException, IOException {
219 		BitmapBuilder reuseBitmap = commitBitmapIndex.newBitmapBuilder();
220 		List<BitmapCommit> reuse = new ArrayList<BitmapCommit>();
221 		for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) {
222 			if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE)
223 				continue;
224 
225 			RevObject ro = rw.peel(rw.parseAny(entry));
226 			if (ro instanceof RevCommit) {
227 				RevCommit rc = (RevCommit) ro;
228 				reuse.add(new BitmapCommit(rc, false, entry.getFlags()));
229 				rw.markUninteresting(rc);
230 
231 				EWAHCompressedBitmap bitmap = bitmapRemapper.ofObjectType(
232 						bitmapRemapper.getBitmap(rc), Constants.OBJ_COMMIT);
233 				writeBitmaps.addBitmap(rc, bitmap, 0);
234 				reuseBitmap.add(rc, Constants.OBJ_COMMIT);
235 			}
236 		}
237 		writeBitmaps.clearBitmaps(); // Remove temporary bitmaps
238 
239 		// Do a RevWalk by commit time descending. Keep track of all the paths
240 		// from the wants.
241 		List<BitmapBuilder> paths = new ArrayList<BitmapBuilder>(want.size());
242 		Set<RevCommit> peeledWant = new HashSet<RevCommit>(want.size());
243 		for (AnyObjectId objectId : want) {
244 			RevObject ro = rw.peel(rw.parseAny(objectId));
245 			if (ro instanceof RevCommit && !reuseBitmap.contains(ro)) {
246 				RevCommit rc = (RevCommit) ro;
247 				peeledWant.add(rc);
248 				rw.markStart(rc);
249 
250 				BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder();
251 				bitmap.or(reuseBitmap);
252 				bitmap.add(rc, Constants.OBJ_COMMIT);
253 				paths.add(bitmap);
254 			}
255 		}
256 
257 		// Update the paths from the wants and create a list of commits in
258 		// reverse iteration order.
259 		RevCommit[] commits = new RevCommit[expectedNumCommits];
260 		int pos = commits.length;
261 		RevCommit rc;
262 		while ((rc = rw.next()) != null) {
263 			commits[--pos] = rc;
264 			for (BitmapBuilder path : paths) {
265 				if (path.contains(rc)) {
266 					for (RevCommit c : rc.getParents())
267 						path.add(c, Constants.OBJ_COMMIT);
268 				}
269 			}
270 
271 			pm.update(1);
272 		}
273 
274 		// Remove the reused bitmaps from the paths
275 		if (!reuse.isEmpty())
276 			for (BitmapBuilder bitmap : paths)
277 				bitmap.andNot(reuseBitmap);
278 
279 		// Sort the paths
280 		List<BitmapBuilder> distinctPaths = new ArrayList<BitmapBuilder>(paths.size());
281 		while (!paths.isEmpty()) {
282 			Collections.sort(paths, BUILDER_BY_CARDINALITY_DSC);
283 			BitmapBuilder largest = paths.remove(0);
284 			distinctPaths.add(largest);
285 
286 			// Update the remaining paths, by removing the objects from
287 			// the path that was just added.
288 			for (int i = paths.size() - 1; i >= 0; i--)
289 				paths.get(i).andNot(largest);
290 		}
291 
292 		return new WalkResult(peeledWant, commits, pos, distinctPaths, reuse);
293 	}
294 
295 	private int nextSelectionDistance(int idx, int cardinality) {
296 		if (idx > cardinality)
297 			throw new IllegalArgumentException();
298 		int idxFromStart = cardinality - idx;
299 		int mustRegionEnd = 100;
300 		if (idxFromStart <= mustRegionEnd)
301 			return 0;
302 
303 		// Commits more toward the start will have more bitmaps.
304 		int minRegionEnd = 20000;
305 		if (idxFromStart <= minRegionEnd)
306 			return Math.min(idxFromStart - mustRegionEnd, minCommits);
307 
308 		// Commits more toward the end will have fewer.
309 		int next = Math.min(idxFromStart - minRegionEnd, maxCommits);
310 		return Math.max(next, minCommits);
311 	}
312 
313 	PackWriterBitmapWalker newBitmapWalker() {
314 		return new PackWriterBitmapWalker(
315 				new ObjectWalk(reader), bitmapIndex, null);
316 	}
317 
318 	static final class BitmapCommit extends ObjectId {
319 		private final boolean reuseWalker;
320 		private final int flags;
321 
322 		private BitmapCommit(
323 				AnyObjectId objectId, boolean reuseWalker, int flags) {
324 			super(objectId);
325 			this.reuseWalker = reuseWalker;
326 			this.flags = flags;
327 		}
328 
329 		boolean isReuseWalker() {
330 			return reuseWalker;
331 		}
332 
333 		int getFlags() {
334 			return flags;
335 		}
336 	}
337 
338 	private static final class WalkResult implements Iterable<RevCommit> {
339 		private final Set<? extends ObjectId> peeledWant;
340 		private final RevCommit[] commitsByOldest;
341 		private final int commitStartPos;
342 		private final List<BitmapBuilder> paths;
343 		private final Iterable<BitmapCommit> reuse;
344 
345 		private WalkResult(Set<? extends ObjectId> peeledWant,
346 				RevCommit[] commitsByOldest, int commitStartPos,
347 				List<BitmapBuilder> paths, Iterable<BitmapCommit> reuse) {
348 			this.peeledWant = peeledWant;
349 			this.commitsByOldest = commitsByOldest;
350 			this.commitStartPos = commitStartPos;
351 			this.paths = paths;
352 			this.reuse = reuse;
353 		}
354 
355 		public Iterator<RevCommit> iterator() {
356 			return new Iterator<RevCommit>() {
357 				int pos = commitStartPos;
358 
359 				public boolean hasNext() {
360 					return pos < commitsByOldest.length;
361 				}
362 
363 				public RevCommit next() {
364 					return commitsByOldest[pos++];
365 				}
366 
367 				public void remove() {
368 					throw new UnsupportedOperationException();
369 				}
370 			};
371 		}
372 	}
373 }