Ability to disable credential helper provided by TeamCity
[teamcity/git-plugin.git] / git-agent / src / jetbrains / buildServer / buildTriggers / vcs / git / agent / GitCommandLine.java
1 /*
2  * Copyright 2000-2014 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;
18
19 import com.intellij.execution.configurations.GeneralCommandLine;
20 import com.intellij.openapi.util.SystemInfo;
21 import com.intellij.openapi.util.io.FileUtil;
22 import jetbrains.buildServer.ExecResult;
23 import jetbrains.buildServer.LineAwareByteArrayOutputStream;
24 import jetbrains.buildServer.agent.BuildInterruptReason;
25 import jetbrains.buildServer.buildTriggers.vcs.git.AuthSettings;
26 import jetbrains.buildServer.buildTriggers.vcs.git.AuthenticationMethod;
27 import jetbrains.buildServer.buildTriggers.vcs.git.GitUtils;
28 import jetbrains.buildServer.buildTriggers.vcs.git.agent.command.ScriptGen;
29 import jetbrains.buildServer.buildTriggers.vcs.git.agent.command.impl.*;
30 import jetbrains.buildServer.ssh.VcsRootSshKeyManager;
31 import jetbrains.buildServer.vcs.VcsException;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34
35 import java.io.ByteArrayOutputStream;
36 import java.io.File;
37 import java.io.IOException;
38 import java.nio.charset.Charset;
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43
44 public class GitCommandLine extends GeneralCommandLine {
45
46   private final GitAgentSSHService mySsh;
47   private final ScriptGen myScriptGen;
48   private final List<Runnable> myPostActions = new ArrayList<Runnable>();
49   private final File myTmpDir;
50   private final boolean myDeleteTempFiles;
51   private final GitProgressLogger myLogger;
52   private final GitVersion myGitVersion;
53   private final Context myCtx;
54   private File myWorkingDirectory;
55   private boolean myRepeatOnEmptyOutput = false;
56   private VcsRootSshKeyManager mySshKeyManager;
57   private boolean myHasProgress = false;
58
59   public GitCommandLine(@Nullable GitAgentSSHService ssh,
60                         @NotNull ScriptGen scriptGen,
61                         @NotNull File tmpDir,
62                         boolean deleteTempFiles,
63                         @NotNull GitProgressLogger logger,
64                         @NotNull GitVersion gitVersion,
65                         @NotNull Map<String, String> env,
66                         @NotNull Context ctx) {
67     mySsh = ssh;
68     myScriptGen = scriptGen;
69     myTmpDir = tmpDir;
70     myDeleteTempFiles = deleteTempFiles;
71     myLogger = logger;
72     myGitVersion = gitVersion;
73     myCtx = ctx;
74     setPassParentEnvs(true);
75     setEnvParams(env);
76   }
77
78   public ExecResult run(@NotNull GitCommandSettings settings) throws VcsException {
79     AuthSettings authSettings = settings.getAuthSettings();
80     if (myCtx.isProvideCredHelper() && !getParametersList().getParametersString().contains("credential.helper") && !myGitVersion.isLessThan(UpdaterImpl.EMPTY_CRED_HELPER)) {
81       //Disable credential helper if it wasn't specified by us, as default
82       //helper can require a user input. Do that even if our repository doesn't
83       //require any auth, auth can be required by submodules or git lfs.
84       //
85       //It would be cleaner to add the following
86       //
87       //[credential]
88       //  helper =
89       //
90       //to the local repository config and not disable helpers in every command.
91       //But some commands ignore this setting, e.g. 'git submodules update':
92       //https://public-inbox.org/git/CAC+L6n0YeX_n_AysCLtBWkA+jPHwg7HmOWq2PLj75byxOZE=qQ@mail.gmail.com/
93       getParametersList().addAt(0, "-c");
94       getParametersList().addAt(1, "credential.helper=");
95     }
96     if (authSettings != null) {
97       if (mySsh == null)
98         throw new IllegalStateException("Ssh is not initialized");
99       if (authSettings.getAuthMethod() == AuthenticationMethod.PASSWORD) {
100         try {
101           final File askPass = myScriptGen.generateAskPass(authSettings);
102           String askPassPath = askPass.getAbsolutePath();
103           if (askPassPath.contains(" ") && SystemInfo.isWindows) {
104             askPassPath = GitUtils.getShortFileName(askPass);
105           }
106           getParametersList().addAt(0, "-c");
107           getParametersList().addAt(1, "core.askpass=" + askPassPath);
108           addPostAction(new Runnable() {
109             public void run() {
110               if (myDeleteTempFiles)
111                 FileUtil.delete(askPass);
112             }
113           });
114           addEnvParam("GIT_ASKPASS", askPassPath);
115         } catch (IOException e) {
116           throw new VcsException(e);
117         }
118       }
119       if (settings.isUseNativeSsh()) {
120         return CommandUtil.runCommand(this, settings.getTimeout());
121       } else {
122         SshHandler h = new SshHandler(mySsh, mySshKeyManager, authSettings, this, myTmpDir, myCtx.getSshMacType());
123         try {
124           return CommandUtil.runCommand(this, settings.getTimeout());
125         } finally {
126           h.unregister();
127         }
128       }
129     } else {
130       return CommandUtil.runCommand(this, settings.getTimeout());
131     }
132   }
133
134
135   @NotNull
136   public GitVersion getGitVersion() {
137     return myGitVersion;
138   }
139
140   public void addPostAction(@NotNull Runnable action) {
141     myPostActions.add(action);
142   }
143
144   public List<Runnable> getPostActions() {
145     return myPostActions;
146   }
147
148   @Override
149   public void setWorkingDirectory(File workingDirectory) {
150     myWorkingDirectory = workingDirectory;
151     super.setWorkingDirectory(workingDirectory);
152   }
153
154   @Nullable
155   public File getWorkingDirectory() {
156     return myWorkingDirectory;
157   }
158
159   public GitCommandLine repeatOnEmptyOutput(boolean doRepeat) {
160     myRepeatOnEmptyOutput = doRepeat;
161     return this;
162   }
163
164   public boolean isRepeatOnEmptyOutput() {
165     return myRepeatOnEmptyOutput;
166   }
167
168   public void setSshKeyManager(VcsRootSshKeyManager sshKeyManager) {
169     mySshKeyManager = sshKeyManager;
170   }
171
172   public void addEnvParam(@NotNull String name, @NotNull String value) {
173     Map<String, String> existing = getEnvParams();
174     if (existing == null)
175       existing = new HashMap<String, String>();
176     Map<String, String> newParams = new HashMap<String, String>(existing);
177     newParams.put(name, value);
178     setEnvParams(newParams);
179   }
180
181   @NotNull
182   public ByteArrayOutputStream createStderrBuffer() {
183     LineAwareByteArrayOutputStream buffer = new LineAwareByteArrayOutputStream(Charset.forName("UTF-8"), new GitProgressListener(myLogger));
184     buffer.setCREndsLine(true);
185     return buffer;
186   }
187
188   public void logStart(@NotNull String msg) {
189     if (myHasProgress) {
190       myLogger.openBlock(msg);
191     } else {
192       myLogger.message(msg);
193     }
194   }
195
196   public void logFinish(@NotNull String msg) {
197     if (myHasProgress)
198       myLogger.closeBlock(msg);
199   }
200
201   public void setHasProgress(final boolean hasProgress) {
202     myHasProgress = hasProgress;
203   }
204
205
206   public void checkCanceled() throws VcsException {
207     BuildInterruptReason reason = myCtx.getInterruptionReason();
208     if (reason != null)
209       throw new CheckoutCanceledException(reason);
210   }
211 }