~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/export/tar_exporter.py

  • Committer: Patch Queue Manager
  • Date: 2016-04-21 04:10:52 UTC
  • mfrom: (6616.1.1 fix-en-user-guide)
  • Revision ID: pqm@pqm.ubuntu.com-20160421041052-clcye7ns1qcl2n7w
(richard-wilbur) Ensure build of English use guide always uses English text
 even when user's locale specifies a different language. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008, 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
16
16
 
17
 
"""Export a Tree to a non-versioned directory.
18
 
"""
19
 
 
 
17
"""Export a tree to a tarball."""
 
18
 
 
19
from __future__ import absolute_import
 
20
 
 
21
import os
20
22
import StringIO
21
23
import sys
22
24
import tarfile
23
 
import time
24
25
 
25
 
from bzrlib import export, osutils
 
26
from bzrlib import (
 
27
    errors,
 
28
    osutils,
 
29
    )
26
30
from bzrlib.export import _export_iter_entries
27
 
from bzrlib.filters import (
28
 
    ContentFilterContext,
29
 
    filtered_output_bytes,
30
 
    )
31
 
from bzrlib.trace import mutter
32
 
 
33
 
 
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.
37
 
 
38
 
    `dest` will be created holding the contents of this tree; if it
39
 
    already exists, it will be clobbered, like with "tar -c".
40
 
    """
41
 
    mutter('export version %r', tree)
42
 
    now = time.time()
43
 
    compression = str(compression or '')
 
31
 
 
32
 
 
33
def prepare_tarball_item(tree, root, final_path, tree_path, entry, force_mtime=None):
 
34
    """Prepare a tarball item for exporting
 
35
 
 
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
 
41
        timestamps.
 
42
 
 
43
    Returns a (tarinfo, fileobj) tuple
 
44
    """
 
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
 
49
    else:
 
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):
 
54
            item.mode = 0755
 
55
        else:
 
56
            item.mode = 0644
 
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
 
66
        item.name += '/'
 
67
        item.size = 0
 
68
        item.mode = 0755
 
69
        fileobj = None
 
70
    elif entry.kind == "symlink":
 
71
        item.type = tarfile.SYMTYPE
 
72
        item.size = 0
 
73
        item.mode = 0755
 
74
        item.linkname = tree.get_symlink_target(entry.file_id, tree_path)
 
75
        fileobj = None
 
76
    else:
 
77
        raise errors.BzrError("don't know how to export {%s} of kind %r"
 
78
                              % (entry.file_id, entry.kind))
 
79
    return (item, fileobj)
 
80
 
 
81
 
 
82
def export_tarball_generator(tree, ball, root, subdir=None, force_mtime=None):
 
83
    """Export tree contents to a tarball.
 
84
 
 
85
    :returns: A generator that will repeatedly produce None as each file is
 
86
        emitted.  The entire generator must be consumed to complete writing
 
87
        the file.
 
88
 
 
89
    :param tree: Tree to export
 
90
 
 
91
    :param ball: Tarball to export to; it will be closed when writing is
 
92
        complete.
 
93
 
 
94
    :param subdir: Sub directory to export
 
95
 
 
96
    :param force_mtime: Option mtime to force, instead of using tree
 
97
        timestamps.
 
98
    """
 
99
    try:
 
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
 
105
    finally:
 
106
        ball.close()
 
107
 
 
108
 
 
109
def tgz_exporter_generator(tree, dest, root, subdir, force_mtime=None,
 
110
    fileobj=None):
 
111
    """Export this tree to a new tar file.
 
112
 
 
113
    `dest` will be created holding the contents of this tree; if it
 
114
    already exists, it will be clobbered, like with "tar -c".
 
115
    """
 
116
    import gzip
 
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())
 
126
    else:
 
127
        root_mtime = None
 
128
 
 
129
    is_stdout = False
 
130
    basename = None
 
131
    if fileobj is not None:
 
132
        stream = fileobj
 
133
    elif dest == '-':
 
134
        stream = sys.stdout
 
135
        is_stdout = True
 
136
    else:
 
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
 
140
        # dest. (bug 102234)
 
141
        basename = os.path.basename(dest)
 
142
    try:
 
143
        zipstream = gzip.GzipFile(basename, 'w', fileobj=stream,
 
144
                                  mtime=root_mtime)
 
145
    except TypeError:
 
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):
 
151
        yield
 
152
    # Closing zipstream may trigger writes to stream
 
153
    zipstream.close()
 
154
    if not is_stdout:
 
155
        # Now we can safely close the stream
 
156
        stream.close()
 
157
 
 
158
 
 
159
def tbz_exporter_generator(tree, dest, root, subdir,
 
160
                           force_mtime=None, fileobj=None):
 
161
    """Export this tree to a new tar file.
 
162
 
 
163
    `dest` will be created holding the contents of this tree; if it
 
164
    already exists, it will be clobbered, like with "tar -c".
 
165
    """
 
166
    if fileobj is not None:
 
167
        ball = tarfile.open(None, 'w|bz2', fileobj)
 
168
    elif dest == '-':
 
169
        ball = tarfile.open(None, 'w|bz2', sys.stdout)
 
170
    else:
 
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)
 
179
 
 
180
 
 
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.
 
184
 
 
185
    `dest` will be created holding the contents of this tree; if it
 
186
    already exists, it will be clobbered, like with "tar -c".
 
187
    """
 
188
    if fileobj is not None:
 
189
        stream = fileobj
 
190
    elif dest == '-':
 
191
        stream = sys.stdout
 
192
    else:
 
193
        stream = open(dest, 'wb')
 
194
    ball = tarfile.open(None, 'w|', stream)
 
195
    return export_tarball_generator(
 
196
        tree, ball, root, subdir, force_mtime)
 
197
 
 
198
 
 
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")
 
203
 
 
204
 
 
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.
 
209
 
 
210
    `dest` will be created holding the contents of this tree; if it
 
211
    already exists, it will be clobbered, like with "tar -c".
 
212
    """
44
213
    if dest == '-':
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)
48
 
    else:
49
 
        if root is None:
50
 
            root = export.get_root_name(dest)
51
 
 
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)
58
 
 
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)
64
 
        else:
65
 
            item.mtime = now
66
 
        if ie.kind == "file":
67
 
            item.type = tarfile.REGTYPE
68
 
            if tree.is_executable(ie.file_id):
69
 
                item.mode = 0755
70
 
            else:
71
 
                item.mode = 0644
72
 
            if filtered:
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)
80
 
            else:
81
 
                item.size = ie.text_size
82
 
                fileobj = tree.get_file(ie.file_id)
83
 
        elif ie.kind == "directory":
84
 
            item.type = tarfile.DIRTYPE
85
 
            item.name += '/'
86
 
            item.size = 0
87
 
            item.mode = 0755
88
 
            fileobj = None
89
 
        elif ie.kind == "symlink":
90
 
            item.type = tarfile.SYMTYPE
91
 
            item.size = 0
92
 
            item.mode = 0755
93
 
            item.linkname = ie.symlink_target
94
 
            fileobj = None
95
 
        else:
96
 
            raise BzrError("don't know how to export {%s} of kind %r" %
97
 
                           (ie.file_id, ie.kind))
98
 
        ball.addfile(item, fileobj)
99
 
    ball.close()
100
 
 
101
 
 
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)
106
 
 
107
 
 
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)
 
214
        raise errors.BzrError("Writing to stdout not supported for .tar.lzma")
 
215
 
 
216
    if fileobj is not None:
 
217
        raise errors.BzrError(
 
218
            "Writing to fileobject not supported for .tar.lzma")
 
219
    try:
 
220
        import lzma
 
221
    except ImportError, e:
 
222
        raise errors.DependencyNotPresent('lzma', e)
 
223
 
 
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)