diff options
author | Jonathan Nieder <jrn@google.com> | 2013-05-24 17:30:18 -0700 |
---|---|---|
committer | Jonathan Nieder <jrn@google.com> | 2013-05-24 17:30:18 -0700 |
commit | 56276d053f44209f25951d3acfba226c563a81f0 (patch) | |
tree | 9eb091e509cf57eefd22d8b099438757d9510b56 /org.eclipse.jgit | |
parent | a544ff72dbf62671d2530f4cfca0e8f9dd693d78 (diff) | |
download | jgit-56276d053f44209f25951d3acfba226c563a81f0.tar.gz jgit-56276d053f44209f25951d3acfba226c563a81f0.zip |
Move ArchiveCommand into standard porcelain API
Allow use of ArchiveCommand without depending on the jgit command-line
tools.
To avoid complicating the process of installing and upgrading JGit,
this does not add a dependency by the org.eclipse.jgit bundle on
commons-compress. Instead, the caller is responsible for registering
any formats they want to use by calling ArchiveCommand.registerFormat.
This patch puts functionality that requires an archiver into a
separate org.eclipse.jgit.archive bundle for people who want it. One
can use it by calling ArchiveCommand.registerFormat directly to
register its formats or by relying on OSGi class loading to load
org.eclipse.jgit.archive.FormatActivator, which takes care of
registration automatically.
Once the appropriate formats are registered, you can make a tar or zip
from a git tree object as follows:
ArchiveCommand cmd = git.archive();
try {
cmd.setTree(tree).setFormat(fmt).setOutputStream(out).call();
} finally {
cmd.release();
}
Change-Id: I418e7e7d76422dc6f010d0b3b624d7bec3b20c6e
Diffstat (limited to 'org.eclipse.jgit')
4 files changed, 311 insertions, 0 deletions
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 8b6211efbb..0920c43e8c 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -11,6 +11,8 @@ aNewObjectIdIsRequired=A NewObjectId is required. anExceptionOccurredWhileTryingToAddTheIdOfHEAD=An exception occurred while trying to add the Id of HEAD anSSHSessionHasBeenAlreadyCreated=An SSH session has been already created applyingCommit=Applying {0} +archiveFormatAlreadyAbsent=Archive format already absent: {0} +archiveFormatAlreadyRegistered=Archive format already registered: {0} atLeastOnePathIsRequired=At least one path is required. atLeastOnePatternIsRequired=At least one pattern is required. atLeastTwoFiltersNeeded=At least two filters needed. @@ -182,6 +184,7 @@ errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on th errorReadingInfoRefs=error reading info/refs errorSymlinksNotSupported=Symlinks are not supported with this OS/JRE exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of add command +exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command exceptionCaughtDuringExecutionOfCherryPickCommand=Exception caught during execution of cherry-pick command. {0} exceptionCaughtDuringExecutionOfCommitCommand=Exception caught during execution of commit command exceptionCaughtDuringExecutionOfFetchCommand=Exception caught during execution of fetch command @@ -524,6 +527,7 @@ unmergedPaths=Repository contains unmerged paths unpackException=Exception while parsing pack stream unreadablePackIndex=Unreadable pack index: {0} unrecognizedRef=Unrecognized ref: {0} +unsupportedArchiveFormat=Unknown archive format ''{0}'' unsupportedCommand0=unsupported command 0 unsupportedEncryptionAlgorithm=Unsupported encryption algorithm: {0} unsupportedEncryptionVersion=Unsupported encryption version: {0} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java new file mode 100644 index 0000000000..bbd9c8d826 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java @@ -0,0 +1,294 @@ +/* + * 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.api; + +import java.io.Closeable; +import java.io.IOException; +import java.io.OutputStream; +import java.text.MessageFormat; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.GitCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.internal.JGitText; +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.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; + +/** + * Create an archive of files from a named tree. + * <p> + * Examples (<code>git</code> is a {@link Git} instance): + * <p> + * Create a tarball from HEAD: + * + * <pre> + * ArchiveCommand.registerFormat("tar", new TarFormat()); + * cmd = git.archive(); + * try { + * cmd.setTree(db.resolve("HEAD")) + * .setOutputStream(out).call(); + * } finally { + * cmd.release(); + * ArchiveCommand.unregisterFormat("tar"); + * } + * </pre> + * <p> + * Create a ZIP file from master: + * + * <pre> + * ArchiveCommand.registerFormat("zip", new ZipFormat()); + * try { + * cmd.setTree(db.resolve("master")) + * .setFormat("zip") + * .setOutputStream(out).call(); + * } finally { + * cmd.release(); + * ArchiveCommand.unregisterFormat("zip"); + * } + * </pre> + * + * @see <a href="http://git-htmldocs.googlecode.com/git/git-archive.html" + * >Git documentation about archive</a> + * + * @since 3.0 + */ +public class ArchiveCommand extends GitCommand<OutputStream> { + /** + * Archival format. + * + * Usage: + * Repository repo = git.getRepository(); + * T out = format.createArchiveOutputStream(System.out); + * try { + * for (...) { + * format.putEntry(out, path, mode, repo.open(objectId)); + * } + * } finally { + * out.close(); + * } + */ + public static interface Format<T extends Closeable> { + T createArchiveOutputStream(OutputStream s); + void putEntry(T out, String path, FileMode mode, + ObjectLoader loader) throws IOException; + } + + /** + * Signals an attempt to use an archival format that ArchiveCommand + * doesn't know about (for example due to a typo). + */ + public static class UnsupportedFormatException extends GitAPIException { + private static final long serialVersionUID = 1L; + + private final String format; + + /** + * @param format the problematic format name + */ + public UnsupportedFormatException(String format) { + super(MessageFormat.format(JGitText.get().unsupportedArchiveFormat, format)); + this.format = format; + } + + /** + * @return the problematic format name + */ + public String getFormat() { + return format; + } + } + + /** + * Available archival formats (corresponding to values for + * the --format= option) + */ + private static final ConcurrentMap<String, Format<?>> formats = + new ConcurrentHashMap<String, Format<?>>(); + + /** + * Adds support for an additional archival format. To avoid + * unnecessary dependencies, ArchiveCommand does not have support + * for any formats built in; use this function to add them. + * + * OSGi plugins providing formats should call this function at + * bundle activation time. + * + * @param name name of a format (e.g., "tar" or "zip"). + * @param fmt archiver for that format + * @throws JGitInternalException + * An archival format with that name was already registered. + */ + public static void registerFormat(String name, Format<?> fmt) { + if (formats.putIfAbsent(name, fmt) != null) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().archiveFormatAlreadyRegistered, + name)); + } + + /** + * Removes support for an archival format so its Format can be + * garbage collected. + * + * @param name name of format (e.g., "tar" or "zip"). + * @throws JGitInternalException + * No such archival format was registered. + */ + public static void unregisterFormat(String name) { + if (formats.remove(name) == null) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().archiveFormatAlreadyAbsent, + name)); + } + + private static Format<?> lookupFormat(String formatName) throws UnsupportedFormatException { + Format<?> fmt = formats.get(formatName); + if (fmt == null) + throw new UnsupportedFormatException(formatName); + return fmt; + } + + private OutputStream out; + private TreeWalk walk; + private String format = "tar"; + + /** + * @param repo + */ + public ArchiveCommand(Repository repo) { + super(repo); + walk = new TreeWalk(repo); + } + + /** + * Release any resources used by the internal ObjectReader. + * <p> + * This does not close the output stream set with setOutputStream, which + * belongs to the caller. + */ + public void release() { + walk.release(); + } + + private <T extends Closeable> + OutputStream writeArchive(Format<T> fmt) throws GitAPIException { + final MutableObjectId idBuf = new MutableObjectId(); + final T 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(outa, name, mode, reader.open(idBuf)); + } + } finally { + outa.close(); + } + } catch (IOException e) { + // TODO(jrn): Throw finer-grained errors. + throw new JGitInternalException( + JGitText.get().exceptionCaughtDuringExecutionOfArchiveCommand, e); + } + + return out; + } + + /** + * @return the stream to which the archive has been written + */ + @Override + public OutputStream call() throws GitAPIException { + final Format<?> fmt = lookupFormat(format); + return writeArchive(fmt); + } + + /** + * @param tree + * the tag, commit, or tree object to produce an archive for + * @return this + * @throws IOException + */ + 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., "tar" or "zip") + * @return this + */ + public ArchiveCommand setFormat(String fmt) { + this.format = fmt; + return this; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 4b8378aead..31bf7660c2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -403,6 +403,15 @@ public class Git { } /** + * Returns a command to create an archive from a tree + * + * @return a {@link ArchiveCommand} + */ + public ArchiveCommand archive() { + return new ArchiveCommand(repo); + } + + /** * Returns a command to add notes to an object * * @return a {@link AddNoteCommand} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 29bec97ee7..7280a38512 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -73,6 +73,8 @@ public class JGitText extends TranslationBundle { /***/ public String anExceptionOccurredWhileTryingToAddTheIdOfHEAD; /***/ public String anSSHSessionHasBeenAlreadyCreated; /***/ public String applyingCommit; + /***/ public String archiveFormatAlreadyAbsent; + /***/ public String archiveFormatAlreadyRegistered; /***/ public String atLeastOnePathIsRequired; /***/ public String atLeastOnePatternIsRequired; /***/ public String atLeastTwoFiltersNeeded; @@ -244,6 +246,7 @@ public class JGitText extends TranslationBundle { /***/ public String errorReadingInfoRefs; /***/ public String errorSymlinksNotSupported; /***/ public String exceptionCaughtDuringExecutionOfAddCommand; + /***/ public String exceptionCaughtDuringExecutionOfArchiveCommand; /***/ public String exceptionCaughtDuringExecutionOfCherryPickCommand; /***/ public String exceptionCaughtDuringExecutionOfCommitCommand; /***/ public String exceptionCaughtDuringExecutionOfFetchCommand; @@ -586,6 +589,7 @@ public class JGitText extends TranslationBundle { /***/ public String unpackException; /***/ public String unreadablePackIndex; /***/ public String unrecognizedRef; + /***/ public String unsupportedArchiveFormat; /***/ public String unsupportedCommand0; /***/ public String unsupportedEncryptionAlgorithm; /***/ public String unsupportedEncryptionVersion; |