Mercurial > jhg
changeset 192:e5407b5a586a
Incoming and Outgoing commands are alive
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Fri, 15 Apr 2011 03:17:03 +0200 |
parents | b777502a06f5 |
children | 37f3d4a596e4 |
files | cmdline/org/tmatesoft/hg/console/Incoming.java cmdline/org/tmatesoft/hg/console/Outgoing.java src/org/tmatesoft/hg/core/ChangesetTransformer.java src/org/tmatesoft/hg/core/HgIncomingCommand.java src/org/tmatesoft/hg/core/HgOutgoingCommand.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/repo/Revlog.java |
diffstat | 7 files changed, 349 insertions(+), 99 deletions(-) [+] |
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Incoming.java Thu Apr 14 19:53:31 2011 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Incoming.java Fri Apr 15 03:17:03 2011 +0200 @@ -16,7 +16,6 @@ */ package org.tmatesoft.hg.console; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -26,20 +25,19 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import java.util.ListIterator; import java.util.Map.Entry; +import org.tmatesoft.hg.console.Outgoing.ChangesetFormatter; import org.tmatesoft.hg.core.HgBadStateException; -import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.RepositoryComparator; import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain; import org.tmatesoft.hg.repo.HgBundle; import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgLookup; import org.tmatesoft.hg.repo.HgRemoteRepository; import org.tmatesoft.hg.repo.HgRepository; -import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; /** @@ -75,14 +73,14 @@ // RepositoryComparator repoCompare = new RepositoryComparator(pw, hgRemote); repoCompare.compare(null); - List<BranchChain> missingBranches0 = repoCompare.calculateMissingBranches(); + List<BranchChain> missingBranches = repoCompare.calculateMissingBranches(); final LinkedHashSet<Nodeid> common = new LinkedHashSet<Nodeid>(); // XXX common can be obtained from repoCompare, but at the moment it would almost duplicate work of calculateMissingBranches // once I refactor latter, common shall be taken from repoCompare. - for (BranchChain bc : missingBranches0) { + for (BranchChain bc : missingBranches) { bc.dump(); common.add(bc.branchRoot); // common known node - List<Nodeid> missing = visitBranches(repoCompare, bc); + List<Nodeid> missing = repoCompare.visitBranches(bc); assert bc.branchRoot.equals(missing.get(0)); missing.remove(0); Collections.reverse(missing); // useful to test output, from newer to older @@ -101,6 +99,7 @@ HgBundle changegroup = hgRemote.getChanges(new LinkedList<Nodeid>(common)); changegroup.changes(hgRepo, new HgChangelog.Inspector() { private int localIndex; + private final ChangesetFormatter formatter = new ChangesetFormatter(); public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { if (pw.knownNode(nodeid)) { @@ -110,56 +109,11 @@ localIndex = hgRepo.getChangelog().getLocalRevision(nodeid); return; } - System.out.printf("changeset: %d:%s\n", ++localIndex, nodeid.toString()); - System.out.printf("user: %s\n", cset.user()); - System.out.printf("date: %s\n", cset.dateString()); - System.out.printf("comment: %s\n\n", cset.comment()); + System.out.println(formatter.simple(++localIndex, nodeid, cset)); } }); } - // returns in order from branch root to head - // for a non-empty BranchChain, shall return modifiable list - private static List<Nodeid> visitBranches(RepositoryComparator repoCompare, BranchChain bc) throws HgException { - if (bc == null) { - return Collections.emptyList(); - } - List<Nodeid> mine = repoCompare.completeBranch(bc.branchRoot, bc.branchHead); - if (bc.isTerminal()) { - return mine; - } - List<Nodeid> parentBranch1 = visitBranches(repoCompare, bc.p1); - List<Nodeid> parentBranch2 = visitBranches(repoCompare, bc.p2); - // merge - LinkedList<Nodeid> merged = new LinkedList<Nodeid>(); - ListIterator<Nodeid> i1 = parentBranch1.listIterator(), i2 = parentBranch2.listIterator(); - while (i1.hasNext() && i2.hasNext()) { - Nodeid n1 = i1.next(); - Nodeid n2 = i2.next(); - if (n1.equals(n2)) { - merged.addLast(n1); - } else { - // first different => add both, and continue adding both tails sequentially - merged.add(n2); - merged.add(n1); - break; - } - } - // copy rest of second parent branch - while (i2.hasNext()) { - merged.add(i2.next()); - } - // copy rest of first parent branch - while (i1.hasNext()) { - merged.add(i1.next()); - } - // - ArrayList<Nodeid> rv = new ArrayList<Nodeid>(mine.size() + merged.size()); - rv.addAll(merged); - rv.addAll(mine); - return rv; - } - private static class SequenceConstructor {
--- a/cmdline/org/tmatesoft/hg/console/Outgoing.java Thu Apr 14 19:53:31 2011 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Outgoing.java Fri Apr 15 03:17:03 2011 +0200 @@ -17,18 +17,15 @@ package org.tmatesoft.hg.console; import java.util.Collection; -import java.util.Collections; import java.util.List; -import org.tmatesoft.hg.core.HgBadStateException; -import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.RepositoryComparator; import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgLookup; import org.tmatesoft.hg.repo.HgRemoteRepository; import org.tmatesoft.hg.repo.HgRepository; -import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; /** @@ -41,6 +38,7 @@ public class Outgoing { public static void main(String[] args) throws Exception { + final boolean debug = true; // perhaps, use hg4j.remote.debug or own property? Options cmdLineOpts = Options.parse(args); HgRepository hgRepo = cmdLineOpts.findRepository(); if (hgRepo.isInvalid()) { @@ -59,46 +57,38 @@ RepositoryComparator repoCompare = new RepositoryComparator(pw, hgRemote); repoCompare.compare(null); - List<Nodeid> commonKnown = repoCompare.getCommon(); - dump("Nodes known to be both locally and at remote server", commonKnown); - // sanity check - for (Nodeid n : commonKnown) { - if (!pw.knownNode(n)) { - throw new HgException("Unknown node reported as common:" + n); - } + if (debug) { + List<Nodeid> commonKnown = repoCompare.getCommon(); + dump("Nodes known to be both locally and at remote server", commonKnown); } // find all local children of commonKnown - List<Nodeid> result = pw.childrenOf(commonKnown); + List<Nodeid> result = repoCompare.getLocalOnlyRevisions(); dump("Lite", result); - // another approach to get all changes after common: - // find index of earliest revision, and report all that were later - int earliestRevision = Integer.MAX_VALUE; - for (Nodeid n : commonKnown) { - if (pw.childrenOf(Collections.singletonList(n)).isEmpty()) { - // there might be (old) nodes, known both locally and remotely, with no children - // hence, we don't need to consider their local revision number - continue; - } - int lr = changelog.getLocalRevision(n); - if (lr < earliestRevision) { - earliestRevision = lr; - } - } - if (earliestRevision < 0 || earliestRevision >= changelog.getLastRevision()) { - throw new HgBadStateException(String.format("Invalid index of common known revision: %d in total of %d", earliestRevision, 1+changelog.getLastRevision())); - } + // + // System.out.println("Full"); // show all, starting from next to common - changelog.range(earliestRevision+1, changelog.getLastRevision(), new HgChangelog.Inspector() { + repoCompare.visitLocalOnlyRevisions(new HgChangelog.Inspector() { + private final ChangesetFormatter formatter = new ChangesetFormatter(); public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { - System.out.printf("changeset: %d:%s\n", revisionNumber, nodeid.toString()); - System.out.printf("user: %s\n", cset.user()); - System.out.printf("date: %s\n", cset.dateString()); - System.out.printf("comment: %s\n\n", cset.comment()); + System.out.println(formatter.simple(revisionNumber, nodeid, cset)); } }); } + + public static class ChangesetFormatter { + private final StringBuilder sb = new StringBuilder(1024); + + public CharSequence simple(int revisionNumber, Nodeid nodeid, RawChangeset cset) { + sb.setLength(0); + sb.append(String.format("changeset: %d:%s\n", revisionNumber, nodeid.toString())); + sb.append(String.format("user: %s\n", cset.user())); + sb.append(String.format("date: %s\n", cset.dateString())); + sb.append(String.format("comment: %s\n\n", cset.comment())); + return sb; + } + } private static void dump(String s, Collection<Nodeid> c) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/ChangesetTransformer.java Fri Apr 15 03:17:03 2011 +0200 @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.core; + +import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgStatusCollector; +import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; +import org.tmatesoft.hg.util.PathPool; +import org.tmatesoft.hg.util.PathRewrite; + +/** + * Bridges {@link HgChangelog.RawChangeset} with high-level {@link HgChangeset} API + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +/*package-local*/ class ChangesetTransformer implements HgChangelog.Inspector { + private final HgLogCommand.Handler handler; + private final HgChangeset changeset; + + public ChangesetTransformer(HgRepository hgRepo, HgLogCommand.Handler delegate) { + if (hgRepo == null || delegate == null) { + throw new IllegalArgumentException(); + } + HgStatusCollector statusCollector = new HgStatusCollector(hgRepo); + PathPool pp = new PathPool(new PathRewrite.Empty()); + statusCollector.setPathPool(pp); + changeset = new HgChangeset(statusCollector, pp); + handler = delegate; + } + + public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { + changeset.init(revisionNumber, nodeid, cset); + handler.next(changeset); + } +} \ No newline at end of file
--- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java Thu Apr 14 19:53:31 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java Fri Apr 15 03:17:03 2011 +0200 @@ -16,26 +16,50 @@ */ package org.tmatesoft.hg.core; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; +import org.tmatesoft.hg.internal.RepositoryComparator; +import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain; +import org.tmatesoft.hg.repo.HgBundle; +import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; +import org.tmatesoft.hg.repo.HgRemoteRepository; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.CancelledException; /** - * + * Command to find out changes available in a remote repository, missing locally. + * * @author Artem Tikhomirov * @author TMate Software Ltd. */ public class HgIncomingCommand { - private final HgRepository repo; + private final HgRepository localRepo; + private HgRemoteRepository remoteRepo; private boolean includeSubrepo; + private RepositoryComparator comparator; + private List<BranchChain> missingBranches; + private HgChangelog.ParentWalker parentHelper; public HgIncomingCommand(HgRepository hgRepo) { - repo = hgRepo; + localRepo = hgRepo; + } + + public HgIncomingCommand against(HgRemoteRepository hgRemote) { + remoteRepo = hgRemote; + comparator = null; + missingBranches = null; + return this; } /** + * PLACEHOLDER, NOT IMPLEMENTED YET. + * * Select specific branch to pull * @return <code>this</code> for convenience */ @@ -44,6 +68,8 @@ } /** + * PLACEHOLDER, NOT IMPLEMENTED YET. + * * Whether to include sub-repositories when collecting changes, default is <code>true</code> XXX or false? * @return <code>this</code> for convenience */ @@ -61,7 +87,16 @@ * @throws CancelledException */ public List<Nodeid> executeLite(Object context) throws HgException, CancelledException { - throw HgRepository.notImplemented(); + LinkedHashSet<Nodeid> result = new LinkedHashSet<Nodeid>(); + RepositoryComparator repoCompare = getComparator(context); + for (BranchChain bc : getMissingBranches(context)) { + List<Nodeid> missing = repoCompare.visitBranches(bc); + assert bc.branchRoot.equals(missing.get(0)); + missing.remove(0); + result.addAll(missing); + } + ArrayList<Nodeid> rv = new ArrayList<Nodeid>(result); + return rv; } /** @@ -70,7 +105,74 @@ * @throws HgException * @throws CancelledException */ - public void executeFull(HgLogCommand.Handler handler) throws HgException, CancelledException { - throw HgRepository.notImplemented(); + public void executeFull(final HgLogCommand.Handler handler) throws HgException, CancelledException { + if (handler == null) { + throw new IllegalArgumentException("Delegate can't be null"); + } + final List<Nodeid> common = getCommon(handler); + HgBundle changegroup = remoteRepo.getChanges(new LinkedList<Nodeid>(common)); + try { + changegroup.changes(localRepo, new HgChangelog.Inspector() { + private int localIndex; + private final HgChangelog.ParentWalker parentHelper; + private final ChangesetTransformer transformer; + private final HgChangelog changelog; + + { + transformer = new ChangesetTransformer(localRepo, handler); + parentHelper = getParentHelper(); + changelog = localRepo.getChangelog(); + } + + public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { + if (parentHelper.knownNode(nodeid)) { + if (!common.contains(nodeid)) { + throw new HgBadStateException("Bundle shall not report known nodes other than roots we've supplied"); + } + localIndex = changelog.getLocalRevision(nodeid); + return; + } + transformer.next(++localIndex, nodeid, cset); + } + }); + } catch (IOException ex) { + throw new HgException(ex); + } + } + + private RepositoryComparator getComparator(Object context) throws HgException, CancelledException { + if (remoteRepo == null) { + throw new HgBadArgumentException("Shall specify remote repository to compare against", null); + } + if (comparator == null) { + comparator = new RepositoryComparator(getParentHelper(), remoteRepo); + comparator.compare(context); + } + return comparator; + } + + private HgChangelog.ParentWalker getParentHelper() { + if (parentHelper == null) { + parentHelper = localRepo.getChangelog().new ParentWalker(); + parentHelper.init(); + } + return parentHelper; + } + + private List<BranchChain> getMissingBranches(Object context) throws HgException, CancelledException { + if (missingBranches == null) { + missingBranches = getComparator(context).calculateMissingBranches(); + } + return missingBranches; + } + + private List<Nodeid> getCommon(Object context) throws HgException, CancelledException { + final LinkedHashSet<Nodeid> common = new LinkedHashSet<Nodeid>(); + // XXX common can be obtained from repoCompare, but at the moment it would almost duplicate work of calculateMissingBranches + // once I refactor latter, common shall be taken from repoCompare. + for (BranchChain bc : getMissingBranches(context)) { + common.add(bc.branchRoot); + } + return new LinkedList<Nodeid>(common); } }
--- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java Thu Apr 14 19:53:31 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgOutgoingCommand.java Fri Apr 15 03:17:03 2011 +0200 @@ -18,23 +18,42 @@ import java.util.List; +import org.tmatesoft.hg.internal.RepositoryComparator; +import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgRemoteRepository; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.CancelledException; /** - * + * Command to find out changes made in a local repository and missing at remote repository. + * * @author Artem Tikhomirov * @author TMate Software Ltd. */ public class HgOutgoingCommand { - private final HgRepository repo; + + private final HgRepository localRepo; + private HgRemoteRepository remoteRepo; private boolean includeSubrepo; + private RepositoryComparator comparator; public HgOutgoingCommand(HgRepository hgRepo) { - repo = hgRepo; + localRepo = hgRepo; } /** + * @param hgRemote remoteRepository to compare against + * @return <code>this</code> for convenience + */ + public HgOutgoingCommand against(HgRemoteRepository hgRemote) { + remoteRepo = hgRemote; + comparator = null; + return this; + } + + /** + * PLACEHOLDER, NOT IMPLEMENTED YET. + * * Select specific branch to pull * @return <code>this</code> for convenience */ @@ -43,6 +62,7 @@ } /** + * PLACEHOLDER, NOT IMPLEMENTED YET. * * @return <code>this</code> for convenience */ @@ -51,11 +71,38 @@ throw HgRepository.notImplemented(); } + /** + * Lightweight check for outgoing changes. + * + * @param context + * @return list on local nodes known to be missing at remote server + */ public List<Nodeid> executeLite(Object context) throws HgException, CancelledException { - throw HgRepository.notImplemented(); + return getComparator(context).getLocalOnlyRevisions(); } - public void executeFull(HgLogCommand.Handler handler) throws HgException, CancelledException { - throw HgRepository.notImplemented(); + /** + * Complete information about outgoing changes + * + * @param handler delegate to process changes + */ + public void executeFull(final HgLogCommand.Handler handler) throws HgException, CancelledException { + if (handler == null) { + throw new IllegalArgumentException("Delegate can't be null"); + } + getComparator(handler).visitLocalOnlyRevisions(new ChangesetTransformer(localRepo, handler)); + } + + private RepositoryComparator getComparator(Object context) throws HgException, CancelledException { + if (remoteRepo == null) { + throw new IllegalArgumentException("Shall specify remote repository to compare against"); + } + if (comparator == null) { + HgChangelog.ParentWalker pw = localRepo.getChangelog().new ParentWalker(); + pw.init(); + comparator = new RepositoryComparator(pw, remoteRepo); + comparator.compare(context); + } + return comparator; } }
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java Thu Apr 14 19:53:31 2011 +0200 +++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java Fri Apr 15 03:17:03 2011 +0200 @@ -18,11 +18,13 @@ import static org.tmatesoft.hg.core.Nodeid.NULL; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; @@ -59,6 +61,12 @@ cancelSupport.checkCancelled(); progressSupport.start(10); common = Collections.unmodifiableList(findCommonWithRemote()); + // sanity check + for (Nodeid n : common) { + if (!localRepo.knownNode(n)) { + throw new HgBadStateException("Unknown node reported as common:" + n); + } + } progressSupport.done(); return this; } @@ -69,6 +77,45 @@ } return common; } + + /** + * @return revisions that are children of common entries, i.e. revisions that are present on the local server and not on remote. + */ + public List<Nodeid> getLocalOnlyRevisions() { + return localRepo.childrenOf(getCommon()); + } + + /** + * Similar to @link {@link #getLocalOnlyRevisions()}, use this one if you need access to changelog entry content, not + * only its revision number. + * @param inspector delegate to analyze changesets, shall not be <code>null</code> + */ + public void visitLocalOnlyRevisions(HgChangelog.Inspector inspector) { + if (inspector == null) { + throw new IllegalArgumentException(); + } + // one can use localRepo.childrenOf(getCommon()) and then iterate over nodeids, but there seems to be + // another approach to get all changes after common: + // find index of earliest revision, and report all that were later + final HgChangelog changelog = localRepo.getRepo().getChangelog(); + int earliestRevision = Integer.MAX_VALUE; + List<Nodeid> commonKnown = getCommon(); + for (Nodeid n : commonKnown) { + if (!localRepo.hasChildren(n)) { + // there might be (old) nodes, known both locally and remotely, with no children + // hence, we don't need to consider their local revision number + continue; + } + int lr = changelog.getLocalRevision(n); + if (lr < earliestRevision) { + earliestRevision = lr; + } + } + if (earliestRevision < 0 || earliestRevision >= changelog.getLastRevision()) { + throw new HgBadStateException(String.format("Invalid index of common known revision: %d in total of %d", earliestRevision, 1+changelog.getLastRevision())); + } + changelog.range(earliestRevision+1, changelog.getLastRevision(), inspector); + } private List<Nodeid> findCommonWithRemote() throws HgException { List<Nodeid> remoteHeads = remoteRepo.heads(); @@ -403,4 +450,49 @@ } return fromRootToHead; } + + /** + * returns in order from branch root to head + * for a non-empty BranchChain, shall return modifiable list + */ + public List<Nodeid> visitBranches(BranchChain bc) throws HgException { + if (bc == null) { + return Collections.emptyList(); + } + List<Nodeid> mine = completeBranch(bc.branchRoot, bc.branchHead); + if (bc.isTerminal()) { + return mine; + } + List<Nodeid> parentBranch1 = visitBranches(bc.p1); + List<Nodeid> parentBranch2 = visitBranches(bc.p2); + // merge + LinkedList<Nodeid> merged = new LinkedList<Nodeid>(); + ListIterator<Nodeid> i1 = parentBranch1.listIterator(), i2 = parentBranch2.listIterator(); + while (i1.hasNext() && i2.hasNext()) { + Nodeid n1 = i1.next(); + Nodeid n2 = i2.next(); + if (n1.equals(n2)) { + merged.addLast(n1); + } else { + // first different => add both, and continue adding both tails sequentially + merged.add(n2); + merged.add(n1); + break; + } + } + // copy rest of second parent branch + while (i2.hasNext()) { + merged.add(i2.next()); + } + // copy rest of first parent branch + while (i1.hasNext()) { + merged.add(i1.next()); + } + // + ArrayList<Nodeid> rv = new ArrayList<Nodeid>(mine.size() + merged.size()); + rv.addAll(merged); + rv.addAll(mine); + return rv; + } + }
--- a/src/org/tmatesoft/hg/repo/Revlog.java Thu Apr 14 19:53:31 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/Revlog.java Fri Apr 15 03:17:03 2011 +0200 @@ -208,6 +208,10 @@ firstParent = secondParent = Collections.emptyMap(); } + public HgRepository getRepo() { + return Revlog.this.getRepo(); + } + public void init() { final RevlogStream stream = Revlog.this.content; final int revisionCount = stream.revisionCount(); @@ -310,6 +314,16 @@ orderedResult.retainAll(result); return orderedResult; } + + /** + * @param node possibly parent node + * @return <code>true</code> if there's any node in this revlog that has specified node as one of its parents. + */ + public boolean hasChildren(Nodeid node) { + // FIXME containsValue is linear, likely. May want to optimize it with another (Tree|Hash)Set, created on demand + // on first use + return firstParent.containsValue(node) || secondParent.containsValue(node); + } } protected static class ContentPipe implements RevlogStream.Inspector, CancelSupport {