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