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 {