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