/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-06-10 21:59:15 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170610215915-zcpu0in3r1irx3ml
Move serializer to bzr.

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
from StringIO import StringIO
 
25
import stat
 
26
import tarfile
 
27
import zipfile
 
28
 
 
29
from . import generate_ids, urlutils
 
30
from .controldir import ControlDir
 
31
from .errors import (BzrError, NoSuchFile, BzrCommandError, NotBranchError)
 
32
from .osutils import (pathjoin, isdir, file_iterator, basename,
 
33
                      file_kind, splitpath)
 
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 = 0666
 
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.path2id(relative_path) is not None:
 
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 top_path(relative_path) == '.bzr'
 
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 & 0111) != 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
    tree.lock_write()
 
318
    try:
 
319
        if tree.changes_from(tree.basis_tree()).has_changed():
 
320
            raise BzrCommandError("Working tree has uncommitted changes.")
 
321
 
 
322
        try:
 
323
            archive, external_compressor = get_archive_type(source)
 
324
        except NotArchiveType:
 
325
            if file_kind(source) == 'directory':
 
326
                s = StringIO(source)
 
327
                s.seek(0)
 
328
                import_dir(tree, s)
 
329
            else:
 
330
                raise BzrCommandError('Unhandled import source')
 
331
        else:
 
332
            if archive == 'zip':
 
333
                import_zip(tree, open_from_url(source))
 
334
            elif archive == 'tar':
 
335
                try:
 
336
                    tar_input = open_from_url(source)
 
337
                    if external_compressor == 'bz2':
 
338
                        import bz2
 
339
                        tar_input = StringIO(bz2.decompress(tar_input.read()))
 
340
                    elif external_compressor == 'lzma':
 
341
                        import lzma
 
342
                        tar_input = StringIO(lzma.decompress(tar_input.read()))
 
343
                except IOError, e:
 
344
                    if e.errno == errno.ENOENT:
 
345
                        raise NoSuchFile(source)
 
346
                try:
 
347
                    import_tar(tree, tar_input)
 
348
                finally:
 
349
                    tar_input.close()
 
350
    finally:
 
351
        tree.unlock()
 
352
 
 
353
 
 
354
def get_archive_type(path):
 
355
    """Return the type of archive and compressor indicated by path name.
 
356
 
 
357
    Only external compressors are returned, so zip files are only
 
358
    ('zip', None).  .tgz is treated as ('tar', 'gz') and '.tar.xz' is treated
 
359
    as ('tar', 'lzma').
 
360
    """
 
361
    matches = re.match(r'.*\.(zip|tgz|tar(.(gz|bz2|lzma|xz))?)$', path)
 
362
    if not matches:
 
363
        raise NotArchiveType(path)
 
364
    external_compressor = None
 
365
    if matches.group(3) is not None:
 
366
        archive = 'tar'
 
367
        external_compressor = matches.group(3)
 
368
        if external_compressor == 'xz':
 
369
            external_compressor = 'lzma'
 
370
    elif matches.group(1) == 'tgz':
 
371
        return 'tar', 'gz'
 
372
    else:
 
373
        archive = matches.group(1)
 
374
    return archive, external_compressor