172e10845e2c71a9d7a0e7113b877577297eea85
[teamcity/git-plugin.git] / git-agent / src / jetbrains / buildServer / buildTriggers / vcs / git / agent / command / impl / CommandUtil.java
1 /*
2  * Copyright 2000-2018 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package jetbrains.buildServer.buildTriggers.vcs.git.agent.command.impl;
18
19 import com.intellij.execution.configurations.GeneralCommandLine;
20 import jetbrains.buildServer.ExecResult;
21 import jetbrains.buildServer.SimpleCommandLineProcessRunner;
22 import jetbrains.buildServer.buildTriggers.vcs.git.agent.GitCommandLine;
23 import jetbrains.buildServer.log.Loggers;
24 import jetbrains.buildServer.util.StringUtil;
25 import jetbrains.buildServer.vcs.VcsException;
26 import org.jetbrains.annotations.NotNull;
27
28 import java.io.*;
29
30 import static com.intellij.openapi.util.text.StringUtil.isEmpty;
31
32 public class CommandUtil {
33   public static final int DEFAULT_COMMAND_TIMEOUT_SEC = 3600;
34
35   @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
36   public static void checkCommandFailed(@NotNull String cmdName, @NotNull ExecResult res, String... errorsLogLevel) throws VcsException {
37     if ("info".equals(logLevel(errorsLogLevel)) && res.getExitCode() != 0 && res.getException() == null) {
38       logMessage("'" + cmdName + "' exit code: " + res.getExitCode() + ". It is expected behaviour.", "info");
39     } else {
40       if (res.getExitCode() != 0 || res.getException() != null) {
41         commandFailed(cmdName, res);
42       }
43       if (res.getStderr().length() > 0) {
44         logMessage("Error output produced by: " + cmdName, errorsLogLevel);
45         logMessage(res.getStderr().trim(), errorsLogLevel);
46       }
47     }
48   }
49
50   @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
51   public static void commandFailed(final String cmdName, final ExecResult res, String... errorsLogLevel) throws VcsException {
52     Throwable exception = res.getException();
53     String stderr = res.getStderr().trim();
54     String stdout = res.getStdout().trim();
55     int exitCode = res.getExitCode();
56     String message = "'" + cmdName + "' command failed." +
57                      (exitCode != 0 ? "\nexit code: " + exitCode : "") +
58                      (!StringUtil.isEmpty(stdout) ? "\n" + "stdout: " + stdout : "") +
59                      (!StringUtil.isEmpty(stderr) ? "\n" + "stderr: " + stderr : "");
60     if (exception != null) {
61       message += "\nexception: ";
62       message += exception.getClass().getName();
63       String exceptionMessage = exception.getMessage();
64       if (!StringUtil.isEmpty(exceptionMessage))
65         message += ": " + exceptionMessage;
66     }
67     if (exception != null && isImportant(exception)) {
68       Writer stackWriter = new StringWriter();
69       exception.printStackTrace(new PrintWriter(stackWriter));
70       message += "\n" + stackWriter.toString();
71     }
72     logMessage(message, errorsLogLevel);
73     if (exception != null)
74       throw new VcsException(message, exception);
75     throw new VcsException(message);
76   }
77
78   private static boolean isImportant(Throwable e) {
79     return e instanceof NullPointerException;
80   }
81
82   /**
83    * Log message using level, if level is not set - use WARN
84    *
85    * @param message message to log
86    * @param level   level to use
87    */
88   private static void logMessage(String message, String... level) {
89     final String theLevel = logLevel(level);
90     if (theLevel.equals("warn")) {
91       Loggers.VCS.warn(message);
92     } else if (theLevel.equals("debug")) {
93       Loggers.VCS.debug(message);
94     } else if (theLevel.equals("info")) {
95       Loggers.VCS.info(message);
96     }
97   }
98
99   private static String logLevel(String... level) {
100     return level.length > 0 ? level[0] : "warn";
101   }
102
103   public static ExecResult runCommand(@NotNull GitCommandLine cli, final String... errorsLogLevel) throws VcsException {
104     return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, errorsLogLevel);
105   }
106
107   public static ExecResult runCommand(@NotNull GitCommandLine cli, int timeoutSeconds, final String... errorsLogLevel) throws VcsException {
108     int attemptsLeft = 2;
109     while (true) {
110       try {
111         cli.checkCanceled();
112         String cmdStr = cli.getCommandLineString();
113         File workingDir = cli.getWorkingDirectory();
114         String inDir = workingDir != null ? "[" + workingDir.getAbsolutePath() + "]" : "";
115         String msg = inDir + ": " + cmdStr;
116         Loggers.VCS.info(msg);
117         cli.logStart(cmdStr);
118         ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream();
119         ByteArrayOutputStream stderrBuffer = cli.createStderrBuffer();
120         ExecResult res = SimpleCommandLineProcessRunner.runCommandSecure(cli, cli.getCommandLineString(), null, new ProcessTimeoutCallback(timeoutSeconds), stdoutBuffer, stderrBuffer);
121         cli.logFinish(cmdStr);
122         CommandUtil.checkCommandFailed(cmdStr, res, errorsLogLevel);
123         String out = res.getStdout().trim();
124         Loggers.VCS.debug(out);
125         if (!isEmpty(out) || !cli.isRepeatOnEmptyOutput() || attemptsLeft <= 0)
126           return res;
127         Loggers.VCS.warn("Get an unexpected empty output, will repeat command, attempts left: " + attemptsLeft);
128         attemptsLeft--;
129       } finally {
130         for (Runnable action : cli.getPostActions()) {
131           action.run();
132         }
133       }
134     }
135   }
136
137   public static void failIfNotEmptyStdErr(@NotNull GeneralCommandLine cli, @NotNull ExecResult res, String... errorsLogLevel) throws VcsException {
138     if (!isEmpty(res.getStderr()))
139       CommandUtil.commandFailed(cli.getCommandLineString(), res, errorsLogLevel);
140   }
141
142
143   public static boolean isTimeoutError(@NotNull VcsException e) {
144     String msg = e.getMessage();
145     return msg != null && msg.contains("exception: jetbrains.buildServer.ProcessTimeoutException");
146   }
147
148   public static boolean isCanceledError(@NotNull VcsException e) {
149     return e instanceof CheckoutCanceledException || e.getCause() instanceof InterruptedException;
150   }
151 }