changeset 304:e9cdb499350d remote-run/subrepos

Do clean checkout if subrepository URL changed Hg cannot do update correctly in this situation. The only thing that helps is a clean checkout.
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Fri, 09 Sep 2011 11:47:59 +0400
parents 7b6ea35e1470
children 844986ea478d 0f910b1f87e2
files mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java mercurial-tests/src/testng.xml
diffstat 10 files changed, 321 insertions(+), 125 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Fri Sep 09 11:47:59 2011 +0400
@@ -15,6 +15,7 @@
  */
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import com.intellij.openapi.util.Pair;
 import jetbrains.buildServer.agent.AgentRunningBuild;
 import jetbrains.buildServer.agent.BuildAgentConfiguration;
 import jetbrains.buildServer.agent.BuildProgressLogger;
@@ -32,7 +33,7 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Collections;
+import java.util.*;
 
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData;
 
@@ -48,30 +49,7 @@
   }
 
   public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final AgentRunningBuild build, boolean cleanCheckoutRequested) throws VcsException {
-    final BuildProgressLogger logger = build.getBuildLogger();
-    final boolean useLocalMirrors = isUseLocalMirrors(build);
-
-    return new IncludeRuleUpdater() {
-      public void process(@NotNull final IncludeRule includeRule, @NotNull final File workingDir) throws VcsException {
-        try {
-          checkRuleIsValid(includeRule);
-          Settings settings = new Settings(myHgPathProvider, vcsRoot);
-          if (useLocalMirrors) {
-            updateLocalMirror(vcsRoot, logger);
-          }
-          updateRepository(workingDir, settings, logger, useLocalMirrors);
-          updateWorkingDir(settings, workingDir, toVersion, logger);
-        } catch (Exception e) {
-          if (e instanceof VcsException)
-            throw (VcsException) e;
-          else
-            throw new VcsException(e);
-        }
-      }
-
-      public void dispose() throws VcsException {
-      }
-    };
+    return new MercurialIncludeRuleUpdater(myMirrorManager, myHgPathProvider, vcsRoot, toVersion, build);
   }
 
   @NotNull
@@ -85,95 +63,4 @@
   public UpdatePolicy getUpdatePolicy() {
     return this;
   }
-
-  private boolean isUseLocalMirrors(AgentRunningBuild build) {
-    String value = build.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors");
-    return "true".equals(value);
-  }
-
-  private void initRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException {
-    try {
-      String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors);
-      logger.message("Init repository at " + workingDir.getAbsolutePath() + ", remote repository is " +
-              removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
-      new Init(settings, workingDir, defaultPullUrl).execute();
-    } catch (IOException e) {
-      throw new VcsException("Error while initializing repository at " + workingDir.getAbsolutePath(), e);
-    }
-  }
-
-  private void updateRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException, IOException {
-    if (!Settings.isValidRepository(workingDir)) {
-      initRepository(workingDir, settings, logger, useLocalMirrors);
-    } else {
-      ensureUseRightRepository(workingDir, settings, logger, useLocalMirrors);
-    }
-    String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors);
-    logger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
-    new PullCommand(settings, workingDir).execute();
-    logger.message("Changes successfully pulled");
-  }
-
-  private void ensureUseRightRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException {
-    boolean clonedFromWrongRepository = useLocalMirrors && !isClonedFromLocalMirror(settings, workingDir)
-                                     || !useLocalMirrors && isClonedFromLocalMirror(settings, workingDir);
-
-    if (clonedFromWrongRepository) {
-      String rightRepository = useLocalMirrors ? "local mirror" : "remote repository";
-      String wrongRepository = useLocalMirrors ? "remote repository" : "local mirror";
-      logger.message("Repository in working directory is cloned from " + wrongRepository + ", clone it from " + rightRepository);
-      FileUtil.delete(workingDir);
-      initRepository(workingDir, settings, logger, useLocalMirrors);
-    }
-  }
-
-  private void updateLocalMirror(VcsRoot root, BuildProgressLogger logger) throws VcsException, IOException {
-    Settings settings = new Settings(myHgPathProvider, root);
-    File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
-    logger.message("Update local mirror at " + mirrorDir);
-    if (!Settings.isValidRepository(mirrorDir)) {
-      initRepository(mirrorDir, settings, logger, false);
-    }
-    final String defaultPullUrl = getDefaultPullUrl(settings, true);
-    logger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
-    new PullCommand(settings, mirrorDir).execute();
-    logger.message("Local mirror changes successfully pulled");
-  }
-
-  private void updateWorkingDir(final Settings settings, File workingDir, @NotNull final String version, final BuildProgressLogger logger) throws VcsException {
-    logger.message("Updating folder " + workingDir.getAbsolutePath() + " to revision " + version);
-    UpdateCommand uc = new UpdateCommand(settings, workingDir);
-    ChangeSet cs = new ChangeSet(version);
-    uc.setToId(cs.getId());
-    uc.execute();
-    logger.message("Folder successfully updated");
-  }
-
-  private String getDefaultPullUrl(Settings settings, boolean useLocalMirror) throws IOException {
-    if (useLocalMirror) {
-      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
-      return mirrorDir.getCanonicalPath();
-    } else {
-      return settings.getRepositoryUrl();
-    }
-  }
-
-  private void checkRuleIsValid(IncludeRule includeRule) throws VcsException {
-    if (includeRule.getTo() != null && includeRule.getTo().length() > 0) {
-      if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) {
-        throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only.");
-      }
-    }
-  }
-
-  public boolean isClonedFromLocalMirror(Settings settings, File workingDir) {
-    try {
-      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
-      File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
-      String config = FileUtil.readText(hgrc);
-      return config.contains("default = " + mirrorDir.getCanonicalPath());
-    } catch (Exception e) {
-      return false;
-    }
-  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Fri Sep 09 11:47:59 2011 +0400
@@ -0,0 +1,292 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.util.Pair;
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildProgressLogger;
+import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.IncludeRule;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData;
+
+/**
+ * @author dmitry.neverov
+ */
+public class MercurialIncludeRuleUpdater implements IncludeRuleUpdater {
+
+  private final MirrorManager myMirrorManager;
+  private final HgPathProvider myHgPathProvider;
+  private final VcsRoot myRoot;
+  private final Settings mySettings;
+  private final String myToVersion;
+  private final BuildProgressLogger myLogger;
+  private final boolean myUseLocalMirrors;
+
+
+  public MercurialIncludeRuleUpdater(@NotNull final MirrorManager mirrorManager,
+                                     @NotNull final HgPathProvider hgPathProvider,
+                                     @NotNull final VcsRoot root,
+                                     @NotNull final String toVersion,
+                                     @NotNull final AgentRunningBuild build) {
+    myMirrorManager = mirrorManager;
+    myHgPathProvider = hgPathProvider;
+    myRoot = root;
+    mySettings = new Settings(myHgPathProvider, myRoot);
+    myToVersion = toVersion;
+    myLogger = build.getBuildLogger();
+    myUseLocalMirrors = isUseLocalMirrors(build);
+  }
+
+
+  public void process(@NotNull IncludeRule rule, @NotNull File workingDir) throws VcsException {
+    try {
+      checkRuleIsValid(rule);
+      if (myUseLocalMirrors)
+        updateLocalMirror();
+      updateRepository(workingDir);
+      updateWorkingDir(workingDir);
+    } catch (Exception e) {
+      throwVcsException(e);
+    }
+  }
+
+
+  public void dispose() throws VcsException {
+  }
+
+
+  private void throwVcsException(Exception e) throws VcsException {
+    if (e instanceof VcsException)
+      throw (VcsException) e;
+    else
+      throw new VcsException(e);
+  }
+
+
+  private boolean isUseLocalMirrors(AgentRunningBuild build) {
+    String value = build.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors");
+    return "true".equals(value);
+  }
+
+
+  private void initRepository(Settings settings, File workingDir, boolean useLocalMirrors) throws VcsException {
+    try {
+      String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors);
+      myLogger.message("Init repository at " + workingDir.getAbsolutePath() + ", remote repository is " +
+              removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
+      new Init(settings, workingDir, defaultPullUrl).execute();
+    } catch (IOException e) {
+      throw new VcsException("Error while initializing repository at " + workingDir.getAbsolutePath(), e);
+    }
+  }
+
+
+  private void updateRepository(File workingDir) throws VcsException, IOException {
+    if (!Settings.isValidRepository(workingDir)) {
+      initRepository(mySettings, workingDir, myUseLocalMirrors);
+    } else {
+      ensureUseRightRepository(workingDir);
+    }
+    String defaultPullUrl = getDefaultPullUrl(mySettings, myUseLocalMirrors);
+    myLogger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(mySettings.getPassword())));
+    new PullCommand(mySettings, workingDir).execute();
+    myLogger.message("Changes successfully pulled");
+  }
+
+
+  private void ensureUseRightRepository(File workingDir) throws VcsException {
+    boolean clonedFromWrongRepository = myUseLocalMirrors && !isClonedFromLocalMirror(workingDir)
+                                     || !myUseLocalMirrors && isClonedFromLocalMirror(workingDir);
+
+    if (clonedFromWrongRepository) {
+      String rightRepository = myUseLocalMirrors ? "local mirror" : "remote repository";
+      String wrongRepository = myUseLocalMirrors ? "remote repository" : "local mirror";
+      myLogger.message("Repository in working directory is cloned from " + wrongRepository + ", clone it from " + rightRepository);
+      FileUtil.delete(workingDir);
+      initRepository(mySettings, workingDir, myUseLocalMirrors);
+    }
+  }
+
+
+  private void updateLocalMirror() throws VcsException, IOException {
+    Settings settings = new Settings(myHgPathProvider, myRoot);
+    File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
+    myLogger.message("Update local mirror at " + mirrorDir);
+    if (!Settings.isValidRepository(mirrorDir)) {
+      initRepository(settings, mirrorDir, false);
+    }
+    final String defaultPullUrl = getDefaultPullUrl(settings, true);
+    myLogger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
+    new PullCommand(settings, mirrorDir).execute();
+    myLogger.message("Local mirror changes successfully pulled");
+  }
+
+
+  private void updateWorkingDir(@NotNull final File workingDir) throws VcsException, IOException {
+    String workingDirRevision = getWorkingDirRevision(mySettings, workingDir);
+    if (isInitialClone(workingDirRevision)) {
+      doUpdateWorkingDir(workingDir);
+    } else {
+      Map<String, Pair<String, String>> currentSubrepos = getSubrepositories(workingDir, workingDirRevision);
+      if (currentSubrepos.isEmpty()) {
+        doUpdateWorkingDir(workingDir);
+      } else {
+        Map<String, Pair<String, String>> toVersionSubrepos = getSubrepositories(workingDir, myToVersion);
+        Map<String, Pair<String, String>> subrepositoriesWithChangedUrls = getSubrepositoriesWithChangedUrls(currentSubrepos, toVersionSubrepos);
+        if (subrepositoriesWithChangedUrls.isEmpty()) {
+          doUpdateWorkingDir(workingDir);
+        } else {
+          myLogger.warning("URLs of subrepositories were changed, do clean checkout");
+          FileUtil.delete(workingDir);
+          updateRepository(workingDir);
+          doUpdateWorkingDir(workingDir);
+        }
+      }
+    }
+  }
+
+
+  /*returns map: relative path -> (repository url, repository revision)*/
+  private Map<String, Pair<String, String>> getSubrepositories(@NotNull final File workingDir, @NotNull final String revision) throws VcsException, IOException {
+    CatCommand cc = new CatCommand(mySettings, workingDir);
+    cc.setRevId(revision);
+    try {
+      File parentDir = cc.execute(Arrays.asList(".hgsub", ".hgsubstate"), false);
+      File hgsub = new File(parentDir, ".hgsub");
+      File hgsubstate = new File(parentDir, ".hgsubstate");
+      return readSubrepositories(hgsub, hgsubstate);
+    } catch (VcsException e) {
+      return Collections.emptyMap();
+    }
+  }
+
+
+  private Map<String, Pair<String, String>> readSubrepositories(@NotNull final File hgsub, @NotNull final File hgsubstate) throws IOException {
+    if (hgsub.exists() && hgsubstate.exists()) {
+      Map<String, String> path2repo = readHgsub(hgsub);
+      Map<String, String> path2revision = readHgsubstate(hgsubstate);
+      Map<String, Pair<String, String>> result = new HashMap<String, Pair<String, String>>();
+      for (Map.Entry<String, String> entry : path2repo.entrySet()) {
+        String path = entry.getKey();
+        String repo = entry.getValue();
+        String revision = path2revision.get(path);
+        if (revision != null) {
+          result.put(path, Pair.create(repo, revision));
+        } else {
+          myLogger.warning("Cannot find revision for subrepository at path " + path + " skip it");
+        }
+      }
+      return result;
+    } else {
+      return Collections.emptyMap();
+    }
+  }
+
+
+  /*returns map: relative path -> repository url */
+  private Map<String, String> readHgsub(@NotNull final File hgsub) throws IOException {
+    Map<String, String> result = new HashMap<String, String>();
+    for (String line : FileUtil.readFile(hgsub)) {
+      String[] parts = line.split(" = ");
+      if (parts.length == 2) {
+        result.put(parts[0], parts[1]);
+      } else {
+        myLogger.warning("Cannot parse the line '" + line + "' from .hgsub, skip it");
+      }
+    }
+    return result;
+  }
+
+
+  /*returns map: relative path -> revision */
+  private Map<String, String> readHgsubstate(@NotNull final File hgsubstate) throws IOException {
+    Map<String, String> result = new HashMap<String, String>();
+    for (String line : FileUtil.readFile(hgsubstate)) {
+      String[] parts = line.split(" ");
+      if (parts.length == 2) {
+        result.put(parts[1], parts[0]);
+      } else {
+        myLogger.warning("Cannot parse the line '" + line + "' from .hgsubstate, skip it");
+      }
+    }
+    return result;
+  }
+
+
+  /*returns map repository path -> (old url, new url)*/
+  private Map<String, Pair<String, String>> getSubrepositoriesWithChangedUrls(@NotNull final Map<String, Pair<String, String>> subrepos1, @NotNull final Map<String, Pair<String, String>> subrepos2) {
+    Map<String, Pair<String, String>> result = new HashMap<String, Pair<String, String>>();
+    for (Map.Entry<String, Pair<String, String>> entry : subrepos1.entrySet()) {
+      String path = entry.getKey();
+      String url1 = entry.getValue().first;
+      Pair<String, String> urlRevision = subrepos2.get(path);
+      if (urlRevision != null && !url1.equals(urlRevision.first))
+        result.put(path, Pair.create(url1, urlRevision.first));
+    }
+    return result;
+  }
+
+
+  private boolean isInitialClone(@NotNull final String workingDirRevision) {
+    return "000000000000".equals(workingDirRevision);
+  }
+
+
+  private String getWorkingDirRevision(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
+    IdentifyCommand id = new IdentifyCommand(settings, workingDir);
+    id.setInLocalRepository(true);
+    return id.execute();
+  }
+
+
+  private void doUpdateWorkingDir(@NotNull final File workingDir) throws VcsException {
+    myLogger.message("Updating folder " + workingDir.getAbsolutePath() + " to revision " + myToVersion);
+    UpdateCommand uc = new UpdateCommand(mySettings, workingDir);
+    ChangeSet cs = new ChangeSet(myToVersion);
+    uc.setToId(cs.getId());
+    uc.execute();
+    myLogger.message("Folder successfully updated");
+  }
+
+
+  private String getDefaultPullUrl(Settings settings, boolean useLocalMirror) throws IOException {
+    if (useLocalMirror) {
+      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
+      return mirrorDir.getCanonicalPath();
+    } else {
+      return settings.getRepositoryUrl();
+    }
+  }
+
+
+  private void checkRuleIsValid(IncludeRule includeRule) throws VcsException {
+    if (includeRule.getTo() != null && includeRule.getTo().length() > 0) {
+      if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) {
+        throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only.");
+      }
+    }
+  }
+
+
+  public boolean isClonedFromLocalMirror(@NotNull final File workingDir) {
+    try {
+      File mirrorDir = myMirrorManager.getMirrorDir(mySettings.getRepositoryUrl());
+      File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
+      String config = FileUtil.readText(hgrc);
+      return config.contains("default = " + mirrorDir.getCanonicalPath());
+    } catch (Exception e) {
+      return false;
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Fri Sep 09 11:47:59 2011 +0400
@@ -39,6 +39,10 @@
   }
 
   public File execute(List<String> relPaths) throws VcsException {
+    return execute(relPaths, true);
+  }
+
+  public File execute(List<String> relPaths, boolean checkFailure) throws VcsException {
     File tempDir;
     try {
       tempDir = FileUtil.createTempDirectory("mercurial", "catresult");
@@ -63,7 +67,7 @@
         cmdSize += path.length();
       } while (cmdSize < MAX_CMD_LEN && !paths.isEmpty());
 
-      runCommand(cli);
+      runCommand(cli, checkFailure);
     }
 
     return tempDir;
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Fri Sep 09 11:47:59 2011 +0400
@@ -86,6 +86,14 @@
   }
 
   public static ExecResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData) throws VcsException {
+    return runCommand(cli, executionTimeout, privateData, true);
+  }
+
+  public static ExecResult runCommand(@NotNull GeneralCommandLine cli, @NotNull Set<String> privateData, final boolean checkFailure) throws VcsException {
+    return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, privateData, checkFailure);
+  }
+
+  public static ExecResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData, final boolean checkFailure) throws VcsException {
     final String cmdStr = removePrivateData(cli.getCommandLineString(), privateData);
     Loggers.VCS.debug("Run command: " + cmdStr);
     final long start = System.currentTimeMillis();
@@ -103,7 +111,8 @@
 
     removePrivateData(privateData, res);
 
-    CommandUtil.checkCommandFailed(cmdStr, res);
+    if (checkFailure)
+      CommandUtil.checkCommandFailed(cmdStr, res);
     Loggers.VCS.debug("Command " + cmdStr + " output:\n" + res.getStdout());
     return res;
   }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Fri Sep 09 11:47:59 2011 +0400
@@ -65,6 +65,7 @@
     }
     ExecResult res = runCommand(cli);
     failIfNotEmptyStdErr(cli, res);
-    return res.getStdout();
+    String output = res.getStdout().trim();
+    return output.contains(" ") ? output.substring(0, output.indexOf(" ")) : output;
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Fri Sep 09 11:47:59 2011 +0400
@@ -228,11 +228,6 @@
     IdentifyCommand identify = new IdentifyCommand(getSettings(), getWorkDirectory());
     identify.setInLocalRepository(true);
     identify.setRevisionNumber(revNumber);
-    String output = identify.execute().trim();
-    if (output.contains(" ")) {
-      return output.substring(0, output.indexOf(" "));
-    } else {
-      return output;
-    }
+    return identify.execute();
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java	Fri Sep 09 11:47:59 2011 +0400
@@ -41,4 +41,9 @@
   protected ExecResult runCommand(@NotNull GeneralCommandLine cli, int executionTimeout) throws VcsException {
     return CommandUtil.runCommand(cli, executionTimeout, getPrivateData());
   }
+
+
+  protected ExecResult runCommand(@NotNull GeneralCommandLine cli, boolean checkFailure) throws VcsException {
+    return CommandUtil.runCommand(cli, getPrivateData(), checkFailure);
+  }
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Fri Sep 09 11:47:59 2011 +0400
@@ -449,6 +449,7 @@
   /**
    * Check if changeSet is present in local repository.
    * @param settings root settings
+   * @param workDir where to run a command
    * @param cset change set of interest
    * @return true if changeSet is present in local repository
    */
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Fri Sep 09 11:47:59 2011 +0400
@@ -60,6 +60,7 @@
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
       allowing(myLogger).message(with(any(String.class)));
+      allowing(myLogger).warning(with(any(String.class)));
     }});
 
     myR1Dir = copy(new File("mercurial-tests/testData/subrepos/r1"));
--- a/mercurial-tests/src/testng.xml	Thu Sep 08 18:32:27 2011 +0400
+++ b/mercurial-tests/src/testng.xml	Fri Sep 09 11:47:59 2011 +0400
@@ -10,6 +10,7 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.IdentifyCommandTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupportTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentSideCheckoutTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentSideCheckoutWithSubreposTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SettingsTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersionTest"/>