/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
19
from __future__ import absolute_import
20
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
21
import errno
22
import os
23
import re
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
24
from StringIO import StringIO
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
25
import stat
26
import tarfile
27
import zipfile
28
6637.1.2 by Jelmer Vernooij
Add tests.
29
from . import generate_ids, urlutils
6681.2.4 by Jelmer Vernooij
More renames.
30
from .controldir import ControlDir, is_control_filename
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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
6637.1.2 by Jelmer Vernooij
Add tests.
36
from .transport import get_transport
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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):
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
68
        return StringIO(self.zipfile.read(infowrapper.name))
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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):
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
99
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
100
    def __init__(self, fileobj, mode='r'):
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
101
        if mode != 'r':
102
            raise AssertionError(
103
                'only readonly supported')
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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):
6681.2.4 by Jelmer Vernooij
More renames.
207
    return is_control_filename(top_path(relative_path))
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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)
6667.2.1 by Jelmer Vernooij
Some cleanup; s/BzrDir/ControlDir/, remove some unused imports.
313
            branch = ControlDir.create_branch_convenience(tree_directory)
6653.6.1 by Jelmer Vernooij
Rename a number of attributes from bzrdir to controldir.
314
            tree = branch.controldir.open_workingtree()
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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)
6637.1.2 by Jelmer Vernooij
Add tests.
324
        except NotArchiveType:
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
325
            if file_kind(source) == 'directory':
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
326
                s = StringIO(source)
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
339
                        tar_input = StringIO(bz2.decompress(tar_input.read()))
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
340
                    elif external_compressor == 'lzma':
341
                        import lzma
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
342
                        tar_input = StringIO(lzma.decompress(tar_input.read()))
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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:
6637.1.2 by Jelmer Vernooij
Add tests.
363
        raise NotArchiveType(path)
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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