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

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