changeset 213:6ec4af642ba8 gradle

Project uses Gradle for build - actual changes
author Alexander Kitaev <kitaev@gmail.com>
date Tue, 10 May 2011 10:52:53 +0200
parents edb2e2829352
children
files .classpath .hgignore .project .settings/org.eclipse.core.resources.prefs .settings/org.eclipse.core.runtime.prefs .settings/org.eclipse.jdt.core.prefs COPYING LICENSE.txt build.gradle build.xml cmdline/org/tmatesoft/hg/console/Bundle.java cmdline/org/tmatesoft/hg/console/Cat.java cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java cmdline/org/tmatesoft/hg/console/Clone.java cmdline/org/tmatesoft/hg/console/Incoming.java cmdline/org/tmatesoft/hg/console/Log.java cmdline/org/tmatesoft/hg/console/Main.java cmdline/org/tmatesoft/hg/console/Manifest.java cmdline/org/tmatesoft/hg/console/Options.java cmdline/org/tmatesoft/hg/console/Outgoing.java cmdline/org/tmatesoft/hg/console/Remote.java cmdline/org/tmatesoft/hg/console/Status.java gradle/wrapper/gradle-wrapper.jar gradle/wrapper/gradle-wrapper.properties gradlew gradlew.bat hg4j-cli/src/main/java/org/tmatesoft/hg/console/Bundle.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Cat.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/ChangesetDumpHandler.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Clone.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Incoming.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Log.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Main.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Manifest.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Options.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Outgoing.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Remote.java hg4j-cli/src/main/java/org/tmatesoft/hg/console/Status.java hg4j/src/main/java/org/tmatesoft/hg/core/ChangesetTransformer.java hg4j/src/main/java/org/tmatesoft/hg/core/HgBadArgumentException.java hg4j/src/main/java/org/tmatesoft/hg/core/HgBadStateException.java hg4j/src/main/java/org/tmatesoft/hg/core/HgCatCommand.java hg4j/src/main/java/org/tmatesoft/hg/core/HgChangeset.java hg4j/src/main/java/org/tmatesoft/hg/core/HgChangesetHandler.java hg4j/src/main/java/org/tmatesoft/hg/core/HgCloneCommand.java hg4j/src/main/java/org/tmatesoft/hg/core/HgDataStreamException.java hg4j/src/main/java/org/tmatesoft/hg/core/HgDate.java hg4j/src/main/java/org/tmatesoft/hg/core/HgException.java hg4j/src/main/java/org/tmatesoft/hg/core/HgIncomingCommand.java hg4j/src/main/java/org/tmatesoft/hg/core/HgLogCommand.java hg4j/src/main/java/org/tmatesoft/hg/core/HgManifestCommand.java hg4j/src/main/java/org/tmatesoft/hg/core/HgOutgoingCommand.java hg4j/src/main/java/org/tmatesoft/hg/core/HgRepoFacade.java hg4j/src/main/java/org/tmatesoft/hg/core/HgStatus.java hg4j/src/main/java/org/tmatesoft/hg/core/HgStatusCommand.java hg4j/src/main/java/org/tmatesoft/hg/core/Nodeid.java hg4j/src/main/java/org/tmatesoft/hg/core/package.html hg4j/src/main/java/org/tmatesoft/hg/internal/ByteArrayChannel.java hg4j/src/main/java/org/tmatesoft/hg/internal/ByteArrayDataAccess.java hg4j/src/main/java/org/tmatesoft/hg/internal/ChangelogHelper.java hg4j/src/main/java/org/tmatesoft/hg/internal/ConfigFile.java hg4j/src/main/java/org/tmatesoft/hg/internal/DataAccess.java hg4j/src/main/java/org/tmatesoft/hg/internal/DataAccessProvider.java hg4j/src/main/java/org/tmatesoft/hg/internal/DigestHelper.java hg4j/src/main/java/org/tmatesoft/hg/internal/Filter.java hg4j/src/main/java/org/tmatesoft/hg/internal/FilterByteChannel.java hg4j/src/main/java/org/tmatesoft/hg/internal/FilterDataAccess.java hg4j/src/main/java/org/tmatesoft/hg/internal/InflaterDataAccess.java hg4j/src/main/java/org/tmatesoft/hg/internal/Internals.java hg4j/src/main/java/org/tmatesoft/hg/internal/KeywordFilter.java hg4j/src/main/java/org/tmatesoft/hg/internal/NewlineFilter.java hg4j/src/main/java/org/tmatesoft/hg/internal/PathGlobMatcher.java hg4j/src/main/java/org/tmatesoft/hg/internal/PathRegexpMatcher.java hg4j/src/main/java/org/tmatesoft/hg/internal/Pool.java hg4j/src/main/java/org/tmatesoft/hg/internal/RelativePathRewrite.java hg4j/src/main/java/org/tmatesoft/hg/internal/RepositoryComparator.java hg4j/src/main/java/org/tmatesoft/hg/internal/RequiresFile.java hg4j/src/main/java/org/tmatesoft/hg/internal/RevlogDump.java hg4j/src/main/java/org/tmatesoft/hg/internal/RevlogStream.java hg4j/src/main/java/org/tmatesoft/hg/internal/StoragePathHelper.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgBundle.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgChangelog.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgDataFile.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgDirstate.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgIgnore.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgInternals.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgLookup.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgManifest.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgRemoteRepository.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgRepository.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgStatusCollector.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgStatusInspector.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgTags.java hg4j/src/main/java/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java hg4j/src/main/java/org/tmatesoft/hg/repo/Revlog.java hg4j/src/main/java/org/tmatesoft/hg/repo/package.html hg4j/src/main/java/org/tmatesoft/hg/util/Adaptable.java hg4j/src/main/java/org/tmatesoft/hg/util/ByteChannel.java hg4j/src/main/java/org/tmatesoft/hg/util/CancelSupport.java hg4j/src/main/java/org/tmatesoft/hg/util/CancelledException.java hg4j/src/main/java/org/tmatesoft/hg/util/FileIterator.java hg4j/src/main/java/org/tmatesoft/hg/util/FileWalker.java hg4j/src/main/java/org/tmatesoft/hg/util/Path.java hg4j/src/main/java/org/tmatesoft/hg/util/PathPool.java hg4j/src/main/java/org/tmatesoft/hg/util/PathRewrite.java hg4j/src/main/java/org/tmatesoft/hg/util/ProgressSupport.java hg4j/src/test/java/org/tmatesoft/hg/test/Configuration.java hg4j/src/test/java/org/tmatesoft/hg/test/ErrorCollectorExt.java hg4j/src/test/java/org/tmatesoft/hg/test/ExecHelper.java hg4j/src/test/java/org/tmatesoft/hg/test/LogOutputParser.java hg4j/src/test/java/org/tmatesoft/hg/test/ManifestOutputParser.java hg4j/src/test/java/org/tmatesoft/hg/test/OutputParser.java hg4j/src/test/java/org/tmatesoft/hg/test/StatusOutputParser.java hg4j/src/test/java/org/tmatesoft/hg/test/TestByteChannel.java hg4j/src/test/java/org/tmatesoft/hg/test/TestClone.java hg4j/src/test/java/org/tmatesoft/hg/test/TestHistory.java hg4j/src/test/java/org/tmatesoft/hg/test/TestIncoming.java hg4j/src/test/java/org/tmatesoft/hg/test/TestManifest.java hg4j/src/test/java/org/tmatesoft/hg/test/TestOutgoing.java hg4j/src/test/java/org/tmatesoft/hg/test/TestStatus.java hg4j/src/test/java/org/tmatesoft/hg/test/TestStorePath.java hg4j/src/test/resources/test-repos.jar lib/junit-4.8.2-src.jar lib/junit-4.8.2.jar settings.gradle src/org/tmatesoft/hg/core/ChangesetTransformer.java src/org/tmatesoft/hg/core/HgBadArgumentException.java src/org/tmatesoft/hg/core/HgBadStateException.java src/org/tmatesoft/hg/core/HgCatCommand.java src/org/tmatesoft/hg/core/HgChangeset.java src/org/tmatesoft/hg/core/HgChangesetHandler.java src/org/tmatesoft/hg/core/HgCloneCommand.java src/org/tmatesoft/hg/core/HgDataStreamException.java src/org/tmatesoft/hg/core/HgDate.java src/org/tmatesoft/hg/core/HgException.java src/org/tmatesoft/hg/core/HgIncomingCommand.java src/org/tmatesoft/hg/core/HgLogCommand.java src/org/tmatesoft/hg/core/HgManifestCommand.java src/org/tmatesoft/hg/core/HgOutgoingCommand.java src/org/tmatesoft/hg/core/HgRepoFacade.java src/org/tmatesoft/hg/core/HgStatus.java src/org/tmatesoft/hg/core/HgStatusCommand.java src/org/tmatesoft/hg/core/Nodeid.java src/org/tmatesoft/hg/core/package.html src/org/tmatesoft/hg/internal/ByteArrayChannel.java src/org/tmatesoft/hg/internal/ByteArrayDataAccess.java src/org/tmatesoft/hg/internal/ChangelogHelper.java src/org/tmatesoft/hg/internal/ConfigFile.java src/org/tmatesoft/hg/internal/DataAccess.java src/org/tmatesoft/hg/internal/DataAccessProvider.java src/org/tmatesoft/hg/internal/DigestHelper.java src/org/tmatesoft/hg/internal/Filter.java src/org/tmatesoft/hg/internal/FilterByteChannel.java src/org/tmatesoft/hg/internal/FilterDataAccess.java src/org/tmatesoft/hg/internal/InflaterDataAccess.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/PathGlobMatcher.java src/org/tmatesoft/hg/internal/PathRegexpMatcher.java src/org/tmatesoft/hg/internal/Pool.java src/org/tmatesoft/hg/internal/RelativePathRewrite.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/internal/RequiresFile.java src/org/tmatesoft/hg/internal/RevlogDump.java src/org/tmatesoft/hg/internal/RevlogStream.java src/org/tmatesoft/hg/internal/StoragePathHelper.java src/org/tmatesoft/hg/repo/HgBundle.java src/org/tmatesoft/hg/repo/HgChangelog.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgDirstate.java src/org/tmatesoft/hg/repo/HgIgnore.java src/org/tmatesoft/hg/repo/HgInternals.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgManifest.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/repo/HgStatusCollector.java src/org/tmatesoft/hg/repo/HgStatusInspector.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/repo/package.html src/org/tmatesoft/hg/util/Adaptable.java src/org/tmatesoft/hg/util/ByteChannel.java src/org/tmatesoft/hg/util/CancelSupport.java src/org/tmatesoft/hg/util/CancelledException.java src/org/tmatesoft/hg/util/FileIterator.java src/org/tmatesoft/hg/util/FileWalker.java src/org/tmatesoft/hg/util/Path.java src/org/tmatesoft/hg/util/PathPool.java src/org/tmatesoft/hg/util/PathRewrite.java src/org/tmatesoft/hg/util/ProgressSupport.java test-data/test-repos.jar test/org/tmatesoft/hg/test/Configuration.java test/org/tmatesoft/hg/test/ErrorCollectorExt.java test/org/tmatesoft/hg/test/ExecHelper.java test/org/tmatesoft/hg/test/LogOutputParser.java test/org/tmatesoft/hg/test/ManifestOutputParser.java test/org/tmatesoft/hg/test/OutputParser.java test/org/tmatesoft/hg/test/StatusOutputParser.java test/org/tmatesoft/hg/test/TestByteChannel.java test/org/tmatesoft/hg/test/TestClone.java test/org/tmatesoft/hg/test/TestHistory.java test/org/tmatesoft/hg/test/TestIncoming.java test/org/tmatesoft/hg/test/TestManifest.java test/org/tmatesoft/hg/test/TestOutgoing.java test/org/tmatesoft/hg/test/TestStatus.java test/org/tmatesoft/hg/test/TestStorePath.java
diffstat 209 files changed, 14554 insertions(+), 14426 deletions(-) [+]
line wrap: on
line diff
--- a/.classpath	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="test"/>
-	<classpathentry kind="src" path="cmdline"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
-	<classpathentry kind="lib" path="lib/junit-4.8.2.jar" sourcepath="lib/junit-4.8.2-src.jar"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
--- a/.hgignore	Mon May 09 11:49:23 2011 +0200
+++ b/.hgignore	Tue May 10 10:52:53 2011 +0200
@@ -1,4 +1,3 @@
-.gradle
 build
 syntax:glob
 bin
--- a/.project	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>hg4j</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
--- a/.settings/org.eclipse.core.resources.prefs	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-#Thu Feb 24 18:49:36 CET 2011
-eclipse.preferences.version=1
-encoding/<project>=UTF-8
--- a/.settings/org.eclipse.core.runtime.prefs	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-#Thu Feb 24 18:49:36 CET 2011
-eclipse.preferences.version=1
-line.separator=\n
--- a/.settings/org.eclipse.jdt.core.prefs	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-#Wed Dec 15 01:43:42 CET 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.5
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.5
--- a/COPYING	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-Copyright (C) 2010-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
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE.txt	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,14 @@
+Copyright (C) 2010-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
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build.gradle	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,67 @@
+group = 'org.tmatesoft.hg4j'
+version = '0.5.0'
+target = '1.5'
+release = false
+
+buildscript {
+    repositories {
+        mavenRepo(urls: [buildPluginRepositoryURL]) {
+            snapshotTimeout = org.gradle.api.internal.artifacts.ivyservice.GradleIBiblioResolver.ALWAYS
+        }
+    }
+    dependencies { classpath 'org.tmatesoft.build:build:0.9.7-SNAPSHOT' }
+}
+
+task wrapper(type: Wrapper) {}
+
+def javaProjects() {
+    return [ project(':hg4j'), project(':hg4j-cli') ]
+}
+
+allprojects {
+    apply plugin : 'base'
+    apply plugin : 'build'
+}
+
+configure(javaProjects()) {
+    apply plugin : 'java'
+
+    sourceCompatibility = target
+    targetCompatibility = target
+
+    configurations {
+        sources
+        javadocs
+    }
+
+    task sourcesJar(type: Jar) {
+        description = 'Builds Java Sources Jar'
+        from sourceSets.main.java.srcDirs
+        classifier = 'sources'
+    }
+
+    jar {
+        metaInf {
+            from rootProject.file('LICENSE.txt')
+        }
+    }
+
+    artifacts { sources sourcesJar }
+}
+
+configure(javaProjects() + rootProject) {
+    apply plugin : 'idea'
+    apply plugin : 'eclipse'
+}
+
+project(':hg4j') {
+    dependencies {
+        testCompile 'junit:junit:4.8.2'
+    }
+}
+
+project(':hg4j-cli') {
+    dependencies {
+        compile project(':hg4j')
+    }
+}
\ No newline at end of file
--- a/build.xml	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Copyright (c) 2010-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
--->
-<project name="hg4j" default="samples">
-	<description>
-		Build, test and showcase hg4j
-		Targets:
-		  * build - compile and jar binary and source bundles
-		  * tests - run tests with JUnit
-		  * samples - few command-line counterparts to demonstrate basic capabiliites
-		  * rebuild - clean build
-	</description>
-
-	<property name="junit.jar" value="lib/junit-4.8.2.jar" />
-	<property name="ver.qualifier" value="" />
-	<property name="version.lib" value="0.1.0" />
-	<property name="version.jar" value="${version.lib}${ver.qualifier}" />
-	<property name="compile-with-debug" value="yes"/>
-
-	<property name="hg4j.jar" value="hg4j_${version.jar}.jar" />
-	<property name="hg4j-tests.jar" value="hg4j-tests_${version.jar}.jar" />
-	<property name="hg4j-console.jar" value="hg4j-console_${version.jar}.jar" />
-	<property name="hg4j-src.jar" value="hg4j-src_${version.jar}.jar" />
-	
-
-	<target name="samples" depends="build-cmdline" description="Run sample command-line tools">
-		<path id="path.cmdline" path="${hg4j.jar};${hg4j-console.jar}" />
-
-		<echo message="History of a specific file(s)" />
-		<java classpathref="path.cmdline" classname="org.tmatesoft.hg.console.Log">
-			<arg line="design.txt .classpath src/org/tmatesoft/hg/core/HgRepoFacade.java" />
-		</java>
-
-		<echo message="${line.separator}>>>Latest commit" />
-		<java classpathref="path.cmdline" classname="org.tmatesoft.hg.console.Log">
-			<arg line="--debug --limit 1" />
-		</java>
-
-		<echo message="${line.separator}>>>Content of a file" />
-		<java classpathref="path.cmdline" classname="org.tmatesoft.hg.console.Cat">
-			<arg line="src/org/tmatesoft/hg/core/HgRepoFacade.java --rev 1" />
-		</java>
-
-		<echo message="${line.separator}>>>Status between two revisions" />
-		<java classpathref="path.cmdline" classname="org.tmatesoft.hg.console.Status">
-			<arg line="--rev 140 --rev 142" />
-		</java>
-
-		<echo message="${line.separator}>>>Status, working copy, all" />
-		<java classpathref="path.cmdline" classname="org.tmatesoft.hg.console.Status">
-			<arg line="-A" />
-		</java>
-
-		<echo message="${line.separator}>>>Manifest" />
-		<java classpathref="path.cmdline" classname="org.tmatesoft.hg.console.Manifest">
-			<arg line="--debug" />
-		</java>
-	</target>
-
-	<target name="tests" depends="build-tests" description="Launch tests with JUnit">
-		<property name="test-repos-root" value="${java.io.tmpdir}/hg4j-tests/"/>
-		<delete dir="${test-repos-root}" quiet="yes"/>
-		<unjar src="test-data/test-repos.jar" dest="${test-repos-root}"/>
-		<junit>
-			<classpath path="${hg4j.jar};${hg4j-tests.jar};${junit.jar}" />
-			<formatter type="xml" />
-			<formatter type="plain" usefile="no" />
-			<sysproperty key="hg4j.tests.repos" value="${test-repos-root}"/>
-			<sysproperty key="hg4j.tests.remote" value="http://hg.serpentine.com/tutorial/hello"/>
-			<test name="org.tmatesoft.hg.test.TestHistory" />
-			<test name="org.tmatesoft.hg.test.TestManifest" />
-			<test name="org.tmatesoft.hg.test.TestStatus" />
-			<test name="org.tmatesoft.hg.test.TestStorePath" />
-			<test name="org.tmatesoft.hg.test.TestByteChannel" />
-			<test name="org.tmatesoft.hg.test.TestClone" />
-			<test name="org.tmatesoft.hg.test.TestIncoming" />
-			<test name="org.tmatesoft.hg.test.TestOutgoing" />
-		</junit>
-	</target>
-
-	<!-- -->
-	<target name="build" depends="build-lib, build-cmdline, build-tests" description="Compile and bundle all jars">
-		<jar destfile="${hg4j-src.jar}">
-			<fileset dir="src/" includes="org/tmatesoft/hg/**" />
-			<fileset dir="test/" includes="org/tmatesoft/hg/**" />
-			<fileset dir="cmdline/" includes="org/tmatesoft/hg/**" />
-			<fileset file="COPYING"/>
-		</jar>
-	</target>
-	
-	<target name="rebuild" depends="cleanup, build" description="Clean and build again"/>
-	
-	<target name="cleanup">
-		<delete dir="bin/" description="Compiled classes"/>
-		<delete description="Jars">
-			<fileset dir="." includes="${hg4j-console.jar}, ${hg4j-src.jar}, ${hg4j-tests.jar}, ${hg4j.jar}"/>
-		</delete>
-		<delete description="Tests artifacts">
-			<fileset dir="." includes="TEST-*.xml"/>
-		</delete>
-	</target>
-
-	<target name="build-lib">
-		<mkdir dir="bin" />
-		<javac srcdir="src" destdir="bin" debug="${compile-with-debug}" />
-		<jar destfile="${hg4j.jar}">
-			<fileset dir="bin/">
-				<include name="org/tmatesoft/hg/core/**" />
-				<include name="org/tmatesoft/hg/util/**" />
-				<include name="org/tmatesoft/hg/repo/**" />
-				<include name="org/tmatesoft/hg/internal/**" />
-			</fileset>
-			<fileset file="COPYING"/>
-		</jar>
-	</target>
-
-	<target name="build-tests" depends="build-lib">
-		<mkdir dir="bin" />
-		<javac srcdir="test" destdir="bin" debug="${compile-with-debug}" >
-			<classpath>
-				<pathelement location="${hg4j.jar}"/>
-				<pathelement location="${junit.jar}"/>
-			</classpath>
-		</javac>
-		<jar destfile="${hg4j-tests.jar}">
-			<fileset dir="bin" includes="org/tmatesoft/hg/test/**"/>
-			<fileset file="COPYING"/>
-		</jar>
-	</target>
-
-	<target name="build-cmdline" depends="build-lib">
-		<mkdir dir="bin" />
-		<javac srcdir="cmdline" destdir="bin" debug="${compile-with-debug}" />
-		<jar destfile="${hg4j-console.jar}">
-			<fileset dir="bin/" includes="org/tmatesoft/hg/console/**"/>
-			<fileset file="COPYING"/>
-		</jar>
-	</target>
-	
-</project>
--- a/cmdline/org/tmatesoft/hg/console/Bundle.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/*
- * 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.console;
-
-import java.io.File;
-
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.HgBundle;
-import org.tmatesoft.hg.repo.HgChangelog;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-
-
-/**
- * WORK IN PROGRESS, DO NOT USE
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Bundle {
-	public static void main(String[] args) throws Exception {
-		Options cmdLineOpts = Options.parse(args);
-		final HgRepository hgRepo = cmdLineOpts.findRepository();
-		if (hgRepo.isInvalid()) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
-			return;
-		}
-		File bundleFile = new File("/temp/hg/hg-bundle-cpython.tmp");
-		HgBundle hgBundle = new HgLookup().loadBundle(bundleFile);
-		hgBundle.inspectFiles(new HgBundle.Dump());
-		if (Boolean.parseBoolean("true")) {
-			return;
-		}
-		/* pass -R <path-to-repo-with-less-revisions-than-bundle>, e.g. for bundle with tip=168 and -R \temp\hg4j-50 with tip:159
-		+Changeset {User: ..., Comment: Integer ....}
-		+Changeset {User: ..., Comment: Approach with ...}
-		-Changeset {User: ..., Comment: Correct project name...}
-		-Changeset {User: ..., Comment: Record possible...}
-		*/
-		hgBundle.changes(hgRepo, new HgChangelog.Inspector() {
-			private final HgChangelog changelog = hgRepo.getChangelog();
-			
-			public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
-				if (changelog.isKnown(nodeid)) {
-					System.out.print("+");
-				} else {
-					System.out.print("-");
-				}
-				System.out.printf("%d:%s\n%s\n", revisionNumber, nodeid.shortNotation(), cset.toString());
-			}
-		});
-	}
-
-/*
- *  TODO EXPLAIN why DataAccess.java on merge from branch has P2 set, and P1 is NULL
- *  
- *  excerpt from dump('hg-bundle-00') output (node, p1, p2, cs):
- src/org/tmatesoft/hg/internal/DataAccess.java
-  186af94a2a7ddb34190e63ce556d0fa4dd24add2 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 6f1b88693d48422e98c3eaaa8428ffd4d4d98ca7; patches:1
-  be8d0fdc4ff268bf5eb0a9120282ce6e63de1606 186af94a2a7ddb34190e63ce556d0fa4dd24add2 0000000000000000000000000000000000000000 a3a2e5deb320d7412ccbb59bdc44668d445bc4c4; patches:2
-  333d7bbd4a80a5d6fb4b44e54e39e290f50dc7f8 be8d0fdc4ff268bf5eb0a9120282ce6e63de1606 0000000000000000000000000000000000000000 e93101b97e4ab0a3f3402ec0e80b6e559237c7c8; patches:1
-  56e4523cb8b42630daf70511d73d29e0b375dfa5 0000000000000000000000000000000000000000 333d7bbd4a80a5d6fb4b44e54e39e290f50dc7f8 d5268ca7715b8d96204fc62abc632e8f55761547; patches:6
-  f85b6d7ed3cc4b7c6f99444eb0a41b58793cc900 56e4523cb8b42630daf70511d73d29e0b375dfa5 0000000000000000000000000000000000000000 b413b16d10a50cc027f4c38e4df5a9fedd618a79; patches:4
-	  
-  RevlogDump for the file says:
-  Index    Offset      Flags     Packed     Actual   Base Rev   Link Rev  Parent1  Parent2     nodeid
-   0:    4295032832      0       1109       2465          0         74       -1       -1     186af94a2a7ddb34190e63ce556d0fa4dd24add2
-   1:          1109      0         70       2364          0        102        0       -1     be8d0fdc4ff268bf5eb0a9120282ce6e63de1606
-   2:          1179      0         63       2365          0        122        1       -1     333d7bbd4a80a5d6fb4b44e54e39e290f50dc7f8
-   3:          1242      0        801       3765          0        157       -1        2     56e4523cb8b42630daf70511d73d29e0b375dfa5
-   4:          2043      0        130       3658          0        158        3       -1     f85b6d7ed3cc4b7c6f99444eb0a41b58793cc900
-
-  Excerpt from changelog dump:
-  155:         30541      0        155        195        155        155      154       -1     a4ec5e08701771b96057522188b16ed289e9e8fe
-  156:         30696      0        154        186        155        156      155       -1     643ddec3be36246fc052cf22ece503fa60cafe22
-  157:         30850      0        478       1422        155        157      156       53     d5268ca7715b8d96204fc62abc632e8f55761547
-  158:         31328      0        247        665        155        158      157       -1     b413b16d10a50cc027f4c38e4df5a9fedd618a79
-			   
-
- */
-}
--- a/cmdline/org/tmatesoft/hg/console/Cat.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2010-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.console;
-
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-
-import org.tmatesoft.hg.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.ByteChannel;
-
-
-/**
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Cat {
-
-	public static void main(String[] args) throws Exception {
-		Options cmdLineOpts = Options.parse(args);
-		HgRepository hgRepo = cmdLineOpts.findRepository();
-		if (hgRepo.isInvalid()) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
-			return;
-		}
-		int rev = cmdLineOpts.getSingleInt(TIP, "-r", "--rev");
-		OutputStreamChannel out = new OutputStreamChannel(System.out);
-		for (String fname : cmdLineOpts.getList("")) {
-			System.out.println(fname);
-			HgDataFile fn = hgRepo.getFileNode(fname);
-			if (fn.exists()) {
-				fn.contentWithFilters(rev, out);
-				System.out.println();
-			} else {
-				System.out.printf("%s not found!\n", fname);
-			}
-		}
-	}
-
-	private static class OutputStreamChannel implements ByteChannel {
-
-		private final OutputStream stream;
-
-		public OutputStreamChannel(OutputStream out) {
-			stream = out;
-		}
-
-		public int write(ByteBuffer buffer) throws IOException {
-			int count = buffer.remaining();
-			while(buffer.hasRemaining()) {
-				stream.write(buffer.get());
-			}
-			return count;
-		}
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-/*
- * 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.console;
-
-import java.util.Formatter;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgChangeset;
-import org.tmatesoft.hg.core.HgChangesetHandler;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.Path;
-
-/**
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class ChangesetDumpHandler implements HgChangesetHandler {
-	// params
-	private boolean complete = false; // roughly --debug
-	private boolean reverseOrder = false;
-	private boolean verbose = false; // roughly -v
-	// own
-	private LinkedList<String> l = new LinkedList<String>();
-	private final HgRepository repo;
-	private final int tip;
-
-	public ChangesetDumpHandler(HgRepository hgRepo) {
-		repo = hgRepo;
-		tip = hgRepo.getChangelog().getLastRevision();
-	}
-
-	public ChangesetDumpHandler complete(boolean b) {
-		complete = b;
-		return this;
-	}
-
-	public ChangesetDumpHandler reversed(boolean b) {
-		reverseOrder = b;
-		return this;
-	}
-
-	public ChangesetDumpHandler verbose(boolean b) {
-		verbose = b;
-		return this;
-	}
-
-	public void next(HgChangeset changeset) {
-		final String s = print(changeset);
-		if (reverseOrder) {
-			// XXX in fact, need to insert s into l according to changeset.getRevision()
-			// because when file history is being followed, revisions of the original file (with smaller revNumber)
-			// are reported *after* revisions of present file and with addFirst appear above them
-			l.addFirst(s);
-		} else {
-			System.out.print(s);
-		}
-	}
-
-	public void done() {
-		if (!reverseOrder) {
-			return;
-		}
-		for (String s : l) {
-			System.out.print(s);
-		}
-		l.clear();
-	}
-
-	private String print(HgChangeset cset) {
-		StringBuilder sb = new StringBuilder();
-		Formatter f = new Formatter(sb);
-		final Nodeid csetNodeid = cset.getNodeid();
-		f.format("changeset:   %d:%s\n", cset.getRevision(), complete ? csetNodeid : csetNodeid.shortNotation());
-		if (cset.getRevision() == tip || repo.getTags().isTagged(csetNodeid)) {
-
-			sb.append("tag:         ");
-			for (String t : repo.getTags().tags(csetNodeid)) {
-				sb.append(t);
-				sb.append(' ');
-			}
-			if (cset.getRevision() == tip) {
-				sb.append("tip");
-			}
-			sb.append('\n');
-		}
-		if (complete) {
-			Nodeid p1 = cset.getFirstParentRevision();
-			Nodeid p2 = cset.getSecondParentRevision();
-			int p1x = p1 == Nodeid.NULL ? -1 : repo.getChangelog().getLocalRevision(p1);
-			int p2x = p2 == Nodeid.NULL ? -1 : repo.getChangelog().getLocalRevision(p2);
-			int mx = repo.getManifest().getLocalRevision(cset.getManifestRevision());
-			f.format("parent:      %d:%s\nparent:      %d:%s\nmanifest:    %d:%s\n", p1x, p1, p2x, p2, mx, cset.getManifestRevision());
-		}
-		f.format("user:        %s\ndate:        %s\n", cset.getUser(), cset.getDate().toString());
-		if (!complete && verbose) {
-			final List<Path> files = cset.getAffectedFiles();
-			sb.append("files:      ");
-			for (Path s : files) {
-				sb.append(' ');
-				sb.append(s);
-			}
-			sb.append('\n');
-		}
-		if (complete) {
-			if (!cset.getModifiedFiles().isEmpty()) {
-				sb.append("files:      ");
-				for (FileRevision s : cset.getModifiedFiles()) {
-					sb.append(' ');
-					sb.append(s.getPath());
-				}
-				sb.append('\n');
-			}
-			if (!cset.getAddedFiles().isEmpty()) {
-				sb.append("files+:     ");
-				for (FileRevision s : cset.getAddedFiles()) {
-					sb.append(' ');
-					sb.append(s.getPath());
-				}
-				sb.append('\n');
-			}
-			if (!cset.getRemovedFiles().isEmpty()) {
-				sb.append("files-:     ");
-				for (Path s : cset.getRemovedFiles()) {
-					sb.append(' ');
-					sb.append(s);
-				}
-				sb.append('\n');
-			}
-			// if (cset.extras() != null) {
-			// sb.append("extra:      ");
-			// for (Map.Entry<String, String> e : cset.extras().entrySet()) {
-			// sb.append(' ');
-			// sb.append(e.getKey());
-			// sb.append('=');
-			// sb.append(e.getValue());
-			// }
-			// sb.append('\n');
-			// }
-		}
-		if (complete || verbose) {
-			f.format("description:\n%s\n\n", cset.getComment());
-		} else {
-			f.format("summary:     %s\n\n", cset.getComment());
-		}
-		return sb.toString();
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/Clone.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- * 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.console;
-
-import java.io.File;
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgCloneCommand;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-
-/**
- * Initial clone of a repository. Creates a brand new repository and populates it from specified source. 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Clone {
-
-	// ran with args: svnkit c:\temp\hg\test-clone
-	public static void main(String[] args) throws Exception {
-		Options cmdLineOpts = Options.parse(args);
-		List<String> noOptsArgs = cmdLineOpts.getList("");
-		if (noOptsArgs.isEmpty()) {
-			System.err.println("Need at least one argument pointing to remote server to pull changes from");
-			return;
-		}
-		HgCloneCommand cmd = new HgCloneCommand();
-		String remoteRepo = noOptsArgs.get(0);
-		HgRemoteRepository hgRemote = new HgLookup().detectRemote(remoteRepo, null);
-		if (hgRemote.isInvalid()) {
-			System.err.printf("Remote repository %s is not valid", hgRemote.getLocation());
-			return;
-		}
-		cmd.source(hgRemote);
-		if (noOptsArgs.size() > 1) {
-			cmd.destination(new File(noOptsArgs.get(1)));
-		} else {
-			cmd.destination(new File(System.getProperty("user.dir")));
-		}
-		cmd.execute();
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/Incoming.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,228 +0,0 @@
-/*
- * 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.console;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map.Entry;
-
-import org.tmatesoft.hg.core.HgIncomingCommand;
-import org.tmatesoft.hg.core.HgRepoFacade;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-
-
-/**
- * <em>hg incoming</em> counterpart
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Incoming {
-
-	public static void main(String[] args) throws Exception {
-		if (Boolean.FALSE.booleanValue()) {
-			new SequenceConstructor().test();
-			return;
-		}
-		Options cmdLineOpts = Options.parse(args);
-		HgRepoFacade hgRepo = new HgRepoFacade();
-		if (!hgRepo.init(cmdLineOpts.findRepository())) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getRepository().getLocation());
-			return;
-		}
-		HgRemoteRepository hgRemote = new HgLookup().detectRemote(cmdLineOpts.getSingle(""), hgRepo.getRepository());
-		if (hgRemote.isInvalid()) {
-			System.err.printf("Remote repository %s is not valid", hgRemote.getLocation());
-			return;
-		}
-		HgIncomingCommand cmd = hgRepo.createIncomingCommand();
-		cmd.against(hgRemote);
-		//
-		List<Nodeid> missing = cmd.executeLite(null);
-		Collections.reverse(missing); // useful to test output, from newer to older
-		Outgoing.dump("Nodes to fetch:", missing);
-		System.out.printf("Total: %d\n\n", missing.size());
-		//
-		// Complete
-		final ChangesetDumpHandler h = new ChangesetDumpHandler(hgRepo.getRepository());
-		h.complete(false); // this option looks up index of parent revision, done via repo.changelog (which doesn't have any of these new revisions)
-		// this can be fixed by tracking all nodeid->revision idx inside ChangesetDumpHandler, and refer to repo.changelog only when that mapping didn't work
-		h.verbose(cmdLineOpts.getBoolean("-v", "--verbose"));
-		cmd.executeFull(h);
-	}
-	
-
-	/*
-	 * This is for investigation purposes only
-	 */
-	private static class SequenceConstructor {
-
-		private int[] between(int root, int head) {
-			if (head <= (root+1)) {
-				return new int[0];
-			}
-			System.out.printf("[%d, %d]\t\t", root, head);
-			int size = 1 + (int) Math.floor(Math.log(head-root - 1) / Math.log(2));
-			int[] rv = new int[size];
-			for (int v = 1, i = 0; i < rv.length; i++) {
-				rv[i] = root + v;
-				v = v << 1;
-			}
-			System.out.println(Arrays.toString(rv));
-			return rv;
-		}
-
-		public void test() {
-			int root = 0, head = 126;
-			int[] data = between(root, head); // max number of elements to recover is 2**data.length-1, when head is exactly
-			// 2**data.length element of the branch.  
-			// In such case, total number of elements in the branch (including head and root, would be 2**data.length+1
-			int[] finalSequence = new int[1 + (1 << data.length >>> 5)]; // div 32 - total bits to integers, +1 for possible modulus
-			int exactNumberOfElements = -1; // exact number of meaningful bits in finalSequence
-			LinkedHashMap<Integer, int[]> datas = new LinkedHashMap<Integer, int[]>();
-			datas.put(root, data);
-			int totalQueries = 1;
-			HashSet<Integer> queried = new HashSet<Integer>();
-			int[] checkSequence = null;
-			while(!datas.isEmpty()) {
-				LinkedList<int[]> toQuery = new LinkedList<int[]>();
-				do {
-					Iterator<Entry<Integer, int[]>> it = datas.entrySet().iterator();
-					Entry<Integer, int[]> next = it.next();
-					int r = next.getKey();
-					data = next.getValue();
-					it.remove();
-					populate(r, head, data, finalSequence);
-					if (checkSequence != null) {
-						boolean match = true;
-//						System.out.println("Try to match:");
-						for (int i = 0; i < checkSequence.length; i++) {
-//							System.out.println(i);
-//							System.out.println("control:" + toBinaryString(checkSequence[i], ' '));
-//							System.out.println("present:" + toBinaryString(finalSequence[i], ' '));
-							if (checkSequence[i] != finalSequence[i]) {
-								match = false;
-							} else {
-								match &= true;
-							}
-						}
-						System.out.println(match ? "Match, on query:" + totalQueries : "Didn't match");
-					}
-					if (data.length > 1) { 
-						/*queries for elements next to head is senseless, hence data.length check above and head-x below*/
-						for (int x : data) {
-							if (!queried.contains(x) && head - x > 1) { 
-								toQuery.add(new int[] {x, head});
-							}
-						}
-					}
-				} while (!datas.isEmpty()) ;
-				if (!toQuery.isEmpty()) {
-					System.out.println();
-					totalQueries++;
-				}
-				Collections.sort(toQuery, new Comparator<int[]>() {
-
-					public int compare(int[] o1, int[] o2) {
-						return o1[0] < o2[0] ? -1 : (o1[0] == o2[0] ? 0 : 1);
-					}
-				});
-				for (int[] x : toQuery) {
-					if (!queried.contains(x[0])) {
-						queried.add(x[0]);
-						data = between(x[0], x[1]);
-						if (exactNumberOfElements == -1 && data.length == 1) {
-							exactNumberOfElements = x[0] + 1;
-							System.out.printf("On query %d found out exact number of missing elements: %d\n", totalQueries, exactNumberOfElements);
-							// get a bit sequence of exactNumberOfElements, 0111..110
-							// to 'and' it with finalSequence later
-							int totalInts = (exactNumberOfElements + 2 /*heading and tailing zero bits*/) >>> 5;
-							int trailingBits = (exactNumberOfElements + 2) & 0x1f;
-							if (trailingBits != 0) {
-								totalInts++;
-							}
-							checkSequence = new int[totalInts];
-							Arrays.fill(checkSequence, 0xffffffff);
-							checkSequence[0] &= 0x7FFFFFFF;
-							if (trailingBits == 0) {
-								checkSequence[totalInts-1] &= 0xFFFFFFFE;
-							} else if (trailingBits == 1) {
-								checkSequence[totalInts-1] = 0;
-							} else {
-								// trailingBits include heading and trailing zero bits
-								int mask = 0x80000000 >> trailingBits-2; // with sign!
-								checkSequence[totalInts - 1] &= mask;
-							}
-							for (int e : checkSequence) {
-								System.out.print(toBinaryString(e, ' '));
-							}
-							System.out.println();
-						}
-						datas.put(x[0], data);
-					}
-				}
-			}
-
-			System.out.println("Total queries:" + totalQueries);
-			for (int x : finalSequence) {
-				System.out.print(toBinaryString(x, ' '));
-			}
-		}
-		
-		private void populate(int root, int head, int[] data, int[] finalSequence) {
-			for (int i = 1, x = 0; root+i < head; i = i << 1, x++) {
-				int value = data[x];
-				int value_check = root+i;
-				if (value != value_check) {
-					throw new IllegalStateException();
-				}
-				int wordIx = (root + i) >>> 5;
-				int bitIx = (root + i) & 0x1f;
-				finalSequence[wordIx] |= 1 << (31-bitIx);
-			}
-		}
-		
-		private static String toBinaryString(int x, char byteSeparator) {
-			StringBuilder sb = new StringBuilder(4*8+4);
-			sb.append(toBinaryString((byte) (x >>> 24)));
-			sb.append(byteSeparator);
-			sb.append(toBinaryString((byte) ((x & 0x00ff0000) >>> 16)));
-			sb.append(byteSeparator);
-			sb.append(toBinaryString((byte) ((x & 0x00ff00) >>> 8)));
-			sb.append(byteSeparator);
-			sb.append(toBinaryString((byte) (x & 0x00ff)));
-			sb.append(byteSeparator);
-			return sb.toString();
-		}
-
-		private static String toBinaryString(byte b) {
-			final String nibbles = "0000000100100011010001010110011110001001101010111100110111101111";
-			assert nibbles.length() == 16*4;
-			int x1 = (b >>> 4) & 0x0f, x2 = b & 0x0f;
-			x1 *= 4; x2 *= 4; // 4 characters per nibble
-			return nibbles.substring(x1, x1+4).concat(nibbles.substring(x2, x2+4));
-		}
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/Log.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-/*
- * Copyright (c) 2010-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.console;
-
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgLogCommand;
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
-import org.tmatesoft.hg.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgRepository;
-
-
-/**
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Log {
-
-	// -agentlib:hprof=heap=sites,depth=10,etc might be handy to debug speed/memory issues
-	
-	public static void main(String[] args) throws Exception {
-		Options cmdLineOpts = Options.parse(args);
-		HgRepository hgRepo = cmdLineOpts.findRepository();
-		if (hgRepo.isInvalid()) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
-			return;
-		}
-		final Dump dump = new Dump(hgRepo);
-		dump.complete(cmdLineOpts.getBoolean("--debug"));
-		dump.verbose(cmdLineOpts.getBoolean("-v", "--verbose"));
-		final boolean reverseOrder = true;
-		dump.reversed(reverseOrder);
-		HgLogCommand cmd = new HgLogCommand(hgRepo);
-		for (String u : cmdLineOpts.getList("-u", "--user")) {
-			cmd.user(u);
-		}
-		for (String b : cmdLineOpts.getList("-b", "--branches")) {
-			cmd.branch(b);
-		}
-		int limit = cmdLineOpts.getSingleInt(-1, "-l", "--limit");
-		if (limit != -1) {
-			cmd.limit(limit);
-		}
-		List<String> files = cmdLineOpts.getList("");
-		final long start = System.currentTimeMillis();
-		if (files.isEmpty()) {
-			if (limit == -1) {
-				// no revisions and no limit
-				cmd.execute(dump);
-			} else {
-				// in fact, external (to dump inspector) --limit processing yelds incorrect results when other args
-				// e.g. -u or -b are used (i.e. with -u shall give <limit> csets with user, not check last <limit> csets for user 
-				int[] r = new int[] { 0, hgRepo.getChangelog().getRevisionCount() };
-				if (fixRange(r, reverseOrder, limit) == 0) {
-					System.out.println("No changes");
-					return;
-				}
-				cmd.range(r[0], r[1]).execute(dump);
-			}
-			dump.done();
-		} else {
-			for (String fname : files) {
-				HgDataFile f1 = hgRepo.getFileNode(fname);
-				System.out.println("History of the file: " + f1.getPath());
-				if (limit == -1) {
-					cmd.file(f1.getPath(), true).execute(dump);
-				} else {
-					int[] r = new int[] { 0, f1.getRevisionCount() };
-					if (fixRange(r, reverseOrder, limit) == 0) {
-						System.out.println("No changes");
-						continue;
-					}
-					cmd.range(r[0], r[1]).file(f1.getPath(), true).execute(dump);
-				}
-				dump.done();
-			}
-		}
-//		cmd = null;
-		System.out.println("Total time:" + (System.currentTimeMillis() - start));
-//		Main.force_gc();
-	}
-	
-	private static int fixRange(int[] start_end, boolean reverse, int limit) {
-		assert start_end.length == 2;
-		if (limit < start_end[1]) {
-			if (reverse) {
-				// adjust left boundary of the range
-				start_end[0] = start_end[1] - limit;
-			} else {
-				start_end[1] = limit; // adjust right boundary
-			}
-		}
-		int rv = start_end[1] - start_end[0];
-		start_end[1]--; // range needs index, not length
-		return rv;
-	}
-
-	private static final class Dump extends ChangesetDumpHandler implements HgLogCommand.FileHistoryHandler {
-
-		public Dump(HgRepository hgRepo) {
-			super(hgRepo);
-		}
-		
-		public void copy(FileRevision from, FileRevision to) {
-			System.out.printf("Got notified that %s(%s) was originally known as %s(%s)\n", to.getPath(), to.getRevision(), from.getPath(), from.getRevision());
-		}
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,290 +0,0 @@
-/*
- * 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.console;
-
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
-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.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgInternals;
-import org.tmatesoft.hg.repo.HgManifest;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.HgStatusCollector;
-import org.tmatesoft.hg.repo.HgStatusInspector;
-import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
-import org.tmatesoft.hg.util.Path;
-
-/**
- * Various debug dumps. 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Main {
-	
-	private Options cmdLineOpts;
-	private HgRepository hgRepo;
-
-	public Main(String[] args) throws Exception {
-		cmdLineOpts = Options.parse(args);
-		hgRepo = cmdLineOpts.findRepository();
-		if (hgRepo.isInvalid()) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
-			return;
-		}
-		System.out.println("REPO:" + hgRepo.getLocation());
-	}
-
-	public static void main(String[] args) throws Exception {
-		Main m = new Main(args);
-		m.inflaterLengthException();
-//		m.dumpIgnored();
-//		m.dumpDirstate();
-//		m.testStatusInternals();
-//		m.catCompleteHistory();
-//		m.dumpCompleteManifestLow();
-//		m.dumpCompleteManifestHigh();
-//		m.bunchOfTests();
-	}
-	
-	private void inflaterLengthException() throws Exception {
-		HgDataFile f1 = hgRepo.getFileNode("src/com/tmate/hgkit/console/Bundle.java");
-		HgDataFile f2 = hgRepo.getFileNode("test-repos.jar");
-		System.out.println(f1.isCopy());
-		System.out.println(f2.isCopy());
-		ByteArrayChannel bac = new ByteArrayChannel();
-		f1.content(1, bac); // 0: 1151, 1: 1139
-		System.out.println(bac.toArray().length);
-		f2.content(0, bac = new ByteArrayChannel()); // 0: 14269
-		System.out.println(bac.toArray().length);
-	}
-	
-	private void dumpIgnored() {
-		HgInternals debug = new HgInternals(hgRepo);
-		String[] toCheck = new String[] {"design.txt", "src/com/tmate/hgkit/ll/Changelog.java", "src/Extras.java", "bin/com/tmate/hgkit/ll/Changelog.class"};
-		boolean[] checkResult = debug.checkIgnored(toCheck);
-		for (int i = 0; i < toCheck.length; i++) {
-			System.out.println("Ignored " + toCheck[i] + ": " + checkResult[i]);
-		}
-	}
-	
-	private void dumpDirstate() {
-		new HgInternals(hgRepo).dumpDirstate();
-	}
-
-	
-	private void catCompleteHistory() throws Exception {
-		DigestHelper dh = new DigestHelper();
-		for (String fname : cmdLineOpts.getList("")) {
-			System.out.println(fname);
-			HgDataFile fn = hgRepo.getFileNode(fname);
-			if (fn.exists()) {
-				int total = fn.getRevisionCount();
-				System.out.printf("Total revisions: %d\n", total);
-				for (int i = 0; i < total; i++) {
-					ByteArrayChannel sink = new ByteArrayChannel();
-					fn.content(i, sink);
-					System.out.println("==========>");
-					byte[] content = sink.toArray();
-					System.out.println(new String(content));
-					int[] parentRevisions = new int[2];
-					byte[] parent1 = new byte[20];
-					byte[] parent2 = new byte[20];
-					fn.parents(i, parentRevisions, parent1, parent2);
-					System.out.println(dh.sha1(parent1, parent2, content).asHexString());
-				}
-			} else {
-				System.out.println(">>>Not found!");
-			}
-		}
-	}
-
-	private void dumpCompleteManifestLow() {
-		hgRepo.getManifest().walk(0, TIP, new ManifestDump());
-	}
-
-	public static final class ManifestDump implements HgManifest.Inspector {
-		public boolean begin(int revision, Nodeid nid) {
-			System.out.printf("%d : %s\n", revision, nid);
-			return true;
-		}
-
-		public boolean next(Nodeid nid, String fname, String flags) {
-			System.out.println(nid + "\t" + fname + "\t\t" + flags);
-			return true;
-		}
-
-		public boolean end(int revision) {
-			System.out.println();
-			return true;
-		}
-	}
-
-	private void dumpCompleteManifestHigh() {
-		new HgManifestCommand(hgRepo).dirs(true).execute(new HgManifestCommand.Handler() {
-			
-			public void begin(Nodeid manifestRevision) {
-				System.out.println(">> " + manifestRevision);
-			}
-			public void dir(Path p) {
-				System.out.println(p);
-			}
-			public void file(FileRevision fileRevision) {
-				System.out.print(fileRevision.getRevision());;
-				System.out.print("   ");
-				System.out.println(fileRevision.getPath());
-			}
-			
-			public void end(Nodeid manifestRevision) {
-				System.out.println();
-			}
-		}); 
-	}
-
-	private void bunchOfTests() throws Exception {
-		HgInternals debug = new HgInternals(hgRepo);
-		debug.dumpDirstate();
-		final StatusDump dump = new StatusDump();
-		dump.showIgnored = false;
-		dump.showClean = false;
-		HgStatusCollector sc = new HgStatusCollector(hgRepo);
-		final int r1 = 0, r2 = 3;
-		System.out.printf("Status for changes between revision %d and %d:\n", r1, r2);
-		sc.walk(r1, r2, dump);
-		// 
-		System.out.println("\n\nSame, but sorted in the way hg status does:");
-		HgStatusCollector.Record r = sc.status(r1, r2);
-		sortAndPrint('M', r.getModified(), null);
-		sortAndPrint('A', r.getAdded(), null);
-		sortAndPrint('R', r.getRemoved(), null);
-		//
-		System.out.println("\n\nTry hg status --change <rev>:");
-		sc.change(0, dump);
-		System.out.println("\nStatus against working dir:");
-		HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(hgRepo);
-		wcc.walk(TIP, dump);
-		System.out.println();
-		System.out.printf("Manifest of the revision %d:\n", r2);
-		hgRepo.getManifest().walk(r2, r2, new ManifestDump());
-		System.out.println();
-		System.out.printf("\nStatus of working dir against %d:\n", r2);
-		r = wcc.status(r2);
-		sortAndPrint('M', r.getModified(), null);
-		sortAndPrint('A', r.getAdded(), r.getCopied());
-		sortAndPrint('R', r.getRemoved(), null);
-		sortAndPrint('?', r.getUnknown(), null);
-		sortAndPrint('I', r.getIgnored(), null);
-		sortAndPrint('C', r.getClean(), null);
-		sortAndPrint('!', r.getMissing(), null);
-	}
-	
-	private void sortAndPrint(char prefix, List<Path> ul, Map<Path, Path> copies) {
-		ArrayList<Path> sortList = new ArrayList<Path>(ul);
-		Collections.sort(sortList);
-		for (Path s : sortList)  {
-			System.out.print(prefix);
-			System.out.print(' ');
-			System.out.println(s);
-			if (copies != null && copies.containsKey(s)) {
-				System.out.println("  " + copies.get(s));
-			}
-		}
-	}
-
-
-	private void testStatusInternals() {
-		HgDataFile n = hgRepo.getFileNode(Path.create("design.txt"));
-		for (String s : new String[] {"011dfd44417c72bd9e54cf89b82828f661b700ed", "e5529faa06d53e06a816e56d218115b42782f1ba", "c18e7111f1fc89a80a00f6a39d51288289a382fc"}) {
-			// expected: 359, 2123, 3079
-			byte[] b = s.getBytes();
-			final Nodeid nid = Nodeid.fromAscii(b, 0, b.length);
-			System.out.println(s + " : " + n.length(nid));
-		}
-	}
-
-	static void force_gc() {
-		Runtime.getRuntime().runFinalization();
-		Runtime.getRuntime().gc();
-		Thread.yield();
-		Runtime.getRuntime().runFinalization();
-		Runtime.getRuntime().gc();
-		Thread.yield();
-	}
-
-	private static class StatusDump implements HgStatusInspector {
-		public boolean hideStatusPrefix = false; // hg status -n option
-		public boolean showCopied = true; // -C
-		public boolean showIgnored = true; // -i
-		public boolean showClean = true; // -c
-
-		public void modified(Path fname) {
-			print('M', fname);
-		}
-
-		public void added(Path fname) {
-			print('A', fname);
-		}
-
-		public void copied(Path fnameOrigin, Path fnameAdded) {
-			added(fnameAdded);
-			if (showCopied) {
-				print(' ', fnameOrigin);
-			}
-		}
-
-		public void removed(Path fname) {
-			print('R', fname);
-		}
-
-		public void clean(Path fname) {
-			if (showClean) {
-				print('C', fname);
-			}
-		}
-
-		public void missing(Path fname) {
-			print('!', fname);
-		}
-
-		public void unknown(Path fname) {
-			print('?', fname);
-		}
-
-		public void ignored(Path fname) {
-			if (showIgnored) {
-				print('I', fname);
-			}
-		}
-		
-		private void print(char status, Path fname) {
-			if (!hideStatusPrefix) {
-				System.out.print(status);
-				System.out.print(' ');
-			}
-			System.out.println(fname);
-		}
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/Manifest.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2010-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.console;
-
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
-import org.tmatesoft.hg.core.HgManifestCommand;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Manifest {
-
-	public static void main(String[] args) throws Exception {
-		Options cmdLineOpts = Options.parse(args);
-		HgRepository hgRepo = cmdLineOpts.findRepository();
-		if (hgRepo.isInvalid()) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
-			return;
-		}
-		final boolean debug = cmdLineOpts.getBoolean("--debug");
-		final boolean verbose = cmdLineOpts.getBoolean("-v", "--verbose");
-		HgManifestCommand.Handler h = new HgManifestCommand.Handler() {
-			
-			public void begin(Nodeid manifestRevision) {
-			}
-			public void dir(Path p) {
-			}
-			public void file(FileRevision fileRevision) {
-				if (debug) {
-					System.out.print(fileRevision.getRevision());;
-				}
-				if (debug || verbose) {
-					System.out.print(" 644"); // FIXME real flags!
-					System.out.print("   ");
-				}
-				System.out.println(fileRevision.getPath());
-			}
-			
-			public void end(Nodeid manifestRevision) {
-			}
-		};
-		int rev = cmdLineOpts.getSingleInt(TIP, "-r", "--rev");
-		new HgManifestCommand(hgRepo).dirs(false).revision(rev).execute(h); 
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/Options.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * 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.console;
-
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRepository;
-
-/**
- * Parse command-line options. Primitive implementation that recognizes options with 0 or 1 argument.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-class Options {
-
-	public final Map<String,List<String>> opt2values = new HashMap<String, List<String>>();
-
-	public boolean getBoolean(String... aliases) {
-		return getBoolean(false, aliases);
-	}
-
-	public boolean getBoolean(boolean def, String... aliases) {
-		for (String s : aliases) {
-			if (opt2values.containsKey(s)) {
-				return true;
-			}
-		}
-		return def;
-	}
-
-	public String getSingle(String... aliases) {
-		String rv = null;
-		for (String s : aliases) {
-			List<String> values = opt2values.get(s);
-			if (values != null && values.size() > 0) {
-				rv = values.get(values.size() - 1); // take last one, most recent
-			}
-		}
-		return rv;
-	}
-	
-	public int getSingleInt(int def, String... aliases) {
-		String r = getSingle(aliases);
-		if (r == null) {
-			return def;
-		}
-		return Integer.parseInt(r);
-	}
-
-	public List<String> getList(String... aliases) {
-		LinkedList<String> rv = new LinkedList<String>();
-		for (String s : aliases) {
-			List<String> values = opt2values.get(s);
-			if (values != null) {
-				rv.addAll(values);
-			}
-		}
-		return rv;
-	}
-	
-	public HgRepository findRepository() throws Exception {
-		String repoLocation = getSingle("-R", "--repository");
-		if (repoLocation != null) {
-			return new HgLookup().detect(repoLocation);
-		}
-		return new HgLookup().detectFromWorkingDir();
-	}
-
-
-	public static Options parse(String[] commandLineArgs) {
-		Options rv = new Options();
-		List<String> values = new LinkedList<String>();
-		rv.opt2values.put("", values); // values with no options
-		for (String arg : commandLineArgs) {
-			if (arg.charAt(0) == '-') {
-				// option
-				if (arg.length() == 1) {
-					throw new IllegalArgumentException("Bad option: -");
-				}
-				values = rv.opt2values.get(arg);
-				if (values == null) {
-					rv.opt2values.put(arg, values = new LinkedList<String>());
-				}
-				// next value, if any, gets into the values list for arg option.
-			} else {
-				values.add(arg);
-				values = rv.opt2values.get("");
-			}
-		}
-		return rv;
-	}
-}
\ No newline at end of file
--- a/cmdline/org/tmatesoft/hg/console/Outgoing.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*
- * 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.console;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgOutgoingCommand;
-import org.tmatesoft.hg.core.HgRepoFacade;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-
-
-/**
- * <em>hg outgoing</em>
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Outgoing {
-
-	public static void main(String[] args) throws Exception {
-		Options cmdLineOpts = Options.parse(args);
-		HgRepoFacade hgRepo = new HgRepoFacade();
-		if (!hgRepo.init(cmdLineOpts.findRepository())) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getRepository().getLocation());
-			return;
-		}
-		// XXX perhaps, HgRepoFacade shall get detectRemote() analog (to get remote server with respect of facade's repo)
-		HgRemoteRepository hgRemote = new HgLookup().detectRemote(cmdLineOpts.getSingle(""), hgRepo.getRepository());
-		if (hgRemote.isInvalid()) {
-			System.err.printf("Remote repository %s is not valid", hgRemote.getLocation());
-			return;
-		}
-		//
-		HgOutgoingCommand cmd = hgRepo.createOutgoingCommand();
-		cmd.against(hgRemote);
-		
-		// find all local children of commonKnown
-		List<Nodeid> result = cmd.executeLite(null);
-		dump("Lite", result);
-		//
-		//
-		System.out.println("Full");
-		// show all, starting from next to common 
-		final ChangesetDumpHandler h = new ChangesetDumpHandler(hgRepo.getRepository());
-		h.complete(cmdLineOpts.getBoolean("--debug")).verbose(cmdLineOpts.getBoolean("-v", "--verbose"));
-		cmd.executeFull(h);
-		h.done();
-	}
-
-//	public static class ChangesetFormatter {
-//		private final StringBuilder sb = new StringBuilder(1024);
-//
-//		public CharSequence simple(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
-//			sb.setLength(0);
-//			sb.append(String.format("changeset:  %d:%s\n", revisionNumber, nodeid.toString()));
-//			sb.append(String.format("user:       %s\n", cset.user()));
-//			sb.append(String.format("date:       %s\n", cset.dateString()));
-//			sb.append(String.format("comment:    %s\n\n", cset.comment()));
-//			return sb;
-//		}
-//	}
-	
-
-	static void dump(String s, Collection<Nodeid> c) {
-		System.out.println(s);
-		for (Nodeid n : c) {
-			System.out.println(n);
-		}
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/Remote.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,183 +0,0 @@
-/*
- * 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.console;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.List;
-import java.util.Map;
-import java.util.prefs.Preferences;
-import java.util.zip.InflaterInputStream;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-
-import org.tmatesoft.hg.internal.ConfigFile;
-import org.tmatesoft.hg.internal.Internals;
-
-/**
- * WORK IN PROGRESS, DO NOT USE
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Remote {
-
-	/*
-	 * @see http://mercurial.selenic.com/wiki/WireProtocol
-	 cmd=branches gives 4 nodeids (head, root, first parent, second parent) per line (few lines possible, per branch, perhaps?)
-	 cmd=capabilities gives lookup ...subset and 3 compress methods
-	 // lookup changegroupsubset unbundle=HG10GZ,HG10BZ,HG10UN
-	 cmd=heads gives space-separated list of nodeids (or just one)
-	 nodeids are in hex (printable) format, need to convert fromAscii()
-	 cmd=branchmap
-	 cmd=between needs argument pairs, with first element in the pair to be head(!), second to be root of the branch (
-	 	i.e. (newer-older), not (older-newer) as one might expect. Returned list of nodes comes in reversed order (from newer
-	 	to older) as well
-
-	cmd=branches&nodes=d6d2a630f4a6d670c90a5ca909150f2b426ec88f+
-	head, root, first parent, second parent
-	received: d6d2a630f4a6d670c90a5ca909150f2b426ec88f dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
-	
-	Sequence, for actual state with merged/closed branch, where 157:d5268ca7715b8d96204fc62abc632e8f55761547 is merge revision of 156 and 53 
-	>branches, 170:71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d
-	 71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d d5268ca7715b8d96204fc62abc632e8f55761547 643ddec3be36246fc052cf22ece503fa60cafe22 a6f39e595b2b54f56304470269a936ead77f5725
-
-	>branches, 156:643ddec3be36246fc052cf22ece503fa60cafe22
-	 643ddec3be36246fc052cf22ece503fa60cafe22 ade65afe0906febafbf8a2e41002052e0e446471 08754fce5778a3409476ecdb3cec6b5172c34367 40d04c4f771ebbd599eb229145252732a596740a
-	>branches, 53:a6f39e595b2b54f56304470269a936ead77f5725
-	 a6f39e595b2b54f56304470269a936ead77f5725 a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5
-
-	>branches, 84:08754fce5778a3409476ecdb3cec6b5172c34367  (p1:82) 
-	 08754fce5778a3409476ecdb3cec6b5172c34367 dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
-	>branches, 83:40d04c4f771ebbd599eb229145252732a596740a (p1:80)
-	 40d04c4f771ebbd599eb229145252732a596740a dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
-
-	>branches, 51:9429c7bd1920fab164a9d2b621d38d57bcb49ae0 (wrap-data-access branch)
-	 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
-	>branches, 52:30bd389788464287cee22ccff54c330a4b715de5 (p1:50)
-	 30bd389788464287cee22ccff54c330a4b715de5 dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
-
-
-	cmd=between&pairs=71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d-d5268ca7715b8d96204fc62abc632e8f55761547+40d04c4f771ebbd599eb229145252732a596740a-dbd663faec1f0175619cf7668bddc6350548b8d6
-	 8c8e3f372fa1fbfcf92b004b6f2ada2dbaf60028 dd525ca65de8e78cb133919de57ea0a6e6454664 1d0654be1466d522994f8bead510e360fbeb8d79 c17a08095e4420202ac1b2d939ef6d5f8bebb569
-	 4222b04f34ee885bc1ad547c7ef330e18a51afc1 5f9635c016819b322ae05a91b3378621b538c933 c677e159391925a50b9a23f557426b2246bc9c5d 0d279bcc44427cb5ae2f3407c02f21187ccc8aea e21df6259f8374ac136767321e837c0c6dd21907 b01500fe2604c2c7eadf44349cce9f438484474b 865bf07f381ff7d1b742453568def92576af80b6
-
-	Between two subsequent revisions (i.e. direct child in remote of a local root) 
-	cmd=between&pairs=71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d-8c8e3f372fa1fbfcf92b004b6f2ada2dbaf60028
-	 empty result
-	 */
-	public static void main(String[] args) throws Exception {
-		ConfigFile cfg = new Internals().newConfigFile();
-		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");
-//		URL url = new URL(svnkitServer + "?cmd=between"); 
-		URL url = new URL(svnkitServer + "?cmd=changegroup&roots=71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d");
-//		URL url = new URL("http://localhost:8000/" + "?cmd=between");
-//		URL url = new URL(svnkitServer + "?cmd=stream_out");
-	
-		SSLContext sslContext = SSLContext.getInstance("SSL");
-		class TrustEveryone implements X509TrustManager {
-			public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
-				System.out.println("checkClientTrusted " + authType);
-			}
-			public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
-				System.out.println("checkServerTrusted" + authType);
-			}
-			public X509Certificate[] getAcceptedIssuers() {
-				return new X509Certificate[0];
-			}
-		}
-		// Hack to get Base64-encoded credentials
-		Preferences tempNode = Preferences.userRoot().node("xxx");
-		tempNode.putByteArray("xxx", url.getUserInfo().getBytes());
-		String authInfo = tempNode.get("xxx", null);
-		tempNode.removeNode();
-		//
-		sslContext.init(null, new TrustManager[] { new TrustEveryone() }, null);
-		HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
-//		HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
-		urlConnection.setRequestProperty("User-Agent", "jhg/0.1.0");
-		urlConnection.setRequestProperty("Accept", "application/mercurial-0.1");
-		urlConnection.setRequestProperty("Authorization", "Basic " + authInfo);
-		urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
-//		byte[] body = "pairs=f5aed108754e817d2ca374d1a4f6daf1218dcc91-9429c7bd1920fab164a9d2b621d38d57bcb49ae0".getBytes();
-//		urlConnection.setRequestMethod("POST");
-//		urlConnection.setRequestProperty("Content-Length", String.valueOf(body.length));
-//		urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-//		urlConnection.setDoOutput(true);
-//		urlConnection.setDoInput(true);
-		urlConnection.connect();
-//		OutputStream os = urlConnection.getOutputStream();
-//		os.write(body);
-//		os.flush();
-//		os.close();
-		System.out.println("Query:" + url.getQuery());
-		System.out.println("Response headers:");
-		final Map<String, List<String>> headerFields = urlConnection.getHeaderFields();
-		for (String s : headerFields.keySet()) {
-			System.out.printf("%s: %s\n", s, urlConnection.getHeaderField(s));
-		}
-		System.out.printf("Content type is %s and its length is %d\n", urlConnection.getContentType(), urlConnection.getContentLength());
-		InputStream is = urlConnection.getInputStream();
-		//
-//		dump(is, -1); // simple dump, any cmd
-		writeBundle(is, false, "HG10GZ"); // cmd=changegroup
-		//writeBundle(is, true, "" or "HG10UN");
-		//
-		urlConnection.disconnect();
-		//
-	}
-
-	private static void dump(InputStream is, int limit) throws IOException {
-		int b;
-		while ((b =is.read()) != -1) {
-			System.out.print((char) b);
-			if (limit != -1) {
-				if (--limit < 0) {
-					break;
-				}
-			}
-		}
-		System.out.println();
-	}
-	
-	private static void writeBundle(InputStream is, boolean decompress, String header) throws IOException {
-		InputStream zipStream = decompress ? new InflaterInputStream(is) : is;
-		File tf = File.createTempFile("hg-bundle-", null);
-		FileOutputStream fos = new FileOutputStream(tf);
-		fos.write(header.getBytes());
-		int r;
-		byte[] buf = new byte[8*1024];
-		while ((r = zipStream.read(buf)) != -1) {
-			fos.write(buf, 0, r);
-		}
-		fos.close();
-		zipStream.close();
-		System.out.println(tf);
-	}
-}
--- a/cmdline/org/tmatesoft/hg/console/Status.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/*
- * Copyright (c) 2010-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.console;
-
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.tmatesoft.hg.core.HgRepoFacade;
-import org.tmatesoft.hg.core.HgStatus;
-import org.tmatesoft.hg.core.HgStatus.Kind;
-import org.tmatesoft.hg.core.HgStatusCommand;
-import org.tmatesoft.hg.util.Path;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Status {
-
-	public static void main(String[] args) throws Exception {
-		Options cmdLineOpts = Options.parse(args);
-		HgRepoFacade hgRepo = new HgRepoFacade();
-		if (!hgRepo.init(cmdLineOpts.findRepository())) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getRepository().getLocation());
-			return;
-		}
-		//
-		HgStatusCommand cmd = hgRepo.createStatusCommand();
-		if (cmdLineOpts.getBoolean("-A", "--all")) {
-			cmd.all();
-		} else {
-			// default: mardu
-			cmd.modified(cmdLineOpts.getBoolean(true, "-m", "--modified"));
-			cmd.added(cmdLineOpts.getBoolean(true, "-a", "--added"));
-			cmd.removed(cmdLineOpts.getBoolean(true, "-r", "--removed"));
-			cmd.deleted(cmdLineOpts.getBoolean(true, "-d", "--deleted"));
-			cmd.unknown(cmdLineOpts.getBoolean(true, "-u", "--unknonwn"));
-			cmd.clean(cmdLineOpts.getBoolean("-c", "--clean"));
-			cmd.ignored(cmdLineOpts.getBoolean("-i", "--ignored"));
-		}
-//		cmd.subrepo(cmdLineOpts.getBoolean("-S", "--subrepos"))
-		final boolean noStatusPrefix = cmdLineOpts.getBoolean("-n", "--no-status");
-		final boolean showCopies = cmdLineOpts.getBoolean("-C", "--copies");
-		class StatusHandler implements HgStatusCommand.Handler {
-			
-			final Map<HgStatus.Kind, List<Path>> data = new TreeMap<HgStatus.Kind, List<Path>>();
-			final Map<Path, Path> copies = showCopies ? new HashMap<Path,Path>() : null;
-			
-			public void handleStatus(HgStatus s) {
-				List<Path> l = data.get(s.getKind());
-				if (l == null) {
-					l = new LinkedList<Path>();
-					data.put(s.getKind(), l);
-				}
-				l.add(s.getPath());
-				if (s.isCopy() && showCopies) {
-					copies.put(s.getPath(), s.getOriginalPath());
-				}
-			}
-			
-			public void dump() {
-				sortAndPrint('M', data.get(Kind.Modified), null);
-				sortAndPrint('A', data.get(Kind.Added), copies);
-				sortAndPrint('R', data.get(Kind.Removed), null);
-				sortAndPrint('?', data.get(Kind.Unknown), null);
-				sortAndPrint('I', data.get(Kind.Ignored), null);
-				sortAndPrint('C', data.get(Kind.Clean), null);
-				sortAndPrint('!', data.get(Kind.Missing), null);
-			}
-
-			private void sortAndPrint(char prefix, List<Path> ul, Map<Path, Path> copies) {
-				if (ul == null) {
-					return;
-				}
-				ArrayList<Path> sortList = new ArrayList<Path>(ul);
-				Collections.sort(sortList);
-				for (Path s : sortList)  {
-					if (!noStatusPrefix) {
-						System.out.print(prefix);
-						System.out.print(' ');
-					}
-					System.out.println(s);
-					if (copies != null && copies.containsKey(s)) {
-						System.out.println("  " + copies.get(s));
-					}
-				}
-			}
-		};
-
-		StatusHandler statusHandler = new StatusHandler(); 
-		int changeRev = cmdLineOpts.getSingleInt(BAD_REVISION, "--change");
-		if (changeRev != BAD_REVISION) {
-			cmd.change(changeRev);
-		} else {
-			List<String> revisions = cmdLineOpts.getList("--rev");
-			int size = revisions.size();
-			if (size > 1) {
-				cmd.base(Integer.parseInt(revisions.get(size - 2))).revision(Integer.parseInt(revisions.get(size - 1)));
-			} else if (size > 0) {
-				cmd.base(Integer.parseInt(revisions.get(0)));
-			}
-		}
-		cmd.execute(statusHandler);
-		statusHandler.dump();
-	}
-}
Binary file gradle/wrapper/gradle-wrapper.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gradle/wrapper/gradle-wrapper.properties	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,6 @@
+#Fri Apr 22 16:45:48 CEST 2011
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://repo.gradle.org/gradle/distributions/gradle-1.0-milestone-3-bin.zip
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gradlew	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,168 @@
+#!/bin/bash
+
+##############################################################################
+##                                                                          ##
+##  Gradle wrapper script for UN*X                                         ##
+##                                                                          ##
+##############################################################################
+
+# Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
+# GRADLE_OPTS="$GRADLE_OPTS -Xmx512m"
+# JAVA_OPTS="$JAVA_OPTS -Xmx512m"
+
+GRADLE_APP_NAME=Gradle
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set JAVA_HOME if it's not already set.
+if [ -z "$JAVA_HOME" ] ; then
+    if $darwin ; then
+        [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
+        [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
+    else
+        javaExecutable="`which javac`"
+        [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
+        # readlink(1) is not available as standard on Solaris 10.
+        readLink=`which readlink`
+        [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+        export JAVA_HOME="$javaHome"
+    fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"`
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain
+CLASSPATH=`dirname "$0"`/gradle/wrapper/gradle-wrapper.jar
+WRAPPER_PROPERTIES=`dirname "$0"`/gradle/wrapper/gradle-wrapper.properties
+# Determine the Java command to use to start the JVM.
+if [ -z "$JAVACMD" ] ; then
+    if [ -n "$JAVA_HOME" ] ; then
+        if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+            # IBM's JDK on AIX uses strange locations for the executables
+            JAVACMD="$JAVA_HOME/jre/sh/java"
+        else
+            JAVACMD="$JAVA_HOME/bin/java"
+        fi
+    else
+        JAVACMD="java"
+    fi
+fi
+if [ ! -x "$JAVACMD" ] ; then
+    die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+if [ -z "$JAVA_HOME" ] ; then
+    warn "JAVA_HOME environment variable is not set"
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name
+if $darwin; then
+    JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME"
+# we may also want to set -Xdock:image
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done 
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+GRADLE_APP_BASE_NAME=`basename "$0"`
+
+exec "$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \
+        -classpath "$CLASSPATH" \
+        -Dorg.gradle.appname="$GRADLE_APP_BASE_NAME" \
+        -Dorg.gradle.wrapper.properties="$WRAPPER_PROPERTIES" \
+        $STARTER_MAIN_CLASS \
+        "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gradlew.bat	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,82 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem                                                                         ##
+@rem  Gradle startup script for Windows                                      ##
+@rem                                                                         ##
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
+@rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512m
+@rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512m
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.\
+
+@rem Find java.exe
+set JAVA_EXE=java.exe
+if not defined JAVA_HOME goto init
+
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+echo.
+goto end
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain
+set CLASSPATH=%DIRNAME%\gradle\wrapper\gradle-wrapper.jar
+set WRAPPER_PROPERTIES=%DIRNAME%\gradle\wrapper\gradle-wrapper.properties
+
+set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%"
+
+@rem Execute Gradle
+"%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
+
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%"
+exit /b "%ERRORLEVEL%"
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Bundle.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,96 @@
+/*
+ * 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.console;
+
+import java.io.File;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgBundle;
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+
+
+/**
+ * WORK IN PROGRESS, DO NOT USE
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Bundle {
+	public static void main(String[] args) throws Exception {
+		Options cmdLineOpts = Options.parse(args);
+		final HgRepository hgRepo = cmdLineOpts.findRepository();
+		if (hgRepo.isInvalid()) {
+			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
+			return;
+		}
+		File bundleFile = new File("/temp/hg/hg-bundle-cpython.tmp");
+		HgBundle hgBundle = new HgLookup().loadBundle(bundleFile);
+		hgBundle.inspectFiles(new HgBundle.Dump());
+		if (Boolean.parseBoolean("true")) {
+			return;
+		}
+		/* pass -R <path-to-repo-with-less-revisions-than-bundle>, e.g. for bundle with tip=168 and -R \temp\hg4j-50 with tip:159
+		+Changeset {User: ..., Comment: Integer ....}
+		+Changeset {User: ..., Comment: Approach with ...}
+		-Changeset {User: ..., Comment: Correct project name...}
+		-Changeset {User: ..., Comment: Record possible...}
+		*/
+		hgBundle.changes(hgRepo, new HgChangelog.Inspector() {
+			private final HgChangelog changelog = hgRepo.getChangelog();
+			
+			public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+				if (changelog.isKnown(nodeid)) {
+					System.out.print("+");
+				} else {
+					System.out.print("-");
+				}
+				System.out.printf("%d:%s\n%s\n", revisionNumber, nodeid.shortNotation(), cset.toString());
+			}
+		});
+	}
+
+/*
+ *  TODO EXPLAIN why DataAccess.java on merge from branch has P2 set, and P1 is NULL
+ *  
+ *  excerpt from dump('hg-bundle-00') output (node, p1, p2, cs):
+ src/org/tmatesoft/hg/internal/DataAccess.java
+  186af94a2a7ddb34190e63ce556d0fa4dd24add2 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 6f1b88693d48422e98c3eaaa8428ffd4d4d98ca7; patches:1
+  be8d0fdc4ff268bf5eb0a9120282ce6e63de1606 186af94a2a7ddb34190e63ce556d0fa4dd24add2 0000000000000000000000000000000000000000 a3a2e5deb320d7412ccbb59bdc44668d445bc4c4; patches:2
+  333d7bbd4a80a5d6fb4b44e54e39e290f50dc7f8 be8d0fdc4ff268bf5eb0a9120282ce6e63de1606 0000000000000000000000000000000000000000 e93101b97e4ab0a3f3402ec0e80b6e559237c7c8; patches:1
+  56e4523cb8b42630daf70511d73d29e0b375dfa5 0000000000000000000000000000000000000000 333d7bbd4a80a5d6fb4b44e54e39e290f50dc7f8 d5268ca7715b8d96204fc62abc632e8f55761547; patches:6
+  f85b6d7ed3cc4b7c6f99444eb0a41b58793cc900 56e4523cb8b42630daf70511d73d29e0b375dfa5 0000000000000000000000000000000000000000 b413b16d10a50cc027f4c38e4df5a9fedd618a79; patches:4
+	  
+  RevlogDump for the file says:
+  Index    Offset      Flags     Packed     Actual   Base Rev   Link Rev  Parent1  Parent2     nodeid
+   0:    4295032832      0       1109       2465          0         74       -1       -1     186af94a2a7ddb34190e63ce556d0fa4dd24add2
+   1:          1109      0         70       2364          0        102        0       -1     be8d0fdc4ff268bf5eb0a9120282ce6e63de1606
+   2:          1179      0         63       2365          0        122        1       -1     333d7bbd4a80a5d6fb4b44e54e39e290f50dc7f8
+   3:          1242      0        801       3765          0        157       -1        2     56e4523cb8b42630daf70511d73d29e0b375dfa5
+   4:          2043      0        130       3658          0        158        3       -1     f85b6d7ed3cc4b7c6f99444eb0a41b58793cc900
+
+  Excerpt from changelog dump:
+  155:         30541      0        155        195        155        155      154       -1     a4ec5e08701771b96057522188b16ed289e9e8fe
+  156:         30696      0        154        186        155        156      155       -1     643ddec3be36246fc052cf22ece503fa60cafe22
+  157:         30850      0        478       1422        155        157      156       53     d5268ca7715b8d96204fc62abc632e8f55761547
+  158:         31328      0        247        665        155        158      157       -1     b413b16d10a50cc027f4c38e4df5a9fedd618a79
+			   
+
+ */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Cat.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2010-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.console;
+
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.ByteChannel;
+
+
+/**
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Cat {
+
+	public static void main(String[] args) throws Exception {
+		Options cmdLineOpts = Options.parse(args);
+		HgRepository hgRepo = cmdLineOpts.findRepository();
+		if (hgRepo.isInvalid()) {
+			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
+			return;
+		}
+		int rev = cmdLineOpts.getSingleInt(TIP, "-r", "--rev");
+		OutputStreamChannel out = new OutputStreamChannel(System.out);
+		for (String fname : cmdLineOpts.getList("")) {
+			System.out.println(fname);
+			HgDataFile fn = hgRepo.getFileNode(fname);
+			if (fn.exists()) {
+				fn.contentWithFilters(rev, out);
+				System.out.println();
+			} else {
+				System.out.printf("%s not found!\n", fname);
+			}
+		}
+	}
+
+	private static class OutputStreamChannel implements ByteChannel {
+
+		private final OutputStream stream;
+
+		public OutputStreamChannel(OutputStream out) {
+			stream = out;
+		}
+
+		public int write(ByteBuffer buffer) throws IOException {
+			int count = buffer.remaining();
+			while(buffer.hasRemaining()) {
+				stream.write(buffer.get());
+			}
+			return count;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/ChangesetDumpHandler.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,165 @@
+/*
+ * 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.console;
+
+import java.util.Formatter;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgChangeset;
+import org.tmatesoft.hg.core.HgChangesetHandler;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ChangesetDumpHandler implements HgChangesetHandler {
+	// params
+	private boolean complete = false; // roughly --debug
+	private boolean reverseOrder = false;
+	private boolean verbose = false; // roughly -v
+	// own
+	private LinkedList<String> l = new LinkedList<String>();
+	private final HgRepository repo;
+	private final int tip;
+
+	public ChangesetDumpHandler(HgRepository hgRepo) {
+		repo = hgRepo;
+		tip = hgRepo.getChangelog().getLastRevision();
+	}
+
+	public ChangesetDumpHandler complete(boolean b) {
+		complete = b;
+		return this;
+	}
+
+	public ChangesetDumpHandler reversed(boolean b) {
+		reverseOrder = b;
+		return this;
+	}
+
+	public ChangesetDumpHandler verbose(boolean b) {
+		verbose = b;
+		return this;
+	}
+
+	public void next(HgChangeset changeset) {
+		final String s = print(changeset);
+		if (reverseOrder) {
+			// XXX in fact, need to insert s into l according to changeset.getRevision()
+			// because when file history is being followed, revisions of the original file (with smaller revNumber)
+			// are reported *after* revisions of present file and with addFirst appear above them
+			l.addFirst(s);
+		} else {
+			System.out.print(s);
+		}
+	}
+
+	public void done() {
+		if (!reverseOrder) {
+			return;
+		}
+		for (String s : l) {
+			System.out.print(s);
+		}
+		l.clear();
+	}
+
+	private String print(HgChangeset cset) {
+		StringBuilder sb = new StringBuilder();
+		Formatter f = new Formatter(sb);
+		final Nodeid csetNodeid = cset.getNodeid();
+		f.format("changeset:   %d:%s\n", cset.getRevision(), complete ? csetNodeid : csetNodeid.shortNotation());
+		if (cset.getRevision() == tip || repo.getTags().isTagged(csetNodeid)) {
+
+			sb.append("tag:         ");
+			for (String t : repo.getTags().tags(csetNodeid)) {
+				sb.append(t);
+				sb.append(' ');
+			}
+			if (cset.getRevision() == tip) {
+				sb.append("tip");
+			}
+			sb.append('\n');
+		}
+		if (complete) {
+			Nodeid p1 = cset.getFirstParentRevision();
+			Nodeid p2 = cset.getSecondParentRevision();
+			int p1x = p1 == Nodeid.NULL ? -1 : repo.getChangelog().getLocalRevision(p1);
+			int p2x = p2 == Nodeid.NULL ? -1 : repo.getChangelog().getLocalRevision(p2);
+			int mx = repo.getManifest().getLocalRevision(cset.getManifestRevision());
+			f.format("parent:      %d:%s\nparent:      %d:%s\nmanifest:    %d:%s\n", p1x, p1, p2x, p2, mx, cset.getManifestRevision());
+		}
+		f.format("user:        %s\ndate:        %s\n", cset.getUser(), cset.getDate().toString());
+		if (!complete && verbose) {
+			final List<Path> files = cset.getAffectedFiles();
+			sb.append("files:      ");
+			for (Path s : files) {
+				sb.append(' ');
+				sb.append(s);
+			}
+			sb.append('\n');
+		}
+		if (complete) {
+			if (!cset.getModifiedFiles().isEmpty()) {
+				sb.append("files:      ");
+				for (FileRevision s : cset.getModifiedFiles()) {
+					sb.append(' ');
+					sb.append(s.getPath());
+				}
+				sb.append('\n');
+			}
+			if (!cset.getAddedFiles().isEmpty()) {
+				sb.append("files+:     ");
+				for (FileRevision s : cset.getAddedFiles()) {
+					sb.append(' ');
+					sb.append(s.getPath());
+				}
+				sb.append('\n');
+			}
+			if (!cset.getRemovedFiles().isEmpty()) {
+				sb.append("files-:     ");
+				for (Path s : cset.getRemovedFiles()) {
+					sb.append(' ');
+					sb.append(s);
+				}
+				sb.append('\n');
+			}
+			// if (cset.extras() != null) {
+			// sb.append("extra:      ");
+			// for (Map.Entry<String, String> e : cset.extras().entrySet()) {
+			// sb.append(' ');
+			// sb.append(e.getKey());
+			// sb.append('=');
+			// sb.append(e.getValue());
+			// }
+			// sb.append('\n');
+			// }
+		}
+		if (complete || verbose) {
+			f.format("description:\n%s\n\n", cset.getComment());
+		} else {
+			f.format("summary:     %s\n\n", cset.getComment());
+		}
+		return sb.toString();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Clone.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,57 @@
+/*
+ * 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.console;
+
+import java.io.File;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgCloneCommand;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+
+/**
+ * Initial clone of a repository. Creates a brand new repository and populates it from specified source. 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Clone {
+
+	// ran with args: svnkit c:\temp\hg\test-clone
+	public static void main(String[] args) throws Exception {
+		Options cmdLineOpts = Options.parse(args);
+		List<String> noOptsArgs = cmdLineOpts.getList("");
+		if (noOptsArgs.isEmpty()) {
+			System.err.println("Need at least one argument pointing to remote server to pull changes from");
+			return;
+		}
+		HgCloneCommand cmd = new HgCloneCommand();
+		String remoteRepo = noOptsArgs.get(0);
+		HgRemoteRepository hgRemote = new HgLookup().detectRemote(remoteRepo, null);
+		if (hgRemote.isInvalid()) {
+			System.err.printf("Remote repository %s is not valid", hgRemote.getLocation());
+			return;
+		}
+		cmd.source(hgRemote);
+		if (noOptsArgs.size() > 1) {
+			cmd.destination(new File(noOptsArgs.get(1)));
+		} else {
+			cmd.destination(new File(System.getProperty("user.dir")));
+		}
+		cmd.execute();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Incoming.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,228 @@
+/*
+ * 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.console;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.tmatesoft.hg.core.HgIncomingCommand;
+import org.tmatesoft.hg.core.HgRepoFacade;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+
+
+/**
+ * <em>hg incoming</em> counterpart
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Incoming {
+
+	public static void main(String[] args) throws Exception {
+		if (Boolean.FALSE.booleanValue()) {
+			new SequenceConstructor().test();
+			return;
+		}
+		Options cmdLineOpts = Options.parse(args);
+		HgRepoFacade hgRepo = new HgRepoFacade();
+		if (!hgRepo.init(cmdLineOpts.findRepository())) {
+			System.err.printf("Can't find repository in: %s\n", hgRepo.getRepository().getLocation());
+			return;
+		}
+		HgRemoteRepository hgRemote = new HgLookup().detectRemote(cmdLineOpts.getSingle(""), hgRepo.getRepository());
+		if (hgRemote.isInvalid()) {
+			System.err.printf("Remote repository %s is not valid", hgRemote.getLocation());
+			return;
+		}
+		HgIncomingCommand cmd = hgRepo.createIncomingCommand();
+		cmd.against(hgRemote);
+		//
+		List<Nodeid> missing = cmd.executeLite(null);
+		Collections.reverse(missing); // useful to test output, from newer to older
+		Outgoing.dump("Nodes to fetch:", missing);
+		System.out.printf("Total: %d\n\n", missing.size());
+		//
+		// Complete
+		final ChangesetDumpHandler h = new ChangesetDumpHandler(hgRepo.getRepository());
+		h.complete(false); // this option looks up index of parent revision, done via repo.changelog (which doesn't have any of these new revisions)
+		// this can be fixed by tracking all nodeid->revision idx inside ChangesetDumpHandler, and refer to repo.changelog only when that mapping didn't work
+		h.verbose(cmdLineOpts.getBoolean("-v", "--verbose"));
+		cmd.executeFull(h);
+	}
+	
+
+	/*
+	 * This is for investigation purposes only
+	 */
+	private static class SequenceConstructor {
+
+		private int[] between(int root, int head) {
+			if (head <= (root+1)) {
+				return new int[0];
+			}
+			System.out.printf("[%d, %d]\t\t", root, head);
+			int size = 1 + (int) Math.floor(Math.log(head-root - 1) / Math.log(2));
+			int[] rv = new int[size];
+			for (int v = 1, i = 0; i < rv.length; i++) {
+				rv[i] = root + v;
+				v = v << 1;
+			}
+			System.out.println(Arrays.toString(rv));
+			return rv;
+		}
+
+		public void test() {
+			int root = 0, head = 126;
+			int[] data = between(root, head); // max number of elements to recover is 2**data.length-1, when head is exactly
+			// 2**data.length element of the branch.  
+			// In such case, total number of elements in the branch (including head and root, would be 2**data.length+1
+			int[] finalSequence = new int[1 + (1 << data.length >>> 5)]; // div 32 - total bits to integers, +1 for possible modulus
+			int exactNumberOfElements = -1; // exact number of meaningful bits in finalSequence
+			LinkedHashMap<Integer, int[]> datas = new LinkedHashMap<Integer, int[]>();
+			datas.put(root, data);
+			int totalQueries = 1;
+			HashSet<Integer> queried = new HashSet<Integer>();
+			int[] checkSequence = null;
+			while(!datas.isEmpty()) {
+				LinkedList<int[]> toQuery = new LinkedList<int[]>();
+				do {
+					Iterator<Entry<Integer, int[]>> it = datas.entrySet().iterator();
+					Entry<Integer, int[]> next = it.next();
+					int r = next.getKey();
+					data = next.getValue();
+					it.remove();
+					populate(r, head, data, finalSequence);
+					if (checkSequence != null) {
+						boolean match = true;
+//						System.out.println("Try to match:");
+						for (int i = 0; i < checkSequence.length; i++) {
+//							System.out.println(i);
+//							System.out.println("control:" + toBinaryString(checkSequence[i], ' '));
+//							System.out.println("present:" + toBinaryString(finalSequence[i], ' '));
+							if (checkSequence[i] != finalSequence[i]) {
+								match = false;
+							} else {
+								match &= true;
+							}
+						}
+						System.out.println(match ? "Match, on query:" + totalQueries : "Didn't match");
+					}
+					if (data.length > 1) { 
+						/*queries for elements next to head is senseless, hence data.length check above and head-x below*/
+						for (int x : data) {
+							if (!queried.contains(x) && head - x > 1) { 
+								toQuery.add(new int[] {x, head});
+							}
+						}
+					}
+				} while (!datas.isEmpty()) ;
+				if (!toQuery.isEmpty()) {
+					System.out.println();
+					totalQueries++;
+				}
+				Collections.sort(toQuery, new Comparator<int[]>() {
+
+					public int compare(int[] o1, int[] o2) {
+						return o1[0] < o2[0] ? -1 : (o1[0] == o2[0] ? 0 : 1);
+					}
+				});
+				for (int[] x : toQuery) {
+					if (!queried.contains(x[0])) {
+						queried.add(x[0]);
+						data = between(x[0], x[1]);
+						if (exactNumberOfElements == -1 && data.length == 1) {
+							exactNumberOfElements = x[0] + 1;
+							System.out.printf("On query %d found out exact number of missing elements: %d\n", totalQueries, exactNumberOfElements);
+							// get a bit sequence of exactNumberOfElements, 0111..110
+							// to 'and' it with finalSequence later
+							int totalInts = (exactNumberOfElements + 2 /*heading and tailing zero bits*/) >>> 5;
+							int trailingBits = (exactNumberOfElements + 2) & 0x1f;
+							if (trailingBits != 0) {
+								totalInts++;
+							}
+							checkSequence = new int[totalInts];
+							Arrays.fill(checkSequence, 0xffffffff);
+							checkSequence[0] &= 0x7FFFFFFF;
+							if (trailingBits == 0) {
+								checkSequence[totalInts-1] &= 0xFFFFFFFE;
+							} else if (trailingBits == 1) {
+								checkSequence[totalInts-1] = 0;
+							} else {
+								// trailingBits include heading and trailing zero bits
+								int mask = 0x80000000 >> trailingBits-2; // with sign!
+								checkSequence[totalInts - 1] &= mask;
+							}
+							for (int e : checkSequence) {
+								System.out.print(toBinaryString(e, ' '));
+							}
+							System.out.println();
+						}
+						datas.put(x[0], data);
+					}
+				}
+			}
+
+			System.out.println("Total queries:" + totalQueries);
+			for (int x : finalSequence) {
+				System.out.print(toBinaryString(x, ' '));
+			}
+		}
+		
+		private void populate(int root, int head, int[] data, int[] finalSequence) {
+			for (int i = 1, x = 0; root+i < head; i = i << 1, x++) {
+				int value = data[x];
+				int value_check = root+i;
+				if (value != value_check) {
+					throw new IllegalStateException();
+				}
+				int wordIx = (root + i) >>> 5;
+				int bitIx = (root + i) & 0x1f;
+				finalSequence[wordIx] |= 1 << (31-bitIx);
+			}
+		}
+		
+		private static String toBinaryString(int x, char byteSeparator) {
+			StringBuilder sb = new StringBuilder(4*8+4);
+			sb.append(toBinaryString((byte) (x >>> 24)));
+			sb.append(byteSeparator);
+			sb.append(toBinaryString((byte) ((x & 0x00ff0000) >>> 16)));
+			sb.append(byteSeparator);
+			sb.append(toBinaryString((byte) ((x & 0x00ff00) >>> 8)));
+			sb.append(byteSeparator);
+			sb.append(toBinaryString((byte) (x & 0x00ff)));
+			sb.append(byteSeparator);
+			return sb.toString();
+		}
+
+		private static String toBinaryString(byte b) {
+			final String nibbles = "0000000100100011010001010110011110001001101010111100110111101111";
+			assert nibbles.length() == 16*4;
+			int x1 = (b >>> 4) & 0x0f, x2 = b & 0x0f;
+			x1 *= 4; x2 *= 4; // 4 characters per nibble
+			return nibbles.substring(x1, x1+4).concat(nibbles.substring(x2, x2+4));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Log.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2010-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.console;
+
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgLogCommand;
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+
+
+/**
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Log {
+
+	// -agentlib:hprof=heap=sites,depth=10,etc might be handy to debug speed/memory issues
+	
+	public static void main(String[] args) throws Exception {
+		Options cmdLineOpts = Options.parse(args);
+		HgRepository hgRepo = cmdLineOpts.findRepository();
+		if (hgRepo.isInvalid()) {
+			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
+			return;
+		}
+		final Dump dump = new Dump(hgRepo);
+		dump.complete(cmdLineOpts.getBoolean("--debug"));
+		dump.verbose(cmdLineOpts.getBoolean("-v", "--verbose"));
+		final boolean reverseOrder = true;
+		dump.reversed(reverseOrder);
+		HgLogCommand cmd = new HgLogCommand(hgRepo);
+		for (String u : cmdLineOpts.getList("-u", "--user")) {
+			cmd.user(u);
+		}
+		for (String b : cmdLineOpts.getList("-b", "--branches")) {
+			cmd.branch(b);
+		}
+		int limit = cmdLineOpts.getSingleInt(-1, "-l", "--limit");
+		if (limit != -1) {
+			cmd.limit(limit);
+		}
+		List<String> files = cmdLineOpts.getList("");
+		final long start = System.currentTimeMillis();
+		if (files.isEmpty()) {
+			if (limit == -1) {
+				// no revisions and no limit
+				cmd.execute(dump);
+			} else {
+				// in fact, external (to dump inspector) --limit processing yelds incorrect results when other args
+				// e.g. -u or -b are used (i.e. with -u shall give <limit> csets with user, not check last <limit> csets for user 
+				int[] r = new int[] { 0, hgRepo.getChangelog().getRevisionCount() };
+				if (fixRange(r, reverseOrder, limit) == 0) {
+					System.out.println("No changes");
+					return;
+				}
+				cmd.range(r[0], r[1]).execute(dump);
+			}
+			dump.done();
+		} else {
+			for (String fname : files) {
+				HgDataFile f1 = hgRepo.getFileNode(fname);
+				System.out.println("History of the file: " + f1.getPath());
+				if (limit == -1) {
+					cmd.file(f1.getPath(), true).execute(dump);
+				} else {
+					int[] r = new int[] { 0, f1.getRevisionCount() };
+					if (fixRange(r, reverseOrder, limit) == 0) {
+						System.out.println("No changes");
+						continue;
+					}
+					cmd.range(r[0], r[1]).file(f1.getPath(), true).execute(dump);
+				}
+				dump.done();
+			}
+		}
+//		cmd = null;
+		System.out.println("Total time:" + (System.currentTimeMillis() - start));
+//		Main.force_gc();
+	}
+	
+	private static int fixRange(int[] start_end, boolean reverse, int limit) {
+		assert start_end.length == 2;
+		if (limit < start_end[1]) {
+			if (reverse) {
+				// adjust left boundary of the range
+				start_end[0] = start_end[1] - limit;
+			} else {
+				start_end[1] = limit; // adjust right boundary
+			}
+		}
+		int rv = start_end[1] - start_end[0];
+		start_end[1]--; // range needs index, not length
+		return rv;
+	}
+
+	private static final class Dump extends ChangesetDumpHandler implements HgLogCommand.FileHistoryHandler {
+
+		public Dump(HgRepository hgRepo) {
+			super(hgRepo);
+		}
+		
+		public void copy(FileRevision from, FileRevision to) {
+			System.out.printf("Got notified that %s(%s) was originally known as %s(%s)\n", to.getPath(), to.getRevision(), from.getPath(), from.getRevision());
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Main.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,290 @@
+/*
+ * 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.console;
+
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+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.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgManifest;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.repo.HgStatusInspector;
+import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Various debug dumps. 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Main {
+	
+	private Options cmdLineOpts;
+	private HgRepository hgRepo;
+
+	public Main(String[] args) throws Exception {
+		cmdLineOpts = Options.parse(args);
+		hgRepo = cmdLineOpts.findRepository();
+		if (hgRepo.isInvalid()) {
+			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
+			return;
+		}
+		System.out.println("REPO:" + hgRepo.getLocation());
+	}
+
+	public static void main(String[] args) throws Exception {
+		Main m = new Main(args);
+		m.inflaterLengthException();
+//		m.dumpIgnored();
+//		m.dumpDirstate();
+//		m.testStatusInternals();
+//		m.catCompleteHistory();
+//		m.dumpCompleteManifestLow();
+//		m.dumpCompleteManifestHigh();
+//		m.bunchOfTests();
+	}
+	
+	private void inflaterLengthException() throws Exception {
+		HgDataFile f1 = hgRepo.getFileNode("src/com/tmate/hgkit/console/Bundle.java");
+		HgDataFile f2 = hgRepo.getFileNode("test-repos.jar");
+		System.out.println(f1.isCopy());
+		System.out.println(f2.isCopy());
+		ByteArrayChannel bac = new ByteArrayChannel();
+		f1.content(1, bac); // 0: 1151, 1: 1139
+		System.out.println(bac.toArray().length);
+		f2.content(0, bac = new ByteArrayChannel()); // 0: 14269
+		System.out.println(bac.toArray().length);
+	}
+	
+	private void dumpIgnored() {
+		HgInternals debug = new HgInternals(hgRepo);
+		String[] toCheck = new String[] {"design.txt", "src/com/tmate/hgkit/ll/Changelog.java", "src/Extras.java", "bin/com/tmate/hgkit/ll/Changelog.class"};
+		boolean[] checkResult = debug.checkIgnored(toCheck);
+		for (int i = 0; i < toCheck.length; i++) {
+			System.out.println("Ignored " + toCheck[i] + ": " + checkResult[i]);
+		}
+	}
+	
+	private void dumpDirstate() {
+		new HgInternals(hgRepo).dumpDirstate();
+	}
+
+	
+	private void catCompleteHistory() throws Exception {
+		DigestHelper dh = new DigestHelper();
+		for (String fname : cmdLineOpts.getList("")) {
+			System.out.println(fname);
+			HgDataFile fn = hgRepo.getFileNode(fname);
+			if (fn.exists()) {
+				int total = fn.getRevisionCount();
+				System.out.printf("Total revisions: %d\n", total);
+				for (int i = 0; i < total; i++) {
+					ByteArrayChannel sink = new ByteArrayChannel();
+					fn.content(i, sink);
+					System.out.println("==========>");
+					byte[] content = sink.toArray();
+					System.out.println(new String(content));
+					int[] parentRevisions = new int[2];
+					byte[] parent1 = new byte[20];
+					byte[] parent2 = new byte[20];
+					fn.parents(i, parentRevisions, parent1, parent2);
+					System.out.println(dh.sha1(parent1, parent2, content).asHexString());
+				}
+			} else {
+				System.out.println(">>>Not found!");
+			}
+		}
+	}
+
+	private void dumpCompleteManifestLow() {
+		hgRepo.getManifest().walk(0, TIP, new ManifestDump());
+	}
+
+	public static final class ManifestDump implements HgManifest.Inspector {
+		public boolean begin(int revision, Nodeid nid) {
+			System.out.printf("%d : %s\n", revision, nid);
+			return true;
+		}
+
+		public boolean next(Nodeid nid, String fname, String flags) {
+			System.out.println(nid + "\t" + fname + "\t\t" + flags);
+			return true;
+		}
+
+		public boolean end(int revision) {
+			System.out.println();
+			return true;
+		}
+	}
+
+	private void dumpCompleteManifestHigh() {
+		new HgManifestCommand(hgRepo).dirs(true).execute(new HgManifestCommand.Handler() {
+			
+			public void begin(Nodeid manifestRevision) {
+				System.out.println(">> " + manifestRevision);
+			}
+			public void dir(Path p) {
+				System.out.println(p);
+			}
+			public void file(FileRevision fileRevision) {
+				System.out.print(fileRevision.getRevision());;
+				System.out.print("   ");
+				System.out.println(fileRevision.getPath());
+			}
+			
+			public void end(Nodeid manifestRevision) {
+				System.out.println();
+			}
+		}); 
+	}
+
+	private void bunchOfTests() throws Exception {
+		HgInternals debug = new HgInternals(hgRepo);
+		debug.dumpDirstate();
+		final StatusDump dump = new StatusDump();
+		dump.showIgnored = false;
+		dump.showClean = false;
+		HgStatusCollector sc = new HgStatusCollector(hgRepo);
+		final int r1 = 0, r2 = 3;
+		System.out.printf("Status for changes between revision %d and %d:\n", r1, r2);
+		sc.walk(r1, r2, dump);
+		// 
+		System.out.println("\n\nSame, but sorted in the way hg status does:");
+		HgStatusCollector.Record r = sc.status(r1, r2);
+		sortAndPrint('M', r.getModified(), null);
+		sortAndPrint('A', r.getAdded(), null);
+		sortAndPrint('R', r.getRemoved(), null);
+		//
+		System.out.println("\n\nTry hg status --change <rev>:");
+		sc.change(0, dump);
+		System.out.println("\nStatus against working dir:");
+		HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(hgRepo);
+		wcc.walk(TIP, dump);
+		System.out.println();
+		System.out.printf("Manifest of the revision %d:\n", r2);
+		hgRepo.getManifest().walk(r2, r2, new ManifestDump());
+		System.out.println();
+		System.out.printf("\nStatus of working dir against %d:\n", r2);
+		r = wcc.status(r2);
+		sortAndPrint('M', r.getModified(), null);
+		sortAndPrint('A', r.getAdded(), r.getCopied());
+		sortAndPrint('R', r.getRemoved(), null);
+		sortAndPrint('?', r.getUnknown(), null);
+		sortAndPrint('I', r.getIgnored(), null);
+		sortAndPrint('C', r.getClean(), null);
+		sortAndPrint('!', r.getMissing(), null);
+	}
+	
+	private void sortAndPrint(char prefix, List<Path> ul, Map<Path, Path> copies) {
+		ArrayList<Path> sortList = new ArrayList<Path>(ul);
+		Collections.sort(sortList);
+		for (Path s : sortList)  {
+			System.out.print(prefix);
+			System.out.print(' ');
+			System.out.println(s);
+			if (copies != null && copies.containsKey(s)) {
+				System.out.println("  " + copies.get(s));
+			}
+		}
+	}
+
+
+	private void testStatusInternals() {
+		HgDataFile n = hgRepo.getFileNode(Path.create("design.txt"));
+		for (String s : new String[] {"011dfd44417c72bd9e54cf89b82828f661b700ed", "e5529faa06d53e06a816e56d218115b42782f1ba", "c18e7111f1fc89a80a00f6a39d51288289a382fc"}) {
+			// expected: 359, 2123, 3079
+			byte[] b = s.getBytes();
+			final Nodeid nid = Nodeid.fromAscii(b, 0, b.length);
+			System.out.println(s + " : " + n.length(nid));
+		}
+	}
+
+	static void force_gc() {
+		Runtime.getRuntime().runFinalization();
+		Runtime.getRuntime().gc();
+		Thread.yield();
+		Runtime.getRuntime().runFinalization();
+		Runtime.getRuntime().gc();
+		Thread.yield();
+	}
+
+	private static class StatusDump implements HgStatusInspector {
+		public boolean hideStatusPrefix = false; // hg status -n option
+		public boolean showCopied = true; // -C
+		public boolean showIgnored = true; // -i
+		public boolean showClean = true; // -c
+
+		public void modified(Path fname) {
+			print('M', fname);
+		}
+
+		public void added(Path fname) {
+			print('A', fname);
+		}
+
+		public void copied(Path fnameOrigin, Path fnameAdded) {
+			added(fnameAdded);
+			if (showCopied) {
+				print(' ', fnameOrigin);
+			}
+		}
+
+		public void removed(Path fname) {
+			print('R', fname);
+		}
+
+		public void clean(Path fname) {
+			if (showClean) {
+				print('C', fname);
+			}
+		}
+
+		public void missing(Path fname) {
+			print('!', fname);
+		}
+
+		public void unknown(Path fname) {
+			print('?', fname);
+		}
+
+		public void ignored(Path fname) {
+			if (showIgnored) {
+				print('I', fname);
+			}
+		}
+		
+		private void print(char status, Path fname) {
+			if (!hideStatusPrefix) {
+				System.out.print(status);
+				System.out.print(' ');
+			}
+			System.out.println(fname);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Manifest.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010-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.console;
+
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.core.HgManifestCommand;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Manifest {
+
+	public static void main(String[] args) throws Exception {
+		Options cmdLineOpts = Options.parse(args);
+		HgRepository hgRepo = cmdLineOpts.findRepository();
+		if (hgRepo.isInvalid()) {
+			System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
+			return;
+		}
+		final boolean debug = cmdLineOpts.getBoolean("--debug");
+		final boolean verbose = cmdLineOpts.getBoolean("-v", "--verbose");
+		HgManifestCommand.Handler h = new HgManifestCommand.Handler() {
+			
+			public void begin(Nodeid manifestRevision) {
+			}
+			public void dir(Path p) {
+			}
+			public void file(FileRevision fileRevision) {
+				if (debug) {
+					System.out.print(fileRevision.getRevision());;
+				}
+				if (debug || verbose) {
+					System.out.print(" 644"); // FIXME real flags!
+					System.out.print("   ");
+				}
+				System.out.println(fileRevision.getPath());
+			}
+			
+			public void end(Nodeid manifestRevision) {
+			}
+		};
+		int rev = cmdLineOpts.getSingleInt(TIP, "-r", "--rev");
+		new HgManifestCommand(hgRepo).dirs(false).revision(rev).execute(h); 
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Options.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,111 @@
+/*
+ * 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.console;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ * Parse command-line options. Primitive implementation that recognizes options with 0 or 1 argument.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+class Options {
+
+	public final Map<String,List<String>> opt2values = new HashMap<String, List<String>>();
+
+	public boolean getBoolean(String... aliases) {
+		return getBoolean(false, aliases);
+	}
+
+	public boolean getBoolean(boolean def, String... aliases) {
+		for (String s : aliases) {
+			if (opt2values.containsKey(s)) {
+				return true;
+			}
+		}
+		return def;
+	}
+
+	public String getSingle(String... aliases) {
+		String rv = null;
+		for (String s : aliases) {
+			List<String> values = opt2values.get(s);
+			if (values != null && values.size() > 0) {
+				rv = values.get(values.size() - 1); // take last one, most recent
+			}
+		}
+		return rv;
+	}
+	
+	public int getSingleInt(int def, String... aliases) {
+		String r = getSingle(aliases);
+		if (r == null) {
+			return def;
+		}
+		return Integer.parseInt(r);
+	}
+
+	public List<String> getList(String... aliases) {
+		LinkedList<String> rv = new LinkedList<String>();
+		for (String s : aliases) {
+			List<String> values = opt2values.get(s);
+			if (values != null) {
+				rv.addAll(values);
+			}
+		}
+		return rv;
+	}
+	
+	public HgRepository findRepository() throws Exception {
+		String repoLocation = getSingle("-R", "--repository");
+		if (repoLocation != null) {
+			return new HgLookup().detect(repoLocation);
+		}
+		return new HgLookup().detectFromWorkingDir();
+	}
+
+
+	public static Options parse(String[] commandLineArgs) {
+		Options rv = new Options();
+		List<String> values = new LinkedList<String>();
+		rv.opt2values.put("", values); // values with no options
+		for (String arg : commandLineArgs) {
+			if (arg.charAt(0) == '-') {
+				// option
+				if (arg.length() == 1) {
+					throw new IllegalArgumentException("Bad option: -");
+				}
+				values = rv.opt2values.get(arg);
+				if (values == null) {
+					rv.opt2values.put(arg, values = new LinkedList<String>());
+				}
+				// next value, if any, gets into the values list for arg option.
+			} else {
+				values.add(arg);
+				values = rv.opt2values.get("");
+			}
+		}
+		return rv;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Outgoing.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,87 @@
+/*
+ * 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.console;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgOutgoingCommand;
+import org.tmatesoft.hg.core.HgRepoFacade;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+
+
+/**
+ * <em>hg outgoing</em>
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Outgoing {
+
+	public static void main(String[] args) throws Exception {
+		Options cmdLineOpts = Options.parse(args);
+		HgRepoFacade hgRepo = new HgRepoFacade();
+		if (!hgRepo.init(cmdLineOpts.findRepository())) {
+			System.err.printf("Can't find repository in: %s\n", hgRepo.getRepository().getLocation());
+			return;
+		}
+		// XXX perhaps, HgRepoFacade shall get detectRemote() analog (to get remote server with respect of facade's repo)
+		HgRemoteRepository hgRemote = new HgLookup().detectRemote(cmdLineOpts.getSingle(""), hgRepo.getRepository());
+		if (hgRemote.isInvalid()) {
+			System.err.printf("Remote repository %s is not valid", hgRemote.getLocation());
+			return;
+		}
+		//
+		HgOutgoingCommand cmd = hgRepo.createOutgoingCommand();
+		cmd.against(hgRemote);
+		
+		// find all local children of commonKnown
+		List<Nodeid> result = cmd.executeLite(null);
+		dump("Lite", result);
+		//
+		//
+		System.out.println("Full");
+		// show all, starting from next to common 
+		final ChangesetDumpHandler h = new ChangesetDumpHandler(hgRepo.getRepository());
+		h.complete(cmdLineOpts.getBoolean("--debug")).verbose(cmdLineOpts.getBoolean("-v", "--verbose"));
+		cmd.executeFull(h);
+		h.done();
+	}
+
+//	public static class ChangesetFormatter {
+//		private final StringBuilder sb = new StringBuilder(1024);
+//
+//		public CharSequence simple(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+//			sb.setLength(0);
+//			sb.append(String.format("changeset:  %d:%s\n", revisionNumber, nodeid.toString()));
+//			sb.append(String.format("user:       %s\n", cset.user()));
+//			sb.append(String.format("date:       %s\n", cset.dateString()));
+//			sb.append(String.format("comment:    %s\n\n", cset.comment()));
+//			return sb;
+//		}
+//	}
+	
+
+	static void dump(String s, Collection<Nodeid> c) {
+		System.out.println(s);
+		for (Nodeid n : c) {
+			System.out.println(n);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Remote.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,183 @@
+/*
+ * 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.console;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+import java.util.prefs.Preferences;
+import java.util.zip.InflaterInputStream;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.tmatesoft.hg.internal.ConfigFile;
+import org.tmatesoft.hg.internal.Internals;
+
+/**
+ * WORK IN PROGRESS, DO NOT USE
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Remote {
+
+	/*
+	 * @see http://mercurial.selenic.com/wiki/WireProtocol
+	 cmd=branches gives 4 nodeids (head, root, first parent, second parent) per line (few lines possible, per branch, perhaps?)
+	 cmd=capabilities gives lookup ...subset and 3 compress methods
+	 // lookup changegroupsubset unbundle=HG10GZ,HG10BZ,HG10UN
+	 cmd=heads gives space-separated list of nodeids (or just one)
+	 nodeids are in hex (printable) format, need to convert fromAscii()
+	 cmd=branchmap
+	 cmd=between needs argument pairs, with first element in the pair to be head(!), second to be root of the branch (
+	 	i.e. (newer-older), not (older-newer) as one might expect. Returned list of nodes comes in reversed order (from newer
+	 	to older) as well
+
+	cmd=branches&nodes=d6d2a630f4a6d670c90a5ca909150f2b426ec88f+
+	head, root, first parent, second parent
+	received: d6d2a630f4a6d670c90a5ca909150f2b426ec88f dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+	
+	Sequence, for actual state with merged/closed branch, where 157:d5268ca7715b8d96204fc62abc632e8f55761547 is merge revision of 156 and 53 
+	>branches, 170:71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d
+	 71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d d5268ca7715b8d96204fc62abc632e8f55761547 643ddec3be36246fc052cf22ece503fa60cafe22 a6f39e595b2b54f56304470269a936ead77f5725
+
+	>branches, 156:643ddec3be36246fc052cf22ece503fa60cafe22
+	 643ddec3be36246fc052cf22ece503fa60cafe22 ade65afe0906febafbf8a2e41002052e0e446471 08754fce5778a3409476ecdb3cec6b5172c34367 40d04c4f771ebbd599eb229145252732a596740a
+	>branches, 53:a6f39e595b2b54f56304470269a936ead77f5725
+	 a6f39e595b2b54f56304470269a936ead77f5725 a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5
+
+	>branches, 84:08754fce5778a3409476ecdb3cec6b5172c34367  (p1:82) 
+	 08754fce5778a3409476ecdb3cec6b5172c34367 dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+	>branches, 83:40d04c4f771ebbd599eb229145252732a596740a (p1:80)
+	 40d04c4f771ebbd599eb229145252732a596740a dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+
+	>branches, 51:9429c7bd1920fab164a9d2b621d38d57bcb49ae0 (wrap-data-access branch)
+	 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+	>branches, 52:30bd389788464287cee22ccff54c330a4b715de5 (p1:50)
+	 30bd389788464287cee22ccff54c330a4b715de5 dbd663faec1f0175619cf7668bddc6350548b8d6 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+
+
+	cmd=between&pairs=71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d-d5268ca7715b8d96204fc62abc632e8f55761547+40d04c4f771ebbd599eb229145252732a596740a-dbd663faec1f0175619cf7668bddc6350548b8d6
+	 8c8e3f372fa1fbfcf92b004b6f2ada2dbaf60028 dd525ca65de8e78cb133919de57ea0a6e6454664 1d0654be1466d522994f8bead510e360fbeb8d79 c17a08095e4420202ac1b2d939ef6d5f8bebb569
+	 4222b04f34ee885bc1ad547c7ef330e18a51afc1 5f9635c016819b322ae05a91b3378621b538c933 c677e159391925a50b9a23f557426b2246bc9c5d 0d279bcc44427cb5ae2f3407c02f21187ccc8aea e21df6259f8374ac136767321e837c0c6dd21907 b01500fe2604c2c7eadf44349cce9f438484474b 865bf07f381ff7d1b742453568def92576af80b6
+
+	Between two subsequent revisions (i.e. direct child in remote of a local root) 
+	cmd=between&pairs=71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d-8c8e3f372fa1fbfcf92b004b6f2ada2dbaf60028
+	 empty result
+	 */
+	public static void main(String[] args) throws Exception {
+		ConfigFile cfg = new Internals().newConfigFile();
+		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");
+//		URL url = new URL(svnkitServer + "?cmd=between"); 
+		URL url = new URL(svnkitServer + "?cmd=changegroup&roots=71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d");
+//		URL url = new URL("http://localhost:8000/" + "?cmd=between");
+//		URL url = new URL(svnkitServer + "?cmd=stream_out");
+	
+		SSLContext sslContext = SSLContext.getInstance("SSL");
+		class TrustEveryone implements X509TrustManager {
+			public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+				System.out.println("checkClientTrusted " + authType);
+			}
+			public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+				System.out.println("checkServerTrusted" + authType);
+			}
+			public X509Certificate[] getAcceptedIssuers() {
+				return new X509Certificate[0];
+			}
+		}
+		// Hack to get Base64-encoded credentials
+		Preferences tempNode = Preferences.userRoot().node("xxx");
+		tempNode.putByteArray("xxx", url.getUserInfo().getBytes());
+		String authInfo = tempNode.get("xxx", null);
+		tempNode.removeNode();
+		//
+		sslContext.init(null, new TrustManager[] { new TrustEveryone() }, null);
+		HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
+//		HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+		urlConnection.setRequestProperty("User-Agent", "jhg/0.1.0");
+		urlConnection.setRequestProperty("Accept", "application/mercurial-0.1");
+		urlConnection.setRequestProperty("Authorization", "Basic " + authInfo);
+		urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
+//		byte[] body = "pairs=f5aed108754e817d2ca374d1a4f6daf1218dcc91-9429c7bd1920fab164a9d2b621d38d57bcb49ae0".getBytes();
+//		urlConnection.setRequestMethod("POST");
+//		urlConnection.setRequestProperty("Content-Length", String.valueOf(body.length));
+//		urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+//		urlConnection.setDoOutput(true);
+//		urlConnection.setDoInput(true);
+		urlConnection.connect();
+//		OutputStream os = urlConnection.getOutputStream();
+//		os.write(body);
+//		os.flush();
+//		os.close();
+		System.out.println("Query:" + url.getQuery());
+		System.out.println("Response headers:");
+		final Map<String, List<String>> headerFields = urlConnection.getHeaderFields();
+		for (String s : headerFields.keySet()) {
+			System.out.printf("%s: %s\n", s, urlConnection.getHeaderField(s));
+		}
+		System.out.printf("Content type is %s and its length is %d\n", urlConnection.getContentType(), urlConnection.getContentLength());
+		InputStream is = urlConnection.getInputStream();
+		//
+//		dump(is, -1); // simple dump, any cmd
+		writeBundle(is, false, "HG10GZ"); // cmd=changegroup
+		//writeBundle(is, true, "" or "HG10UN");
+		//
+		urlConnection.disconnect();
+		//
+	}
+
+	private static void dump(InputStream is, int limit) throws IOException {
+		int b;
+		while ((b =is.read()) != -1) {
+			System.out.print((char) b);
+			if (limit != -1) {
+				if (--limit < 0) {
+					break;
+				}
+			}
+		}
+		System.out.println();
+	}
+	
+	private static void writeBundle(InputStream is, boolean decompress, String header) throws IOException {
+		InputStream zipStream = decompress ? new InflaterInputStream(is) : is;
+		File tf = File.createTempFile("hg-bundle-", null);
+		FileOutputStream fos = new FileOutputStream(tf);
+		fos.write(header.getBytes());
+		int r;
+		byte[] buf = new byte[8*1024];
+		while ((r = zipStream.read(buf)) != -1) {
+			fos.write(buf, 0, r);
+		}
+		fos.close();
+		zipStream.close();
+		System.out.println(tf);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j-cli/src/main/java/org/tmatesoft/hg/console/Status.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2010-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.console;
+
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.tmatesoft.hg.core.HgRepoFacade;
+import org.tmatesoft.hg.core.HgStatus;
+import org.tmatesoft.hg.core.HgStatus.Kind;
+import org.tmatesoft.hg.core.HgStatusCommand;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Status {
+
+	public static void main(String[] args) throws Exception {
+		Options cmdLineOpts = Options.parse(args);
+		HgRepoFacade hgRepo = new HgRepoFacade();
+		if (!hgRepo.init(cmdLineOpts.findRepository())) {
+			System.err.printf("Can't find repository in: %s\n", hgRepo.getRepository().getLocation());
+			return;
+		}
+		//
+		HgStatusCommand cmd = hgRepo.createStatusCommand();
+		if (cmdLineOpts.getBoolean("-A", "--all")) {
+			cmd.all();
+		} else {
+			// default: mardu
+			cmd.modified(cmdLineOpts.getBoolean(true, "-m", "--modified"));
+			cmd.added(cmdLineOpts.getBoolean(true, "-a", "--added"));
+			cmd.removed(cmdLineOpts.getBoolean(true, "-r", "--removed"));
+			cmd.deleted(cmdLineOpts.getBoolean(true, "-d", "--deleted"));
+			cmd.unknown(cmdLineOpts.getBoolean(true, "-u", "--unknonwn"));
+			cmd.clean(cmdLineOpts.getBoolean("-c", "--clean"));
+			cmd.ignored(cmdLineOpts.getBoolean("-i", "--ignored"));
+		}
+//		cmd.subrepo(cmdLineOpts.getBoolean("-S", "--subrepos"))
+		final boolean noStatusPrefix = cmdLineOpts.getBoolean("-n", "--no-status");
+		final boolean showCopies = cmdLineOpts.getBoolean("-C", "--copies");
+		class StatusHandler implements HgStatusCommand.Handler {
+			
+			final Map<HgStatus.Kind, List<Path>> data = new TreeMap<HgStatus.Kind, List<Path>>();
+			final Map<Path, Path> copies = showCopies ? new HashMap<Path,Path>() : null;
+			
+			public void handleStatus(HgStatus s) {
+				List<Path> l = data.get(s.getKind());
+				if (l == null) {
+					l = new LinkedList<Path>();
+					data.put(s.getKind(), l);
+				}
+				l.add(s.getPath());
+				if (s.isCopy() && showCopies) {
+					copies.put(s.getPath(), s.getOriginalPath());
+				}
+			}
+			
+			public void dump() {
+				sortAndPrint('M', data.get(Kind.Modified), null);
+				sortAndPrint('A', data.get(Kind.Added), copies);
+				sortAndPrint('R', data.get(Kind.Removed), null);
+				sortAndPrint('?', data.get(Kind.Unknown), null);
+				sortAndPrint('I', data.get(Kind.Ignored), null);
+				sortAndPrint('C', data.get(Kind.Clean), null);
+				sortAndPrint('!', data.get(Kind.Missing), null);
+			}
+
+			private void sortAndPrint(char prefix, List<Path> ul, Map<Path, Path> copies) {
+				if (ul == null) {
+					return;
+				}
+				ArrayList<Path> sortList = new ArrayList<Path>(ul);
+				Collections.sort(sortList);
+				for (Path s : sortList)  {
+					if (!noStatusPrefix) {
+						System.out.print(prefix);
+						System.out.print(' ');
+					}
+					System.out.println(s);
+					if (copies != null && copies.containsKey(s)) {
+						System.out.println("  " + copies.get(s));
+					}
+				}
+			}
+		};
+
+		StatusHandler statusHandler = new StatusHandler(); 
+		int changeRev = cmdLineOpts.getSingleInt(BAD_REVISION, "--change");
+		if (changeRev != BAD_REVISION) {
+			cmd.change(changeRev);
+		} else {
+			List<String> revisions = cmdLineOpts.getList("--rev");
+			int size = revisions.size();
+			if (size > 1) {
+				cmd.base(Integer.parseInt(revisions.get(size - 2))).revision(Integer.parseInt(revisions.get(size - 1)));
+			} else if (size > 0) {
+				cmd.base(Integer.parseInt(revisions.get(0)));
+			}
+		}
+		cmd.execute(statusHandler);
+		statusHandler.dump();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/ChangesetTransformer.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,65 @@
+/*
+ * 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.util.Set;
+
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ * Bridges {@link HgChangelog.RawChangeset} with high-level {@link HgChangeset} API
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+/*package-local*/ class ChangesetTransformer implements HgChangelog.Inspector {
+	private final HgChangesetHandler handler;
+	private final HgChangeset changeset;
+	private Set<String> branches;
+
+	// repo and delegate can't be null, parent walker can
+	public ChangesetTransformer(HgRepository hgRepo, HgChangesetHandler delegate, HgChangelog.ParentWalker pw) {
+		if (hgRepo == null || delegate == null) {
+			throw new IllegalArgumentException();
+		}
+		HgStatusCollector statusCollector = new HgStatusCollector(hgRepo);
+		// files listed in a changeset don't need their names to be rewritten (they are normalized already)
+		PathPool pp = new PathPool(new PathRewrite.Empty());
+		statusCollector.setPathPool(pp);
+		changeset = new HgChangeset(statusCollector, pp);
+		changeset.setParentHelper(pw);
+		handler = delegate;
+	}
+	
+	public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+		if (branches != null && !branches.contains(cset.branch())) {
+			return;
+		}
+
+		changeset.init(revisionNumber, nodeid, cset);
+		handler.next(changeset);
+	}
+	
+	public void limitBranches(Set<String> branches) {
+		this.branches = branches;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgBadArgumentException.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgBadArgumentException extends HgException {
+
+	public HgBadArgumentException(String message, Throwable cause) {
+		super(message, cause);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgBadStateException.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * hg4j's own internal error or unexpected state.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgBadStateException extends RuntimeException {
+
+	// FIXME quick-n-dirty fix, don't allow exceptions without a cause
+	public HgBadStateException() {
+		super("Internal error");
+	}
+
+	public HgBadStateException(String message) {
+		super(message);
+	}
+
+	public HgBadStateException(Throwable cause) {
+		super(cause);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgCatCommand.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,123 @@
+/*
+ * 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 static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision;
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.ByteChannel;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Command to obtain content of a file, 'hg cat' counterpart. 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgCatCommand {
+
+	private final HgRepository repo;
+	private Path file;
+	private int localRevision = TIP;
+	private Nodeid revision;
+
+	public HgCatCommand(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+
+	/**
+	 * File to read, required parameter 
+	 * @param fname path to a repository file, can't be <code>null</code>
+	 * @return <code>this</code> for convenience
+	 * @throws IllegalArgumentException if supplied fname is null or points to directory
+	 */
+	public HgCatCommand file(Path fname) {
+		if (fname == null || fname.isDirectory()) {
+			throw new IllegalArgumentException(String.valueOf(fname));
+		}
+		file = fname;
+		return this;
+	}
+
+	/**
+	 * Invocation of this method clears revision set with {@link #revision(Nodeid)} or {@link #revision(int)} earlier.
+	 * XXX rev can't be WORKING_COPY (if allowed, need to implement in #execute())
+	 * @param rev local revision number, non-negative, or one of predefined constants. Note, use of {@link HgRepository#BAD_REVISION}, 
+	 * although possible, makes little sense (command would fail if executed).  
+ 	 * @return <code>this</code> for convenience
+	 */
+	public HgCatCommand revision(int rev) {
+		if (wrongLocalRevision(rev)) {
+			throw new IllegalArgumentException(String.valueOf(rev));
+		}
+		localRevision = rev;
+		revision = null;
+		return this;
+	}
+	
+	/**
+	 * Select revision to read. Invocation of this method clears revision set with {@link #revision(int)} or {@link #revision(Nodeid)} earlier.
+	 * 
+	 * @param nodeid - unique revision identifier, Note, use of <code>null</code> or {@link Nodeid#NULL} is senseless
+	 * @return <code>this</code> for convenience
+	 */
+	public HgCatCommand revision(Nodeid nodeid) {
+		if (nodeid != null && nodeid.isNull()) {
+			nodeid = null;
+		}
+		revision = nodeid;
+		localRevision = BAD_REVISION;
+		return this;
+	}
+
+	/**
+	 * Runs the command with current set of parameters and pipes data to provided sink.
+	 * 
+	 * @param sink output channel to write data to.
+	 * @throws HgDataStreamException 
+	 * @throws IllegalArgumentException when command arguments are incomplete or wrong
+	 */
+	public void execute(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
+		if (localRevision == BAD_REVISION && revision == null) {
+			throw new IllegalArgumentException("Either local file revision number or nodeid shall be specified");
+		}
+		if (file == null) {
+			throw new IllegalArgumentException("Name of the file is missing");
+		}
+		if (sink == null) {
+			throw new IllegalArgumentException("Need an output channel");
+		}
+		HgDataFile dataFile = repo.getFileNode(file);
+		if (!dataFile.exists()) {
+			throw new HgDataStreamException(file.toString(), new FileNotFoundException(file.toString()));
+		}
+		int revToExtract;
+		if (revision != null) {
+			revToExtract = dataFile.getLocalRevision(revision);
+		} else {
+			revToExtract = localRevision;
+		}
+		dataFile.contentWithFilters(revToExtract, sink);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgChangeset.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,215 @@
+/*
+ * 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 static org.tmatesoft.hg.core.Nodeid.NULL;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ * Record in the Mercurial changelog, describing single commit.
+ * 
+ * Not thread-safe, don't try to read from different threads
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgChangeset implements Cloneable {
+	private final HgStatusCollector statusHelper;
+	private final Path.Source pathHelper;
+
+	private HgChangelog.ParentWalker parentHelper;
+
+	//
+	private RawChangeset changeset;
+	private Nodeid nodeid;
+
+	//
+	private List<FileRevision> modifiedFiles, addedFiles;
+	private List<Path> deletedFiles;
+	private int revNumber;
+	private byte[] parent1, parent2;
+
+	// XXX consider CommandContext with StatusCollector, PathPool etc. Commands optionally get CC through a cons or create new
+	// and pass it around
+	/*package-local*/HgChangeset(HgStatusCollector statusCollector, Path.Source pathFactory) {
+		statusHelper = statusCollector;
+		pathHelper = pathFactory;
+	}
+
+	/*package-local*/ void init(int localRevNumber, Nodeid nid, RawChangeset rawChangeset) {
+		revNumber = localRevNumber;
+		nodeid = nid;
+		changeset = rawChangeset.clone();
+		modifiedFiles = addedFiles = null;
+		deletedFiles = null;
+		parent1 = parent2 = null;
+		// keep references to parentHelper, statusHelper and pathHelper
+	}
+
+	/*package-local*/ void setParentHelper(HgChangelog.ParentWalker pw) {
+		parentHelper = pw;
+		if (parentHelper != null) {
+			if (parentHelper.getRepo() != statusHelper.getRepo()) {
+				throw new IllegalArgumentException();
+			}
+		}
+	}
+
+	public int getRevision() {
+		return revNumber;
+	}
+	public Nodeid getNodeid() {
+		return nodeid;
+	}
+	public String getUser() {
+		return changeset.user();
+	}
+	public String getComment() {
+		return changeset.comment();
+	}
+	public String getBranch() {
+		return changeset.branch();
+	}
+
+	/**
+	 * @return used to be String, now {@link HgDate}, use {@link HgDate#toString()} to get same result as before 
+	 */
+	public HgDate getDate() {
+		return new HgDate(changeset.date().getTime(), changeset.timezone());
+	}
+	public Nodeid getManifestRevision() {
+		return changeset.manifest();
+	}
+
+	public List<Path> getAffectedFiles() {
+		// reports files as recorded in changelog. Note, merge revisions may have no
+		// files listed, and thus this method would return empty list, while
+		// #getModifiedFiles() would return list with merged file(s) (because it uses status to get 'em, not
+		// what #files() gives).
+		ArrayList<Path> rv = new ArrayList<Path>(changeset.files().size());
+		for (String name : changeset.files()) {
+			rv.add(pathHelper.path(name));
+		}
+		return rv;
+	}
+
+	public List<FileRevision> getModifiedFiles() {
+		if (modifiedFiles == null) {
+			initFileChanges();
+		}
+		return modifiedFiles;
+	}
+
+	public List<FileRevision> getAddedFiles() {
+		if (addedFiles == null) {
+			initFileChanges();
+		}
+		return addedFiles;
+	}
+
+	public List<Path> getRemovedFiles() {
+		if (deletedFiles == null) {
+			initFileChanges();
+		}
+		return deletedFiles;
+	}
+
+	public boolean isMerge() {
+		// p1 == -1 and p2 != -1 is legitimate case
+		return !NULL.equals(getFirstParentRevision()) && !NULL.equals(getSecondParentRevision()); 
+	}
+	
+	public Nodeid getFirstParentRevision() {
+		if (parentHelper != null) {
+			return parentHelper.safeFirstParent(nodeid);
+		}
+		// read once for both p1 and p2
+		if (parent1 == null) {
+			parent1 = new byte[20];
+			parent2 = new byte[20];
+			statusHelper.getRepo().getChangelog().parents(revNumber, new int[2], parent1, parent2);
+		}
+		return Nodeid.fromBinary(parent1, 0);
+	}
+	
+	public Nodeid getSecondParentRevision() {
+		if (parentHelper != null) {
+			return parentHelper.safeSecondParent(nodeid);
+		}
+		if (parent2 == null) {
+			parent1 = new byte[20];
+			parent2 = new byte[20];
+			statusHelper.getRepo().getChangelog().parents(revNumber, new int[2], parent1, parent2);
+		}
+		return Nodeid.fromBinary(parent2, 0);
+	}
+
+	@Override
+	public HgChangeset clone() {
+		try {
+			HgChangeset copy = (HgChangeset) super.clone();
+			// copy.changeset references this.changeset, doesn't need own copy
+			return copy;
+		} catch (CloneNotSupportedException ex) {
+			throw new InternalError(ex.toString());
+		}
+	}
+
+	private /*synchronized*/ void initFileChanges() {
+		ArrayList<Path> deleted = new ArrayList<Path>();
+		ArrayList<FileRevision> modified = new ArrayList<FileRevision>();
+		ArrayList<FileRevision> added = new ArrayList<FileRevision>();
+		HgStatusCollector.Record r = new HgStatusCollector.Record();
+		statusHelper.change(revNumber, r);
+		final HgRepository repo = statusHelper.getRepo();
+		for (Path s : r.getModified()) {
+			Nodeid nid = r.nodeidAfterChange(s);
+			if (nid == null) {
+				throw new HgBadStateException();
+			}
+			modified.add(new FileRevision(repo, nid, s));
+		}
+		for (Path s : r.getAdded()) {
+			Nodeid nid = r.nodeidAfterChange(s);
+			if (nid == null) {
+				throw new HgBadStateException();
+			}
+			added.add(new FileRevision(repo, nid, s));
+		}
+		for (Path s : r.getRemoved()) {
+			// with Path from getRemoved, may just copy
+			deleted.add(s);
+		}
+		modified.trimToSize();
+		added.trimToSize();
+		deleted.trimToSize();
+		modifiedFiles = Collections.unmodifiableList(modified);
+		addedFiles = Collections.unmodifiableList(added);
+		deletedFiles = Collections.unmodifiableList(deleted);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgChangesetHandler.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Callback to process {@link HgChangeset changesets}.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface HgChangesetHandler {
+	/**
+	 * @param changeset not necessarily a distinct instance each time, {@link HgChangeset#clone() clone()} if need a copy.
+	 */
+	void next(HgChangeset changeset);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgCloneCommand.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,333 @@
+/*
+ * 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 static org.tmatesoft.hg.core.Nodeid.NULL;
+import static org.tmatesoft.hg.internal.RequiresFile.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.TreeMap;
+import java.util.zip.DeflaterOutputStream;
+
+import org.tmatesoft.hg.internal.ByteArrayDataAccess;
+import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.DigestHelper;
+import org.tmatesoft.hg.internal.Internals;
+import org.tmatesoft.hg.repo.HgBundle;
+import org.tmatesoft.hg.repo.HgBundle.GroupElement;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ * WORK IN PROGRESS, DO NOT USE
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgCloneCommand {
+
+	private File destination;
+	private HgRemoteRepository srcRepo;
+
+	public HgCloneCommand() {
+	}
+	
+	/**
+	 * @param folder location to become root of the repository (i.e. where <em>.hg</em> folder would reside). Either 
+	 * shall not exist or be empty otherwise. 
+	 * @return <code>this</code> for convenience
+	 */
+	public HgCloneCommand destination(File folder) {
+		destination = folder;
+		return this;
+	}
+
+	public HgCloneCommand source(HgRemoteRepository hgRemote) {
+		srcRepo = hgRemote;
+		return this;
+	}
+
+	public HgRepository execute() throws HgException, CancelledException {
+		if (destination == null) {
+			throw new HgBadArgumentException("Destination not set", null);
+		}
+		if (srcRepo == null || srcRepo.isInvalid()) {
+			throw new HgBadArgumentException("Bad source repository", null);
+		}
+		if (destination.exists()) {
+			if (!destination.isDirectory()) {
+				throw new HgBadArgumentException(String.format("%s is not a directory", destination), null);
+			} else if (destination.list().length > 0) {
+				throw new HgBadArgumentException(String.format("% shall be empty", destination), null);
+			}
+		} else {
+			destination.mkdirs();
+		}
+		// if cloning remote repo, which can stream and no revision is specified -
+		// can use 'stream_out' wireproto
+		//
+		// pull all changes from the very beginning
+		// XXX consult getContext() if by any chance has a bundle ready, if not, then read and register 
+		HgBundle completeChanges = srcRepo.getChanges(Collections.singletonList(NULL));
+		WriteDownMate mate = new WriteDownMate(destination);
+		try {
+			// instantiate new repo in the destdir
+			mate.initEmptyRepository();
+			// pull changes
+			completeChanges.inspectAll(mate);
+			mate.complete();
+		} catch (IOException ex) {
+			throw new HgException(ex);
+		} finally {
+			completeChanges.unlink();
+		}
+		return new HgLookup().detect(destination);
+	}
+
+
+	// 1. process changelog, memorize nodeids to index
+	// 2. process manifest, using map from step 3, collect manifest nodeids
+	// 3. process every file, using map from 3, and consult set from step 4 to ensure repo is correct
+	private static class WriteDownMate implements HgBundle.Inspector {
+		private final File hgDir;
+		private final PathRewrite storagePathHelper;
+		private FileOutputStream indexFile;
+		private String filename; // human-readable name of the file being written, for log/exception purposes 
+
+		private final TreeMap<Nodeid, Integer> changelogIndexes = new TreeMap<Nodeid, Integer>();
+		private boolean collectChangelogIndexes = false;
+
+		private int base = -1;
+		private long offset = 0;
+		private DataAccess prevRevContent;
+		private final DigestHelper dh = new DigestHelper();
+		private final ArrayList<Nodeid> revisionSequence = new ArrayList<Nodeid>(); // last visited nodes first
+
+		private final LinkedList<String> fncacheFiles = new LinkedList<String>();
+		private Internals implHelper;
+
+		public WriteDownMate(File destDir) {
+			hgDir = new File(destDir, ".hg");
+			implHelper = new Internals();
+			implHelper.setStorageConfig(1, STORE | FNCACHE | DOTENCODE);
+			storagePathHelper = implHelper.buildDataFilesHelper();
+		}
+
+		public void initEmptyRepository() throws IOException {
+			implHelper.initEmptyRepository(hgDir);
+		}
+
+		public void complete() throws IOException {
+			FileOutputStream fncacheFile = new FileOutputStream(new File(hgDir, "store/fncache"));
+			for (String s : fncacheFiles) {
+				fncacheFile.write(s.getBytes());
+				fncacheFile.write(0x0A); // http://mercurial.selenic.com/wiki/fncacheRepoFormat
+			}
+			fncacheFile.close();
+		}
+
+		public void changelogStart() {
+			try {
+				base = -1;
+				offset = 0;
+				revisionSequence.clear();
+				indexFile = new FileOutputStream(new File(hgDir, filename = "store/00changelog.i"));
+				collectChangelogIndexes = true;
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex);
+			}
+		}
+
+		public void changelogEnd() {
+			try {
+				if (prevRevContent != null) {
+					prevRevContent.done();
+					prevRevContent = null;
+				}
+				collectChangelogIndexes = false;
+				indexFile.close();
+				indexFile = null;
+				filename = null;
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex);
+			}
+		}
+
+		public void manifestStart() {
+			try {
+				base = -1;
+				offset = 0;
+				revisionSequence.clear();
+				indexFile = new FileOutputStream(new File(hgDir, filename = "store/00manifest.i"));
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex);
+			}
+		}
+
+		public void manifestEnd() {
+			try {
+				if (prevRevContent != null) {
+					prevRevContent.done();
+					prevRevContent = null;
+				}
+				indexFile.close();
+				indexFile = null;
+				filename = null;
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex);
+			}
+		}
+		
+		public void fileStart(String name) {
+			try {
+				base = -1;
+				offset = 0;
+				revisionSequence.clear();
+				fncacheFiles.add("data/" + name + ".i"); // FIXME this is pure guess, 
+				// need to investigate more how filenames are kept in fncache
+				File file = new File(hgDir, filename = storagePathHelper.rewrite(name));
+				file.getParentFile().mkdirs();
+				indexFile = new FileOutputStream(file);
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex);
+			}
+		}
+
+		public void fileEnd(String name) {
+			try {
+				if (prevRevContent != null) {
+					prevRevContent.done();
+					prevRevContent = null;
+				}
+				indexFile.close();
+				indexFile = null;
+				filename = null;
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex);
+			}
+		}
+
+		private int knownRevision(Nodeid p) {
+			if (NULL.equals(p)) {
+				return -1;
+			} else {
+				for (int i = revisionSequence.size() - 1; i >= 0; i--) {
+					if (revisionSequence.get(i).equals(p)) {
+						return i;
+					}
+				}
+			}
+			throw new HgBadStateException(String.format("Can't find index of %s for file %s", p.shortNotation(), filename));
+		}
+
+		public boolean element(GroupElement ge) {
+			try {
+				assert indexFile != null;
+				boolean writeComplete = false;
+				Nodeid p1 = ge.firstParent();
+				Nodeid p2 = ge.secondParent();
+				if (NULL.equals(p1) && NULL.equals(p2) /* or forced flag, does REVIDX_PUNCHED_FLAG indicate that? */) {
+					prevRevContent = new ByteArrayDataAccess(new byte[0]);
+					writeComplete = true;
+				}
+				byte[] content = ge.apply(prevRevContent);
+				byte[] calculated = dh.sha1(p1, p2, content).asBinary();
+				final Nodeid node = ge.node();
+				if (!node.equalsTo(calculated)) {
+					throw new HgBadStateException(String.format("Checksum failed: expected %s, calculated %s. File %s", node, calculated, filename));
+				}
+				final int link;
+				if (collectChangelogIndexes) {
+					changelogIndexes.put(node, revisionSequence.size());
+					link = revisionSequence.size();
+				} else {
+					Integer csRev = changelogIndexes.get(ge.cset());
+					if (csRev == null) {
+						throw new HgBadStateException(String.format("Changelog doesn't contain revision %s of %s", ge.cset().shortNotation(), filename));
+					}
+					link = csRev.intValue();
+				}
+				final int p1Rev = knownRevision(p1), p2Rev = knownRevision(p2);
+				DataAccess patchContent = ge.rawData();
+				writeComplete = writeComplete || patchContent.length() >= (/* 3/4 of actual */content.length - (content.length >>> 2));
+				if (writeComplete) {
+					base = revisionSequence.size();
+				}
+				final byte[] sourceData = writeComplete ? content : patchContent.byteArray();
+				final byte[] data;
+				ByteArrayOutputStream bos = new ByteArrayOutputStream(content.length);
+				DeflaterOutputStream dos = new DeflaterOutputStream(bos);
+				dos.write(sourceData);
+				dos.close();
+				final byte[] compressedData = bos.toByteArray();
+				dos = null;
+				bos = null;
+				final Byte dataPrefix;
+				if (compressedData.length >= (sourceData.length - (sourceData.length >>> 2))) {
+					// compression wasn't too effective,
+					data = sourceData;
+					dataPrefix = 'u';
+				} else {
+					data = compressedData;
+					dataPrefix = null;
+				}
+
+				ByteBuffer header = ByteBuffer.allocate(64 /* REVLOGV1_RECORD_SIZE */);
+				if (offset == 0) {
+					final int INLINEDATA = 1 << 16;
+					header.putInt(1 /* RevlogNG */ | INLINEDATA);
+					header.putInt(0);
+				} else {
+					header.putLong(offset << 16);
+				}
+				final int compressedLen = data.length + (dataPrefix == null ? 0 : 1);
+				header.putInt(compressedLen);
+				header.putInt(content.length);
+				header.putInt(base);
+				header.putInt(link);
+				header.putInt(p1Rev);
+				header.putInt(p2Rev);
+				header.put(node.toByteArray());
+				// assume 12 bytes left are zeros
+				indexFile.write(header.array());
+				if (dataPrefix != null) {
+					indexFile.write(dataPrefix.byteValue());
+				}
+				indexFile.write(data);
+				//
+				offset += compressedLen;
+				revisionSequence.add(node);
+				prevRevContent.done();
+				prevRevContent = new ByteArrayDataAccess(content);
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex);
+			}
+			return true;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgDataStreamException.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,33 @@
+/*
+ * 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.repo.HgDataFile;
+
+/**
+ * Any erroneous state with @link {@link HgDataFile} input/output, read/write operations 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgDataStreamException extends HgException {
+
+	public HgDataStreamException(String message, Throwable cause) {
+		super(message, cause);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgDate.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,111 @@
+/*
+ * 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.util.Calendar;
+import java.util.Formatter;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Compound object to keep time  and time zone of a change. Time zone is not too useful unless you'd like to indicate where 
+ * the change was made (original <em>hg</em> shows date of a change in its original time zone) 
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class HgDate implements Comparable<HgDate>, Cloneable {
+	private final long time;
+	private final TimeZone tzone;
+	
+
+	/**
+	 * @param millis UTC, milliseconds
+	 * @param timezone zone offset in seconds, UTC - local == timezone. I.e. positive in the Western Hemisphere.  
+	 */
+	public HgDate(long millis, int timezone) {
+		time = millis;
+		// @see http://pydoc.org/2.5.1/time.html  time.timezone -- difference in seconds between UTC and local standard time
+		// UTC - local = timezone. local = UTC - timezone
+		// In Java, timezone is positive right of Greenwich, UTC+timezone = local
+		String[] available = TimeZone.getAvailableIDs(-timezone * 1000);
+		assert available != null && available.length > 0 : String.valueOf(timezone);
+		// this is sort of hach, I don't know another way how to get 
+		// abbreviated name from zone offset (other than to have own mapping)
+		// And I can't use any id, because e.g. zone with id  "US/Mountain" 
+		// gives incorrect (according to hg cmdline) result, unlike MST or US/Arizona (all ids for zone -0700)
+		// use 1125044450000L to see the difference
+		String shortID = TimeZone.getTimeZone(available[0]).getDisplayName(false, TimeZone.SHORT);
+		// XXX in fact, might need to handle daylight saving time, but not sure how, 
+		// getTimeZone(GMT-timezone*1000).inDaylightTime()?
+		TimeZone tz = TimeZone.getTimeZone(shortID);
+		tzone = tz;
+	}
+	
+	public long getRawTime() {
+		return time;
+	}
+	
+	/**
+	 * @return zone object by reference, do not alter it (make own copy by {@link TimeZone#clone()}, to modify). 
+	 */
+	public TimeZone getTimeZone() {
+		return tzone;
+	}
+	
+	@Override
+	public String toString() {
+		// format the same way hg does
+		return toString(Locale.US);
+	}
+	
+	public String toString(Locale l) {
+		Calendar c = Calendar.getInstance(getTimeZone());
+		c.setTimeInMillis(getRawTime());
+		Formatter f = new Formatter(new StringBuilder(), l);
+		f.format("%ta %<tb %<td %<tH:%<tM:%<tS %<tY %<tz", c);
+		return f.out().toString();
+	}
+
+	public int compareTo(HgDate o) {
+		return (int) (time - o.time);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (false == obj instanceof HgDate) {
+			return false;
+		}
+		HgDate other = (HgDate) obj;
+		return compareTo(other) == 0;
+	}
+	
+	@Override
+	public int hashCode() {
+		// copied from java.util.Datge
+		return (int) time ^ (int) (time >> 32);
+	}
+
+	@Override
+	protected Object clone() {
+		try {
+			return super.clone();
+		} catch (CloneNotSupportedException ex) {
+			throw new InternalError(ex.toString());
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgException.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * Root class for all hg4j exceptions.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class HgException extends Exception {
+
+	public HgException(String reason) {
+		super(reason);
+	}
+
+	public HgException(String reason, Throwable cause) {
+		super(reason, cause);
+	}
+
+	public HgException(Throwable cause) {
+		super(cause);
+	}
+
+//	/* XXX CONSIDER capability to pass extra information about errors */
+//	public static class Status {
+//		public Status(String message, Throwable cause, int errorCode, Object extraData) {
+//		}
+//	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgIncomingCommand.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,199 @@
+/*
+ * 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.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.internal.RepositoryComparator;
+import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain;
+import org.tmatesoft.hg.repo.HgBundle;
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.CancelledException;
+
+/**
+ * Command to find out changes available in a remote repository, missing locally.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgIncomingCommand {
+
+	private final HgRepository localRepo;
+	private HgRemoteRepository remoteRepo;
+	@SuppressWarnings("unused")
+	private boolean includeSubrepo;
+	private RepositoryComparator comparator;
+	private List<BranchChain> missingBranches;
+	private HgChangelog.ParentWalker parentHelper;
+	private Set<String> branches;
+
+	public HgIncomingCommand(HgRepository hgRepo) {
+	 	localRepo = hgRepo;
+	}
+	
+	public HgIncomingCommand against(HgRemoteRepository hgRemote) {
+		remoteRepo = hgRemote;
+		comparator = null;
+		missingBranches = null;
+		return this;
+	}
+
+	/**
+	 * Select specific branch to push.
+	 * Multiple branch specification possible (changeset from any of these would be included in result).
+	 * Note, {@link #executeLite(Object)} does not respect this setting.
+	 * 
+	 * @param branch - branch name, case-sensitive, non-null.
+	 * @return <code>this</code> for convenience
+	 * @throws IllegalArgumentException when branch argument is null
+	 */
+	public HgIncomingCommand branch(String branch) {
+		if (branch == null) {
+			throw new IllegalArgumentException();
+		}
+		if (branches == null) {
+			branches = new TreeSet<String>();
+		}
+		branches.add(branch);
+		return this;
+	}
+	
+	/**
+	 * PLACEHOLDER, NOT IMPLEMENTED YET.
+	 * 
+	 * Whether to include sub-repositories when collecting changes, default is <code>true</code> XXX or false?
+	 * @return <code>this</code> for convenience
+	 */
+	public HgIncomingCommand subrepo(boolean include) {
+		includeSubrepo = include;
+		throw HgRepository.notImplemented();
+	}
+
+	/**
+	 * Lightweight check for incoming changes, gives only list of revisions to pull.
+	 * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account. 
+	 *   
+	 * @param context anything hg4j can use to get progress and/or cancel support
+	 * @return list of nodes present at remote and missing locally
+	 * @throws HgException
+	 * @throws CancelledException
+	 */
+	public List<Nodeid> executeLite(Object context) throws HgException, CancelledException {
+		LinkedHashSet<Nodeid> result = new LinkedHashSet<Nodeid>();
+		RepositoryComparator repoCompare = getComparator(context);
+		for (BranchChain bc : getMissingBranches(context)) {
+			List<Nodeid> missing = repoCompare.visitBranches(bc);
+			HashSet<Nodeid> common = new HashSet<Nodeid>(); // ordering is irrelevant  
+			repoCompare.collectKnownRoots(bc, common);
+			// missing could only start with common elements. Once non-common, rest is just distinct branch revision trails.
+			for (Iterator<Nodeid> it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; 
+			result.addAll(missing);
+		}
+		ArrayList<Nodeid> rv = new ArrayList<Nodeid>(result);
+		return rv;
+	}
+
+	/**
+	 * Full information about incoming changes
+	 * 
+	 * @throws HgException
+	 * @throws CancelledException
+	 */
+	public void executeFull(final HgChangesetHandler handler) throws HgException, CancelledException {
+		if (handler == null) {
+			throw new IllegalArgumentException("Delegate can't be null");
+		}
+		final List<Nodeid> common = getCommon(handler);
+		HgBundle changegroup = remoteRepo.getChanges(common);
+		try {
+			changegroup.changes(localRepo, new HgChangelog.Inspector() {
+				private int localIndex;
+				private final HgChangelog.ParentWalker parentHelper;
+				private final ChangesetTransformer transformer;
+			
+				{
+					transformer = new ChangesetTransformer(localRepo, handler, getParentHelper());
+					transformer.limitBranches(branches);
+					parentHelper = getParentHelper();
+					// new revisions, if any, would be added after all existing, and would get numbered started with last+1
+					localIndex = localRepo.getChangelog().getRevisionCount();
+				}
+				
+				public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+					if (parentHelper.knownNode(nodeid)) {
+						if (!common.contains(nodeid)) {
+							throw new HgBadStateException("Bundle shall not report known nodes other than roots we've supplied");
+						}
+						return;
+					}
+					transformer.next(localIndex++, nodeid, cset);
+				}
+			});
+		} catch (IOException ex) {
+			throw new HgException(ex);
+		}
+	}
+
+	private RepositoryComparator getComparator(Object context) throws HgException, CancelledException {
+		if (remoteRepo == null) {
+			throw new HgBadArgumentException("Shall specify remote repository to compare against", null);
+		}
+		if (comparator == null) {
+			comparator = new RepositoryComparator(getParentHelper(), remoteRepo);
+//			comparator.compare(context); // XXX meanwhile we use distinct path to calculate common  
+		}
+		return comparator;
+	}
+	
+	private HgChangelog.ParentWalker getParentHelper() {
+		if (parentHelper == null) {
+			parentHelper = localRepo.getChangelog().new ParentWalker();
+			parentHelper.init();
+		}
+		return parentHelper;
+	}
+	
+	private List<BranchChain> getMissingBranches(Object context) throws HgException, CancelledException {
+		if (missingBranches == null) {
+			missingBranches = getComparator(context).calculateMissingBranches();
+		}
+		return missingBranches;
+	}
+
+	private List<Nodeid> getCommon(Object context) throws HgException, CancelledException {
+//		return getComparator(context).getCommon();
+		final LinkedHashSet<Nodeid> common = new LinkedHashSet<Nodeid>();
+		// XXX common can be obtained from repoCompare, but at the moment it would almost duplicate work of calculateMissingBranches
+		// once I refactor latter, common shall be taken from repoCompare.
+		RepositoryComparator repoCompare = getComparator(context);
+		for (BranchChain bc : getMissingBranches(context)) {
+			repoCompare.collectKnownRoots(bc, common);
+		}
+		return new LinkedList<Nodeid>(common);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgLogCommand.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,325 @@
+/*
+ * 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 static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.ByteChannel;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ * Access to changelog, 'hg log' command counterpart.
+ * 
+ * <pre>
+ * Usage:
+ *   new LogCommand().limit(20).branch("maintenance-2.1").user("me").execute(new MyHandler());
+ * </pre>
+ * Not thread-safe (each thread has to use own {@link HgLogCommand} instance).
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgLogCommand implements HgChangelog.Inspector {
+
+	private final HgRepository repo;
+	private Set<String> users;
+	private Set<String> branches;
+	private int limit = 0, count = 0;
+	private int startRev = 0, endRev = TIP;
+	private Calendar date;
+	private Path file;
+	private boolean followHistory; // makes sense only when file != null
+	private ChangesetTransformer csetTransform;
+	private HgChangelog.ParentWalker parentHelper;
+	
+	public HgLogCommand(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+
+	/**
+	 * Limit search to specified user. Multiple user names may be specified. Once set, user names can't be 
+	 * cleared, use new command instance in such cases.
+	 * @param user - full or partial name of the user, case-insensitive, non-null.
+	 * @return <code>this</code> instance for convenience
+	 * @throws IllegalArgumentException when argument is null
+	 */
+	public HgLogCommand user(String user) {
+		if (user == null) {
+			throw new IllegalArgumentException();
+		}
+		if (users == null) {
+			users = new TreeSet<String>();
+		}
+		users.add(user.toLowerCase());
+		return this;
+	}
+
+	/**
+	 * Limit search to specified branch. Multiple branch specification possible (changeset from any of these 
+	 * would be included in result). If unspecified, all branches are considered. There's no way to clean branch selection 
+	 * once set, create fresh new command instead.
+	 * @param branch - branch name, case-sensitive, non-null.
+	 * @return <code>this</code> instance for convenience
+	 * @throws IllegalArgumentException when branch argument is null
+	 */
+	public HgLogCommand branch(String branch) {
+		if (branch == null) {
+			throw new IllegalArgumentException();
+		}
+		if (branches == null) {
+			branches = new TreeSet<String>();
+		}
+		branches.add(branch);
+		return this;
+	}
+	
+	// limit search to specific date
+	// multiple?
+	public HgLogCommand date(Calendar date) {
+		this.date = date;
+		// FIXME implement
+		// isSet(field) - false => don't use in detection of 'same date'
+		throw HgRepository.notImplemented();
+	}
+	
+	/**
+	 * 
+	 * @param num - number of changeset to produce. Pass 0 to clear the limit. 
+	 * @return <code>this</code> instance for convenience
+	 */
+	public HgLogCommand limit(int num) {
+		limit = num;
+		return this;
+	}
+
+	/**
+	 * Limit to specified subset of Changelog, [min(rev1,rev2), max(rev1,rev2)], inclusive.
+	 * Revision may be specified with {@link HgRepository#TIP}  
+	 * @param rev1 - local revision number
+	 * @param rev2 - local revision number
+	 * @return <code>this</code> instance for convenience
+	 */
+	public HgLogCommand range(int rev1, int rev2) {
+		if (rev1 != TIP && rev2 != TIP) {
+			startRev = rev2 < rev1 ? rev2 : rev1;
+			endRev = startRev == rev2 ? rev1 : rev2;
+		} else if (rev1 == TIP && rev2 != TIP) {
+			startRev = rev2;
+			endRev = rev1;
+		} else {
+			startRev = rev1;
+			endRev = rev2;
+		}
+		return this;
+	}
+	
+	/**
+	 * Visit history of a given file only.
+	 * @param file path relative to repository root. Pass <code>null</code> to reset.
+	 * @param followCopyRename true to report changesets of the original file(-s), if copy/rename ever occured to the file. 
+	 */
+	public HgLogCommand file(Path file, boolean followCopyRename) {
+		// multiple? Bad idea, would need to include extra method into Handler to tell start of next file
+		this.file = file;
+		followHistory = followCopyRename;
+		return this;
+	}
+	
+	/**
+	 * Handy analog of {@link #file(Path, boolean)} when clients' paths come from filesystem and need conversion to repository's 
+	 */
+	public HgLogCommand file(String file, boolean followCopyRename) {
+		return file(Path.create(repo.getToRepoPathHelper().rewrite(file)), followCopyRename);
+	}
+
+	/**
+	 * Similar to {@link #execute(org.tmatesoft.hg.repo.RawChangeset.Inspector)}, collects and return result as a list.
+	 */
+	public List<HgChangeset> execute() throws HgException {
+		CollectHandler collector = new CollectHandler();
+		execute(collector);
+		return collector.getChanges();
+	}
+
+	/**
+	 * 
+	 * @param handler callback to process changesets.
+	 * @throws IllegalArgumentException when inspector argument is null
+	 * @throws ConcurrentModificationException if this log command instance is already running
+	 */
+	public void execute(HgChangesetHandler handler) throws HgException {
+		if (handler == null) {
+			throw new IllegalArgumentException();
+		}
+		if (csetTransform != null) {
+			throw new ConcurrentModificationException();
+		}
+		try {
+			count = 0;
+			HgChangelog.ParentWalker pw = parentHelper; // leave it uninitialized unless we iterate whole repo
+			if (file == null) {
+				pw = getParentHelper();
+			}
+			// ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above 
+			// may utilize it as well. CommandContext? How about StatusCollector there as well?
+			csetTransform = new ChangesetTransformer(repo, handler, pw);
+			if (file == null) {
+				repo.getChangelog().range(startRev, endRev, this);
+			} else {
+				HgDataFile fileNode = repo.getFileNode(file);
+				fileNode.history(startRev, endRev, this);
+				if (fileNode.isCopy()) {
+					// even if we do not follow history, report file rename
+					do {
+						if (handler instanceof FileHistoryHandler) {
+							FileRevision src = new FileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName());
+							FileRevision dst = new FileRevision(repo, fileNode.getRevision(0), fileNode.getPath());
+							((FileHistoryHandler) handler).copy(src, dst);
+						}
+						if (limit > 0 && count >= limit) {
+							// if limit reach, follow is useless.
+							break;
+						}
+						if (followHistory) {
+							fileNode = repo.getFileNode(fileNode.getCopySourceName());
+							fileNode.history(this);
+						}
+					} while (followHistory && fileNode.isCopy());
+				}
+			}
+		} finally {
+			csetTransform = null;
+		}
+	}
+
+	//
+	
+	public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+		if (limit > 0 && count >= limit) {
+			return;
+		}
+		if (branches != null && !branches.contains(cset.branch())) {
+			return;
+		}
+		if (users != null) {
+			String csetUser = cset.user().toLowerCase();
+			boolean found = false;
+			for (String u : users) {
+				if (csetUser.indexOf(u) != -1) {
+					found = true;
+					break;
+				}
+			}
+			if (!found) {
+				return;
+			}
+		}
+		if (date != null) {
+			// FIXME
+		}
+		count++;
+		csetTransform.next(revisionNumber, nodeid, cset);
+	}
+	
+	private HgChangelog.ParentWalker getParentHelper() {
+		if (parentHelper == null) {
+			parentHelper = repo.getChangelog().new ParentWalker();
+			parentHelper.init();
+		}
+		return parentHelper;
+	}
+
+
+	/**
+	 * @deprecated Use {@link HgChangesetHandler} instead. This interface is left temporarily for compatibility.
+	 */
+	@Deprecated()
+	public interface Handler extends HgChangesetHandler {
+	}
+	
+	/**
+	 * When {@link HgLogCommand} is executed against file, handler passed to {@link HgLogCommand#execute(HgChangesetHandler)} may optionally
+	 * implement this interface to get information about file renames. Method {@link #copy(FileRevision, FileRevision)} would
+	 * get invoked prior any changeset of the original file (if file history being followed) is reported via {@link #next(HgChangeset)}.
+	 * 
+	 * For {@link HgLogCommand#file(Path, boolean)} with renamed file path and follow argument set to false, 
+	 * {@link #copy(FileRevision, FileRevision)} would be invoked for the first copy/rename in the history of the file, but not 
+	 * followed by any changesets. 
+	 *
+	 * @author Artem Tikhomirov
+	 * @author TMate Software Ltd.
+	 */
+	public interface FileHistoryHandler extends HgChangesetHandler {
+		// XXX perhaps, should distinguish copy from rename? And what about merged revisions and following them?
+		void copy(FileRevision from, FileRevision to);
+	}
+	
+	public static class CollectHandler implements HgChangesetHandler {
+		private final List<HgChangeset> result = new LinkedList<HgChangeset>();
+
+		public List<HgChangeset> getChanges() {
+			return Collections.unmodifiableList(result);
+		}
+
+		public void next(HgChangeset changeset) {
+			result.add(changeset.clone());
+		}
+	}
+
+	public static final class FileRevision {
+		private final HgRepository repo;
+		private final Nodeid revision;
+		private final Path path;
+		
+		/*package-local*/FileRevision(HgRepository hgRepo, Nodeid rev, Path p) {
+			if (hgRepo == null || rev == null || p == null) {
+				// since it's package local, it is our code to blame for non validated arguments
+				throw new HgBadStateException();
+			}
+			repo = hgRepo;
+			revision = rev;
+			path = p;
+		}
+		
+		public Path getPath() {
+			return path;
+		}
+		public Nodeid getRevision() {
+			return revision;
+		}
+		public void putContentTo(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
+			HgDataFile fn = repo.getFileNode(path);
+			int localRevision = fn.getLocalRevision(revision);
+			fn.contentWithFilters(localRevision, sink);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgManifestCommand.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,196 @@
+/*
+ * 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 static org.tmatesoft.hg.repo.HgRepository.*;
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.util.ConcurrentModificationException;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.repo.HgManifest;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+
+/**
+ * Gives access to list of files in each revision (Mercurial manifest information), 'hg manifest' counterpart.
+ *  
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgManifestCommand {
+	
+	private final HgRepository repo;
+	private Path.Matcher matcher;
+	private int startRev = 0, endRev = TIP;
+	private Handler visitor;
+	private boolean needDirs = false;
+	
+	private final Mediator mediator = new Mediator();
+
+	public HgManifestCommand(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+
+	/**
+	 * Parameterize command to visit revisions <code>[rev1..rev2]</code>.
+	 * @param rev1 - local revision number to start from. Non-negative. May be {@link HgRepository#TIP} (rev2 argument shall be {@link HgRepository#TIP} as well, then) 
+	 * @param rev2 - local revision number to end with, inclusive. Non-negative, greater or equal to rev1. May be {@link HgRepository#TIP}.
+	 * @return <code>this</code> for convenience.
+	 * @throws IllegalArgumentException if revision arguments are incorrect (see above).
+	 */
+	public HgManifestCommand range(int rev1, int rev2) {
+		// XXX if manifest range is different from that of changelog, need conversion utils (external?)
+		boolean badArgs = rev1 == BAD_REVISION || rev2 == BAD_REVISION || rev1 == WORKING_COPY || rev2 == WORKING_COPY;
+		badArgs |= rev2 != TIP && rev2 < rev1; // range(3, 1);
+		badArgs |= rev1 == TIP && rev2 != TIP; // range(TIP, 2), although this may be legitimate when TIP points to 2
+		if (badArgs) {
+			throw new IllegalArgumentException(String.format("Bad range: [%d, %d]", rev1, rev2));
+		}
+		startRev = rev1;
+		endRev = rev2;
+		return this;
+	}
+	
+	public HgManifestCommand revision(int rev) {
+		startRev = endRev = rev;
+		return this;
+	}
+	
+	public HgManifestCommand dirs(boolean include) {
+		// XXX whether directories with directories only are include or not
+		// now lists only directories with files
+		needDirs = include;
+		return this;
+	}
+	
+	/**
+	 * Limit manifest walk to a subset of files. 
+	 * @param pathMatcher - filter, pass <code>null</code> to clear.
+	 * @return <code>this</code> instance for convenience
+	 */
+	public HgManifestCommand match(Path.Matcher pathMatcher) {
+		matcher = pathMatcher;
+		return this;
+	}
+	
+	/**
+	 * Runs the command.
+	 * @param handler - callback to get the outcome
+	 * @throws IllegalArgumentException if handler is <code>null</code>
+	 * @throws ConcurrentModificationException if this command is already in use (running)
+	 */
+	public void execute(Handler handler) {
+		if (handler == null) {
+			throw new IllegalArgumentException();
+		}
+		if (visitor != null) {
+			throw new ConcurrentModificationException();
+		}
+		try {
+			visitor = handler;
+			mediator.start();
+			repo.getManifest().walk(startRev, endRev, mediator);
+		} finally {
+			mediator.done();
+			visitor = null;
+		}
+	}
+
+	/**
+	 * Callback to walk file/directory tree of a revision
+	 */
+	public interface Handler {
+		void begin(Nodeid manifestRevision);
+		void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs
+		void file(FileRevision fileRevision); // XXX allow to check p is invalid (df.exists())
+		void end(Nodeid manifestRevision);
+	}
+
+	// I'd rather let HgManifestCommand implement HgManifest.Inspector directly, but this pollutes API alot
+	private class Mediator implements HgManifest.Inspector {
+		// file names are likely to repeat in each revision, hence caching of Paths.
+		// However, once HgManifest.Inspector switches to Path objects, perhaps global Path pool
+		// might be more effective?
+		private PathPool pathPool;
+		private List<FileRevision> manifestContent;
+		private Nodeid manifestNodeid;
+		
+		public void start() {
+			// Manifest keeps normalized paths
+			pathPool = new PathPool(new PathRewrite.Empty());
+		}
+		
+		public void done() {
+			manifestContent = null;
+			pathPool = null;
+		}
+	
+		public boolean begin(int revision, Nodeid nid) {
+			if (needDirs && manifestContent == null) {
+				manifestContent = new LinkedList<FileRevision>();
+			}
+			visitor.begin(manifestNodeid = nid);
+			return true;
+		}
+		public boolean end(int revision) {
+			if (needDirs) {
+				LinkedHashMap<Path, LinkedList<FileRevision>> breakDown = new LinkedHashMap<Path, LinkedList<FileRevision>>();
+				for (FileRevision fr : manifestContent) {
+					Path filePath = fr.getPath();
+					Path dirPath = pathPool.parent(filePath);
+					LinkedList<FileRevision> revs = breakDown.get(dirPath);
+					if (revs == null) {
+						revs = new LinkedList<FileRevision>();
+						breakDown.put(dirPath, revs);
+					}
+					revs.addLast(fr);
+				}
+				for (Path dir : breakDown.keySet()) {
+					visitor.dir(dir);
+					for (FileRevision fr : breakDown.get(dir)) {
+						visitor.file(fr);
+					}
+				}
+				manifestContent.clear();
+			}
+			visitor.end(manifestNodeid);
+			manifestNodeid = null;
+			return true;
+		}
+		public boolean next(Nodeid nid, String fname, String flags) {
+			Path p = pathPool.path(fname);
+			if (matcher != null && !matcher.accept(p)) {
+				return true;
+			}
+			FileRevision fr = new FileRevision(repo, nid, p);
+			if (needDirs) {
+				manifestContent.add(fr);
+			} else {
+				visitor.file(fr);
+			}
+			return true;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgOutgoingCommand.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,133 @@
+/*
+ * 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.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.internal.RepositoryComparator;
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.CancelledException;
+
+/**
+ * Command to find out changes made in a local repository and missing at remote repository. 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgOutgoingCommand {
+
+	private final HgRepository localRepo;
+	private HgRemoteRepository remoteRepo;
+	@SuppressWarnings("unused")
+	private boolean includeSubrepo;
+	private RepositoryComparator comparator;
+	private HgChangelog.ParentWalker parentHelper;
+	private Set<String> branches;
+
+	public HgOutgoingCommand(HgRepository hgRepo) {
+		localRepo = hgRepo;
+	}
+
+	/**
+	 * @param hgRemote remoteRepository to compare against
+	 * @return <code>this</code> for convenience
+	 */
+	public HgOutgoingCommand against(HgRemoteRepository hgRemote) {
+		remoteRepo = hgRemote;
+		comparator = null;
+		return this;
+	}
+
+	/**
+	 * Select specific branch to pull. 
+	 * Multiple branch specification possible (changeset from any of these would be included in result).
+	 * Note, {@link #executeLite(Object)} does not respect this setting.
+	 * 
+	 * @param branch - branch name, case-sensitive, non-null.
+	 * @return <code>this</code> for convenience
+	 * @throws IllegalArgumentException when branch argument is null
+	 */
+	public HgOutgoingCommand branch(String branch) {
+		if (branch == null) {
+			throw new IllegalArgumentException();
+		}
+		if (branches == null) {
+			branches = new TreeSet<String>();
+		}
+		branches.add(branch);
+		return this;
+	}
+	
+	/**
+	 * PLACEHOLDER, NOT IMPLEMENTED YET.
+	 * 
+	 * @return <code>this</code> for convenience
+	 */
+	public HgOutgoingCommand subrepo(boolean include) {
+		includeSubrepo = include;
+		throw HgRepository.notImplemented();
+	}
+
+	/**
+	 * Lightweight check for outgoing changes. 
+	 * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account.
+	 * 
+	 * @param context
+	 * @return list on local nodes known to be missing at remote server 
+	 */
+	public List<Nodeid> executeLite(Object context) throws HgException, CancelledException {
+		return getComparator(context).getLocalOnlyRevisions();
+	}
+
+	/**
+	 * Complete information about outgoing changes
+	 * 
+	 * @param handler delegate to process changes
+	 */
+	public void executeFull(final HgChangesetHandler handler) throws HgException, CancelledException {
+		if (handler == null) {
+			throw new IllegalArgumentException("Delegate can't be null");
+		}
+		ChangesetTransformer inspector = new ChangesetTransformer(localRepo, handler, getParentHelper());
+		inspector.limitBranches(branches);
+		getComparator(handler).visitLocalOnlyRevisions(inspector);
+	}
+
+	private RepositoryComparator getComparator(Object context) throws HgException, CancelledException {
+		if (remoteRepo == null) {
+			throw new IllegalArgumentException("Shall specify remote repository to compare against");
+		}
+		if (comparator == null) {
+			comparator = new RepositoryComparator(getParentHelper(), remoteRepo);
+			comparator.compare(context);
+		}
+		return comparator;
+	}
+	
+	private HgChangelog.ParentWalker getParentHelper() {
+		if (parentHelper == null) {
+			parentHelper = localRepo.getChangelog().new ParentWalker();
+			parentHelper.init();
+		}
+		return parentHelper;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgRepoFacade.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,113 @@
+/*
+ * 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 org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgLookup;
+
+/**
+ * Starting point for the library.
+ * <p>Sample use:
+ * <pre>
+ *  HgRepoFacade f = new HgRepoFacade();
+ *  f.initFrom(System.getenv("whatever.repo.location"));
+ *  HgStatusCommand statusCmd = f.createStatusCommand();
+ *  HgStatusCommand.Handler handler = ...;
+ *  statusCmd.execute(handler);
+ * </pre>
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgRepoFacade {
+	private HgRepository repo;
+
+	public HgRepoFacade() {
+	}
+	
+	/**
+	 * @param hgRepo
+	 * @return true on successful initialization
+	 * @throws IllegalArgumentException when argument is null 
+	 */
+	public boolean init(HgRepository hgRepo) {
+		if (hgRepo == null) {
+			throw new IllegalArgumentException();
+		}
+		repo = hgRepo;
+		return !repo.isInvalid();
+	}
+
+	/**
+	 * 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
+	 */
+	public boolean init() throws HgException {
+		repo = new HgLookup().detectFromWorkingDir();
+		return repo != null && !repo.isInvalid();
+	}
+	
+	/**
+	 * Looks up Mercurial repository starting from specified location and up to filesystem root.
+	 * 
+	 * @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 IllegalArgumentException if argument is <code>null</code>
+	 */
+	public boolean initFrom(File repoLocation) throws HgException {
+		if (repoLocation == null) {
+			throw new IllegalArgumentException();
+		}
+		repo = new HgLookup().detect(repoLocation);
+		return repo != null && !repo.isInvalid();
+	}
+	
+	public HgRepository getRepository() {
+		if (repo == null) {
+			throw new IllegalStateException("Call any of #init*() methods first first");
+		}
+		return repo;
+	}
+
+	public HgLogCommand createLogCommand() {
+		return new HgLogCommand(repo/*, getCommandContext()*/);
+	}
+
+	public HgStatusCommand createStatusCommand() {
+		return new HgStatusCommand(repo/*, getCommandContext()*/);
+	}
+
+	public HgCatCommand createCatCommand() {
+		return new HgCatCommand(repo);
+	}
+
+	public HgManifestCommand createManifestCommand() {
+		return new HgManifestCommand(repo);
+	}
+
+	public HgOutgoingCommand createOutgoingCommand() {
+		return new HgOutgoingCommand(repo);
+	}
+
+	public HgIncomingCommand createIncomingCommand() {
+		return new HgIncomingCommand(repo);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgStatus.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,95 @@
+/*
+ * 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.util.Date;
+
+import org.tmatesoft.hg.internal.ChangelogHelper;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Repository file status and extra handy information.
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgStatus {
+
+	public enum Kind {
+		Modified, Added, Removed, Missing, Unknown, Clean, Ignored
+		// I'd refrain from changing order of these constants, just in case someone (erroneously, of course ;), uses .ordinal()
+	};
+
+	private final HgStatus.Kind kind;
+	private final Path path;
+	private final Path origin;
+	private final ChangelogHelper logHelper;
+		
+	HgStatus(HgStatus.Kind kind, Path path, ChangelogHelper changelogHelper) {
+		this(kind, path, null, changelogHelper);
+	}
+
+	HgStatus(HgStatus.Kind kind, Path path, Path copyOrigin, ChangelogHelper changelogHelper) {
+		this.kind = kind;
+		this.path  = path;
+		origin = copyOrigin;
+		logHelper = changelogHelper;
+	}
+
+	public HgStatus.Kind getKind() {
+		return kind;
+	}
+
+	public Path getPath() {
+		return path;
+	}
+
+	public Path getOriginalPath() {
+		return origin;
+	}
+
+	public boolean isCopy() {
+		return origin != null;
+	}
+
+	/**
+	 * @return <code>null</code> if author for the change can't be deduced (e.g. for clean files it's senseless)
+	 */
+	public String getModificationAuthor() {
+		RawChangeset cset = logHelper.findLatestChangeWith(path);
+		if (cset == null) {
+			if (kind == Kind.Modified || kind == Kind.Added || kind == Kind.Removed /*&& RightBoundary is TIP*/) {
+				// perhaps, also for Kind.Missing?
+				return logHelper.getNextCommitUsername();
+			}
+		} else {
+			return cset.user();
+		}
+		return null;
+	}
+
+	public Date getModificationDate() {
+		RawChangeset cset = logHelper.findLatestChangeWith(path);
+		if (cset == null) {
+			// FIXME check dirstate and/or local file for tstamp
+			return new Date(); // what's correct 
+		} else {
+			return cset.date();
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/HgStatusCommand.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,285 @@
+/*
+ * 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 static org.tmatesoft.hg.core.HgStatus.Kind.*;
+import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision;
+import static org.tmatesoft.hg.repo.HgRepository.*;
+
+import java.util.ConcurrentModificationException;
+
+import org.tmatesoft.hg.internal.ChangelogHelper;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.repo.HgStatusInspector;
+import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
+import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.Path.Matcher;
+
+/**
+ * Command to obtain file status information, 'hg status' counterpart. 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgStatusCommand {
+	private final HgRepository repo;
+
+	private int startRevision = TIP;
+	private int endRevision = WORKING_COPY; 
+	
+	private final Mediator mediator = new Mediator();
+
+	public HgStatusCommand(HgRepository hgRepo) { 
+		repo = hgRepo;
+		defaults();
+	}
+
+	public HgStatusCommand defaults() {
+		final Mediator m = mediator;
+		m.needModified = m.needAdded = m.needRemoved = m.needUnknown = m.needMissing = true;
+		m.needCopies = m.needClean = m.needIgnored = false;
+		return this;
+	}
+	public HgStatusCommand all() {
+		final Mediator m = mediator;
+		m.needModified = m.needAdded = m.needRemoved = m.needUnknown = m.needMissing = true;
+		m.needCopies = m.needClean = m.needIgnored = true;
+		return this;
+	}
+	
+
+	public HgStatusCommand modified(boolean include) {
+		mediator.needModified = include;
+		return this;
+	}
+	public HgStatusCommand added(boolean include) {
+		mediator.needAdded = include;
+		return this;
+	}
+	public HgStatusCommand removed(boolean include) {
+		mediator.needRemoved = include;
+		return this;
+	}
+	public HgStatusCommand deleted(boolean include) {
+		mediator.needMissing = include;
+		return this;
+	}
+	public HgStatusCommand unknown(boolean include) {
+		mediator.needUnknown = include;
+		return this;
+	}
+	public HgStatusCommand clean(boolean include) {
+		mediator.needClean = include;
+		return this;
+	}
+	public HgStatusCommand ignored(boolean include) {
+		mediator.needIgnored = include;
+		return this;
+	}
+	
+	/**
+	 * If set, either base:revision or base:workingdir
+	 * to unset, pass {@link HgRepository#TIP} or {@link HgRepository#BAD_REVISION}
+	 * @param revision - local revision number to base status from
+	 * @return <code>this</code> for convenience
+	 * @throws IllegalArgumentException when revision is negative or {@link HgRepository#WORKING_COPY} 
+	 */
+	public HgStatusCommand base(int revision) {
+		if (revision == WORKING_COPY || wrongLocalRevision(revision)) {
+			throw new IllegalArgumentException(String.valueOf(revision));
+		}
+		if (revision == BAD_REVISION) {
+			revision = TIP;
+		}
+		startRevision = revision;
+		return this;
+	}
+	
+	/**
+	 * Revision without base == --change
+	 * Pass {@link HgRepository#WORKING_COPY} or {@link HgRepository#BAD_REVISION} to reset
+	 * @param revision - non-negative local revision number, or any of {@link HgRepository#BAD_REVISION}, {@link HgRepository#WORKING_COPY} or {@link HgRepository#TIP}  
+	 * @return <code>this</code> for convenience
+	 * @throws IllegalArgumentException if local revision number doesn't specify legitimate revision. 
+	 */
+	public HgStatusCommand revision(int revision) {
+		if (revision == BAD_REVISION) {
+			revision = WORKING_COPY;
+		}
+		if (wrongLocalRevision(revision)) {
+			throw new IllegalArgumentException(String.valueOf(revision));
+		}
+		endRevision = revision;
+		return this;
+	}
+	
+	/**
+	 * Shorthand for {@link #base(int) cmd.base(BAD_REVISION)}{@link #change(int) .revision(revision)}
+	 *  
+	 * @param revision compare given revision against its parent
+	 * @return <code>this</code> for convenience
+	 */
+	public HgStatusCommand change(int revision) {
+		base(BAD_REVISION);
+		return revision(revision);
+	}
+	
+	/**
+	 * Limit status operation to certain sub-tree.
+	 * 
+	 * @param pathMatcher - matcher to use,  pass <code>null/<code> to reset
+	 * @return <code>this</code> for convenience
+	 */
+	public HgStatusCommand match(Path.Matcher pathMatcher) {
+		mediator.matcher = pathMatcher;
+		return this;
+	}
+
+	public HgStatusCommand subrepo(boolean visit) {
+		throw HgRepository.notImplemented();
+	}
+
+	/**
+	 * Perform status operation according to parameters set.
+	 *  
+	 * @param handler callback to get status information
+	 * @throws IllegalArgumentException if handler is <code>null</code>
+	 * @throws ConcurrentModificationException if this command already runs (i.e. being used from another thread)
+	 */
+	public void execute(Handler statusHandler) {
+		if (statusHandler == null) {
+			throw new IllegalArgumentException();
+		}
+		if (mediator.busy()) {
+			throw new ConcurrentModificationException();
+		}
+		HgStatusCollector sc = new HgStatusCollector(repo); // TODO from CommandContext
+//		PathPool pathHelper = new PathPool(repo.getPathHelper()); // TODO from CommandContext
+		try {
+			// XXX if I need a rough estimation (for ProgressMonitor) of number of work units,
+			// I may use number of files in either rev1 or rev2 manifest edition
+			mediator.start(statusHandler, new ChangelogHelper(repo, startRevision));
+			if (endRevision == WORKING_COPY) {
+				HgWorkingCopyStatusCollector wcsc = new HgWorkingCopyStatusCollector(repo);
+				wcsc.setBaseRevisionCollector(sc);
+				wcsc.walk(startRevision, mediator);
+			} else {
+				if (startRevision == TIP) {
+					sc.change(endRevision, mediator);
+				} else {
+					sc.walk(startRevision, endRevision, mediator);
+				}
+			}
+		} finally {
+			mediator.done();
+		}
+	}
+
+	public interface Handler {
+		void handleStatus(HgStatus s);
+	}
+
+	private class Mediator implements HgStatusInspector {
+		boolean needModified;
+		boolean needAdded;
+		boolean needRemoved;
+		boolean needUnknown;
+		boolean needMissing;
+		boolean needClean;
+		boolean needIgnored;
+		boolean needCopies;
+		Matcher matcher;
+		Handler handler;
+		private ChangelogHelper logHelper;
+
+		Mediator() {
+		}
+		
+		public void start(Handler h, ChangelogHelper changelogHelper) {
+			handler = h;
+			logHelper = changelogHelper;
+		}
+
+		public void done() {
+			handler = null;
+			logHelper = null;
+		}
+		
+		public boolean busy() {
+			return handler != null;
+		}
+
+		public void modified(Path fname) {
+			if (needModified) {
+				if (matcher == null || matcher.accept(fname)) {
+					handler.handleStatus(new HgStatus(Modified, fname, logHelper));
+				}
+			}
+		}
+		public void added(Path fname) {
+			if (needAdded) {
+				if (matcher == null || matcher.accept(fname)) {
+					handler.handleStatus(new HgStatus(Added, fname, logHelper));
+				}
+			}
+		}
+		public void removed(Path fname) {
+			if (needRemoved) {
+				if (matcher == null || matcher.accept(fname)) {
+					handler.handleStatus(new HgStatus(Removed, fname, logHelper));
+				}
+			}
+		}
+		public void copied(Path fnameOrigin, Path fnameAdded) {
+			if (needCopies) {
+				if (matcher == null || matcher.accept(fnameAdded)) {
+					// FIXME in fact, merged files may report 'copied from' as well, correct status kind thus may differ from Added
+					handler.handleStatus(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
+				}
+			}
+		}
+		public void missing(Path fname) {
+			if (needMissing) {
+				if (matcher == null || matcher.accept(fname)) {
+					handler.handleStatus(new HgStatus(Missing, fname, logHelper));
+				}
+			}
+		}
+		public void unknown(Path fname) {
+			if (needUnknown) {
+				if (matcher == null || matcher.accept(fname)) {
+					handler.handleStatus(new HgStatus(Unknown, fname, logHelper));
+				}
+			}
+		}
+		public void clean(Path fname) {
+			if (needClean) {
+				if (matcher == null || matcher.accept(fname)) {
+					handler.handleStatus(new HgStatus(Clean, fname, logHelper));
+				}
+			}
+		}
+		public void ignored(Path fname) {
+			if (needIgnored) {
+				if (matcher == null || matcher.accept(fname)) {
+					handler.handleStatus(new HgStatus(Ignored, fname, logHelper));
+				}
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/Nodeid.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2010-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 static org.tmatesoft.hg.internal.DigestHelper.toHexString;
+
+import java.util.Arrays;
+
+
+
+/**
+ * A 20-bytes (40 characters) long hash value to identify a revision.
+ * @see http://mercurial.selenic.com/wiki/Nodeid
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ *
+ */
+public final class Nodeid implements Comparable<Nodeid> {
+	
+	/**
+	 * <b>nullid</b>, empty root revision.
+	 */
+	public static final Nodeid NULL = new Nodeid(new byte[20], false);
+
+	private final byte[] binaryData; 
+
+	/**
+	 * @param binaryRepresentation - array of exactly 20 bytes
+	 * @param shallClone - true if array is subject to future modification and shall be copied, not referenced
+	 * @throws IllegalArgumentException if supplied binary representation doesn't correspond to 20 bytes of sha1 digest 
+	 */
+	public Nodeid(byte[] binaryRepresentation, boolean shallClone) {
+		// 5 int fields => 32 bytes
+		// byte[20] => 48 bytes (16 bytes is Nodeid with one field, 32 bytes for byte[20] 
+		if (binaryRepresentation == null || binaryRepresentation.length != 20) {
+			throw new IllegalArgumentException();
+		}
+		/*
+		 * byte[].clone() is not reflected when ran with -agentlib:hprof=heap=sites
+		 * thus not to get puzzled why there are N Nodeids and much less byte[] instances,
+		 * may use following code to see N byte[] as well.
+		 *
+		if (shallClone) {
+			binaryData = new byte[20];
+			System.arraycopy(binaryRepresentation, 0, binaryData, 0, 20);
+		} else {
+			binaryData = binaryRepresentation;
+		}
+		*/
+		binaryData = shallClone ? binaryRepresentation.clone() : binaryRepresentation;
+	}
+
+	@Override
+	public int hashCode() {
+		// digest (part thereof) seems to be nice candidate for the hashCode
+		byte[] b = binaryData;
+		return b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF);
+	}
+	
+	@Override
+	public boolean equals(Object o) {
+		if (o instanceof Nodeid) {
+			return this == o || equalsTo(((Nodeid) o).binaryData);
+		}
+		return false;
+	}
+
+	public boolean equalsTo(byte[] buf) {
+		return Arrays.equals(this.binaryData, buf);
+	}
+	
+	public int compareTo(Nodeid o) {
+		if (this == o) {
+			return 0;
+		}
+		for (int i = 0; i < 20; i++) {
+			if (binaryData[i] != o.binaryData[i]) {
+				return binaryData[i] < o.binaryData[i] ? -1 : 1;
+			}
+		}
+		return 0;
+	}
+	
+	@Override
+	public String toString() {
+		// XXX may want to output just single 0 for the NULL id?
+		return toHexString(binaryData, 0, binaryData.length);
+	}
+
+	public String shortNotation() {
+		return toHexString(binaryData, 0, 6);
+	}
+	
+	public boolean isNull() {
+		if (this == NULL) {
+			return true;
+		}
+		for (int i = 0; i < 20; i++) {
+			if (this.binaryData[i] != 0) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	// copy 
+	public byte[] toByteArray() {
+		return binaryData.clone();
+	}
+
+	/**
+	 * Factory for {@link Nodeid Nodeids}.
+	 * Primary difference with cons is handling of NULL id (this method returns constant) and control over array 
+	 * duplication - this method always makes a copy of an array passed
+	 * @param binaryRepresentation - byte array of a length at least offset + 20
+	 * @param offset - index in the array to start from
+	 * @throws IllegalArgumentException when arguments don't select 20 bytes
+	 */
+	public static Nodeid fromBinary(byte[] binaryRepresentation, int offset) {
+		if (binaryRepresentation == null || binaryRepresentation.length - offset < 20) {
+			throw new IllegalArgumentException();
+		}
+		int i = 0;
+		while (i < 20 && binaryRepresentation[offset+i] == 0) i++;
+		if (i == 20) {
+			return NULL;
+		}
+		if (offset == 0 && binaryRepresentation.length == 20) {
+			return new Nodeid(binaryRepresentation, true);
+		}
+		byte[] b = new byte[20]; // create new instance if no other reasonable guesses possible
+		System.arraycopy(binaryRepresentation, offset, b, 0, 20);
+		return new Nodeid(b, false);
+	}
+
+	/**
+	 * Parse encoded representation.
+	 * 
+	 * @param asciiRepresentation - encoded form of the Nodeid.
+	 * @return object representation
+	 * @throws IllegalArgumentException when argument doesn't match encoded form of 20-bytes sha1 digest. 
+	 */
+	public static Nodeid fromAscii(String asciiRepresentation) {
+		if (asciiRepresentation.length() != 40) {
+			throw new IllegalArgumentException();
+		}
+		// XXX is better impl for String possible?
+		return fromAscii(asciiRepresentation.getBytes(), 0, 40);
+	}
+
+	/**
+	 * Parse encoded representation. Similar to {@link #fromAscii(String)}.
+	 */
+	public static Nodeid fromAscii(byte[] asciiRepresentation, int offset, int length) {
+		if (length != 40) {
+			throw new IllegalArgumentException();
+		}
+		byte[] data = new byte[20];
+		boolean zeroBytes = true;
+		for (int i = 0, j = offset; i < data.length; i++) {
+			int hiNibble = Character.digit(asciiRepresentation[j++], 16);
+			int lowNibble = Character.digit(asciiRepresentation[j++], 16);
+			byte b = (byte) (((hiNibble << 4) | lowNibble) & 0xFF);
+			data[i] = b;
+			zeroBytes = zeroBytes && b == 0;
+		}
+		if (zeroBytes) {
+			return NULL;
+		}
+		return new Nodeid(data, false);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/core/package.html	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,5 @@
+<html>
+<boody>
+Hi-level API
+</bidy>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/ByteArrayChannel.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,91 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.tmatesoft.hg.util.ByteChannel;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ByteArrayChannel implements ByteChannel {
+	private final List<ByteBuffer> buffers;
+	private ByteBuffer target;
+	private byte[] result;
+	
+	public ByteArrayChannel() {
+		this(-1);
+	}
+	
+	public ByteArrayChannel(int size) {
+		if (size == -1) {
+			buffers = new LinkedList<ByteBuffer>();
+		} else {
+			if (size < 0) {
+				throw new IllegalArgumentException(String.valueOf(size));
+			}
+			buffers = null;
+			target = ByteBuffer.allocate(size);
+		}
+	}
+
+	// TODO document what happens on write after toArray() in each case
+	public int write(ByteBuffer buffer) {
+		int rv = buffer.remaining();
+		if (buffers == null) {
+			target.put(buffer);
+		} else {
+			ByteBuffer copy = ByteBuffer.allocate(rv);
+			copy.put(buffer);
+			buffers.add(copy);
+		}
+		return rv;
+	}
+
+	public byte[] toArray() {
+		if (result != null) {
+			return result;
+		}
+		if (buffers == null) {
+			assert target.hasArray();
+			// int total = target.position();
+			// System.arraycopy(target.array(), new byte[total]);
+			// I don't want to duplicate byte[] for now
+			// although correct way of doing things is to make a copy and discard target
+			return target.array();
+		} else {
+			int total = 0;
+			for (ByteBuffer bb : buffers) {
+				bb.flip();
+				total += bb.limit();
+			}
+			result = new byte[total];
+			int off = 0;
+			for (ByteBuffer bb : buffers) {
+				bb.get(result, off, bb.limit());
+				off += bb.limit();
+			}
+			buffers.clear();
+			return result;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/ByteArrayDataAccess.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,90 @@
+/*
+ * 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.IOException;
+
+
+/**
+ *   
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ByteArrayDataAccess extends DataAccess {
+
+	private final byte[] data;
+	private final int offset;
+	private final int length;
+	private int pos;
+
+	public ByteArrayDataAccess(byte[] data) {
+		this(data, 0, data.length);
+	}
+
+	public ByteArrayDataAccess(byte[] data, int offset, int length) {
+		this.data = data;
+		this.offset = offset;
+		this.length = length;
+		pos = 0;
+	}
+	
+	@Override
+	public byte readByte() throws IOException {
+		if (pos >= length) {
+			throw new IOException();
+		}
+		return data[offset + pos++];
+	}
+	@Override
+	public void readBytes(byte[] buf, int off, int len) throws IOException {
+		if (len > (this.length - pos)) {
+			throw new IOException();
+		}
+		System.arraycopy(data, pos, buf, off, len);
+		pos += len;
+	}
+
+	@Override
+	public ByteArrayDataAccess reset() {
+		pos = 0;
+		return this;
+	}
+	@Override
+	public int length() {
+		return length;
+	}
+	@Override
+	public void seek(int offset) {
+		pos = (int) offset;
+	}
+	@Override
+	public void skip(int bytes) throws IOException {
+		seek(pos + bytes);
+	}
+	@Override
+	public boolean isEmpty() {
+		return pos >= length;
+	}
+	
+	//
+	
+	// when byte[] needed from DA, we may save few cycles and some memory giving this (otherwise unsafe) access to underlying data
+	@Override
+	public byte[] byteArray() {
+		return data;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/ChangelogHelper.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,83 @@
+/*
+ * 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.util.TreeMap;
+
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ChangelogHelper {
+	private final int leftBoundary;
+	private final HgRepository repo;
+	private final TreeMap<Integer, RawChangeset> cache = new TreeMap<Integer, RawChangeset>();
+	private String nextCommitAuthor;
+
+	/**
+	 * @param hgRepo
+	 * @param leftBoundaryRevision walker never visits revisions with local numbers less than specified,
+	 * IOW only revisions [leftBoundaryRevision..TIP] are considered.
+	 */
+	public ChangelogHelper(HgRepository hgRepo, int leftBoundaryRevision) {
+		repo = hgRepo;
+		leftBoundary = leftBoundaryRevision;
+	}
+	
+	/**
+	 * @return the repo
+	 */
+	public HgRepository getRepo() {
+		return repo;
+	}
+
+	/**
+	 * Walks changelog in reverse order
+	 * @param file
+	 * @return changeset where specified file is mentioned among affected files, or 
+	 * <code>null</code> if none found up to leftBoundary 
+	 */
+	public RawChangeset findLatestChangeWith(Path file) {
+		HgDataFile df = repo.getFileNode(file);
+		int changelogRev = df.getChangesetLocalRevision(HgRepository.TIP);
+		if (changelogRev >= leftBoundary) {
+			// the method is likely to be invoked for different files, 
+			// while changesets might be the same. Cache 'em not to read too much. 
+			RawChangeset cs = cache.get(changelogRev);
+			if (cs == null) {
+				cs = repo.getChangelog().range(changelogRev, changelogRev).get(0);
+				cache.put(changelogRev, cs);
+			}
+			return cs;
+		}
+		return null;
+	}
+
+	public String getNextCommitUsername() {
+		if (nextCommitAuthor == null) {
+			nextCommitAuthor = new HgInternals(repo).getNextCommitUsername();
+		}
+		return nextCommitAuthor;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/ConfigFile.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,151 @@
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ConfigFile {
+
+	private List<String> sections;
+	private List<Map<String,String>> content;
+
+	ConfigFile() {
+	}
+
+	public void addLocation(File path) {
+		read(path);
+	}
+	
+	public boolean hasSection(String sectionName) {
+		return sections == null ? false : sections.indexOf(sectionName) == -1;
+	}
+	
+	// XXX perhaps, should be moved to subclass HgRepoConfig, as it is not common operation for any config file
+	public boolean hasEnabledExtension(String extensionName) {
+		int x = sections != null ? sections.indexOf("extensions") : -1;
+		if (x == -1) {
+			return false;
+		}
+		String value = content.get(x).get(extensionName);
+		return value != null && !"!".equals(value);
+	}
+	
+	public List<String> getSectionNames() {
+		return sections == null ? Collections.<String>emptyList() : Collections.unmodifiableList(sections);
+	}
+
+	public Map<String,String> getSection(String sectionName) {
+		if (sections ==  null) {
+			return Collections.emptyMap();
+		}
+		int x = sections.indexOf(sectionName);
+		if (x == -1) {
+			return Collections.emptyMap();
+		}
+		return Collections.unmodifiableMap(content.get(x));
+	}
+
+	public boolean getBoolean(String sectionName, String key, boolean defaultValue) {
+		String value = getSection(sectionName).get(key);
+		if (value == null) {
+			return defaultValue;
+		}
+		for (String s : new String[] { "true", "yes", "on", "1" }) {
+			if (s.equalsIgnoreCase(value)) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	public String getString(String sectionName, String key, String defaultValue) {
+		String value = getSection(sectionName).get(key);
+		return value == null ? defaultValue : value;
+	}
+
+	// TODO handle %include and %unset directives
+	// TODO "" and lists
+	private void read(File f) {
+		if (f == null || !f.canRead()) {
+			return;
+		}
+		if (sections == null) {
+			sections = new ArrayList<String>();
+			content = new ArrayList<Map<String,String>>();
+		}
+		try {
+			BufferedReader br = new BufferedReader(new FileReader(f));
+			String line;
+			String sectionName = "";
+			Map<String,String> section = new LinkedHashMap<String, String>();
+			while ((line = br.readLine()) != null) {
+				line = line.trim();
+				int x;
+				if ((x = line.indexOf('#')) != -1) {
+					// do not keep comments in memory, get new, shorter string
+					line = new String(line.substring(0, x).trim());
+				}
+				if (line.length() <= 2) { // a=b or [a] are at least of length 3
+					continue;
+				}
+				if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') {
+					sectionName = line.substring(1, line.length() - 1);
+					if (sections.indexOf(sectionName) == -1) {
+						sections.add(sectionName);
+						content.add(section = new LinkedHashMap<String, String>());
+					} else {
+						section = null; // drop cached value
+					}
+				} else if ((x = line.indexOf('=')) != -1) {
+					// share char[] of the original string
+					String key = line.substring(0, x).trim();
+					String value = line.substring(x+1).trim();
+					if (section == null) {
+						int i = sections.indexOf(sectionName);
+						assert i >= 0;
+						section = content.get(i);
+					}
+					if (sectionName.length() == 0) {
+						// add fake section only if there are any values 
+						sections.add(sectionName);
+						content.add(section);
+					}
+					section.put(key, value);
+				}
+			}
+			br.close();
+		} catch (IOException ex) {
+			ex.printStackTrace(); // XXX shall outer world care?
+		}
+		((ArrayList<?>) sections).trimToSize();
+		((ArrayList<?>) content).trimToSize();
+		assert sections.size() == content.size();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/DataAccess.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2010-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.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * relevant parts of DataInput, non-stream nature (seek operation), explicit check for end of data.
+ * convenient skip (+/- bytes)
+ * Primary goal - effective file read, so that clients don't need to care whether to call few 
+ * distinct getInt() or readBytes(totalForFewInts) and parse themselves instead in an attempt to optimize.  
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class DataAccess {
+	public boolean isEmpty() {
+		return true;
+	}
+	public int length() {
+		return 0;
+	}
+	/**
+	 * get this instance into initial state
+	 * @throws IOException
+	 * @return <code>this</code> for convenience
+	 */
+	public DataAccess reset() throws IOException {
+		// nop, empty instance is always in the initial state
+		return this;
+	}
+	// absolute positioning
+	public void seek(int offset) throws IOException {
+		throw new UnsupportedOperationException();
+	}
+	// relative positioning
+	public void skip(int bytes) throws IOException {
+		throw new UnsupportedOperationException();
+	}
+	// shall be called once this object no longer needed
+	public void done() {
+		// no-op in this empty implementation
+	}
+	public int readInt() throws IOException {
+		byte[] b = new byte[4];
+		readBytes(b, 0, 4);
+		return b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF);
+	}
+	public long readLong() throws IOException {
+		byte[] b = new byte[8];
+		readBytes(b, 0, 8);
+		int i1 = b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF);
+		int i2 = b[4] << 24 | (b[5] & 0xFF) << 16 | (b[6] & 0xFF) << 8 | (b[7] & 0xFF);
+		return ((long) i1) << 32 | ((long) i2 & 0xFFFFFFFFl);
+	}
+	public void readBytes(byte[] buf, int offset, int length) throws IOException {
+		throw new UnsupportedOperationException();
+	}
+	// reads bytes into ByteBuffer, up to its limit or total data length, whichever smaller
+	// FIXME perhaps, in DataAccess paradigm (when we read known number of bytes, we shall pass specific byte count to read) 
+	public void readBytes(ByteBuffer buf) throws IOException {
+//		int toRead = Math.min(buf.remaining(), (int) length());
+//		if (buf.hasArray()) {
+//			readBytes(buf.array(), buf.arrayOffset(), toRead);
+//		} else {
+//			byte[] bb = new byte[toRead];
+//			readBytes(bb, 0, bb.length);
+//			buf.put(bb);
+//		}
+		// FIXME optimize to read as much as possible at once
+		while (!isEmpty() && buf.hasRemaining()) {
+			buf.put(readByte());
+		}
+	}
+	public byte readByte() throws IOException {
+		throw new UnsupportedOperationException();
+	}
+
+	// XXX decide whether may or may not change position in the DataAccess
+	// FIXME exception handling is not right, just for the sake of quick test
+	public byte[] byteArray() throws IOException {
+		reset();
+		byte[] rv = new byte[length()];
+		readBytes(rv, 0, rv.length);
+		return rv;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/DataAccessProvider.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2010-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.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class DataAccessProvider {
+
+	private final int mapioMagicBoundary;
+	private final int bufferSize;
+
+	public DataAccessProvider() {
+		this(100 * 1024, 8 * 1024);
+	}
+
+	public DataAccessProvider(int mapioBoundary, int regularBufferSize) {
+		mapioMagicBoundary = mapioBoundary;
+		bufferSize = regularBufferSize;
+	}
+
+	public DataAccess create(File f) {
+		if (!f.exists()) {
+			return new DataAccess();
+		}
+		try {
+			FileChannel fc = new FileInputStream(f).getChannel();
+			int flen = (int) fc.size();
+			if (fc.size() - flen != 0) {
+				throw new HgBadStateException("Files greater than 2Gb are not yet supported");
+			}
+			if (flen > mapioMagicBoundary) {
+				// TESTS: bufLen of 1024 was used to test MemMapFileAccess
+				return new MemoryMapFileAccess(fc, flen, mapioMagicBoundary);
+			} else {
+				// XXX once implementation is more or less stable,
+				// may want to try ByteBuffer.allocateDirect() to see
+				// if there's any performance gain. 
+				boolean useDirectBuffer = false;
+				// TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize
+				return new FileAccess(fc, flen, bufferSize, useDirectBuffer);
+			}
+		} catch (IOException ex) {
+			// unlikely to happen, we've made sure file exists.
+			ex.printStackTrace(); // FIXME log error
+		}
+		return new DataAccess(); // non-null, empty.
+	}
+
+	// DOESN'T WORK YET 
+	private static class MemoryMapFileAccess extends DataAccess {
+		private FileChannel fileChannel;
+		private final int size;
+		private long position = 0; // always points to buffer's absolute position in the file
+		private final int memBufferSize;
+		private MappedByteBuffer buffer;
+
+		public MemoryMapFileAccess(FileChannel fc, int channelSize, int /*long?*/ bufferSize) {
+			fileChannel = fc;
+			size = channelSize;
+			memBufferSize = bufferSize;
+		}
+
+		@Override
+		public boolean isEmpty() {
+			return position + (buffer == null ? 0 : buffer.position()) >= size;
+		}
+		
+		@Override
+		public int length() {
+			return size;
+		}
+		
+		@Override
+		public DataAccess reset() throws IOException {
+			seek(0);
+			return this;
+		}
+		
+		@Override
+		public void seek(int offset) {
+			assert offset >= 0;
+			// offset may not necessarily be further than current position in the file (e.g. rewind) 
+			if (buffer != null && /*offset is within buffer*/ offset >= position && (offset - position) < buffer.limit()) {
+				buffer.position((int) (offset - position));
+			} else {
+				position = offset;
+				buffer = null;
+			}
+		}
+
+		@Override
+		public void skip(int bytes) throws IOException {
+			assert bytes >= 0;
+			if (buffer == null) {
+				position += bytes;
+				return;
+			}
+			if (buffer.remaining() > bytes) {
+				buffer.position(buffer.position() + bytes);
+			} else {
+				position += buffer.position() + bytes;
+				buffer = null;
+			}
+		}
+
+		private void fill() throws IOException {
+			if (buffer != null) {
+				position += buffer.position(); 
+			}
+			long left = size - position;
+			buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < memBufferSize ? left : memBufferSize);
+		}
+
+		@Override
+		public void readBytes(byte[] buf, int offset, int length) throws IOException {
+			if (buffer == null || !buffer.hasRemaining()) {
+				fill();
+			}
+			// XXX in fact, we may try to create a MappedByteBuffer of exactly length size here, and read right away
+			while (length > 0) {
+				int tail = buffer.remaining();
+				if (tail == 0) {
+					throw new IOException();
+				}
+				if (tail >= length) {
+					buffer.get(buf, offset, length);
+				} else {
+					buffer.get(buf, offset, tail);
+					fill();
+				}
+				offset += tail;
+				length -= tail;
+			}
+		}
+
+		@Override
+		public byte readByte() throws IOException {
+			if (buffer == null || !buffer.hasRemaining()) {
+				fill();
+			}
+			if (buffer.hasRemaining()) {
+				return buffer.get();
+			}
+			throw new IOException();
+		}
+
+		@Override
+		public void done() {
+			buffer = null;
+			if (fileChannel != null) {
+				try {
+					fileChannel.close();
+				} catch (IOException ex) {
+					ex.printStackTrace(); // log debug
+				}
+				fileChannel = null;
+			}
+		}
+	}
+
+	// (almost) regular file access - FileChannel and buffers.
+	private static class FileAccess extends DataAccess {
+		private FileChannel fileChannel;
+		private final int size;
+		private ByteBuffer buffer;
+		private int bufferStartInFile = 0; // offset of this.buffer in the file.
+
+		public FileAccess(FileChannel fc, int channelSize, int bufferSizeHint, boolean useDirect) {
+			fileChannel = fc;
+			size = channelSize;
+			final int capacity = size < bufferSizeHint ? size : bufferSizeHint;
+			buffer = useDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
+			buffer.flip(); // or .limit(0) to indicate it's empty
+		}
+		
+		@Override
+		public boolean isEmpty() {
+			return bufferStartInFile + buffer.position() >= size;
+		}
+		
+		@Override
+		public int length() {
+			return size;
+		}
+		
+		@Override
+		public DataAccess reset() throws IOException {
+			seek(0);
+			return this;
+		}
+		
+		@Override
+		public void seek(int offset) throws IOException {
+			if (offset > size) {
+				throw new IllegalArgumentException();
+			}
+			if (offset < bufferStartInFile + buffer.limit() && offset >= bufferStartInFile) {
+				buffer.position((int) (offset - bufferStartInFile));
+			} else {
+				// out of current buffer, invalidate it (force re-read) 
+				// XXX or ever re-read it right away?
+				bufferStartInFile = offset;
+				buffer.clear();
+				buffer.limit(0); // or .flip() to indicate we switch to reading
+				fileChannel.position(offset);
+			}
+		}
+
+		@Override
+		public void skip(int bytes) throws IOException {
+			final int newPos = buffer.position() + bytes;
+			if (newPos >= 0 && newPos < buffer.limit()) {
+				// no need to move file pointer, just rewind/seek buffer 
+				buffer.position(newPos);
+			} else {
+				//
+				seek(bufferStartInFile + newPos);
+			}
+		}
+
+		private boolean fill() throws IOException {
+			if (!buffer.hasRemaining()) {
+				bufferStartInFile += buffer.limit();
+				buffer.clear();
+				if (bufferStartInFile < size) { // just in case there'd be any exception on EOF, not -1 
+					fileChannel.read(buffer);
+					// may return -1 when EOF, but empty will reflect this, hence no explicit support here   
+				}
+				buffer.flip();
+			}
+			return buffer.hasRemaining();
+		}
+
+		@Override
+		public void readBytes(byte[] buf, int offset, int length) throws IOException {
+			if (!buffer.hasRemaining()) {
+				fill();
+			}
+			while (length > 0) {
+				int tail = buffer.remaining();
+				if (tail == 0) {
+					throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past isEmpty() == true are made.
+				}
+				if (tail >= length) {
+					buffer.get(buf, offset, length);
+				} else {
+					buffer.get(buf, offset, tail);
+					fill();
+				}
+				offset += tail;
+				length -= tail;
+			}
+		}
+
+		@Override
+		public byte readByte() throws IOException {
+			if (buffer.hasRemaining()) {
+				return buffer.get();
+			}
+			if (fill()) {
+				return buffer.get();
+			}
+			throw new IOException();
+		}
+
+		@Override
+		public void done() {
+			if (buffer != null) {
+				buffer = null;
+			}
+			if (fileChannel != null) {
+				try {
+					fileChannel.close();
+				} catch (IOException ex) {
+					ex.printStackTrace(); // log debug
+				}
+				fileChannel = null;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/DigestHelper.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2010-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.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.tmatesoft.hg.core.Nodeid;
+
+
+/**
+ * <pre>
+ * DigestHelper dh;
+ * dh.sha1(...).asHexString();
+ *  or 
+ * dh = dh.sha1(...);
+ * nodeid.equalsTo(dh.asBinary());
+ * </pre>
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class DigestHelper {
+	private MessageDigest sha1;
+	private byte[] digest;
+
+	public DigestHelper() {
+	}
+	
+	private MessageDigest getSHA1() {
+		if (sha1 == null) {
+			try {
+				sha1 = MessageDigest.getInstance("SHA-1");
+			} catch (NoSuchAlgorithmException ex) {
+				// could hardly happen, JDK from Sun always has sha1.
+				ex.printStackTrace(); // FIXME log error
+			}
+		}
+		return sha1;
+	}
+
+
+	public DigestHelper sha1(Nodeid nodeid1, Nodeid nodeid2, byte[] data) {
+		return sha1(nodeid1.toByteArray(), nodeid2.toByteArray(), data);
+	}
+
+	//  sha1_digest(min(p1,p2) ++ max(p1,p2) ++ final_text)
+	public DigestHelper sha1(byte[] nodeidParent1, byte[] nodeidParent2, byte[] data) {
+		MessageDigest alg = getSHA1();
+		if ((nodeidParent1[0] & 0x00FF) < (nodeidParent2[0] & 0x00FF)) { 
+			alg.update(nodeidParent1);
+			alg.update(nodeidParent2);
+		} else {
+			alg.update(nodeidParent2);
+			alg.update(nodeidParent1);
+		}
+		digest = alg.digest(data);
+		assert digest.length == 20;
+		return this;
+	}
+	
+	public String asHexString() {
+		if (digest == null) {
+			throw new IllegalStateException("Shall init with sha1() call first");
+		}
+		return toHexString(digest, 0, digest.length);
+	}
+	
+	// by reference, be careful not to modify (or #clone() if needed)
+	public byte[] asBinary() {
+		if (digest == null) {
+			throw new IllegalStateException("Shall init with sha1() call first");
+		}
+		return digest; 
+	}
+
+	// XXX perhaps, digest functions should throw an exception, as it's caller responsibility to deal with eof, etc
+	public DigestHelper sha1(InputStream is /*ByteBuffer*/) throws IOException {
+		MessageDigest alg = getSHA1();
+		byte[] buf = new byte[1024];
+		int c;
+		while ((c = is.read(buf)) != -1) {
+			alg.update(buf, 0, c);
+		}
+		digest = alg.digest();
+		return this;
+	}
+	
+	public DigestHelper sha1(CharSequence... seq) {
+		MessageDigest alg = getSHA1();
+		for (CharSequence s : seq) {
+			byte[] b = s.toString().getBytes();
+			alg.update(b);
+		}
+		digest = alg.digest();
+		return this;
+	}
+
+	public static String toHexString(byte[] data, final int offset, final int count) {
+		char[] result = new char[count << 1];
+		final String hexDigits = "0123456789abcdef";
+		final int end = offset+count;
+		for (int i = offset, j = 0; i < end; i++) {
+			result[j++] = hexDigits.charAt((data[i] >>> 4) & 0x0F);
+			result[j++] = hexDigits.charAt(data[i] & 0x0F);
+		}
+		return new String(result);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/Filter.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,57 @@
+/*
+ * 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.nio.ByteBuffer;
+
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface Filter {
+
+	// returns a buffer ready to be read. may return original buffer.
+	// original buffer may not be fully consumed, #compact() might be operation to perform 
+	ByteBuffer filter(ByteBuffer src);
+
+	interface Factory {
+		void initialize(HgRepository hgRepo, ConfigFile cfg);
+		// may return null if for a given path and/or options this filter doesn't make any sense
+		Filter create(Path path, Options opts);
+	}
+
+	enum Direction {
+		FromRepo, ToRepo
+	}
+
+	public class Options {
+
+		private final Direction direction;
+		public Options(Direction dir) {
+			direction = dir;
+		}
+		
+		Direction getDirection() {
+			return direction;
+		}
+
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/FilterByteChannel.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,66 @@
+/*
+ * 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.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+
+import org.tmatesoft.hg.util.Adaptable;
+import org.tmatesoft.hg.util.ByteChannel;
+import org.tmatesoft.hg.util.CancelledException;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class FilterByteChannel implements ByteChannel, Adaptable {
+	private final Filter[] filters;
+	private final ByteChannel delegate;
+	
+	public FilterByteChannel(ByteChannel delegateChannel, Collection<Filter> filtersToApply) {
+		if (delegateChannel == null || filtersToApply == null) {
+			throw new IllegalArgumentException();
+		}
+		delegate = delegateChannel;
+		filters = filtersToApply.toArray(new Filter[filtersToApply.size()]);
+	}
+
+	public int write(ByteBuffer buffer) throws IOException, CancelledException {
+		final int srcPos = buffer.position();
+		ByteBuffer processed = buffer;
+		for (Filter f : filters) {
+			// each next filter consumes not more than previous
+			// hence total consumed equals position shift in the original buffer
+			processed = f.filter(processed);
+		}
+		delegate.write(processed);
+		return buffer.position() - srcPos; // consumed as much from original buffer
+	}
+
+	// adapters or implemented interfaces of the original class shall not be obfuscated by filter
+	public <T> T getAdapter(Class<T> adapterClass) {
+		if (delegate instanceof Adaptable) {
+			return ((Adaptable) delegate).getAdapter(adapterClass);
+		}
+		if (adapterClass != null && adapterClass.isInstance(delegate)) {
+			return adapterClass.cast(delegate);
+		}
+		return null;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/FilterDataAccess.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,116 @@
+/*
+ * 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.IOException;
+
+
+/**
+ * XXX Perhaps, DataAccessSlice? Unlike FilterInputStream, we limit amount of data read from DataAccess being filtered.
+ *   
+ *   
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class FilterDataAccess extends DataAccess {
+	private final DataAccess dataAccess;
+	private final int offset;
+	private final int length;
+	private int count;
+
+	public FilterDataAccess(DataAccess dataAccess, int offset, int length) {
+		this.dataAccess = dataAccess;
+		this.offset = offset;
+		this.length = length;
+		count = length;
+	}
+
+	protected int available() {
+		return count;
+	}
+
+	@Override
+	public FilterDataAccess reset() throws IOException {
+		count = length;
+		return this;
+	}
+	
+	@Override
+	public boolean isEmpty() {
+		return count <= 0;
+	}
+	
+	@Override
+	public int length() {
+		return length;
+	}
+
+	@Override
+	public void seek(int localOffset) throws IOException {
+		if (localOffset < 0 || localOffset > length) {
+			throw new IllegalArgumentException();
+		}
+		dataAccess.seek(offset + localOffset);
+		count = (int) (length - localOffset);
+	}
+
+	@Override
+	public void skip(int bytes) throws IOException {
+		int newCount = count - bytes;
+		if (newCount < 0 || newCount > length) {
+			throw new IllegalArgumentException();
+		}
+		seek(length - newCount);
+		/*
+		 can't use next code because don't want to rewind backing DataAccess on reset()
+		 i.e. this.reset() modifies state of this instance only, while filtered DA may go further.
+		 Only actual this.skip/seek/read would rewind it to desired position 
+	  		dataAccess.skip(bytes);
+			count = newCount;
+		 */
+
+	}
+
+	@Override
+	public byte readByte() throws IOException {
+		if (count <= 0) {
+			throw new IllegalArgumentException("Underflow"); // XXX be descriptive
+		}
+		if (count == length) {
+			dataAccess.seek(offset);
+		}
+		count--;
+		return dataAccess.readByte();
+	}
+
+	@Override
+	public void readBytes(byte[] b, int off, int len) throws IOException {
+		if (len == 0) {
+			return;
+		}
+		if (count <= 0 || len > count) {
+			throw new IllegalArgumentException(String.format("Underflow. Bytes left: %d, asked to read %d", count, len));
+		}
+		if (count == length) {
+			dataAccess.seek(offset);
+		}
+		dataAccess.readBytes(b, off, len);
+		count -= len;
+	}
+
+	// done shall be no-op, as we have no idea what's going on with DataAccess we filter
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/InflaterDataAccess.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,185 @@
+/*
+ * 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.EOFException;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import java.util.zip.ZipException;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+
+
+/**
+ * DataAccess counterpart for InflaterInputStream.
+ * XXX is it really needed to be subclass of FilterDataAccess? 
+ *   
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class InflaterDataAccess extends FilterDataAccess {
+
+	private final Inflater inflater;
+	private final byte[] buffer;
+	private final byte[] singleByte = new byte[1];
+	private int decompressedPos = 0;
+	private int decompressedLength;
+
+	public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength) {
+		this(dataAccess, offset, compressedLength, -1, new Inflater(), 512);
+	}
+
+	public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength, int actualLength) {
+		this(dataAccess, offset, compressedLength, actualLength, new Inflater(), 512);
+	}
+
+	public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength, int actualLength, Inflater inflater, int bufSize) {
+		super(dataAccess, offset, compressedLength);
+		this.inflater = inflater;
+		this.decompressedLength = actualLength;
+		buffer = new byte[bufSize];
+	}
+	
+	@Override
+	public InflaterDataAccess reset() throws IOException {
+		super.reset();
+		inflater.reset();
+		decompressedPos = 0;
+		return this;
+	}
+	
+	@Override
+	protected int available() {
+		return length() - decompressedPos;
+	}
+	
+	@Override
+	public boolean isEmpty() {
+		// can't use super.available() <= 0 because even when 0 < super.count < 6(?)
+		// decompressedPos might be already == length() 
+		return available() <= 0;
+	}
+	
+	@Override
+	public int length() {
+		if (decompressedLength != -1) {
+			return decompressedLength;
+		}
+		decompressedLength = 0; // guard to avoid endless loop in case length() would get invoked from below. 
+		int c = 0;
+		try {
+			int oldPos = decompressedPos;
+			byte[] dummy = new byte[buffer.length];
+			int toRead;
+			while ((toRead = super.available()) > 0) {
+				if (toRead > buffer.length) {
+					toRead = buffer.length;
+				}
+				super.readBytes(buffer, 0, toRead);
+				inflater.setInput(buffer, 0, toRead);
+				try {
+					while (!inflater.needsInput()) {
+						c += inflater.inflate(dummy, 0, dummy.length);
+					}
+				} catch (DataFormatException ex) {
+					throw new HgBadStateException(ex);
+				}
+			}
+			decompressedLength = c + oldPos;
+			reset();
+			seek(oldPos);
+			return decompressedLength;
+		} catch (IOException ex) {
+			decompressedLength = -1; // better luck next time?
+			throw new HgBadStateException(ex); // XXX perhaps, checked exception
+		}
+	}
+	
+	@Override
+	public void seek(int localOffset) throws IOException {
+		if (localOffset < 0 /* || localOffset >= length() */) {
+			throw new IllegalArgumentException();
+		}
+		if (localOffset >= decompressedPos) {
+			skip((int) (localOffset - decompressedPos));
+		} else {
+			reset();
+			skip((int) localOffset);
+		}
+	}
+	
+	@Override
+	public void skip(int bytes) throws IOException {
+		if (bytes < 0) {
+			bytes += decompressedPos;
+			if (bytes < 0) {
+				throw new IOException("Underflow. Rewind past start of the slice.");
+			}
+			reset();
+			// fall-through
+		}
+		while (!isEmpty() && bytes > 0) {
+			readByte();
+			bytes--;
+		}
+		if (bytes != 0) {
+			throw new IOException("Underflow. Rewind past end of the slice");
+		}
+	}
+
+	@Override
+	public byte readByte() throws IOException {
+		readBytes(singleByte, 0, 1);
+		return singleByte[0];
+	}
+
+	@Override
+	public void readBytes(byte[] b, int off, int len) throws IOException {
+		try {
+		    int n;
+		    while (len > 0) {
+			    while ((n = inflater.inflate(b, off, len)) == 0) {
+			    	// FIXME few last bytes (checksum?) may be ignored by inflater, thus inflate may return 0 in
+			    	// perfectly legal conditions (when all data already expanded, but there are still some bytes
+			    	// in the input stream
+					if (inflater.finished() || inflater.needsDictionary()) {
+	                    throw new EOFException();
+					}
+					if (inflater.needsInput()) {
+						// fill:
+						int toRead = super.available();
+						if (toRead > buffer.length) {
+							toRead = buffer.length;
+						}
+						super.readBytes(buffer, 0, toRead);
+						inflater.setInput(buffer, 0, toRead);
+					}
+			    }
+				off += n;
+				len -= n;
+				decompressedPos += n;
+				if (len == 0) {
+					return; // filled
+				}
+		    }
+		} catch (DataFormatException e) {
+		    String s = e.getMessage();
+		    throw new ZipException(s != null ? s : "Invalid ZLIB data format");
+		}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/Internals.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,111 @@
+/*
+ * 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 static org.tmatesoft.hg.internal.RequiresFile.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ * Fields/members that shall not be visible  
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Internals {
+	
+	private int requiresFlags = 0;
+	private List<Filter.Factory> filterFactories;
+	
+
+	public Internals() {
+	}
+
+	public/*for tests, otherwise pkg*/ void setStorageConfig(int version, int flags) {
+		requiresFlags = flags;
+	}
+
+	// XXX perhaps, should keep both fields right here, not in the HgRepository
+	public PathRewrite buildDataFilesHelper() {
+		return new StoragePathHelper((requiresFlags & STORE) != 0, (requiresFlags & FNCACHE) != 0, (requiresFlags & DOTENCODE) != 0);
+	}
+
+	public PathRewrite buildRepositoryFilesHelper() {
+		if ((requiresFlags & STORE) != 0) {
+			return new PathRewrite() {
+				public String rewrite(String path) {
+					return "store/" + path;
+				}
+			};
+		} else {
+			return new PathRewrite() {
+				public String rewrite(String path) {
+					//no-op
+					return path;
+				}
+			};
+		}
+	}
+
+	public ConfigFile newConfigFile() {
+		return new ConfigFile();
+	}
+
+	public List<Filter.Factory> getFilters(HgRepository hgRepo, ConfigFile cfg) {
+		if (filterFactories == null) {
+			filterFactories = new ArrayList<Filter.Factory>();
+			if (cfg.hasEnabledExtension("eol")) {
+				NewlineFilter.Factory ff = new NewlineFilter.Factory();
+				ff.initialize(hgRepo, cfg);
+				filterFactories.add(ff);
+			}
+			if (cfg.hasEnabledExtension("keyword")) {
+				KeywordFilter.Factory ff = new KeywordFilter.Factory();
+				ff.initialize(hgRepo, cfg);
+				filterFactories.add(ff);
+			}
+		}
+		return filterFactories;
+	}
+	
+	public void initEmptyRepository(File hgDir) throws IOException {
+		hgDir.mkdir();
+		FileOutputStream requiresFile = new FileOutputStream(new File(hgDir, "requires"));
+		StringBuilder sb = new StringBuilder(40);
+		sb.append("revlogv1\n");
+		if ((requiresFlags & STORE) != 0) {
+			sb.append("store\n");
+		}
+		if ((requiresFlags & FNCACHE) != 0) {
+			sb.append("fncache\n");
+		}
+		if ((requiresFlags & DOTENCODE) != 0) {
+			sb.append("dotencode\n");
+		}
+		requiresFile.write(sb.toString().getBytes());
+		requiresFile.close();
+		new File(hgDir, "store").mkdir(); // with that, hg verify says ok.
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/KeywordFilter.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,323 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class KeywordFilter implements Filter {
+	// present implementation is stateless, however, filter use pattern shall not assume that. In fact, Factory may us that 
+	private final HgRepository repo;
+	private final boolean isExpanding;
+	private final TreeMap<String,String> keywords;
+	private final int minBufferLen;
+	private final Path path;
+	private RawChangeset latestFileCset;
+
+	/**
+	 * 
+	 * @param hgRepo 
+	 * @param path 
+	 * @param expand <code>true</code> to expand keywords, <code>false</code> to shrink
+	 */
+	private KeywordFilter(HgRepository hgRepo, Path p, boolean expand) {
+		repo = hgRepo;
+		path = p;
+		isExpanding = expand;
+		keywords = new TreeMap<String,String>();
+		keywords.put("Id", "Id");
+		keywords.put("Revision", "Revision");
+		keywords.put("Author", "Author");
+		keywords.put("Date", "Date");
+		keywords.put("LastChangedRevision", "LastChangedRevision");
+		keywords.put("LastChangedBy", "LastChangedBy");
+		keywords.put("LastChangedDate", "LastChangedDate");
+		keywords.put("Source", "Source");
+		keywords.put("Header", "Header");
+
+		int l = 0;
+		for (String s : keywords.keySet()) {
+			if (s.length() > l) {
+				l = s.length();
+			}
+		}
+		// FIXME later may implement #filter() not to read full kw value (just "$kw:"). However, limit of maxLen + 2 would keep valid.
+		// for buffers less then minBufferLen, there are chances #filter() implementation would never end
+		// (i.e. for input "$LongestKey"$
+		minBufferLen = l + 2 + (isExpanding ? 0 : 120 /*any reasonable constant for max possible kw value length*/);
+	}
+
+	/**
+	 * @param src buffer ready to be read
+	 * @return buffer ready to be read and original buffer's position modified to reflect consumed bytes. IOW, if source buffer
+	 * on return has remaining bytes, they are assumed not-read (not processed) and next chunk passed to filter is supposed to 
+	 * start with them  
+	 */
+	public ByteBuffer filter(ByteBuffer src) {
+		if (src.capacity() < minBufferLen) {
+			throw new IllegalStateException(String.format("Need buffer of at least %d bytes to ensure filter won't hang", minBufferLen));
+		}
+		ByteBuffer rv = null;
+		int keywordStart = -1;
+		int x = src.position();
+		int copyFrom = x; // needs to be updated each time we copy a slice, but not each time we modify source index (x)
+		while (x < src.limit()) {
+			if (keywordStart == -1) {
+				int i = indexOf(src, '$', x, false);
+				if (i == -1) {
+					if (rv == null) {
+						return src;
+					} else {
+						copySlice(src, copyFrom, src.limit(), rv);
+						rv.flip();
+						src.position(src.limit());
+						return rv;
+					}
+				}
+				keywordStart = i;
+				// fall-through
+			}
+			if (keywordStart >= 0) {
+				int i = indexOf(src, '$', keywordStart+1, true);
+				if (i == -1) {
+					// end of buffer reached
+					if (rv == null) {
+						if (keywordStart == x) {
+							// FIXME in fact, x might be equal to keywordStart and to src.position() here ('$' is first character in the buffer, 
+							// and there are no other '$' not eols till the end of the buffer). This would lead to deadlock (filter won't consume any
+							// bytes). To prevent this, either shall copy bytes [keywordStart..buffer.limit()) to local buffer and use it on the next invocation,
+							// or add lookup of the keywords right after first '$' is found (do not wait for closing '$'). For now, large enough src buffer would be sufficient
+							// not to run into such situation
+							throw new IllegalStateException("Try src buffer of a greater size");
+						}
+						rv = ByteBuffer.allocate(keywordStart - copyFrom);
+					}
+					// copy all from source till latest possible kw start 
+					copySlice(src, copyFrom, keywordStart, rv);
+					rv.flip();
+					// and tell caller we've consumed only to the potential kw start
+					src.position(keywordStart);
+					return rv;
+				} else if (src.get(i) == '$') {
+					// end of keyword, or start of a new one.
+					String keyword;
+					if ((keyword = matchKeyword(src, keywordStart, i)) != null) {
+						if (rv == null) {
+							// src.remaining(), not .capacity because src is not read, and remaining represents 
+							// actual bytes count, while capacity - potential.
+							// Factor of 4 is pure guess and a HACK, need to be fixed with re-expanding buffer on demand
+							rv = ByteBuffer.allocate(isExpanding ? src.remaining() * 4 : src.remaining());
+						}
+						copySlice(src, copyFrom, keywordStart+1, rv);
+						rv.put(keyword.getBytes());
+						if (isExpanding) {
+							rv.put((byte) ':');
+							rv.put((byte) ' ');
+							expandKeywordValue(keyword, rv);
+							rv.put((byte) ' ');
+						}
+						rv.put((byte) '$');
+						keywordStart = -1;
+						x = i+1;
+						copyFrom = x;
+						continue;
+					} else {
+						if (rv != null) {
+							// we've already did some substitution, thus need to copy bytes we've scanned. 
+							copySlice(src, x, i, rv);
+							copyFrom = i;
+						} // no else in attempt to avoid rv creation if no real kw would be found  
+						keywordStart = i;
+						x = i; // '$' at i wasn't consumed, hence x points to i, not i+1. This is to avoid problems with case: "sdfsd $ asdfs $Id$ sdf"
+						continue;
+					}
+				} else {
+					assert src.get(i) == '\n' || src.get(i) == '\r';
+					// line break
+					if (rv != null) {
+						copySlice(src, x, i+1, rv);
+						copyFrom = i+1;
+					}
+					x = i+1;
+					keywordStart = -1; // Wasn't keyword, really
+					continue; // try once again
+				}
+			}
+		}
+		if (keywordStart != -1) {
+			if (rv == null) {
+				// no expansion happened yet, and we have potential kw start
+				rv = ByteBuffer.allocate(keywordStart - src.position());
+				copySlice(src, src.position(), keywordStart, rv);
+			}
+			src.position(keywordStart);
+		}
+		if (rv != null) {
+			rv.flip();
+			return rv;
+		}
+		return src;
+	}
+	
+	/**
+	 * @param keyword
+	 * @param rv
+	 */
+	private void expandKeywordValue(String keyword, ByteBuffer rv) {
+		if ("Id".equals(keyword)) {
+			rv.put(identityString().getBytes());
+		} else if ("Revision".equals(keyword)) {
+			rv.put(revision().getBytes());
+		} else if ("Author".equals(keyword)) {
+			rv.put(username().getBytes());
+		} else if ("Date".equals(keyword)) {
+			rv.put(date().getBytes());
+		} else {
+			throw new IllegalStateException(String.format("Keyword %s is not yet supported", keyword));
+		}
+	}
+
+	private String matchKeyword(ByteBuffer src, int kwStart, int kwEnd) {
+		assert kwEnd - kwStart - 1 > 0;
+		assert src.get(kwStart) == src.get(kwEnd) && src.get(kwEnd) == '$';
+		char[] chars = new char[kwEnd - kwStart - 1];
+		int i;
+		for (i = 0; i < chars.length; i++) {
+			char c = (char) src.get(kwStart + 1 + i);
+			if (c == ':') {
+				break;
+			}
+			chars[i] = c;
+		}
+		String kw = new String(chars, 0, i);
+//		XXX may use subMap to look up keywords based on few available characters (not waiting till closing $)
+//		System.out.println(keywords.subMap("I", "J"));
+//		System.out.println(keywords.subMap("A", "B"));
+//		System.out.println(keywords.subMap("Au", "B"));
+		return keywords.get(kw);
+	}
+	
+	// copies part of the src buffer, [from..to). doesn't modify src position
+	static void copySlice(ByteBuffer src, int from, int to, ByteBuffer dst) {
+		if (to > src.limit()) {
+			throw new IllegalArgumentException("Bad right boundary");
+		}
+		if (dst.remaining() < to - from) {
+			throw new IllegalArgumentException("Not enough room in the destination buffer");
+		}
+		for (int i = from; i < to; i++) {
+			dst.put(src.get(i));
+		}
+	}
+
+	private static int indexOf(ByteBuffer b, char ch, int from, boolean newlineBreaks) {
+		for (int i = from; i < b.limit(); i++) {
+			byte c = b.get(i);
+			if (ch == c) {
+				return i;
+			}
+			if (newlineBreaks && (c == '\n' || c == '\r')) {
+				return i;
+			}
+		}
+		return -1;
+	}
+
+	private String identityString() {
+		return String.format("%s,v %s %s %s", path, revision(), date(), username());
+	}
+
+	private String revision() {
+		// FIXME add cset's nodeid into Changeset class 
+		int csetRev = repo.getFileNode(path).getChangesetLocalRevision(HgRepository.TIP);
+		return repo.getChangelog().getRevision(csetRev).shortNotation();
+	}
+	
+	private String username() {
+		return getChangeset().user();
+	}
+	
+	private String date() {
+		return String.format("%tY/%<tm/%<td %<tH:%<tM:%<tS", getChangeset().date());
+	}
+	
+	private RawChangeset getChangeset() {
+		if (latestFileCset == null) {
+			// XXX consider use of ChangelogHelper
+			int csetRev = repo.getFileNode(path).getChangesetLocalRevision(HgRepository.TIP);
+			latestFileCset = repo.getChangelog().range(csetRev, csetRev).get(0);
+		}
+		return latestFileCset;
+	}
+
+	public static class Factory implements Filter.Factory {
+		
+		private HgRepository repo;
+		private Path.Matcher matcher;
+
+		public void initialize(HgRepository hgRepo, ConfigFile cfg) {
+			repo = hgRepo;
+			ArrayList<String> patterns = new ArrayList<String>();
+			for (Map.Entry<String,String> e : cfg.getSection("keyword").entrySet()) {
+				if (!"ignore".equalsIgnoreCase(e.getValue())) {
+					patterns.add(e.getKey());
+				}
+			}
+			matcher = new PathGlobMatcher(patterns.toArray(new String[patterns.size()]));
+			// TODO read and respect keyword patterns from [keywordmaps]
+		}
+
+		public Filter create(Path path, Options opts) {
+			if (matcher.accept(path)) {
+				return new KeywordFilter(repo, path, opts.getDirection() == Filter.Direction.FromRepo);
+			}
+			return null;
+		}
+	}
+
+//
+//	public static void main(String[] args) throws Exception {
+//		FileInputStream fis = new FileInputStream(new File("/temp/kwoutput.txt"));
+//		FileOutputStream fos = new FileOutputStream(new File("/temp/kwoutput2.txt"));
+//		ByteBuffer b = ByteBuffer.allocate(256);
+//		KeywordFilter kwFilter = new KeywordFilter(false);
+//		while (fis.getChannel().read(b) != -1) {
+//			b.flip(); // get ready to be read
+//			ByteBuffer f = kwFilter.filter(b);
+//			fos.getChannel().write(f); // XXX in fact, f may not be fully consumed
+//			if (b.hasRemaining()) {
+//				b.compact();
+//			} else {
+//				b.clear();
+//			}
+//		}
+//		fis.close();
+//		fos.flush();
+//		fos.close();
+//	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/NewlineFilter.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,269 @@
+/*
+ * 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 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+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;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class NewlineFilter implements Filter {
+
+	// if allowInconsistent is true, filter simply pass incorrect newline characters (single \r or \r\n on *nix and single \n on Windows) as is,
+	// i.e. doesn't try to convert them into appropriate newline characters. XXX revisit if Keyword extension behaves differently
+	private final boolean allowInconsistent;
+	private final boolean winToNix;
+
+	private NewlineFilter(boolean failIfInconsistent, int transform) {
+		winToNix = transform == 0;
+		allowInconsistent = !failIfInconsistent;
+	}
+
+	public ByteBuffer filter(ByteBuffer src) {
+		if (winToNix) {
+			return win2nix(src);
+		} else {
+			return nix2win(src);
+		}
+	}
+
+	private ByteBuffer win2nix(ByteBuffer src) {
+		int x = src.position(); // source index
+		int lookupStart = x;
+		ByteBuffer dst = null;
+		while (x < src.limit()) {
+			// x, lookupStart, ir and in are absolute positions within src buffer, which is never read with modifying operations
+			int ir = indexOf('\r', src, lookupStart);
+			int in = indexOf('\n', src, lookupStart);
+			if (ir == -1) {
+				if (in == -1 || allowInconsistent) {
+					if (dst != null) {
+						copySlice(src, x, src.limit(), dst);
+						x = src.limit(); // consumed all
+					}
+					break;
+				} else {
+					fail(src, in);
+				}
+			}
+			// in == -1 while ir != -1 may be valid case if ir is the last char of the buffer, we check below for that
+			if (in != -1 && in != ir+1 && !allowInconsistent) {
+				fail(src, in);
+			}
+			if (dst == null) {
+				dst = ByteBuffer.allocate(src.remaining());
+			}
+			copySlice(src, x, ir, dst);
+			if (ir+1 == src.limit()) {
+				// last char of the buffer - 
+				// consume src till that char and let next iteration work on it
+				x = ir;
+				break;
+			}
+			if (in != ir + 1) {
+				x = ir+1; // generally in, but if allowInconsistent==true and \r is not followed by \n, then 
+				// cases like "one \r two \r\n three" shall be processed correctly (second pair would be ignored if x==in)
+				lookupStart = ir+1;
+			} else {
+				x = in;
+				lookupStart = x+1; // skip \n for next lookup
+			}
+		}
+		src.position(x); // mark we've consumed up to x
+		return dst == null ? src : (ByteBuffer) dst.flip();
+	}
+
+	private ByteBuffer nix2win(ByteBuffer src) {
+		int x = src.position();
+		ByteBuffer dst = null;
+		while (x < src.limit()) {
+			int in = indexOf('\n', src, x);
+			int ir = indexOf('\r', src, x, in == -1 ? src.limit() : in);
+			if (in == -1) {
+				if (ir == -1 || allowInconsistent) {
+					break;
+				} else {
+					fail(src, ir);
+				}
+			} else if (ir != -1 && !allowInconsistent) {
+				fail(src, ir);
+			}
+			
+			// x <= in < src.limit
+			// allowInconsistent && x <= ir < in   || ir == -1  
+			if (dst == null) {
+				// buffer full of \n grows as much as twice in size
+				dst = ByteBuffer.allocate(src.remaining() * 2);
+			}
+			copySlice(src, x, in, dst);
+			if (ir == -1 || ir+1 != in) {
+				dst.put((byte) '\r');
+			} // otherwise (ir!=-1 && ir+1==in) we found \r\n pair, don't convert to \r\r\n
+			// we may copy \n at src[in] on the next iteration, but would need extra lookupIndex variable then.
+			dst.put((byte) '\n');
+			x = in+1;
+		}
+		src.position(x);
+		return dst == null ? src : (ByteBuffer) dst.flip();
+	}
+
+
+	private void fail(ByteBuffer b, int pos) {
+		throw new RuntimeException(String.format("Inconsistent newline characters in the stream (char 0x%x, local index:%d)", b.get(pos), pos));
+	}
+
+	private static int indexOf(char ch, ByteBuffer b, int from) {
+		return indexOf(ch, b, from, b.limit());
+	}
+
+	// looks up in buf[from..to)
+	private static int indexOf(char ch, ByteBuffer b, int from, int to) {
+		for (int i = from; i < to; i++) {
+			byte c = b.get(i);
+			if (ch == c) {
+				return i;
+			}
+		}
+		return -1;
+	}
+
+	public static class Factory implements Filter.Factory {
+		private boolean failIfInconsistent = true;
+		private Path.Matcher lfMatcher;
+		private Path.Matcher crlfMatcher;
+		private Path.Matcher binMatcher;
+		private Path.Matcher nativeMatcher;
+		private String nativeRepoFormat;
+		private String nativeOSFormat;
+
+		public void initialize(HgRepository hgRepo, ConfigFile cfg) {
+			failIfInconsistent = cfg.getBoolean("eol", "only-consistent", true);
+			File cfgFile = new File(new HgInternals(hgRepo).getRepositoryDir().getParentFile(), ".hgeol");
+			if (!cfgFile.canRead()) {
+				return;
+			}
+			// XXX if .hgeol is not checked out, we may get it from repository
+//			HgDataFile cfgFileNode = hgRepo.getFileNode(".hgeol");
+//			if (!cfgFileNode.exists()) {
+//				return;
+//			}
+			// XXX perhaps, add HgDataFile.hasWorkingCopy and workingCopyContent()?
+			ConfigFile hgeol = new ConfigFile();
+			hgeol.addLocation(cfgFile);
+			nativeRepoFormat = hgeol.getSection("repository").get("native");
+			if (nativeRepoFormat == null) {
+				nativeRepoFormat = "LF";
+			}
+			final String os = System.getProperty("os.name"); // XXX need centralized set of properties
+			nativeOSFormat = os.indexOf("Windows") != -1 ? "CRLF" : "LF";
+			// I assume pattern ordering in .hgeol is not important
+			ArrayList<String> lfPatterns = new ArrayList<String>();
+			ArrayList<String> crlfPatterns = new ArrayList<String>();
+			ArrayList<String> nativePatterns = new ArrayList<String>();
+			ArrayList<String> binPatterns = new ArrayList<String>();
+			for (Map.Entry<String,String> e : hgeol.getSection("patterns").entrySet()) {
+				if ("CRLF".equals(e.getValue())) {
+					crlfPatterns.add(e.getKey());
+				} else if ("LF".equals(e.getValue())) {
+					lfPatterns.add(e.getKey());
+				} else if ("native".equals(e.getValue())) {
+					nativePatterns.add(e.getKey());
+				} 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
+				}
+			}
+			if (!crlfPatterns.isEmpty()) {
+				crlfMatcher = new PathGlobMatcher(crlfPatterns.toArray(new String[crlfPatterns.size()]));
+			}
+			if (!lfPatterns.isEmpty()) {
+				lfMatcher = new PathGlobMatcher(lfPatterns.toArray(new String[lfPatterns.size()]));
+			}
+			if (!binPatterns.isEmpty()) {
+				binMatcher = new PathGlobMatcher(binPatterns.toArray(new String[binPatterns.size()]));
+			}
+			if (!nativePatterns.isEmpty()) {
+				nativeMatcher = new PathGlobMatcher(nativePatterns.toArray(new String[nativePatterns.size()]));
+			}
+		}
+
+		public Filter create(Path path, Options opts) {
+			if (binMatcher == null && crlfMatcher == null && lfMatcher == null && nativeMatcher == null) {
+				// not initialized - perhaps, no .hgeol found
+				return null;
+			}
+			if (binMatcher != null && binMatcher.accept(path)) {
+				return null;
+			}
+			if (crlfMatcher != null && crlfMatcher.accept(path)) {
+				return new NewlineFilter(failIfInconsistent, 1);
+			} else if (lfMatcher != null && lfMatcher.accept(path)) {
+				return new NewlineFilter(failIfInconsistent, 0);
+			} else if (nativeMatcher != null && nativeMatcher.accept(path)) {
+				if (nativeOSFormat.equals(nativeRepoFormat)) {
+					return null;
+				}
+				if (opts.getDirection() == FromRepo) {
+					int transform = "CRLF".equals(nativeOSFormat) ? 1 : 0;
+					return new NewlineFilter(failIfInconsistent, transform);
+				} else if (opts.getDirection() == ToRepo) {
+					int transform = "CRLF".equals(nativeOSFormat) ? 0 : 1;
+					return new NewlineFilter(failIfInconsistent, transform);
+				}
+				return null;
+			}
+			return null;
+		}
+	}
+
+	public static void main(String[] args) throws Exception {
+		FileInputStream fis = new FileInputStream(new File("/temp/design.lf.txt"));
+		FileOutputStream fos = new FileOutputStream(new File("/temp/design.newline.out"));
+		ByteBuffer b = ByteBuffer.allocate(12);
+		NewlineFilter nlFilter = new NewlineFilter(true, 1);
+		while (fis.getChannel().read(b) != -1) {
+			b.flip(); // get ready to be read
+			ByteBuffer f = nlFilter.filter(b);
+			fos.getChannel().write(f); // XXX in fact, f may not be fully consumed
+			if (b.hasRemaining()) {
+				b.compact();
+			} else {
+				b.clear();
+			}
+		}
+		fis.close();
+		fos.flush();
+		fos.close();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/PathGlobMatcher.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,94 @@
+/*
+ * 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.util.regex.PatternSyntaxException;
+
+import org.tmatesoft.hg.util.Path;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class PathGlobMatcher implements Path.Matcher {
+	
+	private final PathRegexpMatcher delegate;
+	
+	/**
+	 * 
+	 * @param globPatterns
+	 * @throws NullPointerException if argument is null
+	 * @throws IllegalArgumentException if any of the patterns is not valid
+	 */
+	public PathGlobMatcher(String... globPatterns) {
+		String[] regexp = new String[globPatterns.length]; //deliberately let fail with NPE
+		int i = 0;
+		for (String s : globPatterns) {
+			regexp[i] = glob2regexp(s);
+		}
+		try {
+			delegate = new PathRegexpMatcher(regexp);
+		} catch (PatternSyntaxException ex) {
+			ex.printStackTrace();
+			throw new IllegalArgumentException(ex);
+		}
+	}
+	
+
+	// HgIgnore.glob2regex is similar, but IsIgnore solves slightly different task 
+	// (need to match partial paths, e.g. for glob 'bin' shall match not only 'bin' folder, but also any path below it,
+	// which is not generally the case
+	private static String glob2regexp(String glob) {
+		int end = glob.length() - 1;
+		boolean needLineEndMatch = glob.charAt(end) != '*';
+		while (end > 0 && glob.charAt(end) == '*') end--; // remove trailing * that are useless for Pattern.find()
+		StringBuilder sb = new StringBuilder(end*2);
+		if (glob.charAt(0) != '*') {
+			sb.append('^');
+		}
+		for (int i = 0; i <= end; i++) {
+			char ch = glob.charAt(i);
+			if (ch == '*') {
+				if (glob.charAt(i+1) == '*') { // i < end because we've stripped any trailing * earlier
+					// any char, including path segment separator
+					sb.append(".*?");
+					i++;
+				} else {
+					// just path segments
+					sb.append("[^/]*?");
+				}
+				continue;
+			} else if (ch == '?') {
+				sb.append("[^/]");
+				continue;
+			} else if (ch == '.' || ch == '\\') {
+				sb.append('\\');
+			}
+			sb.append(ch);
+		}
+		if (needLineEndMatch) {
+			sb.append('$');
+		}
+		return sb.toString();
+	}
+
+	public boolean accept(Path path) {
+		return delegate.accept(path);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/PathRegexpMatcher.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,64 @@
+/*
+ * 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.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.Path.Matcher;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class PathRegexpMatcher implements Matcher {
+	private Pattern[] patterns;
+	
+	// disjunction, matches if any pattern found
+	// uses pattern.find(), not pattern.matches()
+	public PathRegexpMatcher(Pattern... p) {
+		if (p == null) {
+			throw new IllegalArgumentException();
+		}
+		patterns = p;
+	}
+	
+	public PathRegexpMatcher(String... p) throws PatternSyntaxException {
+		this(compile(p));
+	}
+	
+	private static Pattern[] compile(String[] p) throws PatternSyntaxException {
+		// deliberately do no check for null, let it fail
+		Pattern[] rv = new Pattern[p.length];
+		int i = 0;
+		for (String s : p) {
+			rv[i++] = Pattern.compile(s);
+		}
+		return rv;
+	}
+
+	public boolean accept(Path path) {
+		for (Pattern p : patterns) {
+			if (p.matcher(path).find()) {
+				return true;
+			}
+		}
+		return false;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/Pool.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,53 @@
+/*
+ * 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.util.HashMap;
+
+/**
+ * Instance pooling.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Pool<T> {
+	private final HashMap<T,T> unify = new HashMap<T, T>();
+	
+	public T unify(T t) {
+		T rv = unify.get(t);
+		if (rv == null) {
+			// first time we see a new value
+			unify.put(t, t);
+			rv = t;
+		}
+		return rv;
+	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(Pool.class.getSimpleName());
+		sb.append('<');
+		if (!unify.isEmpty()) {
+			sb.append(unify.keySet().iterator().next().getClass().getName());
+		}
+		sb.append('>');
+		sb.append(':');
+		sb.append(unify.size());
+		return sb.toString();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/RelativePathRewrite.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,49 @@
+/*
+ * 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.File;
+
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class RelativePathRewrite implements PathRewrite {
+	
+	private final String rootPath;
+
+	public RelativePathRewrite(File root) {
+		this(root.getPath());
+	}
+	
+	public RelativePathRewrite(String rootPath) {
+		this.rootPath = rootPath;
+	}
+
+	public String rewrite(String path) {
+		if (path != null && path.startsWith(rootPath)) {
+			if (path.length() == rootPath.length()) {
+				return "";
+			}
+			return path.substring(rootPath.length() + 1);
+		}
+		return path;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/RepositoryComparator.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,570 @@
+/*
+ * 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 static org.tmatesoft.hg.core.Nodeid.NULL;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+import org.tmatesoft.hg.repo.HgRemoteRepository.Range;
+import org.tmatesoft.hg.repo.HgRemoteRepository.RemoteBranch;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.ProgressSupport;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class RepositoryComparator {
+
+	private final boolean debug = Boolean.parseBoolean(System.getProperty("hg4j.remote.debug"));
+	private final HgChangelog.ParentWalker localRepo;
+	private final HgRemoteRepository remoteRepo;
+	private List<Nodeid> common;
+
+	public RepositoryComparator(HgChangelog.ParentWalker pwLocal, HgRemoteRepository hgRemote) {
+		localRepo = pwLocal;
+		remoteRepo = hgRemote;
+	}
+	
+	public RepositoryComparator compare(Object context) throws HgException, CancelledException {
+		ProgressSupport progressSupport = ProgressSupport.Factory.get(context);
+		CancelSupport cancelSupport = CancelSupport.Factory.get(context);
+		cancelSupport.checkCancelled();
+		progressSupport.start(10);
+		common = Collections.unmodifiableList(findCommonWithRemote());
+		// sanity check
+		for (Nodeid n : common) {
+			if (!localRepo.knownNode(n)) {
+				throw new HgBadStateException("Unknown node reported as common:" + n);
+			}
+		}
+		progressSupport.done();
+		return this;
+	}
+	
+	public List<Nodeid> getCommon() {
+		if (common == null) {
+			throw new HgBadStateException("Call #compare(Object) first");
+		}
+		return common;
+	}
+	
+	/**
+	 * @return revisions that are children of common entries, i.e. revisions that are present on the local server and not on remote.
+	 */
+	public List<Nodeid> getLocalOnlyRevisions() {
+		return localRepo.childrenOf(getCommon());
+	}
+	
+	/**
+	 * Similar to @link {@link #getLocalOnlyRevisions()}, use this one if you need access to changelog entry content, not 
+	 * only its revision number. 
+	 * @param inspector delegate to analyze changesets, shall not be <code>null</code>
+	 */
+	public void visitLocalOnlyRevisions(HgChangelog.Inspector inspector) {
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		// one can use localRepo.childrenOf(getCommon()) and then iterate over nodeids, but there seems to be
+		// another approach to get all changes after common:
+		// find index of earliest revision, and report all that were later
+		final HgChangelog changelog = localRepo.getRepo().getChangelog();
+		int earliestRevision = Integer.MAX_VALUE;
+		List<Nodeid> commonKnown = getCommon();
+		for (Nodeid n : commonKnown) {
+			if (!localRepo.hasChildren(n)) {
+				// there might be (old) nodes, known both locally and remotely, with no children
+				// hence, we don't need to consider their local revision number
+				continue;
+			}
+			int lr = changelog.getLocalRevision(n);
+			if (lr < earliestRevision) {
+				earliestRevision = lr;
+			}
+		}
+		if (earliestRevision == Integer.MAX_VALUE) {
+			// either there are no common nodes (known locally and at remote)
+			// or no local children found (local is up to date). In former case, perhaps I shall bit return silently,
+			// but check for possible wrong repo comparison (hs says 'repository is unrelated' if I try to 
+			// check in/out for a repo that has no common nodes.
+			return;
+		}
+		if (earliestRevision < 0 || earliestRevision >= changelog.getLastRevision()) {
+			throw new HgBadStateException(String.format("Invalid index of common known revision: %d in total of %d", earliestRevision, 1+changelog.getLastRevision()));
+		}
+		changelog.range(earliestRevision+1, changelog.getLastRevision(), inspector);
+	}
+
+	private List<Nodeid> findCommonWithRemote() throws HgException {
+		List<Nodeid> remoteHeads = remoteRepo.heads();
+		LinkedList<Nodeid> resultCommon = new LinkedList<Nodeid>(); // these remotes are known in local
+		LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common
+		for (Nodeid rh : remoteHeads) {
+			if (localRepo.knownNode(rh)) {
+				resultCommon.add(rh);
+			} else {
+				toQuery.add(rh);
+			}
+		}
+		if (toQuery.isEmpty()) {
+			return resultCommon; 
+		}
+		LinkedList<RemoteBranch> checkUp2Head = new LinkedList<RemoteBranch>(); // branch.root and branch.head are of interest only.
+		// these are branches with unknown head but known root, which might not be the last common known,
+		// i.e. there might be children changeset that are also available at remote, [..?..common-head..remote-head] - need to 
+		// scroll up to common head.
+		while (!toQuery.isEmpty()) {
+			List<RemoteBranch> remoteBranches = remoteRepo.branches(toQuery);	//head, root, first parent, second parent
+			toQuery.clear();
+			while(!remoteBranches.isEmpty()) {
+				RemoteBranch rb = remoteBranches.remove(0);
+				// I assume branches remote call gives branches with head equal to what I pass there, i.e.
+				// that I don't need to check whether rb.head is unknown.
+				if (localRepo.knownNode(rb.root)) {
+					// we known branch start, common head is somewhere in its descendants line  
+					checkUp2Head.add(rb);
+				} else {
+					// dig deeper in the history, if necessary
+					if (!NULL.equals(rb.p1) && !localRepo.knownNode(rb.p1)) {
+						toQuery.add(rb.p1);
+					}
+					if (!NULL.equals(rb.p2) && !localRepo.knownNode(rb.p2)) {
+						toQuery.add(rb.p2);
+					}
+				}
+			}
+		}
+		// can't check nodes between checkUp2Head element and local heads, remote might have distinct descendants sequence
+		for (RemoteBranch rb : checkUp2Head) {
+			// rb.root is known locally
+			List<Nodeid> remoteRevisions = remoteRepo.between(rb.head, rb.root);
+			if (remoteRevisions.isEmpty()) {
+				// head is immediate child
+				resultCommon.add(rb.root);
+			} else {
+				// between gives result from head to root, I'd like to go in reverse direction
+				Collections.reverse(remoteRevisions);
+				Nodeid root = rb.root;
+				while(!remoteRevisions.isEmpty()) {
+					Nodeid n = remoteRevisions.remove(0);
+					if (localRepo.knownNode(n)) {
+						if (remoteRevisions.isEmpty()) {
+							// this is the last known node before an unknown
+							resultCommon.add(n);
+							break;
+						}
+						if (remoteRevisions.size() == 1) {
+							// there's only one left between known n and unknown head
+							// this check is to save extra between query, not really essential
+							Nodeid last = remoteRevisions.remove(0);
+							resultCommon.add(localRepo.knownNode(last) ? last : n);
+							break;
+						}
+						// might get handy for next between query, to narrow search down
+						root = n;
+					} else {
+						remoteRevisions = remoteRepo.between(n, root);
+						Collections.reverse(remoteRevisions);
+						if (remoteRevisions.isEmpty()) {
+							resultCommon.add(root);
+						}
+					}
+				}
+			}
+		}
+		// TODO ensure unique elements in the list
+		return resultCommon;
+	}
+
+	// somewhat similar to Outgoing.findCommonWithRemote() 
+	public List<BranchChain> calculateMissingBranches() throws HgException {
+		List<Nodeid> remoteHeads = remoteRepo.heads();
+		LinkedList<Nodeid> common = new LinkedList<Nodeid>(); // these remotes are known in local
+		LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common
+		for (Nodeid rh : remoteHeads) {
+			if (localRepo.knownNode(rh)) {
+				common.add(rh);
+			} else {
+				toQuery.add(rh);
+			}
+		}
+		if (toQuery.isEmpty()) {
+			return Collections.emptyList(); // no incoming changes
+		}
+		LinkedList<BranchChain> branches2load = new LinkedList<BranchChain>(); // return value
+		// detailed comments are in Outgoing.findCommonWithRemote
+		LinkedList<RemoteBranch> checkUp2Head = new LinkedList<RemoteBranch>();
+		// records relation between branch head and its parent branch, if any
+		HashMap<Nodeid, BranchChain> head2chain = new HashMap<Nodeid, BranchChain>();
+		while (!toQuery.isEmpty()) {
+			List<RemoteBranch> remoteBranches = remoteRepo.branches(toQuery);	//head, root, first parent, second parent
+			toQuery.clear();
+			while(!remoteBranches.isEmpty()) {
+				RemoteBranch rb = remoteBranches.remove(0);
+				BranchChain chainElement = head2chain.get(rb.head);
+				if (chainElement == null) {
+					chainElement = new BranchChain(rb.head);
+					// record this unknown branch to download later
+					branches2load.add(chainElement);
+					// the only chance we'll need chainElement in the head2chain is when we know this branch's root 
+					head2chain.put(rb.head, chainElement);
+				}
+				if (localRepo.knownNode(rb.root)) {
+					// we known branch start, common head is somewhere in its descendants line  
+					checkUp2Head.add(rb);
+				} else {
+					chainElement.branchRoot = rb.root;
+					// dig deeper in the history, if necessary
+					boolean hasP1 = !NULL.equals(rb.p1), hasP2 = !NULL.equals(rb.p2);  
+					if (hasP1 && !localRepo.knownNode(rb.p1)) {
+						toQuery.add(rb.p1);
+						// we might have seen parent node already, and recorded it as a branch chain
+						// we shall reuse existing BC to get it completely initializer (head2chain map
+						// on second put with the same key would leave first BC uninitialized.
+						
+						// It seems there's no reason to be affraid (XXX although shall double-check)
+						// that BC's chain would get corrupt (its p1 and p2 fields assigned twice with different values)
+						// as parents are always the same (and likely, BC that is common would be the last unknown)
+						BranchChain bc = head2chain.get(rb.p1);
+						if (bc == null) {
+							head2chain.put(rb.p1, bc = new BranchChain(rb.p1));
+						}
+						chainElement.p1 = bc;
+					}
+					if (hasP2 && !localRepo.knownNode(rb.p2)) {
+						toQuery.add(rb.p2);
+						BranchChain bc = head2chain.get(rb.p2);
+						if (bc == null) {
+							head2chain.put(rb.p2, bc = new BranchChain(rb.p2));
+						}
+						chainElement.p2 = bc;
+					}
+					if (!hasP1 && !hasP2) {
+						// special case, when we do incoming against blank repository, chainElement.branchRoot
+						// is first unknown element (revision 0). We need to add another fake BranchChain
+						// to fill the promise that terminal BranchChain has branchRoot that is known both locally and remotely
+						BranchChain fake = new BranchChain(NULL);
+						fake.branchRoot = NULL;
+						chainElement.p1 = chainElement.p2 = fake;
+					}
+				}
+			}
+		}
+		for (RemoteBranch rb : checkUp2Head) {
+			Nodeid h = rb.head;
+			Nodeid r = rb.root;
+			int watchdog = 1000;
+			assert head2chain.containsKey(h);
+			BranchChain bc = head2chain.get(h);
+			assert bc != null : h.toString();
+			// if we know branch root locally, there could be no parent branch chain elements.
+			assert bc.p1 == null;
+			assert bc.p2 == null;
+			do {
+				List<Nodeid> between = remoteRepo.between(h, r);
+				if (between.isEmpty()) {
+					bc.branchRoot = r;
+					break;
+				} else {
+					Collections.reverse(between);
+					for (Nodeid n : between) {
+						if (localRepo.knownNode(n)) {
+							r = n;
+						} else {
+							h = n;
+							break;
+						}
+					}
+					Nodeid lastInBetween = between.get(between.size() - 1);
+					if (r.equals(lastInBetween)) {
+						bc.branchRoot = r;
+						break;
+					} else if (h.equals(lastInBetween)) { // the only chance for current head pointer to point to the sequence tail
+						// is when r is second from the between list end (iow, head,1,[2],4,8...,root)
+						bc.branchRoot = r;
+						break;
+					}
+				}
+			} while(--watchdog > 0);
+			if (watchdog == 0) {
+				throw new HgBadStateException(String.format("Can't narrow down branch [%s, %s]", rb.head.shortNotation(), rb.root.shortNotation()));
+			}
+		}
+		if (debug) {
+			System.out.println("calculateMissingBranches:");
+			for (BranchChain bc : branches2load) {
+				bc.dump();
+			}
+		}
+		return branches2load;
+	}
+
+	// root and head (and all between) are unknown for each chain element but last (terminal), which has known root (revision
+	// known to be locally and at remote server
+	// alternative would be to keep only unknown elements (so that promise of calculateMissingBranches would be 100% true), but that 
+	// seems to complicate the method, while being useful only for the case when we ask incoming for an empty repository (i.e.
+	// where branch chain return all nodes, -1..tip.
+	public static final class BranchChain {
+		// when we construct a chain, we know head which is missing locally, hence init it right away.
+		// as for root (branch unknown start), we might happen to have one locally, and need further digging to find out right branch start  
+		public final Nodeid branchHead;
+		public Nodeid branchRoot;
+		// either of these can be null, or both.
+		// although RemoteBranch has either both parents null, or both non-null, when we construct a chain
+		// we might encounter that we locally know one of branch's parent, hence in the chain corresponding field will be blank.
+		public BranchChain p1;
+		public BranchChain p2;
+
+		public BranchChain(Nodeid head) {
+			assert head != null;
+			branchHead = head;
+		}
+		public boolean isTerminal() {
+			return p1 == null && p2 == null; // either can be null, see comment above. Terminal is only when no way to descent
+		}
+		
+		// true when this BranchChain is a branch that spans up to very start of the repository
+		// Thus, the only common revision is NULL, recorded in a fake BranchChain object shared between p1 and p2
+		/*package-local*/ boolean isRepoStart() {
+			return p1 == p2 && p1 != null && p1.branchHead == p1.branchRoot && NULL.equals(p1.branchHead);
+		}
+
+		@Override
+		public String toString() {
+			return String.format("BranchChain [%s, %s]", branchRoot, branchHead);
+		}
+
+		void dump() {
+			System.out.println(toString());
+			internalDump("  ");
+		}
+
+		private void internalDump(String prefix) {
+			if (p1 != null) {
+				System.out.println(prefix + p1.toString());
+			} else if (p2 != null) {
+				System.out.println(prefix + "NONE?!");
+			}
+			if (p2 != null) {
+				System.out.println(prefix + p2.toString());
+			} else if (p1 != null) {
+				System.out.println(prefix + "NONE?!");
+			}
+			prefix += "  ";
+			if (p1 != null) {
+				p1.internalDump(prefix);
+			}
+			if (p2 != null) {
+				p2.internalDump(prefix);
+			}
+		}
+	}
+
+	/**
+	 * @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 {
+		class DataEntry {
+			public final Nodeid queryHead;
+			public final int headIndex;
+			public List<Nodeid> entries;
+
+			public DataEntry(Nodeid head, int index, List<Nodeid> data) {
+				queryHead = head;
+				headIndex = index;
+				entries = data;
+			}
+		};
+
+		List<Nodeid> initial = remoteRepo.between(branchHead, branchRoot);
+		Nodeid[] result = new Nodeid[1 + (1 << initial.size())];
+		result[0] = branchHead;
+		int rootIndex = -1; // index in the result, where to place branche's root.
+		if (initial.isEmpty()) {
+			rootIndex = 1;
+		} else if (initial.size() == 1) {
+			rootIndex = 2;
+		}
+		LinkedList<DataEntry> datas = new LinkedList<DataEntry>();
+		// DataEntry in datas has entries list filled with 'between' data, whereas 
+		// DataEntry in toQuery keeps only nodeid and its index, with entries to be initialized before 
+		// moving to datas. 
+		LinkedList<DataEntry> toQuery = new LinkedList<DataEntry>();
+		//
+		datas.add(new DataEntry(branchHead, 0, initial));
+		int totalQueries = 1;
+		HashSet<Nodeid> queried = new HashSet<Nodeid>();
+		while(!datas.isEmpty()) {
+			// keep record of those planned to be queried next time we call between()
+			// although may keep these in queried, if really don't want separate collection
+			HashSet<Nodeid> scheduled = new HashSet<Nodeid>();  
+			do {
+				DataEntry de = datas.removeFirst();
+				// populate result with discovered elements between de.qiueryRoot and branch's head
+				for (int i = 1, j = 0; j < de.entries.size(); i = i << 1, j++) {
+					int idx = de.headIndex + i;
+					result[idx] = de.entries.get(j);
+				}
+				// form next query entries from new unknown elements
+				if (de.entries.size() > 1) {
+					/* when entries has only one element, it means de.queryRoot was at head-2 position, and thus
+					 * no new information can be obtained. E.g. when it's 2, it might be case of [0..4] query with
+					 * [1,2] result, and we need one more query to get element 3.   
+					 */
+					for (int i =1, j = 0; j < de.entries.size(); i = i<<1, j++) {
+						int idx = de.headIndex + i;
+						Nodeid x = de.entries.get(j);
+						if (!queried.contains(x) && !scheduled.contains(x) && (rootIndex == -1 || rootIndex - de.headIndex > 1)) {
+							/*queries for elements right before head is senseless, but unless we know head's index, do it anyway*/
+							toQuery.add(new DataEntry(x, idx, null));
+							scheduled.add(x);
+						}
+					}
+				}
+			} while (!datas.isEmpty());
+			if (!toQuery.isEmpty()) {
+				totalQueries++;
+			}
+			// for each query, create an between request range, keep record Range->DataEntry to know range's start index  
+			LinkedList<HgRemoteRepository.Range> betweenBatch = new LinkedList<HgRemoteRepository.Range>();
+			HashMap<HgRemoteRepository.Range, DataEntry> rangeToEntry = new HashMap<HgRemoteRepository.Range, DataEntry>();
+			for (DataEntry de : toQuery) {
+				queried.add(de.queryHead);
+				HgRemoteRepository.Range r = new HgRemoteRepository.Range(branchRoot, de.queryHead);
+				betweenBatch.add(r);
+				rangeToEntry.put(r, de);
+			}
+			if (!betweenBatch.isEmpty()) {
+				Map<Range, List<Nodeid>> between = remoteRepo.between(betweenBatch);
+				for (Entry<Range, List<Nodeid>> e : between.entrySet()) {
+					DataEntry de = rangeToEntry.get(e.getKey());
+					assert de != null;
+					de.entries = e.getValue();
+					if (rootIndex == -1 && de.entries.size() == 1) {
+						// returned sequence of length 1 means we used element from [head-2] as root
+						int numberOfElementsExcludingRootAndHead = de.headIndex + 1;
+						rootIndex = numberOfElementsExcludingRootAndHead + 1;
+						if (debug) {
+							System.out.printf("On query %d found out exact number of missing elements: %d\n", totalQueries, numberOfElementsExcludingRootAndHead);
+						}
+					}
+					datas.add(de); // queue up to record result and construct further requests
+				}
+				betweenBatch.clear();
+				rangeToEntry.clear();
+			}
+			toQuery.clear();
+		}
+		if (rootIndex == -1) {
+			throw new HgBadStateException("Shall not happen, provided between output is correct"); // FIXME
+		}
+		result[rootIndex] = branchRoot;
+		boolean resultOk = true;
+		LinkedList<Nodeid> fromRootToHead = new LinkedList<Nodeid>();
+		for (int i = 0; i <= rootIndex; i++) {
+			Nodeid n = result[i];
+			if (n == null) {
+				System.out.printf("ERROR: element %d wasn't found\n",i);
+				resultOk = false;
+			}
+			fromRootToHead.addFirst(n); // reverse order
+		}
+		if (debug) {
+			System.out.println("Total queries:" + totalQueries);
+		}
+		if (!resultOk) {
+			throw new HgBadStateException("See console for details"); // FIXME
+		}
+		return fromRootToHead;
+	}
+
+	/**
+	 *  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 {
+		if (bc == null) {
+			return Collections.emptyList();
+		}
+		List<Nodeid> mine = completeBranch(bc.branchRoot, bc.branchHead);
+		if (bc.isTerminal() || bc.isRepoStart()) {
+			return mine;
+		}
+		List<Nodeid> parentBranch1 = visitBranches(bc.p1);
+		List<Nodeid> parentBranch2 = visitBranches(bc.p2);
+		// merge
+		LinkedList<Nodeid> merged = new LinkedList<Nodeid>();
+		ListIterator<Nodeid> i1 = parentBranch1.listIterator(), i2 = parentBranch2.listIterator();
+		while (i1.hasNext() && i2.hasNext()) {
+			Nodeid n1 = i1.next();
+			Nodeid n2 = i2.next();
+			if (n1.equals(n2)) {
+				merged.addLast(n1);
+			} else {
+				// first different => add both, and continue adding both tails sequentially 
+				merged.add(n2);
+				merged.add(n1);
+				break;
+			}
+		}
+		// copy rest of second parent branch
+		while (i2.hasNext()) {
+			merged.add(i2.next());
+		}
+		// copy rest of first parent branch
+		while (i1.hasNext()) {
+			merged.add(i1.next());
+		}
+		//
+		ArrayList<Nodeid> rv = new ArrayList<Nodeid>(mine.size() + merged.size());
+		rv.addAll(merged);
+		rv.addAll(mine);
+		return rv;
+	}
+
+	public void collectKnownRoots(BranchChain bc, Set<Nodeid> result) {
+		if (bc == null) {
+			return;
+		}
+		if (bc.isTerminal()) {
+			result.add(bc.branchRoot);
+			return;
+		}
+		if (bc.isRepoStart()) {
+			return;
+		}
+		collectKnownRoots(bc.p1, result);
+		collectKnownRoots(bc.p2, result);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/RequiresFile.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,64 @@
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class RequiresFile {
+	public static final int STORE = 1;
+	public static final int FNCACHE = 2;
+	public static final int DOTENCODE = 4;
+	
+	public RequiresFile() {
+	}
+
+	public void parse(Internals repoImpl, File requiresFile) {
+		if (!requiresFile.exists()) {
+			return;
+		}
+		try {
+			boolean revlogv1 = false;
+			boolean store = false;
+			boolean fncache = false;
+			boolean dotencode = false;
+			BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(requiresFile)));
+			String line;
+			while ((line = br.readLine()) != null) {
+				revlogv1 |= "revlogv1".equals(line);
+				store |= "store".equals(line);
+				fncache |= "fncache".equals(line);
+				dotencode |= "dotencode".equals(line);
+			}
+			int flags = 0;
+			flags += store ? STORE : 0;
+			flags += fncache ? FNCACHE : 0;
+			flags += dotencode ? DOTENCODE : 0;
+			repoImpl.setStorageConfig(revlogv1 ? 1 : 0, flags);
+		} catch (IOException ex) {
+			ex.printStackTrace(); // FIXME log
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/RevlogDump.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2010-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.BufferedInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.math.BigInteger;
+import java.util.zip.Inflater;
+
+/**
+ * Utility to test/debug/troubleshoot
+ *  
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class RevlogDump {
+
+	/**
+	 * Takes 3 command line arguments - 
+	 *   repository path, 
+	 *   path to index file (i.e. store/data/hello.c.i) in the repository (relative) 
+	 *   and "dumpData" whether to print actual content or just revlog headers 
+	 */
+	public static void main(String[] args) throws Exception {
+		String repo = "/temp/hg/hello/.hg/";
+		String filename = "store/00changelog.i";
+//		String filename = "store/data/hello.c.i";
+//		String filename = "store/data/docs/readme.i";
+		boolean dumpData = true;
+		if (args.length > 1) {
+			repo = args[0];
+			filename = args[1];
+			dumpData = args.length > 2 ? "dumpData".equals(args[2]) : false;
+		}
+		//
+		DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File(repo + filename))));
+		DataInput di = dis;
+		dis.mark(10);
+		int versionField = di.readInt();
+		dis.reset();
+		final int INLINEDATA = 1 << 16;
+		
+		boolean inlineData = (versionField & INLINEDATA) != 0;
+		System.out.printf("%#8x, inline: %b\n", versionField, inlineData);
+		System.out.println("Index    Offset      Flags     Packed     Actual   Base Rev   Link Rev  Parent1  Parent2     nodeid");
+		int entryCount = 0;
+		while (dis.available() > 0) {
+			long l = di.readLong();
+			long offset = l >>> 16;
+			int flags = (int) (l & 0X0FFFF);
+			int compressedLen = di.readInt();
+			int actualLen = di.readInt();
+			int baseRevision = di.readInt();
+			int linkRevision = di.readInt();
+			int parent1Revision = di.readInt();
+			int parent2Revision = di.readInt();
+			byte[] buf = new byte[32];
+			di.readFully(buf, 12, 20);
+			dis.skipBytes(12); 
+			// CAN'T USE skip() here without extra precautions. E.g. I ran into situation when 
+			// buffer was 8192 and BufferedInputStream was at position 8182 before attempt to skip(12). 
+			// BIS silently skips available bytes and leaves me two extra bytes that ruin the rest of the code.
+			System.out.printf("%4d:%14d %6X %10d %10d %10d %10d %8d %8d     %040x\n", entryCount, offset, flags, compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, new BigInteger(buf));
+			if (inlineData) {
+				String resultString;
+				byte[] data = new byte[compressedLen];
+				di.readFully(data);
+				if (data[0] == 0x78 /* 'x' */) {
+					Inflater zlib = new Inflater();
+					zlib.setInput(data, 0, compressedLen);
+					byte[] result = new byte[actualLen*2];
+					int resultLen = zlib.inflate(result);
+					zlib.end();
+					resultString = new String(result, 0, resultLen, "UTF-8");
+				} else if (data[0] == 0x75 /* 'u' */) {
+					resultString = new String(data, 1, data.length - 1, "UTF-8");
+				} else {
+					resultString = new String(data);
+				}
+				if (dumpData) { 
+					System.out.println(resultString);
+				}
+			}
+			entryCount++;
+		}
+		dis.close();
+		//
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/RevlogStream.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,460 @@
+/*
+ * Copyright (c) 2010-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 static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgRepository;
+
+
+/**
+ * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), 
+ * or numerous RevlogStream with separate representation of the underlying data (cached, lazy ChunkStream)?
+ * 
+ * @see http://mercurial.selenic.com/wiki/Revlog
+ * @see http://mercurial.selenic.com/wiki/RevlogNG
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class RevlogStream {
+
+	/*
+	 * makes sense for index with inline data only - actual offset of the record in the .i file (record entry + revision * record size))
+	 * 
+	 * long[] in fact (there are 8-bytes field in the revlog)
+	 * However, (a) DataAccess currently doesn't operate with long seek/length
+	 * and, of greater significance, (b) files with inlined data are designated for smaller files,  
+	 * guess, about 130 Kb, and offset there won't ever break int capacity
+	 */
+	private int[] indexRecordOffset;  
+	private int[] baseRevisions;
+	private boolean inline = false;
+	private final File indexFile;
+	private final DataAccessProvider dataAccess;
+
+	// if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP.
+	public RevlogStream(DataAccessProvider dap, File indexFile) {
+		this.dataAccess = dap;
+		this.indexFile = indexFile;
+	}
+
+	/*package*/ DataAccess getIndexStream() {
+		return dataAccess.create(indexFile);
+	}
+
+	/*package*/ DataAccess getDataStream() {
+		final String indexName = indexFile.getName();
+		File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d");
+		return dataAccess.create(dataFile);
+	}
+	
+	public int revisionCount() {
+		initOutline();
+		return baseRevisions.length;
+	}
+	
+	public int dataLength(int revision) {
+		// XXX in fact, use of iterate() instead of this implementation may be quite reasonable.
+		//
+		final int indexSize = revisionCount();
+		DataAccess daIndex = getIndexStream(); // XXX may supply a hint that I'll need really few bytes of data (although at some offset)
+		if (revision == TIP) {
+			revision = indexSize - 1;
+		}
+		try {
+			int recordOffset = getIndexOffsetInt(revision);
+			daIndex.seek(recordOffset + 12); // 6+2+4
+			int actualLen = daIndex.readInt();
+			return actualLen; 
+		} catch (IOException ex) {
+			ex.printStackTrace(); // log error. FIXME better handling
+			throw new IllegalStateException(ex);
+		} finally {
+			daIndex.done();
+		}
+	}
+	
+	public byte[] nodeid(int revision) {
+		final int indexSize = revisionCount();
+		if (revision == TIP) {
+			revision = indexSize - 1;
+		}
+		if (revision < 0 || revision >= indexSize) {
+			throw new IllegalArgumentException(Integer.toString(revision));
+		}
+		DataAccess daIndex = getIndexStream();
+		try {
+			int recordOffset = getIndexOffsetInt(revision);
+			daIndex.seek(recordOffset + 32);
+			byte[] rv = new byte[20];
+			daIndex.readBytes(rv, 0, 20);
+			return rv;
+		} catch (IOException ex) {
+			ex.printStackTrace();
+			throw new IllegalStateException();
+		} finally {
+			daIndex.done();
+		}
+	}
+	
+	public int linkRevision(int revision) {
+		final int last = revisionCount() - 1;
+		if (revision == TIP) {
+			revision = last;
+		}
+		if (revision < 0 || revision > last) {
+			throw new IllegalArgumentException(Integer.toString(revision));
+		}
+		DataAccess daIndex = getIndexStream();
+		try {
+			int recordOffset = getIndexOffsetInt(revision);
+			daIndex.seek(recordOffset + 20);
+			int linkRev = daIndex.readInt();
+			return linkRev;
+		} catch (IOException ex) {
+			ex.printStackTrace();
+			throw new IllegalStateException();
+		} finally {
+			daIndex.done();
+		}
+	}
+	
+	// Perhaps, RevlogStream should be limited to use of plain int revisions for access,
+	// while Nodeids should be kept on the level up, in Revlog. Guess, Revlog better keep
+	// map of nodeids, and once this comes true, we may get rid of this method.
+	// Unlike its counterpart, {@link Revlog#getLocalRevisionNumber()}, doesn't fail with exception if node not found,
+	/**
+	 * @return integer in [0..revisionCount()) or {@link HgRepository#BAD_REVISION} if not found
+	 */
+	public int findLocalRevisionNumber(Nodeid nodeid) {
+		// XXX this one may be implemented with iterate() once there's mechanism to stop iterations
+		final int indexSize = revisionCount();
+		DataAccess daIndex = getIndexStream();
+		try {
+			byte[] nodeidBuf = new byte[20];
+			for (int i = 0; i < indexSize; i++) {
+				daIndex.skip(8);
+				int compressedLen = daIndex.readInt();
+				daIndex.skip(20);
+				daIndex.readBytes(nodeidBuf, 0, 20);
+				if (nodeid.equalsTo(nodeidBuf)) {
+					return i;
+				}
+				daIndex.skip(inline ? 12 + compressedLen : 12);
+			}
+		} catch (IOException ex) {
+			ex.printStackTrace(); // log error. FIXME better handling
+			throw new IllegalStateException(ex);
+		} finally {
+			daIndex.done();
+		}
+		return BAD_REVISION;
+	}
+
+
+	private final int REVLOGV1_RECORD_SIZE = 64;
+
+	// should be possible to use TIP, ALL, or -1, -2, -n notation of Hg
+	// ? boolean needsNodeid
+	public void iterate(int start, int end, boolean needData, Inspector inspector) {
+		initOutline();
+		final int indexSize = revisionCount();
+		if (indexSize == 0) {
+			return;
+		}
+		if (end == TIP) {
+			end = indexSize - 1;
+		}
+		if (start == TIP) {
+			start = indexSize - 1;
+		}
+		if (start < 0 || start >= indexSize) {
+			throw new IllegalArgumentException(String.format("Bad left range boundary %d in [0..%d]", start, indexSize-1));
+		}
+		if (end < start || end >= indexSize) {
+			throw new IllegalArgumentException(String.format("Bad right range boundary %d in [0..%d]", end, indexSize-1));
+		}
+		// XXX may cache [start .. end] from index with a single read (pre-read)
+		
+		DataAccess daIndex = null, daData = null;
+		daIndex = getIndexStream();
+		if (needData && !inline) {
+			daData = getDataStream();
+		}
+		try {
+			byte[] nodeidBuf = new byte[20];
+			DataAccess lastUserData = null;
+			int i;
+			boolean extraReadsToBaseRev = false;
+			if (needData && getBaseRevision(start) < start) {
+				i = getBaseRevision(start);
+				extraReadsToBaseRev = true;
+			} else {
+				i = start;
+			}
+			
+			daIndex.seek(getIndexOffsetInt(i));
+			for (; i <= end; i++ ) {
+				if (inline && needData) {
+					// inspector reading data (though FilterDataAccess) may have affected index position
+					daIndex.seek(getIndexOffsetInt(i));
+				}
+				long l = daIndex.readLong(); // 0
+				long offset = i == 0 ? 0 : (l >>> 16);
+				@SuppressWarnings("unused")
+				int flags = (int) (l & 0X0FFFF);
+				int compressedLen = daIndex.readInt(); // +8
+				int actualLen = daIndex.readInt(); // +12
+				int baseRevision = daIndex.readInt(); // +16
+				int linkRevision = daIndex.readInt(); // +20
+				int parent1Revision = daIndex.readInt();
+				int parent2Revision = daIndex.readInt();
+				// Hg has 32 bytes here, uses 20 for nodeid, and keeps 12 last bytes empty
+				daIndex.readBytes(nodeidBuf, 0, 20); // +32
+				daIndex.skip(12);
+				DataAccess userDataAccess = null;
+				if (needData) {
+					final byte firstByte;
+					int streamOffset;
+					DataAccess streamDataAccess;
+					if (inline) {
+						streamDataAccess = daIndex;
+						streamOffset = getIndexOffsetInt(i) + REVLOGV1_RECORD_SIZE; // don't need to do seek as it's actual position in the index stream
+					} else {
+						streamOffset = (int) offset;
+						streamDataAccess = daData;
+						daData.seek(streamOffset);
+					}
+					final boolean patchToPrevious = baseRevision != i; // the only way I found to tell if it's a patch
+					firstByte = streamDataAccess.readByte();
+					if (firstByte == 0x78 /* 'x' */) {
+						userDataAccess = new InflaterDataAccess(streamDataAccess, streamOffset, compressedLen, patchToPrevious ? -1 : actualLen);
+					} else if (firstByte == 0x75 /* 'u' */) {
+						userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset+1, compressedLen-1);
+					} else {
+						// XXX Python impl in fact throws exception when there's not 'x', 'u' or '0'
+						// but I don't see reason not to return data as is 
+						userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset, compressedLen);
+					}
+					// XXX 
+					if (patchToPrevious) {
+						// this is a patch
+						LinkedList<PatchRecord> patches = new LinkedList<PatchRecord>();
+						while (!userDataAccess.isEmpty()) {
+							PatchRecord pr = PatchRecord.read(userDataAccess);
+//							System.out.printf("PatchRecord:%d %d %d\n", pr.start, pr.end, pr.len);
+							patches.add(pr);
+						}
+						userDataAccess.done();
+						//
+						byte[] userData = apply(lastUserData, actualLen, patches);
+						userDataAccess = new ByteArrayDataAccess(userData);
+					}
+				} else {
+					if (inline) {
+						daIndex.skip(compressedLen);
+					}
+				}
+				if (!extraReadsToBaseRev || i >= start) {
+					inspector.next(i, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeidBuf, userDataAccess);
+				}
+				if (userDataAccess != null) {
+					userDataAccess.reset();
+					if (lastUserData != null) {
+						lastUserData.done();
+					}
+					lastUserData = userDataAccess;
+				}
+			}
+		} catch (IOException ex) {
+			throw new HgBadStateException(ex); // FIXME need better handling
+		} finally {
+			daIndex.done();
+			if (daData != null) {
+				daData.done();
+			}
+		}
+	}
+
+	private int getBaseRevision(int revision) {
+		return baseRevisions[revision];
+	}
+
+	/**
+	 * @return  offset of the revision's record in the index (.i) stream
+	 */
+	private int getIndexOffsetInt(int revision) {
+		return inline ? indexRecordOffset[revision] : revision * REVLOGV1_RECORD_SIZE;
+	}
+
+	private void initOutline() {
+		if (baseRevisions != null && baseRevisions.length > 0) {
+			return;
+		}
+		ArrayList<Integer> resBases = new ArrayList<Integer>();
+		ArrayList<Integer> resOffsets = new ArrayList<Integer>();
+		DataAccess da = getIndexStream();
+		try {
+			if (da.isEmpty()) {
+				// do not fail with exception if stream is empty, it's likely intentional
+				baseRevisions = new int[0];
+				return;
+			}
+			int versionField = da.readInt();
+			da.readInt(); // just to skip next 4 bytes of offset + flags
+			final int INLINEDATA = 1 << 16;
+			inline = (versionField & INLINEDATA) != 0;
+			long offset = 0; // first offset is always 0, thus Hg uses it for other purposes
+			while(true) {
+				int compressedLen = da.readInt();
+				// 8+4 = 12 bytes total read here
+				@SuppressWarnings("unused")
+				int actualLen = da.readInt();
+				int baseRevision = da.readInt();
+				// 12 + 8 = 20 bytes read here
+//				int linkRevision = di.readInt();
+//				int parent1Revision = di.readInt();
+//				int parent2Revision = di.readInt();
+//				byte[] nodeid = new byte[32];
+				resBases.add(baseRevision);
+				if (inline) {
+					int o = (int) offset;
+					if (o != offset) {
+						// just in case, can't happen, ever, unless HG (or some other bad tool) produces index file 
+						// with inlined data of size greater than 2 Gb.
+						throw new HgBadStateException("Data too big, offset didn't fit to sizeof(int)");
+					}
+					resOffsets.add(o + REVLOGV1_RECORD_SIZE * resOffsets.size());
+					da.skip(3*4 + 32 + compressedLen); // Check: 44 (skip) + 20 (read) = 64 (total RevlogNG record size)
+				} else {
+					da.skip(3*4 + 32);
+				}
+				if (da.isEmpty()) {
+					// fine, done then
+					baseRevisions = toArray(resBases);
+					if (inline) {
+						indexRecordOffset = toArray(resOffsets);
+					}
+					break;
+				} else {
+					// start reading next record
+					long l = da.readLong();
+					offset = l >>> 16;
+				}
+			}
+		} catch (IOException ex) {
+			ex.printStackTrace(); // log error
+			// too bad, no outline then, but don't fail with NPE
+			baseRevisions = new int[0];
+		} finally {
+			da.done();
+		}
+	}
+	
+	private static int[] toArray(List<Integer> l) {
+		int[] rv = new int[l.size()];
+		for (int i = 0; i < rv.length; i++) {
+			rv[i] = l.get(i);
+		}
+		return rv;
+	}
+	
+
+	// mpatch.c : apply()
+	// FIXME need to implement patch merge (fold, combine, gather and discard from aforementioned mpatch.[c|py]), also see Revlog and Mercurial PDF
+	public/*for HgBundle; until moved to better place*/static byte[] apply(DataAccess baseRevisionContent, int outcomeLen, List<PatchRecord> patch) throws IOException {
+		int last = 0, destIndex = 0;
+		if (outcomeLen == -1) {
+			outcomeLen = baseRevisionContent.length();
+			for (PatchRecord pr : patch) {
+				outcomeLen += pr.start - last + pr.len;
+				last = pr.end;
+			}
+			outcomeLen -= last;
+			last = 0;
+		}
+		byte[] rv = new byte[outcomeLen];
+		for (PatchRecord pr : patch) {
+			baseRevisionContent.seek(last);
+			baseRevisionContent.readBytes(rv, destIndex, pr.start-last);
+			destIndex += pr.start - last;
+			System.arraycopy(pr.data, 0, rv, destIndex, pr.data.length);
+			destIndex += pr.data.length;
+			last = pr.end;
+		}
+		baseRevisionContent.seek(last);
+		baseRevisionContent.readBytes(rv, destIndex, (int) (baseRevisionContent.length() - last));
+		return rv;
+	}
+
+	// @see http://mercurial.selenic.com/wiki/BundleFormat, in Changelog group description
+	public static class PatchRecord {
+		/*
+		   Given there are pr1 and pr2:
+		     pr1.start to pr1.end will be replaced with pr's data (of pr1.len)
+		     pr1.end to pr2.start gets copied from base
+		 */
+		public int start, end, len;
+		public byte[] data;
+
+		// TODO consider PatchRecord that only records data position (absolute in data source), and acquires data as needed 
+		private PatchRecord(int p1, int p2, int length, byte[] src) {
+			start = p1;
+			end = p2;
+			len = length;
+			data = src;
+		}
+
+		/*package-local*/ static PatchRecord read(byte[] data, int offset) {
+			final int x = offset; // shorthand
+			int p1 =  ((data[x] & 0xFF)<< 24)    | ((data[x+1] & 0xFF) << 16) | ((data[x+2] & 0xFF) << 8)  | (data[x+3] & 0xFF);
+			int p2 =  ((data[x+4] & 0xFF) << 24) | ((data[x+5] & 0xFF) << 16) | ((data[x+6] & 0xFF) << 8)  | (data[x+7] & 0xFF);
+			int len = ((data[x+8] & 0xFF) << 24) | ((data[x+9] & 0xFF) << 16) | ((data[x+10] & 0xFF) << 8) | (data[x+11] & 0xFF);
+			byte[] dataCopy = new byte[len];
+			System.arraycopy(data, x+12, dataCopy, 0, len);
+			return new PatchRecord(p1, p2, len, dataCopy);
+		}
+
+		public /*for HgBundle*/ static PatchRecord read(DataAccess da) throws IOException {
+			int p1 = da.readInt();
+			int p2 = da.readInt();
+			int len = da.readInt();
+			byte[] src = new byte[len];
+			da.readBytes(src, 0, len);
+			return new PatchRecord(p1, p2, len, src);
+		}
+	}
+
+	// FIXME byte[] data might be too expensive, for few usecases it may be better to have intermediate Access object (when we don't need full data 
+	// instantly - e.g. calculate hash, or comparing two revisions
+	public interface Inspector {
+		// XXX boolean retVal to indicate whether to continue?
+		// TODO specify nodeid and data length, and reuse policy (i.e. if revlog stream doesn't reuse nodeid[] for each call)
+		// implementers shall not invoke DataAccess.done(), it's accomplished by #iterate at appropraite moment
+		void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/internal/StoragePathHelper.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,197 @@
+/*
+ * 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.util.Arrays;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ * @see http://mercurial.selenic.com/wiki/CaseFoldingPlan
+ * @see http://mercurial.selenic.com/wiki/fncacheRepoFormat
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+class StoragePathHelper implements PathRewrite {
+	
+	private final boolean store;
+	private final boolean fncache;
+	private final boolean dotencode;
+
+	public StoragePathHelper(boolean isStore, boolean isFncache, boolean isDotencode) {
+		store = isStore;
+		fncache = isFncache;
+		dotencode = isDotencode;
+	}
+
+	// FIXME document what path argument is, whether it includes .i or .d, and whether it's 'normalized' (slashes) or not.
+	// since .hg/store keeps both .i files and files without extension (e.g. fncache), guees, for data == false 
+	// we shall assume path has extension
+	public String rewrite(String path) {
+		final String STR_STORE = "store/";
+		final String STR_DATA = "data/";
+		final String STR_DH = "dh/";
+		final String reservedChars = "\\:*?\"<>|";
+		char[] hexByte = new char[2];
+		
+		path = path.replace(".hg/", ".hg.hg/").replace(".i/", ".i.hg/").replace(".d/", ".d.hg/");
+		StringBuilder sb = new StringBuilder(path.length() << 1);
+		if (store || fncache) {
+			// encodefilename
+			for (int i = 0; i < path.length(); i++) {
+				final char ch = path.charAt(i);
+				if (ch >= 'a' && ch <= 'z') {
+					sb.append(ch); // POIRAE
+				} else if (ch >= 'A' && ch <= 'Z') {
+					sb.append('_');
+					sb.append(Character.toLowerCase(ch)); // Perhaps, (char) (((int) ch) + 32)? Even better, |= 0x20? 
+				} else if (reservedChars.indexOf(ch) != -1) {
+					sb.append('~');
+					sb.append(toHexByte(ch, hexByte));
+				} else if ((ch >= '~' /*126*/ && ch <= 255) || ch < ' ' /*32*/) {
+					sb.append('~');
+					sb.append(toHexByte(ch, hexByte));
+				} else if (ch == '_') {
+					sb.append('_');
+					sb.append('_');
+				} else {
+					sb.append(ch);
+				}
+			}
+			// auxencode
+			if (fncache) {
+				encodeWindowsDeviceNames(sb);
+			}
+		}
+		final int MAX_PATH_LEN = 120;
+		if (fncache && (sb.length() + STR_DATA.length() + ".i".length() > MAX_PATH_LEN)) {
+			String digest = new DigestHelper().sha1(STR_DATA, path, ".i").asHexString();
+			final int DIR_PREFIX_LEN = 8;
+			 // not sure why (-4) is here. 120 - 40 = up to 80 for path with ext. dh/ + ext(.i) = 3+2
+			final int MAX_DIR_PREFIX = 8 * (DIR_PREFIX_LEN + 1) - 4;
+			sb = new StringBuilder(MAX_PATH_LEN);
+			for (int i = 0; i < path.length(); i++) {
+				final char ch = path.charAt(i);
+				if (ch >= 'a' && ch <= 'z') {
+					sb.append(ch);
+				} else if (ch >= 'A' && ch <= 'Z') {
+					sb.append((char) (ch | 0x20)); // lowercase 
+				} else if (reservedChars.indexOf(ch) != -1) {
+					sb.append('~');
+					sb.append(toHexByte(ch, hexByte));
+				} else if ((ch >= '~' /*126*/ && ch <= 255) || ch < ' ' /*32*/) {
+					sb.append('~');
+					sb.append(toHexByte(ch, hexByte));
+				} else {
+					sb.append(ch);
+				}
+			}
+			encodeWindowsDeviceNames(sb);
+			int fnameStart = sb.lastIndexOf("/"); // since we rewrite file names, it never ends with slash (for dirs, I'd pass length-2);
+			StringBuilder completeHashName = new StringBuilder(MAX_PATH_LEN);
+			completeHashName.append(STR_STORE);
+			completeHashName.append(STR_DH);
+			if (fnameStart == -1) {
+				// no dirs, just long filename
+				sb.setLength(MAX_PATH_LEN - 40 /*digest.length()*/ - STR_DH.length() - ".i".length());
+				completeHashName.append(sb);
+			} else {
+				StringBuilder sb2 = new StringBuilder(MAX_PATH_LEN);
+				int x = 0;
+				do {
+					int i = sb.indexOf("/", x);
+					final int sb2Len = sb2.length(); 
+					if (i-x <= DIR_PREFIX_LEN) { // a b c d e f g h /
+						sb2.append(sb, x, i + 1); // with slash
+					} else {
+						sb2.append(sb, x, x + DIR_PREFIX_LEN);
+						// may unexpectedly end with bad character
+						final int last = sb2.length()-1;
+						char lastChar = sb2.charAt(last); 
+						assert lastChar == sb.charAt(x + DIR_PREFIX_LEN - 1);
+						if (lastChar == '.' || lastChar == ' ') {
+							sb2.setCharAt(last, '_');
+						}
+						sb2.append('/');
+					}
+					if (sb2.length()-1 > MAX_DIR_PREFIX) {
+						sb2.setLength(sb2Len); // strip off last segment, it's too much
+						break;
+					}
+					x = i+1; 
+				} while (x < fnameStart);
+				assert sb2.charAt(sb2.length() - 1) == '/';
+				int left = MAX_PATH_LEN - sb2.length() - 40 /*digest.length()*/ - STR_DH.length() - ".i".length();
+				assert left >= 0;
+				fnameStart++; // move from / to actual name
+				sb2.append(sb, fnameStart, fnameStart + left > sb.length() ? sb.length() : fnameStart+left);
+				completeHashName.append(sb2);
+			}
+			completeHashName.append(digest);
+			sb = completeHashName;
+		} else if (store) {
+			sb.insert(0, STR_STORE + STR_DATA);
+		}
+		sb.append(".i");
+		return sb.toString();
+	}
+	
+	private void encodeWindowsDeviceNames(StringBuilder sb) {
+		char[] hexByte = new char[2];
+		int x = 0; // last segment start
+		final TreeSet<String> windowsReservedFilenames = new TreeSet<String>();
+		windowsReservedFilenames.addAll(Arrays.asList("con prn aux nul com1 com2 com3 com4 com5 com6 com7 com8 com9 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9".split(" "))); 
+		do {
+			int i = sb.indexOf("/", x);
+			if (i == -1) {
+				i = sb.length();
+			}
+			// windows reserved filenames are at least of length 3 
+			if (i - x >= 3) {
+				boolean found = false;
+				if (i-x == 3 || i-x == 4) {
+					found = windowsReservedFilenames.contains(sb.subSequence(x, i));
+				} else if (sb.charAt(x+3) == '.') { // implicit i-x > 3
+					found = windowsReservedFilenames.contains(sb.subSequence(x, x+3));
+				} else if (i-x > 4 && sb.charAt(x+4) == '.') {
+					found = windowsReservedFilenames.contains(sb.subSequence(x, x+4));
+				}
+				if (found) {
+					sb.insert(x+3, toHexByte(sb.charAt(x+2), hexByte));
+					sb.setCharAt(x+2, '~');
+					i += 2;
+				}
+			}
+			if (dotencode && (sb.charAt(x) == '.' || sb.charAt(x) == ' ')) {
+				sb.insert(x+1, toHexByte(sb.charAt(x), hexByte));
+				sb.setCharAt(x, '~'); // setChar *after* charAt/insert to get ~2e, not ~7e for '.'
+				i += 2;
+			}
+			x = i+1;
+		} while (x < sb.length());
+	}
+
+	private static char[] toHexByte(int ch, char[] buf) {
+		assert buf.length > 1;
+		final String hexDigits = "0123456789abcdef";
+		buf[0] = hexDigits.charAt((ch & 0x00F0) >>> 4);
+		buf[1] = hexDigits.charAt(ch & 0x0F);
+		return buf;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgBundle.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,429 @@
+/*
+ * 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.repo;
+
+import static org.tmatesoft.hg.core.Nodeid.NULL;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.ByteArrayChannel;
+import org.tmatesoft.hg.internal.ByteArrayDataAccess;
+import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.DataAccessProvider;
+import org.tmatesoft.hg.internal.DigestHelper;
+import org.tmatesoft.hg.internal.InflaterDataAccess;
+import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.util.CancelledException;
+
+/**
+ * @see http://mercurial.selenic.com/wiki/BundleFormat
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgBundle {
+
+	private final File bundleFile;
+	private final DataAccessProvider accessProvider;
+
+	HgBundle(DataAccessProvider dap, File bundle) {
+		accessProvider = dap;
+		bundleFile = bundle;
+	}
+
+	private DataAccess getDataStream() throws IOException {
+		DataAccess da = accessProvider.create(bundleFile);
+		byte[] signature = new byte[6];
+		if (da.length() > 6) {
+			da.readBytes(signature, 0, 6);
+			if (signature[0] == 'H' && signature[1] == 'G' && signature[2] == '1' && signature[3] == '0') {
+				if (signature[4] == 'G' && signature[5] == 'Z') {
+					return new InflaterDataAccess(da, 6, da.length() - 6);
+				}
+				if (signature[4] == 'B' && signature[5] == 'Z') {
+					throw HgRepository.notImplemented();
+				}
+				if (signature[4] != 'U' || signature[5] != 'N') {
+					throw new HgBadStateException("Bad bundle signature:" + new String(signature));
+				}
+				// "...UN", fall-through
+			} else {
+				da.reset();
+			}
+		}
+		return da;
+	}
+
+	private int uses = 0;
+	public HgBundle link() {
+		uses++;
+		return this;
+	}
+	public void unlink() {
+		uses--;
+		if (uses == 0 && bundleFile != null) {
+			bundleFile.deleteOnExit();
+		}
+	}
+	public boolean inUse() {
+		return uses > 0;
+	}
+
+	/**
+	 * Get changes recorded in the bundle that are missing from the supplied repository.
+	 * @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 {
+		Inspector bundleInsp = new Inspector() {
+			DigestHelper dh = new DigestHelper();
+			boolean emptyChangelog = true;
+			private DataAccess prevRevContent;
+			private int revisionIndex;
+
+			public void changelogStart() {
+				emptyChangelog = true;
+				revisionIndex = 0;
+			}
+
+			public void changelogEnd() {
+				if (emptyChangelog) {
+					throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log?
+				}
+			}
+
+/*
+ * Despite that BundleFormat wiki says: "Each Changelog entry patches the result of all previous patches 
+ * (the previous, or parent patch of a given patch p is the patch that has a node equal to p's p1 field)",
+ *  it seems not to hold true. Instead, each entry patches previous one, regardless of whether the one
+ *  before is its parent (i.e. ge.firstParent()) or not.
+ *  
+Actual state in the changelog.i
+Index    Offset      Flags     Packed     Actual   Base Rev   Link Rev  Parent1  Parent2     nodeid
+  50:          9212      0        209        329         48         50       49       -1     f1db8610da62a3e0beb8d360556ee1fd6eb9885e
+  51:          9421      0        278        688         48         51       50       -1     9429c7bd1920fab164a9d2b621d38d57bcb49ae0
+  52:          9699      0        154        179         52         52       50       -1     30bd389788464287cee22ccff54c330a4b715de5
+  53:          9853      0        133        204         52         53       51       52     a6f39e595b2b54f56304470269a936ead77f5725
+  54:          9986      0        156        182         54         54       52       -1     fd4f2c98995beb051070630c272a9be87bef617d
+
+Excerpt from bundle (nodeid, p1, p2, cs):
+   f1db8610da62a3e0beb8d360556ee1fd6eb9885e 26e3eeaa39623de552b45ee1f55c14f36460f220 0000000000000000000000000000000000000000 f1db8610da62a3e0beb8d360556ee1fd6eb9885e; patches:4
+   9429c7bd1920fab164a9d2b621d38d57bcb49ae0 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 0000000000000000000000000000000000000000 9429c7bd1920fab164a9d2b621d38d57bcb49ae0; patches:3
+>  30bd389788464287cee22ccff54c330a4b715de5 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 0000000000000000000000000000000000000000 30bd389788464287cee22ccff54c330a4b715de5; patches:3
+   a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5 a6f39e595b2b54f56304470269a936ead77f5725; patches:3
+   fd4f2c98995beb051070630c272a9be87bef617d 30bd389788464287cee22ccff54c330a4b715de5 0000000000000000000000000000000000000000 fd4f2c98995beb051070630c272a9be87bef617d; patches:3
+
+To recreate 30bd..e5, one have to take content of 9429..e0, not its p1 f1db..5e
+ */
+			public boolean element(GroupElement ge) {
+				emptyChangelog = false;
+				HgChangelog changelog = hgRepo.getChangelog();
+				try {
+					if (prevRevContent == null) { 
+						if (NULL.equals(ge.firstParent()) && NULL.equals(ge.secondParent())) {
+							prevRevContent = new ByteArrayDataAccess(new byte[0]);
+						} else {
+							final Nodeid base = ge.firstParent();
+							if (!changelog.isKnown(base) /*only first parent, that's Bundle contract*/) {
+								throw new IllegalStateException(String.format("Revision %s needs a parent %s, which is missing in the supplied repo %s", ge.node().shortNotation(), base.shortNotation(), hgRepo.toString()));
+							}
+							ByteArrayChannel bac = new ByteArrayChannel();
+							changelog.rawContent(base, bac); // FIXME get DataAccess directly, to avoid
+							// extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap.
+							prevRevContent = new ByteArrayDataAccess(bac.toArray());
+						}
+					}
+					//
+					byte[] csetContent = ge.apply(prevRevContent);
+					dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid?
+					if (!ge.node().equalsTo(dh.asBinary())) {
+						throw new IllegalStateException("Integrity check failed on " + bundleFile + ", node:" + ge.node());
+					}
+					ByteArrayDataAccess csetDataAccess = new ByteArrayDataAccess(csetContent);
+					RawChangeset cs = RawChangeset.parse(csetDataAccess);
+					inspector.next(revisionIndex++, ge.node(), cs);
+					prevRevContent.done();
+					prevRevContent = csetDataAccess.reset();
+				} catch (CancelledException ex) {
+					return false;
+				} catch (Exception ex) {
+					throw new HgBadStateException(ex); // FIXME
+				}
+				return true;
+			}
+
+			public void manifestStart() {}
+			public void manifestEnd() {}
+			public void fileStart(String name) {}
+			public void fileEnd(String name) {}
+
+		};
+		inspectChangelog(bundleInsp);
+	}
+
+	public void dump() throws IOException {
+		Dump dump = new Dump();
+		inspectAll(dump);
+		System.out.println("Total files:" + dump.names.size());
+		for (String s : dump.names) {
+			System.out.println(s);
+		}
+	}
+
+	// callback to minimize amount of Strings and Nodeids instantiated
+	public interface Inspector {
+		void changelogStart();
+
+		void changelogEnd();
+
+		void manifestStart();
+
+		void manifestEnd();
+
+		void fileStart(String name);
+
+		void fileEnd(String name);
+
+		/**
+		 * XXX desperately need exceptions here
+		 * @param element data element, instance might be reused, don't keep a reference to it or its raw data
+		 * @return <code>true</code> to continue
+		 */
+		boolean element(GroupElement element);
+	}
+
+	public static class Dump implements Inspector {
+		public final LinkedList<String> names = new LinkedList<String>();
+
+		public void changelogStart() {
+			System.out.println("Changelog group");
+		}
+
+		public void changelogEnd() {
+		}
+
+		public void manifestStart() {
+			System.out.println("Manifest group");
+		}
+
+		public void manifestEnd() {
+		}
+
+		public void fileStart(String name) {
+			names.add(name);
+			System.out.println(name);
+		}
+
+		public void fileEnd(String name) {
+		}
+
+		public boolean element(GroupElement ge) {
+			try {
+				System.out.printf("  %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches().size());
+			} catch (Exception ex) {
+				ex.printStackTrace(); // FIXME
+			}
+			return true;
+		}
+	}
+
+	public void inspectChangelog(Inspector inspector) throws IOException {
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		DataAccess da = getDataStream();
+		try {
+			internalInspectChangelog(da, inspector);
+		} finally {
+			da.done();
+		}
+	}
+
+	public void inspectManifest(Inspector inspector) throws IOException {
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		DataAccess da = getDataStream();
+		try {
+			if (da.isEmpty()) {
+				return;
+			}
+			skipGroup(da); // changelog
+			internalInspectManifest(da, inspector);
+		} finally {
+			da.done();
+		}
+	}
+
+	public void inspectFiles(Inspector inspector) throws IOException {
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		DataAccess da = getDataStream();
+		try {
+			if (da.isEmpty()) {
+				return;
+			}
+			skipGroup(da); // changelog
+			if (da.isEmpty()) {
+				return;
+			}
+			skipGroup(da); // manifest
+			internalInspectFiles(da, inspector);
+		} finally {
+			da.done();
+		}
+	}
+
+	public void inspectAll(Inspector inspector) throws IOException {
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		DataAccess da = getDataStream();
+		try {
+			internalInspectChangelog(da, inspector);
+			internalInspectManifest(da, inspector);
+			internalInspectFiles(da, inspector);
+		} finally {
+			da.done();
+		}
+	}
+
+	private void internalInspectChangelog(DataAccess da, Inspector inspector) throws IOException {
+		if (da.isEmpty()) {
+			return;
+		}
+		inspector.changelogStart();
+		readGroup(da, inspector);
+		inspector.changelogEnd();
+	}
+
+	private void internalInspectManifest(DataAccess da, Inspector inspector) throws IOException {
+		if (da.isEmpty()) {
+			return;
+		}
+		inspector.manifestStart();
+		readGroup(da, inspector);
+		inspector.manifestEnd();
+	}
+
+	private void internalInspectFiles(DataAccess da, Inspector inspector) throws IOException {
+		while (!da.isEmpty()) {
+			int fnameLen = da.readInt();
+			if (fnameLen <= 4) {
+				break; // null chunk, the last one.
+			}
+			byte[] fnameBuf = new byte[fnameLen - 4];
+			da.readBytes(fnameBuf, 0, fnameBuf.length);
+			String name = new String(fnameBuf);
+			inspector.fileStart(name);
+			readGroup(da, inspector);
+			inspector.fileEnd(name);
+		}
+	}
+
+	private static void readGroup(DataAccess da, Inspector inspector) throws IOException {
+		int len = da.readInt();
+		boolean good2go = true;
+		while (len > 4 && !da.isEmpty() && good2go) {
+			byte[] nb = new byte[80];
+			da.readBytes(nb, 0, 80);
+			int dataLength = len - 84 /* length field + 4 nodeids */;
+			byte[] data = new byte[dataLength];
+			da.readBytes(data, 0, dataLength);
+			DataAccess slice = new ByteArrayDataAccess(data); // XXX in fact, may pass a slicing DataAccess.
+			// Just need to make sure that we seek to proper location afterwards (where next GroupElement starts),
+			// regardless whether that slice has read it or not.
+			GroupElement ge = new GroupElement(nb, slice);
+			good2go = inspector.element(ge);
+			slice.done(); // BADA doesn't implement done(), but it could (e.g. free array) 
+			/// and we'd better tell it we are not going to use it any more. However, it's important to ensure Inspector
+			// implementations out there do not retain GroupElement.rawData()
+			len = da.isEmpty() ? 0 : da.readInt();
+		}
+		// need to skip up to group end if inspector told he don't want to continue with the group, 
+		// because outer code may try to read next group immediately as we return back.
+		while (len > 4 && !da.isEmpty()) {
+			da.skip(len - 4 /* length field */);
+			len = da.isEmpty() ? 0 : da.readInt();
+		}
+	}
+
+	private static void skipGroup(DataAccess da) throws IOException {
+		int len = da.readInt();
+		while (len > 4 && !da.isEmpty()) {
+			da.skip(len - 4); // sizeof(int)
+			len = da.isEmpty() ? 0 : da.readInt();
+		}
+	}
+
+	public static class GroupElement {
+		private final byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192
+		private final DataAccess dataAccess;
+		private List<RevlogStream.PatchRecord> patches;
+
+		GroupElement(byte[] fourNodeids, DataAccess rawDataAccess) {
+			assert fourNodeids != null && fourNodeids.length == 80;
+			header = fourNodeids;
+			dataAccess = rawDataAccess;
+		}
+
+		public Nodeid node() {
+			return Nodeid.fromBinary(header, 0);
+		}
+
+		public Nodeid firstParent() {
+			return Nodeid.fromBinary(header, 20);
+		}
+
+		public Nodeid secondParent() {
+			return Nodeid.fromBinary(header, 40);
+		}
+
+		public Nodeid cset() { // cs seems to be changeset
+			return Nodeid.fromBinary(header, 60);
+		}
+
+		public DataAccess rawData() {
+			return dataAccess;
+		}
+		
+		public List<RevlogStream.PatchRecord> patches() throws IOException {
+			if (patches == null) {
+				dataAccess.reset();
+				LinkedList<RevlogStream.PatchRecord> p = new LinkedList<RevlogStream.PatchRecord>();
+				while (!dataAccess.isEmpty()) {
+					RevlogStream.PatchRecord pr = RevlogStream.PatchRecord.read(dataAccess);
+					p.add(pr);
+				}
+				patches = p;
+			}
+			return patches;
+		}
+
+		public byte[] apply(DataAccess baseContent) throws IOException {
+			return RevlogStream.apply(baseContent, -1, patches());
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgChangelog.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.Pool;
+import org.tmatesoft.hg.internal.RevlogStream;
+
+/**
+ * Representation of the Mercurial changelog file (list of ChangeSets)
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgChangelog extends Revlog {
+
+	/* package-local */HgChangelog(HgRepository hgRepo, RevlogStream content) {
+		super(hgRepo, content);
+	}
+
+	public void all(final HgChangelog.Inspector inspector) {
+		range(0, getLastRevision(), inspector);
+	}
+
+	public void range(int start, int end, final HgChangelog.Inspector inspector) {
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		content.iterate(start, end, true, new RawCsetParser(inspector));
+	}
+
+	public List<RawChangeset> range(int start, int end) {
+		final RawCsetCollector c = new RawCsetCollector(end - start + 1);
+		range(start, end, c);
+		return c.result;
+	}
+
+	public void range(final HgChangelog.Inspector inspector, final int... revisions) {
+		if (revisions == null || revisions.length == 0) {
+			return;
+		}
+		RevlogStream.Inspector i = new RevlogStream.Inspector() {
+			private final RawCsetParser delegate = new RawCsetParser(inspector);
+
+			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
+				if (Arrays.binarySearch(revisions, revisionNumber) >= 0) {
+					delegate.next(revisionNumber, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeid, da);
+				}
+			}
+		};
+		Arrays.sort(revisions);
+		content.iterate(revisions[0], revisions[revisions.length - 1], true, i);
+	}
+
+	public interface Inspector {
+		// TODO describe whether cset is new instance each time
+		// describe what revisionNumber is when Inspector is used with HgBundle (BAD_REVISION or bundle's local order?) 
+		void next(int revisionNumber, Nodeid nodeid, RawChangeset cset);
+	}
+
+	/**
+	 * Entry in the Changelog
+	 */
+	public static class RawChangeset implements Cloneable /* for those that would like to keep a copy */{
+		// TODO immutable
+		private/* final */Nodeid manifest;
+		private String user;
+		private String comment;
+		private List<String> files; // unmodifiable collection (otherwise #files() and implicit #clone() shall be revised)
+		private Date time;
+		private int timezone;
+		// http://mercurial.selenic.com/wiki/PruningDeadBranches - Closing changesets can be identified by close=1 in the changeset's extra field.
+		private Map<String, String> extras;
+
+		/**
+		 * @see mercurial/changelog.py:read()
+		 * 
+		 *      <pre>
+		 *         format used:
+		 *         nodeid\n        : manifest node in ascii
+		 *         user\n          : user, no \n or \r allowed
+		 *         time tz extra\n : date (time is int or float, timezone is int)
+		 *                         : extra is metadatas, encoded and separated by '\0'
+		 *                         : older versions ignore it
+		 *         files\n\n       : files modified by the cset, no \n or \r allowed
+		 *         (.*)            : comment (free text, ideally utf-8)
+		 * 
+		 *         changelog v0 doesn't use extra
+		 * </pre>
+		 */
+		private RawChangeset() {
+		}
+
+		public Nodeid manifest() {
+			return manifest;
+		}
+
+		public String user() {
+			return user;
+		}
+
+		public String comment() {
+			return comment;
+		}
+
+		public List<String> files() {
+			return files;
+		}
+
+		public Date date() {
+			return time;
+		}
+		
+		/**
+		 * @return time zone value, as is, positive for Western Hemisphere.
+		 */
+		public int timezone() {
+			return timezone;
+		}
+
+		public String dateString() {
+			// XXX keep once formatted? Perhaps, there's faster way to set up calendar/time zone?
+			StringBuilder sb = new StringBuilder(30);
+			Formatter f = new Formatter(sb, Locale.US);
+			TimeZone tz = TimeZone.getTimeZone(TimeZone.getAvailableIDs(timezone * 1000)[0]);
+			// apparently timezone field records number of seconds time differs from UTC,
+			// i.e. value to substract from time to get UTC time. Calendar seems to add
+			// timezone offset to UTC, instead, hence sign change.
+//			tz.setRawOffset(timezone * -1000);
+			Calendar c = Calendar.getInstance(tz, Locale.US);
+			c.setTime(time);
+			f.format("%ta %<tb %<td %<tH:%<tM:%<tS %<tY %<tz", c);
+			return sb.toString();
+		}
+
+		public Map<String, String> extras() {
+			return extras;
+		}
+
+		public String branch() {
+			return extras.get("branch");
+		}
+
+		@Override
+		public String toString() {
+			StringBuilder sb = new StringBuilder();
+			sb.append("Changeset {");
+			sb.append("User: ").append(user).append(", ");
+			sb.append("Comment: ").append(comment).append(", ");
+			sb.append("Manifest: ").append(manifest).append(", ");
+			sb.append("Date: ").append(time).append(", ");
+			sb.append("Files: ").append(files.size());
+			for (String s : files) {
+				sb.append(", ").append(s);
+			}
+			if (extras != null) {
+				sb.append(", Extra: ").append(extras);
+			}
+			sb.append("}");
+			return sb.toString();
+		}
+
+		@Override
+		public RawChangeset clone() {
+			try {
+				return (RawChangeset) super.clone();
+			} catch (CloneNotSupportedException ex) {
+				throw new InternalError(ex.toString());
+			}
+		}
+
+		public static RawChangeset parse(DataAccess da) {
+			try {
+				byte[] data = da.byteArray();
+				RawChangeset rv = new RawChangeset();
+				rv.init(data, 0, data.length, null);
+				return rv;
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex); // FIXME "Error reading changeset data"
+			}
+		}
+
+		// @param usersPool - it's likely user names get repeated again and again throughout repository. can be null
+		/* package-local */void init(byte[] data, int offset, int length, Pool<String> usersPool) {
+			final int bufferEndIndex = offset + length;
+			final byte lineBreak = (byte) '\n';
+			int breakIndex1 = indexOf(data, lineBreak, offset, bufferEndIndex);
+			if (breakIndex1 == -1) {
+				throw new IllegalArgumentException("Bad Changeset data");
+			}
+			Nodeid _nodeid = Nodeid.fromAscii(data, 0, breakIndex1);
+			int breakIndex2 = indexOf(data, lineBreak, breakIndex1 + 1, bufferEndIndex);
+			if (breakIndex2 == -1) {
+				throw new IllegalArgumentException("Bad Changeset data");
+			}
+			String _user = new String(data, breakIndex1 + 1, breakIndex2 - breakIndex1 - 1);
+			if (usersPool != null) {
+				_user = usersPool.unify(_user);
+			}
+			int breakIndex3 = indexOf(data, lineBreak, breakIndex2 + 1, bufferEndIndex);
+			if (breakIndex3 == -1) {
+				throw new IllegalArgumentException("Bad Changeset data");
+			}
+			String _timeString = new String(data, breakIndex2 + 1, breakIndex3 - breakIndex2 - 1);
+			int space1 = _timeString.indexOf(' ');
+			if (space1 == -1) {
+				throw new IllegalArgumentException("Bad Changeset data");
+			}
+			int space2 = _timeString.indexOf(' ', space1 + 1);
+			if (space2 == -1) {
+				space2 = _timeString.length();
+			}
+			long unixTime = Long.parseLong(_timeString.substring(0, space1)); // XXX Float, perhaps
+			int _timezone = Integer.parseInt(_timeString.substring(space1 + 1, space2));
+			// XXX not sure need to add timezone here - I can't figure out whether Hg keeps GMT time, and records timezone just for info, or unixTime is taken local
+			// on commit and timezone is recorded to adjust it to UTC.
+			Date _time = new Date(unixTime * 1000);
+			String _extras = space2 < _timeString.length() ? _timeString.substring(space2 + 1) : null;
+			Map<String, String> _extrasMap;
+			if (_extras == null) {
+				_extrasMap = Collections.singletonMap("branch", "default");
+			} else {
+				_extrasMap = new HashMap<String, String>();
+				for (String pair : _extras.split("\00")) {
+					int eq = pair.indexOf(':');
+					// FIXME need to decode key/value, @see changelog.py:decodeextra
+					_extrasMap.put(pair.substring(0, eq), pair.substring(eq + 1));
+				}
+				if (!_extrasMap.containsKey("branch")) {
+					_extrasMap.put("branch", "default");
+				}
+				_extrasMap = Collections.unmodifiableMap(_extrasMap);
+			}
+
+			//
+			int lastStart = breakIndex3 + 1;
+			int breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
+			ArrayList<String> _files = null;
+			if (breakIndex4 > lastStart) {
+				// if breakIndex4 == lastStart, we already found \n\n and hence there are no files (e.g. merge revision)
+				_files = new ArrayList<String>(5);
+				while (breakIndex4 != -1 && breakIndex4 + 1 < bufferEndIndex) {
+					_files.add(new String(data, lastStart, breakIndex4 - lastStart));
+					lastStart = breakIndex4 + 1;
+					if (data[breakIndex4 + 1] == lineBreak) {
+						// found \n\n
+						break;
+					} else {
+						breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
+					}
+				}
+				if (breakIndex4 == -1 || breakIndex4 >= bufferEndIndex) {
+					throw new IllegalArgumentException("Bad Changeset data");
+				}
+			} else {
+				breakIndex4--;
+			}
+			String _comment;
+			try {
+				_comment = new String(data, breakIndex4 + 2, bufferEndIndex - breakIndex4 - 2, "UTF-8");
+				// FIXME respect ui.fallbackencoding and try to decode if set
+			} catch (UnsupportedEncodingException ex) {
+				_comment = "";
+				throw new IllegalStateException("Could hardly happen");
+			}
+			// change this instance at once, don't leave it partially changes in case of error
+			this.manifest = _nodeid;
+			this.user = _user;
+			this.time = _time;
+			this.timezone = _timezone;
+			this.files = _files == null ? Collections.<String> emptyList() : Collections.unmodifiableList(_files);
+			this.comment = _comment;
+			this.extras = _extrasMap;
+		}
+
+		private static int indexOf(byte[] src, byte what, int startOffset, int endIndex) {
+			for (int i = startOffset; i < endIndex; i++) {
+				if (src[i] == what) {
+					return i;
+				}
+			}
+			return -1;
+		}
+	}
+
+	private static class RawCsetCollector implements Inspector {
+		final ArrayList<RawChangeset> result;
+		
+		public RawCsetCollector(int count) {
+			result = new ArrayList<RawChangeset>(count > 0 ? count : 5);
+		}
+
+		public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+			result.add(cset.clone());
+		}
+	}
+
+	private static class RawCsetParser implements RevlogStream.Inspector {
+		
+		private final Inspector inspector;
+		private final Pool<String> usersPool;
+		private final RawChangeset cset = new RawChangeset();
+
+		public RawCsetParser(HgChangelog.Inspector delegate) {
+			assert delegate != null;
+			inspector = delegate;
+			usersPool = new Pool<String>();
+		}
+
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
+			try {
+				byte[] data = da.byteArray();
+				cset.init(data, 0, data.length, usersPool);
+				// XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse
+				inspector.next(revisionNumber, Nodeid.fromBinary(nodeid, 0), cset);
+			} catch (Exception ex) {
+				throw new HgBadStateException(ex); // FIXME exception handling
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgDataFile.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision;
+import static org.tmatesoft.hg.repo.HgRepository.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.TreeMap;
+
+import org.tmatesoft.hg.core.HgDataStreamException;
+import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.FilterByteChannel;
+import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.util.ByteChannel;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.Path;
+
+
+
+/**
+ * ? name:HgFileNode?
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgDataFile extends Revlog {
+
+	// absolute from repo root?
+	// slashes, unix-style?
+	// repo location agnostic, just to give info to user, not to access real storage
+	private final Path path;
+	private Metadata metadata; // get initialized on first access to file content.
+	
+	/*package-local*/HgDataFile(HgRepository hgRepo, Path filePath, RevlogStream content) {
+		super(hgRepo, content);
+		path = filePath;
+	}
+
+	/*package-local*/HgDataFile(HgRepository hgRepo, Path filePath) {
+		super(hgRepo);
+		path = filePath;
+	}
+
+	// exists is not the best name possible. now it means no file with such name was ever known to the repo.
+	// it might be confused with files existed before but lately removed. 
+	public boolean exists() {
+		return content != null; // XXX need better impl
+	}
+
+	// human-readable (i.e. "COPYING", not "store/data/_c_o_p_y_i_n_g.i")
+	public Path getPath() {
+		return path; // hgRepo.backresolve(this) -> name? In this case, what about hashed long names?
+	}
+
+	public int length(Nodeid nodeid) {
+		return content.dataLength(getLocalRevision(nodeid));
+	}
+
+	public void workingCopy(ByteChannel sink) throws IOException, CancelledException {
+		throw HgRepository.notImplemented();
+	}
+	
+//	public void content(int revision, ByteChannel sink, boolean applyFilters) throws HgDataStreamException, IOException, CancelledException {
+//		byte[] content = content(revision);
+//		final CancelSupport cancelSupport = CancelSupport.Factory.get(sink);
+//		final ProgressSupport progressSupport = ProgressSupport.Factory.get(sink);
+//		ByteBuffer buf = ByteBuffer.allocate(512);
+//		int left = content.length;
+//		progressSupport.start(left);
+//		int offset = 0;
+//		cancelSupport.checkCancelled();
+//		ByteChannel _sink = applyFilters ? new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())) : sink;
+//		do {
+//			buf.put(content, offset, Math.min(left, buf.remaining()));
+//			buf.flip();
+//			cancelSupport.checkCancelled();
+//			// XXX I may not rely on returned number of bytes but track change in buf position instead.
+//			int consumed = _sink.write(buf);
+//			buf.compact();
+//			offset += consumed;
+//			left -= consumed;
+//			progressSupport.worked(consumed);
+//		} while (left > 0);
+//		progressSupport.done(); // XXX shall specify whether #done() is invoked always or only if completed successfully.
+//	}
+	
+	/*XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves?*/
+	public void contentWithFilters(int revision, ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
+		content(revision, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())));
+	}
+
+	// for data files need to check heading of the file content for possible metadata
+	// @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8-
+	public void content(int revision, ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
+		if (revision == TIP) {
+			revision = getLastRevision();
+		}
+		if (revision == WORKING_COPY) {
+			workingCopy(sink);
+			return;
+		}
+		if (wrongLocalRevision(revision) || revision == BAD_REVISION) {
+			throw new IllegalArgumentException(String.valueOf(revision));
+		}
+		if (sink == null) {
+			throw new IllegalArgumentException();
+		}
+		if (metadata == null) {
+			metadata = new Metadata();
+		}
+		ContentPipe insp;
+		if (metadata.none(revision)) {
+			insp = new ContentPipe(sink, 0);
+		} else if (metadata.known(revision)) {
+			insp = new ContentPipe(sink, metadata.dataOffset(revision));
+		} else {
+			// do not know if there's metadata
+			insp = new MetadataContentPipe(sink, metadata);
+		}
+		insp.checkCancelled();
+		super.content.iterate(revision, revision, true, insp);
+		try {
+			insp.checkFailed();
+		} catch (HgDataStreamException ex) {
+			throw ex;
+		} catch (HgException ex) {
+			// shall not happen, unless we changed ContentPipe or its subclass
+			throw new HgDataStreamException(ex.getClass().getName(), ex);
+		}
+	}
+	
+	public void history(HgChangelog.Inspector inspector) {
+		history(0, getLastRevision(), inspector);
+	}
+
+	public void history(int start, int end, HgChangelog.Inspector inspector) {
+		if (!exists()) {
+			throw new IllegalStateException("Can't get history of invalid repository file node"); 
+		}
+		final int last = getLastRevision();
+		if (start < 0 || start > last) {
+			throw new IllegalArgumentException();
+		}
+		if (end == TIP) {
+			end = last;
+		} else if (end < start || end > last) {
+			throw new IllegalArgumentException();
+		}
+		final int[] commitRevisions = new int[end - start + 1];
+		RevlogStream.Inspector insp = new RevlogStream.Inspector() {
+			int count = 0;
+			
+			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
+				commitRevisions[count++] = linkRevision;
+			}
+		};
+		content.iterate(start, end, false, insp);
+		getRepo().getChangelog().range(inspector, commitRevisions);
+	}
+	
+	// for a given local revision of the file, find out local revision in the changelog
+	public int getChangesetLocalRevision(int revision) {
+		return content.linkRevision(revision);
+	}
+
+	public Nodeid getChangesetRevision(Nodeid nid) {
+		int changelogRevision = getChangesetLocalRevision(getLocalRevision(nid));
+		return getRepo().getChangelog().getRevision(changelogRevision);
+	}
+
+	public boolean isCopy() throws HgDataStreamException {
+		if (metadata == null || !metadata.checked(0)) {
+			// content() always initializes metadata.
+			// FIXME this is expensive way to find out metadata, distinct RevlogStream.Iterator would be better.
+			// Alternatively, may parameterize MetadataContentPipe to do prepare only.
+			// For reference, when throwing CancelledException, hg status -A --rev 3:80 takes 70 ms
+			// however, if we just consume buffer instead (buffer.position(buffer.limit()), same command takes ~320ms
+			// (compared to command-line counterpart of 190ms)
+			try {
+				content(0, new ByteChannel() { // No-op channel
+					public int write(ByteBuffer buffer) throws IOException, CancelledException {
+						// pretend we consumed whole buffer
+//						int rv = buffer.remaining();
+//						buffer.position(buffer.limit());
+//						return rv;
+						throw new CancelledException();
+					}
+				});
+			} catch (CancelledException ex) {
+				// it's ok, we did that
+			} catch (Exception ex) {
+				throw new HgDataStreamException("Can't initialize metadata", ex);
+			}
+		}
+		if (!metadata.known(0)) {
+			return false;
+		}
+		return metadata.find(0, "copy") != null;
+	}
+
+	public Path getCopySourceName() throws HgDataStreamException {
+		if (isCopy()) {
+			return Path.create(metadata.find(0, "copy"));
+		}
+		throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?)
+	}
+	
+	public Nodeid getCopySourceRevision() throws HgDataStreamException {
+		if (isCopy()) {
+			return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid
+		}
+		throw new UnsupportedOperationException();
+	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder(getClass().getSimpleName());
+		sb.append('(');
+		sb.append(getPath());
+		sb.append(')');
+		return sb.toString();
+	}
+
+	private static final class MetadataEntry {
+		private final String entry;
+		private final int valueStart;
+		/*package-local*/MetadataEntry(String key, String value) {
+			entry = key + value;
+			valueStart = key.length();
+		}
+		/*package-local*/boolean matchKey(String key) {
+			return key.length() == valueStart && entry.startsWith(key);
+		}
+//		uncomment once/if needed
+//		public String key() {
+//			return entry.substring(0, valueStart);
+//		}
+		public String value() {
+			return entry.substring(valueStart);
+		}
+	}
+
+	private static class Metadata {
+		// XXX sparse array needed
+		private final TreeMap<Integer, Integer> offsets = new TreeMap<Integer, Integer>();
+		private final TreeMap<Integer, MetadataEntry[]> entries = new TreeMap<Integer, MetadataEntry[]>();
+		
+		private final Integer NONE = new Integer(-1); // do not duplicate -1 integers at least within single file (don't want statics)
+
+		// true when there's metadata for given revision
+		boolean known(int revision) {
+			Integer i = offsets.get(revision);
+			return i != null && NONE != i;
+		}
+
+		// true when revision has been checked for metadata presence.
+		public boolean checked(int revision) {
+			return offsets.containsKey(revision);
+		}
+
+		// true when revision has been checked and found not having any metadata
+		boolean none(int revision) {
+			Integer i = offsets.get(revision);
+			return i == NONE;
+		}
+
+		// mark revision as having no metadata.
+		void recordNone(int revision) {
+			Integer i = offsets.get(revision);
+			if (i == NONE) {
+				return; // already there
+			} 
+			if (i != null) {
+				throw new IllegalStateException(String.format("Trying to override Metadata state for revision %d (known offset: %d)", revision, i));
+			}
+			offsets.put(revision, NONE);
+		}
+
+		// since this is internal class, callers are supposed to ensure arg correctness (i.e. ask known() before)
+		int dataOffset(int revision) {
+			return offsets.get(revision);
+		}
+		void add(int revision, int dataOffset, Collection<MetadataEntry> e) {
+			assert !offsets.containsKey(revision);
+			offsets.put(revision, dataOffset);
+			entries.put(revision, e.toArray(new MetadataEntry[e.size()]));
+		}
+		String find(int revision, String key) {
+			for (MetadataEntry me : entries.get(revision)) {
+				if (me.matchKey(key)) {
+					return me.value();
+				}
+			}
+			return null;
+		}
+	}
+
+	private static class MetadataContentPipe extends ContentPipe {
+
+		private final Metadata metadata;
+
+		public MetadataContentPipe(ByteChannel sink, Metadata _metadata) {
+			super(sink, 0);
+			metadata = _metadata;
+		}
+
+		@Override
+		protected void prepare(int revisionNumber, DataAccess da) throws HgException, IOException {
+			final int daLength = da.length();
+			if (daLength < 4 || da.readByte() != 1 || da.readByte() != 10) {
+				metadata.recordNone(revisionNumber);
+				da.reset();
+				return;
+			}
+			int lastEntryStart = 2;
+			int lastColon = -1;
+			ArrayList<MetadataEntry> _metadata = new ArrayList<MetadataEntry>();
+			// XXX in fact, need smth like ByteArrayBuilder, similar to StringBuilder,
+			// which can't be used here because we can't convert bytes to chars as we read them
+			// (there might be multi-byte encoding), and we need to collect all bytes before converting to string 
+			ByteArrayOutputStream bos = new ByteArrayOutputStream();
+			String key = null, value = null;
+			boolean byteOne = false;
+			for (int i = 2; i < daLength; i++) {
+				byte b = da.readByte();
+				if (b == '\n') {
+					if (byteOne) { // i.e. \n follows 1
+						lastEntryStart = i+1;
+						// XXX is it possible to have here incomplete key/value (i.e. if last pair didn't end with \n)
+						break;
+					}
+					if (key == null || lastColon == -1 || i <= lastColon) {
+						throw new IllegalStateException(); // FIXME log instead and record null key in the metadata. Ex just to fail fast during dev
+					}
+					value = new String(bos.toByteArray()).trim();
+					bos.reset();
+					_metadata.add(new MetadataEntry(key, value));
+					key = value = null;
+					lastColon = -1;
+					lastEntryStart = i+1;
+					continue;
+				} 
+				// byteOne has to be consumed up to this line, if not jet, consume it
+				if (byteOne) {
+					// insert 1 we've read on previous step into the byte builder
+					bos.write(1);
+					// fall-through to consume current byte
+					byteOne = false;
+				}
+				if (b == (int) ':') {
+					assert value == null;
+					key = new String(bos.toByteArray());
+					bos.reset();
+					lastColon = i;
+				} else if (b == 1) {
+					byteOne = true;
+				} else {
+					bos.write(b);
+				}
+			}
+			_metadata.trimToSize();
+			metadata.add(revisionNumber, lastEntryStart, _metadata);
+			if (da.isEmpty() || !byteOne) {
+				throw new HgDataStreamException(String.format("Metadata for revision %d is not closed properly", revisionNumber), null);
+			}
+			// da is in prepared state (i.e. we consumed all bytes up to metadata end).
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgDirstate.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.DataAccessProvider;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ * @see http://mercurial.selenic.com/wiki/DirState
+ * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+class HgDirstate {
+
+	private final DataAccessProvider accessProvider;
+	private final File dirstateFile;
+	// deliberate String, not Path as it seems useless to keep Path here
+	private Map<String, Record> normal;
+	private Map<String, Record> added;
+	private Map<String, Record> removed;
+	private Map<String, Record> merged;
+
+	/*package-local*/ HgDirstate() {
+		// empty instance
+		accessProvider = null;
+		dirstateFile = null;
+	}
+
+	public HgDirstate(DataAccessProvider dap, File dirstate) {
+		accessProvider = dap;
+		dirstateFile = dirstate;
+	}
+
+	private void read() {
+		normal = added = removed = merged = Collections.<String, Record>emptyMap();
+		if (dirstateFile == null || !dirstateFile.exists()) {
+			return;
+		}
+		DataAccess da = accessProvider.create(dirstateFile);
+		if (da.isEmpty()) {
+			return;
+		}
+		// not sure linked is really needed here, just for ease of debug
+		normal = new LinkedHashMap<String, Record>();
+		added = new LinkedHashMap<String, Record>();
+		removed = new LinkedHashMap<String, Record>();
+		merged = new LinkedHashMap<String, Record>();
+		try {
+			// XXX skip(40) if we don't need these? 
+			byte[] parents = new byte[40];
+			da.readBytes(parents, 0, 40);
+			parents = null;
+			do {
+				final byte state = da.readByte();
+				final int fmode = da.readInt();
+				final int size = da.readInt();
+				final int time = da.readInt();
+				final int nameLen = da.readInt();
+				String fn1 = null, fn2 = null;
+				byte[] name = new byte[nameLen];
+				da.readBytes(name, 0, nameLen);
+				for (int i = 0; i < nameLen; i++) {
+					if (name[i] == 0) {
+						fn1 = new String(name, 0, i, "UTF-8"); // XXX unclear from documentation what encoding is used there
+						fn2 = new String(name, i+1, nameLen - i - 1, "UTF-8"); // need to check with different system codepages
+						break;
+					}
+				}
+				if (fn1 == null) {
+					fn1 = new String(name);
+				}
+				Record r = new Record(fmode, size, time, fn1, fn2);
+				if (state == 'n') {
+					normal.put(r.name1, r);
+				} else if (state == 'a') {
+					added.put(r.name1, r);
+				} else if (state == 'r') {
+					removed.put(r.name1, r);
+				} else if (state == 'm') {
+					merged.put(r.name1, r);
+				} else {
+					// FIXME log error?
+				}
+			} while (!da.isEmpty());
+		} catch (IOException ex) {
+			ex.printStackTrace(); // FIXME log error, clean dirstate?
+		} finally {
+			da.done();
+		}
+	}
+
+	// new, modifiable collection
+	/*package-local*/ TreeSet<String> all() {
+		read();
+		TreeSet<String> rv = new TreeSet<String>();
+		@SuppressWarnings("unchecked")
+		Map<String, Record>[] all = new Map[] { normal, added, removed, merged };
+		for (int i = 0; i < all.length; i++) {
+			for (Record r : all[i].values()) {
+				rv.add(r.name1);
+			}
+		}
+		return rv;
+	}
+	
+	/*package-local*/ Record checkNormal(Path fname) {
+		return normal.get(fname.toString());
+	}
+
+	/*package-local*/ Record checkAdded(Path fname) {
+		return added.get(fname.toString());
+	}
+	/*package-local*/ Record checkRemoved(Path fname) {
+		return removed.get(fname.toString());
+	}
+	/*package-local*/ Record checkRemoved(String fname) {
+		return removed.get(fname);
+	}
+	/*package-local*/ Record checkMerged(Path fname) {
+		return merged.get(fname.toString());
+	}
+
+
+
+
+	/*package-local*/ void dump() {
+		read();
+		@SuppressWarnings("unchecked")
+		Map<String, Record>[] all = new Map[] { normal, added, removed, merged };
+		char[] x = new char[] {'n', 'a', 'r', 'm' };
+		for (int i = 0; i < all.length; i++) {
+			for (Record r : all[i].values()) {
+				System.out.printf("%c %3o%6d %30tc\t\t%s", x[i], r.mode, r.size, (long) r.time * 1000, r.name1);
+				if (r.name2 != null) {
+					System.out.printf(" --> %s", r.name2);
+				}
+				System.out.println();
+			}
+			System.out.println();
+		}
+	}
+	
+	/*package-local*/ static class Record {
+		final int mode;
+		final int size;
+		final int time;
+		final String name1;
+		final String name2;
+
+		public Record(int fmode, int fsize, int ftime, String name1, String name2) {
+			mode = fmode;
+			size = fsize;
+			time = ftime;
+			this.name1 = name1;
+			this.name2 = name2;
+			
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgIgnore.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Handling of ignored paths according to .hgignore configuration
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgIgnore {
+
+	private List<Pattern> entries;
+
+	HgIgnore() {
+		entries = Collections.emptyList();
+	}
+
+	/* package-local */void read(File hgignoreFile) throws IOException {
+		if (!hgignoreFile.exists()) {
+			return;
+		}
+		ArrayList<Pattern> result = new ArrayList<Pattern>(entries); // start with existing
+		String syntax = "regex"; // or "glob"
+		BufferedReader fr = new BufferedReader(new FileReader(hgignoreFile));
+		String line;
+		while ((line = fr.readLine()) != null) {
+			line = line.trim();
+			if (line.startsWith("syntax:")) {
+				syntax = line.substring("syntax:".length()).trim();
+				if (!"regex".equals(syntax) && !"glob".equals(syntax)) {
+					throw new IllegalStateException(line);
+				}
+			} else if (line.length() > 0) {
+				// shall I account for local paths in the file (i.e.
+				// back-slashed on windows)?
+				int x;
+				if ((x = line.indexOf('#')) >= 0) {
+					line = line.substring(0, x).trim();
+					if (line.length() == 0) {
+						continue;
+					}
+				}
+				if ("glob".equals(syntax)) {
+					// hgignore(5)
+					// (http://www.selenic.com/mercurial/hgignore.5.html) says slashes '\' are escape characters,
+					// hence no special  treatment of Windows path
+					// however, own attempts make me think '\' on Windows are not treated as escapes
+					line = glob2regex(line);
+				}
+				result.add(Pattern.compile(line)); // case-sensitive
+			}
+		}
+		result.trimToSize();
+		entries = result;
+	}
+
+	// note, #isIgnored(), even if queried for directories and returned positive reply, may still get
+	// a file from that ignored folder to get examined. Thus, patterns like "bin" shall match not only a folder,
+	// but any file under that folder as well
+	// Alternatively, file walker may memorize folder is ignored and uses this information for all nested files. However,
+	// this approach would require walker (a) return directories (b) provide nesting information. This may become
+	// troublesome when one walks not over io.File, but Eclipse's IResource or any other custom VFS.
+	//
+	//
+	// might be interesting, although looks like of no direct use in my case 
+	// @see http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns
+	private String glob2regex(String line) {
+		assert line.length() > 0;
+		StringBuilder sb = new StringBuilder(line.length() + 10);
+		sb.append('^'); // help avoid matcher.find() to match 'bin' pattern in the middle of the filename
+		int start = 0, end = line.length() - 1;
+		// '*' at the beginning and end of a line are useless for Pattern
+		// XXX although how about **.txt - such globs can be seen in a config, are they valid for HgIgnore?
+		while (start <= end && line.charAt(start) == '*') start++;
+		while (end > start && line.charAt(end) == '*') end--;
+
+		for (int i = start; i <= end; i++) {
+			char ch = line.charAt(i);
+			if (ch == '.' || ch == '\\') {
+				sb.append('\\');
+			} else if (ch == '?') {
+				// simple '.' substitution might work out, however, more formally 
+				// a char class seems more appropriate to avoid accidentally
+				// matching a subdirectory with ? char (i.e. /a/b?d against /a/bad, /a/bed and /a/b/d)
+				// @see http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_03
+				// quote: "The slash character in a pathname shall be explicitly matched by using one or more slashes in the pattern; 
+				// it shall neither be matched by the asterisk or question-mark special characters nor by a bracket expression" 
+				sb.append("[^/]");
+				continue;
+			} else if (ch == '*') {
+				sb.append("[^/]*?");
+				continue;
+			}
+			sb.append(ch);
+		}
+		return sb.toString();
+	}
+
+	// TODO use PathGlobMatcher
+	public boolean isIgnored(Path path) {
+		for (Pattern p : entries) {
+			if (p.matcher(path).find()) {
+				return true;
+			}
+		}
+		return false;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgInternals.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,94 @@
+/*
+ * 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.repo;
+
+import static org.tmatesoft.hg.repo.HgRepository.*;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.tmatesoft.hg.internal.ConfigFile;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ * DO NOT USE THIS CLASS, INTENDED FOR TESTING PURPOSES.
+ * 
+ * Debug helper, to access otherwise restricted (package-local) methods
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+
+ */
+public class HgInternals {
+
+	private final HgRepository repo;
+
+	public HgInternals(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+
+	public void dumpDirstate() {
+		repo.loadDirstate().dump();
+	}
+
+	public boolean[] checkIgnored(String... toCheck) {
+		HgIgnore ignore = repo.getIgnore();
+		boolean[] rv = new boolean[toCheck.length];
+		for (int i = 0; i < toCheck.length; i++) {
+			rv[i] = ignore.isIgnored(Path.create(toCheck[i]));
+		}
+		return rv;
+	}
+
+	public File getRepositoryDir() {
+		return repo.getRepositoryRoot();
+	}
+	
+	public ConfigFile getRepoConfig() {
+		return repo.getConfigFile();
+	}
+
+	// in fact, need a setter for this anyway, shall move to internal.Internals perhaps?
+	public String getNextCommitUsername() {
+		String hgUser = System.getenv("HGUSER");
+		if (hgUser != null && hgUser.trim().length() > 0) {
+			return hgUser.trim();
+		}
+		String configValue = getRepoConfig().getString("ui", "username", null);
+		if (configValue != null) {
+			return configValue;
+		}
+		String email = System.getenv("EMAIL");
+		if (email != null && email.trim().length() > 0) {
+			return email;
+		}
+		String username = System.getProperty("user.name");
+		try {
+			String hostname = InetAddress.getLocalHost().getHostName();
+			return username + '@' + hostname; 
+		} catch (UnknownHostException ex) {
+			return username;
+		}
+	}
+
+	// Convenient check of local revision number for validity (not all negative values are wrong as long as we use negative constants)
+	public static boolean wrongLocalRevision(int rev) {
+		return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION; 
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgLookup.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.tmatesoft.hg.core.HgBadArgumentException;
+import org.tmatesoft.hg.core.HgException;
+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
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgLookup {
+
+	private ConfigFile globalCfg;
+
+	public HgRepository detectFromWorkingDir() throws HgException {
+		return detect(System.getProperty("user.dir"));
+	}
+
+	public HgRepository detect(String location) throws HgException {
+		return detect(new File(location));
+	}
+
+	// look up in specified location and above
+	public HgRepository detect(File location) throws HgException {
+		File dir = location.getAbsoluteFile();
+		File repository;
+		do {
+			repository = new File(dir, ".hg");
+			if (repository.exists() && repository.isDirectory()) {
+				break;
+			}
+			repository = null;
+			dir = dir.getParentFile();
+			
+		} while(dir != null);
+		if (repository == null) {
+			// return invalid repository
+			return new HgRepository(location.getPath());
+		}
+		try {
+			String repoPath = repository.getParentFile().getCanonicalPath();
+			return new HgRepository(repoPath, repository);
+		} catch (IOException ex) {
+			throw new HgException(location.toString(), ex);
+		}
+	}
+	
+	public HgBundle loadBundle(File location) throws HgException {
+		if (location == null || !location.canRead()) {
+			throw new IllegalArgumentException();
+		}
+		return new HgBundle(new DataAccessProvider(), location).link();
+	}
+	
+	/**
+	 * Try to instantiate remote server.
+	 * @param key either URL or a key from configuration file that points to remote server  
+	 * @param hgRepo <em>NOT USED YET<em> local repository that may have extra config, or default remote location
+	 * @return an instance featuring access to remote repository, check {@link HgRemoteRepository#isInvalid()} before actually using it
+	 * @throws HgBadArgumentException if anything is wrong with the remote server's URL
+	 */
+	public HgRemoteRepository detectRemote(String key, HgRepository hgRepo) throws HgBadArgumentException {
+		URL url;
+		Exception toReport;
+		try {
+			url = new URL(key);
+			toReport = null;
+		} catch (MalformedURLException ex) {
+			url = null;
+			toReport = ex;
+		}
+		if (url == null) {
+			String server = getGlobalConfig().getSection("paths").get(key);
+			if (server == null) {
+				throw new HgBadArgumentException(String.format("Can't find server %s specification in the config", key), toReport);
+			}
+			try {
+				url = new URL(server);
+			} catch (MalformedURLException ex) {
+				throw new HgBadArgumentException(String.format("Found %s server spec in the config, but failed to initialize with it", key), ex);
+			}
+		}
+		return new HgRemoteRepository(url);
+	}
+	
+	public HgRemoteRepository detect(URL url) throws HgException {
+		if (url == null) {
+			throw new IllegalArgumentException();
+		}
+		if (Boolean.FALSE.booleanValue()) {
+			throw HgRepository.notImplemented();
+		}
+		return new HgRemoteRepository(url);
+	}
+
+	private ConfigFile getGlobalConfig() {
+		if (globalCfg == null) {
+			globalCfg = new Internals().newConfigFile();
+			globalCfg.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
+		}
+		return globalCfg;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgManifest.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import java.io.IOException;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.Pool;
+import org.tmatesoft.hg.internal.RevlogStream;
+
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgManifest extends Revlog {
+
+	/*package-local*/ HgManifest(HgRepository hgRepo, RevlogStream content) {
+		super(hgRepo, content);
+	}
+
+	public void walk(int start, int end, final Inspector inspector) {
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		content.iterate(start, end, true, new ManifestParser(inspector));
+	}
+
+	public interface Inspector {
+		boolean begin(int revision, Nodeid nid);
+		boolean next(Nodeid nid, String fname, String flags);
+		boolean end(int revision);
+	}
+
+	private static class ManifestParser implements RevlogStream.Inspector {
+		private boolean gtg = true; // good to go
+		private final Inspector inspector;
+		private final Pool<Nodeid> nodeidPool;
+		private final Pool<String> fnamePool;
+		private final Pool<String> flagsPool;
+
+		public ManifestParser(Inspector delegate) {
+			assert delegate != null;
+			inspector = delegate;
+			nodeidPool = new Pool<Nodeid>();
+			fnamePool = new Pool<String>();
+			flagsPool = new Pool<String>();
+		}
+
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
+			if (!gtg) {
+				return;
+			}
+			try {
+				gtg = gtg && inspector.begin(revisionNumber, new Nodeid(nodeid, true));
+				int i;
+				String fname = null;
+				String flags = null;
+				Nodeid nid = null;
+				byte[] data = da.byteArray();
+				for (i = 0; gtg && i < actualLen; i++) {
+					int x = i;
+					for( ; data[i] != '\n' && i < actualLen; i++) {
+						if (fname == null && data[i] == 0) {
+							fname = fnamePool.unify(new String(data, x, i - x));
+							x = i+1;
+						}
+					}
+					if (i < actualLen) {
+						assert data[i] == '\n'; 
+						int nodeidLen = i - x < 40 ? i-x : 40;
+						nid = nodeidPool.unify(Nodeid.fromAscii(data, x, nodeidLen));
+						if (nodeidLen + x < i) {
+							// 'x' and 'l' for executable bits and symlinks?
+							// hg --debug manifest shows 644 for each regular file in my repo
+							flags = flagsPool.unify(new String(data, x + nodeidLen, i-x-nodeidLen));
+						}
+						gtg = gtg && inspector.next(nid, fname, flags);
+					}
+					nid = null;
+					fname = flags = null;
+				}
+				gtg = gtg && inspector.end(revisionNumber);
+			} catch (IOException ex) {
+				throw new HgBadStateException(ex);
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgRemoteRepository.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,447 @@
+/*
+ * 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.repo;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.StreamTokenizer;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import java.util.zip.InflaterInputStream;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.tmatesoft.hg.core.HgBadArgumentException;
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.Nodeid;
+
+/**
+ * WORK IN PROGRESS, DO NOT USE
+ * 
+ * @see http://mercurial.selenic.com/wiki/WireProtocol
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgRemoteRepository {
+	
+	private final URL url;
+	private final SSLContext sslContext;
+	private final String authInfo;
+	private final boolean debug = Boolean.parseBoolean(System.getProperty("hg4j.remote.debug"));
+	private HgLookup lookupHelper;
+
+	HgRemoteRepository(URL url) throws HgBadArgumentException {
+		if (url == null) {
+			throw new IllegalArgumentException();
+		}
+		this.url = url;
+		if ("https".equals(url.getProtocol())) {
+			try {
+				sslContext = SSLContext.getInstance("SSL");
+				class TrustEveryone implements X509TrustManager {
+					public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+						if (debug) {
+							System.out.println("checkClientTrusted:" + authType);
+						}
+					}
+					public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+						if (debug) {
+							System.out.println("checkServerTrusted:" + authType);
+						}
+					}
+					public X509Certificate[] getAcceptedIssuers() {
+						return new X509Certificate[0];
+					}
+				};
+				sslContext.init(null, new TrustManager[] { new TrustEveryone() }, null);
+			} catch (Exception ex) {
+				throw new HgBadArgumentException("Can't initialize secure connection", ex);
+			}
+		} else {
+			sslContext = null;
+		}
+		if (url.getUserInfo() != null) {
+			String ai = null;
+			try {
+				// Hack to get Base64-encoded credentials
+				Preferences tempNode = Preferences.userRoot().node("xxx");
+				tempNode.putByteArray("xxx", url.getUserInfo().getBytes());
+				ai = tempNode.get("xxx", null);
+				tempNode.removeNode();
+			} catch (BackingStoreException ex) {
+				ex.printStackTrace();
+				// IGNORE
+			}
+			authInfo = ai;
+		} else {
+			authInfo = null;
+		}
+	}
+	
+	public boolean isInvalid() throws HgException {
+		// say hello to server, check response
+		if (Boolean.FALSE.booleanValue()) {
+			throw HgRepository.notImplemented();
+		}
+		return false; // FIXME
+	}
+
+	/**
+	 * @return human-readable address of the server, without user credentials or any other security information
+	 */
+	public String getLocation() {
+		if (url.getUserInfo() == null) {
+			return url.toExternalForm();
+		}
+		if (url.getPort() != -1) {
+			return String.format("%s://%s:%d%s", url.getProtocol(), url.getHost(), url.getPort(), url.getPath());
+		} else {
+			return String.format("%s://%s%s", url.getProtocol(), url.getHost(), url.getPath());
+		}
+	}
+
+	public List<Nodeid> heads() throws HgException {
+		try {
+			URL u = new URL(url, url.getPath() + "?cmd=heads");
+			HttpURLConnection c = setupConnection(u.openConnection());
+			c.connect();
+			if (debug) {
+				dumpResponseHeader(u, c);
+			}
+			InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII");
+			StreamTokenizer st = new StreamTokenizer(is);
+			st.ordinaryChars('0', '9');
+			st.wordChars('0', '9');
+			st.eolIsSignificant(false);
+			LinkedList<Nodeid> parseResult = new LinkedList<Nodeid>();
+			while (st.nextToken() != StreamTokenizer.TT_EOF) {
+				parseResult.add(Nodeid.fromAscii(st.sval));
+			}
+			return parseResult;
+		} catch (MalformedURLException ex) {
+			throw new HgException(ex);
+		} catch (IOException ex) {
+			throw new HgException(ex);
+		}
+	}
+	
+	public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgException {
+		Range r = new Range(base, tip);
+		// XXX shall handle errors like no range key in the returned map, not sure how.
+		return between(Collections.singletonList(r)).get(r);
+	}
+
+	/**
+	 * @param ranges
+	 * @return map, where keys are input instances, values are corresponding server reply
+	 * @throws HgException 
+	 */
+	public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgException {
+		if (ranges.isEmpty()) {
+			return Collections.emptyMap();
+		}
+		// if fact, shall do other way round, this method shall send 
+		LinkedHashMap<Range, List<Nodeid>> rv = new LinkedHashMap<HgRemoteRepository.Range, List<Nodeid>>(ranges.size() * 4 / 3);
+		StringBuilder sb = new StringBuilder(20 + ranges.size() * 82);
+		sb.append("pairs=");
+		for (Range r : ranges) {
+			sb.append(r.end.toString());
+			sb.append('-');
+			sb.append(r.start.toString());
+			sb.append('+');
+		}
+		if (sb.charAt(sb.length() - 1) == '+') {
+			// strip last space 
+			sb.setLength(sb.length() - 1);
+		}
+		try {
+			boolean usePOST = ranges.size() > 3;
+			URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString()));
+			HttpURLConnection c = setupConnection(u.openConnection());
+			if (usePOST) {
+				c.setRequestMethod("POST");
+				c.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */));
+				c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+				c.setDoOutput(true);
+				c.connect();
+				OutputStream os = c.getOutputStream();
+				os.write(sb.toString().getBytes());
+				os.flush();
+				os.close();
+			} else {
+				c.connect();
+			}
+			if (debug) {
+				System.out.printf("%d ranges, method:%s \n", ranges.size(), c.getRequestMethod());
+				dumpResponseHeader(u, c);
+			}
+			InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII");
+			StreamTokenizer st = new StreamTokenizer(is);
+			st.ordinaryChars('0', '9');
+			st.wordChars('0', '9');
+			st.eolIsSignificant(true);
+			Iterator<Range> rangeItr = ranges.iterator();
+			LinkedList<Nodeid> currRangeList = null;
+			Range currRange = null;
+			boolean possiblyEmptyNextLine = true;
+			while (st.nextToken() != StreamTokenizer.TT_EOF) {
+				if (st.ttype == StreamTokenizer.TT_EOL) {
+					if (possiblyEmptyNextLine) {
+						// newline follows newline;
+						assert currRange == null;
+						assert currRangeList == null;
+						if (!rangeItr.hasNext()) {
+							throw new HgBadStateException();
+						}
+						rv.put(rangeItr.next(), Collections.<Nodeid>emptyList());
+					} else {
+						if (currRange == null || currRangeList == null) {
+							throw new HgBadStateException();
+						}
+						// indicate next range value is needed
+						currRange = null;
+						currRangeList = null;
+						possiblyEmptyNextLine = true;
+					}
+				} else {
+					possiblyEmptyNextLine = false;
+					if (currRange == null) {
+						if (!rangeItr.hasNext()) {
+							throw new HgBadStateException();
+						}
+						currRange = rangeItr.next();
+						currRangeList = new LinkedList<Nodeid>();
+						rv.put(currRange, currRangeList);
+					}
+					Nodeid nid = Nodeid.fromAscii(st.sval);
+					currRangeList.addLast(nid);
+				}
+			}
+			is.close();
+			return rv;
+		} catch (MalformedURLException ex) {
+			throw new HgException(ex);
+		} catch (IOException ex) {
+			throw new HgException(ex);
+		}
+	}
+
+	public List<RemoteBranch> branches(List<Nodeid> nodes) throws HgException {
+		StringBuilder sb = new StringBuilder(20 + nodes.size() * 41);
+		sb.append("nodes=");
+		for (Nodeid n : nodes) {
+			sb.append(n.toString());
+			sb.append('+');
+		}
+		if (sb.charAt(sb.length() - 1) == '+') {
+			// strip last space 
+			sb.setLength(sb.length() - 1);
+		}
+		try {
+			URL u = new URL(url, url.getPath() + "?cmd=branches&" + sb.toString());
+			HttpURLConnection c = setupConnection(u.openConnection());
+			c.connect();
+			if (debug) {
+				dumpResponseHeader(u, c);
+			}
+			InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII");
+			StreamTokenizer st = new StreamTokenizer(is);
+			st.ordinaryChars('0', '9');
+			st.wordChars('0', '9');
+			st.eolIsSignificant(false);
+			ArrayList<Nodeid> parseResult = new ArrayList<Nodeid>(nodes.size() * 4);
+			while (st.nextToken() != StreamTokenizer.TT_EOF) {
+				parseResult.add(Nodeid.fromAscii(st.sval));
+			}
+			if (parseResult.size() != nodes.size() * 4) {
+				throw new HgException(String.format("Bad number of nodeids in result (shall be factor 4), expected %d, got %d", nodes.size()*4, parseResult.size()));
+			}
+			ArrayList<RemoteBranch> rv = new ArrayList<RemoteBranch>(nodes.size());
+			for (int i = 0; i < nodes.size(); i++) {
+				RemoteBranch rb = new RemoteBranch(parseResult.get(i*4), parseResult.get(i*4 + 1), parseResult.get(i*4 + 2), parseResult.get(i*4 + 3));
+				rv.add(rb);
+			}
+			return rv;
+		} catch (MalformedURLException ex) {
+			throw new HgException(ex);
+		} catch (IOException ex) {
+			throw new HgException(ex);
+		}
+	}
+
+	/*
+	 * XXX need to describe behavior when roots arg is empty; our RepositoryComparator code currently returns empty lists when
+	 * no common elements found, which in turn means we need to query changes starting with NULL nodeid.
+	 * 
+	 * WireProtocol wiki: roots = a list of the latest nodes on every service side changeset branch that both the client and server know about.
+	 * 
+	 * Perhaps, shall be named 'changegroup'
+
+	 * Changegroup: 
+	 * http://mercurial.selenic.com/wiki/Merge 
+	 * http://mercurial.selenic.com/wiki/WireProtocol 
+	 * 
+	 * according to latter, bundleformat data is sent through zlib
+	 * (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 HgException {
+		List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots;
+		StringBuilder sb = new StringBuilder(20 + _roots.size() * 41);
+		sb.append("roots=");
+		for (Nodeid n : _roots) {
+			sb.append(n.toString());
+			sb.append('+');
+		}
+		if (sb.charAt(sb.length() - 1) == '+') {
+			// strip last space 
+			sb.setLength(sb.length() - 1);
+		}
+		try {
+			URL u = new URL(url, url.getPath() + "?cmd=changegroup&" + sb.toString());
+			HttpURLConnection c = setupConnection(u.openConnection());
+			c.connect();
+			if (debug) {
+				dumpResponseHeader(u, c);
+			}
+			File tf = writeBundle(c.getInputStream(), false, "HG10GZ" /*didn't see any other that zip*/);
+			if (debug) {
+				System.out.printf("Wrote bundle %s for roots %s\n", tf, sb);
+			}
+			return getLookupHelper().loadBundle(tf);
+		} catch (MalformedURLException ex) {
+			throw new HgException(ex);
+		} catch (IOException ex) {
+			throw new HgException(ex);
+		}
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + '[' + getLocation() + ']';
+	}
+
+	private HgLookup getLookupHelper() {
+		if (lookupHelper == null) {
+			lookupHelper = new HgLookup();
+		}
+		return lookupHelper;
+	}
+	
+	private HttpURLConnection setupConnection(URLConnection urlConnection) {
+		urlConnection.setRequestProperty("User-Agent", "hg4j/0.5.0");
+		urlConnection.addRequestProperty("Accept", "application/mercurial-0.1");
+		if (authInfo != null) {
+			urlConnection.addRequestProperty("Authorization", "Basic " + authInfo);
+		}
+		if (sslContext != null) {
+			((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory());
+		}
+		return (HttpURLConnection) urlConnection;
+	}
+
+	private void dumpResponseHeader(URL u, HttpURLConnection c) {
+		System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery());
+		System.out.println("Response headers:");
+		final Map<String, List<String>> headerFields = c.getHeaderFields();
+		for (String s : headerFields.keySet()) {
+			System.out.printf("%s: %s\n", s, c.getHeaderField(s));
+		}
+	}
+	
+	private static File writeBundle(InputStream is, boolean decompress, String header) throws IOException {
+		InputStream zipStream = decompress ? new InflaterInputStream(is) : is;
+		File tf = File.createTempFile("hg-bundle-", null);
+		FileOutputStream fos = new FileOutputStream(tf);
+		fos.write(header.getBytes());
+		int r;
+		byte[] buf = new byte[8*1024];
+		while ((r = zipStream.read(buf)) != -1) {
+			fos.write(buf, 0, r);
+		}
+		fos.close();
+		zipStream.close();
+		return tf;
+	}
+
+
+	public static final class Range {
+		/**
+		 * Root of the range, earlier revision
+		 */
+		public final Nodeid start;
+		/**
+		 * Head of the range, later revision.
+		 */
+		public final Nodeid end;
+		
+		/**
+		 * @param from - root/base revision
+		 * @param to - head/tip revision
+		 */
+		public Range(Nodeid from, Nodeid to) {
+			start = from;
+			end = to;
+		}
+	}
+
+	public static final class RemoteBranch {
+		public final Nodeid head, root, p1, p2;
+		
+		public RemoteBranch(Nodeid h, Nodeid r, Nodeid parent1, Nodeid parent2) {
+			head = h;
+			root = r;
+			p1 = parent1;
+			p2 = parent2;
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if (this == obj) {
+				return true;
+			}
+			if (false == obj instanceof RemoteBranch) {
+				return false;
+			}
+			RemoteBranch o = (RemoteBranch) obj;
+			return head.equals(o.head) && root.equals(o.root) && (p1 == null && o.p1 == null || p1.equals(o.p1)) && (p2 == null && o.p2 == null || p2.equals(o.p2));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgRepository.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import org.tmatesoft.hg.internal.ConfigFile;
+import org.tmatesoft.hg.internal.DataAccessProvider;
+import org.tmatesoft.hg.internal.Filter;
+import org.tmatesoft.hg.internal.RelativePathRewrite;
+import org.tmatesoft.hg.internal.RequiresFile;
+import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.util.FileIterator;
+import org.tmatesoft.hg.util.FileWalker;
+import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.PathRewrite;
+
+
+
+/**
+ * Shall be as state-less as possible, all the caching happens outside the repo, in commands/walkers
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class HgRepository {
+
+	// if new constants added, consider fixing HgInternals#wrongLocalRevision
+	public static final int TIP = -3;
+	public static final int BAD_REVISION = Integer.MIN_VALUE;
+	public static final int WORKING_COPY = -2;
+
+	// temp aux marker method
+	public static IllegalStateException notImplemented() {
+		return new IllegalStateException("Not implemented");
+	}
+	
+	private final File repoDir; // .hg folder
+	private final String repoLocation;
+	private final DataAccessProvider dataAccess;
+	private final PathRewrite normalizePath;
+	private final PathRewrite dataPathHelper;
+	private final PathRewrite repoPathHelper;
+
+	private HgChangelog changelog;
+	private HgManifest manifest;
+	private HgTags tags;
+	// XXX perhaps, shall enable caching explicitly
+	private final HashMap<Path, SoftReference<RevlogStream>> streamsCache = new HashMap<Path, SoftReference<RevlogStream>>();
+	
+	private final org.tmatesoft.hg.internal.Internals impl = new org.tmatesoft.hg.internal.Internals();
+	private HgIgnore ignore;
+	private ConfigFile configFile;
+
+	HgRepository(String repositoryPath) {
+		repoDir = null;
+		repoLocation = repositoryPath;
+		dataAccess = null;
+		dataPathHelper = repoPathHelper = null;
+		normalizePath = null;
+	}
+	
+	HgRepository(String repositoryPath, File repositoryRoot) {
+		assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory();
+		assert repositoryPath != null; 
+		assert repositoryRoot != null;
+		repoDir = repositoryRoot;
+		repoLocation = repositoryPath;
+		dataAccess = new DataAccessProvider();
+		final boolean runningOnWindows = System.getProperty("os.name").indexOf("Windows") != -1;
+		if (runningOnWindows) {
+			normalizePath = new PathRewrite() {
+					
+					public String rewrite(String path) {
+						// TODO handle . and .. (although unlikely to face them from GUI client)
+						path = path.replace('\\', '/').replace("//", "/");
+						if (path.startsWith("/")) {
+							path = path.substring(1);
+						}
+						return path;
+					}
+				};
+		} else {
+			normalizePath = new PathRewrite.Empty(); // or strip leading slash, perhaps? 
+		}
+		parseRequires();
+		dataPathHelper = impl.buildDataFilesHelper();
+		repoPathHelper = impl.buildRepositoryFilesHelper();
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + "[" + getLocation() + (isInvalid() ? "(BAD)" : "") + "]";
+	}                         
+	
+	public String getLocation() {
+		return repoLocation;
+	}
+
+	public boolean isInvalid() {
+		return repoDir == null || !repoDir.exists() || !repoDir.isDirectory();
+	}
+	
+	public HgChangelog getChangelog() {
+		if (this.changelog == null) {
+			String storagePath = repoPathHelper.rewrite("00changelog.i");
+			RevlogStream content = resolve(Path.create(storagePath), true);
+			this.changelog = new HgChangelog(this, content);
+		}
+		return this.changelog;
+	}
+	
+	public HgManifest getManifest() {
+		if (this.manifest == null) {
+			RevlogStream content = resolve(Path.create(repoPathHelper.rewrite("00manifest.i")), true);
+			this.manifest = new HgManifest(this, content);
+		}
+		return this.manifest;
+	}
+	
+	public final HgTags getTags() {
+		if (tags == null) {
+			tags = new HgTags();
+			try {
+				tags.readGlobal(new File(repoDir.getParentFile(), ".hgtags"));
+				tags.readLocal(new File(repoDir, "localtags"));
+			} catch (IOException ex) {
+				ex.printStackTrace(); // FIXME log or othewise report
+			}
+		}
+		return tags;
+	}
+	
+	public HgDataFile getFileNode(String path) {
+		String nPath = normalizePath.rewrite(path);
+		String storagePath = dataPathHelper.rewrite(nPath);
+		RevlogStream content = resolve(Path.create(storagePath), false);
+		Path p = Path.create(nPath);
+		if (content == null) {
+			return new HgDataFile(this, p);
+		}
+		return new HgDataFile(this, p, content);
+	}
+
+	public HgDataFile getFileNode(Path path) {
+		String storagePath = dataPathHelper.rewrite(path.toString());
+		RevlogStream content = resolve(Path.create(storagePath), false);
+		// XXX no content when no file? or HgDataFile.exists() to detect that?
+		if (content == null) {
+			return new HgDataFile(this, path);
+		}
+		return new HgDataFile(this, path, content);
+	}
+
+	/* clients need to rewrite path from their FS to a repository-friendly paths, and, perhaps, vice versa*/
+	public PathRewrite getToRepoPathHelper() {
+		return normalizePath;
+	}
+
+	// local to hide use of io.File. 
+	/*package-local*/ File getRepositoryRoot() {
+		return repoDir;
+	}
+
+	// XXX package-local, unless there are cases when required from outside (guess, working dir/revision walkers may hide dirstate access and no public visibility needed)
+	/*package-local*/ final HgDirstate loadDirstate() {
+		return new HgDirstate(getDataAccess(), new File(repoDir, "dirstate"));
+	}
+
+	// package-local, see comment for loadDirstate
+	/*package-local*/ final HgIgnore getIgnore() {
+		// TODO read config for additional locations
+		if (ignore == null) {
+			ignore = new HgIgnore();
+			try {
+				File ignoreFile = new File(repoDir.getParentFile(), ".hgignore");
+				ignore.read(ignoreFile);
+			} catch (IOException ex) {
+				ex.printStackTrace(); // log warn
+			}
+		}
+		return ignore;
+	}
+
+	/*package-local*/ DataAccessProvider getDataAccess() {
+		return dataAccess;
+	}
+
+	// FIXME not sure repository shall create walkers
+	/*package-local*/ FileIterator createWorkingDirWalker() {
+		File repoRoot = repoDir.getParentFile();
+		Path.Source pathSrc = new Path.SimpleSource(new PathRewrite.Composite(new RelativePathRewrite(repoRoot), getToRepoPathHelper()));
+		// Impl note: simple source is enough as files in the working dir are all unique
+		// even if they might get reused (i.e. after FileIterator#reset() and walking once again),
+		// path caching is better to be done in the code which knows that path are being reused 
+		return new FileWalker(repoRoot, pathSrc);
+	}
+
+	/**
+	 * Perhaps, should be separate interface, like ContentLookup
+	 * path - repository storage path (i.e. one usually with .i or .d)
+	 */
+	/*package-local*/ RevlogStream resolve(Path path, boolean shallFakeNonExistent) {
+		final SoftReference<RevlogStream> ref = streamsCache.get(path);
+		RevlogStream cached = ref == null ? null : ref.get();
+		if (cached != null) {
+			return cached;
+		}
+		File f = new File(repoDir, path.toString());
+		if (f.exists()) {
+			RevlogStream s = new RevlogStream(dataAccess, f);
+			streamsCache.put(path, new SoftReference<RevlogStream>(s));
+			return s;
+		} else {
+			if (shallFakeNonExistent) {
+				try {
+					File fake = File.createTempFile(f.getName(), null);
+					fake.deleteOnExit();
+					return new RevlogStream(dataAccess, fake);
+				} catch (IOException ex) {
+					ex.printStackTrace(); // FIXME report in debug
+				}
+			}
+		}
+		return null; // XXX empty stream instead?
+	}
+	
+	// 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"));
+		}
+		return configFile;
+	}
+	
+	/*package-local*/ List<Filter> getFiltersFromRepoToWorkingDir(Path p) {
+		return instantiateFilters(p, new Filter.Options(Filter.Direction.FromRepo));
+	}
+
+	/*package-local*/ List<Filter> getFiltersFromWorkingDirToRepo(Path p) {
+		return instantiateFilters(p, new Filter.Options(Filter.Direction.ToRepo));
+	}
+
+	private List<Filter> instantiateFilters(Path p, Filter.Options opts) {
+		List<Filter.Factory> factories = impl.getFilters(this, getConfigFile());
+		if (factories.isEmpty()) {
+			return Collections.emptyList();
+		}
+		ArrayList<Filter> rv = new ArrayList<Filter>(factories.size());
+		for (Filter.Factory ff : factories) {
+			Filter f = ff.create(p, opts);
+			if (f != null) {
+				rv.add(f);
+			}
+		}
+		return rv;
+	}
+
+	private void parseRequires() {
+		new RequiresFile().parse(impl, new File(repoDir, "requires"));
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgStatusCollector.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,466 @@
+/*
+ * 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.repo;
+
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.core.HgDataStreamException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.Pool;
+import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+
+/**
+ * RevisionWalker?
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgStatusCollector {
+
+	private final HgRepository repo;
+	private final SortedMap<Integer, ManifestRevisionInspector> cache; // sparse array, in fact
+	// with cpython repository, ~70 000 changes, complete Log (direct out, no reverse) output 
+	// no cache limit, no nodeids and fname caching - OOME on changeset 1035
+	// no cache limit, but with cached nodeids and filenames - 1730+
+	// cache limit 100 - 19+ minutes to process 10000, and still working (too long, stopped)
+	private final int cacheMaxSize = 50; // do not keep too much manifest revisions
+	private PathPool pathPool;
+	private final Pool<Nodeid> cacheNodes;
+	private final Pool<String> cacheFilenames; // XXX in fact, need to think if use of PathPool directly instead is better solution
+	private final ManifestRevisionInspector emptyFakeState;
+	
+
+	public HgStatusCollector(HgRepository hgRepo) {
+		this.repo = hgRepo;
+		cache = new TreeMap<Integer, ManifestRevisionInspector>();
+		cacheNodes = new Pool<Nodeid>();
+		cacheFilenames = new Pool<String>();
+
+		emptyFakeState = new ManifestRevisionInspector(null, null);
+		emptyFakeState.begin(-1, null);
+		emptyFakeState.end(-1);
+	}
+	
+	public HgRepository getRepo() {
+		return repo;
+	}
+	
+	private ManifestRevisionInspector get(int rev) {
+		ManifestRevisionInspector i = cache.get(rev);
+		if (i == null) {
+			if (rev == -1) {
+				return emptyFakeState;
+			}
+			while (cache.size() > cacheMaxSize) {
+				// assume usually we go from oldest to newest, hence remove oldest as most likely to be no longer necessary
+				cache.remove(cache.firstKey());
+			}
+			i = new ManifestRevisionInspector(cacheNodes, cacheFilenames);
+			cache.put(rev, i);
+			repo.getManifest().walk(rev, rev, i);
+		}
+		return i;
+	}
+
+	private boolean cached(int revision) {
+		return cache.containsKey(revision) || revision == -1;
+	}
+	
+	private void initCacheRange(int minRev, int maxRev) {
+		while (cache.size() > cacheMaxSize) {
+			// assume usually we go from oldest to newest, hence remove oldest as most likely to be no longer necessary
+			cache.remove(cache.firstKey());
+		}
+		repo.getManifest().walk(minRev, maxRev, new HgManifest.Inspector() {
+			private ManifestRevisionInspector delegate;
+			private boolean cacheHit; // range may include revisions we already know about, do not re-create them
+
+			public boolean begin(int revision, Nodeid nid) {
+				assert delegate == null;
+				if (cache.containsKey(revision)) { // don't need to check emptyFakeState hit as revision never -1 here
+					cacheHit = true;
+				} else {
+					cache.put(revision, delegate = new ManifestRevisionInspector(cacheNodes, cacheFilenames));
+					// cache may grow bigger than max size here, but it's ok as present simplistic cache clearing mechanism may
+					// otherwise remove entries we just added
+					delegate.begin(revision, nid);
+					cacheHit = false;
+				}
+				return true;
+			}
+
+			public boolean next(Nodeid nid, String fname, String flags) {
+				if (!cacheHit) {
+					delegate.next(nid, fname, flags);
+				}
+				return true;
+			}
+			
+			public boolean end(int revision) {
+				if (!cacheHit) {
+					delegate.end(revision);
+				}
+				cacheHit = false;				
+				delegate = null;
+				return true;
+			}
+		});
+	}
+	
+	/*package-local*/ ManifestRevisionInspector raw(int rev) {
+		return get(rev);
+	}
+	/*package-local*/ PathPool getPathPool() {
+		if (pathPool == null) {
+			pathPool = new PathPool(new PathRewrite.Empty());
+		}
+		return pathPool;
+	}
+
+	/**
+	 * Allows sharing of a common path cache 
+	 */
+	public void setPathPool(PathPool pathPool) {
+		this.pathPool = pathPool;
+	}
+		
+	
+	// hg status --change <rev>
+	public void change(int rev, HgStatusInspector inspector) {
+		int[] parents = new int[2];
+		repo.getChangelog().parents(rev, parents, null, null);
+		walk(parents[0], rev, inspector);
+	}
+	
+	// I assume revision numbers are the same for changelog and manifest - here 
+	// user would like to pass changelog revision numbers, and I use them directly to walk manifest.
+	// if this assumption is wrong, fix this (lookup manifest revisions from changeset).
+	// rev1 and rev2 may be -1 to indicate comparison to empty repository
+	// argument order matters 
+	public void walk(int rev1, int rev2, HgStatusInspector inspector) {
+		if (rev1 == rev2) {
+			throw new IllegalArgumentException();
+		}
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		if (inspector instanceof Record) {
+			((Record) inspector).init(rev1, rev2, this);
+		}
+		final int lastManifestRevision = repo.getManifest().getLastRevision();
+		if (rev1 == TIP) {
+			rev1 = lastManifestRevision;
+		}
+		if (rev2 == TIP) {
+			rev2 = lastManifestRevision; 
+		}
+		// in fact, rev1 and rev2 are often next (or close) to each other,
+		// thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2))
+		ManifestRevisionInspector r1, r2 ;
+		boolean need1 = !cached(rev1), need2 = !cached(rev2);
+		if (need1 || need2) {
+			int minRev, maxRev;
+			if (need1 && need2 && Math.abs(rev1 - rev2) < 5 /*subjective equivalent of 'close enough'*/) {
+				minRev = rev1 < rev2 ? rev1 : rev2;
+				maxRev = minRev == rev1 ? rev2 : rev1;
+				if (minRev > 0) {
+					minRev--; // expand range a bit
+				}
+				initCacheRange(minRev, maxRev);
+				need1 = need2 = false;
+			}
+			// either both unknown and far from each other, or just one of them.
+			// read with neighbors to save potential subsequent calls for neighboring elements
+			// XXX perhaps, if revlog.baseRevision is cheap, shall expand minRev up to baseRevision
+			// which going to be read anyway
+			if (need1) {
+				minRev = rev1;
+				maxRev = rev1 < lastManifestRevision-5 ? rev1+5 : lastManifestRevision;
+				initCacheRange(minRev, maxRev);
+			}
+			if (need2) {
+				minRev = rev2;
+				maxRev = rev2 < lastManifestRevision-5 ? rev2+5 : lastManifestRevision;
+				initCacheRange(minRev, maxRev);
+			}
+		}
+		r1 = get(rev1);
+		r2 = get(rev2);
+
+		PathPool pp = getPathPool();
+
+		TreeSet<String> r1Files = new TreeSet<String>(r1.files());
+		for (String fname : r2.files()) {
+			if (r1Files.remove(fname)) {
+				Nodeid nidR1 = r1.nodeid(fname);
+				Nodeid nidR2 = r2.nodeid(fname);
+				String flagsR1 = r1.flags(fname);
+				String flagsR2 = r2.flags(fname);
+				if (nidR1.equals(nidR2) && ((flagsR2 == null && flagsR1 == null) || flagsR2.equals(flagsR1))) {
+					inspector.clean(pp.path(fname));
+				} else {
+					inspector.modified(pp.path(fname));
+				}
+			} else {
+				try {
+					Path copyTarget = pp.path(fname);
+					Path copyOrigin = getOriginIfCopy(repo, copyTarget, r1Files, rev1);
+					if (copyOrigin != null) {
+						inspector.copied(pp.path(copyOrigin) /*pipe through pool, just in case*/, copyTarget);
+					} else {
+						inspector.added(copyTarget);
+					}
+				} catch (HgDataStreamException ex) {
+					ex.printStackTrace();
+					// FIXME perhaps, shall record this exception to dedicated mediator and continue
+					// for a single file not to be irresolvable obstacle for a status operation
+				}
+			}
+		}
+		for (String left : r1Files) {
+			inspector.removed(pp.path(left));
+		}
+	}
+	
+	public Record status(int rev1, int rev2) {
+		Record rv = new Record();
+		walk(rev1, rev2, rv);
+		return rv;
+	}
+	
+	/*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection<String> originals, int originalChangelogRevision) throws HgDataStreamException {
+		HgDataFile df = hgRepo.getFileNode(fname);
+		while (df.isCopy()) {
+			Path original = df.getCopySourceName();
+			if (originals.contains(original.toString())) {
+				df = hgRepo.getFileNode(original);
+				int changelogRevision = df.getChangesetLocalRevision(0);
+				if (changelogRevision <= originalChangelogRevision) {
+					// copy/rename source was known prior to rev1 
+					// (both r1Files.contains is true and original was created earlier than rev1)
+					// without r1Files.contains changelogRevision <= rev1 won't suffice as the file
+					// might get removed somewhere in between (changelogRevision < R < rev1)
+					return original;
+				}
+				break; // copy/rename done later
+			} 
+			df = hgRepo.getFileNode(original); // try more steps away
+		}
+		return null;
+	}
+
+	// XXX for r1..r2 status, only modified, added, removed (and perhaps, clean) make sense
+	// XXX Need to specify whether copy targets are in added or not (@see Inspector#copied above)
+	public static class Record implements HgStatusInspector {
+		private List<Path> modified, added, removed, clean, missing, unknown, ignored;
+		private Map<Path, Path> copied;
+		
+		private int startRev, endRev;
+		private HgStatusCollector statusHelper;
+		
+		// XXX StatusCollector may additionally initialize Record instance to speed lookup of changed file revisions
+		// here I need access to ManifestRevisionInspector via #raw(). Perhaps, non-static class (to get
+		// implicit reference to StatusCollector) may be better?
+		// Since users may want to reuse Record instance we've once created (and initialized), we need to  
+		// ensure functionality is correct for each/any call (#walk checks instanceof Record and fixes it up)
+		// Perhaps, distinct helper (sc.getRevisionHelper().nodeid(fname)) would be better, just not clear
+		// how to supply [start..end] values there easily
+		/*package-local*/void init(int startRevision, int endRevision, HgStatusCollector self) {
+			startRev = startRevision;
+			endRev = endRevision;
+			statusHelper = self;
+		}
+		
+		public Nodeid nodeidBeforeChange(Path fname) {
+			if (statusHelper == null || startRev == BAD_REVISION) {
+				return null;
+			}
+			if ((modified == null || !modified.contains(fname)) && (removed == null || !removed.contains(fname))) {
+				return null;
+			}
+			return statusHelper.raw(startRev).nodeid(fname.toString());
+		}
+		public Nodeid nodeidAfterChange(Path fname) {
+			if (statusHelper == null || endRev == BAD_REVISION) {
+				return null;
+			}
+			if ((modified == null || !modified.contains(fname)) && (added == null || !added.contains(fname))) {
+				return null;
+			}
+			return statusHelper.raw(endRev).nodeid(fname.toString());
+		}
+		
+		public List<Path> getModified() {
+			return proper(modified);
+		}
+
+		public List<Path> getAdded() {
+			return proper(added);
+		}
+
+		public List<Path> getRemoved() {
+			return proper(removed);
+		}
+
+		public Map<Path,Path> getCopied() {
+			if (copied == null) {
+				return Collections.emptyMap();
+			}
+			return Collections.unmodifiableMap(copied);
+		}
+
+		public List<Path> getClean() {
+			return proper(clean);
+		}
+
+		public List<Path> getMissing() {
+			return proper(missing);
+		}
+
+		public List<Path> getUnknown() {
+			return proper(unknown);
+		}
+
+		public List<Path> getIgnored() {
+			return proper(ignored);
+		}
+		
+		private List<Path> proper(List<Path> l) {
+			if (l == null) {
+				return Collections.emptyList();
+			}
+			return Collections.unmodifiableList(l);
+		}
+
+		//
+		//
+		
+		public void modified(Path fname) {
+			modified = doAdd(modified, fname);
+		}
+
+		public void added(Path fname) {
+			added = doAdd(added, fname);
+		}
+
+		public void copied(Path fnameOrigin, Path fnameAdded) {
+			if (copied == null) {
+				copied = new LinkedHashMap<Path, Path>();
+			}
+			added(fnameAdded);
+			copied.put(fnameAdded, fnameOrigin);
+		}
+
+		public void removed(Path fname) {
+			removed = doAdd(removed, fname);
+		}
+
+		public void clean(Path fname) {
+			clean = doAdd(clean, fname);
+		}
+
+		public void missing(Path fname) {
+			missing = doAdd(missing, fname);
+		}
+
+		public void unknown(Path fname) {
+			unknown = doAdd(unknown, fname);
+		}
+
+		public void ignored(Path fname) {
+			ignored = doAdd(ignored, fname);
+		}
+
+		private static List<Path> doAdd(List<Path> l, Path p) {
+			if (l == null) {
+				l = new LinkedList<Path>();
+			}
+			l.add(p);
+			return l;
+		}
+	}
+	
+	/*package-local*/ static final class ManifestRevisionInspector implements HgManifest.Inspector {
+		private final TreeMap<String, Nodeid> idsMap;
+		private final TreeMap<String, String> flagsMap;
+		private final Pool<Nodeid> idsPool;
+		private final Pool<String> namesPool; 
+
+		// optional pools for effective management of nodeids and filenames (they are likely
+		// to be duplicated among different manifest revisions
+		public ManifestRevisionInspector(Pool<Nodeid> nodeidPool, Pool<String> filenamePool) {
+			idsPool = nodeidPool;
+			namesPool = filenamePool;
+			idsMap = new TreeMap<String, Nodeid>();
+			flagsMap = new TreeMap<String, String>();
+		}
+		
+		public Collection<String> files() {
+			return idsMap.keySet();
+		}
+
+		public Nodeid nodeid(String fname) {
+			return idsMap.get(fname);
+		}
+
+		public String flags(String fname) {
+			return flagsMap.get(fname);
+		}
+
+		//
+
+		public boolean next(Nodeid nid, String fname, String flags) {
+			if (namesPool != null) {
+				fname = namesPool.unify(fname);
+			}
+			if (idsPool != null) {
+				nid = idsPool.unify(nid);
+			}
+			idsMap.put(fname, nid);
+			if (flags != null) {
+				// TreeMap$Entry takes 32 bytes. No reason to keep null for such price
+				// Perhaps, Map<String, Pair<Nodeid, String>> might be better solution
+				flagsMap.put(fname, flags);
+			}
+			return true;
+		}
+
+		public boolean end(int revision) {
+			// in fact, this class cares about single revision
+			return false; 
+		}
+
+		public boolean begin(int revision, Nodeid nid) {
+			return true;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgStatusInspector.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Callback to get file status information
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface HgStatusInspector {
+	void modified(Path fname);
+	void added(Path fname);
+	// XXX need to specify whether StatusCollector invokes added() along with copied or not!
+	void copied(Path fnameOrigin, Path fnameAdded); // if copied files of no interest, should delegate to self.added(fnameAdded);
+	void removed(Path fname);
+	void clean(Path fname);
+	void missing(Path fname); // aka deleted (tracked by Hg, but not available in FS any more
+	void unknown(Path fname); // not tracked
+	void ignored(Path fname);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgTags.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,150 @@
+/*
+ * 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.repo;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.tmatesoft.hg.core.Nodeid;
+
+/**
+ * @see http://mercurial.selenic.com/wiki/TagDesign
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgTags {
+	// global tags come from ".hgtags"
+	// local come from ".hg/localtags"
+
+	private final Map<Nodeid, List<String>> globalToName;
+	private final Map<Nodeid, List<String>> localToName;
+	private final Map<String, List<Nodeid>> globalFromName;
+	private final Map<String, List<Nodeid>> localFromName;
+	
+	
+	/*package-local*/ HgTags() {
+		globalToName =  new HashMap<Nodeid, List<String>>();
+		localToName  =  new HashMap<Nodeid, List<String>>();
+		globalFromName = new TreeMap<String, List<Nodeid>>();
+		localFromName  = new TreeMap<String, List<Nodeid>>();
+	}
+	
+	/*package-local*/ void readLocal(File localTags) throws IOException {
+		if (localTags == null || localTags.isDirectory()) {
+			throw new IllegalArgumentException(String.valueOf(localTags));
+		}
+		read(localTags, localToName, localFromName);
+	}
+	
+	/*package-local*/ void readGlobal(File globalTags) throws IOException {
+		if (globalTags == null || globalTags.isDirectory()) {
+			throw new IllegalArgumentException(String.valueOf(globalTags));
+		}
+		read(globalTags, globalToName, globalFromName);
+	}
+	
+	private void read(File f, Map<Nodeid,List<String>> nid2name, Map<String, List<Nodeid>> name2nid) throws IOException {
+		if (!f.canRead()) {
+			return;
+		}
+		BufferedReader r = null;
+		try {
+			r = new BufferedReader(new FileReader(f));
+			read(r, nid2name, name2nid);
+		} finally {
+			if (r != null) {
+				r.close();
+			}
+		}
+	}
+	
+	private void read(BufferedReader reader, Map<Nodeid,List<String>> nid2name, Map<String, List<Nodeid>> name2nid) throws IOException {
+		String line;
+		while ((line = reader.readLine()) != null) {
+			line = line.trim();
+			if (line.length() == 0) {
+				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?) 
+				continue;
+			}
+			int spacePos = line.indexOf(' ');
+			if (spacePos != -1) {
+				assert spacePos == 40;
+				final byte[] nodeidBytes = line.substring(0, spacePos).getBytes();
+				Nodeid nid = Nodeid.fromAscii(nodeidBytes, 0, nodeidBytes.length);
+				String tagName = line.substring(spacePos+1);
+				List<Nodeid> nids = name2nid.get(tagName);
+				if (nids == null) {
+					nids = new LinkedList<Nodeid>();
+					// tagName is substring of full line, thus need a copy to let the line be GC'ed
+					// new String(tagName.toCharArray()) is more expressive, but results in 1 extra arraycopy
+					tagName = new String(tagName);
+					name2nid.put(tagName, nids);
+				}
+				// XXX repo.getNodeidCache().nodeid(nid);
+				((LinkedList<Nodeid>) nids).addFirst(nid);
+				List<String> revTags = nid2name.get(nid);
+				if (revTags == null) {
+					revTags = new LinkedList<String>();
+					nid2name.put(nid, revTags);
+				}
+				revTags.add(tagName);
+			} else {
+				System.out.println("Bad tags line:" + line); // FIXME see above
+			}
+		}
+	}
+
+	public List<String> tags(Nodeid nid) {
+		ArrayList<String> rv = new ArrayList<String>(5);
+		List<String> l;
+		if ((l = localToName.get(nid)) != null) {
+			rv.addAll(l);
+		}
+		if ((l = globalToName.get(nid)) != null) {
+			rv.addAll(l);
+		}
+		return rv;
+	}
+
+	public boolean isTagged(Nodeid nid) {
+		return localToName.containsKey(nid) || globalToName.containsKey(nid);
+	}
+
+	public List<Nodeid> tagged(String tagName) {
+		ArrayList<Nodeid> rv = new ArrayList<Nodeid>(5);
+		List<Nodeid> l;
+		if ((l = localFromName.get(tagName)) != null) {
+			rv.addAll(l);
+		}
+		if ((l = globalFromName.get(tagName)) != null) {
+			rv.addAll(l);
+		}
+		return rv;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,337 @@
+/*
+ * 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.repo;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.core.HgDataStreamException;
+import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.ByteArrayChannel;
+import org.tmatesoft.hg.internal.FilterByteChannel;
+import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector;
+import org.tmatesoft.hg.util.ByteChannel;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.FileIterator;
+import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgWorkingCopyStatusCollector {
+
+	private final HgRepository repo;
+	private final FileIterator repoWalker;
+	private HgDirstate dirstate;
+	private HgStatusCollector baseRevisionCollector;
+	private PathPool pathPool;
+
+	public HgWorkingCopyStatusCollector(HgRepository hgRepo) {
+		this(hgRepo, hgRepo.createWorkingDirWalker());
+	}
+
+	HgWorkingCopyStatusCollector(HgRepository hgRepo, FileIterator hgRepoWalker) {
+		this.repo = hgRepo;
+		this.repoWalker = hgRepoWalker;
+	}
+	
+	/**
+	 * Optionally, supply a collector instance that may cache (or have already cached) base revision
+	 * @param sc may be null
+	 */
+	public void setBaseRevisionCollector(HgStatusCollector sc) {
+		baseRevisionCollector = sc;
+	}
+
+	/*package-local*/ PathPool getPathPool() {
+		if (pathPool == null) {
+			if (baseRevisionCollector == null) {
+				pathPool = new PathPool(new PathRewrite.Empty());
+			} else {
+				return baseRevisionCollector.getPathPool();
+			}
+		}
+		return pathPool;
+	}
+
+	public void setPathPool(PathPool pathPool) {
+		this.pathPool = pathPool;
+	}
+
+	
+	private HgDirstate getDirstate() {
+		if (dirstate == null) {
+			dirstate = repo.loadDirstate();
+		}
+		return dirstate;
+	}
+
+	// may be invoked few times
+	public void walk(int baseRevision, HgStatusInspector inspector) {
+		final HgIgnore hgIgnore = repo.getIgnore();
+		TreeSet<String> knownEntries = getDirstate().all();
+		final boolean isTipBase;
+		if (baseRevision == TIP) {
+			baseRevision = repo.getManifest().getRevisionCount() - 1;
+			isTipBase = true;
+		} else {
+			isTipBase = baseRevision == repo.getManifest().getRevisionCount() - 1;
+		}
+		HgStatusCollector.ManifestRevisionInspector collect = null;
+		Set<String> baseRevFiles = Collections.emptySet();
+		if (!isTipBase) {
+			if (baseRevisionCollector != null) {
+				collect = baseRevisionCollector.raw(baseRevision);
+			} else {
+				collect = new HgStatusCollector.ManifestRevisionInspector(null, null);
+				repo.getManifest().walk(baseRevision, baseRevision, collect);
+			}
+			baseRevFiles = new TreeSet<String>(collect.files());
+		}
+		if (inspector instanceof HgStatusCollector.Record) {
+			HgStatusCollector sc = baseRevisionCollector == null ? new HgStatusCollector(repo) : baseRevisionCollector;
+			((HgStatusCollector.Record) inspector).init(baseRevision, BAD_REVISION, sc);
+		}
+		repoWalker.reset();
+		final PathPool pp = getPathPool();
+		while (repoWalker.hasNext()) {
+			repoWalker.next();
+			Path fname = repoWalker.name();
+			File f = repoWalker.file();
+			if (hgIgnore.isIgnored(fname)) {
+				inspector.ignored(pp.path(fname));
+			} else if (knownEntries.remove(fname.toString())) {
+				// modified, added, removed, clean
+				if (collect != null) { // need to check against base revision, not FS file
+					checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector);
+					baseRevFiles.remove(fname.toString());
+				} else {
+					checkLocalStatusAgainstFile(fname, f, inspector);
+				}
+			} else {
+				inspector.unknown(pp.path(fname));
+			}
+		}
+		if (collect != null) {
+			for (String r : baseRevFiles) {
+				inspector.removed(pp.path(r));
+			}
+		}
+		for (String m : knownEntries) {
+			// missing known file from a working dir  
+			if (getDirstate().checkRemoved(m) == null) {
+				// not removed from the repository = 'deleted'  
+				inspector.missing(pp.path(m));
+			} else {
+				// removed from the repo
+				// if we check against non-tip revision, do not report files that were added past that revision and now removed.
+				if (collect == null || baseRevFiles.contains(m)) {
+					inspector.removed(pp.path(m));
+				}
+			}
+		}
+	}
+
+	public HgStatusCollector.Record status(int baseRevision) {
+		HgStatusCollector.Record rv = new HgStatusCollector.Record();
+		walk(baseRevision, rv);
+		return rv;
+	}
+
+	//********************************************
+
+	
+	private void checkLocalStatusAgainstFile(Path fname, File f, HgStatusInspector inspector) {
+		HgDirstate.Record r;
+		if ((r = getDirstate().checkNormal(fname)) != null) {
+			// either clean or modified
+			if (f.lastModified() / 1000 == r.time && r.size == f.length()) {
+				inspector.clean(getPathPool().path(fname));
+			} else {
+				// check actual content to avoid false modified files
+				HgDataFile df = repo.getFileNode(fname);
+				if (!areTheSame(f, df, HgRepository.TIP)) {
+					inspector.modified(df.getPath());
+				} else {
+					inspector.clean(df.getPath());
+				}
+			}
+		} else if ((r = getDirstate().checkAdded(fname)) != null) {
+			if (r.name2 == null) {
+				inspector.added(getPathPool().path(fname));
+			} else {
+				inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname));
+			}
+		} else if ((r = getDirstate().checkRemoved(fname)) != null) {
+			inspector.removed(getPathPool().path(fname));
+		} else if ((r = getDirstate().checkMerged(fname)) != null) {
+			inspector.modified(getPathPool().path(fname));
+		}
+	}
+	
+	// XXX refactor checkLocalStatus methods in more OO way
+	private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, ManifestRevisionInspector collect, int baseRevision, Path fname, File f, HgStatusInspector inspector) {
+		// fname is in the dirstate, either Normal, Added, Removed or Merged
+		Nodeid nid1 = collect.nodeid(fname.toString());
+		String flags = collect.flags(fname.toString());
+		HgDirstate.Record r;
+		if (nid1 == null) {
+			// normal: added?
+			// added: not known at the time of baseRevision, shall report
+			// merged: was not known, report as added?
+			if ((r = getDirstate().checkNormal(fname)) != null) {
+				try {
+					Path origin = HgStatusCollector.getOriginIfCopy(repo, fname, baseRevNames, baseRevision);
+					if (origin != null) {
+						inspector.copied(getPathPool().path(origin), getPathPool().path(fname));
+						return;
+					}
+				} catch (HgDataStreamException ex) {
+					ex.printStackTrace();
+					// FIXME report to a mediator, continue status collection
+				}
+			} else if ((r = getDirstate().checkAdded(fname)) != null) {
+				if (r.name2 != null && baseRevNames.contains(r.name2)) {
+					baseRevNames.remove(r.name2); // XXX surely I shall not report rename source as Removed?
+					inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname));
+					return;
+				}
+				// fall-through, report as added
+			} else if (getDirstate().checkRemoved(fname) != null) {
+				// removed: removed file was not known at the time of baseRevision, and we should not report it as removed
+				return;
+			}
+			inspector.added(getPathPool().path(fname));
+		} else {
+			// was known; check whether clean or modified
+			// when added - seems to be the case of a file added once again, hence need to check if content is different
+			if ((r = getDirstate().checkNormal(fname)) != null || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) {
+				// either clean or modified
+				HgDataFile fileNode = repo.getFileNode(fname);
+				final int lengthAtRevision = fileNode.length(nid1);
+				if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) {
+					inspector.modified(getPathPool().path(fname));
+				} else {
+					// check actual content to see actual changes
+					if (areTheSame(f, fileNode, fileNode.getLocalRevision(nid1))) {
+						inspector.clean(getPathPool().path(fname));
+					} else {
+						inspector.modified(getPathPool().path(fname));
+					}
+				}
+			}
+			// only those left in idsMap after processing are reported as removed 
+		}
+
+		// TODO think over if content comparison may be done more effectively by e.g. calculating nodeid for a local file and comparing it with nodeid from manifest
+		// we don't need to tell exact difference, hash should be enough to detect difference, and it doesn't involve reading historical file content, and it's relatively 
+		// cheap to calc hash on a file (no need to keep it completely in memory). OTOH, if I'm right that the next approach is used for nodeids: 
+		// changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest
+		// then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids).
+		// The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean'
+	}
+
+	private boolean areTheSame(File f, HgDataFile dataFile, int localRevision) {
+		// XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
+		ByteArrayChannel bac = new ByteArrayChannel();
+		boolean ioFailed = false;
+		try {
+			// need content with metadata striped off - although theoretically chances are metadata may be different,
+			// WC doesn't have it anyway 
+			dataFile.content(localRevision, bac);
+		} catch (CancelledException ex) {
+			// silently ignore - can't happen, ByteArrayChannel is not cancellable
+		} catch (IOException ex) {
+			ioFailed = true;
+		} catch (HgException ex) {
+			ioFailed = true;
+		}
+		return !ioFailed && areTheSame(f, bac.toArray(), dataFile.getPath());
+	}
+	
+	private boolean areTheSame(File f, final byte[] data, Path p) {
+		FileInputStream fis = null;
+		try {
+			try {
+				fis = new FileInputStream(f);
+				FileChannel fc = fis.getChannel();
+				ByteBuffer fb = ByteBuffer.allocate(min(data.length, 8192));
+				final boolean[] checkValue = new boolean[] { true };
+				ByteChannel check = new ByteChannel() {
+					int x = 0;
+					final boolean debug = false; // XXX may want to add global variable to allow clients to turn 
+					public int write(ByteBuffer buffer) {
+						for (int i = buffer.remaining(); i > 0; i--, x++) {
+							if (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) + "<<");
+								}
+								checkValue[0] = false;
+								break;
+							}
+						}
+						buffer.position(buffer.limit()); // mark as read
+						return buffer.limit();
+					}
+				};
+				FilterByteChannel filters = new FilterByteChannel(check, repo.getFiltersFromWorkingDirToRepo(p));
+				while (fc.read(fb) != -1 && checkValue[0]) {
+					fb.flip();
+					filters.write(fb);
+					fb.compact();
+				}
+				return checkValue[0];
+			} catch (IOException ex) {
+				if (fis != null) {
+					fis.close();
+				}
+				ex.printStackTrace(); // log warn
+			}
+		} catch (/*TODO typed*/Exception ex) {
+			ex.printStackTrace();
+		}
+		return false;
+	}
+
+	private static String todoGenerateFlags(Path fname) {
+		// FIXME implement
+		return null;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/Revlog.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2010-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.repo;
+
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.util.ByteChannel;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.ProgressSupport;
+
+
+/**
+ * Base class for all Mercurial entities that are serialized in a so called revlog format (changelog, manifest, data files).
+ * 
+ * Implementation note:
+ * Hides actual actual revlog stream implementation and its access methods (i.e. RevlogStream.Inspector), iow shall not expose anything internal
+ * in public methods.
+ *   
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+abstract class Revlog {
+
+	private final HgRepository repo;
+	protected final RevlogStream content;
+
+	protected Revlog(HgRepository hgRepo, RevlogStream contentStream) {
+		if (hgRepo == null) {
+			throw new IllegalArgumentException();
+		}
+		if (contentStream == null) {
+			throw new IllegalArgumentException();
+		}
+		repo = hgRepo;
+		content = contentStream;
+	}
+	
+	// invalid Revlog
+	protected Revlog(HgRepository hgRepo) {
+		repo = hgRepo;
+		content = null;
+	}
+
+	public final HgRepository getRepo() {
+		return repo;
+	}
+
+	public final int getRevisionCount() {
+		return content.revisionCount();
+	}
+	
+	public final int getLastRevision() {
+		return content.revisionCount() - 1;
+	}
+	
+	public final Nodeid getRevision(int revision) {
+		// XXX cache nodeids?
+		return Nodeid.fromBinary(content.nodeid(revision), 0);
+	}
+
+	public final int getLocalRevision(Nodeid nid) {
+		int revision = content.findLocalRevisionNumber(nid);
+		if (revision == BAD_REVISION) {
+			throw new IllegalArgumentException(String.format("%s doesn't represent a revision of %s", nid.toString(), this /*XXX HgDataFile.getPath might be more suitable here*/));
+		}
+		return revision;
+	}
+
+	// Till now, i follow approach that NULL nodeid is never part of revlog
+	public final boolean isKnown(Nodeid nodeid) {
+		final int rn = content.findLocalRevisionNumber(nodeid);
+		if (Integer.MIN_VALUE == rn) {
+			return false;
+		}
+		if (rn < 0 || rn >= content.revisionCount()) {
+			// Sanity check
+			throw new IllegalStateException();
+		}
+		return true;
+	}
+
+	/**
+	 * Access to revision data as is (decompressed, but otherwise unprocessed, i.e. not parsed for e.g. changeset or manifest entries) 
+	 * @param nodeid
+	 */
+	protected void rawContent(Nodeid nodeid, ByteChannel sink) throws HgException, IOException, CancelledException {
+		rawContent(getLocalRevision(nodeid), sink);
+	}
+	
+	/**
+	 * @param revision - repo-local index of this file change (not a changelog revision number!)
+	 */
+	protected void rawContent(int revision, ByteChannel sink) throws HgException, IOException, CancelledException {
+		if (sink == null) {
+			throw new IllegalArgumentException();
+		}
+		ContentPipe insp = new ContentPipe(sink, 0);
+		insp.checkCancelled();
+		content.iterate(revision, revision, true, insp);
+		insp.checkFailed();
+	}
+
+	/**
+	 * XXX perhaps, return value Nodeid[2] and boolean needNodeids is better (and higher level) API for this query?
+	 * 
+	 * @param revision - revision to query parents, or {@link HgRepository#TIP}
+	 * @param parentRevisions - int[2] to get local revision numbers of parents (e.g. {6, -1})
+	 * @param parent1 - byte[20] or null, if parent's nodeid is not needed
+	 * @param parent2 - byte[20] or null, if second parent's nodeid is not needed
+	 * @return
+	 */
+	public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) {
+		if (revision != TIP && !(revision >= 0 && revision < content.revisionCount())) {
+			throw new IllegalArgumentException(String.valueOf(revision));
+		}
+		if (parentRevisions == null || parentRevisions.length < 2) {
+			throw new IllegalArgumentException(String.valueOf(parentRevisions));
+		}
+		if (parent1 != null && parent1.length < 20) {
+			throw new IllegalArgumentException(parent1.toString());
+		}
+		if (parent2 != null && parent2.length < 20) {
+			throw new IllegalArgumentException(parent2.toString());
+		}
+		class ParentCollector implements RevlogStream.Inspector {
+			public int p1 = -1;
+			public int p2 = -1;
+			public byte[] nodeid;
+			
+			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
+				p1 = parent1Revision;
+				p2 = parent2Revision;
+				this.nodeid = new byte[20];
+				// nodeid arg now comes in 32 byte from (as in file format description), however upper 12 bytes are zeros.
+				System.arraycopy(nodeid, nodeid.length > 20 ? nodeid.length - 20 : 0, this.nodeid, 0, 20);
+			}
+		};
+		ParentCollector pc = new ParentCollector();
+		content.iterate(revision, revision, false, pc);
+		parentRevisions[0] = pc.p1;
+		parentRevisions[1] = pc.p2;
+		if (parent1 != null) {
+			if (parentRevisions[0] == -1) {
+				Arrays.fill(parent1, 0, 20, (byte) 0);
+			} else {
+				content.iterate(parentRevisions[0], parentRevisions[0], false, pc);
+				System.arraycopy(pc.nodeid, 0, parent1, 0, 20);
+			}
+		}
+		if (parent2 != null) {
+			if (parentRevisions[1] == -1) {
+				Arrays.fill(parent2, 0, 20, (byte) 0);
+			} else {
+				content.iterate(parentRevisions[1], parentRevisions[1], false, pc);
+				System.arraycopy(pc.nodeid, 0, parent2, 0, 20);
+			}
+		}
+	}
+
+	/*
+	 * XXX think over if it's better to do either:
+	 * pw = getChangelog().new ParentWalker(); pw.init() and pass pw instance around as needed
+	 * or
+	 * add Revlog#getParentWalker(), static class, make cons() and #init package-local, and keep SoftReference to allow walker reuse.
+	 * 
+	 *  and yes, walker is not a proper name
+	 */
+	public final class ParentWalker {
+
+		
+		private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering
+		private Nodeid[] sorted; // for binary search
+		private int[] sorted2natural;
+		private Nodeid[] firstParent;
+		private Nodeid[] secondParent;
+
+		// Nodeid instances shall be shared between all arrays
+
+		public ParentWalker() {
+		}
+		
+		public HgRepository getRepo() {
+			return Revlog.this.getRepo();
+		}
+		
+		public void init() {
+			final RevlogStream stream = Revlog.this.content;
+			final int revisionCount = stream.revisionCount();
+			firstParent = new Nodeid[revisionCount];
+			// although branches/merges are less frequent, and most of secondParent would be -1/null, some sort of 
+			// SparseOrderedList might be handy, provided its inner structures do not overweight simplicity of an array
+			secondParent = new Nodeid[revisionCount];
+			//
+			sequential = new Nodeid[revisionCount];
+			sorted = new Nodeid[revisionCount];
+		
+			RevlogStream.Inspector insp = new RevlogStream.Inspector() {
+				
+				int ix = 0;
+				public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
+					if (ix != revisionNumber) {
+						// XXX temp code, just to make sure I understand what's going on here
+						throw new IllegalStateException();
+					}
+					if (parent1Revision >= revisionNumber || parent2Revision >= revisionNumber) {
+						throw new IllegalStateException(); // sanity, revisions are sequential
+					}
+					final Nodeid nid = new Nodeid(nodeid, true);
+					sequential[ix] = sorted[ix] = nid;
+					if (parent1Revision != -1) {
+						assert parent1Revision < ix;
+						firstParent[ix] = sequential[parent1Revision];
+					}
+					if (parent2Revision != -1) { // revlog of DataAccess.java has p2 set when p1 is -1
+						assert parent2Revision < ix;
+						secondParent[ix] = sequential[parent2Revision];
+					}
+					ix++;
+				}
+			};
+			stream.iterate(0, TIP, false, insp);
+			Arrays.sort(sorted);
+			sorted2natural = new int[revisionCount];
+			for (int i = 0; i < revisionCount; i++) {
+				Nodeid n = sequential[i];
+				int x = Arrays.binarySearch(sorted, n);
+				assertSortedIndex(x);
+				sorted2natural[x] = i;
+			}
+		}
+		
+		private void assertSortedIndex(int x) {
+			if (x < 0) {
+				throw new HgBadStateException();
+			}
+		}
+		
+		// FIXME need to decide whether Nodeid(00 * 20) is always known or not
+		// right now Nodeid.NULL is not recognized as known if passed to this method, 
+		// caller is supposed to make explicit check 
+		public boolean knownNode(Nodeid nid) {
+			return Arrays.binarySearch(sorted, nid) >= 0;
+		}
+
+		/**
+		 * null if none. only known nodes (as per #knownNode) are accepted as arguments
+		 */
+		public Nodeid firstParent(Nodeid nid) {
+			int x = Arrays.binarySearch(sorted, nid);
+			assertSortedIndex(x);
+			int i = sorted2natural[x];
+			return firstParent[i];
+		}
+
+		// never null, Nodeid.NULL if none known
+		public Nodeid safeFirstParent(Nodeid nid) {
+			Nodeid rv = firstParent(nid);
+			return rv == null ? Nodeid.NULL : rv;
+		}
+		
+		public Nodeid secondParent(Nodeid nid) {
+			int x = Arrays.binarySearch(sorted, nid);
+			assertSortedIndex(x);
+			int i = sorted2natural[x];
+			return secondParent[i];
+		}
+
+		public Nodeid safeSecondParent(Nodeid nid) {
+			Nodeid rv = secondParent(nid);
+			return rv == null ? Nodeid.NULL : rv;
+		}
+
+		public boolean appendParentsOf(Nodeid nid, Collection<Nodeid> c) {
+			int x = Arrays.binarySearch(sorted, nid);
+			assertSortedIndex(x);
+			int i = sorted2natural[x];
+			Nodeid p1 = firstParent[i];
+			boolean modified = false;
+			if (p1 != null) {
+				modified = c.add(p1);
+			}
+			Nodeid p2 = secondParent[i];
+			if (p2 != null) {
+				modified = c.add(p2) || modified;
+			}
+			return modified;
+		}
+
+		// XXX alternative (and perhaps more reliable) approach would be to make a copy of allNodes and remove 
+		// nodes, their parents and so on.
+		
+		// @return ordered collection of all children rooted at supplied nodes. Nodes shall not be descendants of each other!
+		// Nodeids shall belong to this revlog
+		public List<Nodeid> childrenOf(List<Nodeid> roots) {
+			HashSet<Nodeid> parents = new HashSet<Nodeid>();
+			LinkedList<Nodeid> result = new LinkedList<Nodeid>();
+			int earliestRevision = Integer.MAX_VALUE;
+			assert sequential.length == firstParent.length && firstParent.length == secondParent.length;
+			// first, find earliest index of roots in question, as there's  no sense 
+			// to check children among nodes prior to branch's root node
+			for (Nodeid r : roots) {
+				int x = Arrays.binarySearch(sorted, r);
+				assertSortedIndex(x);
+				int i = sorted2natural[x];
+				if (i < earliestRevision) {
+					earliestRevision = i;
+				}
+				parents.add(sequential[i]); // add canonical instance in hope equals() is bit faster when can do a ==
+			}
+			for (int i = earliestRevision + 1; i < sequential.length; i++) {
+				if (parents.contains(firstParent[i]) || parents.contains(secondParent[i])) {
+					parents.add(sequential[i]); // to find next child
+					result.add(sequential[i]);
+				}
+			}
+			return result;
+		}
+		
+		/**
+		 * @param nid possibly parent node, shall be {@link #knownNode(Nodeid) known} in this revlog.
+		 * @return <code>true</code> if there's any node in this revlog that has specified node as one of its parents. 
+		 */
+		public boolean hasChildren(Nodeid nid) {
+			int x = Arrays.binarySearch(sorted, nid);
+			assertSortedIndex(x);
+			int i = sorted2natural[x];
+			assert firstParent.length == secondParent.length; // just in case later I implement sparse array for secondParent
+			assert firstParent.length == sequential.length;
+			// to use == instead of equals, take the same Nodeid instance we used to fill all the arrays.
+			final Nodeid canonicalNode = sequential[i];
+			i++; // no need to check node itself. child nodes may appear in sequential only after revision in question
+			for (; i < sequential.length; i++) {
+				// FIXME likely, not very effective. 
+				// May want to optimize it with another (Tree|Hash)Set, created on demand on first use, 
+				// however, need to be careful with memory usage
+				if (firstParent[i] == canonicalNode || secondParent[i] == canonicalNode) {
+					return true;
+				}
+			}
+			return false;
+		}
+	}
+
+	protected static class ContentPipe implements RevlogStream.Inspector, CancelSupport {
+		private final ByteChannel sink;
+		private final CancelSupport cancelSupport;
+		private Exception failure;
+		private final int offset;
+
+		/**
+		 * @param _sink - cannot be <code>null</code>
+		 * @param seekOffset - when positive, orders to pipe bytes to the sink starting from specified offset, not from the first byte available in DataAccess
+		 */
+		public ContentPipe(ByteChannel _sink, int seekOffset) {
+			assert _sink != null;
+			sink = _sink;
+			cancelSupport = CancelSupport.Factory.get(_sink);
+			offset = seekOffset;
+		}
+		
+		protected void prepare(int revisionNumber, DataAccess da) throws HgException, IOException {
+			if (offset > 0) { // save few useless reset/rewind operations
+				da.seek(offset);
+			}
+		}
+
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
+			try {
+				prepare(revisionNumber, da); // XXX perhaps, prepare shall return DA (sliced, if needed)
+				final ProgressSupport progressSupport = ProgressSupport.Factory.get(sink);
+				ByteBuffer buf = ByteBuffer.allocate(512);
+				progressSupport.start(da.length());
+				while (!da.isEmpty()) {
+					cancelSupport.checkCancelled();
+					da.readBytes(buf);
+					buf.flip();
+					// XXX I may not rely on returned number of bytes but track change in buf position instead.
+					int consumed = sink.write(buf); 
+					// FIXME in fact, bad sink implementation (that consumes no bytes) would result in endless loop. Need to account for this 
+					buf.compact();
+					progressSupport.worked(consumed);
+				}
+				progressSupport.done(); // XXX shall specify whether #done() is invoked always or only if completed successfully.
+			} catch (IOException ex) {
+				recordFailure(ex);
+			} catch (CancelledException ex) {
+				recordFailure(ex);
+			} catch (HgException ex) {
+				recordFailure(ex);
+			}
+		}
+		
+		public void checkCancelled() throws CancelledException {
+			cancelSupport.checkCancelled();
+		}
+
+		protected void recordFailure(Exception ex) {
+			assert failure == null;
+			failure = ex;
+		}
+
+		public void checkFailed() throws HgException, IOException, CancelledException {
+			if (failure == null) {
+				return;
+			}
+			if (failure instanceof IOException) {
+				throw (IOException) failure;
+			}
+			if (failure instanceof CancelledException) {
+				throw (CancelledException) failure;
+			}
+			if (failure instanceof HgException) {
+				throw (HgException) failure;
+			}
+			throw new HgBadStateException(failure);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/repo/package.html	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,5 @@
+<html>
+<boody>
+Low-level API operations
+</bidy>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/Adaptable.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Extension mechanism.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface Adaptable {
+
+	<T> T getAdapter(Class<T> adapterClass);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/ByteChannel.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,35 @@
+/*
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Much like {@link java.nio.channels.WritableByteChannel} except for thrown exception 
+ * 
+ * XXX Perhaps, we'll add CharChannel in the future to deal with character conversions/encodings 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface ByteChannel {
+	// XXX does int return value makes any sense given buffer keeps its read state
+	// not clear what retvalue should be in case some filtering happened inside write - i.e. return
+	// number of bytes consumed in 
+	int write(ByteBuffer buffer) throws IOException, CancelledException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/CancelSupport.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * Mix-in for objects that support cancellation. 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface CancelSupport {
+
+	/**
+	 * This method is invoked to check if target had been brought to canceled state. Shall silently return if target is
+	 * in regular state.
+	 * @throws CancelledException when target internal state has been changed to canceled.
+	 */
+	void checkCancelled() throws CancelledException;
+
+
+	// Yeah, this factory class looks silly now, but perhaps in the future I'll need wrappers for other cancellation sources?
+	// just don't want to have general Utils class with methods like get() below
+	static class Factory {
+
+		/**
+		 * Obtain non-null cancel support object.
+		 * 
+		 * @param target any object (or <code>null</code>) that might have cancel support
+		 * @return target if it's capable checking cancellation status or no-op implementation that never cancels.
+				 */
+		public static CancelSupport get(Object target) {
+			if (target instanceof  CancelSupport) {
+				return (CancelSupport) target;
+			}
+			if (target instanceof Adaptable) {
+				CancelSupport cs = ((Adaptable) target).getAdapter(CancelSupport.class);
+				if (cs != null) {
+					return cs;
+				}
+			}
+			return new CancelSupport() {
+				public void checkCancelled() {
+				}
+			};
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/CancelledException.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+public class CancelledException extends Exception {
+
+	public CancelledException() {
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/FileIterator.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,53 @@
+/*
+ * 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 java.io.File;
+
+/**
+ * Abstracts iteration over file system.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface FileIterator {
+
+	/**
+	 * Brings iterator into initial state to facilitate new use.
+	 */
+	void reset();
+
+	/**
+	 * @return whether can shift to next element
+	 */
+	boolean hasNext();
+
+	/**
+	 * Shift to next element
+	 */
+	void next();
+
+	/**
+	 * @return repository-local path to the current element.
+	 */
+	Path name();
+
+	/**
+	 * @return filesystem element.
+	 */
+	File file();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/FileWalker.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,100 @@
+/*
+ * 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 java.io.File;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class FileWalker implements FileIterator {
+
+	private final File startDir;
+	private final Path.Source pathHelper;
+	private final LinkedList<File> dirQueue;
+	private final LinkedList<File> fileQueue;
+	private File nextFile;
+	private Path nextPath;
+
+	public FileWalker(File dir, Path.Source pathFactory) {
+		startDir = dir;
+		pathHelper = pathFactory;
+		dirQueue = new LinkedList<File>();
+		fileQueue = new LinkedList<File>();
+		reset();
+	}
+
+	public void reset() {
+		fileQueue.clear();
+		dirQueue.clear();
+		dirQueue.add(startDir);
+		nextFile = null;
+		nextPath = null;
+	}
+	
+	public boolean hasNext() {
+		return fill();
+	}
+
+	public void next() {
+		if (!fill()) {
+			throw new NoSuchElementException();
+		}
+		nextFile = fileQueue.removeFirst();
+		nextPath = pathHelper.path(nextFile.getPath());
+	}
+
+	public Path name() {
+		return nextPath;
+	}
+	
+	public File file() {
+		return nextFile;
+	}
+	
+	private File[] listFiles(File f) {
+		// in case we need to solve os-related file issues (mac with some encodings?)
+		return f.listFiles();
+	}
+
+	// return true when fill added any elements to fileQueue. 
+	private boolean fill() {
+		while (fileQueue.isEmpty()) {
+			if (dirQueue.isEmpty()) {
+				return false;
+			}
+			while (!dirQueue.isEmpty()) {
+				File dir = dirQueue.removeFirst();
+				for (File f : listFiles(dir)) {
+					if (f.isDirectory()) {
+						if (!".hg".equals(f.getName())) {
+							dirQueue.addLast(f);
+						}
+					} else {
+						fileQueue.addLast(f);
+					}
+				}
+				break;
+			}
+		}
+		return !fileQueue.isEmpty();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/Path.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+/**
+ * Identify repository files (not String nor io.File). Convenient for pattern matching. Memory-friendly.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class Path implements CharSequence, Comparable<Path>/*Cloneable? - although clone for paths make no sense*/{
+//	private String[] segments;
+//	private int flags; // dir, unparsed
+	private String path;
+	
+	/*package-local*/Path(String p) {
+		path = p;
+	}
+
+	/**
+	 * Check if this is directory's path. 
+	 * Note, this method doesn't perform any file system operation.
+	 * 
+	 * @return true when this path points to a directory 
+	 */
+	public boolean isDirectory() {
+		// XXX simple logic for now. Later we may decide to have an explicit factory method to create directory paths
+		return path.charAt(path.length() - 1) == '/';
+	}
+
+	public int length() {
+		return path.length();
+	}
+
+	public char charAt(int index) {
+		return path.charAt(index);
+	}
+
+	public CharSequence subSequence(int start, int end) {
+		// new Path if start-end matches boundaries of any subpath
+		return path.substring(start, end);
+	}
+	
+	@Override
+	public String toString() {
+		return path; // CharSequence demands toString() impl
+	}
+
+	public int compareTo(Path o) {
+		return path.compareTo(o.path);
+	}
+	
+	@Override
+	public boolean equals(Object obj) {
+		if (obj != null && getClass() == obj.getClass()) {
+			return this == obj || path.equals(((Path) obj).path);
+		}
+		return false;
+	}
+	@Override
+	public int hashCode() {
+		return path.hashCode();
+	}
+
+	public static Path create(String path) {
+		if (path == null) {
+			throw new IllegalArgumentException();
+		}
+		if (path.indexOf('\\') != -1) {
+			throw new IllegalArgumentException();
+		}
+		Path rv = new Path(path);
+		return rv;
+	}
+
+	/**
+	 * Path filter.
+	 */
+	public interface Matcher {
+		boolean accept(Path path);
+	}
+
+	/**
+	 * Factory for paths
+	 */
+	public interface Source {
+		Path path(String p);
+	}
+
+	/**
+	 * Straightforward {@link Source} implementation that creates new Path instance for each supplied string
+	 */
+	public static class SimpleSource implements Source {
+		private final PathRewrite normalizer;
+
+		public SimpleSource(PathRewrite pathRewrite) {
+			if (pathRewrite == null) {
+				throw new IllegalArgumentException();
+			}
+			normalizer = pathRewrite;
+		}
+
+		public Path path(String p) {
+			return Path.create(normalizer.rewrite(p));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/PathPool.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,81 @@
+/*
+ * 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 java.lang.ref.SoftReference;
+import java.util.WeakHashMap;
+
+
+/**
+ * Produces path from strings and caches result for reuse
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class PathPool implements Path.Source {
+	private final WeakHashMap<String, SoftReference<Path>> cache;
+	private final PathRewrite pathRewrite;
+	
+	public PathPool(PathRewrite rewrite) {
+		pathRewrite = rewrite;
+		cache = new WeakHashMap<String, SoftReference<Path>>();
+	}
+
+	public Path path(String p) {
+		p = pathRewrite.rewrite(p);
+		return get(p, true);
+	}
+
+	// pipes path object through cache to reuse instance, if possible
+	public Path path(Path p) {
+		String s = pathRewrite.rewrite(p.toString());
+		Path cached = get(s, false);
+		if (cached == null) {
+			cache.put(s, new SoftReference<Path>(cached = p));
+		}
+		return cached;
+	}
+
+	// XXX what would be parent of an empty path?
+	// Path shall have similar functionality
+	public Path parent(Path path) {
+		if (path.length() == 0) {
+			throw new IllegalArgumentException();
+		}
+		for (int i = path.length() - 2 /*if path represents a dir, trailing char is slash, skip*/; i >= 0; i--) {
+			if (path.charAt(i) == '/') {
+				return get(path.subSequence(0, i+1).toString(), true);
+			}
+		}
+		return get("", true);
+	}
+
+	private Path get(String p, boolean create) {
+		SoftReference<Path> sr = cache.get(p);
+		Path path = sr == null ? null : sr.get();
+		if (path == null) {
+			if (create) {
+				path = Path.create(p);
+				cache.put(p, new SoftReference<Path>(path));
+			} else if (sr != null) {
+				// cached path no longer used, clear cache entry - do not wait for RefQueue to step in
+				cache.remove(p);
+			}
+		} 
+		return path;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/PathRewrite.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,61 @@
+/*
+ * 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 java.util.LinkedList;
+import java.util.List;
+
+/**
+ * File names often need transformations, like Windows-style path to Unix or human-readable data file name to storage location.  
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface PathRewrite {
+
+	// XXX think over CharSequence use instead of String
+	public String rewrite(String path);
+	
+	public static class Empty implements PathRewrite {
+		public String rewrite(String path) {
+			return path;
+		}
+	}
+
+	public class Composite implements PathRewrite {
+		private List<PathRewrite> chain;
+
+		public Composite(PathRewrite... e) {
+			LinkedList<PathRewrite> r = new LinkedList<PathRewrite>();
+			for (int i = 0; e != null && i < e.length; i++) {
+				r.addLast(e[i]);
+			}
+			chain = r;
+		}
+		public Composite chain(PathRewrite e) {
+			chain.add(e);
+			return this;
+		}
+
+		public String rewrite(String path) {
+			for (PathRewrite pr : chain) {
+				path = pr.rewrite(path);
+			}
+			return path;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/main/java/org/tmatesoft/hg/util/ProgressSupport.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+/**
+ * Mix-in to report progress of a long-running operation
+ *  
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface ProgressSupport {
+
+	public void start(long totalUnits);
+	public void worked(int units);
+	public void done();
+
+	static class Factory {
+
+		/**
+		 * @param target object that might be capable to report progress. Can be <code>null</code>
+		 * @return support object extracted from target or an empty, no-op implementation
+		 */
+		public static ProgressSupport get(Object target) {
+			if (target instanceof ProgressSupport) {
+				return (ProgressSupport) target;
+			}
+			if (target instanceof Adaptable) {
+				ProgressSupport ps = ((Adaptable) target).getAdapter(ProgressSupport.class);
+				if (ps != null) {
+					return ps;
+				}
+			}
+			return new ProgressSupport() {
+				public void start(long totalUnits) {
+				}
+				public void worked(int units) {
+				}
+				public void done() {
+				}
+			};
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/Configuration.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,101 @@
+/*
+ * 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.test;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class Configuration {
+	
+	private static Configuration inst;
+	private File root;
+	private final HgLookup lookup;
+	private File tempDir;
+	private List<String> remoteServers;
+	
+	private Configuration() {
+		lookup = new HgLookup();
+	}
+	
+	private File getRoot() {
+		if (root == null) {
+			String repo2 = System.getProperty("hg4j.tests.repos");
+			assertNotNull("System property hg4j.tests.repos is undefined", repo2);
+			root = new File(repo2);
+			assertTrue(root.exists());
+		}
+		return root;
+	}
+	
+	public static Configuration get() {
+		if (inst == null) {
+			inst = new Configuration();
+		}
+		return inst;
+	}
+	
+	public HgRepository own() throws Exception {
+		return lookup.detectFromWorkingDir();
+	}
+
+	// fails if repo not found
+	public HgRepository find(String key) throws Exception {
+		HgRepository rv = lookup.detect(new File(getRoot(), key));
+		assertNotNull(rv);
+		assertFalse(rv.isInvalid());
+		return rv;
+	}
+
+	// easy override for manual test runs
+	public void remoteServers(String... keys) {
+		remoteServers = Arrays.asList(keys);
+	}
+
+	public List<HgRemoteRepository> allRemote() throws Exception {
+		if (remoteServers == null) {
+			String rr = System.getProperty("hg4j.tests.remote");
+			assertNotNull("System property hg4j.tests.remote is undefined", rr);
+			remoteServers = Arrays.asList(rr.split(" "));
+		}
+		ArrayList<HgRemoteRepository> rv = new ArrayList<HgRemoteRepository>(remoteServers.size());
+		for (String key : remoteServers) {
+			rv.add(lookup.detectRemote(key, null));
+		}
+		return rv;
+	}
+
+	public File getTempDir() {
+		if (tempDir == null) {
+			String td = System.getProperty("hg4j.tests.tmpdir", System.getProperty("java.io.tmpdir"));
+			tempDir = new File(td);
+		}
+		return tempDir;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/ErrorCollectorExt.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,45 @@
+/*
+ * 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.test;
+
+import static org.junit.Assert.assertThat;
+
+import java.util.concurrent.Callable;
+
+import org.hamcrest.Matcher;
+import org.junit.rules.ErrorCollector;
+
+/**
+ * Expose verify method for allow not-junit runs to check test outcome 
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+final class ErrorCollectorExt extends ErrorCollector {
+	public void verify() throws Throwable {
+		super.verify();
+	}
+
+	public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) {
+		checkSucceeds(new Callable<Object>() {
+			public Object call() throws Exception {
+				assertThat(reason, value, matcher);
+				return value;
+			}
+		});
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/ExecHelper.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,101 @@
+/*
+ * 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.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.CharBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ExecHelper {
+
+	private final OutputParser parser;
+	private File dir;
+	private int exitValue;
+
+	public ExecHelper(OutputParser outParser, File workingDir) {
+		parser = outParser;
+		dir = workingDir;
+	}
+
+	public void run(String... cmd) throws IOException, InterruptedException {
+		ProcessBuilder pb = null;
+		if (System.getProperty("os.name").startsWith("Windows")) {
+			StringTokenizer st = new StringTokenizer(System.getenv("PATH"), ";");
+			while (st.hasMoreTokens()) {
+				File pe = new File(st.nextToken());
+				if (new File(pe, cmd[0] + ".exe").exists()) {
+					break;
+				}
+				// PATHEXT controls precedence of .exe, .bat and .cmd files, ususlly .exe wins
+				if (new File(pe, cmd[0] + ".bat").exists() || new File(pe, cmd[0] + ".cmd").exists()) {
+					ArrayList<String> command = new ArrayList<String>();
+					command.add("cmd.exe");
+					command.add("/C");
+					command.addAll(Arrays.asList(cmd));
+					pb = new ProcessBuilder(command);
+					break;
+				}
+			}
+		}
+		if (pb == null) {
+			pb = new ProcessBuilder(cmd);
+		}
+		Process p = pb.directory(dir).redirectErrorStream(true).start();
+		InputStreamReader stdOut = new InputStreamReader(p.getInputStream());
+		LinkedList<CharBuffer> l = new LinkedList<CharBuffer>();
+		int r = -1;
+		CharBuffer b = null;
+		do {
+			if (b == null || b.remaining() < b.capacity() / 3) {
+				b = CharBuffer.allocate(512);
+				l.add(b);
+			}
+			r = stdOut.read(b);
+		} while (r != -1);
+		int total = 0;
+		for (CharBuffer cb : l) {
+			total += cb.position();
+			cb.flip();
+		}
+		CharBuffer res = CharBuffer.allocate(total);
+		for (CharBuffer cb : l) {
+			res.put(cb);
+		}
+		res.flip();
+		p.waitFor();
+		exitValue = p.exitValue();
+		parser.parse(res);
+	}
+	
+	public int getExitValue() {
+		return exitValue;
+	}
+
+	public void cwd(File wd) {
+		dir = wd;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/LogOutputParser.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,106 @@
+/*
+ * 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.test;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.tmatesoft.hg.repo.HgRepository;
+
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class LogOutputParser implements OutputParser {
+	private final List<Record> result = new LinkedList<Record>();
+	private Pattern pattern1;
+	private Pattern pattern2;
+	private Pattern pattern3;
+	private Pattern pattern4;
+	private Pattern pattern5;
+	
+	public LogOutputParser(boolean outputWithDebug) {
+		if (outputWithDebug) {
+			pattern1 = Pattern.compile("^changeset:\\s+(\\d+):([a-f0-9]{40})\n(^tag:(.+)$)?", Pattern.MULTILINE);
+			pattern2 = Pattern.compile("^parent:\\s+(-?\\d+):([a-f0-9]{40})\n", Pattern.MULTILINE);
+			pattern3 = Pattern.compile("^manifest:\\s+(\\d+):([a-f0-9]{40})\nuser:\\s+(\\S.+)\ndate:\\s+(\\S.+)\n", Pattern.MULTILINE);
+			pattern4 = Pattern.compile("^description:\\n", Pattern.MULTILINE);
+			pattern5 = Pattern.compile("\\n\\n");
+			//p = "^manifest:\\s+(\\d+):([a-f0-9]{40})\nuser:(.+)$";
+		} else {
+			throw HgRepository.notImplemented();
+		}
+	}
+	
+	public void reset() {
+		result.clear();
+	}
+	
+	public List<Record> getResult() {
+		return result;
+	}
+
+	public void parse(CharSequence seq) {
+		Matcher m = pattern1.matcher(seq);
+		while (m.find()) {
+			Record r = new Record();
+			r.changesetIndex = Integer.parseInt(m.group(1));
+			r.changesetNodeid = m.group(2);
+			//tags = m.group(4);
+			m.usePattern(pattern2);
+			if (m.find()) {
+				r.parent1Index = Integer.parseInt(m.group(1));
+				r.parent1Nodeid = m.group(2);
+			}
+			if (m.find()) {
+				r.parent2Index = Integer.parseInt(m.group(1));
+				r.parent2Nodeid = m.group(2);
+			}
+			m.usePattern(pattern3);
+			if (m.find()) {
+				r.user = m.group(3);
+				r.date = m.group(4);
+			}
+			m.usePattern(pattern4);
+			if (m.find()) {
+				int commentStart = m.end();
+				m.usePattern(pattern5);
+				if (m.find()) {
+					r.description = seq.subSequence(commentStart, m.start()).toString();
+				}
+			}
+			result.add(r);
+			m.usePattern(pattern1);
+		}
+	}
+
+	public static class Record {
+		public int changesetIndex;
+		public String changesetNodeid;
+		public int parent1Index;
+		public int parent2Index;
+		public String parent1Nodeid;
+		public String parent2Nodeid;
+		public String user;
+		public String date;
+		public String description;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/ManifestOutputParser.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,56 @@
+/*
+ * 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.test;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ManifestOutputParser implements OutputParser {
+
+	private final Pattern pattern;
+	private final LinkedHashMap<Path, Nodeid> result = new LinkedHashMap<Path, Nodeid>();
+
+	public ManifestOutputParser() {
+		pattern = Pattern.compile("^([a-f0-9]{40}) (\\d{3})   (.+)$", Pattern.MULTILINE);
+	}
+	
+	public void reset() {
+		result.clear();
+	}
+	
+	public Map<Path, Nodeid> getResult() {
+		return result;
+	}
+	
+	public void parse(CharSequence seq) {
+		Matcher m = pattern.matcher(seq);
+		while (m.find()) {
+			result.put(Path.create(m.group(3)), Nodeid.fromAscii(m.group(1).getBytes(), 0, 40));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/OutputParser.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,43 @@
+/*
+ * 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.test;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface OutputParser {
+
+	public void parse(CharSequence seq);
+
+	public class Stub implements OutputParser {
+		private boolean shallDump;
+		public Stub() {
+			this(false);
+		}
+		public Stub(boolean dump) {
+			shallDump = dump;
+		}
+		public void parse(CharSequence seq) {
+			if (shallDump) {
+				System.out.println(seq);
+			} 
+			// else no-op
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/StatusOutputParser.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,148 @@
+/*
+ * 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.test;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class StatusOutputParser implements OutputParser {
+
+	private final Pattern pattern;
+	// although using StatusCollector.Record is not really quite honest for testing,
+	// it's deemed acceptable as long as that class is primitive 'collect all results'
+	private HgStatusCollector.Record result = new HgStatusCollector.Record();
+	private final PathPool pathHelper;
+
+	public StatusOutputParser() {
+//		pattern = Pattern.compile("^([MAR?IC! ]) ([\\w \\.-/\\\\]+)$", Pattern.MULTILINE);
+		pattern = Pattern.compile("^([MAR?IC! ]) (.+)$", Pattern.MULTILINE);
+		pathHelper = new PathPool(new PathRewrite() {
+			
+			private final boolean winPathSeparator = File.separatorChar == '\\';
+
+			public String rewrite(String s) {
+				if (winPathSeparator) {
+					// Java impl always give slashed path, while Hg uses local, os-specific convention
+					s = s.replace('\\', '/'); 
+				}
+				return s;
+			}
+		});
+	}
+
+	public void reset() {
+		result = new HgStatusCollector.Record();
+	}
+
+	public void parse(CharSequence seq) {
+		Matcher m = pattern.matcher(seq);
+		Path lastEntry = null;
+		while (m.find()) {
+			Path fname = pathHelper.path(m.group(2));
+			switch ((int) m.group(1).charAt(0)) {
+			case (int) 'M' : {
+				result.modified(fname);
+				lastEntry = fname; // for files modified through merge there's also 'copy' source 
+				break;
+			}
+			case (int) 'A' : {
+				result.added(fname);
+				lastEntry = fname;
+				break;
+			}
+			case (int) 'R' : {
+				result.removed(fname);
+				break;
+			}
+			case (int) '?' : {
+				result.unknown(fname);
+				break;
+			}
+			case (int) 'I' : {
+				result.ignored(fname);
+				break;
+			}
+			case (int) 'C' : {
+				result.clean(fname);
+				break;
+			}
+			case (int) '!' : {
+				result.missing(fname);
+				break;
+			}
+			case (int) ' ' : {
+				// last added is copy destination
+				// to get or to remove it - depends on what StatusCollector does in this case
+				result.copied(fname, lastEntry);
+				lastEntry = null;
+				break;
+			}
+			}
+		}
+	}
+
+	// 
+	public List<Path> getModified() {
+		return result.getModified();
+	}
+
+	public List<Path> getAdded() {
+		List<Path> rv = new LinkedList<Path>(result.getAdded());
+		for (Path p : result.getCopied().keySet()) {
+			rv.remove(p); // remove only one duplicate
+		}
+		return rv;
+	}
+
+	public List<Path> getRemoved() {
+		return result.getRemoved();
+	}
+
+	public Map<Path,Path> getCopied() {
+		return result.getCopied();
+	}
+
+	public List<Path> getClean() {
+		return result.getClean();
+	}
+
+	public List<Path> getMissing() {
+		return result.getMissing();
+	}
+
+	public List<Path> getUnknown() {
+		return result.getUnknown();
+	}
+
+	public List<Path> getIgnored() {
+		return result.getIgnored();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/TestByteChannel.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,87 @@
+/*
+ * 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.test;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.tmatesoft.hg.internal.ByteArrayChannel;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestByteChannel {
+
+	private HgRepository repo;
+
+	public static void main(String[] args) throws Exception {
+//		HgRepoFacade rf = new HgRepoFacade();
+//		rf.init();
+//		HgDataFile file = rf.getRepository().getFileNode("src/org/tmatesoft/hg/internal/KeywordFilter.java");
+//		for (int i = file.getLastRevision(); i >= 0; i--) {
+//			System.out.print("Content for revision:" + i);
+//			compareContent(file, i);
+//			System.out.println(" OK");
+//		}
+		//CatCommand cmd = rf.createCatCommand();
+	}
+
+//	private static void compareContent(HgDataFile file, int rev) throws Exception {
+//		byte[] oldAccess = file.content(rev);
+//		ByteArrayChannel ch = new ByteArrayChannel();
+//		file.content(rev, ch);
+//		byte[] newAccess = ch.toArray();
+//		Assert.assertArrayEquals(oldAccess, newAccess);
+//		// don't trust anyone (even JUnit) 
+//		if (!Arrays.equals(oldAccess, newAccess)) {
+//			throw new RuntimeException("Failed:" + rev);
+//		}
+//	}
+
+	@Test
+	public void testContent() throws Exception {
+		repo = Configuration.get().find("log-1");
+		final byte[] expectedContent = new byte[] { 'a', ' ', 13, 10 };
+		ByteArrayChannel ch = new ByteArrayChannel();
+		repo.getFileNode("dir/b").content(0, ch);
+		assertArrayEquals(expectedContent, ch.toArray());
+		repo.getFileNode("d").content(HgRepository.TIP, ch = new ByteArrayChannel() );
+		assertArrayEquals(expectedContent, ch.toArray());
+	}
+
+	@Test
+	public void testStripMetadata() throws Exception {
+		repo = Configuration.get().find("log-1");
+		ByteArrayChannel ch = new ByteArrayChannel();
+		HgDataFile dir_b = repo.getFileNode("dir/b");
+		Assert.assertTrue(dir_b.isCopy());
+		Assert.assertEquals("b", dir_b.getCopySourceName().toString());
+		Assert.assertEquals("e44751cdc2d14f1eb0146aa64f0895608ad15917", dir_b.getCopySourceRevision().toString());
+		dir_b.content(0, ch);
+		// assert rawContent has 1 10 ... 1 10
+		assertArrayEquals("a \r\n".getBytes(), ch.toArray());
+		//
+		// try once again to make sure metadata records/extracts correct offsets
+		dir_b.content(0, ch = new ByteArrayChannel());
+		assertArrayEquals("a \r\n".getBytes(), ch.toArray());
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/TestClone.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,94 @@
+/*
+ * 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.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgCloneCommand;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestClone {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	public static void main(String[] args) throws Throwable {
+		TestClone t = new TestClone();
+		t.testSimpleClone();
+		t.errorCollector.verify();
+	}
+
+	public TestClone() {
+	}
+	
+	@Test
+	public void testSimpleClone() throws Exception {
+		int x = 0;
+		final File tempDir = Configuration.get().getTempDir();
+		for (HgRemoteRepository hgRemote : Configuration.get().allRemote()) {
+			HgCloneCommand cmd = new HgCloneCommand();
+			cmd.source(hgRemote);
+			File dest = new File(tempDir, "test-clone-" + x++);
+			if (dest.exists()) {
+				rmdir(dest);
+			}
+			cmd.destination(dest);
+			cmd.execute();
+			verify(hgRemote, dest);
+		}
+	}
+
+	private void verify(HgRemoteRepository hgRemote, File dest) throws Exception {
+		ExecHelper eh = new ExecHelper(new OutputParser.Stub(), dest);
+		eh.run("hg", "verify");
+		errorCollector.checkThat("Verify", eh.getExitValue(), CoreMatchers.equalTo(0));
+		eh.run("hg", "out", hgRemote.getLocation());
+		errorCollector.checkThat("Outgoing", eh.getExitValue(), CoreMatchers.equalTo(1));
+		eh.run("hg", "in", hgRemote.getLocation());
+		errorCollector.checkThat("Incoming", eh.getExitValue(), CoreMatchers.equalTo(1));
+	}
+
+	static void rmdir(File dest) throws IOException {
+		LinkedList<File> queue = new LinkedList<File>();
+		queue.addAll(Arrays.asList(dest.listFiles()));
+		while (!queue.isEmpty()) {
+			File next = queue.removeFirst();
+			if (next.isDirectory()) {
+				List<File> files = Arrays.asList(next.listFiles());
+				if (!files.isEmpty()) {
+					queue.addAll(files);
+					queue.add(next);
+				}
+				// fall through
+			} 
+			next.delete();
+		}
+		dest.delete();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/TestHistory.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,240 @@
+/*
+ * 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.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgChangeset;
+import org.tmatesoft.hg.core.HgLogCommand;
+import org.tmatesoft.hg.core.HgLogCommand.CollectHandler;
+import org.tmatesoft.hg.core.HgLogCommand.FileHistoryHandler;
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.test.LogOutputParser.Record;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestHistory {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	private HgRepository repo;
+	private final ExecHelper eh;
+	private LogOutputParser changelogParser;
+	
+	public static void main(String[] args) throws Throwable {
+		TestHistory th = new TestHistory();
+		th.testCompleteLog();
+		th.testFollowHistory();
+		th.errorCollector.verify();
+//		th.testPerformance();
+		th.testOriginalTestLogRepo();
+		th.testUsernames();
+		th.testBranches();
+		//
+		th.errorCollector.verify();
+	}
+	
+	public TestHistory() throws Exception {
+		this(new HgLookup().detectFromWorkingDir());
+	}
+
+	private TestHistory(HgRepository hgRepo) {
+		repo = hgRepo;
+		eh = new ExecHelper(changelogParser = new LogOutputParser(true), null);
+		
+	}
+
+	@Test
+	public void testCompleteLog() throws Exception {
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug");
+		List<HgChangeset> r = new HgLogCommand(repo).execute();
+		report("hg log - COMPLETE REPO HISTORY", r, true); 
+	}
+	
+	@Test
+	public void testFollowHistory() throws Exception {
+		final Path f = Path.create("cmdline/org/tmatesoft/hg/console/Remote.java");
+		try {
+			if (repo.getFileNode(f).exists()) { // FIXME getFileNode shall not fail with IAE
+				changelogParser.reset();
+				eh.run("hg", "log", "--debug", "--follow", f.toString());
+				
+				class H extends CollectHandler implements FileHistoryHandler {
+					boolean copyReported = false;
+					boolean fromMatched = false;
+					public void copy(FileRevision from, FileRevision to) {
+						copyReported = true;
+						fromMatched = "src/com/tmate/hgkit/console/Remote.java".equals(from.getPath().toString());
+					}
+				};
+				H h = new H();
+				new HgLogCommand(repo).file(f, true).execute(h);
+				String what = "hg log - FOLLOW FILE HISTORY";
+				errorCollector.checkThat(what + "#copyReported ", h.copyReported, is(true));
+				errorCollector.checkThat(what + "#copyFromMatched", h.fromMatched, is(true));
+				//
+				// cmdline always gives in changesets in order from newest (bigger rev number) to oldest.
+				// LogCommand does other way round, from oldest to newest, follewed by revisions of copy source, if any
+				// (apparently older than oldest of the copy target). Hence need to sort Java results according to rev numbers
+				final LinkedList<HgChangeset> sorted = new LinkedList<HgChangeset>(h.getChanges());
+				Collections.sort(sorted, new Comparator<HgChangeset>() {
+					public int compare(HgChangeset cs1, HgChangeset cs2) {
+						return cs1.getRevision() < cs2.getRevision() ? 1 : -1;
+					}
+				});
+				report(what, sorted, false);
+			}
+		} catch (IllegalArgumentException ex) {
+			System.out.println("Can't test file history with follow because need to query specific file with history");
+		}
+	}
+
+	private void report(String what, List<HgChangeset> r, boolean reverseConsoleResult) {
+		final List<Record> consoleResult = changelogParser.getResult();
+		report(what, r, consoleResult, reverseConsoleResult, errorCollector);
+	}
+	
+	static void report(String what, List<HgChangeset> hg4jResult, List<Record> consoleResult, boolean reverseConsoleResult, ErrorCollectorExt errorCollector) {
+		consoleResult = new ArrayList<Record>(consoleResult); // need a copy in case callee would use result again
+		if (reverseConsoleResult) {
+			Collections.reverse(consoleResult);
+		}
+		errorCollector.checkThat(what + ". Number of changeset reported didn't match", consoleResult.size(), equalTo(hg4jResult.size()));
+		Iterator<Record> consoleResultItr = consoleResult.iterator();
+		for (HgChangeset cs : hg4jResult) {
+			if (!consoleResultItr.hasNext()) {
+				errorCollector.addError(new AssertionError("Ran out of console results while there are still hg4j results"));
+				break;
+			}
+			Record cr = consoleResultItr.next();
+			int x = cs.getRevision() == cr.changesetIndex ? 0x1 : 0;
+			x |= cs.getDate().equals(cr.date) ? 0x2 : 0;
+			x |= cs.getNodeid().toString().equals(cr.changesetNodeid) ? 0x4 : 0;
+			x |= cs.getUser().equals(cr.user) ? 0x8 : 0;
+			// need to do trim() on comment because command-line template does, and there are
+			// repositories that have couple of newlines in the end of the comment (e.g. hello sample repo from the book) 
+			x |= cs.getComment().trim().equals(cr.description) ? 0x10 : 0;
+			errorCollector.checkThat(String.format(what + ". Mismatch (0x%x) in %d hg4j rev comparing to %d cmdline's.", x, cs.getRevision(), cr.changesetIndex), x, equalTo(0x1f));
+			consoleResultItr.remove();
+		}
+		errorCollector.checkThat(what + ". Unprocessed results in console left (insufficient from hg4j)", consoleResultItr.hasNext(), equalTo(false));
+	}
+
+	public void testPerformance() throws Exception {
+		final int runs = 10;
+		final long start1 = System.currentTimeMillis();
+		for (int i = 0; i < runs; i++) {
+			changelogParser.reset();
+			eh.run("hg", "log", "--debug");
+		}
+		final long start2 = System.currentTimeMillis();
+		for (int i = 0; i < runs; i++) {
+			new HgLogCommand(repo).execute();
+		}
+		final long end = System.currentTimeMillis();
+		System.out.printf("'hg log --debug', %d runs: Native client total %d (%d per run), Java client %d (%d)\n", runs, start2-start1, (start2-start1)/runs, end-start2, (end-start2)/runs);
+	}
+
+	@Test
+	public void testOriginalTestLogRepo() throws Exception {
+		repo = Configuration.get().find("log-1");
+		HgLogCommand cmd = new HgLogCommand(repo);
+		// funny enough, but hg log -vf a -R c:\temp\hg\test-log\a doesn't work, while --cwd <same> works fine
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "a", "--cwd", repo.getLocation());
+		report("log a", cmd.file("a", false).execute(), true);
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-f", "a", "--cwd", repo.getLocation());
+		List<HgChangeset> r = cmd.file("a", true).execute();
+		report("log -f a", r, true);
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-f", "e", "--cwd", repo.getLocation());
+		report("log -f e", cmd.file("e", true).execute(), false /*#1, below*/);
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "dir/b", "--cwd", repo.getLocation());
+		report("log dir/b", cmd.file("dir/b", false).execute(), true);
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-f", "dir/b", "--cwd", repo.getLocation());
+		report("log -f dir/b", cmd.file("dir/b", true).execute(), false /*#1, below*/);
+		/*
+		 * #1: false works because presently commands dispatches history of the queried file, and then history
+		 * of it's origin. With history comprising of renames only, this effectively gives reversed (newest to oldest) 
+		 * order of revisions. 
+		 */
+	}
+
+	@Test
+	public void testUsernames() throws Exception {
+		repo = Configuration.get().find("log-users");
+		final String user1 = "User One <user1@example.org>";
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-u", user1, "--cwd", repo.getLocation());
+		report("log -u " + user1, new HgLogCommand(repo).user(user1).execute(), true);
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-u", "user1", "-u", "user2", "--cwd", repo.getLocation());
+		report("log -u user1 -u user2", new HgLogCommand(repo).user("user1").user("user2").execute(), true);
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-u", "user3", "--cwd", repo.getLocation());
+		report("log -u user3", new HgLogCommand(repo).user("user3").execute(), true);
+	}
+
+	@Test
+	public void testBranches() throws Exception {
+		repo = Configuration.get().find("log-branches");
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-b", "default", "--cwd", repo.getLocation());
+		report("log -b default" , new HgLogCommand(repo).branch("default").execute(), true);
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-b", "test", "--cwd", repo.getLocation());
+		report("log -b test" , new HgLogCommand(repo).branch("test").execute(), true);
+		//
+		assertTrue("log -b dummy shall yeild empty result", new HgLogCommand(repo).branch("dummy").execute().isEmpty());
+		//
+		changelogParser.reset();
+		eh.run("hg", "log", "--debug", "-b", "default", "-b", "test", "--cwd", repo.getLocation());
+		report("log -b default -b test" , new HgLogCommand(repo).branch("default").branch("test").execute(), true);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/TestIncoming.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,142 @@
+/*
+ * 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.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.tmatesoft.hg.internal.RequiresFile.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgChangeset;
+import org.tmatesoft.hg.core.HgIncomingCommand;
+import org.tmatesoft.hg.core.HgLogCommand;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.Internals;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestIncoming {
+	
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	public static void main(String[] args) throws Throwable {
+		Configuration.get().remoteServers("http://localhost:8000/");
+		TestIncoming t = new TestIncoming();
+		t.testSimple();
+		t.errorCollector.verify();
+	}
+
+	public TestIncoming() {
+//		Configuration.get().remoteServers("http://localhost:8000/");
+	}
+
+	@Test
+	public void testSimple() throws Exception {
+		int x = 0;
+		HgLookup lookup = new HgLookup();
+		for (HgRemoteRepository hgRemote : Configuration.get().allRemote()) {
+			File dest = initEmptyTempRepo("test-incoming-" + x++);
+			HgRepository localRepo = lookup.detect(dest);
+			// Idea:
+			// hg in, hg4j in, compare
+			// hg pull total/2
+			// hg in, hg4j in, compare
+			List<Nodeid> incoming = runAndCompareIncoming(localRepo, hgRemote);
+			Assert.assertTrue("Need remote repository of reasonable size to test incoming command for partially filled case", incoming.size() >= 5);
+			//
+			Nodeid median = incoming.get(incoming.size() / 2); 
+			System.out.println("About to pull up to revision " + median.shortNotation());
+			new ExecHelper(new OutputParser.Stub(), dest).run("hg", "pull", "-r", median.toString(), hgRemote.getLocation());
+			//
+			// shall re-read repository to pull up new changes 
+			localRepo = lookup.detect(dest);
+			runAndCompareIncoming(localRepo, hgRemote);
+		}
+	}
+	
+	private List<Nodeid> runAndCompareIncoming(HgRepository localRepo, HgRemoteRepository hgRemote) throws Exception {
+		// need new command instance as subsequence exec[Lite|Full] on the same command would yield same result,
+		// regardless of the pull in between.
+		HgIncomingCommand cmd = new HgIncomingCommand(localRepo);
+		cmd.against(hgRemote);
+		HgLogCommand.CollectHandler collector = new HgLogCommand.CollectHandler();
+		LogOutputParser outParser = new LogOutputParser(true);
+		ExecHelper eh = new ExecHelper(outParser, new File(localRepo.getLocation()));
+		cmd.executeFull(collector);
+		eh.run("hg", "incoming", "--debug", hgRemote.getLocation());
+		List<Nodeid> liteResult = cmd.executeLite(null);
+		report(collector, outParser, liteResult, errorCollector);
+		return liteResult;
+	}
+	
+	static void report(HgLogCommand.CollectHandler collector, LogOutputParser outParser, List<Nodeid> liteResult, ErrorCollectorExt errorCollector) {
+		TestHistory.report("hg vs execFull", collector.getChanges(), outParser.getResult(), false, errorCollector);
+		//
+		ArrayList<Nodeid> expected = new ArrayList<Nodeid>(outParser.getResult().size());
+		for (LogOutputParser.Record r : outParser.getResult()) {
+			Nodeid nid = Nodeid.fromAscii(r.changesetNodeid);
+			expected.add(nid);
+		}
+		checkNodeids("hg vs execLite:", liteResult, expected, errorCollector);
+		//
+		expected = new ArrayList<Nodeid>(outParser.getResult().size());
+		for (HgChangeset cs : collector.getChanges()) {
+			expected.add(cs.getNodeid());
+		}
+		checkNodeids("execFull vs execLite:", liteResult, expected, errorCollector);
+	}
+	
+	static void checkNodeids(String what, List<Nodeid> liteResult, List<Nodeid> expected, ErrorCollectorExt errorCollector) {
+		HashSet<Nodeid> set = new HashSet<Nodeid>(liteResult);
+		for (Nodeid nid : expected) {
+			boolean removed = set.remove(nid);
+			errorCollector.checkThat(what + " Missing " +  nid.shortNotation() + " in HgIncomingCommand.execLite result", removed, equalTo(true));
+		}
+		errorCollector.checkThat(what + " Superfluous cset reported by HgIncomingCommand.execLite", set.isEmpty(), equalTo(true));
+	}
+	
+	static File createEmptyDir(String dirName) throws IOException {
+		File dest = new File(Configuration.get().getTempDir(), dirName);
+		if (dest.exists()) {
+			TestClone.rmdir(dest);
+		}
+		dest.mkdirs();
+		return dest;
+	}
+
+	static File initEmptyTempRepo(String dirName) throws IOException {
+		File dest = createEmptyDir(dirName);
+		Internals implHelper = new Internals();
+		implHelper.setStorageConfig(1, STORE | FNCACHE | DOTENCODE);
+		implHelper.initEmptyRepository(new File(dest, ".hg"));
+		return dest;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/TestManifest.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,120 @@
+/*
+ * 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.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertTrue;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.core.HgManifestCommand;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestManifest {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	private final HgRepository repo;
+	private ManifestOutputParser manifestParser;
+	private ExecHelper eh;
+	final LinkedList<FileRevision> revisions = new LinkedList<FileRevision>();
+	private HgManifestCommand.Handler handler  = new HgManifestCommand.Handler() {
+		
+		public void file(FileRevision fileRevision) {
+			revisions.add(fileRevision);
+		}
+		
+		public void end(Nodeid manifestRevision) {}
+		public void dir(Path p) {}
+		public void begin(Nodeid manifestRevision) {}
+	};
+
+	public static void main(String[] args) throws Throwable {
+		TestManifest tm = new TestManifest();
+		tm.testTip();
+		tm.testFirstRevision();
+		tm.testRevisionInTheMiddle();
+		tm.errorCollector.verify();
+	}
+	
+	public TestManifest() throws Exception {
+		this(new HgLookup().detectFromWorkingDir());
+	}
+
+	private TestManifest(HgRepository hgRepo) {
+		repo = hgRepo;
+		assertTrue(!repo.isInvalid());
+		eh = new ExecHelper(manifestParser = new ManifestOutputParser(), null);
+	}
+
+	@Test
+	public void testTip() throws Exception {
+		testRevision(TIP);
+	}
+
+	@Test
+	public void testFirstRevision() throws Exception {
+		testRevision(0);
+	}
+	
+	@Test
+	public void testRevisionInTheMiddle() throws Exception {
+		int rev = repo.getManifest().getRevisionCount() / 2;
+		if (rev == 0) {
+			throw new IllegalStateException("Need manifest with few revisions");
+		}
+		testRevision(rev);
+	}
+
+	private void testRevision(int rev) throws Exception {
+		manifestParser.reset();
+		eh.run("hg", "manifest", "--debug", "--rev", String.valueOf(rev == TIP ? -1 : rev));
+		revisions.clear();
+		new HgManifestCommand(repo).revision(rev).execute(handler);
+		report("manifest " + (rev == TIP ? "TIP:" : "--rev " + rev));
+	}
+
+	private void report(String what) throws Exception {
+		final Map<Path, Nodeid> cmdLineResult = new LinkedHashMap<Path, Nodeid>(manifestParser.getResult());
+		for (FileRevision fr : revisions) {
+			Nodeid nid = cmdLineResult.remove(fr.getPath());
+			errorCollector.checkThat("Extra " + fr.getPath() + " in Java result", nid, notNullValue());
+			if (nid != null) {
+				errorCollector.checkThat("Non-matching nodeid:" + nid, nid, equalTo(fr.getRevision()));
+			}
+		}
+		errorCollector.checkThat("Non-matched entries from command line:", cmdLineResult, equalTo(Collections.<Path,Nodeid>emptyMap()));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/TestOutgoing.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,94 @@
+/*
+ * 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.test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgLogCommand;
+import org.tmatesoft.hg.core.HgOutgoingCommand;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRemoteRepository;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestOutgoing {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	public static void main(String[] args) throws Throwable {
+		Configuration.get().remoteServers("http://localhost:8000/");
+		TestOutgoing t = new TestOutgoing();
+		t.testSimple();
+		t.errorCollector.verify();
+	}
+
+	public TestOutgoing() {
+	}
+
+	@Test
+	public void testSimple() throws Exception {
+		int x = 0;
+		HgLookup lookup = new HgLookup();
+		for (HgRemoteRepository hgRemote : Configuration.get().allRemote()) {
+			File dest = TestIncoming.createEmptyDir("test-outgoing-" + x++);
+			ExecHelper eh0 = new ExecHelper(new OutputParser.Stub(false), null);
+			eh0.run("hg", "clone", hgRemote.getLocation(), dest.toString());
+			eh0.cwd(dest);
+			Assert.assertEquals("initial clone failed", 0, eh0.getExitValue());
+			HgOutgoingCommand cmd = new HgOutgoingCommand(lookup.detect(dest)).against(hgRemote);
+			LogOutputParser outParser = new LogOutputParser(true);
+			ExecHelper eh = new ExecHelper(outParser, dest);
+			HgLogCommand.CollectHandler collector = new HgLogCommand.CollectHandler();
+			//
+			cmd.executeFull(collector);
+			List<Nodeid> liteResult = cmd.executeLite(null);
+			eh.run("hg", "outgoing", "--debug", hgRemote.getLocation());
+			TestIncoming.report(collector, outParser, liteResult, errorCollector);
+			//
+			File f = new File(dest, "Test.txt");
+			append(f, "1");
+			eh0.run("hg", "add");
+			eh0.run("hg", "commit", "-m", "1");
+			append(f, "2");
+			eh0.run("hg", "commit", "-m", "2");
+			//
+			cmd = new HgOutgoingCommand(lookup.detect(dest)).against(hgRemote);
+			cmd.executeFull(collector = new HgLogCommand.CollectHandler());
+			liteResult = cmd.executeLite(null);
+			outParser.reset();
+			eh.run("hg", "outgoing", "--debug", hgRemote.getLocation());
+			TestIncoming.report(collector, outParser, liteResult, errorCollector);
+		}
+	}
+
+	static void append(File f, String s) throws IOException {
+		FileWriter fw = new FileWriter(f);
+		fw.append(s);
+		fw.close();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/TestStatus.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,242 @@
+/*
+ * 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.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.tmatesoft.hg.core.HgStatus.*;
+import static org.tmatesoft.hg.core.HgStatus.Kind.*;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgStatus;
+import org.tmatesoft.hg.core.HgStatusCommand;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestStatus {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	private final HgRepository repo;
+	private StatusOutputParser statusParser;
+	private ExecHelper eh;
+
+	public static void main(String[] args) throws Throwable {
+		TestStatus test = new TestStatus();
+		test.testLowLevel();
+		test.testStatusCommand();
+		test.testPerformance();
+		test.errorCollector.verify();
+	}
+	
+	public TestStatus() throws Exception {
+		this(new HgLookup().detectFromWorkingDir());
+	}
+
+	private TestStatus(HgRepository hgRepo) {
+		repo = hgRepo;
+		Assume.assumeTrue(!repo.isInvalid());
+		statusParser = new StatusOutputParser();
+		eh = new ExecHelper(statusParser, null);
+	}
+	
+	@Test
+	public void testLowLevel() throws Exception {
+		final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo);
+		statusParser.reset();
+		eh.run("hg", "status", "-A");
+		HgStatusCollector.Record r = wcc.status(HgRepository.TIP);
+		report("hg status -A", r, statusParser);
+		//
+		statusParser.reset();
+		int revision = 3;
+		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
+		r = wcc.status(revision);
+		report("status -A --rev " + revision, r, statusParser);
+		//
+		statusParser.reset();
+		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
+		r = new HgStatusCollector.Record();
+		new HgStatusCollector(repo).change(revision, r);
+		report("status -A --change " + revision, r, statusParser);
+		//
+		statusParser.reset();
+		int rev2 = 80;
+		final String range = String.valueOf(revision) + ":" + String.valueOf(rev2);
+		eh.run("hg", "status", "-A", "--rev", range);
+		r = new HgStatusCollector(repo).status(revision, rev2);
+		report("Status -A -rev " + range, r, statusParser);
+	}
+	
+	@Test
+	public void testStatusCommand() throws Exception {
+		final HgStatusCommand sc = new HgStatusCommand(repo).all();
+		StatusCollector r;
+		statusParser.reset();
+		eh.run("hg", "status", "-A");
+		sc.execute(r = new StatusCollector());
+		report("hg status -A", r);
+		//
+		statusParser.reset();
+		int revision = 3;
+		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
+		sc.base(revision).execute(r = new StatusCollector());
+		report("status -A --rev " + revision, r);
+		//
+		statusParser.reset();
+		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
+		sc.base(TIP).revision(revision).execute(r = new StatusCollector());
+		report("status -A --change " + revision, r);
+		
+		// TODO check not -A, but defaults()/custom set of modifications 
+	}
+	
+	private static class StatusCollector implements HgStatusCommand.Handler {
+		private final Map<HgStatus.Kind, List<Path>> map = new TreeMap<HgStatus.Kind, List<Path>>();
+
+		public void handleStatus(HgStatus s) {
+			List<Path> l = map.get(s.getKind());
+			if (l == null) {
+				l = new LinkedList<Path>();
+				map.put(s.getKind(), l);
+			}
+			l.add(s.getPath());
+		}
+		
+		public List<Path> get(Kind k) {
+			List<Path> rv = map.get(k);
+			if (rv == null) {
+				return Collections.emptyList();
+			}
+			return rv;
+		}
+	}
+	
+	public void testRemovedAgainstNonTip() {
+		/*
+		 status --rev N when a file added past revision N was removed ((both physically and in dirstate), but not yet committed
+
+		 Reports extra REMOVED file (the one added and removed in between). Shall not
+		 */
+	}
+	
+	/*
+	 * With warm-up of previous tests, 10 runs, time in milliseconds
+	 * 'hg status -A': Native client total 953 (95 per run), Java client 94 (9)
+	 * 'hg status -A --rev 3:80': Native client total 1828 (182 per run), Java client 235 (23)
+	 * 'hg log --debug', 10 runs: Native client total 1766 (176 per run), Java client 78 (7)
+	 * 
+	 * 18.02.2011
+	 * 'hg status -A --rev 3:80', 10 runs:  Native client total 2000 (200 per run), Java client 250 (25)
+	 * 'hg log --debug', 10 runs: Native client total 2297 (229 per run), Java client 125 (12)
+	 * 
+	 * 9.3.2011 (DataAccess instead of byte[] in ReflogStream.Inspector
+	 * 'hg status -A',				10 runs:  Native client total 1516 (151 per run), Java client 219 (21)
+	 * 'hg status -A --rev 3:80',	10 runs:  Native client total 1875 (187 per run), Java client 3187 (318) (!!! ???)
+	 * 'hg log --debug',			10 runs: Native client total 2484 (248 per run), Java client 344 (34)
+	 */
+	public void testPerformance() throws Exception {
+		final int runs = 10;
+		final long start1 = System.currentTimeMillis();
+		for (int i = 0; i < runs; i++) {
+			statusParser.reset();
+			eh.run("hg", "status", "-A", "--rev", "3:80");
+		}
+		final long start2 = System.currentTimeMillis();
+		for (int i = 0; i < runs; i++) {
+			StatusCollector r = new StatusCollector();
+			new HgStatusCommand(repo).all().base(3).revision(80).execute(r);
+		}
+		final long end = System.currentTimeMillis();
+		System.out.printf("'hg status -A --rev 3:80', %d runs:  Native client total %d (%d per run), Java client %d (%d)\n", runs, start2-start1, (start2-start1)/runs, end-start2, (end-start2)/runs);
+	}
+	
+	private void report(String what, StatusCollector r) {
+		reportNotEqual(what + "#MODIFIED", r.get(Modified), statusParser.getModified());
+		reportNotEqual(what + "#ADDED", r.get(Added), statusParser.getAdded());
+		reportNotEqual(what + "#REMOVED", r.get(Removed), statusParser.getRemoved());
+		reportNotEqual(what + "#CLEAN", r.get(Clean), statusParser.getClean());
+		reportNotEqual(what + "#IGNORED", r.get(Ignored), statusParser.getIgnored());
+		reportNotEqual(what + "#MISSING", r.get(Missing), statusParser.getMissing());
+		reportNotEqual(what + "#UNKNOWN", r.get(Unknown), statusParser.getUnknown());
+		// FIXME test copies
+	}
+
+	private void report(String what, HgStatusCollector.Record r, StatusOutputParser statusParser) {
+		reportNotEqual(what + "#MODIFIED", r.getModified(), statusParser.getModified());
+		reportNotEqual(what + "#ADDED", r.getAdded(), statusParser.getAdded());
+		reportNotEqual(what + "#REMOVED", r.getRemoved(), statusParser.getRemoved());
+		reportNotEqual(what + "#CLEAN", r.getClean(), statusParser.getClean());
+		reportNotEqual(what + "#IGNORED", r.getIgnored(), statusParser.getIgnored());
+		reportNotEqual(what + "#MISSING", r.getMissing(), statusParser.getMissing());
+		reportNotEqual(what + "#UNKNOWN", r.getUnknown(), statusParser.getUnknown());
+		List<Path> copiedKeyDiff = difference(r.getCopied().keySet(), statusParser.getCopied().keySet());
+		HashMap<Path, String> copyDiff = new HashMap<Path,String>();
+		if (copiedKeyDiff.isEmpty()) {
+			for (Path jk : r.getCopied().keySet()) {
+				Path jv = r.getCopied().get(jk);
+				if (statusParser.getCopied().containsKey(jk)) {
+					Path cmdv = statusParser.getCopied().get(jk);
+					if (!jv.equals(cmdv)) {
+						copyDiff.put(jk, jv + " instead of " + cmdv);
+					}
+				} else {
+					copyDiff.put(jk, "ERRONEOUSLY REPORTED IN JAVA");
+				}
+			}
+		}
+		errorCollector.checkThat(what + "#Non-matching 'copied' keys: ", copiedKeyDiff, equalTo(Collections.<Path>emptyList()));
+		errorCollector.checkThat(what + "#COPIED", copyDiff, equalTo(Collections.<Path,String>emptyMap()));
+	}
+	
+	private <T> void reportNotEqual(String what, Collection<T> l1, Collection<T> l2) {
+		List<T> diff = difference(l1, l2);
+		errorCollector.checkThat(what, diff, equalTo(Collections.<T>emptyList()));
+	}
+
+	private static <T> List<T> difference(Collection<T> l1, Collection<T> l2) {
+		LinkedList<T> result = new LinkedList<T>(l2);
+		for (T t : l1) {
+			if (l2.contains(t)) {
+				result.remove(t);
+			} else {
+				result.add(t);
+			}
+		}
+		return result;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg4j/src/test/java/org/tmatesoft/hg/test/TestStorePath.java	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,73 @@
+/*
+ * 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.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import junit.framework.Assert;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.internal.Internals;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestStorePath {
+	
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+	
+	private PathRewrite storePathHelper;
+
+	public static void main(String[] args) throws Throwable {
+		final TestStorePath test = new TestStorePath();
+		test.testWindowsFilenames();
+		test.testHashLongPath();
+		test.errorCollector.verify();
+	}
+	
+	public TestStorePath() {
+		final Internals i = new Internals();
+		i.setStorageConfig(1, 0x7);
+		storePathHelper = i.buildDataFilesHelper();
+	}
+
+	@Test
+	public void testWindowsFilenames() {
+		// see http://mercurial.selenic.com/wiki/fncacheRepoFormat#Encoding_of_Windows_reserved_names
+		String n1 = "aux.bla/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c";
+		String r1 = "store/data/au~78.bla/bla.aux/pr~6e/_p_r_n/lpt/co~6d3/nu~6c/coma/foo._n_u_l/normal.c.i";
+		Assert.assertEquals("Windows filenames are ", r1, storePathHelper.rewrite(n1));
+	}
+
+	@Test
+	public void testHashLongPath() {
+		String n1 = "AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT";
+		String r1 = "store/dh/au~78/second/x.prn/fourth/fi~3afth/sixth/seventh/eighth/nineth/tenth/loremia20419e358ddff1bf8751e38288aff1d7c32ec05.i";
+		String n2 = "enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider";
+		String r2 = "store/dh/enterpri/openesba/contrib-/corba-bc/netbeans/wsdlexte/src/main/java/org.net7018f27961fdf338a598a40c4683429e7ffb9743.i";
+		String n3 = "AUX.THE-QUICK-BROWN-FOX-JU:MPS-OVER-THE-LAZY-DOG-THE-QUICK-BROWN-FOX-JUMPS-OVER-THE-LAZY-DOG.TXT";
+		String r3 = "store/dh/au~78.the-quick-brown-fox-ju~3amps-over-the-lazy-dog-the-quick-brown-fox-jud4dcadd033000ab2b26eb66bae1906bcb15d4a70.i";
+		// TODO segment[8] == [. ], segment[8] in the middle of windows reserved name or character (to see if ~xx is broken)
+		errorCollector.checkThat(storePathHelper.rewrite(n1), equalTo(r1));
+		errorCollector.checkThat(storePathHelper.rewrite(n2), equalTo(r2));
+		errorCollector.checkThat(storePathHelper.rewrite(n3), equalTo(r3));
+	}
+}
Binary file hg4j/src/test/resources/test-repos.jar has changed
Binary file lib/junit-4.8.2-src.jar has changed
Binary file lib/junit-4.8.2.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/settings.gradle	Tue May 10 10:52:53 2011 +0200
@@ -0,0 +1,4 @@
+rootProject.name = 'org.tmatesoft.hg4j'
+
+include 'hg4j'
+include 'hg4j-cli'
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/ChangesetTransformer.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/*
- * 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.util.Set;
-
-import org.tmatesoft.hg.repo.HgChangelog;
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.HgStatusCollector;
-import org.tmatesoft.hg.util.PathPool;
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- * Bridges {@link HgChangelog.RawChangeset} with high-level {@link HgChangeset} API
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-/*package-local*/ class ChangesetTransformer implements HgChangelog.Inspector {
-	private final HgChangesetHandler handler;
-	private final HgChangeset changeset;
-	private Set<String> branches;
-
-	// repo and delegate can't be null, parent walker can
-	public ChangesetTransformer(HgRepository hgRepo, HgChangesetHandler delegate, HgChangelog.ParentWalker pw) {
-		if (hgRepo == null || delegate == null) {
-			throw new IllegalArgumentException();
-		}
-		HgStatusCollector statusCollector = new HgStatusCollector(hgRepo);
-		// files listed in a changeset don't need their names to be rewritten (they are normalized already)
-		PathPool pp = new PathPool(new PathRewrite.Empty());
-		statusCollector.setPathPool(pp);
-		changeset = new HgChangeset(statusCollector, pp);
-		changeset.setParentHelper(pw);
-		handler = delegate;
-	}
-	
-	public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
-		if (branches != null && !branches.contains(cset.branch())) {
-			return;
-		}
-
-		changeset.init(revisionNumber, nodeid, cset);
-		handler.next(changeset);
-	}
-	
-	public void limitBranches(Set<String> branches) {
-		this.branches = branches;
-	}
-}
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/HgBadArgumentException.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/*
- * 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;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-public class HgBadArgumentException extends HgException {
-
-	public HgBadArgumentException(String message, Throwable cause) {
-		super(message, cause);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgBadStateException.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * 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;
-
-/**
- * hg4j's own internal error or unexpected state.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-public class HgBadStateException extends RuntimeException {
-
-	// FIXME quick-n-dirty fix, don't allow exceptions without a cause
-	public HgBadStateException() {
-		super("Internal error");
-	}
-
-	public HgBadStateException(String message) {
-		super(message);
-	}
-
-	public HgBadStateException(Throwable cause) {
-		super(cause);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgCatCommand.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-/*
- * 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 static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision;
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-import org.tmatesoft.hg.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.ByteChannel;
-import org.tmatesoft.hg.util.CancelledException;
-import org.tmatesoft.hg.util.Path;
-
-/**
- * Command to obtain content of a file, 'hg cat' counterpart. 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgCatCommand {
-
-	private final HgRepository repo;
-	private Path file;
-	private int localRevision = TIP;
-	private Nodeid revision;
-
-	public HgCatCommand(HgRepository hgRepo) {
-		repo = hgRepo;
-	}
-
-	/**
-	 * File to read, required parameter 
-	 * @param fname path to a repository file, can't be <code>null</code>
-	 * @return <code>this</code> for convenience
-	 * @throws IllegalArgumentException if supplied fname is null or points to directory
-	 */
-	public HgCatCommand file(Path fname) {
-		if (fname == null || fname.isDirectory()) {
-			throw new IllegalArgumentException(String.valueOf(fname));
-		}
-		file = fname;
-		return this;
-	}
-
-	/**
-	 * Invocation of this method clears revision set with {@link #revision(Nodeid)} or {@link #revision(int)} earlier.
-	 * XXX rev can't be WORKING_COPY (if allowed, need to implement in #execute())
-	 * @param rev local revision number, non-negative, or one of predefined constants. Note, use of {@link HgRepository#BAD_REVISION}, 
-	 * although possible, makes little sense (command would fail if executed).  
- 	 * @return <code>this</code> for convenience
-	 */
-	public HgCatCommand revision(int rev) {
-		if (wrongLocalRevision(rev)) {
-			throw new IllegalArgumentException(String.valueOf(rev));
-		}
-		localRevision = rev;
-		revision = null;
-		return this;
-	}
-	
-	/**
-	 * Select revision to read. Invocation of this method clears revision set with {@link #revision(int)} or {@link #revision(Nodeid)} earlier.
-	 * 
-	 * @param nodeid - unique revision identifier, Note, use of <code>null</code> or {@link Nodeid#NULL} is senseless
-	 * @return <code>this</code> for convenience
-	 */
-	public HgCatCommand revision(Nodeid nodeid) {
-		if (nodeid != null && nodeid.isNull()) {
-			nodeid = null;
-		}
-		revision = nodeid;
-		localRevision = BAD_REVISION;
-		return this;
-	}
-
-	/**
-	 * Runs the command with current set of parameters and pipes data to provided sink.
-	 * 
-	 * @param sink output channel to write data to.
-	 * @throws HgDataStreamException 
-	 * @throws IllegalArgumentException when command arguments are incomplete or wrong
-	 */
-	public void execute(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
-		if (localRevision == BAD_REVISION && revision == null) {
-			throw new IllegalArgumentException("Either local file revision number or nodeid shall be specified");
-		}
-		if (file == null) {
-			throw new IllegalArgumentException("Name of the file is missing");
-		}
-		if (sink == null) {
-			throw new IllegalArgumentException("Need an output channel");
-		}
-		HgDataFile dataFile = repo.getFileNode(file);
-		if (!dataFile.exists()) {
-			throw new HgDataStreamException(file.toString(), new FileNotFoundException(file.toString()));
-		}
-		int revToExtract;
-		if (revision != null) {
-			revToExtract = dataFile.getLocalRevision(revision);
-		} else {
-			revToExtract = localRevision;
-		}
-		dataFile.contentWithFilters(revToExtract, sink);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgChangeset.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-/*
- * 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 static org.tmatesoft.hg.core.Nodeid.NULL;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.repo.HgChangelog;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.HgStatusCollector;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- * Record in the Mercurial changelog, describing single commit.
- * 
- * Not thread-safe, don't try to read from different threads
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgChangeset implements Cloneable {
-	private final HgStatusCollector statusHelper;
-	private final Path.Source pathHelper;
-
-	private HgChangelog.ParentWalker parentHelper;
-
-	//
-	private RawChangeset changeset;
-	private Nodeid nodeid;
-
-	//
-	private List<FileRevision> modifiedFiles, addedFiles;
-	private List<Path> deletedFiles;
-	private int revNumber;
-	private byte[] parent1, parent2;
-
-	// XXX consider CommandContext with StatusCollector, PathPool etc. Commands optionally get CC through a cons or create new
-	// and pass it around
-	/*package-local*/HgChangeset(HgStatusCollector statusCollector, Path.Source pathFactory) {
-		statusHelper = statusCollector;
-		pathHelper = pathFactory;
-	}
-
-	/*package-local*/ void init(int localRevNumber, Nodeid nid, RawChangeset rawChangeset) {
-		revNumber = localRevNumber;
-		nodeid = nid;
-		changeset = rawChangeset.clone();
-		modifiedFiles = addedFiles = null;
-		deletedFiles = null;
-		parent1 = parent2 = null;
-		// keep references to parentHelper, statusHelper and pathHelper
-	}
-
-	/*package-local*/ void setParentHelper(HgChangelog.ParentWalker pw) {
-		parentHelper = pw;
-		if (parentHelper != null) {
-			if (parentHelper.getRepo() != statusHelper.getRepo()) {
-				throw new IllegalArgumentException();
-			}
-		}
-	}
-
-	public int getRevision() {
-		return revNumber;
-	}
-	public Nodeid getNodeid() {
-		return nodeid;
-	}
-	public String getUser() {
-		return changeset.user();
-	}
-	public String getComment() {
-		return changeset.comment();
-	}
-	public String getBranch() {
-		return changeset.branch();
-	}
-
-	/**
-	 * @return used to be String, now {@link HgDate}, use {@link HgDate#toString()} to get same result as before 
-	 */
-	public HgDate getDate() {
-		return new HgDate(changeset.date().getTime(), changeset.timezone());
-	}
-	public Nodeid getManifestRevision() {
-		return changeset.manifest();
-	}
-
-	public List<Path> getAffectedFiles() {
-		// reports files as recorded in changelog. Note, merge revisions may have no
-		// files listed, and thus this method would return empty list, while
-		// #getModifiedFiles() would return list with merged file(s) (because it uses status to get 'em, not
-		// what #files() gives).
-		ArrayList<Path> rv = new ArrayList<Path>(changeset.files().size());
-		for (String name : changeset.files()) {
-			rv.add(pathHelper.path(name));
-		}
-		return rv;
-	}
-
-	public List<FileRevision> getModifiedFiles() {
-		if (modifiedFiles == null) {
-			initFileChanges();
-		}
-		return modifiedFiles;
-	}
-
-	public List<FileRevision> getAddedFiles() {
-		if (addedFiles == null) {
-			initFileChanges();
-		}
-		return addedFiles;
-	}
-
-	public List<Path> getRemovedFiles() {
-		if (deletedFiles == null) {
-			initFileChanges();
-		}
-		return deletedFiles;
-	}
-
-	public boolean isMerge() {
-		// p1 == -1 and p2 != -1 is legitimate case
-		return !NULL.equals(getFirstParentRevision()) && !NULL.equals(getSecondParentRevision()); 
-	}
-	
-	public Nodeid getFirstParentRevision() {
-		if (parentHelper != null) {
-			return parentHelper.safeFirstParent(nodeid);
-		}
-		// read once for both p1 and p2
-		if (parent1 == null) {
-			parent1 = new byte[20];
-			parent2 = new byte[20];
-			statusHelper.getRepo().getChangelog().parents(revNumber, new int[2], parent1, parent2);
-		}
-		return Nodeid.fromBinary(parent1, 0);
-	}
-	
-	public Nodeid getSecondParentRevision() {
-		if (parentHelper != null) {
-			return parentHelper.safeSecondParent(nodeid);
-		}
-		if (parent2 == null) {
-			parent1 = new byte[20];
-			parent2 = new byte[20];
-			statusHelper.getRepo().getChangelog().parents(revNumber, new int[2], parent1, parent2);
-		}
-		return Nodeid.fromBinary(parent2, 0);
-	}
-
-	@Override
-	public HgChangeset clone() {
-		try {
-			HgChangeset copy = (HgChangeset) super.clone();
-			// copy.changeset references this.changeset, doesn't need own copy
-			return copy;
-		} catch (CloneNotSupportedException ex) {
-			throw new InternalError(ex.toString());
-		}
-	}
-
-	private /*synchronized*/ void initFileChanges() {
-		ArrayList<Path> deleted = new ArrayList<Path>();
-		ArrayList<FileRevision> modified = new ArrayList<FileRevision>();
-		ArrayList<FileRevision> added = new ArrayList<FileRevision>();
-		HgStatusCollector.Record r = new HgStatusCollector.Record();
-		statusHelper.change(revNumber, r);
-		final HgRepository repo = statusHelper.getRepo();
-		for (Path s : r.getModified()) {
-			Nodeid nid = r.nodeidAfterChange(s);
-			if (nid == null) {
-				throw new HgBadStateException();
-			}
-			modified.add(new FileRevision(repo, nid, s));
-		}
-		for (Path s : r.getAdded()) {
-			Nodeid nid = r.nodeidAfterChange(s);
-			if (nid == null) {
-				throw new HgBadStateException();
-			}
-			added.add(new FileRevision(repo, nid, s));
-		}
-		for (Path s : r.getRemoved()) {
-			// with Path from getRemoved, may just copy
-			deleted.add(s);
-		}
-		modified.trimToSize();
-		added.trimToSize();
-		deleted.trimToSize();
-		modifiedFiles = Collections.unmodifiableList(modified);
-		addedFiles = Collections.unmodifiableList(added);
-		deletedFiles = Collections.unmodifiableList(deleted);
-	}
-}
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/HgChangesetHandler.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/*
- * 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;
-
-/**
- * Callback to process {@link HgChangeset changesets}.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface HgChangesetHandler {
-	/**
-	 * @param changeset not necessarily a distinct instance each time, {@link HgChangeset#clone() clone()} if need a copy.
-	 */
-	void next(HgChangeset changeset);
-}
--- a/src/org/tmatesoft/hg/core/HgCloneCommand.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,333 +0,0 @@
-/*
- * 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 static org.tmatesoft.hg.core.Nodeid.NULL;
-import static org.tmatesoft.hg.internal.RequiresFile.*;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.TreeMap;
-import java.util.zip.DeflaterOutputStream;
-
-import org.tmatesoft.hg.internal.ByteArrayDataAccess;
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.DigestHelper;
-import org.tmatesoft.hg.internal.Internals;
-import org.tmatesoft.hg.repo.HgBundle;
-import org.tmatesoft.hg.repo.HgBundle.GroupElement;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.CancelledException;
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- * WORK IN PROGRESS, DO NOT USE
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgCloneCommand {
-
-	private File destination;
-	private HgRemoteRepository srcRepo;
-
-	public HgCloneCommand() {
-	}
-	
-	/**
-	 * @param folder location to become root of the repository (i.e. where <em>.hg</em> folder would reside). Either 
-	 * shall not exist or be empty otherwise. 
-	 * @return <code>this</code> for convenience
-	 */
-	public HgCloneCommand destination(File folder) {
-		destination = folder;
-		return this;
-	}
-
-	public HgCloneCommand source(HgRemoteRepository hgRemote) {
-		srcRepo = hgRemote;
-		return this;
-	}
-
-	public HgRepository execute() throws HgException, CancelledException {
-		if (destination == null) {
-			throw new HgBadArgumentException("Destination not set", null);
-		}
-		if (srcRepo == null || srcRepo.isInvalid()) {
-			throw new HgBadArgumentException("Bad source repository", null);
-		}
-		if (destination.exists()) {
-			if (!destination.isDirectory()) {
-				throw new HgBadArgumentException(String.format("%s is not a directory", destination), null);
-			} else if (destination.list().length > 0) {
-				throw new HgBadArgumentException(String.format("% shall be empty", destination), null);
-			}
-		} else {
-			destination.mkdirs();
-		}
-		// if cloning remote repo, which can stream and no revision is specified -
-		// can use 'stream_out' wireproto
-		//
-		// pull all changes from the very beginning
-		// XXX consult getContext() if by any chance has a bundle ready, if not, then read and register 
-		HgBundle completeChanges = srcRepo.getChanges(Collections.singletonList(NULL));
-		WriteDownMate mate = new WriteDownMate(destination);
-		try {
-			// instantiate new repo in the destdir
-			mate.initEmptyRepository();
-			// pull changes
-			completeChanges.inspectAll(mate);
-			mate.complete();
-		} catch (IOException ex) {
-			throw new HgException(ex);
-		} finally {
-			completeChanges.unlink();
-		}
-		return new HgLookup().detect(destination);
-	}
-
-
-	// 1. process changelog, memorize nodeids to index
-	// 2. process manifest, using map from step 3, collect manifest nodeids
-	// 3. process every file, using map from 3, and consult set from step 4 to ensure repo is correct
-	private static class WriteDownMate implements HgBundle.Inspector {
-		private final File hgDir;
-		private final PathRewrite storagePathHelper;
-		private FileOutputStream indexFile;
-		private String filename; // human-readable name of the file being written, for log/exception purposes 
-
-		private final TreeMap<Nodeid, Integer> changelogIndexes = new TreeMap<Nodeid, Integer>();
-		private boolean collectChangelogIndexes = false;
-
-		private int base = -1;
-		private long offset = 0;
-		private DataAccess prevRevContent;
-		private final DigestHelper dh = new DigestHelper();
-		private final ArrayList<Nodeid> revisionSequence = new ArrayList<Nodeid>(); // last visited nodes first
-
-		private final LinkedList<String> fncacheFiles = new LinkedList<String>();
-		private Internals implHelper;
-
-		public WriteDownMate(File destDir) {
-			hgDir = new File(destDir, ".hg");
-			implHelper = new Internals();
-			implHelper.setStorageConfig(1, STORE | FNCACHE | DOTENCODE);
-			storagePathHelper = implHelper.buildDataFilesHelper();
-		}
-
-		public void initEmptyRepository() throws IOException {
-			implHelper.initEmptyRepository(hgDir);
-		}
-
-		public void complete() throws IOException {
-			FileOutputStream fncacheFile = new FileOutputStream(new File(hgDir, "store/fncache"));
-			for (String s : fncacheFiles) {
-				fncacheFile.write(s.getBytes());
-				fncacheFile.write(0x0A); // http://mercurial.selenic.com/wiki/fncacheRepoFormat
-			}
-			fncacheFile.close();
-		}
-
-		public void changelogStart() {
-			try {
-				base = -1;
-				offset = 0;
-				revisionSequence.clear();
-				indexFile = new FileOutputStream(new File(hgDir, filename = "store/00changelog.i"));
-				collectChangelogIndexes = true;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		public void changelogEnd() {
-			try {
-				if (prevRevContent != null) {
-					prevRevContent.done();
-					prevRevContent = null;
-				}
-				collectChangelogIndexes = false;
-				indexFile.close();
-				indexFile = null;
-				filename = null;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		public void manifestStart() {
-			try {
-				base = -1;
-				offset = 0;
-				revisionSequence.clear();
-				indexFile = new FileOutputStream(new File(hgDir, filename = "store/00manifest.i"));
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		public void manifestEnd() {
-			try {
-				if (prevRevContent != null) {
-					prevRevContent.done();
-					prevRevContent = null;
-				}
-				indexFile.close();
-				indexFile = null;
-				filename = null;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-		
-		public void fileStart(String name) {
-			try {
-				base = -1;
-				offset = 0;
-				revisionSequence.clear();
-				fncacheFiles.add("data/" + name + ".i"); // FIXME this is pure guess, 
-				// need to investigate more how filenames are kept in fncache
-				File file = new File(hgDir, filename = storagePathHelper.rewrite(name));
-				file.getParentFile().mkdirs();
-				indexFile = new FileOutputStream(file);
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		public void fileEnd(String name) {
-			try {
-				if (prevRevContent != null) {
-					prevRevContent.done();
-					prevRevContent = null;
-				}
-				indexFile.close();
-				indexFile = null;
-				filename = null;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		private int knownRevision(Nodeid p) {
-			if (NULL.equals(p)) {
-				return -1;
-			} else {
-				for (int i = revisionSequence.size() - 1; i >= 0; i--) {
-					if (revisionSequence.get(i).equals(p)) {
-						return i;
-					}
-				}
-			}
-			throw new HgBadStateException(String.format("Can't find index of %s for file %s", p.shortNotation(), filename));
-		}
-
-		public boolean element(GroupElement ge) {
-			try {
-				assert indexFile != null;
-				boolean writeComplete = false;
-				Nodeid p1 = ge.firstParent();
-				Nodeid p2 = ge.secondParent();
-				if (NULL.equals(p1) && NULL.equals(p2) /* or forced flag, does REVIDX_PUNCHED_FLAG indicate that? */) {
-					prevRevContent = new ByteArrayDataAccess(new byte[0]);
-					writeComplete = true;
-				}
-				byte[] content = ge.apply(prevRevContent);
-				byte[] calculated = dh.sha1(p1, p2, content).asBinary();
-				final Nodeid node = ge.node();
-				if (!node.equalsTo(calculated)) {
-					throw new HgBadStateException(String.format("Checksum failed: expected %s, calculated %s. File %s", node, calculated, filename));
-				}
-				final int link;
-				if (collectChangelogIndexes) {
-					changelogIndexes.put(node, revisionSequence.size());
-					link = revisionSequence.size();
-				} else {
-					Integer csRev = changelogIndexes.get(ge.cset());
-					if (csRev == null) {
-						throw new HgBadStateException(String.format("Changelog doesn't contain revision %s of %s", ge.cset().shortNotation(), filename));
-					}
-					link = csRev.intValue();
-				}
-				final int p1Rev = knownRevision(p1), p2Rev = knownRevision(p2);
-				DataAccess patchContent = ge.rawData();
-				writeComplete = writeComplete || patchContent.length() >= (/* 3/4 of actual */content.length - (content.length >>> 2));
-				if (writeComplete) {
-					base = revisionSequence.size();
-				}
-				final byte[] sourceData = writeComplete ? content : patchContent.byteArray();
-				final byte[] data;
-				ByteArrayOutputStream bos = new ByteArrayOutputStream(content.length);
-				DeflaterOutputStream dos = new DeflaterOutputStream(bos);
-				dos.write(sourceData);
-				dos.close();
-				final byte[] compressedData = bos.toByteArray();
-				dos = null;
-				bos = null;
-				final Byte dataPrefix;
-				if (compressedData.length >= (sourceData.length - (sourceData.length >>> 2))) {
-					// compression wasn't too effective,
-					data = sourceData;
-					dataPrefix = 'u';
-				} else {
-					data = compressedData;
-					dataPrefix = null;
-				}
-
-				ByteBuffer header = ByteBuffer.allocate(64 /* REVLOGV1_RECORD_SIZE */);
-				if (offset == 0) {
-					final int INLINEDATA = 1 << 16;
-					header.putInt(1 /* RevlogNG */ | INLINEDATA);
-					header.putInt(0);
-				} else {
-					header.putLong(offset << 16);
-				}
-				final int compressedLen = data.length + (dataPrefix == null ? 0 : 1);
-				header.putInt(compressedLen);
-				header.putInt(content.length);
-				header.putInt(base);
-				header.putInt(link);
-				header.putInt(p1Rev);
-				header.putInt(p2Rev);
-				header.put(node.toByteArray());
-				// assume 12 bytes left are zeros
-				indexFile.write(header.array());
-				if (dataPrefix != null) {
-					indexFile.write(dataPrefix.byteValue());
-				}
-				indexFile.write(data);
-				//
-				offset += compressedLen;
-				revisionSequence.add(node);
-				prevRevContent.done();
-				prevRevContent = new ByteArrayDataAccess(content);
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-			return true;
-		}
-	}
-
-}
--- a/src/org/tmatesoft/hg/core/HgDataStreamException.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-/*
- * 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.repo.HgDataFile;
-
-/**
- * Any erroneous state with @link {@link HgDataFile} input/output, read/write operations 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-public class HgDataStreamException extends HgException {
-
-	public HgDataStreamException(String message, Throwable cause) {
-		super(message, cause);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgDate.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * 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.util.Calendar;
-import java.util.Formatter;
-import java.util.Locale;
-import java.util.TimeZone;
-
-/**
- * Compound object to keep time  and time zone of a change. Time zone is not too useful unless you'd like to indicate where 
- * the change was made (original <em>hg</em> shows date of a change in its original time zone) 
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public final class HgDate implements Comparable<HgDate>, Cloneable {
-	private final long time;
-	private final TimeZone tzone;
-	
-
-	/**
-	 * @param millis UTC, milliseconds
-	 * @param timezone zone offset in seconds, UTC - local == timezone. I.e. positive in the Western Hemisphere.  
-	 */
-	public HgDate(long millis, int timezone) {
-		time = millis;
-		// @see http://pydoc.org/2.5.1/time.html  time.timezone -- difference in seconds between UTC and local standard time
-		// UTC - local = timezone. local = UTC - timezone
-		// In Java, timezone is positive right of Greenwich, UTC+timezone = local
-		String[] available = TimeZone.getAvailableIDs(-timezone * 1000);
-		assert available != null && available.length > 0 : String.valueOf(timezone);
-		// this is sort of hach, I don't know another way how to get 
-		// abbreviated name from zone offset (other than to have own mapping)
-		// And I can't use any id, because e.g. zone with id  "US/Mountain" 
-		// gives incorrect (according to hg cmdline) result, unlike MST or US/Arizona (all ids for zone -0700)
-		// use 1125044450000L to see the difference
-		String shortID = TimeZone.getTimeZone(available[0]).getDisplayName(false, TimeZone.SHORT);
-		// XXX in fact, might need to handle daylight saving time, but not sure how, 
-		// getTimeZone(GMT-timezone*1000).inDaylightTime()?
-		TimeZone tz = TimeZone.getTimeZone(shortID);
-		tzone = tz;
-	}
-	
-	public long getRawTime() {
-		return time;
-	}
-	
-	/**
-	 * @return zone object by reference, do not alter it (make own copy by {@link TimeZone#clone()}, to modify). 
-	 */
-	public TimeZone getTimeZone() {
-		return tzone;
-	}
-	
-	@Override
-	public String toString() {
-		// format the same way hg does
-		return toString(Locale.US);
-	}
-	
-	public String toString(Locale l) {
-		Calendar c = Calendar.getInstance(getTimeZone());
-		c.setTimeInMillis(getRawTime());
-		Formatter f = new Formatter(new StringBuilder(), l);
-		f.format("%ta %<tb %<td %<tH:%<tM:%<tS %<tY %<tz", c);
-		return f.out().toString();
-	}
-
-	public int compareTo(HgDate o) {
-		return (int) (time - o.time);
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (false == obj instanceof HgDate) {
-			return false;
-		}
-		HgDate other = (HgDate) obj;
-		return compareTo(other) == 0;
-	}
-	
-	@Override
-	public int hashCode() {
-		// copied from java.util.Datge
-		return (int) time ^ (int) (time >> 32);
-	}
-
-	@Override
-	protected Object clone() {
-		try {
-			return super.clone();
-		} catch (CloneNotSupportedException ex) {
-			throw new InternalError(ex.toString());
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgException.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * 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;
-
-/**
- * Root class for all hg4j exceptions.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-public class HgException extends Exception {
-
-	public HgException(String reason) {
-		super(reason);
-	}
-
-	public HgException(String reason, Throwable cause) {
-		super(reason, cause);
-	}
-
-	public HgException(Throwable cause) {
-		super(cause);
-	}
-
-//	/* XXX CONSIDER capability to pass extra information about errors */
-//	public static class Status {
-//		public Status(String message, Throwable cause, int errorCode, Object extraData) {
-//		}
-//	}
-}
--- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-/*
- * 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.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.internal.RepositoryComparator;
-import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain;
-import org.tmatesoft.hg.repo.HgBundle;
-import org.tmatesoft.hg.repo.HgChangelog;
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.CancelledException;
-
-/**
- * Command to find out changes available in a remote repository, missing locally.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgIncomingCommand {
-
-	private final HgRepository localRepo;
-	private HgRemoteRepository remoteRepo;
-	@SuppressWarnings("unused")
-	private boolean includeSubrepo;
-	private RepositoryComparator comparator;
-	private List<BranchChain> missingBranches;
-	private HgChangelog.ParentWalker parentHelper;
-	private Set<String> branches;
-
-	public HgIncomingCommand(HgRepository hgRepo) {
-	 	localRepo = hgRepo;
-	}
-	
-	public HgIncomingCommand against(HgRemoteRepository hgRemote) {
-		remoteRepo = hgRemote;
-		comparator = null;
-		missingBranches = null;
-		return this;
-	}
-
-	/**
-	 * Select specific branch to push.
-	 * Multiple branch specification possible (changeset from any of these would be included in result).
-	 * Note, {@link #executeLite(Object)} does not respect this setting.
-	 * 
-	 * @param branch - branch name, case-sensitive, non-null.
-	 * @return <code>this</code> for convenience
-	 * @throws IllegalArgumentException when branch argument is null
-	 */
-	public HgIncomingCommand branch(String branch) {
-		if (branch == null) {
-			throw new IllegalArgumentException();
-		}
-		if (branches == null) {
-			branches = new TreeSet<String>();
-		}
-		branches.add(branch);
-		return this;
-	}
-	
-	/**
-	 * PLACEHOLDER, NOT IMPLEMENTED YET.
-	 * 
-	 * Whether to include sub-repositories when collecting changes, default is <code>true</code> XXX or false?
-	 * @return <code>this</code> for convenience
-	 */
-	public HgIncomingCommand subrepo(boolean include) {
-		includeSubrepo = include;
-		throw HgRepository.notImplemented();
-	}
-
-	/**
-	 * Lightweight check for incoming changes, gives only list of revisions to pull.
-	 * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account. 
-	 *   
-	 * @param context anything hg4j can use to get progress and/or cancel support
-	 * @return list of nodes present at remote and missing locally
-	 * @throws HgException
-	 * @throws CancelledException
-	 */
-	public List<Nodeid> executeLite(Object context) throws HgException, CancelledException {
-		LinkedHashSet<Nodeid> result = new LinkedHashSet<Nodeid>();
-		RepositoryComparator repoCompare = getComparator(context);
-		for (BranchChain bc : getMissingBranches(context)) {
-			List<Nodeid> missing = repoCompare.visitBranches(bc);
-			HashSet<Nodeid> common = new HashSet<Nodeid>(); // ordering is irrelevant  
-			repoCompare.collectKnownRoots(bc, common);
-			// missing could only start with common elements. Once non-common, rest is just distinct branch revision trails.
-			for (Iterator<Nodeid> it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; 
-			result.addAll(missing);
-		}
-		ArrayList<Nodeid> rv = new ArrayList<Nodeid>(result);
-		return rv;
-	}
-
-	/**
-	 * Full information about incoming changes
-	 * 
-	 * @throws HgException
-	 * @throws CancelledException
-	 */
-	public void executeFull(final HgChangesetHandler handler) throws HgException, CancelledException {
-		if (handler == null) {
-			throw new IllegalArgumentException("Delegate can't be null");
-		}
-		final List<Nodeid> common = getCommon(handler);
-		HgBundle changegroup = remoteRepo.getChanges(common);
-		try {
-			changegroup.changes(localRepo, new HgChangelog.Inspector() {
-				private int localIndex;
-				private final HgChangelog.ParentWalker parentHelper;
-				private final ChangesetTransformer transformer;
-			
-				{
-					transformer = new ChangesetTransformer(localRepo, handler, getParentHelper());
-					transformer.limitBranches(branches);
-					parentHelper = getParentHelper();
-					// new revisions, if any, would be added after all existing, and would get numbered started with last+1
-					localIndex = localRepo.getChangelog().getRevisionCount();
-				}
-				
-				public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
-					if (parentHelper.knownNode(nodeid)) {
-						if (!common.contains(nodeid)) {
-							throw new HgBadStateException("Bundle shall not report known nodes other than roots we've supplied");
-						}
-						return;
-					}
-					transformer.next(localIndex++, nodeid, cset);
-				}
-			});
-		} catch (IOException ex) {
-			throw new HgException(ex);
-		}
-	}
-
-	private RepositoryComparator getComparator(Object context) throws HgException, CancelledException {
-		if (remoteRepo == null) {
-			throw new HgBadArgumentException("Shall specify remote repository to compare against", null);
-		}
-		if (comparator == null) {
-			comparator = new RepositoryComparator(getParentHelper(), remoteRepo);
-//			comparator.compare(context); // XXX meanwhile we use distinct path to calculate common  
-		}
-		return comparator;
-	}
-	
-	private HgChangelog.ParentWalker getParentHelper() {
-		if (parentHelper == null) {
-			parentHelper = localRepo.getChangelog().new ParentWalker();
-			parentHelper.init();
-		}
-		return parentHelper;
-	}
-	
-	private List<BranchChain> getMissingBranches(Object context) throws HgException, CancelledException {
-		if (missingBranches == null) {
-			missingBranches = getComparator(context).calculateMissingBranches();
-		}
-		return missingBranches;
-	}
-
-	private List<Nodeid> getCommon(Object context) throws HgException, CancelledException {
-//		return getComparator(context).getCommon();
-		final LinkedHashSet<Nodeid> common = new LinkedHashSet<Nodeid>();
-		// XXX common can be obtained from repoCompare, but at the moment it would almost duplicate work of calculateMissingBranches
-		// once I refactor latter, common shall be taken from repoCompare.
-		RepositoryComparator repoCompare = getComparator(context);
-		for (BranchChain bc : getMissingBranches(context)) {
-			repoCompare.collectKnownRoots(bc, common);
-		}
-		return new LinkedList<Nodeid>(common);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,325 +0,0 @@
-/*
- * 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 static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.io.IOException;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.ConcurrentModificationException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.repo.HgChangelog;
-import org.tmatesoft.hg.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.ByteChannel;
-import org.tmatesoft.hg.util.CancelledException;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- * Access to changelog, 'hg log' command counterpart.
- * 
- * <pre>
- * Usage:
- *   new LogCommand().limit(20).branch("maintenance-2.1").user("me").execute(new MyHandler());
- * </pre>
- * Not thread-safe (each thread has to use own {@link HgLogCommand} instance).
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgLogCommand implements HgChangelog.Inspector {
-
-	private final HgRepository repo;
-	private Set<String> users;
-	private Set<String> branches;
-	private int limit = 0, count = 0;
-	private int startRev = 0, endRev = TIP;
-	private Calendar date;
-	private Path file;
-	private boolean followHistory; // makes sense only when file != null
-	private ChangesetTransformer csetTransform;
-	private HgChangelog.ParentWalker parentHelper;
-	
-	public HgLogCommand(HgRepository hgRepo) {
-		repo = hgRepo;
-	}
-
-	/**
-	 * Limit search to specified user. Multiple user names may be specified. Once set, user names can't be 
-	 * cleared, use new command instance in such cases.
-	 * @param user - full or partial name of the user, case-insensitive, non-null.
-	 * @return <code>this</code> instance for convenience
-	 * @throws IllegalArgumentException when argument is null
-	 */
-	public HgLogCommand user(String user) {
-		if (user == null) {
-			throw new IllegalArgumentException();
-		}
-		if (users == null) {
-			users = new TreeSet<String>();
-		}
-		users.add(user.toLowerCase());
-		return this;
-	}
-
-	/**
-	 * Limit search to specified branch. Multiple branch specification possible (changeset from any of these 
-	 * would be included in result). If unspecified, all branches are considered. There's no way to clean branch selection 
-	 * once set, create fresh new command instead.
-	 * @param branch - branch name, case-sensitive, non-null.
-	 * @return <code>this</code> instance for convenience
-	 * @throws IllegalArgumentException when branch argument is null
-	 */
-	public HgLogCommand branch(String branch) {
-		if (branch == null) {
-			throw new IllegalArgumentException();
-		}
-		if (branches == null) {
-			branches = new TreeSet<String>();
-		}
-		branches.add(branch);
-		return this;
-	}
-	
-	// limit search to specific date
-	// multiple?
-	public HgLogCommand date(Calendar date) {
-		this.date = date;
-		// FIXME implement
-		// isSet(field) - false => don't use in detection of 'same date'
-		throw HgRepository.notImplemented();
-	}
-	
-	/**
-	 * 
-	 * @param num - number of changeset to produce. Pass 0 to clear the limit. 
-	 * @return <code>this</code> instance for convenience
-	 */
-	public HgLogCommand limit(int num) {
-		limit = num;
-		return this;
-	}
-
-	/**
-	 * Limit to specified subset of Changelog, [min(rev1,rev2), max(rev1,rev2)], inclusive.
-	 * Revision may be specified with {@link HgRepository#TIP}  
-	 * @param rev1 - local revision number
-	 * @param rev2 - local revision number
-	 * @return <code>this</code> instance for convenience
-	 */
-	public HgLogCommand range(int rev1, int rev2) {
-		if (rev1 != TIP && rev2 != TIP) {
-			startRev = rev2 < rev1 ? rev2 : rev1;
-			endRev = startRev == rev2 ? rev1 : rev2;
-		} else if (rev1 == TIP && rev2 != TIP) {
-			startRev = rev2;
-			endRev = rev1;
-		} else {
-			startRev = rev1;
-			endRev = rev2;
-		}
-		return this;
-	}
-	
-	/**
-	 * Visit history of a given file only.
-	 * @param file path relative to repository root. Pass <code>null</code> to reset.
-	 * @param followCopyRename true to report changesets of the original file(-s), if copy/rename ever occured to the file. 
-	 */
-	public HgLogCommand file(Path file, boolean followCopyRename) {
-		// multiple? Bad idea, would need to include extra method into Handler to tell start of next file
-		this.file = file;
-		followHistory = followCopyRename;
-		return this;
-	}
-	
-	/**
-	 * Handy analog of {@link #file(Path, boolean)} when clients' paths come from filesystem and need conversion to repository's 
-	 */
-	public HgLogCommand file(String file, boolean followCopyRename) {
-		return file(Path.create(repo.getToRepoPathHelper().rewrite(file)), followCopyRename);
-	}
-
-	/**
-	 * Similar to {@link #execute(org.tmatesoft.hg.repo.RawChangeset.Inspector)}, collects and return result as a list.
-	 */
-	public List<HgChangeset> execute() throws HgException {
-		CollectHandler collector = new CollectHandler();
-		execute(collector);
-		return collector.getChanges();
-	}
-
-	/**
-	 * 
-	 * @param handler callback to process changesets.
-	 * @throws IllegalArgumentException when inspector argument is null
-	 * @throws ConcurrentModificationException if this log command instance is already running
-	 */
-	public void execute(HgChangesetHandler handler) throws HgException {
-		if (handler == null) {
-			throw new IllegalArgumentException();
-		}
-		if (csetTransform != null) {
-			throw new ConcurrentModificationException();
-		}
-		try {
-			count = 0;
-			HgChangelog.ParentWalker pw = parentHelper; // leave it uninitialized unless we iterate whole repo
-			if (file == null) {
-				pw = getParentHelper();
-			}
-			// ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above 
-			// may utilize it as well. CommandContext? How about StatusCollector there as well?
-			csetTransform = new ChangesetTransformer(repo, handler, pw);
-			if (file == null) {
-				repo.getChangelog().range(startRev, endRev, this);
-			} else {
-				HgDataFile fileNode = repo.getFileNode(file);
-				fileNode.history(startRev, endRev, this);
-				if (fileNode.isCopy()) {
-					// even if we do not follow history, report file rename
-					do {
-						if (handler instanceof FileHistoryHandler) {
-							FileRevision src = new FileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName());
-							FileRevision dst = new FileRevision(repo, fileNode.getRevision(0), fileNode.getPath());
-							((FileHistoryHandler) handler).copy(src, dst);
-						}
-						if (limit > 0 && count >= limit) {
-							// if limit reach, follow is useless.
-							break;
-						}
-						if (followHistory) {
-							fileNode = repo.getFileNode(fileNode.getCopySourceName());
-							fileNode.history(this);
-						}
-					} while (followHistory && fileNode.isCopy());
-				}
-			}
-		} finally {
-			csetTransform = null;
-		}
-	}
-
-	//
-	
-	public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
-		if (limit > 0 && count >= limit) {
-			return;
-		}
-		if (branches != null && !branches.contains(cset.branch())) {
-			return;
-		}
-		if (users != null) {
-			String csetUser = cset.user().toLowerCase();
-			boolean found = false;
-			for (String u : users) {
-				if (csetUser.indexOf(u) != -1) {
-					found = true;
-					break;
-				}
-			}
-			if (!found) {
-				return;
-			}
-		}
-		if (date != null) {
-			// FIXME
-		}
-		count++;
-		csetTransform.next(revisionNumber, nodeid, cset);
-	}
-	
-	private HgChangelog.ParentWalker getParentHelper() {
-		if (parentHelper == null) {
-			parentHelper = repo.getChangelog().new ParentWalker();
-			parentHelper.init();
-		}
-		return parentHelper;
-	}
-
-
-	/**
-	 * @deprecated Use {@link HgChangesetHandler} instead. This interface is left temporarily for compatibility.
-	 */
-	@Deprecated()
-	public interface Handler extends HgChangesetHandler {
-	}
-	
-	/**
-	 * When {@link HgLogCommand} is executed against file, handler passed to {@link HgLogCommand#execute(HgChangesetHandler)} may optionally
-	 * implement this interface to get information about file renames. Method {@link #copy(FileRevision, FileRevision)} would
-	 * get invoked prior any changeset of the original file (if file history being followed) is reported via {@link #next(HgChangeset)}.
-	 * 
-	 * For {@link HgLogCommand#file(Path, boolean)} with renamed file path and follow argument set to false, 
-	 * {@link #copy(FileRevision, FileRevision)} would be invoked for the first copy/rename in the history of the file, but not 
-	 * followed by any changesets. 
-	 *
-	 * @author Artem Tikhomirov
-	 * @author TMate Software Ltd.
-	 */
-	public interface FileHistoryHandler extends HgChangesetHandler {
-		// XXX perhaps, should distinguish copy from rename? And what about merged revisions and following them?
-		void copy(FileRevision from, FileRevision to);
-	}
-	
-	public static class CollectHandler implements HgChangesetHandler {
-		private final List<HgChangeset> result = new LinkedList<HgChangeset>();
-
-		public List<HgChangeset> getChanges() {
-			return Collections.unmodifiableList(result);
-		}
-
-		public void next(HgChangeset changeset) {
-			result.add(changeset.clone());
-		}
-	}
-
-	public static final class FileRevision {
-		private final HgRepository repo;
-		private final Nodeid revision;
-		private final Path path;
-		
-		/*package-local*/FileRevision(HgRepository hgRepo, Nodeid rev, Path p) {
-			if (hgRepo == null || rev == null || p == null) {
-				// since it's package local, it is our code to blame for non validated arguments
-				throw new HgBadStateException();
-			}
-			repo = hgRepo;
-			revision = rev;
-			path = p;
-		}
-		
-		public Path getPath() {
-			return path;
-		}
-		public Nodeid getRevision() {
-			return revision;
-		}
-		public void putContentTo(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
-			HgDataFile fn = repo.getFileNode(path);
-			int localRevision = fn.getLocalRevision(revision);
-			fn.contentWithFilters(localRevision, sink);
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgManifestCommand.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-/*
- * 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 static org.tmatesoft.hg.repo.HgRepository.*;
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.util.ConcurrentModificationException;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
-import org.tmatesoft.hg.repo.HgManifest;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.PathPool;
-import org.tmatesoft.hg.util.PathRewrite;
-
-
-/**
- * Gives access to list of files in each revision (Mercurial manifest information), 'hg manifest' counterpart.
- *  
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgManifestCommand {
-	
-	private final HgRepository repo;
-	private Path.Matcher matcher;
-	private int startRev = 0, endRev = TIP;
-	private Handler visitor;
-	private boolean needDirs = false;
-	
-	private final Mediator mediator = new Mediator();
-
-	public HgManifestCommand(HgRepository hgRepo) {
-		repo = hgRepo;
-	}
-
-	/**
-	 * Parameterize command to visit revisions <code>[rev1..rev2]</code>.
-	 * @param rev1 - local revision number to start from. Non-negative. May be {@link HgRepository#TIP} (rev2 argument shall be {@link HgRepository#TIP} as well, then) 
-	 * @param rev2 - local revision number to end with, inclusive. Non-negative, greater or equal to rev1. May be {@link HgRepository#TIP}.
-	 * @return <code>this</code> for convenience.
-	 * @throws IllegalArgumentException if revision arguments are incorrect (see above).
-	 */
-	public HgManifestCommand range(int rev1, int rev2) {
-		// XXX if manifest range is different from that of changelog, need conversion utils (external?)
-		boolean badArgs = rev1 == BAD_REVISION || rev2 == BAD_REVISION || rev1 == WORKING_COPY || rev2 == WORKING_COPY;
-		badArgs |= rev2 != TIP && rev2 < rev1; // range(3, 1);
-		badArgs |= rev1 == TIP && rev2 != TIP; // range(TIP, 2), although this may be legitimate when TIP points to 2
-		if (badArgs) {
-			throw new IllegalArgumentException(String.format("Bad range: [%d, %d]", rev1, rev2));
-		}
-		startRev = rev1;
-		endRev = rev2;
-		return this;
-	}
-	
-	public HgManifestCommand revision(int rev) {
-		startRev = endRev = rev;
-		return this;
-	}
-	
-	public HgManifestCommand dirs(boolean include) {
-		// XXX whether directories with directories only are include or not
-		// now lists only directories with files
-		needDirs = include;
-		return this;
-	}
-	
-	/**
-	 * Limit manifest walk to a subset of files. 
-	 * @param pathMatcher - filter, pass <code>null</code> to clear.
-	 * @return <code>this</code> instance for convenience
-	 */
-	public HgManifestCommand match(Path.Matcher pathMatcher) {
-		matcher = pathMatcher;
-		return this;
-	}
-	
-	/**
-	 * Runs the command.
-	 * @param handler - callback to get the outcome
-	 * @throws IllegalArgumentException if handler is <code>null</code>
-	 * @throws ConcurrentModificationException if this command is already in use (running)
-	 */
-	public void execute(Handler handler) {
-		if (handler == null) {
-			throw new IllegalArgumentException();
-		}
-		if (visitor != null) {
-			throw new ConcurrentModificationException();
-		}
-		try {
-			visitor = handler;
-			mediator.start();
-			repo.getManifest().walk(startRev, endRev, mediator);
-		} finally {
-			mediator.done();
-			visitor = null;
-		}
-	}
-
-	/**
-	 * Callback to walk file/directory tree of a revision
-	 */
-	public interface Handler {
-		void begin(Nodeid manifestRevision);
-		void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs
-		void file(FileRevision fileRevision); // XXX allow to check p is invalid (df.exists())
-		void end(Nodeid manifestRevision);
-	}
-
-	// I'd rather let HgManifestCommand implement HgManifest.Inspector directly, but this pollutes API alot
-	private class Mediator implements HgManifest.Inspector {
-		// file names are likely to repeat in each revision, hence caching of Paths.
-		// However, once HgManifest.Inspector switches to Path objects, perhaps global Path pool
-		// might be more effective?
-		private PathPool pathPool;
-		private List<FileRevision> manifestContent;
-		private Nodeid manifestNodeid;
-		
-		public void start() {
-			// Manifest keeps normalized paths
-			pathPool = new PathPool(new PathRewrite.Empty());
-		}
-		
-		public void done() {
-			manifestContent = null;
-			pathPool = null;
-		}
-	
-		public boolean begin(int revision, Nodeid nid) {
-			if (needDirs && manifestContent == null) {
-				manifestContent = new LinkedList<FileRevision>();
-			}
-			visitor.begin(manifestNodeid = nid);
-			return true;
-		}
-		public boolean end(int revision) {
-			if (needDirs) {
-				LinkedHashMap<Path, LinkedList<FileRevision>> breakDown = new LinkedHashMap<Path, LinkedList<FileRevision>>();
-				for (FileRevision fr : manifestContent) {
-					Path filePath = fr.getPath();
-					Path dirPath = pathPool.parent(filePath);
-					LinkedList<FileRevision> revs = breakDown.get(dirPath);
-					if (revs == null) {
-						revs = new LinkedList<FileRevision>();
-						breakDown.put(dirPath, revs);
-					}
-					revs.addLast(fr);
-				}
-				for (Path dir : breakDown.keySet()) {
-					visitor.dir(dir);
-					for (FileRevision fr : breakDown.get(dir)) {
-						visitor.file(fr);
-					}
-				}
-				manifestContent.clear();
-			}
-			visitor.end(manifestNodeid);
-			manifestNodeid = null;
-			return true;
-		}
-		public boolean next(Nodeid nid, String fname, String flags) {
-			Path p = pathPool.path(fname);
-			if (matcher != null && !matcher.accept(p)) {
-				return true;
-			}
-			FileRevision fr = new FileRevision(repo, nid, p);
-			if (needDirs) {
-				manifestContent.add(fr);
-			} else {
-				visitor.file(fr);
-			}
-			return true;
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-/*
- * 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.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.internal.RepositoryComparator;
-import org.tmatesoft.hg.repo.HgChangelog;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.CancelledException;
-
-/**
- * Command to find out changes made in a local repository and missing at remote repository. 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgOutgoingCommand {
-
-	private final HgRepository localRepo;
-	private HgRemoteRepository remoteRepo;
-	@SuppressWarnings("unused")
-	private boolean includeSubrepo;
-	private RepositoryComparator comparator;
-	private HgChangelog.ParentWalker parentHelper;
-	private Set<String> branches;
-
-	public HgOutgoingCommand(HgRepository hgRepo) {
-		localRepo = hgRepo;
-	}
-
-	/**
-	 * @param hgRemote remoteRepository to compare against
-	 * @return <code>this</code> for convenience
-	 */
-	public HgOutgoingCommand against(HgRemoteRepository hgRemote) {
-		remoteRepo = hgRemote;
-		comparator = null;
-		return this;
-	}
-
-	/**
-	 * Select specific branch to pull. 
-	 * Multiple branch specification possible (changeset from any of these would be included in result).
-	 * Note, {@link #executeLite(Object)} does not respect this setting.
-	 * 
-	 * @param branch - branch name, case-sensitive, non-null.
-	 * @return <code>this</code> for convenience
-	 * @throws IllegalArgumentException when branch argument is null
-	 */
-	public HgOutgoingCommand branch(String branch) {
-		if (branch == null) {
-			throw new IllegalArgumentException();
-		}
-		if (branches == null) {
-			branches = new TreeSet<String>();
-		}
-		branches.add(branch);
-		return this;
-	}
-	
-	/**
-	 * PLACEHOLDER, NOT IMPLEMENTED YET.
-	 * 
-	 * @return <code>this</code> for convenience
-	 */
-	public HgOutgoingCommand subrepo(boolean include) {
-		includeSubrepo = include;
-		throw HgRepository.notImplemented();
-	}
-
-	/**
-	 * Lightweight check for outgoing changes. 
-	 * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account.
-	 * 
-	 * @param context
-	 * @return list on local nodes known to be missing at remote server 
-	 */
-	public List<Nodeid> executeLite(Object context) throws HgException, CancelledException {
-		return getComparator(context).getLocalOnlyRevisions();
-	}
-
-	/**
-	 * Complete information about outgoing changes
-	 * 
-	 * @param handler delegate to process changes
-	 */
-	public void executeFull(final HgChangesetHandler handler) throws HgException, CancelledException {
-		if (handler == null) {
-			throw new IllegalArgumentException("Delegate can't be null");
-		}
-		ChangesetTransformer inspector = new ChangesetTransformer(localRepo, handler, getParentHelper());
-		inspector.limitBranches(branches);
-		getComparator(handler).visitLocalOnlyRevisions(inspector);
-	}
-
-	private RepositoryComparator getComparator(Object context) throws HgException, CancelledException {
-		if (remoteRepo == null) {
-			throw new IllegalArgumentException("Shall specify remote repository to compare against");
-		}
-		if (comparator == null) {
-			comparator = new RepositoryComparator(getParentHelper(), remoteRepo);
-			comparator.compare(context);
-		}
-		return comparator;
-	}
-	
-	private HgChangelog.ParentWalker getParentHelper() {
-		if (parentHelper == null) {
-			parentHelper = localRepo.getChangelog().new ParentWalker();
-			parentHelper.init();
-		}
-		return parentHelper;
-	}
-
-}
--- a/src/org/tmatesoft/hg/core/HgRepoFacade.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/*
- * 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 org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.HgLookup;
-
-/**
- * Starting point for the library.
- * <p>Sample use:
- * <pre>
- *  HgRepoFacade f = new HgRepoFacade();
- *  f.initFrom(System.getenv("whatever.repo.location"));
- *  HgStatusCommand statusCmd = f.createStatusCommand();
- *  HgStatusCommand.Handler handler = ...;
- *  statusCmd.execute(handler);
- * </pre>
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgRepoFacade {
-	private HgRepository repo;
-
-	public HgRepoFacade() {
-	}
-	
-	/**
-	 * @param hgRepo
-	 * @return true on successful initialization
-	 * @throws IllegalArgumentException when argument is null 
-	 */
-	public boolean init(HgRepository hgRepo) {
-		if (hgRepo == null) {
-			throw new IllegalArgumentException();
-		}
-		repo = hgRepo;
-		return !repo.isInvalid();
-	}
-
-	/**
-	 * 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
-	 */
-	public boolean init() throws HgException {
-		repo = new HgLookup().detectFromWorkingDir();
-		return repo != null && !repo.isInvalid();
-	}
-	
-	/**
-	 * Looks up Mercurial repository starting from specified location and up to filesystem root.
-	 * 
-	 * @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 IllegalArgumentException if argument is <code>null</code>
-	 */
-	public boolean initFrom(File repoLocation) throws HgException {
-		if (repoLocation == null) {
-			throw new IllegalArgumentException();
-		}
-		repo = new HgLookup().detect(repoLocation);
-		return repo != null && !repo.isInvalid();
-	}
-	
-	public HgRepository getRepository() {
-		if (repo == null) {
-			throw new IllegalStateException("Call any of #init*() methods first first");
-		}
-		return repo;
-	}
-
-	public HgLogCommand createLogCommand() {
-		return new HgLogCommand(repo/*, getCommandContext()*/);
-	}
-
-	public HgStatusCommand createStatusCommand() {
-		return new HgStatusCommand(repo/*, getCommandContext()*/);
-	}
-
-	public HgCatCommand createCatCommand() {
-		return new HgCatCommand(repo);
-	}
-
-	public HgManifestCommand createManifestCommand() {
-		return new HgManifestCommand(repo);
-	}
-
-	public HgOutgoingCommand createOutgoingCommand() {
-		return new HgOutgoingCommand(repo);
-	}
-
-	public HgIncomingCommand createIncomingCommand() {
-		return new HgIncomingCommand(repo);
-	}
-}
--- a/src/org/tmatesoft/hg/core/HgStatus.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-/*
- * 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.util.Date;
-
-import org.tmatesoft.hg.internal.ChangelogHelper;
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.util.Path;
-
-/**
- * Repository file status and extra handy information.
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgStatus {
-
-	public enum Kind {
-		Modified, Added, Removed, Missing, Unknown, Clean, Ignored
-		// I'd refrain from changing order of these constants, just in case someone (erroneously, of course ;), uses .ordinal()
-	};
-
-	private final HgStatus.Kind kind;
-	private final Path path;
-	private final Path origin;
-	private final ChangelogHelper logHelper;
-		
-	HgStatus(HgStatus.Kind kind, Path path, ChangelogHelper changelogHelper) {
-		this(kind, path, null, changelogHelper);
-	}
-
-	HgStatus(HgStatus.Kind kind, Path path, Path copyOrigin, ChangelogHelper changelogHelper) {
-		this.kind = kind;
-		this.path  = path;
-		origin = copyOrigin;
-		logHelper = changelogHelper;
-	}
-
-	public HgStatus.Kind getKind() {
-		return kind;
-	}
-
-	public Path getPath() {
-		return path;
-	}
-
-	public Path getOriginalPath() {
-		return origin;
-	}
-
-	public boolean isCopy() {
-		return origin != null;
-	}
-
-	/**
-	 * @return <code>null</code> if author for the change can't be deduced (e.g. for clean files it's senseless)
-	 */
-	public String getModificationAuthor() {
-		RawChangeset cset = logHelper.findLatestChangeWith(path);
-		if (cset == null) {
-			if (kind == Kind.Modified || kind == Kind.Added || kind == Kind.Removed /*&& RightBoundary is TIP*/) {
-				// perhaps, also for Kind.Missing?
-				return logHelper.getNextCommitUsername();
-			}
-		} else {
-			return cset.user();
-		}
-		return null;
-	}
-
-	public Date getModificationDate() {
-		RawChangeset cset = logHelper.findLatestChangeWith(path);
-		if (cset == null) {
-			// FIXME check dirstate and/or local file for tstamp
-			return new Date(); // what's correct 
-		} else {
-			return cset.date();
-		}
-	}
-}
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/HgStatusCommand.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,285 +0,0 @@
-/*
- * 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 static org.tmatesoft.hg.core.HgStatus.Kind.*;
-import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision;
-import static org.tmatesoft.hg.repo.HgRepository.*;
-
-import java.util.ConcurrentModificationException;
-
-import org.tmatesoft.hg.internal.ChangelogHelper;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.HgStatusCollector;
-import org.tmatesoft.hg.repo.HgStatusInspector;
-import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
-import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.Path.Matcher;
-
-/**
- * Command to obtain file status information, 'hg status' counterpart. 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgStatusCommand {
-	private final HgRepository repo;
-
-	private int startRevision = TIP;
-	private int endRevision = WORKING_COPY; 
-	
-	private final Mediator mediator = new Mediator();
-
-	public HgStatusCommand(HgRepository hgRepo) { 
-		repo = hgRepo;
-		defaults();
-	}
-
-	public HgStatusCommand defaults() {
-		final Mediator m = mediator;
-		m.needModified = m.needAdded = m.needRemoved = m.needUnknown = m.needMissing = true;
-		m.needCopies = m.needClean = m.needIgnored = false;
-		return this;
-	}
-	public HgStatusCommand all() {
-		final Mediator m = mediator;
-		m.needModified = m.needAdded = m.needRemoved = m.needUnknown = m.needMissing = true;
-		m.needCopies = m.needClean = m.needIgnored = true;
-		return this;
-	}
-	
-
-	public HgStatusCommand modified(boolean include) {
-		mediator.needModified = include;
-		return this;
-	}
-	public HgStatusCommand added(boolean include) {
-		mediator.needAdded = include;
-		return this;
-	}
-	public HgStatusCommand removed(boolean include) {
-		mediator.needRemoved = include;
-		return this;
-	}
-	public HgStatusCommand deleted(boolean include) {
-		mediator.needMissing = include;
-		return this;
-	}
-	public HgStatusCommand unknown(boolean include) {
-		mediator.needUnknown = include;
-		return this;
-	}
-	public HgStatusCommand clean(boolean include) {
-		mediator.needClean = include;
-		return this;
-	}
-	public HgStatusCommand ignored(boolean include) {
-		mediator.needIgnored = include;
-		return this;
-	}
-	
-	/**
-	 * If set, either base:revision or base:workingdir
-	 * to unset, pass {@link HgRepository#TIP} or {@link HgRepository#BAD_REVISION}
-	 * @param revision - local revision number to base status from
-	 * @return <code>this</code> for convenience
-	 * @throws IllegalArgumentException when revision is negative or {@link HgRepository#WORKING_COPY} 
-	 */
-	public HgStatusCommand base(int revision) {
-		if (revision == WORKING_COPY || wrongLocalRevision(revision)) {
-			throw new IllegalArgumentException(String.valueOf(revision));
-		}
-		if (revision == BAD_REVISION) {
-			revision = TIP;
-		}
-		startRevision = revision;
-		return this;
-	}
-	
-	/**
-	 * Revision without base == --change
-	 * Pass {@link HgRepository#WORKING_COPY} or {@link HgRepository#BAD_REVISION} to reset
-	 * @param revision - non-negative local revision number, or any of {@link HgRepository#BAD_REVISION}, {@link HgRepository#WORKING_COPY} or {@link HgRepository#TIP}  
-	 * @return <code>this</code> for convenience
-	 * @throws IllegalArgumentException if local revision number doesn't specify legitimate revision. 
-	 */
-	public HgStatusCommand revision(int revision) {
-		if (revision == BAD_REVISION) {
-			revision = WORKING_COPY;
-		}
-		if (wrongLocalRevision(revision)) {
-			throw new IllegalArgumentException(String.valueOf(revision));
-		}
-		endRevision = revision;
-		return this;
-	}
-	
-	/**
-	 * Shorthand for {@link #base(int) cmd.base(BAD_REVISION)}{@link #change(int) .revision(revision)}
-	 *  
-	 * @param revision compare given revision against its parent
-	 * @return <code>this</code> for convenience
-	 */
-	public HgStatusCommand change(int revision) {
-		base(BAD_REVISION);
-		return revision(revision);
-	}
-	
-	/**
-	 * Limit status operation to certain sub-tree.
-	 * 
-	 * @param pathMatcher - matcher to use,  pass <code>null/<code> to reset
-	 * @return <code>this</code> for convenience
-	 */
-	public HgStatusCommand match(Path.Matcher pathMatcher) {
-		mediator.matcher = pathMatcher;
-		return this;
-	}
-
-	public HgStatusCommand subrepo(boolean visit) {
-		throw HgRepository.notImplemented();
-	}
-
-	/**
-	 * Perform status operation according to parameters set.
-	 *  
-	 * @param handler callback to get status information
-	 * @throws IllegalArgumentException if handler is <code>null</code>
-	 * @throws ConcurrentModificationException if this command already runs (i.e. being used from another thread)
-	 */
-	public void execute(Handler statusHandler) {
-		if (statusHandler == null) {
-			throw new IllegalArgumentException();
-		}
-		if (mediator.busy()) {
-			throw new ConcurrentModificationException();
-		}
-		HgStatusCollector sc = new HgStatusCollector(repo); // TODO from CommandContext
-//		PathPool pathHelper = new PathPool(repo.getPathHelper()); // TODO from CommandContext
-		try {
-			// XXX if I need a rough estimation (for ProgressMonitor) of number of work units,
-			// I may use number of files in either rev1 or rev2 manifest edition
-			mediator.start(statusHandler, new ChangelogHelper(repo, startRevision));
-			if (endRevision == WORKING_COPY) {
-				HgWorkingCopyStatusCollector wcsc = new HgWorkingCopyStatusCollector(repo);
-				wcsc.setBaseRevisionCollector(sc);
-				wcsc.walk(startRevision, mediator);
-			} else {
-				if (startRevision == TIP) {
-					sc.change(endRevision, mediator);
-				} else {
-					sc.walk(startRevision, endRevision, mediator);
-				}
-			}
-		} finally {
-			mediator.done();
-		}
-	}
-
-	public interface Handler {
-		void handleStatus(HgStatus s);
-	}
-
-	private class Mediator implements HgStatusInspector {
-		boolean needModified;
-		boolean needAdded;
-		boolean needRemoved;
-		boolean needUnknown;
-		boolean needMissing;
-		boolean needClean;
-		boolean needIgnored;
-		boolean needCopies;
-		Matcher matcher;
-		Handler handler;
-		private ChangelogHelper logHelper;
-
-		Mediator() {
-		}
-		
-		public void start(Handler h, ChangelogHelper changelogHelper) {
-			handler = h;
-			logHelper = changelogHelper;
-		}
-
-		public void done() {
-			handler = null;
-			logHelper = null;
-		}
-		
-		public boolean busy() {
-			return handler != null;
-		}
-
-		public void modified(Path fname) {
-			if (needModified) {
-				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Modified, fname, logHelper));
-				}
-			}
-		}
-		public void added(Path fname) {
-			if (needAdded) {
-				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Added, fname, logHelper));
-				}
-			}
-		}
-		public void removed(Path fname) {
-			if (needRemoved) {
-				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Removed, fname, logHelper));
-				}
-			}
-		}
-		public void copied(Path fnameOrigin, Path fnameAdded) {
-			if (needCopies) {
-				if (matcher == null || matcher.accept(fnameAdded)) {
-					// FIXME in fact, merged files may report 'copied from' as well, correct status kind thus may differ from Added
-					handler.handleStatus(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
-				}
-			}
-		}
-		public void missing(Path fname) {
-			if (needMissing) {
-				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Missing, fname, logHelper));
-				}
-			}
-		}
-		public void unknown(Path fname) {
-			if (needUnknown) {
-				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Unknown, fname, logHelper));
-				}
-			}
-		}
-		public void clean(Path fname) {
-			if (needClean) {
-				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Clean, fname, logHelper));
-				}
-			}
-		}
-		public void ignored(Path fname) {
-			if (needIgnored) {
-				if (matcher == null || matcher.accept(fname)) {
-					handler.handleStatus(new HgStatus(Ignored, fname, logHelper));
-				}
-			}
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/core/Nodeid.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +0,0 @@
-/*
- * Copyright (c) 2010-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 static org.tmatesoft.hg.internal.DigestHelper.toHexString;
-
-import java.util.Arrays;
-
-
-
-/**
- * A 20-bytes (40 characters) long hash value to identify a revision.
- * @see http://mercurial.selenic.com/wiki/Nodeid
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- *
- */
-public final class Nodeid implements Comparable<Nodeid> {
-	
-	/**
-	 * <b>nullid</b>, empty root revision.
-	 */
-	public static final Nodeid NULL = new Nodeid(new byte[20], false);
-
-	private final byte[] binaryData; 
-
-	/**
-	 * @param binaryRepresentation - array of exactly 20 bytes
-	 * @param shallClone - true if array is subject to future modification and shall be copied, not referenced
-	 * @throws IllegalArgumentException if supplied binary representation doesn't correspond to 20 bytes of sha1 digest 
-	 */
-	public Nodeid(byte[] binaryRepresentation, boolean shallClone) {
-		// 5 int fields => 32 bytes
-		// byte[20] => 48 bytes (16 bytes is Nodeid with one field, 32 bytes for byte[20] 
-		if (binaryRepresentation == null || binaryRepresentation.length != 20) {
-			throw new IllegalArgumentException();
-		}
-		/*
-		 * byte[].clone() is not reflected when ran with -agentlib:hprof=heap=sites
-		 * thus not to get puzzled why there are N Nodeids and much less byte[] instances,
-		 * may use following code to see N byte[] as well.
-		 *
-		if (shallClone) {
-			binaryData = new byte[20];
-			System.arraycopy(binaryRepresentation, 0, binaryData, 0, 20);
-		} else {
-			binaryData = binaryRepresentation;
-		}
-		*/
-		binaryData = shallClone ? binaryRepresentation.clone() : binaryRepresentation;
-	}
-
-	@Override
-	public int hashCode() {
-		// digest (part thereof) seems to be nice candidate for the hashCode
-		byte[] b = binaryData;
-		return b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF);
-	}
-	
-	@Override
-	public boolean equals(Object o) {
-		if (o instanceof Nodeid) {
-			return this == o || equalsTo(((Nodeid) o).binaryData);
-		}
-		return false;
-	}
-
-	public boolean equalsTo(byte[] buf) {
-		return Arrays.equals(this.binaryData, buf);
-	}
-	
-	public int compareTo(Nodeid o) {
-		if (this == o) {
-			return 0;
-		}
-		for (int i = 0; i < 20; i++) {
-			if (binaryData[i] != o.binaryData[i]) {
-				return binaryData[i] < o.binaryData[i] ? -1 : 1;
-			}
-		}
-		return 0;
-	}
-	
-	@Override
-	public String toString() {
-		// XXX may want to output just single 0 for the NULL id?
-		return toHexString(binaryData, 0, binaryData.length);
-	}
-
-	public String shortNotation() {
-		return toHexString(binaryData, 0, 6);
-	}
-	
-	public boolean isNull() {
-		if (this == NULL) {
-			return true;
-		}
-		for (int i = 0; i < 20; i++) {
-			if (this.binaryData[i] != 0) {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	// copy 
-	public byte[] toByteArray() {
-		return binaryData.clone();
-	}
-
-	/**
-	 * Factory for {@link Nodeid Nodeids}.
-	 * Primary difference with cons is handling of NULL id (this method returns constant) and control over array 
-	 * duplication - this method always makes a copy of an array passed
-	 * @param binaryRepresentation - byte array of a length at least offset + 20
-	 * @param offset - index in the array to start from
-	 * @throws IllegalArgumentException when arguments don't select 20 bytes
-	 */
-	public static Nodeid fromBinary(byte[] binaryRepresentation, int offset) {
-		if (binaryRepresentation == null || binaryRepresentation.length - offset < 20) {
-			throw new IllegalArgumentException();
-		}
-		int i = 0;
-		while (i < 20 && binaryRepresentation[offset+i] == 0) i++;
-		if (i == 20) {
-			return NULL;
-		}
-		if (offset == 0 && binaryRepresentation.length == 20) {
-			return new Nodeid(binaryRepresentation, true);
-		}
-		byte[] b = new byte[20]; // create new instance if no other reasonable guesses possible
-		System.arraycopy(binaryRepresentation, offset, b, 0, 20);
-		return new Nodeid(b, false);
-	}
-
-	/**
-	 * Parse encoded representation.
-	 * 
-	 * @param asciiRepresentation - encoded form of the Nodeid.
-	 * @return object representation
-	 * @throws IllegalArgumentException when argument doesn't match encoded form of 20-bytes sha1 digest. 
-	 */
-	public static Nodeid fromAscii(String asciiRepresentation) {
-		if (asciiRepresentation.length() != 40) {
-			throw new IllegalArgumentException();
-		}
-		// XXX is better impl for String possible?
-		return fromAscii(asciiRepresentation.getBytes(), 0, 40);
-	}
-
-	/**
-	 * Parse encoded representation. Similar to {@link #fromAscii(String)}.
-	 */
-	public static Nodeid fromAscii(byte[] asciiRepresentation, int offset, int length) {
-		if (length != 40) {
-			throw new IllegalArgumentException();
-		}
-		byte[] data = new byte[20];
-		boolean zeroBytes = true;
-		for (int i = 0, j = offset; i < data.length; i++) {
-			int hiNibble = Character.digit(asciiRepresentation[j++], 16);
-			int lowNibble = Character.digit(asciiRepresentation[j++], 16);
-			byte b = (byte) (((hiNibble << 4) | lowNibble) & 0xFF);
-			data[i] = b;
-			zeroBytes = zeroBytes && b == 0;
-		}
-		if (zeroBytes) {
-			return NULL;
-		}
-		return new Nodeid(data, false);
-	}
-}
--- a/src/org/tmatesoft/hg/core/package.html	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-<html>
-<boody>
-Hi-level API
-</bidy>
-</html>
\ No newline at end of file
--- a/src/org/tmatesoft/hg/internal/ByteArrayChannel.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/*
- * 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.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.tmatesoft.hg.util.ByteChannel;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class ByteArrayChannel implements ByteChannel {
-	private final List<ByteBuffer> buffers;
-	private ByteBuffer target;
-	private byte[] result;
-	
-	public ByteArrayChannel() {
-		this(-1);
-	}
-	
-	public ByteArrayChannel(int size) {
-		if (size == -1) {
-			buffers = new LinkedList<ByteBuffer>();
-		} else {
-			if (size < 0) {
-				throw new IllegalArgumentException(String.valueOf(size));
-			}
-			buffers = null;
-			target = ByteBuffer.allocate(size);
-		}
-	}
-
-	// TODO document what happens on write after toArray() in each case
-	public int write(ByteBuffer buffer) {
-		int rv = buffer.remaining();
-		if (buffers == null) {
-			target.put(buffer);
-		} else {
-			ByteBuffer copy = ByteBuffer.allocate(rv);
-			copy.put(buffer);
-			buffers.add(copy);
-		}
-		return rv;
-	}
-
-	public byte[] toArray() {
-		if (result != null) {
-			return result;
-		}
-		if (buffers == null) {
-			assert target.hasArray();
-			// int total = target.position();
-			// System.arraycopy(target.array(), new byte[total]);
-			// I don't want to duplicate byte[] for now
-			// although correct way of doing things is to make a copy and discard target
-			return target.array();
-		} else {
-			int total = 0;
-			for (ByteBuffer bb : buffers) {
-				bb.flip();
-				total += bb.limit();
-			}
-			result = new byte[total];
-			int off = 0;
-			for (ByteBuffer bb : buffers) {
-				bb.get(result, off, bb.limit());
-				off += bb.limit();
-			}
-			buffers.clear();
-			return result;
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/internal/ByteArrayDataAccess.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * 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.IOException;
-
-
-/**
- *   
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class ByteArrayDataAccess extends DataAccess {
-
-	private final byte[] data;
-	private final int offset;
-	private final int length;
-	private int pos;
-
-	public ByteArrayDataAccess(byte[] data) {
-		this(data, 0, data.length);
-	}
-
-	public ByteArrayDataAccess(byte[] data, int offset, int length) {
-		this.data = data;
-		this.offset = offset;
-		this.length = length;
-		pos = 0;
-	}
-	
-	@Override
-	public byte readByte() throws IOException {
-		if (pos >= length) {
-			throw new IOException();
-		}
-		return data[offset + pos++];
-	}
-	@Override
-	public void readBytes(byte[] buf, int off, int len) throws IOException {
-		if (len > (this.length - pos)) {
-			throw new IOException();
-		}
-		System.arraycopy(data, pos, buf, off, len);
-		pos += len;
-	}
-
-	@Override
-	public ByteArrayDataAccess reset() {
-		pos = 0;
-		return this;
-	}
-	@Override
-	public int length() {
-		return length;
-	}
-	@Override
-	public void seek(int offset) {
-		pos = (int) offset;
-	}
-	@Override
-	public void skip(int bytes) throws IOException {
-		seek(pos + bytes);
-	}
-	@Override
-	public boolean isEmpty() {
-		return pos >= length;
-	}
-	
-	//
-	
-	// when byte[] needed from DA, we may save few cycles and some memory giving this (otherwise unsafe) access to underlying data
-	@Override
-	public byte[] byteArray() {
-		return data;
-	}
-}
--- a/src/org/tmatesoft/hg/internal/ChangelogHelper.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/*
- * 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.util.TreeMap;
-
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgInternals;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.Path;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class ChangelogHelper {
-	private final int leftBoundary;
-	private final HgRepository repo;
-	private final TreeMap<Integer, RawChangeset> cache = new TreeMap<Integer, RawChangeset>();
-	private String nextCommitAuthor;
-
-	/**
-	 * @param hgRepo
-	 * @param leftBoundaryRevision walker never visits revisions with local numbers less than specified,
-	 * IOW only revisions [leftBoundaryRevision..TIP] are considered.
-	 */
-	public ChangelogHelper(HgRepository hgRepo, int leftBoundaryRevision) {
-		repo = hgRepo;
-		leftBoundary = leftBoundaryRevision;
-	}
-	
-	/**
-	 * @return the repo
-	 */
-	public HgRepository getRepo() {
-		return repo;
-	}
-
-	/**
-	 * Walks changelog in reverse order
-	 * @param file
-	 * @return changeset where specified file is mentioned among affected files, or 
-	 * <code>null</code> if none found up to leftBoundary 
-	 */
-	public RawChangeset findLatestChangeWith(Path file) {
-		HgDataFile df = repo.getFileNode(file);
-		int changelogRev = df.getChangesetLocalRevision(HgRepository.TIP);
-		if (changelogRev >= leftBoundary) {
-			// the method is likely to be invoked for different files, 
-			// while changesets might be the same. Cache 'em not to read too much. 
-			RawChangeset cs = cache.get(changelogRev);
-			if (cs == null) {
-				cs = repo.getChangelog().range(changelogRev, changelogRev).get(0);
-				cache.put(changelogRev, cs);
-			}
-			return cs;
-		}
-		return null;
-	}
-
-	public String getNextCommitUsername() {
-		if (nextCommitAuthor == null) {
-			nextCommitAuthor = new HgInternals(repo).getNextCommitUsername();
-		}
-		return nextCommitAuthor;
-	}
-}
--- a/src/org/tmatesoft/hg/internal/ConfigFile.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,151 +0,0 @@
-/*
- * 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.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class ConfigFile {
-
-	private List<String> sections;
-	private List<Map<String,String>> content;
-
-	ConfigFile() {
-	}
-
-	public void addLocation(File path) {
-		read(path);
-	}
-	
-	public boolean hasSection(String sectionName) {
-		return sections == null ? false : sections.indexOf(sectionName) == -1;
-	}
-	
-	// XXX perhaps, should be moved to subclass HgRepoConfig, as it is not common operation for any config file
-	public boolean hasEnabledExtension(String extensionName) {
-		int x = sections != null ? sections.indexOf("extensions") : -1;
-		if (x == -1) {
-			return false;
-		}
-		String value = content.get(x).get(extensionName);
-		return value != null && !"!".equals(value);
-	}
-	
-	public List<String> getSectionNames() {
-		return sections == null ? Collections.<String>emptyList() : Collections.unmodifiableList(sections);
-	}
-
-	public Map<String,String> getSection(String sectionName) {
-		if (sections ==  null) {
-			return Collections.emptyMap();
-		}
-		int x = sections.indexOf(sectionName);
-		if (x == -1) {
-			return Collections.emptyMap();
-		}
-		return Collections.unmodifiableMap(content.get(x));
-	}
-
-	public boolean getBoolean(String sectionName, String key, boolean defaultValue) {
-		String value = getSection(sectionName).get(key);
-		if (value == null) {
-			return defaultValue;
-		}
-		for (String s : new String[] { "true", "yes", "on", "1" }) {
-			if (s.equalsIgnoreCase(value)) {
-				return true;
-			}
-		}
-		return false;
-	}
-	
-	public String getString(String sectionName, String key, String defaultValue) {
-		String value = getSection(sectionName).get(key);
-		return value == null ? defaultValue : value;
-	}
-
-	// TODO handle %include and %unset directives
-	// TODO "" and lists
-	private void read(File f) {
-		if (f == null || !f.canRead()) {
-			return;
-		}
-		if (sections == null) {
-			sections = new ArrayList<String>();
-			content = new ArrayList<Map<String,String>>();
-		}
-		try {
-			BufferedReader br = new BufferedReader(new FileReader(f));
-			String line;
-			String sectionName = "";
-			Map<String,String> section = new LinkedHashMap<String, String>();
-			while ((line = br.readLine()) != null) {
-				line = line.trim();
-				int x;
-				if ((x = line.indexOf('#')) != -1) {
-					// do not keep comments in memory, get new, shorter string
-					line = new String(line.substring(0, x).trim());
-				}
-				if (line.length() <= 2) { // a=b or [a] are at least of length 3
-					continue;
-				}
-				if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') {
-					sectionName = line.substring(1, line.length() - 1);
-					if (sections.indexOf(sectionName) == -1) {
-						sections.add(sectionName);
-						content.add(section = new LinkedHashMap<String, String>());
-					} else {
-						section = null; // drop cached value
-					}
-				} else if ((x = line.indexOf('=')) != -1) {
-					// share char[] of the original string
-					String key = line.substring(0, x).trim();
-					String value = line.substring(x+1).trim();
-					if (section == null) {
-						int i = sections.indexOf(sectionName);
-						assert i >= 0;
-						section = content.get(i);
-					}
-					if (sectionName.length() == 0) {
-						// add fake section only if there are any values 
-						sections.add(sectionName);
-						content.add(section);
-					}
-					section.put(key, value);
-				}
-			}
-			br.close();
-		} catch (IOException ex) {
-			ex.printStackTrace(); // XXX shall outer world care?
-		}
-		((ArrayList<?>) sections).trimToSize();
-		((ArrayList<?>) content).trimToSize();
-		assert sections.size() == content.size();
-	}
-}
--- a/src/org/tmatesoft/hg/internal/DataAccess.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/*
- * Copyright (c) 2010-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.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * relevant parts of DataInput, non-stream nature (seek operation), explicit check for end of data.
- * convenient skip (+/- bytes)
- * Primary goal - effective file read, so that clients don't need to care whether to call few 
- * distinct getInt() or readBytes(totalForFewInts) and parse themselves instead in an attempt to optimize.  
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class DataAccess {
-	public boolean isEmpty() {
-		return true;
-	}
-	public int length() {
-		return 0;
-	}
-	/**
-	 * get this instance into initial state
-	 * @throws IOException
-	 * @return <code>this</code> for convenience
-	 */
-	public DataAccess reset() throws IOException {
-		// nop, empty instance is always in the initial state
-		return this;
-	}
-	// absolute positioning
-	public void seek(int offset) throws IOException {
-		throw new UnsupportedOperationException();
-	}
-	// relative positioning
-	public void skip(int bytes) throws IOException {
-		throw new UnsupportedOperationException();
-	}
-	// shall be called once this object no longer needed
-	public void done() {
-		// no-op in this empty implementation
-	}
-	public int readInt() throws IOException {
-		byte[] b = new byte[4];
-		readBytes(b, 0, 4);
-		return b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF);
-	}
-	public long readLong() throws IOException {
-		byte[] b = new byte[8];
-		readBytes(b, 0, 8);
-		int i1 = b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF);
-		int i2 = b[4] << 24 | (b[5] & 0xFF) << 16 | (b[6] & 0xFF) << 8 | (b[7] & 0xFF);
-		return ((long) i1) << 32 | ((long) i2 & 0xFFFFFFFFl);
-	}
-	public void readBytes(byte[] buf, int offset, int length) throws IOException {
-		throw new UnsupportedOperationException();
-	}
-	// reads bytes into ByteBuffer, up to its limit or total data length, whichever smaller
-	// FIXME perhaps, in DataAccess paradigm (when we read known number of bytes, we shall pass specific byte count to read) 
-	public void readBytes(ByteBuffer buf) throws IOException {
-//		int toRead = Math.min(buf.remaining(), (int) length());
-//		if (buf.hasArray()) {
-//			readBytes(buf.array(), buf.arrayOffset(), toRead);
-//		} else {
-//			byte[] bb = new byte[toRead];
-//			readBytes(bb, 0, bb.length);
-//			buf.put(bb);
-//		}
-		// FIXME optimize to read as much as possible at once
-		while (!isEmpty() && buf.hasRemaining()) {
-			buf.put(readByte());
-		}
-	}
-	public byte readByte() throws IOException {
-		throw new UnsupportedOperationException();
-	}
-
-	// XXX decide whether may or may not change position in the DataAccess
-	// FIXME exception handling is not right, just for the sake of quick test
-	public byte[] byteArray() throws IOException {
-		reset();
-		byte[] rv = new byte[length()];
-		readBytes(rv, 0, rv.length);
-		return rv;
-	}
-}
--- a/src/org/tmatesoft/hg/internal/DataAccessProvider.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,307 +0,0 @@
-/*
- * Copyright (c) 2010-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.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-
-import org.tmatesoft.hg.core.HgBadStateException;
-
-/**
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class DataAccessProvider {
-
-	private final int mapioMagicBoundary;
-	private final int bufferSize;
-
-	public DataAccessProvider() {
-		this(100 * 1024, 8 * 1024);
-	}
-
-	public DataAccessProvider(int mapioBoundary, int regularBufferSize) {
-		mapioMagicBoundary = mapioBoundary;
-		bufferSize = regularBufferSize;
-	}
-
-	public DataAccess create(File f) {
-		if (!f.exists()) {
-			return new DataAccess();
-		}
-		try {
-			FileChannel fc = new FileInputStream(f).getChannel();
-			int flen = (int) fc.size();
-			if (fc.size() - flen != 0) {
-				throw new HgBadStateException("Files greater than 2Gb are not yet supported");
-			}
-			if (flen > mapioMagicBoundary) {
-				// TESTS: bufLen of 1024 was used to test MemMapFileAccess
-				return new MemoryMapFileAccess(fc, flen, mapioMagicBoundary);
-			} else {
-				// XXX once implementation is more or less stable,
-				// may want to try ByteBuffer.allocateDirect() to see
-				// if there's any performance gain. 
-				boolean useDirectBuffer = false;
-				// TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize
-				return new FileAccess(fc, flen, bufferSize, useDirectBuffer);
-			}
-		} catch (IOException ex) {
-			// unlikely to happen, we've made sure file exists.
-			ex.printStackTrace(); // FIXME log error
-		}
-		return new DataAccess(); // non-null, empty.
-	}
-
-	// DOESN'T WORK YET 
-	private static class MemoryMapFileAccess extends DataAccess {
-		private FileChannel fileChannel;
-		private final int size;
-		private long position = 0; // always points to buffer's absolute position in the file
-		private final int memBufferSize;
-		private MappedByteBuffer buffer;
-
-		public MemoryMapFileAccess(FileChannel fc, int channelSize, int /*long?*/ bufferSize) {
-			fileChannel = fc;
-			size = channelSize;
-			memBufferSize = bufferSize;
-		}
-
-		@Override
-		public boolean isEmpty() {
-			return position + (buffer == null ? 0 : buffer.position()) >= size;
-		}
-		
-		@Override
-		public int length() {
-			return size;
-		}
-		
-		@Override
-		public DataAccess reset() throws IOException {
-			seek(0);
-			return this;
-		}
-		
-		@Override
-		public void seek(int offset) {
-			assert offset >= 0;
-			// offset may not necessarily be further than current position in the file (e.g. rewind) 
-			if (buffer != null && /*offset is within buffer*/ offset >= position && (offset - position) < buffer.limit()) {
-				buffer.position((int) (offset - position));
-			} else {
-				position = offset;
-				buffer = null;
-			}
-		}
-
-		@Override
-		public void skip(int bytes) throws IOException {
-			assert bytes >= 0;
-			if (buffer == null) {
-				position += bytes;
-				return;
-			}
-			if (buffer.remaining() > bytes) {
-				buffer.position(buffer.position() + bytes);
-			} else {
-				position += buffer.position() + bytes;
-				buffer = null;
-			}
-		}
-
-		private void fill() throws IOException {
-			if (buffer != null) {
-				position += buffer.position(); 
-			}
-			long left = size - position;
-			buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < memBufferSize ? left : memBufferSize);
-		}
-
-		@Override
-		public void readBytes(byte[] buf, int offset, int length) throws IOException {
-			if (buffer == null || !buffer.hasRemaining()) {
-				fill();
-			}
-			// XXX in fact, we may try to create a MappedByteBuffer of exactly length size here, and read right away
-			while (length > 0) {
-				int tail = buffer.remaining();
-				if (tail == 0) {
-					throw new IOException();
-				}
-				if (tail >= length) {
-					buffer.get(buf, offset, length);
-				} else {
-					buffer.get(buf, offset, tail);
-					fill();
-				}
-				offset += tail;
-				length -= tail;
-			}
-		}
-
-		@Override
-		public byte readByte() throws IOException {
-			if (buffer == null || !buffer.hasRemaining()) {
-				fill();
-			}
-			if (buffer.hasRemaining()) {
-				return buffer.get();
-			}
-			throw new IOException();
-		}
-
-		@Override
-		public void done() {
-			buffer = null;
-			if (fileChannel != null) {
-				try {
-					fileChannel.close();
-				} catch (IOException ex) {
-					ex.printStackTrace(); // log debug
-				}
-				fileChannel = null;
-			}
-		}
-	}
-
-	// (almost) regular file access - FileChannel and buffers.
-	private static class FileAccess extends DataAccess {
-		private FileChannel fileChannel;
-		private final int size;
-		private ByteBuffer buffer;
-		private int bufferStartInFile = 0; // offset of this.buffer in the file.
-
-		public FileAccess(FileChannel fc, int channelSize, int bufferSizeHint, boolean useDirect) {
-			fileChannel = fc;
-			size = channelSize;
-			final int capacity = size < bufferSizeHint ? size : bufferSizeHint;
-			buffer = useDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
-			buffer.flip(); // or .limit(0) to indicate it's empty
-		}
-		
-		@Override
-		public boolean isEmpty() {
-			return bufferStartInFile + buffer.position() >= size;
-		}
-		
-		@Override
-		public int length() {
-			return size;
-		}
-		
-		@Override
-		public DataAccess reset() throws IOException {
-			seek(0);
-			return this;
-		}
-		
-		@Override
-		public void seek(int offset) throws IOException {
-			if (offset > size) {
-				throw new IllegalArgumentException();
-			}
-			if (offset < bufferStartInFile + buffer.limit() && offset >= bufferStartInFile) {
-				buffer.position((int) (offset - bufferStartInFile));
-			} else {
-				// out of current buffer, invalidate it (force re-read) 
-				// XXX or ever re-read it right away?
-				bufferStartInFile = offset;
-				buffer.clear();
-				buffer.limit(0); // or .flip() to indicate we switch to reading
-				fileChannel.position(offset);
-			}
-		}
-
-		@Override
-		public void skip(int bytes) throws IOException {
-			final int newPos = buffer.position() + bytes;
-			if (newPos >= 0 && newPos < buffer.limit()) {
-				// no need to move file pointer, just rewind/seek buffer 
-				buffer.position(newPos);
-			} else {
-				//
-				seek(bufferStartInFile + newPos);
-			}
-		}
-
-		private boolean fill() throws IOException {
-			if (!buffer.hasRemaining()) {
-				bufferStartInFile += buffer.limit();
-				buffer.clear();
-				if (bufferStartInFile < size) { // just in case there'd be any exception on EOF, not -1 
-					fileChannel.read(buffer);
-					// may return -1 when EOF, but empty will reflect this, hence no explicit support here   
-				}
-				buffer.flip();
-			}
-			return buffer.hasRemaining();
-		}
-
-		@Override
-		public void readBytes(byte[] buf, int offset, int length) throws IOException {
-			if (!buffer.hasRemaining()) {
-				fill();
-			}
-			while (length > 0) {
-				int tail = buffer.remaining();
-				if (tail == 0) {
-					throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past isEmpty() == true are made.
-				}
-				if (tail >= length) {
-					buffer.get(buf, offset, length);
-				} else {
-					buffer.get(buf, offset, tail);
-					fill();
-				}
-				offset += tail;
-				length -= tail;
-			}
-		}
-
-		@Override
-		public byte readByte() throws IOException {
-			if (buffer.hasRemaining()) {
-				return buffer.get();
-			}
-			if (fill()) {
-				return buffer.get();
-			}
-			throw new IOException();
-		}
-
-		@Override
-		public void done() {
-			if (buffer != null) {
-				buffer = null;
-			}
-			if (fileChannel != null) {
-				try {
-					fileChannel.close();
-				} catch (IOException ex) {
-					ex.printStackTrace(); // log debug
-				}
-				fileChannel = null;
-			}
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/internal/DigestHelper.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-/*
- * Copyright (c) 2010-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.IOException;
-import java.io.InputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-import org.tmatesoft.hg.core.Nodeid;
-
-
-/**
- * <pre>
- * DigestHelper dh;
- * dh.sha1(...).asHexString();
- *  or 
- * dh = dh.sha1(...);
- * nodeid.equalsTo(dh.asBinary());
- * </pre>
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class DigestHelper {
-	private MessageDigest sha1;
-	private byte[] digest;
-
-	public DigestHelper() {
-	}
-	
-	private MessageDigest getSHA1() {
-		if (sha1 == null) {
-			try {
-				sha1 = MessageDigest.getInstance("SHA-1");
-			} catch (NoSuchAlgorithmException ex) {
-				// could hardly happen, JDK from Sun always has sha1.
-				ex.printStackTrace(); // FIXME log error
-			}
-		}
-		return sha1;
-	}
-
-
-	public DigestHelper sha1(Nodeid nodeid1, Nodeid nodeid2, byte[] data) {
-		return sha1(nodeid1.toByteArray(), nodeid2.toByteArray(), data);
-	}
-
-	//  sha1_digest(min(p1,p2) ++ max(p1,p2) ++ final_text)
-	public DigestHelper sha1(byte[] nodeidParent1, byte[] nodeidParent2, byte[] data) {
-		MessageDigest alg = getSHA1();
-		if ((nodeidParent1[0] & 0x00FF) < (nodeidParent2[0] & 0x00FF)) { 
-			alg.update(nodeidParent1);
-			alg.update(nodeidParent2);
-		} else {
-			alg.update(nodeidParent2);
-			alg.update(nodeidParent1);
-		}
-		digest = alg.digest(data);
-		assert digest.length == 20;
-		return this;
-	}
-	
-	public String asHexString() {
-		if (digest == null) {
-			throw new IllegalStateException("Shall init with sha1() call first");
-		}
-		return toHexString(digest, 0, digest.length);
-	}
-	
-	// by reference, be careful not to modify (or #clone() if needed)
-	public byte[] asBinary() {
-		if (digest == null) {
-			throw new IllegalStateException("Shall init with sha1() call first");
-		}
-		return digest; 
-	}
-
-	// XXX perhaps, digest functions should throw an exception, as it's caller responsibility to deal with eof, etc
-	public DigestHelper sha1(InputStream is /*ByteBuffer*/) throws IOException {
-		MessageDigest alg = getSHA1();
-		byte[] buf = new byte[1024];
-		int c;
-		while ((c = is.read(buf)) != -1) {
-			alg.update(buf, 0, c);
-		}
-		digest = alg.digest();
-		return this;
-	}
-	
-	public DigestHelper sha1(CharSequence... seq) {
-		MessageDigest alg = getSHA1();
-		for (CharSequence s : seq) {
-			byte[] b = s.toString().getBytes();
-			alg.update(b);
-		}
-		digest = alg.digest();
-		return this;
-	}
-
-	public static String toHexString(byte[] data, final int offset, final int count) {
-		char[] result = new char[count << 1];
-		final String hexDigits = "0123456789abcdef";
-		final int end = offset+count;
-		for (int i = offset, j = 0; i < end; i++) {
-			result[j++] = hexDigits.charAt((data[i] >>> 4) & 0x0F);
-			result[j++] = hexDigits.charAt(data[i] & 0x0F);
-		}
-		return new String(result);
-	}
-}
--- a/src/org/tmatesoft/hg/internal/Filter.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- * 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.nio.ByteBuffer;
-
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.Path;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface Filter {
-
-	// returns a buffer ready to be read. may return original buffer.
-	// original buffer may not be fully consumed, #compact() might be operation to perform 
-	ByteBuffer filter(ByteBuffer src);
-
-	interface Factory {
-		void initialize(HgRepository hgRepo, ConfigFile cfg);
-		// may return null if for a given path and/or options this filter doesn't make any sense
-		Filter create(Path path, Options opts);
-	}
-
-	enum Direction {
-		FromRepo, ToRepo
-	}
-
-	public class Options {
-
-		private final Direction direction;
-		public Options(Direction dir) {
-			direction = dir;
-		}
-		
-		Direction getDirection() {
-			return direction;
-		}
-
-	}
-}
--- a/src/org/tmatesoft/hg/internal/FilterByteChannel.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/*
- * 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.IOException;
-import java.nio.ByteBuffer;
-import java.util.Collection;
-
-import org.tmatesoft.hg.util.Adaptable;
-import org.tmatesoft.hg.util.ByteChannel;
-import org.tmatesoft.hg.util.CancelledException;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class FilterByteChannel implements ByteChannel, Adaptable {
-	private final Filter[] filters;
-	private final ByteChannel delegate;
-	
-	public FilterByteChannel(ByteChannel delegateChannel, Collection<Filter> filtersToApply) {
-		if (delegateChannel == null || filtersToApply == null) {
-			throw new IllegalArgumentException();
-		}
-		delegate = delegateChannel;
-		filters = filtersToApply.toArray(new Filter[filtersToApply.size()]);
-	}
-
-	public int write(ByteBuffer buffer) throws IOException, CancelledException {
-		final int srcPos = buffer.position();
-		ByteBuffer processed = buffer;
-		for (Filter f : filters) {
-			// each next filter consumes not more than previous
-			// hence total consumed equals position shift in the original buffer
-			processed = f.filter(processed);
-		}
-		delegate.write(processed);
-		return buffer.position() - srcPos; // consumed as much from original buffer
-	}
-
-	// adapters or implemented interfaces of the original class shall not be obfuscated by filter
-	public <T> T getAdapter(Class<T> adapterClass) {
-		if (delegate instanceof Adaptable) {
-			return ((Adaptable) delegate).getAdapter(adapterClass);
-		}
-		if (adapterClass != null && adapterClass.isInstance(delegate)) {
-			return adapterClass.cast(delegate);
-		}
-		return null;
-	}
-}
--- a/src/org/tmatesoft/hg/internal/FilterDataAccess.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/*
- * 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.IOException;
-
-
-/**
- * XXX Perhaps, DataAccessSlice? Unlike FilterInputStream, we limit amount of data read from DataAccess being filtered.
- *   
- *   
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class FilterDataAccess extends DataAccess {
-	private final DataAccess dataAccess;
-	private final int offset;
-	private final int length;
-	private int count;
-
-	public FilterDataAccess(DataAccess dataAccess, int offset, int length) {
-		this.dataAccess = dataAccess;
-		this.offset = offset;
-		this.length = length;
-		count = length;
-	}
-
-	protected int available() {
-		return count;
-	}
-
-	@Override
-	public FilterDataAccess reset() throws IOException {
-		count = length;
-		return this;
-	}
-	
-	@Override
-	public boolean isEmpty() {
-		return count <= 0;
-	}
-	
-	@Override
-	public int length() {
-		return length;
-	}
-
-	@Override
-	public void seek(int localOffset) throws IOException {
-		if (localOffset < 0 || localOffset > length) {
-			throw new IllegalArgumentException();
-		}
-		dataAccess.seek(offset + localOffset);
-		count = (int) (length - localOffset);
-	}
-
-	@Override
-	public void skip(int bytes) throws IOException {
-		int newCount = count - bytes;
-		if (newCount < 0 || newCount > length) {
-			throw new IllegalArgumentException();
-		}
-		seek(length - newCount);
-		/*
-		 can't use next code because don't want to rewind backing DataAccess on reset()
-		 i.e. this.reset() modifies state of this instance only, while filtered DA may go further.
-		 Only actual this.skip/seek/read would rewind it to desired position 
-	  		dataAccess.skip(bytes);
-			count = newCount;
-		 */
-
-	}
-
-	@Override
-	public byte readByte() throws IOException {
-		if (count <= 0) {
-			throw new IllegalArgumentException("Underflow"); // XXX be descriptive
-		}
-		if (count == length) {
-			dataAccess.seek(offset);
-		}
-		count--;
-		return dataAccess.readByte();
-	}
-
-	@Override
-	public void readBytes(byte[] b, int off, int len) throws IOException {
-		if (len == 0) {
-			return;
-		}
-		if (count <= 0 || len > count) {
-			throw new IllegalArgumentException(String.format("Underflow. Bytes left: %d, asked to read %d", count, len));
-		}
-		if (count == length) {
-			dataAccess.seek(offset);
-		}
-		dataAccess.readBytes(b, off, len);
-		count -= len;
-	}
-
-	// done shall be no-op, as we have no idea what's going on with DataAccess we filter
-}
--- a/src/org/tmatesoft/hg/internal/InflaterDataAccess.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-/*
- * 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.EOFException;
-import java.io.IOException;
-import java.util.zip.DataFormatException;
-import java.util.zip.Inflater;
-import java.util.zip.ZipException;
-
-import org.tmatesoft.hg.core.HgBadStateException;
-
-
-/**
- * DataAccess counterpart for InflaterInputStream.
- * XXX is it really needed to be subclass of FilterDataAccess? 
- *   
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class InflaterDataAccess extends FilterDataAccess {
-
-	private final Inflater inflater;
-	private final byte[] buffer;
-	private final byte[] singleByte = new byte[1];
-	private int decompressedPos = 0;
-	private int decompressedLength;
-
-	public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength) {
-		this(dataAccess, offset, compressedLength, -1, new Inflater(), 512);
-	}
-
-	public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength, int actualLength) {
-		this(dataAccess, offset, compressedLength, actualLength, new Inflater(), 512);
-	}
-
-	public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength, int actualLength, Inflater inflater, int bufSize) {
-		super(dataAccess, offset, compressedLength);
-		this.inflater = inflater;
-		this.decompressedLength = actualLength;
-		buffer = new byte[bufSize];
-	}
-	
-	@Override
-	public InflaterDataAccess reset() throws IOException {
-		super.reset();
-		inflater.reset();
-		decompressedPos = 0;
-		return this;
-	}
-	
-	@Override
-	protected int available() {
-		return length() - decompressedPos;
-	}
-	
-	@Override
-	public boolean isEmpty() {
-		// can't use super.available() <= 0 because even when 0 < super.count < 6(?)
-		// decompressedPos might be already == length() 
-		return available() <= 0;
-	}
-	
-	@Override
-	public int length() {
-		if (decompressedLength != -1) {
-			return decompressedLength;
-		}
-		decompressedLength = 0; // guard to avoid endless loop in case length() would get invoked from below. 
-		int c = 0;
-		try {
-			int oldPos = decompressedPos;
-			byte[] dummy = new byte[buffer.length];
-			int toRead;
-			while ((toRead = super.available()) > 0) {
-				if (toRead > buffer.length) {
-					toRead = buffer.length;
-				}
-				super.readBytes(buffer, 0, toRead);
-				inflater.setInput(buffer, 0, toRead);
-				try {
-					while (!inflater.needsInput()) {
-						c += inflater.inflate(dummy, 0, dummy.length);
-					}
-				} catch (DataFormatException ex) {
-					throw new HgBadStateException(ex);
-				}
-			}
-			decompressedLength = c + oldPos;
-			reset();
-			seek(oldPos);
-			return decompressedLength;
-		} catch (IOException ex) {
-			decompressedLength = -1; // better luck next time?
-			throw new HgBadStateException(ex); // XXX perhaps, checked exception
-		}
-	}
-	
-	@Override
-	public void seek(int localOffset) throws IOException {
-		if (localOffset < 0 /* || localOffset >= length() */) {
-			throw new IllegalArgumentException();
-		}
-		if (localOffset >= decompressedPos) {
-			skip((int) (localOffset - decompressedPos));
-		} else {
-			reset();
-			skip((int) localOffset);
-		}
-	}
-	
-	@Override
-	public void skip(int bytes) throws IOException {
-		if (bytes < 0) {
-			bytes += decompressedPos;
-			if (bytes < 0) {
-				throw new IOException("Underflow. Rewind past start of the slice.");
-			}
-			reset();
-			// fall-through
-		}
-		while (!isEmpty() && bytes > 0) {
-			readByte();
-			bytes--;
-		}
-		if (bytes != 0) {
-			throw new IOException("Underflow. Rewind past end of the slice");
-		}
-	}
-
-	@Override
-	public byte readByte() throws IOException {
-		readBytes(singleByte, 0, 1);
-		return singleByte[0];
-	}
-
-	@Override
-	public void readBytes(byte[] b, int off, int len) throws IOException {
-		try {
-		    int n;
-		    while (len > 0) {
-			    while ((n = inflater.inflate(b, off, len)) == 0) {
-			    	// FIXME few last bytes (checksum?) may be ignored by inflater, thus inflate may return 0 in
-			    	// perfectly legal conditions (when all data already expanded, but there are still some bytes
-			    	// in the input stream
-					if (inflater.finished() || inflater.needsDictionary()) {
-	                    throw new EOFException();
-					}
-					if (inflater.needsInput()) {
-						// fill:
-						int toRead = super.available();
-						if (toRead > buffer.length) {
-							toRead = buffer.length;
-						}
-						super.readBytes(buffer, 0, toRead);
-						inflater.setInput(buffer, 0, toRead);
-					}
-			    }
-				off += n;
-				len -= n;
-				decompressedPos += n;
-				if (len == 0) {
-					return; // filled
-				}
-		    }
-		} catch (DataFormatException e) {
-		    String s = e.getMessage();
-		    throw new ZipException(s != null ? s : "Invalid ZLIB data format");
-		}
-    }
-}
--- a/src/org/tmatesoft/hg/internal/Internals.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * 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 static org.tmatesoft.hg.internal.RequiresFile.*;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- * Fields/members that shall not be visible  
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Internals {
-	
-	private int requiresFlags = 0;
-	private List<Filter.Factory> filterFactories;
-	
-
-	public Internals() {
-	}
-
-	public/*for tests, otherwise pkg*/ void setStorageConfig(int version, int flags) {
-		requiresFlags = flags;
-	}
-
-	// XXX perhaps, should keep both fields right here, not in the HgRepository
-	public PathRewrite buildDataFilesHelper() {
-		return new StoragePathHelper((requiresFlags & STORE) != 0, (requiresFlags & FNCACHE) != 0, (requiresFlags & DOTENCODE) != 0);
-	}
-
-	public PathRewrite buildRepositoryFilesHelper() {
-		if ((requiresFlags & STORE) != 0) {
-			return new PathRewrite() {
-				public String rewrite(String path) {
-					return "store/" + path;
-				}
-			};
-		} else {
-			return new PathRewrite() {
-				public String rewrite(String path) {
-					//no-op
-					return path;
-				}
-			};
-		}
-	}
-
-	public ConfigFile newConfigFile() {
-		return new ConfigFile();
-	}
-
-	public List<Filter.Factory> getFilters(HgRepository hgRepo, ConfigFile cfg) {
-		if (filterFactories == null) {
-			filterFactories = new ArrayList<Filter.Factory>();
-			if (cfg.hasEnabledExtension("eol")) {
-				NewlineFilter.Factory ff = new NewlineFilter.Factory();
-				ff.initialize(hgRepo, cfg);
-				filterFactories.add(ff);
-			}
-			if (cfg.hasEnabledExtension("keyword")) {
-				KeywordFilter.Factory ff = new KeywordFilter.Factory();
-				ff.initialize(hgRepo, cfg);
-				filterFactories.add(ff);
-			}
-		}
-		return filterFactories;
-	}
-	
-	public void initEmptyRepository(File hgDir) throws IOException {
-		hgDir.mkdir();
-		FileOutputStream requiresFile = new FileOutputStream(new File(hgDir, "requires"));
-		StringBuilder sb = new StringBuilder(40);
-		sb.append("revlogv1\n");
-		if ((requiresFlags & STORE) != 0) {
-			sb.append("store\n");
-		}
-		if ((requiresFlags & FNCACHE) != 0) {
-			sb.append("fncache\n");
-		}
-		if ((requiresFlags & DOTENCODE) != 0) {
-			sb.append("dotencode\n");
-		}
-		requiresFile.write(sb.toString().getBytes());
-		requiresFile.close();
-		new File(hgDir, "store").mkdir(); // with that, hg verify says ok.
-	}
-
-}
--- a/src/org/tmatesoft/hg/internal/KeywordFilter.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,323 +0,0 @@
-/*
- * 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.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.Path;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class KeywordFilter implements Filter {
-	// present implementation is stateless, however, filter use pattern shall not assume that. In fact, Factory may us that 
-	private final HgRepository repo;
-	private final boolean isExpanding;
-	private final TreeMap<String,String> keywords;
-	private final int minBufferLen;
-	private final Path path;
-	private RawChangeset latestFileCset;
-
-	/**
-	 * 
-	 * @param hgRepo 
-	 * @param path 
-	 * @param expand <code>true</code> to expand keywords, <code>false</code> to shrink
-	 */
-	private KeywordFilter(HgRepository hgRepo, Path p, boolean expand) {
-		repo = hgRepo;
-		path = p;
-		isExpanding = expand;
-		keywords = new TreeMap<String,String>();
-		keywords.put("Id", "Id");
-		keywords.put("Revision", "Revision");
-		keywords.put("Author", "Author");
-		keywords.put("Date", "Date");
-		keywords.put("LastChangedRevision", "LastChangedRevision");
-		keywords.put("LastChangedBy", "LastChangedBy");
-		keywords.put("LastChangedDate", "LastChangedDate");
-		keywords.put("Source", "Source");
-		keywords.put("Header", "Header");
-
-		int l = 0;
-		for (String s : keywords.keySet()) {
-			if (s.length() > l) {
-				l = s.length();
-			}
-		}
-		// FIXME later may implement #filter() not to read full kw value (just "$kw:"). However, limit of maxLen + 2 would keep valid.
-		// for buffers less then minBufferLen, there are chances #filter() implementation would never end
-		// (i.e. for input "$LongestKey"$
-		minBufferLen = l + 2 + (isExpanding ? 0 : 120 /*any reasonable constant for max possible kw value length*/);
-	}
-
-	/**
-	 * @param src buffer ready to be read
-	 * @return buffer ready to be read and original buffer's position modified to reflect consumed bytes. IOW, if source buffer
-	 * on return has remaining bytes, they are assumed not-read (not processed) and next chunk passed to filter is supposed to 
-	 * start with them  
-	 */
-	public ByteBuffer filter(ByteBuffer src) {
-		if (src.capacity() < minBufferLen) {
-			throw new IllegalStateException(String.format("Need buffer of at least %d bytes to ensure filter won't hang", minBufferLen));
-		}
-		ByteBuffer rv = null;
-		int keywordStart = -1;
-		int x = src.position();
-		int copyFrom = x; // needs to be updated each time we copy a slice, but not each time we modify source index (x)
-		while (x < src.limit()) {
-			if (keywordStart == -1) {
-				int i = indexOf(src, '$', x, false);
-				if (i == -1) {
-					if (rv == null) {
-						return src;
-					} else {
-						copySlice(src, copyFrom, src.limit(), rv);
-						rv.flip();
-						src.position(src.limit());
-						return rv;
-					}
-				}
-				keywordStart = i;
-				// fall-through
-			}
-			if (keywordStart >= 0) {
-				int i = indexOf(src, '$', keywordStart+1, true);
-				if (i == -1) {
-					// end of buffer reached
-					if (rv == null) {
-						if (keywordStart == x) {
-							// FIXME in fact, x might be equal to keywordStart and to src.position() here ('$' is first character in the buffer, 
-							// and there are no other '$' not eols till the end of the buffer). This would lead to deadlock (filter won't consume any
-							// bytes). To prevent this, either shall copy bytes [keywordStart..buffer.limit()) to local buffer and use it on the next invocation,
-							// or add lookup of the keywords right after first '$' is found (do not wait for closing '$'). For now, large enough src buffer would be sufficient
-							// not to run into such situation
-							throw new IllegalStateException("Try src buffer of a greater size");
-						}
-						rv = ByteBuffer.allocate(keywordStart - copyFrom);
-					}
-					// copy all from source till latest possible kw start 
-					copySlice(src, copyFrom, keywordStart, rv);
-					rv.flip();
-					// and tell caller we've consumed only to the potential kw start
-					src.position(keywordStart);
-					return rv;
-				} else if (src.get(i) == '$') {
-					// end of keyword, or start of a new one.
-					String keyword;
-					if ((keyword = matchKeyword(src, keywordStart, i)) != null) {
-						if (rv == null) {
-							// src.remaining(), not .capacity because src is not read, and remaining represents 
-							// actual bytes count, while capacity - potential.
-							// Factor of 4 is pure guess and a HACK, need to be fixed with re-expanding buffer on demand
-							rv = ByteBuffer.allocate(isExpanding ? src.remaining() * 4 : src.remaining());
-						}
-						copySlice(src, copyFrom, keywordStart+1, rv);
-						rv.put(keyword.getBytes());
-						if (isExpanding) {
-							rv.put((byte) ':');
-							rv.put((byte) ' ');
-							expandKeywordValue(keyword, rv);
-							rv.put((byte) ' ');
-						}
-						rv.put((byte) '$');
-						keywordStart = -1;
-						x = i+1;
-						copyFrom = x;
-						continue;
-					} else {
-						if (rv != null) {
-							// we've already did some substitution, thus need to copy bytes we've scanned. 
-							copySlice(src, x, i, rv);
-							copyFrom = i;
-						} // no else in attempt to avoid rv creation if no real kw would be found  
-						keywordStart = i;
-						x = i; // '$' at i wasn't consumed, hence x points to i, not i+1. This is to avoid problems with case: "sdfsd $ asdfs $Id$ sdf"
-						continue;
-					}
-				} else {
-					assert src.get(i) == '\n' || src.get(i) == '\r';
-					// line break
-					if (rv != null) {
-						copySlice(src, x, i+1, rv);
-						copyFrom = i+1;
-					}
-					x = i+1;
-					keywordStart = -1; // Wasn't keyword, really
-					continue; // try once again
-				}
-			}
-		}
-		if (keywordStart != -1) {
-			if (rv == null) {
-				// no expansion happened yet, and we have potential kw start
-				rv = ByteBuffer.allocate(keywordStart - src.position());
-				copySlice(src, src.position(), keywordStart, rv);
-			}
-			src.position(keywordStart);
-		}
-		if (rv != null) {
-			rv.flip();
-			return rv;
-		}
-		return src;
-	}
-	
-	/**
-	 * @param keyword
-	 * @param rv
-	 */
-	private void expandKeywordValue(String keyword, ByteBuffer rv) {
-		if ("Id".equals(keyword)) {
-			rv.put(identityString().getBytes());
-		} else if ("Revision".equals(keyword)) {
-			rv.put(revision().getBytes());
-		} else if ("Author".equals(keyword)) {
-			rv.put(username().getBytes());
-		} else if ("Date".equals(keyword)) {
-			rv.put(date().getBytes());
-		} else {
-			throw new IllegalStateException(String.format("Keyword %s is not yet supported", keyword));
-		}
-	}
-
-	private String matchKeyword(ByteBuffer src, int kwStart, int kwEnd) {
-		assert kwEnd - kwStart - 1 > 0;
-		assert src.get(kwStart) == src.get(kwEnd) && src.get(kwEnd) == '$';
-		char[] chars = new char[kwEnd - kwStart - 1];
-		int i;
-		for (i = 0; i < chars.length; i++) {
-			char c = (char) src.get(kwStart + 1 + i);
-			if (c == ':') {
-				break;
-			}
-			chars[i] = c;
-		}
-		String kw = new String(chars, 0, i);
-//		XXX may use subMap to look up keywords based on few available characters (not waiting till closing $)
-//		System.out.println(keywords.subMap("I", "J"));
-//		System.out.println(keywords.subMap("A", "B"));
-//		System.out.println(keywords.subMap("Au", "B"));
-		return keywords.get(kw);
-	}
-	
-	// copies part of the src buffer, [from..to). doesn't modify src position
-	static void copySlice(ByteBuffer src, int from, int to, ByteBuffer dst) {
-		if (to > src.limit()) {
-			throw new IllegalArgumentException("Bad right boundary");
-		}
-		if (dst.remaining() < to - from) {
-			throw new IllegalArgumentException("Not enough room in the destination buffer");
-		}
-		for (int i = from; i < to; i++) {
-			dst.put(src.get(i));
-		}
-	}
-
-	private static int indexOf(ByteBuffer b, char ch, int from, boolean newlineBreaks) {
-		for (int i = from; i < b.limit(); i++) {
-			byte c = b.get(i);
-			if (ch == c) {
-				return i;
-			}
-			if (newlineBreaks && (c == '\n' || c == '\r')) {
-				return i;
-			}
-		}
-		return -1;
-	}
-
-	private String identityString() {
-		return String.format("%s,v %s %s %s", path, revision(), date(), username());
-	}
-
-	private String revision() {
-		// FIXME add cset's nodeid into Changeset class 
-		int csetRev = repo.getFileNode(path).getChangesetLocalRevision(HgRepository.TIP);
-		return repo.getChangelog().getRevision(csetRev).shortNotation();
-	}
-	
-	private String username() {
-		return getChangeset().user();
-	}
-	
-	private String date() {
-		return String.format("%tY/%<tm/%<td %<tH:%<tM:%<tS", getChangeset().date());
-	}
-	
-	private RawChangeset getChangeset() {
-		if (latestFileCset == null) {
-			// XXX consider use of ChangelogHelper
-			int csetRev = repo.getFileNode(path).getChangesetLocalRevision(HgRepository.TIP);
-			latestFileCset = repo.getChangelog().range(csetRev, csetRev).get(0);
-		}
-		return latestFileCset;
-	}
-
-	public static class Factory implements Filter.Factory {
-		
-		private HgRepository repo;
-		private Path.Matcher matcher;
-
-		public void initialize(HgRepository hgRepo, ConfigFile cfg) {
-			repo = hgRepo;
-			ArrayList<String> patterns = new ArrayList<String>();
-			for (Map.Entry<String,String> e : cfg.getSection("keyword").entrySet()) {
-				if (!"ignore".equalsIgnoreCase(e.getValue())) {
-					patterns.add(e.getKey());
-				}
-			}
-			matcher = new PathGlobMatcher(patterns.toArray(new String[patterns.size()]));
-			// TODO read and respect keyword patterns from [keywordmaps]
-		}
-
-		public Filter create(Path path, Options opts) {
-			if (matcher.accept(path)) {
-				return new KeywordFilter(repo, path, opts.getDirection() == Filter.Direction.FromRepo);
-			}
-			return null;
-		}
-	}
-
-//
-//	public static void main(String[] args) throws Exception {
-//		FileInputStream fis = new FileInputStream(new File("/temp/kwoutput.txt"));
-//		FileOutputStream fos = new FileOutputStream(new File("/temp/kwoutput2.txt"));
-//		ByteBuffer b = ByteBuffer.allocate(256);
-//		KeywordFilter kwFilter = new KeywordFilter(false);
-//		while (fis.getChannel().read(b) != -1) {
-//			b.flip(); // get ready to be read
-//			ByteBuffer f = kwFilter.filter(b);
-//			fos.getChannel().write(f); // XXX in fact, f may not be fully consumed
-//			if (b.hasRemaining()) {
-//				b.compact();
-//			} else {
-//				b.clear();
-//			}
-//		}
-//		fis.close();
-//		fos.flush();
-//		fos.close();
-//	}
-}
--- a/src/org/tmatesoft/hg/internal/NewlineFilter.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,269 +0,0 @@
-/*
- * 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 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 java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-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;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class NewlineFilter implements Filter {
-
-	// if allowInconsistent is true, filter simply pass incorrect newline characters (single \r or \r\n on *nix and single \n on Windows) as is,
-	// i.e. doesn't try to convert them into appropriate newline characters. XXX revisit if Keyword extension behaves differently
-	private final boolean allowInconsistent;
-	private final boolean winToNix;
-
-	private NewlineFilter(boolean failIfInconsistent, int transform) {
-		winToNix = transform == 0;
-		allowInconsistent = !failIfInconsistent;
-	}
-
-	public ByteBuffer filter(ByteBuffer src) {
-		if (winToNix) {
-			return win2nix(src);
-		} else {
-			return nix2win(src);
-		}
-	}
-
-	private ByteBuffer win2nix(ByteBuffer src) {
-		int x = src.position(); // source index
-		int lookupStart = x;
-		ByteBuffer dst = null;
-		while (x < src.limit()) {
-			// x, lookupStart, ir and in are absolute positions within src buffer, which is never read with modifying operations
-			int ir = indexOf('\r', src, lookupStart);
-			int in = indexOf('\n', src, lookupStart);
-			if (ir == -1) {
-				if (in == -1 || allowInconsistent) {
-					if (dst != null) {
-						copySlice(src, x, src.limit(), dst);
-						x = src.limit(); // consumed all
-					}
-					break;
-				} else {
-					fail(src, in);
-				}
-			}
-			// in == -1 while ir != -1 may be valid case if ir is the last char of the buffer, we check below for that
-			if (in != -1 && in != ir+1 && !allowInconsistent) {
-				fail(src, in);
-			}
-			if (dst == null) {
-				dst = ByteBuffer.allocate(src.remaining());
-			}
-			copySlice(src, x, ir, dst);
-			if (ir+1 == src.limit()) {
-				// last char of the buffer - 
-				// consume src till that char and let next iteration work on it
-				x = ir;
-				break;
-			}
-			if (in != ir + 1) {
-				x = ir+1; // generally in, but if allowInconsistent==true and \r is not followed by \n, then 
-				// cases like "one \r two \r\n three" shall be processed correctly (second pair would be ignored if x==in)
-				lookupStart = ir+1;
-			} else {
-				x = in;
-				lookupStart = x+1; // skip \n for next lookup
-			}
-		}
-		src.position(x); // mark we've consumed up to x
-		return dst == null ? src : (ByteBuffer) dst.flip();
-	}
-
-	private ByteBuffer nix2win(ByteBuffer src) {
-		int x = src.position();
-		ByteBuffer dst = null;
-		while (x < src.limit()) {
-			int in = indexOf('\n', src, x);
-			int ir = indexOf('\r', src, x, in == -1 ? src.limit() : in);
-			if (in == -1) {
-				if (ir == -1 || allowInconsistent) {
-					break;
-				} else {
-					fail(src, ir);
-				}
-			} else if (ir != -1 && !allowInconsistent) {
-				fail(src, ir);
-			}
-			
-			// x <= in < src.limit
-			// allowInconsistent && x <= ir < in   || ir == -1  
-			if (dst == null) {
-				// buffer full of \n grows as much as twice in size
-				dst = ByteBuffer.allocate(src.remaining() * 2);
-			}
-			copySlice(src, x, in, dst);
-			if (ir == -1 || ir+1 != in) {
-				dst.put((byte) '\r');
-			} // otherwise (ir!=-1 && ir+1==in) we found \r\n pair, don't convert to \r\r\n
-			// we may copy \n at src[in] on the next iteration, but would need extra lookupIndex variable then.
-			dst.put((byte) '\n');
-			x = in+1;
-		}
-		src.position(x);
-		return dst == null ? src : (ByteBuffer) dst.flip();
-	}
-
-
-	private void fail(ByteBuffer b, int pos) {
-		throw new RuntimeException(String.format("Inconsistent newline characters in the stream (char 0x%x, local index:%d)", b.get(pos), pos));
-	}
-
-	private static int indexOf(char ch, ByteBuffer b, int from) {
-		return indexOf(ch, b, from, b.limit());
-	}
-
-	// looks up in buf[from..to)
-	private static int indexOf(char ch, ByteBuffer b, int from, int to) {
-		for (int i = from; i < to; i++) {
-			byte c = b.get(i);
-			if (ch == c) {
-				return i;
-			}
-		}
-		return -1;
-	}
-
-	public static class Factory implements Filter.Factory {
-		private boolean failIfInconsistent = true;
-		private Path.Matcher lfMatcher;
-		private Path.Matcher crlfMatcher;
-		private Path.Matcher binMatcher;
-		private Path.Matcher nativeMatcher;
-		private String nativeRepoFormat;
-		private String nativeOSFormat;
-
-		public void initialize(HgRepository hgRepo, ConfigFile cfg) {
-			failIfInconsistent = cfg.getBoolean("eol", "only-consistent", true);
-			File cfgFile = new File(new HgInternals(hgRepo).getRepositoryDir().getParentFile(), ".hgeol");
-			if (!cfgFile.canRead()) {
-				return;
-			}
-			// XXX if .hgeol is not checked out, we may get it from repository
-//			HgDataFile cfgFileNode = hgRepo.getFileNode(".hgeol");
-//			if (!cfgFileNode.exists()) {
-//				return;
-//			}
-			// XXX perhaps, add HgDataFile.hasWorkingCopy and workingCopyContent()?
-			ConfigFile hgeol = new ConfigFile();
-			hgeol.addLocation(cfgFile);
-			nativeRepoFormat = hgeol.getSection("repository").get("native");
-			if (nativeRepoFormat == null) {
-				nativeRepoFormat = "LF";
-			}
-			final String os = System.getProperty("os.name"); // XXX need centralized set of properties
-			nativeOSFormat = os.indexOf("Windows") != -1 ? "CRLF" : "LF";
-			// I assume pattern ordering in .hgeol is not important
-			ArrayList<String> lfPatterns = new ArrayList<String>();
-			ArrayList<String> crlfPatterns = new ArrayList<String>();
-			ArrayList<String> nativePatterns = new ArrayList<String>();
-			ArrayList<String> binPatterns = new ArrayList<String>();
-			for (Map.Entry<String,String> e : hgeol.getSection("patterns").entrySet()) {
-				if ("CRLF".equals(e.getValue())) {
-					crlfPatterns.add(e.getKey());
-				} else if ("LF".equals(e.getValue())) {
-					lfPatterns.add(e.getKey());
-				} else if ("native".equals(e.getValue())) {
-					nativePatterns.add(e.getKey());
-				} 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
-				}
-			}
-			if (!crlfPatterns.isEmpty()) {
-				crlfMatcher = new PathGlobMatcher(crlfPatterns.toArray(new String[crlfPatterns.size()]));
-			}
-			if (!lfPatterns.isEmpty()) {
-				lfMatcher = new PathGlobMatcher(lfPatterns.toArray(new String[lfPatterns.size()]));
-			}
-			if (!binPatterns.isEmpty()) {
-				binMatcher = new PathGlobMatcher(binPatterns.toArray(new String[binPatterns.size()]));
-			}
-			if (!nativePatterns.isEmpty()) {
-				nativeMatcher = new PathGlobMatcher(nativePatterns.toArray(new String[nativePatterns.size()]));
-			}
-		}
-
-		public Filter create(Path path, Options opts) {
-			if (binMatcher == null && crlfMatcher == null && lfMatcher == null && nativeMatcher == null) {
-				// not initialized - perhaps, no .hgeol found
-				return null;
-			}
-			if (binMatcher != null && binMatcher.accept(path)) {
-				return null;
-			}
-			if (crlfMatcher != null && crlfMatcher.accept(path)) {
-				return new NewlineFilter(failIfInconsistent, 1);
-			} else if (lfMatcher != null && lfMatcher.accept(path)) {
-				return new NewlineFilter(failIfInconsistent, 0);
-			} else if (nativeMatcher != null && nativeMatcher.accept(path)) {
-				if (nativeOSFormat.equals(nativeRepoFormat)) {
-					return null;
-				}
-				if (opts.getDirection() == FromRepo) {
-					int transform = "CRLF".equals(nativeOSFormat) ? 1 : 0;
-					return new NewlineFilter(failIfInconsistent, transform);
-				} else if (opts.getDirection() == ToRepo) {
-					int transform = "CRLF".equals(nativeOSFormat) ? 0 : 1;
-					return new NewlineFilter(failIfInconsistent, transform);
-				}
-				return null;
-			}
-			return null;
-		}
-	}
-
-	public static void main(String[] args) throws Exception {
-		FileInputStream fis = new FileInputStream(new File("/temp/design.lf.txt"));
-		FileOutputStream fos = new FileOutputStream(new File("/temp/design.newline.out"));
-		ByteBuffer b = ByteBuffer.allocate(12);
-		NewlineFilter nlFilter = new NewlineFilter(true, 1);
-		while (fis.getChannel().read(b) != -1) {
-			b.flip(); // get ready to be read
-			ByteBuffer f = nlFilter.filter(b);
-			fos.getChannel().write(f); // XXX in fact, f may not be fully consumed
-			if (b.hasRemaining()) {
-				b.compact();
-			} else {
-				b.clear();
-			}
-		}
-		fis.close();
-		fos.flush();
-		fos.close();
-	}
-
-}
--- a/src/org/tmatesoft/hg/internal/PathGlobMatcher.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/*
- * 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.util.regex.PatternSyntaxException;
-
-import org.tmatesoft.hg.util.Path;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class PathGlobMatcher implements Path.Matcher {
-	
-	private final PathRegexpMatcher delegate;
-	
-	/**
-	 * 
-	 * @param globPatterns
-	 * @throws NullPointerException if argument is null
-	 * @throws IllegalArgumentException if any of the patterns is not valid
-	 */
-	public PathGlobMatcher(String... globPatterns) {
-		String[] regexp = new String[globPatterns.length]; //deliberately let fail with NPE
-		int i = 0;
-		for (String s : globPatterns) {
-			regexp[i] = glob2regexp(s);
-		}
-		try {
-			delegate = new PathRegexpMatcher(regexp);
-		} catch (PatternSyntaxException ex) {
-			ex.printStackTrace();
-			throw new IllegalArgumentException(ex);
-		}
-	}
-	
-
-	// HgIgnore.glob2regex is similar, but IsIgnore solves slightly different task 
-	// (need to match partial paths, e.g. for glob 'bin' shall match not only 'bin' folder, but also any path below it,
-	// which is not generally the case
-	private static String glob2regexp(String glob) {
-		int end = glob.length() - 1;
-		boolean needLineEndMatch = glob.charAt(end) != '*';
-		while (end > 0 && glob.charAt(end) == '*') end--; // remove trailing * that are useless for Pattern.find()
-		StringBuilder sb = new StringBuilder(end*2);
-		if (glob.charAt(0) != '*') {
-			sb.append('^');
-		}
-		for (int i = 0; i <= end; i++) {
-			char ch = glob.charAt(i);
-			if (ch == '*') {
-				if (glob.charAt(i+1) == '*') { // i < end because we've stripped any trailing * earlier
-					// any char, including path segment separator
-					sb.append(".*?");
-					i++;
-				} else {
-					// just path segments
-					sb.append("[^/]*?");
-				}
-				continue;
-			} else if (ch == '?') {
-				sb.append("[^/]");
-				continue;
-			} else if (ch == '.' || ch == '\\') {
-				sb.append('\\');
-			}
-			sb.append(ch);
-		}
-		if (needLineEndMatch) {
-			sb.append('$');
-		}
-		return sb.toString();
-	}
-
-	public boolean accept(Path path) {
-		return delegate.accept(path);
-	}
-
-}
--- a/src/org/tmatesoft/hg/internal/PathRegexpMatcher.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * 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.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.Path.Matcher;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class PathRegexpMatcher implements Matcher {
-	private Pattern[] patterns;
-	
-	// disjunction, matches if any pattern found
-	// uses pattern.find(), not pattern.matches()
-	public PathRegexpMatcher(Pattern... p) {
-		if (p == null) {
-			throw new IllegalArgumentException();
-		}
-		patterns = p;
-	}
-	
-	public PathRegexpMatcher(String... p) throws PatternSyntaxException {
-		this(compile(p));
-	}
-	
-	private static Pattern[] compile(String[] p) throws PatternSyntaxException {
-		// deliberately do no check for null, let it fail
-		Pattern[] rv = new Pattern[p.length];
-		int i = 0;
-		for (String s : p) {
-			rv[i++] = Pattern.compile(s);
-		}
-		return rv;
-	}
-
-	public boolean accept(Path path) {
-		for (Pattern p : patterns) {
-			if (p.matcher(path).find()) {
-				return true;
-			}
-		}
-		return false;
-	}
-}
--- a/src/org/tmatesoft/hg/internal/Pool.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/*
- * 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.util.HashMap;
-
-/**
- * Instance pooling.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Pool<T> {
-	private final HashMap<T,T> unify = new HashMap<T, T>();
-	
-	public T unify(T t) {
-		T rv = unify.get(t);
-		if (rv == null) {
-			// first time we see a new value
-			unify.put(t, t);
-			rv = t;
-		}
-		return rv;
-	}
-	
-	@Override
-	public String toString() {
-		StringBuilder sb = new StringBuilder();
-		sb.append(Pool.class.getSimpleName());
-		sb.append('<');
-		if (!unify.isEmpty()) {
-			sb.append(unify.keySet().iterator().next().getClass().getName());
-		}
-		sb.append('>');
-		sb.append(':');
-		sb.append(unify.size());
-		return sb.toString();
-	}
-}
\ No newline at end of file
--- a/src/org/tmatesoft/hg/internal/RelativePathRewrite.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * 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.File;
-
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class RelativePathRewrite implements PathRewrite {
-	
-	private final String rootPath;
-
-	public RelativePathRewrite(File root) {
-		this(root.getPath());
-	}
-	
-	public RelativePathRewrite(String rootPath) {
-		this.rootPath = rootPath;
-	}
-
-	public String rewrite(String path) {
-		if (path != null && path.startsWith(rootPath)) {
-			if (path.length() == rootPath.length()) {
-				return "";
-			}
-			return path.substring(rootPath.length() + 1);
-		}
-		return path;
-	}
-}
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,570 +0,0 @@
-/*
- * 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 static org.tmatesoft.hg.core.Nodeid.NULL;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.HgChangelog;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-import org.tmatesoft.hg.repo.HgRemoteRepository.Range;
-import org.tmatesoft.hg.repo.HgRemoteRepository.RemoteBranch;
-import org.tmatesoft.hg.util.CancelSupport;
-import org.tmatesoft.hg.util.CancelledException;
-import org.tmatesoft.hg.util.ProgressSupport;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class RepositoryComparator {
-
-	private final boolean debug = Boolean.parseBoolean(System.getProperty("hg4j.remote.debug"));
-	private final HgChangelog.ParentWalker localRepo;
-	private final HgRemoteRepository remoteRepo;
-	private List<Nodeid> common;
-
-	public RepositoryComparator(HgChangelog.ParentWalker pwLocal, HgRemoteRepository hgRemote) {
-		localRepo = pwLocal;
-		remoteRepo = hgRemote;
-	}
-	
-	public RepositoryComparator compare(Object context) throws HgException, CancelledException {
-		ProgressSupport progressSupport = ProgressSupport.Factory.get(context);
-		CancelSupport cancelSupport = CancelSupport.Factory.get(context);
-		cancelSupport.checkCancelled();
-		progressSupport.start(10);
-		common = Collections.unmodifiableList(findCommonWithRemote());
-		// sanity check
-		for (Nodeid n : common) {
-			if (!localRepo.knownNode(n)) {
-				throw new HgBadStateException("Unknown node reported as common:" + n);
-			}
-		}
-		progressSupport.done();
-		return this;
-	}
-	
-	public List<Nodeid> getCommon() {
-		if (common == null) {
-			throw new HgBadStateException("Call #compare(Object) first");
-		}
-		return common;
-	}
-	
-	/**
-	 * @return revisions that are children of common entries, i.e. revisions that are present on the local server and not on remote.
-	 */
-	public List<Nodeid> getLocalOnlyRevisions() {
-		return localRepo.childrenOf(getCommon());
-	}
-	
-	/**
-	 * Similar to @link {@link #getLocalOnlyRevisions()}, use this one if you need access to changelog entry content, not 
-	 * only its revision number. 
-	 * @param inspector delegate to analyze changesets, shall not be <code>null</code>
-	 */
-	public void visitLocalOnlyRevisions(HgChangelog.Inspector inspector) {
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		// one can use localRepo.childrenOf(getCommon()) and then iterate over nodeids, but there seems to be
-		// another approach to get all changes after common:
-		// find index of earliest revision, and report all that were later
-		final HgChangelog changelog = localRepo.getRepo().getChangelog();
-		int earliestRevision = Integer.MAX_VALUE;
-		List<Nodeid> commonKnown = getCommon();
-		for (Nodeid n : commonKnown) {
-			if (!localRepo.hasChildren(n)) {
-				// there might be (old) nodes, known both locally and remotely, with no children
-				// hence, we don't need to consider their local revision number
-				continue;
-			}
-			int lr = changelog.getLocalRevision(n);
-			if (lr < earliestRevision) {
-				earliestRevision = lr;
-			}
-		}
-		if (earliestRevision == Integer.MAX_VALUE) {
-			// either there are no common nodes (known locally and at remote)
-			// or no local children found (local is up to date). In former case, perhaps I shall bit return silently,
-			// but check for possible wrong repo comparison (hs says 'repository is unrelated' if I try to 
-			// check in/out for a repo that has no common nodes.
-			return;
-		}
-		if (earliestRevision < 0 || earliestRevision >= changelog.getLastRevision()) {
-			throw new HgBadStateException(String.format("Invalid index of common known revision: %d in total of %d", earliestRevision, 1+changelog.getLastRevision()));
-		}
-		changelog.range(earliestRevision+1, changelog.getLastRevision(), inspector);
-	}
-
-	private List<Nodeid> findCommonWithRemote() throws HgException {
-		List<Nodeid> remoteHeads = remoteRepo.heads();
-		LinkedList<Nodeid> resultCommon = new LinkedList<Nodeid>(); // these remotes are known in local
-		LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common
-		for (Nodeid rh : remoteHeads) {
-			if (localRepo.knownNode(rh)) {
-				resultCommon.add(rh);
-			} else {
-				toQuery.add(rh);
-			}
-		}
-		if (toQuery.isEmpty()) {
-			return resultCommon; 
-		}
-		LinkedList<RemoteBranch> checkUp2Head = new LinkedList<RemoteBranch>(); // branch.root and branch.head are of interest only.
-		// these are branches with unknown head but known root, which might not be the last common known,
-		// i.e. there might be children changeset that are also available at remote, [..?..common-head..remote-head] - need to 
-		// scroll up to common head.
-		while (!toQuery.isEmpty()) {
-			List<RemoteBranch> remoteBranches = remoteRepo.branches(toQuery);	//head, root, first parent, second parent
-			toQuery.clear();
-			while(!remoteBranches.isEmpty()) {
-				RemoteBranch rb = remoteBranches.remove(0);
-				// I assume branches remote call gives branches with head equal to what I pass there, i.e.
-				// that I don't need to check whether rb.head is unknown.
-				if (localRepo.knownNode(rb.root)) {
-					// we known branch start, common head is somewhere in its descendants line  
-					checkUp2Head.add(rb);
-				} else {
-					// dig deeper in the history, if necessary
-					if (!NULL.equals(rb.p1) && !localRepo.knownNode(rb.p1)) {
-						toQuery.add(rb.p1);
-					}
-					if (!NULL.equals(rb.p2) && !localRepo.knownNode(rb.p2)) {
-						toQuery.add(rb.p2);
-					}
-				}
-			}
-		}
-		// can't check nodes between checkUp2Head element and local heads, remote might have distinct descendants sequence
-		for (RemoteBranch rb : checkUp2Head) {
-			// rb.root is known locally
-			List<Nodeid> remoteRevisions = remoteRepo.between(rb.head, rb.root);
-			if (remoteRevisions.isEmpty()) {
-				// head is immediate child
-				resultCommon.add(rb.root);
-			} else {
-				// between gives result from head to root, I'd like to go in reverse direction
-				Collections.reverse(remoteRevisions);
-				Nodeid root = rb.root;
-				while(!remoteRevisions.isEmpty()) {
-					Nodeid n = remoteRevisions.remove(0);
-					if (localRepo.knownNode(n)) {
-						if (remoteRevisions.isEmpty()) {
-							// this is the last known node before an unknown
-							resultCommon.add(n);
-							break;
-						}
-						if (remoteRevisions.size() == 1) {
-							// there's only one left between known n and unknown head
-							// this check is to save extra between query, not really essential
-							Nodeid last = remoteRevisions.remove(0);
-							resultCommon.add(localRepo.knownNode(last) ? last : n);
-							break;
-						}
-						// might get handy for next between query, to narrow search down
-						root = n;
-					} else {
-						remoteRevisions = remoteRepo.between(n, root);
-						Collections.reverse(remoteRevisions);
-						if (remoteRevisions.isEmpty()) {
-							resultCommon.add(root);
-						}
-					}
-				}
-			}
-		}
-		// TODO ensure unique elements in the list
-		return resultCommon;
-	}
-
-	// somewhat similar to Outgoing.findCommonWithRemote() 
-	public List<BranchChain> calculateMissingBranches() throws HgException {
-		List<Nodeid> remoteHeads = remoteRepo.heads();
-		LinkedList<Nodeid> common = new LinkedList<Nodeid>(); // these remotes are known in local
-		LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common
-		for (Nodeid rh : remoteHeads) {
-			if (localRepo.knownNode(rh)) {
-				common.add(rh);
-			} else {
-				toQuery.add(rh);
-			}
-		}
-		if (toQuery.isEmpty()) {
-			return Collections.emptyList(); // no incoming changes
-		}
-		LinkedList<BranchChain> branches2load = new LinkedList<BranchChain>(); // return value
-		// detailed comments are in Outgoing.findCommonWithRemote
-		LinkedList<RemoteBranch> checkUp2Head = new LinkedList<RemoteBranch>();
-		// records relation between branch head and its parent branch, if any
-		HashMap<Nodeid, BranchChain> head2chain = new HashMap<Nodeid, BranchChain>();
-		while (!toQuery.isEmpty()) {
-			List<RemoteBranch> remoteBranches = remoteRepo.branches(toQuery);	//head, root, first parent, second parent
-			toQuery.clear();
-			while(!remoteBranches.isEmpty()) {
-				RemoteBranch rb = remoteBranches.remove(0);
-				BranchChain chainElement = head2chain.get(rb.head);
-				if (chainElement == null) {
-					chainElement = new BranchChain(rb.head);
-					// record this unknown branch to download later
-					branches2load.add(chainElement);
-					// the only chance we'll need chainElement in the head2chain is when we know this branch's root 
-					head2chain.put(rb.head, chainElement);
-				}
-				if (localRepo.knownNode(rb.root)) {
-					// we known branch start, common head is somewhere in its descendants line  
-					checkUp2Head.add(rb);
-				} else {
-					chainElement.branchRoot = rb.root;
-					// dig deeper in the history, if necessary
-					boolean hasP1 = !NULL.equals(rb.p1), hasP2 = !NULL.equals(rb.p2);  
-					if (hasP1 && !localRepo.knownNode(rb.p1)) {
-						toQuery.add(rb.p1);
-						// we might have seen parent node already, and recorded it as a branch chain
-						// we shall reuse existing BC to get it completely initializer (head2chain map
-						// on second put with the same key would leave first BC uninitialized.
-						
-						// It seems there's no reason to be affraid (XXX although shall double-check)
-						// that BC's chain would get corrupt (its p1 and p2 fields assigned twice with different values)
-						// as parents are always the same (and likely, BC that is common would be the last unknown)
-						BranchChain bc = head2chain.get(rb.p1);
-						if (bc == null) {
-							head2chain.put(rb.p1, bc = new BranchChain(rb.p1));
-						}
-						chainElement.p1 = bc;
-					}
-					if (hasP2 && !localRepo.knownNode(rb.p2)) {
-						toQuery.add(rb.p2);
-						BranchChain bc = head2chain.get(rb.p2);
-						if (bc == null) {
-							head2chain.put(rb.p2, bc = new BranchChain(rb.p2));
-						}
-						chainElement.p2 = bc;
-					}
-					if (!hasP1 && !hasP2) {
-						// special case, when we do incoming against blank repository, chainElement.branchRoot
-						// is first unknown element (revision 0). We need to add another fake BranchChain
-						// to fill the promise that terminal BranchChain has branchRoot that is known both locally and remotely
-						BranchChain fake = new BranchChain(NULL);
-						fake.branchRoot = NULL;
-						chainElement.p1 = chainElement.p2 = fake;
-					}
-				}
-			}
-		}
-		for (RemoteBranch rb : checkUp2Head) {
-			Nodeid h = rb.head;
-			Nodeid r = rb.root;
-			int watchdog = 1000;
-			assert head2chain.containsKey(h);
-			BranchChain bc = head2chain.get(h);
-			assert bc != null : h.toString();
-			// if we know branch root locally, there could be no parent branch chain elements.
-			assert bc.p1 == null;
-			assert bc.p2 == null;
-			do {
-				List<Nodeid> between = remoteRepo.between(h, r);
-				if (between.isEmpty()) {
-					bc.branchRoot = r;
-					break;
-				} else {
-					Collections.reverse(between);
-					for (Nodeid n : between) {
-						if (localRepo.knownNode(n)) {
-							r = n;
-						} else {
-							h = n;
-							break;
-						}
-					}
-					Nodeid lastInBetween = between.get(between.size() - 1);
-					if (r.equals(lastInBetween)) {
-						bc.branchRoot = r;
-						break;
-					} else if (h.equals(lastInBetween)) { // the only chance for current head pointer to point to the sequence tail
-						// is when r is second from the between list end (iow, head,1,[2],4,8...,root)
-						bc.branchRoot = r;
-						break;
-					}
-				}
-			} while(--watchdog > 0);
-			if (watchdog == 0) {
-				throw new HgBadStateException(String.format("Can't narrow down branch [%s, %s]", rb.head.shortNotation(), rb.root.shortNotation()));
-			}
-		}
-		if (debug) {
-			System.out.println("calculateMissingBranches:");
-			for (BranchChain bc : branches2load) {
-				bc.dump();
-			}
-		}
-		return branches2load;
-	}
-
-	// root and head (and all between) are unknown for each chain element but last (terminal), which has known root (revision
-	// known to be locally and at remote server
-	// alternative would be to keep only unknown elements (so that promise of calculateMissingBranches would be 100% true), but that 
-	// seems to complicate the method, while being useful only for the case when we ask incoming for an empty repository (i.e.
-	// where branch chain return all nodes, -1..tip.
-	public static final class BranchChain {
-		// when we construct a chain, we know head which is missing locally, hence init it right away.
-		// as for root (branch unknown start), we might happen to have one locally, and need further digging to find out right branch start  
-		public final Nodeid branchHead;
-		public Nodeid branchRoot;
-		// either of these can be null, or both.
-		// although RemoteBranch has either both parents null, or both non-null, when we construct a chain
-		// we might encounter that we locally know one of branch's parent, hence in the chain corresponding field will be blank.
-		public BranchChain p1;
-		public BranchChain p2;
-
-		public BranchChain(Nodeid head) {
-			assert head != null;
-			branchHead = head;
-		}
-		public boolean isTerminal() {
-			return p1 == null && p2 == null; // either can be null, see comment above. Terminal is only when no way to descent
-		}
-		
-		// true when this BranchChain is a branch that spans up to very start of the repository
-		// Thus, the only common revision is NULL, recorded in a fake BranchChain object shared between p1 and p2
-		/*package-local*/ boolean isRepoStart() {
-			return p1 == p2 && p1 != null && p1.branchHead == p1.branchRoot && NULL.equals(p1.branchHead);
-		}
-
-		@Override
-		public String toString() {
-			return String.format("BranchChain [%s, %s]", branchRoot, branchHead);
-		}
-
-		void dump() {
-			System.out.println(toString());
-			internalDump("  ");
-		}
-
-		private void internalDump(String prefix) {
-			if (p1 != null) {
-				System.out.println(prefix + p1.toString());
-			} else if (p2 != null) {
-				System.out.println(prefix + "NONE?!");
-			}
-			if (p2 != null) {
-				System.out.println(prefix + p2.toString());
-			} else if (p1 != null) {
-				System.out.println(prefix + "NONE?!");
-			}
-			prefix += "  ";
-			if (p1 != null) {
-				p1.internalDump(prefix);
-			}
-			if (p2 != null) {
-				p2.internalDump(prefix);
-			}
-		}
-	}
-
-	/**
-	 * @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 {
-		class DataEntry {
-			public final Nodeid queryHead;
-			public final int headIndex;
-			public List<Nodeid> entries;
-
-			public DataEntry(Nodeid head, int index, List<Nodeid> data) {
-				queryHead = head;
-				headIndex = index;
-				entries = data;
-			}
-		};
-
-		List<Nodeid> initial = remoteRepo.between(branchHead, branchRoot);
-		Nodeid[] result = new Nodeid[1 + (1 << initial.size())];
-		result[0] = branchHead;
-		int rootIndex = -1; // index in the result, where to place branche's root.
-		if (initial.isEmpty()) {
-			rootIndex = 1;
-		} else if (initial.size() == 1) {
-			rootIndex = 2;
-		}
-		LinkedList<DataEntry> datas = new LinkedList<DataEntry>();
-		// DataEntry in datas has entries list filled with 'between' data, whereas 
-		// DataEntry in toQuery keeps only nodeid and its index, with entries to be initialized before 
-		// moving to datas. 
-		LinkedList<DataEntry> toQuery = new LinkedList<DataEntry>();
-		//
-		datas.add(new DataEntry(branchHead, 0, initial));
-		int totalQueries = 1;
-		HashSet<Nodeid> queried = new HashSet<Nodeid>();
-		while(!datas.isEmpty()) {
-			// keep record of those planned to be queried next time we call between()
-			// although may keep these in queried, if really don't want separate collection
-			HashSet<Nodeid> scheduled = new HashSet<Nodeid>();  
-			do {
-				DataEntry de = datas.removeFirst();
-				// populate result with discovered elements between de.qiueryRoot and branch's head
-				for (int i = 1, j = 0; j < de.entries.size(); i = i << 1, j++) {
-					int idx = de.headIndex + i;
-					result[idx] = de.entries.get(j);
-				}
-				// form next query entries from new unknown elements
-				if (de.entries.size() > 1) {
-					/* when entries has only one element, it means de.queryRoot was at head-2 position, and thus
-					 * no new information can be obtained. E.g. when it's 2, it might be case of [0..4] query with
-					 * [1,2] result, and we need one more query to get element 3.   
-					 */
-					for (int i =1, j = 0; j < de.entries.size(); i = i<<1, j++) {
-						int idx = de.headIndex + i;
-						Nodeid x = de.entries.get(j);
-						if (!queried.contains(x) && !scheduled.contains(x) && (rootIndex == -1 || rootIndex - de.headIndex > 1)) {
-							/*queries for elements right before head is senseless, but unless we know head's index, do it anyway*/
-							toQuery.add(new DataEntry(x, idx, null));
-							scheduled.add(x);
-						}
-					}
-				}
-			} while (!datas.isEmpty());
-			if (!toQuery.isEmpty()) {
-				totalQueries++;
-			}
-			// for each query, create an between request range, keep record Range->DataEntry to know range's start index  
-			LinkedList<HgRemoteRepository.Range> betweenBatch = new LinkedList<HgRemoteRepository.Range>();
-			HashMap<HgRemoteRepository.Range, DataEntry> rangeToEntry = new HashMap<HgRemoteRepository.Range, DataEntry>();
-			for (DataEntry de : toQuery) {
-				queried.add(de.queryHead);
-				HgRemoteRepository.Range r = new HgRemoteRepository.Range(branchRoot, de.queryHead);
-				betweenBatch.add(r);
-				rangeToEntry.put(r, de);
-			}
-			if (!betweenBatch.isEmpty()) {
-				Map<Range, List<Nodeid>> between = remoteRepo.between(betweenBatch);
-				for (Entry<Range, List<Nodeid>> e : between.entrySet()) {
-					DataEntry de = rangeToEntry.get(e.getKey());
-					assert de != null;
-					de.entries = e.getValue();
-					if (rootIndex == -1 && de.entries.size() == 1) {
-						// returned sequence of length 1 means we used element from [head-2] as root
-						int numberOfElementsExcludingRootAndHead = de.headIndex + 1;
-						rootIndex = numberOfElementsExcludingRootAndHead + 1;
-						if (debug) {
-							System.out.printf("On query %d found out exact number of missing elements: %d\n", totalQueries, numberOfElementsExcludingRootAndHead);
-						}
-					}
-					datas.add(de); // queue up to record result and construct further requests
-				}
-				betweenBatch.clear();
-				rangeToEntry.clear();
-			}
-			toQuery.clear();
-		}
-		if (rootIndex == -1) {
-			throw new HgBadStateException("Shall not happen, provided between output is correct"); // FIXME
-		}
-		result[rootIndex] = branchRoot;
-		boolean resultOk = true;
-		LinkedList<Nodeid> fromRootToHead = new LinkedList<Nodeid>();
-		for (int i = 0; i <= rootIndex; i++) {
-			Nodeid n = result[i];
-			if (n == null) {
-				System.out.printf("ERROR: element %d wasn't found\n",i);
-				resultOk = false;
-			}
-			fromRootToHead.addFirst(n); // reverse order
-		}
-		if (debug) {
-			System.out.println("Total queries:" + totalQueries);
-		}
-		if (!resultOk) {
-			throw new HgBadStateException("See console for details"); // FIXME
-		}
-		return fromRootToHead;
-	}
-
-	/**
-	 *  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 {
-		if (bc == null) {
-			return Collections.emptyList();
-		}
-		List<Nodeid> mine = completeBranch(bc.branchRoot, bc.branchHead);
-		if (bc.isTerminal() || bc.isRepoStart()) {
-			return mine;
-		}
-		List<Nodeid> parentBranch1 = visitBranches(bc.p1);
-		List<Nodeid> parentBranch2 = visitBranches(bc.p2);
-		// merge
-		LinkedList<Nodeid> merged = new LinkedList<Nodeid>();
-		ListIterator<Nodeid> i1 = parentBranch1.listIterator(), i2 = parentBranch2.listIterator();
-		while (i1.hasNext() && i2.hasNext()) {
-			Nodeid n1 = i1.next();
-			Nodeid n2 = i2.next();
-			if (n1.equals(n2)) {
-				merged.addLast(n1);
-			} else {
-				// first different => add both, and continue adding both tails sequentially 
-				merged.add(n2);
-				merged.add(n1);
-				break;
-			}
-		}
-		// copy rest of second parent branch
-		while (i2.hasNext()) {
-			merged.add(i2.next());
-		}
-		// copy rest of first parent branch
-		while (i1.hasNext()) {
-			merged.add(i1.next());
-		}
-		//
-		ArrayList<Nodeid> rv = new ArrayList<Nodeid>(mine.size() + merged.size());
-		rv.addAll(merged);
-		rv.addAll(mine);
-		return rv;
-	}
-
-	public void collectKnownRoots(BranchChain bc, Set<Nodeid> result) {
-		if (bc == null) {
-			return;
-		}
-		if (bc.isTerminal()) {
-			result.add(bc.branchRoot);
-			return;
-		}
-		if (bc.isRepoStart()) {
-			return;
-		}
-		collectKnownRoots(bc.p1, result);
-		collectKnownRoots(bc.p2, result);
-	}
-}
--- a/src/org/tmatesoft/hg/internal/RequiresFile.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * 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.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class RequiresFile {
-	public static final int STORE = 1;
-	public static final int FNCACHE = 2;
-	public static final int DOTENCODE = 4;
-	
-	public RequiresFile() {
-	}
-
-	public void parse(Internals repoImpl, File requiresFile) {
-		if (!requiresFile.exists()) {
-			return;
-		}
-		try {
-			boolean revlogv1 = false;
-			boolean store = false;
-			boolean fncache = false;
-			boolean dotencode = false;
-			BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(requiresFile)));
-			String line;
-			while ((line = br.readLine()) != null) {
-				revlogv1 |= "revlogv1".equals(line);
-				store |= "store".equals(line);
-				fncache |= "fncache".equals(line);
-				dotencode |= "dotencode".equals(line);
-			}
-			int flags = 0;
-			flags += store ? STORE : 0;
-			flags += fncache ? FNCACHE : 0;
-			flags += dotencode ? DOTENCODE : 0;
-			repoImpl.setStorageConfig(revlogv1 ? 1 : 0, flags);
-		} catch (IOException ex) {
-			ex.printStackTrace(); // FIXME log
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/internal/RevlogDump.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2010-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.BufferedInputStream;
-import java.io.DataInput;
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.math.BigInteger;
-import java.util.zip.Inflater;
-
-/**
- * Utility to test/debug/troubleshoot
- *  
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class RevlogDump {
-
-	/**
-	 * Takes 3 command line arguments - 
-	 *   repository path, 
-	 *   path to index file (i.e. store/data/hello.c.i) in the repository (relative) 
-	 *   and "dumpData" whether to print actual content or just revlog headers 
-	 */
-	public static void main(String[] args) throws Exception {
-		String repo = "/temp/hg/hello/.hg/";
-		String filename = "store/00changelog.i";
-//		String filename = "store/data/hello.c.i";
-//		String filename = "store/data/docs/readme.i";
-		boolean dumpData = true;
-		if (args.length > 1) {
-			repo = args[0];
-			filename = args[1];
-			dumpData = args.length > 2 ? "dumpData".equals(args[2]) : false;
-		}
-		//
-		DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File(repo + filename))));
-		DataInput di = dis;
-		dis.mark(10);
-		int versionField = di.readInt();
-		dis.reset();
-		final int INLINEDATA = 1 << 16;
-		
-		boolean inlineData = (versionField & INLINEDATA) != 0;
-		System.out.printf("%#8x, inline: %b\n", versionField, inlineData);
-		System.out.println("Index    Offset      Flags     Packed     Actual   Base Rev   Link Rev  Parent1  Parent2     nodeid");
-		int entryCount = 0;
-		while (dis.available() > 0) {
-			long l = di.readLong();
-			long offset = l >>> 16;
-			int flags = (int) (l & 0X0FFFF);
-			int compressedLen = di.readInt();
-			int actualLen = di.readInt();
-			int baseRevision = di.readInt();
-			int linkRevision = di.readInt();
-			int parent1Revision = di.readInt();
-			int parent2Revision = di.readInt();
-			byte[] buf = new byte[32];
-			di.readFully(buf, 12, 20);
-			dis.skipBytes(12); 
-			// CAN'T USE skip() here without extra precautions. E.g. I ran into situation when 
-			// buffer was 8192 and BufferedInputStream was at position 8182 before attempt to skip(12). 
-			// BIS silently skips available bytes and leaves me two extra bytes that ruin the rest of the code.
-			System.out.printf("%4d:%14d %6X %10d %10d %10d %10d %8d %8d     %040x\n", entryCount, offset, flags, compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, new BigInteger(buf));
-			if (inlineData) {
-				String resultString;
-				byte[] data = new byte[compressedLen];
-				di.readFully(data);
-				if (data[0] == 0x78 /* 'x' */) {
-					Inflater zlib = new Inflater();
-					zlib.setInput(data, 0, compressedLen);
-					byte[] result = new byte[actualLen*2];
-					int resultLen = zlib.inflate(result);
-					zlib.end();
-					resultString = new String(result, 0, resultLen, "UTF-8");
-				} else if (data[0] == 0x75 /* 'u' */) {
-					resultString = new String(data, 1, data.length - 1, "UTF-8");
-				} else {
-					resultString = new String(data);
-				}
-				if (dumpData) { 
-					System.out.println(resultString);
-				}
-			}
-			entryCount++;
-		}
-		dis.close();
-		//
-	}
-}
--- a/src/org/tmatesoft/hg/internal/RevlogStream.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,460 +0,0 @@
-/*
- * Copyright (c) 2010-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 static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.HgRepository;
-
-
-/**
- * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), 
- * or numerous RevlogStream with separate representation of the underlying data (cached, lazy ChunkStream)?
- * 
- * @see http://mercurial.selenic.com/wiki/Revlog
- * @see http://mercurial.selenic.com/wiki/RevlogNG
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class RevlogStream {
-
-	/*
-	 * makes sense for index with inline data only - actual offset of the record in the .i file (record entry + revision * record size))
-	 * 
-	 * long[] in fact (there are 8-bytes field in the revlog)
-	 * However, (a) DataAccess currently doesn't operate with long seek/length
-	 * and, of greater significance, (b) files with inlined data are designated for smaller files,  
-	 * guess, about 130 Kb, and offset there won't ever break int capacity
-	 */
-	private int[] indexRecordOffset;  
-	private int[] baseRevisions;
-	private boolean inline = false;
-	private final File indexFile;
-	private final DataAccessProvider dataAccess;
-
-	// if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP.
-	public RevlogStream(DataAccessProvider dap, File indexFile) {
-		this.dataAccess = dap;
-		this.indexFile = indexFile;
-	}
-
-	/*package*/ DataAccess getIndexStream() {
-		return dataAccess.create(indexFile);
-	}
-
-	/*package*/ DataAccess getDataStream() {
-		final String indexName = indexFile.getName();
-		File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d");
-		return dataAccess.create(dataFile);
-	}
-	
-	public int revisionCount() {
-		initOutline();
-		return baseRevisions.length;
-	}
-	
-	public int dataLength(int revision) {
-		// XXX in fact, use of iterate() instead of this implementation may be quite reasonable.
-		//
-		final int indexSize = revisionCount();
-		DataAccess daIndex = getIndexStream(); // XXX may supply a hint that I'll need really few bytes of data (although at some offset)
-		if (revision == TIP) {
-			revision = indexSize - 1;
-		}
-		try {
-			int recordOffset = getIndexOffsetInt(revision);
-			daIndex.seek(recordOffset + 12); // 6+2+4
-			int actualLen = daIndex.readInt();
-			return actualLen; 
-		} catch (IOException ex) {
-			ex.printStackTrace(); // log error. FIXME better handling
-			throw new IllegalStateException(ex);
-		} finally {
-			daIndex.done();
-		}
-	}
-	
-	public byte[] nodeid(int revision) {
-		final int indexSize = revisionCount();
-		if (revision == TIP) {
-			revision = indexSize - 1;
-		}
-		if (revision < 0 || revision >= indexSize) {
-			throw new IllegalArgumentException(Integer.toString(revision));
-		}
-		DataAccess daIndex = getIndexStream();
-		try {
-			int recordOffset = getIndexOffsetInt(revision);
-			daIndex.seek(recordOffset + 32);
-			byte[] rv = new byte[20];
-			daIndex.readBytes(rv, 0, 20);
-			return rv;
-		} catch (IOException ex) {
-			ex.printStackTrace();
-			throw new IllegalStateException();
-		} finally {
-			daIndex.done();
-		}
-	}
-	
-	public int linkRevision(int revision) {
-		final int last = revisionCount() - 1;
-		if (revision == TIP) {
-			revision = last;
-		}
-		if (revision < 0 || revision > last) {
-			throw new IllegalArgumentException(Integer.toString(revision));
-		}
-		DataAccess daIndex = getIndexStream();
-		try {
-			int recordOffset = getIndexOffsetInt(revision);
-			daIndex.seek(recordOffset + 20);
-			int linkRev = daIndex.readInt();
-			return linkRev;
-		} catch (IOException ex) {
-			ex.printStackTrace();
-			throw new IllegalStateException();
-		} finally {
-			daIndex.done();
-		}
-	}
-	
-	// Perhaps, RevlogStream should be limited to use of plain int revisions for access,
-	// while Nodeids should be kept on the level up, in Revlog. Guess, Revlog better keep
-	// map of nodeids, and once this comes true, we may get rid of this method.
-	// Unlike its counterpart, {@link Revlog#getLocalRevisionNumber()}, doesn't fail with exception if node not found,
-	/**
-	 * @return integer in [0..revisionCount()) or {@link HgRepository#BAD_REVISION} if not found
-	 */
-	public int findLocalRevisionNumber(Nodeid nodeid) {
-		// XXX this one may be implemented with iterate() once there's mechanism to stop iterations
-		final int indexSize = revisionCount();
-		DataAccess daIndex = getIndexStream();
-		try {
-			byte[] nodeidBuf = new byte[20];
-			for (int i = 0; i < indexSize; i++) {
-				daIndex.skip(8);
-				int compressedLen = daIndex.readInt();
-				daIndex.skip(20);
-				daIndex.readBytes(nodeidBuf, 0, 20);
-				if (nodeid.equalsTo(nodeidBuf)) {
-					return i;
-				}
-				daIndex.skip(inline ? 12 + compressedLen : 12);
-			}
-		} catch (IOException ex) {
-			ex.printStackTrace(); // log error. FIXME better handling
-			throw new IllegalStateException(ex);
-		} finally {
-			daIndex.done();
-		}
-		return BAD_REVISION;
-	}
-
-
-	private final int REVLOGV1_RECORD_SIZE = 64;
-
-	// should be possible to use TIP, ALL, or -1, -2, -n notation of Hg
-	// ? boolean needsNodeid
-	public void iterate(int start, int end, boolean needData, Inspector inspector) {
-		initOutline();
-		final int indexSize = revisionCount();
-		if (indexSize == 0) {
-			return;
-		}
-		if (end == TIP) {
-			end = indexSize - 1;
-		}
-		if (start == TIP) {
-			start = indexSize - 1;
-		}
-		if (start < 0 || start >= indexSize) {
-			throw new IllegalArgumentException(String.format("Bad left range boundary %d in [0..%d]", start, indexSize-1));
-		}
-		if (end < start || end >= indexSize) {
-			throw new IllegalArgumentException(String.format("Bad right range boundary %d in [0..%d]", end, indexSize-1));
-		}
-		// XXX may cache [start .. end] from index with a single read (pre-read)
-		
-		DataAccess daIndex = null, daData = null;
-		daIndex = getIndexStream();
-		if (needData && !inline) {
-			daData = getDataStream();
-		}
-		try {
-			byte[] nodeidBuf = new byte[20];
-			DataAccess lastUserData = null;
-			int i;
-			boolean extraReadsToBaseRev = false;
-			if (needData && getBaseRevision(start) < start) {
-				i = getBaseRevision(start);
-				extraReadsToBaseRev = true;
-			} else {
-				i = start;
-			}
-			
-			daIndex.seek(getIndexOffsetInt(i));
-			for (; i <= end; i++ ) {
-				if (inline && needData) {
-					// inspector reading data (though FilterDataAccess) may have affected index position
-					daIndex.seek(getIndexOffsetInt(i));
-				}
-				long l = daIndex.readLong(); // 0
-				long offset = i == 0 ? 0 : (l >>> 16);
-				@SuppressWarnings("unused")
-				int flags = (int) (l & 0X0FFFF);
-				int compressedLen = daIndex.readInt(); // +8
-				int actualLen = daIndex.readInt(); // +12
-				int baseRevision = daIndex.readInt(); // +16
-				int linkRevision = daIndex.readInt(); // +20
-				int parent1Revision = daIndex.readInt();
-				int parent2Revision = daIndex.readInt();
-				// Hg has 32 bytes here, uses 20 for nodeid, and keeps 12 last bytes empty
-				daIndex.readBytes(nodeidBuf, 0, 20); // +32
-				daIndex.skip(12);
-				DataAccess userDataAccess = null;
-				if (needData) {
-					final byte firstByte;
-					int streamOffset;
-					DataAccess streamDataAccess;
-					if (inline) {
-						streamDataAccess = daIndex;
-						streamOffset = getIndexOffsetInt(i) + REVLOGV1_RECORD_SIZE; // don't need to do seek as it's actual position in the index stream
-					} else {
-						streamOffset = (int) offset;
-						streamDataAccess = daData;
-						daData.seek(streamOffset);
-					}
-					final boolean patchToPrevious = baseRevision != i; // the only way I found to tell if it's a patch
-					firstByte = streamDataAccess.readByte();
-					if (firstByte == 0x78 /* 'x' */) {
-						userDataAccess = new InflaterDataAccess(streamDataAccess, streamOffset, compressedLen, patchToPrevious ? -1 : actualLen);
-					} else if (firstByte == 0x75 /* 'u' */) {
-						userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset+1, compressedLen-1);
-					} else {
-						// XXX Python impl in fact throws exception when there's not 'x', 'u' or '0'
-						// but I don't see reason not to return data as is 
-						userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset, compressedLen);
-					}
-					// XXX 
-					if (patchToPrevious) {
-						// this is a patch
-						LinkedList<PatchRecord> patches = new LinkedList<PatchRecord>();
-						while (!userDataAccess.isEmpty()) {
-							PatchRecord pr = PatchRecord.read(userDataAccess);
-//							System.out.printf("PatchRecord:%d %d %d\n", pr.start, pr.end, pr.len);
-							patches.add(pr);
-						}
-						userDataAccess.done();
-						//
-						byte[] userData = apply(lastUserData, actualLen, patches);
-						userDataAccess = new ByteArrayDataAccess(userData);
-					}
-				} else {
-					if (inline) {
-						daIndex.skip(compressedLen);
-					}
-				}
-				if (!extraReadsToBaseRev || i >= start) {
-					inspector.next(i, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeidBuf, userDataAccess);
-				}
-				if (userDataAccess != null) {
-					userDataAccess.reset();
-					if (lastUserData != null) {
-						lastUserData.done();
-					}
-					lastUserData = userDataAccess;
-				}
-			}
-		} catch (IOException ex) {
-			throw new HgBadStateException(ex); // FIXME need better handling
-		} finally {
-			daIndex.done();
-			if (daData != null) {
-				daData.done();
-			}
-		}
-	}
-
-	private int getBaseRevision(int revision) {
-		return baseRevisions[revision];
-	}
-
-	/**
-	 * @return  offset of the revision's record in the index (.i) stream
-	 */
-	private int getIndexOffsetInt(int revision) {
-		return inline ? indexRecordOffset[revision] : revision * REVLOGV1_RECORD_SIZE;
-	}
-
-	private void initOutline() {
-		if (baseRevisions != null && baseRevisions.length > 0) {
-			return;
-		}
-		ArrayList<Integer> resBases = new ArrayList<Integer>();
-		ArrayList<Integer> resOffsets = new ArrayList<Integer>();
-		DataAccess da = getIndexStream();
-		try {
-			if (da.isEmpty()) {
-				// do not fail with exception if stream is empty, it's likely intentional
-				baseRevisions = new int[0];
-				return;
-			}
-			int versionField = da.readInt();
-			da.readInt(); // just to skip next 4 bytes of offset + flags
-			final int INLINEDATA = 1 << 16;
-			inline = (versionField & INLINEDATA) != 0;
-			long offset = 0; // first offset is always 0, thus Hg uses it for other purposes
-			while(true) {
-				int compressedLen = da.readInt();
-				// 8+4 = 12 bytes total read here
-				@SuppressWarnings("unused")
-				int actualLen = da.readInt();
-				int baseRevision = da.readInt();
-				// 12 + 8 = 20 bytes read here
-//				int linkRevision = di.readInt();
-//				int parent1Revision = di.readInt();
-//				int parent2Revision = di.readInt();
-//				byte[] nodeid = new byte[32];
-				resBases.add(baseRevision);
-				if (inline) {
-					int o = (int) offset;
-					if (o != offset) {
-						// just in case, can't happen, ever, unless HG (or some other bad tool) produces index file 
-						// with inlined data of size greater than 2 Gb.
-						throw new HgBadStateException("Data too big, offset didn't fit to sizeof(int)");
-					}
-					resOffsets.add(o + REVLOGV1_RECORD_SIZE * resOffsets.size());
-					da.skip(3*4 + 32 + compressedLen); // Check: 44 (skip) + 20 (read) = 64 (total RevlogNG record size)
-				} else {
-					da.skip(3*4 + 32);
-				}
-				if (da.isEmpty()) {
-					// fine, done then
-					baseRevisions = toArray(resBases);
-					if (inline) {
-						indexRecordOffset = toArray(resOffsets);
-					}
-					break;
-				} else {
-					// start reading next record
-					long l = da.readLong();
-					offset = l >>> 16;
-				}
-			}
-		} catch (IOException ex) {
-			ex.printStackTrace(); // log error
-			// too bad, no outline then, but don't fail with NPE
-			baseRevisions = new int[0];
-		} finally {
-			da.done();
-		}
-	}
-	
-	private static int[] toArray(List<Integer> l) {
-		int[] rv = new int[l.size()];
-		for (int i = 0; i < rv.length; i++) {
-			rv[i] = l.get(i);
-		}
-		return rv;
-	}
-	
-
-	// mpatch.c : apply()
-	// FIXME need to implement patch merge (fold, combine, gather and discard from aforementioned mpatch.[c|py]), also see Revlog and Mercurial PDF
-	public/*for HgBundle; until moved to better place*/static byte[] apply(DataAccess baseRevisionContent, int outcomeLen, List<PatchRecord> patch) throws IOException {
-		int last = 0, destIndex = 0;
-		if (outcomeLen == -1) {
-			outcomeLen = baseRevisionContent.length();
-			for (PatchRecord pr : patch) {
-				outcomeLen += pr.start - last + pr.len;
-				last = pr.end;
-			}
-			outcomeLen -= last;
-			last = 0;
-		}
-		byte[] rv = new byte[outcomeLen];
-		for (PatchRecord pr : patch) {
-			baseRevisionContent.seek(last);
-			baseRevisionContent.readBytes(rv, destIndex, pr.start-last);
-			destIndex += pr.start - last;
-			System.arraycopy(pr.data, 0, rv, destIndex, pr.data.length);
-			destIndex += pr.data.length;
-			last = pr.end;
-		}
-		baseRevisionContent.seek(last);
-		baseRevisionContent.readBytes(rv, destIndex, (int) (baseRevisionContent.length() - last));
-		return rv;
-	}
-
-	// @see http://mercurial.selenic.com/wiki/BundleFormat, in Changelog group description
-	public static class PatchRecord {
-		/*
-		   Given there are pr1 and pr2:
-		     pr1.start to pr1.end will be replaced with pr's data (of pr1.len)
-		     pr1.end to pr2.start gets copied from base
-		 */
-		public int start, end, len;
-		public byte[] data;
-
-		// TODO consider PatchRecord that only records data position (absolute in data source), and acquires data as needed 
-		private PatchRecord(int p1, int p2, int length, byte[] src) {
-			start = p1;
-			end = p2;
-			len = length;
-			data = src;
-		}
-
-		/*package-local*/ static PatchRecord read(byte[] data, int offset) {
-			final int x = offset; // shorthand
-			int p1 =  ((data[x] & 0xFF)<< 24)    | ((data[x+1] & 0xFF) << 16) | ((data[x+2] & 0xFF) << 8)  | (data[x+3] & 0xFF);
-			int p2 =  ((data[x+4] & 0xFF) << 24) | ((data[x+5] & 0xFF) << 16) | ((data[x+6] & 0xFF) << 8)  | (data[x+7] & 0xFF);
-			int len = ((data[x+8] & 0xFF) << 24) | ((data[x+9] & 0xFF) << 16) | ((data[x+10] & 0xFF) << 8) | (data[x+11] & 0xFF);
-			byte[] dataCopy = new byte[len];
-			System.arraycopy(data, x+12, dataCopy, 0, len);
-			return new PatchRecord(p1, p2, len, dataCopy);
-		}
-
-		public /*for HgBundle*/ static PatchRecord read(DataAccess da) throws IOException {
-			int p1 = da.readInt();
-			int p2 = da.readInt();
-			int len = da.readInt();
-			byte[] src = new byte[len];
-			da.readBytes(src, 0, len);
-			return new PatchRecord(p1, p2, len, src);
-		}
-	}
-
-	// FIXME byte[] data might be too expensive, for few usecases it may be better to have intermediate Access object (when we don't need full data 
-	// instantly - e.g. calculate hash, or comparing two revisions
-	public interface Inspector {
-		// XXX boolean retVal to indicate whether to continue?
-		// TODO specify nodeid and data length, and reuse policy (i.e. if revlog stream doesn't reuse nodeid[] for each call)
-		// implementers shall not invoke DataAccess.done(), it's accomplished by #iterate at appropraite moment
-		void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data);
-	}
-}
--- a/src/org/tmatesoft/hg/internal/StoragePathHelper.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-/*
- * 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.util.Arrays;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- * @see http://mercurial.selenic.com/wiki/CaseFoldingPlan
- * @see http://mercurial.selenic.com/wiki/fncacheRepoFormat
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-class StoragePathHelper implements PathRewrite {
-	
-	private final boolean store;
-	private final boolean fncache;
-	private final boolean dotencode;
-
-	public StoragePathHelper(boolean isStore, boolean isFncache, boolean isDotencode) {
-		store = isStore;
-		fncache = isFncache;
-		dotencode = isDotencode;
-	}
-
-	// FIXME document what path argument is, whether it includes .i or .d, and whether it's 'normalized' (slashes) or not.
-	// since .hg/store keeps both .i files and files without extension (e.g. fncache), guees, for data == false 
-	// we shall assume path has extension
-	public String rewrite(String path) {
-		final String STR_STORE = "store/";
-		final String STR_DATA = "data/";
-		final String STR_DH = "dh/";
-		final String reservedChars = "\\:*?\"<>|";
-		char[] hexByte = new char[2];
-		
-		path = path.replace(".hg/", ".hg.hg/").replace(".i/", ".i.hg/").replace(".d/", ".d.hg/");
-		StringBuilder sb = new StringBuilder(path.length() << 1);
-		if (store || fncache) {
-			// encodefilename
-			for (int i = 0; i < path.length(); i++) {
-				final char ch = path.charAt(i);
-				if (ch >= 'a' && ch <= 'z') {
-					sb.append(ch); // POIRAE
-				} else if (ch >= 'A' && ch <= 'Z') {
-					sb.append('_');
-					sb.append(Character.toLowerCase(ch)); // Perhaps, (char) (((int) ch) + 32)? Even better, |= 0x20? 
-				} else if (reservedChars.indexOf(ch) != -1) {
-					sb.append('~');
-					sb.append(toHexByte(ch, hexByte));
-				} else if ((ch >= '~' /*126*/ && ch <= 255) || ch < ' ' /*32*/) {
-					sb.append('~');
-					sb.append(toHexByte(ch, hexByte));
-				} else if (ch == '_') {
-					sb.append('_');
-					sb.append('_');
-				} else {
-					sb.append(ch);
-				}
-			}
-			// auxencode
-			if (fncache) {
-				encodeWindowsDeviceNames(sb);
-			}
-		}
-		final int MAX_PATH_LEN = 120;
-		if (fncache && (sb.length() + STR_DATA.length() + ".i".length() > MAX_PATH_LEN)) {
-			String digest = new DigestHelper().sha1(STR_DATA, path, ".i").asHexString();
-			final int DIR_PREFIX_LEN = 8;
-			 // not sure why (-4) is here. 120 - 40 = up to 80 for path with ext. dh/ + ext(.i) = 3+2
-			final int MAX_DIR_PREFIX = 8 * (DIR_PREFIX_LEN + 1) - 4;
-			sb = new StringBuilder(MAX_PATH_LEN);
-			for (int i = 0; i < path.length(); i++) {
-				final char ch = path.charAt(i);
-				if (ch >= 'a' && ch <= 'z') {
-					sb.append(ch);
-				} else if (ch >= 'A' && ch <= 'Z') {
-					sb.append((char) (ch | 0x20)); // lowercase 
-				} else if (reservedChars.indexOf(ch) != -1) {
-					sb.append('~');
-					sb.append(toHexByte(ch, hexByte));
-				} else if ((ch >= '~' /*126*/ && ch <= 255) || ch < ' ' /*32*/) {
-					sb.append('~');
-					sb.append(toHexByte(ch, hexByte));
-				} else {
-					sb.append(ch);
-				}
-			}
-			encodeWindowsDeviceNames(sb);
-			int fnameStart = sb.lastIndexOf("/"); // since we rewrite file names, it never ends with slash (for dirs, I'd pass length-2);
-			StringBuilder completeHashName = new StringBuilder(MAX_PATH_LEN);
-			completeHashName.append(STR_STORE);
-			completeHashName.append(STR_DH);
-			if (fnameStart == -1) {
-				// no dirs, just long filename
-				sb.setLength(MAX_PATH_LEN - 40 /*digest.length()*/ - STR_DH.length() - ".i".length());
-				completeHashName.append(sb);
-			} else {
-				StringBuilder sb2 = new StringBuilder(MAX_PATH_LEN);
-				int x = 0;
-				do {
-					int i = sb.indexOf("/", x);
-					final int sb2Len = sb2.length(); 
-					if (i-x <= DIR_PREFIX_LEN) { // a b c d e f g h /
-						sb2.append(sb, x, i + 1); // with slash
-					} else {
-						sb2.append(sb, x, x + DIR_PREFIX_LEN);
-						// may unexpectedly end with bad character
-						final int last = sb2.length()-1;
-						char lastChar = sb2.charAt(last); 
-						assert lastChar == sb.charAt(x + DIR_PREFIX_LEN - 1);
-						if (lastChar == '.' || lastChar == ' ') {
-							sb2.setCharAt(last, '_');
-						}
-						sb2.append('/');
-					}
-					if (sb2.length()-1 > MAX_DIR_PREFIX) {
-						sb2.setLength(sb2Len); // strip off last segment, it's too much
-						break;
-					}
-					x = i+1; 
-				} while (x < fnameStart);
-				assert sb2.charAt(sb2.length() - 1) == '/';
-				int left = MAX_PATH_LEN - sb2.length() - 40 /*digest.length()*/ - STR_DH.length() - ".i".length();
-				assert left >= 0;
-				fnameStart++; // move from / to actual name
-				sb2.append(sb, fnameStart, fnameStart + left > sb.length() ? sb.length() : fnameStart+left);
-				completeHashName.append(sb2);
-			}
-			completeHashName.append(digest);
-			sb = completeHashName;
-		} else if (store) {
-			sb.insert(0, STR_STORE + STR_DATA);
-		}
-		sb.append(".i");
-		return sb.toString();
-	}
-	
-	private void encodeWindowsDeviceNames(StringBuilder sb) {
-		char[] hexByte = new char[2];
-		int x = 0; // last segment start
-		final TreeSet<String> windowsReservedFilenames = new TreeSet<String>();
-		windowsReservedFilenames.addAll(Arrays.asList("con prn aux nul com1 com2 com3 com4 com5 com6 com7 com8 com9 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9".split(" "))); 
-		do {
-			int i = sb.indexOf("/", x);
-			if (i == -1) {
-				i = sb.length();
-			}
-			// windows reserved filenames are at least of length 3 
-			if (i - x >= 3) {
-				boolean found = false;
-				if (i-x == 3 || i-x == 4) {
-					found = windowsReservedFilenames.contains(sb.subSequence(x, i));
-				} else if (sb.charAt(x+3) == '.') { // implicit i-x > 3
-					found = windowsReservedFilenames.contains(sb.subSequence(x, x+3));
-				} else if (i-x > 4 && sb.charAt(x+4) == '.') {
-					found = windowsReservedFilenames.contains(sb.subSequence(x, x+4));
-				}
-				if (found) {
-					sb.insert(x+3, toHexByte(sb.charAt(x+2), hexByte));
-					sb.setCharAt(x+2, '~');
-					i += 2;
-				}
-			}
-			if (dotencode && (sb.charAt(x) == '.' || sb.charAt(x) == ' ')) {
-				sb.insert(x+1, toHexByte(sb.charAt(x), hexByte));
-				sb.setCharAt(x, '~'); // setChar *after* charAt/insert to get ~2e, not ~7e for '.'
-				i += 2;
-			}
-			x = i+1;
-		} while (x < sb.length());
-	}
-
-	private static char[] toHexByte(int ch, char[] buf) {
-		assert buf.length > 1;
-		final String hexDigits = "0123456789abcdef";
-		buf[0] = hexDigits.charAt((ch & 0x00F0) >>> 4);
-		buf[1] = hexDigits.charAt(ch & 0x0F);
-		return buf;
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgBundle.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,429 +0,0 @@
-/*
- * 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.repo;
-
-import static org.tmatesoft.hg.core.Nodeid.NULL;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.ByteArrayChannel;
-import org.tmatesoft.hg.internal.ByteArrayDataAccess;
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.DataAccessProvider;
-import org.tmatesoft.hg.internal.DigestHelper;
-import org.tmatesoft.hg.internal.InflaterDataAccess;
-import org.tmatesoft.hg.internal.RevlogStream;
-import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
-import org.tmatesoft.hg.util.CancelledException;
-
-/**
- * @see http://mercurial.selenic.com/wiki/BundleFormat
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgBundle {
-
-	private final File bundleFile;
-	private final DataAccessProvider accessProvider;
-
-	HgBundle(DataAccessProvider dap, File bundle) {
-		accessProvider = dap;
-		bundleFile = bundle;
-	}
-
-	private DataAccess getDataStream() throws IOException {
-		DataAccess da = accessProvider.create(bundleFile);
-		byte[] signature = new byte[6];
-		if (da.length() > 6) {
-			da.readBytes(signature, 0, 6);
-			if (signature[0] == 'H' && signature[1] == 'G' && signature[2] == '1' && signature[3] == '0') {
-				if (signature[4] == 'G' && signature[5] == 'Z') {
-					return new InflaterDataAccess(da, 6, da.length() - 6);
-				}
-				if (signature[4] == 'B' && signature[5] == 'Z') {
-					throw HgRepository.notImplemented();
-				}
-				if (signature[4] != 'U' || signature[5] != 'N') {
-					throw new HgBadStateException("Bad bundle signature:" + new String(signature));
-				}
-				// "...UN", fall-through
-			} else {
-				da.reset();
-			}
-		}
-		return da;
-	}
-
-	private int uses = 0;
-	public HgBundle link() {
-		uses++;
-		return this;
-	}
-	public void unlink() {
-		uses--;
-		if (uses == 0 && bundleFile != null) {
-			bundleFile.deleteOnExit();
-		}
-	}
-	public boolean inUse() {
-		return uses > 0;
-	}
-
-	/**
-	 * Get changes recorded in the bundle that are missing from the supplied repository.
-	 * @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 {
-		Inspector bundleInsp = new Inspector() {
-			DigestHelper dh = new DigestHelper();
-			boolean emptyChangelog = true;
-			private DataAccess prevRevContent;
-			private int revisionIndex;
-
-			public void changelogStart() {
-				emptyChangelog = true;
-				revisionIndex = 0;
-			}
-
-			public void changelogEnd() {
-				if (emptyChangelog) {
-					throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log?
-				}
-			}
-
-/*
- * Despite that BundleFormat wiki says: "Each Changelog entry patches the result of all previous patches 
- * (the previous, or parent patch of a given patch p is the patch that has a node equal to p's p1 field)",
- *  it seems not to hold true. Instead, each entry patches previous one, regardless of whether the one
- *  before is its parent (i.e. ge.firstParent()) or not.
- *  
-Actual state in the changelog.i
-Index    Offset      Flags     Packed     Actual   Base Rev   Link Rev  Parent1  Parent2     nodeid
-  50:          9212      0        209        329         48         50       49       -1     f1db8610da62a3e0beb8d360556ee1fd6eb9885e
-  51:          9421      0        278        688         48         51       50       -1     9429c7bd1920fab164a9d2b621d38d57bcb49ae0
-  52:          9699      0        154        179         52         52       50       -1     30bd389788464287cee22ccff54c330a4b715de5
-  53:          9853      0        133        204         52         53       51       52     a6f39e595b2b54f56304470269a936ead77f5725
-  54:          9986      0        156        182         54         54       52       -1     fd4f2c98995beb051070630c272a9be87bef617d
-
-Excerpt from bundle (nodeid, p1, p2, cs):
-   f1db8610da62a3e0beb8d360556ee1fd6eb9885e 26e3eeaa39623de552b45ee1f55c14f36460f220 0000000000000000000000000000000000000000 f1db8610da62a3e0beb8d360556ee1fd6eb9885e; patches:4
-   9429c7bd1920fab164a9d2b621d38d57bcb49ae0 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 0000000000000000000000000000000000000000 9429c7bd1920fab164a9d2b621d38d57bcb49ae0; patches:3
->  30bd389788464287cee22ccff54c330a4b715de5 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 0000000000000000000000000000000000000000 30bd389788464287cee22ccff54c330a4b715de5; patches:3
-   a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5 a6f39e595b2b54f56304470269a936ead77f5725; patches:3
-   fd4f2c98995beb051070630c272a9be87bef617d 30bd389788464287cee22ccff54c330a4b715de5 0000000000000000000000000000000000000000 fd4f2c98995beb051070630c272a9be87bef617d; patches:3
-
-To recreate 30bd..e5, one have to take content of 9429..e0, not its p1 f1db..5e
- */
-			public boolean element(GroupElement ge) {
-				emptyChangelog = false;
-				HgChangelog changelog = hgRepo.getChangelog();
-				try {
-					if (prevRevContent == null) { 
-						if (NULL.equals(ge.firstParent()) && NULL.equals(ge.secondParent())) {
-							prevRevContent = new ByteArrayDataAccess(new byte[0]);
-						} else {
-							final Nodeid base = ge.firstParent();
-							if (!changelog.isKnown(base) /*only first parent, that's Bundle contract*/) {
-								throw new IllegalStateException(String.format("Revision %s needs a parent %s, which is missing in the supplied repo %s", ge.node().shortNotation(), base.shortNotation(), hgRepo.toString()));
-							}
-							ByteArrayChannel bac = new ByteArrayChannel();
-							changelog.rawContent(base, bac); // FIXME get DataAccess directly, to avoid
-							// extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap.
-							prevRevContent = new ByteArrayDataAccess(bac.toArray());
-						}
-					}
-					//
-					byte[] csetContent = ge.apply(prevRevContent);
-					dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid?
-					if (!ge.node().equalsTo(dh.asBinary())) {
-						throw new IllegalStateException("Integrity check failed on " + bundleFile + ", node:" + ge.node());
-					}
-					ByteArrayDataAccess csetDataAccess = new ByteArrayDataAccess(csetContent);
-					RawChangeset cs = RawChangeset.parse(csetDataAccess);
-					inspector.next(revisionIndex++, ge.node(), cs);
-					prevRevContent.done();
-					prevRevContent = csetDataAccess.reset();
-				} catch (CancelledException ex) {
-					return false;
-				} catch (Exception ex) {
-					throw new HgBadStateException(ex); // FIXME
-				}
-				return true;
-			}
-
-			public void manifestStart() {}
-			public void manifestEnd() {}
-			public void fileStart(String name) {}
-			public void fileEnd(String name) {}
-
-		};
-		inspectChangelog(bundleInsp);
-	}
-
-	public void dump() throws IOException {
-		Dump dump = new Dump();
-		inspectAll(dump);
-		System.out.println("Total files:" + dump.names.size());
-		for (String s : dump.names) {
-			System.out.println(s);
-		}
-	}
-
-	// callback to minimize amount of Strings and Nodeids instantiated
-	public interface Inspector {
-		void changelogStart();
-
-		void changelogEnd();
-
-		void manifestStart();
-
-		void manifestEnd();
-
-		void fileStart(String name);
-
-		void fileEnd(String name);
-
-		/**
-		 * XXX desperately need exceptions here
-		 * @param element data element, instance might be reused, don't keep a reference to it or its raw data
-		 * @return <code>true</code> to continue
-		 */
-		boolean element(GroupElement element);
-	}
-
-	public static class Dump implements Inspector {
-		public final LinkedList<String> names = new LinkedList<String>();
-
-		public void changelogStart() {
-			System.out.println("Changelog group");
-		}
-
-		public void changelogEnd() {
-		}
-
-		public void manifestStart() {
-			System.out.println("Manifest group");
-		}
-
-		public void manifestEnd() {
-		}
-
-		public void fileStart(String name) {
-			names.add(name);
-			System.out.println(name);
-		}
-
-		public void fileEnd(String name) {
-		}
-
-		public boolean element(GroupElement ge) {
-			try {
-				System.out.printf("  %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches().size());
-			} catch (Exception ex) {
-				ex.printStackTrace(); // FIXME
-			}
-			return true;
-		}
-	}
-
-	public void inspectChangelog(Inspector inspector) throws IOException {
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		DataAccess da = getDataStream();
-		try {
-			internalInspectChangelog(da, inspector);
-		} finally {
-			da.done();
-		}
-	}
-
-	public void inspectManifest(Inspector inspector) throws IOException {
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		DataAccess da = getDataStream();
-		try {
-			if (da.isEmpty()) {
-				return;
-			}
-			skipGroup(da); // changelog
-			internalInspectManifest(da, inspector);
-		} finally {
-			da.done();
-		}
-	}
-
-	public void inspectFiles(Inspector inspector) throws IOException {
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		DataAccess da = getDataStream();
-		try {
-			if (da.isEmpty()) {
-				return;
-			}
-			skipGroup(da); // changelog
-			if (da.isEmpty()) {
-				return;
-			}
-			skipGroup(da); // manifest
-			internalInspectFiles(da, inspector);
-		} finally {
-			da.done();
-		}
-	}
-
-	public void inspectAll(Inspector inspector) throws IOException {
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		DataAccess da = getDataStream();
-		try {
-			internalInspectChangelog(da, inspector);
-			internalInspectManifest(da, inspector);
-			internalInspectFiles(da, inspector);
-		} finally {
-			da.done();
-		}
-	}
-
-	private void internalInspectChangelog(DataAccess da, Inspector inspector) throws IOException {
-		if (da.isEmpty()) {
-			return;
-		}
-		inspector.changelogStart();
-		readGroup(da, inspector);
-		inspector.changelogEnd();
-	}
-
-	private void internalInspectManifest(DataAccess da, Inspector inspector) throws IOException {
-		if (da.isEmpty()) {
-			return;
-		}
-		inspector.manifestStart();
-		readGroup(da, inspector);
-		inspector.manifestEnd();
-	}
-
-	private void internalInspectFiles(DataAccess da, Inspector inspector) throws IOException {
-		while (!da.isEmpty()) {
-			int fnameLen = da.readInt();
-			if (fnameLen <= 4) {
-				break; // null chunk, the last one.
-			}
-			byte[] fnameBuf = new byte[fnameLen - 4];
-			da.readBytes(fnameBuf, 0, fnameBuf.length);
-			String name = new String(fnameBuf);
-			inspector.fileStart(name);
-			readGroup(da, inspector);
-			inspector.fileEnd(name);
-		}
-	}
-
-	private static void readGroup(DataAccess da, Inspector inspector) throws IOException {
-		int len = da.readInt();
-		boolean good2go = true;
-		while (len > 4 && !da.isEmpty() && good2go) {
-			byte[] nb = new byte[80];
-			da.readBytes(nb, 0, 80);
-			int dataLength = len - 84 /* length field + 4 nodeids */;
-			byte[] data = new byte[dataLength];
-			da.readBytes(data, 0, dataLength);
-			DataAccess slice = new ByteArrayDataAccess(data); // XXX in fact, may pass a slicing DataAccess.
-			// Just need to make sure that we seek to proper location afterwards (where next GroupElement starts),
-			// regardless whether that slice has read it or not.
-			GroupElement ge = new GroupElement(nb, slice);
-			good2go = inspector.element(ge);
-			slice.done(); // BADA doesn't implement done(), but it could (e.g. free array) 
-			/// and we'd better tell it we are not going to use it any more. However, it's important to ensure Inspector
-			// implementations out there do not retain GroupElement.rawData()
-			len = da.isEmpty() ? 0 : da.readInt();
-		}
-		// need to skip up to group end if inspector told he don't want to continue with the group, 
-		// because outer code may try to read next group immediately as we return back.
-		while (len > 4 && !da.isEmpty()) {
-			da.skip(len - 4 /* length field */);
-			len = da.isEmpty() ? 0 : da.readInt();
-		}
-	}
-
-	private static void skipGroup(DataAccess da) throws IOException {
-		int len = da.readInt();
-		while (len > 4 && !da.isEmpty()) {
-			da.skip(len - 4); // sizeof(int)
-			len = da.isEmpty() ? 0 : da.readInt();
-		}
-	}
-
-	public static class GroupElement {
-		private final byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192
-		private final DataAccess dataAccess;
-		private List<RevlogStream.PatchRecord> patches;
-
-		GroupElement(byte[] fourNodeids, DataAccess rawDataAccess) {
-			assert fourNodeids != null && fourNodeids.length == 80;
-			header = fourNodeids;
-			dataAccess = rawDataAccess;
-		}
-
-		public Nodeid node() {
-			return Nodeid.fromBinary(header, 0);
-		}
-
-		public Nodeid firstParent() {
-			return Nodeid.fromBinary(header, 20);
-		}
-
-		public Nodeid secondParent() {
-			return Nodeid.fromBinary(header, 40);
-		}
-
-		public Nodeid cset() { // cs seems to be changeset
-			return Nodeid.fromBinary(header, 60);
-		}
-
-		public DataAccess rawData() {
-			return dataAccess;
-		}
-		
-		public List<RevlogStream.PatchRecord> patches() throws IOException {
-			if (patches == null) {
-				dataAccess.reset();
-				LinkedList<RevlogStream.PatchRecord> p = new LinkedList<RevlogStream.PatchRecord>();
-				while (!dataAccess.isEmpty()) {
-					RevlogStream.PatchRecord pr = RevlogStream.PatchRecord.read(dataAccess);
-					p.add(pr);
-				}
-				patches = p;
-			}
-			return patches;
-		}
-
-		public byte[] apply(DataAccess baseContent) throws IOException {
-			return RevlogStream.apply(baseContent, -1, patches());
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgChangelog.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,351 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.Pool;
-import org.tmatesoft.hg.internal.RevlogStream;
-
-/**
- * Representation of the Mercurial changelog file (list of ChangeSets)
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgChangelog extends Revlog {
-
-	/* package-local */HgChangelog(HgRepository hgRepo, RevlogStream content) {
-		super(hgRepo, content);
-	}
-
-	public void all(final HgChangelog.Inspector inspector) {
-		range(0, getLastRevision(), inspector);
-	}
-
-	public void range(int start, int end, final HgChangelog.Inspector inspector) {
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		content.iterate(start, end, true, new RawCsetParser(inspector));
-	}
-
-	public List<RawChangeset> range(int start, int end) {
-		final RawCsetCollector c = new RawCsetCollector(end - start + 1);
-		range(start, end, c);
-		return c.result;
-	}
-
-	public void range(final HgChangelog.Inspector inspector, final int... revisions) {
-		if (revisions == null || revisions.length == 0) {
-			return;
-		}
-		RevlogStream.Inspector i = new RevlogStream.Inspector() {
-			private final RawCsetParser delegate = new RawCsetParser(inspector);
-
-			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
-				if (Arrays.binarySearch(revisions, revisionNumber) >= 0) {
-					delegate.next(revisionNumber, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeid, da);
-				}
-			}
-		};
-		Arrays.sort(revisions);
-		content.iterate(revisions[0], revisions[revisions.length - 1], true, i);
-	}
-
-	public interface Inspector {
-		// TODO describe whether cset is new instance each time
-		// describe what revisionNumber is when Inspector is used with HgBundle (BAD_REVISION or bundle's local order?) 
-		void next(int revisionNumber, Nodeid nodeid, RawChangeset cset);
-	}
-
-	/**
-	 * Entry in the Changelog
-	 */
-	public static class RawChangeset implements Cloneable /* for those that would like to keep a copy */{
-		// TODO immutable
-		private/* final */Nodeid manifest;
-		private String user;
-		private String comment;
-		private List<String> files; // unmodifiable collection (otherwise #files() and implicit #clone() shall be revised)
-		private Date time;
-		private int timezone;
-		// http://mercurial.selenic.com/wiki/PruningDeadBranches - Closing changesets can be identified by close=1 in the changeset's extra field.
-		private Map<String, String> extras;
-
-		/**
-		 * @see mercurial/changelog.py:read()
-		 * 
-		 *      <pre>
-		 *         format used:
-		 *         nodeid\n        : manifest node in ascii
-		 *         user\n          : user, no \n or \r allowed
-		 *         time tz extra\n : date (time is int or float, timezone is int)
-		 *                         : extra is metadatas, encoded and separated by '\0'
-		 *                         : older versions ignore it
-		 *         files\n\n       : files modified by the cset, no \n or \r allowed
-		 *         (.*)            : comment (free text, ideally utf-8)
-		 * 
-		 *         changelog v0 doesn't use extra
-		 * </pre>
-		 */
-		private RawChangeset() {
-		}
-
-		public Nodeid manifest() {
-			return manifest;
-		}
-
-		public String user() {
-			return user;
-		}
-
-		public String comment() {
-			return comment;
-		}
-
-		public List<String> files() {
-			return files;
-		}
-
-		public Date date() {
-			return time;
-		}
-		
-		/**
-		 * @return time zone value, as is, positive for Western Hemisphere.
-		 */
-		public int timezone() {
-			return timezone;
-		}
-
-		public String dateString() {
-			// XXX keep once formatted? Perhaps, there's faster way to set up calendar/time zone?
-			StringBuilder sb = new StringBuilder(30);
-			Formatter f = new Formatter(sb, Locale.US);
-			TimeZone tz = TimeZone.getTimeZone(TimeZone.getAvailableIDs(timezone * 1000)[0]);
-			// apparently timezone field records number of seconds time differs from UTC,
-			// i.e. value to substract from time to get UTC time. Calendar seems to add
-			// timezone offset to UTC, instead, hence sign change.
-//			tz.setRawOffset(timezone * -1000);
-			Calendar c = Calendar.getInstance(tz, Locale.US);
-			c.setTime(time);
-			f.format("%ta %<tb %<td %<tH:%<tM:%<tS %<tY %<tz", c);
-			return sb.toString();
-		}
-
-		public Map<String, String> extras() {
-			return extras;
-		}
-
-		public String branch() {
-			return extras.get("branch");
-		}
-
-		@Override
-		public String toString() {
-			StringBuilder sb = new StringBuilder();
-			sb.append("Changeset {");
-			sb.append("User: ").append(user).append(", ");
-			sb.append("Comment: ").append(comment).append(", ");
-			sb.append("Manifest: ").append(manifest).append(", ");
-			sb.append("Date: ").append(time).append(", ");
-			sb.append("Files: ").append(files.size());
-			for (String s : files) {
-				sb.append(", ").append(s);
-			}
-			if (extras != null) {
-				sb.append(", Extra: ").append(extras);
-			}
-			sb.append("}");
-			return sb.toString();
-		}
-
-		@Override
-		public RawChangeset clone() {
-			try {
-				return (RawChangeset) super.clone();
-			} catch (CloneNotSupportedException ex) {
-				throw new InternalError(ex.toString());
-			}
-		}
-
-		public static RawChangeset parse(DataAccess da) {
-			try {
-				byte[] data = da.byteArray();
-				RawChangeset rv = new RawChangeset();
-				rv.init(data, 0, data.length, null);
-				return rv;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex); // FIXME "Error reading changeset data"
-			}
-		}
-
-		// @param usersPool - it's likely user names get repeated again and again throughout repository. can be null
-		/* package-local */void init(byte[] data, int offset, int length, Pool<String> usersPool) {
-			final int bufferEndIndex = offset + length;
-			final byte lineBreak = (byte) '\n';
-			int breakIndex1 = indexOf(data, lineBreak, offset, bufferEndIndex);
-			if (breakIndex1 == -1) {
-				throw new IllegalArgumentException("Bad Changeset data");
-			}
-			Nodeid _nodeid = Nodeid.fromAscii(data, 0, breakIndex1);
-			int breakIndex2 = indexOf(data, lineBreak, breakIndex1 + 1, bufferEndIndex);
-			if (breakIndex2 == -1) {
-				throw new IllegalArgumentException("Bad Changeset data");
-			}
-			String _user = new String(data, breakIndex1 + 1, breakIndex2 - breakIndex1 - 1);
-			if (usersPool != null) {
-				_user = usersPool.unify(_user);
-			}
-			int breakIndex3 = indexOf(data, lineBreak, breakIndex2 + 1, bufferEndIndex);
-			if (breakIndex3 == -1) {
-				throw new IllegalArgumentException("Bad Changeset data");
-			}
-			String _timeString = new String(data, breakIndex2 + 1, breakIndex3 - breakIndex2 - 1);
-			int space1 = _timeString.indexOf(' ');
-			if (space1 == -1) {
-				throw new IllegalArgumentException("Bad Changeset data");
-			}
-			int space2 = _timeString.indexOf(' ', space1 + 1);
-			if (space2 == -1) {
-				space2 = _timeString.length();
-			}
-			long unixTime = Long.parseLong(_timeString.substring(0, space1)); // XXX Float, perhaps
-			int _timezone = Integer.parseInt(_timeString.substring(space1 + 1, space2));
-			// XXX not sure need to add timezone here - I can't figure out whether Hg keeps GMT time, and records timezone just for info, or unixTime is taken local
-			// on commit and timezone is recorded to adjust it to UTC.
-			Date _time = new Date(unixTime * 1000);
-			String _extras = space2 < _timeString.length() ? _timeString.substring(space2 + 1) : null;
-			Map<String, String> _extrasMap;
-			if (_extras == null) {
-				_extrasMap = Collections.singletonMap("branch", "default");
-			} else {
-				_extrasMap = new HashMap<String, String>();
-				for (String pair : _extras.split("\00")) {
-					int eq = pair.indexOf(':');
-					// FIXME need to decode key/value, @see changelog.py:decodeextra
-					_extrasMap.put(pair.substring(0, eq), pair.substring(eq + 1));
-				}
-				if (!_extrasMap.containsKey("branch")) {
-					_extrasMap.put("branch", "default");
-				}
-				_extrasMap = Collections.unmodifiableMap(_extrasMap);
-			}
-
-			//
-			int lastStart = breakIndex3 + 1;
-			int breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
-			ArrayList<String> _files = null;
-			if (breakIndex4 > lastStart) {
-				// if breakIndex4 == lastStart, we already found \n\n and hence there are no files (e.g. merge revision)
-				_files = new ArrayList<String>(5);
-				while (breakIndex4 != -1 && breakIndex4 + 1 < bufferEndIndex) {
-					_files.add(new String(data, lastStart, breakIndex4 - lastStart));
-					lastStart = breakIndex4 + 1;
-					if (data[breakIndex4 + 1] == lineBreak) {
-						// found \n\n
-						break;
-					} else {
-						breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
-					}
-				}
-				if (breakIndex4 == -1 || breakIndex4 >= bufferEndIndex) {
-					throw new IllegalArgumentException("Bad Changeset data");
-				}
-			} else {
-				breakIndex4--;
-			}
-			String _comment;
-			try {
-				_comment = new String(data, breakIndex4 + 2, bufferEndIndex - breakIndex4 - 2, "UTF-8");
-				// FIXME respect ui.fallbackencoding and try to decode if set
-			} catch (UnsupportedEncodingException ex) {
-				_comment = "";
-				throw new IllegalStateException("Could hardly happen");
-			}
-			// change this instance at once, don't leave it partially changes in case of error
-			this.manifest = _nodeid;
-			this.user = _user;
-			this.time = _time;
-			this.timezone = _timezone;
-			this.files = _files == null ? Collections.<String> emptyList() : Collections.unmodifiableList(_files);
-			this.comment = _comment;
-			this.extras = _extrasMap;
-		}
-
-		private static int indexOf(byte[] src, byte what, int startOffset, int endIndex) {
-			for (int i = startOffset; i < endIndex; i++) {
-				if (src[i] == what) {
-					return i;
-				}
-			}
-			return -1;
-		}
-	}
-
-	private static class RawCsetCollector implements Inspector {
-		final ArrayList<RawChangeset> result;
-		
-		public RawCsetCollector(int count) {
-			result = new ArrayList<RawChangeset>(count > 0 ? count : 5);
-		}
-
-		public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
-			result.add(cset.clone());
-		}
-	}
-
-	private static class RawCsetParser implements RevlogStream.Inspector {
-		
-		private final Inspector inspector;
-		private final Pool<String> usersPool;
-		private final RawChangeset cset = new RawChangeset();
-
-		public RawCsetParser(HgChangelog.Inspector delegate) {
-			assert delegate != null;
-			inspector = delegate;
-			usersPool = new Pool<String>();
-		}
-
-		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
-			try {
-				byte[] data = da.byteArray();
-				cset.init(data, 0, data.length, usersPool);
-				// XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse
-				inspector.next(revisionNumber, Nodeid.fromBinary(nodeid, 0), cset);
-			} catch (Exception ex) {
-				throw new HgBadStateException(ex); // FIXME exception handling
-			}
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,390 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision;
-import static org.tmatesoft.hg.repo.HgRepository.*;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.TreeMap;
-
-import org.tmatesoft.hg.core.HgDataStreamException;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.FilterByteChannel;
-import org.tmatesoft.hg.internal.RevlogStream;
-import org.tmatesoft.hg.util.ByteChannel;
-import org.tmatesoft.hg.util.CancelledException;
-import org.tmatesoft.hg.util.Path;
-
-
-
-/**
- * ? name:HgFileNode?
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgDataFile extends Revlog {
-
-	// absolute from repo root?
-	// slashes, unix-style?
-	// repo location agnostic, just to give info to user, not to access real storage
-	private final Path path;
-	private Metadata metadata; // get initialized on first access to file content.
-	
-	/*package-local*/HgDataFile(HgRepository hgRepo, Path filePath, RevlogStream content) {
-		super(hgRepo, content);
-		path = filePath;
-	}
-
-	/*package-local*/HgDataFile(HgRepository hgRepo, Path filePath) {
-		super(hgRepo);
-		path = filePath;
-	}
-
-	// exists is not the best name possible. now it means no file with such name was ever known to the repo.
-	// it might be confused with files existed before but lately removed. 
-	public boolean exists() {
-		return content != null; // XXX need better impl
-	}
-
-	// human-readable (i.e. "COPYING", not "store/data/_c_o_p_y_i_n_g.i")
-	public Path getPath() {
-		return path; // hgRepo.backresolve(this) -> name? In this case, what about hashed long names?
-	}
-
-	public int length(Nodeid nodeid) {
-		return content.dataLength(getLocalRevision(nodeid));
-	}
-
-	public void workingCopy(ByteChannel sink) throws IOException, CancelledException {
-		throw HgRepository.notImplemented();
-	}
-	
-//	public void content(int revision, ByteChannel sink, boolean applyFilters) throws HgDataStreamException, IOException, CancelledException {
-//		byte[] content = content(revision);
-//		final CancelSupport cancelSupport = CancelSupport.Factory.get(sink);
-//		final ProgressSupport progressSupport = ProgressSupport.Factory.get(sink);
-//		ByteBuffer buf = ByteBuffer.allocate(512);
-//		int left = content.length;
-//		progressSupport.start(left);
-//		int offset = 0;
-//		cancelSupport.checkCancelled();
-//		ByteChannel _sink = applyFilters ? new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())) : sink;
-//		do {
-//			buf.put(content, offset, Math.min(left, buf.remaining()));
-//			buf.flip();
-//			cancelSupport.checkCancelled();
-//			// XXX I may not rely on returned number of bytes but track change in buf position instead.
-//			int consumed = _sink.write(buf);
-//			buf.compact();
-//			offset += consumed;
-//			left -= consumed;
-//			progressSupport.worked(consumed);
-//		} while (left > 0);
-//		progressSupport.done(); // XXX shall specify whether #done() is invoked always or only if completed successfully.
-//	}
-	
-	/*XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves?*/
-	public void contentWithFilters(int revision, ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
-		content(revision, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath())));
-	}
-
-	// for data files need to check heading of the file content for possible metadata
-	// @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8-
-	public void content(int revision, ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
-		if (revision == TIP) {
-			revision = getLastRevision();
-		}
-		if (revision == WORKING_COPY) {
-			workingCopy(sink);
-			return;
-		}
-		if (wrongLocalRevision(revision) || revision == BAD_REVISION) {
-			throw new IllegalArgumentException(String.valueOf(revision));
-		}
-		if (sink == null) {
-			throw new IllegalArgumentException();
-		}
-		if (metadata == null) {
-			metadata = new Metadata();
-		}
-		ContentPipe insp;
-		if (metadata.none(revision)) {
-			insp = new ContentPipe(sink, 0);
-		} else if (metadata.known(revision)) {
-			insp = new ContentPipe(sink, metadata.dataOffset(revision));
-		} else {
-			// do not know if there's metadata
-			insp = new MetadataContentPipe(sink, metadata);
-		}
-		insp.checkCancelled();
-		super.content.iterate(revision, revision, true, insp);
-		try {
-			insp.checkFailed();
-		} catch (HgDataStreamException ex) {
-			throw ex;
-		} catch (HgException ex) {
-			// shall not happen, unless we changed ContentPipe or its subclass
-			throw new HgDataStreamException(ex.getClass().getName(), ex);
-		}
-	}
-	
-	public void history(HgChangelog.Inspector inspector) {
-		history(0, getLastRevision(), inspector);
-	}
-
-	public void history(int start, int end, HgChangelog.Inspector inspector) {
-		if (!exists()) {
-			throw new IllegalStateException("Can't get history of invalid repository file node"); 
-		}
-		final int last = getLastRevision();
-		if (start < 0 || start > last) {
-			throw new IllegalArgumentException();
-		}
-		if (end == TIP) {
-			end = last;
-		} else if (end < start || end > last) {
-			throw new IllegalArgumentException();
-		}
-		final int[] commitRevisions = new int[end - start + 1];
-		RevlogStream.Inspector insp = new RevlogStream.Inspector() {
-			int count = 0;
-			
-			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
-				commitRevisions[count++] = linkRevision;
-			}
-		};
-		content.iterate(start, end, false, insp);
-		getRepo().getChangelog().range(inspector, commitRevisions);
-	}
-	
-	// for a given local revision of the file, find out local revision in the changelog
-	public int getChangesetLocalRevision(int revision) {
-		return content.linkRevision(revision);
-	}
-
-	public Nodeid getChangesetRevision(Nodeid nid) {
-		int changelogRevision = getChangesetLocalRevision(getLocalRevision(nid));
-		return getRepo().getChangelog().getRevision(changelogRevision);
-	}
-
-	public boolean isCopy() throws HgDataStreamException {
-		if (metadata == null || !metadata.checked(0)) {
-			// content() always initializes metadata.
-			// FIXME this is expensive way to find out metadata, distinct RevlogStream.Iterator would be better.
-			// Alternatively, may parameterize MetadataContentPipe to do prepare only.
-			// For reference, when throwing CancelledException, hg status -A --rev 3:80 takes 70 ms
-			// however, if we just consume buffer instead (buffer.position(buffer.limit()), same command takes ~320ms
-			// (compared to command-line counterpart of 190ms)
-			try {
-				content(0, new ByteChannel() { // No-op channel
-					public int write(ByteBuffer buffer) throws IOException, CancelledException {
-						// pretend we consumed whole buffer
-//						int rv = buffer.remaining();
-//						buffer.position(buffer.limit());
-//						return rv;
-						throw new CancelledException();
-					}
-				});
-			} catch (CancelledException ex) {
-				// it's ok, we did that
-			} catch (Exception ex) {
-				throw new HgDataStreamException("Can't initialize metadata", ex);
-			}
-		}
-		if (!metadata.known(0)) {
-			return false;
-		}
-		return metadata.find(0, "copy") != null;
-	}
-
-	public Path getCopySourceName() throws HgDataStreamException {
-		if (isCopy()) {
-			return Path.create(metadata.find(0, "copy"));
-		}
-		throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?)
-	}
-	
-	public Nodeid getCopySourceRevision() throws HgDataStreamException {
-		if (isCopy()) {
-			return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid
-		}
-		throw new UnsupportedOperationException();
-	}
-	
-	@Override
-	public String toString() {
-		StringBuilder sb = new StringBuilder(getClass().getSimpleName());
-		sb.append('(');
-		sb.append(getPath());
-		sb.append(')');
-		return sb.toString();
-	}
-
-	private static final class MetadataEntry {
-		private final String entry;
-		private final int valueStart;
-		/*package-local*/MetadataEntry(String key, String value) {
-			entry = key + value;
-			valueStart = key.length();
-		}
-		/*package-local*/boolean matchKey(String key) {
-			return key.length() == valueStart && entry.startsWith(key);
-		}
-//		uncomment once/if needed
-//		public String key() {
-//			return entry.substring(0, valueStart);
-//		}
-		public String value() {
-			return entry.substring(valueStart);
-		}
-	}
-
-	private static class Metadata {
-		// XXX sparse array needed
-		private final TreeMap<Integer, Integer> offsets = new TreeMap<Integer, Integer>();
-		private final TreeMap<Integer, MetadataEntry[]> entries = new TreeMap<Integer, MetadataEntry[]>();
-		
-		private final Integer NONE = new Integer(-1); // do not duplicate -1 integers at least within single file (don't want statics)
-
-		// true when there's metadata for given revision
-		boolean known(int revision) {
-			Integer i = offsets.get(revision);
-			return i != null && NONE != i;
-		}
-
-		// true when revision has been checked for metadata presence.
-		public boolean checked(int revision) {
-			return offsets.containsKey(revision);
-		}
-
-		// true when revision has been checked and found not having any metadata
-		boolean none(int revision) {
-			Integer i = offsets.get(revision);
-			return i == NONE;
-		}
-
-		// mark revision as having no metadata.
-		void recordNone(int revision) {
-			Integer i = offsets.get(revision);
-			if (i == NONE) {
-				return; // already there
-			} 
-			if (i != null) {
-				throw new IllegalStateException(String.format("Trying to override Metadata state for revision %d (known offset: %d)", revision, i));
-			}
-			offsets.put(revision, NONE);
-		}
-
-		// since this is internal class, callers are supposed to ensure arg correctness (i.e. ask known() before)
-		int dataOffset(int revision) {
-			return offsets.get(revision);
-		}
-		void add(int revision, int dataOffset, Collection<MetadataEntry> e) {
-			assert !offsets.containsKey(revision);
-			offsets.put(revision, dataOffset);
-			entries.put(revision, e.toArray(new MetadataEntry[e.size()]));
-		}
-		String find(int revision, String key) {
-			for (MetadataEntry me : entries.get(revision)) {
-				if (me.matchKey(key)) {
-					return me.value();
-				}
-			}
-			return null;
-		}
-	}
-
-	private static class MetadataContentPipe extends ContentPipe {
-
-		private final Metadata metadata;
-
-		public MetadataContentPipe(ByteChannel sink, Metadata _metadata) {
-			super(sink, 0);
-			metadata = _metadata;
-		}
-
-		@Override
-		protected void prepare(int revisionNumber, DataAccess da) throws HgException, IOException {
-			final int daLength = da.length();
-			if (daLength < 4 || da.readByte() != 1 || da.readByte() != 10) {
-				metadata.recordNone(revisionNumber);
-				da.reset();
-				return;
-			}
-			int lastEntryStart = 2;
-			int lastColon = -1;
-			ArrayList<MetadataEntry> _metadata = new ArrayList<MetadataEntry>();
-			// XXX in fact, need smth like ByteArrayBuilder, similar to StringBuilder,
-			// which can't be used here because we can't convert bytes to chars as we read them
-			// (there might be multi-byte encoding), and we need to collect all bytes before converting to string 
-			ByteArrayOutputStream bos = new ByteArrayOutputStream();
-			String key = null, value = null;
-			boolean byteOne = false;
-			for (int i = 2; i < daLength; i++) {
-				byte b = da.readByte();
-				if (b == '\n') {
-					if (byteOne) { // i.e. \n follows 1
-						lastEntryStart = i+1;
-						// XXX is it possible to have here incomplete key/value (i.e. if last pair didn't end with \n)
-						break;
-					}
-					if (key == null || lastColon == -1 || i <= lastColon) {
-						throw new IllegalStateException(); // FIXME log instead and record null key in the metadata. Ex just to fail fast during dev
-					}
-					value = new String(bos.toByteArray()).trim();
-					bos.reset();
-					_metadata.add(new MetadataEntry(key, value));
-					key = value = null;
-					lastColon = -1;
-					lastEntryStart = i+1;
-					continue;
-				} 
-				// byteOne has to be consumed up to this line, if not jet, consume it
-				if (byteOne) {
-					// insert 1 we've read on previous step into the byte builder
-					bos.write(1);
-					// fall-through to consume current byte
-					byteOne = false;
-				}
-				if (b == (int) ':') {
-					assert value == null;
-					key = new String(bos.toByteArray());
-					bos.reset();
-					lastColon = i;
-				} else if (b == 1) {
-					byteOne = true;
-				} else {
-					bos.write(b);
-				}
-			}
-			_metadata.trimToSize();
-			metadata.add(revisionNumber, lastEntryStart, _metadata);
-			if (da.isEmpty() || !byteOne) {
-				throw new HgDataStreamException(String.format("Metadata for revision %d is not closed properly", revisionNumber), null);
-			}
-			// da is in prepared state (i.e. we consumed all bytes up to metadata end).
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgDirstate.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,184 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.DataAccessProvider;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- * @see http://mercurial.selenic.com/wiki/DirState
- * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-class HgDirstate {
-
-	private final DataAccessProvider accessProvider;
-	private final File dirstateFile;
-	// deliberate String, not Path as it seems useless to keep Path here
-	private Map<String, Record> normal;
-	private Map<String, Record> added;
-	private Map<String, Record> removed;
-	private Map<String, Record> merged;
-
-	/*package-local*/ HgDirstate() {
-		// empty instance
-		accessProvider = null;
-		dirstateFile = null;
-	}
-
-	public HgDirstate(DataAccessProvider dap, File dirstate) {
-		accessProvider = dap;
-		dirstateFile = dirstate;
-	}
-
-	private void read() {
-		normal = added = removed = merged = Collections.<String, Record>emptyMap();
-		if (dirstateFile == null || !dirstateFile.exists()) {
-			return;
-		}
-		DataAccess da = accessProvider.create(dirstateFile);
-		if (da.isEmpty()) {
-			return;
-		}
-		// not sure linked is really needed here, just for ease of debug
-		normal = new LinkedHashMap<String, Record>();
-		added = new LinkedHashMap<String, Record>();
-		removed = new LinkedHashMap<String, Record>();
-		merged = new LinkedHashMap<String, Record>();
-		try {
-			// XXX skip(40) if we don't need these? 
-			byte[] parents = new byte[40];
-			da.readBytes(parents, 0, 40);
-			parents = null;
-			do {
-				final byte state = da.readByte();
-				final int fmode = da.readInt();
-				final int size = da.readInt();
-				final int time = da.readInt();
-				final int nameLen = da.readInt();
-				String fn1 = null, fn2 = null;
-				byte[] name = new byte[nameLen];
-				da.readBytes(name, 0, nameLen);
-				for (int i = 0; i < nameLen; i++) {
-					if (name[i] == 0) {
-						fn1 = new String(name, 0, i, "UTF-8"); // XXX unclear from documentation what encoding is used there
-						fn2 = new String(name, i+1, nameLen - i - 1, "UTF-8"); // need to check with different system codepages
-						break;
-					}
-				}
-				if (fn1 == null) {
-					fn1 = new String(name);
-				}
-				Record r = new Record(fmode, size, time, fn1, fn2);
-				if (state == 'n') {
-					normal.put(r.name1, r);
-				} else if (state == 'a') {
-					added.put(r.name1, r);
-				} else if (state == 'r') {
-					removed.put(r.name1, r);
-				} else if (state == 'm') {
-					merged.put(r.name1, r);
-				} else {
-					// FIXME log error?
-				}
-			} while (!da.isEmpty());
-		} catch (IOException ex) {
-			ex.printStackTrace(); // FIXME log error, clean dirstate?
-		} finally {
-			da.done();
-		}
-	}
-
-	// new, modifiable collection
-	/*package-local*/ TreeSet<String> all() {
-		read();
-		TreeSet<String> rv = new TreeSet<String>();
-		@SuppressWarnings("unchecked")
-		Map<String, Record>[] all = new Map[] { normal, added, removed, merged };
-		for (int i = 0; i < all.length; i++) {
-			for (Record r : all[i].values()) {
-				rv.add(r.name1);
-			}
-		}
-		return rv;
-	}
-	
-	/*package-local*/ Record checkNormal(Path fname) {
-		return normal.get(fname.toString());
-	}
-
-	/*package-local*/ Record checkAdded(Path fname) {
-		return added.get(fname.toString());
-	}
-	/*package-local*/ Record checkRemoved(Path fname) {
-		return removed.get(fname.toString());
-	}
-	/*package-local*/ Record checkRemoved(String fname) {
-		return removed.get(fname);
-	}
-	/*package-local*/ Record checkMerged(Path fname) {
-		return merged.get(fname.toString());
-	}
-
-
-
-
-	/*package-local*/ void dump() {
-		read();
-		@SuppressWarnings("unchecked")
-		Map<String, Record>[] all = new Map[] { normal, added, removed, merged };
-		char[] x = new char[] {'n', 'a', 'r', 'm' };
-		for (int i = 0; i < all.length; i++) {
-			for (Record r : all[i].values()) {
-				System.out.printf("%c %3o%6d %30tc\t\t%s", x[i], r.mode, r.size, (long) r.time * 1000, r.name1);
-				if (r.name2 != null) {
-					System.out.printf(" --> %s", r.name2);
-				}
-				System.out.println();
-			}
-			System.out.println();
-		}
-	}
-	
-	/*package-local*/ static class Record {
-		final int mode;
-		final int size;
-		final int time;
-		final String name1;
-		final String name2;
-
-		public Record(int fmode, int fsize, int ftime, String name1, String name2) {
-			mode = fmode;
-			size = fsize;
-			time = ftime;
-			this.name1 = name1;
-			this.name2 = name2;
-			
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgIgnore.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
-
-import org.tmatesoft.hg.util.Path;
-
-/**
- * Handling of ignored paths according to .hgignore configuration
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgIgnore {
-
-	private List<Pattern> entries;
-
-	HgIgnore() {
-		entries = Collections.emptyList();
-	}
-
-	/* package-local */void read(File hgignoreFile) throws IOException {
-		if (!hgignoreFile.exists()) {
-			return;
-		}
-		ArrayList<Pattern> result = new ArrayList<Pattern>(entries); // start with existing
-		String syntax = "regex"; // or "glob"
-		BufferedReader fr = new BufferedReader(new FileReader(hgignoreFile));
-		String line;
-		while ((line = fr.readLine()) != null) {
-			line = line.trim();
-			if (line.startsWith("syntax:")) {
-				syntax = line.substring("syntax:".length()).trim();
-				if (!"regex".equals(syntax) && !"glob".equals(syntax)) {
-					throw new IllegalStateException(line);
-				}
-			} else if (line.length() > 0) {
-				// shall I account for local paths in the file (i.e.
-				// back-slashed on windows)?
-				int x;
-				if ((x = line.indexOf('#')) >= 0) {
-					line = line.substring(0, x).trim();
-					if (line.length() == 0) {
-						continue;
-					}
-				}
-				if ("glob".equals(syntax)) {
-					// hgignore(5)
-					// (http://www.selenic.com/mercurial/hgignore.5.html) says slashes '\' are escape characters,
-					// hence no special  treatment of Windows path
-					// however, own attempts make me think '\' on Windows are not treated as escapes
-					line = glob2regex(line);
-				}
-				result.add(Pattern.compile(line)); // case-sensitive
-			}
-		}
-		result.trimToSize();
-		entries = result;
-	}
-
-	// note, #isIgnored(), even if queried for directories and returned positive reply, may still get
-	// a file from that ignored folder to get examined. Thus, patterns like "bin" shall match not only a folder,
-	// but any file under that folder as well
-	// Alternatively, file walker may memorize folder is ignored and uses this information for all nested files. However,
-	// this approach would require walker (a) return directories (b) provide nesting information. This may become
-	// troublesome when one walks not over io.File, but Eclipse's IResource or any other custom VFS.
-	//
-	//
-	// might be interesting, although looks like of no direct use in my case 
-	// @see http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns
-	private String glob2regex(String line) {
-		assert line.length() > 0;
-		StringBuilder sb = new StringBuilder(line.length() + 10);
-		sb.append('^'); // help avoid matcher.find() to match 'bin' pattern in the middle of the filename
-		int start = 0, end = line.length() - 1;
-		// '*' at the beginning and end of a line are useless for Pattern
-		// XXX although how about **.txt - such globs can be seen in a config, are they valid for HgIgnore?
-		while (start <= end && line.charAt(start) == '*') start++;
-		while (end > start && line.charAt(end) == '*') end--;
-
-		for (int i = start; i <= end; i++) {
-			char ch = line.charAt(i);
-			if (ch == '.' || ch == '\\') {
-				sb.append('\\');
-			} else if (ch == '?') {
-				// simple '.' substitution might work out, however, more formally 
-				// a char class seems more appropriate to avoid accidentally
-				// matching a subdirectory with ? char (i.e. /a/b?d against /a/bad, /a/bed and /a/b/d)
-				// @see http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_03
-				// quote: "The slash character in a pathname shall be explicitly matched by using one or more slashes in the pattern; 
-				// it shall neither be matched by the asterisk or question-mark special characters nor by a bracket expression" 
-				sb.append("[^/]");
-				continue;
-			} else if (ch == '*') {
-				sb.append("[^/]*?");
-				continue;
-			}
-			sb.append(ch);
-		}
-		return sb.toString();
-	}
-
-	// TODO use PathGlobMatcher
-	public boolean isIgnored(Path path) {
-		for (Pattern p : entries) {
-			if (p.matcher(path).find()) {
-				return true;
-			}
-		}
-		return false;
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/*
- * 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.repo;
-
-import static org.tmatesoft.hg.repo.HgRepository.*;
-
-import java.io.File;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-import org.tmatesoft.hg.internal.ConfigFile;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- * DO NOT USE THIS CLASS, INTENDED FOR TESTING PURPOSES.
- * 
- * Debug helper, to access otherwise restricted (package-local) methods
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
-
- */
-public class HgInternals {
-
-	private final HgRepository repo;
-
-	public HgInternals(HgRepository hgRepo) {
-		repo = hgRepo;
-	}
-
-	public void dumpDirstate() {
-		repo.loadDirstate().dump();
-	}
-
-	public boolean[] checkIgnored(String... toCheck) {
-		HgIgnore ignore = repo.getIgnore();
-		boolean[] rv = new boolean[toCheck.length];
-		for (int i = 0; i < toCheck.length; i++) {
-			rv[i] = ignore.isIgnored(Path.create(toCheck[i]));
-		}
-		return rv;
-	}
-
-	public File getRepositoryDir() {
-		return repo.getRepositoryRoot();
-	}
-	
-	public ConfigFile getRepoConfig() {
-		return repo.getConfigFile();
-	}
-
-	// in fact, need a setter for this anyway, shall move to internal.Internals perhaps?
-	public String getNextCommitUsername() {
-		String hgUser = System.getenv("HGUSER");
-		if (hgUser != null && hgUser.trim().length() > 0) {
-			return hgUser.trim();
-		}
-		String configValue = getRepoConfig().getString("ui", "username", null);
-		if (configValue != null) {
-			return configValue;
-		}
-		String email = System.getenv("EMAIL");
-		if (email != null && email.trim().length() > 0) {
-			return email;
-		}
-		String username = System.getProperty("user.name");
-		try {
-			String hostname = InetAddress.getLocalHost().getHostName();
-			return username + '@' + hostname; 
-		} catch (UnknownHostException ex) {
-			return username;
-		}
-	}
-
-	// Convenient check of local revision number for validity (not all negative values are wrong as long as we use negative constants)
-	public static boolean wrongLocalRevision(int rev) {
-		return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION; 
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgLookup.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.tmatesoft.hg.core.HgBadArgumentException;
-import org.tmatesoft.hg.core.HgException;
-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
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgLookup {
-
-	private ConfigFile globalCfg;
-
-	public HgRepository detectFromWorkingDir() throws HgException {
-		return detect(System.getProperty("user.dir"));
-	}
-
-	public HgRepository detect(String location) throws HgException {
-		return detect(new File(location));
-	}
-
-	// look up in specified location and above
-	public HgRepository detect(File location) throws HgException {
-		File dir = location.getAbsoluteFile();
-		File repository;
-		do {
-			repository = new File(dir, ".hg");
-			if (repository.exists() && repository.isDirectory()) {
-				break;
-			}
-			repository = null;
-			dir = dir.getParentFile();
-			
-		} while(dir != null);
-		if (repository == null) {
-			// return invalid repository
-			return new HgRepository(location.getPath());
-		}
-		try {
-			String repoPath = repository.getParentFile().getCanonicalPath();
-			return new HgRepository(repoPath, repository);
-		} catch (IOException ex) {
-			throw new HgException(location.toString(), ex);
-		}
-	}
-	
-	public HgBundle loadBundle(File location) throws HgException {
-		if (location == null || !location.canRead()) {
-			throw new IllegalArgumentException();
-		}
-		return new HgBundle(new DataAccessProvider(), location).link();
-	}
-	
-	/**
-	 * Try to instantiate remote server.
-	 * @param key either URL or a key from configuration file that points to remote server  
-	 * @param hgRepo <em>NOT USED YET<em> local repository that may have extra config, or default remote location
-	 * @return an instance featuring access to remote repository, check {@link HgRemoteRepository#isInvalid()} before actually using it
-	 * @throws HgBadArgumentException if anything is wrong with the remote server's URL
-	 */
-	public HgRemoteRepository detectRemote(String key, HgRepository hgRepo) throws HgBadArgumentException {
-		URL url;
-		Exception toReport;
-		try {
-			url = new URL(key);
-			toReport = null;
-		} catch (MalformedURLException ex) {
-			url = null;
-			toReport = ex;
-		}
-		if (url == null) {
-			String server = getGlobalConfig().getSection("paths").get(key);
-			if (server == null) {
-				throw new HgBadArgumentException(String.format("Can't find server %s specification in the config", key), toReport);
-			}
-			try {
-				url = new URL(server);
-			} catch (MalformedURLException ex) {
-				throw new HgBadArgumentException(String.format("Found %s server spec in the config, but failed to initialize with it", key), ex);
-			}
-		}
-		return new HgRemoteRepository(url);
-	}
-	
-	public HgRemoteRepository detect(URL url) throws HgException {
-		if (url == null) {
-			throw new IllegalArgumentException();
-		}
-		if (Boolean.FALSE.booleanValue()) {
-			throw HgRepository.notImplemented();
-		}
-		return new HgRemoteRepository(url);
-	}
-
-	private ConfigFile getGlobalConfig() {
-		if (globalCfg == null) {
-			globalCfg = new Internals().newConfigFile();
-			globalCfg.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
-		}
-		return globalCfg;
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import java.io.IOException;
-
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.Pool;
-import org.tmatesoft.hg.internal.RevlogStream;
-
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgManifest extends Revlog {
-
-	/*package-local*/ HgManifest(HgRepository hgRepo, RevlogStream content) {
-		super(hgRepo, content);
-	}
-
-	public void walk(int start, int end, final Inspector inspector) {
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		content.iterate(start, end, true, new ManifestParser(inspector));
-	}
-
-	public interface Inspector {
-		boolean begin(int revision, Nodeid nid);
-		boolean next(Nodeid nid, String fname, String flags);
-		boolean end(int revision);
-	}
-
-	private static class ManifestParser implements RevlogStream.Inspector {
-		private boolean gtg = true; // good to go
-		private final Inspector inspector;
-		private final Pool<Nodeid> nodeidPool;
-		private final Pool<String> fnamePool;
-		private final Pool<String> flagsPool;
-
-		public ManifestParser(Inspector delegate) {
-			assert delegate != null;
-			inspector = delegate;
-			nodeidPool = new Pool<Nodeid>();
-			fnamePool = new Pool<String>();
-			flagsPool = new Pool<String>();
-		}
-
-		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
-			if (!gtg) {
-				return;
-			}
-			try {
-				gtg = gtg && inspector.begin(revisionNumber, new Nodeid(nodeid, true));
-				int i;
-				String fname = null;
-				String flags = null;
-				Nodeid nid = null;
-				byte[] data = da.byteArray();
-				for (i = 0; gtg && i < actualLen; i++) {
-					int x = i;
-					for( ; data[i] != '\n' && i < actualLen; i++) {
-						if (fname == null && data[i] == 0) {
-							fname = fnamePool.unify(new String(data, x, i - x));
-							x = i+1;
-						}
-					}
-					if (i < actualLen) {
-						assert data[i] == '\n'; 
-						int nodeidLen = i - x < 40 ? i-x : 40;
-						nid = nodeidPool.unify(Nodeid.fromAscii(data, x, nodeidLen));
-						if (nodeidLen + x < i) {
-							// 'x' and 'l' for executable bits and symlinks?
-							// hg --debug manifest shows 644 for each regular file in my repo
-							flags = flagsPool.unify(new String(data, x + nodeidLen, i-x-nodeidLen));
-						}
-						gtg = gtg && inspector.next(nid, fname, flags);
-					}
-					nid = null;
-					fname = flags = null;
-				}
-				gtg = gtg && inspector.end(revisionNumber);
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,447 +0,0 @@
-/*
- * 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.repo;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.StreamTokenizer;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.prefs.BackingStoreException;
-import java.util.prefs.Preferences;
-import java.util.zip.InflaterInputStream;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-
-import org.tmatesoft.hg.core.HgBadArgumentException;
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.Nodeid;
-
-/**
- * WORK IN PROGRESS, DO NOT USE
- * 
- * @see http://mercurial.selenic.com/wiki/WireProtocol
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgRemoteRepository {
-	
-	private final URL url;
-	private final SSLContext sslContext;
-	private final String authInfo;
-	private final boolean debug = Boolean.parseBoolean(System.getProperty("hg4j.remote.debug"));
-	private HgLookup lookupHelper;
-
-	HgRemoteRepository(URL url) throws HgBadArgumentException {
-		if (url == null) {
-			throw new IllegalArgumentException();
-		}
-		this.url = url;
-		if ("https".equals(url.getProtocol())) {
-			try {
-				sslContext = SSLContext.getInstance("SSL");
-				class TrustEveryone implements X509TrustManager {
-					public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
-						if (debug) {
-							System.out.println("checkClientTrusted:" + authType);
-						}
-					}
-					public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
-						if (debug) {
-							System.out.println("checkServerTrusted:" + authType);
-						}
-					}
-					public X509Certificate[] getAcceptedIssuers() {
-						return new X509Certificate[0];
-					}
-				};
-				sslContext.init(null, new TrustManager[] { new TrustEveryone() }, null);
-			} catch (Exception ex) {
-				throw new HgBadArgumentException("Can't initialize secure connection", ex);
-			}
-		} else {
-			sslContext = null;
-		}
-		if (url.getUserInfo() != null) {
-			String ai = null;
-			try {
-				// Hack to get Base64-encoded credentials
-				Preferences tempNode = Preferences.userRoot().node("xxx");
-				tempNode.putByteArray("xxx", url.getUserInfo().getBytes());
-				ai = tempNode.get("xxx", null);
-				tempNode.removeNode();
-			} catch (BackingStoreException ex) {
-				ex.printStackTrace();
-				// IGNORE
-			}
-			authInfo = ai;
-		} else {
-			authInfo = null;
-		}
-	}
-	
-	public boolean isInvalid() throws HgException {
-		// say hello to server, check response
-		if (Boolean.FALSE.booleanValue()) {
-			throw HgRepository.notImplemented();
-		}
-		return false; // FIXME
-	}
-
-	/**
-	 * @return human-readable address of the server, without user credentials or any other security information
-	 */
-	public String getLocation() {
-		if (url.getUserInfo() == null) {
-			return url.toExternalForm();
-		}
-		if (url.getPort() != -1) {
-			return String.format("%s://%s:%d%s", url.getProtocol(), url.getHost(), url.getPort(), url.getPath());
-		} else {
-			return String.format("%s://%s%s", url.getProtocol(), url.getHost(), url.getPath());
-		}
-	}
-
-	public List<Nodeid> heads() throws HgException {
-		try {
-			URL u = new URL(url, url.getPath() + "?cmd=heads");
-			HttpURLConnection c = setupConnection(u.openConnection());
-			c.connect();
-			if (debug) {
-				dumpResponseHeader(u, c);
-			}
-			InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII");
-			StreamTokenizer st = new StreamTokenizer(is);
-			st.ordinaryChars('0', '9');
-			st.wordChars('0', '9');
-			st.eolIsSignificant(false);
-			LinkedList<Nodeid> parseResult = new LinkedList<Nodeid>();
-			while (st.nextToken() != StreamTokenizer.TT_EOF) {
-				parseResult.add(Nodeid.fromAscii(st.sval));
-			}
-			return parseResult;
-		} catch (MalformedURLException ex) {
-			throw new HgException(ex);
-		} catch (IOException ex) {
-			throw new HgException(ex);
-		}
-	}
-	
-	public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgException {
-		Range r = new Range(base, tip);
-		// XXX shall handle errors like no range key in the returned map, not sure how.
-		return between(Collections.singletonList(r)).get(r);
-	}
-
-	/**
-	 * @param ranges
-	 * @return map, where keys are input instances, values are corresponding server reply
-	 * @throws HgException 
-	 */
-	public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgException {
-		if (ranges.isEmpty()) {
-			return Collections.emptyMap();
-		}
-		// if fact, shall do other way round, this method shall send 
-		LinkedHashMap<Range, List<Nodeid>> rv = new LinkedHashMap<HgRemoteRepository.Range, List<Nodeid>>(ranges.size() * 4 / 3);
-		StringBuilder sb = new StringBuilder(20 + ranges.size() * 82);
-		sb.append("pairs=");
-		for (Range r : ranges) {
-			sb.append(r.end.toString());
-			sb.append('-');
-			sb.append(r.start.toString());
-			sb.append('+');
-		}
-		if (sb.charAt(sb.length() - 1) == '+') {
-			// strip last space 
-			sb.setLength(sb.length() - 1);
-		}
-		try {
-			boolean usePOST = ranges.size() > 3;
-			URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString()));
-			HttpURLConnection c = setupConnection(u.openConnection());
-			if (usePOST) {
-				c.setRequestMethod("POST");
-				c.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */));
-				c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-				c.setDoOutput(true);
-				c.connect();
-				OutputStream os = c.getOutputStream();
-				os.write(sb.toString().getBytes());
-				os.flush();
-				os.close();
-			} else {
-				c.connect();
-			}
-			if (debug) {
-				System.out.printf("%d ranges, method:%s \n", ranges.size(), c.getRequestMethod());
-				dumpResponseHeader(u, c);
-			}
-			InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII");
-			StreamTokenizer st = new StreamTokenizer(is);
-			st.ordinaryChars('0', '9');
-			st.wordChars('0', '9');
-			st.eolIsSignificant(true);
-			Iterator<Range> rangeItr = ranges.iterator();
-			LinkedList<Nodeid> currRangeList = null;
-			Range currRange = null;
-			boolean possiblyEmptyNextLine = true;
-			while (st.nextToken() != StreamTokenizer.TT_EOF) {
-				if (st.ttype == StreamTokenizer.TT_EOL) {
-					if (possiblyEmptyNextLine) {
-						// newline follows newline;
-						assert currRange == null;
-						assert currRangeList == null;
-						if (!rangeItr.hasNext()) {
-							throw new HgBadStateException();
-						}
-						rv.put(rangeItr.next(), Collections.<Nodeid>emptyList());
-					} else {
-						if (currRange == null || currRangeList == null) {
-							throw new HgBadStateException();
-						}
-						// indicate next range value is needed
-						currRange = null;
-						currRangeList = null;
-						possiblyEmptyNextLine = true;
-					}
-				} else {
-					possiblyEmptyNextLine = false;
-					if (currRange == null) {
-						if (!rangeItr.hasNext()) {
-							throw new HgBadStateException();
-						}
-						currRange = rangeItr.next();
-						currRangeList = new LinkedList<Nodeid>();
-						rv.put(currRange, currRangeList);
-					}
-					Nodeid nid = Nodeid.fromAscii(st.sval);
-					currRangeList.addLast(nid);
-				}
-			}
-			is.close();
-			return rv;
-		} catch (MalformedURLException ex) {
-			throw new HgException(ex);
-		} catch (IOException ex) {
-			throw new HgException(ex);
-		}
-	}
-
-	public List<RemoteBranch> branches(List<Nodeid> nodes) throws HgException {
-		StringBuilder sb = new StringBuilder(20 + nodes.size() * 41);
-		sb.append("nodes=");
-		for (Nodeid n : nodes) {
-			sb.append(n.toString());
-			sb.append('+');
-		}
-		if (sb.charAt(sb.length() - 1) == '+') {
-			// strip last space 
-			sb.setLength(sb.length() - 1);
-		}
-		try {
-			URL u = new URL(url, url.getPath() + "?cmd=branches&" + sb.toString());
-			HttpURLConnection c = setupConnection(u.openConnection());
-			c.connect();
-			if (debug) {
-				dumpResponseHeader(u, c);
-			}
-			InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII");
-			StreamTokenizer st = new StreamTokenizer(is);
-			st.ordinaryChars('0', '9');
-			st.wordChars('0', '9');
-			st.eolIsSignificant(false);
-			ArrayList<Nodeid> parseResult = new ArrayList<Nodeid>(nodes.size() * 4);
-			while (st.nextToken() != StreamTokenizer.TT_EOF) {
-				parseResult.add(Nodeid.fromAscii(st.sval));
-			}
-			if (parseResult.size() != nodes.size() * 4) {
-				throw new HgException(String.format("Bad number of nodeids in result (shall be factor 4), expected %d, got %d", nodes.size()*4, parseResult.size()));
-			}
-			ArrayList<RemoteBranch> rv = new ArrayList<RemoteBranch>(nodes.size());
-			for (int i = 0; i < nodes.size(); i++) {
-				RemoteBranch rb = new RemoteBranch(parseResult.get(i*4), parseResult.get(i*4 + 1), parseResult.get(i*4 + 2), parseResult.get(i*4 + 3));
-				rv.add(rb);
-			}
-			return rv;
-		} catch (MalformedURLException ex) {
-			throw new HgException(ex);
-		} catch (IOException ex) {
-			throw new HgException(ex);
-		}
-	}
-
-	/*
-	 * XXX need to describe behavior when roots arg is empty; our RepositoryComparator code currently returns empty lists when
-	 * no common elements found, which in turn means we need to query changes starting with NULL nodeid.
-	 * 
-	 * WireProtocol wiki: roots = a list of the latest nodes on every service side changeset branch that both the client and server know about.
-	 * 
-	 * Perhaps, shall be named 'changegroup'
-
-	 * Changegroup: 
-	 * http://mercurial.selenic.com/wiki/Merge 
-	 * http://mercurial.selenic.com/wiki/WireProtocol 
-	 * 
-	 * according to latter, bundleformat data is sent through zlib
-	 * (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 HgException {
-		List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots;
-		StringBuilder sb = new StringBuilder(20 + _roots.size() * 41);
-		sb.append("roots=");
-		for (Nodeid n : _roots) {
-			sb.append(n.toString());
-			sb.append('+');
-		}
-		if (sb.charAt(sb.length() - 1) == '+') {
-			// strip last space 
-			sb.setLength(sb.length() - 1);
-		}
-		try {
-			URL u = new URL(url, url.getPath() + "?cmd=changegroup&" + sb.toString());
-			HttpURLConnection c = setupConnection(u.openConnection());
-			c.connect();
-			if (debug) {
-				dumpResponseHeader(u, c);
-			}
-			File tf = writeBundle(c.getInputStream(), false, "HG10GZ" /*didn't see any other that zip*/);
-			if (debug) {
-				System.out.printf("Wrote bundle %s for roots %s\n", tf, sb);
-			}
-			return getLookupHelper().loadBundle(tf);
-		} catch (MalformedURLException ex) {
-			throw new HgException(ex);
-		} catch (IOException ex) {
-			throw new HgException(ex);
-		}
-	}
-
-	@Override
-	public String toString() {
-		return getClass().getSimpleName() + '[' + getLocation() + ']';
-	}
-
-	private HgLookup getLookupHelper() {
-		if (lookupHelper == null) {
-			lookupHelper = new HgLookup();
-		}
-		return lookupHelper;
-	}
-	
-	private HttpURLConnection setupConnection(URLConnection urlConnection) {
-		urlConnection.setRequestProperty("User-Agent", "hg4j/0.5.0");
-		urlConnection.addRequestProperty("Accept", "application/mercurial-0.1");
-		if (authInfo != null) {
-			urlConnection.addRequestProperty("Authorization", "Basic " + authInfo);
-		}
-		if (sslContext != null) {
-			((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory());
-		}
-		return (HttpURLConnection) urlConnection;
-	}
-
-	private void dumpResponseHeader(URL u, HttpURLConnection c) {
-		System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery());
-		System.out.println("Response headers:");
-		final Map<String, List<String>> headerFields = c.getHeaderFields();
-		for (String s : headerFields.keySet()) {
-			System.out.printf("%s: %s\n", s, c.getHeaderField(s));
-		}
-	}
-	
-	private static File writeBundle(InputStream is, boolean decompress, String header) throws IOException {
-		InputStream zipStream = decompress ? new InflaterInputStream(is) : is;
-		File tf = File.createTempFile("hg-bundle-", null);
-		FileOutputStream fos = new FileOutputStream(tf);
-		fos.write(header.getBytes());
-		int r;
-		byte[] buf = new byte[8*1024];
-		while ((r = zipStream.read(buf)) != -1) {
-			fos.write(buf, 0, r);
-		}
-		fos.close();
-		zipStream.close();
-		return tf;
-	}
-
-
-	public static final class Range {
-		/**
-		 * Root of the range, earlier revision
-		 */
-		public final Nodeid start;
-		/**
-		 * Head of the range, later revision.
-		 */
-		public final Nodeid end;
-		
-		/**
-		 * @param from - root/base revision
-		 * @param to - head/tip revision
-		 */
-		public Range(Nodeid from, Nodeid to) {
-			start = from;
-			end = to;
-		}
-	}
-
-	public static final class RemoteBranch {
-		public final Nodeid head, root, p1, p2;
-		
-		public RemoteBranch(Nodeid h, Nodeid r, Nodeid parent1, Nodeid parent2) {
-			head = h;
-			root = r;
-			p1 = parent1;
-			p2 = parent2;
-		}
-
-		@Override
-		public boolean equals(Object obj) {
-			if (this == obj) {
-				return true;
-			}
-			if (false == obj instanceof RemoteBranch) {
-				return false;
-			}
-			RemoteBranch o = (RemoteBranch) obj;
-			return head.equals(o.head) && root.equals(o.root) && (p1 == null && o.p1 == null || p1.equals(o.p1)) && (p2 == null && o.p2 == null || p2.equals(o.p2));
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,287 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.SoftReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-
-import org.tmatesoft.hg.internal.ConfigFile;
-import org.tmatesoft.hg.internal.DataAccessProvider;
-import org.tmatesoft.hg.internal.Filter;
-import org.tmatesoft.hg.internal.RelativePathRewrite;
-import org.tmatesoft.hg.internal.RequiresFile;
-import org.tmatesoft.hg.internal.RevlogStream;
-import org.tmatesoft.hg.util.FileIterator;
-import org.tmatesoft.hg.util.FileWalker;
-import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.PathRewrite;
-
-
-
-/**
- * Shall be as state-less as possible, all the caching happens outside the repo, in commands/walkers
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public final class HgRepository {
-
-	// if new constants added, consider fixing HgInternals#wrongLocalRevision
-	public static final int TIP = -3;
-	public static final int BAD_REVISION = Integer.MIN_VALUE;
-	public static final int WORKING_COPY = -2;
-
-	// temp aux marker method
-	public static IllegalStateException notImplemented() {
-		return new IllegalStateException("Not implemented");
-	}
-	
-	private final File repoDir; // .hg folder
-	private final String repoLocation;
-	private final DataAccessProvider dataAccess;
-	private final PathRewrite normalizePath;
-	private final PathRewrite dataPathHelper;
-	private final PathRewrite repoPathHelper;
-
-	private HgChangelog changelog;
-	private HgManifest manifest;
-	private HgTags tags;
-	// XXX perhaps, shall enable caching explicitly
-	private final HashMap<Path, SoftReference<RevlogStream>> streamsCache = new HashMap<Path, SoftReference<RevlogStream>>();
-	
-	private final org.tmatesoft.hg.internal.Internals impl = new org.tmatesoft.hg.internal.Internals();
-	private HgIgnore ignore;
-	private ConfigFile configFile;
-
-	HgRepository(String repositoryPath) {
-		repoDir = null;
-		repoLocation = repositoryPath;
-		dataAccess = null;
-		dataPathHelper = repoPathHelper = null;
-		normalizePath = null;
-	}
-	
-	HgRepository(String repositoryPath, File repositoryRoot) {
-		assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory();
-		assert repositoryPath != null; 
-		assert repositoryRoot != null;
-		repoDir = repositoryRoot;
-		repoLocation = repositoryPath;
-		dataAccess = new DataAccessProvider();
-		final boolean runningOnWindows = System.getProperty("os.name").indexOf("Windows") != -1;
-		if (runningOnWindows) {
-			normalizePath = new PathRewrite() {
-					
-					public String rewrite(String path) {
-						// TODO handle . and .. (although unlikely to face them from GUI client)
-						path = path.replace('\\', '/').replace("//", "/");
-						if (path.startsWith("/")) {
-							path = path.substring(1);
-						}
-						return path;
-					}
-				};
-		} else {
-			normalizePath = new PathRewrite.Empty(); // or strip leading slash, perhaps? 
-		}
-		parseRequires();
-		dataPathHelper = impl.buildDataFilesHelper();
-		repoPathHelper = impl.buildRepositoryFilesHelper();
-	}
-
-	@Override
-	public String toString() {
-		return getClass().getSimpleName() + "[" + getLocation() + (isInvalid() ? "(BAD)" : "") + "]";
-	}                         
-	
-	public String getLocation() {
-		return repoLocation;
-	}
-
-	public boolean isInvalid() {
-		return repoDir == null || !repoDir.exists() || !repoDir.isDirectory();
-	}
-	
-	public HgChangelog getChangelog() {
-		if (this.changelog == null) {
-			String storagePath = repoPathHelper.rewrite("00changelog.i");
-			RevlogStream content = resolve(Path.create(storagePath), true);
-			this.changelog = new HgChangelog(this, content);
-		}
-		return this.changelog;
-	}
-	
-	public HgManifest getManifest() {
-		if (this.manifest == null) {
-			RevlogStream content = resolve(Path.create(repoPathHelper.rewrite("00manifest.i")), true);
-			this.manifest = new HgManifest(this, content);
-		}
-		return this.manifest;
-	}
-	
-	public final HgTags getTags() {
-		if (tags == null) {
-			tags = new HgTags();
-			try {
-				tags.readGlobal(new File(repoDir.getParentFile(), ".hgtags"));
-				tags.readLocal(new File(repoDir, "localtags"));
-			} catch (IOException ex) {
-				ex.printStackTrace(); // FIXME log or othewise report
-			}
-		}
-		return tags;
-	}
-	
-	public HgDataFile getFileNode(String path) {
-		String nPath = normalizePath.rewrite(path);
-		String storagePath = dataPathHelper.rewrite(nPath);
-		RevlogStream content = resolve(Path.create(storagePath), false);
-		Path p = Path.create(nPath);
-		if (content == null) {
-			return new HgDataFile(this, p);
-		}
-		return new HgDataFile(this, p, content);
-	}
-
-	public HgDataFile getFileNode(Path path) {
-		String storagePath = dataPathHelper.rewrite(path.toString());
-		RevlogStream content = resolve(Path.create(storagePath), false);
-		// XXX no content when no file? or HgDataFile.exists() to detect that?
-		if (content == null) {
-			return new HgDataFile(this, path);
-		}
-		return new HgDataFile(this, path, content);
-	}
-
-	/* clients need to rewrite path from their FS to a repository-friendly paths, and, perhaps, vice versa*/
-	public PathRewrite getToRepoPathHelper() {
-		return normalizePath;
-	}
-
-	// local to hide use of io.File. 
-	/*package-local*/ File getRepositoryRoot() {
-		return repoDir;
-	}
-
-	// XXX package-local, unless there are cases when required from outside (guess, working dir/revision walkers may hide dirstate access and no public visibility needed)
-	/*package-local*/ final HgDirstate loadDirstate() {
-		return new HgDirstate(getDataAccess(), new File(repoDir, "dirstate"));
-	}
-
-	// package-local, see comment for loadDirstate
-	/*package-local*/ final HgIgnore getIgnore() {
-		// TODO read config for additional locations
-		if (ignore == null) {
-			ignore = new HgIgnore();
-			try {
-				File ignoreFile = new File(repoDir.getParentFile(), ".hgignore");
-				ignore.read(ignoreFile);
-			} catch (IOException ex) {
-				ex.printStackTrace(); // log warn
-			}
-		}
-		return ignore;
-	}
-
-	/*package-local*/ DataAccessProvider getDataAccess() {
-		return dataAccess;
-	}
-
-	// FIXME not sure repository shall create walkers
-	/*package-local*/ FileIterator createWorkingDirWalker() {
-		File repoRoot = repoDir.getParentFile();
-		Path.Source pathSrc = new Path.SimpleSource(new PathRewrite.Composite(new RelativePathRewrite(repoRoot), getToRepoPathHelper()));
-		// Impl note: simple source is enough as files in the working dir are all unique
-		// even if they might get reused (i.e. after FileIterator#reset() and walking once again),
-		// path caching is better to be done in the code which knows that path are being reused 
-		return new FileWalker(repoRoot, pathSrc);
-	}
-
-	/**
-	 * Perhaps, should be separate interface, like ContentLookup
-	 * path - repository storage path (i.e. one usually with .i or .d)
-	 */
-	/*package-local*/ RevlogStream resolve(Path path, boolean shallFakeNonExistent) {
-		final SoftReference<RevlogStream> ref = streamsCache.get(path);
-		RevlogStream cached = ref == null ? null : ref.get();
-		if (cached != null) {
-			return cached;
-		}
-		File f = new File(repoDir, path.toString());
-		if (f.exists()) {
-			RevlogStream s = new RevlogStream(dataAccess, f);
-			streamsCache.put(path, new SoftReference<RevlogStream>(s));
-			return s;
-		} else {
-			if (shallFakeNonExistent) {
-				try {
-					File fake = File.createTempFile(f.getName(), null);
-					fake.deleteOnExit();
-					return new RevlogStream(dataAccess, fake);
-				} catch (IOException ex) {
-					ex.printStackTrace(); // FIXME report in debug
-				}
-			}
-		}
-		return null; // XXX empty stream instead?
-	}
-	
-	// 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"));
-		}
-		return configFile;
-	}
-	
-	/*package-local*/ List<Filter> getFiltersFromRepoToWorkingDir(Path p) {
-		return instantiateFilters(p, new Filter.Options(Filter.Direction.FromRepo));
-	}
-
-	/*package-local*/ List<Filter> getFiltersFromWorkingDirToRepo(Path p) {
-		return instantiateFilters(p, new Filter.Options(Filter.Direction.ToRepo));
-	}
-
-	private List<Filter> instantiateFilters(Path p, Filter.Options opts) {
-		List<Filter.Factory> factories = impl.getFilters(this, getConfigFile());
-		if (factories.isEmpty()) {
-			return Collections.emptyList();
-		}
-		ArrayList<Filter> rv = new ArrayList<Filter>(factories.size());
-		for (Filter.Factory ff : factories) {
-			Filter f = ff.create(p, opts);
-			if (f != null) {
-				rv.add(f);
-			}
-		}
-		return rv;
-	}
-
-	private void parseRequires() {
-		new RequiresFile().parse(impl, new File(repoDir, "requires"));
-	}
-
-}
--- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,466 +0,0 @@
-/*
- * 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.repo;
-
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.core.HgDataStreamException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.Pool;
-import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.PathPool;
-import org.tmatesoft.hg.util.PathRewrite;
-
-
-/**
- * RevisionWalker?
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgStatusCollector {
-
-	private final HgRepository repo;
-	private final SortedMap<Integer, ManifestRevisionInspector> cache; // sparse array, in fact
-	// with cpython repository, ~70 000 changes, complete Log (direct out, no reverse) output 
-	// no cache limit, no nodeids and fname caching - OOME on changeset 1035
-	// no cache limit, but with cached nodeids and filenames - 1730+
-	// cache limit 100 - 19+ minutes to process 10000, and still working (too long, stopped)
-	private final int cacheMaxSize = 50; // do not keep too much manifest revisions
-	private PathPool pathPool;
-	private final Pool<Nodeid> cacheNodes;
-	private final Pool<String> cacheFilenames; // XXX in fact, need to think if use of PathPool directly instead is better solution
-	private final ManifestRevisionInspector emptyFakeState;
-	
-
-	public HgStatusCollector(HgRepository hgRepo) {
-		this.repo = hgRepo;
-		cache = new TreeMap<Integer, ManifestRevisionInspector>();
-		cacheNodes = new Pool<Nodeid>();
-		cacheFilenames = new Pool<String>();
-
-		emptyFakeState = new ManifestRevisionInspector(null, null);
-		emptyFakeState.begin(-1, null);
-		emptyFakeState.end(-1);
-	}
-	
-	public HgRepository getRepo() {
-		return repo;
-	}
-	
-	private ManifestRevisionInspector get(int rev) {
-		ManifestRevisionInspector i = cache.get(rev);
-		if (i == null) {
-			if (rev == -1) {
-				return emptyFakeState;
-			}
-			while (cache.size() > cacheMaxSize) {
-				// assume usually we go from oldest to newest, hence remove oldest as most likely to be no longer necessary
-				cache.remove(cache.firstKey());
-			}
-			i = new ManifestRevisionInspector(cacheNodes, cacheFilenames);
-			cache.put(rev, i);
-			repo.getManifest().walk(rev, rev, i);
-		}
-		return i;
-	}
-
-	private boolean cached(int revision) {
-		return cache.containsKey(revision) || revision == -1;
-	}
-	
-	private void initCacheRange(int minRev, int maxRev) {
-		while (cache.size() > cacheMaxSize) {
-			// assume usually we go from oldest to newest, hence remove oldest as most likely to be no longer necessary
-			cache.remove(cache.firstKey());
-		}
-		repo.getManifest().walk(minRev, maxRev, new HgManifest.Inspector() {
-			private ManifestRevisionInspector delegate;
-			private boolean cacheHit; // range may include revisions we already know about, do not re-create them
-
-			public boolean begin(int revision, Nodeid nid) {
-				assert delegate == null;
-				if (cache.containsKey(revision)) { // don't need to check emptyFakeState hit as revision never -1 here
-					cacheHit = true;
-				} else {
-					cache.put(revision, delegate = new ManifestRevisionInspector(cacheNodes, cacheFilenames));
-					// cache may grow bigger than max size here, but it's ok as present simplistic cache clearing mechanism may
-					// otherwise remove entries we just added
-					delegate.begin(revision, nid);
-					cacheHit = false;
-				}
-				return true;
-			}
-
-			public boolean next(Nodeid nid, String fname, String flags) {
-				if (!cacheHit) {
-					delegate.next(nid, fname, flags);
-				}
-				return true;
-			}
-			
-			public boolean end(int revision) {
-				if (!cacheHit) {
-					delegate.end(revision);
-				}
-				cacheHit = false;				
-				delegate = null;
-				return true;
-			}
-		});
-	}
-	
-	/*package-local*/ ManifestRevisionInspector raw(int rev) {
-		return get(rev);
-	}
-	/*package-local*/ PathPool getPathPool() {
-		if (pathPool == null) {
-			pathPool = new PathPool(new PathRewrite.Empty());
-		}
-		return pathPool;
-	}
-
-	/**
-	 * Allows sharing of a common path cache 
-	 */
-	public void setPathPool(PathPool pathPool) {
-		this.pathPool = pathPool;
-	}
-		
-	
-	// hg status --change <rev>
-	public void change(int rev, HgStatusInspector inspector) {
-		int[] parents = new int[2];
-		repo.getChangelog().parents(rev, parents, null, null);
-		walk(parents[0], rev, inspector);
-	}
-	
-	// I assume revision numbers are the same for changelog and manifest - here 
-	// user would like to pass changelog revision numbers, and I use them directly to walk manifest.
-	// if this assumption is wrong, fix this (lookup manifest revisions from changeset).
-	// rev1 and rev2 may be -1 to indicate comparison to empty repository
-	// argument order matters 
-	public void walk(int rev1, int rev2, HgStatusInspector inspector) {
-		if (rev1 == rev2) {
-			throw new IllegalArgumentException();
-		}
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		if (inspector instanceof Record) {
-			((Record) inspector).init(rev1, rev2, this);
-		}
-		final int lastManifestRevision = repo.getManifest().getLastRevision();
-		if (rev1 == TIP) {
-			rev1 = lastManifestRevision;
-		}
-		if (rev2 == TIP) {
-			rev2 = lastManifestRevision; 
-		}
-		// in fact, rev1 and rev2 are often next (or close) to each other,
-		// thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2))
-		ManifestRevisionInspector r1, r2 ;
-		boolean need1 = !cached(rev1), need2 = !cached(rev2);
-		if (need1 || need2) {
-			int minRev, maxRev;
-			if (need1 && need2 && Math.abs(rev1 - rev2) < 5 /*subjective equivalent of 'close enough'*/) {
-				minRev = rev1 < rev2 ? rev1 : rev2;
-				maxRev = minRev == rev1 ? rev2 : rev1;
-				if (minRev > 0) {
-					minRev--; // expand range a bit
-				}
-				initCacheRange(minRev, maxRev);
-				need1 = need2 = false;
-			}
-			// either both unknown and far from each other, or just one of them.
-			// read with neighbors to save potential subsequent calls for neighboring elements
-			// XXX perhaps, if revlog.baseRevision is cheap, shall expand minRev up to baseRevision
-			// which going to be read anyway
-			if (need1) {
-				minRev = rev1;
-				maxRev = rev1 < lastManifestRevision-5 ? rev1+5 : lastManifestRevision;
-				initCacheRange(minRev, maxRev);
-			}
-			if (need2) {
-				minRev = rev2;
-				maxRev = rev2 < lastManifestRevision-5 ? rev2+5 : lastManifestRevision;
-				initCacheRange(minRev, maxRev);
-			}
-		}
-		r1 = get(rev1);
-		r2 = get(rev2);
-
-		PathPool pp = getPathPool();
-
-		TreeSet<String> r1Files = new TreeSet<String>(r1.files());
-		for (String fname : r2.files()) {
-			if (r1Files.remove(fname)) {
-				Nodeid nidR1 = r1.nodeid(fname);
-				Nodeid nidR2 = r2.nodeid(fname);
-				String flagsR1 = r1.flags(fname);
-				String flagsR2 = r2.flags(fname);
-				if (nidR1.equals(nidR2) && ((flagsR2 == null && flagsR1 == null) || flagsR2.equals(flagsR1))) {
-					inspector.clean(pp.path(fname));
-				} else {
-					inspector.modified(pp.path(fname));
-				}
-			} else {
-				try {
-					Path copyTarget = pp.path(fname);
-					Path copyOrigin = getOriginIfCopy(repo, copyTarget, r1Files, rev1);
-					if (copyOrigin != null) {
-						inspector.copied(pp.path(copyOrigin) /*pipe through pool, just in case*/, copyTarget);
-					} else {
-						inspector.added(copyTarget);
-					}
-				} catch (HgDataStreamException ex) {
-					ex.printStackTrace();
-					// FIXME perhaps, shall record this exception to dedicated mediator and continue
-					// for a single file not to be irresolvable obstacle for a status operation
-				}
-			}
-		}
-		for (String left : r1Files) {
-			inspector.removed(pp.path(left));
-		}
-	}
-	
-	public Record status(int rev1, int rev2) {
-		Record rv = new Record();
-		walk(rev1, rev2, rv);
-		return rv;
-	}
-	
-	/*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection<String> originals, int originalChangelogRevision) throws HgDataStreamException {
-		HgDataFile df = hgRepo.getFileNode(fname);
-		while (df.isCopy()) {
-			Path original = df.getCopySourceName();
-			if (originals.contains(original.toString())) {
-				df = hgRepo.getFileNode(original);
-				int changelogRevision = df.getChangesetLocalRevision(0);
-				if (changelogRevision <= originalChangelogRevision) {
-					// copy/rename source was known prior to rev1 
-					// (both r1Files.contains is true and original was created earlier than rev1)
-					// without r1Files.contains changelogRevision <= rev1 won't suffice as the file
-					// might get removed somewhere in between (changelogRevision < R < rev1)
-					return original;
-				}
-				break; // copy/rename done later
-			} 
-			df = hgRepo.getFileNode(original); // try more steps away
-		}
-		return null;
-	}
-
-	// XXX for r1..r2 status, only modified, added, removed (and perhaps, clean) make sense
-	// XXX Need to specify whether copy targets are in added or not (@see Inspector#copied above)
-	public static class Record implements HgStatusInspector {
-		private List<Path> modified, added, removed, clean, missing, unknown, ignored;
-		private Map<Path, Path> copied;
-		
-		private int startRev, endRev;
-		private HgStatusCollector statusHelper;
-		
-		// XXX StatusCollector may additionally initialize Record instance to speed lookup of changed file revisions
-		// here I need access to ManifestRevisionInspector via #raw(). Perhaps, non-static class (to get
-		// implicit reference to StatusCollector) may be better?
-		// Since users may want to reuse Record instance we've once created (and initialized), we need to  
-		// ensure functionality is correct for each/any call (#walk checks instanceof Record and fixes it up)
-		// Perhaps, distinct helper (sc.getRevisionHelper().nodeid(fname)) would be better, just not clear
-		// how to supply [start..end] values there easily
-		/*package-local*/void init(int startRevision, int endRevision, HgStatusCollector self) {
-			startRev = startRevision;
-			endRev = endRevision;
-			statusHelper = self;
-		}
-		
-		public Nodeid nodeidBeforeChange(Path fname) {
-			if (statusHelper == null || startRev == BAD_REVISION) {
-				return null;
-			}
-			if ((modified == null || !modified.contains(fname)) && (removed == null || !removed.contains(fname))) {
-				return null;
-			}
-			return statusHelper.raw(startRev).nodeid(fname.toString());
-		}
-		public Nodeid nodeidAfterChange(Path fname) {
-			if (statusHelper == null || endRev == BAD_REVISION) {
-				return null;
-			}
-			if ((modified == null || !modified.contains(fname)) && (added == null || !added.contains(fname))) {
-				return null;
-			}
-			return statusHelper.raw(endRev).nodeid(fname.toString());
-		}
-		
-		public List<Path> getModified() {
-			return proper(modified);
-		}
-
-		public List<Path> getAdded() {
-			return proper(added);
-		}
-
-		public List<Path> getRemoved() {
-			return proper(removed);
-		}
-
-		public Map<Path,Path> getCopied() {
-			if (copied == null) {
-				return Collections.emptyMap();
-			}
-			return Collections.unmodifiableMap(copied);
-		}
-
-		public List<Path> getClean() {
-			return proper(clean);
-		}
-
-		public List<Path> getMissing() {
-			return proper(missing);
-		}
-
-		public List<Path> getUnknown() {
-			return proper(unknown);
-		}
-
-		public List<Path> getIgnored() {
-			return proper(ignored);
-		}
-		
-		private List<Path> proper(List<Path> l) {
-			if (l == null) {
-				return Collections.emptyList();
-			}
-			return Collections.unmodifiableList(l);
-		}
-
-		//
-		//
-		
-		public void modified(Path fname) {
-			modified = doAdd(modified, fname);
-		}
-
-		public void added(Path fname) {
-			added = doAdd(added, fname);
-		}
-
-		public void copied(Path fnameOrigin, Path fnameAdded) {
-			if (copied == null) {
-				copied = new LinkedHashMap<Path, Path>();
-			}
-			added(fnameAdded);
-			copied.put(fnameAdded, fnameOrigin);
-		}
-
-		public void removed(Path fname) {
-			removed = doAdd(removed, fname);
-		}
-
-		public void clean(Path fname) {
-			clean = doAdd(clean, fname);
-		}
-
-		public void missing(Path fname) {
-			missing = doAdd(missing, fname);
-		}
-
-		public void unknown(Path fname) {
-			unknown = doAdd(unknown, fname);
-		}
-
-		public void ignored(Path fname) {
-			ignored = doAdd(ignored, fname);
-		}
-
-		private static List<Path> doAdd(List<Path> l, Path p) {
-			if (l == null) {
-				l = new LinkedList<Path>();
-			}
-			l.add(p);
-			return l;
-		}
-	}
-	
-	/*package-local*/ static final class ManifestRevisionInspector implements HgManifest.Inspector {
-		private final TreeMap<String, Nodeid> idsMap;
-		private final TreeMap<String, String> flagsMap;
-		private final Pool<Nodeid> idsPool;
-		private final Pool<String> namesPool; 
-
-		// optional pools for effective management of nodeids and filenames (they are likely
-		// to be duplicated among different manifest revisions
-		public ManifestRevisionInspector(Pool<Nodeid> nodeidPool, Pool<String> filenamePool) {
-			idsPool = nodeidPool;
-			namesPool = filenamePool;
-			idsMap = new TreeMap<String, Nodeid>();
-			flagsMap = new TreeMap<String, String>();
-		}
-		
-		public Collection<String> files() {
-			return idsMap.keySet();
-		}
-
-		public Nodeid nodeid(String fname) {
-			return idsMap.get(fname);
-		}
-
-		public String flags(String fname) {
-			return flagsMap.get(fname);
-		}
-
-		//
-
-		public boolean next(Nodeid nid, String fname, String flags) {
-			if (namesPool != null) {
-				fname = namesPool.unify(fname);
-			}
-			if (idsPool != null) {
-				nid = idsPool.unify(nid);
-			}
-			idsMap.put(fname, nid);
-			if (flags != null) {
-				// TreeMap$Entry takes 32 bytes. No reason to keep null for such price
-				// Perhaps, Map<String, Pair<Nodeid, String>> might be better solution
-				flagsMap.put(fname, flags);
-			}
-			return true;
-		}
-
-		public boolean end(int revision) {
-			// in fact, this class cares about single revision
-			return false; 
-		}
-
-		public boolean begin(int revision, Nodeid nid) {
-			return true;
-		}
-	}
-
-}
--- a/src/org/tmatesoft/hg/repo/HgStatusInspector.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import org.tmatesoft.hg.util.Path;
-
-/**
- * Callback to get file status information
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface HgStatusInspector {
-	void modified(Path fname);
-	void added(Path fname);
-	// XXX need to specify whether StatusCollector invokes added() along with copied or not!
-	void copied(Path fnameOrigin, Path fnameAdded); // if copied files of no interest, should delegate to self.added(fnameAdded);
-	void removed(Path fname);
-	void clean(Path fname);
-	void missing(Path fname); // aka deleted (tracked by Hg, but not available in FS any more
-	void unknown(Path fname); // not tracked
-	void ignored(Path fname);
-}
\ No newline at end of file
--- a/src/org/tmatesoft/hg/repo/HgTags.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,150 +0,0 @@
-/*
- * 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.repo;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.tmatesoft.hg.core.Nodeid;
-
-/**
- * @see http://mercurial.selenic.com/wiki/TagDesign
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgTags {
-	// global tags come from ".hgtags"
-	// local come from ".hg/localtags"
-
-	private final Map<Nodeid, List<String>> globalToName;
-	private final Map<Nodeid, List<String>> localToName;
-	private final Map<String, List<Nodeid>> globalFromName;
-	private final Map<String, List<Nodeid>> localFromName;
-	
-	
-	/*package-local*/ HgTags() {
-		globalToName =  new HashMap<Nodeid, List<String>>();
-		localToName  =  new HashMap<Nodeid, List<String>>();
-		globalFromName = new TreeMap<String, List<Nodeid>>();
-		localFromName  = new TreeMap<String, List<Nodeid>>();
-	}
-	
-	/*package-local*/ void readLocal(File localTags) throws IOException {
-		if (localTags == null || localTags.isDirectory()) {
-			throw new IllegalArgumentException(String.valueOf(localTags));
-		}
-		read(localTags, localToName, localFromName);
-	}
-	
-	/*package-local*/ void readGlobal(File globalTags) throws IOException {
-		if (globalTags == null || globalTags.isDirectory()) {
-			throw new IllegalArgumentException(String.valueOf(globalTags));
-		}
-		read(globalTags, globalToName, globalFromName);
-	}
-	
-	private void read(File f, Map<Nodeid,List<String>> nid2name, Map<String, List<Nodeid>> name2nid) throws IOException {
-		if (!f.canRead()) {
-			return;
-		}
-		BufferedReader r = null;
-		try {
-			r = new BufferedReader(new FileReader(f));
-			read(r, nid2name, name2nid);
-		} finally {
-			if (r != null) {
-				r.close();
-			}
-		}
-	}
-	
-	private void read(BufferedReader reader, Map<Nodeid,List<String>> nid2name, Map<String, List<Nodeid>> name2nid) throws IOException {
-		String line;
-		while ((line = reader.readLine()) != null) {
-			line = line.trim();
-			if (line.length() == 0) {
-				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?) 
-				continue;
-			}
-			int spacePos = line.indexOf(' ');
-			if (spacePos != -1) {
-				assert spacePos == 40;
-				final byte[] nodeidBytes = line.substring(0, spacePos).getBytes();
-				Nodeid nid = Nodeid.fromAscii(nodeidBytes, 0, nodeidBytes.length);
-				String tagName = line.substring(spacePos+1);
-				List<Nodeid> nids = name2nid.get(tagName);
-				if (nids == null) {
-					nids = new LinkedList<Nodeid>();
-					// tagName is substring of full line, thus need a copy to let the line be GC'ed
-					// new String(tagName.toCharArray()) is more expressive, but results in 1 extra arraycopy
-					tagName = new String(tagName);
-					name2nid.put(tagName, nids);
-				}
-				// XXX repo.getNodeidCache().nodeid(nid);
-				((LinkedList<Nodeid>) nids).addFirst(nid);
-				List<String> revTags = nid2name.get(nid);
-				if (revTags == null) {
-					revTags = new LinkedList<String>();
-					nid2name.put(nid, revTags);
-				}
-				revTags.add(tagName);
-			} else {
-				System.out.println("Bad tags line:" + line); // FIXME see above
-			}
-		}
-	}
-
-	public List<String> tags(Nodeid nid) {
-		ArrayList<String> rv = new ArrayList<String>(5);
-		List<String> l;
-		if ((l = localToName.get(nid)) != null) {
-			rv.addAll(l);
-		}
-		if ((l = globalToName.get(nid)) != null) {
-			rv.addAll(l);
-		}
-		return rv;
-	}
-
-	public boolean isTagged(Nodeid nid) {
-		return localToName.containsKey(nid) || globalToName.containsKey(nid);
-	}
-
-	public List<Nodeid> tagged(String tagName) {
-		ArrayList<Nodeid> rv = new ArrayList<Nodeid>(5);
-		List<Nodeid> l;
-		if ((l = localFromName.get(tagName)) != null) {
-			rv.addAll(l);
-		}
-		if ((l = globalFromName.get(tagName)) != null) {
-			rv.addAll(l);
-		}
-		return rv;
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,337 +0,0 @@
-/*
- * 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.repo;
-
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.Collections;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.core.HgDataStreamException;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.ByteArrayChannel;
-import org.tmatesoft.hg.internal.FilterByteChannel;
-import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector;
-import org.tmatesoft.hg.util.ByteChannel;
-import org.tmatesoft.hg.util.CancelledException;
-import org.tmatesoft.hg.util.FileIterator;
-import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.PathPool;
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class HgWorkingCopyStatusCollector {
-
-	private final HgRepository repo;
-	private final FileIterator repoWalker;
-	private HgDirstate dirstate;
-	private HgStatusCollector baseRevisionCollector;
-	private PathPool pathPool;
-
-	public HgWorkingCopyStatusCollector(HgRepository hgRepo) {
-		this(hgRepo, hgRepo.createWorkingDirWalker());
-	}
-
-	HgWorkingCopyStatusCollector(HgRepository hgRepo, FileIterator hgRepoWalker) {
-		this.repo = hgRepo;
-		this.repoWalker = hgRepoWalker;
-	}
-	
-	/**
-	 * Optionally, supply a collector instance that may cache (or have already cached) base revision
-	 * @param sc may be null
-	 */
-	public void setBaseRevisionCollector(HgStatusCollector sc) {
-		baseRevisionCollector = sc;
-	}
-
-	/*package-local*/ PathPool getPathPool() {
-		if (pathPool == null) {
-			if (baseRevisionCollector == null) {
-				pathPool = new PathPool(new PathRewrite.Empty());
-			} else {
-				return baseRevisionCollector.getPathPool();
-			}
-		}
-		return pathPool;
-	}
-
-	public void setPathPool(PathPool pathPool) {
-		this.pathPool = pathPool;
-	}
-
-	
-	private HgDirstate getDirstate() {
-		if (dirstate == null) {
-			dirstate = repo.loadDirstate();
-		}
-		return dirstate;
-	}
-
-	// may be invoked few times
-	public void walk(int baseRevision, HgStatusInspector inspector) {
-		final HgIgnore hgIgnore = repo.getIgnore();
-		TreeSet<String> knownEntries = getDirstate().all();
-		final boolean isTipBase;
-		if (baseRevision == TIP) {
-			baseRevision = repo.getManifest().getRevisionCount() - 1;
-			isTipBase = true;
-		} else {
-			isTipBase = baseRevision == repo.getManifest().getRevisionCount() - 1;
-		}
-		HgStatusCollector.ManifestRevisionInspector collect = null;
-		Set<String> baseRevFiles = Collections.emptySet();
-		if (!isTipBase) {
-			if (baseRevisionCollector != null) {
-				collect = baseRevisionCollector.raw(baseRevision);
-			} else {
-				collect = new HgStatusCollector.ManifestRevisionInspector(null, null);
-				repo.getManifest().walk(baseRevision, baseRevision, collect);
-			}
-			baseRevFiles = new TreeSet<String>(collect.files());
-		}
-		if (inspector instanceof HgStatusCollector.Record) {
-			HgStatusCollector sc = baseRevisionCollector == null ? new HgStatusCollector(repo) : baseRevisionCollector;
-			((HgStatusCollector.Record) inspector).init(baseRevision, BAD_REVISION, sc);
-		}
-		repoWalker.reset();
-		final PathPool pp = getPathPool();
-		while (repoWalker.hasNext()) {
-			repoWalker.next();
-			Path fname = repoWalker.name();
-			File f = repoWalker.file();
-			if (hgIgnore.isIgnored(fname)) {
-				inspector.ignored(pp.path(fname));
-			} else if (knownEntries.remove(fname.toString())) {
-				// modified, added, removed, clean
-				if (collect != null) { // need to check against base revision, not FS file
-					checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector);
-					baseRevFiles.remove(fname.toString());
-				} else {
-					checkLocalStatusAgainstFile(fname, f, inspector);
-				}
-			} else {
-				inspector.unknown(pp.path(fname));
-			}
-		}
-		if (collect != null) {
-			for (String r : baseRevFiles) {
-				inspector.removed(pp.path(r));
-			}
-		}
-		for (String m : knownEntries) {
-			// missing known file from a working dir  
-			if (getDirstate().checkRemoved(m) == null) {
-				// not removed from the repository = 'deleted'  
-				inspector.missing(pp.path(m));
-			} else {
-				// removed from the repo
-				// if we check against non-tip revision, do not report files that were added past that revision and now removed.
-				if (collect == null || baseRevFiles.contains(m)) {
-					inspector.removed(pp.path(m));
-				}
-			}
-		}
-	}
-
-	public HgStatusCollector.Record status(int baseRevision) {
-		HgStatusCollector.Record rv = new HgStatusCollector.Record();
-		walk(baseRevision, rv);
-		return rv;
-	}
-
-	//********************************************
-
-	
-	private void checkLocalStatusAgainstFile(Path fname, File f, HgStatusInspector inspector) {
-		HgDirstate.Record r;
-		if ((r = getDirstate().checkNormal(fname)) != null) {
-			// either clean or modified
-			if (f.lastModified() / 1000 == r.time && r.size == f.length()) {
-				inspector.clean(getPathPool().path(fname));
-			} else {
-				// check actual content to avoid false modified files
-				HgDataFile df = repo.getFileNode(fname);
-				if (!areTheSame(f, df, HgRepository.TIP)) {
-					inspector.modified(df.getPath());
-				} else {
-					inspector.clean(df.getPath());
-				}
-			}
-		} else if ((r = getDirstate().checkAdded(fname)) != null) {
-			if (r.name2 == null) {
-				inspector.added(getPathPool().path(fname));
-			} else {
-				inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname));
-			}
-		} else if ((r = getDirstate().checkRemoved(fname)) != null) {
-			inspector.removed(getPathPool().path(fname));
-		} else if ((r = getDirstate().checkMerged(fname)) != null) {
-			inspector.modified(getPathPool().path(fname));
-		}
-	}
-	
-	// XXX refactor checkLocalStatus methods in more OO way
-	private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, ManifestRevisionInspector collect, int baseRevision, Path fname, File f, HgStatusInspector inspector) {
-		// fname is in the dirstate, either Normal, Added, Removed or Merged
-		Nodeid nid1 = collect.nodeid(fname.toString());
-		String flags = collect.flags(fname.toString());
-		HgDirstate.Record r;
-		if (nid1 == null) {
-			// normal: added?
-			// added: not known at the time of baseRevision, shall report
-			// merged: was not known, report as added?
-			if ((r = getDirstate().checkNormal(fname)) != null) {
-				try {
-					Path origin = HgStatusCollector.getOriginIfCopy(repo, fname, baseRevNames, baseRevision);
-					if (origin != null) {
-						inspector.copied(getPathPool().path(origin), getPathPool().path(fname));
-						return;
-					}
-				} catch (HgDataStreamException ex) {
-					ex.printStackTrace();
-					// FIXME report to a mediator, continue status collection
-				}
-			} else if ((r = getDirstate().checkAdded(fname)) != null) {
-				if (r.name2 != null && baseRevNames.contains(r.name2)) {
-					baseRevNames.remove(r.name2); // XXX surely I shall not report rename source as Removed?
-					inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname));
-					return;
-				}
-				// fall-through, report as added
-			} else if (getDirstate().checkRemoved(fname) != null) {
-				// removed: removed file was not known at the time of baseRevision, and we should not report it as removed
-				return;
-			}
-			inspector.added(getPathPool().path(fname));
-		} else {
-			// was known; check whether clean or modified
-			// when added - seems to be the case of a file added once again, hence need to check if content is different
-			if ((r = getDirstate().checkNormal(fname)) != null || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) {
-				// either clean or modified
-				HgDataFile fileNode = repo.getFileNode(fname);
-				final int lengthAtRevision = fileNode.length(nid1);
-				if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) {
-					inspector.modified(getPathPool().path(fname));
-				} else {
-					// check actual content to see actual changes
-					if (areTheSame(f, fileNode, fileNode.getLocalRevision(nid1))) {
-						inspector.clean(getPathPool().path(fname));
-					} else {
-						inspector.modified(getPathPool().path(fname));
-					}
-				}
-			}
-			// only those left in idsMap after processing are reported as removed 
-		}
-
-		// TODO think over if content comparison may be done more effectively by e.g. calculating nodeid for a local file and comparing it with nodeid from manifest
-		// we don't need to tell exact difference, hash should be enough to detect difference, and it doesn't involve reading historical file content, and it's relatively 
-		// cheap to calc hash on a file (no need to keep it completely in memory). OTOH, if I'm right that the next approach is used for nodeids: 
-		// changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest
-		// then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids).
-		// The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean'
-	}
-
-	private boolean areTheSame(File f, HgDataFile dataFile, int localRevision) {
-		// XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
-		ByteArrayChannel bac = new ByteArrayChannel();
-		boolean ioFailed = false;
-		try {
-			// need content with metadata striped off - although theoretically chances are metadata may be different,
-			// WC doesn't have it anyway 
-			dataFile.content(localRevision, bac);
-		} catch (CancelledException ex) {
-			// silently ignore - can't happen, ByteArrayChannel is not cancellable
-		} catch (IOException ex) {
-			ioFailed = true;
-		} catch (HgException ex) {
-			ioFailed = true;
-		}
-		return !ioFailed && areTheSame(f, bac.toArray(), dataFile.getPath());
-	}
-	
-	private boolean areTheSame(File f, final byte[] data, Path p) {
-		FileInputStream fis = null;
-		try {
-			try {
-				fis = new FileInputStream(f);
-				FileChannel fc = fis.getChannel();
-				ByteBuffer fb = ByteBuffer.allocate(min(data.length, 8192));
-				final boolean[] checkValue = new boolean[] { true };
-				ByteChannel check = new ByteChannel() {
-					int x = 0;
-					final boolean debug = false; // XXX may want to add global variable to allow clients to turn 
-					public int write(ByteBuffer buffer) {
-						for (int i = buffer.remaining(); i > 0; i--, x++) {
-							if (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) + "<<");
-								}
-								checkValue[0] = false;
-								break;
-							}
-						}
-						buffer.position(buffer.limit()); // mark as read
-						return buffer.limit();
-					}
-				};
-				FilterByteChannel filters = new FilterByteChannel(check, repo.getFiltersFromWorkingDirToRepo(p));
-				while (fc.read(fb) != -1 && checkValue[0]) {
-					fb.flip();
-					filters.write(fb);
-					fb.compact();
-				}
-				return checkValue[0];
-			} catch (IOException ex) {
-				if (fis != null) {
-					fis.close();
-				}
-				ex.printStackTrace(); // log warn
-			}
-		} catch (/*TODO typed*/Exception ex) {
-			ex.printStackTrace();
-		}
-		return false;
-	}
-
-	private static String todoGenerateFlags(Path fname) {
-		// FIXME implement
-		return null;
-	}
-
-}
--- a/src/org/tmatesoft/hg/repo/Revlog.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,447 +0,0 @@
-/*
- * Copyright (c) 2010-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.repo;
-
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgException;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.RevlogStream;
-import org.tmatesoft.hg.util.ByteChannel;
-import org.tmatesoft.hg.util.CancelSupport;
-import org.tmatesoft.hg.util.CancelledException;
-import org.tmatesoft.hg.util.ProgressSupport;
-
-
-/**
- * Base class for all Mercurial entities that are serialized in a so called revlog format (changelog, manifest, data files).
- * 
- * Implementation note:
- * Hides actual actual revlog stream implementation and its access methods (i.e. RevlogStream.Inspector), iow shall not expose anything internal
- * in public methods.
- *   
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-abstract class Revlog {
-
-	private final HgRepository repo;
-	protected final RevlogStream content;
-
-	protected Revlog(HgRepository hgRepo, RevlogStream contentStream) {
-		if (hgRepo == null) {
-			throw new IllegalArgumentException();
-		}
-		if (contentStream == null) {
-			throw new IllegalArgumentException();
-		}
-		repo = hgRepo;
-		content = contentStream;
-	}
-	
-	// invalid Revlog
-	protected Revlog(HgRepository hgRepo) {
-		repo = hgRepo;
-		content = null;
-	}
-
-	public final HgRepository getRepo() {
-		return repo;
-	}
-
-	public final int getRevisionCount() {
-		return content.revisionCount();
-	}
-	
-	public final int getLastRevision() {
-		return content.revisionCount() - 1;
-	}
-	
-	public final Nodeid getRevision(int revision) {
-		// XXX cache nodeids?
-		return Nodeid.fromBinary(content.nodeid(revision), 0);
-	}
-
-	public final int getLocalRevision(Nodeid nid) {
-		int revision = content.findLocalRevisionNumber(nid);
-		if (revision == BAD_REVISION) {
-			throw new IllegalArgumentException(String.format("%s doesn't represent a revision of %s", nid.toString(), this /*XXX HgDataFile.getPath might be more suitable here*/));
-		}
-		return revision;
-	}
-
-	// Till now, i follow approach that NULL nodeid is never part of revlog
-	public final boolean isKnown(Nodeid nodeid) {
-		final int rn = content.findLocalRevisionNumber(nodeid);
-		if (Integer.MIN_VALUE == rn) {
-			return false;
-		}
-		if (rn < 0 || rn >= content.revisionCount()) {
-			// Sanity check
-			throw new IllegalStateException();
-		}
-		return true;
-	}
-
-	/**
-	 * Access to revision data as is (decompressed, but otherwise unprocessed, i.e. not parsed for e.g. changeset or manifest entries) 
-	 * @param nodeid
-	 */
-	protected void rawContent(Nodeid nodeid, ByteChannel sink) throws HgException, IOException, CancelledException {
-		rawContent(getLocalRevision(nodeid), sink);
-	}
-	
-	/**
-	 * @param revision - repo-local index of this file change (not a changelog revision number!)
-	 */
-	protected void rawContent(int revision, ByteChannel sink) throws HgException, IOException, CancelledException {
-		if (sink == null) {
-			throw new IllegalArgumentException();
-		}
-		ContentPipe insp = new ContentPipe(sink, 0);
-		insp.checkCancelled();
-		content.iterate(revision, revision, true, insp);
-		insp.checkFailed();
-	}
-
-	/**
-	 * XXX perhaps, return value Nodeid[2] and boolean needNodeids is better (and higher level) API for this query?
-	 * 
-	 * @param revision - revision to query parents, or {@link HgRepository#TIP}
-	 * @param parentRevisions - int[2] to get local revision numbers of parents (e.g. {6, -1})
-	 * @param parent1 - byte[20] or null, if parent's nodeid is not needed
-	 * @param parent2 - byte[20] or null, if second parent's nodeid is not needed
-	 * @return
-	 */
-	public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) {
-		if (revision != TIP && !(revision >= 0 && revision < content.revisionCount())) {
-			throw new IllegalArgumentException(String.valueOf(revision));
-		}
-		if (parentRevisions == null || parentRevisions.length < 2) {
-			throw new IllegalArgumentException(String.valueOf(parentRevisions));
-		}
-		if (parent1 != null && parent1.length < 20) {
-			throw new IllegalArgumentException(parent1.toString());
-		}
-		if (parent2 != null && parent2.length < 20) {
-			throw new IllegalArgumentException(parent2.toString());
-		}
-		class ParentCollector implements RevlogStream.Inspector {
-			public int p1 = -1;
-			public int p2 = -1;
-			public byte[] nodeid;
-			
-			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
-				p1 = parent1Revision;
-				p2 = parent2Revision;
-				this.nodeid = new byte[20];
-				// nodeid arg now comes in 32 byte from (as in file format description), however upper 12 bytes are zeros.
-				System.arraycopy(nodeid, nodeid.length > 20 ? nodeid.length - 20 : 0, this.nodeid, 0, 20);
-			}
-		};
-		ParentCollector pc = new ParentCollector();
-		content.iterate(revision, revision, false, pc);
-		parentRevisions[0] = pc.p1;
-		parentRevisions[1] = pc.p2;
-		if (parent1 != null) {
-			if (parentRevisions[0] == -1) {
-				Arrays.fill(parent1, 0, 20, (byte) 0);
-			} else {
-				content.iterate(parentRevisions[0], parentRevisions[0], false, pc);
-				System.arraycopy(pc.nodeid, 0, parent1, 0, 20);
-			}
-		}
-		if (parent2 != null) {
-			if (parentRevisions[1] == -1) {
-				Arrays.fill(parent2, 0, 20, (byte) 0);
-			} else {
-				content.iterate(parentRevisions[1], parentRevisions[1], false, pc);
-				System.arraycopy(pc.nodeid, 0, parent2, 0, 20);
-			}
-		}
-	}
-
-	/*
-	 * XXX think over if it's better to do either:
-	 * pw = getChangelog().new ParentWalker(); pw.init() and pass pw instance around as needed
-	 * or
-	 * add Revlog#getParentWalker(), static class, make cons() and #init package-local, and keep SoftReference to allow walker reuse.
-	 * 
-	 *  and yes, walker is not a proper name
-	 */
-	public final class ParentWalker {
-
-		
-		private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering
-		private Nodeid[] sorted; // for binary search
-		private int[] sorted2natural;
-		private Nodeid[] firstParent;
-		private Nodeid[] secondParent;
-
-		// Nodeid instances shall be shared between all arrays
-
-		public ParentWalker() {
-		}
-		
-		public HgRepository getRepo() {
-			return Revlog.this.getRepo();
-		}
-		
-		public void init() {
-			final RevlogStream stream = Revlog.this.content;
-			final int revisionCount = stream.revisionCount();
-			firstParent = new Nodeid[revisionCount];
-			// although branches/merges are less frequent, and most of secondParent would be -1/null, some sort of 
-			// SparseOrderedList might be handy, provided its inner structures do not overweight simplicity of an array
-			secondParent = new Nodeid[revisionCount];
-			//
-			sequential = new Nodeid[revisionCount];
-			sorted = new Nodeid[revisionCount];
-		
-			RevlogStream.Inspector insp = new RevlogStream.Inspector() {
-				
-				int ix = 0;
-				public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
-					if (ix != revisionNumber) {
-						// XXX temp code, just to make sure I understand what's going on here
-						throw new IllegalStateException();
-					}
-					if (parent1Revision >= revisionNumber || parent2Revision >= revisionNumber) {
-						throw new IllegalStateException(); // sanity, revisions are sequential
-					}
-					final Nodeid nid = new Nodeid(nodeid, true);
-					sequential[ix] = sorted[ix] = nid;
-					if (parent1Revision != -1) {
-						assert parent1Revision < ix;
-						firstParent[ix] = sequential[parent1Revision];
-					}
-					if (parent2Revision != -1) { // revlog of DataAccess.java has p2 set when p1 is -1
-						assert parent2Revision < ix;
-						secondParent[ix] = sequential[parent2Revision];
-					}
-					ix++;
-				}
-			};
-			stream.iterate(0, TIP, false, insp);
-			Arrays.sort(sorted);
-			sorted2natural = new int[revisionCount];
-			for (int i = 0; i < revisionCount; i++) {
-				Nodeid n = sequential[i];
-				int x = Arrays.binarySearch(sorted, n);
-				assertSortedIndex(x);
-				sorted2natural[x] = i;
-			}
-		}
-		
-		private void assertSortedIndex(int x) {
-			if (x < 0) {
-				throw new HgBadStateException();
-			}
-		}
-		
-		// FIXME need to decide whether Nodeid(00 * 20) is always known or not
-		// right now Nodeid.NULL is not recognized as known if passed to this method, 
-		// caller is supposed to make explicit check 
-		public boolean knownNode(Nodeid nid) {
-			return Arrays.binarySearch(sorted, nid) >= 0;
-		}
-
-		/**
-		 * null if none. only known nodes (as per #knownNode) are accepted as arguments
-		 */
-		public Nodeid firstParent(Nodeid nid) {
-			int x = Arrays.binarySearch(sorted, nid);
-			assertSortedIndex(x);
-			int i = sorted2natural[x];
-			return firstParent[i];
-		}
-
-		// never null, Nodeid.NULL if none known
-		public Nodeid safeFirstParent(Nodeid nid) {
-			Nodeid rv = firstParent(nid);
-			return rv == null ? Nodeid.NULL : rv;
-		}
-		
-		public Nodeid secondParent(Nodeid nid) {
-			int x = Arrays.binarySearch(sorted, nid);
-			assertSortedIndex(x);
-			int i = sorted2natural[x];
-			return secondParent[i];
-		}
-
-		public Nodeid safeSecondParent(Nodeid nid) {
-			Nodeid rv = secondParent(nid);
-			return rv == null ? Nodeid.NULL : rv;
-		}
-
-		public boolean appendParentsOf(Nodeid nid, Collection<Nodeid> c) {
-			int x = Arrays.binarySearch(sorted, nid);
-			assertSortedIndex(x);
-			int i = sorted2natural[x];
-			Nodeid p1 = firstParent[i];
-			boolean modified = false;
-			if (p1 != null) {
-				modified = c.add(p1);
-			}
-			Nodeid p2 = secondParent[i];
-			if (p2 != null) {
-				modified = c.add(p2) || modified;
-			}
-			return modified;
-		}
-
-		// XXX alternative (and perhaps more reliable) approach would be to make a copy of allNodes and remove 
-		// nodes, their parents and so on.
-		
-		// @return ordered collection of all children rooted at supplied nodes. Nodes shall not be descendants of each other!
-		// Nodeids shall belong to this revlog
-		public List<Nodeid> childrenOf(List<Nodeid> roots) {
-			HashSet<Nodeid> parents = new HashSet<Nodeid>();
-			LinkedList<Nodeid> result = new LinkedList<Nodeid>();
-			int earliestRevision = Integer.MAX_VALUE;
-			assert sequential.length == firstParent.length && firstParent.length == secondParent.length;
-			// first, find earliest index of roots in question, as there's  no sense 
-			// to check children among nodes prior to branch's root node
-			for (Nodeid r : roots) {
-				int x = Arrays.binarySearch(sorted, r);
-				assertSortedIndex(x);
-				int i = sorted2natural[x];
-				if (i < earliestRevision) {
-					earliestRevision = i;
-				}
-				parents.add(sequential[i]); // add canonical instance in hope equals() is bit faster when can do a ==
-			}
-			for (int i = earliestRevision + 1; i < sequential.length; i++) {
-				if (parents.contains(firstParent[i]) || parents.contains(secondParent[i])) {
-					parents.add(sequential[i]); // to find next child
-					result.add(sequential[i]);
-				}
-			}
-			return result;
-		}
-		
-		/**
-		 * @param nid possibly parent node, shall be {@link #knownNode(Nodeid) known} in this revlog.
-		 * @return <code>true</code> if there's any node in this revlog that has specified node as one of its parents. 
-		 */
-		public boolean hasChildren(Nodeid nid) {
-			int x = Arrays.binarySearch(sorted, nid);
-			assertSortedIndex(x);
-			int i = sorted2natural[x];
-			assert firstParent.length == secondParent.length; // just in case later I implement sparse array for secondParent
-			assert firstParent.length == sequential.length;
-			// to use == instead of equals, take the same Nodeid instance we used to fill all the arrays.
-			final Nodeid canonicalNode = sequential[i];
-			i++; // no need to check node itself. child nodes may appear in sequential only after revision in question
-			for (; i < sequential.length; i++) {
-				// FIXME likely, not very effective. 
-				// May want to optimize it with another (Tree|Hash)Set, created on demand on first use, 
-				// however, need to be careful with memory usage
-				if (firstParent[i] == canonicalNode || secondParent[i] == canonicalNode) {
-					return true;
-				}
-			}
-			return false;
-		}
-	}
-
-	protected static class ContentPipe implements RevlogStream.Inspector, CancelSupport {
-		private final ByteChannel sink;
-		private final CancelSupport cancelSupport;
-		private Exception failure;
-		private final int offset;
-
-		/**
-		 * @param _sink - cannot be <code>null</code>
-		 * @param seekOffset - when positive, orders to pipe bytes to the sink starting from specified offset, not from the first byte available in DataAccess
-		 */
-		public ContentPipe(ByteChannel _sink, int seekOffset) {
-			assert _sink != null;
-			sink = _sink;
-			cancelSupport = CancelSupport.Factory.get(_sink);
-			offset = seekOffset;
-		}
-		
-		protected void prepare(int revisionNumber, DataAccess da) throws HgException, IOException {
-			if (offset > 0) { // save few useless reset/rewind operations
-				da.seek(offset);
-			}
-		}
-
-		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
-			try {
-				prepare(revisionNumber, da); // XXX perhaps, prepare shall return DA (sliced, if needed)
-				final ProgressSupport progressSupport = ProgressSupport.Factory.get(sink);
-				ByteBuffer buf = ByteBuffer.allocate(512);
-				progressSupport.start(da.length());
-				while (!da.isEmpty()) {
-					cancelSupport.checkCancelled();
-					da.readBytes(buf);
-					buf.flip();
-					// XXX I may not rely on returned number of bytes but track change in buf position instead.
-					int consumed = sink.write(buf); 
-					// FIXME in fact, bad sink implementation (that consumes no bytes) would result in endless loop. Need to account for this 
-					buf.compact();
-					progressSupport.worked(consumed);
-				}
-				progressSupport.done(); // XXX shall specify whether #done() is invoked always or only if completed successfully.
-			} catch (IOException ex) {
-				recordFailure(ex);
-			} catch (CancelledException ex) {
-				recordFailure(ex);
-			} catch (HgException ex) {
-				recordFailure(ex);
-			}
-		}
-		
-		public void checkCancelled() throws CancelledException {
-			cancelSupport.checkCancelled();
-		}
-
-		protected void recordFailure(Exception ex) {
-			assert failure == null;
-			failure = ex;
-		}
-
-		public void checkFailed() throws HgException, IOException, CancelledException {
-			if (failure == null) {
-				return;
-			}
-			if (failure instanceof IOException) {
-				throw (IOException) failure;
-			}
-			if (failure instanceof CancelledException) {
-				throw (CancelledException) failure;
-			}
-			if (failure instanceof HgException) {
-				throw (HgException) failure;
-			}
-			throw new HgBadStateException(failure);
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/repo/package.html	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-<html>
-<boody>
-Low-level API operations
-</bidy>
-</html>
\ No newline at end of file
--- a/src/org/tmatesoft/hg/util/Adaptable.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-/*
- * 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;
-
-/**
- * Extension mechanism.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface Adaptable {
-
-	<T> T getAdapter(Class<T> adapterClass);
-}
--- a/src/org/tmatesoft/hg/util/ByteChannel.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/*
- * 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 java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Much like {@link java.nio.channels.WritableByteChannel} except for thrown exception 
- * 
- * XXX Perhaps, we'll add CharChannel in the future to deal with character conversions/encodings 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface ByteChannel {
-	// XXX does int return value makes any sense given buffer keeps its read state
-	// not clear what retvalue should be in case some filtering happened inside write - i.e. return
-	// number of bytes consumed in 
-	int write(ByteBuffer buffer) throws IOException, CancelledException;
-}
--- a/src/org/tmatesoft/hg/util/CancelSupport.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * 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;
-
-/**
- * Mix-in for objects that support cancellation. 
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface CancelSupport {
-
-	/**
-	 * This method is invoked to check if target had been brought to canceled state. Shall silently return if target is
-	 * in regular state.
-	 * @throws CancelledException when target internal state has been changed to canceled.
-	 */
-	void checkCancelled() throws CancelledException;
-
-
-	// Yeah, this factory class looks silly now, but perhaps in the future I'll need wrappers for other cancellation sources?
-	// just don't want to have general Utils class with methods like get() below
-	static class Factory {
-
-		/**
-		 * Obtain non-null cancel support object.
-		 * 
-		 * @param target any object (or <code>null</code>) that might have cancel support
-		 * @return target if it's capable checking cancellation status or no-op implementation that never cancels.
-				 */
-		public static CancelSupport get(Object target) {
-			if (target instanceof  CancelSupport) {
-				return (CancelSupport) target;
-			}
-			if (target instanceof Adaptable) {
-				CancelSupport cs = ((Adaptable) target).getAdapter(CancelSupport.class);
-				if (cs != null) {
-					return cs;
-				}
-			}
-			return new CancelSupport() {
-				public void checkCancelled() {
-				}
-			};
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/util/CancelledException.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-/*
- * 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;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-@SuppressWarnings("serial")
-public class CancelledException extends Exception {
-
-	public CancelledException() {
-	}
-}
--- a/src/org/tmatesoft/hg/util/FileIterator.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/*
- * 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 java.io.File;
-
-/**
- * Abstracts iteration over file system.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface FileIterator {
-
-	/**
-	 * Brings iterator into initial state to facilitate new use.
-	 */
-	void reset();
-
-	/**
-	 * @return whether can shift to next element
-	 */
-	boolean hasNext();
-
-	/**
-	 * Shift to next element
-	 */
-	void next();
-
-	/**
-	 * @return repository-local path to the current element.
-	 */
-	Path name();
-
-	/**
-	 * @return filesystem element.
-	 */
-	File file();
-}
--- a/src/org/tmatesoft/hg/util/FileWalker.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*
- * 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 java.io.File;
-import java.util.LinkedList;
-import java.util.NoSuchElementException;
-
-/**
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class FileWalker implements FileIterator {
-
-	private final File startDir;
-	private final Path.Source pathHelper;
-	private final LinkedList<File> dirQueue;
-	private final LinkedList<File> fileQueue;
-	private File nextFile;
-	private Path nextPath;
-
-	public FileWalker(File dir, Path.Source pathFactory) {
-		startDir = dir;
-		pathHelper = pathFactory;
-		dirQueue = new LinkedList<File>();
-		fileQueue = new LinkedList<File>();
-		reset();
-	}
-
-	public void reset() {
-		fileQueue.clear();
-		dirQueue.clear();
-		dirQueue.add(startDir);
-		nextFile = null;
-		nextPath = null;
-	}
-	
-	public boolean hasNext() {
-		return fill();
-	}
-
-	public void next() {
-		if (!fill()) {
-			throw new NoSuchElementException();
-		}
-		nextFile = fileQueue.removeFirst();
-		nextPath = pathHelper.path(nextFile.getPath());
-	}
-
-	public Path name() {
-		return nextPath;
-	}
-	
-	public File file() {
-		return nextFile;
-	}
-	
-	private File[] listFiles(File f) {
-		// in case we need to solve os-related file issues (mac with some encodings?)
-		return f.listFiles();
-	}
-
-	// return true when fill added any elements to fileQueue. 
-	private boolean fill() {
-		while (fileQueue.isEmpty()) {
-			if (dirQueue.isEmpty()) {
-				return false;
-			}
-			while (!dirQueue.isEmpty()) {
-				File dir = dirQueue.removeFirst();
-				for (File f : listFiles(dir)) {
-					if (f.isDirectory()) {
-						if (!".hg".equals(f.getName())) {
-							dirQueue.addLast(f);
-						}
-					} else {
-						fileQueue.addLast(f);
-					}
-				}
-				break;
-			}
-		}
-		return !fileQueue.isEmpty();
-	}
-}
--- a/src/org/tmatesoft/hg/util/Path.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- * 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;
-
-/**
- * Identify repository files (not String nor io.File). Convenient for pattern matching. Memory-friendly.
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public final class Path implements CharSequence, Comparable<Path>/*Cloneable? - although clone for paths make no sense*/{
-//	private String[] segments;
-//	private int flags; // dir, unparsed
-	private String path;
-	
-	/*package-local*/Path(String p) {
-		path = p;
-	}
-
-	/**
-	 * Check if this is directory's path. 
-	 * Note, this method doesn't perform any file system operation.
-	 * 
-	 * @return true when this path points to a directory 
-	 */
-	public boolean isDirectory() {
-		// XXX simple logic for now. Later we may decide to have an explicit factory method to create directory paths
-		return path.charAt(path.length() - 1) == '/';
-	}
-
-	public int length() {
-		return path.length();
-	}
-
-	public char charAt(int index) {
-		return path.charAt(index);
-	}
-
-	public CharSequence subSequence(int start, int end) {
-		// new Path if start-end matches boundaries of any subpath
-		return path.substring(start, end);
-	}
-	
-	@Override
-	public String toString() {
-		return path; // CharSequence demands toString() impl
-	}
-
-	public int compareTo(Path o) {
-		return path.compareTo(o.path);
-	}
-	
-	@Override
-	public boolean equals(Object obj) {
-		if (obj != null && getClass() == obj.getClass()) {
-			return this == obj || path.equals(((Path) obj).path);
-		}
-		return false;
-	}
-	@Override
-	public int hashCode() {
-		return path.hashCode();
-	}
-
-	public static Path create(String path) {
-		if (path == null) {
-			throw new IllegalArgumentException();
-		}
-		if (path.indexOf('\\') != -1) {
-			throw new IllegalArgumentException();
-		}
-		Path rv = new Path(path);
-		return rv;
-	}
-
-	/**
-	 * Path filter.
-	 */
-	public interface Matcher {
-		boolean accept(Path path);
-	}
-
-	/**
-	 * Factory for paths
-	 */
-	public interface Source {
-		Path path(String p);
-	}
-
-	/**
-	 * Straightforward {@link Source} implementation that creates new Path instance for each supplied string
-	 */
-	public static class SimpleSource implements Source {
-		private final PathRewrite normalizer;
-
-		public SimpleSource(PathRewrite pathRewrite) {
-			if (pathRewrite == null) {
-				throw new IllegalArgumentException();
-			}
-			normalizer = pathRewrite;
-		}
-
-		public Path path(String p) {
-			return Path.create(normalizer.rewrite(p));
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/util/PathPool.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/*
- * 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 java.lang.ref.SoftReference;
-import java.util.WeakHashMap;
-
-
-/**
- * Produces path from strings and caches result for reuse
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class PathPool implements Path.Source {
-	private final WeakHashMap<String, SoftReference<Path>> cache;
-	private final PathRewrite pathRewrite;
-	
-	public PathPool(PathRewrite rewrite) {
-		pathRewrite = rewrite;
-		cache = new WeakHashMap<String, SoftReference<Path>>();
-	}
-
-	public Path path(String p) {
-		p = pathRewrite.rewrite(p);
-		return get(p, true);
-	}
-
-	// pipes path object through cache to reuse instance, if possible
-	public Path path(Path p) {
-		String s = pathRewrite.rewrite(p.toString());
-		Path cached = get(s, false);
-		if (cached == null) {
-			cache.put(s, new SoftReference<Path>(cached = p));
-		}
-		return cached;
-	}
-
-	// XXX what would be parent of an empty path?
-	// Path shall have similar functionality
-	public Path parent(Path path) {
-		if (path.length() == 0) {
-			throw new IllegalArgumentException();
-		}
-		for (int i = path.length() - 2 /*if path represents a dir, trailing char is slash, skip*/; i >= 0; i--) {
-			if (path.charAt(i) == '/') {
-				return get(path.subSequence(0, i+1).toString(), true);
-			}
-		}
-		return get("", true);
-	}
-
-	private Path get(String p, boolean create) {
-		SoftReference<Path> sr = cache.get(p);
-		Path path = sr == null ? null : sr.get();
-		if (path == null) {
-			if (create) {
-				path = Path.create(p);
-				cache.put(p, new SoftReference<Path>(path));
-			} else if (sr != null) {
-				// cached path no longer used, clear cache entry - do not wait for RefQueue to step in
-				cache.remove(p);
-			}
-		} 
-		return path;
-	}
-}
--- a/src/org/tmatesoft/hg/util/PathRewrite.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * 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 java.util.LinkedList;
-import java.util.List;
-
-/**
- * File names often need transformations, like Windows-style path to Unix or human-readable data file name to storage location.  
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface PathRewrite {
-
-	// XXX think over CharSequence use instead of String
-	public String rewrite(String path);
-	
-	public static class Empty implements PathRewrite {
-		public String rewrite(String path) {
-			return path;
-		}
-	}
-
-	public class Composite implements PathRewrite {
-		private List<PathRewrite> chain;
-
-		public Composite(PathRewrite... e) {
-			LinkedList<PathRewrite> r = new LinkedList<PathRewrite>();
-			for (int i = 0; e != null && i < e.length; i++) {
-				r.addLast(e[i]);
-			}
-			chain = r;
-		}
-		public Composite chain(PathRewrite e) {
-			chain.add(e);
-			return this;
-		}
-
-		public String rewrite(String path) {
-			for (PathRewrite pr : chain) {
-				path = pr.rewrite(path);
-			}
-			return path;
-		}
-	}
-}
--- a/src/org/tmatesoft/hg/util/ProgressSupport.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- * 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;
-
-/**
- * Mix-in to report progress of a long-running operation
- *  
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface ProgressSupport {
-
-	public void start(long totalUnits);
-	public void worked(int units);
-	public void done();
-
-	static class Factory {
-
-		/**
-		 * @param target object that might be capable to report progress. Can be <code>null</code>
-		 * @return support object extracted from target or an empty, no-op implementation
-		 */
-		public static ProgressSupport get(Object target) {
-			if (target instanceof ProgressSupport) {
-				return (ProgressSupport) target;
-			}
-			if (target instanceof Adaptable) {
-				ProgressSupport ps = ((Adaptable) target).getAdapter(ProgressSupport.class);
-				if (ps != null) {
-					return ps;
-				}
-			}
-			return new ProgressSupport() {
-				public void start(long totalUnits) {
-				}
-				public void worked(int units) {
-				}
-				public void done() {
-				}
-			};
-		}
-	}
-}
Binary file test-data/test-repos.jar has changed
--- a/test/org/tmatesoft/hg/test/Configuration.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/*
- * 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.test;
-
-import static org.junit.Assert.*;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-import org.tmatesoft.hg.repo.HgRepository;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Configuration {
-	
-	private static Configuration inst;
-	private File root;
-	private final HgLookup lookup;
-	private File tempDir;
-	private List<String> remoteServers;
-	
-	private Configuration() {
-		lookup = new HgLookup();
-	}
-	
-	private File getRoot() {
-		if (root == null) {
-			String repo2 = System.getProperty("hg4j.tests.repos");
-			assertNotNull("System property hg4j.tests.repos is undefined", repo2);
-			root = new File(repo2);
-			assertTrue(root.exists());
-		}
-		return root;
-	}
-	
-	public static Configuration get() {
-		if (inst == null) {
-			inst = new Configuration();
-		}
-		return inst;
-	}
-	
-	public HgRepository own() throws Exception {
-		return lookup.detectFromWorkingDir();
-	}
-
-	// fails if repo not found
-	public HgRepository find(String key) throws Exception {
-		HgRepository rv = lookup.detect(new File(getRoot(), key));
-		assertNotNull(rv);
-		assertFalse(rv.isInvalid());
-		return rv;
-	}
-
-	// easy override for manual test runs
-	public void remoteServers(String... keys) {
-		remoteServers = Arrays.asList(keys);
-	}
-
-	public List<HgRemoteRepository> allRemote() throws Exception {
-		if (remoteServers == null) {
-			String rr = System.getProperty("hg4j.tests.remote");
-			assertNotNull("System property hg4j.tests.remote is undefined", rr);
-			remoteServers = Arrays.asList(rr.split(" "));
-		}
-		ArrayList<HgRemoteRepository> rv = new ArrayList<HgRemoteRepository>(remoteServers.size());
-		for (String key : remoteServers) {
-			rv.add(lookup.detectRemote(key, null));
-		}
-		return rv;
-	}
-
-	public File getTempDir() {
-		if (tempDir == null) {
-			String td = System.getProperty("hg4j.tests.tmpdir", System.getProperty("java.io.tmpdir"));
-			tempDir = new File(td);
-		}
-		return tempDir;
-	}
-}
--- a/test/org/tmatesoft/hg/test/ErrorCollectorExt.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * 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.test;
-
-import static org.junit.Assert.assertThat;
-
-import java.util.concurrent.Callable;
-
-import org.hamcrest.Matcher;
-import org.junit.rules.ErrorCollector;
-
-/**
- * Expose verify method for allow not-junit runs to check test outcome 
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-final class ErrorCollectorExt extends ErrorCollector {
-	public void verify() throws Throwable {
-		super.verify();
-	}
-
-	public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) {
-		checkSucceeds(new Callable<Object>() {
-			public Object call() throws Exception {
-				assertThat(reason, value, matcher);
-				return value;
-			}
-		});
-	}
-}
\ No newline at end of file
--- a/test/org/tmatesoft/hg/test/ExecHelper.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/*
- * 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.test;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.CharBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.StringTokenizer;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class ExecHelper {
-
-	private final OutputParser parser;
-	private File dir;
-	private int exitValue;
-
-	public ExecHelper(OutputParser outParser, File workingDir) {
-		parser = outParser;
-		dir = workingDir;
-	}
-
-	public void run(String... cmd) throws IOException, InterruptedException {
-		ProcessBuilder pb = null;
-		if (System.getProperty("os.name").startsWith("Windows")) {
-			StringTokenizer st = new StringTokenizer(System.getenv("PATH"), ";");
-			while (st.hasMoreTokens()) {
-				File pe = new File(st.nextToken());
-				if (new File(pe, cmd[0] + ".exe").exists()) {
-					break;
-				}
-				// PATHEXT controls precedence of .exe, .bat and .cmd files, ususlly .exe wins
-				if (new File(pe, cmd[0] + ".bat").exists() || new File(pe, cmd[0] + ".cmd").exists()) {
-					ArrayList<String> command = new ArrayList<String>();
-					command.add("cmd.exe");
-					command.add("/C");
-					command.addAll(Arrays.asList(cmd));
-					pb = new ProcessBuilder(command);
-					break;
-				}
-			}
-		}
-		if (pb == null) {
-			pb = new ProcessBuilder(cmd);
-		}
-		Process p = pb.directory(dir).redirectErrorStream(true).start();
-		InputStreamReader stdOut = new InputStreamReader(p.getInputStream());
-		LinkedList<CharBuffer> l = new LinkedList<CharBuffer>();
-		int r = -1;
-		CharBuffer b = null;
-		do {
-			if (b == null || b.remaining() < b.capacity() / 3) {
-				b = CharBuffer.allocate(512);
-				l.add(b);
-			}
-			r = stdOut.read(b);
-		} while (r != -1);
-		int total = 0;
-		for (CharBuffer cb : l) {
-			total += cb.position();
-			cb.flip();
-		}
-		CharBuffer res = CharBuffer.allocate(total);
-		for (CharBuffer cb : l) {
-			res.put(cb);
-		}
-		res.flip();
-		p.waitFor();
-		exitValue = p.exitValue();
-		parser.parse(res);
-	}
-	
-	public int getExitValue() {
-		return exitValue;
-	}
-
-	public void cwd(File wd) {
-		dir = wd;
-	}
-}
--- a/test/org/tmatesoft/hg/test/LogOutputParser.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * 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.test;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.tmatesoft.hg.repo.HgRepository;
-
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class LogOutputParser implements OutputParser {
-	private final List<Record> result = new LinkedList<Record>();
-	private Pattern pattern1;
-	private Pattern pattern2;
-	private Pattern pattern3;
-	private Pattern pattern4;
-	private Pattern pattern5;
-	
-	public LogOutputParser(boolean outputWithDebug) {
-		if (outputWithDebug) {
-			pattern1 = Pattern.compile("^changeset:\\s+(\\d+):([a-f0-9]{40})\n(^tag:(.+)$)?", Pattern.MULTILINE);
-			pattern2 = Pattern.compile("^parent:\\s+(-?\\d+):([a-f0-9]{40})\n", Pattern.MULTILINE);
-			pattern3 = Pattern.compile("^manifest:\\s+(\\d+):([a-f0-9]{40})\nuser:\\s+(\\S.+)\ndate:\\s+(\\S.+)\n", Pattern.MULTILINE);
-			pattern4 = Pattern.compile("^description:\\n", Pattern.MULTILINE);
-			pattern5 = Pattern.compile("\\n\\n");
-			//p = "^manifest:\\s+(\\d+):([a-f0-9]{40})\nuser:(.+)$";
-		} else {
-			throw HgRepository.notImplemented();
-		}
-	}
-	
-	public void reset() {
-		result.clear();
-	}
-	
-	public List<Record> getResult() {
-		return result;
-	}
-
-	public void parse(CharSequence seq) {
-		Matcher m = pattern1.matcher(seq);
-		while (m.find()) {
-			Record r = new Record();
-			r.changesetIndex = Integer.parseInt(m.group(1));
-			r.changesetNodeid = m.group(2);
-			//tags = m.group(4);
-			m.usePattern(pattern2);
-			if (m.find()) {
-				r.parent1Index = Integer.parseInt(m.group(1));
-				r.parent1Nodeid = m.group(2);
-			}
-			if (m.find()) {
-				r.parent2Index = Integer.parseInt(m.group(1));
-				r.parent2Nodeid = m.group(2);
-			}
-			m.usePattern(pattern3);
-			if (m.find()) {
-				r.user = m.group(3);
-				r.date = m.group(4);
-			}
-			m.usePattern(pattern4);
-			if (m.find()) {
-				int commentStart = m.end();
-				m.usePattern(pattern5);
-				if (m.find()) {
-					r.description = seq.subSequence(commentStart, m.start()).toString();
-				}
-			}
-			result.add(r);
-			m.usePattern(pattern1);
-		}
-	}
-
-	public static class Record {
-		public int changesetIndex;
-		public String changesetNodeid;
-		public int parent1Index;
-		public int parent2Index;
-		public String parent1Nodeid;
-		public String parent2Nodeid;
-		public String user;
-		public String date;
-		public String description;
-	}
-}
--- a/test/org/tmatesoft/hg/test/ManifestOutputParser.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/*
- * 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.test;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class ManifestOutputParser implements OutputParser {
-
-	private final Pattern pattern;
-	private final LinkedHashMap<Path, Nodeid> result = new LinkedHashMap<Path, Nodeid>();
-
-	public ManifestOutputParser() {
-		pattern = Pattern.compile("^([a-f0-9]{40}) (\\d{3})   (.+)$", Pattern.MULTILINE);
-	}
-	
-	public void reset() {
-		result.clear();
-	}
-	
-	public Map<Path, Nodeid> getResult() {
-		return result;
-	}
-	
-	public void parse(CharSequence seq) {
-		Matcher m = pattern.matcher(seq);
-		while (m.find()) {
-			result.put(Path.create(m.group(3)), Nodeid.fromAscii(m.group(1).getBytes(), 0, 40));
-		}
-	}
-}
--- a/test/org/tmatesoft/hg/test/OutputParser.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/*
- * 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.test;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public interface OutputParser {
-
-	public void parse(CharSequence seq);
-
-	public class Stub implements OutputParser {
-		private boolean shallDump;
-		public Stub() {
-			this(false);
-		}
-		public Stub(boolean dump) {
-			shallDump = dump;
-		}
-		public void parse(CharSequence seq) {
-			if (shallDump) {
-				System.out.println(seq);
-			} 
-			// else no-op
-		}
-	}
-}
--- a/test/org/tmatesoft/hg/test/StatusOutputParser.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-/*
- * 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.test;
-
-import java.io.File;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.tmatesoft.hg.repo.HgStatusCollector;
-import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.PathPool;
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class StatusOutputParser implements OutputParser {
-
-	private final Pattern pattern;
-	// although using StatusCollector.Record is not really quite honest for testing,
-	// it's deemed acceptable as long as that class is primitive 'collect all results'
-	private HgStatusCollector.Record result = new HgStatusCollector.Record();
-	private final PathPool pathHelper;
-
-	public StatusOutputParser() {
-//		pattern = Pattern.compile("^([MAR?IC! ]) ([\\w \\.-/\\\\]+)$", Pattern.MULTILINE);
-		pattern = Pattern.compile("^([MAR?IC! ]) (.+)$", Pattern.MULTILINE);
-		pathHelper = new PathPool(new PathRewrite() {
-			
-			private final boolean winPathSeparator = File.separatorChar == '\\';
-
-			public String rewrite(String s) {
-				if (winPathSeparator) {
-					// Java impl always give slashed path, while Hg uses local, os-specific convention
-					s = s.replace('\\', '/'); 
-				}
-				return s;
-			}
-		});
-	}
-
-	public void reset() {
-		result = new HgStatusCollector.Record();
-	}
-
-	public void parse(CharSequence seq) {
-		Matcher m = pattern.matcher(seq);
-		Path lastEntry = null;
-		while (m.find()) {
-			Path fname = pathHelper.path(m.group(2));
-			switch ((int) m.group(1).charAt(0)) {
-			case (int) 'M' : {
-				result.modified(fname);
-				lastEntry = fname; // for files modified through merge there's also 'copy' source 
-				break;
-			}
-			case (int) 'A' : {
-				result.added(fname);
-				lastEntry = fname;
-				break;
-			}
-			case (int) 'R' : {
-				result.removed(fname);
-				break;
-			}
-			case (int) '?' : {
-				result.unknown(fname);
-				break;
-			}
-			case (int) 'I' : {
-				result.ignored(fname);
-				break;
-			}
-			case (int) 'C' : {
-				result.clean(fname);
-				break;
-			}
-			case (int) '!' : {
-				result.missing(fname);
-				break;
-			}
-			case (int) ' ' : {
-				// last added is copy destination
-				// to get or to remove it - depends on what StatusCollector does in this case
-				result.copied(fname, lastEntry);
-				lastEntry = null;
-				break;
-			}
-			}
-		}
-	}
-
-	// 
-	public List<Path> getModified() {
-		return result.getModified();
-	}
-
-	public List<Path> getAdded() {
-		List<Path> rv = new LinkedList<Path>(result.getAdded());
-		for (Path p : result.getCopied().keySet()) {
-			rv.remove(p); // remove only one duplicate
-		}
-		return rv;
-	}
-
-	public List<Path> getRemoved() {
-		return result.getRemoved();
-	}
-
-	public Map<Path,Path> getCopied() {
-		return result.getCopied();
-	}
-
-	public List<Path> getClean() {
-		return result.getClean();
-	}
-
-	public List<Path> getMissing() {
-		return result.getMissing();
-	}
-
-	public List<Path> getUnknown() {
-		return result.getUnknown();
-	}
-
-	public List<Path> getIgnored() {
-		return result.getIgnored();
-	}
-}
--- a/test/org/tmatesoft/hg/test/TestByteChannel.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*
- * 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.test;
-
-import static org.junit.Assert.assertArrayEquals;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.tmatesoft.hg.internal.ByteArrayChannel;
-import org.tmatesoft.hg.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgRepository;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class TestByteChannel {
-
-	private HgRepository repo;
-
-	public static void main(String[] args) throws Exception {
-//		HgRepoFacade rf = new HgRepoFacade();
-//		rf.init();
-//		HgDataFile file = rf.getRepository().getFileNode("src/org/tmatesoft/hg/internal/KeywordFilter.java");
-//		for (int i = file.getLastRevision(); i >= 0; i--) {
-//			System.out.print("Content for revision:" + i);
-//			compareContent(file, i);
-//			System.out.println(" OK");
-//		}
-		//CatCommand cmd = rf.createCatCommand();
-	}
-
-//	private static void compareContent(HgDataFile file, int rev) throws Exception {
-//		byte[] oldAccess = file.content(rev);
-//		ByteArrayChannel ch = new ByteArrayChannel();
-//		file.content(rev, ch);
-//		byte[] newAccess = ch.toArray();
-//		Assert.assertArrayEquals(oldAccess, newAccess);
-//		// don't trust anyone (even JUnit) 
-//		if (!Arrays.equals(oldAccess, newAccess)) {
-//			throw new RuntimeException("Failed:" + rev);
-//		}
-//	}
-
-	@Test
-	public void testContent() throws Exception {
-		repo = Configuration.get().find("log-1");
-		final byte[] expectedContent = new byte[] { 'a', ' ', 13, 10 };
-		ByteArrayChannel ch = new ByteArrayChannel();
-		repo.getFileNode("dir/b").content(0, ch);
-		assertArrayEquals(expectedContent, ch.toArray());
-		repo.getFileNode("d").content(HgRepository.TIP, ch = new ByteArrayChannel() );
-		assertArrayEquals(expectedContent, ch.toArray());
-	}
-
-	@Test
-	public void testStripMetadata() throws Exception {
-		repo = Configuration.get().find("log-1");
-		ByteArrayChannel ch = new ByteArrayChannel();
-		HgDataFile dir_b = repo.getFileNode("dir/b");
-		Assert.assertTrue(dir_b.isCopy());
-		Assert.assertEquals("b", dir_b.getCopySourceName().toString());
-		Assert.assertEquals("e44751cdc2d14f1eb0146aa64f0895608ad15917", dir_b.getCopySourceRevision().toString());
-		dir_b.content(0, ch);
-		// assert rawContent has 1 10 ... 1 10
-		assertArrayEquals("a \r\n".getBytes(), ch.toArray());
-		//
-		// try once again to make sure metadata records/extracts correct offsets
-		dir_b.content(0, ch = new ByteArrayChannel());
-		assertArrayEquals("a \r\n".getBytes(), ch.toArray());
-	}
-}
--- a/test/org/tmatesoft/hg/test/TestClone.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/*
- * 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.test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.hamcrest.CoreMatchers;
-import org.junit.Rule;
-import org.junit.Test;
-import org.tmatesoft.hg.core.HgCloneCommand;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class TestClone {
-
-	@Rule
-	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
-
-	public static void main(String[] args) throws Throwable {
-		TestClone t = new TestClone();
-		t.testSimpleClone();
-		t.errorCollector.verify();
-	}
-
-	public TestClone() {
-	}
-	
-	@Test
-	public void testSimpleClone() throws Exception {
-		int x = 0;
-		final File tempDir = Configuration.get().getTempDir();
-		for (HgRemoteRepository hgRemote : Configuration.get().allRemote()) {
-			HgCloneCommand cmd = new HgCloneCommand();
-			cmd.source(hgRemote);
-			File dest = new File(tempDir, "test-clone-" + x++);
-			if (dest.exists()) {
-				rmdir(dest);
-			}
-			cmd.destination(dest);
-			cmd.execute();
-			verify(hgRemote, dest);
-		}
-	}
-
-	private void verify(HgRemoteRepository hgRemote, File dest) throws Exception {
-		ExecHelper eh = new ExecHelper(new OutputParser.Stub(), dest);
-		eh.run("hg", "verify");
-		errorCollector.checkThat("Verify", eh.getExitValue(), CoreMatchers.equalTo(0));
-		eh.run("hg", "out", hgRemote.getLocation());
-		errorCollector.checkThat("Outgoing", eh.getExitValue(), CoreMatchers.equalTo(1));
-		eh.run("hg", "in", hgRemote.getLocation());
-		errorCollector.checkThat("Incoming", eh.getExitValue(), CoreMatchers.equalTo(1));
-	}
-
-	static void rmdir(File dest) throws IOException {
-		LinkedList<File> queue = new LinkedList<File>();
-		queue.addAll(Arrays.asList(dest.listFiles()));
-		while (!queue.isEmpty()) {
-			File next = queue.removeFirst();
-			if (next.isDirectory()) {
-				List<File> files = Arrays.asList(next.listFiles());
-				if (!files.isEmpty()) {
-					queue.addAll(files);
-					queue.add(next);
-				}
-				// fall through
-			} 
-			next.delete();
-		}
-		dest.delete();
-	}
-}
--- a/test/org/tmatesoft/hg/test/TestHistory.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,240 +0,0 @@
-/*
- * 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.test;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertTrue;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.tmatesoft.hg.core.HgChangeset;
-import org.tmatesoft.hg.core.HgLogCommand;
-import org.tmatesoft.hg.core.HgLogCommand.CollectHandler;
-import org.tmatesoft.hg.core.HgLogCommand.FileHistoryHandler;
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.test.LogOutputParser.Record;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class TestHistory {
-
-	@Rule
-	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
-
-	private HgRepository repo;
-	private final ExecHelper eh;
-	private LogOutputParser changelogParser;
-	
-	public static void main(String[] args) throws Throwable {
-		TestHistory th = new TestHistory();
-		th.testCompleteLog();
-		th.testFollowHistory();
-		th.errorCollector.verify();
-//		th.testPerformance();
-		th.testOriginalTestLogRepo();
-		th.testUsernames();
-		th.testBranches();
-		//
-		th.errorCollector.verify();
-	}
-	
-	public TestHistory() throws Exception {
-		this(new HgLookup().detectFromWorkingDir());
-	}
-
-	private TestHistory(HgRepository hgRepo) {
-		repo = hgRepo;
-		eh = new ExecHelper(changelogParser = new LogOutputParser(true), null);
-		
-	}
-
-	@Test
-	public void testCompleteLog() throws Exception {
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug");
-		List<HgChangeset> r = new HgLogCommand(repo).execute();
-		report("hg log - COMPLETE REPO HISTORY", r, true); 
-	}
-	
-	@Test
-	public void testFollowHistory() throws Exception {
-		final Path f = Path.create("cmdline/org/tmatesoft/hg/console/Remote.java");
-		try {
-			if (repo.getFileNode(f).exists()) { // FIXME getFileNode shall not fail with IAE
-				changelogParser.reset();
-				eh.run("hg", "log", "--debug", "--follow", f.toString());
-				
-				class H extends CollectHandler implements FileHistoryHandler {
-					boolean copyReported = false;
-					boolean fromMatched = false;
-					public void copy(FileRevision from, FileRevision to) {
-						copyReported = true;
-						fromMatched = "src/com/tmate/hgkit/console/Remote.java".equals(from.getPath().toString());
-					}
-				};
-				H h = new H();
-				new HgLogCommand(repo).file(f, true).execute(h);
-				String what = "hg log - FOLLOW FILE HISTORY";
-				errorCollector.checkThat(what + "#copyReported ", h.copyReported, is(true));
-				errorCollector.checkThat(what + "#copyFromMatched", h.fromMatched, is(true));
-				//
-				// cmdline always gives in changesets in order from newest (bigger rev number) to oldest.
-				// LogCommand does other way round, from oldest to newest, follewed by revisions of copy source, if any
-				// (apparently older than oldest of the copy target). Hence need to sort Java results according to rev numbers
-				final LinkedList<HgChangeset> sorted = new LinkedList<HgChangeset>(h.getChanges());
-				Collections.sort(sorted, new Comparator<HgChangeset>() {
-					public int compare(HgChangeset cs1, HgChangeset cs2) {
-						return cs1.getRevision() < cs2.getRevision() ? 1 : -1;
-					}
-				});
-				report(what, sorted, false);
-			}
-		} catch (IllegalArgumentException ex) {
-			System.out.println("Can't test file history with follow because need to query specific file with history");
-		}
-	}
-
-	private void report(String what, List<HgChangeset> r, boolean reverseConsoleResult) {
-		final List<Record> consoleResult = changelogParser.getResult();
-		report(what, r, consoleResult, reverseConsoleResult, errorCollector);
-	}
-	
-	static void report(String what, List<HgChangeset> hg4jResult, List<Record> consoleResult, boolean reverseConsoleResult, ErrorCollectorExt errorCollector) {
-		consoleResult = new ArrayList<Record>(consoleResult); // need a copy in case callee would use result again
-		if (reverseConsoleResult) {
-			Collections.reverse(consoleResult);
-		}
-		errorCollector.checkThat(what + ". Number of changeset reported didn't match", consoleResult.size(), equalTo(hg4jResult.size()));
-		Iterator<Record> consoleResultItr = consoleResult.iterator();
-		for (HgChangeset cs : hg4jResult) {
-			if (!consoleResultItr.hasNext()) {
-				errorCollector.addError(new AssertionError("Ran out of console results while there are still hg4j results"));
-				break;
-			}
-			Record cr = consoleResultItr.next();
-			int x = cs.getRevision() == cr.changesetIndex ? 0x1 : 0;
-			x |= cs.getDate().equals(cr.date) ? 0x2 : 0;
-			x |= cs.getNodeid().toString().equals(cr.changesetNodeid) ? 0x4 : 0;
-			x |= cs.getUser().equals(cr.user) ? 0x8 : 0;
-			// need to do trim() on comment because command-line template does, and there are
-			// repositories that have couple of newlines in the end of the comment (e.g. hello sample repo from the book) 
-			x |= cs.getComment().trim().equals(cr.description) ? 0x10 : 0;
-			errorCollector.checkThat(String.format(what + ". Mismatch (0x%x) in %d hg4j rev comparing to %d cmdline's.", x, cs.getRevision(), cr.changesetIndex), x, equalTo(0x1f));
-			consoleResultItr.remove();
-		}
-		errorCollector.checkThat(what + ". Unprocessed results in console left (insufficient from hg4j)", consoleResultItr.hasNext(), equalTo(false));
-	}
-
-	public void testPerformance() throws Exception {
-		final int runs = 10;
-		final long start1 = System.currentTimeMillis();
-		for (int i = 0; i < runs; i++) {
-			changelogParser.reset();
-			eh.run("hg", "log", "--debug");
-		}
-		final long start2 = System.currentTimeMillis();
-		for (int i = 0; i < runs; i++) {
-			new HgLogCommand(repo).execute();
-		}
-		final long end = System.currentTimeMillis();
-		System.out.printf("'hg log --debug', %d runs: Native client total %d (%d per run), Java client %d (%d)\n", runs, start2-start1, (start2-start1)/runs, end-start2, (end-start2)/runs);
-	}
-
-	@Test
-	public void testOriginalTestLogRepo() throws Exception {
-		repo = Configuration.get().find("log-1");
-		HgLogCommand cmd = new HgLogCommand(repo);
-		// funny enough, but hg log -vf a -R c:\temp\hg\test-log\a doesn't work, while --cwd <same> works fine
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "a", "--cwd", repo.getLocation());
-		report("log a", cmd.file("a", false).execute(), true);
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-f", "a", "--cwd", repo.getLocation());
-		List<HgChangeset> r = cmd.file("a", true).execute();
-		report("log -f a", r, true);
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-f", "e", "--cwd", repo.getLocation());
-		report("log -f e", cmd.file("e", true).execute(), false /*#1, below*/);
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "dir/b", "--cwd", repo.getLocation());
-		report("log dir/b", cmd.file("dir/b", false).execute(), true);
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-f", "dir/b", "--cwd", repo.getLocation());
-		report("log -f dir/b", cmd.file("dir/b", true).execute(), false /*#1, below*/);
-		/*
-		 * #1: false works because presently commands dispatches history of the queried file, and then history
-		 * of it's origin. With history comprising of renames only, this effectively gives reversed (newest to oldest) 
-		 * order of revisions. 
-		 */
-	}
-
-	@Test
-	public void testUsernames() throws Exception {
-		repo = Configuration.get().find("log-users");
-		final String user1 = "User One <user1@example.org>";
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-u", user1, "--cwd", repo.getLocation());
-		report("log -u " + user1, new HgLogCommand(repo).user(user1).execute(), true);
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-u", "user1", "-u", "user2", "--cwd", repo.getLocation());
-		report("log -u user1 -u user2", new HgLogCommand(repo).user("user1").user("user2").execute(), true);
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-u", "user3", "--cwd", repo.getLocation());
-		report("log -u user3", new HgLogCommand(repo).user("user3").execute(), true);
-	}
-
-	@Test
-	public void testBranches() throws Exception {
-		repo = Configuration.get().find("log-branches");
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-b", "default", "--cwd", repo.getLocation());
-		report("log -b default" , new HgLogCommand(repo).branch("default").execute(), true);
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-b", "test", "--cwd", repo.getLocation());
-		report("log -b test" , new HgLogCommand(repo).branch("test").execute(), true);
-		//
-		assertTrue("log -b dummy shall yeild empty result", new HgLogCommand(repo).branch("dummy").execute().isEmpty());
-		//
-		changelogParser.reset();
-		eh.run("hg", "log", "--debug", "-b", "default", "-b", "test", "--cwd", repo.getLocation());
-		report("log -b default -b test" , new HgLogCommand(repo).branch("default").branch("test").execute(), true);
-	}
-}
--- a/test/org/tmatesoft/hg/test/TestIncoming.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-/*
- * 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.test;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.tmatesoft.hg.internal.RequiresFile.*;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.tmatesoft.hg.core.HgChangeset;
-import org.tmatesoft.hg.core.HgIncomingCommand;
-import org.tmatesoft.hg.core.HgLogCommand;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.Internals;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-import org.tmatesoft.hg.repo.HgRepository;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class TestIncoming {
-	
-	@Rule
-	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
-
-	public static void main(String[] args) throws Throwable {
-		Configuration.get().remoteServers("http://localhost:8000/");
-		TestIncoming t = new TestIncoming();
-		t.testSimple();
-		t.errorCollector.verify();
-	}
-
-	public TestIncoming() {
-//		Configuration.get().remoteServers("http://localhost:8000/");
-	}
-
-	@Test
-	public void testSimple() throws Exception {
-		int x = 0;
-		HgLookup lookup = new HgLookup();
-		for (HgRemoteRepository hgRemote : Configuration.get().allRemote()) {
-			File dest = initEmptyTempRepo("test-incoming-" + x++);
-			HgRepository localRepo = lookup.detect(dest);
-			// Idea:
-			// hg in, hg4j in, compare
-			// hg pull total/2
-			// hg in, hg4j in, compare
-			List<Nodeid> incoming = runAndCompareIncoming(localRepo, hgRemote);
-			Assert.assertTrue("Need remote repository of reasonable size to test incoming command for partially filled case", incoming.size() >= 5);
-			//
-			Nodeid median = incoming.get(incoming.size() / 2); 
-			System.out.println("About to pull up to revision " + median.shortNotation());
-			new ExecHelper(new OutputParser.Stub(), dest).run("hg", "pull", "-r", median.toString(), hgRemote.getLocation());
-			//
-			// shall re-read repository to pull up new changes 
-			localRepo = lookup.detect(dest);
-			runAndCompareIncoming(localRepo, hgRemote);
-		}
-	}
-	
-	private List<Nodeid> runAndCompareIncoming(HgRepository localRepo, HgRemoteRepository hgRemote) throws Exception {
-		// need new command instance as subsequence exec[Lite|Full] on the same command would yield same result,
-		// regardless of the pull in between.
-		HgIncomingCommand cmd = new HgIncomingCommand(localRepo);
-		cmd.against(hgRemote);
-		HgLogCommand.CollectHandler collector = new HgLogCommand.CollectHandler();
-		LogOutputParser outParser = new LogOutputParser(true);
-		ExecHelper eh = new ExecHelper(outParser, new File(localRepo.getLocation()));
-		cmd.executeFull(collector);
-		eh.run("hg", "incoming", "--debug", hgRemote.getLocation());
-		List<Nodeid> liteResult = cmd.executeLite(null);
-		report(collector, outParser, liteResult, errorCollector);
-		return liteResult;
-	}
-	
-	static void report(HgLogCommand.CollectHandler collector, LogOutputParser outParser, List<Nodeid> liteResult, ErrorCollectorExt errorCollector) {
-		TestHistory.report("hg vs execFull", collector.getChanges(), outParser.getResult(), false, errorCollector);
-		//
-		ArrayList<Nodeid> expected = new ArrayList<Nodeid>(outParser.getResult().size());
-		for (LogOutputParser.Record r : outParser.getResult()) {
-			Nodeid nid = Nodeid.fromAscii(r.changesetNodeid);
-			expected.add(nid);
-		}
-		checkNodeids("hg vs execLite:", liteResult, expected, errorCollector);
-		//
-		expected = new ArrayList<Nodeid>(outParser.getResult().size());
-		for (HgChangeset cs : collector.getChanges()) {
-			expected.add(cs.getNodeid());
-		}
-		checkNodeids("execFull vs execLite:", liteResult, expected, errorCollector);
-	}
-	
-	static void checkNodeids(String what, List<Nodeid> liteResult, List<Nodeid> expected, ErrorCollectorExt errorCollector) {
-		HashSet<Nodeid> set = new HashSet<Nodeid>(liteResult);
-		for (Nodeid nid : expected) {
-			boolean removed = set.remove(nid);
-			errorCollector.checkThat(what + " Missing " +  nid.shortNotation() + " in HgIncomingCommand.execLite result", removed, equalTo(true));
-		}
-		errorCollector.checkThat(what + " Superfluous cset reported by HgIncomingCommand.execLite", set.isEmpty(), equalTo(true));
-	}
-	
-	static File createEmptyDir(String dirName) throws IOException {
-		File dest = new File(Configuration.get().getTempDir(), dirName);
-		if (dest.exists()) {
-			TestClone.rmdir(dest);
-		}
-		dest.mkdirs();
-		return dest;
-	}
-
-	static File initEmptyTempRepo(String dirName) throws IOException {
-		File dest = createEmptyDir(dirName);
-		Internals implHelper = new Internals();
-		implHelper.setStorageConfig(1, STORE | FNCACHE | DOTENCODE);
-		implHelper.initEmptyRepository(new File(dest, ".hg"));
-		return dest;
-	}
-}
--- a/test/org/tmatesoft/hg/test/TestManifest.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,120 +0,0 @@
-/*
- * 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.test;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.junit.Assert.assertTrue;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.Map;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
-import org.tmatesoft.hg.core.HgManifestCommand;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class TestManifest {
-
-	@Rule
-	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
-
-	private final HgRepository repo;
-	private ManifestOutputParser manifestParser;
-	private ExecHelper eh;
-	final LinkedList<FileRevision> revisions = new LinkedList<FileRevision>();
-	private HgManifestCommand.Handler handler  = new HgManifestCommand.Handler() {
-		
-		public void file(FileRevision fileRevision) {
-			revisions.add(fileRevision);
-		}
-		
-		public void end(Nodeid manifestRevision) {}
-		public void dir(Path p) {}
-		public void begin(Nodeid manifestRevision) {}
-	};
-
-	public static void main(String[] args) throws Throwable {
-		TestManifest tm = new TestManifest();
-		tm.testTip();
-		tm.testFirstRevision();
-		tm.testRevisionInTheMiddle();
-		tm.errorCollector.verify();
-	}
-	
-	public TestManifest() throws Exception {
-		this(new HgLookup().detectFromWorkingDir());
-	}
-
-	private TestManifest(HgRepository hgRepo) {
-		repo = hgRepo;
-		assertTrue(!repo.isInvalid());
-		eh = new ExecHelper(manifestParser = new ManifestOutputParser(), null);
-	}
-
-	@Test
-	public void testTip() throws Exception {
-		testRevision(TIP);
-	}
-
-	@Test
-	public void testFirstRevision() throws Exception {
-		testRevision(0);
-	}
-	
-	@Test
-	public void testRevisionInTheMiddle() throws Exception {
-		int rev = repo.getManifest().getRevisionCount() / 2;
-		if (rev == 0) {
-			throw new IllegalStateException("Need manifest with few revisions");
-		}
-		testRevision(rev);
-	}
-
-	private void testRevision(int rev) throws Exception {
-		manifestParser.reset();
-		eh.run("hg", "manifest", "--debug", "--rev", String.valueOf(rev == TIP ? -1 : rev));
-		revisions.clear();
-		new HgManifestCommand(repo).revision(rev).execute(handler);
-		report("manifest " + (rev == TIP ? "TIP:" : "--rev " + rev));
-	}
-
-	private void report(String what) throws Exception {
-		final Map<Path, Nodeid> cmdLineResult = new LinkedHashMap<Path, Nodeid>(manifestParser.getResult());
-		for (FileRevision fr : revisions) {
-			Nodeid nid = cmdLineResult.remove(fr.getPath());
-			errorCollector.checkThat("Extra " + fr.getPath() + " in Java result", nid, notNullValue());
-			if (nid != null) {
-				errorCollector.checkThat("Non-matching nodeid:" + nid, nid, equalTo(fr.getRevision()));
-			}
-		}
-		errorCollector.checkThat("Non-matched entries from command line:", cmdLineResult, equalTo(Collections.<Path,Nodeid>emptyMap()));
-	}
-}
--- a/test/org/tmatesoft/hg/test/TestOutgoing.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/*
- * 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.test;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.List;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.tmatesoft.hg.core.HgLogCommand;
-import org.tmatesoft.hg.core.HgOutgoingCommand;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRemoteRepository;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class TestOutgoing {
-
-	@Rule
-	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
-
-	public static void main(String[] args) throws Throwable {
-		Configuration.get().remoteServers("http://localhost:8000/");
-		TestOutgoing t = new TestOutgoing();
-		t.testSimple();
-		t.errorCollector.verify();
-	}
-
-	public TestOutgoing() {
-	}
-
-	@Test
-	public void testSimple() throws Exception {
-		int x = 0;
-		HgLookup lookup = new HgLookup();
-		for (HgRemoteRepository hgRemote : Configuration.get().allRemote()) {
-			File dest = TestIncoming.createEmptyDir("test-outgoing-" + x++);
-			ExecHelper eh0 = new ExecHelper(new OutputParser.Stub(false), null);
-			eh0.run("hg", "clone", hgRemote.getLocation(), dest.toString());
-			eh0.cwd(dest);
-			Assert.assertEquals("initial clone failed", 0, eh0.getExitValue());
-			HgOutgoingCommand cmd = new HgOutgoingCommand(lookup.detect(dest)).against(hgRemote);
-			LogOutputParser outParser = new LogOutputParser(true);
-			ExecHelper eh = new ExecHelper(outParser, dest);
-			HgLogCommand.CollectHandler collector = new HgLogCommand.CollectHandler();
-			//
-			cmd.executeFull(collector);
-			List<Nodeid> liteResult = cmd.executeLite(null);
-			eh.run("hg", "outgoing", "--debug", hgRemote.getLocation());
-			TestIncoming.report(collector, outParser, liteResult, errorCollector);
-			//
-			File f = new File(dest, "Test.txt");
-			append(f, "1");
-			eh0.run("hg", "add");
-			eh0.run("hg", "commit", "-m", "1");
-			append(f, "2");
-			eh0.run("hg", "commit", "-m", "2");
-			//
-			cmd = new HgOutgoingCommand(lookup.detect(dest)).against(hgRemote);
-			cmd.executeFull(collector = new HgLogCommand.CollectHandler());
-			liteResult = cmd.executeLite(null);
-			outParser.reset();
-			eh.run("hg", "outgoing", "--debug", hgRemote.getLocation());
-			TestIncoming.report(collector, outParser, liteResult, errorCollector);
-		}
-	}
-
-	static void append(File f, String s) throws IOException {
-		FileWriter fw = new FileWriter(f);
-		fw.append(s);
-		fw.close();
-	}
-}
--- a/test/org/tmatesoft/hg/test/TestStatus.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,242 +0,0 @@
-/*
- * 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.test;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.tmatesoft.hg.core.HgStatus.*;
-import static org.tmatesoft.hg.core.HgStatus.Kind.*;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.tmatesoft.hg.core.HgStatus;
-import org.tmatesoft.hg.core.HgStatusCommand;
-import org.tmatesoft.hg.repo.HgLookup;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.HgStatusCollector;
-import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
-import org.tmatesoft.hg.util.Path;
-
-
-/**
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class TestStatus {
-
-	@Rule
-	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
-
-	private final HgRepository repo;
-	private StatusOutputParser statusParser;
-	private ExecHelper eh;
-
-	public static void main(String[] args) throws Throwable {
-		TestStatus test = new TestStatus();
-		test.testLowLevel();
-		test.testStatusCommand();
-		test.testPerformance();
-		test.errorCollector.verify();
-	}
-	
-	public TestStatus() throws Exception {
-		this(new HgLookup().detectFromWorkingDir());
-	}
-
-	private TestStatus(HgRepository hgRepo) {
-		repo = hgRepo;
-		Assume.assumeTrue(!repo.isInvalid());
-		statusParser = new StatusOutputParser();
-		eh = new ExecHelper(statusParser, null);
-	}
-	
-	@Test
-	public void testLowLevel() throws Exception {
-		final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo);
-		statusParser.reset();
-		eh.run("hg", "status", "-A");
-		HgStatusCollector.Record r = wcc.status(HgRepository.TIP);
-		report("hg status -A", r, statusParser);
-		//
-		statusParser.reset();
-		int revision = 3;
-		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
-		r = wcc.status(revision);
-		report("status -A --rev " + revision, r, statusParser);
-		//
-		statusParser.reset();
-		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
-		r = new HgStatusCollector.Record();
-		new HgStatusCollector(repo).change(revision, r);
-		report("status -A --change " + revision, r, statusParser);
-		//
-		statusParser.reset();
-		int rev2 = 80;
-		final String range = String.valueOf(revision) + ":" + String.valueOf(rev2);
-		eh.run("hg", "status", "-A", "--rev", range);
-		r = new HgStatusCollector(repo).status(revision, rev2);
-		report("Status -A -rev " + range, r, statusParser);
-	}
-	
-	@Test
-	public void testStatusCommand() throws Exception {
-		final HgStatusCommand sc = new HgStatusCommand(repo).all();
-		StatusCollector r;
-		statusParser.reset();
-		eh.run("hg", "status", "-A");
-		sc.execute(r = new StatusCollector());
-		report("hg status -A", r);
-		//
-		statusParser.reset();
-		int revision = 3;
-		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
-		sc.base(revision).execute(r = new StatusCollector());
-		report("status -A --rev " + revision, r);
-		//
-		statusParser.reset();
-		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
-		sc.base(TIP).revision(revision).execute(r = new StatusCollector());
-		report("status -A --change " + revision, r);
-		
-		// TODO check not -A, but defaults()/custom set of modifications 
-	}
-	
-	private static class StatusCollector implements HgStatusCommand.Handler {
-		private final Map<HgStatus.Kind, List<Path>> map = new TreeMap<HgStatus.Kind, List<Path>>();
-
-		public void handleStatus(HgStatus s) {
-			List<Path> l = map.get(s.getKind());
-			if (l == null) {
-				l = new LinkedList<Path>();
-				map.put(s.getKind(), l);
-			}
-			l.add(s.getPath());
-		}
-		
-		public List<Path> get(Kind k) {
-			List<Path> rv = map.get(k);
-			if (rv == null) {
-				return Collections.emptyList();
-			}
-			return rv;
-		}
-	}
-	
-	public void testRemovedAgainstNonTip() {
-		/*
-		 status --rev N when a file added past revision N was removed ((both physically and in dirstate), but not yet committed
-
-		 Reports extra REMOVED file (the one added and removed in between). Shall not
-		 */
-	}
-	
-	/*
-	 * With warm-up of previous tests, 10 runs, time in milliseconds
-	 * 'hg status -A': Native client total 953 (95 per run), Java client 94 (9)
-	 * 'hg status -A --rev 3:80': Native client total 1828 (182 per run), Java client 235 (23)
-	 * 'hg log --debug', 10 runs: Native client total 1766 (176 per run), Java client 78 (7)
-	 * 
-	 * 18.02.2011
-	 * 'hg status -A --rev 3:80', 10 runs:  Native client total 2000 (200 per run), Java client 250 (25)
-	 * 'hg log --debug', 10 runs: Native client total 2297 (229 per run), Java client 125 (12)
-	 * 
-	 * 9.3.2011 (DataAccess instead of byte[] in ReflogStream.Inspector
-	 * 'hg status -A',				10 runs:  Native client total 1516 (151 per run), Java client 219 (21)
-	 * 'hg status -A --rev 3:80',	10 runs:  Native client total 1875 (187 per run), Java client 3187 (318) (!!! ???)
-	 * 'hg log --debug',			10 runs: Native client total 2484 (248 per run), Java client 344 (34)
-	 */
-	public void testPerformance() throws Exception {
-		final int runs = 10;
-		final long start1 = System.currentTimeMillis();
-		for (int i = 0; i < runs; i++) {
-			statusParser.reset();
-			eh.run("hg", "status", "-A", "--rev", "3:80");
-		}
-		final long start2 = System.currentTimeMillis();
-		for (int i = 0; i < runs; i++) {
-			StatusCollector r = new StatusCollector();
-			new HgStatusCommand(repo).all().base(3).revision(80).execute(r);
-		}
-		final long end = System.currentTimeMillis();
-		System.out.printf("'hg status -A --rev 3:80', %d runs:  Native client total %d (%d per run), Java client %d (%d)\n", runs, start2-start1, (start2-start1)/runs, end-start2, (end-start2)/runs);
-	}
-	
-	private void report(String what, StatusCollector r) {
-		reportNotEqual(what + "#MODIFIED", r.get(Modified), statusParser.getModified());
-		reportNotEqual(what + "#ADDED", r.get(Added), statusParser.getAdded());
-		reportNotEqual(what + "#REMOVED", r.get(Removed), statusParser.getRemoved());
-		reportNotEqual(what + "#CLEAN", r.get(Clean), statusParser.getClean());
-		reportNotEqual(what + "#IGNORED", r.get(Ignored), statusParser.getIgnored());
-		reportNotEqual(what + "#MISSING", r.get(Missing), statusParser.getMissing());
-		reportNotEqual(what + "#UNKNOWN", r.get(Unknown), statusParser.getUnknown());
-		// FIXME test copies
-	}
-
-	private void report(String what, HgStatusCollector.Record r, StatusOutputParser statusParser) {
-		reportNotEqual(what + "#MODIFIED", r.getModified(), statusParser.getModified());
-		reportNotEqual(what + "#ADDED", r.getAdded(), statusParser.getAdded());
-		reportNotEqual(what + "#REMOVED", r.getRemoved(), statusParser.getRemoved());
-		reportNotEqual(what + "#CLEAN", r.getClean(), statusParser.getClean());
-		reportNotEqual(what + "#IGNORED", r.getIgnored(), statusParser.getIgnored());
-		reportNotEqual(what + "#MISSING", r.getMissing(), statusParser.getMissing());
-		reportNotEqual(what + "#UNKNOWN", r.getUnknown(), statusParser.getUnknown());
-		List<Path> copiedKeyDiff = difference(r.getCopied().keySet(), statusParser.getCopied().keySet());
-		HashMap<Path, String> copyDiff = new HashMap<Path,String>();
-		if (copiedKeyDiff.isEmpty()) {
-			for (Path jk : r.getCopied().keySet()) {
-				Path jv = r.getCopied().get(jk);
-				if (statusParser.getCopied().containsKey(jk)) {
-					Path cmdv = statusParser.getCopied().get(jk);
-					if (!jv.equals(cmdv)) {
-						copyDiff.put(jk, jv + " instead of " + cmdv);
-					}
-				} else {
-					copyDiff.put(jk, "ERRONEOUSLY REPORTED IN JAVA");
-				}
-			}
-		}
-		errorCollector.checkThat(what + "#Non-matching 'copied' keys: ", copiedKeyDiff, equalTo(Collections.<Path>emptyList()));
-		errorCollector.checkThat(what + "#COPIED", copyDiff, equalTo(Collections.<Path,String>emptyMap()));
-	}
-	
-	private <T> void reportNotEqual(String what, Collection<T> l1, Collection<T> l2) {
-		List<T> diff = difference(l1, l2);
-		errorCollector.checkThat(what, diff, equalTo(Collections.<T>emptyList()));
-	}
-
-	private static <T> List<T> difference(Collection<T> l1, Collection<T> l2) {
-		LinkedList<T> result = new LinkedList<T>(l2);
-		for (T t : l1) {
-			if (l2.contains(t)) {
-				result.remove(t);
-			} else {
-				result.add(t);
-			}
-		}
-		return result;
-	}
-}
--- a/test/org/tmatesoft/hg/test/TestStorePath.java	Mon May 09 11:49:23 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * 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.test;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import junit.framework.Assert;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.tmatesoft.hg.internal.Internals;
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class TestStorePath {
-	
-	@Rule
-	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
-	
-	private PathRewrite storePathHelper;
-
-	public static void main(String[] args) throws Throwable {
-		final TestStorePath test = new TestStorePath();
-		test.testWindowsFilenames();
-		test.testHashLongPath();
-		test.errorCollector.verify();
-	}
-	
-	public TestStorePath() {
-		final Internals i = new Internals();
-		i.setStorageConfig(1, 0x7);
-		storePathHelper = i.buildDataFilesHelper();
-	}
-
-	@Test
-	public void testWindowsFilenames() {
-		// see http://mercurial.selenic.com/wiki/fncacheRepoFormat#Encoding_of_Windows_reserved_names
-		String n1 = "aux.bla/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c";
-		String r1 = "store/data/au~78.bla/bla.aux/pr~6e/_p_r_n/lpt/co~6d3/nu~6c/coma/foo._n_u_l/normal.c.i";
-		Assert.assertEquals("Windows filenames are ", r1, storePathHelper.rewrite(n1));
-	}
-
-	@Test
-	public void testHashLongPath() {
-		String n1 = "AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT";
-		String r1 = "store/dh/au~78/second/x.prn/fourth/fi~3afth/sixth/seventh/eighth/nineth/tenth/loremia20419e358ddff1bf8751e38288aff1d7c32ec05.i";
-		String n2 = "enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider";
-		String r2 = "store/dh/enterpri/openesba/contrib-/corba-bc/netbeans/wsdlexte/src/main/java/org.net7018f27961fdf338a598a40c4683429e7ffb9743.i";
-		String n3 = "AUX.THE-QUICK-BROWN-FOX-JU:MPS-OVER-THE-LAZY-DOG-THE-QUICK-BROWN-FOX-JUMPS-OVER-THE-LAZY-DOG.TXT";
-		String r3 = "store/dh/au~78.the-quick-brown-fox-ju~3amps-over-the-lazy-dog-the-quick-brown-fox-jud4dcadd033000ab2b26eb66bae1906bcb15d4a70.i";
-		// TODO segment[8] == [. ], segment[8] in the middle of windows reserved name or character (to see if ~xx is broken)
-		errorCollector.checkThat(storePathHelper.rewrite(n1), equalTo(r1));
-		errorCollector.checkThat(storePathHelper.rewrite(n2), equalTo(r2));
-		errorCollector.checkThat(storePathHelper.rewrite(n3), equalTo(r3));
-	}
-}