/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/export.py

  • Committer: Jelmer Vernooij
  • Date: 2018-05-20 15:54:59 UTC
  • mfrom: (6968.2.10 archive)
  • mto: This revision was merged to the branch mainline in revision 6973.
  • Revision ID: jelmer@jelmer.uk-20180520155459-4u1tpealx8jj3sy3
Merge archive branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
 
20
20
from __future__ import absolute_import
21
21
 
 
22
import errno
22
23
import os
 
24
import sys
23
25
import time
24
26
import warnings
25
27
 
26
 
from .. import (
 
28
from . import (
 
29
    archive,
27
30
    errors,
28
 
    pyutils,
 
31
    osutils,
29
32
    trace,
30
33
    )
31
34
 
32
 
# Maps format name => export function
33
 
_exporters = {}
34
 
# Maps filename extensions => export format name
35
 
_exporter_extensions = {}
36
 
 
37
 
 
38
 
def register_exporter(format, extensions, func, override=False):
39
 
    """Register an exporter.
40
 
 
41
 
    :param format: This is the name of the format, such as 'tgz' or 'zip'
42
 
    :param extensions: Extensions which should be used in the case that a
43
 
                       format was not explicitly specified.
44
 
    :type extensions: List
45
 
    :param func: The function. It will be called with (tree, dest, root)
46
 
    :param override: Whether to override an object which already exists.
47
 
                     Frequently plugins will want to provide functionality
48
 
                     until it shows up in mainline, so the default is False.
49
 
    """
50
 
    global _exporters, _exporter_extensions
51
 
 
52
 
    if (format not in _exporters) or override:
53
 
        _exporters[format] = func
54
 
 
55
 
    for ext in extensions:
56
 
        if (ext not in _exporter_extensions) or override:
57
 
            _exporter_extensions[ext] = format
58
 
 
59
 
 
60
 
def register_lazy_exporter(scheme, extensions, module, funcname):
61
 
    """Register lazy-loaded exporter function.
62
 
 
63
 
    When requesting a specific type of export, load the respective path.
64
 
    """
65
 
    def _loader(tree, dest, root, subdir, force_mtime, fileobj):
66
 
        func = pyutils.get_named_object(module, funcname)
67
 
        return func(tree, dest, root, subdir, force_mtime=force_mtime,
68
 
            fileobj=fileobj)
69
 
 
70
 
    register_exporter(scheme, extensions, _loader)
71
 
 
72
 
 
73
 
def get_stream_export_generator(tree, name=None, format=None, root=None,
74
 
        subdir=None, per_file_timestamps=False):
75
 
    """Returns a generator that exports the given tree as a stream.
76
 
 
77
 
    The generator is expected to yield None while exporting the tree while the
78
 
    actual export is written to ``fileobj``.
79
 
 
80
 
    :param tree: A Tree (such as RevisionTree) to export
81
 
 
82
 
    :param dest: The destination where the files, etc should be put
83
 
 
84
 
    :param format: The format (dir, zip, etc), if None, it will check the
85
 
        extension on dest, looking for a match
86
 
 
87
 
    :param root: The root location inside the format.  It is common practise to
88
 
        have zipfiles and tarballs extract into a subdirectory, rather than
89
 
        into the current working directory.  If root is None, the default root
90
 
        will be selected as the destination without its extension.
91
 
 
92
 
    :param subdir: A starting directory within the tree. None means to export
93
 
        the entire tree, and anything else should specify the relative path to
94
 
        a directory to start exporting from.
95
 
 
96
 
    :param per_file_timestamps: Whether to use the timestamp stored in the tree
97
 
        rather than now(). This will do a revision lookup for every file so
98
 
        will be significantly slower.
99
 
    """
100
 
    global _exporters
101
 
 
102
 
    if format is None and name is not None:
103
 
        format = get_format_from_filename(name)
104
 
 
105
 
    if format is None:
106
 
        # Default to tar
107
 
        format = 'dir'
108
 
 
109
 
    if format in ('dir', 'tlzma', 'txz', 'tbz2'):
110
 
        # formats that don't support streaming
111
 
        raise errors.NoSuchExportFormat(format)
112
 
 
113
 
    if format not in _exporters:
114
 
        raise errors.NoSuchExportFormat(format)
115
 
 
116
 
    # Most of the exporters will just have to call
117
 
    # this function anyway, so why not do it for them
118
 
    if root is None:
119
 
        root = get_root_name(name)
120
 
 
121
 
    if not per_file_timestamps:
122
 
        force_mtime = time.time()
123
 
    else:
124
 
        force_mtime = None
125
 
 
126
 
    oldpos = 0
127
 
    import tempfile
128
 
    with tempfile.NamedTemporaryFile() as temp:
129
 
        with tree.lock_read():
130
 
            for _ in _exporters[format](
131
 
                tree, name, root, subdir,
132
 
                force_mtime=force_mtime, fileobj=temp.file):
133
 
                pos = temp.tell()
134
 
                temp.seek(oldpos)
135
 
                data = temp.read()
136
 
                oldpos = pos
137
 
                temp.seek(pos)
138
 
                yield data
139
 
            # FIXME(JRV): urgh, some exporters close the file for us so we need to reopen
140
 
            # it here.
141
 
            with open(temp.name, 'rb') as temp:
142
 
                temp.seek(oldpos)
143
 
                yield temp.read()
144
 
 
145
 
 
146
 
def get_format_from_filename(name):
147
 
    global _exporter_extensions
148
 
 
149
 
    for ext in _exporter_extensions:
150
 
        if name.endswith(ext):
151
 
            return _exporter_extensions[ext]
152
 
 
153
 
 
154
 
def get_export_generator(tree, dest=None, format=None, root=None, subdir=None,
155
 
                         per_file_timestamps=False, fileobj=None):
156
 
    """Returns a generator that exports the given tree.
157
 
 
158
 
    The generator is expected to yield None while exporting the tree while the
159
 
    actual export is written to ``fileobj``.
160
 
 
161
 
    :param tree: A Tree (such as RevisionTree) to export
162
 
 
163
 
    :param dest: The destination where the files, etc should be put
164
 
 
165
 
    :param format: The format (dir, zip, etc), if None, it will check the
166
 
        extension on dest, looking for a match
167
 
 
168
 
    :param root: The root location inside the format.  It is common practise to
169
 
        have zipfiles and tarballs extract into a subdirectory, rather than
170
 
        into the current working directory.  If root is None, the default root
171
 
        will be selected as the destination without its extension.
172
 
 
173
 
    :param subdir: A starting directory within the tree. None means to export
174
 
        the entire tree, and anything else should specify the relative path to
175
 
        a directory to start exporting from.
176
 
 
177
 
    :param per_file_timestamps: Whether to use the timestamp stored in the tree
178
 
        rather than now(). This will do a revision lookup for every file so
179
 
        will be significantly slower.
180
 
 
181
 
    :param fileobj: Optional file object to use
182
 
    """
183
 
    global _exporters
184
 
 
185
 
    if format is None and dest is not None:
186
 
        format = get_format_from_filename(dest)
187
 
 
188
 
    if format is None:
189
 
        # Default to 'dir'
190
 
        format = 'dir'
191
 
 
192
 
    # Most of the exporters will just have to call
193
 
    # this function anyway, so why not do it for them
194
 
    if root is None:
195
 
        root = get_root_name(dest)
196
 
 
197
 
    if format not in _exporters:
198
 
        raise errors.NoSuchExportFormat(format)
199
 
 
200
 
    if not per_file_timestamps:
201
 
        force_mtime = time.time()
202
 
    else:
203
 
        force_mtime = None
204
 
 
205
 
    trace.mutter('export version %r', tree)
206
 
 
207
 
    with tree.lock_read():
208
 
        for _ in _exporters[format](
209
 
            tree, dest, root, subdir,
210
 
            force_mtime=force_mtime, fileobj=fileobj):
211
 
            yield
212
 
 
213
35
 
214
36
def export(tree, dest, format=None, root=None, subdir=None,
215
37
           per_file_timestamps=False, fileobj=None):
234
56
        for every file so will be significantly slower.
235
57
    :param fileobj: Optional file object to use
236
58
    """
237
 
    for _ in get_export_generator(tree, dest, format, root, subdir,
238
 
                                  per_file_timestamps, fileobj):
239
 
        pass
 
59
    if format is None and dest is not None:
 
60
        format = guess_format(dest)
 
61
 
 
62
    # Most of the exporters will just have to call
 
63
    # this function anyway, so why not do it for them
 
64
    if root is None:
 
65
        root = get_root_name(dest)
 
66
 
 
67
    if not per_file_timestamps:
 
68
        force_mtime = time.time()
 
69
    else:
 
70
        force_mtime = None
 
71
 
 
72
    trace.mutter('export version %r', tree)
 
73
 
 
74
    if format == 'dir':
 
75
        # TODO(jelmer): If the tree is remote (e.g. HPSS, Git Remote),
 
76
        # then we should stream a tar file and unpack that on the fly.
 
77
        with tree.lock_read():
 
78
            for unused in dir_exporter_generator(tree, dest, root, subdir,
 
79
                    force_mtime):
 
80
                pass
 
81
        return
 
82
 
 
83
    with tree.lock_read():
 
84
        chunks = archive.create_archive(format, tree, dest, root, subdir,
 
85
                                        force_mtime)
 
86
        if dest == '-':
 
87
            for chunk in chunks:
 
88
                 sys.stdout.write(chunk)
 
89
        elif fileobj is not None:
 
90
            for chunk in chunks:
 
91
                fileobj.write(chunk)
 
92
        else:
 
93
            with open(dest, 'wb') as f:
 
94
                for chunk in chunks:
 
95
                    f.writelines(chunk)
 
96
 
 
97
 
 
98
def guess_format(filename, default='dir'):
 
99
    """Guess the export format based on a file name.
 
100
 
 
101
    :param filename: Filename to guess from
 
102
    :param default: Default format to fall back to
 
103
    :return: format name
 
104
    """
 
105
    format = archive.format_registry.get_format_from_filename(filename)
 
106
    if format is None:
 
107
        format = default
 
108
    return format
240
109
 
241
110
 
242
111
def get_root_name(dest):
248
117
        # Exporting to -/foo doesn't make sense so use relative paths.
249
118
        return ''
250
119
    dest = os.path.basename(dest)
251
 
    for ext in _exporter_extensions:
 
120
    for ext in archive.format_registry.extensions:
252
121
        if dest.endswith(ext):
253
122
            return dest[:-len(ext)]
254
123
    return dest
293
162
        yield final_path, path, entry
294
163
 
295
164
 
296
 
register_lazy_exporter('dir', [], 'breezy.export.dir_exporter',
297
 
                       'dir_exporter_generator')
298
 
register_lazy_exporter('tar', ['.tar'], 'breezy.export.tar_exporter',
299
 
                       'plain_tar_exporter_generator')
300
 
register_lazy_exporter('tgz', ['.tar.gz', '.tgz'],
301
 
                       'breezy.export.tar_exporter',
302
 
                       'tgz_exporter_generator')
303
 
register_lazy_exporter('tbz2', ['.tar.bz2', '.tbz2'],
304
 
                       'breezy.export.tar_exporter', 'tbz_exporter_generator')
305
 
register_lazy_exporter('tlzma', ['.tar.lzma'], 'breezy.export.tar_exporter',
306
 
                       'tar_lzma_exporter_generator')
307
 
register_lazy_exporter('txz', ['.tar.xz'], 'breezy.export.tar_exporter',
308
 
                       'tar_xz_exporter_generator')
309
 
register_lazy_exporter('zip', ['.zip'], 'breezy.export.zip_exporter',
310
 
                       'zip_exporter_generator')
 
165
def dir_exporter_generator(tree, dest, root, subdir=None,
 
166
                           force_mtime=None, fileobj=None):
 
167
    """Return a generator that exports this tree to a new directory.
 
168
 
 
169
    `dest` should either not exist or should be empty. If it does not exist it
 
170
    will be created holding the contents of this tree.
 
171
 
 
172
    :note: If the export fails, the destination directory will be
 
173
           left in an incompletely exported state: export is not transactional.
 
174
    """
 
175
    try:
 
176
        os.mkdir(dest)
 
177
    except OSError as e:
 
178
        if e.errno == errno.EEXIST:
 
179
            # check if directory empty
 
180
            if os.listdir(dest) != []:
 
181
                raise errors.BzrError(
 
182
                    "Can't export tree to non-empty directory.")
 
183
        else:
 
184
            raise
 
185
    # Iterate everything, building up the files we will want to export, and
 
186
    # creating the directories and symlinks that we need.
 
187
    # This tracks (file_id, (destination_path, executable))
 
188
    # This matches the api that tree.iter_files_bytes() wants
 
189
    # Note in the case of revision trees, this does trigger a double inventory
 
190
    # lookup, hopefully it isn't too expensive.
 
191
    to_fetch = []
 
192
    for dp, tp, ie in _export_iter_entries(tree, subdir):
 
193
        fullpath = osutils.pathjoin(dest, dp)
 
194
        if ie.kind == "file":
 
195
            to_fetch.append((tp, (dp, tp, ie.file_id)))
 
196
        elif ie.kind in ("directory", "tree-reference"):
 
197
            os.mkdir(fullpath)
 
198
        elif ie.kind == "symlink":
 
199
            try:
 
200
                symlink_target = tree.get_symlink_target(tp, ie.file_id)
 
201
                os.symlink(symlink_target, fullpath)
 
202
            except OSError as e:
 
203
                raise errors.BzrError(
 
204
                    "Failed to create symlink %r -> %r, error: %s"
 
205
                    % (fullpath, symlink_target, e))
 
206
        else:
 
207
            raise errors.BzrError("don't know how to export {%s} of kind %r" %
 
208
               (tp, ie.kind))
 
209
 
 
210
        yield
 
211
    # The data returned here can be in any order, but we've already created all
 
212
    # the directories
 
213
    flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | getattr(os, 'O_BINARY', 0)
 
214
    for (relpath, treepath, file_id), chunks in tree.iter_files_bytes(to_fetch):
 
215
        fullpath = osutils.pathjoin(dest, relpath)
 
216
        # We set the mode and let the umask sort out the file info
 
217
        mode = 0o666
 
218
        if tree.is_executable(treepath, file_id):
 
219
            mode = 0o777
 
220
        with os.fdopen(os.open(fullpath, flags, mode), 'wb') as out:
 
221
            out.writelines(chunks)
 
222
        if force_mtime is not None:
 
223
            mtime = force_mtime
 
224
        else:
 
225
            mtime = tree.get_file_mtime(treepath, file_id)
 
226
        os.utime(fullpath, (mtime, mtime))
 
227
 
 
228
        yield