TW-58811 Support 'update-ref --stdin'
[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, byte[] input, final String... errorsLogLevel) throws VcsException {
108     return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, input, errorsLogLevel);
109   }
110
111   public static ExecResult runCommand(@NotNull GitCommandLine cli, int timeoutSeconds, final String... errorsLogLevel)
112     throws VcsException {
113     return runCommand(cli, timeoutSeconds, null, errorsLogLevel);
114   }
115
116   public static ExecResult runCommand(@NotNull GitCommandLine cli, int timeoutSeconds, byte[] input, final String... errorsLogLevel)
117     throws VcsException {
118     int attemptsLeft = 2;
119     while (true) {
120       try {
121         cli.checkCanceled();
122         String cmdStr = cli.getCommandLineString();
123         File workingDir = cli.getWorkingDirectory();
124         String inDir = workingDir != null ? "[" + workingDir.getAbsolutePath() + "]" : "";
125         String msg = inDir + ": " + cmdStr;
126         Loggers.VCS.info(msg);
127         cli.logStart(cmdStr);
128         ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream();
129         ByteArrayOutputStream stderrBuffer = cli.createStderrBuffer();
130         ExecResult res = SimpleCommandLineProcessRunner
131           .runCommandSecure(cli, cli.getCommandLineString(), input, new ProcessTimeoutCallback(timeoutSeconds), stdoutBuffer, stderrBuffer);
132         cli.logFinish(cmdStr);
133         CommandUtil.checkCommandFailed(cmdStr, res, errorsLogLevel);
134         String out = res.getStdout().trim();
135         Loggers.VCS.debug(out);
136         if (!isEmpty(out) || !cli.isRepeatOnEmptyOutput() || attemptsLeft <= 0)
137           return res;
138         Loggers.VCS.warn("Get an unexpected empty output, will repeat command, attempts left: " + attemptsLeft);
139         attemptsLeft--;
140       } finally {
141         for (Runnable action : cli.getPostActions()) {
142           action.run();
143         }
144       }
145     }
146   }
147
148   public static void failIfNotEmptyStdErr(@NotNull GeneralCommandLine cli, @NotNull ExecResult res, String... errorsLogLevel) throws VcsException {
149     if (!isEmpty(res.getStderr()))
150       CommandUtil.commandFailed(cli.getCommandLineString(), res, errorsLogLevel);
151   }
152
153
154   public static boolean isTimeoutError(@NotNull VcsException e) {
155     String msg = e.getMessage();
156     return msg != null && msg.contains("exception: jetbrains.buildServer.ProcessTimeoutException");
157   }
158
159   public static boolean isCanceledError(@NotNull VcsException e) {
160     return e instanceof CheckoutCanceledException || e.getCause() instanceof InterruptedException;
161   }
162 }