changeset 349:e0464f11206c

TW-19698 Handle unrelated repositories When repository becames unrelated - clone it in different directory on the server. When changes are collected and any of revisions is from unrelated repository - return empty changes collection.
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Thu, 12 Jan 2012 18:21:07 +0400
parents fd56b9524834
children ef217e6078b9
files mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.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/PullCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnknownRevisionException.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnrelatedRepositoryException.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java mercurial-server/src/META-INF/build-server-plugin-mercurial.xml mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandFactory.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandFactoryImpl.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseNoRevsets.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseWithRevsets.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java mercurial-tests/src/testng.xml
diffstat 22 files changed, 465 insertions(+), 149 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java	Thu Jan 12 18:21:07 2012 +0400
@@ -7,13 +7,12 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+import static jetbrains.buildServer.util.FileUtil.isEmptyDir;
+
 /**
  * Manages local mirrors of remote repositories.
  * Each unique url get unique local mirror. Each mirror is used for one url only.
@@ -73,6 +72,42 @@
   }
 
 
+  /**
+   * Forget specified dir. After call to this method with non-empty dir,
+   * all urls which were mapped to this dir will be mapped to another.
+   * If dir is empty, subsequent call getMirrorDir(dir) will return the
+   * same dir.
+   *
+   * @param dir dir of interest
+   */
+  public void forgetDir(@NotNull final File dir) {
+    myLock.writeLock().lock();
+    try {
+      removeMappingsToDir(dir);
+      saveMappingToFile();
+    } finally {
+      myLock.writeLock().unlock();
+    }
+  }
+
+  private void removeMappingsToDir(@NotNull final File dir) {
+    Set<String> keysToRemove = getUrlsMappedToDir(dir);
+    for (String key : keysToRemove) {
+      myMirrors.remove(key);
+    }
+  }
+
+  private Set<String> getUrlsMappedToDir(@NotNull final File dir) {
+    Set<String> urlsMappedToDir = new HashSet<String>();
+    for (Map.Entry<String, File> entry : myMirrors.entrySet()) {
+      File f = entry.getValue();
+      if (f.equals(dir))
+        urlsMappedToDir.add(entry.getKey());
+    }
+    return urlsMappedToDir;
+  }
+
+
   //for tests only
   void setHashCalculator(HashCalculator hash) {
     myHash = hash;
@@ -123,7 +158,7 @@
     try {
       String dirName = MIRROR_DIR_PREFIX + hash(normalize(url));
       File result = PathUtil.getCanonicalFile(new File(myRootDir, dirName));
-      while (isUsedForOtherUrl(result, url)) {
+      while (isUsedForOtherUrl(result, url) || !isEmptyDir(result)) {
         dirName = MIRROR_DIR_PREFIX + hash(result.getName());
         result = PathUtil.getCanonicalFile(new File(myRootDir, dirName));
       }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Thu Jan 12 18:21:07 2012 +0400
@@ -46,7 +46,7 @@
     setDestination(cli);
 
     CommandResult res = runCommand(cli);
-    failIfNotEmptyStdErr(cli, res);
+    failIfNotEmptyStdErr(res);
     deleteHgArchival();
   }
 
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Thu Jan 12 18:21:07 2012 +0400
@@ -104,9 +104,9 @@
     return CommandUtil.runCommand(cli, executionTimeout, Collections.<String>emptySet());
   }
 
-  protected void failIfNotEmptyStdErr(@NotNull GeneralCommandLine cli, @NotNull CommandResult res) throws VcsException {
+  protected void failIfNotEmptyStdErr(@NotNull CommandResult res) throws VcsException {
     if (!StringUtil.isEmpty(res.getStderr())) {
-      CommandUtil.commandFailed(cli.getCommandLineString(), res);
+      CommandUtil.commandFailed(res);
     }
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Thu Jan 12 18:21:07 2012 +0400
@@ -1,9 +1,11 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import jetbrains.buildServer.ExecResult;
+import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Collections;
 import java.util.Set;
 
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData;
@@ -15,10 +17,16 @@
  */
 public class CommandResult {
 
+  private final String myCommand;
   private final ExecResult myDelegate;
   private final Set<String> myPrivateData;
 
-  public CommandResult(@NotNull final ExecResult execResult, @NotNull final Set<String> privateData) {
+  public CommandResult(@NotNull final String command, @NotNull final ExecResult execResult) {
+    this(command, execResult, Collections.<String>emptySet());
+  }
+
+  public CommandResult(@NotNull final String command, @NotNull final ExecResult execResult, @NotNull final Set<String> privateData) {
+    myCommand = command;
     myDelegate = execResult;
     myPrivateData = privateData;
   }
@@ -41,4 +49,54 @@
   public int getExitCode() {
     return myDelegate.getExitCode();
   }
+
+  @NotNull
+  public String getCommand() {
+    return myCommand;
+  }
+
+  public boolean isFailure() {
+    return getExitCode() != 0 || getException() != null;
+  }
+
+  public boolean hasImportantException() {
+    Throwable exception = getException();
+    return exception instanceof NullPointerException;
+  }
+
+  @Nullable
+  public String getExceptionMessage() {
+    Throwable exception = getException();
+    if (exception == null)
+      return null;
+    String message = exception.getMessage();
+    if (message == null)
+      message = exception.getClass().getName();
+    return message;
+  }
+
+  public void rethrowDetectedError() throws VcsException {
+    if (!isFailure())
+      return;
+    String stderr = getStderr();
+    checkUnrelatedRepository(stderr);
+    checkUnknownRevision(stderr);
+  }
+
+  private void checkUnrelatedRepository(@NotNull final String stderr) throws UnrelatedRepositoryException {
+    if (stderr.contains("abort: repository is unrelated"))
+      throw new UnrelatedRepositoryException();
+  }
+
+  private void checkUnknownRevision(@NotNull final String stderr) throws UnknownRevisionException {
+    final String message = "abort: unknown revision '";
+    int idx = stderr.indexOf(message);
+    if (idx != -1) {
+      int startIdx = idx + message.length();
+      int endIdx = stderr.indexOf("'", startIdx);
+      String revision = stderr.substring(startIdx, endIdx);
+      throw new UnknownRevisionException(revision);
+    }
+  }
+
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Thu Jan 12 18:21:07 2012 +0400
@@ -22,7 +22,6 @@
 import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import java.util.Collections;
 import java.util.Set;
@@ -30,55 +29,34 @@
 public class CommandUtil {
   private static final int DEFAULT_COMMAND_TIMEOUT_SEC = 3600;
 
-  public static void checkCommandFailed(@NotNull String cmdName, @NotNull CommandResult res) throws VcsException {
-    if (res.getExitCode() != 0 || res.getException() != null)
-      commandFailed(cmdName, res);
+  public static void checkCommandFailed(@NotNull final CommandResult res) throws VcsException {
+    res.rethrowDetectedError();
+    if (res.isFailure())
+      commandFailed(res);
     if (res.getStderr().length() > 0) {
-      Loggers.VCS.warn("Error output produced by: " + cmdName);
+      Loggers.VCS.warn("Error output produced by: " + res.getCommand());
       Loggers.VCS.warn(res.getStderr());
     }
   }
 
-  public static void commandFailed(final String cmdName, final CommandResult res) throws VcsException {
-    final String message = createCommandLogMessage(cmdName, res);
+  public static void commandFailed(@NotNull final CommandResult res) throws VcsException {
+    final String message = createCommandLogMessage(res);
     Loggers.VCS.warn(message);
-    if (hasImportantException(res))
-      Loggers.VCS.error("Error during executing '" + cmdName + "'", res.getException());
+    if (res.hasImportantException())
+      Loggers.VCS.error("Error during executing '" + res.getCommand() + "'", res.getException());
     throw new VcsException(message);
   }
 
-  private static String createCommandLogMessage(final String cmdName, final CommandResult res) {
+  private static String createCommandLogMessage(@NotNull final CommandResult res) {
     String stderr = res.getStderr();
     String stdout = res.getStdout();
-    String exceptionMessage = getExceptionMessage(res);
-    return "'" + cmdName + "' command failed.\n" +
+    String exceptionMessage = res.getExceptionMessage();
+    return "'" + res.getCommand() + "' command failed.\n" +
             (!StringUtil.isEmpty(stdout) ? "stdout: " + stdout + "\n" : "") +
             (!StringUtil.isEmpty(stderr) ? "stderr: " + stderr + "\n" : "") +
             (exceptionMessage != null ? "exception: " + exceptionMessage : "");
   }
 
-  @Nullable
-  private static String getExceptionMessage(CommandResult result) {
-    Throwable exception = result.getException();
-    String message = null;
-    if (exception != null) {
-      message = exception.getMessage();
-      if (message == null) {
-        message = exception.getClass().getName();
-      }
-    }
-    return message;
-  }
-
-  private static boolean hasImportantException(CommandResult result) {
-    Throwable exception = result.getException();
-    if (exception != null) {
-      return exception instanceof NullPointerException;
-    } else {
-      return false;
-    }
-  }
-
   public static CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
     return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, Collections.<String>emptySet());
   }
@@ -100,7 +78,7 @@
     Loggers.VCS.debug("Run command: " + cmdStr);
     CommandResult res = run(cli, executionTimeout, cmdStr,privateData);
     if (checkFailure)
-      CommandUtil.checkCommandFailed(cmdStr, res);
+      CommandUtil.checkCommandFailed(res);
     Loggers.VCS.debug("Command " + cmdStr + " output:\n" + res.getStdout());
     return res;
   }
@@ -118,7 +96,7 @@
         Loggers.VCS.debug("Command " + cmdStr + " took " + duration + "ms");
       }
     });
-    return new CommandResult(res, privateData);
+    return new CommandResult(cmdStr, res, privateData);
   }
 
   public static String removePrivateData(final String str, final Set<String> privateData) {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Thu Jan 12 18:21:07 2012 +0400
@@ -63,7 +63,7 @@
       cli.addParameter(myRevisionNumber.toString());
     }
     CommandResult res = runCommand(cli);
-    failIfNotEmptyStdErr(cli, res);
+    failIfNotEmptyStdErr(res);
     String output = res.getStdout().trim();
     return output.contains(" ") ? output.substring(0, output.indexOf(" ")) : output;
   }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Thu Jan 12 18:21:07 2012 +0400
@@ -42,6 +42,7 @@
     GeneralCommandLine cli = createCommandLine();
     cli.addParameter("pull");
     cli.addParameter(myPullUrl);
-    runCommand(cli, timeout);
+    CommandResult result = CommandUtil.runCommand(cli, timeout, getPrivateData(), false);
+    CommandUtil.checkCommandFailed(result);
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java	Thu Jan 12 18:21:07 2012 +0400
@@ -43,6 +43,6 @@
     }
     cli.addParameter(getSettings().getRepositoryUrlWithCredentials());
     CommandResult res = runCommand(cli);
-    failIfNotEmptyStdErr(cli, res);
+    failIfNotEmptyStdErr(res);
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnknownRevisionException.java	Thu Jan 12 18:21:07 2012 +0400
@@ -0,0 +1,22 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public class UnknownRevisionException extends VcsException {
+
+  private final String myRevision;
+
+  public UnknownRevisionException(@NotNull final String revision) {
+    myRevision = revision;
+  }
+
+  @NotNull
+  public String getRevision() {
+    return myRevision;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnrelatedRepositoryException.java	Thu Jan 12 18:21:07 2012 +0400
@@ -0,0 +1,10 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+
+/**
+ * @author dmitry.neverov
+ */
+public class UnrelatedRepositoryException extends VcsException {
+
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java	Thu Jan 12 18:21:07 2012 +0400
@@ -41,6 +41,9 @@
     return CommandUtil.runCommand(cli, executionTimeout, getPrivateData());
   }
 
+  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, int executionTimeout, boolean checkFailure) throws VcsException {
+    return CommandUtil.runCommand(cli, executionTimeout, getPrivateData(), checkFailure);
+  }
 
   protected CommandResult runCommand(@NotNull GeneralCommandLine cli, boolean checkFailure) throws VcsException {
     return CommandUtil.runCommand(cli, getPrivateData(), checkFailure);
--- a/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Thu Jan 12 18:21:07 2012 +0400
@@ -4,6 +4,6 @@
 <beans default-autowire="constructor">
   <bean id="mercurialServer" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport" />
   <bean id="config" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigImpl" />
-  <bean id="commandFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommandFactory" />
+  <bean id="commandFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommandFactoryImpl" />
   <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgPathProvider"/>
 </beans>
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandFactory.java	Wed Jan 11 15:09:52 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.MergeBaseCommand;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommand;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * @author dmitry.neverov
- */
-public final class CommandFactory {
-
-  //hg version which supports revsets
-  private final static HgVersion REVSET_HG_VERSION = new HgVersion(1, 7, 0);
-  private final static String LOG_TEMPLATE_NAME = "log.template";
-
-  private final File myDefaultWorkingDir;
-  private final File myLogTemplate;
-
-
-  public CommandFactory(@NotNull final ServerPluginConfig config) throws IOException {
-    myDefaultWorkingDir = config.getCachesDir();
-    myLogTemplate = createLogTemplate(config.getPluginDataDir());
-  }
-
-
-  @NotNull
-  public MergeBaseCommand createMergeBase(@NotNull Settings settings, @NotNull File workingDir) throws VcsException {
-    HgVersion hgVersion = getHgVersion(settings);
-    if (hgVersion.isEqualsOrGreaterThan(REVSET_HG_VERSION))
-      return new MergeBaseWithRevsets(settings, workingDir, this);
-    else
-      return new MergeBaseNoRevsets(settings, workingDir, this);
-  }
-
-
-  @NotNull
-  public LogCommand createLog(@NotNull final Settings settings, @NotNull final File workingDir) {
-    return new LogCommand(settings, workingDir, myLogTemplate);
-  }
-
-  @NotNull
-  public CollectChangesCommand getCollectChangesCommand(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
-    HgVersion hgVersion = getHgVersion(settings);
-    if (hgVersion.isEqualsOrGreaterThan(REVSET_HG_VERSION)) {
-      return new CollectChangesWithRevsets(settings, workingDir, myLogTemplate);
-    } else {
-      return new CollectChangesNoRevsets(settings, workingDir, myLogTemplate);
-    }
-  }
-
-  private File createLogTemplate(@NotNull final File templateFileDir) throws IOException {
-    File template = new File(templateFileDir, LOG_TEMPLATE_NAME);
-    if (!template.exists()) {
-      FileUtil.copyResource(CommandFactory.class, "/buildServerResources/log.template", template);
-    }
-    return template;
-  }
-
-  private HgVersion getHgVersion(@NotNull final Settings settings) throws VcsException {
-    VersionCommand versionCommand = new VersionCommand(settings, myDefaultWorkingDir);
-    return versionCommand.execute();
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandFactoryImpl.java	Thu Jan 12 18:21:07 2012 +0400
@@ -0,0 +1,70 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.MergeBaseCommand;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommand;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author dmitry.neverov
+ */
+public final class CommandFactoryImpl implements CommandFactory {
+
+  //hg version which supports revsets
+  private final static HgVersion REVSET_HG_VERSION = new HgVersion(1, 7, 0);
+  private final static String LOG_TEMPLATE_NAME = "log.template";
+
+  private final File myDefaultWorkingDir;
+  private final File myLogTemplate;
+
+
+  public CommandFactoryImpl(@NotNull final ServerPluginConfig config) throws IOException {
+    myDefaultWorkingDir = config.getCachesDir();
+    myLogTemplate = createLogTemplate(config.getPluginDataDir());
+  }
+
+
+  @NotNull
+  public MergeBaseCommand createMergeBase(@NotNull Settings settings, @NotNull File workingDir) throws VcsException {
+    HgVersion hgVersion = getHgVersion(settings);
+    if (hgVersion.isEqualsOrGreaterThan(REVSET_HG_VERSION))
+      return new MergeBaseWithRevsets(settings, workingDir, this);
+    else
+      return new MergeBaseNoRevsets(settings, workingDir, this);
+  }
+
+
+  @NotNull
+  public LogCommand createLog(@NotNull final Settings settings, @NotNull final File workingDir) {
+    return new LogCommand(settings, workingDir, myLogTemplate);
+  }
+
+  @NotNull
+  public CollectChangesCommand getCollectChangesCommand(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
+    HgVersion hgVersion = getHgVersion(settings);
+    if (hgVersion.isEqualsOrGreaterThan(REVSET_HG_VERSION)) {
+      return new CollectChangesWithRevsets(settings, workingDir, myLogTemplate);
+    } else {
+      return new CollectChangesNoRevsets(settings, workingDir, myLogTemplate);
+    }
+  }
+
+  private File createLogTemplate(@NotNull final File templateFileDir) throws IOException {
+    File template = new File(templateFileDir, LOG_TEMPLATE_NAME);
+    if (!template.exists()) {
+      FileUtil.copyResource(CommandFactoryImpl.class, "/buildServerResources/log.template", template);
+    }
+    return template;
+  }
+
+  private HgVersion getHgVersion(@NotNull final Settings settings) throws VcsException {
+    VersionCommand versionCommand = new VersionCommand(settings, myDefaultWorkingDir);
+    return versionCommand.execute();
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Thu Jan 12 18:21:07 2012 +0400
@@ -457,8 +457,14 @@
     try {
       if (Settings.isValidRepository(workingDir)) {
         if (!isChangeSetExist(settings, workingDir, cset)) {
-          PullCommand pull = new PullCommand(settings, workingDir);
-          pull.execute(myConfig.getPullTimeout());
+          try {
+            PullCommand pull = new PullCommand(settings, workingDir);
+            pull.execute(myConfig.getPullTimeout());
+          } catch (UnrelatedRepositoryException e) {
+            Loggers.VCS.warn("Repository at " + settings.getRepository() + " is unrelated, clone it again");
+            myMirrorManager.forgetDir(workingDir);
+            syncRepository(settings, cset);
+          }
         }
       } else {
         CloneCommand cl = new CloneCommand(settings, workingDir);
@@ -475,8 +481,14 @@
     lockWorkDir(workingDir);
     try {
       if (Settings.isValidRepository(workingDir)) {
-        PullCommand pull = new PullCommand(settings, workingDir);
-        pull.execute(myConfig.getPullTimeout());
+        try {
+          PullCommand pull = new PullCommand(settings, workingDir);
+          pull.execute(myConfig.getPullTimeout());
+        } catch (UnrelatedRepositoryException e) {
+          Loggers.VCS.warn("Repository at " + settings.getRepository() + " is unrelated, clone it again");
+          myMirrorManager.forgetDir(workingDir);
+          syncRepository(settings);
+        }
       } else {
         CloneCommand cl = new CloneCommand(settings, workingDir);
         cl.setUpdateWorkingDir(false);
@@ -654,18 +666,22 @@
     String toCommit = new ChangeSetRevision(toVersion).getId();
     File workingDir = getWorkingDir(settings);
     CollectChangesCommand log = myCommandFactory.getCollectChangesCommand(settings, workingDir);
-    List<ChangeSet> changesets = log.execute(fromCommit, toCommit);
-    Iterator<ChangeSet> iter = changesets.iterator();
-    while (iter.hasNext()) {
-      ChangeSet cset = iter.next();
-      if (cset.getId().equals(fromCommit))
-        iter.remove();//skip already reported changes
+    try {
+      List<ChangeSet> changesets = log.execute(fromCommit, toCommit);
+      Iterator<ChangeSet> iter = changesets.iterator();
+      while (iter.hasNext()) {
+        ChangeSet cset = iter.next();
+        if (cset.getId().equals(fromCommit))
+          iter.remove();//skip already reported changes
+      }
+      return changesets;
+    } catch (UnknownRevisionException e) {
+      Loggers.VCS.warn("Revision '" + e.getRevision() + "' is unknown, will return no changes");
+      return Collections.emptyList();
     }
-    return changesets;
   }
 
 
-
   @NotNull
   public BuildPatchPolicy getBuildPatchPolicy() {
     return new BuildPatchByCheckoutRules() {
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseNoRevsets.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseNoRevsets.java	Thu Jan 12 18:21:07 2012 +0400
@@ -19,9 +19,9 @@
 
   private final Settings mySettings;
   private final File myWorkingDir;
-  private final CommandFactory myCommandFactory;
+  private final CommandFactoryImpl myCommandFactory;
 
-  public MergeBaseNoRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final CommandFactory commandFactory) {
+  public MergeBaseNoRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final CommandFactoryImpl commandFactory) {
     mySettings = settings;
     myWorkingDir = workingDir;
     myCommandFactory = commandFactory;
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseWithRevsets.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseWithRevsets.java	Thu Jan 12 18:21:07 2012 +0400
@@ -15,9 +15,9 @@
 
   private final Settings mySettings;
   private final File myWorkingDir;
-  private final CommandFactory myCommandFactory;
+  private final CommandFactoryImpl myCommandFactory;
 
-  public MergeBaseWithRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final CommandFactory commandFactory) {
+  public MergeBaseWithRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final CommandFactoryImpl commandFactory) {
     mySettings = settings;
     myWorkingDir = workingDir;
     myCommandFactory = commandFactory;
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java	Thu Jan 12 18:21:07 2012 +0400
@@ -108,4 +108,15 @@
     assertTrue(manager.getMirrors().contains(mirrorDir1));
     assertTrue(manager.getMirrors().contains(mirrorDir2));
   }
+
+
+  public void should_be_able_to_forget_directory() throws Exception {
+    String url = "hg://some.org/repository";
+    File mirror1 = myManager.getMirrorDir(url);
+    File dotHg = new File(mirror1, ".hg");
+    dotHg.mkdirs();
+    myManager.forgetDir(mirror1);
+    File mirror2 = myManager.getMirrorDir(url);
+    assertFalse(mirror2.equals(mirror1));
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java	Thu Jan 12 18:21:07 2012 +0400
@@ -0,0 +1,104 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.UnknownRevisionException;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class UnrelatedResitoriesTest {
+
+  private final static String CURRENT_VERSION_OF_NEW_REPO = "18:df04faa7575a";
+  private MercurialVcsSupport myVcs;
+  private TempFiles myTempFiles;
+  private File myRepositoryLocation;
+  private VcsRootImpl myRoot;
+  private Mockery myContext;
+  private ServerPluginConfig myPluginConfig;
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    myContext = new Mockery();
+    myTempFiles = new TempFiles();
+    myPluginConfig = new ServerPluginConfigBuilder()
+            .cachesDir(myTempFiles.createTempDir())
+            .pluginDataDir(myTempFiles.createTempDir())
+            .build();
+
+    myRepositoryLocation = myTempFiles.createTempDir();
+    copyRepository(new File("mercurial-tests/testData/rep1"));
+    myRoot = new VcsRootBuilder().repository(myRepositoryLocation.getCanonicalPath()).build();
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    myTempFiles.cleanup();
+  }
+
+
+  public void should_be_able_to_sync_when_repository_became_unrelated() throws Exception {
+    myVcs = createVcs();
+    syncRepository();
+    repositoryBecamesUnrelated();
+    String currentVersion = syncRepository();
+    assertEquals(CURRENT_VERSION_OF_NEW_REPO, currentVersion);
+  }
+
+
+  public void should_return_no_changes_when_fromRevision_is_from_unrelated_repository() throws Exception {
+    final CommandFactory factory = myContext.mock(CommandFactory.class);
+    final CollectChangesCommand commandExecutedWithException = myContext.mock(CollectChangesCommand.class);
+    myVcs = createVcs(factory);
+    myContext.checking(new Expectations(){{
+      allowing(factory).getCollectChangesCommand(with(any(Settings.class)), with(any(File.class)));
+      will(returnValue(commandExecutedWithException));
+      allowing(commandExecutedWithException).execute(with(any(String.class)), with(any(String.class)));
+      will(throwException(new UnknownRevisionException("1234")));
+    }});
+
+    String currentVersionOfOldRepo = syncRepository();
+    repositoryBecamesUnrelated();
+    String currentVersionOfNewRepo = syncRepository();
+    assertTrue(myVcs.collectChanges(myRoot, currentVersionOfOldRepo, currentVersionOfNewRepo, CheckoutRules.DEFAULT).isEmpty());
+  }
+
+
+  private String syncRepository() throws VcsException {
+    return myVcs.getCurrentVersion(myRoot);
+  }
+
+  private void repositoryBecamesUnrelated() throws IOException {
+    FileUtil.delete(myRepositoryLocation);
+    copyRepository(new File("mercurial-tests/testData/rep2"));
+  }
+
+  private void copyRepository(@NotNull final File src) throws IOException {
+    LocalRepositoryUtil.copyRepository(src, myRepositoryLocation);
+  }
+
+  private MercurialVcsSupport createVcs() throws IOException {
+    return Util.createMercurialServerSupport(myContext, myPluginConfig);
+  }
+
+  private MercurialVcsSupport createVcs(@NotNull final CommandFactory factory) throws IOException {
+    return Util.createMercurialServerSupport(myContext, myPluginConfig, factory);
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Thu Jan 12 18:21:07 2012 +0400
@@ -31,6 +31,11 @@
 
 
   public static MercurialVcsSupport createMercurialServerSupport(@NotNull Mockery context, ServerPluginConfig config) throws IOException {
+    return createMercurialServerSupport(context, config, new CommandFactoryImpl(config));
+  }
+
+
+  public static MercurialVcsSupport createMercurialServerSupport(@NotNull Mockery context, @NotNull ServerPluginConfig config, @NotNull CommandFactory commandFactory) throws IOException {
     VcsManager vcsManager = context.mock(VcsManager.class);
     final SBuildServer server = context.mock(SBuildServer.class);
     final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
@@ -38,6 +43,6 @@
       allowing(server).getExecutor(); will(returnValue(executor));
     }});
     EventDispatcher<BuildServerListener> dispatcher = EventDispatcher.create(BuildServerListener.class);
-    return new MercurialVcsSupport(vcsManager, server, dispatcher, config, new ServerHgPathProvider(config), new CommandFactory(config));
+    return new MercurialVcsSupport(vcsManager, server, dispatcher, config, new ServerHgPathProvider(config), commandFactory);
   }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java	Thu Jan 12 18:21:07 2012 +0400
@@ -2,13 +2,15 @@
 
 import jetbrains.buildServer.ExecResult;
 import jetbrains.buildServer.StreamGobbler;
+import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.Test;
 
 import java.io.ByteArrayInputStream;
-import java.util.Collections;
+import java.util.Arrays;
+import java.util.HashSet;
 
-import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.*;
 
 /**
  * @author dmitry.neverov
@@ -18,23 +20,93 @@
 
   public void output_should_not_contain_private_data() {
     String password = "pass";
-    ExecResult result = createExecResult(password, password);
-    CommandResult commandResult = new CommandResult(result, Collections.singleton(password));
+    CommandResult commandResult = commandResultFor(execResult().withStdout(password).withStderr(password), password);
     assertFalse(commandResult.getStdout().contains(password));
     assertFalse(commandResult.getStderr().contains(password));
   }
 
-  private ExecResult createExecResult(@NotNull final String output, @NotNull final String error) {
-    ExecResult result = new ExecResult();
-    result.setOutputGobbler(createStringGobbler(output));
-    result.setErrorGobbler(createStringGobbler(error));
-    return result;
+  @Test(expectedExceptions = UnrelatedRepositoryException.class)
+  public void should_detect_unrelated_repository_error() throws VcsException {
+    String unrelatedRepositoryStderr = "abort: repository is unrelated\n";
+    CommandResult commandResult = commandResultFor(execResult().withStderr(unrelatedRepositoryStderr).withExitCode(255));
+    commandResult.rethrowDetectedError();
   }
 
-  private StreamGobbler createStringGobbler(@NotNull final String str) {
-    StreamGobbler gobbler = new StreamGobbler(new ByteArrayInputStream(str.getBytes()));
-    gobbler.start();
-    return gobbler;
+  @Test
+  public void should_detect_unknown_revision_error() throws VcsException {
+    String unknownRevision = "9c6a6b4aede0";
+    String unknownRevisionError = "abort: unknown revision '" + unknownRevision + "'\n";
+    CommandResult commandResult = commandResultFor(execResult().withStderr(unknownRevisionError).withExitCode(255));
+    try {
+      commandResult.rethrowDetectedError();
+      fail("unknown exception should be thrown");
+    } catch (UnknownRevisionException e) {
+      assertEquals(unknownRevision, e.getRevision());
+    }
   }
 
+  public void should_detect_failure_when_delegate_has_exception() {
+    CommandResult commandResult = commandResultFor(execResult().withException(new RuntimeException()));
+    assertTrue(commandResult.isFailure());
+  }
+
+  public void should_detect_failure_with_non_zero_exit_code() {
+    CommandResult commandResult = commandResultFor(execResult().withExitCode(1));
+    assertTrue(commandResult.isFailure());
+  }
+
+
+  ExecResultBuilder execResult() {
+    return new ExecResultBuilder();
+  }
+
+  CommandResult commandResultFor(ExecResultBuilder builder, String... privateData) {
+    return new CommandResult("", builder.build(), new HashSet<String>(Arrays.asList(privateData)));
+  }
+
+  private class ExecResultBuilder {
+    private String myStdout = "";
+    private String myStderr = "";
+    private Throwable myException = null;
+    private int myExitCode = 0;
+
+    ExecResultBuilder withStdout(String stdout) {
+      myStdout = stdout;
+      return this;
+    }
+    ExecResultBuilder withStderr(String stderr) {
+      myStderr = stderr;
+      return this;
+    }
+    ExecResultBuilder withException(Throwable exception) {
+      myException = exception;
+      return this;
+    }
+    ExecResultBuilder withExitCode(int exitCode) {
+      myExitCode = exitCode;
+      return this;
+    }
+
+    ExecResult build() {
+      ExecResult result = new ExecResult();
+      result.setOutputGobbler(createStringGobbler(myStdout));
+      result.setErrorGobbler(createStringGobbler(myStderr));
+      if (myException != null)
+        result.setException(myException);
+      if (myExitCode != 0)
+        result.setExitCode(myExitCode);
+      return result;
+    }
+
+    private StreamGobbler createStringGobbler(@NotNull final String str) {
+      StreamGobbler gobbler = new StreamGobbler(new ByteArrayInputStream(str.getBytes()));
+      gobbler.start();
+      try {
+        Thread.sleep(10);//wait for gobbler to read string
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+      }
+      return gobbler;
+    }
+  }
 }
--- a/mercurial-tests/src/testng.xml	Wed Jan 11 15:09:52 2012 +0400
+++ b/mercurial-tests/src/testng.xml	Thu Jan 12 18:21:07 2012 +0400
@@ -18,6 +18,7 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommandTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgPathProviderTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.DagFeaturesTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.UnrelatedResitoriesTest"/>
     </classes>
   </test>
 </suite>