~abentley/bzrtools/bzrtools.dev

374 by Aaron Bentley
Start work on import plugin
1
"""Import upstream source into a branch"""
2
384 by Aaron Bentley
Implement bzip support
3
from bz2 import BZ2File
382 by Aaron Bentley
Handle adds and removes efficiently
4
import errno
374 by Aaron Bentley
Start work on import plugin
5
import os
6
from StringIO import StringIO
484 by Aaron Bentley
Get closer to importing directories using the same mechanism as files
7
import stat
374 by Aaron Bentley
Start work on import plugin
8
import tarfile
475 by Aaron Bentley
Add zip import support
9
import zipfile
374 by Aaron Bentley
Start work on import plugin
10
482 by Aaron Bentley
upstream imports honour the execute bit
11
from bzrlib import generate_ids
374 by Aaron Bentley
Start work on import plugin
12
from bzrlib.bzrdir import BzrDir
380 by Aaron Bentley
Got import working decently
13
from bzrlib.errors import NoSuchFile, BzrCommandError, NotBranchError
489 by Aaron Bentley
import now imports directories
14
from bzrlib.osutils import (pathjoin, isdir, file_iterator, basename,
15
                            file_kind)
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
16
from bzrlib.trace import warning
17
from bzrlib.transform import TreeTransform, resolve_conflicts, cook_conflicts
377 by Aaron Bentley
Got import command working
18
from bzrlib.workingtree import WorkingTree
19
475 by Aaron Bentley
Add zip import support
20
class ZipFileWrapper(object):
21
477 by Aaron Bentley
split out upstream_import test cases
22
    def __init__(self, fileobj, mode):
23
        self.zipfile = zipfile.ZipFile(fileobj, mode)
475 by Aaron Bentley
Add zip import support
24
25
    def getmembers(self):
26
        for info in self.zipfile.infolist():
27
            yield ZipInfoWrapper(self.zipfile, info)
28
29
    def extractfile(self, infowrapper):
30
        return StringIO(self.zipfile.read(infowrapper.name))
31
476 by Aaron Bentley
Generalize tests for zip
32
    def add(self, filename):
33
        if isdir(filename):
34
            self.zipfile.writestr(filename+'/', '')
35
        else:
36
            self.zipfile.write(filename)
37
38
    def close(self):
39
        self.zipfile.close()
40
475 by Aaron Bentley
Add zip import support
41
42
class ZipInfoWrapper(object):
43
    
44
    def __init__(self, zipfile, info):
45
        self.info = info
46
        self.type = None
47
        self.name = info.filename
48
        self.zipfile = zipfile
482 by Aaron Bentley
upstream imports honour the execute bit
49
        self.mode = 0666
475 by Aaron Bentley
Add zip import support
50
51
    def isdir(self):
52
        # Really? Eeeew!
53
        return bool(self.name.endswith('/'))
54
55
    def isreg(self):
56
        # Really? Eeeew!
57
        return not self.isdir()
58
374 by Aaron Bentley
Start work on import plugin
59
484 by Aaron Bentley
Get closer to importing directories using the same mechanism as files
60
class DirWrapper(object):
61
    def __init__(self, fileobj, mode='r'):
62
        assert mode == 'r', mode
63
        self.root = os.path.realpath(fileobj.read())
64
488 by Aaron Bentley
Fix tests for importing directories
65
    def __repr__(self):
66
        return 'DirWrapper(%r)' % self.root
67
484 by Aaron Bentley
Get closer to importing directories using the same mechanism as files
68
    def getmembers(self, subdir=None):
69
        if subdir is not None:
70
            mydir = pathjoin(self.root, subdir)
71
        else:
72
            mydir = self.root
73
        for child in os.listdir(mydir):
74
            if subdir is not None:
75
                child = pathjoin(subdir, child)
76
            fi = FileInfo(self.root, child)
77
            yield fi
78
            if fi.isdir():
79
                for v in self.getmembers(child):
80
                    yield v
81
82
    def extractfile(self, member):
83
        return open(member.fullpath)
84
85
86
class FileInfo(object):
87
88
    def __init__(self, root, filepath):
89
        self.fullpath = pathjoin(root, filepath)
90
        self.root = root
489 by Aaron Bentley
import now imports directories
91
        if filepath != '':
92
            self.name = pathjoin(basename(root), filepath)
93
        else:
94
            print 'root %r' % root
95
            self.name = basename(root)
484 by Aaron Bentley
Get closer to importing directories using the same mechanism as files
96
        self.type = None
97
        stat = os.lstat(self.fullpath)
98
        self.mode = stat.st_mode
99
        if self.isdir():
100
            self.name += '/'
101
488 by Aaron Bentley
Fix tests for importing directories
102
    def __repr__(self):
103
        return 'FileInfo(%r)' % self.name
104
484 by Aaron Bentley
Get closer to importing directories using the same mechanism as files
105
    def isreg(self):
106
        return stat.S_ISREG(self.mode)
107
108
    def isdir(self):
109
        return stat.S_ISDIR(self.mode)
110
111
        
374 by Aaron Bentley
Start work on import plugin
112
def top_directory(path):
377 by Aaron Bentley
Got import command working
113
    """Return the top directory given in a path."""
374 by Aaron Bentley
Start work on import plugin
114
    dirname = os.path.dirname(path)
115
    last_dirname = dirname
116
    while True:
117
        dirname = os.path.dirname(dirname)
118
        if dirname == '' or dirname == last_dirname:
119
            return last_dirname
120
        last_dirname = dirname
121
122
123
def common_directory(names):
124
    """Determine a single directory prefix from a list of names"""
125
    possible_prefix = None
126
    for name in names:
127
        name_top = top_directory(name)
484 by Aaron Bentley
Get closer to importing directories using the same mechanism as files
128
        if name_top == '':
129
            return None
374 by Aaron Bentley
Start work on import plugin
130
        if possible_prefix is None:
131
            possible_prefix = name_top
132
        else:
133
            if name_top != possible_prefix:
134
                return None
135
    return possible_prefix
136
137
382 by Aaron Bentley
Handle adds and removes efficiently
138
def do_directory(tt, trans_id, tree, relative_path, path):
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
139
    if isdir(path) and tree.path2id(relative_path) is not None:
140
        tt.cancel_deletion(trans_id)
141
    else:
142
        tt.create_directory(trans_id)
143
144
145
def add_implied_parents(implied_parents, path):
146
    """Update the set of implied parents from a path"""
147
    parent = os.path.dirname(path)
148
    if parent in implied_parents:
149
        return
150
    implied_parents.add(parent)
151
    add_implied_parents(implied_parents, parent)
152
153
383 by Aaron Bentley
Skip the extended header in Linux tarballs
154
def names_of_files(tar_file):
155
    for member in tar_file.getmembers():
156
        if member.type != "g":
157
            yield member.name
158
159
374 by Aaron Bentley
Start work on import plugin
160
def import_tar(tree, tar_input):
377 by Aaron Bentley
Got import command working
161
    """Replace the contents of a working directory with tarfile contents.
384 by Aaron Bentley
Implement bzip support
162
    The tarfile may be a gzipped stream.  File ids will be updated.
377 by Aaron Bentley
Got import command working
163
    """
374 by Aaron Bentley
Start work on import plugin
164
    tar_file = tarfile.open('lala', 'r', tar_input)
475 by Aaron Bentley
Add zip import support
165
    import_archive(tree, tar_file)
166
167
def import_zip(tree, zip_input):
477 by Aaron Bentley
split out upstream_import test cases
168
    zip_file = ZipFileWrapper(zip_input, 'r')
475 by Aaron Bentley
Add zip import support
169
    import_archive(tree, zip_file)
170
484 by Aaron Bentley
Get closer to importing directories using the same mechanism as files
171
def import_dir(tree, dir_input):
172
    dir_file = DirWrapper(dir_input)
173
    import_archive(tree, dir_file)
174
475 by Aaron Bentley
Add zip import support
175
def import_archive(tree, archive_file):
176
    prefix = common_directory(names_of_files(archive_file))
375 by Aaron Bentley
Correctly extract tarfiles
177
    tt = TreeTransform(tree)
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
178
382 by Aaron Bentley
Handle adds and removes efficiently
179
    removed = set()
375 by Aaron Bentley
Correctly extract tarfiles
180
    for path, entry in tree.inventory.iter_entries():
453.1.2 by Aaron Bentley
Handle the fact that roots are included
181
        if entry.parent_id is None:
182
            continue
375 by Aaron Bentley
Correctly extract tarfiles
183
        trans_id = tt.trans_id_tree_path(path)
184
        tt.delete_contents(trans_id)
382 by Aaron Bentley
Handle adds and removes efficiently
185
        removed.add(path)
375 by Aaron Bentley
Correctly extract tarfiles
186
382 by Aaron Bentley
Handle adds and removes efficiently
187
    added = set() 
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
188
    implied_parents = set()
385 by Aaron Bentley
Fix double-add bug
189
    seen = set()
475 by Aaron Bentley
Add zip import support
190
    for member in archive_file.getmembers():
383 by Aaron Bentley
Skip the extended header in Linux tarballs
191
        if member.type == 'g':
192
            # type 'g' is a header
193
            continue
375 by Aaron Bentley
Correctly extract tarfiles
194
        relative_path = member.name 
374 by Aaron Bentley
Start work on import plugin
195
        if prefix is not None:
196
            relative_path = relative_path[len(prefix)+1:]
375 by Aaron Bentley
Correctly extract tarfiles
197
        if relative_path == '':
198
            continue
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
199
        add_implied_parents(implied_parents, relative_path)
375 by Aaron Bentley
Correctly extract tarfiles
200
        trans_id = tt.trans_id_tree_path(relative_path)
382 by Aaron Bentley
Handle adds and removes efficiently
201
        added.add(relative_path.rstrip('/'))
375 by Aaron Bentley
Correctly extract tarfiles
202
        path = tree.abspath(relative_path)
385 by Aaron Bentley
Fix double-add bug
203
        if member.name in seen:
482 by Aaron Bentley
upstream imports honour the execute bit
204
            if tt.final_kind(trans_id) == 'file':
205
                tt.set_executability(None, trans_id)
385 by Aaron Bentley
Fix double-add bug
206
            tt.cancel_creation(trans_id)
207
        seen.add(member.name)
375 by Aaron Bentley
Correctly extract tarfiles
208
        if member.isreg():
475 by Aaron Bentley
Add zip import support
209
            tt.create_file(file_iterator(archive_file.extractfile(member)), 
380 by Aaron Bentley
Got import working decently
210
                           trans_id)
482 by Aaron Bentley
upstream imports honour the execute bit
211
            executable = (member.mode & 0111) != 0
212
            tt.set_executability(executable, trans_id)
375 by Aaron Bentley
Correctly extract tarfiles
213
        elif member.isdir():
382 by Aaron Bentley
Handle adds and removes efficiently
214
            do_directory(tt, trans_id, tree, relative_path, path)
375 by Aaron Bentley
Correctly extract tarfiles
215
        elif member.issym():
216
            tt.create_symlink(member.linkname, trans_id)
482 by Aaron Bentley
upstream imports honour the execute bit
217
        else:
218
            continue
219
        if tt.tree_file_id(trans_id) is None:
220
            name = basename(member.name.rstrip('/'))
221
            file_id = generate_ids.gen_file_id(name)
222
            tt.version_file(file_id, trans_id)
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
223
382 by Aaron Bentley
Handle adds and removes efficiently
224
    for relative_path in implied_parents.difference(added):
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
225
        if relative_path == "":
226
            continue
227
        trans_id = tt.trans_id_tree_path(relative_path)
228
        path = tree.abspath(relative_path)
382 by Aaron Bentley
Handle adds and removes efficiently
229
        do_directory(tt, trans_id, tree, relative_path, path)
482 by Aaron Bentley
upstream imports honour the execute bit
230
        if tt.tree_file_id(trans_id) is None:
231
            tt.version_file(trans_id, trans_id)
382 by Aaron Bentley
Handle adds and removes efficiently
232
        added.add(relative_path)
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
233
482 by Aaron Bentley
upstream imports honour the execute bit
234
    for path in removed.difference(added):
235
        tt.unversion_file(tt.trans_id_tree_path(path))
236
381 by Aaron Bentley
Handle conflicts and tarfiles that omit directories
237
    for conflict in cook_conflicts(resolve_conflicts(tt), tt):
238
        warning(conflict)
375 by Aaron Bentley
Correctly extract tarfiles
239
    tt.apply()
374 by Aaron Bentley
Start work on import plugin
240
377 by Aaron Bentley
Got import command working
241
380 by Aaron Bentley
Got import working decently
242
def do_import(source, tree_directory=None):
377 by Aaron Bentley
Got import command working
243
    """Implementation of import command.  Intended for UI only"""
380 by Aaron Bentley
Got import working decently
244
    if tree_directory is not None:
245
        try:
246
            tree = WorkingTree.open(tree_directory)
247
        except NotBranchError:
248
            if not os.path.exists(tree_directory):
249
                os.mkdir(tree_directory)
250
            branch = BzrDir.create_branch_convenience(tree_directory)
251
            tree = branch.bzrdir.open_workingtree()
252
    else:
253
        tree = WorkingTree.open_containing('.')[0]
377 by Aaron Bentley
Got import command working
254
    tree.lock_write()
255
    try:
423.1.7 by Aaron Bentley
More updates for 0.9
256
        if tree.changes_from(tree.basis_tree()).has_changed():
378 by Aaron Bentley
Check for modified files
257
            raise BzrCommandError("Working tree has uncommitted changes.")
258
377 by Aaron Bentley
Got import command working
259
        if (source.endswith('.tar') or source.endswith('.tar.gz') or 
384 by Aaron Bentley
Implement bzip support
260
            source.endswith('.tar.bz2')) or source.endswith('.tgz'):
377 by Aaron Bentley
Got import command working
261
            try:
384 by Aaron Bentley
Implement bzip support
262
                if source.endswith('.bz2'):
263
                    tar_input = BZ2File(source, 'r')
264
                    tar_input = StringIO(tar_input.read())
265
                else:
266
                    tar_input = file(source, 'rb')
382 by Aaron Bentley
Handle adds and removes efficiently
267
            except IOError, e:
377 by Aaron Bentley
Got import command working
268
                if e.errno == errno.ENOENT:
269
                    raise NoSuchFile(source)
270
            try:
271
                import_tar(tree, tar_input)
272
            finally:
273
                tar_input.close()
475 by Aaron Bentley
Add zip import support
274
        elif source.endswith('.zip'):
275
            import_zip(tree, open(source, 'rb'))
489 by Aaron Bentley
import now imports directories
276
        elif file_kind(source) == 'directory':
277
            s = StringIO(source)
278
            s.seek(0)
279
            import_dir(tree, s)
475 by Aaron Bentley
Add zip import support
280
        else:
281
            raise BzrCommandError('Unhandled import source')
377 by Aaron Bentley
Got import command working
282
    finally:
283
        tree.unlock()