changeset 347:4a49a0baf30b

Merge branch Eluru-6.5.x
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Wed, 11 Jan 2012 13:22:42 +0400
parents 607091bd8ccd (diff) e8f0eb6d4ca4 (current diff)
children fd56b9524834
files mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandResultTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java mercurial-tests/src/testng.xml
diffstat 80 files changed, 4627 insertions(+), 2998 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Wed Jan 11 12:50:45 2012 +0400
+++ b/build.xml	Wed Jan 11 13:22:42 2012 +0400
@@ -1,72 +1,72 @@
-<project name="Mercurial VCS Support" default="dist" basedir=".">
-  <property file="mercurial.properties"/>
-  <import file="mercurial.xml"/>
-
-  <property name="distPath" value="${basedir}/dist"/>
-
-  <property name="plugin.name" value="mercurial"/>
-
-  <property name="build.number" value=""/>
-  <tstamp>
-    <format property="timestamp" pattern="yyyyMMddhhmmss"/>
-  </tstamp>
-  <property name="snapshot.build.number" value="SNAPSHOT-${timestamp}"/>
-  <property name="build.vcs.number" value=""/>
-
-  <condition property="plugin.version" value="${snapshot.build.number}" else="${build.number}">
-    <matches pattern="snapshot-.*" string="${build.number}" casesensitive="false"/>
-  </condition>
-
-  <import file="teamcity-common.xml"/>
-
-  <target name="package" depends="define.version">
-    <package.teamcity.plugin name="${plugin.name}"
-                             server.output="${mercurial-server.output.dir}"
-                             agent.output="${mercurial-agent.output.dir}"
-                             common.output="${mercurial-common.output.dir}"
-                             plugin.descriptor.file="${basedir}/teamcity-plugin.xml"
-                             plugin.version="${plugin.version}"/>
-  </target>
-
-  <target name="define.version" depends="define.version.if.under.teamcity">
-    <tstamp>
-      <format property="current.time" pattern="yyyyMMddHHmm"/>
-    </tstamp>
-    <property name="plugin.version" value="SNAPSHOT-${current.time}"/>
-  </target>
-
-  <target name="define.version.if.under.teamcity" if="build.number">
-    <property name="plugin.version" value="${build.number}"/>
-  </target>
-
-  <target name="dist" depends="check.teamcitydistribution,all,package"/>
-
-  <target name="deploy" depends="dist">
-    <deploy.teamcity.plugin name="${plugin.name}"/>
-  </target>
-
-  <taskdef name="testng" classname="org.testng.TestNGAntTask" classpath="${basedir}/mercurial-tests/lib/testng-5.7-jdk15.jar"/>
-
-  <path id="tests_classpath">
-    <pathelement location="${agent.home.dir}/lib/runtime-util.jar"/>
-    <pathelement location="${agent.home.dir}/lib/buildServerRuntimeUtil.jar"/>
-    <path refid="mercurial-tests.runtime.module.classpath"/>
-  </path>
-
-  <target name="run-tests" depends="clean, init, compile.module.mercurial-tests">
-    <property name="suspend" value="n"/>
-
-    <testng haltonfailure="no" failureProperty="failure_found" listener="org.testng.reporters.TestHTMLReporter"
-            outputdir="${basedir}/test-output" classpathref="tests_classpath" dumpcommand="true" workingDir="${basedir}">
-
-      <jvmarg value="-ea"/>
-      <!--<jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=${suspend},address=5555"/>-->
-
-      <sysproperty key="java.awt.headless" value="true"/>
-
-      <xmlfileset dir="${basedir}/mercurial-tests/src">
-        <include name="testng.xml"/>
-      </xmlfileset>
-    </testng>
-  </target>
-</project>
+<project name="Mercurial VCS Support" default="dist" basedir=".">
+  <property file="mercurial.properties"/>
+  <import file="mercurial.xml"/>
+
+  <property name="distPath" value="${basedir}/dist"/>
+
+  <property name="plugin.name" value="mercurial"/>
+
+  <property name="build.number" value=""/>
+  <tstamp>
+    <format property="timestamp" pattern="yyyyMMddhhmmss"/>
+  </tstamp>
+  <property name="snapshot.build.number" value="SNAPSHOT-${timestamp}"/>
+  <property name="build.vcs.number" value=""/>
+
+  <condition property="plugin.version" value="${snapshot.build.number}" else="${build.number}">
+    <matches pattern="snapshot-.*" string="${build.number}" casesensitive="false"/>
+  </condition>
+
+  <import file="teamcity-common.xml"/>
+
+  <target name="package" depends="define.version">
+    <package.teamcity.plugin name="${plugin.name}"
+                             server.output="${mercurial-server.output.dir}"
+                             agent.output="${mercurial-agent.output.dir}"
+                             common.output="${mercurial-common.output.dir}"
+                             plugin.descriptor.file="${basedir}/teamcity-plugin.xml"
+                             plugin.version="${plugin.version}"/>
+  </target>
+
+  <target name="define.version" depends="define.version.if.under.teamcity">
+    <tstamp>
+      <format property="current.time" pattern="yyyyMMddHHmm"/>
+    </tstamp>
+    <property name="plugin.version" value="SNAPSHOT-${current.time}"/>
+  </target>
+
+  <target name="define.version.if.under.teamcity" if="build.number">
+    <property name="plugin.version" value="${build.number}"/>
+  </target>
+
+  <target name="dist" depends="check.teamcitydistribution,all,package"/>
+
+  <target name="deploy" depends="dist">
+    <deploy.teamcity.plugin name="${plugin.name}"/>
+  </target>
+
+  <taskdef name="testng" classname="org.testng.TestNGAntTask" classpath="${basedir}/mercurial-tests/lib/testng-5.7-jdk15.jar"/>
+
+  <path id="tests_classpath">
+    <pathelement location="${agent.home.dir}/lib/runtime-util.jar"/>
+    <pathelement location="${agent.home.dir}/lib/buildServerRuntimeUtil.jar"/>
+    <path refid="mercurial-tests.runtime.module.classpath"/>
+  </path>
+
+  <target name="run-tests" depends="clean, init, compile.module.mercurial-tests">
+    <property name="suspend" value="n"/>
+
+    <testng haltonfailure="no" failureProperty="failure_found" listener="org.testng.reporters.TestHTMLReporter"
+            outputdir="${basedir}/test-output" classpathref="tests_classpath" dumpcommand="true" workingDir="${basedir}">
+
+      <jvmarg value="-ea"/>
+      <!--<jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=${suspend},address=5555"/>-->
+
+      <sysproperty key="java.awt.headless" value="true"/>
+
+      <xmlfileset dir="${basedir}/mercurial-tests/src">
+        <include name="testng.xml"/>
+      </xmlfileset>
+    </testng>
+  </target>
+</project>
--- a/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Wed Jan 11 13:22:42 2012 +0400
@@ -1,6 +1,9 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
-
-<beans default-autowire="constructor">
-  <bean id="mercurialAgent" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" />
-</beans>
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans default-autowire="constructor">
+  <bean id="mercurialAgent" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" />
+  <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentHgPathProvider" />
+  <bean id="hgDetector" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgDetector" />
+  <bean id="pluginConfig" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentPluginConfigImpl"/>
+</beans>
--- a/mercurial-agent/src/build-agent-plugin.xml	Wed Jan 11 12:50:45 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-<!--
-PicoContainer configuration for old fashioned TeamCity agent plugins.
-In TeamCity 4.0 Spring configuration must be created instead.
--->
-<container>
-  <component class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" />
-</container>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentHgPathProvider.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,32 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.parameters.ProcessingResult;
+import jetbrains.buildServer.parameters.ValueResolver;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public class AgentHgPathProvider implements HgPathProvider {
+
+  private final ValueResolver myResolver;
+
+
+  public AgentHgPathProvider(@NotNull final BuildAgentConfiguration agentConfig) {
+    myResolver = agentConfig.getParametersResolver();
+  }
+
+
+  public String getHgPath(@NotNull final Settings settings) {
+    String pathFromRoot = settings.getHgPath();
+    return resolve(pathFromRoot);
+  }
+
+
+  private String resolve(@NotNull final String value) {
+    ProcessingResult result = myResolver.resolve(value);
+    return result.getResult();
+  }
+}
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,10 +1,14 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import org.jetbrains.annotations.NotNull;
+
 /**
  * @author dmitry.neverov
  */
 public interface AgentPluginConfig extends PluginConfig {
 
-  boolean isUseLocalMirrors();
+  boolean isUseLocalMirrors(@NotNull AgentRunningBuild build);
 
+  int getPullTimeout(@NotNull AgentRunningBuild build);
 }
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,9 +1,12 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
 import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.io.File;
+
 /**
  * @author dmitry.neverov
  */
@@ -12,18 +15,23 @@
   private static final String PULL_TIMEOUT_SECONDS = "teamcity.hg.pull.timeout.seconds";
   private final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600;
 
-  private final AgentRunningBuild myBuild;
+  private final BuildAgentConfiguration myAgentConfig;
 
-  public AgentPluginConfigImpl(@NotNull AgentRunningBuild build) {
-    myBuild = build;
+  public AgentPluginConfigImpl(@NotNull BuildAgentConfiguration agentConfig) {
+    myAgentConfig = agentConfig;
   }
 
-  public boolean isUseLocalMirrors() {
-    return "true".equals(myBuild.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors"));
+  public boolean isUseLocalMirrors(@NotNull AgentRunningBuild build) {
+    return "true".equals(build.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors"));
   }
 
-  public int getPullTimeout() {
-    Integer timeout = parseTimeout(myBuild.getSharedConfigParameters().get(PULL_TIMEOUT_SECONDS));
+  @NotNull
+  public File getCachesDir() {
+    return myAgentConfig.getCacheDirectory("mercurial");
+  }
+
+  public int getPullTimeout(@NotNull AgentRunningBuild build) {
+    Integer timeout = parseTimeout(build.getSharedConfigParameters().get(PULL_TIMEOUT_SECONDS));
     if (timeout != null)
       return timeout;
     return DEFAULT_PULL_TIMEOUT_SECONDS;
@@ -38,7 +46,7 @@
       if (timeout > 0)
         return timeout;
       else
-        return null;
+         return null;
     } catch (NumberFormatException e) {
       return null;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgDetector.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,90 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.agent.AgentLifeCycleAdapter;
+import jetbrains.buildServer.agent.AgentLifeCycleListener;
+import jetbrains.buildServer.agent.BuildAgent;
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommand;
+import jetbrains.buildServer.util.EventDispatcher;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author dmitry.neverov
+ */
+public class HgDetector extends AgentLifeCycleAdapter {
+
+  final static String AGENT_HG_PATH_PROPERTY = "teamcity.hg.agent.path";
+  private final static Logger LOG = Logger.getInstance(HgDetector.class.getName());
+  private final static HgVersion LEAST_SUPPORTED_VERSION = new HgVersion(1, 5, 2);
+  private final List<String> myHgPaths = Arrays.asList("hg");
+
+
+  public HgDetector(@NotNull final EventDispatcher<AgentLifeCycleListener> dispatcher) {
+    dispatcher.addListener(this);
+  }
+
+
+  @Override
+  public void beforeAgentConfigurationLoaded(@NotNull final BuildAgent agent) {
+    BuildAgentConfiguration config = agent.getConfiguration();
+    String agentHgPath = config.getConfigurationParameters().get(AGENT_HG_PATH_PROPERTY);
+    File workDir = config.getTempDirectory();
+    if (agentHgPath == null) {
+      String detectedHg = detectHg(workDir);
+      if (detectedHg != null) {
+        LOG.info("Detect installed mercurial at path " + detectedHg + ", provide it as a property " + AGENT_HG_PATH_PROPERTY);
+        config.addConfigurationParameter(AGENT_HG_PATH_PROPERTY, "hg");
+      } else {
+        LOG.info("Cannot detect installed mercurial");
+      }
+    } else {
+      if (!canRunHg(agentHgPath, workDir, true))
+        LOG.warn("Mercurial executable at path " + agentHgPath + " cannot be run or not compatible with TeamCity");
+    }
+  }
+
+
+  @Nullable
+  private String detectHg(@NotNull final File workDir) {
+    for (String path : myHgPaths) {
+      if (canRunHg(path, workDir))
+        return path;
+    }
+    return null;
+  }
+
+
+  private boolean canRunHg(@NotNull final String hgPath, @NotNull final File workDir) {
+    return canRunHg(hgPath, workDir, false);
+  }
+
+  private boolean canRunHg(@NotNull final String hgPath, @NotNull final File workDir, boolean logWarnings) {
+    VersionCommand versionCommand = new VersionCommand(hgPath, workDir);
+    try {
+      HgVersion version = versionCommand.execute();
+      if (isCompatible(version)) {
+        return true;
+      } else {
+        if (logWarnings)
+          LOG.warn("Mercurial version at path " + hgPath + " is " + version + ", required version is " + LEAST_SUPPORTED_VERSION + "+");
+        return false;
+      }
+    } catch (VcsException e) {
+      if (logWarnings)
+        LOG.warn("Error while trying to get hg version, hg path " + hgPath + ", error: " + e.getMessage());
+      return false;
+    }
+  }
+
+
+  private boolean isCompatible(@NotNull final HgVersion version) {
+    return version.isEqualsOrGreaterThan(LEAST_SUPPORTED_VERSION);
+  }
+}
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Wed Jan 11 13:22:42 2012 +0400
@@ -16,58 +16,32 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
 import jetbrains.buildServer.agent.AgentRunningBuild;
-import jetbrains.buildServer.agent.BuildAgentConfiguration;
-import jetbrains.buildServer.agent.BuildProgressLogger;
 import jetbrains.buildServer.agent.vcs.AgentVcsSupport;
 import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
 import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2;
 import jetbrains.buildServer.agent.vcs.UpdatePolicy;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
-import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.CheckoutRules;
-import jetbrains.buildServer.vcs.IncludeRule;
 import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsRoot;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-
-import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData;
 
 public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules2 {
 
+  private final AgentPluginConfig myConfig;
+  private final HgPathProvider myHgPathProvider;
   private final MirrorManager myMirrorManager;
 
-  public MercurialAgentSideVcsSupport(BuildAgentConfiguration agentConfiguration) {
-    myMirrorManager = new MirrorManager(agentConfiguration.getCacheDirectory("mercurial"));
+  public MercurialAgentSideVcsSupport(@NotNull final AgentPluginConfig pluginConfig,
+                                      @NotNull final HgPathProvider hgPathProvider) {
+    myConfig = pluginConfig;
+    myHgPathProvider = hgPathProvider;
+    myMirrorManager = new MirrorManager(myConfig.getCachesDir());
   }
 
   public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final AgentRunningBuild build, boolean cleanCheckoutRequested) throws VcsException {
-    final AgentPluginConfig config = new AgentPluginConfigImpl(build);
-    final BuildProgressLogger logger = build.getBuildLogger();
-    return new IncludeRuleUpdater() {
-      public void process(@NotNull final IncludeRule includeRule, @NotNull final File workingDir) throws VcsException {
-        try {
-          checkRuleIsValid(includeRule);
-          Settings settings = new Settings(vcsRoot);
-          if (config.isUseLocalMirrors()) {
-            updateLocalMirror(vcsRoot, logger, config);
-          }
-          updateRepository(workingDir, settings, logger, config);
-          updateWorkingDir(settings, workingDir, toVersion, logger);
-        } catch (Exception e) {
-          if (e instanceof VcsException)
-            throw (VcsException) e;
-          else
-            throw new VcsException(e);
-        }
-      }
-
-      public void dispose() throws VcsException {
-      }
-    };
+    return new MercurialIncludeRuleUpdater(myConfig, myMirrorManager, myHgPathProvider, vcsRoot, toVersion, build);
   }
 
   @NotNull
@@ -81,90 +55,4 @@
   public UpdatePolicy getUpdatePolicy() {
     return this;
   }
-
-  private void initRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException {
-    try {
-      String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors);
-      logger.message("Init repository at " + workingDir.getAbsolutePath() + ", remote repository is " +
-              removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
-      new Init(settings, workingDir, defaultPullUrl).execute();
-    } catch (IOException e) {
-      throw new VcsException("Error while initializing repository at " + workingDir.getAbsolutePath(), e);
-    }
-  }
-
-  private void updateRepository(File workingDir, Settings settings, BuildProgressLogger logger, AgentPluginConfig config) throws VcsException, IOException {
-    if (!Settings.isValidRepository(workingDir)) {
-      initRepository(workingDir, settings, logger, config.isUseLocalMirrors());
-    } else {
-      ensureUseRightRepository(workingDir, settings, logger, config.isUseLocalMirrors());
-    }
-    String defaultPullUrl = getDefaultPullUrl(settings, config.isUseLocalMirrors());
-    logger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
-    new PullCommand(settings, workingDir, defaultPullUrl).execute(config.getPullTimeout());
-    logger.message("Changes successfully pulled");
-  }
-
-  private void ensureUseRightRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException {
-    boolean clonedFromWrongRepository = useLocalMirrors && !isClonedFromLocalMirror(settings, workingDir)
-                                     || !useLocalMirrors && isClonedFromLocalMirror(settings, workingDir);
-
-    if (clonedFromWrongRepository) {
-      String rightRepository = useLocalMirrors ? "local mirror" : "remote repository";
-      String wrongRepository = useLocalMirrors ? "remote repository" : "local mirror";
-      logger.message("Repository in working directory is cloned from " + wrongRepository + ", clone it from " + rightRepository);
-      FileUtil.delete(workingDir);
-      initRepository(workingDir, settings, logger, useLocalMirrors);
-    }
-  }
-
-  private void updateLocalMirror(VcsRoot root, BuildProgressLogger logger, AgentPluginConfig config) throws VcsException, IOException {
-    Settings settings = new Settings(root);
-    File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
-    logger.message("Update local mirror at " + mirrorDir);
-    if (!Settings.isValidRepository(mirrorDir)) {
-      initRepository(mirrorDir, settings, logger, false);
-    }
-    final String defaultPullUrl = getDefaultPullUrl(settings, true);
-    logger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
-    new PullCommand(settings, mirrorDir).execute(config.getPullTimeout());
-    logger.message("Local mirror changes successfully pulled");
-  }
-
-  private void updateWorkingDir(final Settings settings, File workingDir, @NotNull final String version, final BuildProgressLogger logger) throws VcsException {
-    logger.message("Updating folder " + workingDir.getAbsolutePath() + " to revision " + version);
-    UpdateCommand uc = new UpdateCommand(settings, workingDir);
-    ChangeSet cs = new ChangeSet(version);
-    uc.setToId(cs.getId());
-    uc.execute();
-    logger.message("Folder successfully updated");
-  }
-
-  private String getDefaultPullUrl(Settings settings, boolean useLocalMirror) throws IOException {
-    if (useLocalMirror) {
-      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
-      return mirrorDir.getCanonicalPath();
-    } else {
-      return settings.getRepositoryUrl();
-    }
-  }
-
-  private void checkRuleIsValid(IncludeRule includeRule) throws VcsException {
-    if (includeRule.getTo() != null && includeRule.getTo().length() > 0) {
-      if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) {
-        throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only.");
-      }
-    }
-  }
-
-  public boolean isClonedFromLocalMirror(Settings settings, File workingDir) {
-    try {
-      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
-      File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
-      String config = FileUtil.readText(hgrc);
-      return config.contains("default = " + mirrorDir.getCanonicalPath());
-    } catch (Exception e) {
-      return false;
-    }
-  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,306 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.util.Pair;
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildProgressLogger;
+import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.IncludeRule;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData;
+
+/**
+ * @author dmitry.neverov
+ */
+public class MercurialIncludeRuleUpdater implements IncludeRuleUpdater {
+
+  private final AgentPluginConfig myConfig;
+  private final MirrorManager myMirrorManager;
+  private final HgPathProvider myHgPathProvider;
+  private final VcsRoot myRoot;
+  private final Settings mySettings;
+  private final String myToVersion;
+  private final BuildProgressLogger myLogger;
+  private final boolean myUseLocalMirrors;
+  private int myPullTimeout;
+
+  public MercurialIncludeRuleUpdater(@NotNull final AgentPluginConfig pluginConfig,
+                                     @NotNull final MirrorManager mirrorManager,
+                                     @NotNull final HgPathProvider hgPathProvider,
+                                     @NotNull final VcsRoot root,
+                                     @NotNull final String toVersion,
+                                     @NotNull final AgentRunningBuild build) {
+    myConfig = pluginConfig;
+    myMirrorManager = mirrorManager;
+    myHgPathProvider = hgPathProvider;
+    myRoot = root;
+    mySettings = new Settings(myHgPathProvider, myRoot);
+    myToVersion = toVersion;
+    myLogger = build.getBuildLogger();
+    myUseLocalMirrors = myConfig.isUseLocalMirrors(build);
+    myPullTimeout = myConfig.getPullTimeout(build);
+  }
+
+
+  public void process(@NotNull IncludeRule rule, @NotNull File workingDir) throws VcsException {
+    try {
+      checkRuleIsValid(rule);
+      if (myUseLocalMirrors)
+        updateLocalMirror();
+      updateRepository(workingDir);
+      updateWorkingDir(workingDir);
+    } catch (Exception e) {
+      throwVcsException(e);
+    }
+  }
+
+
+  public void dispose() throws VcsException {
+  }
+
+
+  private void throwVcsException(Exception e) throws VcsException {
+    if (e instanceof VcsException)
+      throw (VcsException) e;
+    else
+      throw new VcsException(e);
+  }
+
+
+  private void initRepository(Settings settings, File workingDir, boolean useLocalMirrors) throws VcsException {
+    try {
+      String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors);
+      myLogger.message("Init repository at " + workingDir.getAbsolutePath() + ", remote repository is " +
+              removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
+      new Init(settings, workingDir, defaultPullUrl).execute();
+    } catch (IOException e) {
+      throw new VcsException("Error while initializing repository at " + workingDir.getAbsolutePath(), e);
+    }
+  }
+
+
+  private void updateRepository(File workingDir) throws VcsException, IOException {
+    if (!Settings.isValidRepository(workingDir)) {
+      initRepository(mySettings, workingDir, myUseLocalMirrors);
+    } else {
+      ensureUseRightRepository(workingDir);
+    }
+    String defaultPullUrl = getDefaultPullUrl(mySettings, myUseLocalMirrors);
+    myLogger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(mySettings.getPassword())));
+    new PullCommand(mySettings, workingDir, defaultPullUrl).execute(myPullTimeout);
+    myLogger.message("Changes successfully pulled");
+  }
+
+
+  private void ensureUseRightRepository(File workingDir) throws VcsException {
+    boolean clonedFromWrongRepository = myUseLocalMirrors && !isClonedFromLocalMirror(workingDir)
+                                     || !myUseLocalMirrors && isClonedFromLocalMirror(workingDir);
+
+    if (clonedFromWrongRepository) {
+      String rightRepository = myUseLocalMirrors ? "local mirror" : "remote repository";
+      String wrongRepository = myUseLocalMirrors ? "remote repository" : "local mirror";
+      myLogger.message("Repository in working directory is cloned from " + wrongRepository + ", clone it from " + rightRepository);
+      FileUtil.delete(workingDir);
+      initRepository(mySettings, workingDir, myUseLocalMirrors);
+    }
+  }
+
+
+  private void updateLocalMirror() throws VcsException, IOException {
+    Settings settings = new Settings(myHgPathProvider, myRoot);
+    File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
+    myLogger.message("Update local mirror at " + mirrorDir);
+    if (!Settings.isValidRepository(mirrorDir)) {
+      initRepository(settings, mirrorDir, false);
+    }
+    final String defaultPullUrl = getDefaultPullUrl(settings, true);
+    myLogger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
+    new PullCommand(settings, mirrorDir).execute(myPullTimeout);
+    myLogger.message("Local mirror changes successfully pulled");
+  }
+
+
+  private void updateWorkingDir(@NotNull final File workingDir) throws VcsException, IOException {
+    String workingDirRevision = getWorkingDirRevision(mySettings, workingDir);
+    if (isInitialClone(workingDirRevision)) {
+      doUpdateWorkingDir(workingDir);
+    } else {
+      Map<String, Pair<String, String>> currentSubrepos = getSubrepositories(workingDir, workingDirRevision);
+      if (currentSubrepos.isEmpty()) {
+        doUpdateWorkingDir(workingDir);
+      } else {
+        Map<String, Pair<String, String>> toVersionSubrepos = getSubrepositories(workingDir, myToVersion);
+        Map<String, Pair<String, String>> subrepositoriesWithChangedUrls = getSubrepositoriesWithChangedUrls(currentSubrepos, toVersionSubrepos);
+        if (subrepositoriesWithChangedUrls.isEmpty()) {
+          doUpdateWorkingDir(workingDir);
+        } else {
+          logSubrepositoriesUrlsChanged(workingDirRevision, myToVersion, subrepositoriesWithChangedUrls);
+          FileUtil.delete(workingDir);
+          updateRepository(workingDir);
+          doUpdateWorkingDir(workingDir);
+        }
+      }
+    }
+  }
+
+
+  private void logSubrepositoriesUrlsChanged(@NotNull final String fromVersion,
+                                             @NotNull final String toVersion,
+                                             @NotNull final Map<String, Pair<String, String>> subrepositoriesWithChangedUrls) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("Subrepositories' URLs were changed between revisions ").append(fromVersion).append("..").append(toVersion).append(":\n");
+    for (Map.Entry<String, Pair<String, String>> entry : subrepositoriesWithChangedUrls.entrySet()) {
+      String path = entry.getKey();
+      String oldUrl = entry.getValue().first;
+      String newUrl = entry.getValue().second;
+      sb.append("path: \"").append(path).append("\", old URL: \"").append(oldUrl).append("\", new URL: \"").append(newUrl).append("\";\n");
+    }
+    sb.append("do clean checkout.");
+    myLogger.warning(sb.toString());
+  }
+
+
+  /*returns map: relative path -> (repository url, repository revision)*/
+  private Map<String, Pair<String, String>> getSubrepositories(@NotNull final File workingDir, @NotNull final String revision) throws VcsException, IOException {
+    CatCommand cc = new CatCommand(mySettings, workingDir);
+    cc.setRevId(revision);
+    try {
+      File parentDir = cc.execute(Arrays.asList(".hgsub", ".hgsubstate"), false);
+      File hgsub = new File(parentDir, ".hgsub");
+      File hgsubstate = new File(parentDir, ".hgsubstate");
+      return readSubrepositories(hgsub, hgsubstate);
+    } catch (VcsException e) {
+      return Collections.emptyMap();
+    }
+  }
+
+
+  private Map<String, Pair<String, String>> readSubrepositories(@NotNull final File hgsub, @NotNull final File hgsubstate) throws IOException {
+    if (hgsub.exists() && hgsubstate.exists()) {
+      Map<String, String> path2repo = readHgsub(hgsub);
+      Map<String, String> path2revision = readHgsubstate(hgsubstate);
+      Map<String, Pair<String, String>> result = new HashMap<String, Pair<String, String>>();
+      for (Map.Entry<String, String> entry : path2repo.entrySet()) {
+        String path = entry.getKey();
+        String repo = entry.getValue();
+        String revision = path2revision.get(path);
+        if (revision != null) {
+          result.put(path, Pair.create(repo, revision));
+        } else {
+          myLogger.warning("Cannot find revision for subrepository at path " + path + " skip it");
+        }
+      }
+      return result;
+    } else {
+      return Collections.emptyMap();
+    }
+  }
+
+
+  /*returns map: relative path -> repository url */
+  private Map<String, String> readHgsub(@NotNull final File hgsub) throws IOException {
+    Map<String, String> result = new HashMap<String, String>();
+    for (String line : FileUtil.readFile(hgsub)) {
+      String[] parts = line.split(" = ");
+      if (parts.length == 2) {
+        result.put(parts[0], parts[1]);
+      } else {
+        myLogger.warning("Cannot parse the line '" + line + "' from .hgsub, skip it");
+      }
+    }
+    return result;
+  }
+
+
+  /*returns map: relative path -> revision */
+  private Map<String, String> readHgsubstate(@NotNull final File hgsubstate) throws IOException {
+    Map<String, String> result = new HashMap<String, String>();
+    for (String line : FileUtil.readFile(hgsubstate)) {
+      String[] parts = line.split(" ");
+      if (parts.length == 2) {
+        result.put(parts[1], parts[0]);
+      } else {
+        myLogger.warning("Cannot parse the line '" + line + "' from .hgsubstate, skip it");
+      }
+    }
+    return result;
+  }
+
+
+  /*returns map repository path -> (old url, new url)*/
+  private Map<String, Pair<String, String>> getSubrepositoriesWithChangedUrls(@NotNull final Map<String, Pair<String, String>> subrepos1, @NotNull final Map<String, Pair<String, String>> subrepos2) {
+    Map<String, Pair<String, String>> result = new HashMap<String, Pair<String, String>>();
+    for (Map.Entry<String, Pair<String, String>> entry : subrepos1.entrySet()) {
+      String path = entry.getKey();
+      String url1 = entry.getValue().first;
+      Pair<String, String> urlRevision = subrepos2.get(path);
+      if (urlRevision != null && !url1.equals(urlRevision.first))
+        result.put(path, Pair.create(url1, urlRevision.first));
+    }
+    return result;
+  }
+
+
+  private boolean isInitialClone(@NotNull final String workingDirRevision) {
+    return "000000000000".equals(workingDirRevision);
+  }
+
+
+  private String getWorkingDirRevision(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
+    IdentifyCommand id = new IdentifyCommand(settings, workingDir);
+    id.setInLocalRepository(true);
+    return id.execute();
+  }
+
+
+  private void doUpdateWorkingDir(@NotNull final File workingDir) throws VcsException {
+    myLogger.message("Updating folder " + workingDir.getAbsolutePath() + " to revision " + myToVersion);
+    UpdateCommand uc = new UpdateCommand(mySettings, workingDir);
+    ChangeSet cs = new ChangeSet(myToVersion);
+    uc.setToId(cs.getId());
+    uc.execute();
+    myLogger.message("Folder successfully updated");
+  }
+
+
+  private String getDefaultPullUrl(Settings settings, boolean useLocalMirror) throws IOException {
+    if (useLocalMirror) {
+      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
+      return mirrorDir.getCanonicalPath();
+    } else {
+      return settings.getRepositoryUrl();
+    }
+  }
+
+
+  private void checkRuleIsValid(IncludeRule includeRule) throws VcsException {
+    if (includeRule.getTo() != null && includeRule.getTo().length() > 0) {
+      if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) {
+        throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only.");
+      }
+    }
+  }
+
+
+  public boolean isClonedFromLocalMirror(@NotNull final File workingDir) {
+    try {
+      File mirrorDir = myMirrorManager.getMirrorDir(mySettings.getRepositoryUrl());
+      File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
+      String config = FileUtil.readText(hgrc);
+      return config.contains("default = " + mirrorDir.getCanonicalPath());
+    } catch (Exception e) {
+      return false;
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,30 +1,30 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.vcs.VcsRoot;
-
-public interface Constants {
-  String VCS_NAME = "mercurial";
-  String REPOSITORY_PROP = "repositoryPath";
-  String BRANCH_NAME_PROP = "branchName";
-  String HG_COMMAND_PATH_PROP = "hgCommandPath";
-  String HG_PATH_ENV = "TEAMCITY_HG_PATH";
-  String SERVER_CLONE_PATH_PROP = "serverClonePath";
-  String USERNAME = "username";
-  String PASSWORD = VcsRoot.SECURE_PROPERTY_PREFIX + "password";
-  String UNCOMPRESSED_TRANSFER = "uncompressedTransfer";
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.VcsRoot;
+
+public interface Constants {
+  String VCS_NAME = "mercurial";
+  String REPOSITORY_PROP = "repositoryPath";
+  String BRANCH_NAME_PROP = "branchName";
+  String HG_COMMAND_PATH_PROP = "hgCommandPath";
+  String HG_PATH_ENV = "TEAMCITY_HG_PATH";
+  String SERVER_CLONE_PATH_PROP = "serverClonePath";
+  String USERNAME = "username";
+  String PASSWORD = VcsRoot.SECURE_PROPERTY_PREFIX + "password";
+  String UNCOMPRESSED_TRANSFER = "uncompressedTransfer";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgPathProvider.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,13 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public interface HgPathProvider {
+
+  String getHgPath(@NotNull Settings settings);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVersion.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,104 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public class HgVersion implements Comparable<HgVersion> {
+
+  private static final String PREFIX = "Mercurial Distributed SCM (version ";
+
+  private final int myMajor;
+  private final int myMinor;
+  private final int myThird;
+
+
+  public HgVersion(int major, int minor, int third) {
+    myMajor = major;
+    myMinor = minor;
+    myThird = third;
+  }
+
+
+  public static HgVersion parse(@NotNull final String version) {
+    Parser p = new Parser(version);
+    if (!p.skipString(PREFIX))
+      throw new IllegalArgumentException("Incorrect version format: " + version);
+    int major = p.readInt();
+    p.skipString(".");
+    int minor = p.readInt();
+    int third = p.skipString(".") ? p.readInt() : 0;
+    return new HgVersion(major, minor, third);
+  }
+
+
+  public boolean isEqualsOrGreaterThan(HgVersion other) {
+    return compareTo(other) >= 0;
+  }
+
+
+  @Override
+  public String toString() {
+    return myMajor + "." + myMinor + "." + myThird;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return (o instanceof HgVersion) && this.compareTo((HgVersion) o) == 0;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myMajor;
+    result = 31 * result + myMinor;
+    result = 31 * result + myThird;
+    return result;
+  }
+
+  public int compareTo(HgVersion other) {
+    int d = myMajor - other.myMajor;
+    if (d != 0)
+      return d;
+
+    d = myMinor - other.myMinor;
+    if (d != 0)
+      return d;
+
+    return myThird - other.myThird;
+  }
+
+
+  private static final class Parser {
+
+    private final String myString;
+    private int myIndex = 0;
+
+    Parser(@NotNull String string) {
+      myString = string;
+    }
+
+
+    boolean skipString(String str) {
+      if (myIndex == myString.length() || myIndex + str.length() > myString.length())
+        return false;
+      String substr = myString.substring(myIndex, myIndex + str.length());
+      if (substr.equals(str)) {
+        myIndex = myIndex + str.length();
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+
+    int readInt() {
+      int result = 0;
+      while (myIndex < myString.length() && Character.isDigit(myString.codePointAt(myIndex))) {
+        result = result * 10 + Character.digit(myString.codePointAt(myIndex), 10);
+        myIndex++;
+      }
+      return result;
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PathUtil.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PathUtil.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,37 +1,37 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-
-public class PathUtil {
-  @NotNull
-  public static String normalizeSeparator(@NotNull String repPath) {
-    return repPath.replace('\\', '/');
-  }
-
-  @NotNull
-  public static File getCanonicalFile(@NotNull File file) {
-    try {
-      return file.getCanonicalFile();
-    } catch (IOException e) {
-      return file.getAbsoluteFile();
-    }
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+public class PathUtil {
+  @NotNull
+  public static String normalizeSeparator(@NotNull String repPath) {
+    return repPath.replace('\\', '/');
+  }
+
+  @NotNull
+  public static File getCanonicalFile(@NotNull File file) {
+    try {
+      return file.getCanonicalFile();
+    } catch (IOException e) {
+      return file.getAbsoluteFile();
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PluginConfig.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PluginConfig.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,10 +1,15 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
 /**
  * @author dmitry.neverov
  */
 public interface PluginConfig {
 
-  int getPullTimeout();
+  @NotNull
+  File getCachesDir();
 
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -20,7 +20,7 @@
 
 import java.io.File;
 
-public class ArchiveCommand extends BaseCommand {
+public class ArchiveCommand extends VcsRootCommand {
   private File myDestDir;
   private String myToId;
 
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,131 +1,112 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import com.intellij.openapi.util.SystemInfo;
-import jetbrains.buildServer.util.StringUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.Set;
-
-/**
- * @author pavel
- */
-public class BaseCommand {
-  private Settings mySettings;
-  private String myWorkDirectory;
-
-  public BaseCommand(@NotNull final Settings settings, @NotNull File workingDir) {
-    mySettings = settings;
-    myWorkDirectory = workingDir.getAbsolutePath();
-  }
-
-
-  public Settings getSettings() {
-    return mySettings;
-  }
-
-  /**
-   * Sets new working directory, by default working directory is taken from the Settings#getLocalRepositoryDir
-   * @param workDirectory work dir
-   */
-  public void setWorkDirectory(final String workDirectory) {
-    myWorkDirectory = workDirectory;
-  }
-
-  public String getWorkDirectory() {
-    return myWorkDirectory;
-  }
-
-  protected GeneralCommandLine createCommandLine() {
-    GeneralCommandLine cli = createCL();
-    cli.setWorkDirectory(myWorkDirectory);
-    cli.setPassParentEnvs(true);
-    return cli;
-  }
-
-  protected GeneralCommandLine createCL() {
-    GeneralCommandLine cl = new EscapingCommandLineDecorator();
-    setupExecutable(cl);
-    return cl;
-  }
-
-  /**
-   * Escape its parameters
-   */
-  private static class EscapingCommandLineDecorator extends GeneralCommandLine {
-    @Override
-    public void addParameter(@NotNull String parameter) {
-      String escaped = escape(parameter);
-      super.addParameter(escaped);
-    }
-
-    private String escape(String s) {
-      return StringUtil.escapeQuotesIfWindows(s);
-    }
-  }
-
-  /**
-   * Since mercurial 1.7 on Windows the only file inside '<mercurial_install_dir>/bin' is 'hg.cmd'
-   * which run hg.exe placed in the parent dir. GeneralCommandLine will not find hg.cmd, in the
-   * case when $PATH contains <mercurial_install_dir>/bin and doesn't contain <mercurial_install_dir>
-   * and hg executable is set to 'hg'. To fix it - run hg using windows shell which expand
-   * hg to hg.cmd correctly.
-   */
-  private void setupExecutable(GeneralCommandLine cli) {
-    if (SystemInfo.isWindows && getSettings().getHgCommandPath().equals("hg")) {
-      setupCmd(cli);
-    } else {
-      setupHg(cli);
-    }
-  }
-
-  private void setupCmd(GeneralCommandLine cli) {
-    cli.setExePath("cmd");
-    cli.addParameter("/c");
-    cli.addParameter("hg");
-  }
-
-  private void setupHg(GeneralCommandLine cli) {
-    cli.setExePath(getSettings().getHgCommandPath());
-  }
-
-  protected CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
-    return CommandUtil.runCommand(cli, getPrivateData());
-  }
-
-  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, boolean logErrorsInDebug) throws VcsException {
-    return CommandUtil.runCommand(cli, CommandUtil.DEFAULT_COMMAND_TIMEOUT_SEC, getPrivateData(), logErrorsInDebug);
-  }
-
-  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, int executionTimeout) throws VcsException {
-    return CommandUtil.runCommand(cli, executionTimeout, getPrivateData());
-  }
-
-  protected void failIfNotEmptyStdErr(@NotNull GeneralCommandLine cli, @NotNull CommandResult res) throws VcsException {
-    if (!StringUtil.isEmpty(res.getStderr())) {
-      CommandUtil.commandFailed(cli.getCommandLineString(), res);
-    }
-  }
-
-  public Set<String> getPrivateData() {
-    return Collections.singleton(mySettings.getPassword());
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.openapi.util.SystemInfo;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Collections;
+
+/**
+ * @author pavel
+ */
+public class BaseCommand {
+
+  private final String myHgPath;
+  private final File myWorkDirectory;
+
+  public BaseCommand(@NotNull final String hgPath, @NotNull File workingDir) {
+    myHgPath = hgPath;
+    myWorkDirectory = workingDir;
+  }
+
+
+  public File getWorkDirectory() {
+    return myWorkDirectory;
+  }
+
+  protected GeneralCommandLine createCommandLine() {
+    GeneralCommandLine cli = createCL();
+    cli.setWorkDirectory(myWorkDirectory.getAbsolutePath());
+    cli.setPassParentEnvs(true);
+    return cli;
+  }
+
+  protected GeneralCommandLine createCL() {
+    GeneralCommandLine cl = new EscapingCommandLineDecorator();
+    setupExecutable(cl);
+    return cl;
+  }
+
+  /**
+   * Escape its parameters
+   */
+  private static class EscapingCommandLineDecorator extends GeneralCommandLine {
+    @Override
+    public void addParameter(@NotNull String parameter) {
+      String escaped = escape(parameter);
+      super.addParameter(escaped);
+    }
+
+    private String escape(String s) {
+      return StringUtil.escapeQuotesIfWindows(s);
+    }
+  }
+
+  /**
+   * Since mercurial 1.7 on Windows the only file inside '<mercurial_install_dir>/bin' is 'hg.cmd'
+   * which run hg.exe placed in the parent dir. GeneralCommandLine will not find hg.cmd, in the
+   * case when $PATH contains <mercurial_install_dir>/bin and doesn't contain <mercurial_install_dir>
+   * and hg executable is set to 'hg'. To fix it - run hg using windows shell which expand
+   * hg to hg.cmd correctly.
+   * @param cli command line in which to setup hg executable
+   */
+  private void setupExecutable(GeneralCommandLine cli) {
+    if (SystemInfo.isWindows && myHgPath.equals("hg")) {
+      setupCmd(cli);
+    } else {
+      setupHg(cli);
+    }
+  }
+
+  private void setupCmd(GeneralCommandLine cli) {
+    cli.setExePath("cmd");
+    cli.addParameter("/c");
+    cli.addParameter("hg");
+  }
+
+  private void setupHg(GeneralCommandLine cli) {
+    cli.setExePath(myHgPath);
+  }
+
+  protected CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
+    return CommandUtil.runCommand(cli, Collections.<String>emptySet());
+  }
+
+  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, int executionTimeout) throws VcsException {
+    return CommandUtil.runCommand(cli, executionTimeout, Collections.<String>emptySet());
+  }
+
+  protected void failIfNotEmptyStdErr(@NotNull GeneralCommandLine cli, @NotNull CommandResult res) throws VcsException {
+    if (!StringUtil.isEmpty(res.getStderr())) {
+      CommandUtil.commandFailed(cli.getCommandLineString(), res);
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,60 +1,60 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @author Pavel.Sher
- *         Date: 26.10.2008
- */
-public class BranchesCommand extends BaseCommand {
-
-  public BranchesCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-  }
-
-  /**
-   * Returns map of branch name to latest changeset in that branch
-   * @return see above
-   * @throws jetbrains.buildServer.vcs.VcsException if error occurs
-   */
-  public Map<String, ChangeSet> execute() throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("branches");
-    CommandResult res = runCommand(cli);
-    String stdout = res.getStdout();
-    Map<String, ChangeSet> result = new HashMap<String, ChangeSet>();
-    Pattern branchPattern = Pattern.compile("(.*)[\\s]+([0-9]+:[A-Za-z0-9]+).*");
-    for (String line: stdout.split("[\r\n]+")) {
-      Matcher matcher = branchPattern.matcher(line);
-      if (matcher.matches()) {
-        String branchName = matcher.group(1).trim();
-        String changeId = matcher.group(2).trim();
-        result.put(branchName, new ChangeSet(changeId));
-      }
-    }
-    return result;
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Pavel.Sher
+ *         Date: 26.10.2008
+ */
+public class BranchesCommand extends VcsRootCommand {
+
+  public BranchesCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+  }
+
+  /**
+   * Returns map of branch name to latest changeset in that branch
+   * @return see above
+   * @throws jetbrains.buildServer.vcs.VcsException if error occurs
+   */
+  public Map<String, ChangeSet> execute() throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("branches");
+    CommandResult res = runCommand(cli);
+    String stdout = res.getStdout();
+    Map<String, ChangeSet> result = new HashMap<String, ChangeSet>();
+    Pattern branchPattern = Pattern.compile("(.*)[\\s]+([0-9]+:[A-Za-z0-9]+).*");
+    for (String line: stdout.split("[\r\n]+")) {
+      Matcher matcher = branchPattern.matcher(line);
+      if (matcher.matches()) {
+        String branchName = matcher.group(1).trim();
+        String changeId = matcher.group(2).trim();
+        result.put(branchName, new ChangeSet(changeId));
+      }
+    }
+    return result;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,88 +1,87 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-
-public class CatCommand extends BaseCommand {
-  private String myRevId;
-  private final static int MAX_CMD_LEN = 900;
-  private boolean myLogErrorsInDebug = false;
-
-  public CatCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-  }
-
-  public void setRevId(final String revId) {
-    myRevId = revId;
-  }
-
-  public void setLogErrorsInDebug(boolean doLogErrorsInDebug) {
-    myLogErrorsInDebug = doLogErrorsInDebug;
-  }
-
-  public File execute(List<String> relPaths) throws VcsException {
-    File tempDir;
-    try {
-      tempDir = FileUtil.createTempDirectory("mercurial", "catresult");
-    } catch (IOException e) {
-      throw new VcsException("Unable to create temporary directory");
-    }
-    for (String path: relPaths) {
-      final File parentFile = new File(tempDir, path).getParentFile();
-      if (!parentFile.isDirectory() && !parentFile.mkdirs()) {
-        throw new VcsException("Failed to create directory: " + parentFile.getAbsolutePath());
-      }
-    }
-
-    Queue<String> paths = new LinkedList<String>(relPaths);
-    while (!paths.isEmpty()) {
-      GeneralCommandLine cli = createCommandLine(tempDir);
-      int cmdSize = cli.getCommandLineString().length();
-
-      do {
-        String path = paths.poll();
-        cli.addParameter(path);
-        cmdSize += path.length();
-      } while (cmdSize < MAX_CMD_LEN && !paths.isEmpty());
-
-      runCommand(cli, myLogErrorsInDebug);
-    }
-
-    return tempDir;
-  }
-
-  private GeneralCommandLine createCommandLine(final File tempDir) {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("cat");
-    cli.addParameter("-o");
-    cli.addParameter(tempDir.getAbsolutePath() + File.separator + "%p");
-    if (myRevId != null) {
-      cli.addParameter("-r");
-      cli.addParameter(myRevId);
-    }
-    return cli;
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+public class CatCommand extends VcsRootCommand {
+  private String myRevId;
+  private final static int MAX_CMD_LEN = 900;
+
+  public CatCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+  }
+
+  public void setRevId(final String revId) {
+    myRevId = revId;
+  }
+
+  public File execute(List<String> relPaths) throws VcsException {
+    return execute(relPaths, true);
+  }
+
+  public File execute(List<String> relPaths, boolean checkFailure) throws VcsException {
+    File tempDir;
+    try {
+      tempDir = FileUtil.createTempDirectory("mercurial", "catresult");
+    } catch (IOException e) {
+      throw new VcsException("Unable to create temporary directory");
+    }
+    for (String path: relPaths) {
+      final File parentFile = new File(tempDir, path).getParentFile();
+      if (!parentFile.isDirectory() && !parentFile.mkdirs()) {
+        throw new VcsException("Failed to create directory: " + parentFile.getAbsolutePath());
+      }
+    }
+
+    Queue<String> paths = new LinkedList<String>(relPaths);
+    while (!paths.isEmpty()) {
+      GeneralCommandLine cli = createCommandLine(tempDir);
+      int cmdSize = cli.getCommandLineString().length();
+
+      do {
+        String path = paths.poll();
+        cli.addParameter(path);
+        cmdSize += path.length();
+      } while (cmdSize < MAX_CMD_LEN && !paths.isEmpty());
+
+      runCommand(cli, checkFailure);
+    }
+
+    return tempDir;
+  }
+
+  private GeneralCommandLine createCommandLine(final File tempDir) {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("cat");
+    cli.addParameter("-o");
+    cli.addParameter(tempDir.getAbsolutePath() + File.separator + "%p");
+    if (myRevId != null) {
+      cli.addParameter("-r");
+      cli.addParameter(myRevId);
+    }
+    return cli;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,115 +1,113 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Represents Mercurial change set
- */
-public class ChangeSet extends ChangeSetRevision {
-  @NotNull private String myUser;
-  @NotNull private Date myTimestamp;
-  private String myDescription;
-  private List<ChangeSetRevision> myParents = new ArrayList<ChangeSetRevision>();
-  private List<ModifiedFile> myModifiedFiles = new ArrayList<ModifiedFile>();
-
-
-  public ChangeSet(final int revNumber, @NotNull final String id) {
-    super(revNumber, id);
-  }
-
-  /**
-   * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1)
-   * @param fullVersion full changeset version as reported by hg log command
-   */
-  public ChangeSet(@NotNull final String fullVersion) {
-    super(fullVersion);
-  }
-
-  public void setUser(@NotNull final String user) {
-    myUser = user;
-  }
-
-  public void setTimestamp(@NotNull final Date timestamp) {
-    myTimestamp = timestamp;
-  }
-
-  public void setDescription(final String description) {
-    myDescription = description;
-  }
-
-  public void addParent(@NotNull ChangeSetRevision rev) {
-    myParents.add(rev);
-  }
-
-  /**
-   * Returns user who made changeset
-   * @return user who made changeset
-   */
-  @NotNull
-  public String getUser() {
-    return myUser;
-  }
-
-  /**
-   * Returns changeset timestamp
-   * @return changeset timestamp
-   */
-  @NotNull
-  public Date getTimestamp() {
-    return myTimestamp;
-  }
-
-  /**
-   * Returns changeset summary specified by user
-   * @return changeset summary
-   */
-  public String getDescription() {
-    return myDescription;
-  }
-
-  /**
-   * Returns parrents of this change set
-   * @return see above
-   */
-  @NotNull
-  public List<ChangeSetRevision> getParents() {
-    return myParents;
-  }
-
-  /**
-   * Check if changeset is initial changeset (has no parents)
-   * @return true if changeset is initial changeset
-   */
-  public boolean isInitial() {
-    return getParents().isEmpty();
-  }
-
-  public void setModifiedFiles(@NotNull final List<ModifiedFile> files) {
-    myModifiedFiles = files;
-  }
-
-  @NotNull
-  public List<ModifiedFile> getModifiedFiles() {
-    return myModifiedFiles;
-  }
-
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Represents Mercurial change set
+ */
+public class ChangeSet extends ChangeSetRevision {
+  @NotNull private String myUser;
+  @NotNull private Date myTimestamp;
+  private String myDescription;
+  private List<ChangeSetRevision> myParents = new ArrayList<ChangeSetRevision>();
+  private List<ModifiedFile> myModifiedFiles = new ArrayList<ModifiedFile>();
+
+  public ChangeSet(final int revNumber, @NotNull final String id) {
+    super(revNumber, id);
+  }
+
+  /**
+   * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1)
+   * @param fullVersion full changeset version as reported by hg log command
+   */
+  public ChangeSet(@NotNull final String fullVersion) {
+    super(fullVersion);
+  }
+
+  public void setUser(@NotNull final String user) {
+    myUser = user;
+  }
+
+  public void setTimestamp(@NotNull final Date timestamp) {
+    myTimestamp = timestamp;
+  }
+
+  public void setDescription(final String description) {
+    myDescription = description;
+  }
+
+  public void addParent(@NotNull ChangeSetRevision rev) {
+    myParents.add(rev);
+  }
+
+  /**
+   * Returns user who made changeset
+   * @return user who made changeset
+   */
+  @NotNull
+  public String getUser() {
+    return myUser;
+  }
+
+  /**
+   * Returns changeset timestamp
+   * @return changeset timestamp
+   */
+  @NotNull
+  public Date getTimestamp() {
+    return myTimestamp;
+  }
+
+  /**
+   * Returns changeset summary specified by user
+   * @return changeset summary
+   */
+  public String getDescription() {
+    return myDescription;
+  }
+
+  /**
+   * Returns parrents of this change set
+   * @return see above
+   */
+  @NotNull
+  public List<ChangeSetRevision> getParents() {
+    return myParents;
+  }
+
+  /**
+   * Check if changeset is initial changeset (has no parents)
+   * @return true if changeset is initial changeset
+   */
+  public boolean isInitial() {
+    return getParents().isEmpty();
+  }
+
+  public void setModifiedFiles(@NotNull final List<ModifiedFile> files) {
+    myModifiedFiles = files;
+  }
+
+  @NotNull
+  public List<ModifiedFile> getModifiedFiles() {
+    return myModifiedFiles;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSetRevision.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSetRevision.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,99 +1,99 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * @author Pavel.Sher
- */
-public class ChangeSetRevision {
-  private final int myRevNumber;
-  @NotNull
-  private final String myId;
-
-  public ChangeSetRevision(final int revNumber, @NotNull final String id) {
-    myRevNumber = revNumber;
-    myId = id;
-  }
-
-  /**
-   * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1)
-   * @param fullVersion full changeset version as reported by hg log command
-   */
-  public ChangeSetRevision(@NotNull final String fullVersion) {
-    try {
-      int colon = fullVersion.indexOf(":");
-      if (colon != -1) {
-        myRevNumber = Integer.parseInt(fullVersion.substring(0, colon));
-        myId = fullVersion.substring(colon+1);
-      } else {
-        myRevNumber = -1;
-        myId = fullVersion;
-      }
-    } catch (Throwable e) {
-      throw new IllegalArgumentException(e);
-    }
-  }
-
-  /**
-   * Returns changeset revision id
-   * @return changeset revision id
-   */
-  @NotNull
-  public String getId() {
-    return myId;
-  }
-
-  /**
-   * Returns changeset revision number
-   * @return changeset revision number
-   */
-  public int getRevNumber() {
-    return myRevNumber;
-  }
-
-  /**
-   * Returns full changeset version as reported by hg log command: revnum:revid
-   * @return full changeset version as reported by hg log
-   */
-  @NotNull
-  public String getFullVersion() {
-    return myRevNumber + ":" + myId;
-  }
-
-  @Override
-  public boolean equals(final Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    final ChangeSetRevision that = (ChangeSetRevision) o;
-
-    return myRevNumber == that.myRevNumber && myId.equals(that.myId);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = myRevNumber;
-    result = 31 * result + myId.hashCode();
-    return result;
-  }
-
-  @Override
-  public String toString() {
-    return "change:" + myId;
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Pavel.Sher
+ */
+public class ChangeSetRevision {
+  private final int myRevNumber;
+  @NotNull
+  private final String myId;
+
+  public ChangeSetRevision(final int revNumber, @NotNull final String id) {
+    myRevNumber = revNumber;
+    myId = id;
+  }
+
+  /**
+   * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1)
+   * @param fullVersion full changeset version as reported by hg log command
+   */
+  public ChangeSetRevision(@NotNull final String fullVersion) {
+    try {
+      int colon = fullVersion.indexOf(":");
+      if (colon != -1) {
+        myRevNumber = Integer.parseInt(fullVersion.substring(0, colon));
+        myId = fullVersion.substring(colon+1);
+      } else {
+        myRevNumber = -1;
+        myId = fullVersion;
+      }
+    } catch (Throwable e) {
+      throw new IllegalArgumentException(e);
+    }
+  }
+
+  /**
+   * Returns changeset revision id
+   * @return changeset revision id
+   */
+  @NotNull
+  public String getId() {
+    return myId;
+  }
+
+  /**
+   * Returns changeset revision number
+   * @return changeset revision number
+   */
+  public int getRevNumber() {
+    return myRevNumber;
+  }
+
+  /**
+   * Returns full changeset version as reported by hg log command: revnum:revid
+   * @return full changeset version as reported by hg log
+   */
+  @NotNull
+  public String getFullVersion() {
+    return myRevNumber + ":" + myId;
+  }
+
+  @Override
+  public boolean equals(final Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    final ChangeSetRevision that = (ChangeSetRevision) o;
+
+    return myRevNumber == that.myRevNumber && myId.equals(that.myId);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myRevNumber;
+    result = 31 * result + myId.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "change:" + myId;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,75 +1,75 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * @author Pavel.Sher
- */
-public class ChangedFilesCommand extends BaseCommand {
-  private String myRevId;
-
-  public ChangedFilesCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-  }
-
-  public void setRevId(@NotNull final String revId) {
-    myRevId = revId;
-  }
-
-  public List<ModifiedFile> execute() throws VcsException {
-    File styleFile;
-    try {
-      styleFile = getStyleFile();
-    } catch (IOException e) {
-      throw new VcsException("Unable to create style file: " + e.toString(), e);
-    }
-    try {
-      GeneralCommandLine cli = createCommandLine();
-      cli.addParameter("log");
-      cli.addParameter("-r");
-      cli.addParameter(myRevId + ":" + myRevId);
-      cli.addParameter("--style=" + styleFile.getAbsolutePath());
-
-      CommandResult res = runCommand(cli);
-      return parseFiles(res.getStdout());
-    } finally {
-      FileUtil.delete(styleFile);
-    }
-  }
-
-  private File getStyleFile() throws IOException {
-    File styleFile = FileUtil.createTempFile("hg", "style");
-    FileUtil.writeFile(styleFile,
-            "changeset = \"{file_mods}{file_adds}{file_dels}\"\n" +
-            "file_add = \"A {file_add}\\n\"\n" +
-            "file_del = \"R {file_del}\\n\"\n" +
-            "file_mod = \"M {file_mod}\\n\"");
-    return styleFile;
-  }
-
-  private List<ModifiedFile> parseFiles(final String stdout) {
-    return StatusCommand.parseFiles(stdout);
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Pavel.Sher
+ */
+public class ChangedFilesCommand extends VcsRootCommand {
+  private String myRevId;
+
+  public ChangedFilesCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+  }
+
+  public void setRevId(@NotNull final String revId) {
+    myRevId = revId;
+  }
+
+  public List<ModifiedFile> execute() throws VcsException {
+    File styleFile;
+    try {
+      styleFile = getStyleFile();
+    } catch (IOException e) {
+      throw new VcsException("Unable to create style file: " + e.toString(), e);
+    }
+    try {
+      GeneralCommandLine cli = createCommandLine();
+      cli.addParameter("log");
+      cli.addParameter("-r");
+      cli.addParameter(myRevId + ":" + myRevId);
+      cli.addParameter("--style=" + styleFile.getAbsolutePath());
+
+      CommandResult res = runCommand(cli);
+      return parseFiles(res.getStdout());
+    } finally {
+      FileUtil.delete(styleFile);
+    }
+  }
+
+  private File getStyleFile() throws IOException {
+    File styleFile = FileUtil.createTempFile("hg", "style");
+    FileUtil.writeFile(styleFile,
+            "changeset = \"{file_mods}{file_adds}{file_dels}\"\n" +
+            "file_add = \"A {file_add}\\n\"\n" +
+            "file_del = \"R {file_del}\\n\"\n" +
+            "file_mod = \"M {file_mod}\\n\"");
+    return styleFile;
+  }
+
+  private List<ModifiedFile> parseFiles(final String stdout) {
+    return StatusCommand.parseFiles(stdout);
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,79 +1,79 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-public class CloneCommand extends BaseCommand{
-  private String myToId;
-  private boolean myUpdateWorkingDir = true;
-  private String myRepository;
-  private File myWorkingDir;
-  private boolean myUsePullProtocol = true;
-
-  public CloneCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-    myWorkingDir = workingDir;
-    myRepository = getSettings().getRepositoryUrl();
-  }
-
-  /**
-   * Sets repository to clone, by default uses repository from the specified settings
-   * @param repo repository path
-   */
-  public void setRepository(@NotNull String repo) {
-    myRepository = repo;
-  }
-
-  public void setToId(final String toId) {
-    myToId = toId;
-  }
-
-  public void setUpdateWorkingDir(final boolean updateWorkingDir) {
-    myUpdateWorkingDir = updateWorkingDir;
-  }
-
-  public void setUsePullProtocol(boolean usePullProtocol) {
-    myUsePullProtocol = usePullProtocol;
-  }
-
-  public void execute() throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    File parent = myWorkingDir.getParentFile();
-    cli.setWorkDirectory(parent.getAbsolutePath());
-    cli.addParameter("clone");
-    if (myToId != null) {
-      cli.addParameter("-r");
-      cli.addParameter(myToId);
-    }
-    if (myUsePullProtocol)
-      cli.addParameter("--pull");
-    if (!myUpdateWorkingDir) {
-      cli.addParameter("-U");
-    }
-    if (getSettings().isUncompressedTransfer()) {
-      cli.addParameter("--uncompressed");
-    }
-    cli.addParameter(myRepository);
-    cli.addParameter(myWorkingDir.getName());
-
-    runCommand(cli, 24*3600); // some repositories are quite large, we set timeout to 24 hours
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class CloneCommand extends VcsRootCommand {
+  private String myToId;
+  private boolean myUpdateWorkingDir = true;
+  private String myRepository;
+  private File myWorkingDir;
+  private boolean myUsePullProtocol = true;
+
+  public CloneCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+    myWorkingDir = workingDir;
+    myRepository = getSettings().getRepositoryUrl();
+  }
+
+  /**
+   * Sets repository to clone, by default uses repository from the specified settings
+   * @param repo repository path
+   */
+  public void setRepository(@NotNull String repo) {
+    myRepository = repo;
+  }
+
+  public void setToId(final String toId) {
+    myToId = toId;
+  }
+
+  public void setUpdateWorkingDir(final boolean updateWorkingDir) {
+    myUpdateWorkingDir = updateWorkingDir;
+  }
+
+  public void setUsePullProtocol(boolean usePullProtocol) {
+    myUsePullProtocol = usePullProtocol;
+  }
+
+  public void execute() throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    File parent = myWorkingDir.getParentFile();
+    cli.setWorkDirectory(parent.getAbsolutePath());
+    cli.addParameter("clone");
+    if (myToId != null) {
+      cli.addParameter("-r");
+      cli.addParameter(myToId);
+    }
+    if (myUsePullProtocol)
+      cli.addParameter("--pull");
+    if (!myUpdateWorkingDir) {
+      cli.addParameter("-U");
+    }
+    if (getSettings().isUncompressedTransfer()) {
+      cli.addParameter("--uncompressed");
+    }
+    cli.addParameter(myRepository);
+    cli.addParameter(myWorkingDir.getName());
+
+    runCommand(cli, 24*3600); // some repositories are quite large, we set timeout to 24 hours
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,138 +1,133 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.ExecResult;
-import jetbrains.buildServer.SimpleCommandLineProcessRunner;
-import jetbrains.buildServer.log.Loggers;
-import jetbrains.buildServer.util.StringUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Collections;
-import java.util.Set;
-
-public class CommandUtil {
-  public static final int DEFAULT_COMMAND_TIMEOUT_SEC = 3600;
-
-  public static void checkCommandFailed(@NotNull String cmdName, @NotNull CommandResult res) throws VcsException {
-    checkCommandFailed(cmdName, res, false);
-  }
-
-  private static void checkCommandFailed(@NotNull String cmdName, @NotNull CommandResult res, boolean logErrorsInDebug) throws VcsException {
-    if (logErrorsInDebug) {
-      if (res.getExitCode() != 0 || res.getException() != null)
-        Loggers.VCS.debug(createCommandLogMessage(cmdName, res));
-    } else {
-      if (res.getExitCode() != 0 || res.getException() != null)
-        commandFailed(cmdName, res);
-      if (res.getStderr().length() > 0) {
-        Loggers.VCS.warn("Error output produced by: " + cmdName);
-        Loggers.VCS.warn(res.getStderr());
-      }
-    }
-  }
-
-  public static void commandFailed(final String cmdName, final CommandResult res) throws VcsException {
-    final String message = createCommandLogMessage(cmdName, res);
-    Loggers.VCS.warn(message);
-    if (hasImportantException(res)) {
-      Loggers.VCS.error("Error during executing '" + cmdName + "'", res.getException());
-    }
-    throw new VcsException(message);
-  }
-
-  private static String createCommandLogMessage(final String cmdName, final CommandResult res) {
-    String stderr = res.getStderr();
-    String stdout = res.getStdout();
-    String exceptionMessage = getExceptionMessage(res);
-    return "'" + cmdName + "' command failed.\n" +
-            (!StringUtil.isEmpty(stdout) ? "stdout: " + stdout + "\n" : "") +
-            (!StringUtil.isEmpty(stderr) ? "stderr: " + stderr + "\n" : "") +
-            (exceptionMessage != null ? "exception: " + exceptionMessage : "");
-  }
-
-  @Nullable
-  private static String getExceptionMessage(CommandResult result) {
-    Throwable exception = result.getException();
-    String message = null;
-    if (exception != null) {
-      message = exception.getMessage();
-      if (message == null) {
-        message = exception.getClass().getName();
-      }
-    }
-    return message;
-  }
-
-  private static boolean hasImportantException(CommandResult result) {
-    Throwable exception = result.getException();
-    if (exception != null) {
-      return exception instanceof NullPointerException;
-    } else {
-      return false;
-    }
-  }
-
-  public static CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
-    return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, Collections.<String>emptySet());
-  }
-
-  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull Set<String> privateData) throws VcsException {
-    return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, privateData);
-  }
-
-  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData) throws VcsException {
-    return runCommand(cli, executionTimeout, privateData, false);
-  }
-
-  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData, boolean logErrorsInDebug) throws VcsException {
-    final String cmdStr = removePrivateData(cli.getCommandLineString(), privateData);
-    Loggers.VCS.debug("Run command: " + cmdStr);
-    CommandResult res = run(cli, executionTimeout, cmdStr, privateData);
-    CommandUtil.checkCommandFailed(cmdStr, res, logErrorsInDebug);
-    Loggers.VCS.debug("Command " + cmdStr + " output:\n" + res.getStdout());
-    return res;
-  }
-
-  private static CommandResult run(@NotNull final GeneralCommandLine cli, final int executionTimeout, @NotNull final String cmdStr, @NotNull final Set<String> privateData) {
-    final long start = System.currentTimeMillis();
-    ExecResult res = SimpleCommandLineProcessRunner.runCommand(cli, null, new SimpleCommandLineProcessRunner.RunCommandEventsAdapter() {
-      @Override
-      public Integer getOutputIdleSecondsTimeout() {
-        return executionTimeout;
-      }
-      @Override
-      public void onProcessFinished(Process ps) {
-        long duration = System.currentTimeMillis() - start;
-        Loggers.VCS.debug("Command " + cmdStr + " took " + duration + "ms");
-      }
-    });
-    return new CommandResult(res, privateData);
-  }
-
-  public static String removePrivateData(final String str, final Set<String> privateData) {
-    String result = str;
-    for (String data: privateData) {
-      if (data == null || data.length() == 0) continue;
-      result = result.replace(data, "******");
-    }
-
-    return result;
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.ExecResult;
+import jetbrains.buildServer.SimpleCommandLineProcessRunner;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class CommandUtil {
+  private static final int DEFAULT_COMMAND_TIMEOUT_SEC = 3600;
+
+  public static void checkCommandFailed(@NotNull String cmdName, @NotNull CommandResult res) throws VcsException {
+    if (res.getExitCode() != 0 || res.getException() != null)
+      commandFailed(cmdName, res);
+    if (res.getStderr().length() > 0) {
+      Loggers.VCS.warn("Error output produced by: " + cmdName);
+      Loggers.VCS.warn(res.getStderr());
+    }
+  }
+
+  public static void commandFailed(final String cmdName, final CommandResult res) throws VcsException {
+    final String message = createCommandLogMessage(cmdName, res);
+    Loggers.VCS.warn(message);
+    if (hasImportantException(res))
+      Loggers.VCS.error("Error during executing '" + cmdName + "'", res.getException());
+    throw new VcsException(message);
+  }
+
+  private static String createCommandLogMessage(final String cmdName, final CommandResult res) {
+    String stderr = res.getStderr();
+    String stdout = res.getStdout();
+    String exceptionMessage = getExceptionMessage(res);
+    return "'" + cmdName + "' command failed.\n" +
+            (!StringUtil.isEmpty(stdout) ? "stdout: " + stdout + "\n" : "") +
+            (!StringUtil.isEmpty(stderr) ? "stderr: " + stderr + "\n" : "") +
+            (exceptionMessage != null ? "exception: " + exceptionMessage : "");
+  }
+
+  @Nullable
+  private static String getExceptionMessage(CommandResult result) {
+    Throwable exception = result.getException();
+    String message = null;
+    if (exception != null) {
+      message = exception.getMessage();
+      if (message == null) {
+        message = exception.getClass().getName();
+      }
+    }
+    return message;
+  }
+
+  private static boolean hasImportantException(CommandResult result) {
+    Throwable exception = result.getException();
+    if (exception != null) {
+      return exception instanceof NullPointerException;
+    } else {
+      return false;
+    }
+  }
+
+  public static CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
+    return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, Collections.<String>emptySet());
+  }
+
+  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull Set<String> privateData) throws VcsException {
+    return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, privateData);
+  }
+
+  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData) throws VcsException {
+    return runCommand(cli, executionTimeout, privateData, true);
+  }
+
+  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull Set<String> privateData, final boolean checkFailure) throws VcsException {
+    return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, privateData, checkFailure);
+  }
+
+  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData, final boolean checkFailure) throws VcsException {
+    final String cmdStr = removePrivateData(cli.getCommandLineString(), privateData);
+    Loggers.VCS.debug("Run command: " + cmdStr);
+    CommandResult res = run(cli, executionTimeout, cmdStr,privateData);
+    if (checkFailure)
+      CommandUtil.checkCommandFailed(cmdStr, res);
+    Loggers.VCS.debug("Command " + cmdStr + " output:\n" + res.getStdout());
+    return res;
+  }
+
+  private static CommandResult run(@NotNull final GeneralCommandLine cli, final int executionTimeout, @NotNull final String cmdStr, @NotNull final Set<String> privateData) {
+    final long start = System.currentTimeMillis();
+    ExecResult res = SimpleCommandLineProcessRunner.runCommand(cli, null, new SimpleCommandLineProcessRunner.RunCommandEventsAdapter() {
+      @Override
+      public Integer getOutputIdleSecondsTimeout() {
+        return executionTimeout;
+      }
+      @Override
+      public void onProcessFinished(Process ps) {
+        long duration = System.currentTimeMillis() - start;
+        Loggers.VCS.debug("Command " + cmdStr + " took " + duration + "ms");
+      }
+    });
+    return new CommandResult(res, privateData);
+  }
+
+  public static String removePrivateData(final String str, final Set<String> privateData) {
+    String result = str;
+    for (String data: privateData) {
+      if (data == null || data.length() == 0) continue;
+      result = result.replace(data, "******");
+    }
+
+    return result;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,61 +1,70 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-/**
- * @author Pavel.Sher
- *         Date: 16.07.2008
- */
-public class IdentifyCommand extends BaseCommand {
-
-  private boolean myInLocalRepository = false;
-  private ChangeSet myChangeSet;
-
-  public IdentifyCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-  }
-
-  public void setInLocalRepository(boolean inLocalRepository) {
-    myInLocalRepository = inLocalRepository;
-  }
-
-  public void setChangeSet(ChangeSet changeSet) {
-    myChangeSet = changeSet;
-  }
-
-  public String execute() throws VcsException {
-    GeneralCommandLine cli = createCL();
-    cli.addParameter("identify");
-    if (myInLocalRepository) {
-      cli.setWorkDirectory(this.getWorkDirectory());
-    } else {
-      cli.addParameter(getSettings().getRepositoryUrl());
-    }
-    if (myChangeSet != null) {
-      cli.addParameter("--rev");
-      cli.addParameter(myChangeSet.getId());
-    }
-    CommandResult res = runCommand(cli);
-    failIfNotEmptyStdErr(cli, res);
-    return res.getStdout();
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author Pavel.Sher
+ *         Date: 16.07.2008
+ */
+public class IdentifyCommand extends VcsRootCommand {
+
+  private boolean myInLocalRepository = false;
+  private ChangeSet myChangeSet;
+  private Integer myRevisionNumber;
+
+  public IdentifyCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+  }
+
+  public void setInLocalRepository(boolean inLocalRepository) {
+    myInLocalRepository = inLocalRepository;
+  }
+
+  public void setChangeSet(ChangeSet changeSet) {
+    myChangeSet = changeSet;
+  }
+
+  public void setRevisionNumber(int revisionNumber) {
+    myRevisionNumber = revisionNumber;
+  }
+
+  public String execute() throws VcsException {
+    GeneralCommandLine cli = createCL();
+    cli.addParameter("identify");
+    if (myInLocalRepository) {
+      cli.setWorkDirectory(this.getWorkDirectory().getAbsolutePath());
+    } else {
+      cli.addParameter(getSettings().getRepositoryUrl());
+    }
+    if (myChangeSet != null) {
+      cli.addParameter("--rev");
+      cli.addParameter(myChangeSet.getId());
+    } else if (myRevisionNumber != null) {
+      cli.addParameter("--rev");
+      cli.addParameter(myRevisionNumber.toString());
+    }
+    CommandResult res = runCommand(cli);
+    failIfNotEmptyStdErr(cli, res);
+    String output = res.getStdout().trim();
+    return output.contains(" ") ? output.substring(0, output.indexOf(" ")) : output;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Init.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Init.java	Wed Jan 11 13:22:42 2012 +0400
@@ -10,7 +10,7 @@
 /**
  * @author dmitry.neverov
  */
-public class Init extends BaseCommand {
+public class Init extends VcsRootCommand {
 
   private final String myDefaultPullUrl;
 
@@ -20,7 +20,7 @@
   }
 
   public void execute() throws VcsException {
-    new File(getWorkDirectory()).mkdirs();
+    getWorkDirectory().mkdirs();
     GeneralCommandLine cli = createCommandLine();
     cli.addParameter("init");
     runCommand(cli);
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,209 +1,244 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import com.intellij.openapi.util.JDOMUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jdom.Document;
-import org.jdom.Element;
-import org.jdom.JDOMException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.*;
-
-public class LogCommand extends BaseCommand {
-
-  private static final String DATE_FORMAT = "EEE MMM d HH:mm:ss yyyy Z";
-
-  private String myFromId;
-  private String myToId;
-  private Integer myLimit = null;
-  private String myBranchName;
-  private final File myTemplate;
-
-  public LogCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull final File template) {
-    super(settings, workingDir);
-    myTemplate = template;
-    myBranchName = settings.getBranchName();
-  }
-
-  public void setFromRevId(String id) {
-    myFromId = id;
-  }
-
-  public void setToRevId(String id) {
-    myToId = id;
-  }
-
-  public void setLimit(final int limit) {
-    myLimit = limit;
-  }
-
-  public void setBranchName(String branchName) {
-    myBranchName = branchName;
-  }
-
-  public List<ChangeSet> execute() throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("log");
-    cli.addParameter("--style=" + myTemplate.getAbsolutePath());
-    if (myBranchName != null) {
-      cli.addParameter("-b");
-      cli.addParameter(getSettings().getBranchName());
-    }
-    cli.addParameter("-r");
-    String from = myFromId;
-    if (from == null) from = "0";
-    String to = myToId;
-    if (to == null) to = "tip";
-    cli.addParameter(from + ":" + to);
-    if (myLimit != null) {
-      cli.addParameter("--limit");
-      cli.addParameter(myLimit.toString());
-    }
-    CommandResult res = runCommand(cli);
-    try {
-      return parseChangeSetsXml(res.getStdout());
-    } catch (Exception e) {
-      throw new VcsException("Error while parsing log output:\n" + res.getStdout(), e);
-    }
-  }
-
-
-  private List<ChangeSet> parseChangeSetsXml(@NotNull final String xml) throws JDOMException, IOException, ParseException {
-    if ("".equals(xml))
-      return Collections.emptyList();
-    Document doc = loadDocument(xml);
-    Element log = doc.getRootElement();
-    return parseLog(log);
-  }
-
-  private Document loadDocument(@NotNull final String xml) throws IOException, JDOMException {
-    String validXml = makeValidXml(xml);
-    return JDOMUtil.loadDocument(validXml);
-  }
-
-
-  private String makeValidXml(@NotNull final String xml) {
-    String trimmed = xml.trim();
-    if (xml.trim().endsWith("</log>"))
-      return xml;
-    else
-      return trimmed + "</log>";
-  }
-
-
-  private List<ChangeSet> parseLog(@NotNull final Element logElement) throws ParseException {
-    List<ChangeSet> result = new ArrayList<ChangeSet>();
-    for (Object o : logElement.getChildren("logentry")) {
-      Element entry = (Element) o;
-      result.add(parseLogEntry(entry));
-    }
-    return result;
-  }
-
-
-
-  private ChangeSet parseLogEntry(@NotNull final Element logEntry) throws ParseException {
-    ChangeSet cset = new ChangeSet(getRevision(logEntry), getId(logEntry));
-    addParents(cset, logEntry);
-    cset.setUser(getAuthor(logEntry));
-    cset.setDescription(getDescription(logEntry));
-    cset.setTimestamp(getDate(logEntry));
-    cset.setModifiedFiles(getModifiedFiles(logEntry));
-    return cset;
-  }
-
-
-  private int getRevision(@NotNull final Element logEntry) {
-    return Integer.parseInt(logEntry.getAttribute("revision").getValue());
-  }
-
-
-  private String getId(@NotNull final Element logEntry) {
-    return logEntry.getAttribute("shortnode").getValue();
-  }
-
-
-  private void addParents(@NotNull final ChangeSet cset, @NotNull final Element logEntry) {
-    List parents = logEntry.getChildren("parent");
-    for (Object p : parents) {
-      Element parent = (Element) p;
-      ChangeSetRevision parentCset = getParent(parent);
-      cset.addParent(parentCset);
-    }
-  }
-
-
-  private ChangeSetRevision getParent(@NotNull final Element parent) {
-    return new ChangeSetRevision(getRevision(parent), getId(parent));
-  }
-
-
-  private String getAuthor(@NotNull final Element logEntry) {
-    Element author = logEntry.getChild("author");
-    return author.getAttribute("original").getValue();
-  }
-
-
-  private String getDescription(@NotNull final Element logEntry) {
-    Element msg = logEntry.getChild("msg");
-    return msg.getText();
-  }
-
-
-  private Date getDate(@NotNull final Element logEntry) throws ParseException {
-    Element date = logEntry.getChild("date");
-    return new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH).parse(date.getText());
-  }
-
-
-  private List<ModifiedFile> getModifiedFiles(@NotNull final Element logEntry) {
-    List<ModifiedFile> result = new ArrayList<ModifiedFile>();
-    Element paths = logEntry.getChild("paths");
-    for (Object o : paths.getChildren("path")) {
-      Element path = (Element) o;
-      result.add(getModifiedFile(path));
-    }
-    return result;
-  }
-
-
-  private ModifiedFile getModifiedFile(@NotNull final Element path) {
-    String filePath = path.getText();
-    ModifiedFile.Status status = getStatus(path);
-    return new ModifiedFile(status, filePath);
-  }
-
-
-  private ModifiedFile.Status getStatus(@NotNull final Element path) {
-    String action = path.getAttribute("action").getValue();
-    if (action.equals("A")) {
-      return ModifiedFile.Status.ADDED;
-    } else if (action.equals("M")) {
-      return ModifiedFile.Status.MODIFIED;
-    } else if (action.equals("R")) {
-      return ModifiedFile.Status.REMOVED;
-    } else {
-      return ModifiedFile.Status.UNKNOWN;
-    }
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.openapi.util.JDOMUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+public class LogCommand extends VcsRootCommand {
+
+  private final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000";
+  private static final String DATE_FORMAT = "EEE MMM d HH:mm:ss yyyy Z";
+
+  private String myFromId;
+  private String myToId;
+  private Integer myLimit = null;
+  private String myBranchName;
+  private boolean myCalculateParents = true;
+  private String myRevsets;
+  private final File myTemplate;
+
+  public LogCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull final File template) {
+    super(settings, workingDir);
+    myTemplate = template;
+    myBranchName = settings.getBranchName();
+  }
+
+  public void setFromRevId(String id) {
+    myFromId = id;
+  }
+
+  public void setToRevId(String id) {
+    myToId = id;
+  }
+
+  public void setLimit(final int limit) {
+    myLimit = limit;
+  }
+
+  public void showCommitsFromAllBranches() {
+    myBranchName = null;
+  }
+
+  public void setCalculateParents(boolean doCalculate) {
+    myCalculateParents = doCalculate;
+  }
+
+  public void setRevsets(String revsets) {
+    myRevsets = revsets;
+  }
+
+  public List<ChangeSet> execute() throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("log");
+    cli.addParameter("-v");
+    cli.addParameter("--style=" + myTemplate.getAbsolutePath());
+    if (myBranchName != null) {
+      cli.addParameter("-b");
+      cli.addParameter(getSettings().getBranchName());
+    }
+    cli.addParameter("-r");
+    if (myRevsets != null) {
+      cli.addParameter(myRevsets);
+    } else {
+      String from = myFromId != null ? myFromId : "0";
+      String to = myToId != null ? myToId : "tip";
+      cli.addParameter(from + ":" + to);
+    }
+    if (myLimit != null) {
+      cli.addParameter("--limit");
+      cli.addParameter(myLimit.toString());
+    }
+
+    CommandResult res = runCommand(cli);
+    try {
+      List<ChangeSet> changes = parseChangeSetsXml(res.getStdout());
+      if (myCalculateParents)
+        assignTrivialParents(changes);
+      return changes;
+    } catch (Exception e) {
+      throw new VcsException("Error while parsing log output:\n" + res.getStdout(), e);
+    }
+  }
+
+
+  private List<ChangeSet> parseChangeSetsXml(@NotNull final String xml) throws JDOMException, IOException, ParseException {
+    if ("".equals(xml))
+      return Collections.emptyList();
+    Document doc = loadDocument(xml);
+    Element log = doc.getRootElement();
+    return parseLog(log);
+  }
+
+  private Document loadDocument(@NotNull final String xml) throws IOException, JDOMException {
+    String validXml = makeValidXml(xml);
+    return JDOMUtil.loadDocument(validXml);
+  }
+
+  private String makeValidXml(@NotNull final String xml) {
+    String trimmed = xml.trim();
+    if (trimmed.endsWith("</log>"))
+      return xml;
+    else
+      return xml + "</log>";
+  }
+
+
+  private List<ChangeSet> parseLog(@NotNull final Element logElement) throws ParseException {
+    List<ChangeSet> result = new ArrayList<ChangeSet>();
+    for (Object o : logElement.getChildren("logentry")) {
+      Element entry = (Element) o;
+      result.add(parseLogEntry(entry));
+    }
+    return result;
+  }
+
+
+  private ChangeSet parseLogEntry(@NotNull final Element logEntry) throws ParseException {
+    ChangeSet cset = new ChangeSet(getRevision(logEntry), getId(logEntry));
+    addParents(cset, logEntry);
+    cset.setUser(getAuthor(logEntry));
+    cset.setDescription(getDescription(logEntry));
+    cset.setTimestamp(getDate(logEntry));
+    cset.setModifiedFiles(getModifiedFiles(logEntry));
+    return cset;
+  }
+
+
+  private int getRevision(@NotNull final Element logEntry) {
+    return Integer.parseInt(logEntry.getAttribute("revision").getValue());
+  }
+
+
+  private String getId(@NotNull final Element logEntry) {
+    return logEntry.getAttribute("shortnode").getValue();
+  }
+
+
+  private void addParents(@NotNull final ChangeSet cset, @NotNull final Element logEntry) {
+    List parents = logEntry.getChildren("parent");
+    for (Object p : parents) {
+      Element parent = (Element) p;
+      ChangeSetRevision parentCset = getParent(parent);
+      cset.addParent(parentCset);
+    }
+  }
+
+
+  private ChangeSetRevision getParent(@NotNull final Element parent) {
+    return new ChangeSetRevision(getRevision(parent), getId(parent));
+  }
+
+
+  private String getAuthor(@NotNull final Element logEntry) {
+    Element author = logEntry.getChild("author");
+    return author.getAttribute("original").getValue();
+  }
+
+
+  private String getDescription(@NotNull final Element logEntry) {
+    Element msg = logEntry.getChild("msg");
+    return msg.getText();
+  }
+
+
+  private Date getDate(@NotNull final Element logEntry) throws ParseException {
+    Element date = logEntry.getChild("date");
+    return new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH).parse(date.getText());
+  }
+
+
+  private List<ModifiedFile> getModifiedFiles(@NotNull final Element logEntry) {
+    List<ModifiedFile> result = new ArrayList<ModifiedFile>();
+    Element paths = logEntry.getChild("paths");
+    for (Object o : paths.getChildren("path")) {
+      Element path = (Element) o;
+      result.add(getModifiedFile(path));
+    }
+    return result;
+  }
+
+
+  private ModifiedFile getModifiedFile(@NotNull final Element path) {
+    String filePath = path.getText();
+    ModifiedFile.Status status = getStatus(path);
+    return new ModifiedFile(status, filePath);
+  }
+
+
+  private ModifiedFile.Status getStatus(@NotNull final Element path) {
+    String action = path.getAttribute("action").getValue();
+    if (action.equals("A")) {
+      return ModifiedFile.Status.ADDED;
+    } else if (action.equals("M")) {
+      return ModifiedFile.Status.MODIFIED;
+    } else if (action.equals("R")) {
+      return ModifiedFile.Status.REMOVED;
+    } else {
+      return ModifiedFile.Status.UNKNOWN;
+    }
+  }
+
+  private void assignTrivialParents(final @NotNull List<ChangeSet> csets) throws VcsException {
+    for (ChangeSet cset : csets) {
+      if (cset.getParents().isEmpty()) {
+        int parentRevNumber = cset.getRevNumber() - 1;
+        String parentId = getIdOf(parentRevNumber);
+        cset.addParent(new ChangeSetRevision(parentRevNumber, parentId));
+      }
+    }
+  }
+
+  private String getIdOf(int revNumber) throws VcsException {
+    if (revNumber < 0)
+      return ZERO_PARENT_ID;
+    IdentifyCommand identify = new IdentifyCommand(getSettings(), getWorkDirectory());
+    identify.setInLocalRepository(true);
+    identify.setRevisionNumber(revNumber);
+    return identify.execute();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MergeBaseCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,25 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Analog of git merge-base. It returns a last common ancestor between two revisions.
+ *
+ * @author dmitry.neverov
+ */
+public interface MergeBaseCommand {
+
+  /**
+   * Returns hash of least common ancestor between two revisions or null
+   * if common ancestor is not found
+   * @param revision1 first revision
+   * @param revision2 second revision
+   * @return see above
+   * @throws VcsException if some commands fail
+   */
+  @Nullable
+  String execute(@NotNull String revision1, @NotNull String revision2) throws VcsException;
+
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ModifiedFile.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ModifiedFile.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,86 +1,86 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Represents repository modified file
- */
-public class ModifiedFile {
-  /**
-   * Type of modification
-   */
-  public static enum Status {
-    ADDED("added"),
-    MODIFIED("modified"),
-    REMOVED("removed"),
-    UNKNOWN("unknown");
-    private String myName;
-
-    Status(@NotNull final String name) {
-      myName = name;
-    }
-
-    @NotNull
-    public String getName() {
-      return myName;
-    }
-  }
-
-  @NotNull private Status myStatus;
-  @NotNull private String myPath;
-
-  public ModifiedFile(@NotNull final Status status, @NotNull final String path) {
-    myStatus = status;
-    myPath = path;
-  }
-
-  /**
-   * Returns type of modification
-   * @return type of modification
-   */
-  @NotNull
-  public Status getStatus() {
-    return myStatus;
-  }
-
-  /**
-   * Returns file path
-   * @return file path
-   */
-  @NotNull
-  public String getPath() {
-    return myPath;
-  }
-
-  @Override
-  public boolean equals(final Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    final ModifiedFile that = (ModifiedFile) o;
-
-    return myPath.equals(that.myPath) && myStatus == that.myStatus;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = myStatus.hashCode();
-    result = 31 * result + myPath.hashCode();
-    return result;
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Represents repository modified file
+ */
+public class ModifiedFile {
+  /**
+   * Type of modification
+   */
+  public static enum Status {
+    ADDED("added"),
+    MODIFIED("modified"),
+    REMOVED("removed"),
+    UNKNOWN("unknown");
+    private String myName;
+
+    Status(@NotNull final String name) {
+      myName = name;
+    }
+
+    @NotNull
+    public String getName() {
+      return myName;
+    }
+  }
+
+  @NotNull private Status myStatus;
+  @NotNull private String myPath;
+
+  public ModifiedFile(@NotNull final Status status, @NotNull final String path) {
+    myStatus = status;
+    myPath = path;
+  }
+
+  /**
+   * Returns type of modification
+   * @return type of modification
+   */
+  @NotNull
+  public Status getStatus() {
+    return myStatus;
+  }
+
+  /**
+   * Returns file path
+   * @return file path
+   */
+  @NotNull
+  public String getPath() {
+    return myPath;
+  }
+
+  @Override
+  public boolean equals(final Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    final ModifiedFile that = (ModifiedFile) o;
+
+    return myPath.equals(that.myPath) && myStatus == that.myStatus;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myStatus.hashCode();
+    result = 31 * result + myPath.hashCode();
+    return result;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,47 +1,47 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-/**
- * @author Pavel.Sher
- *         Date: 14.07.2008
- */
-public class PullCommand extends BaseCommand {
-
-  private final String myPullUrl;
-
-  public PullCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    this(settings, workingDir, settings.getRepository());
-  }
-
-  public PullCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull String pullUrl) {
-    super(settings, workingDir);
-    myPullUrl = pullUrl;
-  }
-
-  public void execute(int timeout) throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("pull");
-    cli.addParameter(myPullUrl);
-    runCommand(cli, timeout);
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author Pavel.Sher
+ *         Date: 14.07.2008
+ */
+public class PullCommand extends VcsRootCommand {
+
+  private final String myPullUrl;
+
+  public PullCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    this(settings, workingDir, settings.getRepositoryUrl());
+  }
+
+  public PullCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull String pullUrl) {
+    super(settings, workingDir);
+    myPullUrl = pullUrl;
+  }
+
+  public void execute(int timeout) throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("pull");
+    cli.addParameter(myPullUrl);
+    runCommand(cli, timeout);
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,48 +1,48 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-/**
- * @author pavel
- */
-public class PushCommand extends BaseCommand {
-  private boolean myForced;
-
-  public PushCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-  }
-
-  public void setForce(boolean force) {
-    myForced = force;
-  }
-
-  public void execute() throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("push");
-    if (myForced) {
-      cli.addParameter("-f");
-    }
-    cli.addParameter(getSettings().getRepositoryUrl());
-    CommandResult res = runCommand(cli);
-    failIfNotEmptyStdErr(cli, res);
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author pavel
+ */
+public class PushCommand extends VcsRootCommand {
+  private boolean myForced;
+
+  public PushCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+  }
+
+  public void setForce(boolean force) {
+    myForced = force;
+  }
+
+  public void execute() throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("push");
+    if (myForced) {
+      cli.addParameter("-f");
+    }
+    cli.addParameter(getSettings().getRepositoryUrl());
+    CommandResult res = runCommand(cli);
+    failIfNotEmptyStdErr(cli, res);
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,216 +1,226 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.PathUtil;
-import jetbrains.buildServer.log.Loggers;
-import jetbrains.buildServer.util.StringUtil;
-import jetbrains.buildServer.vcs.VcsRoot;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.*;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Represents Mercurial repository settings
- */
-public class Settings {
-  private String myRepository;
-  private String myHgCommandPath;
-  private File myCustomWorkingDir;
-  private String myUsername;
-  private String myPassword;
-  private String myBranchName;
-  private boolean myUncompressedTransfer = false;
-  private static final String DEFAULT_BRANCH_NAME = "default";
-  private String myCustomClonePath;
-
-  public Settings(@NotNull VcsRoot vcsRoot) {
-    myRepository = vcsRoot.getProperty(Constants.REPOSITORY_PROP);
-    myHgCommandPath = vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP);
-    myBranchName = vcsRoot.getProperty(Constants.BRANCH_NAME_PROP);
-    myCustomClonePath = vcsRoot.getProperty(Constants.SERVER_CLONE_PATH_PROP);
-    myUsername = vcsRoot.getProperty(Constants.USERNAME);
-    myPassword = vcsRoot.getProperty(Constants.PASSWORD);
-    myUncompressedTransfer = "true".equals(vcsRoot.getProperty(Constants.UNCOMPRESSED_TRANSFER));
-  }
-
-  public String getCustomClonePath() {
-    return myCustomClonePath;
-  }
-
-  public String getRepository() {
-    return myRepository;
-  }
-
-  /**
-   * Returns name of the branch to use (returns 'default' if no branch specified)
-   * @return see above
-   */
-  @NotNull
-  public String getBranchName() {
-    return StringUtil.isEmpty(myBranchName) ? DEFAULT_BRANCH_NAME : myBranchName;
-  }
-
-  /**
-   * Returns true if current branch is default branch
-   * @return see above
-   */
-  public boolean isDefaultBranch() {
-    return getBranchName().equals(DEFAULT_BRANCH_NAME);
-  }
-
-  public boolean isUncompressedTransfer() {
-    return myUncompressedTransfer;
-  }
-
-  /**
-   * Returns path to hg command
-   * @return path to hg command
-   */
-  @NotNull
-  public String getHgCommandPath() {
-    return myHgCommandPath;
-  }
-
-  public String getUsername() {
-    return myUsername;
-  }
-
-  public String getPassword() {
-    return myPassword;
-  }
-
-  private final static Set<String> AUTH_PROTOS = new HashSet<String>();
-  static {
-    AUTH_PROTOS.add("http");
-    AUTH_PROTOS.add("https");
-    AUTH_PROTOS.add("ssh");
-  }
-
-  /**
-   * Returns URL to use for push command
-   * @return URL to use for push command
-   */
-  public String getRepositoryUrl() {
-    if (isRequireCredentials()) {
-      if (containsCredentials(myRepository)) return myRepository;
-      try {
-        return createURLWithCredentials(myRepository);
-      } catch (MalformedURLException e) {
-        Loggers.VCS.warn("Error while parsing url " + myRepository, e);
-      }
-      return myRepository;
-    } else {
-      return myRepository;
-    }
-  }
-
-  private boolean containsCredentials(final String repository) {
-    try {
-      URL url = new URL(null, repository, new FakeStreamHandler());
-      String userInfo = url.getUserInfo();
-      return userInfo != null && userInfo.contains(":");
-    } catch (MalformedURLException e) {
-      return false;
-    }
-  }
-
-  private String createURLWithCredentials(final String originalUrl) throws MalformedURLException {
-    String userInfo = createUserInfo();
-    if (!"".equals(userInfo)) {
-      URL url = new URL(null, originalUrl, new FakeStreamHandler());
-      return url.getProtocol() + "://"
-              + userInfo + "@"
-              + url.getHost()
-              + (url.getPort() != -1 ? ":" + url.getPort() : "")
-              + url.getFile()
-              + (url.getRef() != null ? url.getRef() : "");
-    } else {
-      return originalUrl;
-    }
-  }
-
-  private boolean isRequireCredentials() {
-    for (String scheme : AUTH_PROTOS) {
-      if (myRepository.startsWith(scheme + ":")) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private String createUserInfo() {
-    String userInfo = "";
-    if (!StringUtil.isEmpty(myUsername)) {
-      userInfo += myUsername;
-      if (!StringUtil.isEmpty(myPassword)) {
-        userInfo += ":" + myPassword;
-      }
-    }
-    return getEscapedUserInfo(userInfo);
-  }
-
-  private static String getEscapedUserInfo(String userInfo) {
-    try {
-      URI uri = new URI("http", userInfo, "somewhere.com", 80, "", "", "");
-      String escapedURI = uri.toASCIIString();
-      int from = "http://".length();
-      int to = escapedURI.indexOf("somewhere.com") - 1;
-      String escapedUserInfo = escapedURI.substring(from, to);
-      escapedUserInfo = escapedUserInfo.replaceAll("&", "%26");
-      return escapedUserInfo;
-    } catch (URISyntaxException e) {
-      assert false;
-    }
-    return userInfo;
-  }
-
-  /**
-   * Set custom working dir for vcs root. This option make sence only for server-side checkout
-   * @param customWorkingDir custom working dir
-   */
-  public void setCustomWorkingDir(@NotNull final File customWorkingDir) {
-    myCustomWorkingDir = PathUtil.getCanonicalFile(customWorkingDir);
-  }
-
-  /**
-   * Returns custom working dir for root or null if default working dir should be used.
-   * This options make sence only with server-side checkout.
-   * @return see above
-   */
-  @Nullable
-  public File getCustomWorkingDir() {
-    return myCustomWorkingDir;
-  }
-
-  public static boolean isValidRepository(File dir) {
-    // need better way to check that repository copy is ok
-    return dir.isDirectory() && new File(dir, ".hg").isDirectory();
-  }
-
-  private class FakeStreamHandler extends URLStreamHandler {
-     @Override
-     protected URLConnection openConnection(URL u) throws IOException {
-       throw new UnsupportedOperationException();
-     }
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgPathProvider;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.PathUtil;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.*;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Represents Mercurial repository settings
+ */
+public class Settings {
+
+  private final HgPathProvider myHgPathProvider;
+  private String myRepository;
+  private String myHgCommandPath;
+  private File myCustomWorkingDir;
+  private String myUsername;
+  private String myPassword;
+  private String myBranchName;
+  private boolean myUncompressedTransfer = false;
+  private static final String DEFAULT_BRANCH_NAME = "default";
+  private String myCustomClonePath;
+
+  public Settings(@NotNull final HgPathProvider hgPathProvider, @NotNull final VcsRoot vcsRoot) {
+    myHgPathProvider = hgPathProvider;
+    myRepository = vcsRoot.getProperty(Constants.REPOSITORY_PROP);
+    myHgCommandPath = vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP);
+    myBranchName = vcsRoot.getProperty(Constants.BRANCH_NAME_PROP);
+    myCustomClonePath = vcsRoot.getProperty(Constants.SERVER_CLONE_PATH_PROP);
+    myUsername = vcsRoot.getProperty(Constants.USERNAME);
+    myPassword = vcsRoot.getProperty(Constants.PASSWORD);
+    myUncompressedTransfer = "true".equals(vcsRoot.getProperty(Constants.UNCOMPRESSED_TRANSFER));
+  }
+
+  public String getCustomClonePath() {
+    return myCustomClonePath;
+  }
+
+  public String getRepository() {
+    return myRepository;
+  }
+
+  /**
+   * Returns name of the branch to use (returns 'default' if no branch specified)
+   * @return see above
+   */
+  @NotNull
+  public String getBranchName() {
+    return StringUtil.isEmpty(myBranchName) ? DEFAULT_BRANCH_NAME : myBranchName;
+  }
+
+  /**
+   * Returns true if current branch is default branch
+   * @return see above
+   */
+  public boolean isDefaultBranch() {
+    return getBranchName().equals(DEFAULT_BRANCH_NAME);
+  }
+
+  public boolean isUncompressedTransfer() {
+    return myUncompressedTransfer;
+  }
+
+  /**
+   * @return path to hg command taking into account server-wide/agent-wide settings
+   */
+  @NotNull
+  public String getHgCommandPath() {
+    return myHgPathProvider.getHgPath(this);
+  }
+
+  /**
+   * @return path to hg command as it is set in VCS root settings
+   */
+  public String getHgPath() {
+    return myHgCommandPath;
+  }
+
+  public String getUsername() {
+    return myUsername;
+  }
+
+  public String getPassword() {
+    return myPassword;
+  }
+
+  private final static Set<String> AUTH_PROTOS = new HashSet<String>();
+  static {
+    AUTH_PROTOS.add("http");
+    AUTH_PROTOS.add("https");
+    AUTH_PROTOS.add("ssh");
+  }
+
+  /**
+   * Returns URL to use for push command
+   * @return URL to use for push command
+   */
+  public String getRepositoryUrl() {
+    if (isRequireCredentials()) {
+      if (containsCredentials(myRepository)) return myRepository;
+      try {
+        return createURLWithCredentials(myRepository);
+      } catch (MalformedURLException e) {
+        Loggers.VCS.warn("Error while parsing url " + myRepository, e);
+      }
+      return myRepository;
+    } else {
+      return myRepository;
+    }
+  }
+
+  private boolean containsCredentials(final String repository) {
+    try {
+      URL url = new URL(null, repository, new FakeStreamHandler());
+      String userInfo = url.getUserInfo();
+      return userInfo != null && userInfo.contains(":");
+    } catch (MalformedURLException e) {
+      return false;
+    }
+  }
+
+  private String createURLWithCredentials(final String originalUrl) throws MalformedURLException {
+    String userInfo = createUserInfo();
+    if (!"".equals(userInfo)) {
+      URL url = new URL(null, originalUrl, new FakeStreamHandler());
+      return url.getProtocol() + "://"
+              + userInfo + "@"
+              + url.getHost()
+              + (url.getPort() != -1 ? ":" + url.getPort() : "")
+              + url.getFile()
+              + (url.getRef() != null ? url.getRef() : "");
+    } else {
+      return originalUrl;
+    }
+  }
+
+  private boolean isRequireCredentials() {
+    for (String scheme : AUTH_PROTOS) {
+      if (myRepository.startsWith(scheme + ":")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private String createUserInfo() {
+    String userInfo = "";
+    if (!StringUtil.isEmpty(myUsername)) {
+      userInfo += myUsername;
+      if (!StringUtil.isEmpty(myPassword)) {
+        userInfo += ":" + myPassword;
+      }
+    }
+    return getEscapedUserInfo(userInfo);
+  }
+
+  private static String getEscapedUserInfo(String userInfo) {
+    try {
+      URI uri = new URI("http", userInfo, "somewhere.com", 80, "", "", "");
+      String escapedURI = uri.toASCIIString();
+      int from = "http://".length();
+      int to = escapedURI.indexOf("somewhere.com") - 1;
+      String escapedUserInfo = escapedURI.substring(from, to);
+      escapedUserInfo = escapedUserInfo.replaceAll("&", "%26");
+      return escapedUserInfo;
+   } catch (URISyntaxException e) {
+      assert false;
+    }
+    return userInfo;
+  }
+
+  /**
+   * Set custom working dir for vcs root. This option make sence only for server-side checkout
+   * @param customWorkingDir custom working dir
+   */
+  public void setCustomWorkingDir(@NotNull final File customWorkingDir) {
+    myCustomWorkingDir = PathUtil.getCanonicalFile(customWorkingDir);
+  }
+
+  /**
+   * Returns custom working dir for root or null if default working dir should be used.
+   * This options make sence only with server-side checkout.
+   * @return see above
+   */
+  @Nullable
+  public File getCustomWorkingDir() {
+    return myCustomWorkingDir;
+  }
+
+  public static boolean isValidRepository(File dir) {
+    // need better way to check that repository copy is ok
+    return dir.isDirectory() && new File(dir, ".hg").isDirectory();
+  }
+
+  private class FakeStreamHandler extends URLStreamHandler {
+    @Override
+    protected URLConnection openConnection(URL u) throws IOException {
+      throw new UnsupportedOperationException();
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,77 +1,77 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-public class StatusCommand extends BaseCommand {
-  private String myFromId;
-  private String myToId;
-
-  public StatusCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-  }
-
-  public void setFromRevId(final String fromId) {
-    myFromId = fromId;
-  }
-
-  public void setToRevId(final String toId) {
-    myToId = toId;
-  }
-
-  public List<ModifiedFile> execute() throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("status");
-    cli.addParameter("--rev");
-    String from = myFromId;
-    if (from == null) from = "0";
-    String to = myToId;
-    if (to == null) to = "0";
-    cli.addParameter(from + ":" + to);
-    CommandResult res = runCommand(cli);
-    return parseFiles(res.getStdout());
-  }
-
-  public static List<ModifiedFile> parseFiles(final String stdout) {
-    List<ModifiedFile> result = new ArrayList<ModifiedFile>();
-    String[] lines = stdout.split("\n");
-    for (String line: lines) {
-      if (line.length() == 0) continue;
-      char modifier = line.charAt(0);
-      String path = line.substring(2);
-      ModifiedFile.Status status = toStatus(modifier);
-      if (status == ModifiedFile.Status.UNKNOWN) continue;
-      result.add(new ModifiedFile(status, path));
-    }
-    return result;
-  }
-
-  public static ModifiedFile.Status toStatus(final char modifier) {
-    switch (modifier) {
-      case 'A': return ModifiedFile.Status.ADDED;
-      case 'M': return ModifiedFile.Status.MODIFIED;
-      case 'R': return ModifiedFile.Status.REMOVED;
-      default: return ModifiedFile.Status.UNKNOWN;
-    }
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class StatusCommand extends VcsRootCommand {
+  private String myFromId;
+  private String myToId;
+
+  public StatusCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+  }
+
+  public void setFromRevId(final String fromId) {
+    myFromId = fromId;
+  }
+
+  public void setToRevId(final String toId) {
+    myToId = toId;
+  }
+
+  public List<ModifiedFile> execute() throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("status");
+    cli.addParameter("--rev");
+    String from = myFromId;
+    if (from == null) from = "0";
+    String to = myToId;
+    if (to == null) to = "0";
+    cli.addParameter(from + ":" + to);
+    CommandResult res = runCommand(cli);
+    return parseFiles(res.getStdout());
+  }
+
+  public static List<ModifiedFile> parseFiles(final String stdout) {
+    List<ModifiedFile> result = new ArrayList<ModifiedFile>();
+    String[] lines = stdout.split("\n");
+    for (String line: lines) {
+      if (line.length() == 0) continue;
+      char modifier = line.charAt(0);
+      String path = line.substring(2);
+      ModifiedFile.Status status = toStatus(modifier);
+      if (status == ModifiedFile.Status.UNKNOWN) continue;
+      result.add(new ModifiedFile(status, path));
+    }
+    return result;
+  }
+
+  public static ModifiedFile.Status toStatus(final char modifier) {
+    switch (modifier) {
+      case 'A': return ModifiedFile.Status.ADDED;
+      case 'M': return ModifiedFile.Status.MODIFIED;
+      case 'R': return ModifiedFile.Status.REMOVED;
+      default: return ModifiedFile.Status.UNKNOWN;
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,48 +1,48 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-public class TagCommand extends BaseCommand {
-  private String myTag;
-  private String myRevId;
-
-  public TagCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-  }
-
-  public void setTag(@NotNull final String tag) {
-    myTag = tag;
-  }
-
-  public void setRevId(@NotNull final String revId) {
-    myRevId = revId;
-  }
-
-  public void execute() throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("tag");
-    cli.addParameter("-r");
-    cli.addParameter(myRevId);
-    cli.addParameter(myTag);
-    runCommand(cli);
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class TagCommand extends VcsRootCommand {
+  private String myTag;
+  private String myRevId;
+
+  public TagCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+  }
+
+  public void setTag(@NotNull final String tag) {
+    myTag = tag;
+  }
+
+  public void setRevId(@NotNull final String revId) {
+    myRevId = revId;
+  }
+
+  public void execute() throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("tag");
+    cli.addParameter("-r");
+    cli.addParameter(myRevId);
+    cli.addParameter(myTag);
+    runCommand(cli);
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,50 +1,50 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-public class UpdateCommand extends BaseCommand {
-
-  private static final int UPDATE_TIMEOUT_SECONDS = 8 * 3600;//8 hours
-
-  private String myToId;
-
-  public UpdateCommand(@NotNull Settings settings, @NotNull File workingDir) {
-    super(settings, workingDir);
-  }
-
-  public void setToId(final String toId) {
-    myToId = toId;
-  }
-
-  public void execute() throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("update");
-    cli.addParameter("-C");
-    cli.addParameter("-r");
-    if (myToId != null) {
-      cli.addParameter(myToId);
-    } else {
-      cli.addParameter(getSettings().getBranchName());
-    }
-    runCommand(cli, UPDATE_TIMEOUT_SECONDS);
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class UpdateCommand extends VcsRootCommand {
+
+  private static final int UPDATE_TIMEOUT_SECONDS = 8 * 3600;//8 hours
+
+  private String myToId;
+
+  public UpdateCommand(@NotNull Settings settings, @NotNull File workingDir) {
+    super(settings, workingDir);
+  }
+
+  public void setToId(final String toId) {
+    myToId = toId;
+  }
+
+  public void execute() throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("update");
+    cli.addParameter("-C");
+    cli.addParameter("-r");
+    if (myToId != null) {
+      cli.addParameter(myToId);
+    } else {
+      cli.addParameter(getSettings().getBranchName());
+    }
+    runCommand(cli, UPDATE_TIMEOUT_SECONDS);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,48 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author dmitry.neverov
+ */
+public class VcsRootCommand extends BaseCommand {
+
+  private final Settings mySettings;
+
+
+  public VcsRootCommand(@NotNull final Settings settings, @NotNull final File workDir) {
+    super(settings.getHgCommandPath(), workDir);
+    mySettings = settings;
+  }
+
+
+  public Settings getSettings() {
+    return mySettings;
+  }
+
+
+  public Set<String> getPrivateData() {
+    return Collections.singleton(mySettings.getPassword());
+  }
+
+
+  protected CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
+    return CommandUtil.runCommand(cli, getPrivateData());
+  }
+
+
+  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, int executionTimeout) throws VcsException {
+    return CommandUtil.runCommand(cli, executionTimeout, getPrivateData());
+  }
+
+
+  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, boolean checkFailure) throws VcsException {
+    return CommandUtil.runCommand(cli, getPrivateData(), checkFailure);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,33 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersion;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author dmitry.neverov
+ */
+public class VersionCommand extends BaseCommand {
+
+  public VersionCommand(@NotNull final Settings settings, @NotNull File workingDir) {
+    super(settings.getHgCommandPath(), workingDir);
+  }
+
+
+  public VersionCommand(@NotNull final String hgPath, @NotNull File workingDir) {
+    super(hgPath, workingDir);
+  }
+
+
+  public HgVersion execute() throws VcsException {
+    GeneralCommandLine cli = createCommandLine();
+    cli.addParameter("version");
+    cli.addParameter("--quiet");
+    CommandResult result = runCommand(cli);
+    return HgVersion.parse(result.getStdout());
+  }
+
+}
--- a/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Wed Jan 11 13:22:42 2012 +0400
@@ -1,7 +1,9 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
-
-<beans default-autowire="constructor">
-  <bean id="mercurialServer" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport" />
-  <bean id="config" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigImpl" />
-</beans>
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans default-autowire="constructor">
+  <bean id="mercurialServer" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport" />
+  <bean id="config" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigImpl" />
+  <bean id="commandFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommandFactory" />
+  <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgPathProvider"/>
+</beans>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesCommand.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,17 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSet;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author dmitry.neverov
+ */
+public interface CollectChangesCommand {
+
+  @NotNull
+  public List<ChangeSet> execute(@NotNull String fromCommit, @NotNull String toCommit) throws VcsException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesNoRevsets.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,83 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.util.Pair;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
+import jetbrains.buildServer.util.graph.DAG;
+import jetbrains.buildServer.util.graph.DAGIterator;
+import jetbrains.buildServer.util.graph.DAGs;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author dmitry.neverov
+ */
+public class CollectChangesNoRevsets implements CollectChangesCommand {
+
+  private final Settings mySettings;
+  private final File myWorkingDir;
+  private final File myTemplate;
+
+  public CollectChangesNoRevsets(@NotNull final Settings settings,
+                                 @NotNull final File workingDir,
+                                 @NotNull final File template) {
+    mySettings = settings;
+    myWorkingDir = workingDir;
+    myTemplate = template;
+  }
+
+
+  @NotNull
+  public List<ChangeSet> execute(@NotNull final String fromCommit, @NotNull final String toCommit) throws VcsException {
+    List<ChangeSet> csets = getRevisionsReachableFrom(toCommit);
+    Map<String, ChangeSet> csetsMap = getChangesetMap(csets);
+    if (csetsMap.containsKey(fromCommit)) {
+      DAG<String> dag = DAGs.createFromEdges(getEdges(csets));
+      DAGIterator<String> iter = dag.iterator(toCommit);
+      iter.markUninteresting(fromCommit);
+      List<ChangeSet> result = new ArrayList<ChangeSet>();
+      while (iter.hasNext()) {
+        String commit = iter.next();
+        ChangeSet cset = csetsMap.get(commit);
+        if (cset == null)
+          throw new IllegalStateException("Cannot find cset for id " + commit + ", csets map: " + csetsMap);
+        result.add(cset);
+      }
+      Collections.reverse(result);
+      return result;
+    } else {
+      return Collections.emptyList();
+    }
+  }
+
+
+  private Map<String, ChangeSet> getChangesetMap(@NotNull final List<ChangeSet> csets) {
+    Map<String, ChangeSet> result = new HashMap<String, ChangeSet>();
+    for (ChangeSet cset : csets) {
+      result.put(cset.getId(), cset);
+    }
+    return result;
+  }
+
+
+  private List<ChangeSet> getRevisionsReachableFrom(@NotNull final String revision) throws VcsException {
+    LogCommand log = new LogCommand(mySettings, myWorkingDir, myTemplate);
+    log.setFromRevId(new ChangeSetRevision(revision).getId());
+    log.showCommitsFromAllBranches();
+    log.setToRevId("0");
+    return log.execute();
+  }
+
+
+  private List<Pair<String, String>> getEdges(List<ChangeSet> csets) {
+    List<Pair<String, String>> result = new ArrayList<Pair<String, String>>();
+    for (ChangeSet cset : csets) {
+      for (ChangeSetRevision parent : cset.getParents()) {
+        result.add(Pair.create(cset.getId(), parent.getId()));
+      }
+    }
+    return result;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesWithRevsets.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,34 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSet;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * @author dmitry.neverov
+ */
+public class CollectChangesWithRevsets implements CollectChangesCommand {
+
+  private final Settings mySettings;
+  private final File myWorkingDir;
+  private final File myTemplate;
+
+  public CollectChangesWithRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final File template) {
+    mySettings = settings;
+    myWorkingDir = workingDir;
+    myTemplate = template;
+  }
+
+  @NotNull
+  public List<ChangeSet> execute(@NotNull final String fromCommit, @NotNull final String toCommit) throws VcsException {
+    LogCommand log = new LogCommand(mySettings, myWorkingDir, myTemplate);
+    log.showCommitsFromAllBranches();
+    log.setRevsets("ancestors(" + toCommit + ") - ancestors(" + fromCommit + ")");
+    return log.execute();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandFactory.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,70 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.MergeBaseCommand;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommand;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author dmitry.neverov
+ */
+public final class CommandFactory {
+
+  //hg version which supports revsets
+  private final static HgVersion REVSET_HG_VERSION = new HgVersion(1, 7, 0);
+  private final static String LOG_TEMPLATE_NAME = "log.template";
+
+  private final File myDefaultWorkingDir;
+  private final File myLogTemplate;
+
+
+  public CommandFactory(@NotNull final ServerPluginConfig config) throws IOException {
+    myDefaultWorkingDir = config.getCachesDir();
+    myLogTemplate = createLogTemplate(config.getPluginDataDir());
+  }
+
+
+  @NotNull
+  public MergeBaseCommand createMergeBase(@NotNull Settings settings, @NotNull File workingDir) throws VcsException {
+    HgVersion hgVersion = getHgVersion(settings);
+    if (hgVersion.isEqualsOrGreaterThan(REVSET_HG_VERSION))
+      return new MergeBaseWithRevsets(settings, workingDir, this);
+    else
+      return new MergeBaseNoRevsets(settings, workingDir, this);
+  }
+
+
+  @NotNull
+  public LogCommand createLog(@NotNull final Settings settings, @NotNull final File workingDir) {
+    return new LogCommand(settings, workingDir, myLogTemplate);
+  }
+
+  @NotNull
+  public CollectChangesCommand getCollectChangesCommand(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
+    HgVersion hgVersion = getHgVersion(settings);
+    if (hgVersion.isEqualsOrGreaterThan(REVSET_HG_VERSION)) {
+      return new CollectChangesWithRevsets(settings, workingDir, myLogTemplate);
+    } else {
+      return new CollectChangesNoRevsets(settings, workingDir, myLogTemplate);
+    }
+  }
+
+  private File createLogTemplate(@NotNull final File templateFileDir) throws IOException {
+    File template = new File(templateFileDir, LOG_TEMPLATE_NAME);
+    if (!template.exists()) {
+      FileUtil.copyResource(CommandFactory.class, "/buildServerResources/log.template", template);
+    }
+    return template;
+  }
+
+  private HgVersion getHgVersion(@NotNull final Settings settings) throws VcsException {
+    VersionCommand versionCommand = new VersionCommand(settings, myDefaultWorkingDir);
+    return versionCommand.execute();
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Jan 11 13:22:42 2012 +0400
@@ -55,28 +55,30 @@
  * <p>Working copy of repository is created in the $TEAMCITY_DATA_PATH/system/caches/hg_&lt;hash code> folder.
  * <p>Personal builds (remote runs) are not yet supported, they require corresponding functionality from the IDE.
  */
-public class MercurialVcsSupport extends ServerVcsSupport implements LabelingSupport, VcsFileContentProvider, BranchSupport {
-
-  private final String LOG_TEMPLATE_NAME = "log.template";
+public class MercurialVcsSupport extends ServerVcsSupport implements LabelingSupport, VcsFileContentProvider, BranchSupport,
+        CollectChangesBetweenRoots {
   private ConcurrentMap<String, Lock> myWorkDirLocks= new ConcurrentHashMap<String, Lock>();
   private VcsManager myVcsManager;
   private File myDefaultWorkFolderParent;
   private MirrorManager myMirrorManager;
   private final ServerPluginConfig myConfig;
-  private File myLogTemplate;
+  private final HgPathProvider myHgPathProvider;
+  private final CommandFactory myCommandFactory;
   private final FileFilter myIgnoreDotHgFilter = new IgnoreDotHgFilter();
   private final FileFilter myAcceptAllFilter = new AcceptAllFilter();
 
   public MercurialVcsSupport(@NotNull final VcsManager vcsManager,
-                             @NotNull final ServerPaths paths,
                              @NotNull final SBuildServer server,
                              @NotNull final EventDispatcher<BuildServerListener> dispatcher,
-                             @NotNull final ServerPluginConfig config) throws IOException {
-    myLogTemplate = createLogTemplate(paths.getPluginDataDirectory());
+                             @NotNull final ServerPluginConfig config,
+                             @NotNull final HgPathProvider hgPathProvider,
+                             @NotNull final CommandFactory commandFactory) {
     myVcsManager = vcsManager;
-    myDefaultWorkFolderParent = new File(paths.getCachesDir(), "mercurial");
+    myConfig = config;
+    myDefaultWorkFolderParent = myConfig.getCachesDir();
     myMirrorManager = new MirrorManager(myDefaultWorkFolderParent);
-    myConfig = config;
+    myHgPathProvider = hgPathProvider;
+    myCommandFactory = commandFactory;
     dispatcher.addListener(new BuildServerAdapter() {
       @Override
       public void cleanupFinished() {
@@ -98,6 +100,15 @@
         });
       }
     });
+    logUsedHg();
+  }
+
+  private void logUsedHg() {
+    String hgPath = myConfig.getHgPath();
+    if (hgPath != null)
+      Loggers.VCS.info("Use server-wide hg path " + hgPath + ", path in the VCS root settings will be ignored");
+    else
+      Loggers.VCS.info("Server-wide hg path is not set, will use path from the VCS root settings");
   }
 
   private void deleteWithLocking(Collection<File> filesForDelete) {
@@ -111,21 +122,6 @@
     }
   }
 
-  private Collection<ModifiedFile> computeModifiedFilesForMergeCommit(final Settings settings, final ChangeSet cur) throws VcsException {
-    File workingDir = getWorkingDir(settings);
-    ChangedFilesCommand cfc = new ChangedFilesCommand(settings, workingDir);
-    cfc.setRevId(cur.getId());
-    return cfc.execute();
-  }
-
-  private File createLogTemplate(@NotNull final File templateFileDir) throws IOException {
-    File template = new File(templateFileDir, LOG_TEMPLATE_NAME);
-    if (!template.exists()) {
-      FileUtil.copyResource(MercurialVcsSupport.class, "/buildServerResources/log.template", template);
-    }
-    return template;
-  }
-
   private List<VcsChange> toVcsChanges(final List<ModifiedFile> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) {
     List<VcsChange> files = new ArrayList<VcsChange>();
     for (ModifiedFile mf: modifiedFiles) {
@@ -194,35 +190,6 @@
     return new byte[0];
   }
 
-  /**
-   * Returns file's content or empty string if it doesn't exist.
-   * @param path path of the file of interest
-   * @param settings root settings
-   * @param cset repository cset (should be present in the repository)
-   * @return see above
-   */
-  @NotNull
-  private String getFileContent(@NotNull final String path, @NotNull final Settings settings, @NotNull final ChangeSet cset) throws VcsException {
-    File dir = getWorkingDir(settings);
-    CatCommand cat = new CatCommand(settings, dir);
-    cat.setRevId(cset.getId());
-    cat.setLogErrorsInDebug(true);
-    File parentDir = null;
-    try {
-      parentDir = cat.execute(Collections.singletonList(path));
-      File f = new File(parentDir, path);
-      if (f.isFile())
-        return FileUtil.readText(f);
-      else
-        return "";
-    } catch (Exception e) {
-      return "";
-    } finally {
-      if (parentDir != null)
-        deleteTmpDir(parentDir);
-    }
-  }
-
   @NotNull
   public String getName() {
     return Constants.VCS_NAME;
@@ -420,6 +387,35 @@
     return !isEmptyOrSpaces(hgsub);
   }
 
+  /**
+   * Returns file's content or empty string if it doesn't exist.
+   * @param path path of the file of interest
+   * @param settings root settings
+   * @param cset repository cset (should be present in the repository)
+   * @return see above
+   */
+  @NotNull
+  private String getFileContent(@NotNull final String path, @NotNull final Settings settings, @NotNull final ChangeSet cset) throws VcsException {
+    File dir = getWorkingDir(settings);
+    CatCommand cat = new CatCommand(settings, dir);
+    cat.setRevId(cset.getId());
+    File parentDir = null;
+    try {
+      parentDir = cat.execute(Collections.singletonList(path), false);
+      File f = new File(parentDir, path);
+      if (f.isFile())
+        return FileUtil.readText(f);
+      else
+        return "";
+    } catch (Exception e) {
+      return "";
+    } finally {
+      if (parentDir != null)
+        deleteTmpDir(parentDir);
+    }
+  }
+
+
   private void buildPatchFromDirectory(final PatchBuilder builder, final File repRoot, final CheckoutRules checkoutRules, @NotNull final FileFilter filter) throws IOException {
     buildPatchFromDirectory(repRoot, builder, repRoot, checkoutRules, filter);
   }
@@ -494,6 +490,7 @@
   /**
    * Check if changeSet is present in local repository.
    * @param settings root settings
+   * @param workDir where to run a command
    * @param cset change set of interest
    * @return true if changeSet is present in local repository
    */
@@ -525,7 +522,12 @@
   }
 
   @NotNull
-  public Map<String, String> getBranchesRevisions(@NotNull VcsRoot root) throws VcsException {
+  public RepositoryState getCurrentState(@NotNull VcsRoot root) throws VcsException {
+    return RepositoryStateFactory.createRepositoryState(getBranchesRevisions(root));
+  }
+
+  @NotNull
+  private Map<String, String> getBranchesRevisions(@NotNull VcsRoot root) throws VcsException {
     Settings settings = createSettings(root);
     syncRepository(settings);
     File workingDir = getWorkingDir(settings);
@@ -551,12 +553,15 @@
     VcsRoot branchRoot = createBranchRoot(root, branchName);
     String baseVersion = getCurrentVersion(root);
     String branchVersion = getCurrentVersion(branchRoot);
-    String branchPoint = getBranchPoint(settings, baseVersion, branchVersion);
+    String mergeBase = getMergeBase(settings, baseVersion, branchVersion);
 
-    LogCommand lc = new LogCommand(settings, getWorkingDir(settings), myLogTemplate);
-    lc.setFromRevId(new ChangeSetRevision(branchPoint).getId());
+    if (mergeBase == null)
+      return null;
+
+    LogCommand lc = myCommandFactory.createLog(settings, getWorkingDir(settings));
+    lc.setFromRevId(new ChangeSetRevision(mergeBase).getId());
     lc.setToRevId(new ChangeSetRevision(branchVersion).getId());
-    lc.setBranchName(null);//do not limit output to particular branch, return all commits
+    lc.showCommitsFromAllBranches();
     List<ChangeSet> changeSets = lc.execute();
     if (changeSets.size() > 1) {//when branch points to the commit in original branch we get 1 cset
       String branchId = changeSets.get(1).getId();
@@ -578,76 +583,89 @@
   public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot, @NotNull String fromRootRevision,
                                                @NotNull VcsRoot toRoot, @Nullable String toRootRevision,
                                                @NotNull CheckoutRules checkoutRules) throws VcsException {
-    //we get all branches while clone, if vcs roots are related it is doesn't matter in which one search for branch point
-    Settings settings = createSettings(fromRoot);
+    Settings settings = createSettings(toRoot);
     syncRepository(settings);
-    String branchPoint = getBranchPoint(settings, fromRootRevision, toRootRevision);
-    return ((CollectChangesByCheckoutRules) getCollectChangesPolicy()).collectChanges(toRoot, branchPoint, toRootRevision, checkoutRules);
+    String toRevision = toRootRevision != null ? toRootRevision : getCurrentVersion(toRoot);
+    String mergeBase = getMergeBase(settings, fromRootRevision, toRevision);
+    if (mergeBase == null)
+      return Collections.emptyList();
+    return collectChanges(toRoot, mergeBase, toRootRevision, checkoutRules);
   }
 
 
-  private String getBranchPoint(@NotNull Settings settings, String branchOneRev, String branchTwoRev) throws VcsException {
-    if (branchOneRev.equals(branchTwoRev))
-      return branchOneRev;
-    File workingDir = getWorkingDir(settings);
-    LogCommand lc = new LogCommand(settings, workingDir, myLogTemplate);
-    lc.setFromRevId(new ChangeSetRevision(branchOneRev).getId());
-    lc.setToRevId(new ChangeSetRevision(branchTwoRev).getId());
-    lc.setLimit(1);
-    List<ChangeSet> changeSets = lc.execute();
-    ChangeSet cs = changeSets.get(0);
-    if (cs.isInitial()) {
-      return cs.getId();
-    } else {
-      return cs.getParents().get(0).getId();
-    }
+  @Nullable
+  private String getMergeBase(@NotNull Settings settings, @NotNull String revision1, @NotNull String revision2) throws VcsException {
+    String result = myCommandFactory.createMergeBase(settings, getWorkingDir(settings)).execute(revision1, revision2);
+    if (result == null)
+      result = getMinusNthCommit(settings, 10);
+    return result;
   }
 
+
+  @Nullable
+  private String getMinusNthCommit(@NotNull Settings settings, int n) throws VcsException {
+    LogCommand log = myCommandFactory.createLog(settings, getWorkingDir(settings));
+    log.setToRevId(settings.getBranchName());
+    if (n > 0)
+      log.setLimit(n);
+    List<ChangeSet> changeSets = log.execute();
+    if (changeSets.isEmpty())
+      return null;
+    return changeSets.get(0).getId();
+  }
+
+
   @NotNull
   public CollectChangesPolicy getCollectChangesPolicy() {
-    return new CollectChangesByCheckoutRules() {
-      @NotNull
-      public List<ModificationData> collectChanges(@NotNull VcsRoot root, @NotNull String fromVersion, @Nullable String currentVersion, @NotNull CheckoutRules checkoutRules) throws VcsException {
-        Settings settings = createSettings(root);
-        syncRepository(settings);
-
-        // first obtain changes between specified versions
-        List<ModificationData> result = new ArrayList<ModificationData>();
-        if (currentVersion == null)
-          return result;
-
-        File workingDir = getWorkingDir(settings);
-        LogCommand lc = new LogCommand(settings, workingDir, myLogTemplate);
-        String fromId = new ChangeSetRevision(fromVersion).getId();
-        lc.setFromRevId(fromId);
-        lc.setToRevId(new ChangeSetRevision(currentVersion).getId());
-        List<ChangeSet> changeSets = lc.execute();
-        if (changeSets.isEmpty()) {
-          return result;
-        }
+    return this;
+  }
 
-        ChangeSet prev = new ChangeSet(fromVersion);
-        for (ChangeSet cur : changeSets) {
-          if (cur.getId().equals(fromId))
-            continue; // skip already reported changeset
+  public List<ModificationData> collectChanges(@NotNull VcsRoot root, @NotNull String fromVersion, @Nullable String currentVersion, @NotNull CheckoutRules checkoutRules) throws VcsException {
+    Settings settings = createSettings(root);
+    syncRepository(settings);
+    List<ModificationData> result = new ArrayList<ModificationData>();
+    for (ChangeSet cset : getChangesets(settings, fromVersion, currentVersion)) {
+      result.add(createModificationData(cset, root, checkoutRules));
+    }
+    return result;
+  }
 
-          boolean merge = cur.getParents().size() > 1;
-          List<ModifiedFile> modifiedFiles = cur.getModifiedFiles();
-          List<VcsChange> files = toVcsChanges(modifiedFiles, prev.getFullVersion(), cur.getFullVersion(), checkoutRules);
-          if (files.isEmpty() && !merge)
-            continue;
-          ModificationData md = new ModificationData(cur.getTimestamp(), files, cur.getDescription(), cur.getUser(), root, cur.getFullVersion(), cur.getId());
-          if (merge)
-            md.setCanBeIgnored(false);
-          result.add(md);
-          prev = cur;
-        }
 
-        return result;
-      }
-    };
+  private ModificationData createModificationData(@NotNull final ChangeSet cset, @NotNull final VcsRoot root, @NotNull final CheckoutRules checkoutRules) {
+    List<ChangeSetRevision> parents = cset.getParents();
+    if (parents.isEmpty())
+      throw new IllegalStateException("Commit " + cset.getId() + " has no parents");
+    List<VcsChange> files = toVcsChanges(cset.getModifiedFiles(), parents.get(0).getFullVersion(), cset.getFullVersion(), checkoutRules);
+    final ModificationData result = new ModificationData(cset.getTimestamp(), files, cset.getDescription(), cset.getUser(), root, cset.getFullVersion(), cset.getId());
+    for (ChangeSetRevision parent : parents) {
+      result.addParentRevision(parent.getFullVersion());
+    }
+    if (result.getParentRevisions().size() > 1)
+      result.setCanBeIgnored(false);
+    return result;
   }
 
+
+  @NotNull
+  private List<ChangeSet> getChangesets(@NotNull final Settings settings, @NotNull final String fromVersion, @Nullable final String toVersion) throws VcsException {
+    if (toVersion == null)
+      return Collections.emptyList();
+    String fromCommit = new ChangeSetRevision(fromVersion).getId();
+    String toCommit = new ChangeSetRevision(toVersion).getId();
+    File workingDir = getWorkingDir(settings);
+    CollectChangesCommand log = myCommandFactory.getCollectChangesCommand(settings, workingDir);
+    List<ChangeSet> changesets = log.execute(fromCommit, toCommit);
+    Iterator<ChangeSet> iter = changesets.iterator();
+    while (iter.hasNext()) {
+      ChangeSet cset = iter.next();
+      if (cset.getId().equals(fromCommit))
+        iter.remove();//skip already reported changes
+    }
+    return changesets;
+  }
+
+
+
   @NotNull
   public BuildPatchPolicy getBuildPatchPolicy() {
     return new BuildPatchByCheckoutRules() {
@@ -761,7 +779,7 @@
   }
 
   private Settings createSettings(final VcsRoot root) throws VcsException {
-    Settings settings = new Settings(root);
+    Settings settings = new Settings(myHgPathProvider, root);
     String customClonePath = settings.getCustomClonePath();
     if (!StringUtil.isEmptyOrSpaces(customClonePath) && !myDefaultWorkFolderParent.equals(new File(customClonePath).getAbsoluteFile())) {
       File parentDir = new File(customClonePath);
@@ -812,6 +830,11 @@
   }
 
 
+  @Override
+  public boolean isDAGBasedVcs() {
+    return true;
+  }
+
   private static class IgnoreDotHgFilter implements FileFilter {
     public boolean accept(final File file) {
       return !(file.isDirectory() && ".hg".equals(file.getName()));
@@ -823,4 +846,14 @@
       return true;
     }
   }
+
+  @NotNull
+  public String getBranchName(@NotNull final VcsRoot root) {
+    try {
+      Settings s = createSettings(root);
+      return s.getBranchName();
+    } catch (VcsException e) {
+      return "default";
+    }
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseNoRevsets.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,64 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.util.Pair;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
+import jetbrains.buildServer.util.graph.DAG;
+import jetbrains.buildServer.util.graph.DAGs;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * Implementation of merge-base for hg versions which don't have revsets
+ * @author dmitry.neverov
+ */
+public final class MergeBaseNoRevsets implements MergeBaseCommand {
+
+  private final Settings mySettings;
+  private final File myWorkingDir;
+  private final CommandFactory myCommandFactory;
+
+  public MergeBaseNoRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final CommandFactory commandFactory) {
+    mySettings = settings;
+    myWorkingDir = workingDir;
+    myCommandFactory = commandFactory;
+  }
+
+
+  @Nullable
+  public String execute(@NotNull final String revision1, @NotNull final String revision2) {
+    if (revision1.equals(revision2))
+      return revision1;
+    try {
+      List<Pair<String, String>> edges = new ArrayList<Pair<String, String>>();
+      fillEdges(edges, getRevisionsReachableFrom(revision1));
+      fillEdges(edges, getRevisionsReachableFrom(revision2));
+      DAG<String> dag = DAGs.createFromEdges(edges);
+      List<String> commonAncestors = dag.getCommonAncestors(new ChangeSetRevision(revision1).getId(), new ChangeSetRevision(revision2).getId());
+      return commonAncestors.isEmpty() ? null : commonAncestors.get(0);
+    } catch (VcsException e) {
+      return null;
+    }
+  }
+
+
+  private List<ChangeSet> getRevisionsReachableFrom(@NotNull final String revision) throws VcsException {
+    LogCommand log = myCommandFactory.createLog(mySettings, myWorkingDir);
+    log.setFromRevId(new ChangeSetRevision(revision).getId());
+    log.showCommitsFromAllBranches();
+    log.setToRevId("0");
+    return log.execute();
+  }
+
+
+  private void fillEdges(List<Pair<String, String>> edges, List<ChangeSet> csets) {
+    for (ChangeSet cset : csets) {
+      for (ChangeSetRevision parent : cset.getParents()) {
+        edges.add(Pair.create(cset.getId(), parent.getId()));
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseWithRevsets.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,38 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Implementation of merge-base using hg revsets
+ * @author dmitry.neverov
+ */
+public final class MergeBaseWithRevsets implements MergeBaseCommand {
+
+  private final Settings mySettings;
+  private final File myWorkingDir;
+  private final CommandFactory myCommandFactory;
+
+  public MergeBaseWithRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final CommandFactory commandFactory) {
+    mySettings = settings;
+    myWorkingDir = workingDir;
+    myCommandFactory = commandFactory;
+  }
+
+  public String execute(@NotNull final String revision1, @NotNull final String revision2) throws VcsException {
+    try {
+      LogCommand log = myCommandFactory.createLog(mySettings, myWorkingDir);
+      log.setRevsets("ancestor(" + new ChangeSetRevision(revision1).getId() + ", " + new ChangeSetRevision(revision2).getId() + ")");
+      log.showCommitsFromAllBranches();
+      log.setCalculateParents(false);
+      List<ChangeSet> csets = log.execute();
+      return csets.isEmpty() ? null : csets.get(0).getId();
+    } catch (VcsException e) {
+      return null;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProvider.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,39 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public class ServerHgPathProvider implements HgPathProvider {
+
+  private final ServerPluginConfig myConfig;
+
+
+  public ServerHgPathProvider(@NotNull final ServerPluginConfig config) {
+    myConfig = config;
+  }
+
+
+  public String getHgPath(@NotNull final Settings settings) {
+    String serverWideHgPath = myConfig.getHgPath();
+    if (serverWideHgPath != null) {
+      return serverWideHgPath;
+    } else {
+      String pathFromRoot = settings.getHgPath();
+      if (pathFromRoot.equals(unresolvedAgentHgPath())) {
+        //try to use hg from the PATH:
+        return "hg";
+      } else {
+        return pathFromRoot;
+      }
+    }
+  }
+
+
+  private String unresolvedAgentHgPath() {
+    //Use hard-coded value here in order to not add dependency on agent part of plugin:
+    return "%teamcity.hg.agent.path%";
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,10 +1,22 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+
 /**
  * @author dmitry.neverov
  */
 public interface ServerPluginConfig extends PluginConfig {
 
+  @Nullable
+  String getHgPath();
+
   public boolean isUsePullProtocol();
 
+  @NotNull
+  public File getPluginDataDir();
+
+  int getPullTimeout();
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,6 +1,11 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.serverSide.ServerPaths;
 import jetbrains.buildServer.serverSide.TeamCityProperties;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
 
 /**
  * @author dmitry.neverov
@@ -8,12 +13,35 @@
 public class ServerPluginConfigImpl implements ServerPluginConfig {
 
   private static final String PULL_TIMEOUT_SECONDS = "teamcity.hg.pull.timeout.seconds";
-  private final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600;
+  public static final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600;
+
+  private final File myCachesDir;
+  private final File myPluginDataDir;
+
+  public ServerPluginConfigImpl(@NotNull final ServerPaths paths) {
+    myCachesDir = new File(paths.getCachesDir(), "mercurial");
+    myPluginDataDir = paths.getPluginDataDirectory();
+  }
 
   public boolean isUsePullProtocol() {
     return TeamCityProperties.getBooleanOrTrue("teamcity.hg.use.pull.protocol");
   }
 
+  @Nullable
+  public String getHgPath() {
+    return TeamCityProperties.getPropertyOrNull("teamcity.hg.server.path");
+  }
+
+  @NotNull
+  public File getCachesDir() {
+    return myCachesDir;
+  }
+
+  @NotNull
+  public File getPluginDataDir() {
+    return myPluginDataDir;
+  }
+
   public int getPullTimeout() {
     int timeout = TeamCityProperties.getInteger(PULL_TIMEOUT_SECONDS, DEFAULT_PULL_TIMEOUT_SECONDS);
     return timeout > 0 ? timeout : DEFAULT_PULL_TIMEOUT_SECONDS;
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,225 +1,240 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.agent.AgentRunningBuild;
-import jetbrains.buildServer.agent.BuildAgentConfiguration;
-import jetbrains.buildServer.agent.BuildProgressLogger;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.vcs.CheckoutRules;
-import jetbrains.buildServer.vcs.IncludeRule;
-import jetbrains.buildServer.vcs.VcsException;
-import jetbrains.buildServer.vcs.VcsRoot;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author Pavel.Sher
- *         Date: 30.07.2008
- */
-@Test
-public class AgentSideCheckoutTest extends BaseMercurialTestCase {
-  private MercurialAgentSideVcsSupport myVcsSupport;
-  private File myWorkDir;
-  private File myMirrorsRootDir;
-  private Mockery myContext;
-  private BuildProgressLogger myLogger;
-  private int myBuildCounter = 0;
-
-  @Override
-  @BeforeMethod
-  protected void setUp() throws Exception {
-    super.setUp();
-
-    myContext = new Mockery();
-
-    myMirrorsRootDir = myTempFiles.createTempDir();
-
-    final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class);
-    myContext.checking(new Expectations() {{
-      allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myMirrorsRootDir));
-    }});
-
-    myVcsSupport = new MercurialAgentSideVcsSupport(agentConfig);
-
-    myLogger = myContext.mock(BuildProgressLogger.class);
-    myContext.checking(new Expectations() {{
-      allowing(myLogger).message(with(any(String.class)));
-    }});
-
-    myWorkDir = myTempFiles.createTempDir();
-
-  }
-
-  public void checkout_on_agent() throws IOException, VcsException {
-    testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null));
-  }
-
-  public void checkout_on_agent_include_rule_with_mapping() throws IOException, VcsException {
-    testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule("+:.", "subdir", null));
-  }
-
-  private void testUpdate(final VcsRoot vcsRoot, String version, String expected, final IncludeRule includeRule) throws VcsException, IOException {
-    File workDir = doUpdate(vcsRoot, version, includeRule);
-
-    checkWorkingDir(expected, workDir);
-  }
-
-  private void checkWorkingDir(final String expected, final File workDir) throws IOException {
-    FileUtil.delete(new File(workDir, ".hg"));
-    checkDirectoriesAreEqual(new File(getTestDataPath(), expected), workDir);
-  }
-
-  private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule) throws VcsException {
-    return doUpdate(vcsRoot, version, includeRule, false);
-  }
-
-  private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule, boolean useLocalMirrors) throws VcsException {
-    File actualWorkDir = new File(myWorkDir, includeRule.getTo());
-    final Map<String, String> sharedConfigParameters = new HashMap<String, String>();
-    sharedConfigParameters.put("teamcity.hg.use.local.mirrors", String.valueOf(useLocalMirrors));
-    final AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++);
-    myContext.checking(new Expectations() {{
-      allowing(build).getBuildLogger(); will(returnValue(myLogger));
-      allowing(build).getSharedConfigParameters(); will(returnValue(sharedConfigParameters));
-    }});
-    myVcsSupport.getUpdater(vcsRoot, new CheckoutRules(""), version, myWorkDir, build, false).process(includeRule, actualWorkDir);
-
-    File hgDir = new File(actualWorkDir, ".hg");
-    assertTrue(hgDir.isDirectory());
-    return actualWorkDir;
-  }
-
-  public void checkout_on_agent_from_branch() throws IOException, VcsException {
-    testUpdate(createVcsRoot(simpleRepo(), "test_branch"), "7:376dcf05cd2a", "patch3/after", new IncludeRule(".", ".", null));
-  }
-
-  public void update_on_agent() throws IOException, VcsException {
-    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
-    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", ".", null));
-    File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null));
-
-    checkWorkingDir("patch1/after", workDir);
-  }
-
-  public void update_on_agent_with_include_rule() throws IOException, VcsException {
-    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
-    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null));
-    File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", "subdir", null));
-
-    checkWorkingDir("patch1/after", workDir);
-  }
-
-  public void update_on_agent_from_branch() throws IOException, VcsException {
-    VcsRoot vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
-    doUpdate(vcsRoot, "7:376dcf05cd2a", new IncludeRule(".", ".", null));
-    File workDir = doUpdate(vcsRoot, "8:04c3ae4c6312", new IncludeRule(".", ".", null));
-
-    checkWorkingDir("patch4/after", workDir);
-  }
-
-  public void by_default_local_mirror_not_created() throws IOException, VcsException {
-    List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
-    assertTrue(mirrors.isEmpty());
-    VcsRoot root = createVcsRoot(simpleRepo());
-    doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
-    mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
-    //though some dirs are created - they are empty => there were no clones into local mirrors
-    for (File mirror : mirrors) {
-      assertTrue(FileUtil.getSubDirectories(mirror).isEmpty());
-    }
-  }
-
-  public void local_mirror_is_created() throws IOException, VcsException {
-    List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
-    assertTrue(mirrors.isEmpty());
-    VcsRoot root = createVcsRoot(simpleRepo());
-    doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
-    mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
-    assertEquals(1, mirrors.size());
-    File mirror = mirrors.get(0);
-    File dotHg = new File(mirror, ".hg");
-    assertTrue(dotHg.exists());
-    File hgrc = new File(dotHg, "hgrc");
-    String hgrcContent = FileUtil.readText(hgrc);
-    assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));
-  }
-
-  public void new_repository_is_cloned_from_local_mirror() throws IOException, VcsException {
-    VcsRoot root = createVcsRoot(simpleRepo());
-    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
-    File mirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
-    File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
-    String hgrcContent = FileUtil.readText(hgrc);
-    assertTrue(hgrcContent.contains("default = " + mirrorDir.getCanonicalPath()));
-  }
-
-  public void repository_cloned_from_remote_start_cloning_from_local_mirror() throws IOException, VcsException {
-    VcsRoot root = createVcsRoot(simpleRepo());
-    //clone from remote repository
-    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
-    String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc"));
-
-    File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
-    File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
-    String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc"));
-    assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed
-    assertTrue(hgrcContent2.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror
-  }
-
-  public void repository_cloned_from_local_mirror_start_cloning_from_remote() throws IOException, VcsException {
-    VcsRoot root = createVcsRoot(simpleRepo());
-    //clone from remote repository
-    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
-    String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc"));
-    File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
-    assertTrue(hgrcContent.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror
-
-    File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
-    String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc"));
-    assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed
-    assertTrue(hgrcContent2.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));//now it clones from remote
-  }
-
-  /**
-   * TW-15984
-   */
-  public void should_be_able_to_clone_into_non_empty_dir() throws IOException, VcsException {
-    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
-    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null));
-    doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null));
-  }
-
-  public void cloned_repo_should_contains_default_parameter_in_hgrc() throws VcsException, IOException {
-    VcsRoot root = createVcsRoot(simpleRepo());
-    File workingDir = doUpdate(root, "4:b06a290a363b", new IncludeRule(".", ".", null));
-    File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
-    String hgrcContent = FileUtil.readText(hgrc);
-    assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));
-  }
-
-  protected String getTestDataPath() {
-    return "mercurial-tests/testData";
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
+import jetbrains.buildServer.agent.BuildProgressLogger;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.IncludeRule;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Pavel.Sher
+ *         Date: 30.07.2008
+ */
+@Test
+public class AgentSideCheckoutTest extends BaseMercurialTestCase {
+
+  final static String HG_PATH_REFERENCE = "%" + HgDetector.AGENT_HG_PATH_PROPERTY + "%";
+  private MercurialAgentSideVcsSupport myVcsSupport;
+  private File myWorkDir;
+  private File myMirrorsRootDir;
+  private Mockery myContext;
+  private BuildProgressLogger myLogger;
+  private int myBuildCounter = 0;
+
+  @Override
+  @BeforeMethod
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    myContext = new Mockery();
+
+    myMirrorsRootDir = myTempFiles.createTempDir();
+
+    final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class);
+    myContext.checking(new Expectations() {{
+      allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myMirrorsRootDir));
+      allowing(agentConfig).getTempDirectory(); will(returnValue(myTempFiles.createTempDir()));
+      allowing(agentConfig).getParametersResolver(); will(returnValue(new HgPathResolver()));
+    }});
+
+    myVcsSupport = new MercurialAgentSideVcsSupport(new AgentPluginConfigImpl(agentConfig), new AgentHgPathProvider(agentConfig));
+
+    myLogger = myContext.mock(BuildProgressLogger.class);
+    myContext.checking(new Expectations() {{
+      allowing(myLogger).message(with(any(String.class)));
+    }});
+
+    myWorkDir = myTempFiles.createTempDir();
+
+  }
+
+  public void should_work_when_path_to_hg_is_property() throws Exception {
+    VcsRootImpl root = new VcsRootBuilder()
+            .repository(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath())
+            .hgPath(HG_PATH_REFERENCE).build();
+    testUpdate(root, "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null));
+  }
+
+
+  public void checkout_on_agent() throws IOException, VcsException {
+    testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null));
+  }
+
+  public void checkout_on_agent_include_rule_with_mapping() throws IOException, VcsException {
+    testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule("+:.", "subdir", null));
+  }
+
+  private void testUpdate(final VcsRoot vcsRoot, String version, String expected, final IncludeRule includeRule) throws VcsException, IOException {
+    File workDir = doUpdate(vcsRoot, version, includeRule);
+
+    checkWorkingDir(expected, workDir);
+  }
+
+  private void checkWorkingDir(final String expected, final File workDir) throws IOException {
+    FileUtil.delete(new File(workDir, ".hg"));
+    checkDirectoriesAreEqual(new File(getTestDataPath(), expected), workDir);
+  }
+
+  private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule) throws VcsException {
+    return doUpdate(vcsRoot, version, includeRule, false);
+  }
+
+  private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule, boolean useLocalMirrors) throws VcsException {
+    File actualWorkDir = new File(myWorkDir, includeRule.getTo());
+    final Map<String, String> sharedConfigParameters = new HashMap<String, String>();
+    sharedConfigParameters.put("teamcity.hg.use.local.mirrors", String.valueOf(useLocalMirrors));
+    final AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++);
+    myContext.checking(new Expectations() {{
+      allowing(build).getBuildLogger(); will(returnValue(myLogger));
+      allowing(build).getSharedConfigParameters(); will(returnValue(sharedConfigParameters));
+    }});
+    myVcsSupport.getUpdater(vcsRoot, new CheckoutRules(""), version, myWorkDir, build, false).process(includeRule, actualWorkDir);
+
+    File hgDir = new File(actualWorkDir, ".hg");
+    assertTrue(hgDir.isDirectory());
+    return actualWorkDir;
+  }
+
+  public void checkout_on_agent_from_branch() throws IOException, VcsException {
+    testUpdate(createVcsRoot(simpleRepo(), "test_branch"), "7:376dcf05cd2a", "patch3/after", new IncludeRule(".", ".", null));
+  }
+
+  public void update_on_agent() throws IOException, VcsException {
+    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
+    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", ".", null));
+    File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null));
+
+    checkWorkingDir("patch1/after", workDir);
+  }
+
+  public void update_on_agent_with_include_rule() throws IOException, VcsException {
+    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
+    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null));
+    File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", "subdir", null));
+
+    checkWorkingDir("patch1/after", workDir);
+  }
+
+  public void update_on_agent_from_branch() throws IOException, VcsException {
+    VcsRoot vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
+    doUpdate(vcsRoot, "7:376dcf05cd2a", new IncludeRule(".", ".", null));
+    File workDir = doUpdate(vcsRoot, "8:04c3ae4c6312", new IncludeRule(".", ".", null));
+
+    checkWorkingDir("patch4/after", workDir);
+  }
+
+  public void by_default_local_mirror_not_created() throws IOException, VcsException {
+    List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
+    assertTrue(mirrors.isEmpty());
+    VcsRoot root = createVcsRoot(simpleRepo());
+    doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
+    mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
+    //though some dirs are created - they are empty => there were no clones into local mirrors
+    for (File mirror : mirrors) {
+      assertTrue(FileUtil.getSubDirectories(mirror).isEmpty());
+    }
+  }
+
+  public void local_mirror_is_created() throws IOException, VcsException {
+    List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
+    assertTrue(mirrors.isEmpty());
+    VcsRoot root = createVcsRoot(simpleRepo());
+    doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
+    mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
+    assertEquals(1, mirrors.size());
+    File mirror = mirrors.get(0);
+    File dotHg = new File(mirror, ".hg");
+    assertTrue(dotHg.exists());
+    File hgrc = new File(dotHg, "hgrc");
+    String hgrcContent = FileUtil.readText(hgrc);
+    assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));
+  }
+
+  public void new_repository_is_cloned_from_local_mirror() throws IOException, VcsException {
+    VcsRoot root = createVcsRoot(simpleRepo());
+    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
+    File mirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
+    File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
+    String hgrcContent = FileUtil.readText(hgrc);
+    assertTrue(hgrcContent.contains("default = " + mirrorDir.getCanonicalPath()));
+  }
+
+  public void repository_cloned_from_remote_start_cloning_from_local_mirror() throws IOException, VcsException {
+    VcsRoot root = createVcsRoot(simpleRepo());
+    //clone from remote repository
+    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
+    String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc"));
+
+    File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
+    File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
+    String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc"));
+    assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed
+    assertTrue(hgrcContent2.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror
+  }
+
+  public void repository_cloned_from_local_mirror_start_cloning_from_remote() throws IOException, VcsException {
+    VcsRoot root = createVcsRoot(simpleRepo());
+    //clone from remote repository
+    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
+    String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc"));
+    File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
+    assertTrue(hgrcContent.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror
+
+    File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
+    String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc"));
+    assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed
+    assertTrue(hgrcContent2.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));//now it clones from remote
+  }
+
+  /**
+   * TW-15984
+   */
+  public void should_be_able_to_clone_into_non_empty_dir() throws IOException, VcsException {
+    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
+    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null));
+    doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null));
+  }
+
+  public void cloned_repo_should_contains_default_parameter_in_hgrc() throws VcsException, IOException {
+    VcsRoot root = createVcsRoot(simpleRepo());
+    File workingDir = doUpdate(root, "4:b06a290a363b", new IncludeRule(".", ".", null));
+    File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
+    String hgrcContent = FileUtil.readText(hgrc);
+    assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));
+  }
+
+  protected String getTestDataPath() {
+    return "mercurial-tests/testData";
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,105 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
+import jetbrains.buildServer.agent.BuildProgressLogger;
+import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2;
+import jetbrains.buildServer.log.Log4jFactory;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.IncludeRule;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class AgentSideCheckoutWithSubreposTest {
+
+  private TempFiles myTempFiles = new TempFiles();
+  private File myOriginalRepositoriesParentDir;
+  private File myWorkDir;
+  private Mockery myContext;
+  private BuildProgressLogger myLogger;
+  private UpdateByIncludeRules2 myVcsSupport;
+  private int myBuildCounter = 0;
+  private File myR1Dir;
+
+  static {
+    Logger.setFactory(new Log4jFactory());
+  }
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    myOriginalRepositoriesParentDir = myTempFiles.createTempDir();
+    myWorkDir = new File(myOriginalRepositoriesParentDir, "agentWorkDir");
+    myContext = new Mockery();
+
+    final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class);
+    myContext.checking(new Expectations() {{
+      allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myTempFiles.createTempDir()));
+      allowing(agentConfig).getParametersResolver(); will(returnValue(new HgPathResolver()));
+    }});
+
+    myVcsSupport = new MercurialAgentSideVcsSupport(new AgentPluginConfigImpl(agentConfig), new AgentHgPathProvider(agentConfig));
+
+    myLogger = myContext.mock(BuildProgressLogger.class);
+    myContext.checking(new Expectations() {{
+      allowing(myLogger).message(with(any(String.class)));
+      allowing(myLogger).warning(with(any(String.class)));
+    }});
+
+    myR1Dir = copy(new File("mercurial-tests/testData/subrepos/r1"));
+    copy(new File("mercurial-tests/testData/subrepos/r2"));
+    copy(new File("mercurial-tests/testData/subrepos/r3"));
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    myTempFiles.cleanup();
+  }
+
+
+  public void subrepository_url_changed() throws Exception {
+    VcsRootImpl root = new VcsRootBuilder()
+            .repository(myR1Dir.getAbsolutePath())
+            .build();
+    doUpdate(root, "34017377d9c3");
+    doUpdate(root, "d350e7209906");
+  }
+
+
+  private void doUpdate(final VcsRoot vcsRoot, final String toVersion) throws VcsException {
+    final AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++);
+    myContext.checking(new Expectations() {{
+      allowing(build).getBuildLogger(); will(returnValue(myLogger));
+      allowing(build).getSharedConfigParameters(); will(returnValue(Collections.emptyMap()));
+    }});
+    myVcsSupport.getUpdater(vcsRoot, CheckoutRules.DEFAULT, toVersion, myWorkDir, build, false).process(IncludeRule.createDefaultInstance(), myWorkDir);
+  }
+
+
+  private File copy(@NotNull File originalRepositoryDir) throws IOException {
+    String dirName = originalRepositoryDir.getName();
+    File copyDir = new File(myOriginalRepositoriesParentDir, dirName);
+    FileUtil.copyDir(originalRepositoryDir, copyDir);
+    if (new File(copyDir, "hg").isDirectory()) {
+      FileUtil.rename(new File(copyDir, "hg"), new File(copyDir, ".hg"));
+    }
+    return copyDir;
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,74 +1,64 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.MockSupport;
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
-import jetbrains.buildServer.vcs.patches.PatchTestCase;
-import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * @author Pavel.Sher
- *         Date: 31.07.2008
- */
-public abstract class BaseMercurialTestCase extends PatchTestCase {
-  protected TempFiles myTempFiles;
-  protected MockSupport myMockSupport;
-
-  @Override
-  @BeforeMethod
-  protected void setUp() throws Exception {
-    super.setUp();
-
-    myMockSupport = new MockSupport();
-    myMockSupport.setUpMocks();
-    myTempFiles = new TempFiles();
-  }
-
-  @AfterMethod
-  protected void tearDown() throws Exception {
-    myMockSupport.tearDownMocks();
-    myTempFiles.cleanup();
-  }
-
-  protected VcsRootImpl createVcsRoot(@NotNull String repPath) throws IOException {
-    VcsRootImpl vcsRoot = new VcsRootImpl(1, Constants.VCS_NAME);
-    vcsRoot.addProperty(Constants.HG_COMMAND_PATH_PROP, new File(Util.getHgPath()).getAbsolutePath());
-    File repository = LocalRepositoryUtil.prepareRepository(repPath);
-    vcsRoot.addProperty(Constants.REPOSITORY_PROP, repository.getAbsolutePath());
-    return vcsRoot;
-  }
-
-  protected VcsRootImpl createVcsRoot(@NotNull String repPath, @NotNull String branchName) throws IOException {
-    VcsRootImpl vcsRoot = createVcsRoot(repPath);
-    vcsRoot.addProperty(Constants.BRANCH_NAME_PROP, branchName);
-    return vcsRoot;
-  }
-
-  protected void cleanRepositoryAfterTest(@NotNull String repPath) {
-    LocalRepositoryUtil.forgetRepository(repPath);
-  }
-
-  protected String simpleRepo() {
-    return new File("mercurial-tests/testData/rep1").getAbsolutePath();
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import jetbrains.buildServer.vcs.patches.PatchTestCase;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Pavel.Sher
+ *         Date: 31.07.2008
+ */
+public abstract class BaseMercurialTestCase extends PatchTestCase {
+  protected TempFiles myTempFiles;
+
+  @Override
+  @BeforeMethod
+  protected void setUp() throws Exception {
+    super.setUp();
+    myTempFiles = new TempFiles();
+  }
+
+  @AfterMethod
+  protected void tearDown() throws Exception {
+    myTempFiles.cleanup();
+  }
+
+  protected VcsRootImpl createVcsRoot(@NotNull String repPath) throws IOException {
+    File repository = LocalRepositoryUtil.prepareRepository(repPath);
+    return new VcsRootBuilder().repository(repository.getAbsolutePath()).build();
+  }
+
+  protected VcsRootImpl createVcsRoot(@NotNull String repPath, @NotNull String branchName) throws IOException {
+    File repository = LocalRepositoryUtil.prepareRepository(repPath);
+    return new VcsRootBuilder().repository(repository.getAbsolutePath()).branch(branchName).build();
+  }
+
+  protected void cleanRepositoryAfterTest(@NotNull String repPath) {
+    LocalRepositoryUtil.forgetRepository(repPath);
+  }
+
+  protected String simpleRepo() {
+    return new File("mercurial-tests/testData/rep1").getAbsolutePath();
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandResultTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.ExecResult;
-import jetbrains.buildServer.StreamGobbler;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResult;
-import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.Test;
-
-import java.io.ByteArrayInputStream;
-import java.util.Collections;
-
-import static org.testng.AssertJUnit.assertFalse;
-
-/**
- * @author dmitry.neverov
- */
-@Test
-public class CommandResultTest {
-
-  public void output_should_not_contain_private_data() {
-    String password = "pass";
-    ExecResult result = createExecResult(password, password);
-    CommandResult commandResult = new CommandResult(result, Collections.singleton(password));
-    assertFalse(commandResult.getStdout().contains(password));
-    assertFalse(commandResult.getStderr().contains(password));
-  }
-
-  private ExecResult createExecResult(@NotNull final String output, @NotNull final String error) {
-    ExecResult result = new ExecResult();
-    result.setOutputGobbler(createStringGobbler(output));
-    result.setErrorGobbler(createStringGobbler(error));
-    return result;
-  }
-
-  private StreamGobbler createStringGobbler(@NotNull final String str) {
-    StreamGobbler gobbler = new StreamGobbler(new ByteArrayInputStream(str.getBytes()));
-    gobbler.start();
-    return gobbler;
-  }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,92 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSetRevision;
+import jetbrains.buildServer.log.Log4jFactory;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.ModificationData;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jmock.Mockery;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.util.List;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class DagFeaturesTest {
+
+  static {
+    Logger.setFactory(new Log4jFactory());
+  }
+
+  private TempFiles myTempFiles = new TempFiles();
+  private MercurialVcsSupport myHg;
+  private String myRepository;
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    ServerPluginConfig config = new ServerPluginConfigBuilder()
+            .cachesDir(myTempFiles.createTempDir())
+            .pluginDataDir(myTempFiles.createTempDir())
+            .build();
+    myHg = Util.createMercurialServerSupport(new Mockery(), config);
+
+    File original = new File("mercurial-tests/testData/rep2");
+    File copy = new File(myTempFiles.createTempDir(), "rep2");
+    LocalRepositoryUtil.copyRepository(original, copy);
+    myRepository = copy.getAbsolutePath();
+  }
+
+  public void tearDown() {
+    myTempFiles.cleanup();
+  }
+
+
+  //TW-17882
+  public void should_detect_changes_from_named_branches() throws Exception {
+    VcsRootImpl root = new VcsRootBuilder().repository(myRepository).build();
+
+    List<ModificationData> changes = myHg.collectChanges(root, "8:b6e2d176fe8e", "12:1e620196c4b6", CheckoutRules.DEFAULT);
+    assertEquals(4, changes.size());
+    for (ModificationData change : changes) {
+      assertFalse(change.getParentRevisions().isEmpty());
+      checkVersionsHaveNumbers(change.getParentRevisions());
+    }
+
+    changes = myHg.collectChanges(root, "12:1e620196c4b6", "18:df04faa7575a", CheckoutRules.DEFAULT);
+    assertEquals(6, changes.size());
+    for (ModificationData change : changes) {
+      assertFalse(change.getParentRevisions().isEmpty());
+      checkVersionsHaveNumbers(change.getParentRevisions());
+    }
+  }
+
+
+  //TW-17882
+  public void should_report_changes_only_from_merged_named_branches() throws Exception {
+    VcsRootImpl root = new VcsRootBuilder().repository(myRepository).build();
+    List<ModificationData> changes = myHg.collectChanges(root, "1e620196c4b6", "505c5b9d01e6", CheckoutRules.DEFAULT);
+    assertEquals(2, changes.size());
+    for (ModificationData change : changes) {
+      checkVersionsHaveNumbers(change.getParentRevisions());
+    }
+  }
+
+
+  private void checkVersionsHaveNumbers(@NotNull List<String> versions) {
+    for (String version : versions) {
+      ChangeSetRevision rev = new ChangeSetRevision(version);
+      assertTrue(rev.getRevNumber() != -1);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgPathResolver.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,64 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.parameters.NullValueResolver;
+import jetbrains.buildServer.parameters.ProcessingResult;
+import jetbrains.buildServer.parameters.ReferencesResolverUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+/**
+* @author dmitry.neverov
+*/
+class HgPathResolver extends NullValueResolver {
+  @NotNull
+  public ProcessingResult resolve(@NotNull String value) {
+    if (ReferencesResolverUtil.containsReference(value)) {
+      if (value.equals(AgentSideCheckoutTest.HG_PATH_REFERENCE)) {
+        try {
+          return new ResolvedPath(Util.getHgPath());
+        } catch (IOException e) {
+          return new Unresolved(value);
+        }
+      } else {
+        throw new IllegalArgumentException("Value resolver is asked to resolve " + value);
+      }
+    } else {
+      return new ResolvedPath(value);
+    }
+  }
+
+  private static class ResolvedPath implements ProcessingResult {
+    private final String myPath;
+    ResolvedPath(final @NotNull String path) {
+      myPath = path;
+    }
+    public boolean isModified() {
+      return true;
+    }
+    @NotNull
+    public String getResult() {
+      return myPath;
+    }
+    public boolean isFullyResolved() {
+      return true;
+    }
+  }
+
+  private static class Unresolved implements ProcessingResult {
+    private final String myUnresolvedValue;
+    Unresolved(@NotNull final String value) {
+      myUnresolvedValue = value;
+    }
+    public boolean isModified() {
+      return false;
+    }
+    @NotNull
+    public String getResult() {
+      return myUnresolvedValue;
+    }
+    public boolean isFullyResolved() {
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVersionTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,24 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import junit.framework.TestCase;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.Test;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class HgVersionTest extends TestCase {
+
+  public void test_parse() {
+    checkParseCorrectly("Mercurial Distributed SCM (version 1.7.1+11-cc4e13c92dfa)", "1.7.1");
+    checkParseCorrectly("Mercurial Distributed SCM (version 1.7)", "1.7.0");
+    checkParseCorrectly("Mercurial Distributed SCM (version 1.5.2)", "1.5.2");
+  }
+
+
+  private void checkParseCorrectly(@NotNull String versionToParse, @NotNull String expected) {
+    assertEquals(expected, HgVersion.parse(versionToParse).toString());
+  }
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/LocalRepositoryUtil.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/LocalRepositoryUtil.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,57 +1,61 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.util.FileUtil;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Pavel.Sher
- *         Date: 14.07.2008
- */
-public class LocalRepositoryUtil {
-  private final static TempFiles myTempFiles = new TempFiles();
-  private final static Map<String, File> myRepositories = new HashMap<String, File>();
-  static {
-    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
-      public void run() {
-        myTempFiles.cleanup();
-      }
-    }));
-  }
-
-  public static File prepareRepository(@NotNull String repPath) throws IOException {
-    File repository = myRepositories.get(repPath);
-    if (repository != null) return repository;
-    final File tempDir = myTempFiles.createTempDir();
-    FileUtil.copyDir(new File(repPath), tempDir);
-    if (new File(tempDir, "hg").isDirectory()) {
-      FileUtil.rename(new File(tempDir, "hg"), new File(tempDir, ".hg"));
-    }
-    myRepositories.put(repPath, tempDir);
-    return tempDir;
-  }
-
-  public static void forgetRepository(@NotNull String repPath) {
-    myRepositories.remove(repPath);
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.util.FileUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Pavel.Sher
+ *         Date: 14.07.2008
+ */
+public class LocalRepositoryUtil {
+  private final static TempFiles myTempFiles = new TempFiles();
+  private final static Map<String, File> myRepositories = new HashMap<String, File>();
+  static {
+    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+      public void run() {
+        myTempFiles.cleanup();
+      }
+    }));
+  }
+
+  public static File prepareRepository(@NotNull String repPath) throws IOException {
+    File repository = myRepositories.get(repPath);
+    if (repository != null) return repository;
+    final File tempDir = myTempFiles.createTempDir();
+    copyRepository(new File(repPath), tempDir);
+    myRepositories.put(repPath, tempDir);
+    return tempDir;
+  }
+
+  public static void forgetRepository(@NotNull String repPath) {
+    myRepositories.remove(repPath);
+  }
+
+
+  public static void copyRepository(File src, File dst) throws IOException {
+    FileUtil.copyDir(src, dst);
+    if (new File(dst, "hg").isDirectory())
+      FileUtil.rename(new File(dst, "hg"), new File(dst, ".hg"));
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -19,16 +19,12 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResult;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
-import jetbrains.buildServer.serverSide.BuildServerListener;
-import jetbrains.buildServer.serverSide.SBuildServer;
-import jetbrains.buildServer.serverSide.ServerPaths;
-import jetbrains.buildServer.util.EventDispatcher;
 import jetbrains.buildServer.vcs.*;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import jetbrains.buildServer.vcs.patches.PatchBuilderImpl;
 import junit.framework.Assert;
 import org.jetbrains.annotations.NotNull;
-import org.jmock.Mock;
+import org.jmock.Mockery;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -37,33 +33,26 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
 
 import static com.intellij.openapi.util.io.FileUtil.copyDir;
 import static com.intellij.openapi.util.io.FileUtil.moveDirWithContent;
 
 @Test
 public class MercurialVcsSupportTest extends BaseMercurialTestCase {
+
   private MercurialVcsSupport myVcs;
-  private ServerPaths myServerPaths;
+  private String myRep2Path = new File("mercurial-tests/testData/rep2").getAbsolutePath();
+  private ServerPluginConfig myPluginConfig;
 
   @BeforeMethod
   protected void setUp() throws Exception {
     super.setUp();
-
-    Mock vcsManagerMock = new Mock(VcsManager.class);
-    vcsManagerMock.stubs().method("registerVcsSupport");
-    Mock serverMock = new Mock(SBuildServer.class);
-    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
-    serverMock.stubs().method("getExecutor").will(myMockSupport.returnValue(executor));
-
-    EventDispatcher<BuildServerListener> dispatcher = EventDispatcher.create(BuildServerListener.class);
-
-    File systemDir = myTempFiles.createTempDir();
-    myServerPaths = new ServerPaths(systemDir.getAbsolutePath(), systemDir.getAbsolutePath(), systemDir.getAbsolutePath());
-    assertTrue(new File(myServerPaths.getCachesDir()).mkdirs());
-    myVcs = new MercurialVcsSupport((VcsManager)vcsManagerMock.proxy(), myServerPaths, (SBuildServer)serverMock.proxy(), dispatcher, createPluginConfig());
+    Mockery context = new Mockery();
+    myPluginConfig = new ServerPluginConfigBuilder()
+            .cachesDir(myTempFiles.createTempDir())
+            .pluginDataDir(myTempFiles.createTempDir())
+            .build();
+    myVcs = Util.createMercurialServerSupport(context, myPluginConfig);
   }
 
   protected String getTestDataPath() {
@@ -82,7 +71,7 @@
   }
 
   private List<ModificationData> collectChanges(@NotNull VcsRoot vcsRoot, @NotNull String from, @NotNull String to, @NotNull CheckoutRules rules) throws VcsException {
-    return ((CollectChangesByCheckoutRules) myVcs.getCollectChangesPolicy()).collectChanges(vcsRoot, from, to, rules);
+    return myVcs.collectChanges(vcsRoot, from, to, rules);
   }
 
   public void test_collect_changes_between_two_same_roots() throws Exception {
@@ -92,6 +81,13 @@
     do_check_for_collect_changes(changes);
   }
 
+  public void test_collect_changes_from_non_existing_revision() throws Exception {
+    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
+    VcsRootImpl sameVcsRoot = createVcsRoot(simpleRepo());
+    List<ModificationData> changes = myVcs.collectChanges(vcsRoot, "0:9875b412a789", sameVcsRoot, "3:9522278aa38d", new CheckoutRules(""));
+    assertFalse(changes.isEmpty());//should return some changes from the toRoot
+  }
+
   public void test_collect_changes() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
     List<ModificationData> changes = collectChanges(vcsRoot, "0:9875b412a788", "3:9522278aa38d", new CheckoutRules(""));
@@ -143,7 +139,7 @@
     ByteArrayOutputStream output = buildPatch(vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));
     checkPatchResult(output.toByteArray());
 
-    File clonedReposParentDir = new File(myServerPaths.getCachesDir(), "mercurial");
+    File clonedReposParentDir = myPluginConfig.getCachesDir();
     assertTrue(clonedReposParentDir.isDirectory());
     assertTrue(1 == clonedReposParentDir.list(new FilenameFilter() {
       public boolean accept(final File dir, final String name) {
@@ -189,8 +185,8 @@
   }
 
   public void test_clean_patch_with_subrepositories() throws Exception {
-    File r1 = new File(myServerPaths.getCachesDir() + File.separator + "mercurial", "r1");
-    File r3 = new File(myServerPaths.getCachesDir() + File.separator + "mercurial", "r3");
+    File r1 = new File(myPluginConfig.getCachesDir(), "r1");
+    File r3 = new File(myPluginConfig.getCachesDir(), "r3");
     copyDir(new File("mercurial-tests/testData/subrepos/r1"), r1);
     copyDir(new File("mercurial-tests/testData/subrepos/r3"), r3);
     moveDirWithContent(new File(r1, "hg"), new File(r1, ".hg"));
@@ -344,15 +340,15 @@
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
     String repPath = vcsRoot.getProperty(Constants.REPOSITORY_PROP);
     vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#test_branch");
-    Settings settings = new Settings(vcsRoot);
+    Settings settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot);
     assertEquals("test_branch", settings.getBranchName());
 
     vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#");
-    settings = new Settings(vcsRoot);
+    settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot);
     assertEquals("default", settings.getBranchName());
 
     vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath);
-    settings = new Settings(vcsRoot);
+    settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot);
     assertEquals("default", settings.getBranchName());
   }
 
@@ -383,6 +379,13 @@
     assertEquals("10:fc524efc2bc4", changes.get(1).getVersion());
   }
 
+  public void collectChanges_should_return_all_changes_from_branch() throws Exception {
+    VcsRootImpl defaultBranchRoot = createVcsRoot(myRep2Path, "default");
+    VcsRootImpl personalBranchRoot = createVcsRoot(myRep2Path, "personal-branch");
+    List<ModificationData> modifications = myVcs.collectChanges(defaultBranchRoot, "16:505c5b9d01e6", personalBranchRoot, "17:9ec402c74298", CheckoutRules.DEFAULT);
+    assertEquals(3, modifications.size());
+  }
+
   public void test_collect_changes_merge() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo());
 
@@ -412,10 +415,10 @@
     VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo());
 
     List<ModificationData> changes = collectChanges(vcsRoot, "8:b6e2d176fe8e", "12:1e620196c4b6", CheckoutRules.DEFAULT);
-    assertEquals(changes.size(), 2);
+    assertEquals(changes.size(), 4);
 
-    assertFiles(Arrays.asList("A dir6/file6.txt"), changes.get(0));
-    assertFiles(Arrays.asList("M dir6/file6.txt", "A dir5/file5.txt"), changes.get(1));
+    assertFiles(Arrays.asList("A dir6/file6.txt"), changes.get(2));
+    assertFiles(Arrays.asList("M dir6/file6.txt", "A dir5/file5.txt"), changes.get(3));
   }
 
   //TW-17530
@@ -443,7 +446,7 @@
     VcsRootImpl root = new VcsRootImpl(1, Constants.VCS_NAME);
     root.addAllProperties(myVcs.getDefaultVcsProperties());
     root.addProperty(Constants.REPOSITORY_PROP, "http://host.com/path");
-    Settings settings = new Settings(root);
+    Settings settings = new Settings(new ServerHgPathProvider(myPluginConfig), root);
     assertFalse(settings.isUncompressedTransfer());
   }
 
@@ -501,16 +504,5 @@
   public void test_collect_changes_using_checkout_rules() {
     assertTrue(myVcs.getCollectChangesPolicy() instanceof CollectChangesByCheckoutRules);
   }
-
-  private ServerPluginConfig createPluginConfig() {
-    return new ServerPluginConfig() {
-      public boolean isUsePullProtocol() {
-        return true;
-      }
-      public int getPullTimeout() {
-        return CommandUtil.DEFAULT_COMMAND_TIMEOUT_SEC;
-      }
-    };
-  }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,54 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class ServerHgPathProviderTest {
+
+  private String myServerWideHgPath;
+  private String myVcsRootHgPath;
+
+
+  @BeforeMethod
+  public void setUp() {
+    myServerWideHgPath = null;
+    myVcsRootHgPath = "/vcs/root/hg/path";
+  }
+
+
+  public void server_should_use_settings_from_vcs_root_if_server_wide_path_is_not_set() throws Exception {
+    myServerWideHgPath = null;
+    HgPathProvider provider = createHgPathProvider();
+    Settings settings = createSettings(provider);
+    assertEquals(myVcsRootHgPath, provider.getHgPath(settings));
+  }
+
+
+  public void server_should_use_server_wide_path_if_it_is_set() throws Exception {
+    myServerWideHgPath = "/server-wide/hg/path";
+    HgPathProvider provider = createHgPathProvider();
+    Settings settings = createSettings(provider);
+    assertEquals(myServerWideHgPath, provider.getHgPath(settings));
+  }
+
+
+  private ServerHgPathProvider createHgPathProvider() {
+    ServerPluginConfig config = new ServerPluginConfigBuilder().hgPath(myServerWideHgPath).build();
+    return new ServerHgPathProvider(config);
+  }
+
+
+  private Settings createSettings(HgPathProvider hgPathProvider) throws Exception {
+    VcsRootImpl root = new VcsRootBuilder().hgPath(myVcsRootHgPath).build();
+    return new Settings(hgPathProvider, root);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigBuilder.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,70 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author dmitry.neverov
+ */
+public class ServerPluginConfigBuilder {
+
+  private boolean myUsePullProtocol = true;
+  private String myHgPath;
+  private File myCachesDir;
+  private File myPluginDataDir;
+
+
+  @NotNull
+  public ServerPluginConfig build() {
+    return new ServerPluginConfig() {
+
+      public boolean isUsePullProtocol() {
+        return myUsePullProtocol;
+      }
+
+      public String getHgPath() {
+        return myHgPath;
+      }
+
+      @NotNull
+      public File getCachesDir() {
+        if (myCachesDir == null)
+          throw new IllegalStateException("Caches dir is not set");
+        return myCachesDir;
+      }
+
+      @NotNull
+      public File getPluginDataDir() {
+        if (myPluginDataDir == null)
+          throw new IllegalStateException("Plugin data dir is not set");
+        return myPluginDataDir;
+      }
+
+      public int getPullTimeout() {
+        return ServerPluginConfigImpl.DEFAULT_PULL_TIMEOUT_SECONDS;
+      }
+    };
+  }
+
+
+  public ServerPluginConfigBuilder userPullProtocol(boolean doUse) {
+    myUsePullProtocol = doUse;
+    return this;
+  }
+
+  public ServerPluginConfigBuilder hgPath(String hgPath) {
+    myHgPath = hgPath;
+    return this;
+  }
+
+  public ServerPluginConfigBuilder cachesDir(File cachesDir) {
+    myCachesDir = cachesDir;
+    return this;
+  }
+
+  public ServerPluginConfigBuilder pluginDataDir(File pluginDataDir) {
+    myPluginDataDir = pluginDataDir;
+    return this;
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,136 +1,143 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
-import junit.framework.TestCase;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-/**
- * @author Pavel.Sher
- */
-@Test
-public class SettingsTest extends TestCase {
-
-  private TempFiles myTempFiles = new TempFiles();
-  private MirrorManager myMirrorManager;
-
-  @Override
-  @BeforeMethod
-  public void setUp() throws Exception {
-    myMirrorManager = new MirrorManager(myTempFiles.createTempDir());
-  }
-
-  @Override
-  @AfterMethod
-  public void tearDown() throws Exception {
-    myTempFiles.cleanup();
-  }
-
-  public void test_url_without_credentials() {
-    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path");
-    Settings settings = new Settings(vcsRoot);
-    assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl());
-  }
-
-  public void test_url_with_credentials() {
-    VcsRootImpl vcsRoot = createVcsRoot("http://user:pwd@host.com/path");
-    Settings settings = new Settings(vcsRoot);
-    assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl());
-  }
-
-  public void test_url_with_username() {
-    VcsRootImpl vcsRoot = createVcsRoot("http://user@host.com/path");
-    Settings settings = new Settings(vcsRoot);
-    assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl());
-  }
-
-  public void test_url_with_at_after_slash() {
-    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path@");
-    Settings settings = new Settings(vcsRoot);
-    assertEquals("http://user:pwd@host.com/path@", settings.getRepositoryUrl());
-  }
-
-  public void test_url_with_at_in_username() {
-    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path", "my.name@gmail.com", "1234");
-    Settings settings = new Settings(vcsRoot);
-    assertEquals("http://my.name%40gmail.com:1234@host.com/path", settings.getRepositoryUrl());
-  }
-
-  /** TW-13768 */
-  public void test_underscore_in_host() {
-		VcsRootImpl vcsRoot = createVcsRoot("http://Klekovkin.SDK_GARANT:8000/", "my.name@gmail.com", "1234");
-    Settings settings = new Settings(vcsRoot);
-		assertEquals("http://my.name%40gmail.com:1234@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrl());
-	}
-
-  /** TW-13768 */
-  public void test_underscore_in_host_with_credentials_in_url() {
-    VcsRootImpl vcsRoot = createVcsRoot("http://me:mypass@Klekovkin.SDK_GARANT:8000/");
-    Settings settings = new Settings(vcsRoot);
-		assertEquals("http://me:mypass@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrl());
-  }
-
-  public void test_windows_path() throws Exception {
-    VcsRootImpl vcsRoot = createVcsRoot("c:\\windows\\path");
-    Settings settings = new Settings(vcsRoot);
-    assertEquals("c:\\windows\\path", settings.getRepositoryUrl());
-  }
-
-  public void test_file_scheme_has_no_credentials() {
-    VcsRootImpl vcsRoot = createVcsRoot("file:///path/to/repo", "my.name@gmail.com", "1234");
-    Settings settings = new Settings(vcsRoot);
-    assertEquals("file:///path/to/repo", settings.getRepositoryUrl());
-  }
-
-  public void uncompressed_transfer() {
-    VcsRootImpl root = createVcsRoot("http://host.com/path");
-    root.addProperty(Constants.UNCOMPRESSED_TRANSFER, "true");
-    Settings settings = new Settings(root);
-    assertTrue(settings.isUncompressedTransfer());
-  }
-
-  //TW-18262
-  public void ampersand_in_password() {
-    VcsRootImpl vcsRoot = createVcsRoot("http://some.org/path", "user", "m&n");
-    Settings settings = new Settings(vcsRoot);
-		assertEquals("http://user:m%26n@some.org/path", settings.getRepositoryUrl());
-  }
-
-  //TW-18835
-  public void test_ssh() {
-    VcsRootImpl vcsRoot = createVcsRoot("ssh://ourserver.com/mercurialrepo/", "user", "pwd");
-    Settings settings = new Settings(vcsRoot);
-    assertEquals("ssh://user:pwd@ourserver.com/mercurialrepo/", settings.getRepositoryUrl());
-  }
-
-  private VcsRootImpl createVcsRoot(String url) {
-    return createVcsRoot(url, "user", "pwd");
-  }
-
-  private VcsRootImpl createVcsRoot(String url, String userName, String password) {
-    VcsRootImpl vcsRoot = new VcsRootImpl(1, Constants.VCS_NAME);
-    vcsRoot.addProperty(Constants.HG_COMMAND_PATH_PROP, "hg.exe");
-    vcsRoot.addProperty(Constants.REPOSITORY_PROP, url);
-    vcsRoot.addProperty(Constants.USERNAME, userName);
-    vcsRoot.addProperty(Constants.PASSWORD, password);
-    return vcsRoot;
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import junit.framework.TestCase;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * @author Pavel.Sher
+ */
+@Test
+public class SettingsTest extends TestCase {
+
+  private TempFiles myTempFiles = new TempFiles();
+  private MirrorManager myMirrorManager;
+
+  @Override
+  @BeforeMethod
+  public void setUp() throws Exception {
+    myMirrorManager = new MirrorManager(myTempFiles.createTempDir());
+  }
+
+  @Override
+  @AfterMethod
+  public void tearDown() throws Exception {
+    myTempFiles.cleanup();
+  }
+
+  public void test_url_without_credentials() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl());
+  }
+
+  public void test_url_with_credentials() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://user:pwd@host.com/path");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl());
+  }
+
+  public void test_url_with_username() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://user@host.com/path");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl());
+  }
+
+  public void test_url_with_at_after_slash() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path@");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("http://user:pwd@host.com/path@", settings.getRepositoryUrl());
+  }
+
+  public void test_url_with_at_in_username() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path", "my.name@gmail.com", "1234");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("http://my.name%40gmail.com:1234@host.com/path", settings.getRepositoryUrl());
+  }
+
+  /** TW-13768 */
+  public void test_underscore_in_host() {
+		VcsRootImpl vcsRoot = createVcsRoot("http://Klekovkin.SDK_GARANT:8000/", "my.name@gmail.com", "1234");
+    Settings settings = createSettings(vcsRoot);
+		assertEquals("http://my.name%40gmail.com:1234@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrl());
+	}
+
+  /** TW-13768 */
+  public void test_underscore_in_host_with_credentials_in_url() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://me:mypass@Klekovkin.SDK_GARANT:8000/");
+    Settings settings = createSettings(vcsRoot);
+		assertEquals("http://me:mypass@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrl());
+  }
+
+  public void test_windows_path() throws Exception {
+    VcsRootImpl vcsRoot = createVcsRoot("c:\\windows\\path");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("c:\\windows\\path", settings.getRepositoryUrl());
+  }
+
+  public void test_file_scheme_has_no_credentials() {
+    VcsRootImpl vcsRoot = createVcsRoot("file:///path/to/repo", "my.name@gmail.com", "1234");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("file:///path/to/repo", settings.getRepositoryUrl());
+  }
+
+  public void uncompressed_transfer() {
+    VcsRootImpl root = createVcsRoot("http://host.com/path");
+    root.addProperty(Constants.UNCOMPRESSED_TRANSFER, "true");
+    Settings settings = createSettings(root);
+    assertTrue(settings.isUncompressedTransfer());
+  }
+
+  //TW-18262
+  public void ampersand_in_password() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://some.org/path", "user", "m&n");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("http://user:m%26n@some.org/path", settings.getRepositoryUrl());
+  }
+
+  //TW-18835
+  public void test_ssh() {
+    VcsRootImpl vcsRoot = createVcsRoot("ssh://ourserver.com/mercurialrepo/", "user", "pwd");
+    Settings settings = createSettings(vcsRoot);
+    assertEquals("ssh://user:pwd@ourserver.com/mercurialrepo/", settings.getRepositoryUrl());
+  }
+
+  private VcsRootImpl createVcsRoot(String url) {
+    return createVcsRoot(url, "user", "pwd");
+  }
+
+  private VcsRootImpl createVcsRoot(String url, String userName, String password) {
+    VcsRootImpl vcsRoot = new VcsRootImpl(1, Constants.VCS_NAME);
+    vcsRoot.addProperty(Constants.HG_COMMAND_PATH_PROP, "hg.exe");
+    vcsRoot.addProperty(Constants.REPOSITORY_PROP, url);
+    vcsRoot.addProperty(Constants.USERNAME, userName);
+    vcsRoot.addProperty(Constants.PASSWORD, password);
+    return vcsRoot;
+  }
+
+  private Settings createSettings(@NotNull final VcsRoot root) {
+    ServerPluginConfig config = new ServerPluginConfigBuilder().build();
+    return new Settings(new ServerHgPathProvider(config), root);
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,6 +1,17 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.serverSide.BuildServerListener;
+import jetbrains.buildServer.serverSide.SBuildServer;
+import jetbrains.buildServer.util.EventDispatcher;
+import jetbrains.buildServer.vcs.VcsManager;
+import org.jetbrains.annotations.NotNull;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+
+import java.io.File;
 import java.io.IOException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 
 /**
  * @author dmitry.neverov
@@ -14,8 +25,19 @@
     if (providedHg != null) {
       return providedHg;
     } else {
-      return "mercurial-tests/testData/bin/hg.exe";
+      return new File("mercurial-tests/testData/bin/hg.exe").getAbsolutePath();
     }
   }
 
+
+  public static MercurialVcsSupport createMercurialServerSupport(@NotNull Mockery context, ServerPluginConfig config) throws IOException {
+    VcsManager vcsManager = context.mock(VcsManager.class);
+    final SBuildServer server = context.mock(SBuildServer.class);
+    final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+    context.checking(new Expectations() {{
+      allowing(server).getExecutor(); will(returnValue(executor));
+    }});
+    EventDispatcher<BuildServerListener> dispatcher = EventDispatcher.create(BuildServerListener.class);
+    return new MercurialVcsSupport(vcsManager, server, dispatcher, config, new ServerHgPathProvider(config), new CommandFactory(config));
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,65 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+/**
+ * @author dmitry.neverov
+ */
+public class VcsRootBuilder {
+
+  private String myRepository;
+  private String myUsername;
+  private String myPassword;
+  private String myBranch;
+  private long myRootId = 1L;
+  private String myHgPath;
+
+  public VcsRootImpl build() throws IOException {
+    VcsRootImpl vcsRoot = new VcsRootImpl(myRootId, Constants.VCS_NAME);
+    vcsRoot.addProperty(Constants.REPOSITORY_PROP, myRepository);
+    vcsRoot.addProperty(Constants.HG_COMMAND_PATH_PROP, myHgPath != null ? myHgPath : Util.getHgPath());
+    vcsRoot.addProperty(Constants.USERNAME, myUsername);
+    vcsRoot.addProperty(Constants.PASSWORD, myPassword);
+    vcsRoot.addProperty(Constants.BRANCH_NAME_PROP, myBranch);
+    return vcsRoot;
+  }
+
+
+  public VcsRootBuilder repository(@NotNull String repository) {
+    myRepository = repository;
+    return this;
+  }
+
+
+  public VcsRootBuilder username(@NotNull String username) {
+    myUsername = username;
+    return this;
+  }
+
+
+  public VcsRootBuilder password(@NotNull String password) {
+    myPassword = password;
+    return this;
+  }
+
+
+  public VcsRootBuilder branch(@NotNull String branch) {
+    myBranch = branch;
+    return this;
+  }
+
+
+  public VcsRootBuilder rootId(long rootId) {
+    myRootId = rootId;
+    return this;
+  }
+
+
+  public VcsRootBuilder hgPath(String hgPath) {
+    myHgPath = hgPath;
+    return this;
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -2,9 +2,6 @@
 
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.openapi.util.SystemInfo;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.Util;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import junit.framework.TestCase;
 import org.testng.annotations.Test;
 
@@ -18,13 +15,8 @@
 public class BaseCommandTest extends TestCase {
 
   public void should_quote_command_line_arguments() throws IOException {
-    VcsRootImpl root = new VcsRootImpl(1, "rootForTest");
-    root.addProperty(Constants.REPOSITORY_PROP, "http://some.org/repo.hg");
-    root.addProperty(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath());
     File workingDir = new File("some dir");
-    Settings settings = new Settings(root);
-
-    BaseCommand command = new BaseCommand(settings, workingDir);
+    BaseCommand command = new BaseCommand("/path/to/hg", workingDir);
     GeneralCommandLine cl = command.createCommandLine();
     cl.addParameter("param with spaces");
     cl.addParameter("param with quote \" rm -rf /");
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,96 +1,94 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import jetbrains.buildServer.BaseTestCase;
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.LocalRepositoryUtil;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManager;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.Util;
-import jetbrains.buildServer.vcs.VcsException;
-import jetbrains.buildServer.vcs.VcsRoot;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-public class BaseCommandTestCase extends BaseTestCase {
-  private String myRepository;
-  private String myUsername;
-  private String myPassword;
-  private boolean myCloneRequired;
-
-  public BaseCommandTestCase() {
-  }
-
-  protected void setRepository(final String repository, boolean cloneRequired) {
-    myRepository = repository;
-    myCloneRequired = cloneRequired;
-  }
-
-  protected void setUsername(final String username) {
-    myUsername = username;
-  }
-
-  protected void setPassword(final String password) {
-    myPassword = password;
-  }
-
-  protected <T> T runCommand(CommandExecutor<T> executor) throws IOException, VcsException {
-    Map<String, String> vcsRootProps = new HashMap<String, String>();
-
-    vcsRootProps.put(Constants.REPOSITORY_PROP, myRepository);
-
-    if (myCloneRequired) {
-      File repository = LocalRepositoryUtil.prepareRepository(new File(myRepository).getAbsolutePath());
-      vcsRootProps.put(Constants.REPOSITORY_PROP, repository.getAbsolutePath());
-    }
-
-    vcsRootProps.put(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath());
-    if (myUsername != null) {
-      vcsRootProps.put(Constants.USERNAME, myUsername);
-    }
-    if (myPassword != null) {
-      vcsRootProps.put(Constants.PASSWORD, myPassword);
-    }
-
-    TempFiles tf = new TempFiles();
-    File parentDir = tf.createTempDir();
-
-    MirrorManager mirrorManager = new MirrorManager(parentDir);
-    VcsRoot vcsRoot = new VcsRootImpl(1, vcsRootProps);
-    Settings settings = new Settings(vcsRoot);
-    final File workingDir = mirrorManager.getMirrorDir(settings.getRepositoryUrl());
-    settings.setCustomWorkingDir(workingDir);
-    try {
-      if (myCloneRequired) {
-        new CloneCommand(settings, workingDir).execute();
-      }
-
-      return executor.execute(settings, workingDir);
-    } finally {
-      tf.cleanup();
-    }
-  }
-
-  public interface CommandExecutor<T> {
-    T execute(@NotNull Settings settings, File workingDir) throws VcsException;
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.BaseTestCase;
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.*;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BaseCommandTestCase extends BaseTestCase {
+  private String myRepository;
+  private String myUsername;
+  private String myPassword;
+  private boolean myCloneRequired;
+
+  public BaseCommandTestCase() {
+  }
+
+  protected void setRepository(final String repository, boolean cloneRequired) {
+    myRepository = repository;
+    myCloneRequired = cloneRequired;
+  }
+
+  protected void setUsername(final String username) {
+    myUsername = username;
+  }
+
+  protected void setPassword(final String password) {
+    myPassword = password;
+  }
+
+  protected <T> T runCommand(CommandExecutor<T> executor) throws IOException, VcsException {
+    Map<String, String> vcsRootProps = new HashMap<String, String>();
+
+    vcsRootProps.put(Constants.REPOSITORY_PROP, myRepository);
+
+    if (myCloneRequired) {
+      File repository = LocalRepositoryUtil.prepareRepository(new File(myRepository).getAbsolutePath());
+      vcsRootProps.put(Constants.REPOSITORY_PROP, repository.getAbsolutePath());
+    }
+
+    vcsRootProps.put(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath());
+    if (myUsername != null) {
+      vcsRootProps.put(Constants.USERNAME, myUsername);
+    }
+    if (myPassword != null) {
+      vcsRootProps.put(Constants.PASSWORD, myPassword);
+    }
+
+    TempFiles tf = new TempFiles();
+    File parentDir = tf.createTempDir();
+
+    MirrorManager mirrorManager = new MirrorManager(parentDir);
+    VcsRoot vcsRoot = new VcsRootImpl(1, vcsRootProps);
+    ServerPluginConfig config = new ServerPluginConfigBuilder().build();
+    Settings settings = new Settings(new ServerHgPathProvider(config), vcsRoot);
+    final File workingDir = mirrorManager.getMirrorDir(settings.getRepositoryUrl());
+    settings.setCustomWorkingDir(workingDir);
+    try {
+      if (myCloneRequired) {
+        new CloneCommand(settings, workingDir).execute();
+      }
+
+      return executor.execute(settings, workingDir);
+    } finally {
+      tf.cleanup();
+    }
+  }
+
+  public interface CommandExecutor<T> {
+    T execute(@NotNull Settings settings, File workingDir) throws VcsException;
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommandTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommandTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -2,9 +2,7 @@
 
 import jetbrains.buildServer.BaseTestCase;
 import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.LocalRepositoryUtil;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.Util;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.*;
 import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.testng.annotations.AfterMethod;
@@ -44,7 +42,8 @@
     root.addProperty(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath());
 
     File workingDir = myTempFiles.createTempDir();
-    Settings settings = new Settings(root);
+    ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(myTempFiles.createTempDir()).build();
+    Settings settings = new Settings(new ServerHgPathProvider(config), root);
     settings.setCustomWorkingDir(workingDir);
 
     CloneCommand clone = new CloneCommand(settings, workingDir);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,40 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.ExecResult;
+import jetbrains.buildServer.StreamGobbler;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayInputStream;
+import java.util.Collections;
+
+import static org.testng.AssertJUnit.assertFalse;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class CommandResultTest {
+
+  public void output_should_not_contain_private_data() {
+    String password = "pass";
+    ExecResult result = createExecResult(password, password);
+    CommandResult commandResult = new CommandResult(result, Collections.singleton(password));
+    assertFalse(commandResult.getStdout().contains(password));
+    assertFalse(commandResult.getStderr().contains(password));
+  }
+
+  private ExecResult createExecResult(@NotNull final String output, @NotNull final String error) {
+    ExecResult result = new ExecResult();
+    result.setOutputGobbler(createStringGobbler(output));
+    result.setErrorGobbler(createStringGobbler(error));
+    return result;
+  }
+
+  private StreamGobbler createStringGobbler(@NotNull final String str) {
+    StreamGobbler gobbler = new StreamGobbler(new ByteArrayInputStream(str.getBytes()));
+    gobbler.start();
+    return gobbler;
+  }
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,140 +1,184 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-@Test
-public class LogCommandTest extends BaseCommandTestCase {
-
-  private TempFiles myTempFiles = new TempFiles();
-  private File myTemplateFile;
-
-
-  @BeforeMethod
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-    setRepository("mercurial-tests/testData/rep1", true);
-    myTemplateFile = myTempFiles.createTempFile();
-    FileUtil.copyResource(MercurialVcsSupport.class, "/buildServerResources/log.template", myTemplateFile);
-  }
-
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
-
-
-  public void testOneChangeSet() throws Exception {
-    final String toId = "9875b412a788";
-    List<ChangeSet> changes = runLog(null, toId);
-    assertEquals(1, changes.size());
-    final ChangeSet changeSet = changes.get(0);
-    assertEquals(0, changeSet.getRevNumber());
-    assertEquals(toId, changeSet.getId());
-    assertEquals("pavel@localhost", changeSet.getUser());
-    assertEquals("dir1 created", changeSet.getDescription());
-    assertTrue(changeSet.getParents().isEmpty());
-  }
-
-  public void testMoreThanOneChangeSet() throws Exception {
-    final String fromId = "9875b412a788";
-    final String toId = "7209b1f1d793";
-    List<ChangeSet> changes = runLog(fromId, toId);
-    assertEquals(3, changes.size());
-    ChangeSet changeSet1 = changes.get(0);
-    final ChangeSet changeSet2 = changes.get(1);
-    final ChangeSet changeSet3 = changes.get(2);
-    assertEquals("dir1 created", changeSet1.getDescription());
-    assertEquals("new file added", changeSet2.getDescription());
-    assertEquals("file4.txt added", changeSet3.getDescription());
-
-    changes = runLog(null, toId);
-    assertEquals(3, changes.size());
-    changeSet1 = changes.get(2);
-    assertEquals("file4.txt added", changeSet1.getDescription());
-  }
-
-  public void changeset_parents() throws VcsException, IOException {
-    setRepository("mercurial-tests/testData/rep2", true);
-    List<ChangeSet> changes = runLog("6eeb8974fe67", "6eeb8974fe67");
-    assertEquals(1, changes.size());
-    ChangeSet cs = changes.get(0);
-    assertNotNull(cs.getParents());
-    assertEquals(2, cs.getParents().size());
-    assertTrue(cs.getParents().contains(new ChangeSetRevision("1:a3d15477d297")));
-    assertTrue(cs.getParents().contains(new ChangeSetRevision("3:2538c02bafeb")));
-  }
-
-  public void parse_multiline_description() throws VcsException, IOException {
-    List<ChangeSet> changes = runLog("9babcf2d5705", "9c6a6b4aede0");
-    assertEquals(1, changes.size());
-    assertEquals("Multiline description\n" +
-            "description with new\n" +
-            "lines\n" +
-            "aaaa\n" +
-            "bbb", changes.get(0).getDescription());
-  }
-
-  public void log_result_should_contain_changed_files() throws Exception {
-    final String fromId = "7209b1f1d793";
-    final String toId = "b06a290a363b";
-    List<ChangeSet> csets = runLog(fromId, toId);
-    assertEquals(3, csets.size());
-
-    List<ModifiedFile> files = csets.get(0).getModifiedFiles();
-    assertEquals(1, files.size());
-    ModifiedFile file = files.get(0);
-    assertEquals(ModifiedFile.Status.ADDED, file.getStatus());
-    assertEquals("dir1/file4.txt", file.getPath());
-
-    files = csets.get(1).getModifiedFiles();
-    assertEquals(1, files.size());
-    file = files.get(0);
-    assertEquals(ModifiedFile.Status.REMOVED, file.getStatus());
-    assertEquals("dir1/file4.txt", file.getPath());
-
-    files = csets.get(2).getModifiedFiles();
-    assertEquals(1, files.size());
-    file = files.get(0);
-    assertEquals(ModifiedFile.Status.MODIFIED, file.getStatus());
-    assertEquals("dir1/file3.txt", file.getPath());
-  }
-
-
-  private List<ChangeSet> runLog(final String fromId, final String toId) throws IOException, VcsException {
-    return runCommand(new CommandExecutor<List<ChangeSet>>() {
-      public List<ChangeSet> execute(@NotNull final Settings settings, @NotNull File workingDir) throws VcsException {
-        LogCommand lc = new LogCommand(settings, workingDir, myTemplateFile);
-        lc.setFromRevId(fromId);
-        lc.setToRevId(toId);
-        return lc.execute();
-      }
-    });
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Test
+public class LogCommandTest extends BaseCommandTestCase {
+
+  private TempFiles myTempFiles = new TempFiles();
+  private File myTemplateFile;
+
+
+  @BeforeMethod
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    setRepository("mercurial-tests/testData/rep1", true);
+    myTemplateFile = myTempFiles.createTempFile();
+    FileUtil.copyResource(MercurialVcsSupport.class, "/buildServerResources/log.template", myTemplateFile);
+  }
+
+
+  @AfterMethod
+  public void tearDown() {
+    myTempFiles.cleanup();
+  }
+
+
+  public void testOneChangeSet() throws Exception {
+    final String toId = "9875b412a788";
+    List<ChangeSet> changes = runLog(null, toId);
+    assertEquals(1, changes.size());
+    final ChangeSet changeSet = changes.get(0);
+    assertEquals(0, changeSet.getRevNumber());
+    assertEquals(toId, changeSet.getId());
+    assertEquals("pavel@localhost", changeSet.getUser());
+    assertEquals("dir1 created", changeSet.getDescription());
+  }
+
+  public void testMoreThanOneChangeSet() throws Exception {
+    final String fromId = "9875b412a788";
+    final String toId = "7209b1f1d793";
+    List<ChangeSet> changes = runLog(fromId, toId);
+    assertEquals(3, changes.size());
+    ChangeSet changeSet1 = changes.get(0);
+    final ChangeSet changeSet2 = changes.get(1);
+    final ChangeSet changeSet3 = changes.get(2);
+    assertEquals("dir1 created", changeSet1.getDescription());
+    assertEquals("new file added", changeSet2.getDescription());
+    assertEquals("file4.txt added", changeSet3.getDescription());
+
+    changes = runLog(null, toId);
+    assertEquals(3, changes.size());
+    changeSet1 = changes.get(2);
+    assertEquals("file4.txt added", changeSet1.getDescription());
+  }
+
+  public void changeset_parents() throws VcsException, IOException {
+    setRepository("mercurial-tests/testData/rep2", true);
+    List<ChangeSet> changes = runLog("6eeb8974fe67", "6eeb8974fe67");
+    assertEquals(1, changes.size());
+    ChangeSet cs = changes.get(0);
+    assertNotNull(cs.getParents());
+    assertEquals(2, cs.getParents().size());
+    assertTrue(cs.getParents().contains(new ChangeSetRevision("1:a3d15477d297")));
+    assertTrue(cs.getParents().contains(new ChangeSetRevision("3:2538c02bafeb")));
+  }
+
+  public void parse_multiline_description() throws VcsException, IOException {
+    List<ChangeSet> changes = runLog("9babcf2d5705", "9c6a6b4aede0");
+    assertEquals(1, changes.size());
+    assertEquals("Multiline description\n" +
+            "description with new\n" +
+            "lines\n" +
+            "aaaa\n" +
+            "bbb", changes.get(0).getDescription());
+  }
+
+
+  public void log_result_should_contain_changed_files() throws Exception {
+    final String fromId = "7209b1f1d793";
+    final String toId = "b06a290a363b";
+    List<ChangeSet> csets = runLog(fromId, toId);
+    assertEquals(3, csets.size());
+
+    List<ModifiedFile> files = csets.get(0).getModifiedFiles();
+    assertEquals(1, files.size());
+    ModifiedFile file = files.get(0);
+    assertEquals(ModifiedFile.Status.ADDED, file.getStatus());
+    assertEquals("dir1/file4.txt", file.getPath());
+
+    files = csets.get(1).getModifiedFiles();
+    assertEquals(1, files.size());
+    file = files.get(0);
+    assertEquals(ModifiedFile.Status.REMOVED, file.getStatus());
+    assertEquals("dir1/file4.txt", file.getPath());
+
+    files = csets.get(2).getModifiedFiles();
+    assertEquals(1, files.size());
+    file = files.get(0);
+    assertEquals(ModifiedFile.Status.MODIFIED, file.getStatus());
+    assertEquals("dir1/file3.txt", file.getPath());
+  }
+
+
+  public void log_results_should_have_parents() throws Exception {
+    setRepository("mercurial-tests/testData/rep2", true);
+    List<ChangeSet> csets = runLog("e0ad3ddde5aa", "df04faa7575a");
+    for (ChangeSet cset : csets) {
+      assertFalse(cset.getParents().isEmpty());
+    }
+
+    Map<String, ChangeSet> csetMap = createChangeSetMap(csets);
+
+    //see testData/README
+    checkParents(csetMap.get("e0ad3ddde5aa"), "0000000000000000000000000000000000000000");
+    checkParents(csetMap.get("a3d15477d297"), "e0ad3ddde5aa");
+    checkParents(csetMap.get("db8a04d262f3"), "e0ad3ddde5aa");
+    checkParents(csetMap.get("2538c02bafeb"), "db8a04d262f3");
+    checkParents(csetMap.get("6eeb8974fe67"), "a3d15477d297", "2538c02bafeb");
+    checkParents(csetMap.get("b4937926e2e3"), "6eeb8974fe67");
+    checkParents(csetMap.get("6066b677d026"), "b4937926e2e3");
+    checkParents(csetMap.get("d6eaab231902"), "6eeb8974fe67");
+    checkParents(csetMap.get("b6e2d176fe8e"), "6066b677d026", "d6eaab231902");
+  }
+
+
+  private Map<String, ChangeSet> createChangeSetMap(@NotNull List<ChangeSet> csets) {
+    Map<String, ChangeSet> result = new HashMap<String, ChangeSet>();
+    for (ChangeSet cset : csets) {
+      result.put(cset.getId(), cset);
+    }
+    return result;
+  }
+
+
+  private void checkParents(@NotNull final ChangeSet cset, String... parents) {
+    assertEquals(parents.length, cset.getParents().size());
+    int i = 0;
+    for (ChangeSetRevision parent : cset.getParents()) {
+      assertEquals(parents[i], parent.getId());
+      i++;
+    }
+  }
+
+
+  private List<ChangeSet> runLog(final String fromId, final String toId) throws IOException, VcsException {
+    return runCommand(new CommandExecutor<List<ChangeSet>>() {
+      public List<ChangeSet> execute(@NotNull final Settings settings, @NotNull File workingDir) throws VcsException {
+        LogCommand lc = new LogCommand(settings, workingDir, myTemplateFile);
+        lc.setFromRevId(fromId);
+        lc.setToRevId(toId);
+        return lc.execute();
+      }
+    });
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommandTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommandTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,48 +1,48 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.Test;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * @author Pavel.Sher
- */
-@Test
-public class PushCommandTest extends BaseCommandTestCase {
-  public void hide_private_data() throws VcsException, IOException {
-    setRepository("http://some.host.com", false);
-    setUsername("user1");
-    final String password = "pwd1";
-    setPassword(password);
-
-    try {
-      runCommand(new CommandExecutor<Boolean>() {
-        public Boolean execute(@NotNull final Settings settings, @NotNull File workingDir) throws VcsException {
-          PushCommand cmd = new PushCommand(settings, workingDir);
-          cmd.execute();
-          return null;
-        }
-      });
-    } catch (VcsException e) {
-      assertFalse(e.getMessage().contains(password));
-    }
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Pavel.Sher
+ */
+@Test
+public class PushCommandTest extends BaseCommandTestCase {
+  public void hide_private_data() throws VcsException, IOException {
+    setRepository("http://some.host.com", false);
+    setUsername("user1");
+    final String password = "pwd1";
+    setPassword(password);
+
+    try {
+      runCommand(new CommandExecutor<Boolean>() {
+        public Boolean execute(@NotNull final Settings settings, @NotNull File workingDir) throws VcsException {
+          PushCommand cmd = new PushCommand(settings, workingDir);
+          cmd.execute();
+          return null;
+        }
+      });
+    } catch (VcsException e) {
+      assertFalse(e.getMessage().contains(password));
+    }
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -1,66 +1,66 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-@Test
-public class StatusCommandTest extends BaseCommandTestCase {
-  public void testAddedFile() throws IOException, VcsException {
-    setRepository("mercurial-tests/testData/rep1", true);
-    List<ModifiedFile> files = runStatus("9875b412a788", "1d446e82d356");
-    assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.ADDED, md.getStatus());
-    assertEquals("dir1/file3.txt", md.getPath().replace(File.separatorChar, '/'));
-  }
-
-  public void testRemovedFile() throws IOException, VcsException {
-    setRepository("mercurial-tests/testData/rep1", true);
-    List<ModifiedFile> files = runStatus("7209b1f1d793", "9522278aa38d");
-    assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.REMOVED, md.getStatus());
-    assertEquals("dir1/file4.txt", md.getPath().replace(File.separatorChar, '/'));
-  }
-
-  public void testModifiedFile() throws IOException, VcsException {
-    setRepository("mercurial-tests/testData/rep1", true);
-    List<ModifiedFile> files = runStatus("9522278aa38d", "b06a290a363b");
-    assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.MODIFIED, md.getStatus());
-    assertEquals("dir1/file3.txt", md.getPath().replace(File.separatorChar, '/'));
-  }
-
-  private List<ModifiedFile> runStatus(final String fromId, final String toId) throws IOException, VcsException {
-    return runCommand(new CommandExecutor<List<ModifiedFile>>() {
-      public List<ModifiedFile> execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
-        StatusCommand st = new StatusCommand(settings, workingDir);
-        st.setFromRevId(fromId);
-        st.setToRevId(toId);
-        return st.execute();
-      }
-    });
-  }
-
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@Test
+public class StatusCommandTest extends BaseCommandTestCase {
+  public void testAddedFile() throws IOException, VcsException {
+    setRepository("mercurial-tests/testData/rep1", true);
+    List<ModifiedFile> files = runStatus("9875b412a788", "1d446e82d356");
+    assertEquals(1, files.size());
+    ModifiedFile md = files.get(0);
+    assertEquals(ModifiedFile.Status.ADDED, md.getStatus());
+    assertEquals("dir1/file3.txt", md.getPath().replace(File.separatorChar, '/'));
+  }
+
+  public void testRemovedFile() throws IOException, VcsException {
+    setRepository("mercurial-tests/testData/rep1", true);
+    List<ModifiedFile> files = runStatus("7209b1f1d793", "9522278aa38d");
+    assertEquals(1, files.size());
+    ModifiedFile md = files.get(0);
+    assertEquals(ModifiedFile.Status.REMOVED, md.getStatus());
+    assertEquals("dir1/file4.txt", md.getPath().replace(File.separatorChar, '/'));
+  }
+
+  public void testModifiedFile() throws IOException, VcsException {
+    setRepository("mercurial-tests/testData/rep1", true);
+    List<ModifiedFile> files = runStatus("9522278aa38d", "b06a290a363b");
+    assertEquals(1, files.size());
+    ModifiedFile md = files.get(0);
+    assertEquals(ModifiedFile.Status.MODIFIED, md.getStatus());
+    assertEquals("dir1/file3.txt", md.getPath().replace(File.separatorChar, '/'));
+  }
+
+  private List<ModifiedFile> runStatus(final String fromId, final String toId) throws IOException, VcsException {
+    return runCommand(new CommandExecutor<List<ModifiedFile>>() {
+      public List<ModifiedFile> execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
+        StatusCommand st = new StatusCommand(settings, workingDir);
+        st.setFromRevId(fromId);
+        st.setToRevId(toId);
+        return st.execute();
+      }
+    });
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommandTest.java	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,25 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.*;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import junit.framework.TestCase;
+import org.testng.annotations.Test;
+
+import java.io.File;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class VersionCommandTest extends TestCase {
+
+  public void test() throws Exception {
+    VcsRootImpl root = new VcsRootBuilder().repository("some/repository").build();
+    ServerPluginConfig config = new ServerPluginConfigBuilder().build();
+    Settings settings = new Settings(new ServerHgPathProvider(config), root);
+    VersionCommand versionCommand = new VersionCommand(settings, new File(".."));
+    HgVersion version = versionCommand.execute();
+    assertNotNull(version);
+  }
+
+}
--- a/mercurial-tests/src/testng.xml	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/src/testng.xml	Wed Jan 11 13:22:42 2012 +0400
@@ -1,18 +1,23 @@
-<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
-<suite name="Mercurial Suite">
-  <test name="Mercurial Tests">
-    <classes>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.BaseCommandTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CloneCommandTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommandTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.StatusCommandTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.PushCommandTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.IdentifyCommandTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupportTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentSideCheckoutTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SettingsTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommandResultTest"/>
-    </classes>
-  </test>
-</suite>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="Mercurial Suite">
+  <test name="Mercurial Tests">
+    <classes>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.BaseCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CloneCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.StatusCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.PushCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.IdentifyCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResultTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupportTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentSideCheckoutTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentSideCheckoutWithSubreposTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SettingsTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersionTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgPathProviderTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.DagFeaturesTest"/>
+    </classes>
+  </test>
+</suite>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/README	Wed Jan 11 13:22:42 2012 +0400
@@ -0,0 +1,63 @@
+rep1 history:
+@   10:9c6a6b4aede0 Multiline description tip
+|
+| o  9:9babcf2d5705 name with space branch name with space
+| |
+| o  8:04c3ae4c6312 file modified test_branch
+| |
+| o  7:376dcf05cd2a new file added in the test_branch test_branch
+|/
+o    6:b9deb9a1c6f4 files with spaces added
+|
+o    5:1d2cc6f3bc29 modified in subdir
+|
+o    4:b06a290a363b file modified
+|
+o    3:9522278aa38d file removed
+|
+o    2:7209b1f1d793 file4.txt added
+|
+o    1:1d446e82d356 new file added
+|
+o    0:9875b412a788 dir1 created
+
+
+rep2 history:
+
+@    18:df04faa7575a merge personal-branch tip
+|\
+| o  17:9ec402c74298 another change to file.txt in personal-branch (personal-branch)
+| |
+o |  16:505c5b9d01e6 change file.txt back
+| |
+| o  15:96b78d73081d change file.txt in personal-branch (personal-branch)
+| |
+o |  14:78e67807f916 change file.txt
+| |
+| o  13:dec47d2d49bf create personal branch (personal-branch)
+|/
+o    12:1e620196c4b6 merge with test branch
+|\
+| o  11:48177654181c dir6 trunk
+| |
+o |  10:fc524efc2bc4 dir6 (test)
+| |
+o |  9:8c44244d6645 dir5 (test)
+|/
+o    8:b6e2d176fe8e merge with conflict
+|\
+| o  7:d6eaab231902 branch dir4
+| |
+o |  6:6066b677d026 file41
+| |
+o |  5:b4937926e2e3 dir4
+|/
+o    4:6eeb8974fe67 merge
+|\
+| o  3:2538c02bafeb dir2
+| |
+| o  2:db8a04d262f3 dir1
+| |
+o |  1:a3d15477d297 dir3
+|/
+o    0:e0ad3ddde5aa file.txt
--- a/mercurial-tests/testData/rep2/hg/branchheads.cache	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/testData/rep2/hg/branchheads.cache	Wed Jan 11 13:22:42 2012 +0400
@@ -1,3 +1,4 @@
-48177654181c50307bfb333f824126f54858cf53 11
-48177654181c50307bfb333f824126f54858cf53 default
+df04faa7575acbe8e4cb112003e7db352a7589bf 18
+df04faa7575acbe8e4cb112003e7db352a7589bf default
 fc524efc2bc481e22365ab1452a41197060dbd9b test
+9ec402c74298567d252e70bd4ef2be8235be62cc personal-branch
Binary file mercurial-tests/testData/rep2/hg/dirstate has changed
Binary file mercurial-tests/testData/rep2/hg/store/00changelog.i has changed
Binary file mercurial-tests/testData/rep2/hg/store/00manifest.i has changed
Binary file mercurial-tests/testData/rep2/hg/store/data/file.txt.i has changed
Binary file mercurial-tests/testData/rep2/hg/store/undo has changed
--- a/mercurial-tests/testData/rep2/hg/tags.cache	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial-tests/testData/rep2/hg/tags.cache	Wed Jan 11 13:22:42 2012 +0400
@@ -1,2 +1,2 @@
-12 1e620196c4b6ee23219fb7bd460afde52b239805
+18 df04faa7575acbe8e4cb112003e7db352a7589bf
 
Binary file mercurial-tests/testData/rep2/hg/undo.dirstate has changed
--- a/mercurial.ipr	Wed Jan 11 12:50:45 2012 +0400
+++ b/mercurial.ipr	Wed Jan 11 13:22:42 2012 +0400
@@ -310,7 +310,7 @@
       <SplitterProportionsDataImpl />
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.5" project-jdk-type="JavaSDK" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK" />
   <component name="ResourceManagerContainer">
     <option name="myResourceBundles">
       <value>