/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: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
"""Export trees to tarballs, non-controlled directories, zipfiles, etc.
18
18
"""
19
19
 
20
 
from __future__ import absolute_import
21
 
 
 
20
import errno
22
21
import os
 
22
import sys
23
23
import time
24
 
import warnings
25
24
 
26
 
from .. import (
 
25
from . import (
 
26
    archive,
27
27
    errors,
28
 
    pyutils,
 
28
    osutils,
29
29
    trace,
30
30
    )
31
31
 
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_export_generator(tree, dest=None, format=None, root=None, subdir=None,
74
 
                         filtered=False, per_file_timestamps=False,
75
 
                         fileobj=None):
76
 
    """Returns a generator that exports the given tree.
77
 
 
78
 
    The generator is expected to yield None while exporting the tree while the
79
 
    actual export is written to ``fileobj``.
80
 
 
81
 
    :param tree: A Tree (such as RevisionTree) to export
82
 
 
83
 
    :param dest: The destination where the files, etc should be put
84
 
 
85
 
    :param format: The format (dir, zip, etc), if None, it will check the
86
 
        extension on dest, looking for a match
87
 
 
88
 
    :param root: The root location inside the format.  It is common practise to
89
 
        have zipfiles and tarballs extract into a subdirectory, rather than
90
 
        into the current working directory.  If root is None, the default root
91
 
        will be selected as the destination without its extension.
92
 
 
93
 
    :param subdir: A starting directory within the tree. None means to export
94
 
        the entire tree, and anything else should specify the relative path to
95
 
        a directory to start exporting from.
96
 
 
97
 
    :param filtered: If True, content filtering is applied to the exported
98
 
        files.  Deprecated in favour of passing a ContentFilterTree
99
 
        as the source.
100
 
 
101
 
    :param per_file_timestamps: Whether to use the timestamp stored in the tree
102
 
        rather than now(). This will do a revision lookup for every file so
103
 
        will be significantly slower.
104
 
 
105
 
    :param fileobj: Optional file object to use
106
 
    """
107
 
    global _exporters, _exporter_extensions
108
 
 
109
 
    if format is None and dest is not None:
110
 
        for ext in _exporter_extensions:
111
 
            if dest.endswith(ext):
112
 
                format = _exporter_extensions[ext]
113
 
                break
114
 
 
115
 
    # Most of the exporters will just have to call
116
 
    # this function anyway, so why not do it for them
117
 
    if root is None:
118
 
        root = get_root_name(dest)
119
 
 
120
 
    if format not in _exporters:
121
 
        raise errors.NoSuchExportFormat(format)
122
 
 
123
 
    if not per_file_timestamps:
124
 
        force_mtime = time.time()
125
 
    else:
126
 
        force_mtime = None
127
 
 
128
 
    trace.mutter('export version %r', tree)
129
 
 
130
 
    if filtered:
131
 
        from breezy.filter_tree import ContentFilterTree
132
 
        warnings.warn(
133
 
            "passing filtered=True to export is deprecated in bzr 2.4",
134
 
            stacklevel=2)
135
 
        tree = ContentFilterTree(tree, tree._content_filter_stack)
136
 
        # We don't want things re-filtered by the specific exporter.
137
 
        filtered = False
138
 
 
139
 
    with tree.lock_read():
140
 
        for _ in _exporters[format](
141
 
            tree, dest, root, subdir,
142
 
            force_mtime=force_mtime, fileobj=fileobj):
143
 
            yield
144
 
 
145
 
 
146
 
def export(tree, dest, format=None, root=None, subdir=None, filtered=False,
 
32
 
 
33
def export(tree, dest, format=None, root=None, subdir=None,
147
34
           per_file_timestamps=False, fileobj=None):
148
35
    """Export the given Tree to the specific destination.
149
36
 
161
48
    :param subdir: A starting directory within the tree. None means to export
162
49
        the entire tree, and anything else should specify the relative path to
163
50
        a directory to start exporting from.
164
 
    :param filtered: If True, content filtering is applied to the
165
 
        files exported.  Deprecated in favor of passing an ContentFilterTree.
166
51
    :param per_file_timestamps: Whether to use the timestamp stored in the
167
52
        tree rather than now(). This will do a revision lookup
168
53
        for every file so will be significantly slower.
169
54
    :param fileobj: Optional file object to use
170
55
    """
171
 
    for _ in get_export_generator(tree, dest, format, root, subdir, filtered,
172
 
                                  per_file_timestamps, fileobj):
173
 
        pass
 
56
    if format is None and dest is not None:
 
57
        format = guess_format(dest)
 
58
 
 
59
    # Most of the exporters will just have to call
 
60
    # this function anyway, so why not do it for them
 
61
    if root is None:
 
62
        root = get_root_name(dest)
 
63
 
 
64
    if not per_file_timestamps:
 
65
        force_mtime = time.time()
 
66
        if getattr(tree, '_repository', None):
 
67
            try:
 
68
                force_mtime = tree._repository.get_revision(
 
69
                    tree.get_revision_id()).timestamp
 
70
            except errors.NoSuchRevision:
 
71
                pass
 
72
    else:
 
73
        force_mtime = None
 
74
 
 
75
    trace.mutter('export version %r', tree)
 
76
 
 
77
    if format == 'dir':
 
78
        # TODO(jelmer): If the tree is remote (e.g. HPSS, Git Remote),
 
79
        # then we should stream a tar file and unpack that on the fly.
 
80
        with tree.lock_read():
 
81
            for unused in dir_exporter_generator(tree, dest, root, subdir,
 
82
                                                 force_mtime):
 
83
                pass
 
84
        return
 
85
 
 
86
    with tree.lock_read():
 
87
        chunks = tree.archive(format, dest, root=root,
 
88
                              subdir=subdir, force_mtime=force_mtime)
 
89
        if dest == '-':
 
90
            for chunk in chunks:
 
91
                getattr(sys.stdout, 'buffer', sys.stdout).write(chunk)
 
92
        elif fileobj is not None:
 
93
            for chunk in chunks:
 
94
                fileobj.write(chunk)
 
95
        else:
 
96
            with open(dest, 'wb') as f:
 
97
                for chunk in chunks:
 
98
                    f.write(chunk)
 
99
 
 
100
 
 
101
def guess_format(filename, default='dir'):
 
102
    """Guess the export format based on a file name.
 
103
 
 
104
    :param filename: Filename to guess from
 
105
    :param default: Default format to fall back to
 
106
    :return: format name
 
107
    """
 
108
    format = archive.format_registry.get_format_from_filename(filename)
 
109
    if format is None:
 
110
        format = default
 
111
    return format
174
112
 
175
113
 
176
114
def get_root_name(dest):
182
120
        # Exporting to -/foo doesn't make sense so use relative paths.
183
121
        return ''
184
122
    dest = os.path.basename(dest)
185
 
    for ext in _exporter_extensions:
 
123
    for ext in archive.format_registry.extensions:
186
124
        if dest.endswith(ext):
187
125
            return dest[:-len(ext)]
188
126
    return dest
202
140
    if subdir is not None:
203
141
        subdir = subdir.rstrip('/')
204
142
    entries = tree.iter_entries_by_dir()
205
 
    next(entries)  # skip root
206
143
    for path, entry in entries:
 
144
        if path == '':
 
145
            continue
 
146
 
207
147
        # The .bzr* namespace is reserved for "magic" files like
208
148
        # .bzrignore and .bzrrules - do not export these
209
149
        if skip_special and path.startswith(".bzr"):
225
165
        yield final_path, path, entry
226
166
 
227
167
 
228
 
register_lazy_exporter(None, [], 'breezy.export.dir_exporter',
229
 
                       'dir_exporter_generator')
230
 
register_lazy_exporter('dir', [], 'breezy.export.dir_exporter',
231
 
                       'dir_exporter_generator')
232
 
register_lazy_exporter('tar', ['.tar'], 'breezy.export.tar_exporter',
233
 
                       'plain_tar_exporter_generator')
234
 
register_lazy_exporter('tgz', ['.tar.gz', '.tgz'],
235
 
                       'breezy.export.tar_exporter',
236
 
                       'tgz_exporter_generator')
237
 
register_lazy_exporter('tbz2', ['.tar.bz2', '.tbz2'],
238
 
                       'breezy.export.tar_exporter', 'tbz_exporter_generator')
239
 
register_lazy_exporter('tlzma', ['.tar.lzma'], 'breezy.export.tar_exporter',
240
 
                       'tar_lzma_exporter_generator')
241
 
register_lazy_exporter('txz', ['.tar.xz'], 'breezy.export.tar_exporter',
242
 
                       'tar_xz_exporter_generator')
243
 
register_lazy_exporter('zip', ['.zip'], 'breezy.export.zip_exporter',
244
 
                       'zip_exporter_generator')
 
168
def dir_exporter_generator(tree, dest, root, subdir=None,
 
169
                           force_mtime=None, fileobj=None):
 
170
    """Return a generator that exports this tree to a new directory.
 
171
 
 
172
    `dest` should either not exist or should be empty. If it does not exist it
 
173
    will be created holding the contents of this tree.
 
174
 
 
175
    :note: If the export fails, the destination directory will be
 
176
           left in an incompletely exported state: export is not transactional.
 
177
    """
 
178
    try:
 
179
        os.mkdir(dest)
 
180
    except OSError as e:
 
181
        if e.errno == errno.EEXIST:
 
182
            # check if directory empty
 
183
            if os.listdir(dest) != []:
 
184
                raise errors.BzrError(
 
185
                    "Can't export tree to non-empty directory.")
 
186
        else:
 
187
            raise
 
188
    # Iterate everything, building up the files we will want to export, and
 
189
    # creating the directories and symlinks that we need.
 
190
    # This tracks (file_id, (destination_path, executable))
 
191
    # This matches the api that tree.iter_files_bytes() wants
 
192
    # Note in the case of revision trees, this does trigger a double inventory
 
193
    # lookup, hopefully it isn't too expensive.
 
194
    to_fetch = []
 
195
    for dp, tp, ie in _export_iter_entries(tree, subdir):
 
196
        file_id = getattr(ie, 'file_id', None)
 
197
        fullpath = osutils.pathjoin(dest, dp)
 
198
        if ie.kind == "file":
 
199
            to_fetch.append((tp, (dp, tp, file_id)))
 
200
        elif ie.kind in ("directory", "tree-reference"):
 
201
            os.mkdir(fullpath)
 
202
        elif ie.kind == "symlink":
 
203
            try:
 
204
                symlink_target = tree.get_symlink_target(tp)
 
205
                os.symlink(symlink_target, fullpath)
 
206
            except OSError as e:
 
207
                raise errors.BzrError(
 
208
                    "Failed to create symlink %r -> %r, error: %s"
 
209
                    % (fullpath, symlink_target, e))
 
210
        else:
 
211
            raise errors.BzrError("don't know how to export {%s} of kind %r" %
 
212
                                  (tp, ie.kind))
 
213
 
 
214
        yield
 
215
    # The data returned here can be in any order, but we've already created all
 
216
    # the directories
 
217
    flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | getattr(os, 'O_BINARY', 0)
 
218
    for (relpath, treepath, file_id), chunks in tree.iter_files_bytes(to_fetch):
 
219
        fullpath = osutils.pathjoin(dest, relpath)
 
220
        # We set the mode and let the umask sort out the file info
 
221
        mode = 0o666
 
222
        if tree.is_executable(treepath):
 
223
            mode = 0o777
 
224
        with os.fdopen(os.open(fullpath, flags, mode), 'wb') as out:
 
225
            out.writelines(chunks)
 
226
        if force_mtime is not None:
 
227
            mtime = force_mtime
 
228
        else:
 
229
            mtime = tree.get_file_mtime(treepath)
 
230
        os.utime(fullpath, (mtime, mtime))
 
231
 
 
232
        yield