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