changeset 423:9c9c442b5f2e

Major refactoring of exception handling. Low-level API uses RuntimeExceptions, while checked are left for higher level
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 23 Mar 2012 22:51:18 +0100
parents 5d1cc7366d04
children 6437d261048a
files cmdline/org/tmatesoft/hg/console/Bundle.java cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java cmdline/org/tmatesoft/hg/console/Main.java cmdline/org/tmatesoft/hg/console/Manifest.java src/org/tmatesoft/hg/core/ChangesetTransformer.java src/org/tmatesoft/hg/core/HgBadStateException.java src/org/tmatesoft/hg/core/HgCallbackTargetException.java src/org/tmatesoft/hg/core/HgCatCommand.java src/org/tmatesoft/hg/core/HgChangeset.java src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java src/org/tmatesoft/hg/core/HgChangesetHandler.java src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java src/org/tmatesoft/hg/core/HgCloneCommand.java src/org/tmatesoft/hg/core/HgDataStreamException.java src/org/tmatesoft/hg/core/HgFileRevision.java src/org/tmatesoft/hg/core/HgIncomingCommand.java src/org/tmatesoft/hg/core/HgInvalidControlFileException.java src/org/tmatesoft/hg/core/HgInvalidFileException.java src/org/tmatesoft/hg/core/HgInvalidRevisionException.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/HgRepoFacade.java src/org/tmatesoft/hg/core/HgRepositoryNotFoundException.java src/org/tmatesoft/hg/core/HgStatus.java src/org/tmatesoft/hg/core/HgStatusCommand.java src/org/tmatesoft/hg/core/HgStatusHandler.java src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java src/org/tmatesoft/hg/internal/Callback.java src/org/tmatesoft/hg/internal/ChangelogHelper.java src/org/tmatesoft/hg/internal/DigestHelper.java src/org/tmatesoft/hg/internal/ExceptionInfo.java src/org/tmatesoft/hg/internal/InflaterDataAccess.java src/org/tmatesoft/hg/internal/KeywordFilter.java src/org/tmatesoft/hg/internal/ManifestRevision.java src/org/tmatesoft/hg/internal/NewlineFilter.java src/org/tmatesoft/hg/internal/ProcessExecHelper.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/internal/RevlogStream.java src/org/tmatesoft/hg/internal/SubrepoManager.java src/org/tmatesoft/hg/internal/WinToNixPathRewrite.java src/org/tmatesoft/hg/repo/HgBranches.java src/org/tmatesoft/hg/repo/HgBundle.java src/org/tmatesoft/hg/repo/HgChangelog.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgDirstate.java src/org/tmatesoft/hg/repo/HgInternals.java src/org/tmatesoft/hg/repo/HgInvalidControlFileException.java src/org/tmatesoft/hg/repo/HgInvalidFileException.java src/org/tmatesoft/hg/repo/HgInvalidRevisionException.java src/org/tmatesoft/hg/repo/HgInvalidStateException.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgManifest.java src/org/tmatesoft/hg/repo/HgMergeState.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/repo/HgRuntimeException.java src/org/tmatesoft/hg/repo/HgStatusCollector.java src/org/tmatesoft/hg/repo/HgSubrepoLocation.java src/org/tmatesoft/hg/repo/HgTags.java src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java src/org/tmatesoft/hg/repo/Revlog.java src/org/tmatesoft/hg/util/CancelSupport.java src/org/tmatesoft/hg/util/RegularFileStats.java test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java test/org/tmatesoft/hg/test/TestAuxUtilities.java test/org/tmatesoft/hg/test/TestHistory.java
diffstat 67 files changed, 1032 insertions(+), 817 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Bundle.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/cmdline/org/tmatesoft/hg/console/Bundle.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -20,7 +20,6 @@
 import java.util.Collections;
 import java.util.LinkedList;
 
-import org.tmatesoft.hg.core.HgCallbackTargetException;
 import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgBundle;
@@ -62,16 +61,12 @@
 			private final HgChangelog changelog = hgRepo.getChangelog();
 			
 			public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
-				try {
-					if (changelog.isKnown(nodeid)) {
-						System.out.print("+");
-					} else {
-						System.out.print("-");
-					}
-					System.out.printf("%d:%s\n%s\n", revisionNumber, nodeid.shortNotation(), cset.toString());
-				} catch (HgException ex) {
-					throw new HgCallbackTargetException.Wrap(ex);
+				if (changelog.isKnown(nodeid)) {
+					System.out.print("+");
+				} else {
+					System.out.print("-");
 				}
+				System.out.printf("%d:%s\n%s\n", revisionNumber, nodeid.shortNotation(), cset.toString());
 			}
 		});
 	}
--- a/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -93,14 +93,14 @@
 		StringBuilder sb = new StringBuilder();
 		Formatter f = new Formatter(sb);
 		final Nodeid csetNodeid = cset.getNodeid();
-		f.format("changeset:   %d:%s\n", cset.getRevision(), complete ? csetNodeid : csetNodeid.shortNotation());
-		if (cset.getRevision() == tip || repo.getTags().isTagged(csetNodeid)) {
+		f.format("changeset:   %d:%s\n", cset.getRevisionIndex(), complete ? csetNodeid : csetNodeid.shortNotation());
+		if (cset.getRevisionIndex() == tip || repo.getTags().isTagged(csetNodeid)) {
 			sb.append("tag:         ");
 			for (String t : repo.getTags().tags(csetNodeid)) {
 				sb.append(t);
 				sb.append(' ');
 			}
-			if (cset.getRevision() == tip) {
+			if (cset.getRevisionIndex() == tip) {
 				sb.append("tip");
 			}
 			sb.append('\n');
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Fri Mar 23 22:51:18 2012 +0100
@@ -28,7 +28,6 @@
 import java.util.Map;
 
 import org.junit.Assert;
-import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgCallbackTargetException;
 import org.tmatesoft.hg.core.HgCatCommand;
 import org.tmatesoft.hg.core.HgChangeset;
@@ -57,6 +56,7 @@
 import org.tmatesoft.hg.repo.HgManifest.Flags;
 import org.tmatesoft.hg.repo.HgMergeState;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.repo.HgStatusInspector;
 import org.tmatesoft.hg.repo.HgSubrepoLocation;
@@ -131,42 +131,38 @@
 		cmd.file("file1", false);
 		cmd.execute(new HgChangesetTreeHandler() {
 			public void next(HgChangesetTreeHandler.TreeElement entry) {
-				try {
-					StringBuilder sb = new StringBuilder();
-					HashSet<Nodeid> test = new HashSet<Nodeid>(entry.childRevisions());
-					for (HgChangeset cc : entry.children()) {
-						sb.append(cc.getRevisionIndex());
-						sb.append(':');
-						sb.append(cc.getNodeid().shortNotation());
-						sb.append(", ");
-					}
-					final Pair<Nodeid, Nodeid> parents = entry.parentRevisions();
-					final boolean isJoin = !parents.first().isNull() && !parents.second().isNull();
-					final boolean isFork = entry.children().size() > 1;
-					final HgChangeset cset = entry.changeset();
-					System.out.printf("%d:%s - %s\n", cset.getRevisionIndex(), cset.getNodeid().shortNotation(), cset.getComment());
-					if (!isJoin && !isFork && !entry.children().isEmpty()) {
-						System.out.printf("\t=> %s\n", sb);
+				StringBuilder sb = new StringBuilder();
+				HashSet<Nodeid> test = new HashSet<Nodeid>(entry.childRevisions());
+				for (HgChangeset cc : entry.children()) {
+					sb.append(cc.getRevisionIndex());
+					sb.append(':');
+					sb.append(cc.getNodeid().shortNotation());
+					sb.append(", ");
+				}
+				final Pair<Nodeid, Nodeid> parents = entry.parentRevisions();
+				final boolean isJoin = !parents.first().isNull() && !parents.second().isNull();
+				final boolean isFork = entry.children().size() > 1;
+				final HgChangeset cset = entry.changeset();
+				System.out.printf("%d:%s - %s\n", cset.getRevisionIndex(), cset.getNodeid().shortNotation(), cset.getComment());
+				if (!isJoin && !isFork && !entry.children().isEmpty()) {
+					System.out.printf("\t=> %s\n", sb);
+				}
+				if (isJoin) {
+					HgChangeset p1 = entry.parents().first();
+					HgChangeset p2 = entry.parents().second();
+					System.out.printf("\tjoin <= (%d:%s, %d:%s)", p1.getRevisionIndex(), p1.getNodeid().shortNotation(), p2.getRevisionIndex(), p2.getNodeid().shortNotation());
+					if (isFork) {
+						System.out.print(", ");
 					}
-					if (isJoin) {
-						HgChangeset p1 = entry.parents().first();
-						HgChangeset p2 = entry.parents().second();
-						System.out.printf("\tjoin <= (%d:%s, %d:%s)", p1.getRevisionIndex(), p1.getNodeid().shortNotation(), p2.getRevisionIndex(), p2.getNodeid().shortNotation());
-						if (isFork) {
-							System.out.print(", ");
-						}
+				}
+				if (isFork) {
+					if (!isJoin) {
+						System.out.print('\t');
 					}
-					if (isFork) {
-						if (!isJoin) {
-							System.out.print('\t');
-						}
-						System.out.printf("fork => [%s]", sb);
-					}
-					if (isJoin || isFork) {
-						System.out.println();
-					}
-				} catch (HgException ex) {
-					ex.printStackTrace();
+					System.out.printf("fork => [%s]", sb);
+				}
+				if (isJoin || isFork) {
+					System.out.println();
 				}
 			}
 		});
@@ -197,7 +193,7 @@
 						System.out.print("]");
 					}
 					fileLocalRevisions++;
-				} catch (HgException ex) {
+				} catch (HgRuntimeException ex) {
 					ex.printStackTrace();
 				}
 			}
@@ -386,7 +382,7 @@
 		return String.format("%s %s (%d bytes)", r.getPath(), r.getRevision(), sink.toArray().length);
 	}
 	
-	private void testFileStatus() throws HgException, IOException {
+	private void testFileStatus() throws Exception {
 //		final Path path = Path.create("src/org/tmatesoft/hg/util/");
 //		final Path path = Path.create("src/org/tmatesoft/hg/internal/Experimental.java");
 //		final Path path = Path.create("missing-dir/");
@@ -506,7 +502,7 @@
 		}
 
 		public boolean next(Nodeid nid, String fname, String flags) {
-			throw new HgBadStateException(HgManifest.Inspector2.class.getName());
+			throw new IllegalStateException(HgManifest.Inspector2.class.getName());
 		}
 		public boolean next(Nodeid nid, Path fname, Flags flags) {
 			System.out.println(nid + "\t" + fname + "\t\t" + flags);
@@ -529,15 +525,11 @@
 				System.out.println(p);
 			}
 			public void file(HgFileRevision fileRevision) {
-				try {
-					System.out.print(fileRevision.getRevision());;
-					System.out.print("   ");
-					System.out.printf("%s %s", fileRevision.getParents().first().shortNotation(), fileRevision.getParents().second().shortNotation());
-					System.out.print("   ");
-					System.out.println(fileRevision.getPath());
-				} catch (HgException ex) {
-					throw new HgCallbackTargetException.Wrap(ex);
-				}
+				System.out.print(fileRevision.getRevision());;
+				System.out.print("   ");
+				System.out.printf("%s %s", fileRevision.getParents().first().shortNotation(), fileRevision.getParents().second().shortNotation());
+				System.out.print("   ");
+				System.out.println(fileRevision.getPath());
 			}
 			
 			public void end(Nodeid manifestRevision) {
--- a/cmdline/org/tmatesoft/hg/console/Manifest.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/cmdline/org/tmatesoft/hg/console/Manifest.java	Fri Mar 23 22:51:18 2012 +0100
@@ -20,10 +20,10 @@
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
 import org.tmatesoft.hg.core.HgFileRevision;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.HgManifestCommand;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidRevisionException;
 import org.tmatesoft.hg.repo.HgManifest;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Path;
--- a/src/org/tmatesoft/hg/core/ChangesetTransformer.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/ChangesetTransformer.java	Fri Mar 23 22:51:18 2012 +0100
@@ -22,6 +22,7 @@
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.util.Adaptable;
 import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.PathPool;
@@ -35,7 +36,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-/*package-local*/ class ChangesetTransformer implements HgChangelog.Inspector {
+/*package-local*/ class ChangesetTransformer implements HgChangelog.Inspector, Adaptable, CancelSupport {
 	private final HgChangesetHandler handler;
 	private final ProgressSupport progressHelper;
 	private final CancelSupport cancelHelper;
@@ -56,14 +57,14 @@
 		HgStatusCollector statusCollector = new HgStatusCollector(hgRepo);
 		t = new Transformation(statusCollector, pw);
 		handler = delegate;
+		// we let HgChangelog#range deal with progress (pipe through getAdapter)
+		// but use own cancellation (which involves CallbackTargetException as well, and preserves original cancellation 
+		// exception in case clients care)
 		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;
 		}
@@ -71,10 +72,9 @@
 		HgChangeset changeset = t.handle(revisionNumber, nodeid, cset);
 		try {
 			handler.next(changeset);
-			progressHelper.worked(1);
 			cancelHelper.checkCancelled();
-		} catch (RuntimeException ex) {
-			failure = new HgCallbackTargetException(ex).setRevision(nodeid).setRevisionIndex(revisionNumber);
+		} catch (HgCallbackTargetException ex) {
+			failure = ex.setRevision(nodeid).setRevisionIndex(revisionNumber);
 		} catch (CancelledException ex) {
 			cancellation = ex;
 		}
@@ -118,4 +118,19 @@
 			return changeset;
 		}
 	}
+
+	public void checkCancelled() throws CancelledException {
+		if (failure != null || cancellation != null) {
+			// stop HgChangelog.Iterator. Our exception is for the purposes of cancellation only,
+			// the one we have stored (this.cancellation) is for user
+			throw new CancelledException(); 
+		}
+	}
+
+	public <T> T getAdapter(Class<T> adapterClass) {
+		if (adapterClass == ProgressSupport.class) {
+			return adapterClass.cast(progressHelper);
+		}
+		return null;
+	}
 }
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/HgBadStateException.java	Fri Mar 23 21:26:01 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2011-2012 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;
-
-/**
- * hg4j's own internal error or unexpected state.
- * XXX unless there's anything additional, there's not too much value in this class
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-public class HgBadStateException extends RuntimeException {
-
-	public HgBadStateException(String message) {
-		super(message);
-	}
-
-	public HgBadStateException(Throwable cause) {
-		super(cause);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgCallbackTargetException.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgCallbackTargetException.java	Fri Mar 23 22:51:18 2012 +0100
@@ -16,26 +16,31 @@
  */
 package org.tmatesoft.hg.core;
 
+import org.tmatesoft.hg.internal.ExceptionInfo;
 import org.tmatesoft.hg.util.Path;
 
 
 
 /**
- * Checked exception that indicates errors in client code and tries to supply extra information about the context it occurred in.
+ * Checked exception that client supplied callback code can use to indicates its own errors.
  * 
- * Generally, client need to pass own error information/exceptions from within implementations of the callback methods they supply. 
+ * <p>Generally, client need to pass own error information/exceptions from within implementations of the callback methods they supply. 
  * However, there's no straightforward way to alter throws clause for these methods, and alternatives like generic {@link Exception} or
  * library's own {@link HgException} are rather obscure. Suggested approach is to wrap whatever exception user code produces with
- * {@link RuntimeException} subclass, {@link Wrap}. Then, unwrap and re-throw with checked {@link HgCallbackTargetException}. 
+ * {@link HgCallbackTargetException}, the only checked exception allowed out from a callback.
  * 
- * FIXME REVISIT perhaps, shall just throw HgCallbackTargetException from any handler, and do not catch anything in commands at all?
- * FIXME decide whether shall root at HgException ("throws HgException, HgCallbackTargetException" looks a bit odd now) 
+ *  <p>It's intentionally not a subclass of {@link HgException} to avoid get mixed with library own errors and be processed separately.
+ * 
+ * FIXME REVISIT shall just throw HgCallbackTargetException from any handler, and do not catch anything in commands at all.
  * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
 @SuppressWarnings("serial")
-public class HgCallbackTargetException extends HgException {
+public class HgCallbackTargetException extends Exception {
+	
+	protected final ExceptionInfo<HgCallbackTargetException> details = new ExceptionInfo<HgCallbackTargetException>(this);
+
 	/**
 	 * @param cause can't be <code>null</code>
 	 */
@@ -44,16 +49,11 @@
 		if (cause == null) {
 			throw new IllegalArgumentException();
 		}
-		if (cause.getClass() == Wrap.class) {
-			// eliminate wrapper
-			initCause(cause.getCause());
-		} else {
-			initCause(cause);
-		}
+		initCause(cause);
 	}
 
 	@SuppressWarnings("unchecked")
-	public <T extends Exception> T getTargetException() {
+	public <T extends Throwable> T getTargetException() {
 		return (T) getCause();
 	}
 	
@@ -64,39 +64,22 @@
 	@Override
 	public String getMessage() {
 		StringBuilder sb = new StringBuilder();
-		sb.append("Original exception thrown: ");
+		sb.append("Error from callback. Original exception thrown: ");
 		sb.append(getCause().getClass().getName());
 		sb.append(" at ");
-		extras.appendDetails(sb);
+		details.appendDetails(sb);
 		return sb.toString();
 	}
 
-	@Override
 	public HgCallbackTargetException setRevision(Nodeid r) {
-		return (HgCallbackTargetException) super.setRevision(r);
-	}
-	@Override
-	public HgCallbackTargetException setRevisionIndex(int rev) {
-		return (HgCallbackTargetException) super.setRevisionIndex(rev);
-	}
-	@Override
-	public HgCallbackTargetException setFileName(Path name) {
-		return (HgCallbackTargetException) super.setFileName(name);
+		return details.setRevision(r);
 	}
 
-	/**
-	 * 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 HgCallbackTargetException setRevisionIndex(int rev) {
+		return details.setRevisionIndex(rev);
+	}
 
-		public Wrap(Throwable cause) {
-			super(cause);
-			if (cause == null) {
-				throw new IllegalArgumentException();
-			}
-		}
+	public HgCallbackTargetException setFileName(Path name) {
+		return details.setFileName(name);
 	}
 }
--- a/src/org/tmatesoft/hg/core/HgCatCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgCatCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -24,6 +24,9 @@
 import java.nio.ByteBuffer;
 
 import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidFileException;
+import org.tmatesoft.hg.repo.HgInvalidRevisionException;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Adaptable;
 import org.tmatesoft.hg.util.ByteChannel;
@@ -161,6 +164,7 @@
 			int csetRev = repo.getChangelog().getRevisionIndex(cset);
 			Nodeid toExtract = null;
 			do {
+				// TODO post-1.0 perhaps, HgChangesetFileSneaker may come handy?
 				toExtract = repo.getManifest().getFileRevision(csetRev, file);
 				if (toExtract == null) {
 					if (dataFile.isCopy()) {
@@ -172,7 +176,8 @@
 				}
 			} while (toExtract == null);
 			if (toExtract == null) {
-				throw new HgBadStateException(String.format("File %s nor its origins were not known at repository %s revision", file, cset.shortNotation()));
+				// TODO explicit FileNotFoundException?
+				throw new HgBadArgumentException(String.format("File %s nor its origins were not known at repository %s revision", file, cset.shortNotation()), null);
 			}
 			revToExtract = dataFile.getRevisionIndex(toExtract);
 		} else if (revision != null) {
--- a/src/org/tmatesoft/hg/core/HgChangeset.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgChangeset.java	Fri Mar 23 22:51:18 2012 +0100
@@ -23,8 +23,12 @@
 
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Path;
 
 
@@ -170,10 +174,9 @@
 	 * Figures out files and specific revisions thereof that were modified in this commit
 	 *  
 	 * @return revisions of files modified in this commit
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
- 	 * @throws HgException in case of some other library issue 
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public List<HgFileRevision> getModifiedFiles() throws HgException {
+	public List<HgFileRevision> getModifiedFiles() throws HgRuntimeException {
 		if (modifiedFiles == null) {
 			initFileChanges();
 		}
@@ -184,10 +187,9 @@
 	 * Figures out files added in this commit
 	 * 
 	 * @return revisions of files added in this commit
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
-	 * @throws HgException in case of some other library issue 
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public List<HgFileRevision> getAddedFiles() throws HgException {
+	public List<HgFileRevision> getAddedFiles() throws HgRuntimeException {
 		if (addedFiles == null) {
 			initFileChanges();
 		}
@@ -198,10 +200,9 @@
 	 * Figures out files that were deleted as part of this commit
 	 * 
 	 * @return revisions of files deleted in this commit
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
-	 * @throws HgException in case of some other library issue 
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public List<Path> getRemovedFiles() throws HgException {
+	public List<Path> getRemovedFiles() throws HgRuntimeException {
 		if (deletedFiles == null) {
 			initFileChanges();
 		}
@@ -215,9 +216,9 @@
 	
 	/**
 	 * @return never <code>null</code>
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public Nodeid getFirstParentRevision() throws HgInvalidControlFileException {
+	public Nodeid getFirstParentRevision() throws HgRuntimeException {
 		if (parentHelper != null) {
 			return parentHelper.safeFirstParent(nodeid);
 		}
@@ -232,9 +233,9 @@
 	
 	/**
 	 * @return never <code>null</code>
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public Nodeid getSecondParentRevision() throws HgInvalidControlFileException {
+	public Nodeid getSecondParentRevision() throws HgRuntimeException {
 		if (parentHelper != null) {
 			return parentHelper.safeSecondParent(nodeid);
 		}
@@ -260,17 +261,22 @@
 		}
 	}
 
-	private /*synchronized*/ void initFileChanges() throws HgException {
+	private /*synchronized*/ void initFileChanges() throws HgRuntimeException {
 		ArrayList<Path> deleted = new ArrayList<Path>();
 		ArrayList<HgFileRevision> modified = new ArrayList<HgFileRevision>();
 		ArrayList<HgFileRevision> added = new ArrayList<HgFileRevision>();
 		HgStatusCollector.Record r = new HgStatusCollector.Record();
-		statusHelper.change(revNumber, r);
+		try {
+			statusHelper.change(revNumber, r);
+		} catch (CancelledException ex) {
+			// Record can't cancel
+			throw new HgInvalidStateException("Internal error");
+		}
 		final HgRepository repo = statusHelper.getRepo();
 		for (Path s : r.getModified()) {
 			Nodeid nid = r.nodeidAfterChange(s);
 			if (nid == null) {
-				throw new HgException(String.format("For the file %s recorded as modified couldn't find revision after change", s));
+				throw new HgInvalidStateException(String.format("For the file %s recorded as modified in changeset %d couldn't find revision after change", s, revNumber));
 			}
 			modified.add(new HgFileRevision(repo, nid, null, s, null));
 		}
@@ -278,7 +284,7 @@
 		for (Path s : r.getAdded()) {
 			Nodeid nid = r.nodeidAfterChange(s);
 			if (nid == null) {
-				throw new HgException(String.format("For the file %s recorded as added couldn't find revision after change", s));
+				throw new HgInvalidStateException(String.format("For the file %s recorded as added in changeset %d couldn't find revision after change", s, revNumber));
 			}
 			added.add(new HgFileRevision(repo, nid, null, s, copied.get(s)));
 		}
--- a/src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java	Fri Mar 23 22:51:18 2012 +0100
@@ -18,8 +18,11 @@
 
 import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgManifest;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.Status;
 
@@ -145,7 +148,7 @@
 					extractRevFlags = cachedManifest.flags(file);
 				}
 			}
-		} catch (HgException ex) {
+		} catch (HgRuntimeException ex) {
 			checkResult = new Status(Status.Kind.ERROR, phaseMsg, ex);
 			return checkResult;
 		}
@@ -212,7 +215,7 @@
 
 	private void assertCheckRan() {
 		if (checkResult == null) {
-			throw new HgBadStateException("Shall invoke #check(Path) first");
+			throw new HgInvalidStateException("Shall invoke #check(Path) first");
 		}
 	}
 
--- a/src/org/tmatesoft/hg/core/HgChangesetHandler.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgChangesetHandler.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -16,6 +16,7 @@
  */
 package org.tmatesoft.hg.core;
 
+import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.util.CancelledException;
 
 /**
@@ -24,12 +25,12 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
+@Callback
 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 HgCallbackTargetException wrapper for any exception user code may produce 
 	 * @throws CancelledException if handler is not interested in more changesets and iteration shall stop
-	 * @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) throws CancelledException;
+	void next(HgChangeset changeset) throws HgCallbackTargetException, CancelledException;
 }
--- a/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java	Fri Mar 23 22:51:18 2012 +0100
@@ -18,6 +18,7 @@
 
 import java.util.Collection;
 
+import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Pair;
 
@@ -29,64 +30,57 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
+@Callback
 public interface HgChangesetTreeHandler {
 	/**
 	 * @param entry access to various pieces of information about current tree node. Instances might be 
 	 * reused across calls and shall not be kept by client's code
-	 * @throws HgException allows implementers propagate errors from {@link TreeElement} or other parts of the library.
-	 * @throws HgCallbackTargetException.Wrap wrapper object for any exception user code may produce. Wrapped exception would get re-thrown with {@link HgCallbackTargetException} 
+	 * @throws HgCallbackTargetException wrapper for any exception user code may produce 
 	 * @throws CancelledException if execution of the operation was cancelled
 	 */
-	public void next(HgChangesetTreeHandler.TreeElement entry) throws HgException, HgCallbackTargetException.Wrap, CancelledException;
+	public void next(HgChangesetTreeHandler.TreeElement entry) throws HgCallbackTargetException, CancelledException;
 
 	interface TreeElement {
 		/**
 		 * Revision of the revlog being iterated. For example, when walking file history, return value represents file revisions.
 		 * 
 		 * @return revision of the revlog being iterated.
-		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public Nodeid fileRevision() throws HgException;
+		public Nodeid fileRevision();
 
 		/**
 		 * @return changeset associated with the current revision
-		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public HgChangeset changeset() throws HgException;
+		public HgChangeset changeset();
 
 		/**
 		 * Lightweight alternative to {@link #changeset()}, identifies changeset in which current file node has been modified 
 		 * @return changeset {@link Nodeid revision} 
-		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public Nodeid changesetRevision() throws HgException;
+		public Nodeid changesetRevision();
 
 		/**
 		 * Node, these are not necessarily in direct relation to parents of changeset from {@link #changeset()} 
 		 * @return changesets that correspond to parents of the current file node, either pair element may be <code>null</code>.
-		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public Pair<HgChangeset, HgChangeset> parents() throws HgException;
+		public Pair<HgChangeset, HgChangeset> parents();
 		
 		/**
 		 * Lightweight alternative to {@link #parents()}, give {@link Nodeid nodeids} only
 		 * @return two values, neither is <code>null</code>, use {@link Nodeid#isNull()} to identify parent not set
-		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public Pair<Nodeid, Nodeid> parentRevisions() throws HgException;
+		public Pair<Nodeid, Nodeid> parentRevisions();
 
 		/**
 		 * Changes that originate from the given change and bear it as their parent. 
 		 * @return collection (possibly empty) of immediate children of the change
-		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public Collection<HgChangeset> children() throws HgException;
+		public Collection<HgChangeset> children();
 
 		/**
 		 * Lightweight alternative to {@link #children()}.
 		 * @return never <code>null</code>
-		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public Collection<Nodeid> childRevisions() throws HgException;
+		public Collection<Nodeid> childRevisions();
 	}
 }
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/HgCloneCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgCloneCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -37,9 +37,13 @@
 import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.repo.HgBundle;
 import org.tmatesoft.hg.repo.HgBundle.GroupElement;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidFileException;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgLookup;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.PathRewrite;
 
@@ -49,7 +53,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class HgCloneCommand {
+public class HgCloneCommand extends HgAbstractCommand<HgCloneCommand> {
 
 	private File destination;
 	private HgRemoteRepository srcRepo;
@@ -72,7 +76,17 @@
 		return this;
 	}
 
-	public HgRepository execute() throws HgBadArgumentException, HgRemoteConnectionException, HgInvalidFileException, CancelledException {
+	/**
+	 * 
+	 * @return
+	 * @throws HgBadArgumentException
+	 * @throws HgRemoteConnectionException
+	 * @throws HgRepositoryNotFoundException
+	 * @throws HgException
+	 * @throws CancelledException
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	public HgRepository execute() throws HgException, CancelledException {
 		if (destination == null) {
 			throw new IllegalArgumentException("Destination not set", null);
 		}
@@ -159,7 +173,7 @@
 				indexFile = new FileOutputStream(new File(hgDir, filename = "store/00changelog.i"));
 				collectChangelogIndexes = true;
 			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
+				throw new HgInvalidControlFileException("Failed to write changelog", ex, new File(filename));
 			}
 		}
 
@@ -174,7 +188,7 @@
 				indexFile = null;
 				filename = null;
 			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
+				throw new HgInvalidControlFileException("Failed to write changelog", ex, new File(filename));
 			}
 		}
 
@@ -185,7 +199,7 @@
 				revisionSequence.clear();
 				indexFile = new FileOutputStream(new File(hgDir, filename = "store/00manifest.i"));
 			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
+				throw new HgInvalidControlFileException("Failed to write manifest", ex, new File(filename));
 			}
 		}
 
@@ -199,7 +213,7 @@
 				indexFile = null;
 				filename = null;
 			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
+				throw new HgInvalidControlFileException("Failed to write changelog", ex, new File(filename));
 			}
 		}
 		
@@ -214,7 +228,8 @@
 				file.getParentFile().mkdirs();
 				indexFile = new FileOutputStream(file);
 			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
+				String m = String.format("Failed to write file %s", filename);
+				throw new HgInvalidControlFileException(m, ex, new File(filename));
 			}
 		}
 
@@ -228,7 +243,8 @@
 				indexFile = null;
 				filename = null;
 			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
+				String m = String.format("Failed to write file %s", filename);
+				throw new HgInvalidControlFileException(m, ex, new File(filename));
 			}
 		}
 
@@ -242,7 +258,8 @@
 					}
 				}
 			}
-			throw new HgBadStateException(String.format("Can't find index of %s for file %s", p.shortNotation(), filename));
+			String m = String.format("Can't find index of %s for file %s", p.shortNotation(), filename);
+			throw new HgInvalidControlFileException(m, null, null).setRevision(p);
 		}
 
 		public boolean element(GroupElement ge) {
@@ -259,7 +276,8 @@
 				byte[] calculated = dh.sha1(p1, p2, content).asBinary();
 				final Nodeid node = ge.node();
 				if (!node.equalsTo(calculated)) {
-					throw new HgBadStateException(String.format("Checksum failed: expected %s, calculated %s. File %s", node, calculated, filename));
+					// TODO post-1.0 custom exception ChecksumCalculationFailed?
+					throw new HgInvalidStateException(String.format("Checksum failed: expected %s, calculated %s. File %s", node, calculated, filename));
 				}
 				final int link;
 				if (collectChangelogIndexes) {
@@ -268,7 +286,7 @@
 				} else {
 					Integer csRev = changelogIndexes.get(ge.cset());
 					if (csRev == null) {
-						throw new HgBadStateException(String.format("Changelog doesn't contain revision %s of %s", ge.cset().shortNotation(), filename));
+						throw new HgInvalidStateException(String.format("Changelog doesn't contain revision %s of %s", ge.cset().shortNotation(), filename));
 					}
 					link = csRev.intValue();
 				}
@@ -325,7 +343,8 @@
 				prevRevContent.done();
 				prevRevContent = new ByteArrayDataAccess(content);
 			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
+				String m = String.format("Failed to write revision %s of file %s", ge.node().shortNotation(), filename);
+				throw new HgInvalidControlFileException(m, ex, new File(filename));
 			}
 			return true;
 		}
--- a/src/org/tmatesoft/hg/core/HgDataStreamException.java	Fri Mar 23 21:26:01 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2011-2012 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.HgDataFile;
-import org.tmatesoft.hg.util.Path;
-
-/**
- * Any erroneous state with @link {@link HgDataFile} input/output, read/write operations
- * 
- * @deprecated {@link HgInvalidControlFileException} and {@link HgInvalidFileException} deemed sufficient
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-@Deprecated
-public class HgDataStreamException extends HgException {
-
-	public HgDataStreamException(Path file, String message, Throwable cause) {
-		super(message, cause);
-		setFileName(file);
-	}
-	
-	public HgDataStreamException(Path file, Throwable cause) {
-		super(cause);
-		setFileName(file);
-	}
-
-	@Override
-	public HgDataStreamException setRevision(Nodeid r) {
-		return (HgDataStreamException) super.setRevision(r);
-	}
-	
-	@Override
-	public HgDataStreamException setRevisionIndex(int rev) {
-		return (HgDataStreamException) super.setRevisionIndex(rev);
-	}
-	@Override
-	public HgDataStreamException setFileName(Path name) {
-		return (HgDataStreamException) super.setFileName(name);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgFileRevision.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgFileRevision.java	Fri Mar 23 22:51:18 2012 +0100
@@ -17,6 +17,8 @@
 package org.tmatesoft.hg.core;
 
 import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidRevisionException;
 import org.tmatesoft.hg.repo.HgManifest;
 import org.tmatesoft.hg.repo.HgManifest.Flags;
 import org.tmatesoft.hg.repo.HgRepository;
--- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -29,7 +29,10 @@
 import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain;
 import org.tmatesoft.hg.repo.HgBundle;
 import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidFileException;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.CancelledException;
@@ -150,7 +153,7 @@
 				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");
+							throw new HgInvalidStateException("Bundle shall not report known nodes other than roots we've supplied");
 						}
 						return;
 					}
--- a/src/org/tmatesoft/hg/core/HgInvalidControlFileException.java	Fri Mar 23 21:26:01 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2011-2012 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 java.io.File;
-
-import org.tmatesoft.hg.internal.Experimental;
-import org.tmatesoft.hg.util.Path;
-
-/**
- * WORK IN PROGRESS
- * 
- * Subclass of {@link HgInvalidFileException} to indicate failure to deal with one of <b>Mercurial</b> control files 
- * (most likely those under .hg/, but also those residing in the repository, with special meaning to the Mercurial, like .hgtags or .hgignore)
- * 
- * XXX Perhaps, HgInvalidRevlogException?
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-@Experimental(reason="WORK IN PROGRESS. Name is likely to change")
-public class HgInvalidControlFileException extends HgInvalidFileException {
-
-	public HgInvalidControlFileException(String message, Throwable th, File file) {
-		super(message, th, file);
-	}
-
-	@Override
-	public HgInvalidControlFileException setFile(File file) {
-		super.setFile(file);
-		return this;
-	}
-	
-	@Override
-	public HgInvalidControlFileException setRevision(Nodeid r) {
-		return (HgInvalidControlFileException) super.setRevision(r);
-	}
-
-	@Override
-	public HgInvalidControlFileException setRevisionIndex(int rev) {
-		return (HgInvalidControlFileException) super.setRevisionIndex(rev);
-	}
-	
-	@Override
-	public HgInvalidControlFileException setFileName(Path name) {
-		return (HgInvalidControlFileException) super.setFileName(name);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgInvalidFileException.java	Fri Mar 23 21:26:01 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2011-2012 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 java.io.File;
-import java.io.IOException;
-
-/**
- * Thrown when there are troubles working with local file. Most likely (but not necessarily) wraps IOException. Might be 
- * perceived as specialized IOException with optional File and other repository information.
- * 
- * <b>Hg4J</b> tries to minimize chances for IOException to occur (i.e. {@link File#canRead()} is checked before attempt to 
- * read a file that might not exist, and doesn't use this exception to wrap each and any {@link IOException} source (e.g. 
- * <code>#close()</code> calls are unlikely to yield it), hence it is likely to address real cases when I/O error occurs.
- * 
- * On the other hand, when a file is supposed to exist and be readable, this exception might get thrown as well to indicate
- * that's not true. 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-public class HgInvalidFileException extends HgException {
-
-	public HgInvalidFileException(String message, Throwable th) {
-		super(message, th);
-	}
-
-	public HgInvalidFileException(String message, Throwable th, File file) {
-		super(message, th);
-		extras.setFile(file); // allows null
-	}
-
-	public HgInvalidFileException setFile(File file) {
-		assert file != null; // doesn't allow null not to clear file accidentally
-		extras.setFile(file);
-		return this;
-	}
-
-	/**
-	 * @return file object that causes troubles, or <code>null</code> if specific file is unknown
-	 */
-	public File getFile() {
-		return extras.getFile();
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgInvalidRevisionException.java	Fri Mar 23 21:26:01 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/*
- * Copyright (c) 2011-2012 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.*;
-
-import org.tmatesoft.hg.internal.Experimental;
-
-/**
- * Use of revision or revision local index that is not valid for a given revlog.
- *  
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-@Experimental(reason="1) Whether to use checked or runtime exception is not yet decided. 2) Perhaps, its use not bound to wrong arguments")
-public class HgInvalidRevisionException extends IllegalArgumentException {
-	private Nodeid rev;
-	private Integer revIdx = BAD_REVISION;
-	// next two make sense only when revIdx is present
-	private int rangeLeftBoundary = BAD_REVISION, rangeRightBoundary = BAD_REVISION;
-
-	/**
-	 * 
-	 * this exception is not expected to be initialized with another exception, although those who need to, 
-	 * may still use {@link #initCause(Throwable)}
-	 * @param message optional description of the issue
-	 * @param revision invalid revision, may be  <code>null</code> if revisionIndex is used
-	 * @param revisionIndex invalid revision index, may be <code>null</code> if not known and revision is supplied 
-	 */
-	public HgInvalidRevisionException(String message, Nodeid revision, Integer revisionIndex) {
-		super(message);
-		assert revision != null || revisionIndex != null; 
-		rev = revision;
-		revIdx = revisionIndex;
-	}
-
-	public HgInvalidRevisionException(Nodeid revision) {
-		this(null, revision, null);
-	}
-	
-	public HgInvalidRevisionException(int revisionIndex) {
-		this(null, null, revisionIndex);
-	}
-
-	public Nodeid getRevision() {
-		return rev;
-	}
-	
-	public Integer getRevisionIndex() {
-		return revIdx;
-	}
-
-	public HgInvalidRevisionException setRevision(Nodeid revision) {
-		assert revision != null;
-		rev = revision;
-		return this;
-	}
-	
-	// int, not Integer is on purpose, not to clear exception completely
-	public HgInvalidRevisionException setRevisionIndex(int revisionIndex) {
-		revIdx = revisionIndex;
-		return this;
-	}
-	
-	public HgInvalidRevisionException setRevisionIndex(int revisionIndex, int rangeLeft, int rangeRight) {
-		revIdx = revisionIndex;
-		rangeLeftBoundary = rangeLeft;
-		rangeRightBoundary = rangeRight;
-		return this;
-	}
-
-	public boolean isRevisionSet() {
-		return rev != null;
-	}
-	
-	public boolean isRevisionIndexSet() {
-		return revIdx != BAD_REVISION;
-	}
-
-	@Override
-	public String getMessage() {
-		String msg = super.getMessage();
-		if (msg != null) {
-			return msg;
-		}
-		StringBuilder sb = new StringBuilder();
-		if (rev != null) {
-			sb.append("Revision:");
-			sb.append(rev.shortNotation());
-			sb.append(' ');
-		}
-		if (revIdx != null) {
-			String sr;
-			switch (revIdx) {
-			case BAD_REVISION : sr = "UNKNOWN"; break;
-			case TIP : sr = "TIP"; break;
-			case WORKING_COPY: sr = "WORKING-COPY"; break;
-			case NO_REVISION : sr = "NO REVISION"; break;
-			default : sr = revIdx.toString();
-			}
-			if (rangeLeftBoundary != BAD_REVISION || rangeRightBoundary != BAD_REVISION) {
-				sb.append(String.format("%s is not from [%d..%d]", sr, rangeLeftBoundary, rangeRightBoundary));
-			} else {
-				sb.append(sr);
-			}
-		}
-		return sb.toString();
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgLogCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -35,7 +35,11 @@
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidRevisionException;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.CancelledException;
@@ -185,14 +189,22 @@
 
 	/**
 	 * Similar to {@link #execute(HgChangesetHandler)}, collects and return result as a list.
+	 * @throws HgException FIXME EXCEPTIONS
 	 */
 	public List<HgChangeset> execute() throws HgException {
 		CollectHandler collector = new CollectHandler();
 		try {
 			execute(collector);
+		} catch (HgCallbackTargetException ex) {
+			// see below for CanceledException
+			HgInvalidStateException t = new HgInvalidStateException("Internal error");
+			t.initCause(ex);
+			throw t;
 		} catch (CancelledException ex) {
 			// can't happen as long as our CollectHandler doesn't throw any exception
-			throw new HgBadStateException(ex);
+			HgInvalidStateException t = new HgInvalidStateException("Internal error");
+			t.initCause(ex);
+			throw t;
 		}
 		return collector.getChanges();
 	}
@@ -201,7 +213,7 @@
 	 * Iterate over range of changesets configured in the command.
 	 * 
 	 * @param handler callback to process changesets.
-	 * @throws HgCallbackTargetException to re-throw exception from the handler
+	 * @throws HgCallbackTargetException wrapper for any exception user callback code may produce
 	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
 	 * @throws HgException in case of some other library issue 
 	 * @throws CancelledException if execution of the command was cancelled
@@ -237,11 +249,7 @@
 						if (handler instanceof FileHistoryHandler) {
 							HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), null, fileNode.getCopySourceName());
 							HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), null, fileNode.getPath(), src.getPath());
-							try {
-								((FileHistoryHandler) handler).copy(src, dst);
-							} catch (HgCallbackTargetException.Wrap ex) {
-								throw new HgCallbackTargetException(ex).setRevision(fileNode.getCopySourceRevision()).setFileName(fileNode.getCopySourceName());
-							}
+							((FileHistoryHandler) handler).copy(src, dst);
 						}
 						if (limit > 0 && count >= limit) {
 							// if limit reach, follow is useless.
@@ -331,17 +339,13 @@
 		} else {
 			ph2 = new ProgressSupport.Sub(progressHelper, 3);
 		}
-		try {
-			ph2.start(completeHistory.length);
-			// XXX shall sort completeHistory according to changeset numbers?
-			for (int i = 0; i < completeHistory.length; i++ ) {
-				final HistoryNode n = completeHistory[i];
-				handler.next(ei.init(n));
-				ph2.worked(1);
-				cancelHelper.checkCancelled();
-			}
-		} catch (HgCallbackTargetException.Wrap ex) {
-			throw new HgCallbackTargetException(ex);
+		ph2.start(completeHistory.length);
+		// XXX shall sort completeHistory according to changeset numbers?
+		for (int i = 0; i < completeHistory.length; i++ ) {
+			final HistoryNode n = completeHistory[i];
+			handler.next(ei.init(n));
+			ph2.worked(1);
+			cancelHelper.checkCancelled();
 		}
 		progressHelper.done();
 	}
@@ -399,9 +403,9 @@
 	public interface FileHistoryHandler extends HgChangesetHandler { // FIXME move to stanalone class file, perhaps?
 		// XXX perhaps, should distinguish copy from rename? And what about merged revisions and following them?
 		/**
-		 * @throws HgCallbackTargetException.Wrap wrapper object for any exception user code may produce. Wrapped exception would get re-thrown with {@link HgCallbackTargetException} 
+		 * @throws HgCallbackTargetException wrapper object for any exception user code may produce 
 		 */
-		void copy(HgFileRevision from, HgFileRevision to) throws HgCallbackTargetException.Wrap;
+		void copy(HgFileRevision from, HgFileRevision to) throws HgCallbackTargetException;
 	}
 	
 	public static class CollectHandler implements HgChangesetHandler {
@@ -471,11 +475,11 @@
 			return historyNode.fileRevision;
 		}
 
-		public HgChangeset changeset() throws HgException {
+		public HgChangeset changeset() {
 			return get(historyNode.changeset)[0];
 		}
 
-		public Pair<HgChangeset, HgChangeset> parents() throws HgException {
+		public Pair<HgChangeset, HgChangeset> parents() {
 			if (parents != null) {
 				return parents;
 			}
@@ -495,7 +499,7 @@
 			return parents = new Pair<HgChangeset, HgChangeset>(r[0], r[1]);
 		}
 
-		public Collection<HgChangeset> children() throws HgException {
+		public Collection<HgChangeset> children() {
 			if (children != null) {
 				return children;
 			}
@@ -516,7 +520,7 @@
 			cachedChangesets.put(cs.getRevisionIndex(), cs);
 		}
 		
-		private HgChangeset[] get(int... changelogRevisionIndex) throws HgException {
+		private HgChangeset[] get(int... changelogRevisionIndex) {
 			HgChangeset[] rv = new HgChangeset[changelogRevisionIndex.length];
 			IntVector misses = new IntVector(changelogRevisionIndex.length, -1);
 			for (int i = 0; i < changelogRevisionIndex.length; i++) {
@@ -538,7 +542,8 @@
 				for (int changeset2read : changesets2read) {
 					HgChangeset cs = cachedChangesets.get(changeset2read);
 					if (cs == null) {
-						throw new HgException(String.format("Can't get changeset for revision %d", changeset2read));
+						HgInvalidStateException t = new HgInvalidStateException(String.format("Can't get changeset for revision %d", changeset2read));
+						throw t.setRevisionIndex(changeset2read);
 					}
 					// HgChangelog.range may reorder changesets according to their order in the changelog
 					// thus need to find original index
@@ -560,7 +565,7 @@
 		}
 
 		// init only when needed
-		void initTransform() throws HgInvalidControlFileException {
+		void initTransform() throws HgRuntimeException {
 			if (transform == null) {
 				transform = new ChangesetTransformer.Transformation(new HgStatusCollector(repo)/*XXX try to reuse from context?*/, getParentHelper(false));
 			}
@@ -571,14 +576,14 @@
 			populate(cs.clone());
 		}
 
-		public Nodeid changesetRevision() throws HgException {
+		public Nodeid changesetRevision() {
 			if (changesetRevision == null) {
 				changesetRevision = getRevision(historyNode.changeset);
 			}
 			return changesetRevision;
 		}
 
-		public Pair<Nodeid, Nodeid> parentRevisions() throws HgException {
+		public Pair<Nodeid, Nodeid> parentRevisions() {
 			if (parentRevisions == null) {
 				HistoryNode p;
 				final Nodeid p1, p2;
@@ -597,7 +602,7 @@
 			return parentRevisions;
 		}
 
-		public Collection<Nodeid> childRevisions() throws HgException {
+		public Collection<Nodeid> childRevisions() {
 			if (childRevisions != null) {
 				return childRevisions;
 			}
@@ -614,8 +619,8 @@
 		}
 		
 		// reading nodeid involves reading index only, guess, can afford not to optimize multiple reads
-		private Nodeid getRevision(int changelogRevisionNumber) throws HgInvalidControlFileException {
-			// TODO [post-1.0] pipe through pool
+		private Nodeid getRevision(int changelogRevisionNumber) {
+			// TODO post-1.0 pipe through pool
 			HgChangeset cs = cachedChangesets.get(changelogRevisionNumber);
 			if (cs != null) {
 				return cs.getNodeid();
--- a/src/org/tmatesoft/hg/core/HgManifestCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgManifestCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -25,6 +25,7 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.repo.HgManifest;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgManifest.Flags;
@@ -121,7 +122,8 @@
 	/**
 	 * Callback to walk file/directory tree of a revision
 	 */
-	public interface Handler {
+	@Callback
+	public interface Handler { // FIXME TLC
 		void begin(Nodeid manifestRevision);
 		void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs
 		void file(HgFileRevision fileRevision); // XXX allow to check p is invalid (df.exists())
@@ -180,7 +182,7 @@
 			return true;
 		}
 		public boolean next(Nodeid nid, String fname, String flags) {
-			throw new HgBadStateException(HgManifest.Inspector2.class.getName());
+			throw new IllegalStateException(HgManifest.Inspector2.class.getName());
 		}
 		
 		public boolean next(Nodeid nid, Path fname, Flags flags) {
--- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -22,6 +22,7 @@
 
 import org.tmatesoft.hg.internal.RepositoryComparator;
 import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.CancelSupport;
--- a/src/org/tmatesoft/hg/core/HgRepoFacade.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgRepoFacade.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -66,10 +66,11 @@
 
 	/**
 	 * Tries to find repository starting from the current working directory.
+	 * 
 	 * @return <code>true</code> if found valid repository
-	 * @throws HgInvalidFileException in case of errors during repository initialization
+	 * @throws HgRepositoryNotFoundException if no repository found in working directory
 	 */
-	public boolean init() throws HgInvalidFileException {
+	public boolean init() throws HgRepositoryNotFoundException {
 		repo = new HgLookup(context).detectFromWorkingDir();
 		return repo != null && !repo.isInvalid();
 	}
@@ -79,10 +80,10 @@
 	 * 
 	 * @param repoLocation path to any folder within structure of a Mercurial repository.
 	 * @return <code>true</code> if found valid repository 
-	 * @throws HgInvalidFileException if there are errors accessing specified location
+	 * @throws HgRepositoryNotFoundException if there's no repository at specified location
 	 * @throws IllegalArgumentException if argument is <code>null</code>
 	 */
-	public boolean initFrom(File repoLocation) throws HgInvalidFileException {
+	public boolean initFrom(File repoLocation) throws HgRepositoryNotFoundException {
 		if (repoLocation == null) {
 			throw new IllegalArgumentException();
 		}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgRepositoryNotFoundException.java	Fri Mar 23 22:51:18 2012 +0100
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2012 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;
+
+/**
+ * Indicates failure to find repository at specified location
+ * XXX may provide information about alternatives tried
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgRepositoryNotFoundException extends HgException {
+
+	private String location;
+
+	public HgRepositoryNotFoundException(String message) {
+		super(message);
+	}
+	
+	public HgRepositoryNotFoundException setLocation(String location) {
+		this.location = location;
+		return this;
+	}
+	
+	public String getLocation() {
+		return this.location;
+	}
+}
--- a/src/org/tmatesoft/hg/core/HgStatus.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgStatus.java	Fri Mar 23 22:51:18 2012 +0100
@@ -20,6 +20,7 @@
 import java.util.Date;
 
 import org.tmatesoft.hg.internal.ChangelogHelper;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.util.Path;
 
--- a/src/org/tmatesoft/hg/core/HgStatusCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgStatusCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -22,13 +22,14 @@
 
 import java.io.IOException;
 import java.util.ConcurrentModificationException;
-import java.util.concurrent.CancellationException;
 
 import org.tmatesoft.hg.internal.ChangelogHelper;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.repo.HgStatusInspector;
 import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.Status;
 
@@ -161,11 +162,13 @@
 	 * Perform status operation according to parameters set.
 	 *  
 	 * @param statusHandler callback to get status information
-	 * @throws IOException if there are (further unspecified) errors while walking working copy
+	 * @throws HgCallbackTargetException wrapper for any exception user callback code may produce
+	 * @throws CancelledException if execution of the command was cancelled
+	 * @throws IOException FIXME EXCEPTIONS WTF it's doing here if there are (further unspecified) errors while walking working copy
 	 * @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(HgStatusHandler statusHandler) throws CancellationException, HgException, IOException {
+	public void execute(HgStatusHandler statusHandler) throws HgCallbackTargetException, CancelledException, HgException, IOException {
 		if (statusHandler == null) {
 			throw new IllegalArgumentException();
 		}
@@ -177,7 +180,7 @@
 		try {
 			// XXX if I need a rough estimation (for ProgressMonitor) of number of work units,
 			// I may use number of files in either rev1 or rev2 manifest edition
-			mediator.start(statusHandler, new ChangelogHelper(repo, startRevision));
+			mediator.start(statusHandler, getCancelSupport(statusHandler, true), new ChangelogHelper(repo, startRevision));
 			if (endRevision == WORKING_COPY) {
 				HgWorkingCopyStatusCollector wcsc = scope != null ? HgWorkingCopyStatusCollector.create(repo, scope) : new HgWorkingCopyStatusCollector(repo);
 				wcsc.setBaseRevisionCollector(sc);
@@ -190,11 +193,10 @@
 					sc.walk(startRevision, endRevision, mediator);
 				}
 			}
-		} catch (HgCallbackTargetException.Wrap ex) { 
-			// seems too general to catch RuntimeException, i.e.
-			// unless catch is for very narrow piece of code, it's better not to catch any RTE (which may happen elsewhere, not only in handler)
-			// XXX Perhaps, need more detailed explanation in handlers that are expected to throw Wrap/RTE (i.e. HgChangesetHandler)
-			throw new HgCallbackTargetException(ex).setRevisionIndex(endRevision);
+		} catch (CancelledException ex) {
+			// this is our exception, thrown from Mediator.
+			// next check shall throw original cause of the stop - either HgCallbackTargetException or original CancelledException
+			mediator.checkFailure();
 		} finally {
 			mediator.done();
 		}
@@ -208,7 +210,7 @@
 		void handleStatus(HgStatus s);
 	}
 
-	private class Mediator implements HgStatusInspector {
+	private class Mediator implements HgStatusInspector, CancelSupport {
 		boolean needModified;
 		boolean needAdded;
 		boolean needRemoved;
@@ -219,68 +221,117 @@
 		boolean needCopies;
 		HgStatusHandler handler;
 		private ChangelogHelper logHelper;
+		private CancelSupport handlerCancelSupport;
+		private HgCallbackTargetException failure;
+		private CancelledException cancellation;
 
 		Mediator() {
 		}
 		
-		public void start(HgStatusHandler h, ChangelogHelper changelogHelper) {
+		public void start(HgStatusHandler h, CancelSupport hcs, ChangelogHelper changelogHelper) {
 			handler = h;
+			handlerCancelSupport = hcs;
 			logHelper = changelogHelper;
 		}
 
 		public void done() {
 			handler = null;
+			handlerCancelSupport = null;
 			logHelper = null;
+			failure = null;
+			cancellation = null;
 		}
 		
 		public boolean busy() {
 			return handler != null;
 		}
 
+		// XXX copy from ChangesetTransformer. Perhaps, can share the code?
+		public void checkFailure() throws HgCallbackTargetException, CancelledException {
+			// do not forget to clear exceptions for reuse of this instance 
+			if (failure != null) {
+				HgCallbackTargetException toThrow = failure;
+				failure = null; 
+				throw toThrow;
+			}
+			if (cancellation != null) {
+				CancelledException toThrow = cancellation;
+				cancellation = null;
+				throw toThrow;
+			}
+		}
+
+		// XXX copy from ChangesetTransformer. code sharing note above applies
+		public void checkCancelled() throws CancelledException {
+			if (failure != null || cancellation != null) {
+				// stop status iteration. Our exception is for the purposes of cancellation only,
+				// the one we have stored (this.cancellation) is for user
+				throw new CancelledException(); 
+			}
+		}
+
+		private void dispatch(HgStatus s) {
+			try {
+				handler.handleStatus(s);
+				handlerCancelSupport.checkCancelled();
+			} catch (HgCallbackTargetException ex) {
+				failure = ex;
+			} catch (CancelledException ex) {
+				cancellation = ex;
+			}
+		}
+
 		public void modified(Path fname) {
 			if (needModified) {
-				handler.handleStatus(new HgStatus(Modified, fname, logHelper));
+				dispatch(new HgStatus(Modified, fname, logHelper));
 			}
 		}
 		public void added(Path fname) {
 			if (needAdded) {
-				handler.handleStatus(new HgStatus(Added, fname, logHelper));
+				dispatch(new HgStatus(Added, fname, logHelper));
 			}
 		}
 		public void removed(Path fname) {
 			if (needRemoved) {
-				handler.handleStatus(new HgStatus(Removed, fname, logHelper));
+				dispatch(new HgStatus(Removed, fname, logHelper));
 			}
 		}
 		public void copied(Path fnameOrigin, Path fnameAdded) {
 			if (needCopies) {
 				// TODO post-1.0 in fact, merged files may report 'copied from' as well, correct status kind thus may differ from Added
-				handler.handleStatus(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
+				dispatch(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
 			}
 		}
 		public void missing(Path fname) {
 			if (needMissing) {
-				handler.handleStatus(new HgStatus(Missing, fname, logHelper));
+				dispatch(new HgStatus(Missing, fname, logHelper));
 			}
 		}
 		public void unknown(Path fname) {
 			if (needUnknown) {
-				handler.handleStatus(new HgStatus(Unknown, fname, logHelper));
+				dispatch(new HgStatus(Unknown, fname, logHelper));
 			}
 		}
 		public void clean(Path fname) {
 			if (needClean) {
-				handler.handleStatus(new HgStatus(Clean, fname, logHelper));
+				dispatch(new HgStatus(Clean, fname, logHelper));
 			}
 		}
 		public void ignored(Path fname) {
 			if (needIgnored) {
-				handler.handleStatus(new HgStatus(Ignored, fname, logHelper));
+				dispatch(new HgStatus(Ignored, fname, logHelper));
 			}
 		}
 		
-		public void invalid(Path fname, Exception ex) {
-			handler.handleError(fname, new Status(Status.Kind.ERROR, "Failed to get file status", ex));
+		public void invalid(Path fname, Exception err) {
+			try {
+				handler.handleError(fname, new Status(Status.Kind.ERROR, "Failed to get file status", err));
+				handlerCancelSupport.checkCancelled();
+			} catch (HgCallbackTargetException ex) {
+				failure = ex;
+			} catch (CancelledException ex) {
+				cancellation = ex;
+			}
 		}
 	}
 }
--- a/src/org/tmatesoft/hg/core/HgStatusHandler.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgStatusHandler.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -16,6 +16,7 @@
  */
 package org.tmatesoft.hg.core;
 
+import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.Status;
 
@@ -24,19 +25,21 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
+@Callback
 public interface HgStatusHandler {
 
-	/* XXX #next() as in HgChangesetHandler?
-	 * perhaps, handle() is better name? If yes, rename method in HgChangesetHandler, too, to make them similar.
+	/** #next() as in HgChangesetHandler?
+	 * FIXME perhaps, handle() is better name? If yes, rename method in HgChangesetHandler, too, to make them similar.
 	 * void next(HgStatus s);
-	 * XXX describe RTE and HgCallbackTargetException
+	 * @throws HgCallbackTargetException wrapper for any exception user code may produce
 	 */
-	void handleStatus(HgStatus s);
+	void handleStatus(HgStatus s) throws HgCallbackTargetException;
 
 	/**
 	 * Report non-critical error processing single file during status operation
 	 * @param file name of the file that caused the trouble
 	 * @param s error description object
+	 * @throws HgCallbackTargetException wrapper for any exception user code may produce
 	 */
-	void handleError(Path file, Status s);
+	void handleError(Path file, Status s) throws HgCallbackTargetException;
 }
--- a/src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -27,6 +27,7 @@
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgInvalidFileException;
 import org.tmatesoft.hg.repo.HgRepository;
 
 /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/Callback.java	Fri Mar 23 22:51:18 2012 +0100
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012 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.internal;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.tmatesoft.hg.core.HgCallbackTargetException;
+
+/**
+ * Marker to ease location of callback interfaces in the API.
+ * 
+ * All classes/interfaces supposed to be subclassed/implemented by users, with methods throwing {@link HgCallbackTargetException} shall bear the mark.
+ * Besides, classes that are low-level callbacks (from {@link org.tmatesoft.hg.repo}) shall bear it, too.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({ ElementType.TYPE })
+public @interface Callback {
+}
--- a/src/org/tmatesoft/hg/internal/ChangelogHelper.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/ChangelogHelper.java	Fri Mar 23 22:51:18 2012 +0100
@@ -16,10 +16,10 @@
  */
 package org.tmatesoft.hg.internal;
 
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Path;
 
--- a/src/org/tmatesoft/hg/internal/DigestHelper.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/DigestHelper.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2011 TMate Software Ltd
+ * Copyright (c) 2010-2012 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
@@ -21,8 +21,8 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
-import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 
 
 /**
@@ -50,7 +50,9 @@
 				sha1 = MessageDigest.getInstance("SHA-1");
 			} catch (NoSuchAlgorithmException ex) {
 				// could hardly happen, JDK from Sun always has sha1.
-				throw new HgBadStateException(ex);
+				HgInvalidStateException t = new HgInvalidStateException("Need SHA-1 algorithm for nodeid calculation");
+				t.initCause(ex);
+				throw t;
 			}
 		}
 		return sha1;
--- a/src/org/tmatesoft/hg/internal/ExceptionInfo.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/ExceptionInfo.java	Fri Mar 23 22:51:18 2012 +0100
@@ -17,6 +17,9 @@
 package org.tmatesoft.hg.internal;
 
 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+import static org.tmatesoft.hg.repo.HgRepository.WORKING_COPY;
 
 import java.io.File;
 
@@ -33,10 +36,12 @@
  */
 public class ExceptionInfo<T> {
 	protected final T owner;
-	protected int revNumber = BAD_REVISION;
+	protected Integer revNumber = null;
 	protected Nodeid revision;
 	protected Path filename;
 	protected File localFile;
+	// next two make sense only when revNumber was set
+	private int rangeLeftBoundary = BAD_REVISION, rangeRightBoundary = BAD_REVISION;
 
 	/**
 	 * @param owner instance to return from setters 
@@ -49,7 +54,7 @@
 	 * @return not {@link HgRepository#BAD_REVISION} only when revision index was supplied at the construction time
 	 */
 	public int getRevisionIndex() {
-		return revNumber;
+		return revNumber == null ? HgRepository.BAD_REVISION : revNumber;
 	}
 
 	public T setRevisionIndex(int rev) {
@@ -58,7 +63,7 @@
 	}
 	
 	public boolean isRevisionIndexSet() {
-		return revNumber != BAD_REVISION;
+		return revNumber != null;
 	}
 
 	/**
@@ -100,6 +105,13 @@
 	public File getFile() {
 		return localFile;
 	}
+	
+	public T setRevisionIndexBoundary(int revisionIndex, int rangeLeft, int rangeRight) {
+		setRevisionIndex(revisionIndex);
+		rangeLeftBoundary = rangeLeft;
+		rangeRightBoundary = rangeRight;
+		return owner;
+	}
 
 	public StringBuilder appendDetails(StringBuilder sb) {
 		if (filename != null) {
@@ -110,14 +122,34 @@
 			sb.append(' ');
 		}
 		sb.append("rev:");
-		if (revNumber != BAD_REVISION) {
-			sb.append(revNumber);
-			if (revision != null) {
-				sb.append(':');
+		boolean needNodeid = true;
+		if (isRevisionIndexSet()) {
+			if (rangeLeftBoundary != BAD_REVISION || rangeRightBoundary != BAD_REVISION) {
+				String sr;
+				switch (getRevisionIndex()) {
+				case BAD_REVISION:
+					sr = "UNKNOWN"; break;
+				case TIP:
+					sr = "TIP"; break;
+				case WORKING_COPY:
+					sr = "WORKING-COPY"; break;
+				case NO_REVISION:
+					sr = "NO REVISION"; break;
+				default:
+					sr = String.valueOf(getRevisionIndex());
+				}
+				sb.append(String.format("%s is not from [%d..%d]", sr, rangeLeftBoundary, rangeRightBoundary));
+			} else {
+				sb.append(getRevisionIndex());
+				if (isRevisionIndexSet()) {
+					sb.append(':');
+					sb.append(getRevision().shortNotation());
+					needNodeid = false;
+				}
 			}
 		}
-		if (revision != null) {
-			sb.append(revision.shortNotation());
+		if (isRevisionSet() && needNodeid) {
+			sb.append(getRevision().shortNotation());
 		}
 		if (localFile != null) {
 			sb.append(';');
--- a/src/org/tmatesoft/hg/internal/InflaterDataAccess.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/InflaterDataAccess.java	Fri Mar 23 22:51:18 2012 +0100
@@ -22,9 +22,6 @@
 import java.util.zip.Inflater;
 import java.util.zip.ZipException;
 
-import org.tmatesoft.hg.core.HgBadStateException;
-
-
 /**
  * DataAccess counterpart for InflaterInputStream.
  * XXX is it really needed to be subclass of FilterDataAccess? 
@@ -99,7 +96,7 @@
 					c += inflater.inflate(dummy, 0, dummy.length);
 				}
 			} catch (DataFormatException ex) {
-				throw new HgBadStateException(ex);
+				throw new IOException(ex);
 			}
 		}
 		decompressedLength = c + oldPos;
--- a/src/org/tmatesoft/hg/internal/KeywordFilter.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/KeywordFilter.java	Fri Mar 23 22:51:18 2012 +0100
@@ -21,12 +21,11 @@
 import java.util.Date;
 import java.util.TreeMap;
 
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.util.Pair;
 import org.tmatesoft.hg.util.Path;
 
@@ -263,7 +262,7 @@
 			// when accessing changelog, see below, #getChangeset
 			int csetRev = repo.getFileNode(path).getChangesetRevisionIndex(HgRepository.TIP);
 			return repo.getChangelog().getRevision(csetRev).shortNotation();
-		} catch (HgException ex) {
+		} catch (HgRuntimeException ex) {
 			HgInternals.getContext(repo).getLog().error(getClass(), ex, null);
 			return Nodeid.NULL.shortNotation(); // XXX perhaps, might return anything better? Not sure how hg approaches this. 
 		}
@@ -272,7 +271,7 @@
 	private String username() {
 		try {
 			return getChangeset().user();
-		} catch (HgException ex) {
+		} catch (HgRuntimeException ex) {
 			HgInternals.getContext(repo).getLog().error(getClass(), ex, null);
 			return "";
 		}
@@ -282,14 +281,14 @@
 		Date d;
 		try {
 			d = getChangeset().date();
-		} catch (HgException ex) {
+		} catch (HgRuntimeException ex) {
 			HgInternals.getContext(repo).getLog().error(getClass(), ex, null);
 			d = new Date(0l);
 		}
 		return String.format("%tY/%<tm/%<td %<tH:%<tM:%<tS", d);
 	}
 	
-	private RawChangeset getChangeset() throws HgInvalidControlFileException {
+	private RawChangeset getChangeset() throws HgRuntimeException {
 		if (latestFileCset == null) {
 			// TODO post-1.0 Use of TIP is likely incorrect in cases when working copy is not based
 			// on latest revision. Perhaps, a constant like HgRepository.DIRSTATE_PARENT may come handy
@@ -315,7 +314,7 @@
 				}
 			}
 			matcher = new PathGlobMatcher(patterns.toArray(new String[patterns.size()]));
-			// TODO read and respect keyword patterns from [keywordmaps]
+			// TODO post-1.0 read and respect keyword patterns from [keywordmaps]
 		}
 
 		public Filter create(Path path, Options opts) {
--- a/src/org/tmatesoft/hg/internal/ManifestRevision.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/ManifestRevision.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -19,7 +19,6 @@
 import java.util.Collection;
 import java.util.TreeMap;
 
-import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgManifest;
 import org.tmatesoft.hg.util.Path;
@@ -75,7 +74,7 @@
 	//
 
 	public boolean next(Nodeid nid, String fname, String flags) {
-		throw new HgBadStateException(HgManifest.Inspector2.class.getName());
+		throw new IllegalStateException(HgManifest.Inspector2.class.getName());
 	}
 
 	public boolean next(Nodeid nid, Path fname, HgManifest.Flags flags) {
--- a/src/org/tmatesoft/hg/internal/NewlineFilter.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/NewlineFilter.java	Fri Mar 23 22:51:18 2012 +0100
@@ -28,8 +28,8 @@
 import java.util.ArrayList;
 import java.util.Map;
 
-import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Adaptable;
 import org.tmatesoft.hg.util.Path;
@@ -67,7 +67,7 @@
 
 	public ByteBuffer filter(ByteBuffer src) {
 		if (!processInconsistent && !previewDone) {
-			throw new HgBadStateException("This filter requires preview operation prior to actual filtering when eol.only-consistent is true");
+			throw new HgInvalidStateException("This filter requires preview operation prior to actual filtering when eol.only-consistent is true");
 		}
 		if (!processInconsistent && foundLoneLF && foundCRLF) {
 			// do not process inconsistent newlines
@@ -270,7 +270,9 @@
 		for (int i = max(pos-10, 0), x = min(pos + 10, b.limit()); i < x; i++) {
 			sb.append(String.format("%02x ", b.get(i)));
 		}
-		throw new HgBadStateException(String.format("Inconsistent newline characters in the stream %s (char 0x%x, local index:%d)", sb.toString(), b.get(pos), pos));
+		// TODO post-1.0 need HgBadDataException (not InvalidState but smth closer to data stream error)
+		// but don't want to add class for the single use now
+		throw new HgInvalidStateException(String.format("Inconsistent newline characters in the stream %s (char 0x%x, local index:%d)", sb.toString(), b.get(pos), pos));
 	}
 
 	private static int indexOf(byte ch, ByteBuffer b, int from) {
--- a/src/org/tmatesoft/hg/internal/ProcessExecHelper.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/ProcessExecHelper.java	Fri Mar 23 22:51:18 2012 +0100
@@ -30,7 +30,7 @@
  * @see http://developers.sun.com/solaris/articles/subprocess/subprocess.html
  * 
  * @author Artem Tikhomirov
- * @author Tmate Software Ltd.
+ * @author TMate Software Ltd.
  */
 public class ProcessExecHelper {
 	private File dir;
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Fri Mar 23 22:51:18 2012 +0100
@@ -19,6 +19,7 @@
 import static org.tmatesoft.hg.core.Nodeid.NULL;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -29,11 +30,11 @@
 import java.util.Map.Entry;
 import java.util.Set;
 
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
 import org.tmatesoft.hg.core.HgRemoteConnectionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
 import org.tmatesoft.hg.repo.HgRemoteRepository.Range;
 import org.tmatesoft.hg.repo.HgRemoteRepository.RemoteBranch;
@@ -65,7 +66,7 @@
 		// sanity check
 		for (Nodeid n : common) {
 			if (!localRepo.knownNode(n)) {
-				throw new HgBadStateException("Unknown node reported as common:" + n);
+				throw new HgInvalidStateException("Unknown node reported as common:" + n);
 			}
 		}
 		progressSupport.done();
@@ -74,7 +75,7 @@
 	
 	public List<Nodeid> getCommon() {
 		if (common == null) {
-			throw new HgBadStateException("Call #compare(Object) first");
+			throw new HgInvalidStateException("Call #compare(Object) first");
 		}
 		return common;
 	}
@@ -120,7 +121,7 @@
 			return;
 		}
 		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()));
+			throw new HgInvalidStateException(String.format("Invalid index of common known revision: %d in total of %d", earliestRevision, 1+changelog.getLastRevision()));
 		}
 		changelog.range(earliestRevision+1, changelog.getLastRevision(), inspector);
 	}
@@ -317,7 +318,7 @@
 				}
 			} while(--watchdog > 0);
 			if (watchdog == 0) {
-				throw new HgBadStateException(String.format("Can't narrow down branch [%s, %s]", rb.head.shortNotation(), rb.root.shortNotation()));
+				throw new HgInvalidStateException(String.format("Can't narrow down branch [%s, %s]", rb.head.shortNotation(), rb.root.shortNotation()));
 			}
 		}
 		if (debug) {
@@ -486,15 +487,16 @@
 			toQuery.clear();
 		}
 		if (rootIndex == -1) {
-			throw new HgBadStateException("Shall not happen, provided between output is correct"); // FIXME EXCEPTIONS
+			throw new HgInvalidStateException("Shall not happen, provided between output is correct"); // FIXME EXCEPTIONS
 		}
 		result[rootIndex] = branchRoot;
 		boolean resultOk = true;
 		LinkedList<Nodeid> fromRootToHead = new LinkedList<Nodeid>();
+		IntVector missing = new IntVector(); 
 		for (int i = 0; i <= rootIndex; i++) {
 			Nodeid n = result[i];
 			if (n == null) {
-				System.out.printf("ERROR: element %d wasn't found\n",i);
+				missing.add(i);
 				resultOk = false;
 			}
 			fromRootToHead.addFirst(n); // reverse order
@@ -503,7 +505,9 @@
 			System.out.println("Total queries:" + totalQueries);
 		}
 		if (!resultOk) {
-			throw new HgBadStateException("See console for details"); // FIXME EXCEPTIONS
+			assert missing.size() > 0;
+			// TODO post-1.0 perhaps, there's better alternative than HgInvalidStateException, e.g. HgDataFormatException?
+			throw new HgInvalidStateException(String.format("Missing elements with indexes: %s", Arrays.toString(missing.toArray())));
 		}
 		return fromRootToHead;
 	}
--- a/src/org/tmatesoft/hg/internal/RevlogStream.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/RevlogStream.java	Fri Mar 23 22:51:18 2012 +0100
@@ -23,12 +23,11 @@
 import java.io.IOException;
 import java.util.zip.Inflater;
 
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidRevisionException;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgRepository;
 
 
@@ -206,7 +205,7 @@
 
 	// should be possible to use TIP, ALL, or -1, -2, -n notation of Hg
 	// ? boolean needsNodeid
-	public void iterate(int start, int end, boolean needData, Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException /*REVISIT - too general exception*/ {
+	public void iterate(int start, int end, boolean needData, Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		initOutline();
 		final int indexSize = revisionCount();
 		if (indexSize == 0) {
@@ -229,8 +228,6 @@
 			throw new HgInvalidControlFileException(String.format("Failed reading [%d..%d]", start, end), ex, indexFile);
 		} catch (HgInvalidControlFileException ex) {
 			throw ex;
-		} catch (HgException ex) {
-			throw new HgInvalidControlFileException(String.format("Failed reading [%d..%d]", start, end), ex, indexFile);
 		} finally {
 			r.finish();
 		}
@@ -277,10 +274,8 @@
 			final int c = sortedRevisions.length;
 			throw new HgInvalidControlFileException(String.format("Failed reading %d revisions in [%d; %d]",c, sortedRevisions[0], sortedRevisions[c-1]), ex, indexFile);
 		} catch (HgInvalidControlFileException ex) {
+			// TODO post-1.0 fill HgRuntimeException with appropriate file (either index or data, depending on error source)
 			throw ex;
-		} catch (HgException ex) {
-			final int c = sortedRevisions.length;
-			throw new HgInvalidControlFileException(String.format("Failed reading %d revisions in [%d; %d]",c, sortedRevisions[0], sortedRevisions[c-1]), ex, indexFile);
 		} finally {
 			r.finish();
 		}
@@ -351,7 +346,7 @@
 					if (o != offset) {
 						// just in case, can't happen, ever, unless HG (or some other bad tool) produces index file 
 						// with inlined data of size greater than 2 Gb.
-						throw new HgBadStateException("Data too big, offset didn't fit to sizeof(int)");
+						throw new HgInvalidStateException("Data too big, offset didn't fit to sizeof(int)");
 					}
 					resOffsets.add(o + REVLOGV1_RECORD_SIZE * resOffsets.size());
 					da.skip(3*4 + 32 + compressedLen); // Check: 44 (skip) + 20 (read) = 64 (total RevlogNG record size)
@@ -372,9 +367,7 @@
 				}
 			}
 		} catch (IOException ex) {
-			ex.printStackTrace(); // FIXME, log error is not enough
-			// too bad, no outline then, but don't fail with NPE
-			baseRevisions = new int[0];
+			throw new HgInvalidControlFileException("Failed to analyze revlog index", ex, indexFile);
 		} finally {
 			da.done();
 		}
@@ -428,7 +421,7 @@
 //			System.out.printf("applyTime:%d ms, inspectorTime: %d ms\n", applyTime, inspectorTime); // TIMING
 		}
 
-		public boolean range(int start, int end) throws IOException, HgException {
+		public boolean range(int start, int end) throws IOException {
 			byte[] nodeidBuf = new byte[20];
 			int i;
 			// it (i.e. replace with i >= start)
@@ -565,6 +558,6 @@
 		// XXX boolean retVal to indicate whether to continue?
 		// TODO specify nodeid and data length, and reuse policy (i.e. if revlog stream doesn't reuse nodeid[] for each call)
 		// implementers shall not invoke DataAccess.done(), it's accomplished by #iterate at appropraite moment
-		void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data) throws HgException;
+		void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data);
 	}
 }
--- a/src/org/tmatesoft/hg/internal/SubrepoManager.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/SubrepoManager.java	Fri Mar 23 22:51:18 2012 +0100
@@ -27,7 +27,7 @@
 import java.util.List;
 import java.util.Map;
 
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgSubrepoLocation;
 
--- a/src/org/tmatesoft/hg/internal/WinToNixPathRewrite.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/WinToNixPathRewrite.java	Fri Mar 23 22:51:18 2012 +0100
@@ -22,7 +22,7 @@
  * Translate windows path separators to Unix/POSIX-style
  * 
  * @author Artem Tikhomirov
- * @author Tmate Software Ltd.
+ * @author TMate Software Ltd.
  */
 public final class WinToNixPathRewrite implements PathRewrite {
 	public CharSequence rewrite(CharSequence p) {
--- a/src/org/tmatesoft/hg/repo/HgBranches.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgBranches.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -34,9 +34,6 @@
 import java.util.TreeMap;
 import java.util.regex.Pattern;
 
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
@@ -240,10 +237,10 @@
 	 * Writes down information about repository branches in a format Mercurial native client can understand.
 	 * Cache file gets overwritten only if it is out of date (i.e. misses some branch information)
 	 * @throws IOException if write to cache file failed
-	 * @throws HgException subclass of {@link HgException} in case of repository access issue
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
 	@Experimental(reason="Usage of cache isn't supposed to be public knowledge")
-	public void writeCache() throws IOException, HgException {
+	public void writeCache() throws IOException, HgRuntimeException {
 		if (isCacheActual) {
 			return;
 		}
--- a/src/org/tmatesoft/hg/repo/HgBundle.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgBundle.java	Fri Mar 23 22:51:18 2012 +0100
@@ -19,9 +19,7 @@
 import java.io.File;
 import java.io.IOException;
 
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgCallbackTargetException;
-import org.tmatesoft.hg.core.HgInvalidFileException;
+import org.tmatesoft.hg.core.HgBadArgumentException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
@@ -36,6 +34,8 @@
 import org.tmatesoft.hg.util.CancelledException;
 
 /**
+ * WORK IN PROGRESS
+ * 
  * @see http://mercurial.selenic.com/wiki/BundleFormat
  * 
  * @author Artem Tikhomirov
@@ -66,7 +66,7 @@
 					throw HgRepository.notImplemented();
 				}
 				if (signature[4] != 'U' || signature[5] != 'N') {
-					throw new HgBadStateException("Bad bundle signature:" + new String(signature));
+					throw new HgInvalidStateException(String.format("Bad bundle signature: %s",  String.valueOf(signature)));
 				}
 				// "...UN", fall-through
 			} else {
@@ -96,7 +96,7 @@
 	 * @param hgRepo repository that shall possess base revision for this bundle
 	 * @param inspector callback to get each changeset found 
 	 */
-	public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgCallbackTargetException, HgInvalidFileException {
+	public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgRuntimeException {
 		Inspector bundleInsp = new Inspector() {
 			DigestHelper dh = new DigestHelper();
 			boolean emptyChangelog = true;
@@ -159,7 +159,7 @@
 					byte[] csetContent = ge.apply(prevRevContent);
 					dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid?
 					if (!ge.node().equalsTo(dh.asBinary())) {
-						throw new IllegalStateException("Integrity check failed on " + bundleFile + ", node:" + ge.node());
+						throw new HgInvalidStateException(String.format("Integrity check failed on %s, node: %s", bundleFile, ge.node().shortNotation()));
 					}
 					ByteArrayDataAccess csetDataAccess = new ByteArrayDataAccess(csetContent);
 					RawChangeset cs = RawChangeset.parse(csetDataAccess);
@@ -168,8 +168,10 @@
 					prevRevContent = csetDataAccess.reset();
 				} catch (CancelledException ex) {
 					return false;
-				} catch (Exception ex) {
-					throw new HgBadStateException(ex); // FIXME EXCEPTIONS
+				} catch (IOException ex) {
+					throw new HgInvalidFileException("Invalid bundle file", ex, bundleFile); // TODO post-1.0 revisit exception handling
+				} catch (HgBadArgumentException ex) {
+					throw new HgInvalidControlFileException("Invalid bundle file", ex, bundleFile);
 				}
 				return true;
 			}
@@ -180,11 +182,7 @@
 			public void fileEnd(String name) {}
 
 		};
-		try {
-			inspectChangelog(bundleInsp);
-		} catch (RuntimeException ex) {
-			throw new HgCallbackTargetException(ex);
-		}
+		inspectChangelog(bundleInsp);
 	}
 
 	// callback to minimize amount of Strings and Nodeids instantiated
@@ -209,7 +207,12 @@
 		boolean element(GroupElement element);
 	}
 
-	public void inspectChangelog(Inspector inspector) throws HgInvalidFileException {
+	/**
+	 * @param inspector callback to visit changelog entries
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 * @throws IllegalArgumentException if inspector argument is null
+	 */
+	public void inspectChangelog(Inspector inspector) throws HgRuntimeException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
@@ -226,7 +229,12 @@
 		}
 	}
 
-	public void inspectManifest(Inspector inspector) throws HgInvalidFileException {
+	/**
+	 * @param inspector callback to visit manifest entries
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 * @throws IllegalArgumentException if inspector argument is null
+	 */
+	public void inspectManifest(Inspector inspector) throws HgRuntimeException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
@@ -247,7 +255,12 @@
 		}
 	}
 
-	public void inspectFiles(Inspector inspector) throws HgInvalidFileException {
+	/**
+	 * @param inspector callback to visit file entries
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 * @throws IllegalArgumentException if inspector argument is null
+	 */
+	public void inspectFiles(Inspector inspector) throws HgRuntimeException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
@@ -272,7 +285,12 @@
 		}
 	}
 
-	public void inspectAll(Inspector inspector) throws HgInvalidFileException {
+	/**
+	 * @param inspector visit complete bundle (changelog, manifest and file entries)
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 * @throws IllegalArgumentException if inspector argument is null
+	 */
+	public void inspectAll(Inspector inspector) throws HgRuntimeException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
--- a/src/org/tmatesoft/hg/repo/HgChangelog.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgChangelog.java	Fri Mar 23 22:51:18 2012 +0100
@@ -32,10 +32,8 @@
 import java.util.TimeZone;
 
 import org.tmatesoft.hg.core.HgBadArgumentException;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.internal.DataAccess;
 import org.tmatesoft.hg.internal.IterateControlMediator;
 import org.tmatesoft.hg.internal.Lifecycle;
@@ -107,10 +105,17 @@
 		return range(x, x).get(0);
 	}
 
+	@Callback
 	public interface Inspector {
-		// TODO describe whether cset is new instance each time
-		// describe what revisionNumber is when Inspector is used with HgBundle (BAD_REVISION or bundle's local order?) 
-		void next(int revisionNumber, Nodeid nodeid, RawChangeset cset);
+		/**
+		 * Access next changeset
+		 * TODO describe what revisionNumber is when Inspector is used with HgBundle (BAD_REVISION or bundle's local order?)
+		 * 
+		 * @param revisionIndex index of revision being inspected, local to the inspected object 
+		 * @param nodeid revision being inspected
+		 * @param cset changeset raw data
+		 */
+		void next(int revisionIndex, Nodeid nodeid, RawChangeset cset);
 	}
 
 	/**
@@ -386,15 +391,19 @@
 			progressHelper = ProgressSupport.Factory.get(delegate);
 		}
 
-		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) throws HgException {
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
 			try {
 				byte[] data = da.byteArray();
 				cset.init(data, 0, data.length, usersPool);
 				// XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse
 				inspector.next(revisionNumber, Nodeid.fromBinary(nodeid, 0), cset);
 				progressHelper.worked(1);
+			} catch (HgBadArgumentException ex) {
+				// see below about better exception
+				throw new HgInvalidControlFileException("Failed reading changelog", ex, null).setRevisionIndex(revisionNumber);  
 			} catch (IOException ex) {
-				throw new HgException(ex); // XXX need better exception, perhaps smth like HgChangelogException (extends HgInvalidControlFileException) 
+				// XXX need better exception, perhaps smth like HgChangelogException (extends HgInvalidControlFileException)
+				throw new HgInvalidControlFileException("Failed reading changelog", ex, null).setRevisionIndex(revisionNumber);  
 			}
 			if (iterateControl != null) {
 				iterateControl.checkCancelled();
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Fri Mar 23 22:51:18 2012 +0100
@@ -32,9 +32,6 @@
 import java.util.List;
 
 import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.HgLogCommand;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.DataAccess;
@@ -714,7 +711,7 @@
 			setCancelSupport(CancelSupport.Factory.get(chain));
 		}
 
-		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgException {
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
 			try {
 				final int daLength = data.length();
 				if (daLength < 4 || data.readByte() != 1 || data.readByte() != 10) {
@@ -736,6 +733,7 @@
 			} catch (IOException ex) {
 				recordFailure(ex);
 			} catch (HgInvalidControlFileException ex) {
+				// TODO RevlogStream, where this RevlogStream.Inspector goes, shall set File (as it's the only one having access to it)
 				recordFailure(ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionNumber));
 			}
 		}
--- a/src/org/tmatesoft/hg/repo/HgDirstate.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgDirstate.java	Fri Mar 23 22:51:18 2012 +0100
@@ -29,7 +29,6 @@
 import java.util.Map;
 import java.util.TreeSet;
 
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.DataAccess;
 import org.tmatesoft.hg.internal.EncodingHelper;
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgInternals.java	Fri Mar 23 22:51:18 2012 +0100
@@ -25,8 +25,6 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.Internals;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgInvalidControlFileException.java	Fri Mar 23 22:51:18 2012 +0100
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2011-2012 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.repo;
+
+import java.io.File;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Subclass of {@link HgInvalidFileException} to indicate failure to deal with one of <b>Mercurial</b> control files 
+ * (most likely those under .hg/, but also those residing in the repository, with special meaning to the Mercurial, like .hgtags or .hgignore)
+ * 
+ * XXX Perhaps, HgInvalidRevlogException? and parent HgInvalidRepositoryFileException?
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+@Experimental(reason="WORK IN PROGRESS. Name is likely to change")
+public class HgInvalidControlFileException extends HgInvalidFileException {
+
+	public HgInvalidControlFileException(String message, Throwable th, File file) {
+		super(message, th, file);
+	}
+
+	@Override
+	public HgInvalidControlFileException setFile(File file) {
+		super.setFile(file);
+		return this;
+	}
+	
+	@Override
+	public HgInvalidControlFileException setRevision(Nodeid r) {
+		return (HgInvalidControlFileException) super.setRevision(r);
+	}
+
+	@Override
+	public HgInvalidControlFileException setRevisionIndex(int rev) {
+		return (HgInvalidControlFileException) super.setRevisionIndex(rev);
+	}
+	
+	@Override
+	public HgInvalidControlFileException setFileName(Path name) {
+		return (HgInvalidControlFileException) super.setFileName(name);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgInvalidFileException.java	Fri Mar 23 22:51:18 2012 +0100
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2011-2012 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.repo;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Thrown when there are troubles working with local file. Most likely (but not necessarily) wraps IOException. Might be 
+ * perceived as specialized IOException with optional File and other repository information.
+ * 
+ * <b>Hg4J</b> tries to minimize chances for IOException to occur (i.e. {@link File#canRead()} is checked before attempt to 
+ * read a file that might not exist, and doesn't use this exception to wrap each and any {@link IOException} source (e.g. 
+ * <code>#close()</code> calls are unlikely to yield it), hence it is likely to address real cases when I/O error occurs.
+ * 
+ * On the other hand, when a file is supposed to exist and be readable, this exception might get thrown as well to indicate
+ * that's not true. 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgInvalidFileException extends HgRuntimeException {
+	// IMPLEMENTATION NOTE: Once needed, there might be intermediate e.g. HgDataStreamException 
+	// (between HgInvalidFileException and HgRuntimeException) to root data access exceptions
+	// that do not originate from local files but e.g. a connection
+
+	public HgInvalidFileException(String message, Throwable th) {
+		super(message, th);
+	}
+
+	public HgInvalidFileException(String message, Throwable th, File file) {
+		super(message, th);
+		details.setFile(file); // allows null
+	}
+
+	public HgInvalidFileException setFile(File file) {
+		assert file != null; // doesn't allow null not to clear file accidentally
+		details.setFile(file);
+		return this;
+	}
+
+	/**
+	 * @return file object that causes troubles, or <code>null</code> if specific file is unknown
+	 */
+	public File getFile() {
+		return details.getFile();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgInvalidRevisionException.java	Fri Mar 23 22:51:18 2012 +0100
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2011-2012 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.repo;
+
+import org.tmatesoft.hg.core.Nodeid;
+
+/**
+ * Use of revision or revision local index that is not valid for a given revlog.
+ *  
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgInvalidRevisionException extends HgRuntimeException {
+
+	/**
+	 * 
+	 * This exception is not expected to be initialized with another exception, although those who need to, 
+	 * may still use {@link #initCause(Throwable)}
+	 * 
+	 * @param message optional description of the issue
+	 * @param revision invalid revision, may be  <code>null</code> if revisionIndex is used
+	 * @param revisionIndex invalid revision index, may be <code>null</code> if not known and revision is supplied 
+	 */
+	public HgInvalidRevisionException(String message, Nodeid revision, Integer revisionIndex) {
+		super(message, null);
+		assert revision != null || revisionIndex != null;
+		if (revision != null) {
+			setRevision(revision);
+		}
+		if (revisionIndex != null) {
+			setRevisionIndex(revisionIndex);
+		}
+	}
+
+	public HgInvalidRevisionException(Nodeid revision) {
+		this(null, revision, null);
+	}
+	
+	public HgInvalidRevisionException(int revisionIndex) {
+		this(null, null, revisionIndex);
+	}
+
+	public HgInvalidRevisionException setRevisionIndex(int revisionIndex, int rangeLeft, int rangeRight) {
+		details.setRevisionIndexBoundary(revisionIndex, rangeLeft, rangeRight);
+		return this;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgInvalidStateException.java	Fri Mar 23 22:51:18 2012 +0100
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012 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.repo;
+
+
+/**
+ * Thrown to indicate unexpected or otherwise inappropriate state of the library, assumptions/preconditions not met, etc.
+ * Unlike {@link HgInvalidFileException} and {@link HgInvalidControlFileException}, to describe error state not related to IO operations.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgInvalidStateException extends HgRuntimeException {
+
+	public HgInvalidStateException(String message) {
+		super(message, null);
+		// no cons with Throwable as it deemed exceptional to use HgInvalidStateException to wrap another exception
+	}
+}
--- a/src/org/tmatesoft/hg/repo/HgLookup.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgLookup.java	Fri Mar 23 22:51:18 2012 +0100
@@ -22,7 +22,7 @@
 import java.net.URL;
 
 import org.tmatesoft.hg.core.HgBadArgumentException;
-import org.tmatesoft.hg.core.HgInvalidFileException;
+import org.tmatesoft.hg.core.HgRepositoryNotFoundException;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.BasicSessionContext;
 import org.tmatesoft.hg.internal.ConfigFile;
@@ -46,40 +46,41 @@
 		sessionContext = ctx;
 	}
 
-	public HgRepository detectFromWorkingDir() throws HgInvalidFileException {
+	public HgRepository detectFromWorkingDir() throws HgRepositoryNotFoundException {
 		return detect(System.getProperty("user.dir"));
 	}
 
-	public HgRepository detect(String location) throws HgInvalidFileException {
+	public HgRepository detect(String location) throws HgRepositoryNotFoundException {
 		return detect(new File(location));
 	}
 
 	// look up in specified location and above
-	public HgRepository detect(File location) throws HgInvalidFileException {
-		File dir = location.getAbsoluteFile();
-		File repository;
-		do {
-			repository = new File(dir, ".hg");
-			if (repository.exists() && repository.isDirectory()) {
-				break;
+	public HgRepository detect(File location) throws HgRepositoryNotFoundException {
+		try {
+			File dir = location.getAbsoluteFile();
+			File repository;
+			do {
+				repository = new File(dir, ".hg");
+				if (repository.exists() && repository.isDirectory()) {
+					break;
+				}
+				repository = null;
+				dir = dir.getParentFile();
+				
+			} while(dir != null);
+			if (repository == null) {
+				throw new HgRepositoryNotFoundException(String.format("Can't locate .hg/ directory of Mercurial repository in %s nor in parent dirs", location)).setLocation(location.getPath());
 			}
-			repository = null;
-			dir = dir.getParentFile();
-			
-		} while(dir != null);
-		if (repository == null) {
-			// return invalid repository
-			return new HgRepository(location.getPath());
-		}
-		try {
 			String repoPath = repository.getParentFile().getCanonicalPath();
 			return new HgRepository(getContext(), repoPath, repository);
 		} catch (IOException ex) {
-			throw new HgInvalidFileException(location.toString(), ex, location);
+			HgRepositoryNotFoundException t = new HgRepositoryNotFoundException("Failed to access repository");
+			t.setLocation(location.getPath()).initCause(ex);
+			throw t;
 		}
 	}
 	
-	public HgBundle loadBundle(File location) throws HgInvalidFileException {
+	public HgBundle loadBundle(File location) throws HgRuntimeException/*FIXME need checked exception for can't find*/ {
 		if (location == null || !location.canRead()) {
 			throw new HgInvalidFileException(String.format("Can't read file %s", location == null ? null : location.getPath()), null, location);
 		}
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Fri Mar 23 22:51:18 2012 +0100
@@ -17,8 +17,7 @@
 package org.tmatesoft.hg.repo;
 
 import static org.tmatesoft.hg.core.Nodeid.NULL;
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
+import static org.tmatesoft.hg.repo.HgRepository.*;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -27,12 +26,9 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgChangesetFileSneaker;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.Callback;
 import org.tmatesoft.hg.internal.DataAccess;
 import org.tmatesoft.hg.internal.DigestHelper;
 import org.tmatesoft.hg.internal.EncodingHelper;
@@ -137,8 +133,10 @@
 	 * incrementally, nor it mandates presence of manifest version for a changeset. Thus, there might be changesets that record {@link Nodeid#NULL}
 	 * as corresponding manifest revision. This situation is deemed exceptional now and what would <code>inspector</code> get depends on whether 
 	 * <code>start</code> or <code>end</code> arguments point to such changeset, or such changeset happen to be somewhere inside the range 
-	 * <code>[start..end]</code>. Implementation does it best to report empty manifests (<code>Inspector.begin(BAD_REVISION, NULL, csetRevIndex);</code>
-	 * followed immediately by <code>Inspector.end(BAD_REVISION)</code> when <code>start</code> and/or <code>end</code> point to changeset with no associated 
+	 * <code>[start..end]</code>. Implementation does it best to report empty manifests 
+	 * (<code>Inspector.begin(HgRepository.NO_REVISION, NULL, csetRevIndex);</code>
+	 * followed immediately by <code>Inspector.end(HgRepository.NO_REVISION)</code> 
+	 * when <code>start</code> and/or <code>end</code> point to changeset with no associated 
 	 * manifest revision. However, if changeset-manifest revision pairs look like:
 	 * <pre>
 	 *   3  8
@@ -148,14 +146,14 @@
 	 * call <code>walk(3,5, insp)</code> would yield only (3,8) and (5,9) to the inspector, without additional empty 
 	 * <code>Inspector.begin(); Inspector.end()</code> call pair.   
 	 * 
+	 * @see HgRepository#NO_REVISION
 	 * @param start changelog (not manifest!) revision to begin with
 	 * @param end changelog (not manifest!) revision to stop, inclusive.
 	 * @param inspector manifest revision visitor, can't be <code>null</code>
-	 * @throws HgInvalidRevisionException if start or end specify non-existent revision index
-	 * @throws IllegalArgumentException if start or end is not a revision index
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 * @throws IllegalArgumentException if inspector callback is <code>null</code>
 	 */
-	public void walk(int start, int end, final Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public void walk(int start, int end, final Inspector inspector) throws HgRuntimeException, IllegalArgumentException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
@@ -164,8 +162,8 @@
 		do {
 			manifestFirst = fromChangelog(csetFirst+i);
 			if (manifestFirst == BAD_REVISION) {
-				inspector.begin(BAD_REVISION, NULL, csetFirst+i);
-				inspector.end(BAD_REVISION);
+				inspector.begin(NO_REVISION, NULL, csetFirst+i);
+				inspector.end(NO_REVISION);
 			}
 			i++;
 		} while (manifestFirst == BAD_REVISION && csetFirst+i <= csetLast);
@@ -179,15 +177,15 @@
 		do {
 			manifestLast = fromChangelog(csetLast-i);
 			if (manifestLast == BAD_REVISION) {
-				inspector.begin(BAD_REVISION, NULL, csetLast-i);
-				inspector.end(BAD_REVISION);
+				inspector.begin(NO_REVISION, NULL, csetLast-i);
+				inspector.end(NO_REVISION);
 			}
 			i++;
 		} while (manifestLast == BAD_REVISION && csetLast-i >= csetFirst);
 		if (manifestLast == BAD_REVISION) {
-			// hmm, manifestFirst != -1 here, hence there's i from [csetFirst..csetLast] for which manifest entry exists, 
-			// and thus it's impossible to run into manifestLast == -1. Nevertheless, never hurts to check.
-			throw new HgBadStateException(String.format("Manifest %d-%d(!) for cset range [%d..%d] ", manifestFirst, manifestLast, csetFirst, csetLast));
+			// hmm, manifestFirst != BAD_REVISION here, hence there's i from [csetFirst..csetLast] for which manifest entry exists, 
+			// and thus it's impossible to run into manifestLast == BAD_REVISION. Nevertheless, never hurts to check.
+			throw new HgInvalidStateException(String.format("Manifest %d-%d(!) for cset range [%d..%d] ", manifestFirst, manifestLast, csetFirst, csetLast));
 		}
 		if (manifestLast < manifestFirst) {
 			// there are tool-constructed repositories that got order of changeset revisions completely different from that of manifest
@@ -207,10 +205,10 @@
 	 * 
 	 * @param inspector manifest revision visitor, can't be <code>null</code>
 	 * @param revisionIndexes local indexes of changesets to visit, non-<code>null</code>
-	 * @throws HgInvalidRevisionException if argument specifies non-existent revision index
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 * @throws InvalidArgumentException if supplied arguments are <code>null</code>s
 	 */
-	public void walk(final Inspector inspector, int... revisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public void walk(final Inspector inspector, int... revisionIndexes) throws HgRuntimeException, IllegalArgumentException {
 		if (inspector == null || revisionIndexes == null) {
 			throw new IllegalArgumentException();
 		}
@@ -310,8 +308,8 @@
 			final int manifestRevisionIndex = fromChangelog(changelogRevisionIndexes[i]);
 			if (manifestRevisionIndex == BAD_REVISION) {
 				if (inspector != null) {
-					inspector.begin(BAD_REVISION, NULL, changelogRevisionIndexes[i]);
-					inspector.end(BAD_REVISION);
+					inspector.begin(NO_REVISION, NULL, changelogRevisionIndexes[i]);
+					inspector.end(NO_REVISION);
 				}
 				// othrwise, ignore changeset without manifest
 			} else {
@@ -335,6 +333,7 @@
 		}
 	}
 
+	@Callback
 	public interface Inspector {
 		boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision);
 		/**
@@ -346,6 +345,7 @@
 	}
 	
 	@Experimental(reason="Explore Path alternative for filenames and enum for flags")
+	@Callback
 	public interface Inspector2 extends Inspector {
 		/**
 		 * @param nid file revision
@@ -448,7 +448,7 @@
 			progressHelper = ProgressSupport.Factory.get(delegate);
 		}
 		
-		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) throws HgException {
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
 			try {
 				if (!inspector.begin(revisionNumber, new Nodeid(nodeid, true), linkRevision)) {
 					iterateControl.stop();
@@ -525,7 +525,7 @@
 				iterateControl.checkCancelled();
 				progressHelper.worked(1);
 			} catch (IOException ex) {
-				throw new HgException(ex);
+				throw new HgInvalidControlFileException("Failed reading manifest", ex, null).setRevisionIndex(revisionNumber);
 			}
 		}
 
@@ -613,19 +613,14 @@
 				}
 			}
 			for (int u : undefinedChangelogRevision) {
-				try {
-					Nodeid manifest = repo.getChangelog().range(u, u).get(0).manifest();
-					// TODO calculate those missing effectively (e.g. cache and sort nodeids to speed lookup
-					// right away in the #next (may refactor ParentWalker's sequential and sorted into dedicated helper and reuse here)
-					if (manifest.isNull()) {
-						repo.getContext().getLog().warn(getClass(), "Changeset %d has no associated manifest entry", u);
-						// keep -1 in the changelog2manifest map.
-					} else {
-						changelog2manifest[u] = repo.getManifest().getRevisionIndex(manifest);
-					}
-				} catch (HgInvalidControlFileException ex) {
-					// FIXME EXCEPTIONS need to propagate the error up to client  
-					repo.getContext().getLog().error(getClass(), ex, null);
+				Nodeid manifest = repo.getChangelog().range(u, u).get(0).manifest();
+				// TODO calculate those missing effectively (e.g. cache and sort nodeids to speed lookup
+				// right away in the #next (may refactor ParentWalker's sequential and sorted into dedicated helper and reuse here)
+				if (manifest.isNull()) {
+					repo.getContext().getLog().warn(getClass(), "Changeset %d has no associated manifest entry", u);
+					// keep -1 in the changelog2manifest map.
+				} else {
+					changelog2manifest[u] = repo.getManifest().getRevisionIndex(manifest);
 				}
 			}
 		}
@@ -649,7 +644,7 @@
 			filenameAsBytes = eh.toManifest(fileToLookUp.toString());
 		}
 		
-		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgException {
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
 			ByteArrayOutputStream bos = new ByteArrayOutputStream();
 			try {
 				byte b;
@@ -689,7 +684,7 @@
 					}
 				}
 			} catch (IOException ex) {
-				throw new HgException(ex); // FIXME EXCEPTIONS
+				throw new HgInvalidControlFileException("Failed reading manifest", ex, null);
 			}
 		}
 	}
--- a/src/org/tmatesoft/hg/repo/HgMergeState.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgMergeState.java	Fri Mar 23 22:51:18 2012 +0100
@@ -27,9 +27,7 @@
 import java.util.Collections;
 import java.util.List;
 
-import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgFileRevision;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.internal.Pool;
@@ -39,7 +37,8 @@
 import org.tmatesoft.hg.util.PathRewrite;
 
 /**
- *
+ * Access to repository's merge state
+ * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
@@ -92,7 +91,11 @@
 		repo = hgRepo;
 	}
 
-	public void refresh() throws HgInvalidControlFileException {
+	/**
+	 * Update our knowledge about repository's merge state
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	public void refresh() throws HgRuntimeException {
 		entries = null;
 		// it's possible there are two parents but no merge/state, we shall report this case as 'merging', with proper
 		// first and second parent values
@@ -153,7 +156,7 @@
 				} else if ("r".equals(r[1])) {
 					k = Kind.Resolved;
 				} else {
-					throw new HgBadStateException(r[1]);
+					throw new HgInvalidStateException(String.format("Unknown merge kind %s", r[1]));
 				}
 				Entry e = new Entry(k, pathPool.path(r[0]), p1, p2, ca);
 				result.add(e);
@@ -184,7 +187,7 @@
 	 */
 	public boolean isStale() {
 		if (wcp1 == null) {
-			throw new HgBadStateException("Call #refresh() first");
+			refresh();
 		}
 		return !stateParent.isNull() /*there's merge state*/ && !wcp1.equals(stateParent) /*and it doesn't match*/; 
 	}
@@ -192,11 +195,12 @@
 	/**
 	 * It's possible for a repository to be in a 'merging' state (@see {@link #isMerging()} without any
 	 * conflict to resolve (no merge state information file).
+	 * 
 	 * @return first parent of the working copy, never <code>null</code>
 	 */
 	public Nodeid getFirstParent() {
 		if (wcp1 == null) {
-			throw new HgBadStateException("Call #refresh() first");
+			refresh();
 		}
 		return wcp1;
 	}
@@ -206,7 +210,7 @@
 	 */
 	public Nodeid getSecondParent() {
 		if (wcp2 == null) {
-			throw new HgBadStateException("Call #refresh() first");
+			refresh();
 		}
 		return wcp2;
 	}
@@ -216,7 +220,7 @@
 	 */
 	public Nodeid getStateParent() {
 		if (stateParent == null) {
-			throw new HgBadStateException("Call #refresh() first");
+			refresh();
 		}
 		return stateParent;
 	}
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Fri Mar 23 22:51:18 2012 +0100
@@ -47,8 +47,6 @@
 import javax.net.ssl.X509TrustManager;
 
 import org.tmatesoft.hg.core.HgBadArgumentException;
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgInvalidFileException;
 import org.tmatesoft.hg.core.HgRemoteConnectionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
@@ -234,12 +232,12 @@
 						assert currRange == null;
 						assert currRangeList == null;
 						if (!rangeItr.hasNext()) {
-							throw new HgBadStateException("Internal error");
+							throw new HgInvalidStateException("Internal error"); // TODO revisit-1.1
 						}
 						rv.put(rangeItr.next(), Collections.<Nodeid>emptyList());
 					} else {
 						if (currRange == null || currRangeList == null) {
-							throw new HgBadStateException("Internal error");
+							throw new HgInvalidStateException("Internal error"); // TODO revisit-1.1
 						}
 						// indicate next range value is needed
 						currRange = null;
@@ -250,7 +248,7 @@
 					possiblyEmptyNextLine = false;
 					if (currRange == null) {
 						if (!rangeItr.hasNext()) {
-							throw new HgBadStateException("Internal error");
+							throw new HgInvalidStateException("Internal error"); // TODO revisit-1.1
 						}
 						currRange = rangeItr.next();
 						currRangeList = new LinkedList<Nodeid>();
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Fri Mar 23 22:51:18 2012 +0100
@@ -26,8 +26,6 @@
 import java.util.List;
 
 import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
@@ -75,9 +73,14 @@
 	public static final int WORKING_COPY = -2; // XXX WORKING_COPY_REVISION?
 	
 	/**
-	 * Constant ({@value #NO_REVISION}) to indicate revision absence (e.g. missing parent in from {@link HgChangelog#parents(int, int[], byte[], byte[])} call) 
-	 * or a fictitious revision of an empty repository, to use as an argument (contrary to {@link #BAD_REVISION})
-	 * e.g in a status operation to visit changes from the very beginning of a repository. 
+	 * Constant ({@value #NO_REVISION}) to indicate revision absence or a fictitious revision of an empty repository.
+	 * 
+	 * <p>Revision absence is vital e.g. for missing parent from {@link HgChangelog#parents(int, int[], byte[], byte[])} call and
+	 * to report cases when changeset records no corresponding manifest 
+	 * revision {@link HgManifest#walk(int, int, org.tmatesoft.hg.repo.HgManifest.Inspector)}.
+	 * 
+	 * <p> Use as imaginary revision/empty repository is handy as an argument (contrary to {@link #BAD_REVISION})
+	 * e.g in a status operation to visit changes from the very beginning of a repository.
 	 */
 	public static final int NO_REVISION = -1;
 	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgRuntimeException.java	Fri Mar 23 22:51:18 2012 +0100
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012 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.repo;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.ExceptionInfo;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Almost any method in Hg4J low-level API may throw subclass of this exception to indicate unexpected
+ * state/condition encountered, flawed data or IO error. Since most cases can't be handled in a reasonable 
+ * manner (other than catch all exceptions and tell client something went wrong), and propagating all possible
+ * exceptions up through API is dubious task, low-level exceptions are made runtime, rooting at this single class.
+ * 
+ * <p>Hi-level api, {@link org.tmatesoft.hg.core}, where interaction with user-supplied values is more explicit,
+ * may follow different exception strategy.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public abstract class HgRuntimeException extends RuntimeException {
+
+	protected final ExceptionInfo<HgRuntimeException> details = new ExceptionInfo<HgRuntimeException>(this);
+	
+	protected HgRuntimeException(String reason, Throwable cause) {
+		super(reason, cause);
+	}
+
+	/**
+	 * @return {@link HgRepository#BAD_REVISION} unless revision index was set during exception instantiation
+	 */
+	public int getRevisionIndex() {
+		return details.getRevisionIndex();
+	}
+
+	public HgRuntimeException setRevisionIndex(int rev) {
+		return details.setRevisionIndex(rev);
+	}
+	
+	public boolean isRevisionIndexSet() {
+		return details.isRevisionIndexSet();
+	}
+
+
+	/**
+	 * @return non-<code>null</code> when revision was supplied at construction time
+	 */
+	public Nodeid getRevision() {
+		return details.getRevision();
+	}
+
+	public HgRuntimeException setRevision(Nodeid r) {
+		return details.setRevision(r);
+	}
+	
+	public boolean isRevisionSet() {
+		return details.isRevisionSet();
+	}
+
+	/**
+	 * @return non-null only if file name was set at construction time
+	 */
+	public Path getFileName() {
+		return details.getFileName();
+	}
+
+	public HgRuntimeException setFileName(Path name) {
+		return details.setFileName(name);
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder(super.toString());
+		sb.append(' ');
+		sb.append('(');
+		details.appendDetails(sb);
+		sb.append(')');
+		return sb.toString();
+	}
+}
--- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Fri Mar 23 22:51:18 2012 +0100
@@ -26,21 +26,20 @@
 import java.util.Map;
 import java.util.TreeSet;
 
-import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.IntMap;
 import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.internal.Pool;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathPool;
 import org.tmatesoft.hg.util.PathRewrite;
 
 
 /**
- * RevisionWalker?
+ * Collect status information for changes between two repository revisions.
  *
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
@@ -130,7 +129,7 @@
 			}
 
 			public boolean next(Nodeid nid, String fname, String flags) {
-				throw new HgBadStateException(HgManifest.Inspector2.class.getName());
+				throw new IllegalStateException(HgManifest.Inspector2.class.getName());
 			}
 
 			public boolean next(Nodeid nid, Path fname, HgManifest.Flags flags) {
@@ -193,10 +192,10 @@
 	/**
 	 * 'hg status --change REV' command counterpart.
 	 * 
-	 * @throws HgInvalidRevisionException if argument specifies non-existent revision index
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
+	 * @throws CancelledException if operation execution was cancelled
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public void change(int revisionIndex, HgStatusInspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public void change(int revisionIndex, HgStatusInspector inspector) throws CancelledException, HgRuntimeException {
 		int p;
 		if (revisionIndex == 0) {
 			p = NO_REVISION;
@@ -213,16 +212,14 @@
 	 * Parameters <b>rev1</b> and <b>rev2</b> are changelog revision indexes, shall not be the same. Argument order matters.
 	 * Either rev1 or rev2 may be {@link HgRepository#NO_REVISION} to indicate comparison to empty repository
 	 * 
-	 * FIXME cancellation (at least exception)?
-	 * 
 	 * @param rev1 <em>from</em> changeset index, non-negative or {@link HgRepository#TIP}
 	 * @param rev2 <em>to</em> changeset index, non-negative or {@link HgRepository#TIP}
 	 * @param inspector callback for status information
-	 * @throws HgInvalidRevisionException if any argument specifies non-existent revision index
+	 * @throws CancelledException if operation execution was cancelled
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 * @throws IllegalArgumentException inspector other incorrect argument values
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
 	 */
-	public void walk(int rev1, int rev2, HgStatusInspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public void walk(int rev1, int rev2, HgStatusInspector inspector) throws CancelledException, HgRuntimeException, IllegalArgumentException {
 		if (rev1 == rev2) {
 			throw new IllegalArgumentException();
 		}
@@ -278,6 +275,8 @@
 		r1 = get(rev1);
 		r2 = get(rev2);
 
+		final CancelSupport cs = CancelSupport.Factory.get(inspector);
+
 		TreeSet<Path> r1Files = new TreeSet<Path>(r1.files());
 		for (Path r2fname : r2.files()) {
 			if (!scope.accept(r2fname)) {
@@ -293,6 +292,7 @@
 				} else {
 					inspector.modified(r2fname);
 				}
+				cs.checkCancelled();
 			} else {
 				try {
 					Path copyTarget = r2fname;
@@ -307,11 +307,13 @@
 					// for a single file not to be irresolvable obstacle for a status operation
 					inspector.invalid(r2fname, ex);
 				}
+				cs.checkCancelled();
 			}
 		}
 		for (Path r1fname : r1Files) {
 			if (scope.accept(r1fname)) {
 				inspector.removed(r1fname);
+				cs.checkCancelled();
 			}
 		}
 	}
@@ -322,12 +324,18 @@
 	 * @param rev1 <em>from</em> changeset index 
 	 * @param rev2 <em>to</em> changeset index
 	 * @return information object that describes change between the revisions
-	 * @throws HgInvalidRevisionException if any argument specifies non-existent revision index
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
 	public Record status(int rev1, int rev2) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		Record rv = new Record();
-		walk(rev1, rev2, rv);
+		try {
+			walk(rev1, rev2, rv);
+		} catch (CancelledException ex) {
+			// can't happen as long our Record class doesn't implement CancelSupport
+			HgInvalidStateException t = new HgInvalidStateException("Internal error");
+			t.initCause(ex);
+			throw t;
+		}
 		return rv;
 	}
 	
@@ -368,6 +376,7 @@
 	 * from {@link #getAdded()}.  
 	 */
 	public static class Record implements HgStatusInspector {
+		// NOTE, shall not implement CancelSupport, or methods that use it and don't expect this exception shall be changed
 		private List<Path> modified, added, removed, clean, missing, unknown, ignored;
 		private Map<Path, Path> copied;
 		private Map<Path, Exception> failures;
--- a/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Fri Mar 23 22:51:18 2012 +0100
@@ -18,8 +18,7 @@
 
 import java.io.File;
 
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgInvalidFileException;
+import org.tmatesoft.hg.core.HgRepositoryNotFoundException;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.util.Path;
 
@@ -86,9 +85,15 @@
 		return owner;
 	}
 
-	public HgRepository getRepo() throws HgInvalidFileException {
+	/**
+	 * 
+	 * @return object to access sub-repository
+	 * @throws HgRepositoryNotFoundException if failed to find repository
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	public HgRepository getRepo() throws HgRepositoryNotFoundException {
 		if (kind != Kind.Hg) {
-			throw new HgBadStateException(String.format("Unsupported subrepository %s", kind));
+			throw new HgInvalidStateException(String.format("Unsupported subrepository %s", kind));
 		}
 		return new HgLookup().detect(new File(owner.getWorkingDir(), source));
 	}
--- a/src/org/tmatesoft/hg/repo/HgTags.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgTags.java	Fri Mar 23 22:51:18 2012 +0100
@@ -29,7 +29,6 @@
 import java.util.Map;
 import java.util.TreeMap;
 
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
 import org.tmatesoft.hg.core.Nodeid;
 
 /**
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Fri Mar 23 22:51:18 2012 +0100
@@ -31,8 +31,6 @@
 import java.util.TreeSet;
 
 import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidFileException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.Experimental;
@@ -43,6 +41,7 @@
 import org.tmatesoft.hg.internal.Preview;
 import org.tmatesoft.hg.util.Adaptable;
 import org.tmatesoft.hg.util.ByteChannel;
+import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.FileInfo;
 import org.tmatesoft.hg.util.FileIterator;
@@ -151,9 +150,17 @@
 		return dirstateParentManifest;
 	}
 	
-	// may be invoked few times, TIP or WORKING_COPY indicate comparison shall be run against working copy parent
-	// NOTE, use of TIP constant requires certain care. TIP here doesn't mean latest cset, but actual working copy parent.
-	public void walk(int baseRevision, HgStatusInspector inspector) throws HgInvalidControlFileException, IOException {
+	/**
+	 * may be invoked few times, TIP or WORKING_COPY indicate comparison shall be run against working copy parent
+	 * XXX NOTE, use of TIP constant requires certain care. TIP here doesn't mean latest cset, but actual working copy parent.
+	 * 
+	 * @param baseRevision
+	 * @param inspector
+	 * @throws IOException to propagate IO errors from {@link FileIterator}
+	 * @throws CancelledException if operation execution was cancelled
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	public void walk(int baseRevision, HgStatusInspector inspector) throws IOException, CancelledException, HgRuntimeException {
 		if (HgInternals.wrongRevisionIndex(baseRevision) || baseRevision == BAD_REVISION) {
 			throw new IllegalArgumentException(String.valueOf(baseRevision));
 		}
@@ -184,12 +191,14 @@
 			}
 			((HgStatusCollector.Record) inspector).init(rev1, rev2, sc);
 		}
+		final CancelSupport cs = CancelSupport.Factory.get(inspector);
 		final HgIgnore hgIgnore = repo.getIgnore();
 		repoWalker.reset();
 		TreeSet<Path> processed = new TreeSet<Path>(); // names of files we handled as they known to Dirstate (not FileIterator)
 		final HgDirstate ds = getDirstateImpl();
 		TreeSet<Path> knownEntries = ds.all(); // here just to get dirstate initialized
 		while (repoWalker.hasNext()) {
+			cs.checkCancelled();
 			repoWalker.next();
 			final Path fname = getPathPool().path(repoWalker.name());
 			FileInfo f = repoWalker.file();
@@ -250,6 +259,7 @@
 			for (Path fromBase : baseRevFiles) {
 				if (repoWalker.inScope(fromBase)) {
 					inspector.removed(fromBase);
+					cs.checkCancelled();
 				}
 			}
 		}
@@ -259,6 +269,7 @@
 				// do not report as missing/removed those FileIterator doesn't care about.
 				continue;
 			}
+			cs.checkCancelled();
 			// missing known file from a working dir  
 			if (ds.checkRemoved(m) == null) {
 				// not removed from the repository = 'deleted'  
@@ -273,9 +284,24 @@
 		}
 	}
 
-	public HgStatusCollector.Record status(int baseRevision) throws HgInvalidControlFileException, IOException {
+	/**
+	 * 
+	 * @param baseRevision
+	 * @return information object that describes change between the revisions
+	 * @throws IOException to propagate IO errors from {@link FileIterator}
+	 * @throws CancelledException if operation execution was cancelled
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	public HgStatusCollector.Record status(int baseRevision) throws IOException, HgRuntimeException {
 		HgStatusCollector.Record rv = new HgStatusCollector.Record();
-		walk(baseRevision, rv);
+		try {
+			walk(baseRevision, rv);
+		} catch (CancelledException ex) {
+			// can't happen as long our Record class doesn't implement CancelSupport
+			HgInvalidStateException t = new HgInvalidStateException("Internal error");
+			t.initCause(ex);
+			throw t;
+		}
 		return rv;
 	}
 
@@ -430,7 +456,7 @@
 		// The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean'
 	}
 
-	private boolean areTheSame(FileInfo f, HgDataFile dataFile, Nodeid revision) throws HgException {
+	private boolean areTheSame(FileInfo f, HgDataFile dataFile, Nodeid revision) throws HgException, HgInvalidFileException {
 		// XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
 		ByteArrayChannel bac = new ByteArrayChannel();
 		try {
@@ -444,7 +470,7 @@
 		return areTheSame(f, bac.toArray(), dataFile.getPath());
 	}
 	
-	private boolean areTheSame(FileInfo f, final byte[] data, Path p) throws HgException {
+	private boolean areTheSame(FileInfo f, final byte[] data, Path p) throws HgInvalidFileException {
 		ReadableByteChannel is = null;
 		class Check implements ByteChannel {
 			final boolean debug = repo.getContext().getLog().isDebug(); 
@@ -666,13 +692,11 @@
 		}
 		
 		public boolean supportsExecFlag() {
-			// TODO Auto-generated method stub
-			return false;
+			return execCap;
 		}
 		
 		public boolean supportsLinkFlag() {
-			// TODO Auto-generated method stub
-			return false;
+			return linkCap;
 		}
 	}
 	
--- a/src/org/tmatesoft/hg/repo/Revlog.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/Revlog.java	Fri Mar 23 22:51:18 2012 +0100
@@ -29,10 +29,7 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.HgInvalidControlFileException;
-import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ArrayHelper;
 import org.tmatesoft.hg.internal.DataAccess;
@@ -172,7 +169,7 @@
 		}
 		if (rn < 0 || rn >= content.revisionCount()) {
 			// Sanity check
-			throw new HgBadStateException(String.format("Revision index %d found for nodeid %s is not from the range [0..%d]", rn, nodeid.shortNotation(), content.revisionCount()-1));
+			throw new HgInvalidStateException(String.format("Revision index %d found for nodeid %s is not from the range [0..%d]", rn, nodeid.shortNotation(), content.revisionCount()-1));
 		}
 		return true;
 	}
@@ -399,7 +396,7 @@
 		
 		private void assertSortedIndex(int x) {
 			if (x < 0) {
-				throw new HgBadStateException(String.format("Bad index", x));
+				throw new HgInvalidStateException(String.format("Bad index", x));
 			}
 		}
 		
@@ -617,6 +614,7 @@
 			failure = ex;
 		}
 
+		// FIXME is HgException of any use here now?  
 		// TODO consider if IOException in addition to HgException is of any real utility
 		public void checkFailed() throws HgException, IOException, CancelledException {
 			if (failure == null) {
@@ -631,7 +629,7 @@
 			if (failure instanceof HgException) {
 				throw (HgException) failure;
 			}
-			throw new HgBadStateException(failure);
+			throw new HgInvalidStateException(failure.toString());
 		}
 
 		public void checkCancelled() throws CancelledException {
@@ -697,7 +695,7 @@
 						logFacility.warn(getClass(), "Bad data sink when reading revision %d. Reported %d bytes consumed, byt actually read %d", revisionNumber, consumed, buf.position());
 					}
 					if (buf.position() == 0) {
-						throw new HgBadStateException("Bad sink implementation (consumes no bytes) results in endless loop");
+						throw new HgInvalidStateException("Bad sink implementation (consumes no bytes) results in endless loop");
 					}
 					buf.compact(); // ensure (a) there's space for new (b) data starts at 0
 					progressSupport.worked(consumed);
--- a/src/org/tmatesoft/hg/util/CancelSupport.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/util/CancelSupport.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -47,10 +47,11 @@
 			if (cs != null) {
 				return cs;
 			}
-			return new CancelSupport() {
+			class NoCancel implements CancelSupport {
 				public void checkCancelled() {
 				}
 			};
+			return new NoCancel();
 		}
 		
 		public static CancelSupport get(Object target, CancelSupport defaultValue) {
--- a/src/org/tmatesoft/hg/util/RegularFileStats.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/util/RegularFileStats.java	Fri Mar 23 22:51:18 2012 +0100
@@ -46,7 +46,7 @@
  * TODO post-1.0 Add extraction of link modification time, see RegularFileInfo#lastModified()
  * 
  * @author Artem Tikhomirov
- * @author Tmate Software Ltd.
+ * @author TMate Software Ltd.
  */
 /*package-local*/ class RegularFileStats {
 	private boolean isExec, isSymlink;
--- a/test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java	Fri Mar 23 22:51:18 2012 +0100
@@ -11,7 +11,7 @@
 import java.util.Map;
 
 import org.junit.Assert;
-import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgCallbackTargetException;
 import org.tmatesoft.hg.core.HgChangeset;
 import org.tmatesoft.hg.core.HgChangesetHandler;
 import org.tmatesoft.hg.core.HgException;
@@ -122,11 +122,7 @@
 		fileNode.walk(0, TIP, new HgDataFile.RevisionInspector() {
 
 			public void next(int fileRevisionIndex, Nodeid revision, int linkedRevisionIndex) {
-				try {
-					changesetToNodeid_3.put(clog.getRevision(linkedRevisionIndex), revision);
-				} catch (HgException ex) {
-					ex.printStackTrace();
-				}
+				changesetToNodeid_3.put(clog.getRevision(linkedRevisionIndex), revision);
 			}
 		});
 		final long end_3 = System.nanoTime();
@@ -242,8 +238,8 @@
 		//
 		// build cache
 		//
-		final TagInfo[] allTags = new TagInfo[tags.getTags().size()];
-		tags.getTags().values().toArray(allTags);
+		final TagInfo[] allTags = new TagInfo[tags.getAllTags().size()];
+		tags.getAllTags().values().toArray(allTags);
 		// effective translation of changeset revisions to their local indexes
 		final HgChangelog.RevisionMap clogrmap = repository.getChangelog().new RevisionMap().init();
 		// map to look up tag by changeset local number
@@ -295,7 +291,7 @@
 			}
 			
 			public boolean next(Nodeid nid, String fname, String flags) {
-				throw new HgBadStateException(HgManifest.Inspector2.class.getName());
+				throw new IllegalStateException(HgManifest.Inspector2.class.getName());
 			}
 
 			public boolean next(Nodeid nid, Path fname, HgManifest.Flags flags) {
@@ -379,7 +375,7 @@
 			return true;
 		}
 		public boolean next(Nodeid nid, String fname, String flags) {
-			throw new HgBadStateException(HgManifest.Inspector2.class.getName());
+			throw new IllegalStateException(HgManifest.Inspector2.class.getName());
 		}
 		public boolean next(Nodeid nid, Path fname, Flags flags) {
 			return true;
@@ -389,11 +385,11 @@
 		}
 	}
 	
-	public static void main2(String[] args) throws HgException, CancelledException {
+	public static void main2(String[] args) throws HgCallbackTargetException, HgException, CancelledException {
 		final HgRepository repository = new HgLookup().detect(new File("/temp/hg/cpython"));
 		final Path targetPath = Path.create("README");
 		final HgTags tags = repository.getTags();
-		final Map<String, HgTags.TagInfo> tagToInfo = tags.getTags();
+		final Map<String, HgTags.TagInfo> tagToInfo = tags.getAllTags();
 		final HgManifest manifest = repository.getManifest();
 		final Map<Nodeid, List<String>> changeSetRevisionToTags = new HashMap<Nodeid, List<String>>();
 		final HgDataFile fileNode = repository.getFileNode(targetPath);
@@ -420,7 +416,7 @@
 		logCommand.execute(new HgChangesetHandler() {
 			public void next(HgChangeset changeset) {
 				if (changeset.getAffectedFiles().contains(targetPath)) {
-					System.out.println(changeset.getRevision() + " " + changeSetRevisionToTags.get(changeset.getNodeid()));
+					System.out.println(changeset.getRevisionIndex() + " " + changeSetRevisionToTags.get(changeset.getNodeid()));
 				}
 			}
 		});
--- a/test/org/tmatesoft/hg/test/TestAuxUtilities.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/test/org/tmatesoft/hg/test/TestAuxUtilities.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -25,7 +25,6 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.tmatesoft.hg.core.HgCatCommand;
-import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ArrayHelper;
 import org.tmatesoft.hg.repo.HgChangelog;
@@ -235,13 +234,9 @@
 			int i = 0;
 
 			public void next(int localRevision, Nodeid revision, int linkedRevision) {
-				try {
-					Assert.assertEquals(i++, localRevision);
-					Assert.assertEquals(fileNode.getChangesetRevisionIndex(localRevision), linkedRevision);
-					Assert.assertEquals(fileNode.getRevision(localRevision), revision);
-				} catch (HgException ex) {
-					Assert.fail(ex.toString());
-				}
+				Assert.assertEquals(i++, localRevision);
+				Assert.assertEquals(fileNode.getChangesetRevisionIndex(localRevision), linkedRevision);
+				Assert.assertEquals(fileNode.getRevision(localRevision), revision);
 			}
 		});
 		fileNode.walk(0, TIP, new HgDataFile.ParentInspector() {
--- a/test/org/tmatesoft/hg/test/TestHistory.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/test/org/tmatesoft/hg/test/TestHistory.java	Fri Mar 23 22:51:18 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -113,7 +113,7 @@
 		final LinkedList<HgChangeset> sorted = new LinkedList<HgChangeset>(h.getChanges());
 		Collections.sort(sorted, new Comparator<HgChangeset>() {
 			public int compare(HgChangeset cs1, HgChangeset cs2) {
-				return cs1.getRevision() < cs2.getRevision() ? 1 : -1;
+				return cs1.getRevisionIndex() < cs2.getRevisionIndex() ? 1 : -1;
 			}
 		});
 		report(what, sorted, false);
@@ -137,14 +137,14 @@
 				break;
 			}
 			Record cr = consoleResultItr.next();
-			int x = cs.getRevision() == cr.changesetIndex ? 0x1 : 0;
+			int x = cs.getRevisionIndex() == cr.changesetIndex ? 0x1 : 0;
 			x |= cs.getDate().toString().equals(cr.date) ? 0x2 : 0;
 			x |= cs.getNodeid().toString().equals(cr.changesetNodeid) ? 0x4 : 0;
 			x |= cs.getUser().equals(cr.user) ? 0x8 : 0;
 			// need to do trim() on comment because command-line template does, and there are
 			// repositories that have couple of newlines in the end of the comment (e.g. hello sample repo from the book) 
 			x |= cs.getComment().trim().equals(cr.description) ? 0x10 : 0;
-			errorCollector.checkThat(String.format(what + ". Mismatch (0x%x) in %d hg4j rev comparing to %d cmdline's.", x, cs.getRevision(), cr.changesetIndex), x, equalTo(0x1f));
+			errorCollector.checkThat(String.format(what + ". Mismatch (0x%x) in %d hg4j rev comparing to %d cmdline's.", x, cs.getRevisionIndex(), cr.changesetIndex), x, equalTo(0x1f));
 			consoleResultItr.remove();
 		}
 		errorCollector.checkThat(what + ". Unprocessed results in console left (insufficient from hg4j)", consoleResultItr.hasNext(), equalTo(false));