/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:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Export trees to tarballs, non-controlled directories, zipfiles, etc.
 
18
"""
 
19
 
 
20
import errno
 
21
import os
 
22
import sys
 
23
import time
 
24
 
 
25
from . import (
 
26
    archive,
 
27
    errors,
 
28
    osutils,
 
29
    trace,
 
30
    )
 
31
 
 
32
 
 
33
def export(tree, dest, format=None, root=None, subdir=None,
 
34
           per_file_timestamps=False, fileobj=None):
 
35
    """Export the given Tree to the specific destination.
 
36
 
 
37
    :param tree: A Tree (such as RevisionTree) to export
 
38
    :param dest: The destination where the files,etc should be put
 
39
    :param format: The format (dir, zip, etc), if None, it will check the
 
40
                   extension on dest, looking for a match
 
41
    :param root: The root location inside the format.
 
42
                 It is common practise to have zipfiles and tarballs
 
43
                 extract into a subdirectory, rather than into the
 
44
                 current working directory.
 
45
                 If root is None, the default root will be
 
46
                 selected as the destination without its
 
47
                 extension.
 
48
    :param subdir: A starting directory within the tree. None means to export
 
49
        the entire tree, and anything else should specify the relative path to
 
50
        a directory to start exporting from.
 
51
    :param per_file_timestamps: Whether to use the timestamp stored in the
 
52
        tree rather than now(). This will do a revision lookup
 
53
        for every file so will be significantly slower.
 
54
    :param fileobj: Optional file object to use
 
55
    """
 
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
 
112
 
 
113
 
 
114
def get_root_name(dest):
 
115
    """Get just the root name for an export.
 
116
 
 
117
    """
 
118
    global _exporter_extensions
 
119
    if dest == '-':
 
120
        # Exporting to -/foo doesn't make sense so use relative paths.
 
121
        return ''
 
122
    dest = os.path.basename(dest)
 
123
    for ext in archive.format_registry.extensions:
 
124
        if dest.endswith(ext):
 
125
            return dest[:-len(ext)]
 
126
    return dest
 
127
 
 
128
 
 
129
def _export_iter_entries(tree, subdir, skip_special=True):
 
130
    """Iter the entries for tree suitable for exporting.
 
131
 
 
132
    :param tree: A tree object.
 
133
    :param subdir: None or the path of an entry to start exporting from.
 
134
    :param skip_special: Whether to skip .bzr files.
 
135
    :return: iterator over tuples with final path, tree path and inventory
 
136
        entry for each entry to export
 
137
    """
 
138
    if subdir == '':
 
139
        subdir = None
 
140
    if subdir is not None:
 
141
        subdir = subdir.rstrip('/')
 
142
    entries = tree.iter_entries_by_dir()
 
143
    for path, entry in entries:
 
144
        if path == '':
 
145
            continue
 
146
 
 
147
        # The .bzr* namespace is reserved for "magic" files like
 
148
        # .bzrignore and .bzrrules - do not export these
 
149
        if skip_special and path.startswith(".bzr"):
 
150
            continue
 
151
        if path == subdir:
 
152
            if entry.kind == 'directory':
 
153
                continue
 
154
            final_path = entry.name
 
155
        elif subdir is not None:
 
156
            if path.startswith(subdir + '/'):
 
157
                final_path = path[len(subdir) + 1:]
 
158
            else:
 
159
                continue
 
160
        else:
 
161
            final_path = path
 
162
        if not tree.has_filename(path):
 
163
            continue
 
164
 
 
165
        yield final_path, path, entry
 
166
 
 
167
 
 
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