/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/upstream_import.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2019-01-28 23:51:14 UTC
  • mfrom: (7251.1.1 skip-crypto-deprecation)
  • Revision ID: breezy.the.bot@gmail.com-20190128235114-wurthh67hpldlyip
Ignore UserWarning on travis.

Merged from https://code.launchpad.net/~jelmer/brz/skip-crypto-deprecation/+merge/362227

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2012 Aaron Bentley
 
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
"""Import upstream source into a branch"""
 
18
 
 
19
from __future__ import absolute_import
 
20
 
 
21
import errno
 
22
from io import (
 
23
    BytesIO,
 
24
    )
 
25
import os
 
26
import re
 
27
import stat
 
28
import tarfile
 
29
import zipfile
 
30
 
 
31
from . import generate_ids, urlutils
 
32
from .controldir import ControlDir, is_control_filename
 
33
from .errors import (BzrError, NoSuchFile, BzrCommandError, NotBranchError)
 
34
from .osutils import (pathjoin, isdir, file_iterator, basename,
 
35
                      file_kind, splitpath)
 
36
from .sixish import (
 
37
    text_type,
 
38
    )
 
39
from .trace import warning
 
40
from .transform import TreeTransform, resolve_conflicts, cook_conflicts
 
41
from .transport import get_transport
 
42
from .workingtree import WorkingTree
 
43
 
 
44
 
 
45
# TODO(jelmer): Move this to transport.py ?
 
46
def open_from_url(location):
 
47
    location = urlutils.normalize_url(location)
 
48
    dirname, basename = urlutils.split(location)
 
49
    if location.endswith('/') and not basename.endswith('/'):
 
50
        basename += '/'
 
51
    return get_transport(dirname).get(basename)
 
52
 
 
53
 
 
54
class NotArchiveType(BzrError):
 
55
 
 
56
    _fmt = '%(path)s is not an archive.'
 
57
 
 
58
    def __init__(self, path):
 
59
        BzrError.__init__(self)
 
60
        self.path = path
 
61
 
 
62
 
 
63
class ZipFileWrapper(object):
 
64
 
 
65
    def __init__(self, fileobj, mode):
 
66
        self.zipfile = zipfile.ZipFile(fileobj, mode)
 
67
 
 
68
    def getmembers(self):
 
69
        for info in self.zipfile.infolist():
 
70
            yield ZipInfoWrapper(self.zipfile, info)
 
71
 
 
72
    def extractfile(self, infowrapper):
 
73
        return BytesIO(self.zipfile.read(infowrapper.name))
 
74
 
 
75
    def add(self, filename):
 
76
        if isdir(filename):
 
77
            self.zipfile.writestr(filename + '/', '')
 
78
        else:
 
79
            self.zipfile.write(filename)
 
80
 
 
81
    def close(self):
 
82
        self.zipfile.close()
 
83
 
 
84
 
 
85
class ZipInfoWrapper(object):
 
86
 
 
87
    def __init__(self, zipfile, info):
 
88
        self.info = info
 
89
        self.type = None
 
90
        self.name = info.filename
 
91
        self.zipfile = zipfile
 
92
        self.mode = 0o666
 
93
 
 
94
    def isdir(self):
 
95
        # Really? Eeeew!
 
96
        return bool(self.name.endswith('/'))
 
97
 
 
98
    def isreg(self):
 
99
        # Really? Eeeew!
 
100
        return not self.isdir()
 
101
 
 
102
 
 
103
class DirWrapper(object):
 
104
 
 
105
    def __init__(self, fileobj, mode='r'):
 
106
        if mode != 'r':
 
107
            raise AssertionError(
 
108
                'only readonly supported')
 
109
        self.root = os.path.realpath(fileobj.read().decode('utf-8'))
 
110
 
 
111
    def __repr__(self):
 
112
        return 'DirWrapper(%r)' % self.root
 
113
 
 
114
    def getmembers(self, subdir=None):
 
115
        if subdir is not None:
 
116
            mydir = pathjoin(self.root, subdir)
 
117
        else:
 
118
            mydir = self.root
 
119
        for child in os.listdir(mydir):
 
120
            if subdir is not None:
 
121
                child = pathjoin(subdir, child)
 
122
            fi = FileInfo(self.root, child)
 
123
            yield fi
 
124
            if fi.isdir():
 
125
                for v in self.getmembers(child):
 
126
                    yield v
 
127
 
 
128
    def extractfile(self, member):
 
129
        return open(member.fullpath, 'rb')
 
130
 
 
131
 
 
132
class FileInfo(object):
 
133
 
 
134
    def __init__(self, root, filepath):
 
135
        self.fullpath = pathjoin(root, filepath)
 
136
        self.root = root
 
137
        if filepath != '':
 
138
            self.name = pathjoin(basename(root), filepath)
 
139
        else:
 
140
            print('root %r' % root)
 
141
            self.name = basename(root)
 
142
        self.type = None
 
143
        stat = os.lstat(self.fullpath)
 
144
        self.mode = stat.st_mode
 
145
        if self.isdir():
 
146
            self.name += '/'
 
147
 
 
148
    def __repr__(self):
 
149
        return 'FileInfo(%r)' % self.name
 
150
 
 
151
    def isreg(self):
 
152
        return stat.S_ISREG(self.mode)
 
153
 
 
154
    def isdir(self):
 
155
        return stat.S_ISDIR(self.mode)
 
156
 
 
157
    def issym(self):
 
158
        if stat.S_ISLNK(self.mode):
 
159
            self.linkname = os.readlink(self.fullpath)
 
160
            return True
 
161
        else:
 
162
            return False
 
163
 
 
164
 
 
165
def top_path(path):
 
166
    """Return the top directory given in a path."""
 
167
    components = splitpath(path)
 
168
    if len(components) > 0:
 
169
        return components[0]
 
170
    else:
 
171
        return ''
 
172
 
 
173
 
 
174
def common_directory(names):
 
175
    """Determine a single directory prefix from a list of names"""
 
176
    possible_prefix = None
 
177
    for name in names:
 
178
        name_top = top_path(name)
 
179
        if name_top == '':
 
180
            return None
 
181
        if possible_prefix is None:
 
182
            possible_prefix = name_top
 
183
        else:
 
184
            if name_top != possible_prefix:
 
185
                return None
 
186
    return possible_prefix
 
187
 
 
188
 
 
189
def do_directory(tt, trans_id, tree, relative_path, path):
 
190
    if isdir(path) and tree.is_versioned(relative_path):
 
191
        tt.cancel_deletion(trans_id)
 
192
    else:
 
193
        tt.create_directory(trans_id)
 
194
 
 
195
 
 
196
def add_implied_parents(implied_parents, path):
 
197
    """Update the set of implied parents from a path"""
 
198
    parent = os.path.dirname(path)
 
199
    if parent in implied_parents:
 
200
        return
 
201
    implied_parents.add(parent)
 
202
    add_implied_parents(implied_parents, parent)
 
203
 
 
204
 
 
205
def names_of_files(tar_file):
 
206
    for member in tar_file.getmembers():
 
207
        if member.type != "g":
 
208
            yield member.name
 
209
 
 
210
 
 
211
def should_ignore(relative_path):
 
212
    return is_control_filename(top_path(relative_path))
 
213
 
 
214
 
 
215
def import_tar(tree, tar_input):
 
216
    """Replace the contents of a working directory with tarfile contents.
 
217
    The tarfile may be a gzipped stream.  File ids will be updated.
 
218
    """
 
219
    tar_file = tarfile.open('lala', 'r', tar_input)
 
220
    import_archive(tree, tar_file)
 
221
 
 
222
 
 
223
def import_zip(tree, zip_input):
 
224
    zip_file = ZipFileWrapper(zip_input, 'r')
 
225
    import_archive(tree, zip_file)
 
226
 
 
227
 
 
228
def import_dir(tree, dir_input):
 
229
    dir_file = DirWrapper(dir_input)
 
230
    import_archive(tree, dir_file)
 
231
 
 
232
 
 
233
def import_archive(tree, archive_file):
 
234
    tt = TreeTransform(tree)
 
235
    try:
 
236
        import_archive_to_transform(tree, archive_file, tt)
 
237
        tt.apply()
 
238
    finally:
 
239
        tt.finalize()
 
240
 
 
241
 
 
242
def import_archive_to_transform(tree, archive_file, tt):
 
243
    prefix = common_directory(names_of_files(archive_file))
 
244
    removed = set()
 
245
    for path, entry in tree.iter_entries_by_dir():
 
246
        if entry.parent_id is None:
 
247
            continue
 
248
        trans_id = tt.trans_id_tree_path(path)
 
249
        tt.delete_contents(trans_id)
 
250
        removed.add(path)
 
251
 
 
252
    added = set()
 
253
    implied_parents = set()
 
254
    seen = set()
 
255
    for member in archive_file.getmembers():
 
256
        if member.type == 'g':
 
257
            # type 'g' is a header
 
258
            continue
 
259
        # Inverse functionality in bzr uses utf-8.  We could also
 
260
        # interpret relative to fs encoding, which would match native
 
261
        # behaviour better.
 
262
        relative_path = member.name
 
263
        if not isinstance(relative_path, text_type):
 
264
            relative_path = relative_path.decode('utf-8')
 
265
        if prefix is not None:
 
266
            relative_path = relative_path[len(prefix) + 1:]
 
267
            relative_path = relative_path.rstrip('/')
 
268
        if relative_path == '':
 
269
            continue
 
270
        if should_ignore(relative_path):
 
271
            continue
 
272
        add_implied_parents(implied_parents, relative_path)
 
273
        trans_id = tt.trans_id_tree_path(relative_path)
 
274
        added.add(relative_path.rstrip('/'))
 
275
        path = tree.abspath(relative_path)
 
276
        if member.name in seen:
 
277
            if tt.final_kind(trans_id) == 'file':
 
278
                tt.set_executability(None, trans_id)
 
279
            tt.cancel_creation(trans_id)
 
280
        seen.add(member.name)
 
281
        if member.isreg():
 
282
            tt.create_file(file_iterator(archive_file.extractfile(member)),
 
283
                           trans_id)
 
284
            executable = (member.mode & 0o111) != 0
 
285
            tt.set_executability(executable, trans_id)
 
286
        elif member.isdir():
 
287
            do_directory(tt, trans_id, tree, relative_path, path)
 
288
        elif member.issym():
 
289
            tt.create_symlink(member.linkname, trans_id)
 
290
        else:
 
291
            continue
 
292
        if tt.tree_file_id(trans_id) is None:
 
293
            name = basename(member.name.rstrip('/'))
 
294
            file_id = generate_ids.gen_file_id(name)
 
295
            tt.version_file(file_id, trans_id)
 
296
 
 
297
    for relative_path in implied_parents.difference(added):
 
298
        if relative_path == "":
 
299
            continue
 
300
        trans_id = tt.trans_id_tree_path(relative_path)
 
301
        path = tree.abspath(relative_path)
 
302
        do_directory(tt, trans_id, tree, relative_path, path)
 
303
        if tt.tree_file_id(trans_id) is None:
 
304
            tt.version_file(trans_id, trans_id)
 
305
        added.add(relative_path)
 
306
 
 
307
    for path in removed.difference(added):
 
308
        tt.unversion_file(tt.trans_id_tree_path(path))
 
309
 
 
310
    for conflict in cook_conflicts(resolve_conflicts(tt), tt):
 
311
        warning(conflict)
 
312
 
 
313
 
 
314
def do_import(source, tree_directory=None):
 
315
    """Implementation of import command.  Intended for UI only"""
 
316
    if tree_directory is not None:
 
317
        try:
 
318
            tree = WorkingTree.open(tree_directory)
 
319
        except NotBranchError:
 
320
            if not os.path.exists(tree_directory):
 
321
                os.mkdir(tree_directory)
 
322
            branch = ControlDir.create_branch_convenience(tree_directory)
 
323
            tree = branch.controldir.open_workingtree()
 
324
    else:
 
325
        tree = WorkingTree.open_containing('.')[0]
 
326
    with tree.lock_write():
 
327
        if tree.changes_from(tree.basis_tree()).has_changed():
 
328
            raise BzrCommandError("Working tree has uncommitted changes.")
 
329
 
 
330
        try:
 
331
            archive, external_compressor = get_archive_type(source)
 
332
        except NotArchiveType:
 
333
            if file_kind(source) == 'directory':
 
334
                s = BytesIO(source.encode('utf-8'))
 
335
                s.seek(0)
 
336
                import_dir(tree, s)
 
337
            else:
 
338
                raise BzrCommandError('Unhandled import source')
 
339
        else:
 
340
            if archive == 'zip':
 
341
                import_zip(tree, open_from_url(source))
 
342
            elif archive == 'tar':
 
343
                try:
 
344
                    tar_input = open_from_url(source)
 
345
                    if external_compressor == 'bz2':
 
346
                        import bz2
 
347
                        tar_input = BytesIO(bz2.decompress(tar_input.read()))
 
348
                    elif external_compressor == 'lzma':
 
349
                        import lzma
 
350
                        tar_input = BytesIO(lzma.decompress(tar_input.read()))
 
351
                except IOError as e:
 
352
                    if e.errno == errno.ENOENT:
 
353
                        raise NoSuchFile(source)
 
354
                try:
 
355
                    import_tar(tree, tar_input)
 
356
                finally:
 
357
                    tar_input.close()
 
358
 
 
359
 
 
360
def get_archive_type(path):
 
361
    """Return the type of archive and compressor indicated by path name.
 
362
 
 
363
    Only external compressors are returned, so zip files are only
 
364
    ('zip', None).  .tgz is treated as ('tar', 'gz') and '.tar.xz' is treated
 
365
    as ('tar', 'lzma').
 
366
    """
 
367
    matches = re.match(r'.*\.(zip|tgz|tar(.(gz|bz2|lzma|xz))?)$', path)
 
368
    if not matches:
 
369
        raise NotArchiveType(path)
 
370
    external_compressor = None
 
371
    if matches.group(3) is not None:
 
372
        archive = 'tar'
 
373
        external_compressor = matches.group(3)
 
374
        if external_compressor == 'xz':
 
375
            external_compressor = 'lzma'
 
376
    elif matches.group(1) == 'tgz':
 
377
        return 'tar', 'gz'
 
378
    else:
 
379
        archive = matches.group(1)
 
380
    return archive, external_compressor