changeset 433:2617dcc70c15

Merge branch Faradi-7.0.x
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Tue, 15 May 2012 10:33:21 +0400
parents 9a2b6a7a3381 (diff) 4a76645fe087 (current diff)
children a40ae1cfb67c
files mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java
diffstat 62 files changed, 1353 insertions(+), 671 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Tue May 15 10:33:21 2012 +0400
@@ -7,4 +7,5 @@
   <bean id="hgDetector" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgDetector" />
   <bean id="pluginConfig" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentPluginConfigImpl"/>
   <bean id="mirrorManager" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerImpl" />
+  <bean id="mirrorCleaner" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentMirrorCleaner" />
 </beans>
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentHgPathProvider.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentHgPathProvider.java	Tue May 15 10:33:21 2012 +0400
@@ -1,7 +1,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
 import jetbrains.buildServer.agent.BuildAgentConfiguration;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import jetbrains.buildServer.parameters.ProcessingResult;
 import jetbrains.buildServer.parameters.ValueResolver;
 import org.jetbrains.annotations.NotNull;
@@ -19,8 +19,8 @@
   }
 
 
-  public String getHgPath(@NotNull final Settings settings) {
-    String pathFromRoot = settings.getHgPath();
+  public String getHgPath(@NotNull final HgVcsRoot root) {
+    String pathFromRoot = root.getHgPath();
     return resolve(pathFromRoot);
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleaner.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,61 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.agent.DirectoryCleanersProvider;
+import jetbrains.buildServer.agent.DirectoryCleanersProviderContext;
+import jetbrains.buildServer.agent.DirectoryCleanersRegistry;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.vcs.VcsRootEntry;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author dmitry.neverov
+ */
+public class AgentMirrorCleaner implements DirectoryCleanersProvider {
+
+  private final static Logger ourLog = Logger.getInstance(AgentMirrorCleaner.class.getName());
+  private final MirrorManager myMirrorManager;
+
+  public AgentMirrorCleaner(@NotNull final MirrorManager mirrorManager) {
+    myMirrorManager = mirrorManager;
+  }
+
+  @NotNull
+  public String getCleanerName() {
+    return "Mercurial mirrors clean";
+  }
+
+  public void registerDirectoryCleaners(@NotNull DirectoryCleanersProviderContext context,
+                                        @NotNull DirectoryCleanersRegistry registry) {
+    Set<String> repositoriesUsedInBuild = getRunningBuildRepositories(context);
+    for (Map.Entry<String, File> entry : myMirrorManager.getMappings().entrySet()) {
+      String repository = entry.getKey();
+      File mirror = entry.getValue();
+      if (!repositoriesUsedInBuild.contains(repository)) {
+        ourLog.debug("Register cleaner for mirror " + mirror.getAbsolutePath());
+        registry.addCleaner(mirror, new Date(myMirrorManager.getLastUsedTime(mirror)));
+      }
+    }
+  }
+
+  private Set<String> getRunningBuildRepositories(@NotNull DirectoryCleanersProviderContext context) {
+    Set<String> repositories = new HashSet<String>();
+    for (VcsRootEntry entry : context.getRunningBuild().getVcsRootEntries()) {
+      VcsRoot root = entry.getVcsRoot();
+      HgVcsRoot hgRoot = new HgVcsRoot(root);
+      AuthSettings auth = hgRoot.getAuthSettings();
+      ourLog.debug("Repository " + auth.getRepositoryUrlWithHiddenPassword(hgRoot.getRepository()) +
+              " is used in the build, its mirror won't be cleaned");
+      repositories.add(hgRoot.getRepository());
+    }
+    return repositories;
+  }
+}
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Tue May 15 10:33:21 2012 +0400
@@ -4,7 +4,8 @@
 import jetbrains.buildServer.agent.BuildProgressLogger;
 import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException;
 import jetbrains.buildServer.vcs.IncludeRule;
 import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsRoot;
@@ -23,7 +24,7 @@
 
   private final AgentPluginConfig myConfig;
   private final MirrorManager myMirrorManager;
-  private final Settings mySettings;
+  private final HgVcsRoot myRoot;
   private final AuthSettings myAuthSettings;
   private final String myHgPath;
   private final String myToVersion;
@@ -39,9 +40,9 @@
                                      @NotNull final AgentRunningBuild build) {
     myConfig = pluginConfig;
     myMirrorManager = mirrorManager;
-    mySettings = new Settings(hgPathProvider, root);
-    myAuthSettings = mySettings.getAuthSettings();
-    myHgPath = mySettings.getHgCommandPath();
+    myRoot = new HgVcsRoot(root);
+    myAuthSettings = myRoot.getAuthSettings();
+    myHgPath = hgPathProvider.getHgPath(myRoot);
     myToVersion = toVersion;
     myLogger = build.getBuildLogger();
     myUseLocalMirrors = myConfig.isUseLocalMirrors(build);
@@ -53,9 +54,9 @@
     try {
       checkRuleIsValid(rule);
       if (myUseLocalMirrors)
-        updateLocalMirror(mySettings.getRepository(), myToVersion);
+        updateLocalMirror(myRoot.getRepository(), myToVersion);
       updateRepository(workingDir);
-      updateWorkingDir(workingDir, myToVersion, mySettings.getRepository());
+      updateWorkingDir(workingDir, myToVersion, myRoot.getRepository());
     } catch (Exception e) {
       throwVcsException(e);
     }
@@ -75,7 +76,7 @@
       mirrorRepo.doClone().fromRepository(repositoryUrl)
               .setUpdateWorkingDir(false)
               .setUsePullProtocol(false)
-              .useUncompressedTransfer(mySettings.isUncompressedTransfer())
+              .useUncompressedTransfer(myRoot.isUncompressedTransfer())
               .call();
       myLogger.message("Clone successfully finished");
     } else {
@@ -94,7 +95,7 @@
 
 
   private void updateRepository(@NotNull File workingDir) throws VcsException, IOException {
-    String repositoryUrl = getDefaultPullUrl(mySettings, myUseLocalMirrors);
+    String repositoryUrl = getDefaultPullUrl(myRoot, myUseLocalMirrors);
     HgRepo repo = new HgRepo(workingDir, myHgPath, myAuthSettings);
     myLogger.message("Update repository " + workingDir.getAbsolutePath());
     if (repo.isEmpty()) {//can do clone only in empty dir
@@ -102,21 +103,25 @@
       repo.doClone().fromRepository(repositoryUrl)
               .setUsePullProtocol(false)
               .setUpdateWorkingDir(false)
-              .useUncompressedTransfer(!myUseLocalMirrors && mySettings.isUncompressedTransfer())
+              .useUncompressedTransfer(!myUseLocalMirrors && myRoot.isUncompressedTransfer())
               .call();
-      repo.setDefaultPath(mySettings.getRepository());
+      repo.setDefaultPath(myRoot.getRepository());
       myLogger.message("Repository successfully cloned");
     } else {
       if (!repo.isValidRepository())
         repo.init().call();
-      repo.setDefaultPath(mySettings.getRepository());
+      repo.setDefaultPath(myRoot.getRepository());
       if (repo.containsRevision(myToVersion)) {
         myLogger.message("Repository already contains revision " + myToVersion);
       } else {
         myLogger.message("Start pulling changes from " + (myUseLocalMirrors ? "local mirror " : "") + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl));
-        repo.pull().fromRepository(repositoryUrl)
-                .withTimeout(myPullTimeout)
-                .call();
+        try {
+          repo.pull().fromRepository(repositoryUrl)
+                  .withTimeout(myPullTimeout)
+                  .call();
+        } catch (UnrelatedRepositoryException e) {
+          throw new UnrelatedRepositoryException(myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl), workingDir);
+        }
         myLogger.message("Changes successfully pulled");
       }
     }
@@ -185,12 +190,12 @@
   }
 
 
-  private String getDefaultPullUrl(Settings settings, boolean useLocalMirror) throws IOException {
+  private String getDefaultPullUrl(HgVcsRoot root, boolean useLocalMirror) throws IOException {
     if (useLocalMirror) {
-      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepository());
+      File mirrorDir = myMirrorManager.getMirrorDir(root.getRepository());
       return mirrorDir.getCanonicalPath();
     } else {
-      return settings.getRepository();
+      return root.getRepository();
     }
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgFileUtil.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,36 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.util.FileUtil;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author dmitry.neverov
+ */
+public final class HgFileUtil {
+
+  private final static String TEMP_DIR_PREFIX = "hg";
+
+  private HgFileUtil() {
+  }
+
+  /**
+   * Create a temp dir with short name
+   * @return created dir
+   * @throws IOException in case of I/O error
+   */
+  public static File createTempDir() throws IOException {
+    File parentDir = new File(FileUtil.getTempDirectory());
+    int suffix = 0;
+    File dir;
+    do {
+      suffix++;
+      dir = new File(parentDir, TEMP_DIR_PREFIX + suffix);
+    } while (!dir.createNewFile());
+    dir.delete();
+    dir.mkdir();
+    return dir;
+  }
+
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgPathProvider.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgPathProvider.java	Tue May 15 10:33:21 2012 +0400
@@ -1,6 +1,6 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import org.jetbrains.annotations.NotNull;
 
 /**
@@ -8,6 +8,6 @@
  */
 public interface HgPathProvider {
 
-  String getHgPath(@NotNull Settings settings);
+  String getHgPath(@NotNull HgVcsRoot root);
 
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Tue May 15 10:33:21 2012 +0400
@@ -7,9 +7,7 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import static com.intellij.openapi.util.io.FileUtil.delete;
 import static java.util.Collections.emptyMap;
@@ -95,6 +93,20 @@
     return isEmptyDir(myWorkingDir);
   }
 
+  @NotNull
+  public List<String> listFiles() throws VcsException {
+    List<FileStatus> fileStatuses = status()
+            .fromRevision("tip")
+            .toRevision("tip")
+            .hideStatus()
+            .showAllFiles()
+            .call();
+    List<String> files = new ArrayList<String>(fileStatuses.size());
+    for (FileStatus fileStatus : fileStatuses)
+      files.add(fileStatus.getPath());
+    return files;
+  }
+
   public String getWorkingDirRevision() throws VcsException {
     return id().inLocalRepository().call();
   }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java	Tue May 15 10:33:21 2012 +0400
@@ -26,6 +26,8 @@
   @NotNull
   public List<File> getMirrors();
 
+  public long getLastUsedTime(@NotNull final File mirrorDir);
+
   /**
    * Forget specified dir. After call to this method with non-empty dir,
    * all urls which were mapped to this dir will be mapped to another.
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerImpl.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerImpl.java	Tue May 15 10:33:21 2012 +0400
@@ -56,6 +56,7 @@
     if (result == null) {
       result = createDirFor(url);
     }
+    updateLastUsedTime(result);
     return result;
   }
 
@@ -319,6 +320,44 @@
     return sb.toString();
   }
 
+  public long getLastUsedTime(@NotNull final File mirrorDir) {
+    File dotHg = new File(mirrorDir, ".hg");
+    File timestamp = new File(dotHg, "timestamp");
+    if (timestamp.exists()) {
+      try {
+        List<String> lines = FileUtil.readFile(timestamp);
+        if (lines.isEmpty())
+          return mirrorDir.lastModified();
+        else
+          return Long.valueOf(lines.get(0));
+      } catch (IOException e) {
+        return mirrorDir.lastModified();
+      }
+    } else {
+      return mirrorDir.lastModified();
+    }
+  }
+
+  private void updateLastUsedTime(@NotNull final File dir) {
+    File dotHg = new File(dir, ".hg");
+    //create timestamp only if .hg exist, otherwise subsequent clone in this directory will
+    //fail since directory is not empty
+    if (!dotHg.exists())
+      return;
+
+    lockDir(dir);
+    try {
+      File timestamp = new File(dotHg, "timestamp");
+      if (!timestamp.exists())
+        timestamp.createNewFile();
+      FileUtil.writeFileAndReportErrors(timestamp, String.valueOf(System.currentTimeMillis()));
+    } catch (IOException e) {
+      LOG.error("Error while updating timestamp in " + dir.getAbsolutePath(), e);
+    } finally {
+      unlockDir(dir);
+    }
+  }
+
   final static class StandartHash implements HashCalculator {
     public long calc(String value) {
       return Hash.calc(value);
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Tue May 15 10:33:21 2012 +0400
@@ -16,7 +16,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
@@ -90,7 +90,7 @@
 
   private File createTmpDir() throws VcsException {
     try {
-      return FileUtil.createTempDirectory("mercurial", "catresult");
+      return HgFileUtil.createTempDir();
     } catch (IOException e) {
       throw new VcsException("Unable to create temporary directory");
     }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Tue May 15 10:33:21 2012 +0400
@@ -29,7 +29,7 @@
   @NotNull private Date myTimestamp;
   private String myDescription;
   private List<ChangeSetRevision> myParents = new ArrayList<ChangeSetRevision>();
-  private List<ModifiedFile> myModifiedFiles = new ArrayList<ModifiedFile>();
+  private List<FileStatus> myModifiedFiles = new ArrayList<FileStatus>();
 
   public ChangeSet(final int revNumber, @NotNull final String id) {
     super(revNumber, id);
@@ -102,12 +102,12 @@
     return getParents().isEmpty();
   }
 
-  public void setModifiedFiles(@NotNull final List<ModifiedFile> files) {
+  public void setModifiedFiles(@NotNull final List<FileStatus> files) {
     myModifiedFiles = files;
   }
 
   @NotNull
-  public List<ModifiedFile> getModifiedFiles() {
+  public List<FileStatus> getModifiedFiles() {
     return myModifiedFiles;
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Tue May 15 10:33:21 2012 +0400
@@ -1,17 +1,15 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import com.intellij.execution.process.ProcessNotCreatedException;
 import com.intellij.openapi.diagnostic.Logger;
 import jetbrains.buildServer.ExecResult;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.ConnectionRefusedException;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnknownFileException;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnknownRevisionException;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.*;
 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.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -34,16 +32,19 @@
   //e.g. pull command in hg 2.1 exits with 1 if no new changes were pulled.
   private static final Set<Integer> ERROR_EXIT_CODES = setOf(-1, 255);
 
+  private static final String MERCURIAL_NOT_FOUND_MESSAGE_PREFIX = "Cannot run program \"";
+  private static final String MERCURIAL_NOT_FOUND_MESSAGE_SUFFIX1 = "No such file or directory";
+  private static final String MERCURIAL_NOT_FOUND_MESSAGE_SUFFIX2 = "The system cannot find the file specified";
+
   private final Logger myLogger;
   private final String myCommand;
   private final ExecResult myDelegate;
   private final Set<String> myPrivateData;
 
-  public CommandResult(@NotNull Logger logger, @NotNull String command, @NotNull ExecResult execResult) {
-    this(logger, command, execResult, Collections.<String>emptySet());
-  }
-
-  public CommandResult(@NotNull Logger logger, @NotNull String command, @NotNull ExecResult execResult, @NotNull Set<String> privateData) {
+  public CommandResult(@NotNull Logger logger,
+                       @NotNull String command,
+                       @NotNull ExecResult execResult,
+                       @NotNull Set<String> privateData) {
     myLogger = logger;
     myCommand = command;
     myDelegate = execResult;
@@ -77,9 +78,32 @@
     myLogger.warn(message);
     if (hasImportantException())
       myLogger.error("Error during executing '" + getCommand() + "'", getException());
+    throwVcsException(message);
+  }
+
+  private void throwVcsException(@NotNull String message) throws VcsException {
+    //noinspection ThrowableResultOfMethodCallIgnored
+    Throwable e = getException();
+    if (isMercurialNotFoundException(e)) {
+      assert e != null;
+      throw new MercurialNotFoundException(myCommand, e);
+    }
     throw new VcsException(message);
   }
 
+  private boolean isMercurialNotFoundException(@Nullable Throwable e) {
+    return e instanceof ProcessNotCreatedException &&
+           e.getCause() instanceof IOException &&
+           isMercurialNotFoundErrorMessage(e.getMessage());
+  }
+
+  private boolean isMercurialNotFoundErrorMessage(@Nullable String message) {
+    return message != null &&
+           message.startsWith(MERCURIAL_NOT_FOUND_MESSAGE_PREFIX) &&
+           (message.endsWith(MERCURIAL_NOT_FOUND_MESSAGE_SUFFIX1) ||
+            message.endsWith(MERCURIAL_NOT_FOUND_MESSAGE_SUFFIX2));
+  }
+
   private void logStderr(String stderr) {
     myLogger.warn("Error output produced by: " + getCommand());
     myLogger.warn(stderr);
@@ -96,6 +120,7 @@
   }
 
   private boolean isFailure() {
+    //noinspection ThrowableResultOfMethodCallIgnored
     return getException() != null || isErrorExitCode();
   }
 
@@ -114,6 +139,7 @@
   }
 
   private boolean hasImportantException() {
+    //noinspection ThrowableResultOfMethodCallIgnored
     Throwable exception = getException();
     return exception instanceof NullPointerException;
   }
@@ -130,6 +156,7 @@
 
   @Nullable
   private String getExceptionMessage() {
+    //noinspection ThrowableResultOfMethodCallIgnored
     Throwable exception = getException();
     if (exception == null)
       return null;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/FileStatus.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+/**
+ * Status of a file in a repository
+ */
+public class FileStatus {
+
+  @NotNull private final Status myStatus;
+  @NotNull private final String myPath;
+
+  public FileStatus(@NotNull final Status status, @NotNull final String path) {
+    myStatus = status;
+    myPath = path;
+  }
+
+  /**
+   * @return status of a file
+   */
+  @NotNull
+  public Status getStatus() {
+    return myStatus;
+  }
+
+  /**
+   * @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 FileStatus that = (FileStatus) o;
+
+    return myPath.equals(that.myPath) && myStatus == that.myStatus;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myStatus.hashCode();
+    result = 31 * result + myPath.hashCode();
+    return result;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/HgVcsRoot.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,160 @@
+/*
+ * 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.util.StringUtil;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Represents mercurial VCS root
+ */
+public class HgVcsRoot implements VcsRoot {
+
+  private static final String DEFAULT_BRANCH_NAME = "default";
+
+  private final VcsRoot myRoot;
+  private final String myRepository;
+  private final String myHgCommandPath;
+  private final String myBranchName;
+  private final boolean myUncompressedTransfer;
+  private final String myCustomClonePath;
+  private final String myUserForTag;
+  private final AuthSettings myAuthSettings;
+  private File myCustomWorkingDir;
+
+  public HgVcsRoot(@NotNull final VcsRoot vcsRoot) {
+    myRoot = vcsRoot;
+    myRepository = getProperty(Constants.REPOSITORY_PROP);
+    myHgCommandPath = getProperty(Constants.HG_COMMAND_PATH_PROP);
+    myBranchName = getProperty(Constants.BRANCH_NAME_PROP);
+    myCustomClonePath = getProperty(Constants.SERVER_CLONE_PATH_PROP);
+    myUncompressedTransfer = "true".equals(getProperty(Constants.UNCOMPRESSED_TRANSFER));
+    myUserForTag = getProperty(Constants.USER_FOR_TAG);
+    myAuthSettings = new AuthSettings(getProperty(Constants.USERNAME), getProperty(Constants.PASSWORD));
+  }
+
+  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;
+  }
+
+  public boolean isUncompressedTransfer() {
+    return myUncompressedTransfer;
+  }
+
+  /**
+   * @return path to hg command as it is set in VCS root settings
+   */
+  public String getHgPath() {
+    return myHgCommandPath;
+  }
+
+  @Nullable
+  public String getUserForTag() {
+    return myUserForTag;
+  }
+
+  public String getRepositoryUrlWithCredentials() {
+    return myAuthSettings.getRepositoryUrlWithCredentials(myRepository);
+  }
+
+  /**
+   * 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 AuthSettings getAuthSettings() {
+    return myAuthSettings;
+  }
+
+
+
+  public String getVcsName() {
+    return myRoot.getVcsName();
+  }
+
+  public String getProperty(String propertyName) {
+    return myRoot.getProperty(propertyName);
+  }
+
+  public String getProperty(String propertyName, String defaultValue) {
+    return myRoot.getProperty(propertyName, defaultValue);
+  }
+
+  public Map<String, String> getProperties() {
+    return myRoot.getProperties();
+  }
+
+  public String convertToString() {
+    return myRoot.convertToString();
+  }
+
+  public String convertToPresentableString() {
+    return myRoot.convertToPresentableString();
+  }
+
+  public long getPropertiesHash() {
+    return myRoot.getPropertiesHash();
+  }
+
+  public long getVcsRepositoryPropertiesHash() {
+    return myRoot.getVcsRepositoryPropertiesHash();
+  }
+
+  public String getName() {
+    return myRoot.getName();
+  }
+
+  public long getId() {
+    return myRoot.getId();
+  }
+
+  public Map<String, String> getPublicProperties() {
+    return myRoot.getPublicProperties();
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Tue May 15 10:33:21 2012 +0400
@@ -31,6 +31,8 @@
 import java.text.SimpleDateFormat;
 import java.util.*;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Status.makeStatus;
+
 public class LogCommand extends VcsRootCommand {
 
   private final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000";
@@ -216,8 +218,8 @@
   }
 
 
-  private List<ModifiedFile> getModifiedFiles(@NotNull final Element logEntry) {
-    List<ModifiedFile> result = new ArrayList<ModifiedFile>();
+  private List<FileStatus> getModifiedFiles(@NotNull final Element logEntry) {
+    List<FileStatus> result = new ArrayList<FileStatus>();
     Element paths = logEntry.getChild("paths");
     if (paths == null)
       return result;
@@ -229,24 +231,10 @@
   }
 
 
-  private ModifiedFile getModifiedFile(@NotNull final Element path) {
+  private FileStatus 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;
-    }
+    return new FileStatus(makeStatus(action), filePath);
   }
 
   private void assignTrivialParents(final @NotNull List<ChangeSet> csets) throws VcsException {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ModifiedFile.java	Tue May 15 10:21:05 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/*
- * 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/Settings.java	Tue May 15 10:21:05 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/*
- * 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.util.StringUtil;
-import jetbrains.buildServer.vcs.VcsRoot;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-
-/**
- * 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;
-  private final String myUserForTag;
-  private final AuthSettings myAuthSettings;
-
-  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));
-    myUserForTag = vcsRoot.getProperty(Constants.USER_FOR_TAG);
-    myAuthSettings = new AuthSettings(myUsername, myPassword);
-  }
-
-  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;
-  }
-
-  @Nullable
-  public String getUserForTag() {
-    return myUserForTag;
-  }
-
-  public String getRepositoryUrlWithCredentials() {
-    return myAuthSettings.getRepositoryUrlWithCredentials(myRepository);
-  }
-
-  /**
-   * 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 AuthSettings getAuthSettings() {
-    return myAuthSettings;
-  }
-
-  public static boolean isValidRepository(File dir) {
-    // need better way to check that repository copy is ok
-    return dir.isDirectory() && new File(dir, ".hg").isDirectory();
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Status.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,51 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;
+
+/**
+ * File status, see 'hg help status'.
+ */
+public enum Status {
+
+  ADDED("added"),
+  MODIFIED("modified"),
+  REMOVED("removed"),
+  CLEAN("clean"),
+  MISSING("missing"),
+  NOT_TRACKED("not tracked"),
+  IGNORED("ignored"),
+  UNKNOWN("unknown");
+
+  private final String myName;
+
+  Status(@NotNull String name) {
+    myName = name;
+  }
+
+  @NotNull
+  public String getName() {
+    return myName;
+  }
+
+  public static Status makeStatus(@NotNull final String s) {
+    if (isEmpty(s))
+      return UNKNOWN;
+    return makeStatus(s.charAt(0));
+  }
+
+  public static Status makeStatus(final char c) {
+    switch (c) {
+      case 'A': return ADDED;
+      case 'M': return MODIFIED;
+      case 'R': return REMOVED;
+      case 'C': return CLEAN;
+      case '!': return MISSING;
+      case '?': return NOT_TRACKED;
+      case 'I': return IGNORED;
+      case ' ': return ADDED;
+      default : return UNKNOWN;
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Tue May 15 10:33:21 2012 +0400
@@ -23,9 +23,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;
+
 public class StatusCommand extends VcsRootCommand {
   private String myFromId;
   private String myToId;
+  private boolean myShowAllFiles = false;
+  private boolean myHideStatus = false;
 
   public StatusCommand(@NotNull String hgPath, @NotNull File workingDir, @NotNull AuthSettings authSettings) {
     super(hgPath, workingDir, authSettings);
@@ -51,39 +55,64 @@
     return this;
   }
 
-  public List<ModifiedFile> call() throws VcsException {
+  /**
+   * Adds option -A (--all)
+   * @return self
+   */
+  public StatusCommand showAllFiles() {
+    myShowAllFiles = true;
+    return this;
+  }
+
+  /**
+   * Adds option -n (--no-status)
+   * @return self
+   */
+  public StatusCommand hideStatus() {
+    myHideStatus = true;
+    return this;
+  }
+
+  public List<FileStatus> call() throws VcsException {
     GeneralCommandLine cli = createCommandLine();
     cli.addParameter("status");
+    if (myShowAllFiles)
+      cli.addParameter("-A");
+    if (myHideStatus)
+      cli.addParameter("-n");
     cli.addParameter("--rev");
     String from = myFromId;
-    if (from == null) from = "0";
+    if (from == null)
+      from = "0";
     String to = myToId;
-    if (to == null) to = "0";
+    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>();
+  private List<FileStatus> parseFiles(@NotNull String stdout) {
+    List<FileStatus> result = new ArrayList<FileStatus>();
     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));
+    for (String line : lines) {
+      if (isEmpty(line))
+        continue;
+      FileStatus fileStatus = parseLine(line);
+      if (!myHideStatus && fileStatus.getStatus() == Status.UNKNOWN)
+        continue;
+      result.add(fileStatus);
     }
     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;
-    }
+  @NotNull
+  private FileStatus parseLine(@NotNull String line) {
+    if (myHideStatus)
+      return new FileStatus(Status.UNKNOWN, line);
+    char modifier = line.charAt(0);
+    String path = line.substring(2);
+    Status status = Status.makeStatus(modifier);
+    return new FileStatus(status, path);
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java	Tue May 15 10:33:21 2012 +0400
@@ -15,11 +15,6 @@
  */
 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);
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/exception/MercurialNotFoundException.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,15 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public class MercurialNotFoundException extends VcsException {
+
+  public MercurialNotFoundException(@NotNull String failedHgCommand, Throwable cause) {
+    super("'" + failedHgCommand + "' command failed: mercurial executable not found", cause);
+  }
+
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/exception/UnrelatedRepositoryException.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/exception/UnrelatedRepositoryException.java	Tue May 15 10:33:21 2012 +0400
@@ -1,10 +1,20 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception;
 
 import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
 
 /**
  * @author dmitry.neverov
  */
 public class UnrelatedRepositoryException extends VcsException {
 
+  public UnrelatedRepositoryException() {
+    super("Repository is unrelated");
+  }
+
+  public UnrelatedRepositoryException(@NotNull String repositoryUrl, @NotNull File workingDir) {
+    super("Repository " + repositoryUrl + " is unrelated to the repository in " + workingDir.getAbsolutePath());
+  }
 }
--- a/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Tue May 15 10:33:21 2012 +0400
@@ -7,4 +7,6 @@
   <bean id="repoFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.RepoFactory" />
   <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgPathProvider"/>
   <bean id="mirrorManager" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerImpl" />
+  <bean id="hgVcsRootFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVcsRootFactory" />
+  <bean id="testConnection" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgTestConnectionSupport" />
 </beans>
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Cleanup.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Cleanup.java	Tue May 15 10:33:21 2012 +0400
@@ -1,7 +1,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
 import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import jetbrains.buildServer.serverSide.impl.LogUtil;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.VcsManager;
@@ -24,16 +24,13 @@
   private final VcsManager myVcsManager;
   private final MirrorManager myMirrorManager;
   private final PluginConfig myConfig;
-  private final HgPathProvider myHgPathProvider;
 
   public Cleanup(@NotNull final VcsManager vcsManager,
                  @NotNull final MirrorManager mirrorManager,
-                 @NotNull final PluginConfig config,
-                 @NotNull final HgPathProvider hgPathProvider) {
+                 @NotNull final PluginConfig config) {
     myVcsManager = vcsManager;
     myMirrorManager = mirrorManager;
     myConfig = config;
-    myHgPathProvider = hgPathProvider;
   }
 
   public void run() {
@@ -68,18 +65,14 @@
     Map<String, File> mirrorMap = myMirrorManager.getMappings();
     List<File> result = new ArrayList<File>();
     for (VcsRoot root : mercurialVcsRoots()) {
-      File mirrorDir = mirrorMap.get(urlOf(root));
+      HgVcsRoot hgRoot = new HgVcsRoot(root);
+      File mirrorDir = mirrorMap.get(hgRoot.getRepository());
       if (mirrorDir != null)
         result.add(mirrorDir);
     }
     return result;
   }
 
-  private String urlOf(VcsRoot root) {
-    Settings s = new Settings(myHgPathProvider, root);
-    return s.getRepository();
-  }
-
   private Collection<VcsRoot> mercurialVcsRoots() {
     List<VcsRoot> mercurialRoots = new ArrayList<VcsRoot>();
     for (VcsRoot root : myVcsManager.getAllRegisteredVcsRoots()) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgTestConnectionSupport.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,59 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.MercurialNotFoundException;
+import jetbrains.buildServer.vcs.TestConnectionSupport;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author dmitry.neverov
+ */
+public class HgTestConnectionSupport implements TestConnectionSupport {
+
+  private final HgVcsRootFactory myHgVcsRootFactory;
+  private final RepoFactory myRepoFactory;
+  private final MirrorManager myMirrorManager;
+  private final HgPathProvider myHgPathProvider;
+
+  public HgTestConnectionSupport(@NotNull HgVcsRootFactory hgVcsRootFactory,
+                                 @NotNull RepoFactory repoFactory,
+                                 @NotNull MirrorManager mirrorManager,
+                                 @NotNull HgPathProvider hgPathProvider) {
+    myHgVcsRootFactory = hgVcsRootFactory;
+    myRepoFactory = repoFactory;
+    myMirrorManager = mirrorManager;
+    myHgPathProvider = hgPathProvider;
+  }
+
+
+  public String testConnection(@NotNull VcsRoot vcsRoot) throws VcsException {
+    HgVcsRoot root = myHgVcsRootFactory.createHgRoot(vcsRoot);
+    HgRepo repo = createRepo(root);
+    try {
+      repo.id().repository(root.getRepository())
+              .withAuthSettings(root.getAuthSettings())
+              .call();
+      return null;
+    } catch (MercurialNotFoundException e) {
+      throw friendlyException(root, e);
+    }
+  }
+
+
+  private VcsException friendlyException(HgVcsRoot root, MercurialNotFoundException e) {
+    return new VcsException("Cannot find mercurial executable at path '" + myHgPathProvider.getHgPath(root) + "'", e);
+  }
+
+  private ServerHgRepo createRepo(HgVcsRoot root) throws VcsException {
+    return myRepoFactory.create(getWorkingDir(root), myHgPathProvider.getHgPath(root), root.getAuthSettings());
+  }
+
+  private File getWorkingDir(HgVcsRoot root) {
+    File customDir = root.getCustomWorkingDir();
+    return customDir != null ? customDir : myMirrorManager.getMirrorDir(root.getRepository());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVcsRootFactory.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,48 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author dmitry.neverov
+ */
+public class HgVcsRootFactory {
+
+  private File myDefaultWorkFolderParent;
+
+  public HgVcsRootFactory(@NotNull final ServerPluginConfig config) {
+    myDefaultWorkFolderParent = config.getCachesDir();
+  }
+
+
+  public HgVcsRoot createHgRoot(@NotNull VcsRoot root) throws VcsException {
+    HgVcsRoot hgRoot = new HgVcsRoot(root);
+    String customClonePath = hgRoot.getCustomClonePath();
+    if (!StringUtil.isEmptyOrSpaces(customClonePath) && !myDefaultWorkFolderParent.equals(new File(customClonePath).getAbsoluteFile())) {
+      File parentDir = new File(customClonePath);
+      createClonedRepositoryParentDir(parentDir);
+
+      // take last part of repository path
+      String repPath = hgRoot.getRepositoryUrlWithCredentials();
+      String[] splitted = repPath.split("[/\\\\]");
+      if (splitted.length > 0) {
+        repPath = splitted[splitted.length-1];
+      }
+
+      File customWorkingDir = new File(parentDir, repPath);
+      hgRoot.setCustomWorkingDir(customWorkingDir);
+    }
+    return hgRoot;
+  }
+
+  private void createClonedRepositoryParentDir(final File parentDir) throws VcsException {
+    if (!parentDir.exists() && !parentDir.mkdirs())
+      throw new VcsException("Failed to create parent directory for cloned repository: " + parentDir.getAbsolutePath());
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ListFilesSupport.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,61 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.ListDirectChildrenPolicy;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsFileData;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;
+import static java.util.regex.Pattern.compile;
+import static java.util.regex.Pattern.quote;
+
+/**
+ * @author dmitry.neverov
+ */
+public class ListFilesSupport implements ListDirectChildrenPolicy {
+
+  private final MercurialVcsSupport myVcs;
+  private final HgVcsRoot myRoot;
+  private final HgRepo myRepo;
+
+  public ListFilesSupport(@NotNull MercurialVcsSupport vcs,
+                          @NotNull HgVcsRoot root,
+                          @NotNull HgRepo repo) {
+    myVcs = vcs;
+    myRoot = root;
+    myRepo = repo;
+  }
+
+  @NotNull
+  public List<VcsFileData> listFiles(@NotNull String dir) throws VcsException {
+    myVcs.syncRepository(myRoot);
+    String dirPath = isEmpty(dir) || dir.endsWith("/") ? dir : dir + "/";
+    return listFilesIn(dirPath);
+  }
+
+
+  @NotNull
+  private List<VcsFileData> listFilesIn(@NotNull String dir) throws VcsException {
+    List<VcsFileData> result = new ArrayList<VcsFileData>();
+    Pattern p = compile(quote(File.separator));
+    for (String file : myRepo.listFiles()) {
+      String canonicalFile = p.matcher(file).replaceAll("/");
+      if (!canonicalFile.startsWith(dir))
+        continue;
+      String relativePath = canonicalFile.substring(dir.length());
+      int idx = relativePath.indexOf("/");
+      if (idx >= 0) {
+        result.add(new VcsFileData(relativePath.substring(0, idx), true));
+      } else {
+        result.add(new VcsFileData(relativePath, false));
+      }
+    }
+    return result;
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Tue May 15 10:33:21 2012 +0400
@@ -25,7 +25,6 @@
 import jetbrains.buildServer.serverSide.*;
 import jetbrains.buildServer.util.EventDispatcher;
 import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.util.cache.ResetCacheRegister;
 import jetbrains.buildServer.vcs.*;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
@@ -51,15 +50,16 @@
  * <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,
-        CollectChangesBetweenRoots {
+        CollectChangesBetweenRoots, BuildPatchByCheckoutRules {
   private final VcsManager myVcsManager;
-  private final File myDefaultWorkFolderParent;
   private final MirrorManager myMirrorManager;
   private final ServerPluginConfig myConfig;
   private final HgPathProvider myHgPathProvider;
   private final RepoFactory myRepoFactory;
+  private final HgVcsRootFactory myHgVcsRootFactory;
   private final FileFilter myIgnoreDotHgFilter = new IgnoreDotHgFilter();
   private final FileFilter myAcceptAllFilter = new AcceptAllFilter();
+  private final HgTestConnectionSupport myTestConnection;
 
   public MercurialVcsSupport(@NotNull final VcsManager vcsManager,
                              @NotNull final SBuildServer server,
@@ -68,18 +68,21 @@
                              @NotNull final ServerPluginConfig config,
                              @NotNull final HgPathProvider hgPathProvider,
                              @NotNull final RepoFactory repoFactory,
-                             @NotNull final MirrorManager mirrorManager) {
+                             @NotNull final MirrorManager mirrorManager,
+                             @NotNull final HgVcsRootFactory hgVcsRootFactory,
+                             @NotNull final HgTestConnectionSupport testConnection) {
     myVcsManager = vcsManager;
     myConfig = config;
-    myDefaultWorkFolderParent = myConfig.getCachesDir();
     myMirrorManager = mirrorManager;
     myHgPathProvider = hgPathProvider;
     myRepoFactory = repoFactory;
+    myHgVcsRootFactory = hgVcsRootFactory;
+    myTestConnection = testConnection;
     resetCacheHandlerManager.registerHandler(new MercurialResetCacheHandler(myMirrorManager));
     dispatcher.addListener(new BuildServerAdapter() {
       @Override
       public void cleanupFinished() {
-        server.getExecutor().submit(new Cleanup(myVcsManager, myMirrorManager, myConfig, myHgPathProvider));
+        server.getExecutor().submit(new Cleanup(myVcsManager, myMirrorManager, myConfig));
       }
 
       @Override
@@ -119,9 +122,9 @@
     }
   }
 
-  private List<VcsChange> toVcsChanges(final List<ModifiedFile> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) {
+  private List<VcsChange> toVcsChanges(final List<FileStatus> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) {
     List<VcsChange> files = new ArrayList<VcsChange>();
-    for (ModifiedFile mf: modifiedFiles) {
+    for (FileStatus mf: modifiedFiles) {
       final String path = rules.map(mf.getPath());
       if (shouldInclude(path))
         files.add(toVcsChange(mf, prevVer, curVer, path));
@@ -133,7 +136,7 @@
     return path != null;
   }
 
-  private VcsChange toVcsChange(ModifiedFile mf, String prevVer, String curVer, String mappedPath) {
+  private VcsChange toVcsChange(FileStatus mf, String prevVer, String curVer, String mappedPath) {
     String normalizedPath = PathUtil.normalizeSeparator(mf.getPath());
     VcsChangeInfo.Type changeType = getChangeType(mf.getStatus());
     if (changeType == null) {
@@ -143,7 +146,7 @@
     return new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, mappedPath, prevVer, curVer);
   }
 
-  private VcsChangeInfo.Type getChangeType(final ModifiedFile.Status status) {
+  private VcsChangeInfo.Type getChangeType(final Status status) {
     switch (status) {
       case ADDED:return VcsChangeInfo.Type.ADDED;
       case MODIFIED:return VcsChangeInfo.Type.CHANGED;
@@ -164,9 +167,9 @@
   @NotNull
   public byte[] getContent(@NotNull final String filePath, @NotNull final VcsRoot vcsRoot, @NotNull final String version) throws VcsException {
     ChangeSet cset = new ChangeSet(version);
-    Settings settings = createSettings(vcsRoot);
-    syncRepository(settings, cset);
-    HgRepo repo = createRepo(settings);
+    HgVcsRoot root = myHgVcsRootFactory.createHgRoot(vcsRoot);
+    syncRepository(root, cset);
+    HgRepo repo = createRepo(root);
     File parentDir = repo.cat().files(filePath).atRevision(cset).call();
     File file = new File(parentDir, filePath);
     try {
@@ -212,14 +215,14 @@
 
   @NotNull
   public String getCurrentVersion(@NotNull final VcsRoot root) throws VcsException {
-    Settings settings = createSettings(root);
-    syncRepository(settings);
-    HgRepo repo = createRepo(settings);
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    syncRepository(hgRoot);
+    HgRepo repo = createRepo(hgRoot);
     Map<String, ChangeSet> result = repo.branches().call();
-    if (!result.containsKey(settings.getBranchName())) {
-      throw new VcsException("Unable to find current version for the branch: " + settings.getBranchName());
+    if (!result.containsKey(hgRoot.getBranchName())) {
+      throw new VcsException("Unable to find current version for the branch: " + hgRoot.getBranchName());
     }
-    return result.get(settings.getBranchName()).getFullVersion();
+    return result.get(hgRoot.getBranchName()).getFullVersion();
   }
 
   public boolean sourcesUpdatePossibleIfChangesNotFound(@NotNull final VcsRoot root) {
@@ -233,28 +236,7 @@
 
   @Override
   public TestConnectionSupport getTestConnectionSupport() {
-    return new TestConnectionSupport() {
-      public String testConnection(@NotNull final VcsRoot vcsRoot) throws VcsException {
-        Settings settings = createSettings(vcsRoot);
-        String idResult = createRepo(settings).id()
-                .repository(settings.getRepository())
-                .withAuthSettings(settings.getAuthSettings())
-                .call();
-        StringBuilder res = new StringBuilder();
-        res.append(quoteIfNeeded(settings.getHgCommandPath()));
-        res.append(" identify ");
-        res.append(quoteIfNeeded(settings.getAuthSettings().getRepositoryUrlWithHiddenPassword(settings.getRepository())));
-        res.append('\n').append(idResult);
-        return res.toString();
-      }
-    };
-  }
-
-  private String quoteIfNeeded(@NotNull String str) {
-    if (str.indexOf(' ') != -1) {
-      return "\"" + str + "\"";
-    }
-    return str;
+    return myTestConnection;
   }
 
   @Nullable
@@ -286,13 +268,17 @@
   }
 
   // builds patch from version to version
-  private void buildIncrementalPatch(final Settings settings, @NotNull final ChangeSet fromVer, @NotNull final ChangeSet toVer, final PatchBuilder builder, final CheckoutRules checkoutRules)
+  private void buildIncrementalPatch(@NotNull final HgVcsRoot root,
+                                     @NotNull final ChangeSet fromVer,
+                                     @NotNull final ChangeSet toVer,
+                                     @NotNull final PatchBuilder builder,
+                                     @NotNull final CheckoutRules checkoutRules)
     throws VcsException, IOException {
-    HgRepo repo = createRepo(settings);
-    List<ModifiedFile> modifiedFiles = repo.status().fromRevision(fromVer).toRevision(toVer).call();
+    HgRepo repo = createRepo(root);
+    List<FileStatus> modifiedFiles = repo.status().fromRevision(fromVer).toRevision(toVer).call();
     List<String> notDeletedFiles = new ArrayList<String>();
-    for (ModifiedFile f: modifiedFiles) {
-      if (f.getStatus() != ModifiedFile.Status.REMOVED) {
+    for (FileStatus f: modifiedFiles) {
+      if (f.getStatus() != Status.REMOVED) {
         notDeletedFiles.add(f.getPath());
       }
     }
@@ -302,11 +288,11 @@
 
     File parentDir = repo.cat().files(notDeletedFiles).atRevision(toVer).call();
     try {
-      for (ModifiedFile f: modifiedFiles) {
+      for (FileStatus f: modifiedFiles) {
         String mappedPath = checkoutRules.map(f.getPath());
         if (mappedPath == null) continue; // skip
         final File virtualFile = new File(mappedPath);
-        if (f.getStatus() == ModifiedFile.Status.REMOVED) {
+        if (f.getStatus() == Status.REMOVED) {
           builder.deleteFile(virtualFile, true);
         } else {
           File realFile = new File(parentDir, f.getPath());
@@ -331,25 +317,27 @@
   }
 
   // builds patch by exporting files using specified version
-  private void buildFullPatch(final Settings settings, @NotNull final ChangeSet toVer, final PatchBuilder builder, final CheckoutRules checkoutRules)
-    throws IOException, VcsException {
-    File tempDir = FileUtil.createTempDirectory("mercurial", toVer.getId());
+  private void buildFullPatch(@NotNull final HgVcsRoot root,
+                              @NotNull final ChangeSet toVer,
+                              @NotNull final PatchBuilder builder,
+                              @NotNull final CheckoutRules checkoutRules) throws IOException, VcsException {
+    File tempDir = HgFileUtil.createTempDir();
     try {
-      HgRepo repo = createRepo(settings);
+      HgRepo repo = createRepo(root);
       if (repo.hasSubreposAtRevision(toVer)) {
-        Loggers.VCS.debug("Repository '" + settings.getRepository() + "' has submodules at revision " + toVer.getId() + ", use 'hg clone' to build clean patch");
-        File mirrorDir = getWorkingDir(settings);
-        HgRepo cloneOfTheMirror = createRepo(settings, tempDir);
+        Loggers.VCS.debug("Repository '" + root.getRepository() + "' has submodules at revision " + toVer.getId() + ", use 'hg clone' to build clean patch");
+        File mirrorDir = getWorkingDir(root);
+        HgRepo cloneOfTheMirror = createRepo(root, tempDir);
         cloneOfTheMirror.doClone().fromRepository(mirrorDir)
                 .setUpdateWorkingDir(false)
                 .setUsePullProtocol(false)
                 .useUncompressedTransfer(false)
                 .call();
-        cloneOfTheMirror.setDefaultPath(settings.getRepository());
+        cloneOfTheMirror.setDefaultPath(root.getRepository());
         cloneOfTheMirror.update().toRevision(toVer).call();
         buildPatchFromDirectory(builder, tempDir, checkoutRules, myIgnoreDotHgFilter);
       } else {
-        Loggers.VCS.debug("Repository '" + settings.getRepository() + "' doesn't have submodules at revision " + toVer.getId() + ", use 'hg archive' to build clean patch");
+        Loggers.VCS.debug("Repository '" + root.getRepository() + "' doesn't have submodules at revision " + toVer.getId() + ", use 'hg archive' to build clean patch");
         repo.archive().revision(toVer).toDir(tempDir).call();
         buildPatchFromDirectory(builder, tempDir, checkoutRules, myAcceptAllFilter);
       }
@@ -391,56 +379,56 @@
   }
 
   /* clone the repo if it doesn't exist, pull the repo if it doesn't contain specified changeSet */
-  private void syncRepository(final Settings settings, final ChangeSet cset) throws VcsException {
-    File workingDir = getWorkingDir(settings);
+  private void syncRepository(@NotNull final HgVcsRoot root, @NotNull final ChangeSet cset) throws VcsException {
+    File workingDir = getWorkingDir(root);
     lockWorkDir(workingDir);
-    HgRepo repo = createRepo(settings);
+    HgRepo repo = createRepo(root);
     try {
       if (repo.isValidRepository()) {
         if (repo.containsRevision(cset))
           return;
         try {
-          repo.pull().fromRepository(settings.getRepository())
+          repo.pull().fromRepository(root.getRepository())
                   .withTimeout(myConfig.getPullTimeout())
                   .call();
         } catch (UnrelatedRepositoryException e) {
-          Loggers.VCS.warn("Repository at " + settings.getRepository() + " is unrelated, clone it again");
+          Loggers.VCS.warn("Repository at " + root.getRepository() + " is unrelated, clone it again");
           myMirrorManager.forgetDir(workingDir);
-          syncRepository(settings, cset);
+          syncRepository(root, cset);
         }
       } else {
-        repo.doClone().fromRepository(settings.getRepository())
-                .useUncompressedTransfer(settings.isUncompressedTransfer())
+        repo.doClone().fromRepository(root.getRepository())
+                .useUncompressedTransfer(root.isUncompressedTransfer())
                 .setUpdateWorkingDir(false)
                 .call();
-        repo.setDefaultPath(settings.getRepository());
+        repo.setDefaultPath(root.getRepository());
       }
     } finally {
       unlockWorkDir(workingDir);
     }
   }
 
-  private void syncRepository(final Settings settings) throws VcsException {
-    File workingDir = getWorkingDir(settings);
+  public void syncRepository(@NotNull final HgVcsRoot root) throws VcsException {
+    File workingDir = getWorkingDir(root);
     lockWorkDir(workingDir);
-    HgRepo repo = createRepo(settings);
+    HgRepo repo = createRepo(root);
     try {
       if (repo.isValidRepository()) {
         try {
-          repo.pull().fromRepository(settings.getRepository())
+          repo.pull().fromRepository(root.getRepository())
                   .withTimeout(myConfig.getPullTimeout())
                   .call();
         } catch (UnrelatedRepositoryException e) {
-          Loggers.VCS.warn("Repository at " + settings.getRepository() + " is unrelated, clone it again");
+          Loggers.VCS.warn("Repository at " + root.getRepository() + " is unrelated, clone it again");
           myMirrorManager.forgetDir(workingDir);
-          syncRepository(settings);
+          syncRepository(root);
         }
       } else {
-        repo.doClone().fromRepository(settings.getRepository())
+        repo.doClone().fromRepository(root.getRepository())
                 .setUpdateWorkingDir(false)
-                .useUncompressedTransfer(settings.isUncompressedTransfer())
+                .useUncompressedTransfer(root.isUncompressedTransfer())
                 .call();
-        repo.setDefaultPath(settings.getRepository());
+        repo.setDefaultPath(root.getRepository());
       }
     } finally {
       unlockWorkDir(workingDir);
@@ -469,9 +457,9 @@
 
   @NotNull
   private Map<String, String> getBranchesRevisions(@NotNull VcsRoot root) throws VcsException {
-    Settings settings = createSettings(root);
-    syncRepository(settings);
-    HgRepo repo = createRepo(settings);
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    syncRepository(hgRoot);
+    HgRepo repo = createRepo(hgRoot);
     Map<String, String> result = new HashMap<String, String>();
     for (Map.Entry<String, ChangeSet> entry : repo.branches().call().entrySet()) {
       result.put(entry.getKey(), entry.getValue().getId());
@@ -489,16 +477,16 @@
 
   @Nullable
   public PersonalBranchDescription getPersonalBranchDescription(@NotNull VcsRoot root, @NotNull String branchName) throws VcsException {
-    Settings settings = createSettings(root);
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
     VcsRoot branchRoot = createBranchRoot(root, branchName);
     String baseVersion = getCurrentVersion(root);
     String branchVersion = getCurrentVersion(branchRoot);
-    String mergeBase = getMergeBase(settings, baseVersion, branchVersion);
+    String mergeBase = getMergeBase(hgRoot, baseVersion, branchVersion);
 
     if (mergeBase == null)
       return null;
 
-    List<ChangeSet> changeSets = createRepo(settings).log()
+    List<ChangeSet> changeSets = createRepo(hgRoot).log()
             .fromRevision(mergeBase)
             .toRevision(branchVersion)
             .showCommitsFromAllBranches()
@@ -523,10 +511,10 @@
   public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot, @NotNull String fromRootRevision,
                                                @NotNull VcsRoot toRoot, @Nullable String toRootRevision,
                                                @NotNull CheckoutRules checkoutRules) throws VcsException {
-    Settings settings = createSettings(toRoot);
-    syncRepository(settings);
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(toRoot);
+    syncRepository(hgRoot);
     String toRevision = toRootRevision != null ? toRootRevision : getCurrentVersion(toRoot);
-    String mergeBase = getMergeBase(settings, fromRootRevision, toRevision);
+    String mergeBase = getMergeBase(hgRoot, fromRootRevision, toRevision);
     if (mergeBase == null)
       return Collections.emptyList();
     return collectChanges(toRoot, mergeBase, toRootRevision, checkoutRules);
@@ -534,22 +522,22 @@
 
 
   @Nullable
-  private String getMergeBase(@NotNull Settings settings, @NotNull String revision1, @NotNull String revision2) throws VcsException {
-    String result = createRepo(settings).mergeBase()
+  private String getMergeBase(@NotNull HgVcsRoot root, @NotNull String revision1, @NotNull String revision2) throws VcsException {
+    String result = createRepo(root).mergeBase()
             .revision1(revision1)
             .revision2(revision2)
             .call();
     if (result == null)
-      result = getMinusNthCommit(settings, 10);
+      result = getMinusNthCommit(root, 10);
     return result;
   }
 
 
   @Nullable
-  private String getMinusNthCommit(@NotNull Settings settings, int n) throws VcsException {
-    LogCommand log = createRepo(settings).log()
-            .inBranch(settings.getBranchName())
-            .toNamedRevision(settings.getBranchName());
+  private String getMinusNthCommit(@NotNull HgVcsRoot root, int n) throws VcsException {
+    LogCommand log = createRepo(root).log()
+            .inBranch(root.getBranchName())
+            .toNamedRevision(root.getBranchName());
     if (n > 0)
       log.setLimit(n);
     List<ChangeSet> changeSets = log.call();
@@ -565,10 +553,10 @@
   }
 
   public List<ModificationData> collectChanges(@NotNull VcsRoot root, @NotNull String fromVersion, @Nullable String currentVersion, @NotNull CheckoutRules checkoutRules) throws VcsException {
-    Settings settings = createSettings(root);
-    syncRepository(settings);
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    syncRepository(hgRoot);
     List<ModificationData> result = new ArrayList<ModificationData>();
-    for (ChangeSet cset : getChangesets(settings, fromVersion, currentVersion)) {
+    for (ChangeSet cset : getChangesets(hgRoot, fromVersion, currentVersion)) {
       result.add(createModificationData(cset, root, checkoutRules));
     }
     return result;
@@ -591,13 +579,13 @@
 
 
   @NotNull
-  private List<ChangeSet> getChangesets(@NotNull final Settings settings, @NotNull final String fromVersion, @Nullable final String toVersion) throws VcsException {
+  private List<ChangeSet> getChangesets(@NotNull final HgVcsRoot root, @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();
     try {
-      List<ChangeSet> changesets = createRepo(settings).collectChanges()
+      List<ChangeSet> changesets = createRepo(root).collectChanges()
               .fromRevision(fromCommit)
               .toRevision(toCommit)
               .call();
@@ -617,21 +605,30 @@
 
   @NotNull
   public BuildPatchPolicy getBuildPatchPolicy() {
-    return new BuildPatchByCheckoutRules() {
-      public void buildPatch(@NotNull final VcsRoot root,
-                             @Nullable final String fromVersion,
-                             @NotNull final String toVersion,
-                             @NotNull final PatchBuilder builder,
-                             @NotNull final CheckoutRules checkoutRules) throws IOException, VcsException {
-        Settings settings = createSettings(root);
-        syncRepository(settings);
-        if (fromVersion == null) {
-          buildFullPatch(settings, new ChangeSet(toVersion), builder, checkoutRules);
-        } else {
-          buildIncrementalPatch(settings, new ChangeSet(fromVersion), new ChangeSet(toVersion), builder, checkoutRules);
-        }
+    return this;
+  }
+
+  public void buildPatch(@NotNull VcsRoot root, @Nullable String fromVersion, @NotNull String toVersion, @NotNull PatchBuilder builder, @NotNull CheckoutRules checkoutRules) throws IOException, VcsException {
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    syncRepository(hgRoot);
+    ChangeSet to = new ChangeSet(toVersion);
+    if (fromVersion == null) {
+      buildFullPatch(hgRoot, new ChangeSet(toVersion), builder, checkoutRules);
+    } else {
+      ChangeSet from = new ChangeSet(fromVersion);
+      HgRepo repo = createRepo(hgRoot);
+      if (!repo.containsRevision(from)) {
+        Loggers.VCS.info("Cannot find revision " + fromVersion + " in repository " + hgRoot.getRepository() + ", will build a full patch");
+        cleanCheckoutDir(builder, checkoutRules);
+        buildFullPatch(hgRoot, to, builder, checkoutRules);
+      } else {
+        buildIncrementalPatch(hgRoot, new ChangeSet(fromVersion), new ChangeSet(toVersion), builder, checkoutRules);
       }
-    };
+    }
+  }
+
+  private void cleanCheckoutDir(@NotNull PatchBuilder builder, @NotNull CheckoutRules checkoutRules) throws IOException {
+    builder.deleteDirectory(new File(checkoutRules.map("")), true);
   }
 
   private void lockWorkDir(@NotNull File workDir) {
@@ -653,19 +650,19 @@
     File tmpDir = null;
     try {
       tmpDir = createLabelingTmpDir();
-      Settings settings = createSettings(root);
-      settings.setCustomWorkingDir(tmpDir);
-      syncRepository(settings);
-      HgRepo repo = createRepo(settings);
-      repo.update().branch(settings.getBranchName()).call();
+      HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+      hgRoot.setCustomWorkingDir(tmpDir);
+      syncRepository(hgRoot);
+      HgRepo repo = createRepo(hgRoot);
+      repo.update().branch(hgRoot.getBranchName()).call();
 
       String fixedTagname = fixTagName(label);
       repo.tag().revision(version)
               .tagName(fixedTagname)
-              .byUser(settings.getUserForTag())
+              .byUser(hgRoot.getUserForTag())
               .call();
 
-      repo.push().toRepository(settings.getRepository()).call();
+      repo.push().toRepository(hgRoot.getRepository()).call();
       return fixedTagname;
     } finally {
       if (tmpDir != null)
@@ -683,36 +680,11 @@
     return label.replace(':', '_').replace('\r', '_').replace('\n', '_');
   }
 
-  private File getWorkingDir(Settings s) {
-    File customDir = s.getCustomWorkingDir();
-    return customDir != null ? customDir : myMirrorManager.getMirrorDir(s.getRepository());
+  private File getWorkingDir(HgVcsRoot root) {
+    File customDir = root.getCustomWorkingDir();
+    return customDir != null ? customDir : myMirrorManager.getMirrorDir(root.getRepository());
   }
 
-  private Settings createSettings(final VcsRoot root) throws VcsException {
-    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);
-      createClonedRepositoryParentDir(parentDir);
-
-      // take last part of repository path
-      String repPath = settings.getRepositoryUrlWithCredentials();
-      String[] splitted = repPath.split("[/\\\\]");
-      if (splitted.length > 0) {
-        repPath = splitted[splitted.length-1];
-      }
-
-      File customWorkingDir = new File(parentDir, repPath);
-      settings.setCustomWorkingDir(customWorkingDir);
-    }
-    return settings;
-  }
-
-  private void createClonedRepositoryParentDir(final File parentDir) throws VcsException {
-    if (!parentDir.exists() && !parentDir.mkdirs()) {
-      throw new VcsException("Failed to create parent directory for cloned repository: " + parentDir.getAbsolutePath());
-    }
-  }
 
   public boolean isAgentSideCheckoutAvailable() {
     return true;
@@ -751,21 +723,42 @@
     }
   }
 
-  private ServerHgRepo createRepo(@NotNull Settings s) throws VcsException {
-    return myRepoFactory.create(getWorkingDir(s), s.getHgCommandPath(), s.getAuthSettings());
+  private ServerHgRepo createRepo(@NotNull HgVcsRoot root) throws VcsException {
+    return myRepoFactory.create(getWorkingDir(root), myHgPathProvider.getHgPath(root), root.getAuthSettings());
   }
 
-  private HgRepo createRepo(@NotNull Settings s, @NotNull File customDir) throws VcsException {
-    return myRepoFactory.create(customDir, s.getHgCommandPath(), s.getAuthSettings());
+  private HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File customDir) throws VcsException {
+    return myRepoFactory.create(customDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
   }
 
   @NotNull
   public String getBranchName(@NotNull final VcsRoot root) {
     try {
-      Settings s = createSettings(root);
-      return s.getBranchName();
+      HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+      return hgRoot.getBranchName();
     } catch (VcsException e) {
       return "default";
     }
   }
+
+  @NotNull
+  @Override
+  public Map<String, String> getVcsRepositoryProperties(@NotNull VcsRoot root) {
+    Map<String, String> rootProperties = root.getProperties();
+    Map<String, String> repositoryProperties = new HashMap<String, String>();
+    repositoryProperties.put(Constants.REPOSITORY_PROP, rootProperties.get(Constants.REPOSITORY_PROP));
+    return repositoryProperties;
+  }
+
+
+  @Override
+  public ListFilesPolicy getListFilesPolicy(@NotNull VcsRoot root) {
+    try {
+      HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+      HgRepo repo = createRepo(hgRoot);
+      return new ListFilesSupport(this, hgRoot, repo);
+    } catch (VcsException e) {
+      return null;
+    }
+  }
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProvider.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProvider.java	Tue May 15 10:33:21 2012 +0400
@@ -1,6 +1,6 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import org.jetbrains.annotations.NotNull;
 
 /**
@@ -16,12 +16,12 @@
   }
 
 
-  public String getHgPath(@NotNull final Settings settings) {
+  public String getHgPath(@NotNull final HgVcsRoot root) {
     String serverWideHgPath = myConfig.getHgPath();
     if (serverWideHgPath != null) {
       return serverWideHgPath;
     } else {
-      String pathFromRoot = settings.getHgPath();
+      String pathFromRoot = root.getHgPath();
       if (pathFromRoot.equals(unresolvedAgentHgPath())) {
         //try to use hg from the PATH:
         return "hg";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,120 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.agent.*;
+import jetbrains.buildServer.log.Log4jFactory;
+import jetbrains.buildServer.vcs.*;
+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.util.Date;
+import java.util.HashMap;
+
+import static java.util.Arrays.asList;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.LocalRepositoryUtil.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class AgentMirrorCleanerTest {
+
+  static {
+    Logger.setFactory(new Log4jFactory());
+  }
+
+  private TempFiles myTempFiles = new TempFiles();
+  private Mockery myContext;
+  private MercurialAgentSideVcsSupport myVcsSupport;
+  private AgentMirrorCleaner myCleaner;
+  private BuildProgressLogger myLogger;
+  private File myWorkDir;
+  private int myBuildCounter;
+  private MirrorManager myMirrorManager;
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    myContext = new Mockery();
+    myWorkDir = myTempFiles.createTempDir();
+
+    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()));
+    }});
+
+    AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
+    AgentHgPathProvider hgPathProvider = new AgentHgPathProvider(agentConfig);
+    myMirrorManager = new MirrorManagerImpl(pluginConfig);
+    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, hgPathProvider, myMirrorManager);
+    myCleaner = new AgentMirrorCleaner(myMirrorManager);
+    myLogger = myContext.mock(BuildProgressLogger.class);
+    myContext.checking(new Expectations() {{
+      allowing(myLogger).message(with(any(String.class)));
+      allowing(myLogger).warning(with(any(String.class)));
+    }});
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    myTempFiles.cleanup();
+  }
+
+
+  public void should_add_cleaners_only_for_roots_not_used_in_build() throws Exception {
+    //setup mirrors for 2 roots on an agent:
+    final File repo1 = myTempFiles.createTempDir();
+    final File repo2 = myTempFiles.createTempDir();
+    copyRepository(new File("mercurial-tests/testData/rep1"), repo1);
+    copyRepository(new File("mercurial-tests/testData/rep2"), repo2);
+
+    final VcsRoot root1 = vcsRoot().withUrl(repo1.getAbsolutePath()).build();
+    final VcsRoot root2 = vcsRoot().withUrl(repo2.getAbsolutePath()).build();
+    //update will initialize mirrors
+    doUpdate(root1, "4:b06a290a363b", myWorkDir);
+    doUpdate(root2, "8:b6e2d176fe8e", new File(myWorkDir, "subdir"));
+
+    final File mirrorDir1 = myMirrorManager.getMirrorDir(repo1.getAbsolutePath());
+    final File mirrorDir2 = myMirrorManager.getMirrorDir(repo2.getAbsolutePath());
+    final Date mirrorDir1LastUsedTime = new Date(myMirrorManager.getLastUsedTime(mirrorDir1));
+    final Date mirrorDir2LastUsedTime = new Date(myMirrorManager.getLastUsedTime(mirrorDir2));
+
+    //run build which uses root1:
+    final DirectoryCleanersProviderContext ctx = myContext.mock(DirectoryCleanersProviderContext.class);
+    myContext.checking(new Expectations(){{
+      AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++);
+      allowing(build).getVcsRootEntries(); will(returnValue(asList(new VcsRootEntry(root1, CheckoutRules.DEFAULT))));
+      allowing(ctx).getRunningBuild(); will(returnValue(build));
+    }});
+
+    //cleaner should add cleaners only for roots which are not used in the running build
+    final DirectoryCleanersRegistry registry = myContext.mock(DirectoryCleanersRegistry.class);
+    myContext.checking(new Expectations() {{
+      never(registry).addCleaner(with(mirrorDir1), with(mirrorDir1LastUsedTime));
+      one(registry).addCleaner(with(mirrorDir2), with(mirrorDir2LastUsedTime));
+    }});
+
+    myCleaner.registerDirectoryCleaners(ctx, registry);
+    myContext.assertIsSatisfied();
+  }
+
+
+  private void doUpdate(@NotNull VcsRoot vcsRoot, @NotNull String toVersion, @NotNull File workDir) 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(new HashMap<String, String>() {{
+        put("teamcity.hg.use.local.mirrors", "true");
+      }}));
+    }});
+    myVcsSupport.getUpdater(vcsRoot, CheckoutRules.DEFAULT, toVersion, workDir, build, false).process(IncludeRule.createDefaultInstance(), workDir);
+  }
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java	Tue May 15 10:33:21 2012 +0400
@@ -48,7 +48,7 @@
     myMirrorManager = new MirrorManagerImpl(config);
 
     myVcsManager = myContext.mock(VcsManager.class);
-    myCleanup = new Cleanup(myVcsManager, myMirrorManager, config, new ServerHgPathProvider(config));
+    myCleanup = new Cleanup(myVcsManager, myMirrorManager, config);
   }
 
   @AfterMethod
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVcsRootTest.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,118 @@
+/*
+ * 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.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import junit.framework.TestCase;
+import org.testng.annotations.Test;
+
+/**
+ * @author Pavel.Sher
+ */
+@Test
+public class HgVcsRootTest extends TestCase {
+
+  public void test_url_without_credentials() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("http://user:pwd@host.com/path", root.getRepositoryUrlWithCredentials());
+  }
+
+  public void test_url_with_credentials() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://user:pwd@host.com/path");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("http://user:pwd@host.com/path", root.getRepositoryUrlWithCredentials());
+  }
+
+  public void test_url_with_username() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://user@host.com/path");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("http://user:pwd@host.com/path", root.getRepositoryUrlWithCredentials());
+  }
+
+  public void test_url_with_at_after_slash() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path@");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("http://user:pwd@host.com/path@", root.getRepositoryUrlWithCredentials());
+  }
+
+  public void test_url_with_at_in_username() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path", "my.name@gmail.com", "1234");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("http://my.name%40gmail.com:1234@host.com/path", root.getRepositoryUrlWithCredentials());
+  }
+
+  /** TW-13768 */
+  public void test_underscore_in_host() {
+		VcsRootImpl vcsRoot = createVcsRoot("http://Klekovkin.SDK_GARANT:8000/", "my.name@gmail.com", "1234");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+		assertEquals("http://my.name%40gmail.com:1234@Klekovkin.SDK_GARANT:8000/", root.getRepositoryUrlWithCredentials());
+	}
+
+  /** TW-13768 */
+  public void test_underscore_in_host_with_credentials_in_url() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://me:mypass@Klekovkin.SDK_GARANT:8000/");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+		assertEquals("http://me:mypass@Klekovkin.SDK_GARANT:8000/", root.getRepositoryUrlWithCredentials());
+  }
+
+  public void test_windows_path() throws Exception {
+    VcsRootImpl vcsRoot = createVcsRoot("c:\\windows\\path");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("c:\\windows\\path", root.getRepositoryUrlWithCredentials());
+  }
+
+  public void test_file_scheme_has_no_credentials() {
+    VcsRootImpl vcsRoot = createVcsRoot("file:///path/to/repo", "my.name@gmail.com", "1234");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("file:///path/to/repo", root.getRepositoryUrlWithCredentials());
+  }
+
+  public void uncompressed_transfer() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path");
+    vcsRoot.addProperty(Constants.UNCOMPRESSED_TRANSFER, "true");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertTrue(root.isUncompressedTransfer());
+  }
+
+  //TW-18262
+  public void ampersand_in_password() {
+    VcsRootImpl vcsRoot = createVcsRoot("http://some.org/path", "user", "m&n");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("http://user:m%26n@some.org/path", root.getRepositoryUrlWithCredentials());
+  }
+
+  //TW-18835
+  public void test_ssh() {
+    VcsRootImpl vcsRoot = createVcsRoot("ssh://ourserver.com/mercurialrepo/", "user", "pwd");
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    assertEquals("ssh://user:pwd@ourserver.com/mercurialrepo/", root.getRepositoryUrlWithCredentials());
+  }
+
+  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;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ListFilesSupportTest.java	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,90 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.log.Log4jFactory;
+import jetbrains.buildServer.vcs.ListDirectChildrenPolicy;
+import jetbrains.buildServer.vcs.VcsFileData;
+import jetbrains.buildServer.vcs.VcsRoot;
+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 jetbrains.buildServer.buildTriggers.vcs.mercurial.LocalRepositoryUtil.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+import static org.testng.AssertJUnit.assertNotNull;
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class ListFilesSupportTest {
+
+  static {
+    Logger.setFactory(new Log4jFactory());
+  }
+
+  private MercurialVcsSupport myVcs;
+  private VcsRoot myRoot;
+
+  @BeforeMethod
+  protected void setUp() throws Exception {
+    TempFiles tempFiles = new TempFiles();
+    Mockery context = new Mockery();
+    ServerPluginConfig myPluginConfig = new ServerPluginConfigBuilder()
+            .cachesDir(tempFiles.createTempDir())
+            .build();
+    myVcs = Util.createMercurialServerSupport(context, myPluginConfig);
+
+    File repo = tempFiles.createTempDir();
+    copyRepository(new File("mercurial-tests/testData/rep1"), repo);
+    myRoot = vcsRoot().withUrl(repo.getAbsolutePath()).build();
+  }
+
+
+  public void should_support_list_files() throws Exception {
+    assertNotNull(myVcs.getListFilesPolicy(myRoot));
+  }
+
+
+  public void list_root_dir() throws Exception {
+    ListDirectChildrenPolicy policy = getListFilesPolicy();
+    List<VcsFileData> files = policy.listFiles("");
+    assertTrue(files.contains(dir("dir1")));
+    assertTrue(files.contains(dir("dir with space")));
+    assertTrue(files.contains(file("file.txt")));
+  }
+
+
+  public void list_subdir() throws Exception {
+    ListDirectChildrenPolicy policy = getListFilesPolicy();
+    List<VcsFileData> files = policy.listFiles("dir1");
+    assertTrue(files.contains(file("file1.txt")));
+    assertTrue(files.contains(file("file3.txt")));
+    assertTrue(files.contains(dir("subdir")));
+
+    files = policy.listFiles("dir1/subdir/");
+    assertTrue(files.contains(file("file2.txt")));
+  }
+
+  @NotNull
+  private ListDirectChildrenPolicy getListFilesPolicy() {
+    ListDirectChildrenPolicy listFilesPolicy = (ListDirectChildrenPolicy) myVcs.getListFilesPolicy(myRoot);
+    assert listFilesPolicy != null;
+    return listFilesPolicy;
+  }
+
+  private VcsFileData dir(@NotNull String path) {
+    return new VcsFileData(path, true);
+  }
+
+  private VcsFileData file(@NotNull String path) {
+    return new VcsFileData(path, false);
+  }
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Tue May 15 10:33:21 2012 +0400
@@ -32,8 +32,7 @@
 import java.io.IOException;
 import java.util.*;
 
-import static com.intellij.openapi.util.io.FileUtil.copyDir;
-import static com.intellij.openapi.util.io.FileUtil.moveDirWithContent;
+import static com.intellij.openapi.util.io.FileUtil.*;
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
 
 @Test
@@ -123,8 +122,7 @@
   private ByteArrayOutputStream buildPatch(VcsRoot vcsRoot, String from, String to, CheckoutRules rules) throws IOException, VcsException {
     final ByteArrayOutputStream output = new ByteArrayOutputStream();
     final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    ((BuildPatchByCheckoutRules)myVcs.getBuildPatchPolicy()).buildPatch(vcsRoot, from, to, builder, rules);
+    myVcs.buildPatch(vcsRoot, from, to, builder, rules);
     builder.close();
     return output;
   }
@@ -233,6 +231,22 @@
     }
   }
 
+
+  public void should_throw_friendly_exception_when_cannot_run_hg() throws Exception {
+    VcsRootImpl root = createVcsRoot(simpleRepo());
+    File nonExistingHg = myTempFiles.createTempFile();
+    delete(nonExistingHg);
+    String nonExistingHgPath = nonExistingHg.getAbsolutePath();
+    root.addProperty(Constants.HG_COMMAND_PATH_PROP, nonExistingHgPath);
+    try {
+      myVcs.getTestConnectionSupport().testConnection(root);
+      fail("Exception expected");
+    } catch (VcsException e) {
+      assertEquals(e.getMessage(), "Cannot find mercurial executable at path '" + nonExistingHgPath + "'");
+    }
+  }
+
+
   public void test_tag() throws IOException, VcsException {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
     cleanRepositoryAfterTest(simpleRepo());
@@ -364,16 +378,16 @@
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
     String repPath = vcsRoot.getProperty(Constants.REPOSITORY_PROP);
     vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#test_branch");
-    Settings settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot);
-    assertEquals("test_branch", settings.getBranchName());
+    HgVcsRoot hgRoot = new HgVcsRoot(vcsRoot);
+    assertEquals("test_branch", hgRoot.getBranchName());
 
     vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#");
-    settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot);
-    assertEquals("default", settings.getBranchName());
+    hgRoot = new HgVcsRoot(vcsRoot);
+    assertEquals("default", hgRoot.getBranchName());
 
     vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath);
-    settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot);
-    assertEquals("default", settings.getBranchName());
+    hgRoot = new HgVcsRoot(vcsRoot);
+    assertEquals("default", hgRoot.getBranchName());
   }
 
   public void build_patch_using_custom_clone_path() throws IOException, VcsException {
@@ -389,6 +403,20 @@
     assertTrue(new File(cloneDir, new File(vcsRoot.getProperty(Constants.REPOSITORY_PROP)).getName()).isDirectory());
   }
 
+  public void build_patch_from_newer_revision_to_earlier() throws Exception {
+    setName("patch5");
+    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
+    ByteArrayOutputStream output = buildPatch(vcsRoot, "6:b9deb9a1c6f4", "3:9522278aa38d", CheckoutRules.DEFAULT);
+    checkPatchResult(output.toByteArray());
+  }
+
+  public void build_patch_from_unknown_revision() throws Exception {
+    setName("patch6");
+    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
+    ByteArrayOutputStream output = buildPatch(vcsRoot, "6:hahahahahaha", "3:9522278aa38d", new CheckoutRules("+:.=>path"));
+    checkPatchResult(output.toByteArray());
+  }
+
   private String mergeCommittsRepo() {
     return new File("mercurial-tests/testData/rep2").getAbsolutePath();
   }
@@ -470,8 +498,8 @@
     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(new ServerHgPathProvider(myPluginConfig), root);
-    assertFalse(settings.isUncompressedTransfer());
+    HgVcsRoot hgRoot = new HgVcsRoot(root);
+    assertFalse(hgRoot.isUncompressedTransfer());
   }
 
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java	Tue May 15 10:33:21 2012 +0400
@@ -1,7 +1,6 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -27,16 +26,16 @@
   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));
+    HgVcsRoot root = createHgRoot();
+    assertEquals(myVcsRootHgPath, provider.getHgPath(root));
   }
 
 
   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));
+    HgVcsRoot root = createHgRoot();
+    assertEquals(myServerWideHgPath, provider.getHgPath(root));
   }
 
 
@@ -46,9 +45,8 @@
   }
 
 
-  private Settings createSettings(HgPathProvider hgPathProvider) throws Exception {
-    VcsRootImpl root = new VcsRootBuilder().withHgPath(myVcsRootHgPath).build();
-    return new Settings(hgPathProvider, root);
+  private HgVcsRoot createHgRoot() throws Exception {
+    return new HgVcsRoot(new VcsRootBuilder().withHgPath(myVcsRootHgPath).build());
   }
 
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java	Tue May 15 10:21:05 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-/*
- * 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.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.Test;
-
-/**
- * @author Pavel.Sher
- */
-@Test
-public class SettingsTest extends TestCase {
-
-  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.getRepositoryUrlWithCredentials());
-  }
-
-  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.getRepositoryUrlWithCredentials());
-  }
-
-  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.getRepositoryUrlWithCredentials());
-  }
-
-  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.getRepositoryUrlWithCredentials());
-  }
-
-  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.getRepositoryUrlWithCredentials());
-  }
-
-  /** 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.getRepositoryUrlWithCredentials());
-	}
-
-  /** 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.getRepositoryUrlWithCredentials());
-  }
-
-  public void test_windows_path() throws Exception {
-    VcsRootImpl vcsRoot = createVcsRoot("c:\\windows\\path");
-    Settings settings = createSettings(vcsRoot);
-    assertEquals("c:\\windows\\path", settings.getRepositoryUrlWithCredentials());
-  }
-
-  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.getRepositoryUrlWithCredentials());
-  }
-
-  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.getRepositoryUrlWithCredentials());
-  }
-
-  //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.getRepositoryUrlWithCredentials());
-  }
-
-  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	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Tue May 15 10:33:21 2012 +0400
@@ -36,7 +36,7 @@
   }
 
 
-  public static MercurialVcsSupport createMercurialServerSupport(@NotNull Mockery context, @NotNull ServerPluginConfig config, @NotNull RepoFactory commandFactory) throws IOException {
+  public static MercurialVcsSupport createMercurialServerSupport(@NotNull Mockery context, @NotNull ServerPluginConfig config, @NotNull RepoFactory repoFactory) throws IOException {
     VcsManager vcsManager = context.mock(VcsManager.class);
     final SBuildServer server = context.mock(SBuildServer.class);
     final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
@@ -44,6 +44,11 @@
       allowing(server).getExecutor(); will(returnValue(executor));
     }});
     EventDispatcher<BuildServerListener> dispatcher = EventDispatcher.create(BuildServerListener.class);
-    return new MercurialVcsSupport(vcsManager, server, dispatcher, new ResetCacheRegister(), config, new ServerHgPathProvider(config), commandFactory, new MirrorManagerImpl(config));
+    HgVcsRootFactory hgVcsRootFactory = new HgVcsRootFactory(config);
+    MirrorManagerImpl mirrorManager = new MirrorManagerImpl(config);
+    ServerHgPathProvider hgPathProvider = new ServerHgPathProvider(config);
+    HgTestConnectionSupport testConnection = new HgTestConnectionSupport(hgVcsRootFactory, repoFactory, mirrorManager, hgPathProvider);
+    return new MercurialVcsSupport(vcsManager, server, dispatcher, new ResetCacheRegister(), config, hgPathProvider,
+            repoFactory, mirrorManager, hgVcsRootFactory, testConnection);
   }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java	Tue May 15 10:33:21 2012 +0400
@@ -72,21 +72,22 @@
     VcsRoot vcsRoot = new VcsRootImpl(1, vcsRootProps);
     ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(tf.createTempDir()).build();
     MirrorManager mirrorManager = new MirrorManagerImpl(config);
-    Settings settings = new Settings(new ServerHgPathProvider(config), vcsRoot);
-    final File workingDir = mirrorManager.getMirrorDir(settings.getRepository());
-    settings.setCustomWorkingDir(workingDir);
+    ServerHgPathProvider hgPathProvider = new ServerHgPathProvider(config);
+    HgVcsRoot root = new HgVcsRoot(vcsRoot);
+    final File workingDir = mirrorManager.getMirrorDir(root.getRepository());
+    root.setCustomWorkingDir(workingDir);
     try {
       if (myCloneRequired) {
-        new HgRepo(workingDir, settings.getHgCommandPath(), settings.getAuthSettings()).doClone().fromRepository(settings.getRepository()).call();
+        new HgRepo(workingDir, hgPathProvider.getHgPath(root), root.getAuthSettings()).doClone().fromRepository(root.getRepository()).call();
       }
 
-      return executor.execute(settings, workingDir);
+      return executor.execute(root, hgPathProvider, workingDir);
     } finally {
       tf.cleanup();
     }
   }
 
   public interface CommandExecutor<T> {
-    T execute(@NotNull Settings settings, File workingDir) throws VcsException;
+    T execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException;
   }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommandTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommandTest.java	Tue May 15 10:33:21 2012 +0400
@@ -1,5 +1,6 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgPathProvider;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnknownFileException;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.VcsException;
@@ -38,8 +39,8 @@
     cleanCatResultDirs();
     setRepository("mercurial-tests/testData/rep1", true);
     runCommand(new CommandExecutor<File>() {
-      public File execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
-        return new CatCommand(settings.getHgCommandPath(), workingDir, settings.getAuthSettings()).files("/non/existing/path").checkForFailure(false).call();
+      public File execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException {
+        return new CatCommand(hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings()).files("/non/existing/path").checkForFailure(false).call();
       }
     });
   }
@@ -65,8 +66,8 @@
 
   private File runCat(@NotNull final List<String> paths) throws IOException, VcsException {
     return runCommand(new CommandExecutor<File>() {
-      public File execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
-        CatCommand cat = new CatCommand(settings.getHgCommandPath(), workingDir, settings.getAuthSettings());
+      public File execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException {
+        CatCommand cat = new CatCommand(hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings());
         return cat.execute(paths);
       }
     });
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommandTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommandTest.java	Tue May 15 10:33:21 2012 +0400
@@ -43,10 +43,11 @@
 
     File workingDir = myTempFiles.createTempDir();
     ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(myTempFiles.createTempDir()).build();
-    Settings settings = new Settings(new ServerHgPathProvider(config), root);
-    settings.setCustomWorkingDir(workingDir);
+    ServerHgPathProvider hgPathProvider = new ServerHgPathProvider(config);
+    HgVcsRoot hgRoot = new HgVcsRoot(root);
+    hgRoot.setCustomWorkingDir(workingDir);
 
-    new CloneCommand(settings.getHgCommandPath(), workingDir, settings.getAuthSettings()).fromRepository(settings.getRepository()).call();
+    new CloneCommand(hgPathProvider.getHgPath(hgRoot), workingDir, hgRoot.getAuthSettings()).fromRepository(hgRoot.getRepository()).call();
 
     String[] files = new String[] {".hg", "dir1", "dir with space", "file.txt"};
     for (String f : files) {
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommandTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommandTest.java	Tue May 15 10:33:21 2012 +0400
@@ -1,5 +1,6 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgPathProvider;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.Test;
@@ -36,8 +37,8 @@
 
   private Void runIdentify(final ChangeSet cset) throws IOException, VcsException {
     return runCommand(new CommandExecutor<Void>() {
-      public Void execute(@NotNull final Settings settings, File workingDir) throws VcsException {
-        new IdentifyCommand(settings.getHgCommandPath(), workingDir, settings.getAuthSettings())
+      public Void execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException {
+        new IdentifyCommand(hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings())
                 .revision(cset)
                 .inLocalRepository()
                 .call();
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Tue May 15 10:33:21 2012 +0400
@@ -16,6 +16,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgPathProvider;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.VcsException;
@@ -110,22 +111,22 @@
     List<ChangeSet> csets = runLog(fromId, toId);
     assertEquals(3, csets.size());
 
-    List<ModifiedFile> files = csets.get(0).getModifiedFiles();
+    List<FileStatus> files = csets.get(0).getModifiedFiles();
     assertEquals(1, files.size());
-    ModifiedFile file = files.get(0);
-    assertEquals(ModifiedFile.Status.ADDED, file.getStatus());
+    FileStatus file = files.get(0);
+    assertEquals(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(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(Status.MODIFIED, file.getStatus());
     assertEquals("dir1/file3.txt", file.getPath());
   }
 
@@ -173,9 +174,9 @@
 
   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 {
-        return new LogCommand(settings.getHgCommandPath(), workingDir, settings.getAuthSettings())
-                .inBranch(settings.getBranchName())
+      public List<ChangeSet> execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException {
+        return new LogCommand(hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings())
+                .inBranch(root.getBranchName())
                 .withTemplate(myTemplateFile)
                 .fromRevision(fromId)
                 .toRevision(toId)
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommandTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommandTest.java	Tue May 15 10:33:21 2012 +0400
@@ -15,6 +15,7 @@
  */
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgPathProvider;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.Test;
@@ -35,8 +36,8 @@
 
     try {
       runCommand(new CommandExecutor<Boolean>() {
-        public Boolean execute(@NotNull final Settings settings, @NotNull File workingDir) throws VcsException {
-          new PushCommand(settings.getHgCommandPath(), workingDir, settings.getAuthSettings()).toRepository(settings.getRepository()).call();
+        public Boolean execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException {
+          new PushCommand(hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings()).toRepository(root.getRepository()).call();
           return null;
         }
       });
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java	Tue May 15 10:33:21 2012 +0400
@@ -15,6 +15,7 @@
  */
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgPathProvider;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.Test;
@@ -27,35 +28,35 @@
 public class StatusCommandTest extends BaseCommandTestCase {
   public void testAddedFile() throws IOException, VcsException {
     setRepository("mercurial-tests/testData/rep1", true);
-    List<ModifiedFile> files = runStatus("9875b412a788", "1d446e82d356");
+    List<FileStatus> files = runStatus("9875b412a788", "1d446e82d356");
     assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.ADDED, md.getStatus());
+    FileStatus md = files.get(0);
+    assertEquals(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");
+    List<FileStatus> files = runStatus("7209b1f1d793", "9522278aa38d");
     assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.REMOVED, md.getStatus());
+    FileStatus md = files.get(0);
+    assertEquals(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");
+    List<FileStatus> files = runStatus("9522278aa38d", "b06a290a363b");
     assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.MODIFIED, md.getStatus());
+    FileStatus md = files.get(0);
+    assertEquals(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 {
-        return new StatusCommand(settings.getHgCommandPath(), workingDir, settings.getAuthSettings())
+  private List<FileStatus> runStatus(final String fromId, final String toId) throws IOException, VcsException {
+    return runCommand(new CommandExecutor<List<FileStatus>>() {
+      public List<FileStatus> execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException {
+        return new StatusCommand(hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings())
                 .fromRevision(fromId)
                 .toRevision(toId)
                 .call();
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommandTest.java	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommandTest.java	Tue May 15 10:33:21 2012 +0400
@@ -16,8 +16,9 @@
   public void test() throws Exception {
     VcsRootImpl root = new VcsRootBuilder().withUrl("some/repository").build();
     ServerPluginConfig config = new ServerPluginConfigBuilder().build();
-    Settings settings = new Settings(new ServerHgPathProvider(config), root);
-    VersionCommand versionCommand = new VersionCommand(settings, new File(".."));
+    final ServerHgPathProvider hgPathProvider = new ServerHgPathProvider(config);
+    HgVcsRoot hgRoot = new HgVcsRoot(root);
+    VersionCommand versionCommand = new VersionCommand(hgPathProvider.getHgPath(hgRoot), new File(".."));
     HgVersion version = versionCommand.call();
     assertNotNull(version);
   }
--- a/mercurial-tests/src/testng.xml	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial-tests/src/testng.xml	Tue May 15 10:33:21 2012 +0400
@@ -13,7 +13,7 @@
       <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.HgVcsRootTest"/>
       <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"/>
@@ -22,6 +22,8 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.UnrelatedResitoriesTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.CleanupTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialResetCacheHandlerTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentMirrorCleanerTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.ListFilesSupportTest"/>
     </classes>
   </test>
 </suite>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch5/after/dir1/file1.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+aaa
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch5/after/dir1/file3.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+ccc
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch5/after/dir1/subdir/file2.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+bbb
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch5/before/dir with space/file with space.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+some text
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch5/before/dir1/file1.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+aaa
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch5/before/dir1/file3.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,2 @@
+ccc
+ddd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch5/before/dir1/subdir/file2.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,2 @@
+modified
+bbb
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch6/after/dir with space/file with space.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+some text
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch6/after/file_in_branch.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+file from the test_branch
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch6/after/path/dir1/file1.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+aaa
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch6/after/path/dir1/file3.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+ccc
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch6/after/path/dir1/subdir/file2.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+bbb
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch6/before/dir with space/file with space.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+some text
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch6/before/file_in_branch.txt	Tue May 15 10:33:21 2012 +0400
@@ -0,0 +1,1 @@
+file from the test_branch
\ No newline at end of file
--- a/mercurial.ipr	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial.ipr	Tue May 15 10:33:21 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>
@@ -444,6 +444,7 @@
     <library name="TeamCityAPI-agent">
       <CLASSES>
         <root url="jar://$TeamCityDistribution$/devPackage/agent-api.jar!/" />
+        <root url="jar://$TeamCityDistribution$/buildAgent/lib/agent.jar!/" />
       </CLASSES>
       <JAVADOC />
       <SOURCES>
--- a/mercurial.xml	Tue May 15 10:21:05 2012 +0400
+++ b/mercurial.xml	Tue May 15 10:33:21 2012 +0400
@@ -128,6 +128,7 @@
   </path>
   
   <path id="library.teamcityapi-agent.classpath">
+    <pathelement location="${path.variable.teamcitydistribution}/buildAgent/lib/agent.jar"/>
     <pathelement location="${path.variable.teamcitydistribution}/devPackage/agent-api.jar"/>
   </path>