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