changeset 295:981f9f50bb6c

Issue 11: Error log facility. SessionContext to share common facilities
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 16 Sep 2011 05:35:32 +0200
parents 32890bab7209
children 02f2963c70fa
files cmdline/org/tmatesoft/hg/console/Main.java cmdline/org/tmatesoft/hg/console/Remote.java src/org/tmatesoft/hg/core/HgBadArgumentException.java src/org/tmatesoft/hg/core/HgCloneCommand.java src/org/tmatesoft/hg/core/HgFileInformer.java src/org/tmatesoft/hg/core/HgIncomingCommand.java src/org/tmatesoft/hg/core/HgInvalidFileException.java src/org/tmatesoft/hg/core/HgOutgoingCommand.java src/org/tmatesoft/hg/core/HgRepoFacade.java src/org/tmatesoft/hg/core/SessionContext.java src/org/tmatesoft/hg/internal/BasicSessionContext.java src/org/tmatesoft/hg/internal/ConfigFile.java src/org/tmatesoft/hg/internal/DataAccessProvider.java src/org/tmatesoft/hg/internal/DigestHelper.java src/org/tmatesoft/hg/internal/Internals.java src/org/tmatesoft/hg/internal/NewlineFilter.java src/org/tmatesoft/hg/internal/PathGlobMatcher.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/internal/RequiresFile.java src/org/tmatesoft/hg/internal/RevlogStream.java src/org/tmatesoft/hg/internal/StreamLogFacility.java src/org/tmatesoft/hg/internal/SubrepoManager.java src/org/tmatesoft/hg/repo/HgBranches.java src/org/tmatesoft/hg/repo/HgBundle.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgDirstate.java src/org/tmatesoft/hg/repo/HgInternals.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/repo/HgSubrepoLocation.java src/org/tmatesoft/hg/repo/HgTags.java src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java src/org/tmatesoft/hg/util/LogFacility.java src/org/tmatesoft/hg/util/RegularFileInfo.java
diffstat 35 files changed, 632 insertions(+), 175 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Fri Sep 16 05:35:32 2011 +0200
@@ -26,35 +26,37 @@
 
 import org.junit.Assert;
 import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgCatCommand;
 import org.tmatesoft.hg.core.HgDataStreamException;
-import org.tmatesoft.hg.core.HgLogCommand;
-import org.tmatesoft.hg.core.HgCatCommand;
 import org.tmatesoft.hg.core.HgFileInformer;
 import org.tmatesoft.hg.core.HgFileRevision;
+import org.tmatesoft.hg.core.HgLogCommand;
 import org.tmatesoft.hg.core.HgManifestCommand;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.DigestHelper;
 import org.tmatesoft.hg.internal.PathGlobMatcher;
 import org.tmatesoft.hg.internal.RelativePathRewrite;
+import org.tmatesoft.hg.internal.StreamLogFacility;
 import org.tmatesoft.hg.repo.HgBranches;
 import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgDirstate;
+import org.tmatesoft.hg.repo.HgDirstate.EntryKind;
+import org.tmatesoft.hg.repo.HgDirstate.Record;
 import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgManifest;
+import org.tmatesoft.hg.repo.HgManifest.Flags;
 import org.tmatesoft.hg.repo.HgMergeState;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.repo.HgStatusInspector;
 import org.tmatesoft.hg.repo.HgSubrepoLocation;
-import org.tmatesoft.hg.repo.HgManifest.Flags;
 import org.tmatesoft.hg.repo.HgSubrepoLocation.Kind;
 import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.repo.HgDirstate.EntryKind;
-import org.tmatesoft.hg.repo.HgDirstate.Record;
 import org.tmatesoft.hg.util.FileWalker;
+import org.tmatesoft.hg.util.LogFacility;
 import org.tmatesoft.hg.util.Pair;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathRewrite;
@@ -65,6 +67,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
+@SuppressWarnings("unused")
 public class Main {
 	
 	private Options cmdLineOpts;
@@ -82,6 +85,7 @@
 
 	public static void main(String[] args) throws Exception {
 		Main m = new Main(args);
+		m.testConsoleLog();
 //		m.testTreeTraversal();
 //		m.testRevisionMap();
 //		m.testSubrepos();
@@ -94,7 +98,7 @@
 //		m.dumpBranches();
 //		m.inflaterLengthException();
 //		m.dumpIgnored();
-		m.dumpDirstate();
+//		m.dumpDirstate();
 //		m.testStatusInternals();
 //		m.catCompleteHistory();
 //		m.dumpCompleteManifestLow();
@@ -102,6 +106,20 @@
 //		m.bunchOfTests();
 	}
 	
+	private void testConsoleLog() {
+		LogFacility fc = new StreamLogFacility(true, true, true, System.out);
+		System.out.printf("isDebug: %s, isInfo:%s\n", fc.isDebug(), fc.isInfo());
+		fc.debug(getClass(), "%d", 1);
+		fc.info(getClass(), "%d\n", 2);
+		fc.warn(getClass(), "%d\n", 3);
+		fc.error(getClass(), "%d", 4);
+		Exception ex = new Exception();
+		fc.debug(getClass(), ex, "message");
+		fc.info(getClass(), ex, null);
+		fc.warn(getClass(), ex, null);
+		fc.error(getClass(), ex, "message");
+	}
+	
 	private void testTreeTraversal() throws Exception {
 		File repoRoot = hgRepo.getWorkingDir();
 		Path.Source pathSrc = new Path.SimpleSource(new PathRewrite.Composite(new RelativePathRewrite(repoRoot), hgRepo.getToRepoPathHelper()));
--- a/cmdline/org/tmatesoft/hg/console/Remote.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Remote.java	Fri Sep 16 05:35:32 2011 +0200
@@ -36,7 +36,6 @@
 import javax.net.ssl.X509TrustManager;
 
 import org.tmatesoft.hg.internal.ConfigFile;
-import org.tmatesoft.hg.internal.Internals;
 
 /**
  * WORK IN PROGRESS, DO NOT USE
@@ -91,7 +90,7 @@
 	 empty result
 	 */
 	public static void main(String[] args) throws Exception {
-		ConfigFile cfg = new Internals().newConfigFile();
+		ConfigFile cfg =  new ConfigFile();
 		cfg.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
 		String svnkitServer = cfg.getSection("paths").get("svnkit");
 //		URL url = new URL(svnkitServer + "?cmd=branches&nodes=30bd389788464287cee22ccff54c330a4b715de5");
--- a/src/org/tmatesoft/hg/core/HgBadArgumentException.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgBadArgumentException.java	Fri Sep 16 05:35:32 2011 +0200
@@ -17,7 +17,14 @@
 package org.tmatesoft.hg.core;
 
 /**
- *
+ * Thrown when client supplied an argument that turned out to be incorrect.
+ * E.g. an {@link java.net.URL URL} of remote server  or {@link java.io.File File} destination for a new repository
+ * might be otherwise valid, but unsuitable for the purpose of the operation.
+ *  
+ * Not a replacement for {@link IllegalArgumentException} or {@link NullPointerException}.
+ * 
+ * TODO review usage to match description
+ * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
--- a/src/org/tmatesoft/hg/core/HgCloneCommand.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgCloneCommand.java	Fri Sep 16 05:35:32 2011 +0200
@@ -71,9 +71,9 @@
 		return this;
 	}
 
-	public HgRepository execute() throws HgException, CancelledException {
+	public HgRepository execute() throws HgBadArgumentException, HgRemoteConnectionException, HgInvalidFileException, CancelledException {
 		if (destination == null) {
-			throw new HgBadArgumentException("Destination not set", null);
+			throw new IllegalArgumentException("Destination not set", null);
 		}
 		if (srcRepo == null || srcRepo.isInvalid()) {
 			throw new HgBadArgumentException("Bad source repository", null);
@@ -101,7 +101,7 @@
 			completeChanges.inspectAll(mate);
 			mate.complete();
 		} catch (IOException ex) {
-			throw new HgException(ex);
+			throw new HgInvalidFileException(getClass().getName(), ex);
 		} finally {
 			completeChanges.unlink();
 		}
--- a/src/org/tmatesoft/hg/core/HgFileInformer.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgFileInformer.java	Fri Sep 16 05:35:32 2011 +0200
@@ -18,6 +18,7 @@
 
 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;
 
@@ -114,7 +115,7 @@
 				}
 			}
 		} catch (HgDataStreamException ex) {
-			ex.printStackTrace(); // XXX log(INFO) 
+			HgInternals.getContext(repo).getLog().warn(getClass(), ex, "Follow copy/rename failed");
 			// ignore now, however if there's IStatus retval, might report error with reasonable explanation.
 			// Perhaps, may add a String reason() method with such info?
 		}
--- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Fri Sep 16 05:35:32 2011 +0200
@@ -16,7 +16,6 @@
  */
 package org.tmatesoft.hg.core;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -100,10 +99,10 @@
 	 * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account. 
 	 *   
 	 * @return list of nodes present at remote and missing locally
-	 * @throws HgException
+	 * @throws HgRemoteConnectionException when failed to communicate with remote repository
 	 * @throws CancelledException
 	 */
-	public List<Nodeid> executeLite() throws HgException, CancelledException {
+	public List<Nodeid> executeLite() throws HgRemoteConnectionException, CancelledException {
 		LinkedHashSet<Nodeid> result = new LinkedHashSet<Nodeid>();
 		RepositoryComparator repoCompare = getComparator();
 		for (BranchChain bc : getMissingBranches()) {
@@ -121,10 +120,12 @@
 	/**
 	 * Full information about incoming changes
 	 * 
-	 * @throws HgException
+	 * @throws HgRemoteConnectionException when failed to communicate with remote repository
+	 * @throws HgInvalidFileException to indicate failure working with locally downloaded changes in a bundle file
+	 * @throws HgCallbackTargetException to re-throw exception from the handler
 	 * @throws CancelledException
 	 */
-	public void executeFull(final HgChangesetHandler handler) throws HgException/*FIXME specific type*/, HgException, CancelledException {
+	public void executeFull(final HgChangesetHandler handler) throws HgRemoteConnectionException, HgInvalidFileException, HgCallbackTargetException, CancelledException {
 		if (handler == null) {
 			throw new IllegalArgumentException("Delegate can't be null");
 		}
@@ -155,8 +156,6 @@
 				}
 			});
 			transformer.checkFailure();
-		} catch (IOException ex) {
-			throw new HgException(ex);
 		} finally {
 			ps.done();
 		}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgInvalidFileException.java	Fri Sep 16 05:35:32 2011 +0200
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.core;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Thrown when there are troubles working with local file. Most likely (but not necessarily) wraps IOException. Might be 
+ * perceived as specialized IOException with optional File and other repository information.
+ * 
+ * <b>Hg4J</b> tries to minimize chances for IOException to occur (i.e. {@link File#canRead()} is checked before attempt to 
+ * read a file that might not exist, and doesn't use this exception to wrap each and any {@link IOException} source (e.g. 
+ * <code>#close()</code> calls are unlikely to yield it), hence it is likely to address real cases when I/O error occurs.
+ * 
+ * On the other hand, when a file is supposed to exist and be readable, this exception might get thrown as well to indicate
+ * that's not true. 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgInvalidFileException extends HgException {
+
+	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;
+	}
+
+	public HgInvalidFileException setFile(File file) {
+		assert file != null;
+		localFile = file;
+		return this;
+	}
+
+	/**
+	 * @return file object that causes troubles, or <code>null</code> if specific file is unknown
+	 */
+	public File getFile() {
+		return localFile;
+	}
+}
--- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Fri Sep 16 05:35:32 2011 +0200
@@ -109,7 +109,7 @@
 	 * 
 	 * @param handler delegate to process changes
 	 */
-	public void executeFull(final HgChangesetHandler handler) throws HgRemoteConnectionException, HgException, CancelledException {
+	public void executeFull(final HgChangesetHandler handler) throws HgRemoteConnectionException, HgCallbackTargetException, CancelledException {
 		if (handler == null) {
 			throw new IllegalArgumentException("Delegate can't be null");
 		}
--- a/src/org/tmatesoft/hg/core/HgRepoFacade.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgRepoFacade.java	Fri Sep 16 05:35:32 2011 +0200
@@ -18,6 +18,7 @@
 
 import java.io.File;
 
+import org.tmatesoft.hg.internal.BasicSessionContext;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgLookup;
 
@@ -37,8 +38,17 @@
  */
 public class HgRepoFacade {
 	private HgRepository repo;
+	private final SessionContext context;
 
 	public HgRepoFacade() {
+		this(new BasicSessionContext(null, null));
+	}
+	
+	public HgRepoFacade(SessionContext ctx) {
+		if (ctx == null) {
+			throw new IllegalArgumentException();
+		}
+		context = ctx;
 	}
 	
 	/**
@@ -57,10 +67,10 @@
 	/**
 	 * Tries to find repository starting from the current working directory.
 	 * @return <code>true</code> if found valid repository
-	 * @throws HgException in case of errors during repository initialization
+	 * @throws HgInvalidFileException in case of errors during repository initialization
 	 */
-	public boolean init() throws HgException {
-		repo = new HgLookup().detectFromWorkingDir();
+	public boolean init() throws HgInvalidFileException {
+		repo = new HgLookup(context).detectFromWorkingDir();
 		return repo != null && !repo.isInvalid();
 	}
 	
@@ -69,14 +79,14 @@
 	 * 
 	 * @param repoLocation path to any folder within structure of a Mercurial repository.
 	 * @return <code>true</code> if found valid repository 
-	 * @throws HgException if there are errors accessing specified location
+	 * @throws HgInvalidFileException if there are errors accessing specified location
 	 * @throws IllegalArgumentException if argument is <code>null</code>
 	 */
-	public boolean initFrom(File repoLocation) throws HgException {
+	public boolean initFrom(File repoLocation) throws HgInvalidFileException {
 		if (repoLocation == null) {
 			throw new IllegalArgumentException();
 		}
-		repo = new HgLookup().detect(repoLocation);
+		repo = new HgLookup(context).detect(repoLocation);
 		return repo != null && !repo.isInvalid();
 	}
 	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/SessionContext.java	Fri Sep 16 05:35:32 2011 +0200
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.core;
+
+import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.util.LogFacility;
+
+/**
+ * WORK IN PROGRESS
+ * 
+ * Access to objects that might need to be shared between various distinct operations ran during the same working session 
+ * (i.e. caches, log, etc.). It's unspecified whether session context is per repository or can span multiple repositories 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@Experimental(reason="Work in progress")
+public interface SessionContext {
+	LogFacility getLog();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/BasicSessionContext.java	Fri Sep 16 05:35:32 2011 +0200
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.internal;
+
+import org.tmatesoft.hg.core.SessionContext;
+import org.tmatesoft.hg.util.LogFacility;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class BasicSessionContext implements SessionContext {
+
+	private PathPool pathPool;
+	private final LogFacility logFacility;
+	
+	public BasicSessionContext(PathPool pathFactory, LogFacility log) {
+		pathPool = pathFactory;
+		logFacility = log != null ? log : new StreamLogFacility(true, true, true, System.out);
+	}
+
+	public PathPool getPathPool() {
+		if (pathPool == null) {
+			pathPool = new PathPool(new PathRewrite.Empty());
+		}
+		return pathPool;
+	}
+
+	public LogFacility getLog() {
+		// e.g. for exceptions that we can't handle but log (e.g. FileNotFoundException when we've checked beforehand file.canRead()
+		return logFacility;
+	}
+
+}
--- a/src/org/tmatesoft/hg/internal/ConfigFile.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/ConfigFile.java	Fri Sep 16 05:35:32 2011 +0200
@@ -36,10 +36,10 @@
 	private List<String> sections;
 	private List<Map<String,String>> content;
 
-	ConfigFile() {
+	public ConfigFile() {
 	}
 
-	public void addLocation(File path) {
+	public void addLocation(File path) throws IOException {
 		read(path);
 	}
 	
@@ -92,7 +92,7 @@
 
 	// TODO handle %include and %unset directives
 	// TODO "" and lists
-	private void read(File f) {
+	private void read(File f) throws IOException {
 		if (f == null || !f.canRead()) {
 			return;
 		}
@@ -100,8 +100,9 @@
 			sections = new ArrayList<String>();
 			content = new ArrayList<Map<String,String>>();
 		}
+		BufferedReader br = null;
 		try {
-			BufferedReader br = new BufferedReader(new FileReader(f));
+			br = new BufferedReader(new FileReader(f));
 			String line;
 			String sectionName = "";
 			Map<String,String> section = new LinkedHashMap<String, String>();
@@ -140,12 +141,13 @@
 					section.put(key, value);
 				}
 			}
-			br.close();
-		} catch (IOException ex) {
-			ex.printStackTrace(); // XXX shall outer world care?
+		} finally {
+			((ArrayList<?>) sections).trimToSize();
+			((ArrayList<?>) content).trimToSize();
+			if (br != null) {
+				br.close();
+			}
 		}
-		((ArrayList<?>) sections).trimToSize();
-		((ArrayList<?>) content).trimToSize();
 		assert sections.size() == content.size();
 	}
 }
--- a/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Fri Sep 16 05:35:32 2011 +0200
@@ -24,6 +24,7 @@
 import java.nio.channels.FileChannel;
 
 import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.SessionContext;
 
 /**
  * 
@@ -34,12 +35,14 @@
 
 	private final int mapioMagicBoundary;
 	private final int bufferSize;
+	private final SessionContext context;
 
-	public DataAccessProvider() {
-		this(100 * 1024, 8 * 1024);
+	public DataAccessProvider(SessionContext ctx) {
+		this(ctx, 100 * 1024, 8 * 1024);
 	}
 
-	public DataAccessProvider(int mapioBoundary, int regularBufferSize) {
+	public DataAccessProvider(SessionContext ctx, int mapioBoundary, int regularBufferSize) {
+		context = ctx;
 		mapioMagicBoundary = mapioBoundary;
 		bufferSize = regularBufferSize;
 	}
@@ -67,7 +70,7 @@
 			}
 		} catch (IOException ex) {
 			// unlikely to happen, we've made sure file exists.
-			ex.printStackTrace(); // FIXME log error
+			context.getLog().error(getClass(), ex, null);
 		}
 		return new DataAccess(); // non-null, empty.
 	}
@@ -177,7 +180,7 @@
 				try {
 					fileChannel.close();
 				} catch (IOException ex) {
-					ex.printStackTrace(); // log debug
+					StreamLogFacility.newDefault().debug(getClass(), ex, null);
 				}
 				fileChannel = null;
 			}
@@ -298,7 +301,7 @@
 				try {
 					fileChannel.close();
 				} catch (IOException ex) {
-					ex.printStackTrace(); // log debug
+					StreamLogFacility.newDefault().debug(getClass(), ex, null);
 				}
 				fileChannel = null;
 			}
--- a/src/org/tmatesoft/hg/internal/DigestHelper.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/DigestHelper.java	Fri Sep 16 05:35:32 2011 +0200
@@ -21,6 +21,7 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
+import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.Nodeid;
 
 
@@ -49,7 +50,7 @@
 				sha1 = MessageDigest.getInstance("SHA-1");
 			} catch (NoSuchAlgorithmException ex) {
 				// could hardly happen, JDK from Sun always has sha1.
-				ex.printStackTrace(); // FIXME log error
+				throw new HgBadStateException(ex);
 			}
 		}
 		return sha1;
--- a/src/org/tmatesoft/hg/internal/Internals.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/Internals.java	Fri Sep 16 05:35:32 2011 +0200
@@ -24,6 +24,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.PathRewrite;
 
@@ -41,6 +42,15 @@
 
 	public Internals() {
 	}
+	
+	public void parseRequires(HgRepository hgRepo, File requiresFile) {
+		try {
+			new RequiresFile().parse(this, requiresFile);
+		} catch (IOException ex) {
+			// FIXME not quite sure error reading requires file shall be silently logged only.
+			HgInternals.getContext(hgRepo).getLog().error(getClass(), ex, null);
+		}
+	}
 
 	public/*for tests, otherwise pkg*/ void setStorageConfig(int version, int flags) {
 		requiresFlags = flags;
@@ -63,10 +73,6 @@
 		}
 	}
 
-	public ConfigFile newConfigFile() {
-		return new ConfigFile();
-	}
-
 	public List<Filter.Factory> getFilters(HgRepository hgRepo, ConfigFile cfg) {
 		if (filterFactories == null) {
 			filterFactories = new ArrayList<Filter.Factory>();
--- a/src/org/tmatesoft/hg/internal/NewlineFilter.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/NewlineFilter.java	Fri Sep 16 05:35:32 2011 +0200
@@ -23,10 +23,12 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Map;
 
+import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Path;
 
@@ -177,7 +179,11 @@
 //			}
 			// XXX perhaps, add HgDataFile.hasWorkingCopy and workingCopyContent()?
 			ConfigFile hgeol = new ConfigFile();
-			hgeol.addLocation(cfgFile);
+			try {
+				hgeol.addLocation(cfgFile);
+			} catch (IOException ex) {
+				HgInternals.getContext(hgRepo).getLog().warn(getClass(), ex, null);
+			}
 			nativeRepoFormat = hgeol.getSection("repository").get("native");
 			if (nativeRepoFormat == null) {
 				nativeRepoFormat = "LF";
@@ -199,7 +205,7 @@
 				} else if ("BIN".equals(e.getValue())) {
 					binPatterns.add(e.getKey());
 				} else {
-					System.out.printf("Can't recognize .hgeol entry: %s for %s", e.getValue(), e.getKey()); // FIXME log warning
+					HgInternals.getContext(hgRepo).getLog().warn(getClass(), "Can't recognize .hgeol entry: %s for %s", e.getValue(), e.getKey());
 				}
 			}
 			if (!crlfPatterns.isEmpty()) {
--- a/src/org/tmatesoft/hg/internal/PathGlobMatcher.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/PathGlobMatcher.java	Fri Sep 16 05:35:32 2011 +0200
@@ -44,7 +44,6 @@
 		try {
 			delegate = new PathRegexpMatcher(regexp);
 		} catch (PatternSyntaxException ex) {
-			ex.printStackTrace();
 			throw new IllegalArgumentException(ex);
 		}
 	}
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Fri Sep 16 05:35:32 2011 +0200
@@ -30,7 +30,6 @@
 import java.util.Set;
 
 import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.HgRemoteConnectionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgChangelog;
@@ -393,7 +392,7 @@
 	/**
 	 * @return list of nodeids from branchRoot to branchHead, inclusive. IOW, first element of the list is always root of the branch 
 	 */
-	public List<Nodeid> completeBranch(final Nodeid branchRoot, final Nodeid branchHead) throws HgException {
+	public List<Nodeid> completeBranch(final Nodeid branchRoot, final Nodeid branchHead) throws HgRemoteConnectionException {
 		class DataEntry {
 			public final Nodeid queryHead;
 			public final int headIndex;
@@ -512,7 +511,7 @@
 	 *  returns in order from branch root to head
 	 *  for a non-empty BranchChain, shall return modifiable list
 	 */
-	public List<Nodeid> visitBranches(BranchChain bc) throws HgException {
+	public List<Nodeid> visitBranches(BranchChain bc) throws HgRemoteConnectionException {
 		if (bc == null) {
 			return Collections.emptyList();
 		}
--- a/src/org/tmatesoft/hg/internal/RequiresFile.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/RequiresFile.java	Fri Sep 16 05:35:32 2011 +0200
@@ -35,16 +35,17 @@
 	public RequiresFile() {
 	}
 
-	public void parse(Internals repoImpl, File requiresFile) {
+	public void parse(Internals repoImpl, File requiresFile) throws IOException {
 		if (!requiresFile.exists()) {
 			return;
 		}
+		BufferedReader br = null;
 		try {
 			boolean revlogv1 = false;
 			boolean store = false;
 			boolean fncache = false;
 			boolean dotencode = false;
-			BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(requiresFile)));
+			br = new BufferedReader(new InputStreamReader(new FileInputStream(requiresFile)));
 			String line;
 			while ((line = br.readLine()) != null) {
 				revlogv1 |= "revlogv1".equals(line);
@@ -57,9 +58,10 @@
 			flags += fncache ? FNCACHE : 0;
 			flags += dotencode ? DOTENCODE : 0;
 			repoImpl.setStorageConfig(revlogv1 ? 1 : 0, flags);
-			br.close();
-		} catch (IOException ex) {
-			ex.printStackTrace(); // FIXME log
+		} finally {
+			if (br != null) {
+				br.close();
+			}
 		}
 	}
 }
--- a/src/org/tmatesoft/hg/internal/RevlogStream.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/RevlogStream.java	Fri Sep 16 05:35:32 2011 +0200
@@ -79,6 +79,9 @@
 		return baseRevisions.length;
 	}
 	
+	/**
+	 * @throws HgBadStateException if internal read operation failed
+	 */
 	public int dataLength(int revision) {
 		// XXX in fact, use of iterate() instead of this implementation may be quite reasonable.
 		//
@@ -94,12 +97,15 @@
 			return actualLen; 
 		} catch (IOException ex) {
 			ex.printStackTrace(); // log error. FIXME better handling
-			throw new IllegalStateException(ex);
+			throw new HgBadStateException(ex);
 		} finally {
 			daIndex.done();
 		}
 	}
 	
+	/**
+	 * @throws HgBadStateException if internal read operation failed
+	 */
 	public byte[] nodeid(int revision) {
 		final int indexSize = revisionCount();
 		if (revision == TIP) {
@@ -117,12 +123,16 @@
 			return rv;
 		} catch (IOException ex) {
 			ex.printStackTrace();
-			throw new IllegalStateException();
+			throw new HgBadStateException();
 		} finally {
 			daIndex.done();
 		}
 	}
-	
+
+	/**
+	 * Get link field from the index record.
+	 * @throws HgBadStateException if internal read operation failed
+	 */
 	public int linkRevision(int revision) {
 		final int last = revisionCount() - 1;
 		if (revision == TIP) {
@@ -139,7 +149,7 @@
 			return linkRev;
 		} catch (IOException ex) {
 			ex.printStackTrace();
-			throw new IllegalStateException();
+			throw new HgBadStateException();
 		} finally {
 			daIndex.done();
 		}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/StreamLogFacility.java	Fri Sep 16 05:35:32 2011 +0200
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.internal;
+
+import java.io.PrintStream;
+
+import org.tmatesoft.hg.util.LogFacility;
+
+/**
+ * Primitive implementation of {@link LogFacility} that directs all output to specified {@link PrintStream}.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class StreamLogFacility implements LogFacility {
+	
+	private final boolean isDebug;
+	private final boolean isInfo;
+	protected final boolean timestamp;
+	protected final PrintStream outStream;
+
+	public StreamLogFacility(boolean pringDebug, boolean printInfo, boolean needTimestamp, PrintStream out) {
+		isDebug = pringDebug;
+		isInfo = printInfo;
+		timestamp = needTimestamp;
+		outStream = out;
+	}
+
+	public boolean isDebug() {
+		return isDebug;
+	}
+
+	public boolean isInfo() {
+		return isInfo;
+	}
+
+	public void debug(Class<?> src, String format, Object... args) {
+		if (!isDebug) {
+			return;
+		}
+		printf("DEBUG", src, format, args);
+	}
+
+	public void info(Class<?> src, String format, Object... args) {
+		if (!isInfo) {
+			return;
+		}
+		printf("INFO", src, format, args);
+	}
+
+	public void warn(Class<?> src, String format, Object... args) {
+		printf("WARN", src, format, args);
+	}
+
+	public void error(Class<?> src, String format, Object... args) {
+		printf("ERROR", src, format, args);
+	}
+
+	public void debug(Class<?> src, Throwable th, String message) {
+		if (!isDebug) {
+			return;
+		}
+		printf("DEBUG", src, th, message);
+	}
+
+	public void info(Class<?> src, Throwable th, String message) {
+		if (!isInfo) {
+			return;
+		}
+		printf("INFO", src, th, message);
+	}
+
+	public void warn(Class<?> src, Throwable th, String message) {
+		printf("WARN", src, th, message);
+	}
+
+	public void error(Class<?> src, Throwable th, String message) {
+		printf("ERROR", src, th, message);
+	}
+
+	protected void printf(String level, Class<?> src, String format, Object... args) {
+		String msg = String.format(format, args);
+		if (timestamp) {
+			outStream.printf(isDebug ? "%tT.%1$tL " : "%tT ", System.currentTimeMillis());
+		}
+		if (isDebug) {
+			String cn = src.getName();
+			if (cn.startsWith("org.tmatesoft.hg.")) {
+				cn = "oth." + cn.substring("org.tmatesoft.hg.".length());
+			}
+			outStream.printf("(%s) ", cn);
+		}
+		outStream.printf("%s: %s", level, msg);
+		if (format.length() == 0 || format.charAt(format.length() - 1) != '\n') {
+			outStream.println();
+		}
+	}
+	protected void printf(String level, Class<?> src, Throwable th, String msg) {
+		if (msg != null || timestamp || isDebug || th == null) {
+			printf(level, src, msg == null ? "" : msg, (Object[]) null);
+		}
+		if (th != null) {
+			if (isDebug || isInfo) {
+				// full stack trace
+				th.printStackTrace(outStream);
+			} else {
+				// just title of the exception
+				outStream.printf("%s: %s\n", th.getClass().getName(), th.getMessage());
+			}
+		}
+	}
+
+	// alternative to hardcore System.out where SessionContext is not available now (or ever)
+	public static LogFacility newDefault() {
+		return new StreamLogFacility(true, true, true, System.out); 
+	}
+}
--- a/src/org/tmatesoft/hg/internal/SubrepoManager.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/internal/SubrepoManager.java	Fri Sep 16 05:35:32 2011 +0200
@@ -27,6 +27,7 @@
 import java.util.List;
 import java.util.Map;
 
+import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgSubrepoLocation;
 
@@ -61,7 +62,7 @@
 			BufferedReader br = new BufferedReader(new FileReader(hgsubFile));
 			return readConfig(br, state);
 		} catch (IOException ex) {
-			ex.printStackTrace(); // XXX log. Generally, shall not happen
+			HgInternals.getContext(repo).getLog().error(getClass(), ex, "Subrepo state read failed");
 		}
 		return Collections.emptyList();
 	}
--- a/src/org/tmatesoft/hg/repo/HgBranches.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgBranches.java	Fri Sep 16 05:35:32 2011 +0200
@@ -89,13 +89,13 @@
 			}
 			return lastInCache;
 		} catch (IOException ex) {
-			ex.printStackTrace(); // XXX log error, but otherwise do nothing 
+			repo.getContext().getLog().warn(getClass(), ex, null); // log error, but otherwise do nothing
 		} finally {
 			if (br != null) {
 				try {
 					br.close();
 				} catch (IOException ex) {
-					ex.printStackTrace(); // ignore
+					repo.getContext().getLog().info(getClass(), ex, null); // ignore
 				}
 			}
 		}
@@ -283,9 +283,8 @@
 				}
 			}
 			bw.close();
-		} catch (IOException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
+		} catch (IOException ex) {
+			repo.getContext().getLog().error(getClass(), ex, "Error writing branch cache file");
 		}
 	}
 
--- a/src/org/tmatesoft/hg/repo/HgBundle.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgBundle.java	Fri Sep 16 05:35:32 2011 +0200
@@ -23,6 +23,7 @@
 
 import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.HgInvalidFileException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.ByteArrayDataAccess;
@@ -93,7 +94,7 @@
 	 * @param hgRepo repository that shall possess base revision for this bundle
 	 * @param inspector callback to get each changeset found 
 	 */
-	public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgException, IOException {
+	public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgInvalidFileException {
 		Inspector bundleInsp = new Inspector() {
 			DigestHelper dh = new DigestHelper();
 			boolean emptyChangelog = true;
@@ -180,7 +181,7 @@
 		inspectChangelog(bundleInsp);
 	}
 
-	public void dump() throws IOException {
+	public void dump() throws HgException {
 		Dump dump = new Dump();
 		inspectAll(dump);
 		System.out.println("Total files:" + dump.names.size());
@@ -246,40 +247,51 @@
 		}
 	}
 
-	public void inspectChangelog(Inspector inspector) throws IOException {
+	public void inspectChangelog(Inspector inspector) throws HgInvalidFileException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
-		DataAccess da = getDataStream();
+		DataAccess da = null;
 		try {
+			da = getDataStream();
 			internalInspectChangelog(da, inspector);
+		} catch (IOException ex) {
+			throw new HgInvalidFileException("Bundle.inspectChangelog failed", ex, bundleFile);
 		} finally {
-			da.done();
+			if (da != null) {
+				da.done();
+			}
 		}
 	}
 
-	public void inspectManifest(Inspector inspector) throws IOException {
+	public void inspectManifest(Inspector inspector) throws HgInvalidFileException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
-		DataAccess da = getDataStream();
+		DataAccess da = null;
 		try {
+			da = getDataStream();
 			if (da.isEmpty()) {
 				return;
 			}
 			skipGroup(da); // changelog
 			internalInspectManifest(da, inspector);
+		} catch (IOException ex) {
+			throw new HgInvalidFileException("Bundle.inspectManifest failed", ex, bundleFile);
 		} finally {
-			da.done();
+			if (da != null) {
+				da.done();
+			}
 		}
 	}
 
-	public void inspectFiles(Inspector inspector) throws IOException {
+	public void inspectFiles(Inspector inspector) throws HgInvalidFileException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
-		DataAccess da = getDataStream();
+		DataAccess da = null;
 		try {
+			da = getDataStream();
 			if (da.isEmpty()) {
 				return;
 			}
@@ -289,22 +301,31 @@
 			}
 			skipGroup(da); // manifest
 			internalInspectFiles(da, inspector);
+		} catch (IOException ex) {
+			throw new HgInvalidFileException("Bundle.inspectFiles failed", ex, bundleFile);
 		} finally {
-			da.done();
+			if (da != null) {
+				da.done();
+			}
 		}
 	}
 
-	public void inspectAll(Inspector inspector) throws IOException {
+	public void inspectAll(Inspector inspector) throws HgInvalidFileException {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
-		DataAccess da = getDataStream();
+		DataAccess da = null;
 		try {
+			da = getDataStream();
 			internalInspectChangelog(da, inspector);
 			internalInspectManifest(da, inspector);
 			internalInspectFiles(da, inspector);
+		} catch (IOException ex) {
+			throw new HgInvalidFileException("Bundle.inspectAll failed", ex, bundleFile);
 		} finally {
-			da.done();
+			if (da != null) {
+				da.done();
+			}
 		}
 	}
 
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Fri Sep 16 05:35:32 2011 +0200
@@ -139,7 +139,7 @@
 					try {
 						fc.close();
 					} catch (IOException ex) {
-						ex.printStackTrace();
+						getRepo().getContext().getLog().info(getClass(), ex, null);
 					}
 				}
 			}
--- a/src/org/tmatesoft/hg/repo/HgDirstate.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgDirstate.java	Fri Sep 16 05:35:32 2011 +0200
@@ -144,7 +144,8 @@
 				}
 			}
 		} catch (IOException ex) {
-			ex.printStackTrace(); // FIXME log error, clean dirstate?
+			repo.getContext().getLog().error(getClass(), ex, null); 
+			// FIXME clean dirstate?
 		} finally {
 			da.done();
 		}
@@ -218,7 +219,7 @@
 				branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b;
 				r.close();
 			} catch (IOException ex) {
-				ex.printStackTrace(); // XXX log verbose debug, exception might be legal here (i.e. FileNotFound)
+				repo.getContext().getLog().debug(HgDirstate.class, ex, null); // log verbose debug, exception might be legal here (i.e. FileNotFound)
 				// IGNORE
 			}
 		}
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgInternals.java	Fri Sep 16 05:35:32 2011 +0200
@@ -25,6 +25,7 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
+import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ConfigFile;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.RelativePathRewrite;
@@ -114,6 +115,11 @@
 		// path caching is better to be done in the code which knows that path are being reused 
 		return new FileWalker(repoRoot, pathSrc, workindDirScope);
 	}
+	
+	// expose othewise 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 local revision number for validity (not all negative values are wrong as long as we use negative constants)
--- a/src/org/tmatesoft/hg/repo/HgLookup.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgLookup.java	Fri Sep 16 05:35:32 2011 +0200
@@ -22,10 +22,11 @@
 import java.net.URL;
 
 import org.tmatesoft.hg.core.HgBadArgumentException;
-import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.HgInvalidFileException;
+import org.tmatesoft.hg.core.SessionContext;
+import org.tmatesoft.hg.internal.BasicSessionContext;
 import org.tmatesoft.hg.internal.ConfigFile;
 import org.tmatesoft.hg.internal.DataAccessProvider;
-import org.tmatesoft.hg.internal.Internals;
 
 /**
  * Utility methods to find Mercurial repository at a given location
@@ -36,17 +37,25 @@
 public class HgLookup {
 
 	private ConfigFile globalCfg;
+	private SessionContext sessionContext;
+	
+	public HgLookup() {
+	}
+	
+	public HgLookup(SessionContext ctx) {
+		sessionContext = ctx;
+	}
 
-	public HgRepository detectFromWorkingDir() throws HgException {
+	public HgRepository detectFromWorkingDir() throws HgInvalidFileException {
 		return detect(System.getProperty("user.dir"));
 	}
 
-	public HgRepository detect(String location) throws HgException {
+	public HgRepository detect(String location) throws HgInvalidFileException {
 		return detect(new File(location));
 	}
 
 	// look up in specified location and above
-	public HgRepository detect(File location) throws HgException {
+	public HgRepository detect(File location) throws HgInvalidFileException {
 		File dir = location.getAbsoluteFile();
 		File repository;
 		do {
@@ -64,17 +73,17 @@
 		}
 		try {
 			String repoPath = repository.getParentFile().getCanonicalPath();
-			return new HgRepository(repoPath, repository);
+			return new HgRepository(getContext(), repoPath, repository);
 		} catch (IOException ex) {
-			throw new HgException(location.toString(), ex);
+			throw new HgInvalidFileException(location.toString(), ex, location);
 		}
 	}
 	
-	public HgBundle loadBundle(File location) /*XXX perhaps, HgDataStreamException or anything like HgMalformedDataException? Or RuntimeEx is better?*/{
+	public HgBundle loadBundle(File location) throws HgInvalidFileException {
 		if (location == null || !location.canRead()) {
-			throw new IllegalArgumentException();
+			throw new HgInvalidFileException(String.format("Can't read file %s", location == null ? null : location.getPath()), null, location);
 		}
-		return new HgBundle(new DataAccessProvider(), location).link();
+		return new HgBundle(new DataAccessProvider(getContext()), location).link();
 	}
 	
 	/**
@@ -105,24 +114,36 @@
 				throw new HgBadArgumentException(String.format("Found %s server spec in the config, but failed to initialize with it", key), ex);
 			}
 		}
-		return new HgRemoteRepository(url);
+		return new HgRemoteRepository(getContext(), url);
 	}
 	
-	public HgRemoteRepository detect(URL url) throws HgException {
+	public HgRemoteRepository detect(URL url) throws HgBadArgumentException {
 		if (url == null) {
 			throw new IllegalArgumentException();
 		}
 		if (Boolean.FALSE.booleanValue()) {
 			throw HgRepository.notImplemented();
 		}
-		return new HgRemoteRepository(url);
+		return new HgRemoteRepository(getContext(), url);
 	}
 
 	private ConfigFile getGlobalConfig() {
 		if (globalCfg == null) {
-			globalCfg = new Internals().newConfigFile();
-			globalCfg.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
+			globalCfg = new ConfigFile();
+			try {
+				globalCfg.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
+			} catch (IOException ex) {
+				// XXX perhaps, makes sense to let caller/client know that we've failed to read global config? 
+				getContext().getLog().warn(getClass(), ex, null);
+			}
 		}
 		return globalCfg;
 	}
+
+	private SessionContext getContext() {
+		if (sessionContext == null) {
+			sessionContext = new BasicSessionContext(null, null);
+		}
+		return sessionContext;
+	}
 }
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Fri Sep 16 05:35:32 2011 +0200
@@ -48,8 +48,10 @@
 
 import org.tmatesoft.hg.core.HgBadArgumentException;
 import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgInvalidFileException;
 import org.tmatesoft.hg.core.HgRemoteConnectionException;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.core.SessionContext;
 
 /**
  * WORK IN PROGRESS, DO NOT USE
@@ -66,12 +68,14 @@
 	private final String authInfo;
 	private final boolean debug = Boolean.parseBoolean(System.getProperty("hg4j.remote.debug"));
 	private HgLookup lookupHelper;
+	private final SessionContext sessionContext;
 
-	HgRemoteRepository(URL url) throws HgBadArgumentException {
-		if (url == null) {
+	HgRemoteRepository(SessionContext ctx, URL url) throws HgBadArgumentException {
+		if (url == null || ctx == null) {
 			throw new IllegalArgumentException();
 		}
 		this.url = url;
+		sessionContext = ctx;
 		if ("https".equals(url.getProtocol())) {
 			try {
 				sslContext = SSLContext.getInstance("SSL");
@@ -322,7 +326,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 {
+	public HgBundle getChanges(List<Nodeid> roots) throws HgRemoteConnectionException, HgInvalidFileException {
 		List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots;
 		StringBuilder sb = new StringBuilder(20 + _roots.size() * 41);
 		sb.append("roots=");
@@ -361,7 +365,7 @@
 
 	private HgLookup getLookupHelper() {
 		if (lookupHelper == null) {
-			lookupHelper = new HgLookup();
+			lookupHelper = new HgLookup(sessionContext);
 		}
 		return lookupHelper;
 	}
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Fri Sep 16 05:35:32 2011 +0200
@@ -27,13 +27,13 @@
 
 import org.tmatesoft.hg.core.HgDataStreamException;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.ConfigFile;
 import org.tmatesoft.hg.internal.DataAccessProvider;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.Filter;
 import org.tmatesoft.hg.internal.Internals;
-import org.tmatesoft.hg.internal.RequiresFile;
 import org.tmatesoft.hg.internal.RevlogStream;
 import org.tmatesoft.hg.internal.SubrepoManager;
 import org.tmatesoft.hg.util.CancelledException;
@@ -73,6 +73,7 @@
 	private final PathRewrite dataPathHelper;
 	private final PathRewrite repoPathHelper;
 	private final boolean isCaseSensitiveFileSystem;
+	private final SessionContext sessionContext;
 
 	private HgChangelog changelog;
 	private HgManifest manifest;
@@ -95,20 +96,23 @@
 		dataAccess = null;
 		dataPathHelper = repoPathHelper = null;
 		normalizePath = null;
+		sessionContext = null;
 		isCaseSensitiveFileSystem = !Internals.runningOnWindows();
 	}
 	
-	HgRepository(String repositoryPath, File repositoryRoot) {
+	HgRepository(SessionContext ctx, String repositoryPath, File repositoryRoot) {
 		assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory();
 		assert repositoryPath != null; 
 		assert repositoryRoot != null;
+		assert ctx != null;
 		repoDir = repositoryRoot;
 		workingDir = repoDir.getParentFile();
 		if (workingDir == null) {
 			throw new IllegalArgumentException(repoDir.toString());
 		}
 		repoLocation = repositoryPath;
-		dataAccess = new DataAccessProvider();
+		sessionContext = ctx;
+		dataAccess = new DataAccessProvider(ctx);
 		final boolean runningOnWindows = Internals.runningOnWindows();
 		isCaseSensitiveFileSystem = !runningOnWindows;
 		if (runningOnWindows) {
@@ -127,7 +131,7 @@
 		} else {
 			normalizePath = new PathRewrite.Empty(); // or strip leading slash, perhaps? 
 		}
-		parseRequires();
+		impl.parseRequires(this, new File(repoDir, "requires"));
 		dataPathHelper = impl.buildDataFilesHelper();
 		repoPathHelper = impl.buildRepositoryFilesHelper();
 	}
@@ -176,20 +180,23 @@
 							final String content = new String(sink.toArray(), "UTF8");
 							tags.readGlobal(new StringReader(content));
 						} catch (CancelledException ex) {
-							ex.printStackTrace(); // IGNORE, can't happen, we did not configure cancellation
+							 // IGNORE, can't happen, we did not configure cancellation
+							getContext().getLog().debug(getClass(), ex, null);
 						} catch (HgDataStreamException ex) {
-							ex.printStackTrace(); // FIXME need to react
+							getContext().getLog().error(getClass(), ex, null);
+							// FIXME need to react
 						} catch (IOException ex) {
 							// UnsupportedEncodingException can't happen (UTF8)
 							// only from readGlobal. Need to reconsider exceptions thrown from there
-							ex.printStackTrace(); // XXX need to decide what to do this. failure to read single revision shall not break complete cycle
+							getContext().getLog().error(getClass(), ex, null);
+							// XXX need to decide what to do this. failure to read single revision shall not break complete cycle
 						}
 					}
 				}
 				tags.readGlobal(new File(getWorkingDir(), ".hgtags")); // XXX replace with HgDataFile.workingCopy
 				tags.readLocal(new File(repoDir, "localtags"));
 			} catch (IOException ex) {
-				ex.printStackTrace(); // FIXME log or othewise report
+				getContext().getLog().error(getClass(), ex, null);
 			}
 		}
 		return tags;
@@ -302,7 +309,7 @@
 				File ignoreFile = new File(getWorkingDir(), ".hgignore");
 				ignore.read(ignoreFile);
 			} catch (IOException ex) {
-				ex.printStackTrace(); // log warn
+				getContext().getLog().warn(getClass(), ex, null);
 			}
 		}
 		return ignore;
@@ -334,7 +341,7 @@
 					fake.deleteOnExit();
 					return new RevlogStream(dataAccess, fake);
 				} catch (IOException ex) {
-					ex.printStackTrace(); // FIXME report in debug
+					getContext().getLog().info(getClass(), ex, null);
 				}
 			}
 		}
@@ -344,11 +351,15 @@
 	// can't expose internal class, otherwise seems reasonable to have it in API
 	/*package-local*/ ConfigFile getConfigFile() {
 		if (configFile == null) {
-			configFile = impl.newConfigFile();
-			configFile.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
-			// last one, overrides anything else
-			// <repo>/.hg/hgrc
-			configFile.addLocation(new File(getRepositoryRoot(), "hgrc"));
+			configFile = new ConfigFile();
+			try {
+				configFile.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
+				// last one, overrides anything else
+				// <repo>/.hg/hgrc
+				configFile.addLocation(new File(getRepositoryRoot(), "hgrc"));
+			} catch (IOException ex) {
+				getContext().getLog().warn(getClass(), ex, "Errors while reading user configuration file");
+			}
 		}
 		return configFile;
 	}
@@ -364,6 +375,10 @@
 	/*package-local*/ File getFile(HgDataFile dataFile) {
 		return new File(getWorkingDir(), dataFile.getPath().toString());
 	}
+	
+	/*package-local*/ SessionContext getContext() {
+		return sessionContext;
+	}
 
 	private List<Filter> instantiateFilters(Path p, Filter.Options opts) {
 		List<Filter.Factory> factories = impl.getFilters(this, getConfigFile());
@@ -379,9 +394,4 @@
 		}
 		return rv;
 	}
-
-	private void parseRequires() {
-		new RequiresFile().parse(impl, new File(repoDir, "requires"));
-	}
-
 }
--- a/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Fri Sep 16 05:35:32 2011 +0200
@@ -19,7 +19,7 @@
 import java.io.File;
 
 import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.HgInvalidFileException;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.util.Path;
 
@@ -86,7 +86,7 @@
 		return owner;
 	}
 
-	public HgRepository getRepo() throws HgException {
+	public HgRepository getRepo() throws HgInvalidFileException {
 		if (kind != Kind.Hg) {
 			throw new HgBadStateException();
 		}
--- a/src/org/tmatesoft/hg/repo/HgTags.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgTags.java	Fri Sep 16 05:35:32 2011 +0200
@@ -107,7 +107,7 @@
 				continue;
 			}
 			if (line.length() < 40+2 /*nodeid, space and at least single-char tagname*/) {
-				System.out.println("Bad tags line:" + line); // FIXME log or otherwise report (IStatus analog?) 
+				repo.getContext().getLog().warn(getClass(), "Bad tags line: %s", line); 
 				continue;
 			}
 			int spacePos = line.indexOf(' ');
@@ -151,7 +151,7 @@
 				}
 				
 			} else {
-				System.out.println("Bad tags line:" + line); // FIXME see above
+				repo.getContext().getLog().warn(getClass(), "Bad tags line: %s", line);
 			}
 		}
 	}
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Fri Sep 16 05:35:32 2011 +0200
@@ -397,59 +397,61 @@
 	
 	private boolean areTheSame(FileInfo f, final byte[] data, Path p) {
 		ReadableByteChannel is = null;
-		try {
-			try {
-				is = f.newInputChannel();
-				ByteBuffer fb = ByteBuffer.allocate(min(1 + data.length * 2 /*to fit couple of lines appended; never zero*/, 8192));
-				class Check implements ByteChannel {
-					final boolean debug = false; // XXX may want to add global variable to allow clients to turn 
-					boolean sameSoFar = true;
-					int x = 0;
+		class Check implements ByteChannel {
+			final boolean debug = repo.getContext().getLog().isDebug(); 
+			boolean sameSoFar = true;
+			int x = 0;
 
-					public int write(ByteBuffer buffer) {
-						for (int i = buffer.remaining(); i > 0; i--, x++) {
-							if (x >= data.length /*file has been appended*/ || data[x] != buffer.get()) {
-								if (debug) {
-									byte[] xx = new byte[15];
-									if (buffer.position() > 5) {
-										buffer.position(buffer.position() - 5);
-									}
-									buffer.get(xx);
-									System.out.print("expected >>" + new String(data, max(0, x - 4), 20) + "<< but got >>");
-									System.out.println(new String(xx) + "<<");
-								}
-								sameSoFar = false;
-								break;
+			public int write(ByteBuffer buffer) {
+				for (int i = buffer.remaining(); i > 0; i--, x++) {
+					if (x >= data.length /*file has been appended*/ || data[x] != buffer.get()) {
+						if (debug) {
+							byte[] xx = new byte[15];
+							if (buffer.position() > 5) {
+								buffer.position(buffer.position() - 5);
 							}
+							buffer.get(xx, 0, min(xx.length, i));
+							repo.getContext().getLog().debug(getClass(), "expected >>%s<< but got >>%s<<", new String(data, max(0, x - 4), min(data.length - x, 20)), new String(xx));
 						}
-						buffer.position(buffer.limit()); // mark as read
-						return buffer.limit();
-					}
-					
-					public boolean sameSoFar() {
-						return sameSoFar;
-					}
-					public boolean ultimatelyTheSame() {
-						return sameSoFar && x == data.length;
+						sameSoFar = false;
+						break;
 					}
-				};
-				Check check = new Check(); 
-				FilterByteChannel filters = new FilterByteChannel(check, repo.getFiltersFromWorkingDirToRepo(p));
-				while (is.read(fb) != -1 && check.sameSoFar()) {
-					fb.flip();
-					filters.write(fb);
-					fb.compact();
 				}
-				return check.ultimatelyTheSame();
-			} catch (IOException ex) {
-				ex.printStackTrace(); // log warn
-			} finally {
-				if (is != null) {
+				buffer.position(buffer.limit()); // mark as read
+				return buffer.limit();
+			}
+			
+			public boolean sameSoFar() {
+				return sameSoFar;
+			}
+			public boolean ultimatelyTheSame() {
+				return sameSoFar && x == data.length;
+			}
+		};
+		Check check = new Check(); 
+		try {
+			is = f.newInputChannel();
+			ByteBuffer fb = ByteBuffer.allocate(min(1 + data.length * 2 /*to fit couple of lines appended; never zero*/, 8192));
+			FilterByteChannel filters = new FilterByteChannel(check, repo.getFiltersFromWorkingDirToRepo(p));
+			while (is.read(fb) != -1 && check.sameSoFar()) {
+				fb.flip();
+				filters.write(fb);
+				fb.compact();
+			}
+			return check.ultimatelyTheSame();
+		} catch (CancelledException ex) {
+			repo.getContext().getLog().warn(getClass(), ex, "Unexpected cancellation");
+			return check.ultimatelyTheSame();
+		} catch (IOException ex) {
+			repo.getContext().getLog().warn(getClass(), ex, null);
+		} finally {
+			if (is != null) {
+				try {
 					is.close();
+				} catch (IOException ex) {
+					repo.getContext().getLog().info(getClass(), ex, null);
 				}
 			}
-		} catch (/*TODO typed*/Exception ex) {
-			ex.printStackTrace();
 		}
 		return false;
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/util/LogFacility.java	Fri Sep 16 05:35:32 2011 +0200
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.util;
+
+import org.tmatesoft.hg.internal.Experimental;
+
+/**
+ * WORK IN PROGRESS
+ * 
+ * Intention of this class is to abstract away almost any log facility out there clients might be using with the <b>Hg4J</b> library, 
+ * not to be a full-fledged logging facility of its own.
+ * 
+ * Implementations may wrap platform- or application-specific loggers, e.g. {@link java.util.logging.Logger} or 
+ * <code>org.eclipse.core.runtime.ILog</code>
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@Experimental(reason="API might get changed")
+public interface LogFacility {
+
+	boolean isDebug();
+	boolean isInfo();
+
+	// src and format never null
+	void debug(Class<?> src, String format, Object... args);
+	void info(Class<?> src, String format, Object... args);
+	void warn(Class<?> src, String format, Object... args);
+	void error(Class<?> src, String format, Object... args);
+
+	// src shall be non null, either th or message or both
+	void debug(Class<?> src, Throwable th, String message);
+	void info(Class<?> src, Throwable th, String message);
+	void warn(Class<?> src, Throwable th, String message);
+	void error(Class<?> src, Throwable th, String message);
+}
--- a/src/org/tmatesoft/hg/util/RegularFileInfo.java	Wed Sep 14 04:41:57 2011 +0200
+++ b/src/org/tmatesoft/hg/util/RegularFileInfo.java	Fri Sep 16 05:35:32 2011 +0200
@@ -23,6 +23,8 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
 
+import org.tmatesoft.hg.internal.StreamLogFacility;
+
 /**
  *
  * @author Artem Tikhomirov
@@ -54,7 +56,7 @@
 		try {
 			return new FileInputStream(file).getChannel();
 		} catch (FileNotFoundException ex) {
-			ex.printStackTrace(); // FIXME log debug.
+			StreamLogFacility.newDefault().debug(getClass(), ex, null);
 			// shall not happen, provided this class is used correctly
 			return new ReadableByteChannel() {