From b525e696d558bee033652d847b18514e5656fbda Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 26 Apr 2013 13:43:23 -0700 Subject: [PATCH] Add internal porcelain-style API for ArchiveCommand One step closer to exposing archive creation functionality in a org.eclipse.jgit.archive bundle. Change-Id: If0ebb2417a941d9d3fc0d3f444316d0d1c494ff3 --- .../org/eclipse/jgit/pgm/CLIText.properties | 1 + .../src/org/eclipse/jgit/pgm/Archive.java | 133 +-------- .../src/org/eclipse/jgit/pgm/CLIText.java | 1 + .../jgit/pgm/archive/ArchiveCommand.java | 277 ++++++++++++++++++ 4 files changed, 288 insertions(+), 124 deletions(-) create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/archive/ArchiveCommand.java 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 39c126839..4808df995 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 @@ -48,6 +48,7 @@ deletedRemoteBranch=Deleted remote branch {0} doesNotExist={0} does not exist dontOverwriteLocalChanges=error: Your local changes to the following file would be overwritten by merge: everythingUpToDate=Everything up-to-date +exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command expectedNumberOfbytes=Expected {0} bytes. exporting=Exporting {0} failedToCommitIndex=failed to commit index 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 9075cf068..5685aa426 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 @@ -43,148 +43,33 @@ package org.eclipse.jgit.pgm; -import java.lang.String; -import java.lang.System; -import java.io.IOException; -import java.io.OutputStream; -import java.util.EnumMap; -import java.util.Map; -import java.text.MessageFormat; - -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; -import org.eclipse.jgit.lib.MutableObjectId; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.pgm.CLIText; import org.eclipse.jgit.pgm.TextBuiltin; -import org.eclipse.jgit.treewalk.AbstractTreeIterator; -import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.pgm.archive.ArchiveCommand; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_archive") class Archive extends TextBuiltin { @Argument(index = 0, metaVar = "metaVar_treeish") - private AbstractTreeIterator tree; + private ObjectId tree; @Option(name = "--format", metaVar = "metaVar_archiveFormat", usage = "usage_archiveFormat") - private Format format = Format.ZIP; + private ArchiveCommand.Format format = ArchiveCommand.Format.ZIP; @Override protected void run() throws Exception { - final MutableObjectId idBuf = new MutableObjectId(); - final Archiver fmt = formats.get(format); - if (tree == null) throw die(CLIText.get().treeIsRequired); - final ArchiveOutputStream outa = fmt.createArchiveOutputStream(outs); - final TreeWalk walk = new TreeWalk(db); - final ObjectReader reader = walk.getObjectReader(); - + final ArchiveCommand cmd = new ArchiveCommand(db); try { - walk.reset(); - walk.addTree(tree); - walk.setRecursive(true); - while (walk.next()) { - final String name = walk.getPathString(); - final FileMode mode = walk.getFileMode(0); - - if (mode == FileMode.TREE) - // ZIP entries for directories are optional. - // Leave them out, mimicking "git archive". - continue; - - walk.getObjectId(idBuf, 0); - fmt.putEntry(name, mode, reader.open(idBuf), outa); - } + cmd.setTree(tree) + .setFormat(format) + .setOutputStream(outs).call(); } finally { - reader.release(); - outa.close(); + cmd.release(); } } - - static private void warnArchiveEntryModeIgnored(String name) { - System.err.println(MessageFormat.format( // - CLIText.get().archiveEntryModeIgnored, // - name)); - } - - public enum Format { - ZIP, - TAR - } - - private static interface Archiver { - ArchiveOutputStream createArchiveOutputStream(OutputStream s); - void putEntry(String path, FileMode mode, // - ObjectLoader loader, ArchiveOutputStream out) // - throws IOException; - } - - private static final Map formats; - static { - Map fmts = new EnumMap(Format.class); - fmts.put(Format.ZIP, new Archiver() { - public ArchiveOutputStream createArchiveOutputStream(OutputStream s) { - return new ZipArchiveOutputStream(s); - } - - public void putEntry(String path, FileMode mode, // - ObjectLoader loader, ArchiveOutputStream out) // - throws IOException { - final ZipArchiveEntry entry = new ZipArchiveEntry(path); - - if (mode == FileMode.REGULAR_FILE) { - // ok - } else if (mode == FileMode.EXECUTABLE_FILE - || mode == FileMode.SYMLINK) { - entry.setUnixMode(mode.getBits()); - } else { - warnArchiveEntryModeIgnored(path); - } - entry.setSize(loader.getSize()); - out.putArchiveEntry(entry); - loader.copyTo(out); - out.closeArchiveEntry(); - } - }); - fmts.put(Format.TAR, new Archiver() { - public ArchiveOutputStream createArchiveOutputStream(OutputStream s) { - return new TarArchiveOutputStream(s); - } - - 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")); //$NON-NLS-1$ - 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; - } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java index fe1c87b8a..727317b83 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java @@ -117,6 +117,7 @@ public class CLIText extends TranslationBundle { /***/ public String doesNotExist; /***/ public String dontOverwriteLocalChanges; /***/ public String everythingUpToDate; + /***/ public String exceptionCaughtDuringExecutionOfArchiveCommand; /***/ public String expectedNumberOfbytes; /***/ public String exporting; /***/ public String failedToCommitIndex; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/archive/ArchiveCommand.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/archive/ArchiveCommand.java new file mode 100644 index 000000000..eaa80aee2 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/archive/ArchiveCommand.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2012 Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.pgm.archive; + +import java.lang.String; +import java.lang.System; +import java.io.IOException; +import java.io.OutputStream; +import java.util.EnumMap; +import java.util.Map; +import java.text.MessageFormat; + +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.api.GitCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.MutableObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.pgm.CLIText; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; + +/** + * Create an archive of files from a named tree. + *

+ * Examples (git is a {@link Git} instance): + *

+ * Create a tarball from HEAD: + * + *

+ * cmd = new ArchiveCommand(git.getRepository());
+ * try {
+ *	cmd.setTree(db.resolve("HEAD"))
+ *		.setOutputStream(out).call();
+ * } finally {
+ *	cmd.release();
+ * }
+ * 
+ *

+ * Create a ZIP file from master: + * + *

+ * try {
+ *	cmd.setTree(db.resolve("master"))
+ *		.setFormat(ArchiveCommand.Format.ZIP)
+ *		.setOutputStream(out).call();
+ * } finally {
+ *	cmd.release();
+ * }
+ * 
+ * + * @see Git documentation about archive + */ +public class ArchiveCommand extends GitCommand { + /** + * Available archival formats (corresponding to values for + * the --format= option) + */ + public static enum Format { + ZIP, + TAR + } + + private static interface Archiver { + ArchiveOutputStream createArchiveOutputStream(OutputStream s); + void putEntry(String path, FileMode mode, // + ObjectLoader loader, ArchiveOutputStream out) // + throws IOException; + } + + private static void warnArchiveEntryModeIgnored(String name) { + System.err.println(MessageFormat.format( // + CLIText.get().archiveEntryModeIgnored, // + name)); + } + + private static final Map formats; + + static { + Map fmts = new EnumMap(Format.class); + fmts.put(Format.ZIP, new Archiver() { + public ArchiveOutputStream createArchiveOutputStream(OutputStream s) { + return new ZipArchiveOutputStream(s); + } + + public void putEntry(String path, FileMode mode, // + ObjectLoader loader, ArchiveOutputStream out) // + throws IOException { + final ZipArchiveEntry entry = new ZipArchiveEntry(path); + + if (mode == FileMode.REGULAR_FILE) { + // ok + } else if (mode == FileMode.EXECUTABLE_FILE + || mode == FileMode.SYMLINK) { + entry.setUnixMode(mode.getBits()); + } else { + warnArchiveEntryModeIgnored(path); + } + entry.setSize(loader.getSize()); + out.putArchiveEntry(entry); + loader.copyTo(out); + out.closeArchiveEntry(); + } + }); + fmts.put(Format.TAR, new Archiver() { + public ArchiveOutputStream createArchiveOutputStream(OutputStream s) { + return new TarArchiveOutputStream(s); + } + + 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")); //$NON-NLS-1$ + 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; + } + + private OutputStream out; + private TreeWalk walk; + private Format format = Format.TAR; + + /** + * @param repo + */ + public ArchiveCommand(Repository repo) { + super(repo); + walk = new TreeWalk(repo); + } + + /** + * Release any resources used by the internal ObjectReader. + *

+ * This does not close the output stream set with setOutputStream, which + * belongs to the caller. + */ + public void release() { + walk.release(); + } + + /** + * @return the stream to which the archive has been written + */ + @Override + public OutputStream call() throws GitAPIException { + final MutableObjectId idBuf = new MutableObjectId(); + final Archiver fmt = formats.get(format); + final ArchiveOutputStream outa = fmt.createArchiveOutputStream(out); + final ObjectReader reader = walk.getObjectReader(); + + try { + try { + walk.setRecursive(true); + while (walk.next()) { + final String name = walk.getPathString(); + final FileMode mode = walk.getFileMode(0); + + if (mode == FileMode.TREE) + // ZIP entries for directories are optional. + // Leave them out, mimicking "git archive". + continue; + + walk.getObjectId(idBuf, 0); + fmt.putEntry(name, mode, reader.open(idBuf), outa); + } + } finally { + outa.close(); + } + } catch (IOException e) { + // TODO(jrn): Throw finer-grained errors. + throw new JGitInternalException( + CLIText.get().exceptionCaughtDuringExecutionOfArchiveCommand, e); + } + + return out; + } + + /** + * @param tree + * the tag, commit, or tree object to produce an archive for + * @return this + */ + public ArchiveCommand setTree(ObjectId tree) throws IOException { + final RevWalk rw = new RevWalk(walk.getObjectReader()); + walk.reset(rw.parseTree(tree)); + return this; + } + + /** + * @param out + * the stream to which to write the archive + * @return this + */ + public ArchiveCommand setOutputStream(OutputStream out) { + this.out = out; + return this; + } + + /** + * @param fmt + * archive format (e.g., Format.TAR) + * @return this + */ + public ArchiveCommand setFormat(Format fmt) { + this.format = fmt; + return this; + } +}