~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/export/tar_exporter.py

  • Committer: Vincent Ladeuil
  • Date: 2011-07-06 09:22:00 UTC
  • mfrom: (6008 +trunk)
  • mto: (6012.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 6013.
  • Revision ID: v.ladeuil+lp@free.fr-20110706092200-7iai2mwzc0sqdsvf
MergingĀ inĀ trunk

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
 
"""
 
17
"""Export a Tree to a non-versioned directory."""
19
18
 
 
19
import os
20
20
import StringIO
21
21
import sys
22
22
import tarfile
23
 
import time
24
23
 
25
 
from bzrlib import export, osutils
 
24
from bzrlib import (
 
25
    errors,
 
26
    osutils,
 
27
    )
26
28
from bzrlib.export import _export_iter_entries
27
29
from bzrlib.filters import (
28
30
    ContentFilterContext,
29
31
    filtered_output_bytes,
30
32
    )
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 '')
 
33
 
 
34
 
 
35
def prepare_tarball_item(tree, root, final_path, entry, filtered=False,
 
36
                         force_mtime=None):
 
37
    """Prepare a tarball item for exporting
 
38
 
 
39
    :param tree: Tree to export
 
40
 
 
41
    :param final_path: Final path to place item
 
42
 
 
43
    :param entry: Entry to export
 
44
 
 
45
    :param filtered: Whether to apply filters
 
46
 
 
47
    :param force_mtime: Option mtime to force, instead of using tree
 
48
        timestamps.
 
49
 
 
50
    Returns a (tarinfo, fileobj) tuple
 
51
    """
 
52
    filename = osutils.pathjoin(root, final_path).encode('utf8')
 
53
    item = tarfile.TarInfo(filename)
 
54
    if force_mtime is not None:
 
55
        item.mtime = force_mtime
 
56
    else:
 
57
        item.mtime = tree.get_file_mtime(entry.file_id, final_path)
 
58
    if entry.kind == "file":
 
59
        item.type = tarfile.REGTYPE
 
60
        if tree.is_executable(entry.file_id):
 
61
            item.mode = 0755
 
62
        else:
 
63
            item.mode = 0644
 
64
        if filtered:
 
65
            chunks = tree.get_file_lines(entry.file_id)
 
66
            filters = tree._content_filter_stack(final_path)
 
67
            context = ContentFilterContext(final_path, tree, entry)
 
68
            contents = filtered_output_bytes(chunks, filters, context)
 
69
            content = ''.join(contents)
 
70
            item.size = len(content)
 
71
            fileobj = StringIO.StringIO(content)
 
72
        else:
 
73
            item.size = tree.get_file_size(entry.file_id)
 
74
            fileobj = tree.get_file(entry.file_id)
 
75
    elif entry.kind == "directory":
 
76
        item.type = tarfile.DIRTYPE
 
77
        item.name += '/'
 
78
        item.size = 0
 
79
        item.mode = 0755
 
80
        fileobj = None
 
81
    elif entry.kind == "symlink":
 
82
        item.type = tarfile.SYMTYPE
 
83
        item.size = 0
 
84
        item.mode = 0755
 
85
        item.linkname = tree.get_symlink_target(entry.file_id)
 
86
        fileobj = None
 
87
    else:
 
88
        raise errors.BzrError("don't know how to export {%s} of kind %r"
 
89
                              % (entry.file_id, entry.kind))
 
90
    return (item, fileobj)
 
91
 
 
92
 
 
93
def export_tarball_generator(tree, ball, root, subdir=None, filtered=False,
 
94
                   force_mtime=None):
 
95
    """Export tree contents to a tarball.
 
96
 
 
97
    :returns: A generator that will repeatedly produce None as each file is
 
98
        emitted.  The entire generator must be consumed to complete writing
 
99
        the file.
 
100
 
 
101
    :param tree: Tree to export
 
102
 
 
103
    :param ball: Tarball to export to; it will be closed when writing is
 
104
        complete.
 
105
 
 
106
    :param filtered: Whether to apply filters
 
107
 
 
108
    :param subdir: Sub directory to export
 
109
 
 
110
    :param force_mtime: Option mtime to force, instead of using tree
 
111
        timestamps.
 
112
    """
 
113
    try:
 
114
        for final_path, entry in _export_iter_entries(tree, subdir):
 
115
            (item, fileobj) = prepare_tarball_item(
 
116
                tree, root, final_path, entry, filtered, force_mtime)
 
117
            ball.addfile(item, fileobj)
 
118
            yield
 
119
    finally:
 
120
        ball.close()
 
121
 
 
122
 
 
123
def tgz_exporter_generator(tree, dest, root, subdir, filtered=False,
 
124
                           force_mtime=None, fileobj=None):
 
125
    """Export this tree to a new tar file.
 
126
 
 
127
    `dest` will be created holding the contents of this tree; if it
 
128
    already exists, it will be clobbered, like with "tar -c".
 
129
    """
 
130
    import gzip
 
131
    if force_mtime is not None:
 
132
        root_mtime = force_mtime
 
133
    elif (getattr(tree, "repository", None) and
 
134
          getattr(tree, "get_revision_id", None)):
 
135
        # If this is a revision tree, use the revisions' timestamp
 
136
        rev = tree.repository.get_revision(tree.get_revision_id())
 
137
        root_mtime = rev.timestamp
 
138
    elif tree.get_root_id() is not None:
 
139
        root_mtime = tree.get_file_mtime(tree.get_root_id())
 
140
    else:
 
141
        root_mtime = None
 
142
 
 
143
    is_stdout = False
 
144
    if fileobj is not None:
 
145
        stream = fileobj
 
146
    elif dest == '-':
 
147
        basename = None
 
148
        stream = sys.stdout
 
149
        is_stdout = True
 
150
    else:
 
151
        stream = open(dest, 'wb')
 
152
        # gzip file is used with an explicit fileobj so that
 
153
        # the basename can be stored in the gzip file rather than
 
154
        # dest. (bug 102234)
 
155
        basename = os.path.basename(dest)
 
156
    try:
 
157
        zipstream = gzip.GzipFile(basename, 'w', fileobj=stream,
 
158
                                  mtime=root_mtime)
 
159
    except TypeError:
 
160
        # Python < 2.7 doesn't support the mtime argument
 
161
        zipstream = gzip.GzipFile(basename, 'w', fileobj=stream)
 
162
    ball = tarfile.open(None, 'w|', fileobj=zipstream)
 
163
    for _ in export_tarball_generator(
 
164
        tree, ball, root, subdir, filtered, force_mtime):
 
165
        yield
 
166
    # Closing zipstream may trigger writes to stream
 
167
    zipstream.close()
 
168
    if not is_stdout:
 
169
        # Now we can safely close the stream
 
170
        stream.close()
 
171
 
 
172
 
 
173
def tbz_exporter_generator(tree, dest, root, subdir, filtered=False,
 
174
                           force_mtime=None, fileobj=None):
 
175
    """Export this tree to a new tar file.
 
176
 
 
177
    `dest` will be created holding the contents of this tree; if it
 
178
    already exists, it will be clobbered, like with "tar -c".
 
179
    """
 
180
    if fileobj is not None:
 
181
        ball = tarfile.open(None, 'w|bz2', fileobj)
 
182
    elif dest == '-':
 
183
        ball = tarfile.open(None, 'w|bz2', sys.stdout)
 
184
    else:
 
185
        # tarfile.open goes on to do 'os.getcwd() + dest' for opening the
 
186
        # tar file. With dest being unicode, this throws UnicodeDecodeError
 
187
        # unless we encode dest before passing it on. This works around
 
188
        # upstream python bug http://bugs.python.org/issue8396 (fixed in
 
189
        # Python 2.6.5 and 2.7b1)
 
190
        ball = tarfile.open(dest.encode(osutils._fs_enc), 'w:bz2')
 
191
    return export_tarball_generator(
 
192
        tree, ball, root, subdir, filtered, force_mtime)
 
193
 
 
194
 
 
195
def plain_tar_exporter_generator(tree, dest, root, subdir, compression=None,
 
196
                                 filtered=False, force_mtime=None,
 
197
                                 fileobj=None):
 
198
    """Export this tree to a new tar file.
 
199
 
 
200
    `dest` will be created holding the contents of this tree; if it
 
201
    already exists, it will be clobbered, like with "tar -c".
 
202
    """
 
203
    if fileobj is not None:
 
204
        stream = fileobj
 
205
    elif dest == '-':
 
206
        stream = sys.stdout
 
207
    else:
 
208
        stream = open(dest, 'wb')
 
209
    ball = tarfile.open(None, 'w|', stream)
 
210
    return export_tarball_generator(
 
211
        tree, ball, root, subdir, filtered, force_mtime)
 
212
 
 
213
 
 
214
def tar_xz_exporter_generator(tree, dest, root, subdir, filtered=False,
 
215
                              force_mtime=None, fileobj=None):
 
216
    return tar_lzma_exporter_generator(tree, dest, root, subdir, filtered,
 
217
                                       force_mtime, fileobj, "xz")
 
218
 
 
219
 
 
220
def tar_lzma_exporter_generator(tree, dest, root, subdir, filtered=False,
 
221
                      force_mtime=None, fileobj=None,
 
222
                                compression_format="alone"):
 
223
    """Export this tree to a new .tar.lzma file.
 
224
 
 
225
    `dest` will be created holding the contents of this tree; if it
 
226
    already exists, it will be clobbered, like with "tar -c".
 
227
    """
44
228
    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)
 
229
        raise errors.BzrError("Writing to stdout not supported for .tar.lzma")
 
230
 
 
231
    if fileobj is not None:
 
232
        raise errors.BzrError(
 
233
            "Writing to fileobject not supported for .tar.lzma")
 
234
    try:
 
235
        import lzma
 
236
    except ImportError, e:
 
237
        raise errors.DependencyNotPresent('lzma', e)
 
238
 
 
239
    stream = lzma.LZMAFile(dest.encode(osutils._fs_enc), 'w',
 
240
        options={"format": compression_format})
 
241
    ball = tarfile.open(None, 'w:', fileobj=stream)
 
242
    return export_tarball_generator(
 
243
        tree, ball, root, subdir, filtered=filtered, force_mtime=force_mtime)