/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-12-10 01:46:35 UTC
  • mto: This revision was merged to the branch mainline in revision 7219.
  • Revision ID: jelmer@jelmer.uk-20181210014635-oxhnf8s46u0nvp5c
Fix import

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
"""Export functionality, which can take a Tree and create a different representation.
18
 
 
19
 
Such as non-controlled directories, tarfiles, zipfiles, etc.
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Export trees to tarballs, non-controlled directories, zipfiles, etc.
20
18
"""
21
19
 
22
 
from bzrlib.trace import mutter
 
20
from __future__ import absolute_import
 
21
 
 
22
import errno
23
23
import os
24
 
import bzrlib.errors as errors
25
 
 
26
 
# Maps format name => export function
27
 
_exporters = {}
28
 
# Maps filename extensions => export format name
29
 
_exporter_extensions = {}
30
 
 
31
 
def register_exporter(format, extensions, func, override=False):
32
 
    """Register an exporter.
33
 
 
34
 
    :param format: This is the name of the format, such as 'tgz' or 'zip'
35
 
    :param extensions: Extensions which should be used in the case that a 
36
 
                       format was not explicitly specified.
37
 
    :type extensions: List
38
 
    :param func: The function. It will be called with (tree, dest, root)
39
 
    :param override: Whether to override an object which already exists.
40
 
                     Frequently plugins will want to provide functionality
41
 
                     until it shows up in mainline, so the default is False.
42
 
    """
43
 
    global _exporters, _exporter_extensions
44
 
 
45
 
    if not _exporters.has_key(format) or override:
46
 
        _exporters[format] = func
47
 
 
48
 
    for ext in extensions:
49
 
        if not _exporter_extensions.has_key(ext) or override:
50
 
            _exporter_extensions[ext] = format
51
 
 
52
 
 
53
 
def register_lazy_exporter(scheme, extensions, module, funcname):
54
 
    """Register lazy-loaded exporter function.
55
 
 
56
 
    When requesting a specific type of export, load the respective path.
57
 
    """
58
 
    def _loader(tree, dest, root):
59
 
        mod = __import__(module, globals(), locals(), [funcname])
60
 
        func = getattr(mod, funcname)
61
 
        return func(tree, dest, root)
62
 
    register_exporter(scheme, extensions, _loader)
63
 
 
64
 
 
65
 
def export(tree, dest, format=None, root=None):
 
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):
66
37
    """Export the given Tree to the specific destination.
67
38
 
68
39
    :param tree: A Tree (such as RevisionTree) to export
70
41
    :param format: The format (dir, zip, etc), if None, it will check the
71
42
                   extension on dest, looking for a match
72
43
    :param root: The root location inside the format.
73
 
                 It is common practise to have zipfiles and tarballs 
 
44
                 It is common practise to have zipfiles and tarballs
74
45
                 extract into a subdirectory, rather than into the
75
46
                 current working directory.
76
47
                 If root is None, the default root will be
77
48
                 selected as the destination without its
78
49
                 extension.
 
50
    :param subdir: A starting directory within the tree. None means to export
 
51
        the entire tree, and anything else should specify the relative path to
 
52
        a directory to start exporting from.
 
53
    :param per_file_timestamps: Whether to use the timestamp stored in the
 
54
        tree rather than now(). This will do a revision lookup
 
55
        for every file so will be significantly slower.
 
56
    :param fileobj: Optional file object to use
79
57
    """
80
 
    global _exporters, _exporter_extensions
81
 
 
82
 
    if format is None:
83
 
        for ext in _exporter_extensions:
84
 
            if dest.endswith(ext):
85
 
                format = _exporter_extensions[ext]
86
 
                break
 
58
    if format is None and dest is not None:
 
59
        format = guess_format(dest)
87
60
 
88
61
    # Most of the exporters will just have to call
89
62
    # this function anyway, so why not do it for them
90
63
    if root is None:
91
64
        root = get_root_name(dest)
92
65
 
93
 
    if not _exporters.has_key(format):
94
 
        raise errors.NoSuchExportFormat(format)
95
 
    return _exporters[format](tree, dest, root)
 
66
    if not per_file_timestamps:
 
67
        force_mtime = time.time()
 
68
    else:
 
69
        force_mtime = None
 
70
 
 
71
    trace.mutter('export version %r', tree)
 
72
 
 
73
    if format == 'dir':
 
74
        # TODO(jelmer): If the tree is remote (e.g. HPSS, Git Remote),
 
75
        # then we should stream a tar file and unpack that on the fly.
 
76
        with tree.lock_read():
 
77
            for unused in dir_exporter_generator(tree, dest, root, subdir,
 
78
                                                 force_mtime):
 
79
                pass
 
80
        return
 
81
 
 
82
    with tree.lock_read():
 
83
        chunks = tree.archive(format, dest, root=root,
 
84
                              subdir=subdir, force_mtime=force_mtime)
 
85
        if dest == '-':
 
86
            for chunk in chunks:
 
87
                getattr(sys.stdout, 'buffer', sys.stdout).write(chunk)
 
88
        elif fileobj is not None:
 
89
            for chunk in chunks:
 
90
                fileobj.write(chunk)
 
91
        else:
 
92
            with open(dest, 'wb') as f:
 
93
                for chunk in chunks:
 
94
                    f.write(chunk)
 
95
 
 
96
 
 
97
def guess_format(filename, default='dir'):
 
98
    """Guess the export format based on a file name.
 
99
 
 
100
    :param filename: Filename to guess from
 
101
    :param default: Default format to fall back to
 
102
    :return: format name
 
103
    """
 
104
    format = archive.format_registry.get_format_from_filename(filename)
 
105
    if format is None:
 
106
        format = default
 
107
    return format
96
108
 
97
109
 
98
110
def get_root_name(dest):
99
111
    """Get just the root name for an export.
100
112
 
101
 
    >>> get_root_name('mytar.tar')
102
 
    'mytar'
103
 
    >>> get_root_name('mytar.tar.bz2')
104
 
    'mytar'
105
 
    >>> get_root_name('tar.tar.tar.tgz')
106
 
    'tar.tar.tar'
107
 
    >>> get_root_name('bzr-0.0.5.tar.gz')
108
 
    'bzr-0.0.5'
109
 
    >>> get_root_name('bzr-0.0.5.zip')
110
 
    'bzr-0.0.5'
111
 
    >>> get_root_name('bzr-0.0.5')
112
 
    'bzr-0.0.5'
113
 
    >>> get_root_name('a/long/path/mytar.tgz')
114
 
    'mytar'
115
 
    >>> get_root_name('../parent/../dir/other.tbz2')
116
 
    'other'
117
113
    """
118
114
    global _exporter_extensions
 
115
    if dest == '-':
 
116
        # Exporting to -/foo doesn't make sense so use relative paths.
 
117
        return ''
119
118
    dest = os.path.basename(dest)
120
 
    for ext in _exporter_extensions:
 
119
    for ext in archive.format_registry.extensions:
121
120
        if dest.endswith(ext):
122
121
            return dest[:-len(ext)]
123
122
    return dest
124
123
 
125
124
 
126
 
register_lazy_exporter(None, [], 'bzrlib.export.dir_exporter', 'dir_exporter')
127
 
register_lazy_exporter('dir', [], 'bzrlib.export.dir_exporter', 'dir_exporter')
128
 
register_lazy_exporter('tar', ['.tar'], 'bzrlib.export.tar_exporter', 'tar_exporter')
129
 
register_lazy_exporter('tgz', ['.tar.gz', '.tgz'], 'bzrlib.export.tar_exporter', 'tgz_exporter')
130
 
register_lazy_exporter('tbz2', ['.tar.bz2', '.tbz2'], 'bzrlib.export.tar_exporter', 'tbz_exporter')
131
 
register_lazy_exporter('zip', ['.zip'], 'bzrlib.export.zip_exporter', 'zip_exporter')
132
 
 
 
125
def _export_iter_entries(tree, subdir, skip_special=True):
 
126
    """Iter the entries for tree suitable for exporting.
 
127
 
 
128
    :param tree: A tree object.
 
129
    :param subdir: None or the path of an entry to start exporting from.
 
130
    :param skip_special: Whether to skip .bzr files.
 
131
    :return: iterator over tuples with final path, tree path and inventory
 
132
        entry for each entry to export
 
133
    """
 
134
    if subdir == '':
 
135
        subdir = None
 
136
    if subdir is not None:
 
137
        subdir = subdir.rstrip('/')
 
138
    entries = tree.iter_entries_by_dir()
 
139
    for path, entry in entries:
 
140
        if path == '':
 
141
            continue
 
142
 
 
143
        # The .bzr* namespace is reserved for "magic" files like
 
144
        # .bzrignore and .bzrrules - do not export these
 
145
        if skip_special and path.startswith(".bzr"):
 
146
            continue
 
147
        if path == subdir:
 
148
            if entry.kind == 'directory':
 
149
                continue
 
150
            final_path = entry.name
 
151
        elif subdir is not None:
 
152
            if path.startswith(subdir + '/'):
 
153
                final_path = path[len(subdir) + 1:]
 
154
            else:
 
155
                continue
 
156
        else:
 
157
            final_path = path
 
158
        if not tree.has_filename(path):
 
159
            continue
 
160
 
 
161
        yield final_path, path, entry
 
162
 
 
163
 
 
164
def dir_exporter_generator(tree, dest, root, subdir=None,
 
165
                           force_mtime=None, fileobj=None):
 
166
    """Return a generator that exports this tree to a new directory.
 
167
 
 
168
    `dest` should either not exist or should be empty. If it does not exist it
 
169
    will be created holding the contents of this tree.
 
170
 
 
171
    :note: If the export fails, the destination directory will be
 
172
           left in an incompletely exported state: export is not transactional.
 
173
    """
 
174
    try:
 
175
        os.mkdir(dest)
 
176
    except OSError as e:
 
177
        if e.errno == errno.EEXIST:
 
178
            # check if directory empty
 
179
            if os.listdir(dest) != []:
 
180
                raise errors.BzrError(
 
181
                    "Can't export tree to non-empty directory.")
 
182
        else:
 
183
            raise
 
184
    # Iterate everything, building up the files we will want to export, and
 
185
    # creating the directories and symlinks that we need.
 
186
    # This tracks (file_id, (destination_path, executable))
 
187
    # This matches the api that tree.iter_files_bytes() wants
 
188
    # Note in the case of revision trees, this does trigger a double inventory
 
189
    # lookup, hopefully it isn't too expensive.
 
190
    to_fetch = []
 
191
    for dp, tp, ie in _export_iter_entries(tree, subdir):
 
192
        file_id = getattr(ie, 'file_id', None)
 
193
        fullpath = osutils.pathjoin(dest, dp)
 
194
        if ie.kind == "file":
 
195
            to_fetch.append((tp, (dp, tp, 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)
 
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):
 
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)
 
226
        os.utime(fullpath, (mtime, mtime))
 
227
 
 
228
        yield