changeset 396:0ae53c32ecef

Straighten out exceptions thrown when file access failed - three is too much
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 23 Feb 2012 01:06:24 +0100
parents 4732fffff58a
children c76c57f6b961
files build.xml design.txt src/org/tmatesoft/hg/core/HgCatCommand.java src/org/tmatesoft/hg/core/HgDataStreamException.java src/org/tmatesoft/hg/core/HgException.java src/org/tmatesoft/hg/core/HgFileInformer.java src/org/tmatesoft/hg/core/HgFileRevision.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/internal/DataAccessProvider.java src/org/tmatesoft/hg/internal/RevlogStream.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/repo/HgStatusCollector.java
diffstat 16 files changed, 167 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Wed Feb 22 16:54:34 2012 +0100
+++ b/build.xml	Thu Feb 23 01:06:24 2012 +0100
@@ -145,6 +145,7 @@
 		<jar destfile="${hg4j-tests.jar}">
 			<fileset dir="bin" includes="org/tmatesoft/hg/test/**"/>
 			<fileset file="COPYING"/>
+			<!-- XXX perhaps, shall include test-data as well? -->
 		</jar>
 	</target>
 
--- a/design.txt	Wed Feb 22 16:54:34 2012 +0100
+++ b/design.txt	Thu Feb 23 01:06:24 2012 +0100
@@ -44,6 +44,7 @@
 
 delta merge
 DataAccess - collect debug info (buffer misses, file size/total read operations) to find out better strategy to buffer size detection. Compare performance.
+RevlogStream - inflater buffer (and other buffers) size may be too small for repositories out there (i.e. inflater buffer of 512 bytes for 200k revision)  
 
 
 Parameterize StatusCollector to produce copy only when needed. And HgDataFile.metadata perhaps should be moved to cacheable place?
--- a/src/org/tmatesoft/hg/core/HgCatCommand.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgCatCommand.java	Thu Feb 23 01:06:24 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 static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
@@ -132,10 +131,17 @@
 	 * Runs the command with current set of parameters and pipes data to provided sink.
 	 * 
 	 * @param sink output channel to write data to.
-	 * @throws HgDataStreamException 
+	 * 
+	 * @throws HgBadArgumentException if no target file node found 
+	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
+	 * @throws HgInvalidFileException if access to file in working directory failed
+	 * @throws HgException in case of some other library issue 
+	 * @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 IllegalArgumentException when command arguments are incomplete or wrong
 	 */
-	public void execute(ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException {
+	public void execute(ByteChannel sink) throws HgException, CancelledException {
+		// XXX perhaps, IAE together with HgBadArgumentException is not the best idea
 		if (revisionIndex == BAD_REVISION && revision == null && cset == null) {
 			throw new IllegalArgumentException("File revision, corresponing local number, or a changset nodeid shall be specified");
 		}
@@ -147,7 +153,8 @@
 		}
 		HgDataFile dataFile = repo.getFileNode(file);
 		if (!dataFile.exists()) {
-			throw new HgDataStreamException(file, new FileNotFoundException(file.toString()));
+			// TODO may benefit from repo.getStoragePath to print revlog location in addition to human-friendly file path 
+			throw new HgBadArgumentException(String.format("File %s not found in the repository", file), null).setFileName(file);
 		}
 		int revToExtract;
 		if (cset != null) {
--- a/src/org/tmatesoft/hg/core/HgDataStreamException.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgDataStreamException.java	Thu Feb 23 01:06:24 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
@@ -21,12 +21,14 @@
 
 /**
  * Any erroneous state with @link {@link HgDataFile} input/output, read/write operations
- * FIXME/REVISIT if HgInvalidControlFileExceptio and HgInvalidFileException is not sufficient? Is there real need for all 3?  
+ * 
+ * @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) {
--- a/src/org/tmatesoft/hg/core/HgException.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgException.java	Thu Feb 23 01:06:24 2012 +0100
@@ -61,12 +61,15 @@
 		return getRevisionIndex();
 	}
 	
-
 	public HgException setRevisionIndex(int rev) {
 		revNumber = rev;
 		return this;
 	}
 	
+	public boolean isRevisionIndexSet() {
+		return revNumber != BAD_REVISION;
+	}
+
 	/**
 	 * @deprecated use {@link #setRevisionIndex(int)}
 	 */
@@ -86,6 +89,10 @@
 		revision = r;
 		return this;
 	}
+	
+	public boolean isRevisionSet() {
+		return revision != null;
+	}
 
 	/**
 	 * @return non-null only if file name was set at construction time
--- a/src/org/tmatesoft/hg/core/HgFileInformer.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgFileInformer.java	Thu Feb 23 01:06:24 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
@@ -18,7 +18,6 @@
 
 import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.Status;
@@ -122,6 +121,7 @@
 			return checkResult;
 		}
 		Nodeid toExtract = null;
+		String phaseMsg = "Extract manifest revision failed";
 		try {
 			if (cachedManifest == null) {
 				int csetRev = repo.getChangelog().getRevisionIndex(cset);
@@ -130,6 +130,7 @@
 				// cachedManifest shall be meaningful - changelog.getRevisionIndex() above ensures we've got version that exists.
 			}
 			toExtract = cachedManifest.nodeid(file);
+			phaseMsg = "Follow copy/rename failed";
 			if (toExtract == null && followRenames) {
 				while (toExtract == null && dataFile.isCopy()) {
 					renamed = true;
@@ -138,12 +139,8 @@
 					toExtract = cachedManifest.nodeid(file);
 				}
 			}
-		} catch (HgInvalidControlFileException ex) {
-			checkResult = new Status(Status.Kind.ERROR, "", ex);
-			return checkResult;
-		} catch (HgDataStreamException ex) {
-			checkResult = new Status(Status.Kind.ERROR, "Follow copy/rename failed", ex);
-			HgInternals.getContext(repo).getLog().warn(getClass(), ex, checkResult.getMessage());
+		} catch (HgException ex) {
+			checkResult = new Status(Status.Kind.ERROR, phaseMsg, ex);
 			return checkResult;
 		}
 		if (toExtract != null) {
--- a/src/org/tmatesoft/hg/core/HgFileRevision.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgFileRevision.java	Thu Feb 23 01:06:24 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
@@ -99,13 +99,13 @@
 		return parents;
 	}
 
-	public void putContentTo(ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException {
+	public void putContentTo(ByteChannel sink) throws HgException, CancelledException {
 		HgDataFile fn = repo.getFileNode(path);
 		int revisionIndex = fn.getRevisionIndex(revision);
 		fn.contentWithFilters(revisionIndex, sink);
 	}
 
-	private void checkCopy() throws HgInvalidControlFileException, HgDataStreamException {
+	private void checkCopy() throws HgException {
 		HgDataFile fn = repo.getFileNode(path);
 		if (fn.isCopy()) {
 			if (fn.getRevision(0).equals(revision)) {
--- a/src/org/tmatesoft/hg/core/HgInvalidControlFileException.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgInvalidControlFileException.java	Thu Feb 23 01:06:24 2012 +0100
@@ -19,12 +19,15 @@
 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.
  */
@@ -44,13 +47,16 @@
 	
 	@Override
 	public HgInvalidControlFileException setRevision(Nodeid r) {
-		super.setRevision(r);
-		return this;
+		return (HgInvalidControlFileException) super.setRevision(r);
 	}
 
 	@Override
-	public HgException setRevisionIndex(int rev) {
-		super.setRevisionIndex(rev);
-		return this;
+	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	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgInvalidFileException.java	Thu Feb 23 01:06:24 2012 +0100
@@ -64,6 +64,8 @@
 	protected void appendDetails(StringBuilder sb) {
 		super.appendDetails(sb);
 		if (localFile != null) {
+			sb.append(';');
+			sb.append(' ');
 			sb.append(" file:");
 			sb.append(localFile.getPath());
 			sb.append(',');
--- a/src/org/tmatesoft/hg/core/HgInvalidRevisionException.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgInvalidRevisionException.java	Thu Feb 23 01:06:24 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,8 +16,9 @@
  */
 package org.tmatesoft.hg.core;
 
+import static org.tmatesoft.hg.repo.HgRepository.*;
+
 import org.tmatesoft.hg.internal.Experimental;
-import org.tmatesoft.hg.repo.HgRepository;
 
 /**
  * Use of revision or revision local index that is not valid for a given revlog.
@@ -29,7 +30,7 @@
 @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;
+	private Integer revIdx = BAD_REVISION;
 	// next two make sense only when revIdx is present
 	private int rangeLeftBoundary = -1, rangeRightBoundary = -1;
 
@@ -75,7 +76,7 @@
 		revIdx = revisionIndex;
 		return this;
 	}
-
+	
 	public HgInvalidRevisionException setRevisionIndex(int revisionIndex, int rangeLeft, int rangeRight) {
 		revIdx = revisionIndex;
 		rangeLeftBoundary = rangeLeft;
@@ -83,6 +84,14 @@
 		return this;
 	}
 
+	public boolean isRevisionSet() {
+		return rev != null;
+	}
+	
+	public boolean isRevisionIndexSet() {
+		return revIdx != BAD_REVISION;
+	}
+
 	@Override
 	public String getMessage() {
 		String msg = super.getMessage();
@@ -98,9 +107,9 @@
 		if (revIdx != null) {
 			String sr;
 			switch (revIdx) {
-			case HgRepository.BAD_REVISION : sr = "UNKNOWN"; break;
-			case HgRepository.TIP : sr = "TIP"; break;
-			case HgRepository.WORKING_COPY: sr = "WORKING-COPY"; break;
+			case BAD_REVISION : sr = "UNKNOWN"; break;
+			case TIP : sr = "TIP"; break;
+			case WORKING_COPY: sr = "WORKING-COPY"; break;
 			default : sr = revIdx.toString();
 			}
 			if (rangeLeftBoundary != -1 || rangeRightBoundary != -1) {
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgLogCommand.java	Thu Feb 23 01:06:24 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
@@ -186,15 +186,12 @@
 	/**
 	 * Similar to {@link #execute(org.tmatesoft.hg.repo.RawChangeset.Inspector)}, collects and return result as a list.
 	 */
-	public List<HgChangeset> execute() throws HgDataStreamException {
+	public List<HgChangeset> execute() throws HgException {
 		CollectHandler collector = new CollectHandler();
 		try {
 			execute(collector);
-		} catch (HgException ex) {
+		} catch (CancelledException ex) {
 			// can't happen as long as our CollectHandler doesn't throw any exception
-			throw new HgBadStateException(ex.getCause());
-		} catch (CancelledException ex) {
-			// can't happen, see above
 			throw new HgBadStateException(ex);
 		}
 		return collector.getChanges();
--- a/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Thu Feb 23 01:06:24 2012 +0100
@@ -317,6 +317,7 @@
 				try {
 					fileChannel.close();
 				} catch (IOException ex) {
+					// FIXME/TODO log facility can be obtained from session context 
 					StreamLogFacility.newDefault().debug(getClass(), ex, null);
 				}
 				fileChannel = null;
--- a/src/org/tmatesoft/hg/internal/RevlogStream.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/internal/RevlogStream.java	Thu Feb 23 01:06:24 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
@@ -56,6 +56,7 @@
 	private int[] baseRevisions;
 	private boolean inline = false;
 	private final File indexFile;
+	private File dataFile;
 	private final DataAccessProvider dataAccess;
 
 	// if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP.
@@ -71,11 +72,34 @@
 	}
 
 	/*package*/ DataAccess getDataStream() {
-		final String indexName = indexFile.getName();
-		File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d");
-		return dataAccess.create(dataFile);
+		return dataAccess.create(getDataFile());
 	}
 	
+	/**
+	 * Constructs file object that corresponds to .d revlog counterpart. 
+	 * Note, it's caller responsibility to ensure this file makes any sense (i.e. check {@link #inline} attribute)
+	 */
+	private File getDataFile() {
+		if (dataFile == null) {
+			final String indexName = indexFile.getName();
+			dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d");
+		}
+		return dataFile;
+	}
+	
+	// initialize exception with the file where revlog structure information comes from
+	public HgInvalidControlFileException initWithIndexFile(HgInvalidControlFileException ex) {
+		return ex.setFile(indexFile);
+	}
+
+	// initialize exception with the file where revlog data comes from
+	public HgInvalidControlFileException initWithDataFile(HgInvalidControlFileException ex) {
+		// exceptions are usually raised after read attepmt, hence inline shall be initialized
+		// although honest approach is to call #initOutline() first
+		return ex.setFile(inline ? indexFile : getDataFile());
+	}
+
+	
 	public int revisionCount() {
 		initOutline();
 		return baseRevisions.length;
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Thu Feb 23 01:06:24 2012 +0100
@@ -31,9 +31,9 @@
 import java.util.Collections;
 import java.util.List;
 
-import org.tmatesoft.hg.core.HgDataStreamException;
 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;
@@ -93,23 +93,27 @@
 	 * @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  
-	 * @throws HgDataStreamException if attempt to access file metadata failed
+	 * @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
 	 */
-	public int length(Nodeid nodeid) throws HgDataStreamException, HgInvalidControlFileException, HgInvalidRevisionException {
-		return length(getRevisionIndex(nodeid));
+	public int length(Nodeid nodeid) throws HgInvalidControlFileException, HgInvalidRevisionException {
+		try {
+			return length(getRevisionIndex(nodeid));
+		} catch (HgInvalidControlFileException ex) {
+			throw ex.isRevisionSet() ? ex : ex.setRevision(nodeid);
+		} catch (HgInvalidRevisionException ex) {
+			throw ex.isRevisionSet() ? ex : ex.setRevision(nodeid);
+		}
 	}
 	
 	/**
  	 * @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
-	 * @throws HgDataStreamException if attempt to access file metadata failed
+	 * @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
 	 */
-	public int length(int fileRevisionIndex) throws HgDataStreamException, HgInvalidControlFileException, HgInvalidRevisionException {
-		// TODO support WORKING_COPY constant
+	public int length(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException {
+		// FIXME support WORKING_COPY constant
 		if (metadata == null || !metadata.checked(fileRevisionIndex)) {
 			checkAndRecordMetadata(fileRevisionIndex);
 		}
@@ -126,11 +130,12 @@
 	 * as if it would be refreshed in the working copy, i.e. its corresponding revision 
 	 * (XXX according to dirstate? file tip?) is read from the repository, and filters repo -> working copy get applied.
 	 *     
-	 * @param sink where to pipe content to
-	 * @throws HgDataStreamException to indicate troubles reading repository file
+	 * @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
 	 */
-	public void workingCopy(ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException {
+	public void workingCopy(ByteChannel sink) throws HgException, CancelledException {
 		File f = getRepo().getFile(this);
 		if (f.exists()) {
 			final CancelSupport cs = CancelSupport.Factory.get(sink);
@@ -150,7 +155,7 @@
 					buf.compact();
 				}
 			} catch (IOException ex) {
-				throw new HgDataStreamException(getPath(), ex);
+				throw new HgInvalidFileException("Working copy read failed", ex, f);
 			} finally {
 				progress.done();
 				if (fc != null) {
@@ -165,7 +170,8 @@
 			final Pair<Nodeid, Nodeid> wcParents = getRepo().getWorkingCopyParents();
 			Nodeid p = wcParents.first().isNull() ? wcParents.second() : wcParents.first();
 			if (p.isNull()) {
-				// no dirstate parents - no content 
+				// no dirstate parents - no content
+				// XXX what if it's repository with no dirstate? Shall I use TIP then?
 				return;
 			}
 			final HgChangelog clog = getRepo().getChangelog();
@@ -184,49 +190,39 @@
 		}
 	}
 	
-//	public void content(int revision, ByteChannel sink, boolean applyFilters) throws HgDataStreamException, IOException, CancelledException {
-//		byte[] content = content(revision);
-//		final CancelSupport cancelSupport = CancelSupport.Factory.get(sink);
-//		final ProgressSupport progressSupport = ProgressSupport.Factory.get(sink);
-//		ByteBuffer buf = ByteBuffer.allocate(512);
-//		int left = content.length;
-//		progressSupport.start(left);
-//		int offset = 0;
-//		cancelSupport.checkCancelled();
-//		ByteChannel _sink = applyFilters ? new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())) : sink;
-//		do {
-//			buf.put(content, offset, Math.min(left, buf.remaining()));
-//			buf.flip();
-//			cancelSupport.checkCancelled();
-//			// XXX I may not rely on returned number of bytes but track change in buf position instead.
-//			int consumed = _sink.write(buf);
-//			buf.compact();
-//			offset += consumed;
-//			left -= consumed;
-//			progressSupport.worked(consumed);
-//		} while (left > 0);
-//		progressSupport.done(); // XXX shall specify whether #done() is invoked always or only if completed successfully.
-//	}
-	
-	/*XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves?*/
-	public void contentWithFilters(int revision, ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException, HgInvalidRevisionException {
-		if (revision == WORKING_COPY) {
+	/**
+	 * Access content of a file revision
+	 * XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves?
+	 * 
+ 	 * @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>)
+	 */
+	public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException {
+		if (fileRevisionIndex == WORKING_COPY) {
 			workingCopy(sink); // pass un-mangled sink
 		} else {
-			content(revision, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())));
+			content(fileRevisionIndex, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())));
 		}
 	}
 
 	/**
+	 * Retrieve content of specific revision. Content is provided as is, without any filters (e.g. keywords, eol, etc.) applied.
+	 * For filtered content, use {@link #contentWithFilters(int, ByteChannel)}. 
 	 * 
  	 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. 
-	 * @param sink
-	 * @throws HgDataStreamException FIXME EXCEPTIONS
+	 * @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
+	 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>)
 	 */
-	public void content(int fileRevisionIndex, ByteChannel sink) throws HgDataStreamException, HgInvalidControlFileException, CancelledException, HgInvalidRevisionException {
+	public void content(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException {
 		// 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) {
@@ -255,19 +251,22 @@
 			insp = new ContentPipe(sink, metadata.dataOffset(fileRevisionIndex), lf);
 		} else {
 			// do not know if there's metadata
-			insp = new MetadataInspector(metadata, lf, getPath(), new ContentPipe(sink, 0, lf));
+			insp = new MetadataInspector(metadata, lf, new ContentPipe(sink, 0, lf));
 		}
 		insp.checkCancelled();
 		super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp);
 		try {
 			insp.checkFailed(); // XXX is there real need to throw IOException from ContentPipe?
-		} catch (HgDataStreamException ex) {
-			throw ex;
+		} catch (HgInvalidControlFileException ex) {
+			ex = ex.setFileName(getPath());
+			throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(fileRevisionIndex);
 		} catch (IOException ex) {
-			throw new HgDataStreamException(getPath(), ex).setRevisionIndex(fileRevisionIndex);
+			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
-			throw new HgDataStreamException(getPath(), ex.getClass().getName(), ex);
+			HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null);
+			throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex);
 		}
 	}
 	
@@ -460,11 +459,11 @@
 	}
 
 	/**
-	 * 
-	 * @return
-	 * @throws HgDataStreamException if attempt to access file metadata failed
+	 * 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
 	 */
-	public boolean isCopy() throws HgDataStreamException {
+	public boolean isCopy() throws HgInvalidControlFileException {
 		if (metadata == null || !metadata.checked(0)) {
 			checkAndRecordMetadata(0);
 		}
@@ -478,17 +477,17 @@
 	 * Get name of the file this one was copied from.
 	 * 
 	 * @return name of the file origin
-	 * @throws HgDataStreamException if attempt to access file metadata failed
+	 * @throws HgInvalidControlFileException if access to revlog or file metadata failed
 	 * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false)
 	 */
-	public Path getCopySourceName() throws HgDataStreamException {
+	public Path getCopySourceName() throws HgInvalidControlFileException {
 		if (isCopy()) {
 			return Path.create(metadata.find(0, "copy"));
 		}
 		throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?)
 	}
 	
-	public Nodeid getCopySourceRevision() throws HgDataStreamException {
+	public Nodeid getCopySourceRevision() throws HgInvalidControlFileException {
 		if (isCopy()) {
 			return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid
 		}
@@ -504,7 +503,7 @@
 		return sb.toString();
 	}
 	
-	private void checkAndRecordMetadata(int localRev) throws HgDataStreamException {
+	private void checkAndRecordMetadata(int localRev) throws HgInvalidControlFileException {
 		// content() always initializes metadata.
 		// FIXME this is expensive way to find out metadata, distinct RevlogStream.Iterator would be better.
 		// Alternatively, may parameterize MetadataContentPipe to do prepare only.
@@ -520,7 +519,10 @@
 		} catch (CancelledException ex) {
 			// it's ok, we did that
 		} catch (HgInvalidControlFileException ex) {
-			throw new HgDataStreamException(getPath(), 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));
 		}
 	}
 
@@ -616,12 +618,10 @@
 	private static class MetadataInspector extends ErrorHandlingInspector implements RevlogStream.Inspector {
 		private final Metadata metadata;
 		private final RevlogStream.Inspector delegate;
-		private final Path fname; // need these only for error reporting
 		private final LogFacility log;
 
-		public MetadataInspector(Metadata _metadata, LogFacility logFacility, Path file, RevlogStream.Inspector chain) {
+		public MetadataInspector(Metadata _metadata, LogFacility logFacility, RevlogStream.Inspector chain) {
 			metadata = _metadata;
-			fname = file;
 			log = logFacility;
 			delegate = chain;
 			setCancelSupport(CancelSupport.Factory.get(chain));
@@ -648,12 +648,12 @@
 				}
 			} catch (IOException ex) {
 				recordFailure(ex);
-			} catch (HgDataStreamException ex) {
-				recordFailure(ex.setRevisionIndex(revisionNumber));
+			} catch (HgInvalidControlFileException ex) {
+				recordFailure(ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionNumber));
 			}
 		}
 
-		private int parseMetadata(DataAccess data, final int daLength, ArrayList<MetadataEntry> _metadata) throws IOException, HgDataStreamException {
+		private int parseMetadata(DataAccess data, final int daLength, ArrayList<MetadataEntry> _metadata) throws IOException, HgInvalidControlFileException {
 			int lastEntryStart = 2;
 			int lastColon = -1;
 			// XXX in fact, need smth like ByteArrayBuilder, similar to StringBuilder,
@@ -705,7 +705,7 @@
 			// data.isEmpty is not reliable, renamed files of size==0 keep only metadata
 			if (!metadataIsComplete) {
 				// XXX perhaps, worth a testcase (empty file, renamed, read or ask ifCopy
-				throw new HgDataStreamException(fname, "Metadata is not closed properly", null);
+				throw new HgInvalidControlFileException("Metadata is not closed properly", null, null);
 			}
 			return lastEntryStart;
 		}
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Thu Feb 23 01:06:24 2012 +0100
@@ -25,7 +25,7 @@
 import java.util.HashMap;
 import java.util.List;
 
-import org.tmatesoft.hg.core.HgDataStreamException;
+import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.HgInvalidControlFileException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
@@ -164,7 +164,7 @@
 					} catch (CancelledException ex) {
 						 // IGNORE, can't happen, we did not configure cancellation
 						getContext().getLog().debug(getClass(), ex, null);
-					} catch (HgDataStreamException ex) {
+					} catch (HgException ex) {
 						getContext().getLog().error(getClass(), ex, null);
 						// FIXME need to react
 					} catch (IOException ex) {
--- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Wed Feb 22 16:54:34 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Thu Feb 23 01:06:24 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
@@ -28,7 +28,6 @@
 import java.util.TreeSet;
 
 import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgDataStreamException;
 import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.HgInvalidControlFileException;
 import org.tmatesoft.hg.core.Nodeid;
@@ -290,11 +289,11 @@
 		return rv;
 	}
 	
-	/*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection<Path> originals, int originalChangelogRevision) throws HgDataStreamException, HgInvalidControlFileException {
+	/*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection<Path> originals, int originalChangelogRevision) throws HgException {
 		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 HgDataStreamException(fname, msg, null).setRevisionIndex(originalChangelogRevision);
+			throw new HgException(msg).setFileName(fname).setRevisionIndex(originalChangelogRevision);
 		}
 		while (df.isCopy()) {
 			Path original = df.getCopySourceName();