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