/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: Robert Collins
  • Date: 2005-10-19 10:11:57 UTC
  • mfrom: (1185.16.78)
  • mto: This revision was merged to the branch mainline in revision 1470.
  • Revision ID: robertc@robertcollins.net-20051019101157-17438d311e746b4f
mergeĀ fromĀ upstream

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