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