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 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  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.BufferedInputStream;
  48. import java.io.BufferedReader;
  49. import java.io.ByteArrayInputStream;
  50. import java.io.File;
  51. import java.io.FileInputStream;
  52. import java.io.FileOutputStream;
  53. import java.io.IOException;
  54. import java.io.InputStreamReader;
  55. import java.io.OutputStream;
  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.ExecutorService;
  61. import java.util.concurrent.Executors;
  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.junit.Before;
  70. import org.junit.Test;
  71. public class ArchiveTest extends CLIRepositoryTestCase {
  72. private Git git;
  73. private String emptyTree;
  74. @Override
  75. @Before
  76. public void setUp() throws Exception {
  77. super.setUp();
  78. git = new Git(db);
  79. git.commit().setMessage("initial commit").call();
  80. emptyTree = db.resolve("HEAD^{tree}").abbreviate(12).name();
  81. }
  82. @Test
  83. public void testEmptyArchive() throws Exception {
  84. byte[] result = CLIGitCommand.rawExecute(
  85. "git archive --format=zip " + emptyTree, db);
  86. assertArrayEquals(new String[0], listZipEntries(result));
  87. }
  88. @Test
  89. public void testEmptyTar() throws Exception {
  90. byte[] result = CLIGitCommand.rawExecute(
  91. "git archive --format=tar " + emptyTree, db);
  92. assertArrayEquals(new String[0], listTarEntries(result));
  93. }
  94. @Test
  95. public void testUnrecognizedFormat() throws Exception {
  96. String[] expect = new String[] { "fatal: Unknown archive format 'nonsense'" };
  97. String[] actual = executeUnchecked(
  98. "git archive --format=nonsense " + emptyTree);
  99. assertArrayEquals(expect, actual);
  100. }
  101. @Test
  102. public void testArchiveWithFiles() throws Exception {
  103. writeTrashFile("a", "a file with content!");
  104. writeTrashFile("c", ""); // empty file
  105. writeTrashFile("unrelated", "another file, just for kicks");
  106. git.add().addFilepattern("a").call();
  107. git.add().addFilepattern("c").call();
  108. git.commit().setMessage("populate toplevel").call();
  109. byte[] result = CLIGitCommand.rawExecute(
  110. "git archive --format=zip HEAD", db);
  111. assertArrayEquals(new String[] { "a", "c" },
  112. listZipEntries(result));
  113. }
  114. private void commitGreeting() throws Exception {
  115. writeTrashFile("greeting", "hello, world!");
  116. git.add().addFilepattern("greeting").call();
  117. git.commit().setMessage("a commit with a file").call();
  118. }
  119. @Test
  120. public void testDefaultFormatIsTar() throws Exception {
  121. commitGreeting();
  122. byte[] result = CLIGitCommand.rawExecute(
  123. "git archive HEAD", db);
  124. assertArrayEquals(new String[] { "greeting" },
  125. listTarEntries(result));
  126. }
  127. private static String shellQuote(String s) {
  128. return "'" + s.replace("'", "'\\''") + "'";
  129. }
  130. @Test
  131. public void testFormatOverridesFilename() throws Exception {
  132. File archive = new File(db.getWorkTree(), "format-overrides-name.tar");
  133. String path = archive.getAbsolutePath();
  134. commitGreeting();
  135. assertArrayEquals(new String[] { "" },
  136. execute("git archive " +
  137. "--format=zip " +
  138. shellQuote("--output=" + path) + " " +
  139. "HEAD"));
  140. assertContainsEntryWithMode(path, "", "greeting");
  141. assertIsZip(archive);
  142. }
  143. @Test
  144. public void testUnrecognizedExtensionMeansTar() throws Exception {
  145. File archive = new File(db.getWorkTree(), "example.txt");
  146. String path = archive.getAbsolutePath();
  147. commitGreeting();
  148. assertArrayEquals(new String[] { "" },
  149. execute("git archive " +
  150. shellQuote("--output=" + path) + " " +
  151. "HEAD"));
  152. assertTarContainsEntry(path, "", "greeting");
  153. assertIsTar(archive);
  154. }
  155. @Test
  156. public void testNoExtensionMeansTar() throws Exception {
  157. File archive = new File(db.getWorkTree(), "example");
  158. String path = archive.getAbsolutePath();
  159. commitGreeting();
  160. assertArrayEquals(new String[] { "" },
  161. execute("git archive " +
  162. shellQuote("--output=" + path) + " " +
  163. "HEAD"));
  164. assertIsTar(archive);
  165. }
  166. @Test
  167. public void testExtensionMatchIsAnchored() throws Exception {
  168. File archive = new File(db.getWorkTree(), "two-extensions.zip.bak");
  169. String path = archive.getAbsolutePath();
  170. commitGreeting();
  171. assertArrayEquals(new String[] { "" },
  172. execute("git archive " +
  173. shellQuote("--output=" + path) + " " +
  174. "HEAD"));
  175. assertIsTar(archive);
  176. }
  177. @Test
  178. public void testZipExtension() throws Exception {
  179. File archiveWithDot = new File(db.getWorkTree(), "greeting.zip");
  180. File archiveNoDot = new File(db.getWorkTree(), "greetingzip");
  181. commitGreeting();
  182. execute("git archive " +
  183. shellQuote("--output=" + archiveWithDot.getAbsolutePath()) + " " +
  184. "HEAD");
  185. execute("git archive " +
  186. shellQuote("--output=" + archiveNoDot.getAbsolutePath()) + " " +
  187. "HEAD");
  188. assertIsZip(archiveWithDot);
  189. assertIsTar(archiveNoDot);
  190. }
  191. @Test
  192. public void testTarExtension() throws Exception {
  193. File archive = new File(db.getWorkTree(), "tarball.tar");
  194. String path = archive.getAbsolutePath();
  195. commitGreeting();
  196. assertArrayEquals(new String[] { "" },
  197. execute("git archive " +
  198. shellQuote("--output=" + path) + " " +
  199. "HEAD"));
  200. assertIsTar(archive);
  201. }
  202. @Test
  203. public void testTgzExtensions() throws Exception {
  204. commitGreeting();
  205. for (String ext : Arrays.asList("tar.gz", "tgz")) {
  206. File archiveWithDot = new File(db.getWorkTree(), "tarball." + ext);
  207. File archiveNoDot = new File(db.getWorkTree(), "tarball" + ext);
  208. execute("git archive " +
  209. shellQuote("--output=" + archiveWithDot.getAbsolutePath()) + " " +
  210. "HEAD");
  211. execute("git archive " +
  212. shellQuote("--output=" + archiveNoDot.getAbsolutePath()) + " " +
  213. "HEAD");
  214. assertIsGzip(archiveWithDot);
  215. assertIsTar(archiveNoDot);
  216. }
  217. }
  218. @Test
  219. public void testTbz2Extension() throws Exception {
  220. commitGreeting();
  221. for (String ext : Arrays.asList("tar.bz2", "tbz", "tbz2")) {
  222. File archiveWithDot = new File(db.getWorkTree(), "tarball." + ext);
  223. File archiveNoDot = new File(db.getWorkTree(), "tarball" + ext);
  224. execute("git archive " +
  225. shellQuote("--output=" + archiveWithDot.getAbsolutePath()) + " " +
  226. "HEAD");
  227. execute("git archive " +
  228. shellQuote("--output=" + archiveNoDot.getAbsolutePath()) + " " +
  229. "HEAD");
  230. assertIsBzip2(archiveWithDot);
  231. assertIsTar(archiveNoDot);
  232. }
  233. }
  234. @Test
  235. public void testTxzExtension() throws Exception {
  236. commitGreeting();
  237. for (String ext : Arrays.asList("tar.xz", "txz")) {
  238. File archiveWithDot = new File(db.getWorkTree(), "tarball." + ext);
  239. File archiveNoDot = new File(db.getWorkTree(), "tarball" + ext);
  240. execute("git archive " +
  241. shellQuote("--output=" + archiveWithDot.getAbsolutePath()) + " " +
  242. "HEAD");
  243. execute("git archive " +
  244. shellQuote("--output=" + archiveNoDot.getAbsolutePath()) + " " +
  245. "HEAD");
  246. assertIsXz(archiveWithDot);
  247. assertIsTar(archiveNoDot);
  248. }
  249. }
  250. @Test
  251. public void testArchiveWithSubdir() throws Exception {
  252. writeTrashFile("a", "a file with content!");
  253. writeTrashFile("b.c", "before subdir in git sort order");
  254. writeTrashFile("b0c", "after subdir in git sort order");
  255. writeTrashFile("c", "");
  256. git.add().addFilepattern("a").call();
  257. git.add().addFilepattern("b.c").call();
  258. git.add().addFilepattern("b0c").call();
  259. git.add().addFilepattern("c").call();
  260. git.commit().setMessage("populate toplevel").call();
  261. writeTrashFile("b/b", "file in subdirectory");
  262. writeTrashFile("b/a", "another file in subdirectory");
  263. git.add().addFilepattern("b").call();
  264. git.commit().setMessage("add subdir").call();
  265. byte[] result = CLIGitCommand.rawExecute(
  266. "git archive --format=zip master", db);
  267. String[] expect = { "a", "b.c", "b0c", "b/", "b/a", "b/b", "c" };
  268. String[] actual = listZipEntries(result);
  269. Arrays.sort(expect);
  270. Arrays.sort(actual);
  271. assertArrayEquals(expect, actual);
  272. }
  273. @Test
  274. public void testTarWithSubdir() throws Exception {
  275. writeTrashFile("a", "a file with content!");
  276. writeTrashFile("b.c", "before subdir in git sort order");
  277. writeTrashFile("b0c", "after subdir in git sort order");
  278. writeTrashFile("c", "");
  279. git.add().addFilepattern("a").call();
  280. git.add().addFilepattern("b.c").call();
  281. git.add().addFilepattern("b0c").call();
  282. git.add().addFilepattern("c").call();
  283. git.commit().setMessage("populate toplevel").call();
  284. writeTrashFile("b/b", "file in subdirectory");
  285. writeTrashFile("b/a", "another file in subdirectory");
  286. git.add().addFilepattern("b").call();
  287. git.commit().setMessage("add subdir").call();
  288. byte[] result = CLIGitCommand.rawExecute(
  289. "git archive --format=tar master", db);
  290. String[] expect = { "a", "b.c", "b0c", "b/", "b/a", "b/b", "c" };
  291. String[] actual = listTarEntries(result);
  292. Arrays.sort(expect);
  293. Arrays.sort(actual);
  294. assertArrayEquals(expect, actual);
  295. }
  296. private void commitBazAndFooSlashBar() throws Exception {
  297. writeTrashFile("baz", "a file");
  298. writeTrashFile("foo/bar", "another file");
  299. git.add().addFilepattern("baz").call();
  300. git.add().addFilepattern("foo").call();
  301. git.commit().setMessage("sample commit").call();
  302. }
  303. @Test
  304. public void testArchivePrefixOption() throws Exception {
  305. commitBazAndFooSlashBar();
  306. byte[] result = CLIGitCommand.rawExecute(
  307. "git archive --prefix=x/ --format=zip master", db);
  308. String[] expect = { "x/baz", "x/foo/", "x/foo/bar" };
  309. String[] actual = listZipEntries(result);
  310. Arrays.sort(expect);
  311. Arrays.sort(actual);
  312. assertArrayEquals(expect, actual);
  313. }
  314. @Test
  315. public void testTarPrefixOption() throws Exception {
  316. commitBazAndFooSlashBar();
  317. byte[] result = CLIGitCommand.rawExecute(
  318. "git archive --prefix=x/ --format=tar master", db);
  319. String[] expect = { "x/baz", "x/foo/", "x/foo/bar" };
  320. String[] actual = listTarEntries(result);
  321. Arrays.sort(expect);
  322. Arrays.sort(actual);
  323. assertArrayEquals(expect, actual);
  324. }
  325. private void commitFoo() throws Exception {
  326. writeTrashFile("foo", "a file");
  327. git.add().addFilepattern("foo").call();
  328. git.commit().setMessage("boring commit").call();
  329. }
  330. @Test
  331. public void testPrefixDoesNotNormalizeDoubleSlash() throws Exception {
  332. commitFoo();
  333. byte[] result = CLIGitCommand.rawExecute(
  334. "git archive --prefix=x// --format=zip master", db);
  335. String[] expect = { "x//foo" };
  336. assertArrayEquals(expect, listZipEntries(result));
  337. }
  338. @Test
  339. public void testPrefixDoesNotNormalizeDoubleSlashInTar() throws Exception {
  340. commitFoo();
  341. byte[] result = CLIGitCommand.rawExecute(
  342. "git archive --prefix=x// --format=tar master", db);
  343. String[] expect = { "x//foo" };
  344. assertArrayEquals(expect, listTarEntries(result));
  345. }
  346. /**
  347. * The prefix passed to "git archive" need not end with '/'.
  348. * In practice it is not very common to have a nonempty prefix
  349. * that does not name a directory (and hence end with /), but
  350. * since git has historically supported other prefixes, we do,
  351. * too.
  352. *
  353. * @throws Exception
  354. */
  355. @Test
  356. public void testPrefixWithoutTrailingSlash() throws Exception {
  357. commitBazAndFooSlashBar();
  358. byte[] result = CLIGitCommand.rawExecute(
  359. "git archive --prefix=my- --format=zip master", db);
  360. String[] expect = { "my-baz", "my-foo/", "my-foo/bar" };
  361. String[] actual = listZipEntries(result);
  362. Arrays.sort(expect);
  363. Arrays.sort(actual);
  364. assertArrayEquals(expect, actual);
  365. }
  366. @Test
  367. public void testTarPrefixWithoutTrailingSlash() throws Exception {
  368. commitBazAndFooSlashBar();
  369. byte[] result = CLIGitCommand.rawExecute(
  370. "git archive --prefix=my- --format=tar master", db);
  371. String[] expect = { "my-baz", "my-foo/", "my-foo/bar" };
  372. String[] actual = listTarEntries(result);
  373. Arrays.sort(expect);
  374. Arrays.sort(actual);
  375. assertArrayEquals(expect, actual);
  376. }
  377. @Test
  378. public void testArchiveIncludesSubmoduleDirectory() throws Exception {
  379. writeTrashFile("a", "a file with content!");
  380. writeTrashFile("c", "after submodule");
  381. git.add().addFilepattern("a").call();
  382. git.add().addFilepattern("c").call();
  383. git.commit().setMessage("initial commit").call();
  384. git.submoduleAdd().setURI("./.").setPath("b").call().close();
  385. git.commit().setMessage("add submodule").call();
  386. byte[] result = CLIGitCommand.rawExecute(
  387. "git archive --format=zip master", db);
  388. String[] expect = { ".gitmodules", "a", "b/", "c" };
  389. String[] actual = listZipEntries(result);
  390. Arrays.sort(expect);
  391. Arrays.sort(actual);
  392. assertArrayEquals(expect, actual);
  393. }
  394. @Test
  395. public void testTarIncludesSubmoduleDirectory() throws Exception {
  396. writeTrashFile("a", "a file with content!");
  397. writeTrashFile("c", "after submodule");
  398. git.add().addFilepattern("a").call();
  399. git.add().addFilepattern("c").call();
  400. git.commit().setMessage("initial commit").call();
  401. git.submoduleAdd().setURI("./.").setPath("b").call().close();
  402. git.commit().setMessage("add submodule").call();
  403. byte[] result = CLIGitCommand.rawExecute(
  404. "git archive --format=tar master", db);
  405. String[] expect = { ".gitmodules", "a", "b/", "c" };
  406. String[] actual = listTarEntries(result);
  407. Arrays.sort(expect);
  408. Arrays.sort(actual);
  409. assertArrayEquals(expect, actual);
  410. }
  411. @Test
  412. public void testArchivePreservesMode() throws Exception {
  413. writeTrashFile("plain", "a file with content");
  414. writeTrashFile("executable", "an executable file");
  415. writeTrashFile("symlink", "plain");
  416. writeTrashFile("dir/content", "clutter in a subdir");
  417. git.add().addFilepattern("plain").call();
  418. git.add().addFilepattern("executable").call();
  419. git.add().addFilepattern("symlink").call();
  420. git.add().addFilepattern("dir").call();
  421. DirCache cache = db.lockDirCache();
  422. cache.getEntry("executable").setFileMode(FileMode.EXECUTABLE_FILE);
  423. cache.getEntry("symlink").setFileMode(FileMode.SYMLINK);
  424. cache.write();
  425. cache.commit();
  426. cache.unlock();
  427. git.commit().setMessage("three files with different modes").call();
  428. byte[] zipData = CLIGitCommand.rawExecute(
  429. "git archive --format=zip master", db);
  430. writeRaw("zip-with-modes.zip", zipData);
  431. assertContainsEntryWithMode("zip-with-modes.zip", "-rw-", "plain");
  432. assertContainsEntryWithMode("zip-with-modes.zip", "-rwx", "executable");
  433. assertContainsEntryWithMode("zip-with-modes.zip", "l", "symlink");
  434. assertContainsEntryWithMode("zip-with-modes.zip", "-rw-", "dir/");
  435. }
  436. @Test
  437. public void testTarPreservesMode() throws Exception {
  438. writeTrashFile("plain", "a file with content");
  439. writeTrashFile("executable", "an executable file");
  440. writeTrashFile("symlink", "plain");
  441. writeTrashFile("dir/content", "clutter in a subdir");
  442. git.add().addFilepattern("plain").call();
  443. git.add().addFilepattern("executable").call();
  444. git.add().addFilepattern("symlink").call();
  445. git.add().addFilepattern("dir").call();
  446. DirCache cache = db.lockDirCache();
  447. cache.getEntry("executable").setFileMode(FileMode.EXECUTABLE_FILE);
  448. cache.getEntry("symlink").setFileMode(FileMode.SYMLINK);
  449. cache.write();
  450. cache.commit();
  451. cache.unlock();
  452. git.commit().setMessage("three files with different modes").call();
  453. byte[] archive = CLIGitCommand.rawExecute(
  454. "git archive --format=tar master", db);
  455. writeRaw("with-modes.tar", archive);
  456. assertTarContainsEntry("with-modes.tar", "-rw-r--r--", "plain");
  457. assertTarContainsEntry("with-modes.tar", "-rwxr-xr-x", "executable");
  458. assertTarContainsEntry("with-modes.tar", "l", "symlink -> plain");
  459. assertTarContainsEntry("with-modes.tar", "drwxr-xr-x", "dir/");
  460. }
  461. @Test
  462. public void testArchiveWithLongFilename() throws Exception {
  463. String filename = "";
  464. List<String> l = new ArrayList<String>();
  465. for (int i = 0; i < 20; i++) {
  466. filename = filename + "1234567890/";
  467. l.add(filename);
  468. }
  469. filename = filename + "1234567890";
  470. l.add(filename);
  471. writeTrashFile(filename, "file with long path");
  472. git.add().addFilepattern("1234567890").call();
  473. git.commit().setMessage("file with long name").call();
  474. byte[] result = CLIGitCommand.rawExecute(
  475. "git archive --format=zip HEAD", db);
  476. assertArrayEquals(l.toArray(new String[l.size()]),
  477. listZipEntries(result));
  478. }
  479. @Test
  480. public void testTarWithLongFilename() throws Exception {
  481. String filename = "";
  482. List<String> l = new ArrayList<String>();
  483. for (int i = 0; i < 20; i++) {
  484. filename = filename + "1234567890/";
  485. l.add(filename);
  486. }
  487. filename = filename + "1234567890";
  488. l.add(filename);
  489. writeTrashFile(filename, "file with long path");
  490. git.add().addFilepattern("1234567890").call();
  491. git.commit().setMessage("file with long name").call();
  492. byte[] result = CLIGitCommand.rawExecute(
  493. "git archive --format=tar HEAD", db);
  494. assertArrayEquals(l.toArray(new String[l.size()]),
  495. listTarEntries(result));
  496. }
  497. @Test
  498. public void testArchivePreservesContent() throws Exception {
  499. String payload = "“The quick brown fox jumps over the lazy dog!”";
  500. writeTrashFile("xyzzy", payload);
  501. git.add().addFilepattern("xyzzy").call();
  502. git.commit().setMessage("add file with content").call();
  503. byte[] result = CLIGitCommand.rawExecute(
  504. "git archive --format=zip HEAD", db);
  505. assertArrayEquals(new String[] { payload },
  506. zipEntryContent(result, "xyzzy"));
  507. }
  508. @Test
  509. public void testTarPreservesContent() throws Exception {
  510. String payload = "“The quick brown fox jumps over the lazy dog!”";
  511. writeTrashFile("xyzzy", payload);
  512. git.add().addFilepattern("xyzzy").call();
  513. git.commit().setMessage("add file with content").call();
  514. byte[] result = CLIGitCommand.rawExecute(
  515. "git archive --format=tar HEAD", db);
  516. assertArrayEquals(new String[] { payload },
  517. tarEntryContent(result, "xyzzy"));
  518. }
  519. private Process spawnAssumingCommandPresent(String... cmdline) {
  520. File cwd = db.getWorkTree();
  521. ProcessBuilder procBuilder = new ProcessBuilder(cmdline)
  522. .directory(cwd)
  523. .redirectErrorStream(true);
  524. Process proc = null;
  525. try {
  526. proc = procBuilder.start();
  527. } catch (IOException e) {
  528. // On machines without `cmdline[0]`, let the test pass.
  529. assumeNoException(e);
  530. }
  531. return proc;
  532. }
  533. private BufferedReader readFromProcess(Process proc) throws Exception {
  534. return new BufferedReader(
  535. new InputStreamReader(proc.getInputStream(), "UTF-8"));
  536. }
  537. private void grepForEntry(String name, String mode, String... cmdline)
  538. throws Exception {
  539. Process proc = spawnAssumingCommandPresent(cmdline);
  540. proc.getOutputStream().close();
  541. BufferedReader reader = readFromProcess(proc);
  542. try {
  543. String line;
  544. while ((line = reader.readLine()) != null)
  545. if (line.startsWith(mode) && line.endsWith(name))
  546. // found it!
  547. return;
  548. fail("expected entry " + name + " with mode " + mode + " but found none");
  549. } finally {
  550. proc.getOutputStream().close();
  551. proc.destroy();
  552. }
  553. }
  554. private void assertMagic(long offset, byte[] magicBytes, File file) throws Exception {
  555. BufferedInputStream in = new BufferedInputStream(
  556. new FileInputStream(file));
  557. try {
  558. in.skip(offset);
  559. byte[] actual = new byte[magicBytes.length];
  560. in.read(actual);
  561. assertArrayEquals(magicBytes, actual);
  562. } finally {
  563. in.close();
  564. }
  565. }
  566. private void assertMagic(byte[] magicBytes, File file) throws Exception {
  567. assertMagic(0, magicBytes, file);
  568. }
  569. private void assertIsTar(File file) throws Exception {
  570. assertMagic(257, new byte[] { 'u', 's', 't', 'a', 'r', 0 }, file);
  571. }
  572. private void assertIsZip(File file) throws Exception {
  573. assertMagic(new byte[] { 'P', 'K', 3, 4 }, file);
  574. }
  575. private void assertIsGzip(File file) throws Exception {
  576. assertMagic(new byte[] { 037, (byte) 0213 }, file);
  577. }
  578. private void assertIsBzip2(File file) throws Exception {
  579. assertMagic(new byte[] { 'B', 'Z', 'h' }, file);
  580. }
  581. private void assertIsXz(File file) throws Exception {
  582. assertMagic(new byte[] { (byte) 0xfd, '7', 'z', 'X', 'Z', 0 }, file);
  583. }
  584. private void assertContainsEntryWithMode(String zipFilename, String mode, String name)
  585. throws Exception {
  586. grepForEntry(name, mode, "zipinfo", zipFilename);
  587. }
  588. private void assertTarContainsEntry(String tarfile, String mode, String name)
  589. throws Exception {
  590. grepForEntry(name, mode, "tar", "tvf", tarfile);
  591. }
  592. private void writeRaw(String filename, byte[] data)
  593. throws IOException {
  594. File path = new File(db.getWorkTree(), filename);
  595. OutputStream out = new FileOutputStream(path);
  596. try {
  597. out.write(data);
  598. } finally {
  599. out.close();
  600. }
  601. }
  602. private static String[] listZipEntries(byte[] zipData) throws IOException {
  603. List<String> l = new ArrayList<String>();
  604. ZipInputStream in = new ZipInputStream(
  605. new ByteArrayInputStream(zipData));
  606. ZipEntry e;
  607. while ((e = in.getNextEntry()) != null)
  608. l.add(e.getName());
  609. in.close();
  610. return l.toArray(new String[l.size()]);
  611. }
  612. private static Future<Object> writeAsync(final OutputStream stream, final byte[] data) {
  613. ExecutorService executor = Executors.newSingleThreadExecutor();
  614. return executor.submit(new Callable<Object>() {
  615. public Object call() throws IOException {
  616. try {
  617. stream.write(data);
  618. return null;
  619. } finally {
  620. stream.close();
  621. }
  622. }
  623. });
  624. }
  625. private String[] listTarEntries(byte[] tarData) throws Exception {
  626. List<String> l = new ArrayList<String>();
  627. Process proc = spawnAssumingCommandPresent("tar", "tf", "-");
  628. BufferedReader reader = readFromProcess(proc);
  629. OutputStream out = proc.getOutputStream();
  630. // Dump tarball to tar stdin in background
  631. Future<?> writing = writeAsync(out, tarData);
  632. try {
  633. String line;
  634. while ((line = reader.readLine()) != null)
  635. l.add(line);
  636. return l.toArray(new String[l.size()]);
  637. } finally {
  638. writing.get();
  639. reader.close();
  640. proc.destroy();
  641. }
  642. }
  643. private static String[] zipEntryContent(byte[] zipData, String path)
  644. throws IOException {
  645. ZipInputStream in = new ZipInputStream(
  646. new ByteArrayInputStream(zipData));
  647. ZipEntry e;
  648. while ((e = in.getNextEntry()) != null) {
  649. if (!e.getName().equals(path))
  650. continue;
  651. // found!
  652. List<String> l = new ArrayList<String>();
  653. BufferedReader reader = new BufferedReader(
  654. new InputStreamReader(in, "UTF-8"));
  655. String line;
  656. while ((line = reader.readLine()) != null)
  657. l.add(line);
  658. return l.toArray(new String[l.size()]);
  659. }
  660. // not found
  661. return null;
  662. }
  663. private String[] tarEntryContent(byte[] tarData, String path)
  664. throws Exception {
  665. List<String> l = new ArrayList<String>();
  666. Process proc = spawnAssumingCommandPresent("tar", "Oxf", "-", path);
  667. BufferedReader reader = readFromProcess(proc);
  668. OutputStream out = proc.getOutputStream();
  669. Future<?> writing = writeAsync(out, tarData);
  670. try {
  671. String line;
  672. while ((line = reader.readLine()) != null)
  673. l.add(line);
  674. return l.toArray(new String[l.size()]);
  675. } finally {
  676. writing.get();
  677. reader.close();
  678. proc.destroy();
  679. }
  680. }
  681. }