2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
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
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
.Repository
;
49 import org
.spearce
.jgit
.treewalk
.CanonicalTreeParser
;
52 * Specialized subclass of RevWalk to include trees, blobs and tags.
54 * Unlike RevWalk this subclass is able to remember starting roots that include
55 * annotated tags, or arbitrary trees or blobs. Once commit generation is
56 * complete and all commits have been popped by the application, individual
57 * annotated tag, tree and blob objects can be popped through the additional
58 * method {@link #nextObject()}.
60 * Tree and blob objects reachable from interesting commits are automatically
61 * scheduled for inclusion in the results of {@link #nextObject()}, returning
62 * each object exactly once. Objects are sorted and returned according to the
63 * the commits that reference them and the order they appear within a tree.
64 * Ordering can be affected by changing the {@link RevSort} used to order the
65 * commits that are returned first.
67 public class ObjectWalk
extends RevWalk
{
69 * Indicates a non-RevCommit is in {@link #pendingObjects}.
71 * We can safely reuse {@link RevWalk#REWRITE} here for the same value as it
72 * is only set on RevCommit and {@link #pendingObjects} never has RevCommit
73 * instances inserted into it.
75 private static final int IN_PENDING
= RevWalk
.REWRITE
;
77 private CanonicalTreeParser treeWalk
;
79 private BlockObjQueue pendingObjects
;
81 private RevTree currentTree
;
83 private boolean fromTreeWalk
;
85 private RevTree nextSubtree
;
88 * Create a new revision and object walker for a given repository.
91 * the repository the walker will obtain data from.
93 public ObjectWalk(final Repository repo
) {
95 pendingObjects
= new BlockObjQueue();
96 treeWalk
= new CanonicalTreeParser();
100 * Mark an object or commit to start graph traversal from.
102 * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)}
103 * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method
104 * requires the object to be parsed before it can be added as a root for the
107 * The method will automatically parse an unparsed object, but error
108 * handling may be more difficult for the application to explain why a
109 * RevObject is not actually valid. The object pool of this walker would
110 * also be 'poisoned' by the invalid RevObject.
112 * This method will automatically call {@link RevWalk#markStart(RevCommit)}
113 * if passed RevCommit instance, or a RevTag that directly (or indirectly)
114 * references a RevCommit.
117 * the object to start traversing from. The object passed must be
118 * from this same revision walker.
119 * @throws MissingObjectException
120 * the object supplied is not available from the object
121 * database. This usually indicates the supplied object is
122 * invalid, but the reference was constructed during an earlier
123 * invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}.
124 * @throws IncorrectObjectTypeException
125 * the object was not parsed yet and it was discovered during
126 * parsing that it is not actually the type of the instance
127 * passed in. This usually indicates the caller used the wrong
128 * type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call.
129 * @throws IOException
130 * a pack file or loose object could not be read.
132 public void markStart(RevObject o
) throws MissingObjectException
,
133 IncorrectObjectTypeException
, IOException
{
134 while (o
instanceof RevTag
) {
136 o
= ((RevTag
) o
).getObject();
140 if (o
instanceof RevCommit
)
141 super.markStart((RevCommit
) o
);
147 * Mark an object to not produce in the output.
149 * Uninteresting objects denote not just themselves but also their entire
150 * reachable chain, back until the merge base of an uninteresting commit and
151 * an otherwise interesting commit.
153 * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)}
154 * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method
155 * requires the object to be parsed before it can be added as a root for the
158 * The method will automatically parse an unparsed object, but error
159 * handling may be more difficult for the application to explain why a
160 * RevObject is not actually valid. The object pool of this walker would
161 * also be 'poisoned' by the invalid RevObject.
163 * This method will automatically call {@link RevWalk#markStart(RevCommit)}
164 * if passed RevCommit instance, or a RevTag that directly (or indirectly)
165 * references a RevCommit.
168 * the object to start traversing from. The object passed must be
169 * @throws MissingObjectException
170 * the object supplied is not available from the object
171 * database. This usually indicates the supplied object is
172 * invalid, but the reference was constructed during an earlier
173 * invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}.
174 * @throws IncorrectObjectTypeException
175 * the object was not parsed yet and it was discovered during
176 * parsing that it is not actually the type of the instance
177 * passed in. This usually indicates the caller used the wrong
178 * type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call.
179 * @throws IOException
180 * a pack file or loose object could not be read.
182 public void markUninteresting(RevObject o
) throws MissingObjectException
,
183 IncorrectObjectTypeException
, IOException
{
184 while (o
instanceof RevTag
) {
185 o
.flags
|= UNINTERESTING
;
186 if (hasRevSort(RevSort
.BOUNDARY
))
188 o
= ((RevTag
) o
).getObject();
192 if (o
instanceof RevCommit
)
193 super.markUninteresting((RevCommit
) o
);
194 else if (o
instanceof RevTree
)
195 markTreeUninteresting((RevTree
) o
);
197 o
.flags
|= UNINTERESTING
;
199 if (o
.getType() != Constants
.OBJ_COMMIT
&& hasRevSort(RevSort
.BOUNDARY
)) {
205 public RevCommit
next() throws MissingObjectException
,
206 IncorrectObjectTypeException
, IOException
{
208 final RevCommit r
= super.next();
211 if ((r
.flags
& UNINTERESTING
) != 0) {
212 markTreeUninteresting(r
.getTree());
213 if (hasRevSort(RevSort
.BOUNDARY
)) {
214 pendingObjects
.add(r
.getTree());
219 pendingObjects
.add(r
.getTree());
225 * Pop the next most recent object.
227 * @return next most recent object; null if traversal is over.
228 * @throws MissingObjectException
229 * one or or more of the next objects are not available from the
230 * object database, but were thought to be candidates for
231 * traversal. This usually indicates a broken link.
232 * @throws IncorrectObjectTypeException
233 * one or or more of the objects in a tree do not match the type
235 * @throws IOException
236 * a pack file or loose object could not be read.
238 public RevObject
nextObject() throws MissingObjectException
,
239 IncorrectObjectTypeException
, IOException
{
240 fromTreeWalk
= false;
242 if (nextSubtree
!= null) {
243 treeWalk
= treeWalk
.createSubtreeIterator0(db
, nextSubtree
, curs
);
247 while (!treeWalk
.eof()) {
248 final FileMode mode
= treeWalk
.getEntryFileMode();
249 final int sType
= mode
.getObjectType();
252 case Constants
.OBJ_BLOB
: {
253 treeWalk
.getEntryObjectId(idBuffer
);
254 final RevBlob o
= lookupBlob(idBuffer
);
255 if ((o
.flags
& SEEN
) != 0)
258 if (shouldSkipObject(o
))
263 case Constants
.OBJ_TREE
: {
264 treeWalk
.getEntryObjectId(idBuffer
);
265 final RevTree o
= lookupTree(idBuffer
);
266 if ((o
.flags
& SEEN
) != 0)
269 if (shouldSkipObject(o
))
276 if (FileMode
.GITLINK
.equals(mode
))
278 treeWalk
.getEntryObjectId(idBuffer
);
279 throw new CorruptObjectException("Invalid mode " + mode
280 + " for " + idBuffer
.name() + " "
281 + treeWalk
.getEntryPathString() + " in " + currentTree
285 treeWalk
= treeWalk
.next();
289 final RevObject o
= pendingObjects
.next();
292 if ((o
.flags
& SEEN
) != 0)
295 if (shouldSkipObject(o
))
297 if (o
instanceof RevTree
) {
298 currentTree
= (RevTree
) o
;
299 treeWalk
= treeWalk
.resetRoot(db
, currentTree
, curs
);
305 private final boolean shouldSkipObject(final RevObject o
) {
306 return (o
.flags
& UNINTERESTING
) != 0 && !hasRevSort(RevSort
.BOUNDARY
);
310 * Verify all interesting objects are available, and reachable.
312 * Callers should populate starting points and ending points with
313 * {@link #markStart(RevObject)} and {@link #markUninteresting(RevObject)}
314 * and then use this method to verify all objects between those two points
315 * exist in the repository and are readable.
317 * This method returns successfully if everything is connected; it throws an
318 * exception if there is a connectivity problem. The exception message
319 * provides some detail about the connectivity failure.
321 * @throws MissingObjectException
322 * one or or more of the next objects are not available from the
323 * object database, but were thought to be candidates for
324 * traversal. This usually indicates a broken link.
325 * @throws IncorrectObjectTypeException
326 * one or or more of the objects in a tree do not match the type
328 * @throws IOException
329 * a pack file or loose object could not be read.
331 public void checkConnectivity() throws MissingObjectException
,
332 IncorrectObjectTypeException
, IOException
{
334 final RevCommit c
= next();
339 final RevObject o
= nextObject();
342 if (o
instanceof RevBlob
&& !db
.hasObject(o
))
343 throw new MissingObjectException(o
, Constants
.TYPE_BLOB
);
348 * Get the current object's complete path.
350 * This method is not very efficient and is primarily meant for debugging
351 * and final output generation. Applications should try to avoid calling it,
352 * and if invoked do so only once per interesting entry, where the name is
353 * absolutely required for correct function.
355 * @return complete path of the current entry, from the root of the
356 * repository. If the current entry is in a subtree there will be at
357 * least one '/' in the returned string. Null if the current entry
358 * has no path, such as for annotated tags or root level trees.
360 public String
getPathString() {
361 return fromTreeWalk ? treeWalk
.getEntryPathString() : null;
365 public void dispose() {
367 pendingObjects
= new BlockObjQueue();
373 protected void reset(final int retainFlags
) {
374 super.reset(retainFlags
);
375 pendingObjects
= new BlockObjQueue();
379 private void addObject(final RevObject o
) {
380 if ((o
.flags
& IN_PENDING
) == 0) {
381 o
.flags
|= IN_PENDING
;
382 pendingObjects
.add(o
);
386 private void markTreeUninteresting(final RevTree tree
)
387 throws MissingObjectException
, IncorrectObjectTypeException
,
389 if ((tree
.flags
& UNINTERESTING
) != 0)
391 tree
.flags
|= UNINTERESTING
;
393 treeWalk
= treeWalk
.resetRoot(db
, tree
, curs
);
394 while (!treeWalk
.eof()) {
395 final FileMode mode
= treeWalk
.getEntryFileMode();
396 final int sType
= mode
.getObjectType();
399 case Constants
.OBJ_BLOB
: {
400 treeWalk
.getEntryObjectId(idBuffer
);
401 lookupBlob(idBuffer
).flags
|= UNINTERESTING
;
404 case Constants
.OBJ_TREE
: {
405 treeWalk
.getEntryObjectId(idBuffer
);
406 final RevTree t
= lookupTree(idBuffer
);
407 if ((t
.flags
& UNINTERESTING
) == 0) {
408 t
.flags
|= UNINTERESTING
;
409 treeWalk
= treeWalk
.createSubtreeIterator0(db
, t
, curs
);
415 if (FileMode
.GITLINK
.equals(mode
))
417 treeWalk
.getEntryObjectId(idBuffer
);
418 throw new CorruptObjectException("Invalid mode " + mode
419 + " for " + idBuffer
.name() + " "
420 + treeWalk
.getEntryPathString() + " in " + tree
+ ".");
423 treeWalk
= treeWalk
.next();