changeset 842:951ace007006

Merge branch Gaya-8.1.x
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Wed, 18 Jun 2014 12:14:56 +0200
parents e36e8d036ebd (diff) 46eee4b37e58 (current diff)
children eb4a87121757
files mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/HgVcsRoot.java
diffstat 86 files changed, 1888 insertions(+), 501 deletions(-) [+]
line wrap: on
line diff
--- a/.idea/libraries/TeamCity_impl.xml	Tue Jun 17 22:30:00 2014 +0200
+++ b/.idea/libraries/TeamCity_impl.xml	Wed Jun 18 12:14:56 2014 +0200
@@ -3,6 +3,7 @@
     <CLASSES>
       <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/patches-impl.jar!/" />
       <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/trove4j.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/trove-3.0.3.jar!/" />
     </CLASSES>
     <JAVADOC />
     <SOURCES />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/runConfigurations/tests_via_file.xml	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,32 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="tests via file" type="TestNG" factoryName="TestNG">
+    <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
+    <module name="mercurial-tests" />
+    <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+    <option name="ALTERNATIVE_JRE_PATH" value="" />
+    <option name="SUITE_NAME" value="$PROJECT_DIR$/mercurial-tests/src/testng-via-cmd.xml" />
+    <option name="PACKAGE_NAME" value="" />
+    <option name="MAIN_CLASS_NAME" value="" />
+    <option name="METHOD_NAME" value="" />
+    <option name="GROUP_NAME" value="" />
+    <option name="TEST_OBJECT" value="SUITE" />
+    <option name="VM_PARAMETERS" value="-ea -Dteamcity.mercurial.use.commandline.via.file.wrapper=true" />
+    <option name="PARAMETERS" value="" />
+    <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
+    <option name="OUTPUT_DIRECTORY" value="" />
+    <option name="ANNOTATION_TYPE" />
+    <option name="ENV_VARIABLES" />
+    <option name="PASS_PARENT_ENVS" value="true" />
+    <option name="TEST_SEARCH_SCOPE">
+      <value defaultName="moduleWithDependencies" />
+    </option>
+    <option name="USE_DEFAULT_REPORTERS" value="false" />
+    <option name="PROPERTIES_FILE" value="" />
+    <envs />
+    <properties />
+    <listeners />
+    <RunnerSettings RunnerId="Run" />
+    <ConfigurationWrapper RunnerId="Run" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
--- a/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Wed Jun 18 12:14:56 2014 +0200
@@ -26,4 +26,8 @@
   <bean id="mirrorCleaner" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentMirrorCleaner" />
   <bean id="commandSettingsFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentCommandSettingsFactory" />
   <bean id="agentRepoFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentRepoFactory"/>
+
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver"/>
 </beans>
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentCommandSettingsFactory.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentCommandSettingsFactory.java	Wed Jun 18 12:14:56 2014 +0200
@@ -18,9 +18,11 @@
 
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
+import org.jetbrains.annotations.NotNull;
 
 public class AgentCommandSettingsFactory implements CommandSettingsFactory {
 
+  @NotNull
   public CommandSettings create() {
     return new CommandSettings().setLogLevel("info");
   }
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentRepoFactory.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentRepoFactory.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,8 +16,7 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRoot;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
@@ -26,23 +25,16 @@
 
 public class AgentRepoFactory implements HgRepoFactory {
 
-  private final CommandSettingsFactory myCommandSettingsFactory;
+  private final CommandSettingsForRoot myCommandSettingsFactory;
   private final HgPathProvider myHgPathProvider;
 
-  public AgentRepoFactory(@NotNull CommandSettingsFactory commandSettingsFactory,
+  public AgentRepoFactory(@NotNull CommandSettingsForRoot commandSettingsFactory,
                           @NotNull HgPathProvider hgPathProvider) {
     myCommandSettingsFactory = commandSettingsFactory;
     myHgPathProvider = hgPathProvider;
   }
 
   public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException {
-    return new HgRepo(myCommandSettingsFactory, workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
+    return new HgRepo(myCommandSettingsFactory.forRoot(root), workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
   }
-
-  public HgRepo create(@NotNull File workingDir,
-                       @NotNull String hgPath,
-                       @NotNull AuthSettings authSettings) {
-    return new HgRepo(myCommandSettingsFactory, workingDir, hgPath, authSettings);
-  }
-
 }
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Wed Jun 18 12:14:56 2014 +0200
@@ -31,6 +31,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 import static com.intellij.openapi.util.io.FileUtil.delete;
@@ -152,11 +154,29 @@
 
   private void updateWorkingDir(@NotNull File workingDir, @NotNull String toVersion, @NotNull String repositoryUrl) throws VcsException, IOException {
     HgRepo repo = myRepoFactory.createRepo(myRoot, workingDir);
-    updateSubrepositories(repo, toVersion, repositoryUrl);
+    List<File> repos = new ArrayList<File>();
+    updateSubrepositories(repo, toVersion, repositoryUrl, repos);
     doUpdateWorkingDir(repo, toVersion);
+    purge(repos);
   }
 
-  private void updateSubrepositories(@NotNull HgRepo repo, @NotNull String toVersion, @NotNull String parentRepositoryUrl) throws VcsException, IOException {
+  private void purge(@NotNull List<File> dirs) throws VcsException {
+    HgVcsRoot.PurgePolicy purgePolicy = myRoot.getPurgePolicy();
+    if (purgePolicy == HgVcsRoot.PurgePolicy.DONT_RUN)
+      return;
+    for (File dir : dirs) {
+      myLogger.message("Run purge in " + dir.getAbsolutePath());
+      HgRepo repo = myRepoFactory.createRepo(myRoot, dir);
+      repo.purge().withPolicy(purgePolicy).call();
+      myLogger.message("Purge in " + dir.getAbsolutePath() + " is finished");
+    }
+  }
+
+  private void updateSubrepositories(@NotNull HgRepo repo,
+                                     @NotNull String toVersion,
+                                     @NotNull String parentRepositoryUrl,
+                                     @NotNull List<File> repoAccumulator) throws VcsException, IOException {
+    repoAccumulator.add(repo.getWorkingDir());
     if (!repo.hasSubreposAtRevision(toVersion))
       return;
     myLogger.message("Process subrepos of " + parentRepositoryUrl);
@@ -183,7 +203,7 @@
         Loggers.VCS.warn("Failed to resolve subrepo url '" + subrepoConfig.url() + "'", e);
         subrepoUrl = subrepoConfig.url();
       }
-      updateSubrepositories(subrepository, subrepoConfig.revision(), subrepoUrl);
+      updateSubrepositories(subrepository, subrepoConfig.revision(), subrepoUrl, repoAccumulator);
     }
   }
 
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Wed Jun 18 12:14:56 2014 +0200
@@ -32,6 +32,8 @@
   String USE_TAGS_AS_BRANCHES = "useTagsAsBranches";
   String INCLUDE_SUBREPOS_IN_PATCH = "includeSubreposInPatch";
   String USE_ARCHIVE_FOR_PATCH = "useArchiveForPatch";
-
+  String HG_EXTENSIONS = "hg.extensions";
+  String HG_COMMANDLINE_VIA_FILE = "hg.pass.commandline.via.file";
+  String PURGE_POLICY = "purgePolicy";
   String GLOBAL_DETECT_SUBREPO_CHANGES = "teamcity.hg.detectSubrepoChanges";
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgFileUtil.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgFileUtil.java	Wed Jun 18 12:14:56 2014 +0200
@@ -44,12 +44,15 @@
    * @return created dir
    * @throws IOException in case of I/O error
    */
+  @NotNull
   public static File createTempDir() throws IOException {
     File parentDir = new File(FileUtil.getTempDirectory());
     return createTempDir(parentDir);
   }
 
-  public static File createTempDir(@NotNull File parentDir) throws IOException {
+  @NotNull
+  public static File createTempDir(@NotNull final File parentDir) throws IOException {
+    //noinspection ResultOfMethodCallIgnored
     parentDir.mkdirs();
 
     int suffix = 0;
@@ -80,15 +83,15 @@
     }
   }
 
-
-  private static Object getTmpDirLock(@NotNull String tmpDirName) {
+  @NotNull
+  private static Object getTmpDirLock(@NotNull final String tmpDirName) {
     Object tmpDirLock = new Object();
     Object existingLock = myTmpDirLocks.putIfAbsent(tmpDirName, tmpDirLock);
     return existingLock != null ? existingLock : tmpDirLock;
   }
 
 
-  public static void deleteDir(@Nullable File dir, @NotNull Logger logger) {
+  public static void deleteDir(@Nullable final File dir, @NotNull final Logger logger) {
     if (dir == null)
       return;
     final String dirPath;
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Wed Jun 18 12:14:56 2014 +0200
@@ -24,7 +24,10 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
@@ -166,6 +169,10 @@
     FileUtil.delete(new File(dotHg, "bookmarks.current"));
   }
 
+  public PurgeCommand purge() {
+    return new PurgeCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
   public String getHgPath() {
     return myHgPath;
   }
@@ -184,8 +191,13 @@
     return files;
   }
 
+  @NotNull
   public String getWorkingDirRevision() throws VcsException {
-    return id().inLocalRepository().call();
+    List<String> workingDirParents = parents().call();
+    if (workingDirParents.isEmpty())
+      return LogCommand.ZERO_PARENT_SHORT_ID;//'hg id' shows zeroid when a working dir has no parents
+    //if a working dir is in an uncommitted merge state, choose the first parent
+    return workingDirParents.get(0);
   }
 
   public boolean containsRevision(@NotNull String revision) {
@@ -313,16 +325,19 @@
   }
 
   //path->subrepo
-  public Map<String, SubRepo> getSubrepositories(@NotNull ChangeSet cset) {
-    String revId = cset.getId();
+  @NotNull
+  public Map<String, SubRepo> getSubrepositories(@NotNull final ChangeSet cset) {
+    final String revId = cset.getId();
+
     Map<String, SubRepo> subrepos = mySubreposCache.get(revId);
-    if (subrepos != null)
+    if (subrepos != null) {
       return new HashMap<String, SubRepo>(subrepos);
-    CatCommand cc = cat();
-    cc.setRevId(revId);
+    }
+
     File catDir = null;
+    final CatCommand cc = cat().setRevId(revId).files(asList(".hgsub", ".hgsubstate")).checkForFailure(false);
     try {
-      catDir = cc.execute(asList(".hgsub", ".hgsubstate"), false);
+      catDir = cc.call();
       subrepos = HgSubs.readSubrepositories(new File(catDir, ".hgsub"), new File(catDir, ".hgsubstate"));
       mySubreposCache.put(revId, subrepos);
       return new HashMap<String, SubRepo>(subrepos);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialClasspathTemplate.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2000-2014 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.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+import static com.intellij.openapi.util.io.FileUtil.createTempFile;
+import static com.intellij.openapi.util.io.FileUtil.delete;
+
+/**
+* Created 25.02.14 11:29
+*
+* @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+*/
+public class MercurialClasspathTemplate implements MercurialTemplate {
+  private final String myResourcePath;
+  private final String myTmpFileSuffix;
+
+  public MercurialClasspathTemplate(@NotNull final String resourcePath,
+                                    @NotNull final String tmpFileSuffix) {
+    myResourcePath = resourcePath;
+    myTmpFileSuffix = tmpFileSuffix;
+  }
+
+  @NotNull
+  public <T> T withTemplate(@NotNull final WithTemplate<T> action) throws VcsException {
+    //TODO: we may pack plugin and use jetbrains.buildServer.web.openapi.PluginDescriptor.getPluginRoot()
+    //TODO: to get path to existing file
+    final File template = copyTemplate();
+    try {
+      return action.action(template);
+    } finally {
+      delete(template);
+    }
+  }
+
+  private File copyTemplate() throws VcsException {
+    try {
+      final File template = createTempFile("teamcity", myTmpFileSuffix);
+      FileUtil.copyResource(getClass(), myResourcePath, template);
+      return template;
+    } catch (IOException e) {
+      throw new VcsException("Cannot create mercurial log template", e);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialTemplate.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * Created 25.02.14 11:51
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public interface MercurialTemplate {
+  @NotNull
+  <T> T withTemplate(@NotNull WithTemplate<T> action) throws VcsException;
+
+  interface WithTemplate<T> {
+    @NotNull
+    T action(@NotNull final File template) throws VcsException;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OS.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OS.java	Wed Jun 18 12:14:56 2014 +0200
@@ -29,7 +29,7 @@
   private static int _COMMAND_LINE_LIMIT() {
     final String OS_NAME = System.getProperty("os.name").toLowerCase();
     ///http://partmaps.org/era/unix/arg-max.html
-    if (!OS_NAME.startsWith("windows")) return 128 * 1024 - 1; //128kb
+    if (!OS_NAME.contains("windows")) return 128 * 1024 - 1; //128kb
 
     //http://support.microsoft.com/kb/830473/en-us
     if (OS_NAME.matches("windows\\s+(2000|xp|nt)")) {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -29,7 +29,6 @@
  */
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import com.intellij.execution.configurations.GeneralCommandLine;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
@@ -39,8 +38,6 @@
 import java.util.List;
 
 public class ArchiveCommand extends AuthCommand {
-  private final static int MAX_CMD_LEN = 900;
-
   private File myDestination;
   private String myToId;
   private String myType = "files";
@@ -68,11 +65,14 @@
     return this;
   }
 
-  public boolean addIncludeRule(@NotNull String rule) {
-    MercurialCommandLine cmd = createCmd();
-    int cmdSize = cmd.getCommandLineString().length();
-    if (cmdSize + rule.length() + (myIncludeRules.isEmpty() ? 0 : "-I ".length()) > MAX_CMD_LEN)
+  public boolean addIncludeRule(@NotNull final String rule) {
+    final MercurialCommandLine cmd = createCmd();
+
+    final int cmdSize = cmd.getCommandLineLength();
+    if (cmdSize + rule.length() + 3 + (myIncludeRules.isEmpty() ? 0 : "-I ".length()) > getMaxCommandLineSize()) {
       return false;
+    }
+
     myIncludeRules.add(rule);
     return true;
   }
@@ -86,40 +86,30 @@
     deleteHgArchival();
   }
 
+  @NotNull
   private MercurialCommandLine createCmd() {
-    MercurialCommandLine cli = createCommandLine();
-    addHttpAuthParams(cli);
-    cli.addParameter("archive");
-    setType(cli);
-    setRevision(cli);
-    setDestination(cli);
-    addIncludeRules(cli);
-    return cli;
-  }
+    final MercurialCommandLine cli = createCommandLine();
 
-  private void addIncludeRules(@NotNull MercurialCommandLine cli) {
-    if (myIncludeRules.isEmpty())
-      return;
-    cli.addParameter("-I");
-    cli.addParameters(myIncludeRules);
-  }
+    addHttpAuthParams(cli);
 
-  private void setDestination(GeneralCommandLine cli) {
-    cli.addParameter(myDestination.getAbsolutePath());
-  }
+    cli.addParameter("archive");
+    cli.addParameter("-t");
+    cli.addParameter(myType);
+    cli.addParameter("-r");
 
-  private void setRevision(GeneralCommandLine cli) {
-    cli.addParameter("-r");
     if (myToId != null) {
       cli.addParameter(myToId);
     } else {
       cli.addParameter("tip");
     }
-  }
 
-  private void setType(GeneralCommandLine cli) {
-    cli.addParameter("-t");
-    cli.addParameter(myType);
+    for (String include : myIncludeRules) {
+      cli.addParameter("-I");
+      cli.addParameter(include);
+    }
+    cli.addParameter(myDestination.getAbsolutePath());
+
+    return cli;
   }
 
   /**
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/AuthCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/AuthCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,7 +16,6 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import com.intellij.execution.configurations.GeneralCommandLine;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
@@ -33,13 +32,14 @@
     super(commandSettings, hgPath, workingDir, authSettings);
   }
 
+  @NotNull
   protected MercurialCommandLine createCL() {
     MercurialCommandLine cmd = super.createCL();
     cmd.addParameters("--config", "ui.interactive=False");
     return cmd;
   }
 
-  protected void addHttpAuthParams(@NotNull GeneralCommandLine cmd) {
+  protected void addHttpAuthParams(@NotNull final MercurialCommandLine cmd) {
     if (myAuthSettings.getUsername() == null || myAuthSettings.getPassword() == null)
       return;
     cmd.addParameters("--config", "auth.tc.prefix=*");
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -32,37 +32,70 @@
   private final String myHgPath;
   private final File myWorkDirectory;
 
-  public BaseCommand(@NotNull CommandSettings commandSettings,
+  public BaseCommand(@NotNull final CommandSettings commandSettings,
                      @NotNull final String hgPath,
-                     @NotNull File workingDir) {
+                     @NotNull final File workingDir) {
     myCommandSettings = commandSettings;
     myHgPath = hgPath;
     myWorkDirectory = workingDir;
   }
 
+  protected final int getMaxCommandLineSize() {
+    return allowCommandlineViaFileWrapper()
+            ? Integer.MAX_VALUE
+            : myCommandSettings.getMaxCommandLineSize();
+  }
+
+  protected boolean allowCommandlineViaFileWrapper() {
+    return true;
+  }
 
   public File getWorkDirectory() {
     return myWorkDirectory;
   }
 
+  @NotNull
   protected MercurialCommandLine createCommandLine() {
     MercurialCommandLine cli = createCL();
     cli.setWorkDirectory(myWorkDirectory.getAbsolutePath());
     return cli;
   }
 
+  @NotNull
   protected MercurialCommandLine createCL() {
-    MercurialCommandLine cl = new MercurialCommandLine(getPrivateData());
+    final MercurialCommandLine cl = new MercurialCommandLine(getPrivateData());
     cl.setExePath(myHgPath);
     cl.setEnvParams(myCommandSettings.getHgEnv());
-    cl.setPassParentEnvs(true);
+
+    //include global arguments if any
+    cl.addParameters(myCommandSettings.getGlobalArguments());
+
     return cl;
   }
 
-  protected CommandResult runCommand(@NotNull MercurialCommandLine cli) throws VcsException {
-    return CommandUtil.runCommand(cli, myCommandSettings);
+  @NotNull
+  protected final CommandResult runCommand(@NotNull MercurialCommandLine cli) throws VcsException {
+    return runCommand(cli, myCommandSettings);
   }
 
+  @NotNull
+  protected final CommandResult runCommand(@NotNull final MercurialCommandLine cli,
+                                           @NotNull final CommandSettings commandSettings) throws VcsException {
+
+    if (!myCommandSettings.getUseCommandlineViaFileWrapper() || !allowCommandlineViaFileWrapper()) {
+      return CommandUtil.runCommand(cli, commandSettings.setPrivateData(getPrivateData()));
+    }
+
+    return CommandUtil.runWrappedCommand(cli, commandSettings);
+  }
+
+  protected void setupExtensionsFromResource(@NotNull final MercurialCommandLine cli,
+                                             @NotNull final File tempDir,
+                                             @NotNull final String commandPy) throws VcsException{
+    CommandUtil.setupExtensionsFromResource(cli, tempDir, commandPy);
+  }
+
+  @NotNull
   protected Set<String> getPrivateData() {
     return emptySet();
   }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,7 +16,6 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.OS;
 import jetbrains.buildServer.log.Loggers;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
@@ -44,45 +43,43 @@
     super(commandSettings, hgPath, workingDir, authSettings);
   }
 
+  @NotNull
   public CatCommand files(@NotNull String relativePath) {
     myRelativePaths = singletonList(relativePath);
     return this;
   }
 
+  @NotNull
   public CatCommand files(@NotNull List<String> relativePaths) {
     myRelativePaths = relativePaths;
     return this;
   }
 
+  @NotNull
   public CatCommand setRevId(final String revId) {
     myRevId = revId;
     return this;
   }
 
+  @NotNull
   public CatCommand atRevision(@NotNull ChangeSet cset) {
     myRevId = cset.getId();
     return this;
   }
 
+  @NotNull
   public CatCommand checkForFailure(boolean doCheckForFailure) {
     myCheckForFailure = doCheckForFailure;
     return this;
   }
 
+  @NotNull
   public File call() throws VcsException {
-    return execute(myRelativePaths, myCheckForFailure);
-  }
-
-  public File execute(List<String> relPaths) throws VcsException {
-    return execute(relPaths, true);
-  }
-
-  public File execute(List<String> relPaths, boolean checkFailure) throws VcsException {
     File tempDir = null;
     try {
       tempDir = createTmpDir();
-      createDirectories(relPaths, tempDir);
-      catFiles(relPaths, checkFailure, tempDir);
+      createDirectories(myRelativePaths, tempDir);
+      catFiles(myRelativePaths, myCheckForFailure, tempDir);
       return tempDir;
     } catch (VcsException e) {
       deleteDir(tempDir, Loggers.VCS);
@@ -90,6 +87,7 @@
     }
   }
 
+  @NotNull
   private File createTmpDir() throws VcsException {
     try {
       return HgFileUtil.createTempDir();
@@ -98,7 +96,7 @@
     }
   }
 
-  private void createDirectories(List<String> relPaths, File tempDir) throws VcsException {
+  private void createDirectories(@NotNull final List<String> relPaths, @NotNull final File tempDir) throws VcsException {
     for (String path: relPaths) {
       File parentFile = new File(tempDir, path).getParentFile();
       if (!parentFile.isDirectory() && !parentFile.mkdirs())
@@ -106,24 +104,27 @@
     }
   }
 
-  private void catFiles(List<String> relPaths, boolean checkFailure, File tempDir) throws VcsException {
-    Queue<String> paths = new LinkedList<String>(relPaths);
+  private void catFiles(@NotNull final List<String> relPaths,
+                        final boolean checkFailure,
+                        @NotNull final File tempDir) throws VcsException {
+    final Queue<String> paths = new LinkedList<String>(relPaths);
     while (!paths.isEmpty()) {
       MercurialCommandLine cli = createCommandLine(tempDir);
-      int cmdSize = cli.getCommandLineString().length() + 42;
+      int cmdSize = cli.getCommandLineLength() + 42;
 
       do {
         String path = paths.poll();
         cli.addParameter(path);
         cmdSize += path.length() + 3; //quotes + space
-      } while (cmdSize < OS.getMaxCommandLineSize() && !paths.isEmpty());
+      } while (cmdSize < getMaxCommandLineSize() && !paths.isEmpty());
 
       runCommand(cli, myCommandSettings.setCheckForFailure(checkFailure));
     }
   }
 
-  private MercurialCommandLine createCommandLine(final File tempDir) {
-    MercurialCommandLine cli = createCommandLine();
+  @NotNull
+  private MercurialCommandLine createCommandLine(@NotNull final File tempDir) {
+    final MercurialCommandLine cli = createCommandLine();
     addHttpAuthParams(cli);
     cli.addParameter("cat");
     cli.addParameter("-o");
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -29,6 +29,7 @@
   private boolean myUsePullProtocol = true;
   private boolean myUseUncompressedTransfer = false;
   private boolean myTraceback;
+  private ProgressParser.ProgressConsumer myProgressConsumer;
 
   public CloneCommand(@NotNull CommandSettings commandSettings,
                       @NotNull String hgPath,
@@ -76,6 +77,11 @@
     return this;
   }
 
+  public CloneCommand withProgressConsumer(ProgressParser.ProgressConsumer progressConsumer) {
+    myProgressConsumer = progressConsumer;
+    return this;
+  }
+
   public void call() throws VcsException {
     myWorkingDir.mkdirs();
     MercurialCommandLine cli = createCommandLine();
@@ -96,10 +102,20 @@
     if (myUseUncompressedTransfer) {
       cli.addParameter("--uncompressed");
     }
+
+    CommandSettings settings = myCommandSettings.setTimeout(24 * 3600); // some repositories are quite large, we set timeout to 24 hours
+    if (myProgressConsumer != null) {
+      cli.addParameters("--config", "extensions.progress=");
+      cli.addParameters("--config", "progress.format=topic number");
+      cli.addParameters("--config", "progress.delay=0");
+      cli.addParameters("--config", "progress.assume-tty=True");
+      settings.setProgressConsumer(myProgressConsumer);
+    }
+
     String repositoryUrl = myAuthSettings.getRepositoryUrlWithCredentials(myRepository);
     cli.addParameter(repositoryUrl);
     cli.addParameter(myWorkingDir.getName());
 
-    runCommand(cli, myCommandSettings.setTimeout(24 * 3600)); // some repositories are quite large, we set timeout to 24 hours
+    runCommand(cli, settings);
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandSettings.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandSettings.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,12 +16,10 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.OS;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 /**
  * @author dmitry.neverov
@@ -29,28 +27,29 @@
 public class CommandSettings {
 
   private int myTimeout = 3600;
-  private Set<String> myPrivateData = new HashSet<String>();
+  private final Set<String> myPrivateData = new HashSet<String>();
   private boolean myCheckForFailure = true;
   private boolean myFailWhenStderrNotEmpty = false;
   private String myLogLevel = "debug";
   private Map<String, String> myHgEnv = new HashMap<String, String>();
   private int myLogOutputLimit = -1;
   private int myExceptionOutputLimit = 5000;
+  private List<String> myGlobalArguments = new ArrayList<String>(0);
+  private ProgressParser.ProgressConsumer myProgressConsumer;
+  private boolean myUseCommandlineViaFileWrapper = false;
 
-  public CommandSettings() {
-
+  public final int getMaxCommandLineSize() {
+    return OS.getMaxCommandLineSize();
   }
 
-  public CommandSettings(int timeout,
-                         @NotNull Set<String> privateData,
-                         boolean checkForFailure,
-                         boolean failWhenStderrNotEmpty,
-                         @NotNull String logLevel) {
-    myTimeout = timeout;
-    myPrivateData = privateData;
-    myCheckForFailure = checkForFailure;
-    myFailWhenStderrNotEmpty = failWhenStderrNotEmpty;
-    myLogLevel = logLevel;
+  public boolean getUseCommandlineViaFileWrapper() {
+    return myUseCommandlineViaFileWrapper;
+  }
+
+  @NotNull
+  public CommandSettings setUseCommandlineViaFileWrapper(final boolean useCommandlineViaFileWrapper) {
+    myUseCommandlineViaFileWrapper = useCommandlineViaFileWrapper;
+    return this;
   }
 
   public CommandSettings setTimeout(int timeout) {
@@ -58,12 +57,23 @@
     return this;
   }
 
+  @NotNull
+  public List<String> getGlobalArguments() {
+    return myGlobalArguments;
+  }
+
+  @NotNull
+  public CommandSettings withGlobalArguments(@NotNull String... argz) {
+    myGlobalArguments.addAll(Arrays.asList(argz));
+    return this;
+  }
+
   public int getTimeout() {
     return myTimeout;
   }
 
   public CommandSettings setPrivateData(@NotNull Set<String> privateData) {
-    myPrivateData = privateData;
+    myPrivateData.addAll(privateData);
     return this;
   }
 
@@ -124,4 +134,12 @@
   public void setExceptionOutputLimit(int limit) {
     myExceptionOutputLimit = limit;
   }
+
+  public ProgressParser.ProgressConsumer getProgressConsumer() {
+    return myProgressConsumer;
+  }
+
+  public void setProgressConsumer(ProgressParser.ProgressConsumer progressConsumer) {
+    myProgressConsumer = progressConsumer;
+  }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandSettingsFactory.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandSettingsFactory.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,8 +16,11 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import org.jetbrains.annotations.NotNull;
+
 public interface CommandSettingsFactory {
 
+  @NotNull
   CommandSettings create();
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandSettingsForRoot.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2014 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;
+
+/**
+ * Created 25.02.14 12:58
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public interface CommandSettingsForRoot {
+  @NotNull CommandSettingsFactory forRoot(@NotNull HgVcsRoot root);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandSettingsForRootImpl.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2014 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;
+
+/**
+ * Created 25.02.14 13:01
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public class CommandSettingsForRootImpl implements CommandSettingsForRoot {
+  private final CommandSettingsFactory myFactory;
+  private final CommandSettingsWeaver[] myWeavers;
+
+  public CommandSettingsForRootImpl(@NotNull CommandSettingsFactory factory,
+                                    @NotNull final CommandSettingsWeaver... weavers) {
+    myFactory = factory;
+    myWeavers = weavers;
+  }
+
+  @NotNull
+  public CommandSettingsFactory forRoot(@NotNull final HgVcsRoot root) {
+    return new CommandSettingsFactory() {
+      @NotNull
+      public CommandSettings create() {
+        CommandSettings settings = myFactory.create();
+        for (CommandSettingsWeaver weaver : myWeavers) {
+          settings = weaver.update(root, settings);
+        }
+        return settings;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandSettingsWeaver.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2014 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;
+
+/**
+ * Created 25.02.14 13:02
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public interface CommandSettingsWeaver {
+  @NotNull
+  CommandSettings update(@NotNull HgVcsRoot root, @NotNull CommandSettings settings);
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,20 +16,94 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import jetbrains.buildServer.ExecResult;
+import jetbrains.buildServer.LineAwareByteArrayOutputStream;
 import jetbrains.buildServer.SimpleCommandLineProcessRunner;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil;
 import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
 import java.util.Set;
 
 public class CommandUtil {
 
-  public static CommandResult runCommand(@NotNull MercurialCommandLine cli, @NotNull CommandSettings settings) throws VcsException {
-    final String command = removePrivateData(cli.getCommandLineString(), settings.getPrivateData());
-    logRunCommand(cli, command, settings);
+  @NotNull
+  private static File extractCommandPy(@NotNull final File root, @NotNull final String commandPy) throws VcsException {
+    try {
+      final File py = new File(root, commandPy);
+      FileUtil.copyResource(CommandUtil.class, "/python/" + commandPy, py);
+
+      if (py.length() < 100) throw new IOException("Failed to unpack command resource");
+      return py;
+    } catch (IOException e) {
+      throw new VcsException("Failed to extract .py file: " + e.getMessage(), e);
+    }
+  }
+
+  public static void setupExtensionsFromResource(@NotNull final MercurialCommandLine cli,
+                                                 @NotNull final File tempDir,
+                                                 @NotNull final String commandPy) throws VcsException {
+    final File file = extractCommandPy(tempDir, commandPy);
+    final String extName = commandPy.replaceAll("[^a-zA-Z]+", "");
+    cli.addParameters("--config", "extensions." + extName + "=" + file);
+  }
+
+  @NotNull
+  public static CommandResult runWrappedCommand(@NotNull final MercurialCommandLine originalCommandLine,
+                                                @NotNull final CommandSettings settings) throws VcsException {
+    final String realCommand = logRunCommand(originalCommandLine, settings);
+
+    final File tempDir = createTempDir();
+    try {
+      final File commands = writeCommandArguments(originalCommandLine, tempDir);
+
+      final MercurialCommandLine fork = originalCommandLine.forkWithoutCommandlineArguments();
+      setupExtensionsFromResource(fork, tempDir, "load-commands-command.py");
+      fork.addParameters("CMD", commands.getAbsolutePath());
+
+      String forkCommand = logRunCommand(fork, settings);
+
+      return runCommandWithName(fork, settings, forkCommand + " \n|| for command: " + realCommand);
+    } finally {
+      FileUtil.delete(tempDir);
+    }
+  }
+
+  @NotNull
+  private static File writeCommandArguments(@NotNull MercurialCommandLine originalCommandLine,
+                                            @NotNull File tempDir) throws VcsException {
+    try {
+      final File commands = new File(tempDir, "command.args");
+      FileUtil.writeFile(commands, StringUtil.join("\n", originalCommandLine.getArguments()), "utf-8");
+      return commands;
+    } catch (IOException e) {
+      throw new VcsException("Failed to generate commands file. " + e.getMessage(), e);
+    }
+
+  }
+
+  @NotNull
+  private static File createTempDir() throws VcsException {
+    try {
+      return HgFileUtil.createTempDir();
+    } catch (IOException e) {
+      throw new VcsException("Failed to create temp file. " + e.getMessage(), e);
+    }
+  }
+
+  @NotNull
+  public static CommandResult runCommand(@NotNull final MercurialCommandLine cli,
+                                         @NotNull final CommandSettings settings) throws VcsException {
+    return runCommandWithName(cli, settings, logRunCommand(cli, settings));
+  }
+
+  private static CommandResult runCommandWithName(MercurialCommandLine cli, CommandSettings settings, String command) throws VcsException {
     CommandResult res = run(cli, settings.getTimeout(), command, settings.getPrivateData(), settings);
     if (settings.isCheckForFailure() || settings.isFailWithNonEmptyStderr())
       res.checkFailure(settings.isFailWithNonEmptyStderr());
@@ -37,23 +111,34 @@
     return res;
   }
 
+  @NotNull
   private static CommandResult run(@NotNull final MercurialCommandLine cli,
                                    final int executionTimeout,
                                    @NotNull final String command,
                                    @NotNull final Set<String> privateData,
                                    @NotNull CommandSettings settings) {
     final long start = System.currentTimeMillis();
-    ExecResult res = SimpleCommandLineProcessRunner.runCommand(cli, null, new SimpleCommandLineProcessRunner.ProcessRunCallbackAdapter() {
+    ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream();
+    ProgressParser.ProgressConsumer progressConsumer = settings.getProgressConsumer();
+    ByteArrayOutputStream stderrBuffer;
+    if (progressConsumer != null) {
+      stderrBuffer =  new LineAwareByteArrayOutputStream(Charset.forName("UTF-8"), new ProgressParser(progressConsumer));
+      ((LineAwareByteArrayOutputStream) stderrBuffer).setCREndsLine(true);
+    } else {
+      stderrBuffer = new ByteArrayOutputStream();
+    }
+    ExecResult res = SimpleCommandLineProcessRunner.runCommandSecure(cli.toGeneralCommandLine(), command, null, new SimpleCommandLineProcessRunner.ProcessRunCallbackAdapter() {
       @Override
       public Integer getOutputIdleSecondsTimeout() {
         return executionTimeout;
       }
+
       @Override
-      public void onProcessFinished(Process ps) {
+      public void onProcessFinished(@NotNull Process ps) {
         long duration = System.currentTimeMillis() - start;
         Loggers.VCS.debug("Command " + command + " took " + duration + "ms");
       }
-    });
+    }, stdoutBuffer, stderrBuffer);
     return new CommandResult(Loggers.VCS, command, res, privateData, settings);
   }
 
@@ -77,6 +162,14 @@
     }
   }
 
+  @NotNull
+  private static String logRunCommand(@NotNull final MercurialCommandLine cli,
+                                      @NotNull final CommandSettings settings) {
+    final String command = removePrivateData(cli.getCommandLineString(), settings.getPrivateData());
+    logRunCommand(cli, command, settings);
+    return command;
+  }
+
   private static void logCommandOutput(@NotNull String command, @NotNull CommandResult result, @NotNull CommandSettings settings) {
     int limit = settings.getLogOutputLimit();
     if (limit == -1) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandlineViaFileWrapperWeaver.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2014 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.serverSide.TeamCityProperties;
+import jetbrains.buildServer.util.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Created 30.05.2014 11:26
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public class CommandlineViaFileWrapperWeaver implements CommandSettingsWeaver {
+  @NotNull
+  public CommandSettings update(@NotNull HgVcsRoot root, @NotNull CommandSettings settings) {
+    final String pass = root.getProperty(Constants.HG_COMMANDLINE_VIA_FILE);
+    return settings.setUseCommandlineViaFileWrapper(
+            TeamCityProperties.getBoolean("teamcity.mercurial.use.commandline.via.file.wrapper")
+            || !StringUtil.isEmpty(pass));
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommitsAndMountPointsCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommitsAndMountPointsCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,9 +16,7 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgRepo;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersion;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.*;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
@@ -58,20 +56,6 @@
     }
   }
 
-  @NotNull
-  private File extractCommandPy(@NotNull final File root) throws VcsException {
-    try {
-      final File py = new File(root, "load-substates-command.py");
-
-      FileUtil.copyResource(getClass(), "/python/load-substates-command.py", py);
-
-      if (py.length() < 100) throw new IOException("Failed to unpack command resource");
-      return py;
-    } catch (IOException e) {
-      throw new VcsException("Failed to extract .py file: " + e.getMessage(), e);
-    }
-  }
-
   public void call(@NotNull final Callback consumer) throws VcsException {
     final HgVersion hgVersion = myRepo.version().call();
     if (!hgVersion.isEqualsOrGreaterThan(REQUIRED_VERSION)) {
@@ -81,9 +65,7 @@
     final File root = createTmpDir();
 
     try {
-      final File py = extractCommandPy(root);
-
-      callImpl(root, py, consumer);
+      callImpl(root, consumer);
     } finally {
       FileUtil.delete(root);
     }
@@ -106,12 +88,12 @@
   }
 
   private void callImpl(@NotNull final File root,
-                        @NotNull final File commandPy,
                         @NotNull final Callback consumer) throws VcsException {
     final MercurialCommandLine cli = createCommandLine();
     cli.addParameter("--debug");
-    cli.addParameter("--config");
-    cli.addParameter("extensions.logextcj=" + commandPy);
+
+    setupExtensionsFromResource(cli, root, "load-substates-command.py");
+
     cli.addParameter("load-substates");
     cli.addParameter(new File(root, "result").getPath());
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ExtensionsWeaver.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2014 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.util.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Created 25.02.14 13:03
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public class ExtensionsWeaver implements CommandSettingsWeaver {
+  @NotNull
+  public CommandSettings update(@NotNull HgVcsRoot root, @NotNull CommandSettings settings) {
+    String extensions = root.getProperty(Constants.HG_EXTENSIONS);
+    if (StringUtil.isEmpty(extensions)) return settings;
+
+    for (String _line : extensions.split("[\\r\\n]+")) {
+      String line = _line.trim();
+      if (line.isEmpty()) continue;
+
+      if (!line.contains("=")) line += "=";
+
+      settings = settings.withGlobalArguments("--config", "extensions." + line);
+    }
+
+    return settings;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/HgVcsRoot.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/HgVcsRoot.java	Wed Jun 18 12:14:56 2014 +0200
@@ -46,6 +46,7 @@
   private final boolean myUseTagsAsBranches;
   private final boolean myIncludeSubreposInPatch;
   private final boolean myUseArchiveForPatch;
+  private final PurgePolicy myPurgePolicy;
 
   public HgVcsRoot(@NotNull final VcsRoot vcsRoot) {
     this(vcsRoot.getProperties());
@@ -65,6 +66,15 @@
     String includeSubreposProp = getProperty(Constants.INCLUDE_SUBREPOS_IN_PATCH);
     myIncludeSubreposInPatch = includeSubreposProp == null ? true : Boolean.parseBoolean(includeSubreposProp);//true by default
     myUseArchiveForPatch = Boolean.parseBoolean(getProperty(Constants.USE_ARCHIVE_FOR_PATCH));
+    myPurgePolicy = readPurgePolicy(vcsRootProperties);
+  }
+
+  @NotNull
+  private PurgePolicy readPurgePolicy(@NotNull Map<String, String> properties) {
+    String policy = properties.get(Constants.PURGE_POLICY);
+    if (StringUtil.isEmpty(policy))
+      return PurgePolicy.DONT_RUN;
+    return PurgePolicy.valueOf(policy);
   }
 
   public HgVcsRoot withUrl(@NotNull String repositoryUrl) {
@@ -174,7 +184,19 @@
   }
 
   @NotNull
+  public PurgePolicy getPurgePolicy() {
+    return myPurgePolicy;
+  }
+
+  @NotNull
   public Map<String, String> getProperties() {
     return myVcsRootProperties;
   }
+
+
+  public static enum PurgePolicy {
+    DONT_RUN,
+    PURGE_UNKNOWN,
+    PURGE_ALL
+  }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LoadDagCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LoadDagCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -17,6 +17,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import com.intellij.openapi.util.Pair;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialTemplate;
 import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
@@ -27,14 +28,14 @@
 
 public class LoadDagCommand extends VcsRootCommand {
 
-  private final File myDagLogTemplate;
+  private final MercurialTemplate myDagLogTemplate;
   private int myMaxDagNodesCount;
 
   public LoadDagCommand(@NotNull CommandSettings commandSettings,
                         @NotNull String hgPath,
                         @NotNull File workingDir,
                         @NotNull AuthSettings authSettings,
-                        @NotNull File dagLogTemplate) {
+                        @NotNull MercurialTemplate dagLogTemplate) {
     super(commandSettings, hgPath, workingDir, authSettings);
     myDagLogTemplate = dagLogTemplate;
   }
@@ -45,31 +46,42 @@
 
   @NotNull
   public List<Pair<String, String>> call() throws VcsException {
-    List<Pair<String, String>> edges = new ArrayList<Pair<String, String>>();
-    MercurialCommandLine cli = createCommandLine();
-    cli.addParameter("log");
-    cli.addParameter("--style=" + myDagLogTemplate.getAbsolutePath());
-    if (myMaxDagNodesCount > 0)
-      cli.addParameters("--limit", String.valueOf(myMaxDagNodesCount));
-    CommandResult res = runCommand(cli);
-    String output = res.getStdout();
-    String fromNode = null;
-    for (String line : StringUtil.splitByLines(output)) {
-      String[] revs = line.split(" ");
-      if (revs.length == 0)
-        continue;
-      if (fromNode != null) {
-        edges.add(Pair.create(fromNode, revs[0]));
-        fromNode = null;
+    return myDagLogTemplate.withTemplate(new MercurialTemplate.WithTemplate<List<Pair<String, String>>>() {
+      @NotNull
+      public List<Pair<String, String>> action(@NotNull File template) throws VcsException {
+        final List<Pair<String, String>> edges = new ArrayList<Pair<String, String>>();
+        final MercurialCommandLine cli = createCommandLine();
+        cli.addParameter("log");
+        cli.addParameter("--style=" + template.getAbsolutePath());
+        if (myMaxDagNodesCount > 0) {
+          cli.addParameters("--limit", String.valueOf(myMaxDagNodesCount));
+        }
+
+        final CommandResult res = runCommand(cli);
+
+
+        final String output = res.getStdout();
+        String fromNode = null;
+        for (String line : StringUtil.splitByLines(output)) {
+          final String[] revs = line.split(" ");
+          if (revs.length == 0) continue;
+
+          if (fromNode != null) {
+            edges.add(Pair.create(fromNode, revs[0]));
+            fromNode = null;
+          }
+
+          if (revs.length == 1) {
+            fromNode = revs[0];
+          } else {
+            edges.add(Pair.create(revs[0], revs[1]));
+            if (revs.length == 3) {
+              edges.add(Pair.create(revs[0], revs[2]));
+            }
+          }
+        }
+        return edges;
       }
-      if (revs.length == 1) {
-        fromNode = revs[0];
-      } else {
-        edges.add(Pair.create(revs[0], revs[1]));
-        if (revs.length == 3)
-          edges.add(Pair.create(revs[0], revs[2]));
-      }
-    }
-    return edges;
+    });
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,6 +16,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialTemplate;
 import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
@@ -44,7 +45,7 @@
   private String myBranchName;
   private boolean myCalculateParents = true;
   private String myRevsets;
-  private File myTemplate;
+  private MercurialTemplate myTemplate;
   private List<String> myFiles = new ArrayList<String>();
 
   public LogCommand(@NotNull CommandSettings commandSettings,
@@ -54,7 +55,7 @@
     super(commandSettings, hgPath, workingDir, authSettings);
   }
 
-  public LogCommand withTemplate(@NotNull File template) {
+  public LogCommand withTemplate(@NotNull MercurialTemplate template) {
     myTemplate = template;
     return this;
   }
@@ -104,49 +105,57 @@
     return this;
   }
 
+  @NotNull
   public List<ChangeSet> call() throws VcsException {
-    MercurialCommandLine cli = createCommandLine();
-    cli.setCharset(Charset.forName("UTF-8"));
-    cli.addParameters("--encoding", "UTF-8");
-    cli.addParameter("log");
-    cli.addParameter("-v");
-    if (myTemplate != null)
-      cli.addParameter("--style=" + myTemplate.getAbsolutePath());
-    if (myBranchName != null) {
-      cli.addParameter("-b");
-      cli.addParameter(myBranchName);
-    }
-    cli.addParameter("-r");
-    if (myRevsets != null) {
-      cli.addParameter(myRevsets);
-    } else {
-      String from = myFromId != null ? myFromId : "0";
-      String to = myToId != null ? myToId : "tip";
-      cli.addParameter(from + ":" + to);
-    }
-    if (myLimit != null) {
-      cli.addParameter("--limit");
-      cli.addParameter(myLimit.toString());
-    }
+    return myTemplate.withTemplate(new MercurialTemplate.WithTemplate<List<ChangeSet>>() {
+      @NotNull
+      public List<ChangeSet> action(@NotNull File template) throws VcsException {
+        final MercurialCommandLine cli = createCommandLine();
+        cli.setCharset(Charset.forName("UTF-8"));
+        cli.addParameters("--encoding", "UTF-8");
+        cli.addParameter("log");
+        cli.addParameter("-v");
+        if (myTemplate != null) {
+          cli.addParameter("--style=" + template.getAbsolutePath());
+        }
 
-    cli.addParameters(myFiles);
+        if (myBranchName != null) {
+          cli.addParameter("-b");
+          cli.addParameter(myBranchName);
+        }
+        cli.addParameter("-r");
+        if (myRevsets != null) {
+          cli.addParameter(myRevsets);
+        } else {
+          String from = myFromId != null ? myFromId : "0";
+          String to = myToId != null ? myToId : "tip";
+          cli.addParameter(from + ":" + to);
+        }
+        if (myLimit != null) {
+          cli.addParameter("--limit");
+          cli.addParameter(myLimit.toString());
+        }
 
-    CommandResult res = runCommand(cli);
-    String output = res.getStdout();
-    try {
-      List<ChangeSet> changes = parseChangeSetsXml(output);
-      if (myCalculateParents)
-        assignTrivialParents(changes);
-      return changes;
-    } catch (Exception e) {
-      int limit = myCommandSettings.getLogOutputLimit();
-      if (limit == -1) {
-        LOG.error("Error while parsing log output:\n" + output, e);
-      } else {
-        LOG.error("Error while parsing log output:\n" + StringUtil.truncateStringValueWithDotsAtEnd(output, limit), e);
+        cli.addParameters(myFiles);
+
+        final CommandResult res = runCommand(cli);
+        final String output = res.getStdout();
+        try {
+          List<ChangeSet> changes = parseChangeSetsXml(output);
+          if (myCalculateParents)
+            assignTrivialParents(changes);
+          return changes;
+        } catch (Exception e) {
+          int limit = myCommandSettings.getLogOutputLimit();
+          if (limit == -1) {
+            LOG.error("Error while parsing log output:\n" + output, e);
+          } else {
+            LOG.error("Error while parsing log output:\n" + StringUtil.truncateStringValueWithDotsAtEnd(output, limit), e);
+          }
+          throw new VcsException("Error while parsing log output, see teamcity-vcs.log for details", e);
+        }
       }
-      throw new VcsException("Error while parsing log output, see teamcity-vcs.log for details", e);
-    }
+    });
   }
 
   private List<ChangeSet> parseChangeSetsXml(@NotNull final String xml) throws SAXException, ParserConfigurationException, IOException {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MercurialCommandLine.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MercurialCommandLine.java	Wed Jun 18 12:14:56 2014 +0200
@@ -22,49 +22,94 @@
 import org.jetbrains.annotations.NotNull;
 
 import java.nio.charset.Charset;
-import java.util.Collections;
-import java.util.Set;
+import java.util.*;
 
-public class MercurialCommandLine extends GeneralCommandLine {
+public class MercurialCommandLine {
+  private String myExePath;
+  private final List<String> myArguments = new ArrayList<String>();
+  private final Map<String, String> myEnvPatch = new TreeMap<String, String>();
 
   private String myWorkingDirectory;
   private final Set<String> myPrivateData;
   private Charset myCharset;
 
-  public MercurialCommandLine() {
-    this(Collections.<String>emptySet());
-  }
-
-  public MercurialCommandLine(@NotNull Set<String> privateData) {
+  public MercurialCommandLine(@NotNull final Set<String> privateData) {
     myPrivateData = privateData;
   }
 
-  @Override
-  public String getCommandLineString() {
-    String original = super.getCommandLineString();
-    return CommandUtil.removePrivateData(original, myPrivateData);
+  @NotNull
+  public MercurialCommandLine forkWithoutCommandlineArguments() {
+    final MercurialCommandLine fork = fork();
+    fork.myArguments.clear();
+    return fork;
   }
 
-  @Override
-  public void addParameter(@NotNull String parameter) {
-    String escaped = escape(parameter);
-    super.addParameter(escaped);
+  @NotNull
+  public MercurialCommandLine fork() {
+    final MercurialCommandLine line = new MercurialCommandLine(myPrivateData);
+    line.myExePath = myExePath;
+    line.myArguments.addAll(myArguments);
+    line.myEnvPatch.putAll(myEnvPatch);
+    line.myWorkingDirectory = myWorkingDirectory;
+    line.myCharset = myCharset;
+    return line;
   }
 
-  @Override
+  public int getCommandLineLength() {
+    return getCommandLineString().length() + 42;
+  }
+
+  @NotNull
+  public String getCommandLineString() {
+    return toGeneralCommandLine().getCommandLineString();
+  }
+
+  public void setExePath(@NotNull final String hgPath) {
+    myExePath = hgPath;
+  }
+
+  @NotNull
+  public List<String> getArguments() {
+    return new ArrayList<String>(myArguments);
+  }
+
+  public void addParameter(@NotNull final String parameter) {
+    String escaped = escape(parameter);
+    myArguments.add(escaped);
+  }
+
+  public void addParameters(@NotNull final Collection<String> argz) {
+    for (String s : argz) {
+      addParameter(s);
+    }
+  }
+
+  public void addParameters(@NotNull final String... argz) {
+    for (String s : argz) {
+      addParameter(s);
+    }
+  }
+
+  public void addParameters(@NotNull final String a, @NotNull final String b) {
+    addParameter(a);
+    addParameter(b);
+  }
+
+  public void setEnvParams(@NotNull final Map<String, String> hgEnv) {
+    myEnvPatch.clear();
+    myEnvPatch.putAll(hgEnv);
+  }
+
+  public void addEnvParam(@NotNull final String key, @NotNull final String value) {
+    myEnvPatch.put(key, value);
+  }
+
   public void setCharset(@NotNull Charset charset) {
     myCharset = charset;
   }
 
-  @Override
-  public Charset getCharset() {
-    return myCharset != null ? myCharset : super.getCharset();
-  }
-
-  @Override
   public void setWorkDirectory(@NonNls String path) {
     myWorkingDirectory = path;
-    super.setWorkDirectory(path);
   }
 
   public String getWorkingDirectory() {
@@ -74,4 +119,45 @@
   private String escape(String s) {
     return StringUtil.escapeQuotesIfWindows(s);
   }
+
+  @NotNull
+  public GeneralCommandLine toGeneralCommandLine() {
+    final Charset charset = myCharset;
+    final GeneralCommandLine cmd = new GeneralCommandLine() {
+      @Override
+      public String getCommandLineParams() {
+        return CommandUtil.removePrivateData(super.getCommandLineParams(), myPrivateData);
+      }
+
+      @Override
+      public String getCommandLineString() {
+        return CommandUtil.removePrivateData(super.getCommandLineString(), myPrivateData);
+      }
+
+      @Override
+      public Charset getCharset() {
+        if (charset != null) return charset;
+        return super.getCharset();
+      }
+    };
+
+    if (myExePath != null) {
+      cmd.setExePath(myExePath);
+    }
+
+    if (myWorkingDirectory != null) {
+      cmd.setWorkDirectory(myWorkingDirectory);
+    }
+
+    for (String argument : myArguments) {
+      cmd.addParameter(argument);
+    }
+
+    if (!myEnvPatch.isEmpty()) {
+      cmd.setPassParentEnvs(true);
+      cmd.setEnvParams(new TreeMap<String, String>(myEnvPatch));
+    }
+
+    return cmd;
+  }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ParentsCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ParentsCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,6 +16,7 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
@@ -47,6 +48,8 @@
     CommandResult res = runCommand(cli);
     List<String> parentRevisions = new ArrayList<String>();
     for (String line : res.getStdout().split("\n")) {
+      if (StringUtil.isEmpty(line))
+        continue;
       parentRevisions.add(new ChangeSet(line).getId());
     }
     return parentRevisions;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ProgressParser.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2000-2014 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.LineAwareByteArrayOutputStream;
+import org.jetbrains.annotations.NotNull;
+
+public class ProgressParser implements LineAwareByteArrayOutputStream.LineListener {
+
+  private final ProgressConsumer myConsumer;
+
+  public ProgressParser(@NotNull ProgressConsumer consumer) {
+    myConsumer = consumer;
+  }
+
+  public interface ProgressConsumer {
+    void consume(float progress, @NotNull String stage);
+  }
+
+  public void newLineDetected(@NotNull String line) {
+    String trimmed = line.trim();
+    if (trimmed.isEmpty())
+      return;
+
+    int spaceIdx = trimmed.indexOf(' ');
+    if (spaceIdx == -1) {
+      myConsumer.consume(-1, trimmed);
+      return;
+    }
+
+    String stage = trimmed.substring(0, spaceIdx);
+    String progress = trimmed.substring(spaceIdx).trim();
+    int ratioIdx = progress.indexOf('/');
+    if (ratioIdx == -1 || ratioIdx == trimmed.length() - 1) {
+      myConsumer.consume(-1, stage);
+      return;
+    }
+
+    try {
+      int nom = Integer.parseInt(progress.substring(0, ratioIdx));
+      int denom = Integer.parseInt(progress.substring(ratioIdx+1));
+      myConsumer.consume(nom * 1.0f / denom, stage);
+    } catch (NumberFormatException e) {
+      myConsumer.consume(-1, stage);
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -32,6 +32,7 @@
   private String myPullUrl;
   private int myTimeout;
   private boolean myTraceback;
+  private ProgressParser.ProgressConsumer myProgressConsumer;
 
   public PullCommand(@NotNull CommandSettings commandSettings,
                      @NotNull String hgPath,
@@ -60,15 +61,30 @@
     return this;
   }
 
+  public PullCommand withProgressConsumer(ProgressParser.ProgressConsumer progressConsumer) {
+    myProgressConsumer = progressConsumer;
+    return this;
+  }
+
   public void call() throws VcsException {
     ensureRepositoryIsNotLocked();
     MercurialCommandLine cli = createCommandLine();
     cli.addParameter("pull");
     if (myTraceback)
       cli.addParameter("--traceback");
-    String pullUrl = myAuthSettings != null ? myAuthSettings.getRepositoryUrlWithCredentials(myPullUrl) : myPullUrl;
+
+    CommandSettings settings = myCommandSettings.setTimeout(myTimeout);
+    if (myProgressConsumer != null) {
+      cli.addParameters("--config", "extensions.progress=");
+      cli.addParameters("--config", "progress.format=topic number");
+      cli.addParameters("--config", "progress.delay=0");
+      cli.addParameters("--config", "progress.assume-tty=True");
+      settings.setProgressConsumer(myProgressConsumer);
+    }
+
+    String pullUrl = myAuthSettings.getRepositoryUrlWithCredentials(myPullUrl);
     cli.addParameter(pullUrl);
-    runCommand(cli, myCommandSettings.setTimeout(myTimeout));
+    runCommand(cli, settings);
   }
 
   private void ensureRepositoryIsNotLocked() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PurgeCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class PurgeCommand extends BaseCommand {
+
+  private HgVcsRoot.PurgePolicy myPolicy;
+
+  public PurgeCommand(@NotNull CommandSettings commandSettings,
+                      @NotNull String hgPath,
+                      @NotNull File workingDir) {
+    super(commandSettings, hgPath, workingDir);
+  }
+
+  @NotNull
+  public PurgeCommand withPolicy(@NotNull HgVcsRoot.PurgePolicy policy) {
+    myPolicy = policy;
+    return this;
+  }
+
+  public void call() throws VcsException {
+    if (myPolicy == null || myPolicy == HgVcsRoot.PurgePolicy.DONT_RUN)
+      return;
+    MercurialCommandLine cmd = createCommandLine();
+    cmd.addParameters("--config", "extensions.purge=");
+    cmd.addParameter("purge");
+    if (myPolicy == HgVcsRoot.PurgePolicy.PURGE_ALL)
+      cmd.addParameter("--all");
+    runCommand(cmd);
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -37,21 +37,25 @@
     super(commandSettings, hgPath, workingDir, authSettings);
   }
 
+  @NotNull
   public StatusCommand fromRevision(@NotNull ChangeSet fromCset) {
     myFromId = fromCset.getId();
     return this;
   }
 
+  @NotNull
   public StatusCommand fromRevision(@NotNull String fromRevision) {
     myFromId = new ChangeSet(fromRevision).getId();
     return this;
   }
 
+  @NotNull
   public StatusCommand toRevision(@NotNull ChangeSet toCset) {
     myToId = toCset.getId();
     return this;
   }
 
+  @NotNull
   public StatusCommand toRevision(@NotNull String toRevision) {
     myToId = new ChangeSet(toRevision).getId();
     return this;
@@ -61,6 +65,7 @@
    * Adds option -A (--all)
    * @return self
    */
+  @NotNull
   public StatusCommand showAllFiles() {
     myShowAllFiles = true;
     return this;
@@ -70,32 +75,43 @@
    * Adds option -n (--no-status)
    * @return self
    */
+  @NotNull
   public StatusCommand hideStatus() {
     myHideStatus = true;
     return this;
   }
 
+  @NotNull
   public List<FileStatus> call() throws VcsException {
-    MercurialCommandLine cli = createCommandLine();
+    final MercurialCommandLine cli = createCommandLine();
     cli.addParameter("status");
-    if (myShowAllFiles)
+
+    if (myShowAllFiles) {
       cli.addParameter("-A");
-    if (myHideStatus)
+    }
+
+    if (myHideStatus) {
       cli.addParameter("-n");
+    }
     cli.addParameter("--rev");
+
     String from = myFromId;
     if (from == null)
       from = "0";
+
     String to = myToId;
-    if (to == null)
+    if (to == null) {
       to = "0";
+    }
     cli.addParameter(from + ":" + to);
-    CommandResult res = runCommand(cli);
+
+    final CommandResult res = runCommand(cli);
     return parseFiles(res.getStdout());
   }
 
-  private List<FileStatus> parseFiles(@NotNull String stdout) {
-    List<FileStatus> result = new ArrayList<FileStatus>();
+  @NotNull
+  private List<FileStatus> parseFiles(@NotNull final String stdout) {
+    final List<FileStatus> result = new ArrayList<FileStatus>();
     String[] lines = stdout.split("\n");
     for (String line : lines) {
       if (isEmpty(line))
@@ -109,12 +125,12 @@
   }
 
   @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);
+  private FileStatus parseLine(@NotNull final String line) {
+    if (myHideStatus) return new FileStatus(Status.UNKNOWN, line);
+
+    final char modifier = line.charAt(0);
+    final String path = line.substring(2);
+    final Status status = Status.makeStatus(modifier);
     return new FileStatus(status, path);
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -15,7 +15,6 @@
  */
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import com.intellij.execution.configurations.GeneralCommandLine;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -52,15 +51,12 @@
   public void call() throws VcsException {
     MercurialCommandLine cli = createCommandLine();
     cli.addParameter("tag");
-    setUser(cli);
+    if (myUsername != null) {
+      cli.addParameters("--user", myUsername);
+    }
     cli.addParameter("-r");
     cli.addParameter(myRevId);
     cli.addParameter(myTag);
     runCommand(cli);
   }
-
-  private void setUser(GeneralCommandLine cli) {
-    if (myUsername != null)
-      cli.addParameters("--user", myUsername);
-  }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,7 +16,6 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
@@ -40,12 +39,7 @@
     myAuthSettings = authSettings;
   }
 
-
-  protected CommandResult runCommand(@NotNull MercurialCommandLine cli, @NotNull CommandSettings s) throws VcsException {
-    s.setPrivateData(getPrivateData());
-    return CommandUtil.runCommand(cli, s);
-  }
-
+  @NotNull
   protected Set<String> getPrivateData() {
     String password = myAuthSettings.getPassword();
     if (password == null)
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,15 +16,12 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import com.intellij.execution.configurations.GeneralCommandLine;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersion;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.ParseHgVersionException;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * @author dmitry.neverov
@@ -38,24 +35,16 @@
   }
 
 
+  @NotNull
   public HgVersion call() throws VcsException, ParseHgVersionException {
     MercurialCommandLine cli = createCommandLine();
     cli.addParameter("version");
     cli.addParameter("--quiet");
-    setDefaultLocale(cli);
-    CommandResult result = CommandUtil.runCommand(cli, myCommandSettings);
+    cli.addEnvParam("LANG", "en_US");
+    cli.addEnvParam("LANGUAGE", "en_US");
+    cli.addEnvParam("LC_MESSAGE", "en_US");
+
+    CommandResult result = runCommand(cli, myCommandSettings);
     return HgVersion.parse(result.getStdout());
   }
-
-
-  private void setDefaultLocale(GeneralCommandLine commandLine) {
-    Map<String, String> env = commandLine.getEnvParams();
-    if (env == null)
-      env = new HashMap<String, String>();
-    env.put("LANG", "en_US");
-    env.put("LANGUAGE", "en_US");
-    env.put("LC_MESSAGE", "en_US");
-    commandLine.setEnvParams(env);
-  }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/python/load-commands-command.py	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+##
+##    Copyright 2000-2014 JetBrains
+##
+##     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.
+##
+##
+##  http://www.gnu.org/licenses/gpl-faq.html#GPLModuleLicense
+##  http://www.gnu.org/licenses/license-list.html#apache2
+##  http://en.wikipedia.org/wiki/Apache_License#GPL_compatibility
+##
+##
+"""
+load-commands-command
+"""
+
+import codecs
+from mercurial import dispatch
+from mercurial import commands
+
+def loadArguments(ui, params_file):
+  file_commands = []
+  with codecs.open(params_file, "r", "utf-8") as f:
+    for _line in f:
+      line = _line.strip()
+      if len(line) <= 0:
+        continue
+
+      file_commands.append(str(line))
+  return file_commands
+
+
+def load_commands_command(ui, params_file, *params):
+  command_arguments = loadArguments(ui, params_file)
+  return dispatch.dispatch(dispatch.request(command_arguments))
+
+#so here goes command registration and options
+cmdtable = {
+    "CMD": (load_commands_command, [], " OUTPUT_FILE")
+}
+
+commands.norepo += " CMD"
+
+testedwith = '2.2.2'
+buglink = "@jonnyzzz"
+
--- a/mercurial-server-tc/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialSubrepoUsageStatistics.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server-tc/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialSubrepoUsageStatistics.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,29 +16,30 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.usageStatistics.impl.providers.BaseVCSFeatureUsageStatisticsProvider;
 import jetbrains.buildServer.vcs.SVcsRoot;
 import jetbrains.buildServer.vcs.VcsManager;
 import org.jetbrains.annotations.NotNull;
 
-public class MercurialSubrepoUsageStatistics /*extends BaseVCSFeatureUsageStatisticsProvider*/ {
+public class MercurialSubrepoUsageStatistics extends BaseVCSFeatureUsageStatisticsProvider {
 
   public MercurialSubrepoUsageStatistics(@NotNull VcsManager vcsManager) {
-//    super(vcsManager);
+    super(vcsManager);
   }
 
   @NotNull
-//  @Override
+  @Override
   protected String getFeatureName() {
     return "subRepoSupport-mercurial";
   }
 
   @NotNull
-//  @Override
+  @Override
   protected String getFeatureDisplayName() {
     return "Mercurial VCS roots with subrepo support enabled";
   }
 
-//  @Override
+  @Override
   protected boolean hasFeature(@NotNull SVcsRoot root) {
     return Boolean.parseBoolean(root.getProperty(Constants.DETECT_SUBREPO_CHANGES));
   }
--- a/mercurial-server/resources/buildServerResources/mercurialSettings.jsp	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/resources/buildServerResources/mercurialSettings.jsp	Wed Jun 18 12:14:56 2014 +0200
@@ -39,7 +39,7 @@
     <th><label for="branchName">Default branch: </label></th>
     <td>
         <props:textProperty name="branchName" className="longField"/>
-        <div class="smallNote" style="margin: 0;">Branch to be used if no branch from Branch Specification is set</div>
+        <div class="smallNote" style="margin: 0;">The main branch to be monitored</div>
     </td>
   </tr>
   <bs:branchSpecTableRow/>
@@ -101,5 +101,21 @@
     </td>
   </tr>
   </l:settingsGroup>
-
+  <l:settingsGroup title="Agent Settings" className="advancedSetting">
+    <tr class="advancedSetting">
+      <td colspan="2">Agent-specific settings that are used in case of agent checkout.</td>
+    </tr>
+    <tr class="advancedSetting">
+      <th>
+        <label for="purgePolicy">Purge settings:</label>
+      </th>
+      <td>
+        <props:selectProperty name="purgePolicy" enableFilter="true" className="mediumField">
+          <props:option value="">Don't run purge</props:option>
+          <props:option value="PURGE_UNKNOWN">Purge unknown files</props:option>
+          <props:option value="PURGE_ALL">Purge ignored & unknown files</props:option>
+        </props:selectProperty>
+      </td>
+    </tr>
+  </l:settingsGroup>
 </table>
--- a/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Wed Jun 18 12:14:56 2014 +0200
@@ -31,4 +31,10 @@
 
   <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialCommitsInfoBuilderSupport"/>
   <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialModificationInfoBuilder"/>
+
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver"/>
+
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialFetchService"/>
 </beans>
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgTestConnectionSupport.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgTestConnectionSupport.java	Wed Jun 18 12:14:56 2014 +0200
@@ -47,8 +47,8 @@
 
 
   public String testConnection(@NotNull VcsRoot vcsRoot) throws VcsException {
-    HgVcsRoot root = myHgVcsRootFactory.createHgRoot(vcsRoot);
-    HgRepo repo = createRepo(root);
+    final HgVcsRoot root = myHgVcsRootFactory.createHgRoot(vcsRoot);
+    final HgRepo repo = createRepo(root);
     try {
       repo.id().repository(root.getRepository())
               .withAuthSettings(root.getAuthSettings())
@@ -64,8 +64,9 @@
     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());
+  @NotNull
+  private HgRepo createRepo(HgVcsRoot root) throws VcsException {
+    return myRepoFactory.createRepo(root, getWorkingDir(root));
   }
 
   private File getWorkingDir(HgVcsRoot root) {
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java	Wed Jun 18 12:14:56 2014 +0200
@@ -61,11 +61,12 @@
   @NotNull
   public RepositoryStateData getCurrentState(@NotNull VcsRoot root) throws VcsException {
     final HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
-    Map<String, String> revisions = myVcs.syncRepository(hgRoot, new VcsCallable<Map<String, String>>() {
+    VcsCallable<Map<String, String>> cmd = new VcsCallable<Map<String, String>>() {
       public Map<String, String> call() throws VcsException {
         return getHeads(hgRoot);
       }
-    });
+    };
+    Map<String, String> revisions = myVcs.syncRepository(hgRoot, new SyncSettings<Map<String, String>>(cmd));
     String defaultBranchName = hgRoot.getBranchName();
     if (revisions.get(defaultBranchName) == null) {
       throw new VcsException("Cannot find revision of the default branch '" +
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCommitsInfoBuilderSupport.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCommitsInfoBuilderSupport.java	Wed Jun 18 12:14:56 2014 +0200
@@ -59,11 +59,12 @@
 
     final HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
     final ServerHgRepo repo = mySupport.createRepo(hgRoot);
-    final MultiMapToList<String, String> heads = mySupport.syncRepository(hgRoot, new VcsCallable<MultiMapToList<String, String>>() {
+    VcsCallable<MultiMapToList<String, String>> cmd = new VcsCallable<MultiMapToList<String, String>>() {
       public MultiMapToList<String, String> call() throws VcsException {
         return commitToBranchs(mySupport.getCollectChangesPolicy().getHeads(hgRoot));
       }
-    });
+    };
+    final MultiMapToList<String, String> heads = mySupport.syncRepository(hgRoot, new SyncSettings<MultiMapToList<String, String>>(cmd));
     repo.logSubstates().call(new CommitsAndMountPointsCommand.Callback() {
       private final MercurialCommitsInfoBuilderStates subs = new MercurialCommitsInfoBuilderStates();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialFetchService.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2014 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.ProgressParser;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.FetchService;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+public class MercurialFetchService implements FetchService, MercurialServerExtension {
+
+  private final MercurialVcsSupport myVcs;
+  private final HgVcsRootFactory myHgVcsRootFactory;
+
+  public MercurialFetchService(@NotNull MercurialVcsSupport vcs,
+                               @NotNull HgVcsRootFactory hgVcsRootFactory) {
+    myVcs = vcs;
+    myHgVcsRootFactory = hgVcsRootFactory;
+    vcs.addExtension(this);
+  }
+
+  public void fetchRepository(@NotNull VcsRoot root,
+                              @NotNull CheckoutRules rules,
+                              @NotNull FetchRepositoryCallback callback) throws VcsException {
+    SyncSettings<Void> settings = new SyncSettings<Void>(VcsCallable.NO_OP);
+    settings.setProgressConsumer(new FetchProgressConsumer(callback));
+    myVcs.syncRepository(myHgVcsRootFactory.createHgRoot(root));
+  }
+
+  private class FetchProgressConsumer implements ProgressParser.ProgressConsumer {
+    private final FetchRepositoryCallback myCallback;
+    private FetchProgressConsumer(@NotNull FetchRepositoryCallback callback) {
+      myCallback = callback;
+    }
+
+    public void consume(float progress, @NotNull String stage) {
+      myCallback.update(progress, stage);
+    }
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Jun 18 12:14:56 2014 +0200
@@ -38,10 +38,7 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.IOException;
+import java.io.*;
 import java.util.*;
 
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil.deleteDir;
@@ -123,7 +120,7 @@
   @NotNull
   public byte[] getContent(@NotNull final String filePath, @NotNull final VcsRoot vcsRoot, @NotNull final String version) throws VcsException {
     ChangeSet cset = new ChangeSet(version);
-    HgVcsRoot root = myHgVcsRootFactory.createHgRoot(vcsRoot);
+    HgVcsRoot root = getHgRoot(vcsRoot);
     syncRepository(root, cset);
     HgRepo repo = createRepo(root);
     File parentDir = repo.cat().files(filePath).atRevision(cset).call();
@@ -138,6 +135,11 @@
   }
 
   @NotNull
+  public HgVcsRoot getHgRoot(@NotNull final VcsRoot vcsRoot) throws VcsException {
+    return myHgVcsRootFactory.createHgRoot(vcsRoot);
+  }
+
+  @NotNull
   public String getName() {
     return Constants.VCS_NAME;
   }
@@ -174,11 +176,12 @@
   }
 
   @NotNull
-  public String describeVcsRoot(final VcsRoot vcsRoot) {
+  public String describeVcsRoot(@NotNull final VcsRoot vcsRoot) {
     return "mercurial: " + vcsRoot.getProperty(Constants.REPOSITORY_PROP);
   }
 
   @Override
+  @NotNull
   public TestConnectionSupport getTestConnectionSupport() {
     return myTestConnection;
   }
@@ -218,30 +221,37 @@
                                      @NotNull final ChangeSet toVer,
                                      @NotNull final PatchBuilder builder,
                                      @NotNull final CheckoutRules checkoutRules) throws VcsException, IOException {
-    HgRepo repo = createRepo(root);
-    List<FileStatus> modifiedFiles = repo.status().fromRevision(fromVer).toRevision(toVer).call();
-    List<String> notDeletedFiles = new ArrayList<String>();
+    final HgRepo repo = createRepo(root);
+    final List<FileStatus> modifiedFiles = repo.status().fromRevision(fromVer).toRevision(toVer).call();
+    final List<String> notDeletedFiles = new ArrayList<String>();
     for (FileStatus f: modifiedFiles) {
       if (f.getStatus() != Status.REMOVED) {
         notDeletedFiles.add(f.getPath());
       }
     }
 
-    File parentDir = repo.cat().files(notDeletedFiles).atRevision(toVer).call();
+    File parentDir = null;
     try {
-      for (FileStatus f: modifiedFiles) {
-        String mappedPath = checkoutRules.map(f.getPath());
-        if (mappedPath == null) continue; // skip
-        final File virtualFile = new File(mappedPath);
-        if (f.getStatus() == Status.REMOVED) {
-          builder.deleteFile(virtualFile, true);
-        } else {
-          File realFile = new File(parentDir, f.getPath());
-          FileInputStream is = new FileInputStream(realFile);
-          try {
-            builder.changeOrCreateBinaryFile(virtualFile, null, is, realFile.length());
-          } finally {
-            is.close();
+      if (root.useArchiveForPatch()) {
+        parentDir = HgFileUtil.createTempDir();
+        final File archFile = new File(parentDir, "arch.tar");
+        buildIncrementalPatchWithArchive(builder, repo, toVer, checkoutRules, modifiedFiles, notDeletedFiles, archFile);
+      } else {
+        parentDir = repo.cat().files(notDeletedFiles).atRevision(toVer).call();
+        for (FileStatus f: modifiedFiles) {
+          String mappedPath = checkoutRules.map(f.getPath());
+          if (mappedPath == null) continue; // skip
+          final File virtualFile = new File(mappedPath);
+          if (f.getStatus() == Status.REMOVED) {
+            builder.deleteFile(virtualFile, true);
+          } else {
+            final File realFile = new File(parentDir, f.getPath());
+            final InputStream is = new BufferedInputStream(new FileInputStream(realFile));
+            try {
+              builder.changeOrCreateBinaryFile(virtualFile, null, is, realFile.length());
+            } finally {
+              FileUtil.close(is);
+            }
           }
         }
       }
@@ -253,7 +263,50 @@
       builder.deleteDirectory(new File(""), true);//clean patch
       buildFullPatch(root, toVer, builder, checkoutRules);
     } finally {
-      deleteDir(parentDir, Loggers.VCS);
+      if (parentDir != null)
+        deleteDir(parentDir, Loggers.VCS);
+    }
+  }
+
+  private void buildIncrementalPatchWithArchive(@NotNull final PatchBuilder builder,
+                                                @NotNull final HgRepo repo,
+                                                @NotNull final ChangeSet toVer,
+                                                @NotNull final CheckoutRules checkoutRules,
+                                                @NotNull final List<FileStatus> modifiedFiles,
+                                                @NotNull final List<String> notDeletedFiles,
+                                                @NotNull final File archiveFile) throws VcsException, IOException {
+    ArchiveCommand archive = repo.archive().revision(toVer).type("tar").destination(archiveFile);
+    int i = 0;
+    while (i < notDeletedFiles.size()) {
+      String mappedPath = checkoutRules.map(notDeletedFiles.get(i));
+      if (mappedPath == null) {
+        i++;
+        continue;
+      }
+      if (archive.addIncludeRule(notDeletedFiles.get(i))) {
+        i++;
+        continue;
+      }
+      //archive command is full, call it
+      archive.call();
+      buildPatchFromArchive(builder, archiveFile, checkoutRules, new ExcludeHgArchival());
+      FileUtil.delete(archiveFile);
+      archive = repo.archive().revision(toVer).type("tar").destination(archiveFile);
+    }
+    if (!notDeletedFiles.isEmpty()) {
+      archive.call();
+      buildPatchFromArchive(builder, archiveFile, checkoutRules, new ExcludeHgArchival());
+      FileUtil.delete(archiveFile);
+    }
+
+    //delete removed files
+    for (FileStatus f: modifiedFiles) {
+      if (f.getStatus() != Status.REMOVED) continue; //other files processed below
+
+      final String mappedPath = checkoutRules.map(f.getPath());
+      if (mappedPath == null) continue; // skip
+
+      builder.deleteFile(new File(mappedPath), true);
     }
   }
 
@@ -315,15 +368,15 @@
                               @NotNull final ChangeSet toVer,
                               @NotNull final PatchBuilder builder,
                               @NotNull final CheckoutRules checkoutRules) throws IOException, VcsException {
-    File tempDir = HgFileUtil.createTempDir();
+    final File tempDir = HgFileUtil.createTempDir();
     try {
-      HgRepo repo = createRepo(root);
+      final HgRepo repo = createRepo(root);
       if (root.includeSubreposInPatch()) {
-        Map<String, SubRepo> subrepos = repo.getSubrepositories(toVer);
+        final Map<String, SubRepo> subrepos = repo.getSubrepositories(toVer);
         if (!subrepos.isEmpty()) {
           Loggers.VCS.debug("Repository '" + root.getRepository() + "' has subrepos at revision " + toVer.getId() + ", use 'hg clone' to build clean patch");
-          File mirrorDir = getWorkingDir(root);
-          HgRepo cloneOfTheMirror = createRepo(root, tempDir);
+          final File mirrorDir = getWorkingDir(root);
+          final HgRepo cloneOfTheMirror = createRepo(root, tempDir);
           cloneOfTheMirror.doClone().fromRepository(mirrorDir)
                   .setUpdateWorkingDir(false)
                   .setUsePullProtocol(false)
@@ -338,11 +391,7 @@
           if (root.useArchiveForPatch()) {
             File archive = new File(tempDir, "arch.tar");
             repo.archive().revision(toVer).type("tar").destination(archive).call();
-            buildPatchFromArchive(builder, archive, checkoutRules, new FileFilter() {
-              public boolean accept(File f) {
-                return !f.getName().equals(".hg_archival.txt");
-              }
-            });
+            buildPatchFromArchive(builder, archive, checkoutRules, new ExcludeHgArchival());
           } else {
             repo.archive().revision(toVer).destination(tempDir).call();
             buildPatchFromDirectory(builder, tempDir, checkoutRules, myAcceptAllFilter);
@@ -351,13 +400,9 @@
       } else {
         Loggers.VCS.debug("Subrepos disabled in VCS root, use 'hg archive' to build clean patch");
         if (root.useArchiveForPatch()) {
-          File archive = new File(tempDir, "arch.tar");
+          final File archive = new File(tempDir, "arch.tar");
           repo.archive().revision(toVer).type("tar").destination(archive).call();
-          buildPatchFromArchive(builder, archive, checkoutRules, new FileFilter() {
-            public boolean accept(File f) {
-              return !f.getName().equals(".hg_archival.txt");
-            }
-          });
+          buildPatchFromArchive(builder, archive, checkoutRules, new ExcludeHgArchival());
         } else {
           repo.archive().revision(toVer).destination(tempDir).call();
           buildPatchFromDirectory(builder, tempDir, checkoutRules, myAcceptAllFilter);
@@ -401,37 +446,48 @@
     }
   }
 
-  private void buildPatchFromArchive(@NotNull PatchBuilder builder,
-                                     @NotNull File archive,
-                                     @NotNull CheckoutRules checkoutRules,
-                                     @NotNull FileFilter filter) throws IOException {
-    FileInputStream fis = new FileInputStream(archive);
+  private void buildPatchFromArchive(@NotNull final PatchBuilder builder,
+                                     @NotNull final File archive,
+                                     @NotNull final CheckoutRules checkoutRules,
+                                     @NotNull final FileFilter filter) throws IOException {
+    InputStream fis = null;
     ArchiveInputStream is = null;
     try {
+      fis = new BufferedInputStream(new FileInputStream(archive));
       is = new TarArchiveInputStream(fis);
-      ArchiveEntry entry = null;
+
+      ArchiveEntry entry;
       while ((entry = is.getNextEntry()) != null) {
+        if (entry.isDirectory()) continue;
+
         String fileName = entry.getName();
-        if (fileName.startsWith("arch/"))
-          fileName = fileName.substring(5);
-        if (!filter.accept(new File(fileName)))
-          continue;
-        String mappedFile = checkoutRules.map(fileName);
-        if (!StringUtil.isEmpty(mappedFile))
+        //TODO: does it work if I have arch/ in my repo?
+        if (fileName.startsWith("arch/")) fileName = fileName.substring(5);
+
+        if (!filter.accept(new File(fileName))) continue;
+
+        final String mappedFile = checkoutRules.map(fileName);
+        if (!StringUtil.isEmpty(mappedFile)) {
           builder.createBinaryFile(new File(mappedFile), null, is, entry.getSize());
+        }
       }
     } finally {
-      fis.close();
-      if (is != null)
-        is.close();
+      FileUtil.closeAll(is, fis);
     }
   }
 
-  private void buildPatchFromDirectory(final PatchBuilder builder, final File repRoot, final CheckoutRules checkoutRules, @NotNull final FileFilter filter) throws IOException {
+  private void buildPatchFromDirectory(@NotNull final PatchBuilder builder,
+                                       @NotNull final File repRoot,
+                                       @NotNull final CheckoutRules checkoutRules,
+                                       @NotNull final FileFilter filter) throws IOException {
     buildPatchFromDirectory(repRoot, builder, repRoot, checkoutRules, filter);
   }
 
-  private void buildPatchFromDirectory(File curDir, final PatchBuilder builder, final File repRoot, final CheckoutRules checkoutRules, @NotNull final FileFilter filter) throws IOException {
+  private void buildPatchFromDirectory(@NotNull final File curDir,
+                                       @NotNull final PatchBuilder builder,
+                                       @NotNull final File repRoot,
+                                       @NotNull final CheckoutRules checkoutRules,
+                                       @NotNull final FileFilter filter) throws IOException {
     File[] files = curDir.listFiles(filter);
     if (files != null) {
       for (File realFile: files) {
@@ -490,21 +546,21 @@
   }
 
   public void syncRepository(@NotNull final VcsRoot root) throws VcsException {
-    syncRepository(myHgVcsRootFactory.createHgRoot(root));
+    syncRepository(getHgRoot(root));
   }
 
   public void syncRepository(@NotNull final HgVcsRoot root) throws VcsException {
-    syncRepository(root, VcsCallable.NO_OP);
+    syncRepository(root, new SyncSettings<Void>(VcsCallable.NO_OP));
   }
 
-  public <T> T syncRepository(@NotNull HgVcsRoot root, @NotNull VcsCallable<T> cmd) throws VcsException {
+  public <T> T syncRepository(@NotNull HgVcsRoot root, @NotNull SyncSettings<T> settings) throws VcsException {
     boolean customWorkingDir = root.getCustomWorkingDir() != null;
     File workingDir = getWorkingDir(root);
     int attemptsLeft = 3;
     VcsException lastError = null;
     while (attemptsLeft-- > 0) {
       try {
-        return syncRepositoryOnce(root, cmd, workingDir);
+        return syncRepositoryOnce(root, settings, workingDir);
       } catch (UnrelatedRepositoryException e) {
         if (customWorkingDir)
           throw new VcsException(e.getMessage() + ". VCS root uses a custom clone dir, manual recovery is required.", e);
@@ -525,7 +581,7 @@
   }
 
 
-  private <T> T syncRepositoryOnce(@NotNull HgVcsRoot root, @NotNull VcsCallable<T> cmd, @NotNull File workingDir) throws VcsException {
+  private <T> T syncRepositoryOnce(@NotNull HgVcsRoot root, @NotNull SyncSettings<T> settings, @NotNull File workingDir) throws VcsException {
     lockWorkDir(workingDir);
     HgRepo repo = createRepo(root);
     try {
@@ -533,15 +589,17 @@
           resetBookmarks(repo);
           repo.pull().fromRepository(root.getRepository())
                   .withTimeout(myConfig.getPullTimeout())
+                  .withProgressConsumer(settings.getProgressConsumer())
                   .call();
       } else {
         repo.doClone().fromRepository(root.getRepository())
                 .setUpdateWorkingDir(false)
                 .useUncompressedTransfer(root.isUncompressedTransfer())
+                .withProgressConsumer(settings.getProgressConsumer())
                 .call();
         repo.setDefaultPath(root.getRepository());
       }
-      return cmd.call();
+      return settings.getCmd().call();
     } finally {
       unlockWorkDir(workingDir);
     }
@@ -577,31 +635,39 @@
     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);
+  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 {
+    final HgVcsRoot hgRoot = getHgRoot(root);
     buildPatch(hgRoot, fromVersion, toVersion, builder, checkoutRules);
   }
 
-  public void buildPatch(@NotNull HgVcsRoot hgRoot,
-                         @Nullable String fromVersion,
-                         @NotNull String toVersion,
-                         @NotNull PatchBuilder builder,
-                         @NotNull CheckoutRules checkoutRules) throws IOException, VcsException {
+  public void buildPatch(@NotNull final HgVcsRoot hgRoot,
+                         @Nullable final String fromVersion,
+                         @NotNull final String toVersion,
+                         @NotNull final PatchBuilder builder,
+                         @NotNull final CheckoutRules checkoutRules) throws IOException, VcsException {
     syncRepository(hgRoot);
-    ChangeSet to = new ChangeSet(toVersion);
+
+    final 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);
-      }
+      return;
     }
+
+    final ChangeSet from = new ChangeSet(fromVersion);
+    final HgRepo repo = createRepo(hgRoot);
+    if (repo.containsRevision(from)) {
+      buildIncrementalPatch(hgRoot, from, to, builder, checkoutRules);
+      return;
+    }
+
+    Loggers.VCS.info("Cannot find revision " + fromVersion + " in repository " + hgRoot.getRepository() + ", will build a full patch");
+    cleanCheckoutDir(builder, checkoutRules);
+    buildFullPatch(hgRoot, to, builder, checkoutRules);
   }
 
   private void cleanCheckoutDir(@NotNull PatchBuilder builder, @NotNull CheckoutRules checkoutRules) throws IOException {
@@ -621,11 +687,12 @@
     return myConfig.allowSourceCaching();
   }
 
+  @NotNull
   public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules) throws VcsException {
     File tmpDir = null;
     try {
       tmpDir = createLabelingTmpDir();
-      HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+      HgVcsRoot hgRoot = getHgRoot(root);
       hgRoot.setCustomWorkingDir(tmpDir);
       syncRepository(hgRoot);
       HgRepo repo = createRepo(hgRoot);
@@ -660,7 +727,7 @@
     return label.replace(':', '_').replace('\r', '_').replace('\n', '_');
   }
 
-  public File getWorkingDir(@NotNull HgVcsRoot root) {
+  public File getWorkingDir(HgVcsRoot root) {
     File customDir = root.getCustomWorkingDir();
     return customDir != null ? customDir : myMirrorManager.getMirrorDir(root.getRepository());
   }
@@ -703,17 +770,19 @@
     }
   }
 
+  @NotNull
   public ServerHgRepo createRepo(@NotNull HgVcsRoot root) throws VcsException {
-    return myRepoFactory.create(getWorkingDir(root), myHgPathProvider.getHgPath(root), root.getAuthSettings());
+    return myRepoFactory.createRepo(root, getWorkingDir(root));
   }
 
+  @NotNull
   public ServerHgRepo createRepo(@NotNull OperationContext ctx, @NotNull HgVcsRoot root) throws VcsException {
-    return ctx.createRepo(getWorkingDir(root), myHgPathProvider.getHgPath(root), root.getAuthSettings());
+    return ctx.createRepo(root, getWorkingDir(root));
   }
 
-
-  public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File customDir) throws VcsException {
-    return myRepoFactory.create(customDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
+  @NotNull
+  public ServerHgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File customDir) throws VcsException {
+    return myRepoFactory.createRepo(root, customDir);
   }
 
   @NotNull
@@ -746,4 +815,11 @@
     }
     return super.getVcsCustomExtension(extensionClass);
   }
+
+
+  private static class ExcludeHgArchival implements FileFilter {
+    public boolean accept(File f) {
+      return !f.getName().equals(".hg_archival.txt");
+    }
+  }
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java	Wed Jun 18 12:14:56 2014 +0200
@@ -18,10 +18,10 @@
 
 import com.intellij.openapi.util.Pair;
 import gnu.trove.TLongObjectHashMap;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import jetbrains.buildServer.util.Hash;
-import jetbrains.buildServer.util.graph.*;
+import jetbrains.buildServer.util.graph.BFSVisitorAdapter;
+import jetbrains.buildServer.util.graph.DAG;
 import jetbrains.buildServer.vcs.ModificationData;
 import jetbrains.buildServer.vcs.RepositoryStateData;
 import jetbrains.buildServer.vcs.VcsException;
@@ -117,11 +117,13 @@
   }
 
   @NotNull
-  public ServerHgRepo createRepo(@NotNull File workingDir, @NotNull String hgPath, @NotNull AuthSettings authSettings) throws VcsException {
+  public ServerHgRepo createRepo(@NotNull final HgVcsRoot root, @NotNull final File workingDir) throws VcsException {
     ServerHgRepo repo = myRepos.get(workingDir);
-    if (repo != null)
+    if (repo != null) {
       return repo;
-    repo = myRepoFactory.create(workingDir, hgPath, authSettings);
+    }
+
+    repo = myRepoFactory.createRepo(root, workingDir);
     repo.setOperationContext(this);
     myRepos.put(workingDir, repo);
     return repo;
@@ -208,7 +210,7 @@
                                                       @NotNull String fromRevision,
                                                       @NotNull String toRevision) throws VcsException {
     syncRepository(root);
-    ServerHgRepo repo = createRepo(myVcs.getWorkingDir(root), myHgPathProvider.getHgPath(root), root.getAuthSettings());
+    ServerHgRepo repo = createRepo(root, myVcs.getWorkingDir(root));
 
     if (!repo.supportRevsets())
       return singleton(fromRevision);
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RepoFactory.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RepoFactory.java	Wed Jun 18 12:14:56 2014 +0200
@@ -18,33 +18,26 @@
 
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRoot;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
-import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
 import java.io.IOException;
 
-import static com.intellij.openapi.util.io.FileUtil.createTempFile;
-import static com.intellij.openapi.util.io.FileUtil.delete;
-
 /**
  * @author dmitry.neverov
  */
 public class RepoFactory implements HgRepoFactory {
 
   protected final ServerPluginConfig myConfig;
-  protected final CommandSettingsFactory myCommandSettingsFactory;
+  protected final CommandSettingsForRoot myCommandSettingsFactory;
   protected final HgPathProvider myHgPathProvider;
 
-  protected final MercurialLogTemplate myLogTemplate = new MercurialLogTemplate("/buildServerResources/log.template", "hg.log.template");
-  protected final MercurialLogTemplate myLogNoFilesTemplate = new MercurialLogTemplate("/buildServerResources/log.no.files.template", "hg.short.log.template");
-  protected final MercurialLogTemplate myDagTemplate = new MercurialLogTemplate("/buildServerResources/dag.template", "hg.dag.template");
-  protected final MercurialLogTemplate myFastLogTemplate = new MercurialLogTemplate("/buildServerResources/fastlog.template", "hg.fastlog.template");
 
   public RepoFactory(@NotNull ServerPluginConfig config,
-                     @NotNull CommandSettingsFactory commandSettingsFactory,
+                     @NotNull CommandSettingsForRoot commandSettingsFactory,
                      @NotNull HgPathProvider hgPathProvider) throws IOException {
     myConfig = config;
     myCommandSettingsFactory = commandSettingsFactory;
@@ -52,58 +45,20 @@
   }
 
   @NotNull
-  public ServerHgRepo create(@NotNull File workingDir,
-                             @NotNull String hgPath,
-                             @NotNull AuthSettings authSettings) throws VcsException {
-    return new ServerHgRepo(myCommandSettingsFactory, myConfig, workingDir, hgPath, authSettings)
-            .withLogTemplates(myLogTemplate.getTemplate(),
-                    myLogNoFilesTemplate.getTemplate(),
-                    myDagTemplate.getTemplate(),
-                    myFastLogTemplate.getTemplate());
+  protected ServerHgRepo create(@NotNull File workingDir,
+                                @NotNull String hgPath,
+                                @NotNull AuthSettings authSettings,
+                                @NotNull CommandSettingsFactory commandSettingsFactory,
+                                @NotNull ServerPluginConfig config) {
+    return new ServerHgRepo(commandSettingsFactory, config, workingDir, hgPath, authSettings);
   }
 
-  public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException {
-    return create(workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
+  @NotNull
+  public ServerHgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException {
+    return create(workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings(), myCommandSettingsFactory.forRoot(root), myConfig);
   }
 
   public void dispose() {
-    myLogTemplate.dispose();
-    myLogNoFilesTemplate.dispose();
-    myDagTemplate.dispose();
-    myFastLogTemplate.dispose();
   }
 
-  static class MercurialLogTemplate {
-    private final String myResourcePath;
-    private final String myTmpFileSuffix;
-    private File myFile;
-
-    private MercurialLogTemplate(@NotNull String resourcePath, @NotNull String tmpFileSuffix) throws IOException {
-      myResourcePath = resourcePath;
-      myTmpFileSuffix = tmpFileSuffix;
-      copyTemplate();
-    }
-
-    @NotNull
-    public File getTemplate() throws VcsException {
-      if (myFile.isFile() && myFile.exists())
-        return myFile;
-      try {
-        copyTemplate();
-        return myFile;
-      } catch (IOException e) {
-        throw new VcsException("Cannot create mercurial log template", e);
-      }
-    }
-
-    public void dispose() {
-      delete(myFile);
-    }
-
-    private void copyTemplate() throws IOException {
-      File template = createTempFile("teamcity", myTmpFileSuffix);
-      FileUtil.copyResource(RepoFactory.class, myResourcePath, template);
-      myFile = template;
-    }
-  }
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerCommandSettingsFactory.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerCommandSettingsFactory.java	Wed Jun 18 12:14:56 2014 +0200
@@ -28,6 +28,7 @@
     myConfig = config;
   }
 
+  @NotNull
   public CommandSettings create() {
     return new CommandSettings().setLogLevel("debug").setLogOutputLimit(myConfig.getLogOutputLimit());
   }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java	Wed Jun 18 12:14:56 2014 +0200
@@ -39,10 +39,10 @@
   private final static HgVersion REVSET_HG_VERSION = new HgVersion(1, 7, 0);
   private final CommandSettingsFactory myCommandSettingsFactory;
   private final ServerPluginConfig myConfig;
-  private File myLogTemplate;
-  private File myLogNoFilesTemplate;
-  private File myDagTemplate;
-  private File myFastLogTemplate;
+  protected final MercurialClasspathTemplate myLogTemplate = new MercurialClasspathTemplate("/buildServerResources/log.template", "hg.log.template");
+  protected final MercurialClasspathTemplate myLogNoFilesTemplate = new MercurialClasspathTemplate("/buildServerResources/log.no.files.template", "hg.short.log.template");
+  protected final MercurialClasspathTemplate myDagTemplate = new MercurialClasspathTemplate("/buildServerResources/dag.template", "hg.dag.template");
+  protected final MercurialClasspathTemplate myFastLogTemplate = new MercurialClasspathTemplate("/buildServerResources/fastlog.template", "hg.fastlog.template");
   private OperationContext myContext;
 
   public ServerHgRepo(@NotNull CommandSettingsFactory commandSettingsFactory,
@@ -59,17 +59,6 @@
     myContext = context;
   }
 
-  public ServerHgRepo withLogTemplates(@NotNull File logTemplate,
-                                       @NotNull File logNoFilesTemplate,
-                                       @NotNull File dagTemplate,
-                                       @NotNull File fastLogTemplate) {
-    myLogTemplate = logTemplate;
-    myLogNoFilesTemplate = logNoFilesTemplate;
-    myDagTemplate = dagTemplate;
-    myFastLogTemplate = fastLogTemplate;
-    return this;
-  }
-
   public LogCommand log() {
     return new LogCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings).withTemplate(myLogTemplate);
   }
@@ -80,7 +69,7 @@
   }
 
   public LogCommand log(@NotNull HgVcsRoot root) {
-    File template = root.isSubrepo() && !myConfig.reportSubrepoChangesFileStatus() ? myFastLogTemplate : myLogTemplate;
+    final MercurialTemplate template = root.isSubrepo() && !myConfig.reportSubrepoChangesFileStatus() ? myFastLogTemplate : myLogTemplate;
     return new LogCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings).withTemplate(template);
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SyncSettings.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2000-2014 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.ProgressParser;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class SyncSettings<T> {
+  private VcsCallable<T> myCmd;
+  private ProgressParser.ProgressConsumer myProgressConsumer;
+
+  public SyncSettings(@NotNull VcsCallable<T> cmd) {
+    myCmd = cmd;
+  }
+
+  @NotNull
+  public VcsCallable<T> getCmd() {
+    return myCmd;
+  }
+
+  @Nullable
+  public ProgressParser.ProgressConsumer getProgressConsumer() {
+    return myProgressConsumer;
+  }
+
+  @NotNull
+  public SyncSettings<T> setProgressConsumer(ProgressParser.ProgressConsumer progressConsumer) {
+    myProgressConsumer = progressConsumer;
+    return this;
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CollectChangesNoRevsets.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CollectChangesNoRevsets.java	Wed Jun 18 12:14:56 2014 +0200
@@ -17,6 +17,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import com.intellij.openapi.util.Pair;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialTemplate;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgRepo;
 import jetbrains.buildServer.util.graph.DAG;
 import jetbrains.buildServer.util.graph.DAGIterator;
@@ -24,7 +25,6 @@
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
-import java.io.File;
 import java.util.*;
 
 /**
@@ -34,9 +34,9 @@
 
   private final HgVcsRoot myRoot;
   private final ServerHgRepo myRepo;
-  private final File myLogNoFilesTemplate;
+  private final MercurialTemplate myLogNoFilesTemplate;
 
-  public CollectChangesNoRevsets(@NotNull HgVcsRoot root, @NotNull ServerHgRepo repo, @NotNull File logNoFilesTemplate) {
+  public CollectChangesNoRevsets(@NotNull HgVcsRoot root, @NotNull ServerHgRepo repo, @NotNull MercurialTemplate logNoFilesTemplate) {
     myRoot = root;
     myRepo = repo;
     myLogNoFilesTemplate = logNoFilesTemplate;
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -17,6 +17,9 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
 import jetbrains.buildServer.agent.*;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
 import jetbrains.buildServer.vcs.*;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
@@ -62,7 +65,7 @@
 
     AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
     myMirrorManager = new MirrorManagerImpl(pluginConfig);
-    AgentRepoFactory repoFactory = new AgentRepoFactory(new TestCommandSettingsFactory(), new AgentHgPathProvider(agentConfig));
+    AgentRepoFactory repoFactory = new AgentRepoFactory(new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig));
     myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, myMirrorManager, repoFactory);
     myCleaner = new AgentMirrorCleaner(myMirrorManager);
     myLogger = myContext.mock(BuildProgressLogger.class);
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -18,6 +18,9 @@
 import jetbrains.buildServer.agent.AgentRunningBuild;
 import jetbrains.buildServer.agent.BuildAgentConfiguration;
 import jetbrains.buildServer.agent.BuildProgressLogger;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.TestFor;
@@ -75,7 +78,7 @@
     final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
     myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig,
             new MirrorManagerImpl(pluginConfig),
-            new AgentRepoFactory(new TestCommandSettingsFactory(), new AgentHgPathProvider(agentConfig)));
+            new AgentRepoFactory(new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig)));
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
@@ -127,7 +130,7 @@
     return future.get();
   }
 
-  private File doUpdate(@NotNull VcsRoot root, @NotNull String version) throws VcsException {
+  protected File doUpdate(@NotNull VcsRoot root, @NotNull String version) throws VcsException {
     return doUpdate(root, version, IncludeRule.createDefaultInstance());
   }
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -20,8 +20,7 @@
 import jetbrains.buildServer.agent.BuildAgentConfiguration;
 import jetbrains.buildServer.agent.BuildProgressLogger;
 import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.ConnectionRefusedException;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.CheckoutRules;
@@ -83,7 +82,7 @@
     myMirrorManager = new MirrorManagerImpl(pluginConfig);
     myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig,
             myMirrorManager,
-            new AgentRepoFactory(new TestCommandSettingsFactory(), new AgentHgPathProvider(agentConfig)));
+            new AgentRepoFactory(new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig)));
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseAgentSideCheckoutTestCase.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
+import jetbrains.buildServer.agent.BuildProgressLogger;
+import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.IncludeRule;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.annotations.BeforeMethod;
+
+import java.io.File;
+import java.util.HashMap;
+
+public abstract class BaseAgentSideCheckoutTestCase extends BaseMercurialTestCase {
+
+  protected Mockery myContext;
+  protected BuildProgressLogger myLogger;
+  protected UpdateByIncludeRules2 myVcsSupport;
+  private int myBuildCounter = 0;
+  protected File myWorkDir;
+
+  @Override
+  @BeforeMethod
+  public void setUp() throws Exception {
+    super.setUp();
+
+    myContext = new Mockery();
+
+    final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class);
+    myContext.checking(new Expectations() {{
+      allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myTempFiles.createTempDir()));
+      allowing(agentConfig).getParametersResolver(); will(returnValue(new HgPathResolver()));
+    }});
+
+    final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
+    MirrorManager mirrorManager = new MirrorManagerImpl(pluginConfig);
+    CommandSettingsForRootImpl commandSettingsFactory = new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver());
+    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, mirrorManager, new AgentRepoFactory(commandSettingsFactory, new AgentHgPathProvider(agentConfig)));
+
+    myLogger = myContext.mock(BuildProgressLogger.class);
+    myContext.checking(new Expectations() {{
+      allowing(myLogger).message(with(any(String.class)));
+      allowing(myLogger).warning(with(any(String.class)));
+    }});
+
+    myWorkDir = myTempFiles.createTempDir();
+  }
+
+
+  protected void checkout(@NotNull VcsRoot vcsRoot, @NotNull String toVersion) throws VcsException {
+    checkout(vcsRoot, toVersion, false);
+  }
+
+  protected void checkout(@NotNull VcsRoot vcsRoot, @NotNull String toVersion, final boolean useLocalMirrors) 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", useLocalMirrors ? "true" : "false");
+      }}));
+    }});
+    myVcsSupport.getUpdater(vcsRoot, CheckoutRules.DEFAULT, toVersion, myWorkDir, build, false).process(IncludeRule.createDefaultInstance(), myWorkDir);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ExtensionsTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import junit.framework.Assert;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialSupportBuilder.mercurialSupport;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigBuilder.serverPluginConfig;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+
+/**
+ * Created 25.02.14 13:17
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+@RequiredHgVersion(min = "2.0.0")
+public class ExtensionsTest extends BaseMercurialTestCase {
+
+  @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
+  public void test_no_extension(HgVersion _) throws IOException, VcsException {
+    String extension = "HGExtensionThatDoesNotExits";
+
+    try {
+      runWithExtensions(extension);
+      Assert.fail();
+    } catch (VcsException e) {
+      Assert.assertTrue(e.getMessage().contains(extension));
+    }
+  }
+
+  @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
+  public void test_extension(HgVersion _) throws IOException, VcsException {
+    runWithExtensions("mq", "largefiles");
+  }
+
+  private void runWithExtensions(@NotNull String... extensions) throws IOException, VcsException {
+    ServerPluginConfig config = serverPluginConfig()
+            .cachesDir(myTempFiles.createTempDir())
+            .hgPath(Util.getHgPath())
+            .build();
+
+    final File myRemoteRepository = myTempFiles.createTempDir();
+    Util.copyRepository(new File("mercurial-tests/testData/rep2"), myRemoteRepository);
+
+
+    final MercurialSupportBuilder hgBuilder = mercurialSupport().withConfig(config);
+    final MercurialVcsSupport vcs = hgBuilder.build();
+    final VcsRoot root = vcsRoot().withUrl(myRemoteRepository.getAbsolutePath()).withBranch("default").withExtensions(extensions).build();
+
+    vcs.getCollectChangesPolicy().getCurrentState(root);
+    vcs.getTestConnectionSupport().testConnection(root);
+  }
+
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialSupportBuilder.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialSupportBuilder.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,6 +16,9 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
 import jetbrains.buildServer.serverSide.ServerListener;
 import jetbrains.buildServer.util.EventDispatcher;
@@ -52,7 +55,7 @@
     MirrorManagerImpl mirrorManager = new MirrorManagerImpl(myConfig);
     myHgPathProvider = new ServerHgPathProvider(myConfig);
     if (myRepoFactory == null)
-      myRepoFactory = new RepoFactory(myConfig, new TestCommandSettingsFactory(), myHgPathProvider);
+      myRepoFactory = new RepoFactory(myConfig, new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), myHgPathProvider);
     HgTestConnectionSupport testConnection = new HgTestConnectionSupport(myHgRootFactory, myRepoFactory, mirrorManager, myHgPathProvider);
     final ResetCacheRegister resetCacheManager = myContext.mock(ResetCacheRegister.class);
     myContext.checking(new Expectations() {{
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -264,11 +264,10 @@
     assertEquals(actualTag, "new_tag");
 
     // check the tag is pushed to the parent repository
-    MercurialCommandLine cli = new MercurialCommandLine();
+    MercurialCommandLine cli = new MercurialCommandLine(Collections.<String>emptySet());
     cli.setExePath(vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP));
     cli.setWorkDirectory(vcsRoot.getProperty(Constants.REPOSITORY_PROP));
     cli.setEnvParams(map("HGRCPATH", ""));
-    cli.setPassParentEnvs(true);
     cli.addParameter("tags");
     CommandResult res = CommandUtil.runCommand(cli, new CommandSettings());
     assertTrue(res.getStdout().contains("new_tag"));
@@ -282,12 +281,11 @@
     assertEquals(actualTag, "branch_tag");
 
     // check the tag is pushed to the parent repository
-    MercurialCommandLine cli = new MercurialCommandLine();
+    MercurialCommandLine cli = new MercurialCommandLine(Collections.<String>emptySet());
     cli.setExePath(vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP));
     cli.setWorkDirectory(vcsRoot.getProperty(Constants.REPOSITORY_PROP));
     cli.addParameter("tags");
     cli.setEnvParams(map("HGRCPATH", ""));
-    cli.setPassParentEnvs(true);
     CommandResult res = CommandUtil.runCommand(cli, new CommandSettings());
     assertTrue(res.getStdout().contains("branch_tag"));
     assertTrue(res.getStdout().contains("7:376dcf05cd2a"));
@@ -601,8 +599,6 @@
     if (!SystemInfo.isUnix)
       return;
 
-    RepoFactory repoFactory = new RepoFactory(myPluginConfig, new TestCommandSettingsFactory(), myHgPathProvider);
-
     //create a file on the server
     File dirOnTheServer = myTempFiles.createTempDir();
     File fileOnTheServer = new File(dirOnTheServer, "file.on.server");
@@ -610,7 +606,7 @@
 
     //create a remote repository with symlink pointing to the file on the server
     File repository = copyRepository(myTempFiles, simpleRepo());
-    ServerHgRepo repo = repoFactory.create(repository, getHgPath(), new AuthSettings());
+    ServerHgRepo repo = new ServerHgRepo(new TestCommandSettingsFactory(), myPluginConfig, repository, getHgPath(), new AuthSettings());
     repo.update().toRevision("9c6a6b4aede0").call();
     new ProcessBuilder("ln", "-s", dirOnTheServer.getCanonicalPath()).directory(repository).start().waitFor();
     new ProcessBuilder(getHgPath(), "add", dirOnTheServer.getName()).directory(repository).start().waitFor();
@@ -634,17 +630,14 @@
 
     VersionCommand russianLocalVersion = new VersionCommand(new TestCommandSettingsFactory().create(), Util.getHgPath(), new File(simpleRepo())) {
       @Override
+      @NotNull
       protected MercurialCommandLine createCommandLine() {
-        MercurialCommandLine commandLine = super.createCommandLine();
-        Map<String, String> env = commandLine.getEnvParams();
-        if (env == null)
-          env = new HashMap<String, String>();
-        env.put("LANG", "ru_RU");
-        env.put("LANGUAGE", "ru_RU");
-        env.put("LC_MESSAGE", "ru_RU");
-        env.put("HGRCPATH", "");
-        commandLine.setEnvParams(env);
-        return commandLine;
+        final MercurialCommandLine env = super.createCommandLine();
+        env.addEnvParam("LANG", "ru_RU");
+        env.addEnvParam("LANGUAGE", "ru_RU");
+        env.addEnvParam("LC_MESSAGE", "ru_RU");
+        env.addEnvParam("HGRCPATH", "");
+        return env;
       }
     };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PurgeTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2000-2014 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.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+@Test
+public class PurgeTest extends BaseAgentSideCheckoutTestCase {
+
+  private String myRemoteRepository;
+
+  @Override
+  @BeforeMethod
+  public void setUp() throws Exception {
+    super.setUp();
+
+    File tmp = myTempFiles.createTempDir();
+    File remoteRepo = new File(tmp, "testPurge");
+    copyRepository(new File("mercurial-tests/testData/testPurge"), remoteRepo);
+    myRemoteRepository = remoteRepo.getAbsolutePath();
+  }
+
+
+  public void purge_unknown() throws Exception {
+    VcsRoot vcsRoot = vcsRoot().withUrl(myRemoteRepository).withPurgePolicy(HgVcsRoot.PurgePolicy.PURGE_UNKNOWN).build();
+    checkout(vcsRoot, "1:1db20368ddc7");
+
+    File unknownFile = createUnknownFile(myWorkDir);
+    File ignoredFile = createIgnoredFile(myWorkDir);
+
+    checkout(vcsRoot, "1:1db20368ddc7");
+
+    assertFalse(unknownFile.exists());
+    assertTrue(ignoredFile.exists());
+  }
+
+
+  public void purge_all() throws Exception {
+    VcsRoot vcsRoot = vcsRoot().withUrl(myRemoteRepository).withPurgePolicy(HgVcsRoot.PurgePolicy.PURGE_ALL).build();
+    checkout(vcsRoot, "1:1db20368ddc7");
+
+    File unknownFile = createUnknownFile(myWorkDir);
+    File ignoredFile = createIgnoredFile(myWorkDir);
+
+    checkout(vcsRoot, "1:1db20368ddc7");
+
+    assertFalse(unknownFile.exists());
+    assertFalse(ignoredFile.exists());
+  }
+
+
+  public void purge_unknown_subrepos() throws Exception {
+    VcsRoot vcsRoot = vcsRoot().withUrl(myRemoteRepository).withPurgePolicy(HgVcsRoot.PurgePolicy.PURGE_UNKNOWN).build();
+    checkout(vcsRoot, "2:47e24ed2a4a9");
+
+    File unknownFile = createUnknownFile(myWorkDir);
+    File ignoredFile = createIgnoredFile(myWorkDir);
+    File unknownFileSubrepo = createUnknownFile(new File(myWorkDir, "self"));
+    File ignoredFileSubrepo = createIgnoredFile(new File(myWorkDir, "self"));
+
+    checkout(vcsRoot, "2:47e24ed2a4a9");
+
+    assertFalse(unknownFile.exists());
+    assertFalse(unknownFileSubrepo.exists());
+    assertTrue(ignoredFile.exists());
+    assertTrue(ignoredFileSubrepo.exists());
+  }
+
+
+  public void purge_all_subrepos() throws Exception {
+    VcsRoot vcsRoot = vcsRoot().withUrl(myRemoteRepository).withPurgePolicy(HgVcsRoot.PurgePolicy.PURGE_ALL).build();
+    checkout(vcsRoot, "2:47e24ed2a4a9");
+
+    File unknownFile = createUnknownFile(myWorkDir);
+    File ignoredFile = createIgnoredFile(myWorkDir);
+    File unknownFileSubrepo = createUnknownFile(new File(myWorkDir, "self"));
+    File ignoredFileSubrepo = createIgnoredFile(new File(myWorkDir, "self"));
+
+    checkout(vcsRoot, "2:47e24ed2a4a9");
+
+    assertFalse(unknownFile.exists());
+    assertFalse(unknownFileSubrepo.exists());
+    assertFalse(ignoredFile.exists());
+    assertFalse(ignoredFileSubrepo.exists());
+  }
+
+
+  private File createUnknownFile(@NotNull File parentDir) throws IOException {
+    File unknownFile = new File(parentDir, "unknownFile");
+    FileUtil.writeFile(unknownFile, "some data", "UTF-8");
+    return unknownFile;
+  }
+
+
+  private File createIgnoredFile(@NotNull File parentDir) throws IOException {
+    File ignoredFile = new File(parentDir, "ignoredFile");
+    FileUtil.writeFile(ignoredFile, "some data", "UTF-8");
+    return ignoredFile;
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoChangesTest.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoChangesTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,10 +16,7 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CatCommand;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
 import jetbrains.buildServer.vcs.*;
 import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.BeforeMethod;
@@ -230,15 +227,11 @@
             .build();
 
     final AtomicInteger catCallCounter = new AtomicInteger(0);
-    RepoFactory repoFactory = new RepoFactory(pluginConfig, new TestCommandSettingsFactory(), myHgPathProvider) {
+    RepoFactory repoFactory = new RepoFactory(pluginConfig, new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), myHgPathProvider) {
       @NotNull
       @Override
-      public ServerHgRepo create(@NotNull File workingDir, @NotNull String hgPath, @NotNull AuthSettings authSettings) throws VcsException {
-        return new CountingServerHgRepo(myCommandSettingsFactory, myConfig, workingDir, hgPath, authSettings, catCallCounter)
-                .withLogTemplates(myLogTemplate.getTemplate(),
-                        myLogNoFilesTemplate.getTemplate(),
-                        myDagTemplate.getTemplate(),
-                        myFastLogTemplate.getTemplate());
+      protected ServerHgRepo create(@NotNull File workingDir, @NotNull String hgPath, @NotNull AuthSettings authSettings, @NotNull CommandSettingsFactory commandSettingsFactory, @NotNull ServerPluginConfig config) {
+        return new CountingServerHgRepo(commandSettingsFactory, config, workingDir, hgPath, authSettings, catCallCounter);
       }
     };
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,6 +16,8 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.SVcsRoot;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.jetbrains.annotations.NotNull;
@@ -30,6 +32,7 @@
  */
 public class VcsRootBuilder {
 
+  private String myExtensions;
   private String myRepository;
   private String myUsername;
   private String myPassword;
@@ -43,6 +46,7 @@
   private boolean myTagsAsBranches = false;
   private boolean myIncludeSubreposInPatch = true;
   private boolean myUseArchiveForPatch = false;
+  private HgVcsRoot.PurgePolicy myPurgePolicy;
 
   public static VcsRootBuilder vcsRoot() {
     return new VcsRootBuilder();
@@ -60,9 +64,12 @@
     vcsRoot.addProperty(Constants.DETECT_SUBREPO_CHANGES, String.valueOf(myDetectSubrepoChanges));
     vcsRoot.addProperty(Constants.INCLUDE_SUBREPOS_IN_PATCH, String.valueOf(myIncludeSubreposInPatch));
     vcsRoot.addProperty(Constants.USE_ARCHIVE_FOR_PATCH, String.valueOf(myUseArchiveForPatch));
+    vcsRoot.addProperty(Constants.HG_EXTENSIONS, myExtensions);
     if (myCloneRepositoryTo != null)
       vcsRoot.addProperty(Constants.SERVER_CLONE_PATH_PROP, String.valueOf(myCloneRepositoryTo.getAbsolutePath()));
     vcsRoot.addProperty(Constants.USE_TAGS_AS_BRANCHES, String.valueOf(myTagsAsBranches));
+    if (myPurgePolicy != null)
+      vcsRoot.addProperty(Constants.PURGE_POLICY, myPurgePolicy.name());
     return vcsRoot;
   }
 
@@ -82,6 +89,7 @@
       allowing(root).getProperty(with(Constants.USER_FOR_TAG)); will(returnValue(myUserForTag));
       allowing(root).getProperty(with(Constants.DETECT_SUBREPO_CHANGES)); will(returnValue(String.valueOf(myDetectSubrepoChanges)));
       allowing(root).getProperty(with(Constants.USE_TAGS_AS_BRANCHES)); will(returnValue(String.valueOf(myTagsAsBranches)));
+      allowing(root).getProperty(with(Constants.HG_EXTENSIONS)); will(returnValue(myExtensions));
     }});
     if (myCloneRepositoryTo != null) {
       context.checking(new Expectations() {{
@@ -97,6 +105,12 @@
     return this;
   }
 
+  @NotNull
+  public VcsRootBuilder withExtensions(@NotNull String... extensions) {
+    myExtensions = StringUtil.join(extensions, "\n");
+    return this;
+  }
+
 
   public VcsRootBuilder withLocalRepository(@NotNull final File repo) {
     return withUrl(repo.getPath()).withCloneRepositoryTo(repo.getParentFile());
@@ -173,4 +187,10 @@
     myTagsAsBranches = useTagsAsBranches;
     return this;
   }
+
+
+  public VcsRootBuilder withPurgePolicy(HgVcsRoot.PurgePolicy policy) {
+    myPurgePolicy = policy;
+    return this;
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ViaCMDInterceptor.java	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2014 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.TestCommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommand;
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created 03.06.2014 13:22
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public class ViaCMDInterceptor implements IMethodInterceptor {
+  public List<IMethodInstance> intercept(List<IMethodInstance> list, ITestContext iTestContext) {
+    if (!"true".equalsIgnoreCase(System.getProperty("teamcity.mercurial.use.commandline.via.file.wrapper"))) {
+      return list;
+    }
+
+    try {
+      final String path = Util.getHgPath();
+      final VersionCommand versionCommand = new VersionCommand(new TestCommandSettingsFactory().create(), path, new File(".."));
+      final HgVersion version = versionCommand.call();
+
+      if (!version.isEqualsOrGreaterThan(new HgVersion(2,5,0))) {
+        System.out.println("!!! Mercurial version is too old: " + version + "  @ " + path);
+        return Collections.emptyList();
+      }
+
+      return list;
+    } catch (Throwable t) {
+      System.out.println("Invalid mercurial: " + t.getMessage());
+      return Collections.emptyList();
+    }
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTest.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,7 +16,6 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.openapi.util.SystemInfo;
 import junit.framework.TestCase;
 import org.testng.annotations.Test;
@@ -33,13 +32,15 @@
   public void should_quote_command_line_arguments() throws IOException {
     File workingDir = new File("some dir");
     BaseCommand command = new BaseCommand(new CommandSettings(), "/path/to/hg", workingDir);
-    GeneralCommandLine cl = command.createCommandLine();
+    MercurialCommandLine cl = command.createCommandLine();
     cl.addParameter("param with spaces");
     cl.addParameter("param with quote \" rm -rf /");
     if (SystemInfo.isWindows) {
       assertTrue(cl.getCommandLineString().endsWith(" \"param with spaces\" \"param with quote \\\" rm -rf /\""));
+      assertTrue(cl.toGeneralCommandLine().getCommandLineString().endsWith(" \"param with spaces\" \"param with quote \\\" rm -rf /\""));
     } else {
       assertTrue(cl.getCommandLineString().endsWith(" param with spaces param with quote \" rm -rf /"));
+      assertTrue(cl.toGeneralCommandLine().getCommandLineString().endsWith(" param with spaces param with quote \" rm -rf /"));
     }
   }
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommandTest.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommandTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -80,11 +80,12 @@
     });
   }
 
+  @NotNull
   private File runCat(@NotNull final List<String> paths) throws IOException, VcsException {
     return runCommand(new CommandExecutor<File>() {
       public File execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException {
-        CatCommand cat = new CatCommand(new CommandSettings(), hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings());
-        return cat.execute(paths);
+        final CatCommand cat = new CatCommand(new CommandSettings(), hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings());
+        return cat.files(paths).checkForFailure(true).call();
       }
     });
   }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Wed Jun 18 12:14:56 2014 +0200
@@ -17,8 +17,8 @@
 
 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.buildTriggers.vcs.mercurial.MercurialClasspathTemplate;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialTemplate;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.AfterMethod;
@@ -35,7 +35,7 @@
 public class LogCommandTest extends BaseCommandTestCase {
 
   private TempFiles myTempFiles = new TempFiles();
-  private File myTemplateFile;
+  private MercurialTemplate myTemplateFile;
 
 
   @BeforeMethod
@@ -43,8 +43,7 @@
   protected void setUp() throws Exception {
     super.setUp();
     setRepository("mercurial-tests/testData/rep1", true);
-    myTemplateFile = myTempFiles.createTempFile();
-    FileUtil.copyResource(MercurialVcsSupport.class, "/buildServerResources/log.template", myTemplateFile);
+    myTemplateFile = new MercurialClasspathTemplate("/buildServerResources/log.template", "log.template") {};
   }
 
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TestCommandSettingsFactory.java	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TestCommandSettingsFactory.java	Wed Jun 18 12:14:56 2014 +0200
@@ -16,8 +16,11 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import org.jetbrains.annotations.NotNull;
+
 public class TestCommandSettingsFactory implements CommandSettingsFactory {
 
+  @NotNull
   public CommandSettings create() {
     return new CommandSettings().addHgEnv("HGRCPATH", "");
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/testng-via-cmd.xml	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,46 @@
+<!--
+  ~ Copyright 2000-2014 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.
+  -->
+
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="Mercurial Suite">
+  <listeners>
+    <listener class-name="jetbrains.buildServer.buildTriggers.vcs.mercurial.ViaCMDInterceptor"/>
+  </listeners>
+
+  <test name="Mercurial via commands file tests">
+    <classes>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CatCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CloneCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.StatusCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.PushCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.IdentifyCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupportTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommandTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.DagFeaturesTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.UnrelatedResitoriesTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.ListFilesSupportTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SubrepoChangesTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SubrepoPatchTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MergeSupportTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.TagsTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommitsInfoBuilderSupportTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialModificationInfoBuilderTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialUrlSupportTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.PurgeTest"/>
+    </classes>
+  </test>
+</suite>
--- a/mercurial-tests/src/testng.xml	Tue Jun 17 22:30:00 2014 +0200
+++ b/mercurial-tests/src/testng.xml	Wed Jun 18 12:14:56 2014 +0200
@@ -54,6 +54,7 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialModificationInfoBuilderTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialUrlSupportTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommitsAndMountPointsCommandParserTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.PurgeTest"/>
     </classes>
   </test>
 </suite>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/testPurge/README	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,5 @@
+o  changeset:   2:47e24ed2a4a9 //add same repo as a subrepo at path self
+|
+o  changeset:   1:1db20368ddc7 //ignores ignoredFile
+|
+o  changeset:   0:acefc1a0763c //contains file a
Binary file mercurial-tests/testData/testPurge/hg/00changelog.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/testPurge/hg/cache/tags	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,2 @@
+2 47e24ed2a4a91d036e740d4a8bdcd7fd156ed3dc
+
Binary file mercurial-tests/testData/testPurge/hg/dirstate has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/testPurge/hg/last-message.txt	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,1 @@
+add same repo as a subrepo at path 'self'
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/testPurge/hg/requires	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,3 @@
+revlogv1
+store
+fncache
Binary file mercurial-tests/testData/testPurge/hg/store/00changelog.i has changed
Binary file mercurial-tests/testData/testPurge/hg/store/00manifest.i has changed
Binary file mercurial-tests/testData/testPurge/hg/store/data/.hgignore.i has changed
Binary file mercurial-tests/testData/testPurge/hg/store/data/.hgsub.i has changed
Binary file mercurial-tests/testData/testPurge/hg/store/data/.hgsubstate.i has changed
Binary file mercurial-tests/testData/testPurge/hg/store/data/a.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/testPurge/hg/store/fncache	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,4 @@
+data/a.i
+data/.hgignore.i
+data/.hgsub.i
+data/.hgsubstate.i
Binary file mercurial-tests/testData/testPurge/hg/store/undo has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/testPurge/hg/undo.branch	Wed Jun 18 12:14:56 2014 +0200
@@ -0,0 +1,1 @@
+default
\ No newline at end of file
Binary file mercurial-tests/testData/testPurge/hg/undo.dirstate has changed