/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
24
import stat
25
import tarfile
26
import zipfile
27
6637.1.2 by Jelmer Vernooij
Add tests.
28
from . import generate_ids, urlutils
6681.2.4 by Jelmer Vernooij
More renames.
29
from .controldir import ControlDir, is_control_filename
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
30
from .errors import (BzrError, NoSuchFile, BzrCommandError, NotBranchError)
31
from .osutils import (pathjoin, isdir, file_iterator, basename,
32
                      file_kind, splitpath)
6791.2.3 by Jelmer Vernooij
Fix more imports.
33
from .sixish import StringIO
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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
6791.2.3 by Jelmer Vernooij
Fix more imports.
87
        self.mode = 0o666
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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:
6791.2.3 by Jelmer Vernooij
Fix more imports.
135
            print('root %r' % root)
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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):
6852.3.1 by Jelmer Vernooij
add Tree.is_versioned.
185
    if isdir(path) and tree.is_versioned(relative_path):
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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)
6791.2.3 by Jelmer Vernooij
Fix more imports.
275
            executable = (member.mode & 0o111) != 0
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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]
6969.3.2 by Jelmer Vernooij
Use context managers for locking.
317
    with tree.lock_write():
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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)
6637.1.2 by Jelmer Vernooij
Add tests.
323
        except NotArchiveType:
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
324
            if file_kind(source) == 'directory':
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
325
                s = StringIO(source)
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
338
                        tar_input = StringIO(bz2.decompress(tar_input.read()))
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
339
                    elif external_compressor == 'lzma':
340
                        import lzma
6637.1.4 by Jelmer Vernooij
Switch back to StringIO for now.
341
                        tar_input = StringIO(lzma.decompress(tar_input.read()))
6791.2.3 by Jelmer Vernooij
Fix more imports.
342
                except IOError as e:
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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:
6637.1.2 by Jelmer Vernooij
Add tests.
360
        raise NotArchiveType(path)
6637.1.1 by Jelmer Vernooij
Bundle upstream_import.
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