/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-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-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 functionality, which can take a Tree and create a different representation.
18
 
 
19
 
Such as non-controlled directories, tarfiles, zipfiles, etc.
 
17
"""Export trees to tarballs, non-controlled directories, zipfiles, etc.
20
18
"""
21
19
 
 
20
from __future__ import absolute_import
 
21
 
 
22
import errno
22
23
import os
23
 
import bzrlib.errors as errors
24
 
 
25
 
# Maps format name => export function
26
 
_exporters = {}
27
 
# Maps filename extensions => export format name
28
 
_exporter_extensions = {}
29
 
 
30
 
def register_exporter(format, extensions, func, override=False):
31
 
    """Register an exporter.
32
 
 
33
 
    :param format: This is the name of the format, such as 'tgz' or 'zip'
34
 
    :param extensions: Extensions which should be used in the case that a
35
 
                       format was not explicitly specified.
36
 
    :type extensions: List
37
 
    :param func: The function. It will be called with (tree, dest, root)
38
 
    :param override: Whether to override an object which already exists.
39
 
                     Frequently plugins will want to provide functionality
40
 
                     until it shows up in mainline, so the default is False.
41
 
    """
42
 
    global _exporters, _exporter_extensions
43
 
 
44
 
    if (format not in _exporters) or override:
45
 
        _exporters[format] = func
46
 
 
47
 
    for ext in extensions:
48
 
        if (ext not in _exporter_extensions) or override:
49
 
            _exporter_extensions[ext] = format
50
 
 
51
 
 
52
 
def register_lazy_exporter(scheme, extensions, module, funcname):
53
 
    """Register lazy-loaded exporter function.
54
 
 
55
 
    When requesting a specific type of export, load the respective path.
56
 
    """
57
 
    def _loader(tree, dest, root, subdir, filtered, per_file_timestamps):
58
 
        mod = __import__(module, globals(), locals(), [funcname])
59
 
        func = getattr(mod, funcname)
60
 
        return func(tree, dest, root, subdir, filtered=filtered,
61
 
                    per_file_timestamps=per_file_timestamps)
62
 
    register_exporter(scheme, extensions, _loader)
63
 
 
64
 
 
65
 
def export(tree, dest, format=None, root=None, subdir=None, filtered=False,
66
 
           per_file_timestamps=False):
 
24
import sys
 
25
import time
 
26
 
 
27
from . import (
 
28
    archive,
 
29
    errors,
 
30
    osutils,
 
31
    trace,
 
32
    )
 
33
 
 
34
 
 
35
def export(tree, dest, format=None, root=None, subdir=None,
 
36
           per_file_timestamps=False, fileobj=None):
67
37
    """Export the given Tree to the specific destination.
68
38
 
69
39
    :param tree: A Tree (such as RevisionTree) to export
80
50
    :param subdir: A starting directory within the tree. None means to export
81
51
        the entire tree, and anything else should specify the relative path to
82
52
        a directory to start exporting from.
83
 
    :param filtered: If True, content filtering is applied to the
84
 
                     files exported.
85
 
    :param per_file_timestamps: Whether to use the timestamp stored in the 
86
 
        tree rather than now(). This will do a revision lookup 
 
53
    :param per_file_timestamps: Whether to use the timestamp stored in the
 
54
        tree rather than now(). This will do a revision lookup
87
55
        for every file so will be significantly slower.
 
56
    :param fileobj: Optional file object to use
88
57
    """
89
 
    global _exporters, _exporter_extensions
90
 
 
91
 
    if format is None:
92
 
        for ext in _exporter_extensions:
93
 
            if dest.endswith(ext):
94
 
                format = _exporter_extensions[ext]
95
 
                break
 
58
    if format is None and dest is not None:
 
59
        format = guess_format(dest)
96
60
 
97
61
    # Most of the exporters will just have to call
98
62
    # this function anyway, so why not do it for them
99
63
    if root is None:
100
64
        root = get_root_name(dest)
101
65
 
102
 
    if format not in _exporters:
103
 
        raise errors.NoSuchExportFormat(format)
104
 
    tree.lock_read()
105
 
    try:
106
 
        return _exporters[format](tree, dest, root, subdir, filtered=filtered,
107
 
                                  per_file_timestamps=per_file_timestamps)
108
 
    finally:
109
 
        tree.unlock()
 
66
    if not per_file_timestamps:
 
67
        force_mtime = time.time()
 
68
        if getattr(tree, '_repository', None):
 
69
            try:
 
70
                force_mtime = tree._repository.get_revision(
 
71
                    tree.get_revision_id()).timestamp
 
72
            except errors.NoSuchRevision:
 
73
                pass
 
74
    else:
 
75
        force_mtime = None
 
76
 
 
77
    trace.mutter('export version %r', tree)
 
78
 
 
79
    if format == 'dir':
 
80
        # TODO(jelmer): If the tree is remote (e.g. HPSS, Git Remote),
 
81
        # then we should stream a tar file and unpack that on the fly.
 
82
        with tree.lock_read():
 
83
            for unused in dir_exporter_generator(tree, dest, root, subdir,
 
84
                                                 force_mtime):
 
85
                pass
 
86
        return
 
87
 
 
88
    with tree.lock_read():
 
89
        chunks = tree.archive(format, dest, root=root,
 
90
                              subdir=subdir, force_mtime=force_mtime)
 
91
        if dest == '-':
 
92
            for chunk in chunks:
 
93
                getattr(sys.stdout, 'buffer', sys.stdout).write(chunk)
 
94
        elif fileobj is not None:
 
95
            for chunk in chunks:
 
96
                fileobj.write(chunk)
 
97
        else:
 
98
            with open(dest, 'wb') as f:
 
99
                for chunk in chunks:
 
100
                    f.write(chunk)
 
101
 
 
102
 
 
103
def guess_format(filename, default='dir'):
 
104
    """Guess the export format based on a file name.
 
105
 
 
106
    :param filename: Filename to guess from
 
107
    :param default: Default format to fall back to
 
108
    :return: format name
 
109
    """
 
110
    format = archive.format_registry.get_format_from_filename(filename)
 
111
    if format is None:
 
112
        format = default
 
113
    return format
110
114
 
111
115
 
112
116
def get_root_name(dest):
113
117
    """Get just the root name for an export.
114
118
 
115
 
    >>> get_root_name('../mytest.tar')
116
 
    'mytest'
117
 
    >>> get_root_name('mytar.tar')
118
 
    'mytar'
119
 
    >>> get_root_name('mytar.tar.bz2')
120
 
    'mytar'
121
 
    >>> get_root_name('tar.tar.tar.tgz')
122
 
    'tar.tar.tar'
123
 
    >>> get_root_name('bzr-0.0.5.tar.gz')
124
 
    'bzr-0.0.5'
125
 
    >>> get_root_name('bzr-0.0.5.zip')
126
 
    'bzr-0.0.5'
127
 
    >>> get_root_name('bzr-0.0.5')
128
 
    'bzr-0.0.5'
129
 
    >>> get_root_name('a/long/path/mytar.tgz')
130
 
    'mytar'
131
 
    >>> get_root_name('../parent/../dir/other.tbz2')
132
 
    'other'
133
119
    """
134
120
    global _exporter_extensions
 
121
    if dest == '-':
 
122
        # Exporting to -/foo doesn't make sense so use relative paths.
 
123
        return ''
135
124
    dest = os.path.basename(dest)
136
 
    for ext in _exporter_extensions:
 
125
    for ext in archive.format_registry.extensions:
137
126
        if dest.endswith(ext):
138
127
            return dest[:-len(ext)]
139
128
    return dest
140
129
 
141
130
 
142
 
def _export_iter_entries(tree, subdir):
 
131
def _export_iter_entries(tree, subdir, skip_special=True):
143
132
    """Iter the entries for tree suitable for exporting.
144
133
 
145
134
    :param tree: A tree object.
146
135
    :param subdir: None or the path of an entry to start exporting from.
 
136
    :param skip_special: Whether to skip .bzr files.
 
137
    :return: iterator over tuples with final path, tree path and inventory
 
138
        entry for each entry to export
147
139
    """
148
 
    inv = tree.inventory
149
 
    if subdir is None:
150
 
        subdir_object = None
151
 
    else:
152
 
        subdir_id = inv.path2id(subdir)
153
 
        if subdir_id is not None:
154
 
            subdir_object = inv[subdir_id]
155
 
        # XXX: subdir is path not an id, so NoSuchId isn't proper error
156
 
        else:
157
 
            raise errors.NoSuchId(tree, subdir)
158
 
    if subdir_object is not None and subdir_object.kind != 'directory':
159
 
        yield subdir_object.name, subdir_object
160
 
        return
161
 
    else:
162
 
        entries = inv.iter_entries(subdir_object)
163
 
    if subdir is None:
164
 
        entries.next() # skip root
165
 
    for entry in entries:
 
140
    if subdir == '':
 
141
        subdir = None
 
142
    if subdir is not None:
 
143
        subdir = subdir.rstrip('/')
 
144
    entries = tree.iter_entries_by_dir()
 
145
    for path, entry in entries:
 
146
        if path == '':
 
147
            continue
 
148
 
166
149
        # The .bzr* namespace is reserved for "magic" files like
167
150
        # .bzrignore and .bzrrules - do not export these
168
 
        if entry[0].startswith(".bzr"):
169
 
            continue
170
 
        if subdir is None:
171
 
            if not tree.has_filename(entry[0]):
172
 
                continue
173
 
        else:
174
 
            if not tree.has_filename(os.path.join(subdir, entry[0])):
175
 
                continue
176
 
        yield entry
177
 
 
178
 
 
179
 
register_lazy_exporter(None, [], 'bzrlib.export.dir_exporter', 'dir_exporter')
180
 
register_lazy_exporter('dir', [], 'bzrlib.export.dir_exporter', 'dir_exporter')
181
 
register_lazy_exporter('tar', ['.tar'], 'bzrlib.export.tar_exporter', 'tar_exporter')
182
 
register_lazy_exporter('tgz', ['.tar.gz', '.tgz'], 'bzrlib.export.tar_exporter', 'tgz_exporter')
183
 
register_lazy_exporter('tbz2', ['.tar.bz2', '.tbz2'], 'bzrlib.export.tar_exporter', 'tbz_exporter')
184
 
register_lazy_exporter('zip', ['.zip'], 'bzrlib.export.zip_exporter', 'zip_exporter')
185
 
 
 
151
        if skip_special and path.startswith(".bzr"):
 
152
            continue
 
153
        if path == subdir:
 
154
            if entry.kind == 'directory':
 
155
                continue
 
156
            final_path = entry.name
 
157
        elif subdir is not None:
 
158
            if path.startswith(subdir + '/'):
 
159
                final_path = path[len(subdir) + 1:]
 
160
            else:
 
161
                continue
 
162
        else:
 
163
            final_path = path
 
164
        if not tree.has_filename(path):
 
165
            continue
 
166
 
 
167
        yield final_path, path, entry
 
168
 
 
169
 
 
170
def dir_exporter_generator(tree, dest, root, subdir=None,
 
171
                           force_mtime=None, fileobj=None):
 
172
    """Return a generator that exports this tree to a new directory.
 
173
 
 
174
    `dest` should either not exist or should be empty. If it does not exist it
 
175
    will be created holding the contents of this tree.
 
176
 
 
177
    :note: If the export fails, the destination directory will be
 
178
           left in an incompletely exported state: export is not transactional.
 
179
    """
 
180
    try:
 
181
        os.mkdir(dest)
 
182
    except OSError as e:
 
183
        if e.errno == errno.EEXIST:
 
184
            # check if directory empty
 
185
            if os.listdir(dest) != []:
 
186
                raise errors.BzrError(
 
187
                    "Can't export tree to non-empty directory.")
 
188
        else:
 
189
            raise
 
190
    # Iterate everything, building up the files we will want to export, and
 
191
    # creating the directories and symlinks that we need.
 
192
    # This tracks (file_id, (destination_path, executable))
 
193
    # This matches the api that tree.iter_files_bytes() wants
 
194
    # Note in the case of revision trees, this does trigger a double inventory
 
195
    # lookup, hopefully it isn't too expensive.
 
196
    to_fetch = []
 
197
    for dp, tp, ie in _export_iter_entries(tree, subdir):
 
198
        file_id = getattr(ie, 'file_id', None)
 
199
        fullpath = osutils.pathjoin(dest, dp)
 
200
        if ie.kind == "file":
 
201
            to_fetch.append((tp, (dp, tp, file_id)))
 
202
        elif ie.kind in ("directory", "tree-reference"):
 
203
            os.mkdir(fullpath)
 
204
        elif ie.kind == "symlink":
 
205
            try:
 
206
                symlink_target = tree.get_symlink_target(tp)
 
207
                os.symlink(symlink_target, fullpath)
 
208
            except OSError as e:
 
209
                raise errors.BzrError(
 
210
                    "Failed to create symlink %r -> %r, error: %s"
 
211
                    % (fullpath, symlink_target, e))
 
212
        else:
 
213
            raise errors.BzrError("don't know how to export {%s} of kind %r" %
 
214
                                  (tp, ie.kind))
 
215
 
 
216
        yield
 
217
    # The data returned here can be in any order, but we've already created all
 
218
    # the directories
 
219
    flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | getattr(os, 'O_BINARY', 0)
 
220
    for (relpath, treepath, file_id), chunks in tree.iter_files_bytes(to_fetch):
 
221
        fullpath = osutils.pathjoin(dest, relpath)
 
222
        # We set the mode and let the umask sort out the file info
 
223
        mode = 0o666
 
224
        if tree.is_executable(treepath):
 
225
            mode = 0o777
 
226
        with os.fdopen(os.open(fullpath, flags, mode), 'wb') as out:
 
227
            out.writelines(chunks)
 
228
        if force_mtime is not None:
 
229
            mtime = force_mtime
 
230
        else:
 
231
            mtime = tree.get_file_mtime(treepath)
 
232
        os.utime(fullpath, (mtime, mtime))
 
233
 
 
234
        yield