/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: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

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