changeset 425:48f993aa2f41

FIXMEs: exceptions, javadoc
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 28 Mar 2012 18:39:29 +0200
parents 6437d261048a
children 063b0663495a
files cmdline/org/tmatesoft/hg/console/Main.java src/org/tmatesoft/hg/core/HgException.java src/org/tmatesoft/hg/internal/DataAccess.java src/org/tmatesoft/hg/internal/DataAccessProvider.java src/org/tmatesoft/hg/internal/Internals.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/internal/RevlogStream.java src/org/tmatesoft/hg/internal/StoragePathHelper.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgInternals.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgManifest.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/HgWorkingCopyStatusCollector.java src/org/tmatesoft/hg/repo/Revlog.java src/org/tmatesoft/hg/util/FileWalker.java src/org/tmatesoft/hg/util/ProgressSupport.java src/org/tmatesoft/hg/util/RegularFileInfo.java src/org/tmatesoft/hg/util/RegularFileStats.java
diffstat 22 files changed, 254 insertions(+), 187 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Wed Mar 28 18:39:29 2012 +0200
@@ -38,6 +38,7 @@
 import org.tmatesoft.hg.core.HgLogCommand;
 import org.tmatesoft.hg.core.HgManifestCommand;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.BasicSessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.DigestHelper;
 import org.tmatesoft.hg.internal.PathGlobMatcher;
@@ -67,6 +68,7 @@
 import org.tmatesoft.hg.util.Pair;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathRewrite;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 /**
  * Various debug dumps. 
@@ -92,7 +94,8 @@
 
 	public static void main(String[] args) throws Exception {
 		Main m = new Main(args);
-		m.checkFileFlags();
+		m.checkSubProgress();
+//		m.checkFileFlags();
 //		m.buildFileLog();
 //		m.testConsoleLog();
 //		m.testTreeTraversal();
@@ -114,6 +117,41 @@
 //		m.dumpCompleteManifestHigh();
 //		m.bunchOfTests();
 	}
+	
+	// no repo
+	private void checkSubProgress() {
+		ProgressSupport ps = new ProgressSupport() {
+			private int units;
+			
+			public void start(int totalUnits) {
+				units = totalUnits;
+				System.out.printf("%d:", totalUnits);
+				
+			}
+			public void worked(int wu) {
+				for (int i = 0; i < wu; i++) {
+					System.out.print(units-- == 0 ? '!' : '.');
+				}
+			}
+			public void done() {
+				System.out.println("DONE");
+			}
+		};
+		ps.start(10);
+		ProgressSupport.Sub s1 = new ProgressSupport.Sub(ps, 3);
+		ProgressSupport.Sub s2 = new ProgressSupport.Sub(ps, 7);
+		s1.start(10);
+		s1.worked(1);
+		s1.worked(2);
+		s1.worked(3);
+		s1.worked(4);
+		s1.done();
+		//
+		s2.start(5);
+		s2.worked(3);
+		s2.worked(2);
+		s2.done();
+	}
 
 	private void checkFileFlags() throws Exception {
 		// ~/hg/test-flags repo
@@ -218,7 +256,7 @@
 	private void testTreeTraversal() throws Exception {
 		File repoRoot = hgRepo.getWorkingDir();
 		Path.Source pathSrc = new Path.SimpleSource(new PathRewrite.Composite(new RelativePathRewrite(repoRoot), hgRepo.getToRepoPathHelper()));
-		FileWalker w =  new FileWalker(repoRoot, pathSrc);
+		FileWalker w =  new FileWalker(new BasicSessionContext(null, null), repoRoot, pathSrc);
 		int count = 0;
 		final long start = System.currentTimeMillis();
 		while (w.hasNext()) {
--- a/src/org/tmatesoft/hg/core/HgException.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/core/HgException.java	Wed Mar 28 18:39:29 2012 +0200
@@ -21,7 +21,7 @@
 import org.tmatesoft.hg.util.Path;
 
 /**
- * Root class for all hg4j exceptions.
+ * Root class for all <b>Hg4J</b> checked exceptions.
  * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
--- a/src/org/tmatesoft/hg/internal/DataAccess.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/DataAccess.java	Wed Mar 28 18:39:29 2012 +0200
@@ -121,7 +121,7 @@
 	}
 
 	// XXX decide whether may or may not change position in the DataAccess
-	// FIXME exception handling is not right, just for the sake of quick test
+	// TODO REVISIT exception handling may not be right, initially just for the sake of quick test
 	public byte[] byteArray() throws IOException {
 		reset();
 		byte[] rv = new byte[length()];
--- a/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Wed Mar 28 18:39:29 2012 +0200
@@ -24,6 +24,7 @@
 import java.nio.channels.FileChannel;
 
 import org.tmatesoft.hg.core.SessionContext;
+import org.tmatesoft.hg.util.LogFacility;
 
 /**
  * 
@@ -39,13 +40,17 @@
 	public static final String CFG_PROPERTY_MAPIO_LIMIT				= "hg4j.dap.mapio_limit";
 	public static final String CFG_PROPERTY_MAPIO_BUFFER_SIZE		= "hg4j.dap.mapio_buffer";
 	public static final String CFG_PROPERTY_FILE_BUFFER_SIZE		= "hg4j.dap.file_buffer";
+	
+	private static final int DEFAULT_MAPIO_LIMIT = 100 * 1024; // 100 kB
+	private static final int DEFAULT_FILE_BUFFER =   8 * 1024; // 8 kB
+	private static final int DEFAULT_MAPIO_BUFFER = DEFAULT_MAPIO_LIMIT; // same as default boundary
 
 	private final int mapioMagicBoundary;
 	private final int bufferSize;
 	private final SessionContext context;
 
 	public DataAccessProvider(SessionContext ctx) {
-		this(ctx, getConfigOption(ctx, CFG_PROPERTY_MAPIO_LIMIT, 100 * 1024), getConfigOption(ctx, CFG_PROPERTY_FILE_BUFFER_SIZE, 8 * 1024));
+		this(ctx, getConfigOption(ctx, CFG_PROPERTY_MAPIO_LIMIT, DEFAULT_MAPIO_LIMIT), getConfigOption(ctx, CFG_PROPERTY_FILE_BUFFER_SIZE, DEFAULT_FILE_BUFFER));
 	}
 	
 	private static int getConfigOption(SessionContext ctx, String optName, int defaultValue) {
@@ -71,14 +76,15 @@
 			long flen = fc.size();
 			if (flen > mapioMagicBoundary) {
 				// TESTS: bufLen of 1024 was used to test MemMapFileAccess
-				return new MemoryMapFileAccess(fc, flen, getConfigOption(context, CFG_PROPERTY_MAPIO_BUFFER_SIZE, 100*1024 /*same as default boundary*/));
+				int mapioBufSize = getConfigOption(context, CFG_PROPERTY_MAPIO_BUFFER_SIZE, DEFAULT_MAPIO_BUFFER);
+				return new MemoryMapFileAccess(fc, flen, mapioBufSize, context.getLog());
 			} else {
 				// XXX once implementation is more or less stable,
 				// may want to try ByteBuffer.allocateDirect() to see
 				// if there's any performance gain. 
 				boolean useDirectBuffer = false; // XXX might be another config option
 				// TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize
-				return new FileAccess(fc, flen, bufferSize, useDirectBuffer);
+				return new FileAccess(fc, flen, bufferSize, useDirectBuffer, context.getLog());
 			}
 		} catch (IOException ex) {
 			// unlikely to happen, we've made sure file exists.
@@ -89,14 +95,16 @@
 
 	private static class MemoryMapFileAccess extends DataAccess {
 		private FileChannel fileChannel;
+		private long position = 0; // always points to buffer's absolute position in the file
+		private MappedByteBuffer buffer;
 		private final long size;
-		private long position = 0; // always points to buffer's absolute position in the file
 		private final int memBufferSize;
-		private MappedByteBuffer buffer;
+		private final LogFacility logFacility;
 
-		public MemoryMapFileAccess(FileChannel fc, long channelSize, int bufferSize) {
+		public MemoryMapFileAccess(FileChannel fc, long channelSize, int bufferSize, LogFacility log) {
 			fileChannel = fc;
 			size = channelSize;
+			logFacility = log;
 			memBufferSize = bufferSize > channelSize ? (int) channelSize : bufferSize; // no reason to waste memory more than there's data 
 		}
 
@@ -202,7 +210,7 @@
 				try {
 					fileChannel.close();
 				} catch (IOException ex) {
-					StreamLogFacility.newDefault().debug(getClass(), ex, null);
+					logFacility.debug(getClass(), ex, null);
 				}
 				fileChannel = null;
 			}
@@ -212,13 +220,15 @@
 	// (almost) regular file access - FileChannel and buffers.
 	private static class FileAccess extends DataAccess {
 		private FileChannel fileChannel;
-		private final long size;
 		private ByteBuffer buffer;
 		private long bufferStartInFile = 0; // offset of this.buffer in the file.
+		private final long size;
+		private final LogFacility logFacility;
 
-		public FileAccess(FileChannel fc, long channelSize, int bufferSizeHint, boolean useDirect) {
+		public FileAccess(FileChannel fc, long channelSize, int bufferSizeHint, boolean useDirect, LogFacility log) {
 			fileChannel = fc;
 			size = channelSize;
+			logFacility = log;
 			final int capacity = size < bufferSizeHint ? (int) size : bufferSizeHint;
 			buffer = useDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
 			buffer.flip(); // or .limit(0) to indicate it's empty
@@ -334,8 +344,7 @@
 				try {
 					fileChannel.close();
 				} catch (IOException ex) {
-					// FIXME/TODO log facility can be obtained from session context 
-					StreamLogFacility.newDefault().debug(getClass(), ex, null);
+					logFacility.debug(getClass(), ex, null);
 				}
 				fileChannel = null;
 			}
--- a/src/org/tmatesoft/hg/internal/Internals.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/Internals.java	Wed Mar 28 18:39:29 2012 +0200
@@ -32,6 +32,7 @@
 
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgRepoConfig.ExtensionsSection;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.PathRewrite;
@@ -80,18 +81,17 @@
 	private final boolean shallCacheRevlogsInRepo;
 
 	public Internals(SessionContext ctx) {
-		this.sessionContext = ctx;
+		sessionContext = ctx;
 		isCaseSensitiveFileSystem = !runningOnWindows();
 		Object p = ctx.getProperty(CFG_PROPERTY_REVLOG_STREAM_CACHE, true);
 		shallCacheRevlogsInRepo = p instanceof Boolean ? ((Boolean) p).booleanValue() : Boolean.parseBoolean(String.valueOf(p));
 	}
 	
-	public void parseRequires(HgRepository hgRepo, File requiresFile) {
+	public void parseRequires(HgRepository hgRepo, File requiresFile) throws HgInvalidControlFileException {
 		try {
 			new RequiresFile().parse(this, requiresFile);
 		} catch (IOException ex) {
-			// FIXME EXCEPTIONS not quite sure error reading requires file shall be silently logged only.
-			HgInternals.getContext(hgRepo).getLog().error(getClass(), ex, null);
+			throw new HgInvalidControlFileException("Parse failed", ex, requiresFile);
 		}
 	}
 
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Wed Mar 28 18:39:29 2012 +0200
@@ -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
@@ -487,7 +487,7 @@
 			toQuery.clear();
 		}
 		if (rootIndex == -1) {
-			throw new HgInvalidStateException("Shall not happen, provided between output is correct"); // FIXME EXCEPTIONS
+			throw new HgInvalidStateException("Shall not happen, provided between output is correct");
 		}
 		result[rootIndex] = branchRoot;
 		boolean resultOk = true;
--- a/src/org/tmatesoft/hg/internal/RevlogStream.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/RevlogStream.java	Wed Mar 28 18:39:29 2012 +0200
@@ -305,7 +305,7 @@
 		return revisionIndex;
 	}
 
-	private void initOutline() {
+	private void initOutline() throws HgInvalidControlFileException {
 		if (baseRevisions != null && baseRevisions.length > 0) {
 			return;
 		}
--- a/src/org/tmatesoft/hg/internal/StoragePathHelper.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/StoragePathHelper.java	Wed Mar 28 18:39:29 2012 +0200
@@ -56,7 +56,7 @@
 		fncache = isFncache;
 		dotencode = isDotencode;
 		suffix2replace = Pattern.compile("\\.([id]|hg)/");
-		csEncoder = fsEncoding.newEncoder(); // FIXME catch exception and rethrow as our's RT
+		csEncoder = fsEncoding.newEncoder();
 		byteEncodingBuf = ByteBuffer.allocate(Math.round(csEncoder.maxBytesPerChar()) + 1/*in fact, need ceil, hence +1*/);
 		charEncodingBuf = CharBuffer.allocate(1);
 	}
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed Mar 28 18:39:29 2012 +0200
@@ -29,7 +29,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 
-import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.HgChangesetFileSneaker;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.DataAccess;
 import org.tmatesoft.hg.internal.FilterByteChannel;
@@ -48,7 +48,12 @@
 
 
 /**
- * ? name:HgFileNode?
+ * Regular user data file stored in the repository.
+ * 
+ * <p> Note, most methods accept index in the file's revision history, not that of changelog. Easy way to obtain 
+ * changeset revision index from file's is to use {@link #getChangesetRevisionIndex(int)}. To obtain file's revision 
+ * index for a given changeset, {@link HgManifest#getFileRevision(int, Path)} or {@link HgChangesetFileSneaker} may 
+ * come handy. 
  *
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
@@ -88,10 +93,9 @@
 	 * @param nodeid revision of the file
 	 * 
 	 * @return size of the file content at the given revision
-	 * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog (<em>runtime exception</em>)  
-	 * @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 int getLength(Nodeid nodeid) throws HgInvalidControlFileException, HgInvalidRevisionException {
+	public int getLength(Nodeid nodeid) throws HgRuntimeException {
 		try {
 			return getLength(getRevisionIndex(nodeid));
 		} catch (HgInvalidControlFileException ex) {
@@ -104,10 +108,9 @@
 	/**
  	 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. 
 	 * @return size of the file content at the revision identified by local revision number.
-	 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>)
-	 * @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 int getLength(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException {
+	public int getLength(int fileRevisionIndex) throws HgRuntimeException {
 		if (wrongRevisionIndex(fileRevisionIndex) || fileRevisionIndex == BAD_REVISION) {
 			throw new HgInvalidRevisionException(fileRevisionIndex);
 		}
@@ -145,11 +148,10 @@
 	 * e.g. from another branch), no content would be supplied.
 	 *     
 	 * @param sink content consumer
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
-	 * @throws HgInvalidFileException if access to file in working directory failed
 	 * @throws CancelledException if execution of the operation was cancelled
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public void workingCopy(ByteChannel sink) throws HgException, CancelledException {
+	public void workingCopy(ByteChannel sink) throws CancelledException, HgRuntimeException {
 		File f = getRepo().getFile(this);
 		if (f.exists()) {
 			final CancelSupport cs = CancelSupport.Factory.get(sink);
@@ -234,12 +236,10 @@
  	 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. 
 	 * @param sink content consumer
 	 * 
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
-	 * @throws HgInvalidFileException if access to file in working directory failed
 	 * @throws CancelledException if execution of the operation was cancelled
-	 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>)
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException {
+	public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws CancelledException, HgRuntimeException {
 		if (fileRevisionIndex == WORKING_COPY) {
 			workingCopy(sink); // pass un-mangled sink
 		} else {
@@ -254,12 +254,10 @@
  	 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. 
 	 * @param sink content consumer
 	 * 
-	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
-	 * @throws HgInvalidFileException if access to file in working directory failed
 	 * @throws CancelledException if execution of the operation was cancelled
-	 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>)
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public void content(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException {
+	public void content(int fileRevisionIndex, ByteChannel sink) throws CancelledException, HgRuntimeException {
 		// for data files need to check heading of the file content for possible metadata
 		// @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8-
 		if (fileRevisionIndex == TIP) {
@@ -293,35 +291,33 @@
 		insp.checkCancelled();
 		super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp);
 		try {
-			insp.checkFailed(); // XXX is there real need to throw IOException from ContentPipe?
+			insp.checkFailed();
 		} catch (HgInvalidControlFileException ex) {
 			ex = ex.setFileName(getPath());
 			throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(fileRevisionIndex);
 		} catch (IOException ex) {
 			HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null);
 			throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex);
-		} catch (HgException ex) {
-			// shall not happen, unless we changed ContentPipe or its subclass
-			HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null);
-			throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex);
 		}
 	}
-	
-	// FIXME is that still needed?
-	public void history(HgChangelog.Inspector inspector) throws HgInvalidControlFileException {
+
+	/**
+	 * Walk complete change history of the file.
+	 * @param inspector callback to visit changesets
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	public void history(HgChangelog.Inspector inspector) throws HgRuntimeException {
 		history(0, getLastRevision(), inspector);
 	}
 
 	/**
-	 * 
-	 * @param start local revision index
-	 * @param end local revision index
-	 * @param inspector
-	 * FIXME EXCEPTIONS
-	 * @throws HgInvalidRevisionException
-	 * @throws HgInvalidControlFileException
+	 * Walk subset of the file's change history.
+	 * @param start revision local index, inclusive; non-negative or {@link HgRepository#TIP}
+	 * @param end revision local index, inclusive; non-negative or {@link HgRepository#TIP}
+	 * @param inspector callback to visit changesets
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public void history(int start, int end, HgChangelog.Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public void history(int start, int end, HgChangelog.Inspector inspector) throws HgRuntimeException {
 		if (!exists()) {
 			throw new IllegalStateException("Can't get history of invalid repository file node"); 
 		}
@@ -361,10 +357,9 @@
 	 * For a given revision of the file (identified with revision index), find out index of the corresponding changeset.
 	 *
 	 * @return changeset revision index
-	 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog
-	 * @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 int getChangesetRevisionIndex(int revision) throws HgInvalidControlFileException, HgInvalidRevisionException {
+	public int getChangesetRevisionIndex(int revision) throws HgRuntimeException {
 		return content.linkRevision(revision);
 	}
 
@@ -373,10 +368,9 @@
 	 * 
 	 * @param nid revision of the file
 	 * @return changeset revision
-	 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog
-	 * @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 getChangesetRevision(Nodeid nid) throws HgInvalidControlFileException, HgInvalidRevisionException {
+	public Nodeid getChangesetRevision(Nodeid nid) throws HgRuntimeException {
 		int changelogRevision = getChangesetRevisionIndex(getRevisionIndex(nid));
 		return getRepo().getChangelog().getRevision(changelogRevision);
 	}
@@ -384,9 +378,9 @@
 	/**
 	 * Tells whether this file originates from another repository file
 	 * @return <code>true</code> if this file is a copy of another from the repository
-	 * @throws HgInvalidControlFileException if access to revlog or file metadata failed
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public boolean isCopy() throws HgInvalidControlFileException {
+	public boolean isCopy() throws HgRuntimeException {
 		if (metadata == null || !metadata.checked(0)) {
 			checkAndRecordMetadata(0);
 		}
@@ -400,10 +394,9 @@
 	 * Get name of the file this one was copied from.
 	 * 
 	 * @return name of the file origin
-	 * @throws HgInvalidControlFileException if access to revlog or file metadata failed
-	 * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false)
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public Path getCopySourceName() throws HgInvalidControlFileException {
+	public Path getCopySourceName() throws HgRuntimeException {
 		if (isCopy()) {
 			return Path.create(metadata.find(0, "copy"));
 		}
@@ -413,10 +406,9 @@
 	/**
 	 * 
 	 * @return revision this file was copied from
-	 * @throws HgInvalidControlFileException if access to revlog or file metadata failed
-	 * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false)
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public Nodeid getCopySourceRevision() throws HgInvalidControlFileException {
+	public Nodeid getCopySourceRevision() throws HgRuntimeException {
 		if (isCopy()) {
 			return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid
 		}
@@ -427,11 +419,9 @@
 	 * Get file flags recorded in the manifest
  	 * @param fileRevisionIndex - revision local index, non-negative, or {@link HgRepository#TIP}. 
 	 * @see HgManifest#getFileFlags(int, Path) 
-	 * FIXME EXCEPTIONS
-	 * @throws HgInvalidControlFileException
-	 * @throws HgInvalidRevisionException
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public HgManifest.Flags getFlags(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException {
+	public HgManifest.Flags getFlags(int fileRevisionIndex) throws HgRuntimeException {
 		int changesetRevIndex = getChangesetRevisionIndex(fileRevisionIndex);
 		return getRepo().getManifest().getFileFlags(changesetRevIndex, getPath());
 	}
@@ -462,9 +452,6 @@
 			// it's ok, we did that
 		} catch (HgInvalidControlFileException ex) {
 			throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(localRev);
-		} catch (HgException ex) {
-			// metadata comes from the content, hence initWithDataFile
-			throw content.initWithDataFile(new HgInvalidControlFileException(null, ex, null));
 		}
 	}
 
@@ -654,10 +641,10 @@
 		}
 
 		@Override
-		public void checkFailed() throws HgException, IOException, CancelledException {
+		public void checkFailed() throws HgRuntimeException, IOException, CancelledException {
 			super.checkFailed();
 			if (delegate instanceof ErrorHandlingInspector) {
-				// XXX need to add ErrorDestination and pass it around (much like CancelSupport get passed)
+				// TODO need to add ErrorDestination (ErrorTarget/Acceptor?) and pass it around (much like CancelSupport get passed)
 				// so that delegate would be able report its failures directly to caller without this hack
 				((ErrorHandlingInspector) delegate).checkFailed();
 			}
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgInternals.java	Wed Mar 28 18:39:29 2012 +0200
@@ -139,7 +139,7 @@
 		// Impl note: simple source is enough as files in the working dir are all unique
 		// even if they might get reused (i.e. after FileIterator#reset() and walking once again),
 		// path caching is better to be done in the code which knows that path are being reused 
-		return new FileWalker(repoRoot, pathSrc, workindDirScope);
+		return new FileWalker(repo.getContext(), repoRoot, pathSrc, workindDirScope);
 	}
 	
 	// expose otherwise package-local information primarily to use in our own o.t.hg.core package
@@ -149,8 +149,8 @@
 
 
 	// Convenient check of revision index for validity (not all negative values are wrong as long as we use negative constants)
-	public static boolean wrongRevisionIndex(int rev) { // FIXME guess, usages shall throw HgInvalidRevision. \
-		// TODO Another method to check,throw and expand TIP at once
+	public static boolean wrongRevisionIndex(int rev) {
+		// TODO Another method to check,throw and expand TIP at once (check[Revision|Revlog]Index()
 		return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION && rev != NO_REVISION; 
 	}
 	
--- a/src/org/tmatesoft/hg/repo/HgLookup.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgLookup.java	Wed Mar 28 18:39:29 2012 +0200
@@ -80,9 +80,9 @@
 		}
 	}
 	
-	public HgBundle loadBundle(File location) throws HgRuntimeException/*FIXME need checked exception for can't find*/ {
+	public HgBundle loadBundle(File location) throws HgRepositoryNotFoundException {
 		if (location == null || !location.canRead()) {
-			throw new HgInvalidFileException(String.format("Can't read file %s", location == null ? null : location.getPath()), null, location);
+			throw new HgRepositoryNotFoundException(String.format("Can't read file %s", location)).setLocation(String.valueOf(location));
 		}
 		return new HgBundle(getContext(), new DataAccessProvider(getContext()), location).link();
 	}
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Wed Mar 28 18:39:29 2012 +0200
@@ -44,7 +44,9 @@
 
 
 /**
- *
+ * Representation of Mercurial manifest file (list of file names and their revisions in a particular changeset)
+ * 
+ * @see http://mercurial.selenic.com/wiki/Manifest
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
@@ -222,15 +224,14 @@
 	 * if changeset has no associated manifest (cset records NULL nodeid for manifest).
 	 * @return manifest revision index, non-negative, or {@link HgRepository#BAD_REVISION}.
 	 * @throws HgInvalidRevisionException if method argument specifies non-existent revision index
-	 * @throws IllegalArgumentException if argument is not a revision index
 	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
 	 */
 	/*package-local*/ int fromChangelog(int changesetRevisionIndex) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		if (HgInternals.wrongRevisionIndex(changesetRevisionIndex)) {
-			throw new IllegalArgumentException(String.valueOf(changesetRevisionIndex));
+			throw new HgInvalidRevisionException(changesetRevisionIndex);
 		}
 		if (changesetRevisionIndex == HgRepository.WORKING_COPY || changesetRevisionIndex == HgRepository.BAD_REVISION) {
-			throw new IllegalArgumentException("Can't use constants like WORKING_COPY or BAD_REVISION");
+			throw new HgInvalidRevisionException("Can't use constants like WORKING_COPY or BAD_REVISION", null, changesetRevisionIndex);
 		}
 		// revisionNumber == TIP is processed by RevisionMapper 
 		if (revisionMap == null) {
@@ -248,8 +249,7 @@
 	 * @param changelogRevisionIndex local changeset index 
 	 * @param file path to file in question
 	 * @return file revision or <code>null</code> if manifest at specified revision doesn't list such file
-	 * @throws HgInvalidRevisionException if method 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 Nodeid getFileRevision(int changelogRevisionIndex, final Path file) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		// there's no need for HgDataFile to own this method, or get a delegate
@@ -281,9 +281,7 @@
 	 * @param changesetRevIndex changeset revision index
 	 * @param file path to look up
 	 * @return one of predefined enum values, or <code>null</code> if file was not known in the specified revision
-	 * FIXME EXCEPTIONS
-	 * @throws HgInvalidControlFileException
-	 * @throws HgInvalidRevisionException 
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
 	public Flags getFileFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		int manifestRevIdx = fromChangelog(changesetRevIndex);
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Wed Mar 28 18:39:29 2012 +0200
@@ -48,6 +48,7 @@
 
 import org.tmatesoft.hg.core.HgBadArgumentException;
 import org.tmatesoft.hg.core.HgRemoteConnectionException;
+import org.tmatesoft.hg.core.HgRepositoryNotFoundException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
 
@@ -326,7 +327,7 @@
 	 * (there's no header like HG10?? with the server output, though, 
 	 * as one may expect according to http://mercurial.selenic.com/wiki/BundleFormat)
 	 */
-	public HgBundle getChanges(List<Nodeid> roots) throws HgRemoteConnectionException, HgInvalidFileException {
+	public HgBundle getChanges(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException {
 		List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots;
 		StringBuilder sb = new StringBuilder(20 + _roots.size() * 41);
 		sb.append("roots=");
@@ -355,6 +356,8 @@
 			throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("changegroup").setServerInfo(getLocation());
 		} catch (IOException ex) {
 			throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation());
+		} catch (HgRepositoryNotFoundException ex) {
+			throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation());
 		}
 	}
 
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Wed Mar 28 18:39:29 2012 +0200
@@ -25,7 +25,6 @@
 import java.util.HashMap;
 import java.util.List;
 
-import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
@@ -128,7 +127,10 @@
 		impl = null;
 	}
 	
-	HgRepository(SessionContext ctx, String repositoryPath, File repositoryRoot) {
+	/**
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	HgRepository(SessionContext ctx, String repositoryPath, File repositoryRoot) throws HgRuntimeException {
 		assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory();
 		assert repositoryPath != null; 
 		assert repositoryRoot != null;
@@ -193,9 +195,6 @@
 					} catch (CancelledException ex) {
 						 // IGNORE, can't happen, we did not configure cancellation
 						getContext().getLog().debug(getClass(), ex, null);
-					} catch (HgException ex) {
-						getContext().getLog().error(getClass(), ex, null);
-						// FIXME EXCEPTIONS need to react
 					} catch (IOException ex) {
 						// UnsupportedEncodingException can't happen (UTF8)
 						// only from readGlobal. Need to reconsider exceptions thrown from there:
--- a/src/org/tmatesoft/hg/repo/HgRuntimeException.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRuntimeException.java	Wed Mar 28 18:39:29 2012 +0200
@@ -16,19 +16,22 @@
  */
 package org.tmatesoft.hg.repo;
 
+import org.tmatesoft.hg.core.HgException;
 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.
+ * Almost any method in <b>Hg4J</b> low-level API (@link org.tmatesoft.hg.repo} 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.
  * 
+ * @see HgException
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
--- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Wed Mar 28 18:39:29 2012 +0200
@@ -26,7 +26,6 @@
 import java.util.Map;
 import java.util.TreeSet;
 
-import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.IntMap;
 import org.tmatesoft.hg.internal.ManifestRevision;
@@ -298,7 +297,7 @@
 					} else {
 						inspector.added(copyTarget);
 					}
-				} catch (HgException ex) {
+				} catch (HgInvalidFileException ex) {
 					// record exception to a mediator and continue, 
 					// for a single file not to be irresolvable obstacle for a status operation
 					inspector.invalid(r2fname, ex);
@@ -335,11 +334,11 @@
 		return rv;
 	}
 	
-	/*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection<Path> originals, int originalChangelogRevision) throws HgException {
+	/*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection<Path> originals, int originalChangelogRevision) throws HgInvalidFileException {
 		HgDataFile df = hgRepo.getFileNode(fname);
 		if (!df.exists()) {
 			String msg = String.format("Didn't find file '%s' in the repo. Perhaps, bad storage name conversion?", fname);
-			throw new HgException(msg).setFileName(fname).setRevisionIndex(originalChangelogRevision);
+			throw new HgInvalidFileException(msg, null).setFileName(fname).setRevisionIndex(originalChangelogRevision);
 		}
 		while (df.isCopy()) {
 			Path original = df.getCopySourceName();
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Wed Mar 28 18:39:29 2012 +0200
@@ -30,8 +30,8 @@
 import java.util.Set;
 import java.util.TreeSet;
 
-import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.FilterByteChannel;
@@ -162,7 +162,7 @@
 	 */
 	public void walk(int baseRevision, HgStatusInspector inspector) throws IOException, CancelledException, HgRuntimeException {
 		if (HgInternals.wrongRevisionIndex(baseRevision) || baseRevision == BAD_REVISION) {
-			throw new IllegalArgumentException(String.valueOf(baseRevision));
+			throw new HgInvalidRevisionException(baseRevision);
 		}
 		if (getDirstateImpl() == null) {
 				getDirstate();
@@ -346,7 +346,7 @@
 							inspector.clean(df.getPath());
 						}
 					}
-				} catch (HgException ex) {
+				} catch (HgRuntimeException ex) {
 					repo.getContext().getLog().warn(getClass(), ex, null);
 					inspector.invalid(fname, ex);
 				}
@@ -381,7 +381,7 @@
 						inspector.copied(getPathPool().path(origin), fname);
 						return;
 					}
-				} catch (HgException ex) {
+				} catch (HgInvalidFileException ex) {
 					// report failure and continue status collection
 					inspector.invalid(fname, ex);
 				}
@@ -435,7 +435,7 @@
 					} else {
 						inspector.modified(fname);
 					}
-				} catch (HgException ex) {
+				} catch (HgRuntimeException ex) {
 					repo.getContext().getLog().warn(getClass(), ex, null);
 					inspector.invalid(fname, ex);
 				}
@@ -456,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, HgInvalidFileException {
+	private boolean areTheSame(FileInfo f, HgDataFile dataFile, Nodeid revision) throws HgInvalidFileException {
 		// XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
 		ByteArrayChannel bac = new ByteArrayChannel();
 		try {
@@ -611,7 +611,7 @@
 //		final Path[] dirs = f.toArray(new Path[d.size()]);
 		if (d.isEmpty()) {
 			final Path[] files = f.toArray(new Path[f.size()]);
-			FileIterator fi = new FileListIterator(hgRepo.getWorkingDir(), files);
+			FileIterator fi = new FileListIterator(hgRepo.getContext(), hgRepo.getWorkingDir(), files);
 			return new HgWorkingCopyStatusCollector(hgRepo, fi);
 		}
 		//
@@ -648,8 +648,10 @@
 		private int index;
 		private RegularFileInfo nextFile;
 		private final boolean execCap, linkCap;
+		private final SessionContext sessionContext;
 
-		public FileListIterator(File startDir, Path... files) {
+		public FileListIterator(SessionContext ctx, File startDir, Path... files) {
+			sessionContext = ctx;
 			dir = startDir;
 			paths = files;
 			reset();
@@ -659,7 +661,7 @@
 
 		public void reset() {
 			index = -1;
-			nextFile = new RegularFileInfo(execCap, linkCap);
+			nextFile = new RegularFileInfo(sessionContext, execCap, linkCap);
 		}
 
 		public boolean hasNext() {
--- a/src/org/tmatesoft/hg/repo/Revlog.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/Revlog.java	Wed Mar 28 18:39:29 2012 +0200
@@ -16,9 +16,7 @@
  */
 package org.tmatesoft.hg.repo;
 
-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.*;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -29,7 +27,6 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ArrayHelper;
 import org.tmatesoft.hg.internal.DataAccess;
@@ -80,11 +77,19 @@
 		return repo;
 	}
 
-	public final int getRevisionCount() {
+	/**
+	 * @return total number of revisions kept in this revlog
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	public final int getRevisionCount() throws HgRuntimeException {
 		return content.revisionCount();
 	}
 	
-	public final int getLastRevision() {
+	/**
+	 * @return index of last known revision, a.k.a. {@link HgRepository#TIP}
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
+	public final int getLastRevision() throws HgRuntimeException {
 		return content.revisionCount() - 1;
 	}
 
@@ -94,18 +99,22 @@
 	 * @param revision index of the entry in this revlog, may be {@link HgRepository#TIP}
 	 * @return revision nodeid of the entry
 	 * 
-	 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog
-	 * @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 final Nodeid getRevision(int revision) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public final Nodeid getRevision(int revision) throws HgRuntimeException {
 		// XXX cache nodeids? Rather, if context.getCache(this).getRevisionMap(create == false) != null, use it
 		return Nodeid.fromBinary(content.nodeid(revision), 0);
 	}
 	
 	/**
-	 * FIXME need to be careful about (1) ordering of the revisions in the return list; (2) modifications (sorting) of the argument array 
+	 * Effective alternative to map few revision indexes to corresponding nodeids at once.
+	 * <p>Note, there are few aspects to be careful about when using this method<ul>
+	 * <li>ordering of the revisions in the return list is unspecified, it's likely won't match that of the method argument
+	 * <li>supplied array get modified (sorted)</ul>
+	 * @return list of mapped revisions in no particular order
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public final List<Nodeid> getRevisions(int... revisions) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public final List<Nodeid> getRevisions(int... revisions) throws HgRuntimeException {
 		ArrayList<Nodeid> rv = new ArrayList<Nodeid>(revisions.length);
 		Arrays.sort(revisions);
 		getRevisionsInternal(rv, revisions);
@@ -132,10 +141,9 @@
 	 * 
 	 * @param nid revision to look up 
 	 * @return revision local index in this revlog
-	 * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog  
-	 * @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 final int getRevisionIndex(Nodeid nid) throws HgInvalidControlFileException, HgInvalidRevisionException {
+	public final int getRevisionIndex(Nodeid nid) throws HgRuntimeException {
 		int revision = content.findRevisionIndex(nid);
 		if (revision == BAD_REVISION) {
 			// using toString() to identify revlog. HgDataFile.toString includes path, HgManifest and HgChangelog instances 
@@ -151,9 +159,9 @@
 	 * 
 	 * @param nodeid
 	 * @return <code>true</code> if revision is part of this revlog
-	 * @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 final boolean isKnown(Nodeid nodeid) throws HgInvalidControlFileException {
+	public final boolean isKnown(Nodeid nodeid) throws HgRuntimeException {
 		final int rn = content.findRevisionIndex(nodeid);
 		if (BAD_REVISION == rn) {
 			return false;
@@ -208,11 +216,7 @@
 			// Do the same (add file name) below
 			throw e;
 		} catch (HgInvalidControlFileException ex) {
-			throw ex;
-		} catch (HgException ex) {
-			HgInvalidControlFileException e = new HgInvalidControlFileException(ex.getClass().getSimpleName(), ex, null);
-			e.setRevisionIndex(revisionIndex);
-			throw e;
+			throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionIndex);
 		}
 	}
 
@@ -223,11 +227,10 @@
 	 * @param parentRevisions - int[2] to get local revision numbers of parents (e.g. {6, -1}), {@link HgRepository#NO_REVISION} indicates parent not set
 	 * @param parent1 - byte[20] or null, if parent's nodeid is not needed
 	 * @param parent2 - byte[20] or null, if second parent's nodeid is not needed
-	 * @throws HgInvalidRevisionException
-	 * @throws HgInvalidControlFileException FIXME EXCEPTIONS
-	 * @throws IllegalArgumentException
+	 * @throws IllegalArgumentException if passed arrays can't fit requested data
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
 	 */
-	public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) throws HgRuntimeException, IllegalArgumentException {
 		if (revision != TIP && !(revision >= 0 && revision < content.revisionCount())) {
 			throw new HgInvalidRevisionException(revision);
 		}
@@ -275,9 +278,19 @@
 			}
 		}
 	}
-	
+
+	/**
+	 * EXPERIMENTAL CODE, DO NOT USE
+	 * 
+	 * Alternative revlog iteration
+	 * 
+	 * @param start
+	 * @param end
+	 * @param inspector
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 */
 	@Experimental
-	public void walk(int start, int end, final Revlog.Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
+	public void walk(int start, int end, final Revlog.Inspector inspector) throws HgRuntimeException {
 		int lastRev = getLastRevision();
 		if (start == TIP) {
 			start = lastRev;
@@ -325,7 +338,7 @@
 	}
 	
 	/*
-	 * XXX think over if it's better to do either:
+	 * FIXME think over if it's better to do either:
 	 * pw = getChangelog().new ParentWalker(); pw.init() and pass pw instance around as needed
 	 * or
 	 * add Revlog#getParentWalker(), static class, make cons() and #init package-local, and keep SoftReference to allow walker reuse.
@@ -598,9 +611,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 {
+		public void checkFailed() throws HgRuntimeException, IOException, CancelledException {
 			if (failure == null) {
 				return;
 			}
@@ -610,8 +621,8 @@
 			if (failure instanceof CancelledException) {
 				throw (CancelledException) failure;
 			}
-			if (failure instanceof HgException) {
-				throw (HgException) failure;
+			if (failure instanceof HgRuntimeException) {
+				throw (HgRuntimeException) failure;
 			}
 			throw new HgInvalidStateException(failure.toString());
 		}
@@ -641,7 +652,7 @@
 			logFacility = log;
 		}
 		
-		protected void prepare(int revisionNumber, DataAccess da) throws HgException, IOException {
+		protected void prepare(int revisionNumber, DataAccess da) throws IOException {
 			if (offset > 0) { // save few useless reset/rewind operations
 				da.seek(offset);
 			}
@@ -689,8 +700,6 @@
 				recordFailure(ex);
 			} catch (CancelledException ex) {
 				recordFailure(ex);
-			} catch (HgException ex) {
-				recordFailure(ex);
 			}
 		}
 	}
--- a/src/org/tmatesoft/hg/util/FileWalker.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/util/FileWalker.java	Wed Mar 28 18:39:29 2012 +0200
@@ -20,6 +20,7 @@
 import java.util.LinkedList;
 import java.util.NoSuchElementException;
 
+import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.Internals;
 
 /**
@@ -36,11 +37,12 @@
 	private final LinkedList<File> fileQueue;
 	private final Path.Matcher scope;
 	private final boolean execCap, linkCap;
+	private final SessionContext sessionContext;
 	private RegularFileInfo nextFile;
 	private Path nextPath;
 
-	public FileWalker(File dir, Path.Source pathFactory) {
-		this(dir, pathFactory, null);
+	public FileWalker(SessionContext ctx, File dir, Path.Source pathFactory) {
+		this(ctx, dir, pathFactory, null);
 	}
 
 	/**
@@ -51,7 +53,8 @@
 	 * also whether directories shall be traversed or not (Paths it gets in {@link Path.Matcher#accept(Path)} may 
 	 * point to directories)   
 	 */
-	public FileWalker(File dir, Path.Source pathFactory, Path.Matcher scopeMatcher) {
+	public FileWalker(SessionContext ctx, File dir, Path.Source pathFactory, Path.Matcher scopeMatcher) {
+		sessionContext = ctx;
 		startDir = dir;
 		pathHelper = pathFactory;
 		dirQueue = new LinkedList<File>();
@@ -66,7 +69,7 @@
 		fileQueue.clear();
 		dirQueue.clear();
 		dirQueue.add(startDir);
-		nextFile = new RegularFileInfo(supportsExecFlag(), supportsLinkFlag());
+		nextFile = new RegularFileInfo(sessionContext, supportsExecFlag(), supportsLinkFlag());
 		nextPath = null;
 	}
 	
--- a/src/org/tmatesoft/hg/util/ProgressSupport.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/util/ProgressSupport.java	Wed Mar 28 18:39:29 2012 +0200
@@ -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
@@ -26,7 +26,7 @@
 
 	// -1 for unspecified?
 	public void start(int totalUnits);
-	public void worked(int units);
+	public void worked(int units); // fraction of totalUnits from #start(int)
 	// XXX have to specify whether PS implementors may expect #done regardless of job completion (i.e. in case of cancellation) 
 	public void done();
 
@@ -53,10 +53,12 @@
 	}
 	
 	class Sub implements ProgressSupport {
+		private int perChildWorkUnitMultiplier; // to multiply child ps units
+		private int perChildWorkUnitDivisor; // to scale down to parent ps units
+		private int unitsConsumed; // parent ps units consumed so far
+		private int fraction = 0; // leftovers of previous not completely consumed work units
 		private final ProgressSupport ps;
-		private int total;
-		private int units;
-		private int psUnits;
+		private final int psUnits; // total parent ps units
 
 		public Sub(ProgressSupport parent, int parentUnits) {
 			if (parent == null) {
@@ -67,23 +69,27 @@
 		}
 
 		public void start(int totalUnits) {
-			total = totalUnits;
+//			perChildWorkUnit = (psUnits*100) / totalUnits;
+			perChildWorkUnitDivisor = 10 * totalUnits;
+			perChildWorkUnitMultiplier = psUnits * perChildWorkUnitDivisor / totalUnits;
+			
 		}
 
 		public void worked(int worked) {
-			// FIXME fine-grained subprogress report. now only report at about 50% 
-			if (psUnits > 1 && units < total/2 && units+worked > total/2) {
-				ps.worked(psUnits/2);
-				psUnits -= psUnits/2;
+			int x = fraction + worked * perChildWorkUnitMultiplier;
+			int u = x / perChildWorkUnitDivisor;
+			fraction = x % perChildWorkUnitDivisor;
+			if (u > 0) {
+				ps.worked(u);
+				unitsConsumed += u;
 			}
-			units += worked;
 		}
 
 		public void done() {
-			ps.worked(psUnits);
+			ps.worked(psUnits - unitsConsumed);
 		}
 	}
-
+	
 	interface Target<T> {
 		T set(ProgressSupport ps);
 	}
--- a/src/org/tmatesoft/hg/util/RegularFileInfo.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/util/RegularFileInfo.java	Wed Mar 28 18:39:29 2012 +0200
@@ -24,6 +24,7 @@
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
 
+import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.StreamLogFacility;
 
 /**
@@ -36,14 +37,14 @@
 	private final RegularFileStats fileFlagsHelper; // null if both supportsLink and supportExec are false
 	private File file;
 	
-	public RegularFileInfo() {
-		this(false, false);
+	public RegularFileInfo(SessionContext ctx) {
+		this(ctx, false, false);
 	}
-	public RegularFileInfo(boolean supportExecFlag, boolean supportSymlink) {
+	public RegularFileInfo(SessionContext ctx, boolean supportExecFlag, boolean supportSymlink) {
 		supportsLink = supportSymlink;
 		supportsExec = supportExecFlag;
 		if (supportSymlink || supportExecFlag) {
-			fileFlagsHelper = new RegularFileStats();
+			fileFlagsHelper = new RegularFileStats(ctx);
 		} else  {
 			fileFlagsHelper = null;
 		}
--- a/src/org/tmatesoft/hg/util/RegularFileStats.java	Wed Mar 28 15:42:15 2012 +0200
+++ b/src/org/tmatesoft/hg/util/RegularFileStats.java	Wed Mar 28 18:39:29 2012 +0200
@@ -30,6 +30,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.ProcessExecHelper;
 
@@ -54,6 +55,7 @@
 	private final List<String> command;
 	private final ProcessExecHelper execHelper;
 	private final Matcher linkMatcher, execMatcher;
+	private final SessionContext sessionContext;
 	
 	
 	// directory name to (short link name -> link target)
@@ -62,7 +64,8 @@
 	private Map<String, Set<String>> dir2execs = new TreeMap<String, Set<String>>();
 
 
-	RegularFileStats() {
+	RegularFileStats(SessionContext ctx) {
+		sessionContext = ctx;
 		if (Internals.runningOnWindows()) {
 			// XXX this implementation is not yet tested against any Windows repository, 
 			// only against sample dir listings. As long as Mercurial doesn't handle Windows
@@ -88,7 +91,14 @@
 		execHelper = new ProcessExecHelper();
 	}
 
+	/**
+	 * Fails silently indicating false for both x and l in case interaction with file system failed
+	 * @param f file to check, doesn't need to exist
+	 */
 	public void init(File f) {
+		isExec = isSymlink = false;
+		symlinkValue = null;
+		//
 		// can't check isFile because Java would say false for a symlink with non-existing target
 		if (f.isDirectory()) {
 			// perhaps, shall just collect stats for all files and set false to exec/link flags?
@@ -122,22 +132,22 @@
 				}
 				dir2links.put(dirName, links);
 				dir2execs.put(dirName, execs);
+				isExec = execs.contains(fileName);
+				isSymlink = links.containsKey(fileName);
+				if (isSymlink) {
+					symlinkValue = links.get(fileName);
+				} else {
+					symlinkValue = null;
+				}
 			} catch (InterruptedException ex) {
+				sessionContext.getLog().warn(getClass(), ex, String.format("Failed to detect flags for %s", f));
 				// try again? ensure not too long? stop right away?
-				// FIXME EXCEPTIONS
-				throw new RuntimeException();
+				// IGNORE, keep isExec and isSymlink false
 			} catch (IOException ex) {
-				// FIXME EXCEPTIONS perhaps, fail silently indicating false for both x and l?
-				throw new RuntimeException();
+				sessionContext.getLog().warn(getClass(), ex, String.format("Failed to detect flags for %s", f));
+				// IGNORE, keep isExec and isSymlink false
 			}
 		}
-		isExec = execs.contains(fileName);
-		isSymlink = links.containsKey(fileName);
-		if (isSymlink) {
-			symlinkValue = links.get(fileName);
-		} else {
-			symlinkValue = null;
-		}
 	}
 
 	public boolean isExecutable() {