1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.api;
11
12 import java.io.Closeable;
13 import java.io.IOException;
14 import java.io.OutputStream;
15 import java.text.MessageFormat;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.ConcurrentMap;
23
24 import org.eclipse.jgit.api.errors.GitAPIException;
25 import org.eclipse.jgit.api.errors.JGitInternalException;
26 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
27 import org.eclipse.jgit.internal.JGitText;
28 import org.eclipse.jgit.lib.Constants;
29 import org.eclipse.jgit.lib.FileMode;
30 import org.eclipse.jgit.lib.MutableObjectId;
31 import org.eclipse.jgit.lib.ObjectId;
32 import org.eclipse.jgit.lib.ObjectLoader;
33 import org.eclipse.jgit.lib.ObjectReader;
34 import org.eclipse.jgit.lib.Repository;
35 import org.eclipse.jgit.revwalk.RevCommit;
36 import org.eclipse.jgit.revwalk.RevObject;
37 import org.eclipse.jgit.revwalk.RevTree;
38 import org.eclipse.jgit.revwalk.RevWalk;
39 import org.eclipse.jgit.treewalk.TreeWalk;
40 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 public class ArchiveCommand extends GitCommand<OutputStream> {
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 public static interface Format<T extends Closeable> {
95
96
97
98
99
100
101
102
103
104
105
106 T createArchiveOutputStream(OutputStream s) throws IOException;
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 T createArchiveOutputStream(OutputStream s, Map<String, Object> o)
125 throws IOException;
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 void putEntry(T out, ObjectId tree, String path, FileMode mode,
148 ObjectLoader loader) throws IOException;
149
150
151
152
153
154
155
156
157
158
159
160 Iterable<String> suffixes();
161 }
162
163
164
165
166
167 public static class UnsupportedFormatException extends GitAPIException {
168 private static final long serialVersionUID = 1L;
169
170 private final String format;
171
172
173
174
175 public UnsupportedFormatException(String format) {
176 super(MessageFormat.format(JGitText.get().unsupportedArchiveFormat, format));
177 this.format = format;
178 }
179
180
181
182
183 public String getFormat() {
184 return format;
185 }
186 }
187
188 private static class FormatEntry {
189 final Format<?> format;
190
191 final int refcnt;
192
193 public FormatEntry(Format<?> format, int refcnt) {
194 if (format == null)
195 throw new NullPointerException();
196 this.format = format;
197 this.refcnt = refcnt;
198 }
199 }
200
201
202
203
204
205 private static final ConcurrentMap<String, FormatEntry> formats =
206 new ConcurrentHashMap<>();
207
208
209
210
211
212
213
214
215
216
217
218 private static <K, V> boolean replace(ConcurrentMap<K, V> map,
219 K key, V oldValue, V newValue) {
220 if (oldValue == null && newValue == null)
221 return true;
222
223 if (oldValue == null)
224 return map.putIfAbsent(key, newValue) == null;
225 else if (newValue == null)
226 return map.remove(key, oldValue);
227 else
228 return map.replace(key, oldValue, newValue);
229 }
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253 public static void registerFormat(String name, Format<?> fmt) {
254 if (fmt == null)
255 throw new NullPointerException();
256
257 FormatEntry old, entry;
258 do {
259 old = formats.get(name);
260 if (old == null) {
261 entry = new FormatEntry(fmt, 1);
262 continue;
263 }
264 if (!old.format.equals(fmt))
265 throw new JGitInternalException(MessageFormat.format(
266 JGitText.get().archiveFormatAlreadyRegistered,
267 name));
268 entry = new FormatEntry(old.format, old.refcnt + 1);
269 } while (!replace(formats, name, old, entry));
270 }
271
272
273
274
275
276
277
278
279
280
281
282
283
284 public static void unregisterFormat(String name) {
285 FormatEntry old, entry;
286 do {
287 old = formats.get(name);
288 if (old == null)
289 throw new JGitInternalException(MessageFormat.format(
290 JGitText.get().archiveFormatAlreadyAbsent,
291 name));
292 if (old.refcnt == 1) {
293 entry = null;
294 continue;
295 }
296 entry = new FormatEntry(old.format, old.refcnt - 1);
297 } while (!replace(formats, name, old, entry));
298 }
299
300 private static Format<?> formatBySuffix(String filenameSuffix)
301 throws UnsupportedFormatException {
302 if (filenameSuffix != null)
303 for (FormatEntry entry : formats.values()) {
304 Format<?> fmt = entry.format;
305 for (String sfx : fmt.suffixes())
306 if (filenameSuffix.endsWith(sfx))
307 return fmt;
308 }
309 return lookupFormat("tar");
310 }
311
312 private static Format<?> lookupFormat(String formatName) throws UnsupportedFormatException {
313 FormatEntry entry = formats.get(formatName);
314 if (entry == null)
315 throw new UnsupportedFormatException(formatName);
316 return entry.format;
317 }
318
319 private OutputStream out;
320 private ObjectId tree;
321 private String prefix;
322 private String format;
323 private Map<String, Object> formatOptions = new HashMap<>();
324 private List<String> paths = new ArrayList<>();
325
326
327 private String suffix;
328
329
330
331
332
333
334
335 public ArchiveCommand(Repository repo) {
336 super(repo);
337 setCallable(false);
338 }
339
340 private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
341 try {
342 try (TreeWalkeeWalk.html#TreeWalk">TreeWalk walk = new TreeWalk(repo);
343 RevWalk rw = new RevWalk(walk.getObjectReader());
344 T outa = fmt.createArchiveOutputStream(out,
345 formatOptions)) {
346 String pfx = prefix == null ? "" : prefix;
347 MutableObjectId idBuf = new MutableObjectId();
348 ObjectReader reader = walk.getObjectReader();
349
350 RevObject o = rw.peel(rw.parseAny(tree));
351 walk.reset(getTree(o));
352 if (!paths.isEmpty()) {
353 walk.setFilter(PathFilterGroup.createFromStrings(paths));
354 }
355
356
357 if (pfx.endsWith("/")) {
358 fmt.putEntry(outa, o, pfx.replaceAll("[/]+$", "/"),
359 FileMode.TREE, null);
360 }
361
362 while (walk.next()) {
363 String name = pfx + walk.getPathString();
364 FileMode mode = walk.getFileMode(0);
365
366 if (walk.isSubtree())
367 walk.enterSubtree();
368
369 if (mode == FileMode.GITLINK) {
370
371
372 mode = FileMode.TREE;
373 }
374
375 if (mode == FileMode.TREE) {
376 fmt.putEntry(outa, o, name + "/", mode, null);
377 continue;
378 }
379 walk.getObjectId(idBuf, 0);
380 fmt.putEntry(outa, o, name, mode, reader.open(idBuf));
381 }
382 return out;
383 } finally {
384 out.close();
385 }
386 } catch (IOException e) {
387
388 throw new JGitInternalException(
389 JGitText.get().exceptionCaughtDuringExecutionOfArchiveCommand, e);
390 }
391 }
392
393
394 @Override
395 public OutputStream call() throws GitAPIException {
396 checkCallable();
397
398 Format<?> fmt;
399 if (format == null)
400 fmt = formatBySuffix(suffix);
401 else
402 fmt = lookupFormat(format);
403 return writeArchive(fmt);
404 }
405
406
407
408
409
410
411
412
413 public ArchiveCommand setTree(ObjectId tree) {
414 if (tree == null)
415 throw new IllegalArgumentException();
416
417 this.tree = tree;
418 setCallable(true);
419 return this;
420 }
421
422
423
424
425
426
427
428
429
430
431 public ArchiveCommand setPrefix(String prefix) {
432 this.prefix = prefix;
433 return this;
434 }
435
436
437
438
439
440
441
442
443
444
445 public ArchiveCommand setFilename(String filename) {
446 int slash = filename.lastIndexOf('/');
447 int dot = filename.indexOf('.', slash + 1);
448
449 if (dot == -1)
450 this.suffix = "";
451 else
452 this.suffix = filename.substring(dot);
453 return this;
454 }
455
456
457
458
459
460
461
462
463 public ArchiveCommand setOutputStream(OutputStream out) {
464 this.out = out;
465 return this;
466 }
467
468
469
470
471
472
473
474
475
476 public ArchiveCommand setFormat(String fmt) {
477 this.format = fmt;
478 return this;
479 }
480
481
482
483
484
485
486
487
488
489 public ArchiveCommand setFormatOptions(Map<String, Object> options) {
490 this.formatOptions = options;
491 return this;
492 }
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508 public ArchiveCommand setPaths(String... paths) {
509 this.paths = Arrays.asList(paths);
510 return this;
511 }
512
513 private RevTree getTree(RevObject o)
514 throws IncorrectObjectTypeException {
515 final RevTree t;
516 if (o instanceof RevCommit) {
517 t = ((RevCommit) o).getTree();
518 } else if (!(o instanceof RevTree)) {
519 throw new IncorrectObjectTypeException(tree.toObjectId(),
520 Constants.TYPE_TREE);
521 } else {
522 t = (RevTree) o;
523 }
524 return t;
525 }
526
527 }