changeset 403:2747b0723867

FIXMEs: work on exceptions and javadoc
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 05 Mar 2012 14:50:51 +0100
parents 1fcc7f7b6d65
children 866fc3b597a0
files 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/HgChangeset.java src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java src/org/tmatesoft/hg/core/HgException.java src/org/tmatesoft/hg/core/HgInvalidFileException.java src/org/tmatesoft/hg/core/HgLogCommand.java src/org/tmatesoft/hg/internal/ExceptionInfo.java src/org/tmatesoft/hg/repo/HgInternals.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/HgStatusCollector.java src/org/tmatesoft/hg/repo/HgSubrepoLocation.java src/org/tmatesoft/hg/repo/Revlog.java
diffstat 16 files changed, 399 insertions(+), 179 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/ChangesetTransformer.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/core/ChangesetTransformer.java	Mon Mar 05 14:50:51 2012 +0100
@@ -108,7 +108,8 @@
 			changeset = new HgChangeset(statusCollector, pp);
 			changeset.setParentHelper(pw);
 		}
-		
+
+		// FIXME document instance reuse policy
 		HgChangeset handle(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
 			changeset.init(revisionNumber, nodeid, cset);
 			return changeset;
--- a/src/org/tmatesoft/hg/core/HgBadStateException.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgBadStateException.java	Mon Mar 05 14:50:51 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,6 +18,7 @@
 
 /**
  * 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.
@@ -25,11 +26,6 @@
 @SuppressWarnings("serial")
 public class HgBadStateException extends RuntimeException {
 
-	// FIXME quick-n-dirty fix, don't allow exceptions without a cause
-	public HgBadStateException() {
-		super("Internal error");
-	}
-
 	public HgBadStateException(String message) {
 		super(message);
 	}
--- a/src/org/tmatesoft/hg/core/HgCallbackTargetException.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgCallbackTargetException.java	Mon Mar 05 14:50:51 2012 +0100
@@ -67,7 +67,7 @@
 		sb.append("Original exception thrown: ");
 		sb.append(getCause().getClass().getName());
 		sb.append(" at ");
-		appendDetails(sb);
+		extras.appendDetails(sb);
 		return sb.toString();
 	}
 
--- a/src/org/tmatesoft/hg/core/HgChangeset.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgChangeset.java	Mon Mar 05 14:50:51 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
@@ -78,18 +78,52 @@
 		}
 	}
 
+	/**
+	 * Index of the changeset in local repository. Note, this number is relevant only for local repositories/operations, use 
+	 * {@link Nodeid nodeid} to uniquely identify a revision.
+	 *   
+	 * @return index of the changeset revision
+	 */
+	public int getRevisionIndex() {
+		return revNumber;
+	}
+
+	/**
+	 * @deprecated use {@link #getRevisionIndex()}
+	 */
+	@Deprecated
 	public int getRevision() {
 		return revNumber;
 	}
+
+	/**
+	 * Unique identity of this changeset revision
+	 * @return revision identifier, never <code>null</code>
+	 */
 	public Nodeid getNodeid() {
 		return nodeid;
 	}
+
+	/**
+	 * Name of the user who made this commit
+	 * @return author of the commit, never <code>null</code>
+	 */
 	public String getUser() {
 		return changeset.user();
 	}
+	
+	/**
+	 * Commit description
+	 * @return content of the corresponding field in changeset record; empty string if none specified.
+	 */
 	public String getComment() {
 		return changeset.comment();
 	}
+
+	/**
+	 * Name of the branch this commit was made in. Returns "default" for main branch.
+	 * @return name of the branch, non-empty string
+	 */
 	public String getBranch() {
 		return changeset.branch();
 	}
@@ -100,10 +134,26 @@
 	public HgDate getDate() {
 		return new HgDate(changeset.date().getTime(), changeset.timezone());
 	}
+
+	/**
+	 * Indicates revision of manifest that tracks state of repository at the moment of this commit.
+	 * Note, may be {@link Nodeid#NULL} in certain scenarios (e.g. first changeset in an empty repository, usually by bogus tools)
+	 *  
+	 * @return revision identifier, never <code>null</code>
+	 */
 	public Nodeid getManifestRevision() {
 		return changeset.manifest();
 	}
 
+	/**
+	 * Lists names of files affected by this commit, as recorded in the changeset itself. Unlike {@link #getAddedFiles()}, 
+	 * {@link #getModifiedFiles()} and {@link #getRemovedFiles()}, this method doesn't analyze actual changes done 
+	 * in the commit, rather extracts value from the changeset record.
+	 * 
+	 * List returned by this method may be empty, while aforementioned methods may produce non-empty result.
+	 *   
+	 * @return list of filenames, never <code>null</code>
+	 */
 	public List<Path> getAffectedFiles() {
 		// reports files as recorded in changelog. Note, merge revisions may have no
 		// files listed, and thus this method would return empty list, while
@@ -116,21 +166,42 @@
 		return rv;
 	}
 
-	public List<HgFileRevision> getModifiedFiles() throws HgInvalidControlFileException {
+	/**
+	 * 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 
+	 */
+	public List<HgFileRevision> getModifiedFiles() throws HgException {
 		if (modifiedFiles == null) {
 			initFileChanges();
 		}
 		return modifiedFiles;
 	}
 
-	public List<HgFileRevision> getAddedFiles() throws HgInvalidControlFileException {
+	/**
+	 * 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 
+	 */
+	public List<HgFileRevision> getAddedFiles() throws HgException {
 		if (addedFiles == null) {
 			initFileChanges();
 		}
 		return addedFiles;
 	}
 
-	public List<Path> getRemovedFiles() throws HgInvalidControlFileException {
+	/**
+	 * 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 
+	 */
+	public List<Path> getRemovedFiles() throws HgException {
 		if (deletedFiles == null) {
 			initFileChanges();
 		}
@@ -175,6 +246,9 @@
 		return Nodeid.fromBinary(parent2, 0);
 	}
 
+	/**
+	 * Create a copy of this changeset 
+	 */
 	@Override
 	public HgChangeset clone() {
 		try {
@@ -186,7 +260,7 @@
 		}
 	}
 
-	private /*synchronized*/ void initFileChanges() throws HgInvalidControlFileException {
+	private /*synchronized*/ void initFileChanges() throws HgException {
 		ArrayList<Path> deleted = new ArrayList<Path>();
 		ArrayList<HgFileRevision> modified = new ArrayList<HgFileRevision>();
 		ArrayList<HgFileRevision> added = new ArrayList<HgFileRevision>();
@@ -196,7 +270,7 @@
 		for (Path s : r.getModified()) {
 			Nodeid nid = r.nodeidAfterChange(s);
 			if (nid == null) {
-				throw new HgBadStateException();
+				throw new HgException(String.format("For the file %s recorded as modified couldn't find revision after change", s));
 			}
 			modified.add(new HgFileRevision(repo, nid, s, null));
 		}
@@ -204,7 +278,7 @@
 		for (Path s : r.getAdded()) {
 			Nodeid nid = r.nodeidAfterChange(s);
 			if (nid == null) {
-				throw new HgBadStateException();
+				throw new HgException(String.format("For the file %s recorded as added couldn't find revision after change", s));
 			}
 			added.add(new HgFileRevision(repo, nid, s, copied.get(s)));
 		}
--- a/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java	Mon Mar 05 14:50:51 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
@@ -22,7 +22,10 @@
 import org.tmatesoft.hg.util.Pair;
 
 /**
- *
+ * Handler to iterate file history (generally, any revlog) with access to parent-child relations between changesets.
+ * 
+ * @see HgLogCommand#execute(HgChangesetTreeHandler)
+ * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
@@ -30,55 +33,60 @@
 	/**
 	 * @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 CancelledException if execution of the operation was cancelled
 	 */
-	public void next(HgChangesetTreeHandler.TreeElement entry) throws HgCallbackTargetException.Wrap, CancelledException;
+	public void next(HgChangesetTreeHandler.TreeElement entry) throws HgException, HgCallbackTargetException.Wrap, 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();
+		public Nodeid fileRevision() throws HgException;
 
 		/**
 		 * @return changeset associated with the current revision
-		 * @throws HgException indicates failure dealing with Mercurial data
+		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
 		public HgChangeset changeset() throws HgException;
 
 		/**
 		 * Lightweight alternative to {@link #changeset()}, identifies changeset in which current file node has been modified 
-		 * @return changeset {@link Nodeid} 
+		 * @return changeset {@link Nodeid revision} 
+		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public Nodeid changesetRevision();
+		public Nodeid changesetRevision() throws HgException;
 
 		/**
 		 * 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 indicates failure dealing with Mercurial data
+		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
 		public Pair<HgChangeset, HgChangeset> parents() throws HgException;
 		
 		/**
 		 * 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();
+		public Pair<Nodeid, Nodeid> parentRevisions() throws HgException;
 
 		/**
 		 * 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 indicates failure dealing with Mercurial data
+		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
 		public Collection<HgChangeset> children() throws HgException;
 
 		/**
 		 * Lightweight alternative to {@link #children()}.
 		 * @return never <code>null</code>
+		 * @throws HgException to indicate failure dealing with Mercurial data
 		 */
-		public Collection<Nodeid> childRevisions();
+		public Collection<Nodeid> childRevisions() throws HgException;
 	}
 }
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/HgException.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgException.java	Mon Mar 05 14:50:51 2012 +0100
@@ -16,8 +16,7 @@
  */
 package org.tmatesoft.hg.core;
 
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-
+import org.tmatesoft.hg.internal.ExceptionInfo;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Path;
 
@@ -29,10 +28,8 @@
  */
 @SuppressWarnings("serial")
 public class HgException extends Exception {
-
-	protected int revNumber = BAD_REVISION;
-	protected Nodeid revision;
-	protected Path filename;
+	
+	protected final ExceptionInfo<HgException> extras = new ExceptionInfo<HgException>(this);
 
 	public HgException(String reason) {
 		super(reason);
@@ -50,7 +47,7 @@
 	 * @return not {@link HgRepository#BAD_REVISION} only when revision index was supplied at the construction time
 	 */
 	public int getRevisionIndex() {
-		return revNumber;
+		return extras.getRevisionIndex();
 	}
 
 	/**
@@ -62,12 +59,11 @@
 	}
 	
 	public HgException setRevisionIndex(int rev) {
-		revNumber = rev;
-		return this;
+		return extras.setRevisionIndex(rev);
 	}
 	
 	public boolean isRevisionIndexSet() {
-		return revNumber != BAD_REVISION;
+		return extras.isRevisionIndexSet();
 	}
 
 	/**
@@ -82,48 +78,26 @@
 	 * @return non-null only when revision was supplied at construction time
 	 */
 	public Nodeid getRevision() {
-		return revision;
+		return extras.getRevision();
 	}
 
 	public HgException setRevision(Nodeid r) {
-		revision = r;
-		return this;
+		return extras.setRevision(r);
 	}
 	
 	public boolean isRevisionSet() {
-		return revision != null;
+		return extras.isRevisionSet();
 	}
 
 	/**
 	 * @return non-null only if file name was set at construction time
 	 */
 	public Path getFileName() {
-		return filename;
+		return extras.getFileName();
 	}
 
 	public HgException setFileName(Path name) {
-		filename = name;
-		return this;
-	}
-	
-	protected void appendDetails(StringBuilder sb) {
-		if (filename != null) {
-			sb.append("path:'");
-			sb.append(filename);
-			sb.append('\'');
-			sb.append(';');
-			sb.append(' ');
-		}
-		sb.append("rev:");
-		if (revNumber != BAD_REVISION) {
-			sb.append(revNumber);
-			if (revision != null) {
-				sb.append(':');
-			}
-		}
-		if (revision != null) {
-			sb.append(revision.shortNotation());
-		}
+		return extras.setFileName(name);
 	}
 
 	@Override
@@ -131,7 +105,7 @@
 		StringBuilder sb = new StringBuilder(super.toString());
 		sb.append(' ');
 		sb.append('(');
-		appendDetails(sb);
+		extras.appendDetails(sb);
 		sb.append(')');
 		return sb.toString();
 	}
--- a/src/org/tmatesoft/hg/core/HgInvalidFileException.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgInvalidFileException.java	Mon Mar 05 14:50:51 2012 +0100
@@ -36,20 +36,18 @@
 @SuppressWarnings("serial")
 public class HgInvalidFileException extends HgException {
 
-	private File localFile;
-
 	public HgInvalidFileException(String message, Throwable th) {
 		super(message, th);
 	}
 
 	public HgInvalidFileException(String message, Throwable th, File file) {
 		super(message, th);
-		localFile = file; // allows null
+		extras.setFile(file); // allows null
 	}
 
 	public HgInvalidFileException setFile(File file) {
 		assert file != null; // doesn't allow null not to clear file accidentally
-		localFile = file;
+		extras.setFile(file);
 		return this;
 	}
 
@@ -57,23 +55,6 @@
 	 * @return file object that causes troubles, or <code>null</code> if specific file is unknown
 	 */
 	public File getFile() {
-		return localFile;
-	}
-
-	@Override
-	protected void appendDetails(StringBuilder sb) {
-		super.appendDetails(sb);
-		if (localFile != null) {
-			sb.append(';');
-			sb.append(' ');
-			sb.append(" file:");
-			sb.append(localFile.getPath());
-			sb.append(',');
-			if (localFile.exists()) {
-				sb.append("EXISTS");
-			} else {
-				sb.append("DOESN'T EXIST");
-			}
-		}
+		return extras.getFile();
 	}
 }
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgLogCommand.java	Mon Mar 05 14:50:51 2012 +0100
@@ -513,22 +513,22 @@
 		}
 		
 		void populate(HgChangeset cs) {
-			cachedChangesets.put(cs.getRevision(), cs);
+			cachedChangesets.put(cs.getRevisionIndex(), cs);
 		}
 		
-		private HgChangeset[] get(int... changelogRevisionNumber) throws HgInvalidControlFileException {
-			HgChangeset[] rv = new HgChangeset[changelogRevisionNumber.length];
-			IntVector misses = new IntVector(changelogRevisionNumber.length, -1);
-			for (int i = 0; i < changelogRevisionNumber.length; i++) {
-				if (changelogRevisionNumber[i] == -1) {
+		private HgChangeset[] get(int... changelogRevisionIndex) throws HgException {
+			HgChangeset[] rv = new HgChangeset[changelogRevisionIndex.length];
+			IntVector misses = new IntVector(changelogRevisionIndex.length, -1);
+			for (int i = 0; i < changelogRevisionIndex.length; i++) {
+				if (changelogRevisionIndex[i] == -1) {
 					rv[i] = null;
 					continue;
 				}
-				HgChangeset cached = cachedChangesets.get(changelogRevisionNumber[i]);
+				HgChangeset cached = cachedChangesets.get(changelogRevisionIndex[i]);
 				if (cached != null) {
 					rv[i] = cached;
 				} else {
-					misses.add(changelogRevisionNumber[i]);
+					misses.add(changelogRevisionIndex[i]);
 				}
 			}
 			if (misses.size() > 0) {
@@ -537,20 +537,23 @@
 				repo.getChangelog().range(this, changesets2read);
 				for (int changeset2read : changesets2read) {
 					HgChangeset cs = cachedChangesets.get(changeset2read);
-						if (cs == null) {
-							throw new HgBadStateException();
+					if (cs == null) {
+						throw new HgException(String.format("Can't get changeset for revision %d", changeset2read));
+					}
+					// HgChangelog.range may reorder changesets according to their order in the changelog
+					// thus need to find original index
+					boolean sanity = false;
+					for (int i = 0; i < changelogRevisionIndex.length; i++) {
+						if (changelogRevisionIndex[i] == cs.getRevisionIndex()) {
+							rv[i] = cs;
+							sanity = true;
+							break;
 						}
-						// HgChangelog.range may reorder changesets according to their order in the changelog
-						// thus need to find original index
-						boolean sanity = false;
-						for (int i = 0; i < changelogRevisionNumber.length; i++) {
-							if (changelogRevisionNumber[i] == cs.getRevision()) {
-								rv[i] = cs;
-								sanity = true;
-								break;
-							}
-						}
-						assert sanity;
+					}
+					if (!sanity) {
+						HgInternals.getContext(repo).getLog().error(getClass(), "Index of revision %d:%s doesn't match any of requested", cs.getRevisionIndex(), cs.getNodeid().shortNotation());
+					}
+					assert sanity;
 				}
 			}
 			return rv;
@@ -568,14 +571,14 @@
 			populate(cs.clone());
 		}
 
-		public Nodeid changesetRevision() {
+		public Nodeid changesetRevision() throws HgException {
 			if (changesetRevision == null) {
 				changesetRevision = getRevision(historyNode.changeset);
 			}
 			return changesetRevision;
 		}
 
-		public Pair<Nodeid, Nodeid> parentRevisions() {
+		public Pair<Nodeid, Nodeid> parentRevisions() throws HgException {
 			if (parentRevisions == null) {
 				HistoryNode p;
 				final Nodeid p1, p2;
@@ -594,7 +597,7 @@
 			return parentRevisions;
 		}
 
-		public Collection<Nodeid> childRevisions() {
+		public Collection<Nodeid> childRevisions() throws HgException {
 			if (childRevisions != null) {
 				return childRevisions;
 			}
@@ -611,19 +614,13 @@
 		}
 		
 		// reading nodeid involves reading index only, guess, can afford not to optimize multiple reads
-		private Nodeid getRevision(int changelogRevisionNumber) {
-			// XXX pipe through pool
+		private Nodeid getRevision(int changelogRevisionNumber) throws HgInvalidControlFileException {
+			// TODO [post-1.0] pipe through pool
 			HgChangeset cs = cachedChangesets.get(changelogRevisionNumber);
 			if (cs != null) {
 				return cs.getNodeid();
 			} else {
-				try {
-					return repo.getChangelog().getRevision(changelogRevisionNumber);
-				} catch (HgException ex) {
-					HgInternals.getContext(repo).getLog().error(getClass(), ex, null);
-					// FIXME propagate, perhaps?
-					return Nodeid.NULL; // FIXME this is quick-n-dirty hack to move forward with introducing exceptions 
-				}
+				return repo.getChangelog().getRevision(changelogRevisionNumber);
 			}
 		}
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/ExceptionInfo.java	Mon Mar 05 14:50:51 2012 +0100
@@ -0,0 +1,136 @@
+/*
+ * 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 static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+
+import java.io.File;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Extras to record with exception to describe it better.
+ * XXX perhaps, not only with exception, may utilize it with status object? 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ExceptionInfo<T> {
+	protected final T owner;
+	protected int revNumber = BAD_REVISION;
+	protected Nodeid revision;
+	protected Path filename;
+	protected File localFile;
+
+	/**
+	 * @param owner instance to return from setters 
+	 */
+	public ExceptionInfo(T owner) {
+		this.owner = owner;
+	}
+	
+	/**
+	 * @return not {@link HgRepository#BAD_REVISION} only when revision index was supplied at the construction time
+	 */
+	public int getRevisionIndex() {
+		return revNumber;
+	}
+
+	public T setRevisionIndex(int rev) {
+		revNumber = rev;
+		return owner;
+	}
+	
+	public boolean isRevisionIndexSet() {
+		return revNumber != BAD_REVISION;
+	}
+
+	/**
+	 * @return non-null only when revision was supplied at construction time
+	 */
+	public Nodeid getRevision() {
+		return revision;
+	}
+
+	public T setRevision(Nodeid r) {
+		revision = r;
+		return owner;
+	}
+	
+	public boolean isRevisionSet() {
+		return revision != null;
+	}
+
+	/**
+	 * @return non-null only if file name was set at construction time
+	 */
+	public Path getFileName() {
+		return filename;
+	}
+
+	public T setFileName(Path name) {
+		filename = name;
+		return owner;
+	}
+
+	public T setFile(File file) {
+		localFile = file;
+		return owner;
+	}
+
+	/**
+	 * @return file object that causes troubles, or <code>null</code> if specific file is unknown
+	 */
+	public File getFile() {
+		return localFile;
+	}
+
+	public StringBuilder appendDetails(StringBuilder sb) {
+		if (filename != null) {
+			sb.append("path:'");
+			sb.append(filename);
+			sb.append('\'');
+			sb.append(';');
+			sb.append(' ');
+		}
+		sb.append("rev:");
+		if (revNumber != BAD_REVISION) {
+			sb.append(revNumber);
+			if (revision != null) {
+				sb.append(':');
+			}
+		}
+		if (revision != null) {
+			sb.append(revision.shortNotation());
+		}
+		if (localFile != null) {
+			sb.append(';');
+			sb.append(' ');
+			sb.append(" file:");
+			sb.append(localFile.getPath());
+			sb.append(',');
+			if (localFile.exists()) {
+				sb.append("EXISTS");
+			} else {
+				sb.append("DOESN'T EXIST");
+			}
+		}
+		return sb;
+	}
+}
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgInternals.java	Mon Mar 05 14:50:51 2012 +0100
@@ -128,17 +128,18 @@
 		return new FileWalker(repoRoot, pathSrc, workindDirScope);
 	}
 	
-	// expose othewise package-local information primarily to use in our own o.t.hg.core package
+	// expose otherwise package-local information primarily to use in our own o.t.hg.core package
 	public static SessionContext getContext(HgRepository repo) {
 		return repo.getContext();
 	}
 
 
 	// 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) {
+	public static boolean wrongRevisionIndex(int rev) { // FIXME guess, usages shall throw HgInvalidRevision. \
+		// TODO Another method to check,throw and expand TIP at once
 		return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION; 
 	}
-
+	
 	// throws HgInvalidRevisionException or IllegalArgumentException if [start..end] range is not a subrange of [0..lastRevision]
 	public static void checkRevlogRange(int start, int end, int lastRevision) throws HgInvalidRevisionException {
 		if (start < 0 || start > lastRevision) {
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Mon Mar 05 14:50:51 2012 +0100
@@ -30,6 +30,7 @@
 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.DataAccess;
 import org.tmatesoft.hg.internal.DigestHelper;
@@ -129,9 +130,11 @@
 	 * @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
 	 */
-	public void walk(int start, int end, final Inspector inspector) throws /*FIXME HgInvalidRevisionException,*/ HgInvalidControlFileException {
+	public void walk(int start, int end, final Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
@@ -139,13 +142,13 @@
 		int manifestFirst, manifestLast, i = 0;
 		do {
 			manifestFirst = fromChangelog(csetFirst+i);
-			if (manifestFirst == -1) {
+			if (manifestFirst == BAD_REVISION) {
 				inspector.begin(BAD_REVISION, NULL, csetFirst+i);
 				inspector.end(BAD_REVISION);
 			}
 			i++;
-		} while (manifestFirst == -1 && csetFirst+i <= csetLast);
-		if (manifestFirst == -1) {
+		} while (manifestFirst == BAD_REVISION && csetFirst+i <= csetLast);
+		if (manifestFirst == BAD_REVISION) {
 			getRepo().getContext().getLog().info(getClass(), "None of changesets [%d..%d] have associated manifest revision", csetFirst, csetLast);
 			// we ran through all revisions in [start..end] and none of them had manifest.
 			// we reported that to inspector and proceeding is done now.
@@ -154,13 +157,13 @@
 		i = 0;
 		do {
 			manifestLast = fromChangelog(csetLast-i);
-			if (manifestLast == -1) {
+			if (manifestLast == BAD_REVISION) {
 				inspector.begin(BAD_REVISION, NULL, csetLast-i);
 				inspector.end(BAD_REVISION);
 			}
 			i++;
-		} while (manifestLast == -1 && csetLast-i >= csetFirst);
-		if (manifestLast == -1) {
+		} 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));
@@ -183,8 +186,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
 	 */
-	public void walk(final Inspector inspector, int... revisionIndexes) throws HgInvalidControlFileException{
+	public void walk(final Inspector inspector, int... revisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		if (inspector == null || revisionIndexes == null) {
 			throw new IllegalArgumentException();
 		}
@@ -194,10 +199,14 @@
 	
 	// 
 	/**
-	 * Tells manifest revision number that corresponds to the given changeset.
-	 * @return manifest revision index, or -1 if changeset has no associated manifest (cset records NULL nodeid for manifest) 
+	 * Tells manifest revision number that corresponds to the given changeset. May return {@link HgRepository#BAD_REVISION} 
+	 * 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 HgInvalidControlFileException {
+	/*package-local*/ int fromChangelog(int changesetRevisionIndex) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		if (HgInternals.wrongRevisionIndex(changesetRevisionIndex)) {
 			throw new IllegalArgumentException(String.valueOf(changesetRevisionIndex));
 		}
@@ -218,16 +227,18 @@
 	 * @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
 	 */
 	@Experimental(reason="Perhaps, HgDataFile shall own this method, or get a delegate?")
-	public Nodeid getFileRevision(int changelogRevisionIndex, final Path file) throws HgInvalidControlFileException{
+	public Nodeid getFileRevision(int changelogRevisionIndex, final Path file) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		return getFileRevisions(file, changelogRevisionIndex).get(changelogRevisionIndex);
 	}
 	
 	// XXX package-local, IntMap, and HgDataFile getFileRevisionAt(int... localChangelogRevisions)
 	@Experimental(reason="@see #getFileRevision")
-	public Map<Integer, Nodeid> getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidControlFileException{
-		// FIXME need tests
+	public Map<Integer, Nodeid> getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException {
+		// TODO need tests
 		int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null);
 		final HashMap<Integer,Nodeid> rv = new HashMap<Integer, Nodeid>(changelogRevisionIndexes.length);
 		content.iterate(manifestRevisionIndexes, true, new RevlogStream.Inspector() {
@@ -267,14 +278,17 @@
 	/**
 	 * @param changelogRevisionIndexes non-null
 	 * @param inspector may be null if reporting of missing manifests is not needed
+	 * @throws HgInvalidRevisionException if arguments specify non-existent revision index
+	 * @throws IllegalArgumentException if any index argument is not a revision index
+	 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
 	 */
-	private int[] toManifestRevisionIndexes(int[] changelogRevisionIndexes, Inspector inspector) throws HgInvalidControlFileException {
+	private int[] toManifestRevisionIndexes(int[] changelogRevisionIndexes, Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		int[] manifestRevs = new int[changelogRevisionIndexes.length];
 		boolean needsSort = false;
 		int j = 0;
 		for (int i = 0; i < changelogRevisionIndexes.length; i++) {
 			final int manifestRevisionIndex = fromChangelog(changelogRevisionIndexes[i]);
-			if (manifestRevisionIndex == -1) {
+			if (manifestRevisionIndex == BAD_REVISION) {
 				if (inspector != null) {
 					inspector.begin(BAD_REVISION, NULL, changelogRevisionIndexes[i]);
 					inspector.end(BAD_REVISION);
@@ -498,24 +512,32 @@
 	
 	private static class RevisionMapper implements RevlogStream.Inspector, Lifecycle {
 		
-		private final int changelogRevisions;
+		private final int changelogRevisionCount;
 		private int[] changelog2manifest;
 		private final HgRepository repo;
 
 		public RevisionMapper(HgRepository hgRepo) {
 			repo = hgRepo;
-			changelogRevisions = repo.getChangelog().getRevisionCount();
+			changelogRevisionCount = repo.getChangelog().getRevisionCount();
 		}
 
-		// respects TIP
-		public int at(int revisionNumber) {
-			if (revisionNumber == TIP) {
-				revisionNumber = changelogRevisions - 1;
+		/**
+		 * Get index of manifest revision that corresponds to specified changeset
+		 * @param changesetRevisionIndex non-negative index of changelog revision, or {@link HgRepository#TIP}
+		 * @return index of manifest revision, or {@link HgRepository#BAD_REVISION} if changeset doesn't reference a valid manifest
+		 * @throws HgInvalidRevisionException if method argument specifies non-existent revision index
+		 */
+		public int at(int changesetRevisionIndex) throws HgInvalidRevisionException {
+			if (changesetRevisionIndex == TIP) {
+				changesetRevisionIndex = changelogRevisionCount - 1;
+			}
+			if (changesetRevisionIndex >= changelogRevisionCount) {
+				throw new HgInvalidRevisionException(changesetRevisionIndex);
 			}
 			if (changelog2manifest != null) {
-				return changelog2manifest[revisionNumber];
+				return changelog2manifest[changesetRevisionIndex];
 			}
-			return revisionNumber;
+			return changesetRevisionIndex;
 		}
 
 		// XXX likely can be replaced with Revlog.RevisionInspector
@@ -524,12 +546,12 @@
 				// next assertion is not an error, rather assumption check, which is too development-related to be explicit exception - 
 				// I just wonder if there are manifests that have two entries pointing to single changeset. It seems unrealistic, though -
 				// changeset records one and only one manifest nodeid
-				assert changelog2manifest[linkRevision] == -1 : String.format("revision:%d, link:%d, already linked to revision:%d", revisionNumber, linkRevision, changelog2manifest[linkRevision]);
+				assert changelog2manifest[linkRevision] == BAD_REVISION : String.format("revision:%d, link:%d, already linked to revision:%d", revisionNumber, linkRevision, changelog2manifest[linkRevision]);
 				changelog2manifest[linkRevision] = revisionNumber;
 			} else {
 				if (revisionNumber != linkRevision) {
-					changelog2manifest = new int[changelogRevisions];
-					Arrays.fill(changelog2manifest, -1);
+					changelog2manifest = new int[changelogRevisionCount];
+					Arrays.fill(changelog2manifest, BAD_REVISION);
 					for (int i = 0; i < revisionNumber; changelog2manifest[i] = i, i++)
 						;
 					changelog2manifest[linkRevision] = revisionNumber;
@@ -538,14 +560,14 @@
 		}
 		
 		public void start(int count, Callback callback, Object token) {
-			if (count != changelogRevisions) {
-				assert count < changelogRevisions; // no idea what to do if manifest has more revisions than changelog
+			if (count != changelogRevisionCount) {
+				assert count < changelogRevisionCount; // no idea what to do if manifest has more revisions than changelog
 				// the way how manifest may contain more revisions than changelog, as I can imagine, is a result of  
 				// some kind of an import tool (e.g. from SVN or CVS), that creates manifest and changelog independently.
 				// Note, it's pure guess, I didn't see such repository yet (although the way manifest revisions
 				// in cpython repo are numbered makes me think aforementioned way) 
-				changelog2manifest = new int[changelogRevisions];
-				Arrays.fill(changelog2manifest, -1);
+				changelog2manifest = new int[changelogRevisionCount];
+				Arrays.fill(changelog2manifest, BAD_REVISION);
 			}
 		}
 
@@ -556,7 +578,7 @@
 			// I assume there'd be not too many revisions we don't know manifest of
 			ArrayList<Integer> undefinedChangelogRevision = new ArrayList<Integer>();
 			for (int i = 0; i < changelog2manifest.length; i++) {
-				if (changelog2manifest[i] == -1) {
+				if (changelog2manifest[i] == BAD_REVISION) {
 					undefinedChangelogRevision.add(i);
 				}
 			}
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Mon Mar 05 14:50:51 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
@@ -232,12 +232,12 @@
 						assert currRange == null;
 						assert currRangeList == null;
 						if (!rangeItr.hasNext()) {
-							throw new HgBadStateException();
+							throw new HgBadStateException("Internal error");
 						}
 						rv.put(rangeItr.next(), Collections.<Nodeid>emptyList());
 					} else {
 						if (currRange == null || currRangeList == null) {
-							throw new HgBadStateException();
+							throw new HgBadStateException("Internal error");
 						}
 						// indicate next range value is needed
 						currRange = null;
@@ -248,7 +248,7 @@
 					possiblyEmptyNextLine = false;
 					if (currRange == null) {
 						if (!rangeItr.hasNext()) {
-							throw new HgBadStateException();
+							throw new HgBadStateException("Internal error");
 						}
 						currRange = rangeItr.next();
 						currRangeList = new LinkedList<Nodeid>();
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Mon Mar 05 14:50:51 2012 +0100
@@ -55,7 +55,7 @@
 
 	// if new constants added, consider fixing HgInternals#wrongRevisionIndex
 	public static final int TIP = -3;
-	public static final int BAD_REVISION = Integer.MIN_VALUE;
+	public static final int BAD_REVISION = Integer.MIN_VALUE; // XXX INVALID_REVISION?
 	public static final int WORKING_COPY = -2;
 	
 	public static final String DEFAULT_BRANCH_NAME = "default";
--- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Mon Mar 05 14:50:51 2012 +0100
@@ -16,8 +16,7 @@
  */
 package org.tmatesoft.hg.repo;
 
-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.util.Collection;
 import java.util.Collections;
@@ -30,6 +29,7 @@
 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;
@@ -184,30 +184,51 @@
 		scope = scopeMatcher == null ? new Path.Matcher.Any() : scopeMatcher;
 	}
 	
-	// hg status --change <rev>
-	public void change(int rev, HgStatusInspector inspector) throws /*FIXME HInvalidRevisionException,*/ HgInvalidControlFileException {
+	/**
+	 * '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
+	 */
+	public void change(int revisionIndex, HgStatusInspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		int[] parents = new int[2];
-		repo.getChangelog().parents(rev, parents, null, null);
-		walk(parents[0], rev, inspector);
+		repo.getChangelog().parents(revisionIndex, parents, null, null);
+		walk(parents[0], revisionIndex, inspector);
 	}
 	
-	// rev1 and rev2 are changelog revision numbers, argument order matters.
-	// Either rev1 or rev2 may be -1 to indicate comparison to empty repository (XXX this is due to use of 
-	// parents in #change(), I believe. Perhaps, need a constant for this? Otherwise this hidden knowledge gets
-	// exposed to e.g. Record
-	public void walk(int rev1, int rev2, HgStatusInspector inspector) throws /*FIXME HInvalidRevisionException,*/ HgInvalidControlFileException {
+	/**
+	 * Parameters <b>rev1</b> and <b>rev2</b> are changelog revision indexes, shall not be the same. Argument order matters.
+	 * FIXME Either rev1 or rev2 may be -1 to indicate comparison to empty repository (this is due to use of
+	 * parents in #change(), I believe. Perhaps, need a constant for this? Otherwise this hidden knowledge gets
+	 * exposed to e.g. Record
+	 * XXX cancellation?
+	 * 
+	 * @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 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 {
 		if (rev1 == rev2) {
 			throw new IllegalArgumentException();
 		}
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
-		final int lastManifestRevision = repo.getChangelog().getLastRevision();
+		final int lastChangelogRevision = repo.getChangelog().getLastRevision();
 		if (rev1 == TIP) {
-			rev1 = lastManifestRevision;
+			rev1 = lastChangelogRevision;
 		}
 		if (rev2 == TIP) {
-			rev2 = lastManifestRevision; 
+			rev2 = lastChangelogRevision; 
+		}
+		if (rev1 != -1 && (HgInternals.wrongRevisionIndex(rev1) || rev1 == WORKING_COPY || rev1 == BAD_REVISION || rev1 > lastChangelogRevision)) {
+			throw new HgInvalidRevisionException(rev1);
+		}
+		if (rev2 != -1 && (HgInternals.wrongRevisionIndex(rev2) || rev2 == WORKING_COPY || rev2 == BAD_REVISION || rev2 > lastChangelogRevision)) {
+			throw new HgInvalidRevisionException(rev2);
 		}
 		if (inspector instanceof Record) {
 			((Record) inspector).init(rev1, rev2, this);
@@ -233,12 +254,12 @@
 			// which going to be read anyway
 			if (need1) {
 				minRev = rev1;
-				maxRev = rev1 < lastManifestRevision-5 ? rev1+5 : lastManifestRevision;
+				maxRev = rev1 < lastChangelogRevision-5 ? rev1+5 : lastChangelogRevision;
 				initCacheRange(minRev, maxRev);
 			}
 			if (need2) {
 				minRev = rev2;
-				maxRev = rev2 < lastManifestRevision-5 ? rev2+5 : lastManifestRevision;
+				maxRev = rev2 < lastChangelogRevision-5 ? rev2+5 : lastChangelogRevision;
 				initCacheRange(minRev, maxRev);
 			}
 		}
@@ -283,7 +304,16 @@
 		}
 	}
 	
-	public Record status(int rev1, int rev2) throws /*FIXME HInvalidRevisionException,*/ HgInvalidControlFileException {
+	/**
+	 * Collects status between two revisions, changes from <b>rev1</b> up to <b>rev2</b>.
+	 * 
+	 * @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
+	 */
+	public Record status(int rev1, int rev2) throws HgInvalidRevisionException, HgInvalidControlFileException {
 		Record rv = new Record();
 		walk(rev1, rev2, rv);
 		return rv;
--- a/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Mon Mar 05 14:50:51 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
@@ -88,7 +88,7 @@
 
 	public HgRepository getRepo() throws HgInvalidFileException {
 		if (kind != Kind.Hg) {
-			throw new HgBadStateException();
+			throw new HgBadStateException(String.format("Unsupported subrepository %s", kind));
 		}
 		return new HgLookup().detect(new File(owner.getWorkingDir(), source));
 	}
--- a/src/org/tmatesoft/hg/repo/Revlog.java	Mon Feb 27 19:38:44 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/Revlog.java	Mon Mar 05 14:50:51 2012 +0100
@@ -396,7 +396,7 @@
 		
 		private void assertSortedIndex(int x) {
 			if (x < 0) {
-				throw new HgBadStateException();
+				throw new HgBadStateException(String.format("Bad index", x));
 			}
 		}