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(final Repository local, final URIish uri) {
145 super(local, uri);
146 initSshSessionFactory();
147 }
148
149 TransportGitSsh(final 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 @Override
168 public FetchConnection openFetch() throws TransportException {
169 return new SshFetchConnection();
170 }
171
172 @Override
173 public PushConnection openPush() throws TransportException {
174 return new SshPushConnection();
175 }
176
177 String commandFor(final String exe) {
178 String path = uri.getPath();
179 if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
180 path = (uri.getPath().substring(1));
181
182 final StringBuilder cmd = new StringBuilder();
183 cmd.append(exe);
184 cmd.append(' ');
185 cmd.append(QuotedString.BOURNE.quote(path));
186 return cmd.toString();
187 }
188
189 void checkExecFailure(int status, String exe, String why)
190 throws TransportException {
191 if (status == 127) {
192 IOException cause = null;
193 if (why != null && why.length() > 0)
194 cause = new IOException(why);
195 throw new TransportException(uri, MessageFormat.format(
196 JGitText.get().cannotExecute, commandFor(exe)), cause);
197 }
198 }
199
200 NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf,
201 String why) {
202 if (why == null || why.length() == 0)
203 return nf;
204
205 String path = uri.getPath();
206 if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
207 path = uri.getPath().substring(1);
208
209 final StringBuilder pfx = new StringBuilder();
210 pfx.append("fatal: ");
211 pfx.append(QuotedString.BOURNE.quote(path));
212 pfx.append(": ");
213 if (why.startsWith(pfx.toString()))
214 why = why.substring(pfx.length());
215
216 return new NoRemoteRepositoryException(uri, why);
217 }
218
219 private static boolean useExtSession() {
220 return SystemReader.getInstance().getenv("GIT_SSH") != null;
221 }
222
223 private class ExtSession implements RemoteSession {
224 @Override
225 public Process exec(String command, int timeout)
226 throws TransportException {
227 String ssh = SystemReader.getInstance().getenv("GIT_SSH");
228 boolean putty = ssh.toLowerCase(Locale.ROOT).contains("plink");
229
230 List<String> args = new ArrayList<>();
231 args.add(ssh);
232 if (putty
233 && !ssh.toLowerCase(Locale.ROOT).contains("tortoiseplink"))
234 args.add("-batch");
235 if (0 < getURI().getPort()) {
236 args.add(putty ? "-P" : "-p");
237 args.add(String.valueOf(getURI().getPort()));
238 }
239 if (getURI().getUser() != null)
240 args.add(getURI().getUser() + "@" + getURI().getHost());
241 else
242 args.add(getURI().getHost());
243 args.add(command);
244
245 ProcessBuilder pb = new ProcessBuilder();
246 pb.command(args);
247
248 File directory = local.getDirectory();
249 if (directory != null)
250 pb.environment().put(Constants.GIT_DIR_KEY,
251 directory.getPath());
252
253 try {
254 return pb.start();
255 } catch (IOException err) {
256 throw new TransportException(err.getMessage(), err);
257 }
258 }
259
260 @Override
261 public void disconnect() {
262
263 }
264 }
265
266 class SshFetchConnection extends BasePackFetchConnection {
267 private final Process process;
268
269 private StreamCopyThread errorThread;
270
271 SshFetchConnection() throws TransportException {
272 super(TransportGitSsh.this);
273 try {
274 process = getSession().exec(commandFor(getOptionUploadPack()),
275 getTimeout());
276 final MessageWriter msg = new MessageWriter();
277 setMessageWriter(msg);
278
279 final InputStream upErr = process.getErrorStream();
280 errorThread = new StreamCopyThread(upErr, msg.getRawStream());
281 errorThread.start();
282
283 init(process.getInputStream(), process.getOutputStream());
284
285 } catch (TransportException err) {
286 close();
287 throw err;
288 } catch (IOException err) {
289 close();
290 throw new TransportException(uri,
291 JGitText.get().remoteHungUpUnexpectedly, err);
292 }
293
294 try {
295 readAdvertisedRefs();
296 } catch (NoRemoteRepositoryException notFound) {
297 final String msgs = getMessages();
298 checkExecFailure(process.exitValue(), getOptionUploadPack(),
299 msgs);
300 throw cleanNotFound(notFound, msgs);
301 }
302 }
303
304 @Override
305 public void close() {
306 endOut();
307
308 if (errorThread != null) {
309 try {
310 errorThread.halt();
311 } catch (InterruptedException e) {
312
313 } finally {
314 errorThread = null;
315 }
316 }
317
318 super.close();
319 if (process != null)
320 process.destroy();
321 }
322 }
323
324 class SshPushConnection extends BasePackPushConnection {
325 private final Process process;
326
327 private StreamCopyThread errorThread;
328
329 SshPushConnection() throws TransportException {
330 super(TransportGitSsh.this);
331 try {
332 process = getSession().exec(commandFor(getOptionReceivePack()),
333 getTimeout());
334 final MessageWriter msg = new MessageWriter();
335 setMessageWriter(msg);
336
337 final InputStream rpErr = process.getErrorStream();
338 errorThread = new StreamCopyThread(rpErr, msg.getRawStream());
339 errorThread.start();
340
341 init(process.getInputStream(), process.getOutputStream());
342
343 } catch (TransportException err) {
344 close();
345 throw err;
346 } catch (IOException err) {
347 close();
348 throw new TransportException(uri,
349 JGitText.get().remoteHungUpUnexpectedly, err);
350 }
351
352 try {
353 readAdvertisedRefs();
354 } catch (NoRemoteRepositoryException notFound) {
355 final String msgs = getMessages();
356 checkExecFailure(process.exitValue(), getOptionReceivePack(),
357 msgs);
358 throw cleanNotFound(notFound, msgs);
359 }
360 }
361
362 @Override
363 public void close() {
364 endOut();
365
366 if (errorThread != null) {
367 try {
368 errorThread.halt();
369 } catch (InterruptedException e) {
370
371 } finally {
372 errorThread = null;
373 }
374 }
375
376 super.close();
377 if (process != null)
378 process.destroy();
379 }
380 }
381 }