summaryrefslogtreecommitdiffstatsabout
diff options
context:
space:
mode:
authorJonathan Nieder2012-12-03 19:08:04 (EST)
committer Jonathan Nieder2012-12-04 18:37:42 (EST)
commit78009782cdabdba42841f2a71fbd5867f2ae683f (patch)
treea17f08bb0f076ba41fd019b731083ea7e4a5ae60
parent345ab401ce27e9d4cb4002edd7f8e732b5e2e6fc (diff)
downloadjgit-78009782cdabdba42841f2a71fbd5867f2ae683f.zip
jgit-78009782cdabdba42841f2a71fbd5867f2ae683f.tar.gz
jgit-78009782cdabdba42841f2a71fbd5867f2ae683f.tar.bz2
archive: Add tar supportrefs/changes/18/9018/3
Unlike ZIP files, tar files do not treat symlinks as ordinary files with a different mode, so tar support involves a little more code than would be ideal. Change-Id: Ica2568f4a0e443bf4b955ef0c029bc8eec62d369
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java167
-rw-r--r--org.eclipse.jgit.pgm/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties2
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java38
4 files changed, 199 insertions, 9 deletions
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
index cad3797..bcf2728 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
@@ -53,11 +53,15 @@ import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
-
+import java.lang.Object;
import java.lang.String;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -92,6 +96,13 @@ public class ArchiveTest extends CLIRepositoryTestCase {
}
@Test
+ public void testEmptyTar() throws Exception {
+ final byte[] result = CLIGitCommand.rawExecute( //
+ "git archive --format=tar " + emptyTree, db);
+ assertArrayEquals(new String[0], listTarEntries(result));
+ }
+
+ @Test
public void testArchiveWithFiles() throws Exception {
writeTrashFile("a", "a file with content!");
writeTrashFile("c", ""); // empty file
@@ -133,6 +144,32 @@ public class ArchiveTest extends CLIRepositoryTestCase {
}
@Test
+ public void testTarWithSubdir() throws Exception {
+ writeTrashFile("a", "a file with content!");
+ writeTrashFile("b.c", "before subdir in git sort order");
+ writeTrashFile("b0c", "after subdir in git sort order");
+ writeTrashFile("c", "");
+ git.add().addFilepattern("a").call();
+ git.add().addFilepattern("b.c").call();
+ git.add().addFilepattern("b0c").call();
+ git.add().addFilepattern("c").call();
+ git.commit().setMessage("populate toplevel").call();
+ writeTrashFile("b/b", "file in subdirectory");
+ writeTrashFile("b/a", "another file in subdirectory");
+ git.add().addFilepattern("b").call();
+ git.commit().setMessage("add subdir").call();
+
+ final byte[] result = CLIGitCommand.rawExecute( //
+ "git archive --format=tar master", db);
+ String[] expect = { "a", "b.c", "b0c", "b/a", "b/b", "c" };
+ String[] actual = listTarEntries(result);
+
+ Arrays.sort(expect);
+ Arrays.sort(actual);
+ assertArrayEquals(expect, actual);
+ }
+
+ @Test
public void testArchivePreservesMode() throws Exception {
writeTrashFile("plain", "a file with content");
writeTrashFile("executable", "an executable file");
@@ -159,6 +196,32 @@ public class ArchiveTest extends CLIRepositoryTestCase {
}
@Test
+ public void testTarPreservesMode() throws Exception {
+ writeTrashFile("plain", "a file with content");
+ writeTrashFile("executable", "an executable file");
+ writeTrashFile("symlink", "plain");
+ git.add().addFilepattern("plain").call();
+ git.add().addFilepattern("executable").call();
+ git.add().addFilepattern("symlink").call();
+
+ DirCache cache = db.lockDirCache();
+ cache.getEntry("executable").setFileMode(FileMode.EXECUTABLE_FILE);
+ cache.getEntry("symlink").setFileMode(FileMode.SYMLINK);
+ cache.write();
+ cache.commit();
+ cache.unlock();
+
+ git.commit().setMessage("three files with different modes").call();
+
+ final byte[] archive = CLIGitCommand.rawExecute( //
+ "git archive --format=tar master", db);
+ writeRaw("with-modes.tar", archive);
+ assertTarContainsEntry("with-modes.tar", "-rw-r--r--", "plain");
+ assertTarContainsEntry("with-modes.tar", "-rwxr-xr-x", "executable");
+ assertTarContainsEntry("with-modes.tar", "l", "symlink -> plain");
+ }
+
+ @Test
public void testArchivePreservesContent() throws Exception {
final String payload = "“The quick brown fox jumps over the lazy dog!”";
writeTrashFile("xyzzy", payload);
@@ -171,23 +234,45 @@ public class ArchiveTest extends CLIRepositoryTestCase {
zipEntryContent(result, "xyzzy"));
}
- private void assertContainsEntryWithMode(String zipFilename, String mode, String name) //
- throws Exception {
+ @Test
+ public void testTarPreservesContent() throws Exception {
+ final String payload = "“The quick brown fox jumps over the lazy dog!”";
+ writeTrashFile("xyzzy", payload);
+ git.add().addFilepattern("xyzzy").call();
+ git.commit().setMessage("add file with content").call();
+
+ final byte[] result = CLIGitCommand.rawExecute( //
+ "git archive --format=tar HEAD", db);
+ assertArrayEquals(new String[] { payload }, //
+ tarEntryContent(result, "xyzzy"));
+ }
+
+ private Process spawnAssumingCommandPresent(String... cmdline) {
final File cwd = db.getWorkTree();
- final ProcessBuilder procBuilder = new ProcessBuilder("zipinfo", zipFilename) //
+ final ProcessBuilder procBuilder = new ProcessBuilder(cmdline) //
.directory(cwd) //
.redirectErrorStream(true);
Process proc = null;
try {
proc = procBuilder.start();
} catch (IOException e) {
- // On machines without a "zipinfo" command, let the test pass.
+ // On machines without `cmdline[0]`, let the test pass.
assumeNoException(e);
}
- proc.getOutputStream().close();
- final BufferedReader reader = new BufferedReader( //
+ return proc;
+ }
+
+ private BufferedReader readFromProcess(Process proc) throws Exception {
+ return new BufferedReader( //
new InputStreamReader(proc.getInputStream(), "UTF-8"));
+ }
+
+ private void grepForEntry(String name, String mode, String... cmdline) //
+ throws Exception {
+ final Process proc = spawnAssumingCommandPresent(cmdline);
+ proc.getOutputStream().close();
+ final BufferedReader reader = readFromProcess(proc);
try {
String line;
while ((line = reader.readLine()) != null)
@@ -201,6 +286,16 @@ public class ArchiveTest extends CLIRepositoryTestCase {
}
}
+ private void assertContainsEntryWithMode(String zipFilename, String mode, String name) //
+ throws Exception {
+ grepForEntry(name, mode, "zipinfo", zipFilename);
+ }
+
+ private void assertTarContainsEntry(String tarfile, String mode, String name) //
+ throws Exception {
+ grepForEntry(name, mode, "tar", "tvf", tarfile);
+ }
+
private void writeRaw(String filename, byte[] data) //
throws IOException {
final File path = new File(db.getWorkTree(), filename);
@@ -224,6 +319,43 @@ public class ArchiveTest extends CLIRepositoryTestCase {
return l.toArray(new String[l.size()]);
}
+ private static Future<Object> writeAsync(final OutputStream stream, final byte[] data) {
+ final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ return executor.submit(new Callable<Object>() { //
+ public Object call() throws IOException {
+ try {
+ stream.write(data);
+ return null;
+ } finally {
+ stream.close();
+ }
+ }
+ });
+ }
+
+ private String[] listTarEntries(byte[] tarData) throws Exception {
+ final List<String> l = new ArrayList<String>();
+ final Process proc = spawnAssumingCommandPresent("tar", "tf", "-");
+ final BufferedReader reader = readFromProcess(proc);
+ final OutputStream out = proc.getOutputStream();
+
+ // Dump tarball to tar stdin in background
+ final Future<?> writing = writeAsync(out, tarData);
+
+ try {
+ String line;
+ while ((line = reader.readLine()) != null)
+ l.add(line);
+
+ return l.toArray(new String[l.size()]);
+ } finally {
+ writing.get();
+ reader.close();
+ proc.destroy();
+ }
+ }
+
private static String[] zipEntryContent(byte[] zipData, String path) //
throws IOException {
final ZipInputStream in = new ZipInputStream( //
@@ -246,4 +378,25 @@ public class ArchiveTest extends CLIRepositoryTestCase {
// not found
return null;
}
+
+ private String[] tarEntryContent(byte[] tarData, String path) //
+ throws Exception {
+ final List<String> l = new ArrayList<String>();
+ final Process proc = spawnAssumingCommandPresent("tar", "Oxf", "-", path);
+ final BufferedReader reader = readFromProcess(proc);
+ final OutputStream out = proc.getOutputStream();
+ final Future<?> writing = writeAsync(out, tarData);
+
+ try {
+ String line;
+ while ((line = reader.readLine()) != null)
+ l.add(line);
+
+ return l.toArray(new String[l.size()]);
+ } finally {
+ writing.get();
+ reader.close();
+ proc.destroy();
+ }
+ }
}
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 247f93c..b2a988c 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -7,6 +7,7 @@ Bundle-Vendor: %provider_name
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)",
+ org.apache.commons.compress.archivers.tar;version="[1.3,2.0)",
org.apache.commons.compress.archivers.zip;version="[1.3,2.0)",
org.eclipse.jgit.api;version="[2.2.0,2.3.0)",
org.eclipse.jgit.api.errors;version="[2.2.0,2.3.0)",
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
index fe70e71..5586a28 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
@@ -197,7 +197,7 @@ usage_addFileContentsToTheIndex=Add file contents to the index
usage_alterTheDetailShown=alter the detail shown
usage_approveDestructionOfRepository=approve destruction of repository
usage_archive=zip up files from the named tree
-usage_archiveFormat=archive format. Currently supported formats: 'zip'
+usage_archiveFormat=archive format. Currently supported formats: 'tar', 'zip'
usage_blameLongRevision=show long revision
usage_blameRange=annotate only the given range
usage_blameRawTimestamp=show raw timestamp
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java
index ad32638..4a5bf1c 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java
@@ -53,6 +53,9 @@ import java.text.MessageFormat;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.compress.archivers.tar.TarConstants;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.eclipse.jgit.lib.FileMode;
@@ -111,7 +114,8 @@ class Archive extends TextBuiltin {
}
public enum Format {
- ZIP
+ ZIP,
+ TAR
};
private static interface Archiver {
@@ -149,6 +153,38 @@ class Archive extends TextBuiltin {
out.closeArchiveEntry();
}
});
+ fmts.put(Format.TAR, new Archiver() {
+ @Override
+ public ArchiveOutputStream createArchiveOutputStream(OutputStream s) {
+ return new TarArchiveOutputStream(s);
+ }
+
+ @Override
+ public void putEntry(String path, FileMode mode, //
+ ObjectLoader loader, ArchiveOutputStream out) //
+ throws IOException {
+ if (mode == FileMode.SYMLINK) {
+ final TarArchiveEntry entry = new TarArchiveEntry( //
+ path, TarConstants.LF_SYMLINK);
+ entry.setLinkName(new String( //
+ loader.getCachedBytes(100), "UTF-8"));
+ out.putArchiveEntry(entry);
+ out.closeArchiveEntry();
+ return;
+ }
+
+ final TarArchiveEntry entry = new TarArchiveEntry(path);
+ if (mode == FileMode.REGULAR_FILE ||
+ mode == FileMode.EXECUTABLE_FILE)
+ entry.setMode(mode.getBits());
+ else
+ warnArchiveEntryModeIgnored(path);
+ entry.setSize(loader.getSize());
+ out.putArchiveEntry(entry);
+ loader.copyTo(out);
+ out.closeArchiveEntry();
+ }
+ });
formats = fmts;
}
}