changeset 215:41a778e3fd31

Issue 5: Facilities for progress and cancellation. More specific exceptions. Exceptions from callbacks as RuntimeException
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 17 May 2011 00:56:54 +0200
parents 4252faa556cd
children c251bbc979cf
files cmdline/org/tmatesoft/hg/console/Incoming.java cmdline/org/tmatesoft/hg/console/Log.java cmdline/org/tmatesoft/hg/console/Outgoing.java src/org/tmatesoft/hg/core/ChangesetTransformer.java src/org/tmatesoft/hg/core/HgAbstractCommand.java src/org/tmatesoft/hg/core/HgCallbackTargetException.java src/org/tmatesoft/hg/core/HgCatCommand.java src/org/tmatesoft/hg/core/HgChangesetHandler.java src/org/tmatesoft/hg/core/HgDataStreamException.java src/org/tmatesoft/hg/core/HgIncomingCommand.java src/org/tmatesoft/hg/core/HgLogCommand.java src/org/tmatesoft/hg/core/HgManifestCommand.java src/org/tmatesoft/hg/core/HgOutgoingCommand.java src/org/tmatesoft/hg/core/HgRemoteConnectionException.java src/org/tmatesoft/hg/core/HgStatusCommand.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java src/org/tmatesoft/hg/util/CancelSupport.java src/org/tmatesoft/hg/util/ProgressSupport.java test/org/tmatesoft/hg/test/TestIncoming.java test/org/tmatesoft/hg/test/TestOutgoing.java
diffstat 23 files changed, 470 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Incoming.java	Mon May 16 21:10:36 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Incoming.java	Tue May 17 00:56:54 2011 +0200
@@ -60,7 +60,7 @@
 		HgIncomingCommand cmd = hgRepo.createIncomingCommand();
 		cmd.against(hgRemote);
 		//
-		List<Nodeid> missing = cmd.executeLite(null);
+		List<Nodeid> missing = cmd.executeLite();
 		Collections.reverse(missing); // useful to test output, from newer to older
 		Outgoing.dump("Nodes to fetch:", missing);
 		System.out.printf("Total: %d\n\n", missing.size());
--- a/cmdline/org/tmatesoft/hg/console/Log.java	Mon May 16 21:10:36 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Log.java	Tue May 17 00:56:54 2011 +0200
@@ -22,6 +22,8 @@
 import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 
 /**
@@ -39,6 +41,11 @@
 			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
 			return;
 		}
+		//
+		// in fact, neither cancel nor progress of any use, need them just to check comamnd API
+		final CancelSupport noCancel = CancelSupport.Factory.get(null);
+		final ProgressSupport noProgress = ProgressSupport.Factory.get(null);
+		//
 		final Dump dump = new Dump(hgRepo);
 		dump.complete(cmdLineOpts.getBoolean("--debug"));
 		dump.verbose(cmdLineOpts.getBoolean("-v", "--verbose"));
@@ -55,6 +62,7 @@
 		if (limit != -1) {
 			cmd.limit(limit);
 		}
+		cmd.set(noCancel).set(noProgress);
 		List<String> files = cmdLineOpts.getList("");
 		final long start = System.currentTimeMillis();
 		if (files.isEmpty()) {
--- a/cmdline/org/tmatesoft/hg/console/Outgoing.java	Mon May 16 21:10:36 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Outgoing.java	Tue May 17 00:56:54 2011 +0200
@@ -52,7 +52,7 @@
 		cmd.against(hgRemote);
 		
 		// find all local children of commonKnown
-		List<Nodeid> result = cmd.executeLite(null);
+		List<Nodeid> result = cmd.executeLite();
 		dump("Lite", result);
 		//
 		//
--- a/src/org/tmatesoft/hg/core/ChangesetTransformer.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/ChangesetTransformer.java	Tue May 17 00:56:54 2011 +0200
@@ -22,11 +22,15 @@
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.PathPool;
 import org.tmatesoft.hg.util.PathRewrite;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 /**
  * Bridges {@link HgChangelog.RawChangeset} with high-level {@link HgChangeset} API
+ * FIXME move to .internal
  * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
@@ -34,13 +38,21 @@
 /*package-local*/ class ChangesetTransformer implements HgChangelog.Inspector {
 	private final HgChangesetHandler handler;
 	private final HgChangeset changeset;
+	private final ProgressSupport progressHelper;
+	private final CancelSupport cancelHelper;
 	private Set<String> branches;
+	private HgCallbackTargetException failure;
+	private CancelledException cancellation;
 
 	// repo and delegate can't be null, parent walker can
-	public ChangesetTransformer(HgRepository hgRepo, HgChangesetHandler delegate, HgChangelog.ParentWalker pw) {
+	// ps and cs can't be null
+	public ChangesetTransformer(HgRepository hgRepo, HgChangesetHandler delegate, HgChangelog.ParentWalker pw, ProgressSupport ps, CancelSupport cs) {
 		if (hgRepo == null || delegate == null) {
 			throw new IllegalArgumentException();
 		}
+		if (ps == null || cs == null) {
+			throw new IllegalArgumentException();
+		}
 		HgStatusCollector statusCollector = new HgStatusCollector(hgRepo);
 		// files listed in a changeset don't need their names to be rewritten (they are normalized already)
 		PathPool pp = new PathPool(new PathRewrite.Empty());
@@ -48,15 +60,41 @@
 		changeset = new HgChangeset(statusCollector, pp);
 		changeset.setParentHelper(pw);
 		handler = delegate;
+		cancelHelper = cs;
+		progressHelper = ps;
 	}
 	
 	public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+		if (failure != null || cancellation != null) {
+			return; // FIXME need a better way to stop iterating revlog 
+		}
 		if (branches != null && !branches.contains(cset.branch())) {
 			return;
 		}
 
 		changeset.init(revisionNumber, nodeid, cset);
-		handler.next(changeset);
+		try {
+			handler.next(changeset);
+			progressHelper.worked(1);
+			cancelHelper.checkCancelled();
+		} catch (RuntimeException ex) {
+			failure = new HgCallbackTargetException(ex).setRevision(nodeid).setRevisionNumber(revisionNumber);
+		} catch (CancelledException ex) {
+			cancellation = ex;
+		}
+	}
+	
+	public void checkFailure() throws HgCallbackTargetException, CancelledException {
+		if (failure != null) {
+			HgCallbackTargetException toThrow = failure;
+			failure = null; // just in (otherwise unexpected) case this instance would get reused
+			throw toThrow;
+		}
+		if (cancellation != null) {
+			CancelledException toThrow = cancellation;
+			cancellation = null;
+			throw toThrow;
+		}
 	}
 	
 	public void limitBranches(Set<String> branches) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgAbstractCommand.java	Tue May 17 00:56:54 2011 +0200
@@ -0,0 +1,60 @@
+/*
+ * 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.util.CancelSupport;
+import org.tmatesoft.hg.util.ProgressSupport;
+
+/**
+ * intentionally package-local, might be removed or refactored in future
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+class HgAbstractCommand<T extends HgAbstractCommand<?>> implements ProgressSupport.Target<T>, CancelSupport.Target<T> {
+	private ProgressSupport progressHelper;
+	private CancelSupport cancelHelper;
+
+	@SuppressWarnings("unchecked")
+	public T set(ProgressSupport ps) {
+		progressHelper = ps;
+		return (T) this;
+	}
+	
+	@SuppressWarnings("unchecked")
+	public T set(CancelSupport cs) {
+		cancelHelper = cs;
+		return (T) this;
+	}
+
+	// shall not return null
+	protected ProgressSupport getProgressSupport(Object context) {
+		if (progressHelper != null) {
+			return progressHelper;
+		}
+		return ProgressSupport.Factory.get(context);
+	}
+
+	// shall not return null
+	protected CancelSupport getCancelSupport(Object context) {
+		if (cancelHelper != null) {
+			return cancelHelper;
+		}
+		return CancelSupport.Factory.get(context);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgCallbackTargetException.java	Tue May 17 00:56:54 2011 +0200
@@ -0,0 +1,132 @@
+/*
+ * 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 static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Checked exception that indicates errors in client code and tries to supply extra information about the context it occured in.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgCallbackTargetException extends HgException {
+	private int revNumber = BAD_REVISION;
+	private Nodeid revision;
+	private Path filename;
+
+	/**
+	 * @param cause can't be <code>null</code>
+	 */
+	public HgCallbackTargetException(Throwable cause) {
+		super((Throwable) null);
+		if (cause == null) {
+			throw new IllegalArgumentException();
+		}
+		if (cause.getClass() == Wrap.class) {
+			// eliminate wrapper
+			initCause(cause.getCause());
+		} else {
+			initCause(cause);
+		}
+	}
+
+	/**
+	 * @return not {@link HgRepository#BAD_REVISION} only when local revision number was supplied at the construction time
+	 */
+	public int getRevisionNumber() {
+		return revNumber;
+	}
+
+	public HgCallbackTargetException setRevisionNumber(int rev) {
+		revNumber = rev;
+		return this;
+	}
+
+	/**
+	 * @return non-null only when revision was supplied at construction time
+	 */
+	public Nodeid getRevision() {
+		return revision;
+	}
+
+	public HgCallbackTargetException setRevision(Nodeid r) {
+		revision = r;
+		return this;
+	}
+
+	/**
+	 * @return non-null only if file name was set at construction time
+	 */
+	public Path getFileName() {
+		return filename;
+	}
+
+	public HgCallbackTargetException setFileName(Path name) {
+		filename = name;
+		return this;
+	}
+
+	@SuppressWarnings("unchecked")
+	public <T extends Exception> T getTargetException() {
+		return (T) getCause();
+	}
+	
+	/**
+	 * Despite this exception is merely a way to give users access to their own exceptions, it may still supply 
+	 * valuable debugging information about what led to the error.
+	 */
+	@Override
+	public String getMessage() {
+		StringBuilder sb = new StringBuilder();
+		if (filename != null) {
+			sb.append(filename);
+			sb.append(':');
+			sb.append(' ');
+		}
+		if (revNumber != BAD_REVISION) {
+			sb.append(revNumber);
+			if (revision != null) {
+				sb.append(':');
+			}
+		}
+		if (revision != null) {
+			sb.append(revision.shortNotation());
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Given the approach high-level handlers throw RuntimeExceptions to indicate errors, and
+	 * a need to throw reasonable checked exception from client code, clients may utilize this class
+	 * to get their checked exceptions unwrapped by {@link HgCallbackTargetException} and serve as that 
+	 * exception cause, eliminating {@link RuntimeException} mediator.
+	 */
+	public static final class Wrap extends RuntimeException {
+
+		public Wrap(Throwable cause) {
+			super(cause);
+			if (cause == null) {
+				throw new IllegalArgumentException();
+			}
+		}
+	}
+}
--- a/src/org/tmatesoft/hg/core/HgCatCommand.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgCatCommand.java	Tue May 17 00:56:54 2011 +0200
@@ -35,7 +35,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class HgCatCommand {
+public class HgCatCommand extends HgAbstractCommand<HgCatCommand> {
 
 	private final HgRepository repo;
 	private Path file;
@@ -110,7 +110,7 @@
 		}
 		HgDataFile dataFile = repo.getFileNode(file);
 		if (!dataFile.exists()) {
-			throw new HgDataStreamException(file.toString(), new FileNotFoundException(file.toString()));
+			throw new HgDataStreamException(file, new FileNotFoundException(file.toString()));
 		}
 		int revToExtract;
 		if (revision != null) {
--- a/src/org/tmatesoft/hg/core/HgChangesetHandler.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgChangesetHandler.java	Tue May 17 00:56:54 2011 +0200
@@ -22,9 +22,11 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public interface HgChangesetHandler {
+public interface HgChangesetHandler/*XXX perhaps, shall parameterize with exception clients can throw, like: <E extends Exception>*/ {
 	/**
 	 * @param changeset not necessarily a distinct instance each time, {@link HgChangeset#clone() clone()} if need a copy.
+	 * @throws RuntimeException or any subclass thereof to indicate error. General contract is that RuntimeExceptions 
+	 * will be re-thrown wrapped into {@link HgCallbackTargetException}.  
 	 */
-	void next(HgChangeset changeset);
+	void next(HgChangeset changeset)/* throws E*/;
 }
--- a/src/org/tmatesoft/hg/core/HgDataStreamException.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgDataStreamException.java	Tue May 17 00:56:54 2011 +0200
@@ -17,6 +17,7 @@
 package org.tmatesoft.hg.core;
 
 import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.util.Path;
 
 /**
  * Any erroneous state with @link {@link HgDataFile} input/output, read/write operations 
@@ -26,8 +27,19 @@
  */
 @SuppressWarnings("serial")
 public class HgDataStreamException extends HgException {
+	private final Path fname;
 
-	public HgDataStreamException(String message, Throwable cause) {
+	public HgDataStreamException(Path file, String message, Throwable cause) {
 		super(message, cause);
+		fname = file;
+	}
+	
+	public HgDataStreamException(Path file, Throwable cause) {
+		super(cause);
+		fname = file;
+	}
+
+	public Path getFileName() {
+		return fname;
 	}
 }
--- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Tue May 17 00:56:54 2011 +0200
@@ -34,6 +34,7 @@
 import org.tmatesoft.hg.repo.HgRemoteRepository;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 /**
  * Command to find out changes available in a remote repository, missing locally.
@@ -41,7 +42,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class HgIncomingCommand {
+public class HgIncomingCommand extends HgAbstractCommand<HgIncomingCommand> {
 
 	private final HgRepository localRepo;
 	private HgRemoteRepository remoteRepo;
@@ -98,15 +99,14 @@
 	 * Lightweight check for incoming changes, gives only list of revisions to pull.
 	 * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account. 
 	 *   
-	 * @param context anything hg4j can use to get progress and/or cancel support
 	 * @return list of nodes present at remote and missing locally
 	 * @throws HgException
 	 * @throws CancelledException
 	 */
-	public List<Nodeid> executeLite(Object context) throws HgException, CancelledException {
+	public List<Nodeid> executeLite() throws HgException, CancelledException {
 		LinkedHashSet<Nodeid> result = new LinkedHashSet<Nodeid>();
-		RepositoryComparator repoCompare = getComparator(context);
-		for (BranchChain bc : getMissingBranches(context)) {
+		RepositoryComparator repoCompare = getComparator();
+		for (BranchChain bc : getMissingBranches()) {
 			List<Nodeid> missing = repoCompare.visitBranches(bc);
 			HashSet<Nodeid> common = new HashSet<Nodeid>(); // ordering is irrelevant  
 			repoCompare.collectKnownRoots(bc, common);
@@ -124,21 +124,21 @@
 	 * @throws HgException
 	 * @throws CancelledException
 	 */
-	public void executeFull(final HgChangesetHandler handler) throws HgException, CancelledException {
+	public void executeFull(final HgChangesetHandler handler) throws HgException/*FIXME specific type*/, HgCallbackTargetException, CancelledException {
 		if (handler == null) {
 			throw new IllegalArgumentException("Delegate can't be null");
 		}
-		final List<Nodeid> common = getCommon(handler);
+		final List<Nodeid> common = getCommon();
 		HgBundle changegroup = remoteRepo.getChanges(common);
+		final ProgressSupport ps = getProgressSupport(handler);
 		try {
+			final ChangesetTransformer transformer = new ChangesetTransformer(localRepo, handler, getParentHelper(), ps, getCancelSupport(handler));
+			transformer.limitBranches(branches);
 			changegroup.changes(localRepo, new HgChangelog.Inspector() {
 				private int localIndex;
 				private final HgChangelog.ParentWalker parentHelper;
-				private final ChangesetTransformer transformer;
 			
 				{
-					transformer = new ChangesetTransformer(localRepo, handler, getParentHelper());
-					transformer.limitBranches(branches);
 					parentHelper = getParentHelper();
 					// new revisions, if any, would be added after all existing, and would get numbered started with last+1
 					localIndex = localRepo.getChangelog().getRevisionCount();
@@ -154,14 +154,17 @@
 					transformer.next(localIndex++, nodeid, cset);
 				}
 			});
+			transformer.checkFailure();
 		} catch (IOException ex) {
 			throw new HgException(ex);
+		} finally {
+			ps.done();
 		}
 	}
 
-	private RepositoryComparator getComparator(Object context) throws HgException, CancelledException {
+	private RepositoryComparator getComparator() throws CancelledException {
 		if (remoteRepo == null) {
-			throw new HgBadArgumentException("Shall specify remote repository to compare against", null);
+			throw new IllegalArgumentException("Shall specify remote repository to compare against", null);
 		}
 		if (comparator == null) {
 			comparator = new RepositoryComparator(getParentHelper(), remoteRepo);
@@ -178,20 +181,20 @@
 		return parentHelper;
 	}
 	
-	private List<BranchChain> getMissingBranches(Object context) throws HgException, CancelledException {
+	private List<BranchChain> getMissingBranches() throws HgRemoteConnectionException, CancelledException {
 		if (missingBranches == null) {
-			missingBranches = getComparator(context).calculateMissingBranches();
+			missingBranches = getComparator().calculateMissingBranches();
 		}
 		return missingBranches;
 	}
 
-	private List<Nodeid> getCommon(Object context) throws HgException, CancelledException {
+	private List<Nodeid> getCommon() throws HgRemoteConnectionException, CancelledException {
 //		return getComparator(context).getCommon();
 		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.
-		RepositoryComparator repoCompare = getComparator(context);
-		for (BranchChain bc : getMissingBranches(context)) {
+		RepositoryComparator repoCompare = getComparator();
+		for (BranchChain bc : getMissingBranches()) {
 			repoCompare.collectKnownRoots(bc, common);
 		}
 		return new LinkedList<Nodeid>(common);
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgLogCommand.java	Tue May 17 00:56:54 2011 +0200
@@ -27,13 +27,14 @@
 import java.util.Set;
 import java.util.TreeSet;
 
+import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.ByteChannel;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 
 /**
@@ -48,7 +49,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class HgLogCommand implements HgChangelog.Inspector {
+public class HgLogCommand extends HgAbstractCommand<HgLogCommand> implements HgChangelog.Inspector {
 
 	private final HgRepository repo;
 	private Set<String> users;
@@ -164,9 +165,17 @@
 	/**
 	 * Similar to {@link #execute(org.tmatesoft.hg.repo.RawChangeset.Inspector)}, collects and return result as a list.
 	 */
-	public List<HgChangeset> execute() throws HgException {
+	public List<HgChangeset> execute() throws HgDataStreamException {
 		CollectHandler collector = new CollectHandler();
-		execute(collector);
+		try {
+			execute(collector);
+		} catch (HgCallbackTargetException ex) {
+			// can't happen as long as our CollectHandler doesn't throw any exception
+			throw new HgBadStateException(ex.getCause());
+		} catch (CancelledException ex) {
+			// can't happen, see above
+			throw new HgBadStateException(ex);
+		}
 		return collector.getChanges();
 	}
 
@@ -176,13 +185,14 @@
 	 * @throws IllegalArgumentException when inspector argument is null
 	 * @throws ConcurrentModificationException if this log command instance is already running
 	 */
-	public void execute(HgChangesetHandler handler) throws HgException {
+	public void execute(HgChangesetHandler handler) throws HgDataStreamException, HgCallbackTargetException, CancelledException {
 		if (handler == null) {
 			throw new IllegalArgumentException();
 		}
 		if (csetTransform != null) {
 			throw new ConcurrentModificationException();
 		}
+		final ProgressSupport progressHelper = getProgressSupport(handler);
 		try {
 			count = 0;
 			HgChangelog.ParentWalker pw = parentHelper; // leave it uninitialized unless we iterate whole repo
@@ -191,19 +201,27 @@
 			}
 			// ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above 
 			// may utilize it as well. CommandContext? How about StatusCollector there as well?
-			csetTransform = new ChangesetTransformer(repo, handler, pw);
+			csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler));
 			if (file == null) {
+				progressHelper.start(endRev - startRev + 1);
 				repo.getChangelog().range(startRev, endRev, this);
+				csetTransform.checkFailure();
 			} else {
+				progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/);
 				HgDataFile fileNode = repo.getFileNode(file);
 				fileNode.history(startRev, endRev, this);
+				csetTransform.checkFailure();
 				if (fileNode.isCopy()) {
 					// even if we do not follow history, report file rename
 					do {
 						if (handler instanceof FileHistoryHandler) {
 							FileRevision src = new FileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName());
 							FileRevision dst = new FileRevision(repo, fileNode.getRevision(0), fileNode.getPath());
-							((FileHistoryHandler) handler).copy(src, dst);
+							try {
+								((FileHistoryHandler) handler).copy(src, dst);
+							} catch (RuntimeException ex) {
+								throw new HgCallbackTargetException(ex).setRevision(fileNode.getCopySourceRevision()).setFileName(fileNode.getCopySourceName());
+							}
 						}
 						if (limit > 0 && count >= limit) {
 							// if limit reach, follow is useless.
@@ -212,12 +230,14 @@
 						if (followHistory) {
 							fileNode = repo.getFileNode(fileNode.getCopySourceName());
 							fileNode.history(this);
+							csetTransform.checkFailure();
 						}
 					} while (followHistory && fileNode.isCopy());
 				}
 			}
 		} finally {
 			csetTransform = null;
+			progressHelper.done();
 		}
 	}
 
--- a/src/org/tmatesoft/hg/core/HgManifestCommand.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgManifestCommand.java	Tue May 17 00:56:54 2011 +0200
@@ -39,7 +39,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class HgManifestCommand {
+public class HgManifestCommand extends HgAbstractCommand<HgManifestCommand> {
 	
 	private final HgRepository repo;
 	private Path.Matcher matcher;
--- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Tue May 17 00:56:54 2011 +0200
@@ -24,7 +24,9 @@
 import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 /**
  * Command to find out changes made in a local repository and missing at remote repository. 
@@ -32,7 +34,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class HgOutgoingCommand {
+public class HgOutgoingCommand extends HgAbstractCommand<HgOutgoingCommand> {
 
 	private final HgRepository localRepo;
 	private HgRemoteRepository remoteRepo;
@@ -90,11 +92,16 @@
 	 * Lightweight check for outgoing changes. 
 	 * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account.
 	 * 
-	 * @param context
 	 * @return list on local nodes known to be missing at remote server 
 	 */
-	public List<Nodeid> executeLite(Object context) throws HgException, CancelledException {
-		return getComparator(context).getLocalOnlyRevisions();
+	public List<Nodeid> executeLite() throws HgRemoteConnectionException, CancelledException {
+		final ProgressSupport ps = getProgressSupport(null);
+		try {
+			ps.start(10);
+			return getComparator(new ProgressSupport.Sub(ps, 5), getCancelSupport(null)).getLocalOnlyRevisions();
+		} finally {
+			ps.done();
+		}
 	}
 
 	/**
@@ -102,22 +109,30 @@
 	 * 
 	 * @param handler delegate to process changes
 	 */
-	public void executeFull(final HgChangesetHandler handler) throws HgException, CancelledException {
+	public void executeFull(final HgChangesetHandler handler) throws HgRemoteConnectionException, HgCallbackTargetException, CancelledException {
 		if (handler == null) {
 			throw new IllegalArgumentException("Delegate can't be null");
 		}
-		ChangesetTransformer inspector = new ChangesetTransformer(localRepo, handler, getParentHelper());
-		inspector.limitBranches(branches);
-		getComparator(handler).visitLocalOnlyRevisions(inspector);
+		final ProgressSupport ps = getProgressSupport(handler);
+		final CancelSupport cs = getCancelSupport(handler);
+		try {
+			ps.start(-1);
+			ChangesetTransformer inspector = new ChangesetTransformer(localRepo, handler, getParentHelper(), ps, cs);
+			inspector.limitBranches(branches);
+			getComparator(new ProgressSupport.Sub(ps, 1), cs).visitLocalOnlyRevisions(inspector);
+			inspector.checkFailure();
+		} finally {
+			ps.done();
+		}
 	}
 
-	private RepositoryComparator getComparator(Object context) throws HgException, CancelledException {
+	private RepositoryComparator getComparator(ProgressSupport ps, CancelSupport cs) throws HgRemoteConnectionException, CancelledException {
 		if (remoteRepo == null) {
 			throw new IllegalArgumentException("Shall specify remote repository to compare against");
 		}
 		if (comparator == null) {
 			comparator = new RepositoryComparator(getParentHelper(), remoteRepo);
-			comparator.compare(context);
+			comparator.compare(ps, cs);
 		}
 		return comparator;
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgRemoteConnectionException.java	Tue May 17 00:56:54 2011 +0200
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgRemoteConnectionException extends HgException {
+
+	private String serverInfo;
+	private String cmdName;
+
+	public HgRemoteConnectionException(String reason) {
+		super(reason);
+	}
+
+	public HgRemoteConnectionException(String reason, Throwable cause) {
+		super(reason, cause);
+	}
+
+	public HgRemoteConnectionException setServerInfo(String si) {
+		serverInfo = si;
+		return this;
+	}
+	public String getServerInfo() {
+		return serverInfo;
+	}
+	
+	public HgRemoteConnectionException setRemoteCommand(String commandName) {
+		cmdName = commandName;
+		return this;
+	}
+
+	public String getRemoteCommand() {
+		return cmdName;
+	}
+}
--- a/src/org/tmatesoft/hg/core/HgStatusCommand.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgStatusCommand.java	Tue May 17 00:56:54 2011 +0200
@@ -21,6 +21,7 @@
 import static org.tmatesoft.hg.repo.HgRepository.*;
 
 import java.util.ConcurrentModificationException;
+import java.util.concurrent.CancellationException;
 
 import org.tmatesoft.hg.internal.ChangelogHelper;
 import org.tmatesoft.hg.repo.HgRepository;
@@ -36,7 +37,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class HgStatusCommand {
+public class HgStatusCommand extends HgAbstractCommand<HgStatusCommand> {
 	private final HgRepository repo;
 
 	private int startRevision = TIP;
@@ -161,7 +162,7 @@
 	 * @throws IllegalArgumentException if handler is <code>null</code>
 	 * @throws ConcurrentModificationException if this command already runs (i.e. being used from another thread)
 	 */
-	public void execute(Handler statusHandler) {
+	public void execute(Handler statusHandler) throws CancellationException, HgCallbackTargetException {
 		if (statusHandler == null) {
 			throw new IllegalArgumentException();
 		}
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Tue May 17 00:56:54 2011 +0200
@@ -31,6 +31,7 @@
 
 import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.HgRemoteConnectionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
@@ -57,9 +58,7 @@
 		remoteRepo = hgRemote;
 	}
 	
-	public RepositoryComparator compare(Object context) throws HgException, CancelledException {
-		ProgressSupport progressSupport = ProgressSupport.Factory.get(context);
-		CancelSupport cancelSupport = CancelSupport.Factory.get(context);
+	public RepositoryComparator compare(ProgressSupport progressSupport, CancelSupport cancelSupport) throws HgRemoteConnectionException, CancelledException {
 		cancelSupport.checkCancelled();
 		progressSupport.start(10);
 		common = Collections.unmodifiableList(findCommonWithRemote());
@@ -126,7 +125,7 @@
 		changelog.range(earliestRevision+1, changelog.getLastRevision(), inspector);
 	}
 
-	private List<Nodeid> findCommonWithRemote() throws HgException {
+	private List<Nodeid> findCommonWithRemote() throws HgRemoteConnectionException {
 		List<Nodeid> remoteHeads = remoteRepo.heads();
 		LinkedList<Nodeid> resultCommon = new LinkedList<Nodeid>(); // these remotes are known in local
 		LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common
@@ -208,7 +207,7 @@
 	}
 
 	// somewhat similar to Outgoing.findCommonWithRemote() 
-	public List<BranchChain> calculateMissingBranches() throws HgException {
+	public List<BranchChain> calculateMissingBranches() throws HgRemoteConnectionException {
 		List<Nodeid> remoteHeads = remoteRepo.heads();
 		LinkedList<Nodeid> common = new LinkedList<Nodeid>(); // these remotes are known in local
 		LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Tue May 17 00:56:54 2011 +0200
@@ -136,7 +136,7 @@
 			insp = new ContentPipe(sink, metadata.dataOffset(revision));
 		} else {
 			// do not know if there's metadata
-			insp = new MetadataContentPipe(sink, metadata);
+			insp = new MetadataContentPipe(sink, metadata, getPath());
 		}
 		insp.checkCancelled();
 		super.content.iterate(revision, revision, true, insp);
@@ -146,7 +146,7 @@
 			throw ex;
 		} catch (HgException ex) {
 			// shall not happen, unless we changed ContentPipe or its subclass
-			throw new HgDataStreamException(ex.getClass().getName(), ex);
+			throw new HgDataStreamException(getPath(), ex.getClass().getName(), ex);
 		}
 	}
 	
@@ -210,7 +210,7 @@
 			} catch (CancelledException ex) {
 				// it's ok, we did that
 			} catch (Exception ex) {
-				throw new HgDataStreamException("Can't initialize metadata", ex);
+				throw new HgDataStreamException(getPath(), "Can't initialize metadata", ex);
 			}
 		}
 		if (!metadata.known(0)) {
@@ -319,10 +319,12 @@
 	private static class MetadataContentPipe extends ContentPipe {
 
 		private final Metadata metadata;
+		private final Path fname; // need this only for error reporting
 
-		public MetadataContentPipe(ByteChannel sink, Metadata _metadata) {
+		public MetadataContentPipe(ByteChannel sink, Metadata _metadata, Path file) {
 			super(sink, 0);
 			metadata = _metadata;
+			fname = file;
 		}
 
 		@Override
@@ -382,7 +384,7 @@
 			_metadata.trimToSize();
 			metadata.add(revisionNumber, lastEntryStart, _metadata);
 			if (da.isEmpty() || !byteOne) {
-				throw new HgDataStreamException(String.format("Metadata for revision %d is not closed properly", revisionNumber), null);
+				throw new HgDataStreamException(fname, String.format("Metadata for revision %d is not closed properly", revisionNumber), null);
 			}
 			// da is in prepared state (i.e. we consumed all bytes up to metadata end).
 		}
--- a/src/org/tmatesoft/hg/repo/HgLookup.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgLookup.java	Tue May 17 00:56:54 2011 +0200
@@ -70,7 +70,7 @@
 		}
 	}
 	
-	public HgBundle loadBundle(File location) throws HgException {
+	public HgBundle loadBundle(File location) /*XXX perhaps, HgDataStreamException or anything like HgMalformedDataException? Or RuntimeEx is better?*/{
 		if (location == null || !location.canRead()) {
 			throw new IllegalArgumentException();
 		}
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Tue May 17 00:56:54 2011 +0200
@@ -48,7 +48,7 @@
 
 import org.tmatesoft.hg.core.HgBadArgumentException;
 import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.HgRemoteConnectionException;
 import org.tmatesoft.hg.core.Nodeid;
 
 /**
@@ -115,7 +115,7 @@
 		}
 	}
 	
-	public boolean isInvalid() throws HgException {
+	public boolean isInvalid() throws HgRemoteConnectionException {
 		// say hello to server, check response
 		if (Boolean.FALSE.booleanValue()) {
 			throw HgRepository.notImplemented();
@@ -137,7 +137,7 @@
 		}
 	}
 
-	public List<Nodeid> heads() throws HgException {
+	public List<Nodeid> heads() throws HgRemoteConnectionException {
 		try {
 			URL u = new URL(url, url.getPath() + "?cmd=heads");
 			HttpURLConnection c = setupConnection(u.openConnection());
@@ -156,13 +156,13 @@
 			}
 			return parseResult;
 		} catch (MalformedURLException ex) {
-			throw new HgException(ex);
+			throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("heads").setServerInfo(getLocation());
 		} catch (IOException ex) {
-			throw new HgException(ex);
+			throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("heads").setServerInfo(getLocation());
 		}
 	}
 	
-	public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgException {
+	public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgRemoteConnectionException {
 		Range r = new Range(base, tip);
 		// XXX shall handle errors like no range key in the returned map, not sure how.
 		return between(Collections.singletonList(r)).get(r);
@@ -171,9 +171,9 @@
 	/**
 	 * @param ranges
 	 * @return map, where keys are input instances, values are corresponding server reply
-	 * @throws HgException 
+	 * @throws HgRemoteConnectionException 
 	 */
-	public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgException {
+	public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgRemoteConnectionException {
 		if (ranges.isEmpty()) {
 			return Collections.emptyMap();
 		}
@@ -257,13 +257,13 @@
 			is.close();
 			return rv;
 		} catch (MalformedURLException ex) {
-			throw new HgException(ex);
+			throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("between").setServerInfo(getLocation());
 		} catch (IOException ex) {
-			throw new HgException(ex);
+			throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("between").setServerInfo(getLocation());
 		}
 	}
 
-	public List<RemoteBranch> branches(List<Nodeid> nodes) throws HgException {
+	public List<RemoteBranch> branches(List<Nodeid> nodes) throws HgRemoteConnectionException {
 		StringBuilder sb = new StringBuilder(20 + nodes.size() * 41);
 		sb.append("nodes=");
 		for (Nodeid n : nodes) {
@@ -291,7 +291,7 @@
 				parseResult.add(Nodeid.fromAscii(st.sval));
 			}
 			if (parseResult.size() != nodes.size() * 4) {
-				throw new HgException(String.format("Bad number of nodeids in result (shall be factor 4), expected %d, got %d", nodes.size()*4, parseResult.size()));
+				throw new HgRemoteConnectionException(String.format("Bad number of nodeids in result (shall be factor 4), expected %d, got %d", nodes.size()*4, parseResult.size()));
 			}
 			ArrayList<RemoteBranch> rv = new ArrayList<RemoteBranch>(nodes.size());
 			for (int i = 0; i < nodes.size(); i++) {
@@ -300,9 +300,9 @@
 			}
 			return rv;
 		} catch (MalformedURLException ex) {
-			throw new HgException(ex);
+			throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("branches").setServerInfo(getLocation());
 		} catch (IOException ex) {
-			throw new HgException(ex);
+			throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("branches").setServerInfo(getLocation());
 		}
 	}
 
@@ -322,7 +322,7 @@
 	 * (there's no header like HG10?? with the server output, though, 
 	 * as one may expect according to http://mercurial.selenic.com/wiki/BundleFormat)
 	 */
-	public HgBundle getChanges(List<Nodeid> roots) throws HgException {
+	public HgBundle getChanges(List<Nodeid> roots) throws HgRemoteConnectionException {
 		List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots;
 		StringBuilder sb = new StringBuilder(20 + _roots.size() * 41);
 		sb.append("roots=");
@@ -346,10 +346,11 @@
 				System.out.printf("Wrote bundle %s for roots %s\n", tf, sb);
 			}
 			return getLookupHelper().loadBundle(tf);
-		} catch (MalformedURLException ex) {
-			throw new HgException(ex);
+		} catch (MalformedURLException ex) { // XXX in fact, this exception might be better to be re-thrown as RuntimeEx,
+			// as there's little user can do about this issue (URLs are constructed by our code)
+			throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("changegroup").setServerInfo(getLocation());
 		} catch (IOException ex) {
-			throw new HgException(ex);
+			throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation());
 		}
 	}
 
--- a/src/org/tmatesoft/hg/util/CancelSupport.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/util/CancelSupport.java	Tue May 17 00:56:54 2011 +0200
@@ -39,7 +39,7 @@
 		/**
 		 * Obtain non-null cancel support object.
 		 * 
-		 * @param target any object (or <code>null</code>) that might have cancel support
+		 * @param target any object (or <code>null</code>) that might have cancel support. For <code>null</code>, returns an instance than never cancels.
 		 * @return target if it's capable checking cancellation status or no-op implementation that never cancels.
 				 */
 		public static CancelSupport get(Object target) {
@@ -58,4 +58,8 @@
 			};
 		}
 	}
+
+	interface Target<T> {
+		T set(CancelSupport cs);
+	}
 }
--- a/src/org/tmatesoft/hg/util/ProgressSupport.java	Mon May 16 21:10:36 2011 +0200
+++ b/src/org/tmatesoft/hg/util/ProgressSupport.java	Tue May 17 00:56:54 2011 +0200
@@ -24,8 +24,10 @@
  */
 public interface ProgressSupport {
 
-	public void start(long totalUnits);
+	// -1 for unspecified?
+	public void start(int totalUnits);
 	public void worked(int units);
+	// XXX have to specify whether PS implementors may expect #done regardless of job completion (i.e. in case of cancellation) 
 	public void done();
 
 	static class Factory {
@@ -45,7 +47,7 @@
 				}
 			}
 			return new ProgressSupport() {
-				public void start(long totalUnits) {
+				public void start(int totalUnits) {
 				}
 				public void worked(int units) {
 				}
@@ -54,4 +56,40 @@
 			};
 		}
 	}
+	
+	class Sub implements ProgressSupport {
+		private final ProgressSupport ps;
+		private int total;
+		private int units;
+		private int psUnits;
+
+		public Sub(ProgressSupport parent, int parentUnits) {
+			if (parent == null) {
+				throw new IllegalArgumentException();
+			}
+			ps = parent;
+			psUnits = parentUnits;
+		}
+
+		public void start(int totalUnits) {
+			total = totalUnits;
+		}
+
+		public void worked(int worked) {
+			// FIXME fine-grained subprogress report. now only report at about 50% 
+			if (psUnits > 1 && units < total/2 && units+worked > total/2) {
+				ps.worked(psUnits/2);
+				psUnits -= psUnits/2;
+			}
+			units += worked;
+		}
+
+		public void done() {
+			ps.worked(psUnits);
+		}
+	}
+
+	interface Target<T> {
+		T set(ProgressSupport ps);
+	}
 }
--- a/test/org/tmatesoft/hg/test/TestIncoming.java	Mon May 16 21:10:36 2011 +0200
+++ b/test/org/tmatesoft/hg/test/TestIncoming.java	Tue May 17 00:56:54 2011 +0200
@@ -92,7 +92,7 @@
 		ExecHelper eh = new ExecHelper(outParser, new File(localRepo.getLocation()));
 		cmd.executeFull(collector);
 		eh.run("hg", "incoming", "--debug", hgRemote.getLocation());
-		List<Nodeid> liteResult = cmd.executeLite(null);
+		List<Nodeid> liteResult = cmd.executeLite();
 		report(collector, outParser, liteResult, errorCollector);
 		return liteResult;
 	}
--- a/test/org/tmatesoft/hg/test/TestOutgoing.java	Mon May 16 21:10:36 2011 +0200
+++ b/test/org/tmatesoft/hg/test/TestOutgoing.java	Tue May 17 00:56:54 2011 +0200
@@ -66,7 +66,7 @@
 			HgLogCommand.CollectHandler collector = new HgLogCommand.CollectHandler();
 			//
 			cmd.executeFull(collector);
-			List<Nodeid> liteResult = cmd.executeLite(null);
+			List<Nodeid> liteResult = cmd.executeLite();
 			eh.run("hg", "outgoing", "--debug", hgRemote.getLocation());
 			TestIncoming.report(collector, outParser, liteResult, errorCollector);
 			//
@@ -79,7 +79,7 @@
 			//
 			cmd = new HgOutgoingCommand(lookup.detect(dest)).against(hgRemote);
 			cmd.executeFull(collector = new HgLogCommand.CollectHandler());
-			liteResult = cmd.executeLite(null);
+			liteResult = cmd.executeLite();
 			outParser.reset();
 			eh.run("hg", "outgoing", "--debug", hgRemote.getLocation());
 			TestIncoming.report(collector, outParser, liteResult, errorCollector);