13
13
# You should have received a copy of the GNU General Public License
14
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Export a Tree to a non-versioned directory.
25
from bzrlib import errors, export, osutils
30
26
from bzrlib.export import _export_iter_entries
33
def prepare_tarball_item(tree, root, final_path, tree_path, entry, force_mtime=None):
34
"""Prepare a tarball item for exporting
36
:param tree: Tree to export
37
:param final_path: Final path to place item
38
:param tree_path: Path for the entry in the tree
39
:param entry: Entry to export
40
:param force_mtime: Option mtime to force, instead of using tree
43
Returns a (tarinfo, fileobj) tuple
27
from bzrlib.trace import mutter
30
def tar_exporter(tree, dest, root, subdir, compression=None):
31
"""Export this tree to a new tar file.
33
`dest` will be created holding the contents of this tree; if it
34
already exists, it will be clobbered, like with "tar -c".
45
filename = osutils.pathjoin(root, final_path).encode('utf8')
46
item = tarfile.TarInfo(filename)
47
if force_mtime is not None:
48
item.mtime = force_mtime
36
mutter('export version %r', tree)
38
compression = str(compression or '')
40
# XXX: If no root is given, the output tarball will contain files
41
# named '-/foo'; perhaps this is the most reasonable thing.
42
ball = tarfile.open(None, 'w|' + compression, sys.stdout)
50
item.mtime = tree.get_file_mtime(entry.file_id, tree_path)
51
if entry.kind == "file":
52
item.type = tarfile.REGTYPE
53
if tree.is_executable(entry.file_id, tree_path):
45
root = export.get_root_name(dest)
46
ball = tarfile.open(dest, 'w:' + compression)
47
for dp, ie in _export_iter_entries(tree, subdir):
48
filename = osutils.pathjoin(root, dp).encode('utf8')
49
item = tarfile.TarInfo(filename)
52
item.type = tarfile.REGTYPE
53
if tree.is_executable(ie.file_id):
57
item.size = ie.text_size
58
fileobj = tree.get_file(ie.file_id)
59
elif ie.kind == "directory":
60
item.type = tarfile.DIRTYPE
65
elif ie.kind == "symlink":
66
item.type = tarfile.SYMTYPE
69
item.linkname = ie.symlink_target
57
# This brings the whole file into memory, but that's almost needed for
58
# the tarfile contract, which wants the size of the file up front. We
59
# want to make sure it doesn't change, and we need to read it in one
60
# go for content filtering.
61
content = tree.get_file_text(entry.file_id, tree_path)
62
item.size = len(content)
63
fileobj = StringIO.StringIO(content)
64
elif entry.kind == "directory":
65
item.type = tarfile.DIRTYPE
70
elif entry.kind == "symlink":
71
item.type = tarfile.SYMTYPE
74
item.linkname = tree.get_symlink_target(entry.file_id, tree_path)
77
raise errors.BzrError("don't know how to export {%s} of kind %r"
78
% (entry.file_id, entry.kind))
79
return (item, fileobj)
82
def export_tarball_generator(tree, ball, root, subdir=None, force_mtime=None):
83
"""Export tree contents to a tarball.
85
:returns: A generator that will repeatedly produce None as each file is
86
emitted. The entire generator must be consumed to complete writing
89
:param tree: Tree to export
91
:param ball: Tarball to export to; it will be closed when writing is
94
:param subdir: Sub directory to export
96
:param force_mtime: Option mtime to force, instead of using tree
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)
109
def tgz_exporter_generator(tree, dest, root, subdir, force_mtime=None,
111
"""Export this tree to a new tar file.
113
`dest` will be created holding the contents of this tree; if it
114
already exists, it will be clobbered, like with "tar -c".
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.get_root_id() is not None:
125
root_mtime = tree.get_file_mtime(tree.get_root_id())
131
if fileobj is not None:
137
stream = open(dest, 'wb')
138
# gzip file is used with an explicit fileobj so that
139
# the basename can be stored in the gzip file rather than
141
basename = os.path.basename(dest)
143
zipstream = gzip.GzipFile(basename, 'w', fileobj=stream,
146
# Python < 2.7 doesn't support the mtime argument
147
zipstream = gzip.GzipFile(basename, 'w', fileobj=stream)
148
ball = tarfile.open(None, 'w|', fileobj=zipstream)
149
for _ in export_tarball_generator(
150
tree, ball, root, subdir, force_mtime):
152
# Closing zipstream may trigger writes to stream
155
# Now we can safely close the stream
159
def tbz_exporter_generator(tree, dest, root, subdir,
160
force_mtime=None, fileobj=None):
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
if fileobj is not None:
167
ball = tarfile.open(None, 'w|bz2', fileobj)
169
ball = tarfile.open(None, 'w|bz2', sys.stdout)
171
# tarfile.open goes on to do 'os.getcwd() + dest' for opening the
172
# tar file. With dest being unicode, this throws UnicodeDecodeError
173
# unless we encode dest before passing it on. This works around
174
# upstream python bug http://bugs.python.org/issue8396 (fixed in
175
# Python 2.6.5 and 2.7b1)
176
ball = tarfile.open(dest.encode(osutils._fs_enc), 'w:bz2')
177
return export_tarball_generator(
178
tree, ball, root, subdir, force_mtime)
181
def plain_tar_exporter_generator(tree, dest, root, subdir, compression=None,
182
force_mtime=None, fileobj=None):
183
"""Export this tree to a new tar file.
185
`dest` will be created holding the contents of this tree; if it
186
already exists, it will be clobbered, like with "tar -c".
188
if fileobj is not None:
193
stream = open(dest, 'wb')
194
ball = tarfile.open(None, 'w|', stream)
195
return export_tarball_generator(
196
tree, ball, root, subdir, force_mtime)
199
def tar_xz_exporter_generator(tree, dest, root, subdir,
200
force_mtime=None, fileobj=None):
201
return tar_lzma_exporter_generator(tree, dest, root, subdir,
202
force_mtime, fileobj, "xz")
205
def tar_lzma_exporter_generator(tree, dest, root, subdir,
206
force_mtime=None, fileobj=None,
207
compression_format="alone"):
208
"""Export this tree to a new .tar.lzma file.
210
`dest` will be created holding the contents of this tree; if it
211
already exists, it will be clobbered, like with "tar -c".
214
raise errors.BzrError("Writing to stdout not supported for .tar.lzma")
216
if fileobj is not None:
217
raise errors.BzrError(
218
"Writing to fileobject not supported for .tar.lzma")
221
except ImportError, e:
222
raise errors.DependencyNotPresent('lzma', e)
224
stream = lzma.LZMAFile(dest.encode(osutils._fs_enc), 'w',
225
options={"format": compression_format})
226
ball = tarfile.open(None, 'w:', fileobj=stream)
227
return export_tarball_generator(
228
tree, ball, root, subdir, force_mtime=force_mtime)
72
raise BzrError("don't know how to export {%s} of kind %r" %
73
(ie.file_id, ie.kind))
74
ball.addfile(item, fileobj)
78
def tgz_exporter(tree, dest, root, subdir):
79
tar_exporter(tree, dest, root, subdir, compression='gz')
82
def tbz_exporter(tree, dest, root, subdir):
83
tar_exporter(tree, dest, root, subdir, compression='bz2')