14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Export a Tree to a non-versioned directory.
17
"""Export a tree to a tarball."""
19
from __future__ import absolute_import
21
from contextlib import closing
25
from bzrlib import export, osutils
26
from bzrlib.export import _export_iter_entries
27
from bzrlib.filters import (
29
filtered_output_bytes,
31
from bzrlib.trace import mutter
34
def tar_exporter(tree, dest, root, subdir, compression=None, filtered=False,
35
per_file_timestamps=False):
36
"""Export this tree to a new tar file.
38
`dest` will be created holding the contents of this tree; if it
39
already exists, it will be clobbered, like with "tar -c".
41
mutter('export version %r', tree)
43
compression = str(compression or '')
45
# XXX: If no root is given, the output tarball will contain files
46
# named '-/foo'; perhaps this is the most reasonable thing.
47
ball = tarfile.open(None, 'w|' + compression, sys.stdout)
50
root = export.get_root_name(dest)
52
# tarfile.open goes on to do 'os.getcwd() + dest' for opening
53
# the tar file. With dest being unicode, this throws UnicodeDecodeError
54
# unless we encode dest before passing it on. This works around
55
# upstream python bug http://bugs.python.org/issue8396
56
# (fixed in Python 2.6.5 and 2.7b1)
57
ball = tarfile.open(dest.encode(osutils._fs_enc), 'w:' + compression)
59
for dp, ie in _export_iter_entries(tree, subdir):
60
filename = osutils.pathjoin(root, dp).encode('utf8')
61
item = tarfile.TarInfo(filename)
62
if per_file_timestamps:
63
item.mtime = tree.get_file_mtime(ie.file_id, dp)
67
item.type = tarfile.REGTYPE
68
if tree.is_executable(ie.file_id):
73
chunks = tree.get_file_lines(ie.file_id)
74
filters = tree._content_filter_stack(dp)
75
context = ContentFilterContext(dp, tree, ie)
76
contents = filtered_output_bytes(chunks, filters, context)
77
content = ''.join(contents)
78
item.size = len(content)
79
fileobj = StringIO.StringIO(content)
81
item.size = ie.text_size
82
fileobj = tree.get_file(ie.file_id)
83
elif ie.kind == "directory":
84
item.type = tarfile.DIRTYPE
89
elif ie.kind == "symlink":
90
item.type = tarfile.SYMTYPE
93
item.linkname = ie.symlink_target
96
raise BzrError("don't know how to export {%s} of kind %r" %
97
(ie.file_id, ie.kind))
98
ball.addfile(item, fileobj)
102
def tgz_exporter(tree, dest, root, subdir, filtered=False,
103
per_file_timestamps=False):
104
tar_exporter(tree, dest, root, subdir, compression='gz',
105
filtered=filtered, per_file_timestamps=per_file_timestamps)
108
def tbz_exporter(tree, dest, root, subdir, filtered=False,
109
per_file_timestamps=False):
110
tar_exporter(tree, dest, root, subdir, compression='bz2',
111
filtered=filtered, per_file_timestamps=per_file_timestamps)
30
from ..export import _export_iter_entries
31
from ..sixish import (
36
def prepare_tarball_item(tree, root, final_path, tree_path, entry, force_mtime=None):
37
"""Prepare a tarball item for exporting
39
:param tree: Tree to export
40
:param final_path: Final path to place item
41
:param tree_path: Path for the entry in the tree
42
:param entry: Entry to export
43
:param force_mtime: Option mtime to force, instead of using tree
46
Returns a (tarinfo, fileobj) tuple
48
file_id = getattr(entry, 'file_id', None)
49
filename = osutils.pathjoin(root, final_path)
50
item = tarfile.TarInfo(filename)
51
if force_mtime is not None:
52
item.mtime = force_mtime
54
item.mtime = tree.get_file_mtime(tree_path)
55
if entry.kind == "file":
56
item.type = tarfile.REGTYPE
57
if tree.is_executable(tree_path):
61
# This brings the whole file into memory, but that's almost needed for
62
# the tarfile contract, which wants the size of the file up front. We
63
# want to make sure it doesn't change, and we need to read it in one
64
# go for content filtering.
65
content = tree.get_file_text(tree_path)
66
item.size = len(content)
67
fileobj = BytesIO(content)
68
elif entry.kind in ("directory", "tree-reference"):
69
item.type = tarfile.DIRTYPE
74
elif entry.kind == "symlink":
75
item.type = tarfile.SYMTYPE
78
item.linkname = tree.get_symlink_target(tree_path)
81
raise errors.BzrError("don't know how to export {%s} of kind %r"
82
% (file_id, entry.kind))
83
return (item, fileobj)
86
def tarball_generator(tree, root, subdir=None, force_mtime=None, format=''):
87
"""Export tree contents to a tarball.
89
:returns: A generator that will produce file content chunks.
91
:param tree: Tree to export
93
:param subdir: Sub directory to export
95
:param force_mtime: Option mtime to force, instead of using tree
99
with closing(tarfile.open(None, "w:%s" % format, buf)) as ball, tree.lock_read():
100
for final_path, tree_path, entry in _export_iter_entries(tree, subdir):
101
(item, fileobj) = prepare_tarball_item(
102
tree, root, final_path, tree_path, entry, force_mtime)
103
ball.addfile(item, fileobj)
104
# Yield the data that was written so far, rinse, repeat.
111
def tgz_generator(tree, dest, root, subdir, force_mtime=None):
112
"""Export this tree to a new tar file.
114
`dest` will be created holding the contents of this tree; if it
115
already exists, it will be clobbered, like with "tar -c".
117
with tree.lock_read():
119
if force_mtime is not None:
120
root_mtime = force_mtime
121
elif (getattr(tree, "repository", None) and
122
getattr(tree, "get_revision_id", None)):
123
# If this is a revision tree, use the revisions' timestamp
124
rev = tree.repository.get_revision(tree.get_revision_id())
125
root_mtime = rev.timestamp
126
elif tree.is_versioned(u''):
127
root_mtime = tree.get_file_mtime('')
133
# gzip file is used with an explicit fileobj so that
134
# the basename can be stored in the gzip file rather than
136
basename = os.path.basename(dest)
138
zipstream = gzip.GzipFile(basename, 'w', fileobj=buf,
140
for chunk in tarball_generator(tree, root, subdir, force_mtime):
141
zipstream.write(chunk)
142
# Yield the data that was written so far, rinse, repeat.
146
# Closing zipstream may trigger writes to stream
151
def tbz_generator(tree, dest, root, subdir, force_mtime=None):
152
"""Export this tree to a new tar file.
154
`dest` will be created holding the contents of this tree; if it
155
already exists, it will be clobbered, like with "tar -c".
157
return tarball_generator(
158
tree, root, subdir, force_mtime, format='bz2')
161
def plain_tar_generator(tree, dest, root, subdir,
163
"""Export this tree to a new tar file.
165
`dest` will be created holding the contents of this tree; if it
166
already exists, it will be clobbered, like with "tar -c".
168
return tarball_generator(
169
tree, root, subdir, force_mtime, format='')
172
def tar_xz_generator(tree, dest, root, subdir, force_mtime=None):
173
return tar_lzma_generator(tree, dest, root, subdir, force_mtime, "xz")
176
def tar_lzma_generator(tree, dest, root, subdir, force_mtime=None,
177
compression_format="alone"):
178
"""Export this tree to a new .tar.lzma file.
180
`dest` will be created holding the contents of this tree; if it
181
already exists, it will be clobbered, like with "tar -c".
185
except ImportError as e:
186
raise errors.DependencyNotPresent('lzma', e)
188
if sys.version_info[0] == 2:
189
compressor = lzma.LZMACompressor(
190
options={"format": compression_format})
192
compressor = lzma.LZMACompressor(
194
'xz': lzma.FORMAT_XZ,
195
'raw': lzma.FORMAT_RAW,
196
'alone': lzma.FORMAT_ALONE,
197
}[compression_format])
199
for chunk in tarball_generator(
200
tree, root, subdir, force_mtime=force_mtime):
201
yield compressor.compress(chunk)
203
yield compressor.flush()