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