1
# Copyright (C) 2005, 2006, 2008-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Export a tree to a tarball."""
19
from __future__ import absolute_import
21
from contextlib import closing
22
from io import BytesIO
31
from ..export import _export_iter_entries
34
def prepare_tarball_item(tree, root, final_path, tree_path, entry, force_mtime=None):
35
"""Prepare a tarball item for exporting
37
:param tree: Tree to export
38
:param final_path: Final path to place item
39
:param tree_path: Path for the entry in the tree
40
:param entry: Entry to export
41
:param force_mtime: Option mtime to force, instead of using tree
44
Returns a (tarinfo, fileobj) tuple
46
file_id = getattr(entry, 'file_id', None)
47
filename = osutils.pathjoin(root, final_path)
48
item = tarfile.TarInfo(filename)
49
if force_mtime is not None:
50
item.mtime = force_mtime
52
item.mtime = tree.get_file_mtime(tree_path)
53
if entry.kind == "file":
54
item.type = tarfile.REGTYPE
55
if tree.is_executable(tree_path):
59
# This brings the whole file into memory, but that's almost needed for
60
# the tarfile contract, which wants the size of the file up front. We
61
# want to make sure it doesn't change, and we need to read it in one
62
# go for content filtering.
63
content = tree.get_file_text(tree_path)
64
item.size = len(content)
65
fileobj = BytesIO(content)
66
elif entry.kind in ("directory", "tree-reference"):
67
item.type = tarfile.DIRTYPE
72
elif entry.kind == "symlink":
73
item.type = tarfile.SYMTYPE
76
item.linkname = tree.get_symlink_target(tree_path)
79
raise errors.BzrError("don't know how to export {%s} of kind %r"
80
% (file_id, entry.kind))
81
return (item, fileobj)
84
def tarball_generator(tree, root, subdir=None, force_mtime=None, format=''):
85
"""Export tree contents to a tarball.
87
:returns: A generator that will produce file content chunks.
89
:param tree: Tree to export
91
:param subdir: Sub directory to export
93
:param force_mtime: Option mtime to force, instead of using tree
97
with closing(tarfile.open(None, "w:%s" % format, buf)) as ball, tree.lock_read():
98
for final_path, tree_path, entry in _export_iter_entries(tree, subdir):
99
(item, fileobj) = prepare_tarball_item(
100
tree, root, final_path, tree_path, entry, force_mtime)
101
ball.addfile(item, fileobj)
102
# Yield the data that was written so far, rinse, repeat.
109
def tgz_generator(tree, dest, root, subdir, force_mtime=None):
110
"""Export this tree to a new tar file.
112
`dest` will be created holding the contents of this tree; if it
113
already exists, it will be clobbered, like with "tar -c".
115
with tree.lock_read():
117
if force_mtime is not None:
118
root_mtime = force_mtime
119
elif (getattr(tree, "repository", None) and
120
getattr(tree, "get_revision_id", None)):
121
# If this is a revision tree, use the revisions' timestamp
122
rev = tree.repository.get_revision(tree.get_revision_id())
123
root_mtime = rev.timestamp
124
elif tree.is_versioned(u''):
125
root_mtime = tree.get_file_mtime('')
131
# gzip file is used with an explicit fileobj so that
132
# the basename can be stored in the gzip file rather than
134
basename = os.path.basename(dest)
136
zipstream = gzip.GzipFile(basename, 'w', fileobj=buf,
138
for chunk in tarball_generator(tree, root, subdir, force_mtime):
139
zipstream.write(chunk)
140
# Yield the data that was written so far, rinse, repeat.
144
# Closing zipstream may trigger writes to stream
149
def tbz_generator(tree, dest, root, subdir, force_mtime=None):
150
"""Export this tree to a new tar file.
152
`dest` will be created holding the contents of this tree; if it
153
already exists, it will be clobbered, like with "tar -c".
155
return tarball_generator(
156
tree, root, subdir, force_mtime, format='bz2')
159
def plain_tar_generator(tree, dest, root, subdir,
161
"""Export this tree to a new tar file.
163
`dest` will be created holding the contents of this tree; if it
164
already exists, it will be clobbered, like with "tar -c".
166
return tarball_generator(
167
tree, root, subdir, force_mtime, format='')
170
def tar_xz_generator(tree, dest, root, subdir, force_mtime=None):
171
return tar_lzma_generator(tree, dest, root, subdir, force_mtime, "xz")
174
def tar_lzma_generator(tree, dest, root, subdir, force_mtime=None,
175
compression_format="alone"):
176
"""Export this tree to a new .tar.lzma file.
178
`dest` will be created holding the contents of this tree; if it
179
already exists, it will be clobbered, like with "tar -c".
183
except ImportError as e:
184
raise errors.DependencyNotPresent('lzma', e)
186
compressor = lzma.LZMACompressor(
188
'xz': lzma.FORMAT_XZ,
189
'raw': lzma.FORMAT_RAW,
190
'alone': lzma.FORMAT_ALONE,
191
}[compression_format])
193
for chunk in tarball_generator(
194
tree, root, subdir, force_mtime=force_mtime):
195
yield compressor.compress(chunk)
197
yield compressor.flush()