changeset 388:3b799724b82b Eluru-6.5.x

TW-20304 port fix from branch default
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Fri, 17 Feb 2012 10:55:58 +0400
parents 55c2c88a2d82
children b0802861feaa
files 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/CatCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandExecutionSettings.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandExecutionSettingsBuilder.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/UnknownFileException.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/UpdateCommand.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandResultTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java mercurial-tests/src/testng.xml
diffstat 21 files changed, 592 insertions(+), 222 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Fri Feb 17 10:55:58 2012 +0400
@@ -20,6 +20,8 @@
 
 import java.io.File;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
 public class ArchiveCommand extends BaseCommand {
   private File myDestDir;
   private String myToId;
@@ -45,8 +47,7 @@
     setRevision(cli);
     setDestination(cli);
 
-    CommandResult res = runCommand(cli);
-    failIfNotEmptyStdErr(cli, res);
+    runCommand(cli, with().failureWhenStderrNotEmpty());
     deleteHgArchival();
   }
 
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Fri Feb 17 10:55:58 2012 +0400
@@ -16,7 +16,6 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
@@ -24,6 +23,8 @@
 import java.util.Collections;
 import java.util.Set;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
 /**
  * @author pavel
  */
@@ -67,21 +68,11 @@
   }
 
   protected CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
-    return CommandUtil.runCommand(cli, getPrivateData());
-  }
-
-  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, boolean logErrorsInDebug) throws VcsException {
-    return CommandUtil.runCommand(cli, CommandUtil.DEFAULT_COMMAND_TIMEOUT_SEC, getPrivateData(), logErrorsInDebug);
+    return CommandUtil.runCommand(cli, with().privateData(getPrivateData()));
   }
 
-  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, int executionTimeout) throws VcsException {
-    return CommandUtil.runCommand(cli, executionTimeout, getPrivateData());
-  }
-
-  protected void failIfNotEmptyStdErr(@NotNull GeneralCommandLine cli, @NotNull CommandResult res) throws VcsException {
-    if (!StringUtil.isEmpty(res.getStderr())) {
-      CommandUtil.commandFailed(cli.getCommandLineString(), res);
-    }
+  protected CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull CommandExecutionSettingsBuilder with) throws VcsException {
+    return CommandUtil.runCommand(cli, with.privateData(getPrivateData()));
   }
 
   public Set<String> getPrivateData() {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Fri Feb 17 10:55:58 2012 +0400
@@ -26,10 +26,12 @@
 import java.util.List;
 import java.util.Queue;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
 public class CatCommand extends BaseCommand {
   private String myRevId;
   private final static int MAX_CMD_LEN = 900;
-  private boolean myLogErrorsInDebug = false;
+  private boolean myCheckForFailure = true;
 
   public CatCommand(@NotNull Settings settings, @NotNull File workingDir) {
     super(settings, workingDir);
@@ -39,8 +41,8 @@
     myRevId = revId;
   }
 
-  public void setLogErrorsInDebug(boolean doLogErrorsInDebug) {
-    myLogErrorsInDebug = doLogErrorsInDebug;
+  public void checkForFailure(boolean doCheckFailure) {
+    myCheckForFailure = doCheckFailure;
   }
 
   public File execute(List<String> relPaths) throws VcsException {
@@ -68,7 +70,7 @@
         cmdSize += path.length();
       } while (cmdSize < MAX_CMD_LEN && !paths.isEmpty());
 
-      runCommand(cli, myLogErrorsInDebug);
+      runCommand(cli, with().checkForFailure(myCheckForFailure));
     }
 
     return tempDir;
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java	Fri Feb 17 10:55:58 2012 +0400
@@ -21,6 +21,8 @@
 
 import java.io.File;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
 public class CloneCommand extends BaseCommand{
   private String myToId;
   private boolean myUpdateWorkingDir = true;
@@ -74,6 +76,6 @@
     cli.addParameter(myRepository);
     cli.addParameter(myWorkingDir.getName());
 
-    runCommand(cli, 24*3600); // some repositories are quite large, we set timeout to 24 hours
+    runCommand(cli, with().timeout(24 * 3600)); // some repositories are quite large, we set timeout to 24 hours
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandExecutionSettings.java	Fri Feb 17 10:55:58 2012 +0400
@@ -0,0 +1,40 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+/**
+ * @author dmitry.neverov
+ */
+public class CommandExecutionSettings {
+
+  private final int myTimeout;
+  private final Set<String> myPrivateData;
+  private final boolean myCheckForFailure;
+  private final boolean myFailWhenStderrNotEmpty;
+
+  CommandExecutionSettings(int timeout, @NotNull Set<String> privateData, boolean checkForFailure, boolean failWhenStderrNotEmpty) {
+    myTimeout = timeout;
+    myPrivateData = privateData;
+    myCheckForFailure = checkForFailure;
+    myFailWhenStderrNotEmpty = failWhenStderrNotEmpty;
+  }
+
+  public int timeout() {
+    return myTimeout;
+  }
+
+  @NotNull
+  public Set<String> privateData() {
+    return myPrivateData;
+  }
+
+  public boolean shouldCheckForFailure() {
+    return myCheckForFailure;
+  }
+
+  public boolean shouldFailWithNonEmptyStderr() {
+    return myFailWhenStderrNotEmpty;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandExecutionSettingsBuilder.java	Fri Feb 17 10:55:58 2012 +0400
@@ -0,0 +1,47 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author dmitry.neverov
+ */
+public class CommandExecutionSettingsBuilder {
+
+  private static final int DEFAULT_COMMAND_TIMEOUT_SEC = 3600;
+
+  private int myTimeout = DEFAULT_COMMAND_TIMEOUT_SEC;
+  private Set<String> myPrivateData = Collections.emptySet();
+  private boolean myCheckForFailure = true;
+  private boolean myFailWhenStderrNotEmpty = false;
+
+  public static CommandExecutionSettingsBuilder with() {
+    return new CommandExecutionSettingsBuilder();
+  }
+
+  public CommandExecutionSettings build() {
+    return new CommandExecutionSettings(myTimeout, myPrivateData, myCheckForFailure, myFailWhenStderrNotEmpty);
+  }
+
+  public CommandExecutionSettingsBuilder timeout(int timeout) {
+    myTimeout = timeout;
+    return this;
+  }
+
+  public CommandExecutionSettingsBuilder privateData(@NotNull Set<String> privateData) {
+    myPrivateData = privateData;
+    return this;
+  }
+
+  public CommandExecutionSettingsBuilder checkForFailure(boolean checkForFailure) {
+    myCheckForFailure = checkForFailure;
+    return this;
+  }
+
+  public CommandExecutionSettingsBuilder failureWhenStderrNotEmpty() {
+    myFailWhenStderrNotEmpty = true;
+    return this;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Fri Feb 17 10:55:58 2012 +0400
@@ -1,24 +1,47 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
+import com.intellij.openapi.diagnostic.Logger;
 import jetbrains.buildServer.ExecResult;
+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.HashSet;
 import java.util.Set;
 
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;
+import static java.util.Arrays.asList;
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData;
 
 /**
- * Decorator for ExecResult that filters out private data from stdout and strerr.
+ * Mercurial command result. Filters out private data from stdout and detects errors.
  *
  * @author dmitry.neverov
  */
 public class CommandResult {
 
+  //Mercurial returns -1 in the case of errors (see dispatch.py)
+  //and on some shells (e.g. windows cmd) it is truncated to 255.
+  //A non-zero exit code is not always an error:
+  //http://mercurial.selenic.com/bts/issue186
+  //http://mercurial.selenic.com/bts/issue2189
+  //e.g. pull command in hg 2.1 exits with 1 if no new changes were pulled.
+  private static final Set<Integer> ERROR_EXIT_CODES = setOf(-1, 255);
+
+  private final Logger myLogger;
+  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 Logger logger, @NotNull String command, @NotNull ExecResult execResult) {
+    this(logger, command, execResult, Collections.<String>emptySet());
+  }
+
+  public CommandResult(@NotNull Logger logger, @NotNull String command, @NotNull ExecResult execResult, @NotNull Set<String> privateData) {
+    myLogger = logger;
+    myCommand = command;
     myDelegate = execResult;
     myPrivateData = privateData;
   }
@@ -28,21 +51,133 @@
     return removePrivateData(myDelegate.getStdout(), myPrivateData);
   }
 
-  @NotNull
-  public String getStderr() {
-    return removePrivateData(myDelegate.getStderr(), myPrivateData);
+  public void checkCommandFailed() throws VcsException {
+    checkFailure(false);
   }
 
-  @Nullable
-  public Throwable getException() {
-    return myDelegate.getException();
-  }
-
-  public int getExitCode() {
-    return myDelegate.getExitCode();
+  public void checkFailure(boolean failWhenStderrIsNonEmpty) throws VcsException {
+    rethrowDetectedError();
+    if (isFailure())
+      logAndThrowError();
+    String stderr = getStderr();
+    if (!isEmpty(stderr)) {
+      if (failWhenStderrIsNonEmpty)
+        logAndThrowError();
+      else
+        logStderr(stderr);
+    }
   }
 
   public byte[] getByteOut() {
     return myDelegate.getByteOut();
   }
+
+  private void logAndThrowError() throws VcsException {
+    String message = createCommandLogMessage();
+    myLogger.warn(message);
+    if (hasImportantException())
+      myLogger.error("Error during executing '" + getCommand() + "'", getException());
+    throw new VcsException(message);
+  }
+
+  private void logStderr(String stderr) {
+    myLogger.warn("Error output produced by: " + getCommand());
+    myLogger.warn(stderr);
+  }
+
+  @NotNull
+  private String getStderr() {
+    return removePrivateData(myDelegate.getStderr(), myPrivateData);
+  }
+
+  @Nullable
+  private Throwable getException() {
+    return myDelegate.getException();
+  }
+
+  private boolean isFailure() {
+    return getException() != null || isErrorExitCode();
+  }
+
+  private boolean isErrorExitCode() {
+    int exitCode = myDelegate.getExitCode();
+    return ERROR_EXIT_CODES.contains(exitCode);
+  }
+
+  private boolean shouldDetectErrors() {
+    return isFailure() || myDelegate.getExitCode() != 0;
+  }
+
+  @NotNull
+  private String getCommand() {
+    return removePrivateData(myCommand, myPrivateData);
+  }
+
+  private boolean hasImportantException() {
+    Throwable exception = getException();
+    return exception instanceof NullPointerException;
+  }
+
+  private String createCommandLogMessage() {
+    String stderr = getStderr();
+    String stdout = getStdout();
+    String exceptionMessage = getExceptionMessage();
+    return "'" + getCommand() + "' command failed.\n" +
+            (!StringUtil.isEmpty(stdout) ? "stdout: " + stdout + "\n" : "") +
+            (!StringUtil.isEmpty(stderr) ? "stderr: " + stderr + "\n" : "") +
+            (exceptionMessage != null ? "exception: " + exceptionMessage : "");
+  }
+
+  @Nullable
+  private String getExceptionMessage() {
+    Throwable exception = getException();
+    if (exception == null)
+      return null;
+    String message = exception.getMessage();
+    if (message == null)
+      message = exception.getClass().getName();
+    return message;
+  }
+
+  private void rethrowDetectedError() throws VcsException {
+    if (!shouldDetectErrors())
+      return;
+    String stderr = getStderr().trim();
+    checkUnrelatedRepository(stderr);
+    checkUnknownRevision(stderr);
+    checkFileNotUnderTheRoot(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);
+    }
+  }
+
+  private void checkFileNotUnderTheRoot(@NotNull final String stderr) throws VcsException {
+    final String prefix = "abort: ";
+    int idx = stderr.indexOf("abort: ");
+    if (idx != -1) {
+      int startIdx = idx + prefix.length();
+      int endIdx = stderr.indexOf(" not under root");
+      if (endIdx != -1) {
+        String path = stderr.substring(startIdx, endIdx);
+        throw new UnknownFileException(path);
+      }
+    }
+  }
+
+  private static Set<Integer> setOf(Integer... ints) {
+    return new HashSet<Integer>(asList(ints));
+  }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java	Fri Feb 17 10:55:58 2012 +0400
@@ -1,138 +1,73 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.ExecResult;
-import jetbrains.buildServer.SimpleCommandLineProcessRunner;
-import jetbrains.buildServer.log.Loggers;
-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;
-
-public class CommandUtil {
-  public static final int DEFAULT_COMMAND_TIMEOUT_SEC = 3600;
-
-  public static void checkCommandFailed(@NotNull String cmdName, @NotNull CommandResult res) throws VcsException {
-    checkCommandFailed(cmdName, res, false);
-  }
-
-  private static void checkCommandFailed(@NotNull String cmdName, @NotNull CommandResult res, boolean logErrorsInDebug) throws VcsException {
-    if (logErrorsInDebug) {
-      if (res.getExitCode() != 0 || res.getException() != null)
-        Loggers.VCS.debug(createCommandLogMessage(cmdName, res));
-    } else {
-      if (res.getExitCode() != 0 || res.getException() != null)
-        commandFailed(cmdName, res);
-      if (res.getStderr().length() > 0) {
-        Loggers.VCS.warn("Error output produced by: " + cmdName);
-        Loggers.VCS.warn(res.getStderr());
-      }
-    }
-  }
-
-  public static void commandFailed(final String cmdName, final CommandResult res) throws VcsException {
-    final String message = createCommandLogMessage(cmdName, res);
-    Loggers.VCS.warn(message);
-    if (hasImportantException(res)) {
-      Loggers.VCS.error("Error during executing '" + cmdName + "'", res.getException());
-    }
-    throw new VcsException(message);
-  }
-
-  private static String createCommandLogMessage(final String cmdName, final CommandResult res) {
-    String stderr = res.getStderr();
-    String stdout = res.getStdout();
-    String exceptionMessage = getExceptionMessage(res);
-    return "'" + cmdName + "' 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());
-  }
-
-  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull Set<String> privateData) throws VcsException {
-    return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, privateData);
-  }
-
-  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData) throws VcsException {
-    return runCommand(cli, executionTimeout, privateData, false);
-  }
-
-  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData, boolean logErrorsInDebug) throws VcsException {
-    final String cmdStr = removePrivateData(cli.getCommandLineString(), privateData);
-    Loggers.VCS.debug("Run command: " + cmdStr);
-    CommandResult res = run(cli, executionTimeout, cmdStr, privateData);
-    CommandUtil.checkCommandFailed(cmdStr, res, logErrorsInDebug);
-    Loggers.VCS.debug("Command " + cmdStr + " output:\n" + res.getStdout());
-    return res;
-  }
-
-  private static CommandResult run(@NotNull final GeneralCommandLine cli, final int executionTimeout, @NotNull final String cmdStr, @NotNull final Set<String> privateData) {
-    final long start = System.currentTimeMillis();
-    ExecResult res = SimpleCommandLineProcessRunner.runCommand(cli, null, new SimpleCommandLineProcessRunner.RunCommandEventsAdapter() {
-      @Override
-      public Integer getOutputIdleSecondsTimeout() {
-        return executionTimeout;
-      }
-      @Override
-      public void onProcessFinished(Process ps) {
-        long duration = System.currentTimeMillis() - start;
-        Loggers.VCS.debug("Command " + cmdStr + " took " + duration + "ms");
-      }
-    });
-    return new CommandResult(res, privateData);
-  }
-
-  public static String removePrivateData(final String str, final Set<String> privateData) {
-    String result = str;
-    for (String data: privateData) {
-      if (data == null || data.length() == 0) continue;
-      result = result.replace(data, "******");
-    }
-
-    return result;
-  }
-}
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import jetbrains.buildServer.ExecResult;
+import jetbrains.buildServer.SimpleCommandLineProcessRunner;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
+public class CommandUtil {
+
+  public static CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
+    return runCommand(cli, with());
+  }
+
+  public static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull CommandExecutionSettingsBuilder executionSettingsBuilder) throws VcsException {
+    return runCommand(cli, executionSettingsBuilder.build());
+  }
+
+  private static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull CommandExecutionSettings executionSettings) throws VcsException {
+    final String command = removePrivateData(cli.getCommandLineString(), executionSettings.privateData());
+    Loggers.VCS.debug("Run command: " + command);
+    CommandResult res = run(cli, executionSettings.timeout(), command, executionSettings.privateData());
+    if (executionSettings.shouldCheckForFailure() || executionSettings.shouldFailWithNonEmptyStderr())
+      res.checkFailure(executionSettings.shouldFailWithNonEmptyStderr());
+    Loggers.VCS.debug("Command " + command + " output:\n" + res.getStdout());
+    return res;
+  }
+
+  private static CommandResult run(@NotNull final GeneralCommandLine cli, final int executionTimeout, @NotNull final String command, @NotNull final Set<String> privateData) {
+    final long start = System.currentTimeMillis();
+    ExecResult res = SimpleCommandLineProcessRunner.runCommand(cli, null, new SimpleCommandLineProcessRunner.RunCommandEventsAdapter() {
+      @Override
+      public Integer getOutputIdleSecondsTimeout() {
+        return executionTimeout;
+      }
+      @Override
+      public void onProcessFinished(Process ps) {
+        long duration = System.currentTimeMillis() - start;
+        Loggers.VCS.debug("Command " + command + " took " + duration + "ms");
+      }
+    });
+    return new CommandResult(Loggers.VCS, command, res, privateData);
+  }
+
+  public static String removePrivateData(final String str, final Set<String> privateData) {
+    String result = str;
+    for (String data: privateData) {
+      if (data == null || data.length() == 0) continue;
+      result = result.replace(data, "******");
+    }
+    return result;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Fri Feb 17 10:55:58 2012 +0400
@@ -21,6 +21,8 @@
 
 import java.io.File;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
 /**
  * @author Pavel.Sher
  *         Date: 16.07.2008
@@ -54,8 +56,7 @@
       cli.addParameter("--rev");
       cli.addParameter(myChangeSet.getId());
     }
-    CommandResult res = runCommand(cli);
-    failIfNotEmptyStdErr(cli, res);
+    CommandResult res = runCommand(cli, with().failureWhenStderrNotEmpty());
     return res.getStdout();
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java	Fri Feb 17 10:55:58 2012 +0400
@@ -21,6 +21,8 @@
 
 import java.io.File;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
 /**
  * @author Pavel.Sher
  *         Date: 14.07.2008
@@ -42,6 +44,6 @@
     GeneralCommandLine cli = createCommandLine();
     cli.addParameter("pull");
     cli.addParameter(myPullUrl);
-    runCommand(cli, timeout);
+    runCommand(cli, with().timeout(timeout));
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java	Fri Feb 17 10:55:58 2012 +0400
@@ -21,6 +21,8 @@
 
 import java.io.File;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
 /**
  * @author pavel
  */
@@ -42,7 +44,6 @@
       cli.addParameter("-f");
     }
     cli.addParameter(getSettings().getRepositoryUrl());
-    CommandResult res = runCommand(cli);
-    failIfNotEmptyStdErr(cli, res);
+    runCommand(cli, with().failureWhenStderrNotEmpty());
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnknownFileException.java	Fri Feb 17 10:55:58 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 UnknownFileException extends VcsException {
+
+  private final String myPath;
+
+  public UnknownFileException(@NotNull String path) {
+    super("Unknown file " + path);
+    myPath = path;
+  }
+
+  public String getPath() {
+    return myPath;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnknownRevisionException.java	Fri Feb 17 10:55:58 2012 +0400
@@ -0,0 +1,23 @@
+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) {
+    super("Unknown revision " + 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	Fri Feb 17 10:55:58 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/UpdateCommand.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java	Fri Feb 17 10:55:58 2012 +0400
@@ -21,6 +21,8 @@
 
 import java.io.File;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with;
+
 public class UpdateCommand extends BaseCommand {
 
   private static final int UPDATE_TIMEOUT_SECONDS = 8 * 3600;//8 hours
@@ -45,6 +47,6 @@
     } else {
       cli.addParameter(getSettings().getBranchName());
     }
-    runCommand(cli, UPDATE_TIMEOUT_SECONDS);
+    runCommand(cli, with().timeout(UPDATE_TIMEOUT_SECONDS));
   }
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Fri Feb 17 10:55:58 2012 +0400
@@ -204,7 +204,7 @@
     File dir = getWorkingDir(settings);
     CatCommand cat = new CatCommand(settings, dir);
     cat.setRevId(cset.getId());
-    cat.setLogErrorsInDebug(true);
+    cat.checkForFailure(false);
     File parentDir = null;
     try {
       parentDir = cat.execute(Collections.singletonList(path));
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java	Fri Feb 17 10:55:58 2012 +0400
@@ -8,7 +8,7 @@
 public class ServerPluginConfigImpl implements ServerPluginConfig {
 
   private static final String PULL_TIMEOUT_SECONDS = "teamcity.hg.pull.timeout.seconds";
-  private final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600;
+  public static final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600;
 
   public boolean isUsePullProtocol() {
     return TeamCityProperties.getBooleanOrTrue("teamcity.hg.use.pull.protocol");
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandResultTest.java	Wed Feb 15 12:07:12 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.ExecResult;
-import jetbrains.buildServer.StreamGobbler;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResult;
-import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.Test;
-
-import java.io.ByteArrayInputStream;
-import java.util.Collections;
-
-import static org.testng.AssertJUnit.assertFalse;
-
-/**
- * @author dmitry.neverov
- */
-@Test
-public class CommandResultTest {
-
-  public void output_should_not_contain_private_data() {
-    String password = "pass";
-    ExecResult result = createExecResult(password, password);
-    CommandResult commandResult = new CommandResult(result, Collections.singleton(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;
-  }
-
-  private StreamGobbler createStringGobbler(@NotNull final String str) {
-    StreamGobbler gobbler = new StreamGobbler(new ByteArrayInputStream(str.getBytes()));
-    gobbler.start();
-    return gobbler;
-  }
-
-}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Fri Feb 17 10:55:58 2012 +0400
@@ -517,7 +517,7 @@
         return true;
       }
       public int getPullTimeout() {
-        return CommandUtil.DEFAULT_COMMAND_TIMEOUT_SEC;
+        return ServerPluginConfigImpl.DEFAULT_PULL_TIMEOUT_SECONDS;
       }
     };
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java	Fri Feb 17 10:55:58 2012 +0400
@@ -0,0 +1,197 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.ExecResult;
+import jetbrains.buildServer.StreamGobbler;
+import jetbrains.buildServer.vcs.VcsException;
+import org.apache.log4j.Level;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.testng.AssertJUnit.*;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class CommandResultTest {
+
+  private RecordingLogger myLogger;
+
+  @BeforeMethod
+  public void setUp() {
+    myLogger = new RecordingLogger();
+  }
+
+  public void output_should_not_contain_private_data() {
+    String password = "pass";
+    CommandResult commandResult = commandResultFor(execResult().withStdout(password).withStderr(password), password);
+    assertFalse(commandResult.getStdout().contains(password));
+    myLogger.assertLogMessagesDontContain(password);
+  }
+
+  @DataProvider(name = "exitCodesForErrors")
+  public static Object[][] exitCodesForErrors() {
+    return new Object[][] {
+            new Object[] { -1 },
+            new Object[] { 255}};
+  }
+
+  @Test(expectedExceptions = VcsException.class,
+        dataProvider = "exitCodesForErrors")
+  public void should_detect_error_for_exit_code(int exitCode) throws VcsException {
+    CommandResult commandResult = commandResultFor(execResult().withExitCode(exitCode));
+    commandResult.checkCommandFailed();
+  }
+
+  public void exit_code_of_one_is_not_an_error() throws VcsException {
+    CommandResult commandResult = commandResultFor(execResult().withStdout("pull: no new changes").withExitCode(1));
+    commandResult.checkCommandFailed();
+  }
+
+  @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.checkCommandFailed();
+  }
+
+  @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.checkCommandFailed();
+      fail("unknown exception should be thrown");
+    } catch (UnknownRevisionException e) {
+      assertEquals(unknownRevision, e.getRevision());
+    }
+  }
+
+  @Test(expectedExceptions = VcsException.class)
+  public void should_detect_failure_when_delegate_has_exception() throws VcsException {
+    CommandResult commandResult = commandResultFor(execResult().withException(new RuntimeException()));
+    commandResult.checkCommandFailed();
+  }
+
+
+  ExecResultBuilder execResult() {
+    return new ExecResultBuilder();
+  }
+
+  CommandResult commandResultFor(ExecResultBuilder builder, String... privateData) {
+    return new CommandResult(myLogger, "", 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;
+    }
+  }
+
+  private class RecordingLogger extends Logger {
+
+    private List<String> myMessages = new ArrayList<String>();
+
+    public void assertLogMessagesDontContain(@NotNull String... strs) {
+      for (String s : strs) {
+        for (String message : myMessages)
+          assertFalse("'" + s + "' was logged", message.contains(s));
+      }
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+      return true;
+    }
+
+    @Override
+    public void debug(@NonNls String s) {
+      myMessages.add(s);
+    }
+
+    @Override
+    public void debug(Throwable throwable) {
+      myMessages.add(throwable.getMessage());
+    }
+
+    @Override
+    public void debug(@NonNls String s, Throwable throwable) {
+      myMessages.add(s);
+    }
+
+    @Override
+    public void error(@NonNls String s, Throwable throwable, @NonNls String... strings) {
+      myMessages.add(s);
+    }
+
+    @Override
+    public void info(@NonNls String s) {
+      myMessages.add(s);
+    }
+
+    @Override
+    public void info(@NonNls String s, Throwable throwable) {
+      myMessages.add(s);
+    }
+
+    @Override
+    public void warn(@NonNls String s, Throwable throwable) {
+      myMessages.add(s);
+    }
+
+    @Override
+    public void setLevel(Level level) {
+    }
+  }
+}
--- a/mercurial-tests/src/testng.xml	Wed Feb 15 12:07:12 2012 +0400
+++ b/mercurial-tests/src/testng.xml	Fri Feb 17 10:55:58 2012 +0400
@@ -12,7 +12,7 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentSideCheckoutTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SettingsTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerTest"/>
-      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommandResultTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResultTest"/>
     </classes>
   </test>
 </suite>