changeset 460:8eb05f24d883

Merge Faradi-7.0.x
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Fri, 06 Jul 2012 21:26:25 +0400
parents cb5263bf8b25 (diff) b5e79418bacf (current diff)
children b21f3b3a33b1
files mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/HgVcsRoot.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigBuilder.java
diffstat 80 files changed, 1735 insertions(+), 929 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentHgPathProvider.java	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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/AgentPluginConfig.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java	Fri Jul 06 21:26:25 2012 +0400
@@ -11,4 +11,6 @@
   boolean isUseLocalMirrors(@NotNull AgentRunningBuild build);
 
   int getPullTimeout(@NotNull AgentRunningBuild build);
+
+  boolean runWithTraceback(@NotNull AgentRunningBuild build);
 }
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java	Fri Jul 06 21:26:25 2012 +0400
@@ -37,6 +37,10 @@
     return DEFAULT_PULL_TIMEOUT_SECONDS;
   }
 
+  public boolean runWithTraceback(@NotNull AgentRunningBuild build) {
+    return "true".equals(build.getSharedConfigParameters().get("teamcity.hg.run.commands.with.traceback"));
+  }
+
   @Nullable
   public Integer parseTimeout(@Nullable String timeoutStr) {
     if (timeoutStr == null)
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Fri Jul 06 21:26:25 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,13 +24,14 @@
 
   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;
   private final BuildProgressLogger myLogger;
   private final boolean myUseLocalMirrors;
   private int myPullTimeout;
+  private final boolean myUseTraceback;
 
   public MercurialIncludeRuleUpdater(@NotNull final AgentPluginConfig pluginConfig,
                                      @NotNull final MirrorManager mirrorManager,
@@ -39,13 +41,14 @@
                                      @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);
     myPullTimeout = myConfig.getPullTimeout(build);
+    myUseTraceback = myConfig.runWithTraceback(build);
   }
 
 
@@ -53,9 +56,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);
     }
@@ -73,9 +76,10 @@
       delete(mirrorDir);
       myLogger.message("Clone repository " + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl) + " into local mirror " + mirrorRepo.path());
       mirrorRepo.doClone().fromRepository(repositoryUrl)
+              .withTraceback(myUseTraceback)
               .setUpdateWorkingDir(false)
               .setUsePullProtocol(false)
-              .useUncompressedTransfer(mySettings.isUncompressedTransfer())
+              .useUncompressedTransfer(myRoot.isUncompressedTransfer())
               .call();
       myLogger.message("Clone successfully finished");
     } else {
@@ -85,6 +89,7 @@
       } else {
         myLogger.message("Start pulling changes from " + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl));
         mirrorRepo.pull().fromRepository(repositoryUrl)
+                .withTraceback(myUseTraceback)
                 .withTimeout(myPullTimeout)
                 .call();
         myLogger.message("Local mirror changes successfully pulled");
@@ -94,29 +99,35 @@
 
 
   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
       myLogger.message("Start cloning from " + (myUseLocalMirrors ? "local mirror " : "") + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl));
       repo.doClone().fromRepository(repositoryUrl)
+              .withTraceback(myUseTraceback)
               .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)
+                  .withTraceback(myUseTraceback)
+                  .withTimeout(myPullTimeout)
+                  .call();
+        } catch (UnrelatedRepositoryException e) {
+          throw new UnrelatedRepositoryException(myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl), workingDir);
+        }
         myLogger.message("Changes successfully pulled");
       }
     }
@@ -138,8 +149,8 @@
     Map<String, SubRepo> subrepos = repo.getSubrepositories(toVersion);
     for (Map.Entry<String, SubRepo> entry : subrepos.entrySet()) {
       String path = entry.getKey();
-      myLogger.message("Process subrepo " + path);
       SubRepo subrepo = entry.getValue();
+      myLogger.message("Process subrepo at path " + path + " (url: " + subrepo.url() + ")");
       SubRepo workingDirSubrepo = workingDirSubrepos.get(path);
       if (workingDirSubrepo != null && subrepo.hasDifferentUrlThan(workingDirSubrepo)) {
         myLogger.message("The url of subrepo was changed between revisions " + workingDirRevision + " and " + toVersion + " , delete the subrepo");
@@ -156,12 +167,14 @@
             if (subrepository.isValidRepository()) {
               myLogger.message("Pull from local mirror");
               subrepository.pull().fromRepository(mirrorDir)
+                      .withTraceback(myUseTraceback)
                       .withTimeout(myPullTimeout)
                       .call();
               myLogger.message("done");
             } else {
               myLogger.message("Clone subrepo from local mirror");
               subrepository.doClone().fromRepository(mirrorDir)
+                      .withTraceback(myUseTraceback)
                       .setUpdateWorkingDir(false)
                       .setUsePullProtocol(false)
                       .call();
@@ -170,8 +183,6 @@
             }
           }
         }
-      } else {
-        myLogger.message("Local mirrors aren't used, subrepo will be updated during the parent repo update");
       }
       updateSubrepositories(subrepository, subrepo.revision(), subrepo.url());
     }
@@ -180,17 +191,17 @@
 
   private void doUpdateWorkingDir(@NotNull HgRepo repo, @NotNull String revision) throws VcsException {
     myLogger.message("Updating working dir " + repo.path() + " to revision " + revision);
-    repo.update().toRevision(revision).call();
+    repo.update().withTraceback(myUseTraceback).toRevision(revision).call();
     myLogger.message("Working dir updated");
   }
 
 
-  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	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgPathProvider.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerImpl.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Fri Jul 06 21:26:25 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/CloneCommand.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java	Fri Jul 06 21:26:25 2012 +0400
@@ -31,6 +31,7 @@
   private File myWorkingDir;
   private boolean myUsePullProtocol = true;
   private boolean myUseUncompressedTransfer = false;
+  private boolean myTraceback;
 
   public CloneCommand(@NotNull String hgPath, @NotNull File workingDir, @NotNull AuthSettings authSettings) {
     super(hgPath, workingDir, authSettings);
@@ -70,12 +71,19 @@
     return this;
   }
 
+  public CloneCommand withTraceback(boolean runWithTraceback) {
+    myTraceback = runWithTraceback;
+    return this;
+  }
+
   public void call() throws VcsException {
     myWorkingDir.mkdirs();
     GeneralCommandLine cli = createCommandLine();
     File parent = myWorkingDir.getParentFile();
     cli.setWorkDirectory(parent.getAbsolutePath());
     cli.addParameter("clone");
+    if (myTraceback)
+      cli.addParameter("--traceback");
     if (myToId != null) {
       cli.addParameter("-r");
       cli.addParameter(myToId);
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 2012 +0400
@@ -0,0 +1,156 @@
+/*
+ * 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 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/IdentifyCommand.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Fri Jul 06 21:26:25 2012 +0400
@@ -34,12 +34,17 @@
   private Integer myRevisionNumber;
   private AuthSettings myAuthSettings;
   private String myRepositoryUrl;
-  private String myBranchName;
+  private String myNamedRevision;
 
   public IdentifyCommand(@NotNull String hgPath, @NotNull File workingDir, @NotNull AuthSettings authSettings) {
     super(hgPath, workingDir, authSettings);
   }
 
+  public IdentifyCommand namedRevision(@NotNull String name) {
+    myNamedRevision = name;
+    return this;
+  }
+
   public IdentifyCommand revision(@NotNull String revision) {
     myChangeSet = new ChangeSet(revision);
     return this;
@@ -60,11 +65,6 @@
     return this;
   }
 
-  public IdentifyCommand branch(@NotNull String branchName) {
-    myBranchName = branchName;
-    return this;
-  }
-
   public IdentifyCommand withAuthSettings(@NotNull AuthSettings authSettings) {
     myAuthSettings = authSettings;
     return this;
@@ -89,9 +89,9 @@
     } else if (myRevisionNumber != null) {
       cli.addParameter("--rev");
       cli.addParameter(myRevisionNumber.toString());
-    } else if (myBranchName != null) {
+    } else if (myNamedRevision != null) {
       cli.addParameter("--rev");
-      cli.addParameter(myBranchName);
+      cli.addParameter(myNamedRevision);
     }
     CommandResult res = runCommand(cli, with().failureWhenStderrNotEmpty());
     String output = res.getStdout().trim();
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MercurialXmlLogParser.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MercurialXmlLogParser.java	Fri Jul 06 21:26:25 2012 +0400
@@ -12,8 +12,6 @@
 import java.util.List;
 import java.util.Locale;
 
-import static com.intellij.openapi.util.text.StringUtil.isEmpty;
-
 /**
 * @author dmitry.neverov
 */
@@ -24,7 +22,7 @@
   private List<ChangeSet> myChangeSets = new ArrayList<ChangeSet>();
   private ChangeSet myCset = null;
   private StringBuilder myText = new StringBuilder();
-  private List<ModifiedFile> myFiles;
+  private List<FileStatus> myFiles;
   private String myFileAction;
 
   @NotNull
@@ -42,7 +40,7 @@
     } else if ("author".equals(qName)) {
       myCset.setUser(attrs.getValue("original"));
     } else if ("paths".equals(qName)) {
-      myFiles = new ArrayList<ModifiedFile>();
+      myFiles = new ArrayList<FileStatus>();
     } else if ("path".equals(qName)) {
       myFileAction = attrs.getValue("action");
       resetText();
@@ -60,7 +58,7 @@
     } else if ("paths".equals(qName)) {
       myCset.setModifiedFiles(myFiles);
     } else if ("path".equals(qName)) {
-      myFiles.add(new ModifiedFile(getStatus(myFileAction), getText()));
+      myFiles.add(new FileStatus(Status.makeStatus(myFileAction), getText()));
     } else if ("msg".equals(qName)) {
       myCset.setDescription(getText());
     } else if ("date".equals(qName)) {
@@ -96,18 +94,4 @@
   private String getId(@NotNull Attributes attrs) {
     return attrs.getValue("shortnode");
   }
-
-  private ModifiedFile.Status getStatus(String action) {
-    if (isEmpty(action))
-      return ModifiedFile.Status.UNKNOWN;
-    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;
-    }
-  }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ModifiedFile.java	Thu Jul 05 21:07:04 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/PullCommand.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Fri Jul 06 21:26:25 2012 +0400
@@ -33,6 +33,7 @@
 
   private String myPullUrl;
   private int myTimeout;
+  private boolean myTraceback;
 
   public PullCommand(@NotNull String hgPath, @NotNull File workingDir, @NotNull AuthSettings authSettings) {
     super(hgPath, workingDir, authSettings);
@@ -53,10 +54,17 @@
     return this;
   }
 
+  public PullCommand withTraceback(boolean runWithTraceback) {
+    myTraceback = runWithTraceback;
+    return this;
+  }
+
   public void call() throws VcsException {
     ensureRepositoryIsNotLocked();
     GeneralCommandLine cli = createCommandLine();
     cli.addParameter("pull");
+    if (myTraceback)
+      cli.addParameter("--traceback");
     String pullUrl = myAuthSettings != null ? myAuthSettings.getRepositoryUrlWithCredentials(myPullUrl) : myPullUrl;
     cli.addParameter(pullUrl);
     runCommand(cli, with().timeout(myTimeout));
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java	Thu Jul 05 21:07:04 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +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 VcsRoot myVcsRoot;
-  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) {
-    myVcsRoot = 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);
-  }
-
-  @NotNull
-  public VcsRoot getVcsRoot() {
-    return myVcsRoot;
-  }
-
-  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	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Fri Jul 06 21:26:25 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/UpdateCommand.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java	Fri Jul 06 21:26:25 2012 +0400
@@ -30,6 +30,7 @@
 
   private String myToId;
   private String myBranchName;
+  private boolean myTraceback;
 
   public UpdateCommand(@NotNull String hgPath, @NotNull File workingDir, @NotNull AuthSettings authSettings) {
     super(hgPath, workingDir,authSettings);
@@ -49,11 +50,18 @@
     return this;
   }
 
+  public UpdateCommand withTraceback(boolean runWithTraceback) {
+    myTraceback = runWithTraceback;
+    return this;
+  }
+
   public void call() throws VcsException {
     ensureWorkingDirIsNotLocked();
 
     GeneralCommandLine cli = createCommandLine();
     cli.addParameter("update");
+    if (myTraceback)
+      cli.addParameter("--traceback");
     addAuthConfigParams(cli);
     cli.addParameter("-C");
     cli.addParameter("-r");
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/exception/UnrelatedRepositoryException.java	Fri Jul 06 21:26:25 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/resources/buildServerResources/mercurialSettings.jsp	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/resources/buildServerResources/mercurialSettings.jsp	Fri Jul 06 21:26:25 2012 +0400
@@ -26,6 +26,13 @@
     <td><props:textProperty name="branchName" /></td>
   </tr>
   <tr>
+    <th><label>Branch Specification:</label></th>
+    <td>
+      <props:multilineProperty name="teamcity:branchSpec" value="${vcsPropertiesBean.branchSpec}" rows="3" cols="60" linkTitle="Edit branch specification" expanded="${true}" className="longField"/>
+      <span class="smallNote">Newline-delimited set or rules in the form of <b>+|-:branch name</b> (with optional <b>*</b> placeholder)</span>
+    </td>
+  </tr>
+  <tr>
     <th><label for="serverClonePath">Clone repository to: </label></th>
     <td><props:textProperty name="serverClonePath" className="longField"/>
       <div class="smallNote" style="margin: 0;">Provide path to a parent directory on TeamCity server where a cloned repository should be created (applicable to "Automatically on server" checkout mode only). Leave blank to use default path.</div>
@@ -47,11 +54,17 @@
   <l:settingsGroup title="Authorization settings">
   <tr>
     <th><label for="username">User name:</label></th>
-    <td><props:textProperty name="username"/></td>
+    <td>
+      <props:textProperty name="username"/>
+      <span class="smallNoteAttention">Leave blank to use settings from the server hgrc (see 'man hgrc' for details)</span>
+    </td>
   </tr>
   <tr>
     <th><label for="secure:password">Password:</label></th>
-    <td><props:passwordProperty name="secure:password"/></td>
+    <td>
+      <props:passwordProperty name="secure:password"/>
+      <span class="smallNoteAttention">Leave blank to use settings from the server hgrc (see 'man hgrc' for details)</span>
+    </td>
   </tr>
   </l:settingsGroup>
 
--- a/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Cleanup.java	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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 jetbrains.buildServer.vcs.VcsRoot;
+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 HgVcsRootFactory myHgVcsRootFactory;
+
+  public ListFilesSupport(@NotNull MercurialVcsSupport vcs,
+                          @NotNull HgVcsRootFactory hgVcsRootFactory) {
+    myVcs = vcs;
+    myHgVcsRootFactory = hgVcsRootFactory;
+  }
+
+  @NotNull
+  public List<VcsFileData> listFiles(@NotNull VcsRoot root, @NotNull String dir) throws VcsException {
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    myVcs.syncRepository(hgRoot);
+    String dirPath = isEmpty(dir) || dir.endsWith("/") ? dir : dir + "/";
+    return listFilesIn(hgRoot, dirPath);
+  }
+
+
+  @NotNull
+  private List<VcsFileData> listFilesIn(@NotNull HgVcsRoot root, @NotNull String dir) throws VcsException {
+    HgRepo repo = myVcs.createRepo(root);
+    List<VcsFileData> result = new ArrayList<VcsFileData>();
+    Pattern p = compile(quote(File.separator));
+    for (String file : repo.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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Fri Jul 06 21:26:25 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, CollectChangesBetweenRepositories, 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,28 +215,14 @@
 
   @NotNull
   public String getCurrentVersion(@NotNull final VcsRoot root) throws VcsException {
-    Settings settings = createSettings(root);
-    if (myConfig.checkRemoteRepositoryUpdateBeforePull()) {
-      HgRepo repo = createRepo(settings);
-      if (repo.isValidRepository()) {
-        String remoteCsetId = repo.id()
-                .repository(settings.getRepository())
-                .withAuthSettings(settings.getAuthSettings())
-                .branch(settings.getBranchName())
-                .call();
-        Map<String, ChangeSet> localBranches = repo.branches().call();
-        ChangeSet localCset = localBranches.get(settings.getBranchName());
-        if (localCset != null && localCset.getId().equals(remoteCsetId))
-          return localCset.getFullVersion();
-      }
-    }
-    syncRepository(settings);
-    HgRepo repo = createRepo(settings);//create new repo because repository cache dir can be changed (when repository became unrelated)
+    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());
-    }
-    return result.get(settings.getBranchName()).getFullVersion();
+    ChangeSet cset = result.get(hgRoot.getBranchName());
+    if (cset == null)
+      throw new VcsException("Unable to find current version for the branch: " + hgRoot.getBranchName());
+    return cset.getId();
   }
 
   public boolean sourcesUpdatePossibleIfChangesNotFound(@NotNull final VcsRoot root) {
@@ -247,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
@@ -300,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());
       }
     }
@@ -316,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());
@@ -345,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);
       }
@@ -405,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);
@@ -478,14 +452,14 @@
 
   @NotNull
   public RepositoryState getCurrentState(@NotNull VcsRoot root) throws VcsException {
-    return RepositoryStateFactory.createRepositoryState(getBranchesRevisions(root));
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    return RepositoryStateFactory.createRepositoryState(getBranchesRevisions(hgRoot), hgRoot.getBranchName());
   }
 
   @NotNull
-  private Map<String, String> getBranchesRevisions(@NotNull VcsRoot root) throws VcsException {
-    Settings settings = createSettings(root);
-    syncRepository(settings);
-    HgRepo repo = createRepo(settings);
+  private Map<String, String> getBranchesRevisions(@NotNull HgVcsRoot root) throws VcsException {
+    syncRepository(root);
+    HgRepo repo = createRepo(root);
     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());
@@ -503,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()
@@ -534,13 +508,46 @@
   }
 
   @NotNull
+  public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot,
+                                               @NotNull RepositoryState fromState,
+                                               @NotNull VcsRoot toRoot,
+                                               @NotNull RepositoryState toState,
+                                               @NotNull CheckoutRules rules) throws VcsException {
+    return collectChanges(toRoot, fromState, toState, rules);
+  }
+
+  @NotNull
+  public List<ModificationData> collectChanges(@NotNull VcsRoot root,
+                                               @NotNull RepositoryState fromState,
+                                               @NotNull RepositoryState toState,
+                                               @NotNull CheckoutRules rules) throws VcsException {
+    Set<String> reportedCsetIds = new HashSet<String>();
+    List<ModificationData> changes = new ArrayList<ModificationData>();
+    for (Map.Entry<String, String> entry : toState.getBranchRevisions().entrySet()) {
+      String branch = entry.getKey();
+      String toRevision = entry.getValue();
+      String fromRevision = fromState.getBranchRevisions().get(branch);
+      if (fromRevision == null)
+        fromRevision = fromState.getBranchRevisions().get(fromState.getDefaultBranchName());
+      if (toRevision.equals(fromRevision))
+        continue;
+      List<ModificationData> branchChanges = collectChanges(root, fromRevision, toRevision, rules);
+      for (ModificationData change : branchChanges) {
+        if (reportedCsetIds.add(change.getVersion()))
+          changes.add(change);
+      }
+    }
+    return changes;
+  }
+
+  @NotNull
   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);
@@ -548,22 +555,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();
@@ -579,10 +586,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;
@@ -593,25 +600,35 @@
     List<ChangeSetRevision> parents = cset.getParents();
     if (parents.isEmpty())
       throw new IllegalStateException("Commit " + cset.getId() + " has no parents");
-    List<VcsChange> files = toVcsChanges(cset.getModifiedFiles(), parents.get(0).getFullVersion(), cset.getFullVersion(), checkoutRules);
-    final ModificationData result = new ModificationData(cset.getTimestamp(), files, cset.getDescription(), cset.getUser(), root, cset.getFullVersion(), cset.getId());
+    List<VcsChange> files = toVcsChanges(cset.getModifiedFiles(), parents.get(0).getId(), cset.getId(), checkoutRules);
+    final ModificationData result = new ModificationData(cset.getTimestamp(), files, cset.getDescription(), cset.getUser(), root, cset.getId(), cset.getId());
     for (ChangeSetRevision parent : parents) {
-      result.addParentRevision(parent.getFullVersion());
+      result.addParentRevision(parent.getId());
     }
-    if (result.getParentRevisions().size() > 1)
-      result.setCanBeIgnored(false);
+    setCanBeIgnored(result);
     return result;
   }
 
 
+  private void setCanBeIgnored(@NotNull ModificationData md) {
+    if (md.getParentRevisions().size() > 1) {
+      //don't ignore merge commits
+      md.setCanBeIgnored(false);
+    } else if (md.getChangeCount() == 0) {
+      //don't ignore empty commits
+      md.setCanBeIgnored(false);
+    }
+  }
+
+
   @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(settings)
+      List<ChangeSet> changesets = createRepo(root).collectChanges(root)
               .fromRevision(fromCommit)
               .toRevision(toCommit)
               .call();
@@ -631,21 +648,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, to, 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, from, to, 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) {
@@ -667,19 +693,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)
@@ -697,36 +723,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;
@@ -765,21 +766,36 @@
     }
   }
 
-  private ServerHgRepo createRepo(@NotNull Settings s) throws VcsException {
-    return myRepoFactory.create(getWorkingDir(s), s.getHgCommandPath(), s.getAuthSettings());
+  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> getCheckoutProperties(@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() {
+    return new ListFilesSupport(this, myHgVcsRootFactory);
+  }
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RepoFactory.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RepoFactory.java	Fri Jul 06 21:26:25 2012 +0400
@@ -14,7 +14,7 @@
 /**
  * @author dmitry.neverov
  */
-public class RepoFactory {
+public final class RepoFactory {
 
   private final ServerPluginConfig myConfig;
   private File myLogTemplate;
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProvider.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProvider.java	Fri Jul 06 21:26:25 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";
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java	Fri Jul 06 21:26:25 2012 +0400
@@ -42,9 +42,8 @@
   }
 
   @NotNull
-  public CollectChangesCommand collectChanges(@NotNull Settings s) throws VcsException {
+  public CollectChangesCommand collectChanges(@NotNull HgVcsRoot root) throws VcsException {
     if (myConfig.dontUseRevsets()) {
-      VcsRoot root = s.getVcsRoot();
       if (shouldUseRevsetsFor(root))
         return new CollectChangesWithRevsets(this);
       return new CollectChangesNoRevsets(this, myLogNoFilesTemplate);
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java	Fri Jul 06 21:26:25 2012 +0400
@@ -19,8 +19,6 @@
 
   public boolean dontUseRevsets();
 
-  public boolean checkRemoteRepositoryUpdateBeforePull();
-
   @NotNull
   Set<Long> getRevsetParentRootIds();
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java	Fri Jul 06 21:26:25 2012 +0400
@@ -51,10 +51,6 @@
     return timeout > 0 ? timeout : DEFAULT_PULL_TIMEOUT_SECONDS;
   }
 
-  public boolean checkRemoteRepositoryUpdateBeforePull() {
-    return TeamCityProperties.getBoolean("teamcity.hg.check.repository.updated.before.pull");
-  }
-
   @NotNull
   public Set<Long> getRevsetParentRootIds() {
     String parentRootIds = TeamCityProperties.getProperty("teamcity.hg.use.revsets.root.ids", "");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java	Fri Jul 06 21:26:25 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.Util.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/AgentSideCheckoutTest.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Fri Jul 06 21:26:25 2012 +0400
@@ -19,6 +19,7 @@
 import jetbrains.buildServer.agent.BuildAgentConfiguration;
 import jetbrains.buildServer.agent.BuildProgressLogger;
 import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.util.TestFor;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.IncludeRule;
 import jetbrains.buildServer.vcs.VcsException;
@@ -37,6 +38,8 @@
 import java.util.Map;
 import java.util.concurrent.*;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+
 /**
  * @author Pavel.Sher
  *         Date: 30.07.2008
@@ -82,9 +85,9 @@
 
   public void should_work_when_path_to_hg_is_property() throws Exception {
     VcsRootImpl root = new VcsRootBuilder()
-            .withUrl(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath())
+            .withUrl(copyRepository(myTempFiles, simpleRepo()).getAbsolutePath())
             .withHgPath(HG_PATH_REFERENCE).build();
-    testUpdate(root, "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null));
+    testUpdate(root, "b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null));
   }
 
 
@@ -253,10 +256,10 @@
     assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));
   }
 
-  //TW-19703
+  @TestFor(issues = "TW-19703")
   public void should_work_when_repository_is_locked() throws Exception{
     VcsRootImpl root = new VcsRootBuilder()
-            .withUrl(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath())
+            .withUrl(copyRepository(myTempFiles, simpleRepo()).getAbsolutePath())
             .withHgPath(HG_PATH_REFERENCE).build();
     File workingDir = doUpdate(root, "4:b06a290a363b");
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Fri Jul 06 21:26:25 2012 +0400
@@ -25,6 +25,8 @@
 import java.io.File;
 import java.io.IOException;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+
 /**
  * @author Pavel.Sher
  *         Date: 31.07.2008
@@ -45,19 +47,15 @@
   }
 
   protected VcsRootImpl createVcsRoot(@NotNull String repPath) throws IOException {
-    File repository = LocalRepositoryUtil.prepareRepository(repPath);
+    File repository = copyRepository(myTempFiles, repPath);
     return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).build();
   }
 
   protected VcsRootImpl createVcsRoot(@NotNull String repPath, @NotNull String branchName) throws IOException {
-    File repository = LocalRepositoryUtil.prepareRepository(repPath);
+    File repository = copyRepository(myTempFiles, repPath);
     return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).withBranch(branchName).build();
   }
 
-  protected void cleanRepositoryAfterTest(@NotNull String repPath) {
-    LocalRepositoryUtil.forgetRepository(repPath);
-  }
-
   protected String simpleRepo() {
     return new File("mercurial-tests/testData/rep1").getAbsolutePath();
   }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java	Fri Jul 06 21:26:25 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
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Fri Jul 06 21:26:25 2012 +0400
@@ -2,12 +2,11 @@
 
 import com.intellij.openapi.diagnostic.Logger;
 import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSetRevision;
 import jetbrains.buildServer.log.Log4jFactory;
+import jetbrains.buildServer.util.TestFor;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.ModificationData;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
-import org.jetbrains.annotations.NotNull;
 import org.jmock.Mockery;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -15,9 +14,9 @@
 import java.io.File;
 import java.util.List;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertTrue;
 
 /**
  * @author dmitry.neverov
@@ -42,7 +41,7 @@
 
     File original = new File("mercurial-tests/testData/rep2");
     File copy = new File(myTempFiles.createTempDir(), "rep2");
-    LocalRepositoryUtil.copyRepository(original, copy);
+    copyRepository(original, copy);
     myRepository = copy.getAbsolutePath();
   }
 
@@ -51,7 +50,7 @@
   }
 
 
-  //TW-17882
+  @TestFor(issues = "TW-17882")
   public void should_detect_changes_from_named_branches() throws Exception {
     VcsRootImpl root = new VcsRootBuilder().withUrl(myRepository).build();
 
@@ -59,33 +58,20 @@
     assertEquals(4, changes.size());
     for (ModificationData change : changes) {
       assertFalse(change.getParentRevisions().isEmpty());
-      checkVersionsHaveNumbers(change.getParentRevisions());
     }
 
     changes = myHg.collectChanges(root, "12:1e620196c4b6", "18:df04faa7575a", CheckoutRules.DEFAULT);
     assertEquals(6, changes.size());
     for (ModificationData change : changes) {
       assertFalse(change.getParentRevisions().isEmpty());
-      checkVersionsHaveNumbers(change.getParentRevisions());
     }
   }
 
 
-  //TW-17882
+  @TestFor(issues = "TW-17882")
   public void should_report_changes_only_from_merged_named_branches() throws Exception {
     VcsRootImpl root = new VcsRootBuilder().withUrl(myRepository).build();
     List<ModificationData> changes = myHg.collectChanges(root, "1e620196c4b6", "505c5b9d01e6", CheckoutRules.DEFAULT);
     assertEquals(2, changes.size());
-    for (ModificationData change : changes) {
-      checkVersionsHaveNumbers(change.getParentRevisions());
-    }
-  }
-
-
-  private void checkVersionsHaveNumbers(@NotNull List<String> versions) {
-    for (String version : versions) {
-      ChangeSetRevision rev = new ChangeSetRevision(version);
-      assertTrue(rev.getRevNumber() != -1);
-    }
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVcsRootTest.java	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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.Collection;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.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());
+  }
+
+
+  public void list_root_dir() throws Exception {
+    ListDirectChildrenPolicy policy = getListFilesPolicy();
+    Collection<VcsFileData> files = policy.listFiles(myRoot, "");
+    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();
+    Collection<VcsFileData> files = policy.listFiles(myRoot, "dir1");
+    assertTrue(files.contains(file("file1.txt")));
+    assertTrue(files.contains(file("file3.txt")));
+    assertTrue(files.contains(dir("subdir")));
+
+    files = policy.listFiles(myRoot, "dir1/subdir/");
+    assertTrue(files.contains(file("file2.txt")));
+  }
+
+  @NotNull
+  private ListDirectChildrenPolicy getListFilesPolicy() {
+    ListDirectChildrenPolicy listFilesPolicy = (ListDirectChildrenPolicy) myVcs.getListFilesPolicy();
+    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/LocalRepositoryUtil.java	Thu Jul 05 21:07:04 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +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.TempFiles;
-import jetbrains.buildServer.util.FileUtil;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Pavel.Sher
- *         Date: 14.07.2008
- */
-public class LocalRepositoryUtil {
-  private final static TempFiles myTempFiles = new TempFiles();
-  private final static Map<String, File> myRepositories = new HashMap<String, File>();
-  static {
-    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
-      public void run() {
-        myTempFiles.cleanup();
-      }
-    }));
-  }
-
-  public static File prepareRepository(@NotNull String repPath) throws IOException {
-    File repository = myRepositories.get(repPath);
-    if (repository != null) return repository;
-    final File tempDir = myTempFiles.createTempDir();
-    copyRepository(new File(repPath), tempDir);
-    myRepositories.put(repPath, tempDir);
-    return tempDir;
-  }
-
-  public static void forgetRepository(@NotNull String repPath) {
-    myRepositories.remove(repPath);
-  }
-
-
-  public static void copyRepository(File src, File dst) throws IOException {
-    FileUtil.copyDir(src, dst);
-    if (new File(dst, "hg").isDirectory())
-      FileUtil.rename(new File(dst, "hg"), new File(dst, ".hg"));
-  }
-}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Fri Jul 06 21:26:25 2012 +0400
@@ -19,8 +19,9 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
 import jetbrains.buildServer.vcs.*;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
-import jetbrains.buildServer.vcs.patches.PatchBuilderImpl;
 import junit.framework.Assert;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
 import org.jetbrains.annotations.NotNull;
 import org.jmock.Mockery;
 import org.testng.annotations.BeforeMethod;
@@ -31,12 +32,15 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
 
-import static com.intellij.openapi.util.io.FileUtil.copyDir;
-import static com.intellij.openapi.util.io.FileUtil.moveDirWithContent;
-import static jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigBuilder.serverPluginConfig;
+import static com.intellij.openapi.util.io.FileUtil.*;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.buildPatch;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+import static jetbrains.buildServer.util.Util.map;
+import static jetbrains.buildServer.vcs.RepositoryStateFactory.createRepositoryState;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
 
 @Test
 public class MercurialVcsSupportTest extends BaseMercurialTestCase {
@@ -62,12 +66,12 @@
   public void test_get_current_version() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    assertEquals(myVcs.getCurrentVersion(vcsRoot), "10:9c6a6b4aede0");
+    assertEquals(myVcs.getCurrentVersion(vcsRoot), "9c6a6b4aede0");
     assertEquals("9c6a6b4aede0", myVcs.getVersionDisplayName("10:9c6a6b4aede0", vcsRoot));
 
-    assertEquals(myVcs.getCurrentVersion(createVcsRoot(simpleRepo(), "test_branch")), "8:04c3ae4c6312");
+    assertEquals(myVcs.getCurrentVersion(createVcsRoot(simpleRepo(), "test_branch")), "04c3ae4c6312");
 
-    assertEquals(myVcs.getCurrentVersion(createVcsRoot(simpleRepo(), "name with space")), "9:9babcf2d5705");
+    assertEquals(myVcs.getCurrentVersion(createVcsRoot(simpleRepo(), "name with space")), "9babcf2d5705");
   }
 
   private List<ModificationData> collectChanges(@NotNull VcsRoot vcsRoot, @NotNull String from, @NotNull String to, @NotNull CheckoutRules rules) throws VcsException {
@@ -100,21 +104,21 @@
     ModificationData md1 = changes.get(0);
     ModificationData md2 = changes.get(1);
     ModificationData md3 = changes.get(2);
-    assertEquals(md1.getVersion(), "1:1d446e82d356");
+    assertEquals(md1.getVersion(), "1d446e82d356");
     assertEquals(md1.getDescription(), "new file added");
     List<VcsChange> files1 = md1.getChanges();
     assertEquals(1, files1.size());
     assertEquals(VcsChangeInfo.Type.ADDED, files1.get(0).getType());
     assertEquals(normalizePath(files1.get(0).getRelativeFileName()), "dir1/file3.txt");
 
-    assertEquals(md2.getVersion(), "2:7209b1f1d793");
+    assertEquals(md2.getVersion(), "7209b1f1d793");
     assertEquals(md2.getDescription(), "file4.txt added");
     List<VcsChange> files2 = md2.getChanges();
     assertEquals(1, files2.size());
     assertEquals(files2.get(0).getType(), VcsChangeInfo.Type.ADDED);
     assertEquals(normalizePath(files2.get(0).getRelativeFileName()), "dir1/file4.txt");
 
-    assertEquals(md3.getVersion(), "3:9522278aa38d");
+    assertEquals(md3.getVersion(), "9522278aa38d");
     assertEquals(md3.getDescription(), "file removed");
     List<VcsChange> files3 = md3.getChanges();
     assertEquals(1, files3.size());
@@ -122,21 +126,12 @@
     assertEquals(normalizePath(files3.get(0).getRelativeFileName()), "dir1/file4.txt");
   }
 
-  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);
-    builder.close();
-    return output;
-  }
-
   @Test
   public void test_build_patch() throws IOException, VcsException {
     setName("cleanPatch1");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));
     checkPatchResult(output.toByteArray());
 
     File clonedReposParentDir = myPluginConfig.getCachesDir();
@@ -152,7 +147,7 @@
     setName("patch1");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, "3:9522278aa38d", "4:b06a290a363b", new CheckoutRules(""));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "3:9522278aa38d", "4:b06a290a363b", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
   }
@@ -161,7 +156,7 @@
     setName("patch1_checkout_rules");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, "3:9522278aa38d", "4:b06a290a363b", new CheckoutRules("+:dir1=>path"));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "3:9522278aa38d", "4:b06a290a363b", new CheckoutRules("+:dir1=>path"));
 
     checkPatchResult(output.toByteArray());
   }
@@ -170,7 +165,7 @@
     setName("cleanPatch1_checkout_rules");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "4:b06a290a363b", new CheckoutRules("+:dir1/subdir=>."));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "4:b06a290a363b", new CheckoutRules("+:dir1/subdir=>."));
 
     checkPatchResult(output.toByteArray());
   }
@@ -179,7 +174,7 @@
     setName("patch2");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, "3:9522278aa38d", "6:b9deb9a1c6f4", new CheckoutRules(""));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "3:9522278aa38d", "6:b9deb9a1c6f4", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
   }
@@ -195,7 +190,7 @@
     VcsRootImpl root = vcsRoot().withUrl(r1.getAbsolutePath()).build();
 
     setName("clean_patch_with_subrepositories");
-    checkPatchResult(buildPatch(root, null, "3:d350e7209906", CheckoutRules.DEFAULT).toByteArray());
+    checkPatchResult(buildPatch(myVcs, root, null, "3:d350e7209906", CheckoutRules.DEFAULT).toByteArray());
   }
 
   public void test_get_content() throws IOException, VcsException {
@@ -235,9 +230,24 @@
     }
   }
 
+
+  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());
 
     String actualTag = myVcs.label("new:tag", "1:1d446e82d356", vcsRoot, new CheckoutRules(""));
     assertEquals(actualTag, "new_tag");
@@ -254,7 +264,6 @@
 
   public void test_tag_in_branch() throws IOException, VcsException {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
-    cleanRepositoryAfterTest(simpleRepo());
 
     String actualTag = myVcs.label("branch_tag", "7:376dcf05cd2a", vcsRoot, new CheckoutRules(""));
     assertEquals(actualTag, "branch_tag");
@@ -272,9 +281,8 @@
 
   public void test_tag_with_specified_username() throws IOException, VcsException {
     final String customUserForTag = "John Doe <john@some.org>";
-    File repository = LocalRepositoryUtil.prepareRepository(simpleRepo());
+    File repository = copyRepository(myTempFiles, simpleRepo());
     VcsRoot root = vcsRoot().withUrl(repository.getAbsolutePath()).withUserForTag(customUserForTag).build();
-    cleanRepositoryAfterTest(simpleRepo());
 
     myVcs.label("tag_by_specified_user", "10:9c6a6b4aede0", root, CheckoutRules.DEFAULT);
 
@@ -287,7 +295,6 @@
 
   public void labeling_should_not_populate_files_in_local_mirror() throws Exception {
     VcsRootImpl root = createVcsRoot(simpleRepo());
-    cleanRepositoryAfterTest(simpleRepo());
     myVcs.getCurrentVersion(root);
     File mirror = myVcs.getMirrorManager().getMirrorDir(root.getProperty(Constants.REPOSITORY_PROP));
     File[] files = mirror.listFiles();
@@ -310,7 +317,7 @@
     assertEquals(1, changes.size());
 
     ModificationData md1 = changes.get(0);
-    assertEquals(md1.getVersion(), "7:376dcf05cd2a");
+    assertEquals(md1.getVersion(), "376dcf05cd2a");
     assertEquals(md1.getDescription(), "new file added in the test_branch");
     List<VcsChange> files1 = md1.getChanges();
     assertEquals(1, files1.size());
@@ -321,7 +328,7 @@
     assertEquals(1, changes.size());
 
     md1 = changes.get(0);
-    assertEquals(md1.getVersion(), "8:04c3ae4c6312");
+    assertEquals(md1.getVersion(), "04c3ae4c6312");
     assertEquals(md1.getDescription(), "file modified");
   }
 
@@ -329,7 +336,7 @@
     setName("patch3");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules(""));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
   }
@@ -338,7 +345,7 @@
     setName("patch3_checkout_rules1");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules("+:.=>path"));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules("+:.=>path"));
 
     checkPatchResult(output.toByteArray());
   }
@@ -347,7 +354,7 @@
     setName("patch3_checkout_rules2");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules("+:dir1=>path/dir1\n+:dir with space"));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules("+:dir1=>path/dir1\n+:dir with space"));
 
     checkPatchResult(output.toByteArray());
   }
@@ -356,7 +363,7 @@
     setName("patch4");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, "7:376dcf05cd2a", "8:04c3ae4c6312", new CheckoutRules(""));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "7:376dcf05cd2a", "8:04c3ae4c6312", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
   }
@@ -366,16 +373,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 {
@@ -384,13 +391,27 @@
     File cloneDir = myTempFiles.createTempDir();
     vcsRoot.addProperty(Constants.SERVER_CLONE_PATH_PROP, cloneDir.getAbsolutePath());
 
-    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));
+    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
 
     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(myVcs, 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(myVcs, vcsRoot, "6:hahahahahaha", "3:9522278aa38d", new CheckoutRules("+:.=>path"));
+    checkPatchResult(output.toByteArray());
+  }
+
   private String mergeCommittsRepo() {
     return new File("mercurial-tests/testData/rep2").getAbsolutePath();
   }
@@ -401,8 +422,8 @@
     List<ModificationData> changes = myVcs.collectChanges(defaultRoot, "11:48177654181c", branchRoot, "10:fc524efc2bc4", CheckoutRules.DEFAULT);
     assertEquals(changes.size(), 2);
 
-    assertEquals("9:8c44244d6645", changes.get(0).getVersion());
-    assertEquals("10:fc524efc2bc4", changes.get(1).getVersion());
+    assertEquals(changes.get(0).getVersion(), "8c44244d6645");
+    assertEquals(changes.get(1).getVersion(), "fc524efc2bc4");
   }
 
   public void collectChanges_should_return_all_changes_from_branch() throws Exception {
@@ -418,9 +439,9 @@
     List<ModificationData> changes = collectChanges(vcsRoot, "1:a3d15477d297", "4:6eeb8974fe67", CheckoutRules.DEFAULT);
     assertEquals(changes.size(), 3);
 
-    assertEquals("2:db8a04d262f3", changes.get(0).getVersion());
-    assertEquals("3:2538c02bafeb", changes.get(1).getVersion());
-    assertEquals("4:6eeb8974fe67", changes.get(2).getVersion());
+    assertEquals(changes.get(0).getVersion(), "db8a04d262f3");
+    assertEquals(changes.get(1).getVersion(), "2538c02bafeb");
+    assertEquals(changes.get(2).getVersion(), "6eeb8974fe67");
 
     assertFiles(Arrays.asList("A dir1/file1.txt"), changes.get(0));
     assertFiles(Arrays.asList("A dir2/file2.txt"), changes.get(1));
@@ -472,8 +493,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());
   }
 
 
@@ -504,7 +525,6 @@
 
   public void labeling_should_not_change_vcs_root_settings() throws Exception {
     VcsRootImpl root = createVcsRoot(simpleRepo(), "test_branch");
-    cleanRepositoryAfterTest(simpleRepo());
     assertNull(root.getProperty(Constants.SERVER_CLONE_PATH_PROP));
     myVcs.label("branch_tag", "7:376dcf05cd2a", root, CheckoutRules.DEFAULT);
     assertNull(root.getProperty(Constants.SERVER_CLONE_PATH_PROP));
@@ -535,72 +555,57 @@
   }
 
 
-  public void getCurrentVersion_should_pull_only_when_remote_repository_is_updated() throws IOException, VcsException {
-    ServerPluginConfig config = serverPluginConfig().
-            cachesDir(myTempFiles.createTempDir())
-            .checkRemoteRepositoryUpdatedBeforePull()
-            .build();
-
-    CustomRepoFactory repoFactory = new CustomRepoFactory(config);
-    MercurialVcsSupport vcs = Util.createMercurialServerSupport(new Mockery(), config, repoFactory);
-    VcsRootImpl root = createVcsRoot(simpleRepo());
-
-    vcs.getCurrentVersion(root);
-
-    AtomicInteger fetchCount = repoFactory.getFetchCount();
-    assertEquals(fetchCount.get(), 1);
-
-    vcs.getCurrentVersion(root);
-
-    fetchCount = repoFactory.getFetchCount();
-    assertEquals(fetchCount.get(), 1);
+  public void collect_changes_between_states() throws Exception {
+    VcsRootImpl root = createVcsRoot(myRep2Path);
+    List<ModificationData> changes = myVcs.collectChanges(root,
+            createRepositoryState(map("default", "1e620196c4b6"), "default"),
+            createRepositoryState(map("default", "505c5b9d01e6", "personal-branch", "96b78d73081d"), "default"),
+            CheckoutRules.DEFAULT);
+    assertEquals(changes.size(), 4);
+    assertThat(changes, hasItem(withVersion("dec47d2d49bf")));
+    assertThat(changes, hasItem(withVersion("78e67807f916")));
+    assertThat(changes, hasItem(withVersion("96b78d73081d")));
+    assertThat(changes, hasItem(withVersion("505c5b9d01e6")));
   }
 
 
-  private class CustomRepoFactory extends RepoFactory {
-    private final ServerPluginConfig myConfig;
-    private final AtomicInteger myFetchCount;
-
-    private CustomRepoFactory(@NotNull ServerPluginConfig config) throws IOException {
-      super(config);
-      myConfig = config;
-      myFetchCount = new AtomicInteger(0);
-    }
-
-    @NotNull
-    @Override
-    public ServerHgRepo create(@NotNull File workingDir, @NotNull String hgPath, @NotNull AuthSettings authSettings) throws VcsException {
-      return new PullCountingHgRepo(myConfig, workingDir, hgPath, authSettings, myFetchCount);
-    }
-
-    AtomicInteger getFetchCount() {
-      return myFetchCount;
-    }
+  public void collect_changes_between_states_does_not_report_duplicate_changes() throws Exception {
+    VcsRootImpl root = createVcsRoot(myRep2Path);
+    List<ModificationData> changes = myVcs.collectChanges(root,
+            createRepositoryState(map("default", "8c44244d6645"), "default"),
+            createRepositoryState(map("default", "505c5b9d01e6", "personal-branch", "9ec402c74298"), "default"),
+            CheckoutRules.DEFAULT);
+    assertEquals(changes.size(), 8);
+    assertThat(changes, hasItem(withVersion("9ec402c74298")));
+    assertThat(changes, hasItem(withVersion("505c5b9d01e6")));
+    assertThat(changes, hasItem(withVersion("96b78d73081d")));
+    assertThat(changes, hasItem(withVersion("78e67807f916")));
+    assertThat(changes, hasItem(withVersion("dec47d2d49bf")));
+    assertThat(changes, hasItem(withVersion("1e620196c4b6")));
+    assertThat(changes, hasItem(withVersion("48177654181c")));
+    assertThat(changes, hasItem(withVersion("fc524efc2bc4")));
   }
 
-  private class PullCountingHgRepo extends ServerHgRepo {
-
-    private final AtomicInteger myPullCount;
 
-    private PullCountingHgRepo(@NotNull ServerPluginConfig config,
-                               @NotNull File workingDir,
-                               @NotNull String hgPath,
-                               @NotNull AuthSettings authSettings,
-                               @NotNull AtomicInteger pullCount) {
-      super(config, workingDir, hgPath, authSettings);
-      myPullCount = pullCount;
+  private ModificationDataVersionMatcher withVersion(@NotNull String version) {
+    return new ModificationDataVersionMatcher(version);
+  }
+
+  class ModificationDataVersionMatcher extends TypeSafeMatcher<ModificationData> {
+
+    private final String myVersion;
+
+    public ModificationDataVersionMatcher(@NotNull String version) {
+      myVersion = version;
     }
 
     @Override
-    public PullCommand pull() {
-      myPullCount.incrementAndGet();
-      return super.pull();
+    public boolean matchesSafely(ModificationData m) {
+      return myVersion.equals(m.getVersion());
     }
 
-    @Override
-    public CloneCommand doClone() {
-      myPullCount.incrementAndGet();
-      return super.doClone();
+    public void describeTo(Description description) {
+      description.appendText("modification with version").appendValue(myVersion);
     }
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RevisionFormatTest.java	Fri Jul 06 21:26:25 2012 +0400
@@ -0,0 +1,145 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
+import jetbrains.buildServer.log.Log4jFactory;
+import jetbrains.buildServer.vcs.*;
+import jetbrains.buildServer.vcs.patches.PatchTestCase;
+import org.jetbrains.annotations.NotNull;
+import org.jmock.Mockery;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.buildPatch;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+
+@Test
+public class RevisionFormatTest extends PatchTestCase {
+
+  static {
+    Logger.setFactory(new Log4jFactory());
+  }
+
+  private TempFiles myTempFiles;
+  private MercurialVcsSupport myVcs;
+  private VcsRoot myRoot;
+  private File myRemoteRepoDir;
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    myTempFiles = new TempFiles();
+    Mockery context = new Mockery();
+    ServerPluginConfig myPluginConfig = new ServerPluginConfigBuilder()
+            .cachesDir(myTempFiles.createTempDir())
+            .build();
+    myVcs = Util.createMercurialServerSupport(context, myPluginConfig);
+
+    myRemoteRepoDir = copyRepository(myTempFiles, new File("mercurial-tests/testData/rep1").getAbsolutePath());
+    myRoot = vcsRoot().withUrl(myRemoteRepoDir.getAbsolutePath()).build();
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    myTempFiles.cleanup();
+  }
+
+
+  public void collect_changes_result_does_not_depend_on_revnums() throws VcsException {
+    List<ModificationData> changesWithRevnums = myVcs.collectChanges(myRoot,
+            "1:1d446e82d356", "3:9522278aa38d", CheckoutRules.DEFAULT);
+    List<ModificationData> changesWithoutRevnums = myVcs.collectChanges(myRoot,
+            "1d446e82d356", "9522278aa38d", CheckoutRules.DEFAULT);
+    assertEquals(changesWithoutRevnums, changesWithRevnums);
+  }
+
+
+  public void labeling_does_not_depend_on_revnums() throws Exception {
+    myVcs.label("tag1", "3:9522278aa38d", myRoot, CheckoutRules.DEFAULT);
+    myVcs.label("tag2", "9522278aa38d", myRoot, CheckoutRules.DEFAULT);
+    HgRepo repo = createRepo(myRemoteRepoDir);
+    String tag1 = repo.id().inLocalRepository().namedRevision("tag1").call();
+    String tag2 = repo.id().inLocalRepository().namedRevision("tag2").call();
+    assertEquals(tag2, tag1);
+  }
+
+
+  public void get_file_content_does_not_depend_on_revnums() throws Exception {
+    byte[] contentWithRevnums = myVcs.getContent("dir1/file4.txt", myRoot, "2:7209b1f1d793");
+    byte[] contentWithoutRevnums = myVcs.getContent("dir1/file4.txt", myRoot, "7209b1f1d793");
+    assertEquals(contentWithoutRevnums, contentWithRevnums);
+  }
+
+
+  public void clean_patch_does_not_depend_on_revnums() throws Exception {
+    setName("cleanPatch1_checkout_rules");
+    ByteArrayOutputStream output = buildPatch(myVcs, myRoot, null, "4:b06a290a363b", new CheckoutRules("+:dir1/subdir=>."));
+    checkPatchResult(output.toByteArray());
+    output = buildPatch(myVcs, myRoot, null, "b06a290a363b", new CheckoutRules("+:dir1/subdir=>."));
+    checkPatchResult(output.toByteArray());
+  }
+
+  public void incremental_patch_does_not_depend_on_revnums() throws Exception {
+    setName("patch2");
+    ByteArrayOutputStream output = buildPatch(myVcs, myRoot, "3:9522278aa38d", "6:b9deb9a1c6f4", CheckoutRules.DEFAULT);
+    checkPatchResult(output.toByteArray());
+    output = buildPatch(myVcs, myRoot, "9522278aa38d", "6:b9deb9a1c6f4", CheckoutRules.DEFAULT);
+    checkPatchResult(output.toByteArray());
+    output = buildPatch(myVcs, myRoot, "3:9522278aa38d", "b9deb9a1c6f4", CheckoutRules.DEFAULT);
+    checkPatchResult(output.toByteArray());
+    output = buildPatch(myVcs, myRoot, "9522278aa38d", "b9deb9a1c6f4", CheckoutRules.DEFAULT);
+    checkPatchResult(output.toByteArray());
+  }
+
+
+  public void should_not_include_revnum_in_current_version() throws VcsException {
+    String currentVersion = myVcs.getCurrentVersion(myRoot);
+    assertFalse(currentVersion.contains(":"));
+  }
+
+
+  public void should_not_include_revnum_in_current_state() throws VcsException {
+    RepositoryState state = myVcs.getCurrentState(myRoot);
+    for (Map.Entry<String, String> entry : state.getBranchRevisions().entrySet()) {
+      String branchName = entry.getKey();
+      String revision = entry.getKey();
+      assertFalse(revision.contains(":"), "Revision of branch " + branchName + " contains revnum: " + revision);
+    }
+  }
+
+
+  public void should_not_include_revnum_in_collected_changes() throws VcsException {
+    List<ModificationData> changes = myVcs.collectChanges(myRoot, "1d446e82d356", "9522278aa38d", CheckoutRules.DEFAULT);
+    for (ModificationData c : changes) {
+      assertFalse(c.getVersion().contains(":"), "Change version contains revnum: " + c.toString());
+      for (String parentVersion : c.getParentRevisions()) {
+        assertFalse(parentVersion.contains(":"), "Parent version contains revnum: " + c.toString());
+      }
+      for (VcsChange changedFile : c.getChanges()) {
+        assertFalse(changedFile.getAfterChangeRevisionNumber().contains(":"),
+                "Vcs change contains revnum : " + changedFile.toString());
+        assertFalse(changedFile.getBeforeChangeRevisionNumber().contains(":"),
+                "Vcs change contains revnum : " + changedFile.toString());
+      }
+    }
+  }
+
+
+  @Override
+  protected String getTestDataPath() {
+    return "mercurial-tests/testData";
+  }
+
+  private HgRepo createRepo(@NotNull File dir) throws IOException {
+    return new HgRepo(dir, Util.getHgPath(), new AuthSettings());
+  }
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java	Fri Jul 06 21:26:25 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/ServerPluginConfigBuilder.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigBuilder.java	Fri Jul 06 21:26:25 2012 +0400
@@ -15,7 +15,6 @@
   private String myHgPath;
   private File myCachesDir;
   private boolean myDontUseRevsets = false;
-  private boolean myCheckRemoteRepositoryUpdatedBeforePull = false;
 
   @NotNull
   public ServerPluginConfig build() {
@@ -44,10 +43,6 @@
         return myDontUseRevsets;
       }
 
-      public boolean checkRemoteRepositoryUpdateBeforePull() {
-        return myCheckRemoteRepositoryUpdatedBeforePull;
-      }
-
       @NotNull
       public Set<Long> getRevsetParentRootIds() {
         return new HashSet<Long>();
@@ -55,9 +50,6 @@
     };
   }
 
-  public static ServerPluginConfigBuilder serverPluginConfig() {
-    return new ServerPluginConfigBuilder();
-  }
 
   public ServerPluginConfigBuilder userPullProtocol(boolean doUse) {
     myUsePullProtocol = doUse;
@@ -78,9 +70,4 @@
     myDontUseRevsets = true;
     return this;
   }
-
-  public ServerPluginConfigBuilder checkRemoteRepositoryUpdatedBeforePull() {
-    myCheckRemoteRepositoryUpdatedBeforePull = true;
-    return this;
-  }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java	Thu Jul 05 21:07:04 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/UnrelatedResitoriesTest.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java	Fri Jul 06 21:26:25 2012 +0400
@@ -25,7 +25,7 @@
 @Test
 public class UnrelatedResitoriesTest {
 
-  private final static String CURRENT_VERSION_OF_NEW_REPO = "18:df04faa7575a";
+  private final static String CURRENT_VERSION_OF_NEW_REPO = "df04faa7575a";
   private MercurialVcsSupport myVcs;
   private TempFiles myTempFiles;
   private File myRepositoryLocation;
@@ -84,7 +84,7 @@
   }
 
   private void copyRepository(@NotNull final File src) throws IOException {
-    LocalRepositoryUtil.copyRepository(src, myRepositoryLocation);
+    Util.copyRepository(src, myRepositoryLocation);
   }
 
   private MercurialVcsSupport createVcs() throws IOException {
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Fri Jul 06 21:26:25 2012 +0400
@@ -1,14 +1,22 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.TempFiles;
 import jetbrains.buildServer.serverSide.BuildServerListener;
 import jetbrains.buildServer.serverSide.SBuildServer;
 import jetbrains.buildServer.util.EventDispatcher;
+import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.cache.ResetCacheRegister;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsManager;
+import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.vcs.patches.PatchBuilderImpl;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.util.concurrent.Executors;
@@ -36,7 +44,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 +52,36 @@
       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);
+  }
+
+
+  public static File copyRepository(@NotNull TempFiles tempFiles, @NotNull String repPath) throws IOException {
+    File tempDir = tempFiles.createTempDir();
+    copyRepository(new File(repPath), tempDir);
+    return tempDir;
+  }
+
+  public static void copyRepository(@NotNull File src, @NotNull File dst) throws IOException {
+    FileUtil.copyDir(src, dst);
+    if (new File(dst, "hg").isDirectory())
+      FileUtil.rename(new File(dst, "hg"), new File(dst, ".hg"));
+  }
+
+  public static ByteArrayOutputStream buildPatch(@NotNull MercurialVcsSupport vcs,
+                                                 @NotNull VcsRoot vcsRoot,
+                                                 @Nullable String from,
+                                                 @NotNull String to,
+                                                 @NotNull CheckoutRules rules) throws IOException, VcsException {
+    ByteArrayOutputStream output = new ByteArrayOutputStream();
+    PatchBuilderImpl builder = new PatchBuilderImpl(output);
+    vcs.buildPatch(vcsRoot, from, to, builder, rules);
+    builder.close();
+    return output;
   }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java	Fri Jul 06 21:26:25 2012 +0400
@@ -28,6 +28,8 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+
 public class BaseCommandTestCase extends BaseTestCase {
   private String myRepository;
   private String myUsername;
@@ -55,8 +57,10 @@
 
     vcsRootProps.put(Constants.REPOSITORY_PROP, myRepository);
 
+    TempFiles tempFiles = new TempFiles();
+
     if (myCloneRequired) {
-      File repository = LocalRepositoryUtil.prepareRepository(new File(myRepository).getAbsolutePath());
+      File repository = copyRepository(tempFiles, new File(myRepository).getAbsolutePath());
       vcsRootProps.put(Constants.REPOSITORY_PROP, repository.getAbsolutePath());
     }
 
@@ -68,25 +72,25 @@
       vcsRootProps.put(Constants.PASSWORD, myPassword);
     }
 
-    TempFiles tf = new TempFiles();
     VcsRoot vcsRoot = new VcsRootImpl(1, vcsRootProps);
-    ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(tf.createTempDir()).build();
+    ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(tempFiles.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();
+      tempFiles.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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommandTest.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommandTest.java	Fri Jul 06 21:26:25 2012 +0400
@@ -12,6 +12,8 @@
 import java.io.File;
 import java.io.IOException;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+
 /**
  * @author dmitry.neverov
  */
@@ -35,7 +37,7 @@
 
 
   public void clone_should_be_made_to_its_destination_dir() throws IOException, VcsException {
-    File repository = LocalRepositoryUtil.prepareRepository(new File("mercurial-tests/testData/rep1").getAbsolutePath());
+    File repository = copyRepository(myTempFiles, new File("mercurial-tests/testData/rep1").getAbsolutePath());
 
     VcsRootImpl root = new VcsRootImpl(1, "rootForTest");
     root.addProperty(Constants.REPOSITORY_PROP, repository.getAbsolutePath());
@@ -43,10 +45,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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommandTest.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommandTest.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommandTest.java	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial-tests/src/testng.xml	Fri Jul 06 21:26:25 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,9 @@
       <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"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.RevisionFormatTest"/>
     </classes>
   </test>
 </suite>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/patch5/after/dir1/file1.txt	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 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	Fri Jul 06 21:26:25 2012 +0400
@@ -0,0 +1,1 @@
+file from the test_branch
\ No newline at end of file
--- a/mercurial.ipr	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial.ipr	Fri Jul 06 21:26:25 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	Thu Jul 05 21:07:04 2012 +0400
+++ b/mercurial.xml	Fri Jul 06 21:26:25 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>