/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: 2018-07-08 14:45:27 UTC
  • mto: This revision was merged to the branch mainline in revision 7036.
  • Revision ID: jelmer@jelmer.uk-20180708144527-codhlvdcdg9y0nji
Fix a bunch of merge tests.

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