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 static java.nio.charset.StandardCharsets.UTF_8;
46 import static org.junit.Assert.assertEquals;
47 import static org.junit.Assert.assertNull;
48
49 import java.beans.Statement;
50 import java.io.BufferedInputStream;
51 import java.io.File;
52 import java.io.FileNotFoundException;
53 import java.io.FileOutputStream;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.OutputStream;
57 import java.nio.file.Files;
58 import java.util.Arrays;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.List;
62 import java.util.Map;
63
64 import org.apache.commons.compress.archivers.ArchiveEntry;
65 import org.apache.commons.compress.archivers.ArchiveInputStream;
66 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
67 import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
68 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
69 import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
70 import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
71 import org.eclipse.jgit.api.errors.AbortedByHookException;
72 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
73 import org.eclipse.jgit.api.errors.GitAPIException;
74 import org.eclipse.jgit.api.errors.NoFilepatternException;
75 import org.eclipse.jgit.api.errors.NoHeadException;
76 import org.eclipse.jgit.api.errors.NoMessageException;
77 import org.eclipse.jgit.api.errors.UnmergedPathsException;
78 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
79 import org.eclipse.jgit.archive.ArchiveFormats;
80 import org.eclipse.jgit.errors.AmbiguousObjectException;
81 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
82 import org.eclipse.jgit.junit.RepositoryTestCase;
83 import org.eclipse.jgit.lib.FileMode;
84 import org.eclipse.jgit.lib.ObjectId;
85 import org.eclipse.jgit.lib.ObjectLoader;
86 import org.eclipse.jgit.revwalk.RevCommit;
87 import org.eclipse.jgit.util.IO;
88 import org.eclipse.jgit.util.StringUtils;
89 import org.junit.After;
90 import org.junit.Before;
91 import org.junit.Test;
92
93 public class ArchiveCommandTest extends RepositoryTestCase {
94
95
96 private static final int WAIT = 2000;
97 private static final String UNEXPECTED_ARCHIVE_SIZE = "Unexpected archive size";
98 private static final String UNEXPECTED_FILE_CONTENTS = "Unexpected file contents";
99 private static final String UNEXPECTED_TREE_CONTENTS = "Unexpected tree contents";
100 private static final String UNEXPECTED_LAST_MODIFIED =
101 "Unexpected lastModified mocked by MockSystemReader, truncated to 1 second";
102 private static final String UNEXPECTED_DIFFERENT_HASH = "Unexpected different hash";
103
104 private MockFormat format = null;
105
106 @Before
107 public void setup() {
108 format = new MockFormat();
109 ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format);
110 ArchiveFormats.registerAll();
111 }
112
113 @Override
114 @After
115 public void tearDown() {
116 ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0));
117 ArchiveFormats.unregisterAll();
118 }
119
120 @Test
121 public void archiveHeadAllFiles() throws IOException, GitAPIException {
122 try (Git git = new Git(db)) {
123 createTestContent(git);
124
125 git.archive().setOutputStream(new MockOutputStream())
126 .setFormat(format.SUFFIXES.get(0))
127 .setTree(git.getRepository().resolve("HEAD")).call();
128
129 assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size());
130 assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath("file_1.txt"));
131 assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath("file_2.txt"));
132 }
133 }
134
135 @Test
136 public void archiveHeadSpecificPath() throws IOException, GitAPIException {
137 try (Git git = new Git(db)) {
138 writeTrashFile("file_1.txt", "content_1_1");
139 git.add().addFilepattern("file_1.txt").call();
140 git.commit().setMessage("create file").call();
141
142 writeTrashFile("file_1.txt", "content_1_2");
143 String expectedFilePath = "some_directory/file_2.txt";
144 writeTrashFile(expectedFilePath, "content_2_2");
145 git.add().addFilepattern(".").call();
146 git.commit().setMessage("updated file").call();
147
148 git.archive().setOutputStream(new MockOutputStream())
149 .setFormat(format.SUFFIXES.get(0))
150 .setTree(git.getRepository().resolve("HEAD"))
151 .setPaths(expectedFilePath).call();
152
153 assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size());
154 assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath));
155 assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory"));
156 }
157 }
158
159 @Test
160 public void archiveByIdSpecificFile() throws IOException, GitAPIException {
161 try (Git git = new Git(db)) {
162 writeTrashFile("file_1.txt", "content_1_1");
163 git.add().addFilepattern("file_1.txt").call();
164 RevCommit first = git.commit().setMessage("create file").call();
165
166 writeTrashFile("file_1.txt", "content_1_2");
167 String expectedFilePath = "some_directory/file_2.txt";
168 writeTrashFile(expectedFilePath, "content_2_2");
169 git.add().addFilepattern(".").call();
170 git.commit().setMessage("updated file").call();
171
172 Map<String, Object> options = new HashMap<>();
173 Integer opt = Integer.valueOf(42);
174 options.put("foo", opt);
175 MockOutputStream out = new MockOutputStream();
176 git.archive().setOutputStream(out)
177 .setFormat(format.SUFFIXES.get(0))
178 .setFormatOptions(options)
179 .setTree(first)
180 .setPaths("file_1.txt").call();
181
182 assertEquals(opt.intValue(), out.getFoo());
183 assertEquals(UNEXPECTED_ARCHIVE_SIZE, 1, format.size());
184 assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_1", format.getByPath("file_1.txt"));
185 }
186 }
187
188 @Test
189 public void archiveByDirectoryPath() throws GitAPIException, IOException {
190 try (Git git = new Git(db)) {
191 writeTrashFile("file_0.txt", "content_0_1");
192 git.add().addFilepattern("file_0.txt").call();
193 git.commit().setMessage("commit_1").call();
194
195 writeTrashFile("file_0.txt", "content_0_2");
196 String expectedFilePath1 = "some_directory/file_1.txt";
197 writeTrashFile(expectedFilePath1, "content_1_2");
198 String expectedFilePath2 = "some_directory/file_2.txt";
199 writeTrashFile(expectedFilePath2, "content_2_2");
200 String expectedFilePath3 = "some_directory/nested_directory/file_3.txt";
201 writeTrashFile(expectedFilePath3, "content_3_2");
202 git.add().addFilepattern(".").call();
203 git.commit().setMessage("commit_2").call();
204 git.archive().setOutputStream(new MockOutputStream())
205 .setFormat(format.SUFFIXES.get(0))
206 .setTree(git.getRepository().resolve("HEAD"))
207 .setPaths("some_directory/").call();
208
209 assertEquals(UNEXPECTED_ARCHIVE_SIZE, 5, format.size());
210 assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath(expectedFilePath1));
211 assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath2));
212 assertEquals(UNEXPECTED_FILE_CONTENTS, "content_3_2", format.getByPath(expectedFilePath3));
213 assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory"));
214 assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory/nested_directory"));
215 }
216 }
217
218 @Test
219 public void archiveHeadAllFilesTarTimestamps() throws Exception {
220 try (Git git = new Git(db)) {
221 createTestContent(git);
222 String fmt = "tar";
223 File archive = new File(getTemporaryDirectory(),
224 "archive." + format);
225 archive(git, archive, fmt);
226 ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
227
228 try (InputStream fi = Files.newInputStream(archive.toPath());
229 InputStream bi = new BufferedInputStream(fi);
230 ArchiveInputStream o = new TarArchiveInputStream(bi)) {
231 assertEntries(o);
232 }
233
234 Thread.sleep(WAIT);
235 archive(git, archive, fmt);
236 assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
237 ObjectId.fromRaw(IO.readFully(archive)));
238 }
239 }
240
241 @Test
242 public void archiveHeadAllFilesTgzTimestamps() throws Exception {
243 try (Git git = new Git(db)) {
244 createTestContent(git);
245 String fmt = "tgz";
246 File archive = new File(getTemporaryDirectory(),
247 "archive." + fmt);
248 archive(git, archive, fmt);
249 ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
250
251 try (InputStream fi = Files.newInputStream(archive.toPath());
252 InputStream bi = new BufferedInputStream(fi);
253 InputStream gzi = new GzipCompressorInputStream(bi);
254 ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
255 assertEntries(o);
256 }
257
258 Thread.sleep(WAIT);
259 archive(git, archive, fmt);
260 assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
261 ObjectId.fromRaw(IO.readFully(archive)));
262 }
263 }
264
265 @Test
266 public void archiveHeadAllFilesTbz2Timestamps() throws Exception {
267 try (Git git = new Git(db)) {
268 createTestContent(git);
269 String fmt = "tbz2";
270 File archive = new File(getTemporaryDirectory(),
271 "archive." + fmt);
272 archive(git, archive, fmt);
273 ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
274
275 try (InputStream fi = Files.newInputStream(archive.toPath());
276 InputStream bi = new BufferedInputStream(fi);
277 InputStream gzi = new BZip2CompressorInputStream(bi);
278 ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
279 assertEntries(o);
280 }
281
282 Thread.sleep(WAIT);
283 archive(git, archive, fmt);
284 assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
285 ObjectId.fromRaw(IO.readFully(archive)));
286 }
287 }
288
289 @Test
290 public void archiveHeadAllFilesTxzTimestamps() throws Exception {
291 try (Git git = new Git(db)) {
292 createTestContent(git);
293 String fmt = "txz";
294 File archive = new File(getTemporaryDirectory(), "archive." + fmt);
295 archive(git, archive, fmt);
296 ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
297
298 try (InputStream fi = Files.newInputStream(archive.toPath());
299 InputStream bi = new BufferedInputStream(fi);
300 InputStream gzi = new XZCompressorInputStream(bi);
301 ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
302 assertEntries(o);
303 }
304
305 Thread.sleep(WAIT);
306 archive(git, archive, fmt);
307 assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
308 ObjectId.fromRaw(IO.readFully(archive)));
309 }
310 }
311
312 @Test
313 public void archiveHeadAllFilesZipTimestamps() throws Exception {
314 try (Git git = new Git(db)) {
315 createTestContent(git);
316 String fmt = "zip";
317 File archive = new File(getTemporaryDirectory(), "archive." + fmt);
318 archive(git, archive, fmt);
319 ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
320
321 try (InputStream fi = Files.newInputStream(archive.toPath());
322 InputStream bi = new BufferedInputStream(fi);
323 ArchiveInputStream o = new ZipArchiveInputStream(bi)) {
324 assertEntries(o);
325 }
326
327 Thread.sleep(WAIT);
328 archive(git, archive, fmt);
329 assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
330 ObjectId.fromRaw(IO.readFully(archive)));
331 }
332 }
333
334 private void createTestContent(Git git) throws IOException, GitAPIException,
335 NoFilepatternException, NoHeadException, NoMessageException,
336 UnmergedPathsException, ConcurrentRefUpdateException,
337 WrongRepositoryStateException, AbortedByHookException {
338 writeTrashFile("file_1.txt", "content_1_1");
339 git.add().addFilepattern("file_1.txt").call();
340 git.commit().setMessage("create file").call();
341
342 writeTrashFile("file_1.txt", "content_1_2");
343 writeTrashFile("file_2.txt", "content_2_2");
344 git.add().addFilepattern(".").call();
345 git.commit().setMessage("updated file").call();
346 }
347
348 private static void archive(Git git, File archive, String fmt)
349 throws GitAPIException,
350 FileNotFoundException, AmbiguousObjectException,
351 IncorrectObjectTypeException, IOException {
352 git.archive().setOutputStream(new FileOutputStream(archive))
353 .setFormat(fmt)
354 .setTree(git.getRepository().resolve("HEAD")).call();
355 }
356
357 private static void assertEntries(ArchiveInputStream o) throws IOException {
358 ArchiveEntry e;
359 int n = 0;
360 while ((e = o.getNextEntry()) != null) {
361 n++;
362 assertEquals(UNEXPECTED_LAST_MODIFIED,
363 (1250379778668L / 1000L) * 1000L,
364 e.getLastModifiedDate().getTime());
365 }
366 assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, n);
367 }
368
369 private static class MockFormat
370 implements ArchiveCommand.Format<MockOutputStream> {
371
372 private Map<String, String> entries = new HashMap<>();
373
374 private int size() {
375 return entries.size();
376 }
377
378 private String getByPath(String path) {
379 return entries.get(path);
380 }
381
382 private final List<String> SUFFIXES = Collections
383 .unmodifiableList(Arrays.asList(".mck"));
384
385 @Override
386 public MockOutputStream createArchiveOutputStream(OutputStream s)
387 throws IOException {
388 return createArchiveOutputStream(s,
389 Collections.<String, Object> emptyMap());
390 }
391
392 @Override
393 public MockOutputStream createArchiveOutputStream(OutputStream s,
394 Map<String, Object> o) throws IOException {
395 for (Map.Entry<String, Object> p : o.entrySet()) {
396 try {
397 String methodName = "set"
398 + StringUtils.capitalize(p.getKey());
399 new Statement(s, methodName, new Object[] { p.getValue() })
400 .execute();
401 } catch (Exception e) {
402 throw new IOException("cannot set option: " + p.getKey(), e);
403 }
404 }
405 return new MockOutputStream();
406 }
407
408 @Override
409 public void putEntry(MockOutputStream out, ObjectId tree, String path, FileMode mode, ObjectLoader loader) {
410 String content = mode != FileMode.TREE
411 ? new String(loader.getBytes(), UTF_8)
412 : null;
413 entries.put(path, content);
414 }
415
416 @Override
417 public Iterable<String> suffixes() {
418 return SUFFIXES;
419 }
420 }
421
422 public static class MockOutputStream extends OutputStream {
423
424 private int foo;
425
426 public void setFoo(int foo) {
427 this.foo = foo;
428 }
429
430 public int getFoo() {
431 return foo;
432 }
433
434 @Override
435 public void write(int b) throws IOException {
436
437 }
438 }
439 }