/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: 2019-01-01 21:08:01 UTC
  • mto: This revision was merged to the branch mainline in revision 7231.
  • Revision ID: jelmer@jelmer.uk-20190101210801-2dlsv7b1lvydmpkl
Fix tests.

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
    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
 
108
 
 
109
 
 
110
def get_root_name(dest):
 
111
    """Get just the root name for an export.
 
112
 
 
113
    """
 
114
    global _exporter_extensions
 
115
    if dest == '-':
 
116
        # Exporting to -/foo doesn't make sense so use relative paths.
 
117
        return ''
 
118
    dest = os.path.basename(dest)
 
119
    for ext in archive.format_registry.extensions:
 
120
        if dest.endswith(ext):
 
121
            return dest[:-len(ext)]
 
122
    return dest
 
123
 
 
124
 
 
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