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