n>boolean enable) { assertNotStarted(); assertNoCommitsMarkedStart(); firstParent = enable; queue = new DateRevQueue(firstParent); pending = new StartGenerator(this); } /** * Locate a reference to a blob without loading it. * <p> * The blob may or may not exist in the repository. It is impossible to tell * from this method's return value. * * @param id * name of the blob object. * @return reference to the blob object. Never null. */ @NonNull public RevBlob lookupBlob(AnyObjectId id) { RevBlob c = (RevBlob) objects.get(id); if (c == null) { c = new RevBlob(id); objects.add(c); } return c; } /** * Locate a reference to a tree without loading it. * <p> * The tree may or may not exist in the repository. It is impossible to tell * from this method's return value. * * @param id * name of the tree object. * @return reference to the tree object. Never null. */ @NonNull public RevTree lookupTree(AnyObjectId id) { RevTree c = (RevTree) objects.get(id); if (c == null) { c = new RevTree(id); objects.add(c); } return c; } /** * Locate a reference to a commit without loading it. * <p> * The commit may or may not exist in the repository. It is impossible to * tell from this method's return value. * <p> * See {@link #parseHeaders(RevObject)} and {@link #parseBody(RevObject)} * for loading contents. * * @param id * name of the commit object. * @return reference to the commit object. Never null. */ @NonNull public RevCommit lookupCommit(AnyObjectId id) { RevCommit c = (RevCommit) objects.get(id); if (c == null) { c = createCommit(id); objects.add(c); } return c; } /** * This method is intended to be invoked only by {@link RevCommitCG}, in * order to give commit the correct graphPosition before accessing the * commit-graph. In this way, the headers of the commit can be obtained in * constant time. * * @param id * name of the commit object. * @param graphPos * the position in the commit-graph of the object. * @return reference to the commit object. Never null. * @since 6.5 */ @NonNull protected RevCommit lookupCommit(AnyObjectId id, int graphPos) { RevCommit c = (RevCommit) objects.get(id); if (c == null) { c = createCommit(id, graphPos); objects.add(c); } return c; } /** * Locate a reference to a tag without loading it. * <p> * The tag may or may not exist in the repository. It is impossible to tell * from this method's return value. * * @param id * name of the tag object. * @return reference to the tag object. Never null. */ @NonNull public RevTag lookupTag(AnyObjectId id) { RevTag c = (RevTag) objects.get(id); if (c == null) { c = new RevTag(id); objects.add(c); } return c; } /** * Locate a reference to any object without loading it. * <p> * The object may or may not exist in the repository. It is impossible to * tell from this method's return value. * * @param id * name of the object. * @param type * type of the object. Must be a valid Git object type. * @return reference to the object. Never null. */ @NonNull public RevObject lookupAny(AnyObjectId id, int type) { RevObject r = objects.get(id); if (r == null) { switch (type) { case Constants.OBJ_COMMIT: r = createCommit(id); break; case Constants.OBJ_TREE: r = new RevTree(id); break; case Constants.OBJ_BLOB: r = new RevBlob(id); break; case Constants.OBJ_TAG: r = new RevTag(id); break; default: throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidGitType, Integer.valueOf(type))); } objects.add(r); } return r; } /** * Locate an object that was previously allocated in this walk. * * @param id * name of the object. * @return reference to the object if it has been previously located; * otherwise null. */ public RevObject lookupOrNull(AnyObjectId id) { return objects.get(id); } /** * Locate a reference to a commit and immediately parse its content. * <p> * Unlike {@link #lookupCommit(AnyObjectId)} this method only returns * successfully if the commit object exists, is verified to be a commit, and * was parsed without error. * * @param id * name of the commit object. * @return reference to the commit object. Never null. * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied commit does not exist. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the supplied id is not a commit or an annotated tag. * @throws java.io.IOException * a pack file or loose object could not be read. */ @NonNull public RevCommit parseCommit(AnyObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException { RevObject c = peel(parseAny(id)); if (!(c instanceof RevCommit)) throw new IncorrectObjectTypeException(id.toObjectId(), Constants.TYPE_COMMIT); return (RevCommit) c; } /** * Locate a reference to a tree. * <p> * This method only returns successfully if the tree object exists, is * verified to be a tree. * * @param id * name of the tree object, or a commit or annotated tag that may * reference a tree. * @return reference to the tree object. Never null. * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied tree does not exist. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the supplied id is not a tree, a commit or an annotated tag. * @throws java.io.IOException * a pack file or loose object could not be read. */ @NonNull public RevTree parseTree(AnyObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException { RevObject c = peel(parseAny(id)); final RevTree t; if (c instanceof RevCommit) t = ((RevCommit) c).getTree(); else if (!(c instanceof RevTree)) throw new IncorrectObjectTypeException(id.toObjectId(), Constants.TYPE_TREE); else t = (RevTree) c; parseHeaders(t); return t; } /** * Locate a reference to an annotated tag and immediately parse its content. * <p> * Unlike {@link #lookupTag(AnyObjectId)} this method only returns * successfully if the tag object exists, is verified to be a tag, and was * parsed without error. * * @param id * name of the tag object. * @return reference to the tag object. Never null. * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied tag does not exist. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the supplied id is not a tag or an annotated tag. * @throws java.io.IOException * a pack file or loose object could not be read. */ @NonNull public RevTag parseTag(AnyObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException { RevObject c = parseAny(id); if (!(c instanceof RevTag)) throw new IncorrectObjectTypeException(id.toObjectId(), Constants.TYPE_TAG); return (RevTag) c; } /** * Locate a reference to any object and immediately parse its headers. * <p> * This method only returns successfully if the object exists and was parsed * without error. Parsing an object can be expensive as the type must be * determined. For blobs this may mean the blob content was unpacked * unnecessarily, and thrown away. * * @param id * name of the object. * @return reference to the object. Never null. * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied does not exist. * @throws java.io.IOException * a pack file or loose object could not be read. */ @NonNull public RevObject parseAny(AnyObjectId id) throws MissingObjectException, IOException { RevObject r = objects.get(id); if (r == null) r = parseNew(id, reader.open(id)); else parseHeaders(r); return r; } private RevObject parseNew(AnyObjectId id, ObjectLoader ldr) throws LargeObjectException, CorruptObjectException, MissingObjectException, IOException { RevObject r; int type = ldr.getType(); switch (type) { case Constants.OBJ_COMMIT: { final RevCommit c = createCommit(id); c.parseCanonical(this, getCachedBytes(c, ldr)); r = c; break; } case Constants.OBJ_TREE: { r = new RevTree(id); r.flags |= PARSED; break; } case Constants.OBJ_BLOB: { r = new RevBlob(id); r.flags |= PARSED; break; } case Constants.OBJ_TAG: { final RevTag t = new RevTag(id); t.parseCanonical(this, getCachedBytes(t, ldr)); r = t; break; } default: throw new IllegalArgumentException(MessageFormat.format( JGitText.get().badObjectType, Integer.valueOf(type))); } objects.add(r); return r; } byte[] getCachedBytes(RevObject obj) throws LargeObjectException, MissingObjectException, IncorrectObjectTypeException, IOException { return getCachedBytes(obj, reader.open(obj, obj.getType())); } byte[] getCachedBytes(RevObject obj, ObjectLoader ldr) throws LargeObjectException, MissingObjectException, IOException { try { return ldr.getCachedBytes(5 * MB); } catch (LargeObjectException tooBig) { tooBig.setObjectId(obj); throw tooBig; } } /** * Get the commit-graph. * * @return the commit-graph. Never null. * @since 6.5 */ @NonNull CommitGraph commitGraph() { if (commitGraph == null) { try { commitGraph = reader != null ? reader.getCommitGraph().orElse(EMPTY) : EMPTY; } catch (IOException e) { commitGraph = EMPTY; } } return commitGraph; } /** * Asynchronous object parsing. * * @param objectIds * objects to open from the object store. The supplied collection * must not be modified until the queue has finished. * @param reportMissing * if true missing objects are reported by calling failure with a * MissingObjectException. This may be more expensive for the * implementation to guarantee. If false the implementation may * choose to report MissingObjectException, or silently skip over * the object with no warning. * @return queue to read the objects from. */ public <T extends ObjectId> AsyncRevObjectQueue parseAny( Iterable<T> objectIds, boolean reportMissing) { List<T> need = new ArrayList<>(); List<RevObject> have = new ArrayList<>(); for (T id : objectIds) { RevObject r = objects.get(id); if (r != null && (r.flags & PARSED) != 0) have.add(r); else need.add(id); } final Iterator<RevObject> objItr = have.iterator(); if (need.isEmpty()) { return new AsyncRevObjectQueue() { @Override public RevObject next() { return objItr.hasNext() ? objItr.next() : null; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return true; } @Override public void release() { // In-memory only, no action required. } }; } final AsyncObjectLoaderQueue<T> lItr = reader.open(need, reportMissing); return new AsyncRevObjectQueue() { @Override public RevObject next() throws MissingObjectException, IncorrectObjectTypeException, IOException { if (objItr.hasNext()) return objItr.next(); if (!lItr.next()) return null; ObjectId id = lItr.getObjectId(); ObjectLoader ldr = lItr.open(); RevObject r = objects.get(id); if (r == null) r = parseNew(id, ldr); else if (r instanceof RevCommit) { byte[] raw = ldr.getCachedBytes(); ((RevCommit) r).parseCanonical(RevWalk.this, raw); } else if (r instanceof RevTag) { byte[] raw = ldr.getCachedBytes(); ((RevTag) r).parseCanonical(RevWalk.this, raw); } else r.flags |= PARSED; return r; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return lItr.cancel(mayInterruptIfRunning); } @Override public void release() { lItr.release(); } }; } /** * Ensure the object's critical headers have been parsed. * <p> * This method only returns successfully if the object exists and was parsed * without error. * * @param obj * the object the caller needs to be parsed. * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied does not exist. * @throws java.io.IOException * a pack file or loose object could not be read. */ public void parseHeaders(RevObject obj) throws MissingObjectException, IOException { if ((obj.flags & PARSED) == 0) obj.parseHeaders(this); } /** * Ensure the object's full body content is available. * <p> * This method only returns successfully if the object exists and was parsed * without error. * * @param obj * the object the caller needs to be parsed. * @throws org.eclipse.jgit.errors.MissingObjectException * the supplied does not exist. * @throws java.io.IOException * a pack file or loose object could not be read. */ public void parseBody(RevObject obj) throws MissingObjectException, IOException { obj.parseBody(this); } /** * Peel back annotated tags until a non-tag object is found. * * @param obj * the starting object. * @return If {@code obj} is not an annotated tag, {@code obj}. Otherwise * the first non-tag object that {@code obj} references. The * returned object's headers have been parsed. * @throws org.eclipse.jgit.errors.MissingObjectException * a referenced object cannot be found. * @throws java.io.IOException * a pack file or loose object could not be read. */ public RevObject peel(RevObject obj) throws MissingObjectException, IOException { while (obj instanceof RevTag) { parseHeaders(obj); obj = ((RevTag) obj).getObject(); } parseHeaders(obj); return obj; } /** * Create a new flag for application use during walking. * <p> * Applications are only assured to be able to create 24 unique flags on any * given revision walker instance. Any flags beyond 24 are offered only if * the implementation has extra free space within its internal storage. * * @param name * description of the flag, primarily useful for debugging. * @return newly constructed flag instance. * @throws java.lang.IllegalArgumentException * too many flags have been reserved on this revision walker. */ public RevFlag newFlag(String name) { final int m = allocFlag(); return new RevFlag(this, name, m); } int allocFlag() { if (freeFlags == 0) throw new IllegalArgumentException( MessageFormat.format(JGitText.get().flagsAlreadyCreated, Integer.valueOf(32 - RESERVED_FLAGS))); final int m = Integer.lowestOneBit(freeFlags); freeFlags &= ~m; return m; } /** * Automatically carry a flag from a child commit to its parents. * <p> * A carried flag is copied from the child commit onto its parents when the * child commit is popped from the lowest level of walk's internal graph. * * @param flag * the flag to carry onto parents, if set on a descendant. */ public void carry(RevFlag flag) { if ((freeFlags & flag.mask) != 0) throw new IllegalArgumentException(MessageFormat .format(JGitText.get().flagIsDisposed, flag.name)); if (flag.walker != this) throw new IllegalArgumentException(MessageFormat .format(JGitText.get().flagNotFromThis, flag.name)); carryFlags |= flag.mask; } /** * Automatically carry flags from a child commit to its parents. * <p> * A carried flag is copied from the child commit onto its parents when the * child commit is popped from the lowest level of walk's internal graph. * * @param set * the flags to carry onto parents, if set on a descendant. */ public void carry(Collection<RevFlag> set) { for (RevFlag flag : set) carry(flag); } /** * Preserve a RevFlag during all {@code reset} methods. * <p> * Calling {@code retainOnReset(flag)} avoids needing to pass the flag * during each {@code resetRetain()} invocation on this instance. * <p> * Clearing flags marked retainOnReset requires disposing of the flag with * {@code #disposeFlag(RevFlag)} or disposing of the entire RevWalk by * {@code #dispose()}. * * @param flag * the flag to retain during all resets. * @since 3.6 */ public final void retainOnReset(RevFlag flag) { if ((freeFlags & flag.mask) != 0) throw new IllegalArgumentException(MessageFormat .format(JGitText.get().flagIsDisposed, flag.name)); if (flag.walker != this) throw new IllegalArgumentException(MessageFormat .format(JGitText.get().flagNotFromThis, flag.name)); retainOnReset |= flag.mask; } /** * Preserve a set of RevFlags during all {@code reset} methods. * <p> * Calling {@code retainOnReset(set)} avoids needing to pass the flags * during each {@code resetRetain()} invocation on this instance. * <p> * Clearing flags marked retainOnReset requires disposing of the flag with * {@code #disposeFlag(RevFlag)} or disposing of the entire RevWalk by * {@code #dispose()}. * * @param flags * the flags to retain during all resets. * @since 3.6 */ public final void retainOnReset(Collection<RevFlag> flags) { for (RevFlag f : flags) retainOnReset(f); } /** * Allow a flag to be recycled for a different use. * <p> * Recycled flags always come back as a different Java object instance when * assigned again by {@link #newFlag(String)}. * <p> * If the flag was previously being carried, the carrying request is * removed. Disposing of a carried flag while a traversal is in progress has * an undefined behavior. * * @param flag * the to recycle. */ public void disposeFlag(RevFlag flag) { freeFlag(flag.mask); } void freeFlag(int mask) { retainOnReset &= ~mask; if (isNotStarted()) { freeFlags |= mask; carryFlags &= ~mask; } else { delayFreeFlags |= mask; } } private void finishDelayedFreeFlags() { if (delayFreeFlags != 0) { freeFlags |= delayFreeFlags; carryFlags &= ~delayFreeFlags; delayFreeFlags = 0; } } /** * Resets internal state and allows this instance to be used again. * <p> * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit) * instances are not invalidated. RevFlag instances are not invalidated, but * are removed from all RevObjects. */ public final void reset() { reset(0); } /** * Resets internal state and allows this instance to be used again. * <p> * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit) * instances are not invalidated. RevFlag instances are not invalidated, but * are removed from all RevObjects. * * @param retainFlags * application flags that should <b>not</b> be cleared from * existing commit objects. */ public final void resetRetain(RevFlagSet retainFlags) { reset(retainFlags.mask); } /** * Resets internal state and allows this instance to be used again. * <p> * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit) * instances are not invalidated. RevFlag instances are not invalidated, but * are removed from all RevObjects. * <p> * See {@link #retainOnReset(RevFlag)} for an alternative that does not * require passing the flags during each reset. * * @param retainFlags * application flags that should <b>not</b> be cleared from * existing commit objects. */ public final void resetRetain(RevFlag... retainFlags) { int mask = 0; for (RevFlag flag : retainFlags) mask |= flag.mask; reset(mask); } /** * Resets internal state and allows this instance to be used again. * <p> * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit) * instances are not invalidated. RevFlag instances are not invalidated, but * are removed from all RevObjects. The value of {@code firstParent} is * retained. * * @param retainFlags * application flags that should <b>not</b> be cleared from * existing commit objects. */ protected void reset(int retainFlags) { finishDelayedFreeFlags(); retainFlags |= PARSED | retainOnReset; final int clearFlags = ~retainFlags; final FIFORevQueue q = new FIFORevQueue(); for (RevCommit c : roots) { if ((c.flags & clearFlags) == 0) continue; c.flags &= retainFlags; c.reset(); q.add(c); } for (;;) { final RevCommit c = q.next(); if (c == null) break; if (c.getParents() == null) continue; for (RevCommit p : c.getParents()) { if ((p.flags & clearFlags) == 0) continue; p.flags &= retainFlags; p.reset(); q.add(p); } } roots.clear(); queue = new DateRevQueue(firstParent); pending = new StartGenerator(this); } /** * Dispose all internal state and invalidate all RevObject instances. * <p> * All RevObject (and thus RevCommit, etc.) instances previously acquired * from this RevWalk are invalidated by a dispose call. Applications must * not retain or use RevObject instances obtained prior to the dispose call. * All RevFlag instances are also invalidated, and must not be reused. */ public void dispose() { reader.close(); freeFlags = APP_FLAGS; delayFreeFlags = 0; retainOnReset = 0; carryFlags = UNINTERESTING; firstParent = false; objects.clear(); roots.clear(); queue = new DateRevQueue(firstParent); pending = new StartGenerator(this); shallowCommitsInitialized = false; } /** * Like {@link #next()}, but if a checked exception is thrown during the * walk it is rethrown as a {@link RevWalkException}. * * @throws RevWalkException * if an {@link IOException} was thrown. * @return next most recent commit; null if traversal is over. */ @Nullable private RevCommit nextForIterator() { try { return next(); } catch (IOException e) { throw new RevWalkException(e); } } /** * {@inheritDoc} * <p> * Returns an Iterator over the commits of this walker. * <p> * The returned iterator is only useful for one walk. If this RevWalk gets * reset a new iterator must be obtained to walk over the new results. * <p> * Applications must not use both the Iterator and the {@link #next()} API * at the same time. Pick one API and use that for the entire walk. * <p> * If a checked exception is thrown during the walk (see {@link #next()}) it * is rethrown from the Iterator as a {@link RevWalkException}. * * @see RevWalkException */ @Override public Iterator<RevCommit> iterator() { RevCommit first = nextForIterator(); return new Iterator<>() { RevCommit next = first; @Override public boolean hasNext() { return next != null; } @Override public RevCommit next() { RevCommit r = next; next = nextForIterator(); return r; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } /** * Throws an exception if we have started producing output. */ protected void assertNotStarted() { if (isNotStarted()) return; throw new IllegalStateException( JGitText.get().outputHasAlreadyBeenStarted); } /** * Throws an exception if any commits have been marked as start. * <p> * If {@link #markStart(RevCommit)} has already been called, * {@link #reset()} can be called to satisfy this condition. * * @since 5.5 */ protected void assertNoCommitsMarkedStart() { if (roots.isEmpty()) return; throw new IllegalStateException( JGitText.get().commitsHaveAlreadyBeenMarkedAsStart); } private boolean isNotStarted() { return pending instanceof StartGenerator; } /** * Create and return an {@link org.eclipse.jgit.revwalk.ObjectWalk} using * the same objects. * <p> * Prior to using this method, the caller must reset this RevWalk to clean * any flags that were used during the last traversal. * <p> * The returned ObjectWalk uses the same ObjectReader, internal object pool, * and free RevFlags. Once the ObjectWalk is created, this RevWalk should * not be used anymore. * * @return a new walk, using the exact same object pool. */ public ObjectWalk toObjectWalkWithSameObjects() { ObjectWalk ow = new ObjectWalk(reader); RevWalk rw = ow; rw.objects = objects; rw.freeFlags = freeFlags; return ow; } /** * Construct a new unparsed commit for the given object. * * @param id * the object this walker requires a commit reference for. * @return a new unparsed reference for the object. */ protected RevCommit createCommit(AnyObjectId id) { return createCommit(id, commitGraph().findGraphPosition(id)); } private RevCommit createCommit(AnyObjectId id, int graphPos) { if (graphPos >= 0) { return new RevCommitCG(id, graphPos); } return new RevCommit(id); } void carryFlagsImpl(RevCommit c) { final int carry = c.flags & carryFlags; if (carry != 0) RevCommit.carryFlags(c, carry); } /** * Assume additional commits are shallow (have no parents). * <p> * This method is a No-op if the collection is empty. * * @param ids * commits that should be treated as shallow commits, in addition * to any commits already known to be shallow by the repository. * @since 3.3 */ public void assumeShallow(Collection<? extends ObjectId> ids) { for (ObjectId id : ids) lookupCommit(id).parents = RevCommit.NO_PARENTS; } /** * Reads the "shallow" file and applies it by setting the parents of shallow * commits to an empty array. * <p> * There is a sequencing problem if the first commit being parsed is a * shallow commit, since {@link RevCommit#parseCanonical(RevWalk, byte[])} * calls this method before its callers add the new commit to the * {@link RevWalk#objects} map. That means a call from this method to * {@link #lookupCommit(AnyObjectId)} fails to find that commit and creates * a new one, which is promptly discarded. * <p> * To avoid that, {@link RevCommit#parseCanonical(RevWalk, byte[])} passes * its commit to this method, so that this method can apply the shallow * state to it directly and avoid creating the duplicate commit object. * * @param rc * the initial commit being parsed * @throws IOException * if the shallow commits file can't be read */ void initializeShallowCommits(RevCommit rc) throws IOException { if (shallowCommitsInitialized) { throw new IllegalStateException( JGitText.get().shallowCommitsAlreadyInitialized); } shallowCommitsInitialized = true; if (reader == null) { return; } for (ObjectId id : reader.getShallowCommits()) { if (id.equals(rc.getId())) { rc.parents = RevCommit.NO_PARENTS; } else { lookupCommit(id).parents = RevCommit.NO_PARENTS; } } } }