1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jgit.transport;
15
16 import java.io.File;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.text.MessageFormat;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.EnumSet;
24 import java.util.LinkedHashSet;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Set;
28
29 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
30 import org.eclipse.jgit.errors.NotSupportedException;
31 import org.eclipse.jgit.errors.TransportException;
32 import org.eclipse.jgit.internal.JGitText;
33 import org.eclipse.jgit.lib.Constants;
34 import org.eclipse.jgit.lib.Repository;
35 import org.eclipse.jgit.util.FS;
36 import org.eclipse.jgit.util.QuotedString;
37 import org.eclipse.jgit.util.SystemReader;
38 import org.eclipse.jgit.util.io.MessageWriter;
39 import org.eclipse.jgit.util.io.StreamCopyThread;
40
41
42
43
44
45
46
47
48
49
50
51
52 public class TransportGitSsh extends SshTransport implements PackTransport {
53 private static final String EXT = "ext";
54
55 static final TransportProtocolol.html#TransportProtocol">TransportProtocol PROTO_SSH = new TransportProtocol() {
56 private final String[] schemeNames = { "ssh", "ssh+git", "git+ssh" };
57
58 private final Set<String> schemeSet = Collections
59 .unmodifiableSet(new LinkedHashSet<>(Arrays
60 .asList(schemeNames)));
61
62 @Override
63 public String getName() {
64 return JGitText.get().transportProtoSSH;
65 }
66
67 @Override
68 public Set<String> getSchemes() {
69 return schemeSet;
70 }
71
72 @Override
73 public Set<URIishField> getRequiredFields() {
74 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
75 URIishField.PATH));
76 }
77
78 @Override
79 public Set<URIishField> getOptionalFields() {
80 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
81 URIishField.PASS, URIishField.PORT));
82 }
83
84 @Override
85 public int getDefaultPort() {
86 return 22;
87 }
88
89 @Override
90 public boolean canHandle(URIish uri, Repository local, String remoteName) {
91 if (uri.getScheme() == null) {
92
93 return uri.getHost() != null
94 && uri.getPath() != null
95 && uri.getHost().length() != 0
96 && uri.getPath().length() != 0;
97 }
98 return super.canHandle(uri, local, remoteName);
99 }
100
101 @Override
102 public Transport open(URIish uri, Repository local, String remoteName)
103 throws NotSupportedException {
104 return new TransportGitSsh(local, uri);
105 }
106
107 @Override
108 public Transport open(URIish uri) throws NotSupportedException, TransportException {
109 return new TransportGitSsh(uri);
110 }
111 };
112
113 TransportGitSsh(Repository local, URIish uri) {
114 super(local, uri);
115 initSshSessionFactory();
116 }
117
118 TransportGitSsh(URIish uri) {
119 super(uri);
120 initSshSessionFactory();
121 }
122
123 private void initSshSessionFactory() {
124 if (useExtSession()) {
125 setSshSessionFactory(new SshSessionFactory() {
126 @Override
127 public RemoteSession getSession(URIish uri2,
128 CredentialsProvider credentialsProvider, FS fs, int tms)
129 throws TransportException {
130 return new ExtSession();
131 }
132
133 @Override
134 public String getType() {
135 return EXT;
136 }
137 });
138 }
139 }
140
141
142 @Override
143 public FetchConnection openFetch() throws TransportException {
144 return new SshFetchConnection();
145 }
146
147
148 @Override
149 public PushConnection openPush() throws TransportException {
150 return new SshPushConnection();
151 }
152
153 String commandFor(String exe) {
154 String path = uri.getPath();
155 if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
156 path = (uri.getPath().substring(1));
157
158 final StringBuilder cmd = new StringBuilder();
159 cmd.append(exe);
160 cmd.append(' ');
161 cmd.append(QuotedString.BOURNE.quote(path));
162 return cmd.toString();
163 }
164
165 void checkExecFailure(int status, String exe, String why)
166 throws TransportException {
167 if (status == 127) {
168 IOException cause = null;
169 if (why != null && why.length() > 0)
170 cause = new IOException(why);
171 throw new TransportException(uri, MessageFormat.format(
172 JGitText.get().cannotExecute, commandFor(exe)), cause);
173 }
174 }
175
176 NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf,
177 String why) {
178 if (why == null || why.length() == 0)
179 return nf;
180
181 String path = uri.getPath();
182 if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
183 path = uri.getPath().substring(1);
184
185 final StringBuilder pfx = new StringBuilder();
186 pfx.append("fatal: ");
187 pfx.append(QuotedString.BOURNE.quote(path));
188 pfx.append(": ");
189 if (why.startsWith(pfx.toString()))
190 why = why.substring(pfx.length());
191
192 return new NoRemoteRepositoryException(uri, why);
193 }
194
195 private static boolean useExtSession() {
196 return SystemReader.getInstance().getenv("GIT_SSH") != null;
197 }
198
199 private class ExtSession implements RemoteSession {
200 @Override
201 public Process exec(String command, int timeout)
202 throws TransportException {
203 String ssh = SystemReader.getInstance().getenv("GIT_SSH");
204 boolean putty = ssh.toLowerCase(Locale.ROOT).contains("plink");
205
206 List<String> args = new ArrayList<>();
207 args.add(ssh);
208 if (putty
209 && !ssh.toLowerCase(Locale.ROOT).contains("tortoiseplink"))
210 args.add("-batch");
211 if (0 < getURI().getPort()) {
212 args.add(putty ? "-P" : "-p");
213 args.add(String.valueOf(getURI().getPort()));
214 }
215 if (getURI().getUser() != null)
216 args.add(getURI().getUser() + "@" + getURI().getHost());
217 else
218 args.add(getURI().getHost());
219 args.add(command);
220
221 ProcessBuilder pb = createProcess(args);
222 try {
223 return pb.start();
224 } catch (IOException err) {
225 throw new TransportException(err.getMessage(), err);
226 }
227 }
228
229 private ProcessBuilder createProcess(List<String> args) {
230 ProcessBuilder pb = new ProcessBuilder();
231 pb.command(args);
232 File directory = local != null ? local.getDirectory() : null;
233 if (directory != null) {
234 pb.environment().put(Constants.GIT_DIR_KEY,
235 directory.getPath());
236 }
237 return pb;
238 }
239
240 @Override
241 public void disconnect() {
242
243 }
244 }
245
246 class SshFetchConnection extends BasePackFetchConnection {
247 private final Process process;
248
249 private StreamCopyThread errorThread;
250
251 SshFetchConnection() throws TransportException {
252 super(TransportGitSsh.this);
253 try {
254 process = getSession().exec(commandFor(getOptionUploadPack()),
255 getTimeout());
256 final MessageWriterWriter.html#MessageWriter">MessageWriter msg = new MessageWriter();
257 setMessageWriter(msg);
258
259 final InputStream upErr = process.getErrorStream();
260 errorThread = new StreamCopyThread(upErr, msg.getRawStream());
261 errorThread.start();
262
263 init(process.getInputStream(), process.getOutputStream());
264
265 } catch (TransportException err) {
266 close();
267 throw err;
268 } catch (Throwable err) {
269 close();
270 throw new TransportException(uri,
271 JGitText.get().remoteHungUpUnexpectedly, err);
272 }
273
274 try {
275 readAdvertisedRefs();
276 } catch (NoRemoteRepositoryException notFound) {
277 final String msgs = getMessages();
278 checkExecFailure(process.exitValue(), getOptionUploadPack(),
279 msgs);
280 throw cleanNotFound(notFound, msgs);
281 }
282 }
283
284 @Override
285 public void close() {
286 endOut();
287
288 if (process != null) {
289 process.destroy();
290 }
291 if (errorThread != null) {
292 try {
293 errorThread.halt();
294 } catch (InterruptedException e) {
295
296 } finally {
297 errorThread = null;
298 }
299 }
300
301 super.close();
302 }
303 }
304
305 class SshPushConnection extends BasePackPushConnection {
306 private final Process process;
307
308 private StreamCopyThread errorThread;
309
310 SshPushConnection() throws TransportException {
311 super(TransportGitSsh.this);
312 try {
313 process = getSession().exec(commandFor(getOptionReceivePack()),
314 getTimeout());
315 final MessageWriterWriter.html#MessageWriter">MessageWriter msg = new MessageWriter();
316 setMessageWriter(msg);
317
318 final InputStream rpErr = process.getErrorStream();
319 errorThread = new StreamCopyThread(rpErr, msg.getRawStream());
320 errorThread.start();
321
322 init(process.getInputStream(), process.getOutputStream());
323
324 } catch (TransportException err) {
325 try {
326 close();
327 } catch (Exception e) {
328
329 }
330 throw err;
331 } catch (Throwable err) {
332 try {
333 close();
334 } catch (Exception e) {
335
336 }
337 throw new TransportException(uri,
338 JGitText.get().remoteHungUpUnexpectedly, err);
339 }
340
341 try {
342 readAdvertisedRefs();
343 } catch (NoRemoteRepositoryException notFound) {
344 final String msgs = getMessages();
345 checkExecFailure(process.exitValue(), getOptionReceivePack(),
346 msgs);
347 throw cleanNotFound(notFound, msgs);
348 }
349 }
350
351 @Override
352 public void close() {
353 endOut();
354
355 if (process != null) {
356 process.destroy();
357 }
358 if (errorThread != null) {
359 try {
360 errorThread.halt();
361 } catch (InterruptedException e) {
362
363 } finally {
364 errorThread = null;
365 }
366 }
367
368 super.close();
369 }
370 }
371 }