Mercurial > hg > mercurial
view mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java @ 674:61da07eceaa1
add more tests for fetchModificationInfo, simplified code
author | eugene.petrenko@gmail.com |
---|---|
date | Fri, 15 Nov 2013 13:28:45 +0100 |
parents | 2f6c4b61686a |
children | 1da36037196d |
line wrap: on
line source
package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; import com.intellij.execution.process.ProcessNotCreatedException; import com.intellij.openapi.diagnostic.Logger; import jetbrains.buildServer.ExecResult; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.*; import jetbrains.buildServer.util.StringUtil; import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; 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; /** * 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 static final String MERCURIAL_NOT_FOUND_MESSAGE_PREFIX = "Cannot run program \""; private static final String MERCURIAL_NOT_FOUND_MESSAGE_SUFFIX1 = "No such file or directory"; private static final String MERCURIAL_NOT_FOUND_MESSAGE_SUFFIX2 = "The system cannot find the file specified"; private final Logger myLogger; private final String myCommand; private final ExecResult myDelegate; private final Set<String> myPrivateData; private final CommandSettings mySettings; public CommandResult(@NotNull Logger logger, @NotNull String command, @NotNull ExecResult execResult, @NotNull Set<String> privateData, @NotNull CommandSettings settings) { myLogger = logger; myCommand = command; myDelegate = execResult; myPrivateData = privateData; mySettings = settings; } public int getExitCode() { return myDelegate.getExitCode(); } @NotNull public String getStdout() { return removePrivateData(myDelegate.getStdout(), myPrivateData); } public void checkCommandFailed() throws VcsException { checkFailure(false); } public void checkFailure(boolean failWhenStderrIsNonEmpty) throws VcsException { rethrowDetectedError(); if (isFailure()) logAndThrowError(); String stderr = getStderr(); if (!isEmpty(stderr)) { if (failWhenStderrIsNonEmpty) logAndThrowError(); else logStderr(stderr); } } private void logAndThrowError() throws VcsException { String message = createCommandLogMessage(); myLogger.warn(message); if (hasImportantException()) myLogger.error("Error during executing '" + getCommand() + "'", getException()); throwVcsException(getExceptionMessage()); } private void throwVcsException(@NotNull String message) throws VcsException { //noinspection ThrowableResultOfMethodCallIgnored Throwable e = getException(); if (isMercurialNotFoundException(e)) { assert e != null; throw new MercurialNotFoundException(myCommand, e); } throw new VcsException(message); } private boolean isMercurialNotFoundException(@Nullable Throwable e) { return e instanceof ProcessNotCreatedException && e.getCause() instanceof IOException && isMercurialNotFoundErrorMessage(e.getMessage()); } private boolean isMercurialNotFoundErrorMessage(@Nullable String message) { return message != null && (message.startsWith(MERCURIAL_NOT_FOUND_MESSAGE_PREFIX) && (message.endsWith(MERCURIAL_NOT_FOUND_MESSAGE_SUFFIX1) || message.endsWith(MERCURIAL_NOT_FOUND_MESSAGE_SUFFIX2)) || message.startsWith("CreateProcess") && message.endsWith("error=2")); } 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() { //noinspection ThrowableResultOfMethodCallIgnored 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() { //noinspection ThrowableResultOfMethodCallIgnored Throwable exception = getException(); return exception instanceof NullPointerException; } private String createCommandLogMessage() { StringBuilder message = new StringBuilder(); message.append("'").append(getCommand()).append("' command failed."); String stderr = getStderr(); if (!isEmpty(stderr)) { int logOutputLimit = mySettings.getLogOutputLimit(); if (logOutputLimit == -1) { message.append("\nstderr:\n").append(stderr); } else { message.append(StringUtil.truncateStringValueWithDotsAtEnd(stderr, logOutputLimit)); } } return message.toString(); } private String getExceptionMessage() { StringBuilder message = new StringBuilder(); message.append("'").append(getCommand()).append("' command failed."); return message.toString(); } private void rethrowDetectedError() throws VcsException { if (!shouldDetectErrors()) return; String stderr = getStderr().trim(); checkUnrelatedRepository(stderr); checkUnknownRevision(stderr); checkFileNotUnderTheRoot(stderr); checkConnectionRefused(stderr); checkAbandonedTransaction(stderr); checkMergeWithWorkDirAncestor(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 void checkConnectionRefused(@NotNull final String stderr) throws ConnectionRefusedException { if (stderr.equals("abort: error: Connection refused")) throw new ConnectionRefusedException(); } private void checkAbandonedTransaction(@NotNull final String stderr) throws AbandonedTransactionFound { if (stderr.contains("abort: abandoned transaction found - run hg recover")) throw new AbandonedTransactionFound(); } private void checkMergeWithWorkDirAncestor(@NotNull final String stderr) throws MergeWithWorkingDirAncestor { if (stderr.equals("abort: merging with a working directory ancestor has no effect")) throw new MergeWithWorkingDirAncestor(); } private static Set<Integer> setOf(Integer... ints) { return new HashSet<Integer>(asList(ints)); } }