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 package org.eclipse.jgit.transport;
45
46 import java.io.BufferedReader;
47 import java.io.FileNotFoundException;
48 import java.io.IOException;
49 import java.io.OutputStream;
50 import java.text.MessageFormat;
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.Collections;
54 import java.util.Comparator;
55 import java.util.EnumSet;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Set;
60 import java.util.TreeMap;
61
62 import org.eclipse.jgit.errors.NotSupportedException;
63 import org.eclipse.jgit.errors.TransportException;
64 import org.eclipse.jgit.internal.JGitText;
65 import org.eclipse.jgit.lib.Constants;
66 import org.eclipse.jgit.lib.ObjectId;
67 import org.eclipse.jgit.lib.ObjectIdRef;
68 import org.eclipse.jgit.lib.ProgressMonitor;
69 import org.eclipse.jgit.lib.Ref;
70 import org.eclipse.jgit.lib.Ref.Storage;
71 import org.eclipse.jgit.lib.Repository;
72 import org.eclipse.jgit.lib.SymbolicRef;
73
74 import com.jcraft.jsch.Channel;
75 import com.jcraft.jsch.ChannelSftp;
76 import com.jcraft.jsch.JSchException;
77 import com.jcraft.jsch.SftpATTRS;
78 import com.jcraft.jsch.SftpException;
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 public class TransportSftp extends SshTransport implements WalkTransport {
102 static final TransportProtocol PROTO_SFTP = new TransportProtocol() {
103 @Override
104 public String getName() {
105 return JGitText.get().transportProtoSFTP;
106 }
107
108 @Override
109 public Set<String> getSchemes() {
110 return Collections.singleton("sftp");
111 }
112
113 @Override
114 public Set<URIishField> getRequiredFields() {
115 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
116 URIishField.PATH));
117 }
118
119 @Override
120 public Set<URIishField> getOptionalFields() {
121 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
122 URIishField.PASS, URIishField.PORT));
123 }
124
125 @Override
126 public int getDefaultPort() {
127 return 22;
128 }
129
130 @Override
131 public Transport open(URIish uri, Repository local, String remoteName)
132 throws NotSupportedException {
133 return new TransportSftp(local, uri);
134 }
135 };
136
137 TransportSftp(final Repository local, final URIish uri) {
138 super(local, uri);
139 }
140
141
142 @Override
143 public FetchConnection openFetch() throws TransportException {
144 final SftpObjectDB c = new SftpObjectDB(uri.getPath());
145 final WalkFetchConnection r = new WalkFetchConnection(this, c);
146 r.available(c.readAdvertisedRefs());
147 return r;
148 }
149
150
151 @Override
152 public PushConnection openPush() throws TransportException {
153 final SftpObjectDB c = new SftpObjectDB(uri.getPath());
154 final WalkPushConnection r = new WalkPushConnection(this, c);
155 r.available(c.readAdvertisedRefs());
156 return r;
157 }
158
159 ChannelSftp newSftp() throws TransportException {
160 final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
161 try {
162
163
164 final Channel channel = ((JschSession) getSession())
165 .getSftpChannel();
166 channel.connect(tms);
167 return (ChannelSftp) channel;
168 } catch (JSchException je) {
169 throw new TransportException(uri, je.getMessage(), je);
170 }
171 }
172
173 class SftpObjectDB extends WalkRemoteObjectDatabase {
174 private final String objectsPath;
175
176 private ChannelSftp ftp;
177
178 SftpObjectDB(String path) throws TransportException {
179 if (path.startsWith("/~"))
180 path = path.substring(1);
181 if (path.startsWith("~/"))
182 path = path.substring(2);
183 try {
184 ftp = newSftp();
185 ftp.cd(path);
186 ftp.cd("objects");
187 objectsPath = ftp.pwd();
188 } catch (TransportException err) {
189 close();
190 throw err;
191 } catch (SftpException je) {
192 throw new TransportException(MessageFormat.format(
193 JGitText.get().cannotEnterObjectsPath, path,
194 je.getMessage()), je);
195 }
196 }
197
198 SftpObjectDB(final SftpObjectDB parent, final String p)
199 throws TransportException {
200 try {
201 ftp = newSftp();
202 ftp.cd(parent.objectsPath);
203 ftp.cd(p);
204 objectsPath = ftp.pwd();
205 } catch (TransportException err) {
206 close();
207 throw err;
208 } catch (SftpException je) {
209 throw new TransportException(MessageFormat.format(
210 JGitText.get().cannotEnterPathFromParent, p,
211 parent.objectsPath, je.getMessage()), je);
212 }
213 }
214
215 @Override
216 URIish getURI() {
217 return uri.setPath(objectsPath);
218 }
219
220 @Override
221 Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
222 try {
223 return readAlternates(INFO_ALTERNATES);
224 } catch (FileNotFoundException err) {
225 return null;
226 }
227 }
228
229 @Override
230 WalkRemoteObjectDatabase openAlternate(final String location)
231 throws IOException {
232 return new SftpObjectDB(this, location);
233 }
234
235 @Override
236 Collection<String> getPackNames() throws IOException {
237 final List<String> packs = new ArrayList<>();
238 try {
239 @SuppressWarnings("unchecked")
240 final Collection<ChannelSftp.LsEntry> list = ftp.ls("pack");
241 final HashMap<String, ChannelSftp.LsEntry> files;
242 final HashMap<String, Integer> mtimes;
243
244 files = new HashMap<>();
245 mtimes = new HashMap<>();
246
247 for (final ChannelSftp.LsEntry ent : list)
248 files.put(ent.getFilename(), ent);
249 for (final ChannelSftp.LsEntry ent : list) {
250 final String n = ent.getFilename();
251 if (!n.startsWith("pack-") || !n.endsWith(".pack"))
252 continue;
253
254 final String in = n.substring(0, n.length() - 5) + ".idx";
255 if (!files.containsKey(in))
256 continue;
257
258 mtimes.put(n, Integer.valueOf(ent.getAttrs().getMTime()));
259 packs.add(n);
260 }
261
262 Collections.sort(packs, new Comparator<String>() {
263 @Override
264 public int compare(final String o1, final String o2) {
265 return mtimes.get(o2).intValue()
266 - mtimes.get(o1).intValue();
267 }
268 });
269 } catch (SftpException je) {
270 throw new TransportException(
271 MessageFormat.format(JGitText.get().cannotListPackPath,
272 objectsPath, je.getMessage()),
273 je);
274 }
275 return packs;
276 }
277
278 @Override
279 FileStream open(final String path) throws IOException {
280 try {
281 final SftpATTRS a = ftp.lstat(path);
282 return new FileStream(ftp.get(path), a.getSize());
283 } catch (SftpException je) {
284 if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
285 throw new FileNotFoundException(path);
286 throw new TransportException(MessageFormat.format(
287 JGitText.get().cannotGetObjectsPath, objectsPath, path,
288 je.getMessage()), je);
289 }
290 }
291
292 @Override
293 void deleteFile(final String path) throws IOException {
294 try {
295 ftp.rm(path);
296 } catch (SftpException je) {
297 if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
298 return;
299 throw new TransportException(MessageFormat.format(
300 JGitText.get().cannotDeleteObjectsPath, objectsPath,
301 path, je.getMessage()), je);
302 }
303
304
305
306 String dir = path;
307 int s = dir.lastIndexOf('/');
308 while (s > 0) {
309 try {
310 dir = dir.substring(0, s);
311 ftp.rmdir(dir);
312 s = dir.lastIndexOf('/');
313 } catch (SftpException je) {
314
315
316
317
318 break;
319 }
320 }
321 }
322
323 @Override
324 OutputStream writeFile(final String path,
325 final ProgressMonitor monitor, final String monitorTask)
326 throws IOException {
327 try {
328 return ftp.put(path);
329 } catch (SftpException je) {
330 if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
331 mkdir_p(path);
332 try {
333 return ftp.put(path);
334 } catch (SftpException je2) {
335 je = je2;
336 }
337 }
338
339 throw new TransportException(MessageFormat.format(
340 JGitText.get().cannotWriteObjectsPath, objectsPath,
341 path, je.getMessage()), je);
342 }
343 }
344
345 @Override
346 void writeFile(final String path, final byte[] data) throws IOException {
347 final String lock = path + ".lock";
348 try {
349 super.writeFile(lock, data);
350 try {
351 ftp.rename(lock, path);
352 } catch (SftpException je) {
353 throw new TransportException(MessageFormat.format(
354 JGitText.get().cannotWriteObjectsPath, objectsPath,
355 path, je.getMessage()), je);
356 }
357 } catch (IOException err) {
358 try {
359 ftp.rm(lock);
360 } catch (SftpException e) {
361
362
363 }
364 throw err;
365 }
366 }
367
368 private void mkdir_p(String path) throws IOException {
369 final int s = path.lastIndexOf('/');
370 if (s <= 0)
371 return;
372
373 path = path.substring(0, s);
374 try {
375 ftp.mkdir(path);
376 } catch (SftpException je) {
377 if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
378 mkdir_p(path);
379 try {
380 ftp.mkdir(path);
381 return;
382 } catch (SftpException je2) {
383 je = je2;
384 }
385 }
386
387 throw new TransportException(MessageFormat.format(
388 JGitText.get().cannotMkdirObjectPath, objectsPath, path,
389 je.getMessage()), je);
390 }
391 }
392
393 Map<String, Ref> readAdvertisedRefs() throws TransportException {
394 final TreeMap<String, Ref> avail = new TreeMap<>();
395 readPackedRefs(avail);
396 readRef(avail, ROOT_DIR + Constants.HEAD, Constants.HEAD);
397 readLooseRefs(avail, ROOT_DIR + "refs", "refs/");
398 return avail;
399 }
400
401 @SuppressWarnings("unchecked")
402 private void readLooseRefs(final TreeMap<String, Ref> avail,
403 final String dir, final String prefix)
404 throws TransportException {
405 final Collection<ChannelSftp.LsEntry> list;
406 try {
407 list = ftp.ls(dir);
408 } catch (SftpException je) {
409 throw new TransportException(MessageFormat.format(
410 JGitText.get().cannotListObjectsPath, objectsPath, dir,
411 je.getMessage()), je);
412 }
413
414 for (final ChannelSftp.LsEntry ent : list) {
415 final String n = ent.getFilename();
416 if (".".equals(n) || "..".equals(n))
417 continue;
418
419 final String nPath = dir + "/" + n;
420 if (ent.getAttrs().isDir())
421 readLooseRefs(avail, nPath, prefix + n + "/");
422 else
423 readRef(avail, nPath, prefix + n);
424 }
425 }
426
427 private Ref readRef(final TreeMap<String, Ref> avail,
428 final String path, final String name) throws TransportException {
429 final String line;
430 try {
431 final BufferedReader br = openReader(path);
432 try {
433 line = br.readLine();
434 } finally {
435 br.close();
436 }
437 } catch (FileNotFoundException noRef) {
438 return null;
439 } catch (IOException err) {
440 throw new TransportException(MessageFormat.format(
441 JGitText.get().cannotReadObjectsPath, objectsPath, path,
442 err.getMessage()), err);
443 }
444
445 if (line == null)
446 throw new TransportException(
447 MessageFormat.format(JGitText.get().emptyRef, name));
448
449 if (line.startsWith("ref: ")) {
450 final String target = line.substring("ref: ".length());
451 Ref r = avail.get(target);
452 if (r == null)
453 r = readRef(avail, ROOT_DIR + target, target);
454 if (r == null)
455 r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
456 r = new SymbolicRef(name, r);
457 avail.put(r.getName(), r);
458 return r;
459 }
460
461 if (ObjectId.isId(line)) {
462 final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(name)),
463 name, ObjectId.fromString(line));
464 avail.put(r.getName(), r);
465 return r;
466 }
467
468 throw new TransportException(
469 MessageFormat.format(JGitText.get().badRef, name, line));
470 }
471
472 private Storage loose(final Ref r) {
473 if (r != null && r.getStorage() == Storage.PACKED)
474 return Storage.LOOSE_PACKED;
475 return Storage.LOOSE;
476 }
477
478 @Override
479 void close() {
480 if (ftp != null) {
481 try {
482 if (ftp.isConnected())
483 ftp.disconnect();
484 } finally {
485 ftp = null;
486 }
487 }
488 }
489 }
490 }