| /* | |
| * Copyright 2012 gitblit.com. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package com.gitblit.utils; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.EOFException; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.text.MessageFormat; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import org.apache.commons.compress.archivers.tar.TarArchiveEntry; | |
| import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; | |
| import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; | |
| import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; | |
| import org.apache.commons.compress.compressors.CompressorException; | |
| import org.apache.commons.compress.compressors.CompressorStreamFactory; | |
| import org.apache.commons.io.IOUtils; | |
| import org.eclipse.jgit.lib.Constants; | |
| 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.RevCommit; | |
| import org.eclipse.jgit.revwalk.RevWalk; | |
| import org.eclipse.jgit.treewalk.TreeWalk; | |
| import org.eclipse.jgit.treewalk.filter.PathFilter; | |
| import org.slf4j.Logger; | |
| import org.slf4j.LoggerFactory; | |
| import com.gitblit.GitBlit; | |
| import com.gitblit.manager.IFilestoreManager; | |
| import com.gitblit.models.FilestoreModel; | |
| import com.gitblit.models.FilestoreModel.Status; | |
| /** | |
| * Collection of static methods for retrieving information from a repository. | |
| * | |
| * @author James Moger | |
| * | |
| */ | |
| public class CompressionUtils { | |
| static final Logger LOGGER = LoggerFactory.getLogger(CompressionUtils.class); | |
| /** | |
| * Log an error message and exception. | |
| * | |
| * @param t | |
| * @param repository | |
| * if repository is not null it MUST be the {0} parameter in the | |
| * pattern. | |
| * @param pattern | |
| * @param objects | |
| */ | |
| private static void error(Throwable t, Repository repository, String pattern, Object... objects) { | |
| List<Object> parameters = new ArrayList<Object>(); | |
| if (objects != null && objects.length > 0) { | |
| for (Object o : objects) { | |
| parameters.add(o); | |
| } | |
| } | |
| if (repository != null) { | |
| parameters.add(0, repository.getDirectory().getAbsolutePath()); | |
| } | |
| LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t); | |
| } | |
| /** | |
| * Zips the contents of the tree at the (optionally) specified revision and | |
| * the (optionally) specified basepath to the supplied outputstream. | |
| * | |
| * @param repository | |
| * @param basePath | |
| * if unspecified, entire repository is assumed. | |
| * @param objectId | |
| * if unspecified, HEAD is assumed. | |
| * @param os | |
| * @return true if repository was successfully zipped to supplied output | |
| * stream | |
| */ | |
| public static boolean zip(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |
| OutputStream os) { | |
| RevCommit commit = JGitUtils.getCommit(repository, objectId); | |
| if (commit == null) { | |
| return false; | |
| } | |
| boolean success = false; | |
| RevWalk rw = new RevWalk(repository); | |
| TreeWalk tw = new TreeWalk(repository); | |
| try { | |
| tw.reset(); | |
| tw.addTree(commit.getTree()); | |
| ZipArchiveOutputStream zos = new ZipArchiveOutputStream(os); | |
| zos.setComment("Generated by Gitblit"); | |
| if (!StringUtils.isEmpty(basePath)) { | |
| PathFilter f = PathFilter.create(basePath); | |
| tw.setFilter(f); | |
| } | |
| tw.setRecursive(true); | |
| MutableObjectId id = new MutableObjectId(); | |
| ObjectReader reader = tw.getObjectReader(); | |
| long modified = commit.getAuthorIdent().getWhen().getTime(); | |
| while (tw.next()) { | |
| FileMode mode = tw.getFileMode(0); | |
| if (mode == FileMode.GITLINK || mode == FileMode.TREE) { | |
| continue; | |
| } | |
| tw.getObjectId(id, 0); | |
| ObjectLoader loader = repository.open(id); | |
| ZipArchiveEntry entry = new ZipArchiveEntry(tw.getPathString()); | |
| FilestoreModel filestoreItem = null; | |
| if (JGitUtils.isPossibleFilestoreItem(loader.getSize())) { | |
| filestoreItem = JGitUtils.getFilestoreItem(tw.getObjectReader().open(id)); | |
| } | |
| final long size = (filestoreItem == null) ? loader.getSize() : filestoreItem.getSize(); | |
| entry.setSize(size); | |
| entry.setComment(commit.getName()); | |
| entry.setUnixMode(mode.getBits()); | |
| entry.setTime(modified); | |
| zos.putArchiveEntry(entry); | |
| if (filestoreItem == null) { | |
| //Copy repository stored file | |
| loader.copyTo(zos); | |
| } else { | |
| //Copy filestore file | |
| try (FileInputStream streamIn = new FileInputStream(filestoreManager.getStoragePath(filestoreItem.oid))) { | |
| IOUtils.copyLarge(streamIn, zos); | |
| } catch (Throwable e) { | |
| LOGGER.error(MessageFormat.format("Failed to archive filestore item {0}", filestoreItem.oid), e); | |
| //Handle as per other errors | |
| throw e; | |
| } | |
| } | |
| zos.closeArchiveEntry(); | |
| } | |
| zos.finish(); | |
| success = true; | |
| } catch (IOException e) { | |
| error(e, repository, "{0} failed to zip files from commit {1}", commit.getName()); | |
| } finally { | |
| tw.close(); | |
| rw.dispose(); | |
| } | |
| return success; | |
| } | |
| /** | |
| * tar the contents of the tree at the (optionally) specified revision and | |
| * the (optionally) specified basepath to the supplied outputstream. | |
| * | |
| * @param repository | |
| * @param basePath | |
| * if unspecified, entire repository is assumed. | |
| * @param objectId | |
| * if unspecified, HEAD is assumed. | |
| * @param os | |
| * @return true if repository was successfully zipped to supplied output | |
| * stream | |
| */ | |
| public static boolean tar(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |
| OutputStream os) { | |
| return tar(null, repository, filestoreManager, basePath, objectId, os); | |
| } | |
| /** | |
| * tar.gz the contents of the tree at the (optionally) specified revision and | |
| * the (optionally) specified basepath to the supplied outputstream. | |
| * | |
| * @param repository | |
| * @param basePath | |
| * if unspecified, entire repository is assumed. | |
| * @param objectId | |
| * if unspecified, HEAD is assumed. | |
| * @param os | |
| * @return true if repository was successfully zipped to supplied output | |
| * stream | |
| */ | |
| public static boolean gz(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |
| OutputStream os) { | |
| return tar(CompressorStreamFactory.GZIP, repository, filestoreManager, basePath, objectId, os); | |
| } | |
| /** | |
| * tar.xz the contents of the tree at the (optionally) specified revision and | |
| * the (optionally) specified basepath to the supplied outputstream. | |
| * | |
| * @param repository | |
| * @param basePath | |
| * if unspecified, entire repository is assumed. | |
| * @param objectId | |
| * if unspecified, HEAD is assumed. | |
| * @param os | |
| * @return true if repository was successfully zipped to supplied output | |
| * stream | |
| */ | |
| public static boolean xz(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |
| OutputStream os) { | |
| return tar(CompressorStreamFactory.XZ, repository, filestoreManager, basePath, objectId, os); | |
| } | |
| /** | |
| * tar.bzip2 the contents of the tree at the (optionally) specified revision and | |
| * the (optionally) specified basepath to the supplied outputstream. | |
| * | |
| * @param repository | |
| * @param basePath | |
| * if unspecified, entire repository is assumed. | |
| * @param objectId | |
| * if unspecified, HEAD is assumed. | |
| * @param os | |
| * @return true if repository was successfully zipped to supplied output | |
| * stream | |
| */ | |
| public static boolean bzip2(Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |
| OutputStream os) { | |
| return tar(CompressorStreamFactory.BZIP2, repository, filestoreManager, basePath, objectId, os); | |
| } | |
| /** | |
| * Compresses/archives the contents of the tree at the (optionally) | |
| * specified revision and the (optionally) specified basepath to the | |
| * supplied outputstream. | |
| * | |
| * @param algorithm | |
| * compression algorithm for tar (optional) | |
| * @param repository | |
| * @param basePath | |
| * if unspecified, entire repository is assumed. | |
| * @param objectId | |
| * if unspecified, HEAD is assumed. | |
| * @param os | |
| * @return true if repository was successfully zipped to supplied output | |
| * stream | |
| */ | |
| private static boolean tar(String algorithm, Repository repository, IFilestoreManager filestoreManager, String basePath, String objectId, | |
| OutputStream os) { | |
| RevCommit commit = JGitUtils.getCommit(repository, objectId); | |
| if (commit == null) { | |
| return false; | |
| } | |
| OutputStream cos = os; | |
| if (!StringUtils.isEmpty(algorithm)) { | |
| try { | |
| cos = new CompressorStreamFactory().createCompressorOutputStream(algorithm, os); | |
| } catch (CompressorException e1) { | |
| error(e1, repository, "{0} failed to open {1} stream", algorithm); | |
| } | |
| } | |
| boolean success = false; | |
| RevWalk rw = new RevWalk(repository); | |
| TreeWalk tw = new TreeWalk(repository); | |
| try { | |
| tw.reset(); | |
| tw.addTree(commit.getTree()); | |
| TarArchiveOutputStream tos = new TarArchiveOutputStream(cos); | |
| tos.setAddPaxHeadersForNonAsciiNames(true); | |
| tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); | |
| if (!StringUtils.isEmpty(basePath)) { | |
| PathFilter f = PathFilter.create(basePath); | |
| tw.setFilter(f); | |
| } | |
| tw.setRecursive(true); | |
| MutableObjectId id = new MutableObjectId(); | |
| long modified = commit.getAuthorIdent().getWhen().getTime(); | |
| while (tw.next()) { | |
| FileMode mode = tw.getFileMode(0); | |
| if (mode == FileMode.GITLINK || mode == FileMode.TREE) { | |
| continue; | |
| } | |
| tw.getObjectId(id, 0); | |
| ObjectLoader loader = repository.open(id); | |
| if (FileMode.SYMLINK == mode) { | |
| TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString(),TarArchiveEntry.LF_SYMLINK); | |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
| loader.copyTo(bos); | |
| entry.setLinkName(bos.toString()); | |
| entry.setModTime(modified); | |
| tos.putArchiveEntry(entry); | |
| tos.closeArchiveEntry(); | |
| } else { | |
| TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString()); | |
| entry.setMode(mode.getBits()); | |
| entry.setModTime(modified); | |
| FilestoreModel filestoreItem = null; | |
| if (JGitUtils.isPossibleFilestoreItem(loader.getSize())) { | |
| filestoreItem = JGitUtils.getFilestoreItem(tw.getObjectReader().open(id)); | |
| } | |
| final long size = (filestoreItem == null) ? loader.getSize() : filestoreItem.getSize(); | |
| entry.setSize(size); | |
| tos.putArchiveEntry(entry); | |
| if (filestoreItem == null) { | |
| //Copy repository stored file | |
| loader.copyTo(tos); | |
| } else { | |
| //Copy filestore file | |
| try (FileInputStream streamIn = new FileInputStream(filestoreManager.getStoragePath(filestoreItem.oid))) { | |
| IOUtils.copyLarge(streamIn, tos); | |
| } catch (Throwable e) { | |
| LOGGER.error(MessageFormat.format("Failed to archive filestore item {0}", filestoreItem.oid), e); | |
| //Handle as per other errors | |
| throw e; | |
| } | |
| } | |
| tos.closeArchiveEntry(); | |
| } | |
| } | |
| tos.finish(); | |
| tos.close(); | |
| cos.close(); | |
| success = true; | |
| } catch (IOException e) { | |
| error(e, repository, "{0} failed to {1} stream files from commit {2}", algorithm, commit.getName()); | |
| } finally { | |
| tw.close(); | |
| rw.dispose(); | |
| } | |
| return success; | |
| } | |
| } |