You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ArchiveTest.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. * Copyright (C) 2012 Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.pgm;
  44. import static org.junit.Assert.assertArrayEquals;
  45. import static org.junit.Assert.fail;
  46. import static org.junit.Assume.assumeNoException;
  47. import java.io.BufferedReader;
  48. import java.io.ByteArrayInputStream;
  49. import java.io.File;
  50. import java.io.FileOutputStream;
  51. import java.io.InputStreamReader;
  52. import java.io.IOException;
  53. import java.io.OutputStream;
  54. import java.lang.Object;
  55. import java.lang.String;
  56. import java.util.ArrayList;
  57. import java.util.Arrays;
  58. import java.util.List;
  59. import java.util.concurrent.Callable;
  60. import java.util.concurrent.Executors;
  61. import java.util.concurrent.ExecutorService;
  62. import java.util.concurrent.Future;
  63. import java.util.zip.ZipEntry;
  64. import java.util.zip.ZipInputStream;
  65. import org.eclipse.jgit.api.Git;
  66. import org.eclipse.jgit.dircache.DirCache;
  67. import org.eclipse.jgit.lib.CLIRepositoryTestCase;
  68. import org.eclipse.jgit.lib.FileMode;
  69. import org.eclipse.jgit.pgm.CLIGitCommand;
  70. import org.junit.Before;
  71. import org.junit.Ignore;
  72. import org.junit.Test;
  73. public class ArchiveTest extends CLIRepositoryTestCase {
  74. private Git git;
  75. private String emptyTree;
  76. @Override
  77. @Before
  78. public void setUp() throws Exception {
  79. super.setUp();
  80. git = new Git(db);
  81. git.commit().setMessage("initial commit").call();
  82. emptyTree = db.resolve("HEAD^{tree}").abbreviate(12).name();
  83. }
  84. @Ignore("Some versions of java.util.zip refuse to write an empty ZIP")
  85. @Test
  86. public void testEmptyArchive() throws Exception {
  87. final byte[] result = CLIGitCommand.rawExecute( //
  88. "git archive " + emptyTree, db);
  89. assertArrayEquals(new String[0], listZipEntries(result));
  90. }
  91. @Test
  92. public void testEmptyTar() throws Exception {
  93. final byte[] result = CLIGitCommand.rawExecute( //
  94. "git archive --format=tar " + emptyTree, db);
  95. assertArrayEquals(new String[0], listTarEntries(result));
  96. }
  97. @Test
  98. public void testUnrecognizedFormat() throws Exception {
  99. final String[] expect = new String[] { "fatal: Unknown archive format 'nonsense'" };
  100. final String[] actual = execute("git archive --format=nonsense " + emptyTree);
  101. assertArrayEquals(expect, actual);
  102. }
  103. @Test
  104. public void testArchiveWithFiles() throws Exception {
  105. writeTrashFile("a", "a file with content!");
  106. writeTrashFile("c", ""); // empty file
  107. writeTrashFile("unrelated", "another file, just for kicks");
  108. git.add().addFilepattern("a").call();
  109. git.add().addFilepattern("c").call();
  110. git.commit().setMessage("populate toplevel").call();
  111. final byte[] result = CLIGitCommand.rawExecute( //
  112. "git archive HEAD", db);
  113. assertArrayEquals(new String[] { "a", "c" }, //
  114. listZipEntries(result));
  115. }
  116. @Test
  117. public void testArchiveWithSubdir() throws Exception {
  118. writeTrashFile("a", "a file with content!");
  119. writeTrashFile("b.c", "before subdir in git sort order");
  120. writeTrashFile("b0c", "after subdir in git sort order");
  121. writeTrashFile("c", "");
  122. git.add().addFilepattern("a").call();
  123. git.add().addFilepattern("b.c").call();
  124. git.add().addFilepattern("b0c").call();
  125. git.add().addFilepattern("c").call();
  126. git.commit().setMessage("populate toplevel").call();
  127. writeTrashFile("b/b", "file in subdirectory");
  128. writeTrashFile("b/a", "another file in subdirectory");
  129. git.add().addFilepattern("b").call();
  130. git.commit().setMessage("add subdir").call();
  131. final byte[] result = CLIGitCommand.rawExecute( //
  132. "git archive master", db);
  133. String[] expect = { "a", "b.c", "b0c", "b/a", "b/b", "c" };
  134. String[] actual = listZipEntries(result);
  135. Arrays.sort(expect);
  136. Arrays.sort(actual);
  137. assertArrayEquals(expect, actual);
  138. }
  139. @Test
  140. public void testTarWithSubdir() throws Exception {
  141. writeTrashFile("a", "a file with content!");
  142. writeTrashFile("b.c", "before subdir in git sort order");
  143. writeTrashFile("b0c", "after subdir in git sort order");
  144. writeTrashFile("c", "");
  145. git.add().addFilepattern("a").call();
  146. git.add().addFilepattern("b.c").call();
  147. git.add().addFilepattern("b0c").call();
  148. git.add().addFilepattern("c").call();
  149. git.commit().setMessage("populate toplevel").call();
  150. writeTrashFile("b/b", "file in subdirectory");
  151. writeTrashFile("b/a", "another file in subdirectory");
  152. git.add().addFilepattern("b").call();
  153. git.commit().setMessage("add subdir").call();
  154. final byte[] result = CLIGitCommand.rawExecute( //
  155. "git archive --format=tar master", db);
  156. String[] expect = { "a", "b.c", "b0c", "b/a", "b/b", "c" };
  157. String[] actual = listTarEntries(result);
  158. Arrays.sort(expect);
  159. Arrays.sort(actual);
  160. assertArrayEquals(expect, actual);
  161. }
  162. @Test
  163. public void testArchivePreservesMode() throws Exception {
  164. writeTrashFile("plain", "a file with content");
  165. writeTrashFile("executable", "an executable file");
  166. writeTrashFile("symlink", "plain");
  167. git.add().addFilepattern("plain").call();
  168. git.add().addFilepattern("executable").call();
  169. git.add().addFilepattern("symlink").call();
  170. DirCache cache = db.lockDirCache();
  171. cache.getEntry("executable").setFileMode(FileMode.EXECUTABLE_FILE);
  172. cache.getEntry("symlink").setFileMode(FileMode.SYMLINK);
  173. cache.write();
  174. cache.commit();
  175. cache.unlock();
  176. git.commit().setMessage("three files with different modes").call();
  177. final byte[] zipData = CLIGitCommand.rawExecute( //
  178. "git archive master", db);
  179. writeRaw("zip-with-modes.zip", zipData);
  180. assertContainsEntryWithMode("zip-with-modes.zip", "-rw-", "plain");
  181. assertContainsEntryWithMode("zip-with-modes.zip", "-rwx", "executable");
  182. assertContainsEntryWithMode("zip-with-modes.zip", "l", "symlink");
  183. }
  184. @Test
  185. public void testTarPreservesMode() throws Exception {
  186. writeTrashFile("plain", "a file with content");
  187. writeTrashFile("executable", "an executable file");
  188. writeTrashFile("symlink", "plain");
  189. git.add().addFilepattern("plain").call();
  190. git.add().addFilepattern("executable").call();
  191. git.add().addFilepattern("symlink").call();
  192. DirCache cache = db.lockDirCache();
  193. cache.getEntry("executable").setFileMode(FileMode.EXECUTABLE_FILE);
  194. cache.getEntry("symlink").setFileMode(FileMode.SYMLINK);
  195. cache.write();
  196. cache.commit();
  197. cache.unlock();
  198. git.commit().setMessage("three files with different modes").call();
  199. final byte[] archive = CLIGitCommand.rawExecute( //
  200. "git archive --format=tar master", db);
  201. writeRaw("with-modes.tar", archive);
  202. assertTarContainsEntry("with-modes.tar", "-rw-r--r--", "plain");
  203. assertTarContainsEntry("with-modes.tar", "-rwxr-xr-x", "executable");
  204. assertTarContainsEntry("with-modes.tar", "l", "symlink -> plain");
  205. }
  206. @Test
  207. public void testArchiveWithLongFilename() throws Exception {
  208. String filename = "1234567890";
  209. for (int i = 0; i < 20; i++)
  210. filename = filename + "/1234567890";
  211. writeTrashFile(filename, "file with long path");
  212. git.add().addFilepattern("1234567890").call();
  213. git.commit().setMessage("file with long name").call();
  214. final byte[] result = CLIGitCommand.rawExecute( //
  215. "git archive HEAD", db);
  216. assertArrayEquals(new String[] { filename },
  217. listZipEntries(result));
  218. }
  219. @Test
  220. public void testTarWithLongFilename() throws Exception {
  221. String filename = "1234567890";
  222. for (int i = 0; i < 20; i++)
  223. filename = filename + "/1234567890";
  224. writeTrashFile(filename, "file with long path");
  225. git.add().addFilepattern("1234567890").call();
  226. git.commit().setMessage("file with long name").call();
  227. final byte[] result = CLIGitCommand.rawExecute( //
  228. "git archive --format=tar HEAD", db);
  229. assertArrayEquals(new String[] { filename },
  230. listTarEntries(result));
  231. }
  232. @Test
  233. public void testArchivePreservesContent() throws Exception {
  234. final String payload = "“The quick brown fox jumps over the lazy dog!”";
  235. writeTrashFile("xyzzy", payload);
  236. git.add().addFilepattern("xyzzy").call();
  237. git.commit().setMessage("add file with content").call();
  238. final byte[] result = CLIGitCommand.rawExecute( //
  239. "git archive HEAD", db);
  240. assertArrayEquals(new String[] { payload }, //
  241. zipEntryContent(result, "xyzzy"));
  242. }
  243. @Test
  244. public void testTarPreservesContent() throws Exception {
  245. final String payload = "“The quick brown fox jumps over the lazy dog!”";
  246. writeTrashFile("xyzzy", payload);
  247. git.add().addFilepattern("xyzzy").call();
  248. git.commit().setMessage("add file with content").call();
  249. final byte[] result = CLIGitCommand.rawExecute( //
  250. "git archive --format=tar HEAD", db);
  251. assertArrayEquals(new String[] { payload }, //
  252. tarEntryContent(result, "xyzzy"));
  253. }
  254. private Process spawnAssumingCommandPresent(String... cmdline) {
  255. final File cwd = db.getWorkTree();
  256. final ProcessBuilder procBuilder = new ProcessBuilder(cmdline) //
  257. .directory(cwd) //
  258. .redirectErrorStream(true);
  259. Process proc = null;
  260. try {
  261. proc = procBuilder.start();
  262. } catch (IOException e) {
  263. // On machines without `cmdline[0]`, let the test pass.
  264. assumeNoException(e);
  265. }
  266. return proc;
  267. }
  268. private BufferedReader readFromProcess(Process proc) throws Exception {
  269. return new BufferedReader( //
  270. new InputStreamReader(proc.getInputStream(), "UTF-8"));
  271. }
  272. private void grepForEntry(String name, String mode, String... cmdline) //
  273. throws Exception {
  274. final Process proc = spawnAssumingCommandPresent(cmdline);
  275. proc.getOutputStream().close();
  276. final BufferedReader reader = readFromProcess(proc);
  277. try {
  278. String line;
  279. while ((line = reader.readLine()) != null)
  280. if (line.startsWith(mode) && line.endsWith(name))
  281. // found it!
  282. return;
  283. fail("expected entry " + name + " with mode " + mode + " but found none");
  284. } finally {
  285. proc.getOutputStream().close();
  286. proc.destroy();
  287. }
  288. }
  289. private void assertContainsEntryWithMode(String zipFilename, String mode, String name) //
  290. throws Exception {
  291. grepForEntry(name, mode, "zipinfo", zipFilename);
  292. }
  293. private void assertTarContainsEntry(String tarfile, String mode, String name) //
  294. throws Exception {
  295. grepForEntry(name, mode, "tar", "tvf", tarfile);
  296. }
  297. private void writeRaw(String filename, byte[] data) //
  298. throws IOException {
  299. final File path = new File(db.getWorkTree(), filename);
  300. final OutputStream out = new FileOutputStream(path);
  301. try {
  302. out.write(data);
  303. } finally {
  304. out.close();
  305. }
  306. }
  307. private static String[] listZipEntries(byte[] zipData) throws IOException {
  308. final List<String> l = new ArrayList<String>();
  309. final ZipInputStream in = new ZipInputStream( //
  310. new ByteArrayInputStream(zipData));
  311. ZipEntry e;
  312. while ((e = in.getNextEntry()) != null)
  313. l.add(e.getName());
  314. in.close();
  315. return l.toArray(new String[l.size()]);
  316. }
  317. private static Future<Object> writeAsync(final OutputStream stream, final byte[] data) {
  318. final ExecutorService executor = Executors.newSingleThreadExecutor();
  319. return executor.submit(new Callable<Object>() { //
  320. public Object call() throws IOException {
  321. try {
  322. stream.write(data);
  323. return null;
  324. } finally {
  325. stream.close();
  326. }
  327. }
  328. });
  329. }
  330. private String[] listTarEntries(byte[] tarData) throws Exception {
  331. final List<String> l = new ArrayList<String>();
  332. final Process proc = spawnAssumingCommandPresent("tar", "tf", "-");
  333. final BufferedReader reader = readFromProcess(proc);
  334. final OutputStream out = proc.getOutputStream();
  335. // Dump tarball to tar stdin in background
  336. final Future<?> writing = writeAsync(out, tarData);
  337. try {
  338. String line;
  339. while ((line = reader.readLine()) != null)
  340. l.add(line);
  341. return l.toArray(new String[l.size()]);
  342. } finally {
  343. writing.get();
  344. reader.close();
  345. proc.destroy();
  346. }
  347. }
  348. private static String[] zipEntryContent(byte[] zipData, String path) //
  349. throws IOException {
  350. final ZipInputStream in = new ZipInputStream( //
  351. new ByteArrayInputStream(zipData));
  352. ZipEntry e;
  353. while ((e = in.getNextEntry()) != null) {
  354. if (!e.getName().equals(path))
  355. continue;
  356. // found!
  357. final List<String> l = new ArrayList<String>();
  358. final BufferedReader reader = new BufferedReader( //
  359. new InputStreamReader(in, "UTF-8"));
  360. String line;
  361. while ((line = reader.readLine()) != null)
  362. l.add(line);
  363. return l.toArray(new String[l.size()]);
  364. }
  365. // not found
  366. return null;
  367. }
  368. private String[] tarEntryContent(byte[] tarData, String path) //
  369. throws Exception {
  370. final List<String> l = new ArrayList<String>();
  371. final Process proc = spawnAssumingCommandPresent("tar", "Oxf", "-", path);
  372. final BufferedReader reader = readFromProcess(proc);
  373. final OutputStream out = proc.getOutputStream();
  374. final Future<?> writing = writeAsync(out, tarData);
  375. try {
  376. String line;
  377. while ((line = reader.readLine()) != null)
  378. l.add(line);
  379. return l.toArray(new String[l.size()]);
  380. } finally {
  381. writing.get();
  382. reader.close();
  383. proc.destroy();
  384. }
  385. }
  386. }