/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-02-07 02:14:30 UTC
  • mto: This revision was merged to the branch mainline in revision 7492.
  • Revision ID: jelmer@jelmer.uk-20200207021430-m49iq3x4x8xlib6x
Drop python2 support.

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
from __future__ import absolute_import
 
21
 
 
22
import errno
 
23
import os
 
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):
 
37
    """Export the given Tree to the specific destination.
 
38
 
 
39
    :param tree: A Tree (such as RevisionTree) to export
 
40
    :param dest: The destination where the files,etc should be put
 
41
    :param format: The format (dir, zip, etc), if None, it will check the
 
42
                   extension on dest, looking for a match
 
43
    :param root: The root location inside the format.
 
44
                 It is common practise to have zipfiles and tarballs
 
45
                 extract into a subdirectory, rather than into the
 
46
                 current working directory.
 
47
                 If root is None, the default root will be
 
48
                 selected as the destination without its
 
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
 
57
    """
 
58
    if format is None and dest is not None:
 
59
        format = guess_format(dest)
 
60
 
 
61
    # Most of the exporters will just have to call
 
62
    # this function anyway, so why not do it for them
 
63
    if root is None:
 
64
        root = get_root_name(dest)
 
65
 
 
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
 
114
 
 
115
 
 
116
def get_root_name(dest):
 
117
    """Get just the root name for an export.
 
118
 
 
119
    """
 
120
    global _exporter_extensions
 
121
    if dest == '-':
 
122
        # Exporting to -/foo doesn't make sense so use relative paths.
 
123
        return ''
 
124
    dest = os.path.basename(dest)
 
125
    for ext in archive.format_registry.extensions:
 
126
        if dest.endswith(ext):
 
127
            return dest[:-len(ext)]
 
128
    return dest
 
129
 
 
130
 
 
131
def _export_iter_entries(tree, subdir, skip_special=True):
 
132
    """Iter the entries for tree suitable for exporting.
 
133
 
 
134
    :param tree: A tree object.
 
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
 
139
    """
 
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
 
 
149
        # The .bzr* namespace is reserved for "magic" files like
 
150
        # .bzrignore and .bzrrules - do not export these
 
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