diff options
Diffstat (limited to 'bzrlib/export/tar_exporter.py')
-rw-r--r-- | bzrlib/export/tar_exporter.py | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/bzrlib/export/tar_exporter.py b/bzrlib/export/tar_exporter.py new file mode 100644 index 0000000..b385666 --- /dev/null +++ b/bzrlib/export/tar_exporter.py @@ -0,0 +1,228 @@ +# Copyright (C) 2005, 2006, 2008-2011 Canonical Ltd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""Export a tree to a tarball.""" + +from __future__ import absolute_import + +import os +import StringIO +import sys +import tarfile + +from bzrlib import ( + errors, + osutils, + ) +from bzrlib.export import _export_iter_entries + + +def prepare_tarball_item(tree, root, final_path, tree_path, entry, force_mtime=None): + """Prepare a tarball item for exporting + + :param tree: Tree to export + :param final_path: Final path to place item + :param tree_path: Path for the entry in the tree + :param entry: Entry to export + :param force_mtime: Option mtime to force, instead of using tree + timestamps. + + Returns a (tarinfo, fileobj) tuple + """ + filename = osutils.pathjoin(root, final_path).encode('utf8') + item = tarfile.TarInfo(filename) + if force_mtime is not None: + item.mtime = force_mtime + else: + item.mtime = tree.get_file_mtime(entry.file_id, tree_path) + if entry.kind == "file": + item.type = tarfile.REGTYPE + if tree.is_executable(entry.file_id, tree_path): + item.mode = 0755 + else: + item.mode = 0644 + # This brings the whole file into memory, but that's almost needed for + # the tarfile contract, which wants the size of the file up front. We + # want to make sure it doesn't change, and we need to read it in one + # go for content filtering. + content = tree.get_file_text(entry.file_id, tree_path) + item.size = len(content) + fileobj = StringIO.StringIO(content) + elif entry.kind == "directory": + item.type = tarfile.DIRTYPE + item.name += '/' + item.size = 0 + item.mode = 0755 + fileobj = None + elif entry.kind == "symlink": + item.type = tarfile.SYMTYPE + item.size = 0 + item.mode = 0755 + item.linkname = tree.get_symlink_target(entry.file_id, tree_path) + fileobj = None + else: + raise errors.BzrError("don't know how to export {%s} of kind %r" + % (entry.file_id, entry.kind)) + return (item, fileobj) + + +def export_tarball_generator(tree, ball, root, subdir=None, force_mtime=None): + """Export tree contents to a tarball. + + :returns: A generator that will repeatedly produce None as each file is + emitted. The entire generator must be consumed to complete writing + the file. + + :param tree: Tree to export + + :param ball: Tarball to export to; it will be closed when writing is + complete. + + :param subdir: Sub directory to export + + :param force_mtime: Option mtime to force, instead of using tree + timestamps. + """ + try: + for final_path, tree_path, entry in _export_iter_entries(tree, subdir): + (item, fileobj) = prepare_tarball_item( + tree, root, final_path, tree_path, entry, force_mtime) + ball.addfile(item, fileobj) + yield + finally: + ball.close() + + +def tgz_exporter_generator(tree, dest, root, subdir, force_mtime=None, + fileobj=None): + """Export this tree to a new tar file. + + `dest` will be created holding the contents of this tree; if it + already exists, it will be clobbered, like with "tar -c". + """ + import gzip + if force_mtime is not None: + root_mtime = force_mtime + elif (getattr(tree, "repository", None) and + getattr(tree, "get_revision_id", None)): + # If this is a revision tree, use the revisions' timestamp + rev = tree.repository.get_revision(tree.get_revision_id()) + root_mtime = rev.timestamp + elif tree.get_root_id() is not None: + root_mtime = tree.get_file_mtime(tree.get_root_id()) + else: + root_mtime = None + + is_stdout = False + basename = None + if fileobj is not None: + stream = fileobj + elif dest == '-': + stream = sys.stdout + is_stdout = True + else: + stream = open(dest, 'wb') + # gzip file is used with an explicit fileobj so that + # the basename can be stored in the gzip file rather than + # dest. (bug 102234) + basename = os.path.basename(dest) + try: + zipstream = gzip.GzipFile(basename, 'w', fileobj=stream, + mtime=root_mtime) + except TypeError: + # Python < 2.7 doesn't support the mtime argument + zipstream = gzip.GzipFile(basename, 'w', fileobj=stream) + ball = tarfile.open(None, 'w|', fileobj=zipstream) + for _ in export_tarball_generator( + tree, ball, root, subdir, force_mtime): + yield + # Closing zipstream may trigger writes to stream + zipstream.close() + if not is_stdout: + # Now we can safely close the stream + stream.close() + + +def tbz_exporter_generator(tree, dest, root, subdir, + force_mtime=None, fileobj=None): + """Export this tree to a new tar file. + + `dest` will be created holding the contents of this tree; if it + already exists, it will be clobbered, like with "tar -c". + """ + if fileobj is not None: + ball = tarfile.open(None, 'w|bz2', fileobj) + elif dest == '-': + ball = tarfile.open(None, 'w|bz2', sys.stdout) + else: + # tarfile.open goes on to do 'os.getcwd() + dest' for opening the + # tar file. With dest being unicode, this throws UnicodeDecodeError + # unless we encode dest before passing it on. This works around + # upstream python bug http://bugs.python.org/issue8396 (fixed in + # Python 2.6.5 and 2.7b1) + ball = tarfile.open(dest.encode(osutils._fs_enc), 'w:bz2') + return export_tarball_generator( + tree, ball, root, subdir, force_mtime) + + +def plain_tar_exporter_generator(tree, dest, root, subdir, compression=None, + force_mtime=None, fileobj=None): + """Export this tree to a new tar file. + + `dest` will be created holding the contents of this tree; if it + already exists, it will be clobbered, like with "tar -c". + """ + if fileobj is not None: + stream = fileobj + elif dest == '-': + stream = sys.stdout + else: + stream = open(dest, 'wb') + ball = tarfile.open(None, 'w|', stream) + return export_tarball_generator( + tree, ball, root, subdir, force_mtime) + + +def tar_xz_exporter_generator(tree, dest, root, subdir, + force_mtime=None, fileobj=None): + return tar_lzma_exporter_generator(tree, dest, root, subdir, + force_mtime, fileobj, "xz") + + +def tar_lzma_exporter_generator(tree, dest, root, subdir, + force_mtime=None, fileobj=None, + compression_format="alone"): + """Export this tree to a new .tar.lzma file. + + `dest` will be created holding the contents of this tree; if it + already exists, it will be clobbered, like with "tar -c". + """ + if dest == '-': + raise errors.BzrError("Writing to stdout not supported for .tar.lzma") + + if fileobj is not None: + raise errors.BzrError( + "Writing to fileobject not supported for .tar.lzma") + try: + import lzma + except ImportError, e: + raise errors.DependencyNotPresent('lzma', e) + + stream = lzma.LZMAFile(dest.encode(osutils._fs_enc), 'w', + options={"format": compression_format}) + ball = tarfile.open(None, 'w:', fileobj=stream) + return export_tarball_generator( + tree, ball, root, subdir, force_mtime=force_mtime) |