Support for RevSort.BOUNDARY in ObjectWalk
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / revwalk / ObjectWalk.java
blob81cebbdc9ec901c589ff9a09db926525bc4cb62f
1 /*
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
21 * written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org.spearce.jgit.revwalk;
40 import java.io.IOException;
42 import org.spearce.jgit.errors.CorruptObjectException;
43 import org.spearce.jgit.errors.IncorrectObjectTypeException;
44 import org.spearce.jgit.errors.MissingObjectException;
45 import org.spearce.jgit.lib.AnyObjectId;
46 import org.spearce.jgit.lib.Constants;
47 import org.spearce.jgit.lib.FileMode;
48 import org.spearce.jgit.lib.ObjectId;
49 import org.spearce.jgit.lib.Repository;
50 import org.spearce.jgit.treewalk.TreeWalk;
52 /**
53 * Specialized subclass of RevWalk to include trees, blobs and tags.
54 * <p>
55 * Unlike RevWalk this subclass is able to remember starting roots that include
56 * annotated tags, or arbitrary trees or blobs. Once commit generation is
57 * complete and all commits have been popped by the application, individual
58 * annotated tag, tree and blob objects can be popped through the additional
59 * method {@link #nextObject()}.
60 * <p>
61 * Tree and blob objects reachable from interesting commits are automatically
62 * scheduled for inclusion in the results of {@link #nextObject()}, returning
63 * each object exactly once. Objects are sorted and returned according to the
64 * the commits that reference them and the order they appear within a tree.
65 * Ordering can be affected by changing the {@link RevSort} used to order the
66 * commits that are returned first.
68 public class ObjectWalk extends RevWalk {
69 private final TreeWalk treeWalk;
71 private BlockObjQueue objects;
73 private RevTree currentTree;
75 private boolean fromTreeWalk;
77 private boolean enterSubtree;
79 /**
80 * Create a new revision and object walker for a given repository.
82 * @param repo
83 * the repository the walker will obtain data from.
85 public ObjectWalk(final Repository repo) {
86 super(repo);
87 treeWalk = new TreeWalk(repo);
88 objects = new BlockObjQueue();
91 /**
92 * Mark an object or commit to start graph traversal from.
93 * <p>
94 * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)}
95 * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method
96 * requires the object to be parsed before it can be added as a root for the
97 * traversal.
98 * <p>
99 * The method will automatically parse an unparsed object, but error
100 * handling may be more difficult for the application to explain why a
101 * RevObject is not actually valid. The object pool of this walker would
102 * also be 'poisoned' by the invalid RevObject.
103 * <p>
104 * This method will automatically call {@link RevWalk#markStart(RevCommit)}
105 * if passed RevCommit instance, or a RevTag that directly (or indirectly)
106 * references a RevCommit.
108 * @param o
109 * the object to start traversing from. The object passed must be
110 * from this same revision walker.
111 * @throws MissingObjectException
112 * the object supplied is not available from the object
113 * database. This usually indicates the supplied object is
114 * invalid, but the reference was constructed during an earlier
115 * invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}.
116 * @throws IncorrectObjectTypeException
117 * the object was not parsed yet and it was discovered during
118 * parsing that it is not actually the type of the instance
119 * passed in. This usually indicates the caller used the wrong
120 * type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call.
121 * @throws IOException
122 * a pack file or loose object could not be read.
124 public void markStart(RevObject o) throws MissingObjectException,
125 IncorrectObjectTypeException, IOException {
126 while (o instanceof RevTag) {
127 addObject(o);
128 o = ((RevTag) o).getObject();
129 parse(o);
132 if (o instanceof RevCommit)
133 super.markStart((RevCommit) o);
134 else
135 addObject(o);
139 * Mark an object to not produce in the output.
140 * <p>
141 * Uninteresting objects denote not just themselves but also their entire
142 * reachable chain, back until the merge base of an uninteresting commit and
143 * an otherwise interesting commit.
144 * <p>
145 * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)}
146 * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method
147 * requires the object to be parsed before it can be added as a root for the
148 * traversal.
149 * <p>
150 * The method will automatically parse an unparsed object, but error
151 * handling may be more difficult for the application to explain why a
152 * RevObject is not actually valid. The object pool of this walker would
153 * also be 'poisoned' by the invalid RevObject.
154 * <p>
155 * This method will automatically call {@link RevWalk#markStart(RevCommit)}
156 * if passed RevCommit instance, or a RevTag that directly (or indirectly)
157 * references a RevCommit.
159 * @param o
160 * the object to start traversing from. The object passed must be
161 * @throws MissingObjectException
162 * the object supplied is not available from the object
163 * database. This usually indicates the supplied object is
164 * invalid, but the reference was constructed during an earlier
165 * invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}.
166 * @throws IncorrectObjectTypeException
167 * the object was not parsed yet and it was discovered during
168 * parsing that it is not actually the type of the instance
169 * passed in. This usually indicates the caller used the wrong
170 * type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call.
171 * @throws IOException
172 * a pack file or loose object could not be read.
174 public void markUninteresting(RevObject o) throws MissingObjectException,
175 IncorrectObjectTypeException, IOException {
176 while (o instanceof RevTag) {
177 o.flags |= UNINTERESTING;
178 if (hasRevSort(RevSort.BOUNDARY))
179 addObject(o);
180 o = ((RevTag) o).getObject();
181 parse(o);
184 if (o instanceof RevCommit)
185 super.markUninteresting((RevCommit) o);
186 else if (o instanceof RevTree)
187 markTreeUninteresting((RevTree) o);
188 else
189 o.flags |= UNINTERESTING;
191 if (o.getType() != Constants.OBJ_COMMIT && hasRevSort(RevSort.BOUNDARY)) {
192 addObject(o);
196 @Override
197 public RevCommit next() throws MissingObjectException,
198 IncorrectObjectTypeException, IOException {
199 for (;;) {
200 final RevCommit r = super.next();
201 if (r == null)
202 return null;
203 if ((r.flags & UNINTERESTING) != 0) {
204 markTreeUninteresting(r.getTree());
205 if (hasRevSort(RevSort.BOUNDARY)) {
206 objects.add(r.getTree());
207 return r;
209 continue;
211 objects.add(r.getTree());
212 return r;
217 * Pop the next most recent object.
219 * @return next most recent object; null if traversal is over.
220 * @throws MissingObjectException
221 * one or or more of the next objects are not available from the
222 * object database, but were thought to be candidates for
223 * traversal. This usually indicates a broken link.
224 * @throws IncorrectObjectTypeException
225 * one or or more of the objects in a tree do not match the type
226 * indicated.
227 * @throws IOException
228 * a pack file or loose object could not be read.
230 public RevObject nextObject() throws MissingObjectException,
231 IncorrectObjectTypeException, IOException {
232 fromTreeWalk = false;
234 if (enterSubtree) {
235 treeWalk.enterSubtree();
236 enterSubtree = false;
239 while (treeWalk.next()) {
240 final FileMode mode = treeWalk.getFileMode(0);
241 final int sType = mode.getObjectType();
243 switch (sType) {
244 case Constants.OBJ_BLOB: {
245 final RevObject o = lookupAny(treeWalk.getObjectId(0), sType);
246 if ((o.flags & SEEN) != 0)
247 continue;
248 o.flags |= SEEN;
249 if ((o.flags & UNINTERESTING) != 0
250 && !hasRevSort(RevSort.BOUNDARY))
251 continue;
252 fromTreeWalk = true;
253 return o;
255 case Constants.OBJ_TREE: {
256 final RevObject o = lookupAny(treeWalk.getObjectId(0), sType);
257 if ((o.flags & SEEN) != 0)
258 continue;
259 o.flags |= SEEN;
260 if ((o.flags & UNINTERESTING) != 0
261 && !hasRevSort(RevSort.BOUNDARY))
262 continue;
263 enterSubtree = true;
264 fromTreeWalk = true;
265 return o;
267 default:
268 if (FileMode.GITLINK.equals(mode))
269 continue;
270 throw new CorruptObjectException("Invalid mode " + mode
271 + " for " + treeWalk.getObjectId(0) + " "
272 + treeWalk.getPathString() + " in " + currentTree + ".");
276 for (;;) {
277 final RevObject o = objects.next();
278 if (o == null)
279 return null;
280 if ((o.flags & SEEN) != 0)
281 continue;
282 o.flags |= SEEN;
283 if ((o.flags & UNINTERESTING) != 0 && !hasRevSort(RevSort.BOUNDARY))
284 continue;
285 if (o instanceof RevTree) {
286 currentTree = (RevTree) o;
287 treeWalk.reset(new ObjectId[] { currentTree });
289 return o;
294 * Verify all interesting objects are available, and reachable.
295 * <p>
296 * Callers should populate starting points and ending points with
297 * {@link #markStart(RevObject)} and {@link #markUninteresting(RevObject)}
298 * and then use this method to verify all objects between those two points
299 * exist in the repository and are readable.
300 * <p>
301 * This method returns successfully if everything is connected; it throws an
302 * exception if there is a connectivity problem. The exception message
303 * provides some detail about the connectivity failure.
305 * @throws MissingObjectException
306 * one or or more of the next objects are not available from the
307 * object database, but were thought to be candidates for
308 * traversal. This usually indicates a broken link.
309 * @throws IncorrectObjectTypeException
310 * one or or more of the objects in a tree do not match the type
311 * indicated.
312 * @throws IOException
313 * a pack file or loose object could not be read.
315 public void checkConnectivity() throws MissingObjectException,
316 IncorrectObjectTypeException, IOException {
317 for (;;) {
318 final RevCommit c = next();
319 if (c == null)
320 break;
322 for (;;) {
323 final RevObject o = nextObject();
324 if (o == null)
325 break;
326 if (o instanceof RevBlob && !db.hasObject(o))
327 throw new MissingObjectException(o, Constants.TYPE_BLOB);
332 * Get the current object's complete path.
333 * <p>
334 * This method is not very efficient and is primarily meant for debugging
335 * and final output generation. Applications should try to avoid calling it,
336 * and if invoked do so only once per interesting entry, where the name is
337 * absolutely required for correct function.
339 * @return complete path of the current entry, from the root of the
340 * repository. If the current entry is in a subtree there will be at
341 * least one '/' in the returned string. Null if the current entry
342 * has no path, such as for annotated tags or root level trees.
344 public String getPathString() {
345 return fromTreeWalk ? treeWalk.getPathString() : null;
348 @Override
349 public void dispose() {
350 super.dispose();
351 objects = new BlockObjQueue();
352 enterSubtree = false;
353 currentTree = null;
356 @Override
357 protected void reset(final int retainFlags) {
358 super.reset(retainFlags);
359 objects = new BlockObjQueue();
360 enterSubtree = false;
363 private void addObject(final RevObject o) {
364 if ((o.flags & SEEN) == 0) {
365 o.flags |= SEEN;
366 objects.add(o);
370 private void markTreeUninteresting(final RevTree tree)
371 throws MissingObjectException, IncorrectObjectTypeException,
372 IOException {
373 if ((tree.flags & UNINTERESTING) != 0)
374 return;
375 tree.flags |= UNINTERESTING;
377 treeWalk.reset(new ObjectId[] { tree });
378 while (treeWalk.next()) {
379 final FileMode mode = treeWalk.getFileMode(0);
380 final int sType = mode.getObjectType();
382 switch (sType) {
383 case Constants.OBJ_BLOB: {
384 final ObjectId sid = treeWalk.getObjectId(0);
385 lookupAny(sid, sType).flags |= UNINTERESTING;
386 continue;
388 case Constants.OBJ_TREE: {
389 final ObjectId sid = treeWalk.getObjectId(0);
390 final RevObject subtree = lookupAny(sid, sType);
391 if ((subtree.flags & UNINTERESTING) == 0) {
392 subtree.flags |= UNINTERESTING;
393 treeWalk.enterSubtree();
395 continue;
397 default:
398 if (FileMode.GITLINK.equals(mode))
399 continue;
400 throw new CorruptObjectException("Invalid mode " + mode
401 + " for " + treeWalk.getObjectId(0) + " "
402 + treeWalk.getPathString() + " in " + tree + ".");