changeset 456:909306e412e2

Refactor LogFacility and SessionContext, better API for both
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 18 Jun 2012 16:54:00 +0200
parents 36fd1fd06492
children d78cb5ca3053
files cmdline/org/tmatesoft/hg/console/Main.java cmdline/org/tmatesoft/hg/console/Status.java src/org/tmatesoft/hg/core/HgLogCommand.java src/org/tmatesoft/hg/core/SessionContext.java src/org/tmatesoft/hg/internal/BasicSessionContext.java src/org/tmatesoft/hg/internal/DataAccessProvider.java src/org/tmatesoft/hg/internal/EncodingHelper.java src/org/tmatesoft/hg/internal/Internals.java src/org/tmatesoft/hg/internal/KeywordFilter.java src/org/tmatesoft/hg/internal/NewlineFilter.java src/org/tmatesoft/hg/internal/PropertyMarshal.java src/org/tmatesoft/hg/internal/StreamLogFacility.java src/org/tmatesoft/hg/repo/HgBranches.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgDirstate.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgManifest.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/repo/HgRepositoryFiles.java src/org/tmatesoft/hg/repo/HgTags.java src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java src/org/tmatesoft/hg/repo/Revlog.java src/org/tmatesoft/hg/util/LogFacility.java src/org/tmatesoft/hg/util/Outcome.java src/org/tmatesoft/hg/util/RegularFileInfo.java src/org/tmatesoft/hg/util/RegularFileStats.java
diffstat 27 files changed, 248 insertions(+), 177 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Mon Jun 18 16:54:00 2012 +0200
@@ -18,6 +18,7 @@
 
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 import static org.tmatesoft.hg.repo.HgRepository.WORKING_COPY;
+import static org.tmatesoft.hg.util.LogFacility.Severity.*;
 
 import java.io.File;
 import java.io.IOException;
@@ -72,6 +73,7 @@
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathRewrite;
 import org.tmatesoft.hg.util.ProgressSupport;
+import org.tmatesoft.hg.util.LogFacility.Severity;
 
 /**
  * Various debug dumps. 
@@ -250,17 +252,17 @@
 	}
 	
 	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);
+		LogFacility fc = new StreamLogFacility(Debug, true, System.out);
+		System.out.printf("isDebug: %s, isInfo:%s\n", fc.isDebug(), fc.getLevel() == Info);
+		fc.dump(getClass(), Debug, "%d", 1);
+		fc.dump(getClass(), Info, "%d\n", 2);
+		fc.dump(getClass(), Warn, "%d\n", 3);
+		fc.dump(getClass(), Error, "%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");
+		fc.dump(getClass(), Debug, ex, "message");
+		fc.dump(getClass(), Info, ex, null);
+		fc.dump(getClass(), Warn, ex, null);
+		fc.dump(getClass(), Error, ex, "message");
 	}
 	
 	private void testTreeTraversal() throws Exception {
--- a/cmdline/org/tmatesoft/hg/console/Status.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Status.java	Mon Jun 18 16:54:00 2012 +0200
@@ -33,6 +33,7 @@
 import org.tmatesoft.hg.core.HgStatus.Kind;
 import org.tmatesoft.hg.core.HgStatusCommand;
 import org.tmatesoft.hg.core.HgStatusHandler;
+import org.tmatesoft.hg.util.Outcome;
 import org.tmatesoft.hg.util.Path;
 
 /**
@@ -86,7 +87,7 @@
 				}
 			}
 			
-			public void error(Path file, org.tmatesoft.hg.util.Outcome s) {
+			public void error(Path file, Outcome s) {
 				System.out.printf("FAILURE: %s %s\n", s.getMessage(), file);
 				s.getException().printStackTrace(System.out);
 			}
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/core/HgLogCommand.java	Mon Jun 18 16:54:00 2012 +0200
@@ -17,6 +17,7 @@
 package org.tmatesoft.hg.core;
 
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Error;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -37,10 +38,10 @@
 import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgInvalidStateException;
+import org.tmatesoft.hg.repo.HgParentChildMap;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.repo.HgStatusCollector;
-import org.tmatesoft.hg.repo.HgParentChildMap;
 import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Pair;
@@ -545,7 +546,7 @@
 						}
 					}
 					if (!sanity) {
-						HgInternals.getContext(repo).getLog().error(getClass(), "Index of revision %d:%s doesn't match any of requested", cs.getRevisionIndex(), cs.getNodeid().shortNotation());
+						HgInternals.getContext(repo).getLog().dump(getClass(), Error, "Index of revision %d:%s doesn't match any of requested", cs.getRevisionIndex(), cs.getNodeid().shortNotation());
 					}
 					assert sanity;
 				}
--- a/src/org/tmatesoft/hg/core/SessionContext.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/core/SessionContext.java	Mon Jun 18 16:54:00 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -16,24 +16,33 @@
  */
 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
  * 
- * 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 
+ * <p>Note, API is likely to be extended in future versions, adding more object to share. 
  * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-@Experimental(reason="Work in progress")
-public interface SessionContext {
-	LogFacility getLog();
+public abstract class SessionContext {
+	// abstract class to facilitate adding more functionality without API break
 	
 	/**
-	 * LIKELY TO CHANGE TO STANDALONE CONFIGURATION OBJECT
+	 * Access wrapper for a system log facility.
+	 * @return facility to direct dumps to, never <code>null</code>
 	 */
-	Object getProperty(String name, Object defaultValue);
+	public abstract LogFacility getLog();
+	
+	/**
+	 * Access configuration parameters of the session.
+	 * @param name name of the session configuration parameter
+	 * @param defaultValue value to return if parameter is not configured
+	 * @return value of the session parameter, defaultValue if none found
+	 */
+	public abstract Object getConfigurationProperty(String name, Object defaultValue);
+	// perhaps, later may add Configuration object, with PropertyMarshal's helpers
+	// e.g. when there's standalone Caches and WritableSessionProperties objects
 }
--- a/src/org/tmatesoft/hg/internal/BasicSessionContext.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/BasicSessionContext.java	Mon Jun 18 16:54:00 2012 +0200
@@ -21,13 +21,14 @@
 
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.util.LogFacility;
+import org.tmatesoft.hg.util.LogFacility.Severity;
 
 /**
  *
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class BasicSessionContext implements SessionContext {
+public class BasicSessionContext extends SessionContext {
 
 	private LogFacility logFacility;
 	private final Map<String, Object> properties;
@@ -42,25 +43,23 @@
 		properties = propertyOverrides == null ? Collections.<String,Object>emptyMap() : (Map<String, Object>) propertyOverrides;
 	}
 
+	@Override
 	public LogFacility getLog() {
 		// e.g. for exceptions that we can't handle but log (e.g. FileNotFoundException when we've checked beforehand file.canRead()
 		if (logFacility == null) {
-			boolean needDebug = _getBooleanProperty("hg.consolelog.debug", false);
-			boolean needInfo = needDebug || _getBooleanProperty("hg.consolelog.info", false);
-			logFacility = new StreamLogFacility(needDebug, needInfo, true, System.out);
+			PropertyMarshal pm = new PropertyMarshal(this);
+			boolean needDebug = pm.getBoolean("hg4j.consolelog.debug", false);
+			boolean needInfo = pm.getBoolean("hg4j.consolelog.info", false);
+			boolean needTime = pm.getBoolean("hg4j.consolelog.tstamp", true);
+			Severity l = needDebug ? Severity.Debug : (needInfo ? Severity.Info : Severity.Warn);
+			logFacility = new StreamLogFacility(l, needTime, System.out);
 		}
 		return logFacility;
 	}
 	
-	private boolean _getBooleanProperty(String name, boolean defaultValue) {
-		// can't use <T> and unchecked cast because got no confidence passed properties are strictly of the kind of my default values,
-		// i.e. if boolean from outside comes as "true", while I pass default as Boolean or vice versa.  
-		Object p = getProperty(name, defaultValue);
-		return p instanceof Boolean ? ((Boolean) p).booleanValue() : Boolean.parseBoolean(String.valueOf(p));
-	}
-
-	// TODO specific helpers for boolean and int values
-	public Object getProperty(String name, Object defaultValue) {
+	// specific helpers for boolean and int values are available from PropertyMarshal
+	@Override
+	public Object getConfigurationProperty(String name, Object defaultValue) {
 		// NOTE, this method is invoked from getLog(), hence do not call getLog from here unless changed appropriately
 		Object value = properties.get(name);
 		if (value != null) {
--- a/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Mon Jun 18 16:54:00 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2011 TMate Software Ltd
+ * Copyright (c) 2010-2012 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -16,6 +16,9 @@
  */
 package org.tmatesoft.hg.internal;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Error;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -46,25 +49,22 @@
 	private static final int DEFAULT_MAPIO_BUFFER = DEFAULT_MAPIO_LIMIT; // same as default boundary
 
 	private final int mapioMagicBoundary;
-	private final int bufferSize;
+	private final int bufferSize, mapioBufSize;
 	private final SessionContext context;
 
 	public DataAccessProvider(SessionContext ctx) {
-		this(ctx, getConfigOption(ctx, CFG_PROPERTY_MAPIO_LIMIT, DEFAULT_MAPIO_LIMIT), getConfigOption(ctx, CFG_PROPERTY_FILE_BUFFER_SIZE, DEFAULT_FILE_BUFFER));
+		context = ctx;
+		PropertyMarshal pm = new PropertyMarshal(ctx);
+		mapioMagicBoundary = pm.getInt(CFG_PROPERTY_MAPIO_LIMIT, DEFAULT_MAPIO_LIMIT);
+		bufferSize = pm.getInt(CFG_PROPERTY_FILE_BUFFER_SIZE, DEFAULT_FILE_BUFFER);
+		mapioBufSize = pm.getInt(CFG_PROPERTY_MAPIO_BUFFER_SIZE, DEFAULT_MAPIO_BUFFER);
 	}
 	
-	private static int getConfigOption(SessionContext ctx, String optName, int defaultValue) {
-		Object v = ctx.getProperty(optName, defaultValue);
-		if (false == v instanceof Number) {
-			v = Integer.parseInt(v.toString());
-		}
-		return ((Number) v).intValue();
-	}
-
-	public DataAccessProvider(SessionContext ctx, int mapioBoundary, int regularBufferSize) {
+	public DataAccessProvider(SessionContext ctx, int mapioBoundary, int regularBufferSize, int mapioBufferSize) {
 		context = ctx;
 		mapioMagicBoundary = mapioBoundary == 0 ? Integer.MAX_VALUE : mapioBoundary;
 		bufferSize = regularBufferSize;
+		mapioBufSize = mapioBufferSize;
 	}
 
 	public DataAccess create(File f) {
@@ -76,7 +76,6 @@
 			long flen = fc.size();
 			if (flen > mapioMagicBoundary) {
 				// TESTS: bufLen of 1024 was used to test MemMapFileAccess
-				int mapioBufSize = getConfigOption(context, CFG_PROPERTY_MAPIO_BUFFER_SIZE, DEFAULT_MAPIO_BUFFER);
 				return new MemoryMapFileAccess(fc, flen, mapioBufSize, context.getLog());
 			} else {
 				// XXX once implementation is more or less stable,
@@ -88,7 +87,7 @@
 			}
 		} catch (IOException ex) {
 			// unlikely to happen, we've made sure file exists.
-			context.getLog().error(getClass(), ex, null);
+			context.getLog().dump(getClass(), Error, ex, null);
 		}
 		return new DataAccess(); // non-null, empty.
 	}
@@ -177,14 +176,14 @@
 					}
 					if (i == 0) {
 						// if first attempt failed, try to free some virtual memory, see Issue 30 for details
-						logFacility.warn(getClass(), ex, "Memory-map failed, gonna try gc() to free virtual memory");
+						logFacility.dump(getClass(), Warn, ex, "Memory-map failed, gonna try gc() to free virtual memory");
 					}
 					try {
 						buffer = null;
 						System.gc();
 						Thread.sleep((1+i) * 1000);
 					} catch (Throwable t) {
-						logFacility.error(getClass(), t, "Bad luck");
+						logFacility.dump(getClass(), Error, t, "Bad luck");
 					}
 				}
 			}
@@ -230,7 +229,7 @@
 				try {
 					fileChannel.close();
 				} catch (IOException ex) {
-					logFacility.debug(getClass(), ex, null);
+					logFacility.dump(getClass(), Warn, ex, null);
 				}
 				fileChannel = null;
 			}
@@ -364,7 +363,7 @@
 				try {
 					fileChannel.close();
 				} catch (IOException ex) {
-					logFacility.debug(getClass(), ex, null);
+					logFacility.dump(getClass(), Warn, ex, null);
 				}
 				fileChannel = null;
 			}
--- a/src/org/tmatesoft/hg/internal/EncodingHelper.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/EncodingHelper.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.internal;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Error;
+
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
@@ -71,7 +73,7 @@
 			bb.get(rv, 0, rv.length);
 			return rv;
 		} catch (CharacterCodingException ex) {
-			sessionContext.getLog().error(getClass(), ex, String.format("Use of charset %s failed, resort to system default", charset().name()));
+			sessionContext.getLog().dump(getClass(), Error, ex, String.format("Use of charset %s failed, resort to system default", charset().name()));
 			// resort to system-default
 			return s.getBytes();
 		}
@@ -88,7 +90,7 @@
 		try {
 			return decoder.decode(ByteBuffer.wrap(data, start, length)).toString();
 		} catch (CharacterCodingException ex) {
-			sessionContext.getLog().error(getClass(), ex, String.format("Use of charset %s failed, resort to system default", charset().name()));
+			sessionContext.getLog().dump(getClass(), Error, ex, String.format("Use of charset %s failed, resort to system default", charset().name()));
 			// resort to system-default
 			return new String(data, start, length);
 		}
--- a/src/org/tmatesoft/hg/internal/Internals.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/Internals.java	Mon Jun 18 16:54:00 2012 +0200
@@ -17,6 +17,7 @@
 package org.tmatesoft.hg.internal;
 
 import static org.tmatesoft.hg.internal.RequiresFile.*;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Error;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -83,8 +84,7 @@
 	public Internals(SessionContext ctx) {
 		sessionContext = ctx;
 		isCaseSensitiveFileSystem = !runningOnWindows();
-		Object p = ctx.getProperty(CFG_PROPERTY_REVLOG_STREAM_CACHE, true);
-		shallCacheRevlogsInRepo = p instanceof Boolean ? ((Boolean) p).booleanValue() : Boolean.parseBoolean(String.valueOf(p));
+		shallCacheRevlogsInRepo = new PropertyMarshal(ctx).getBoolean(CFG_PROPERTY_REVLOG_STREAM_CACHE, true);
 	}
 	
 	public void parseRequires(HgRepository hgRepo, File requiresFile) throws HgInvalidControlFileException {
@@ -173,7 +173,7 @@
 	}
 	
 	private Charset getFileEncoding() {
-		Object altEncoding = sessionContext.getProperty(CFG_PROPERTY_FS_FILENAME_ENCODING, null);
+		Object altEncoding = sessionContext.getConfigurationProperty(CFG_PROPERTY_FS_FILENAME_ENCODING, null);
 		Charset cs;
 		if (altEncoding == null) {
 			cs = Charset.defaultCharset();
@@ -183,7 +183,7 @@
 			} catch (IllegalArgumentException ex) {
 				// both IllegalCharsetNameException and UnsupportedCharsetException are subclasses of IAE, too
 				// not severe enough to throw an exception, imo. Just record the fact it's bad ad we ignore it 
-				sessionContext.getLog().error(Internals.class, ex, String.format("Bad configuration value for filename encoding %s", altEncoding));
+				sessionContext.getLog().dump(Internals.class, Error, ex, String.format("Bad configuration value for filename encoding %s", altEncoding));
 				cs = Charset.defaultCharset();
 			}
 		}
@@ -224,7 +224,7 @@
 	 */
 	private static File findHgInstallRoot(SessionContext ctx) {
 		// let clients to override Hg install location 
-		String p = (String) ctx.getProperty(CFG_PROPERTY_HG_INSTALL_ROOT, null);
+		String p = (String) ctx.getConfigurationProperty(CFG_PROPERTY_HG_INSTALL_ROOT, null);
 		if (p != null) {
 			return new File(p);
 		}
--- a/src/org/tmatesoft/hg/internal/KeywordFilter.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/KeywordFilter.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.internal;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Error;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Date;
@@ -263,7 +265,7 @@
 			int csetRev = repo.getFileNode(path).getChangesetRevisionIndex(HgRepository.TIP);
 			return repo.getChangelog().getRevision(csetRev).shortNotation();
 		} catch (HgRuntimeException ex) {
-			HgInternals.getContext(repo).getLog().error(getClass(), ex, null);
+			HgInternals.getContext(repo).getLog().dump(getClass(), Error, ex, null);
 			return Nodeid.NULL.shortNotation(); // XXX perhaps, might return anything better? Not sure how hg approaches this. 
 		}
 	}
@@ -272,7 +274,7 @@
 		try {
 			return getChangeset().user();
 		} catch (HgRuntimeException ex) {
-			HgInternals.getContext(repo).getLog().error(getClass(), ex, null);
+			HgInternals.getContext(repo).getLog().dump(getClass(), Error, ex, null);
 			return "";
 		}
 	}
@@ -282,7 +284,7 @@
 		try {
 			d = getChangeset().date();
 		} catch (HgRuntimeException ex) {
-			HgInternals.getContext(repo).getLog().error(getClass(), ex, null);
+			HgInternals.getContext(repo).getLog().dump(getClass(), Error, ex, null);
 			d = new Date(0l);
 		}
 		return String.format("%tY/%<tm/%<td %<tH:%<tM:%<tS", d);
--- a/src/org/tmatesoft/hg/internal/NewlineFilter.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/NewlineFilter.java	Mon Jun 18 16:54:00 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
 import static org.tmatesoft.hg.internal.Filter.Direction.FromRepo;
 import static org.tmatesoft.hg.internal.Filter.Direction.ToRepo;
 import static org.tmatesoft.hg.internal.KeywordFilter.copySlice;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
 
 import java.io.File;
 import java.io.IOException;
@@ -315,7 +316,7 @@
 			try {
 				hgeol.addLocation(cfgFile);
 			} catch (IOException ex) {
-				HgInternals.getContext(hgRepo).getLog().warn(getClass(), ex, null);
+				HgInternals.getContext(hgRepo).getLog().dump(getClass(), Warn, ex, null);
 			}
 			nativeRepoFormat = hgeol.getSection("repository").get("native");
 			if (nativeRepoFormat == null) {
@@ -338,7 +339,7 @@
 				} else if ("BIN".equals(e.getValue())) {
 					binPatterns.add(e.getKey());
 				} else {
-					HgInternals.getContext(hgRepo).getLog().warn(getClass(), "Can't recognize .hgeol entry: %s for %s", e.getValue(), e.getKey());
+					HgInternals.getContext(hgRepo).getLog().dump(getClass(), Warn, "Can't recognize .hgeol entry: %s for %s", e.getValue(), e.getKey());
 				}
 			}
 			if (!crlfPatterns.isEmpty()) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/PropertyMarshal.java	Mon Jun 18 16:54:00 2012 +0200
@@ -0,0 +1,48 @@
+/*
+ * 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 org.tmatesoft.hg.core.SessionContext;
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class PropertyMarshal {
+	
+	private final SessionContext sessionContext;
+
+	public PropertyMarshal(SessionContext ctx) {
+		sessionContext = ctx;
+	}
+
+	public boolean getBoolean(String propertyName, boolean defaultValue) {
+		// can't use <T> and unchecked cast because got no confidence passed properties are strictly of the kind of my default values,
+		// i.e. if boolean from outside comes as "true", while I pass default as Boolean or vice versa.  
+		Object p = sessionContext.getConfigurationProperty(propertyName, defaultValue);
+		return p instanceof Boolean ? ((Boolean) p).booleanValue() : Boolean.parseBoolean(String.valueOf(p));
+	}
+	
+	public int getInt(String propertyName, int defaultValue) {
+		Object v = sessionContext.getConfigurationProperty(propertyName, defaultValue);
+		if (false == v instanceof Number) {
+			v = Integer.parseInt(String.valueOf(v));
+		}
+		return ((Number) v).intValue();
+	}
+}
--- a/src/org/tmatesoft/hg/internal/StreamLogFacility.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/StreamLogFacility.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.internal;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Info;
+
 import java.io.PrintStream;
 
 import org.tmatesoft.hg.util.LogFacility;
@@ -29,13 +31,14 @@
 public class StreamLogFacility implements LogFacility {
 	
 	private final boolean isDebug;
-	private final boolean isInfo;
+	private final Severity severity;
 	protected final boolean timestamp;
 	protected final PrintStream outStream;
 
-	public StreamLogFacility(boolean pringDebug, boolean printInfo, boolean needTimestamp, PrintStream out) {
-		isDebug = pringDebug;
-		isInfo = printInfo;
+	public StreamLogFacility(Severity level, boolean needTimestamp, PrintStream out) {
+		assert level != null;
+		severity = level;
+		isDebug = level == Severity.Debug;
 		timestamp = needTimestamp;
 		outStream = out;
 	}
@@ -43,56 +46,24 @@
 	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 Severity getLevel() {
+		return severity;
 	}
 
-	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;
+	public void dump(Class<?> src, Severity severity, String format, Object... args) {
+		if (severity.ordinal() >= getLevel().ordinal()) {
+			printf(severity, src, format, args);
 		}
-		printf("DEBUG", src, th, message);
 	}
 
-	public void info(Class<?> src, Throwable th, String message) {
-		if (!isInfo) {
-			return;
+	public void dump(Class<?> src, Severity severity, Throwable th, String message) {
+		if (severity.ordinal() >= getLevel().ordinal()) {
+			printf(severity, src, th, message);
 		}
-		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) {
+	protected void printf(Severity level, Class<?> src, String format, Object... args) {
 		String msg = String.format(format, args);
 		if (timestamp) {
 			outStream.printf(isDebug ? "%tT.%1$tL " : "%tT ", System.currentTimeMillis());
@@ -104,17 +75,17 @@
 			}
 			outStream.printf("(%s) ", cn);
 		}
-		outStream.printf("%s: %s", level, msg);
+		outStream.printf("%s: %s", level.toString().toUpperCase(), msg);
 		if (format.length() == 0 || format.charAt(format.length() - 1) != '\n') {
 			outStream.println();
 		}
 	}
-	protected void printf(String level, Class<?> src, Throwable th, String msg) {
+	protected void printf(Severity 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) {
+			if (getLevel().ordinal() <= Info.ordinal()) {
 				// full stack trace
 				th.printStackTrace(outStream);
 			} else {
@@ -126,6 +97,6 @@
 
 	// 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); 
+		return new StreamLogFacility(Severity.Debug, true, System.out); 
 	}
 }
--- a/src/org/tmatesoft/hg/repo/HgBranches.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgBranches.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,6 +16,9 @@
  */
 package org.tmatesoft.hg.repo;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Error;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
+
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
@@ -98,24 +101,24 @@
 			return lastInCache;
 		} catch (IOException ex) {
 			 // log error, but otherwise do nothing
-			repo.getContext().getLog().warn(getClass(), ex, null);
+			repo.getContext().getLog().dump(getClass(), Warn, ex, null);
 			// FALL THROUGH to return -1 indicating no cache information 
 		} catch (NumberFormatException ex) {
-			repo.getContext().getLog().warn(getClass(), ex, null);
+			repo.getContext().getLog().dump(getClass(), Warn, ex, null);
 			// FALL THROUGH
 		} catch (HgInvalidControlFileException ex) {
 			// shall not happen, thus log as error
-			repo.getContext().getLog().error(getClass(), ex, null);
+			repo.getContext().getLog().dump(getClass(), Error, ex, null);
 			// FALL THROUGH
 		} catch (HgInvalidRevisionException ex) {
-			repo.getContext().getLog().error(getClass(), ex, null);
+			repo.getContext().getLog().dump(getClass(), Error, ex, null);
 			// FALL THROUGH
 		} finally {
 			if (br != null) {
 				try {
 					br.close();
 				} catch (IOException ex) {
-					repo.getContext().getLog().info(getClass(), ex, null); // ignore
+					repo.getContext().getLog().dump(getClass(), Warn, ex, null); // ignore
 				}
 			}
 		}
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Mon Jun 18 16:54:00 2012 +0200
@@ -18,6 +18,7 @@
 
 import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex;
 import static org.tmatesoft.hg.repo.HgRepository.*;
+import static org.tmatesoft.hg.util.LogFacility.Severity.*;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -181,7 +182,7 @@
 					try {
 						fc.close();
 					} catch (IOException ex) {
-						getRepo().getContext().getLog().info(getClass(), ex, null);
+						getRepo().getContext().getLog().dump(getClass(), Warn, ex, null);
 					}
 				}
 			}
@@ -207,7 +208,7 @@
 		final int csetRevIndex;
 		if (p.isNull()) {
 			// no dirstate parents 
-			getRepo().getContext().getLog().info(getClass(), "No dirstate parents, resort to TIP", getPath());
+			getRepo().getContext().getLog().dump(getClass(), Info, "No dirstate parents, resort to TIP", getPath());
 			// if it's a repository with no dirstate, use TIP then
 			csetRevIndex = clog.getLastRevision();
 			if (csetRevIndex == -1) {
@@ -607,7 +608,7 @@
 						break;
 					}
 					if (key == null || lastColon == -1 || i <= lastColon) {
-						log.error(getClass(), "Missing key in file revision metadata at index %d", i);
+						log.dump(getClass(), Error, "Missing key in file revision metadata at index %d", i);
 					}
 					value = new String(bos.toByteArray()).trim();
 					bos.reset();
--- a/src/org/tmatesoft/hg/repo/HgDirstate.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgDirstate.java	Mon Jun 18 16:54:00 2012 +0200
@@ -17,6 +17,7 @@
 package org.tmatesoft.hg.repo;
 
 import static org.tmatesoft.hg.core.Nodeid.NULL;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Debug;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -35,6 +36,7 @@
 import org.tmatesoft.hg.util.Pair;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathRewrite;
+import org.tmatesoft.hg.util.LogFacility.Severity;
 
 
 /**
@@ -141,7 +143,7 @@
 				} else if (state == 'm') {
 					merged.put(r.name1, r);
 				} else {
-					repo.getContext().getLog().warn(getClass(), "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name1, r.size(), r.time, state);
+					repo.getContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name1, r.size(), r.time, state);
 				}
 			}
 		} catch (IOException ex) {
@@ -205,7 +207,7 @@
 				branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b;
 				r.close();
 			} catch (FileNotFoundException ex) {
-				repo.getContext().getLog().debug(HgDirstate.class, ex, null); // log verbose debug, exception might be legal here 
+				repo.getContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here 
 				// IGNORE
 			} catch (IOException ex) {
 				throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile);
--- a/src/org/tmatesoft/hg/repo/HgLookup.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgLookup.java	Mon Jun 18 16:54:00 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2011 TMate Software Ltd
+ * Copyright (c) 2010-2012 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.repo;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
+
 import java.io.File;
 import java.io.IOException;
 import java.net.MalformedURLException;
@@ -135,7 +137,7 @@
 				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);
+				getContext().getLog().dump(getClass(), Warn, ex, null);
 			}
 		}
 		return globalCfg;
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Mon Jun 18 16:54:00 2012 +0200
@@ -18,6 +18,7 @@
 
 import static org.tmatesoft.hg.core.Nodeid.NULL;
 import static org.tmatesoft.hg.repo.HgRepository.*;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Info;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -38,6 +39,7 @@
 import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.ProgressSupport;
+import org.tmatesoft.hg.util.LogFacility.Severity;
 
 
 /**
@@ -167,7 +169,7 @@
 			i++;
 		} 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);
+			getRepo().getContext().getLog().dump(getClass(), Info, "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.
 			return;
@@ -616,7 +618,7 @@
 				// TODO calculate those missing effectively (e.g. cache and sort nodeids to speed lookup
 				// right away in the #next (may refactor ParentWalker's sequential and sorted into dedicated helper and reuse here)
 				if (manifest.isNull()) {
-					repo.getContext().getLog().warn(getClass(), "Changeset %d has no associated manifest entry", u);
+					repo.getContext().getLog().dump(getClass(), Severity.Warn, "Changeset %d has no associated manifest entry", u);
 					// keep -1 in the changelog2manifest map.
 				} else {
 					changelog2manifest[u] = repo.getManifest().getRevisionIndex(manifest);
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.repo;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Info;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -55,6 +57,7 @@
 import org.tmatesoft.hg.core.HgRepositoryNotFoundException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
+import org.tmatesoft.hg.internal.PropertyMarshal;
 
 /**
  * WORK IN PROGRESS, DO NOT USE
@@ -80,8 +83,7 @@
 		}
 		this.url = url;
 		sessionContext = ctx;
-		Object debugProp = ctx.getProperty("hg4j.remote.debug", false);
-		debug = debugProp instanceof Boolean ? ((Boolean) debugProp).booleanValue() : Boolean.parseBoolean(String.valueOf(debugProp));
+		debug = new PropertyMarshal(ctx).getBoolean("hg4j.remote.debug", false);
 		if ("https".equals(url.getProtocol())) {
 			try {
 				sslContext = SSLContext.getInstance("SSL");
@@ -116,7 +118,7 @@
 				ai = tempNode.get("xxx", null);
 				tempNode.removeNode();
 			} catch (BackingStoreException ex) {
-				sessionContext.getLog().info(getClass(), ex, null);
+				sessionContext.getLog().dump(getClass(), Info, ex, null);
 				// IGNORE
 			}
 			authInfo = ai;
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.repo;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.*;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.StringReader;
@@ -201,12 +203,12 @@
 						tags.readGlobal(new StringReader(content));
 					} catch (CancelledException ex) {
 						 // IGNORE, can't happen, we did not configure cancellation
-						getContext().getLog().debug(getClass(), ex, null);
+						getContext().getLog().dump(getClass(), Debug, ex, null);
 					} catch (IOException ex) {
 						// UnsupportedEncodingException can't happen (UTF8)
 						// only from readGlobal. Need to reconsider exceptions thrown from there:
 						// BufferedReader wraps String and unlikely to throw IOException, perhaps, log is enough?
-						getContext().getLog().error(getClass(), ex, null);
+						getContext().getLog().dump(getClass(), Error, ex, null);
 						// XXX need to decide what to do this. failure to read single revision shall not break complete cycle
 					}
 				}
@@ -218,7 +220,7 @@
 				file2read = new File(repoDir, "localtags");
 				tags.readLocal(file2read);
 			} catch (IOException ex) {
-				getContext().getLog().error(getClass(), ex, null);
+				getContext().getLog().dump(getClass(), Error, ex, null);
 				throw new HgInvalidControlFileException("Failed to read tags", ex, file2read);
 			}
 		}
@@ -314,7 +316,7 @@
 				repoConfig = new HgRepoConfig(configFile);
 			} catch (IOException ex) {
 				String m = "Errors while reading user configuration file";
-				getContext().getLog().warn(getClass(), ex, m);
+				getContext().getLog().dump(getClass(), Warn, ex, m);
 				return new HgRepoConfig(new ConfigFile()); // empty config, do not cache, allow to try once again
 				//throw new HgInvalidControlFileException(m, ex, null);
 			}
@@ -361,11 +363,11 @@
 			try {
 				final List<String> errors = ignore.read(ignoreFile);
 				if (errors != null) {
-					getContext().getLog().warn(getClass(), "Syntax errors parsing .hgignore:\n%s", Internals.join(errors, ",\n"));
+					getContext().getLog().dump(getClass(), Warn, "Syntax errors parsing .hgignore:\n%s", Internals.join(errors, ",\n"));
 				}
 			} catch (IOException ex) {
 				final String m = "Error reading .hgignore file";
-				getContext().getLog().warn(getClass(), ex, m);
+				getContext().getLog().dump(getClass(), Warn, ex, m);
 //				throw new HgInvalidControlFileException(m, ex, ignoreFile);
 			}
 		}
@@ -400,7 +402,7 @@
 					fake.deleteOnExit();
 					return new RevlogStream(dataAccess, fake);
 				} catch (IOException ex) {
-					getContext().getLog().info(getClass(), ex, null);
+					getContext().getLog().dump(getClass(), Info, ex, null);
 				}
 			}
 		}
--- a/src/org/tmatesoft/hg/repo/HgRepositoryFiles.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepositoryFiles.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,7 +16,6 @@
  */
 package org.tmatesoft.hg.repo;
 
-import org.tmatesoft.hg.internal.Experimental;
 
 /**
  * Names of some Mercurial configuration/service files.
@@ -24,7 +23,6 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-@Experimental
 public enum HgRepositoryFiles {
 
 	HgIgnore(".hgignore"), HgTags(".hgtags"), HgEol(".hgeol"), 
--- a/src/org/tmatesoft/hg/repo/HgTags.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgTags.java	Mon Jun 18 16:54:00 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.repo;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
@@ -107,7 +109,7 @@
 				continue;
 			}
 			if (line.length() < 40+2 /*nodeid, space and at least single-char tagname*/) {
-				repo.getContext().getLog().warn(getClass(), "Bad tags line: %s", line); 
+				repo.getContext().getLog().dump(getClass(), Warn, "Bad tags line: %s", line); 
 				continue;
 			}
 			int spacePos = line.indexOf(' ');
@@ -151,7 +153,7 @@
 				}
 				
 			} else {
-				repo.getContext().getLog().warn(getClass(), "Bad tags line: %s", line);
+				repo.getContext().getLog().dump(getClass(), Warn, "Bad tags line: %s", line);
 			}
 		}
 	}
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Mon Jun 18 16:54:00 2012 +0200
@@ -19,6 +19,7 @@
 import static java.lang.Math.max;
 import static java.lang.Math.min;
 import static org.tmatesoft.hg.repo.HgRepository.*;
+import static org.tmatesoft.hg.util.LogFacility.Severity.*;
 
 import java.io.File;
 import java.io.IOException;
@@ -33,7 +34,6 @@
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
-import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.FilterByteChannel;
 import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.ManifestRevision;
@@ -362,7 +362,7 @@
 						}
 					}
 				} catch (HgRuntimeException ex) {
-					repo.getContext().getLog().warn(getClass(), ex, null);
+					repo.getContext().getLog().dump(getClass(), Warn, ex, null);
 					inspector.invalid(fname, ex);
 				}
 			}
@@ -451,7 +451,7 @@
 						inspector.modified(fname);
 					}
 				} catch (HgRuntimeException ex) {
-					repo.getContext().getLog().warn(getClass(), ex, null);
+					repo.getContext().getLog().dump(getClass(), Warn, ex, null);
 					inspector.invalid(fname, ex);
 				}
 				baseRevNames.remove(fname); // consumed, processed, handled.
@@ -508,7 +508,7 @@
 								int offset = max(0, x - 4);
 								exp = new String(data, offset, min(data.length - offset, 20));
 							}
-							repo.getContext().getLog().debug(getClass(), "expected >>%s<< but got >>%s<<", exp, new String(xx));
+							repo.getContext().getLog().dump(getClass(), Debug, "expected >>%s<< but got >>%s<<", exp, new String(xx));
 						}
 						sameSoFar = false;
 						break;
@@ -541,7 +541,7 @@
 				try {
 					is.close();
 				} catch (IOException ex) {
-					repo.getContext().getLog().info(getClass(), ex, null);
+					repo.getContext().getLog().dump(getClass(), Info, ex, null);
 				}
 				is = f.newInputChannel();
 				fb.clear();
@@ -553,7 +553,7 @@
 			}
 			return check.ultimatelyTheSame();
 		} catch (CancelledException ex) {
-			repo.getContext().getLog().warn(getClass(), ex, "Unexpected cancellation");
+			repo.getContext().getLog().dump(getClass(), Warn, ex, "Unexpected cancellation");
 			return check.ultimatelyTheSame();
 		} catch (IOException ex) {
 			throw new HgInvalidFileException("File comparison failed", ex).setFileName(p);
@@ -562,7 +562,7 @@
 				try {
 					is.close();
 				} catch (IOException ex) {
-					repo.getContext().getLog().info(getClass(), ex, null);
+					repo.getContext().getLog().dump(getClass(), Info, ex, null);
 				}
 			}
 		}
@@ -612,7 +612,6 @@
 	 * 
 	 * @return new instance of {@link HgWorkingCopyStatusCollector}, ready to {@link #walk(int, HgStatusInspector) walk} associated working copy 
 	 */
-	@Experimental(reason="Provisional API")
 	public static HgWorkingCopyStatusCollector create(HgRepository hgRepo, Path... paths) {
 		ArrayList<Path> f = new ArrayList<Path>(5);
 		ArrayList<Path> d = new ArrayList<Path>(5);
@@ -644,7 +643,6 @@
 	 *  
 	 * @return new instance of {@link HgWorkingCopyStatusCollector}, ready to {@link #walk(int, HgStatusInspector) walk} associated working copy
 	 */
-	@Experimental(reason="Provisional API. May add boolean strict argument for those who write smart matchers that can be used in FileWalker")
 	public static HgWorkingCopyStatusCollector create(HgRepository hgRepo, Path.Matcher scope) {
 		FileIterator w = new HgInternals(hgRepo).createWorkingDirWalker(null);
 		FileIterator wf = (scope == null || scope instanceof Path.Matcher.Any) ? w : new FileIteratorFilter(w, scope);
--- a/src/org/tmatesoft/hg/repo/Revlog.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/Revlog.java	Mon Jun 18 16:54:00 2012 +0200
@@ -17,6 +17,7 @@
 package org.tmatesoft.hg.repo;
 
 import static org.tmatesoft.hg.repo.HgRepository.*;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -432,7 +433,7 @@
 					
 					int consumed = sink.write(buf);
 					if ((consumed == 0 || consumed != buf.position()) && logFacility != null) {
-						logFacility.warn(getClass(), "Bad data sink when reading revision %d. Reported %d bytes consumed, byt actually read %d", revisionNumber, consumed, buf.position());
+						logFacility.dump(getClass(), Warn, "Bad data sink when reading revision %d. Reported %d bytes consumed, byt actually read %d", revisionNumber, consumed, buf.position());
 					}
 					if (buf.position() == 0) {
 						throw new HgInvalidStateException("Bad sink implementation (consumes no bytes) results in endless loop");
--- a/src/org/tmatesoft/hg/util/LogFacility.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/util/LogFacility.java	Mon Jun 18 16:54:00 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -16,10 +16,9 @@
  */
 package org.tmatesoft.hg.util;
 
-import org.tmatesoft.hg.internal.Experimental;
 
 /**
- * WORK IN PROGRESS
+ * Facility to dump various messages.
  * 
  * 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.
@@ -30,21 +29,41 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-@Experimental(reason="API might get changed")
 public interface LogFacility {
-
-	boolean isDebug();
-	boolean isInfo();
+	
+	public enum Severity {
+		Debug, Info, Warn, Error // order is important
+	}
 
-	// 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);
+	/**
+	 * Effective way to avoid attempts to construct debug dumps when they are of no interest. Basically, <code>getLevel() < Info</code>
+	 * 
+	 * @return <code>true</code> if interested in debug dumps
+	 */
+	boolean isDebug();
 
-	// 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);
+	/**
+	 * 
+	 * @return lowest (from {@link Severity#Debug} to {@link Severity#Error} active severity level 
+	 */
+	Severity getLevel();
+	
+	/**
+	 * Dump a message
+	 * @param src identifies source of the message, never <code>null</code>
+	 * @param severity one of predefined levels
+	 * @param format message format suitable for {@link String#format(String, Object...)}, never <code>null</code>
+	 * @param args optional arguments for the preceding format argument, may be <code>null</code>
+	 */
+	void dump(Class<?> src, Severity severity, String format, Object... args);
+	
+	/**
+	 * Alternative to dump an exception
+	 *  
+	 * @param src identifies source of the message, never <code>null</code>
+	 * @param severity one of predefined levels
+	 * @param th original exception, never <code>null</code>
+	 * @param message additional description of the error/conditions, may be <code>null</code>
+	 */
+	void dump(Class<?> src, Severity severity, Throwable th, String message);
 }
--- a/src/org/tmatesoft/hg/util/Outcome.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/util/Outcome.java	Mon Jun 18 16:54:00 2012 +0200
@@ -22,7 +22,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class Outcome {
+public final class Outcome {
 	// XXX perhaps private enum and factory method createError() and createOk()?
 	public enum Kind {
 		Success, Failure;
--- a/src/org/tmatesoft/hg/util/RegularFileInfo.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/util/RegularFileInfo.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.util;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Info;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -85,7 +87,7 @@
 				return new FileInputStream(file).getChannel();
 			}
 		} catch (FileNotFoundException ex) {
-			StreamLogFacility.newDefault().debug(getClass(), ex, null);
+			StreamLogFacility.newDefault().dump(getClass(), Info, ex, null);
 			// shall not happen, provided this class is used correctly
 			return new ByteArrayReadableChannel(null);
 		}
--- a/src/org/tmatesoft/hg/util/RegularFileStats.java	Wed Jun 13 21:07:39 2012 +0200
+++ b/src/org/tmatesoft/hg/util/RegularFileStats.java	Mon Jun 18 16:54:00 2012 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.util;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -140,11 +142,11 @@
 					symlinkValue = null;
 				}
 			} catch (InterruptedException ex) {
-				sessionContext.getLog().warn(getClass(), ex, String.format("Failed to detect flags for %s", f));
+				sessionContext.getLog().dump(getClass(), Warn, ex, String.format("Failed to detect flags for %s", f));
 				// try again? ensure not too long? stop right away?
 				// IGNORE, keep isExec and isSymlink false
 			} catch (IOException ex) {
-				sessionContext.getLog().warn(getClass(), ex, String.format("Failed to detect flags for %s", f));
+				sessionContext.getLog().dump(getClass(), Warn, ex, String.format("Failed to detect flags for %s", f));
 				// IGNORE, keep isExec and isSymlink false
 			}
 		}