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