1
1
"""Import upstream source into a branch"""
3
from bz2 import BZ2File
6
from shutil import rmtree
7
6
from StringIO import StringIO
9
from unittest import makeSuite
11
from bzrlib import generate_ids
11
12
from bzrlib.bzrdir import BzrDir
12
13
from bzrlib.errors import NoSuchFile, BzrCommandError, NotBranchError
13
from bzrlib.osutils import pathjoin, isdir, file_iterator
14
from bzrlib.tests import TestCaseInTempDir
14
from bzrlib.osutils import (pathjoin, isdir, file_iterator, basename,
15
16
from bzrlib.trace import warning
16
17
from bzrlib.transform import TreeTransform, resolve_conflicts, cook_conflicts
17
18
from bzrlib.workingtree import WorkingTree
20
def top_directory(path):
19
from bzrlib.plugins.bzrtools.bzrtools import open_from_url
20
from bzrlib.plugins.bzrtools import errors
22
class ZipFileWrapper(object):
24
def __init__(self, fileobj, mode):
25
self.zipfile = zipfile.ZipFile(fileobj, mode)
28
for info in self.zipfile.infolist():
29
yield ZipInfoWrapper(self.zipfile, info)
31
def extractfile(self, infowrapper):
32
return StringIO(self.zipfile.read(infowrapper.name))
34
def add(self, filename):
36
self.zipfile.writestr(filename+'/', '')
38
self.zipfile.write(filename)
44
class ZipInfoWrapper(object):
46
def __init__(self, zipfile, info):
49
self.name = info.filename
50
self.zipfile = zipfile
55
return bool(self.name.endswith('/'))
59
return not self.isdir()
62
class DirWrapper(object):
63
def __init__(self, fileobj, mode='r'):
64
assert mode == 'r', mode
65
self.root = os.path.realpath(fileobj.read())
68
return 'DirWrapper(%r)' % self.root
70
def getmembers(self, subdir=None):
71
if subdir is not None:
72
mydir = pathjoin(self.root, subdir)
75
for child in os.listdir(mydir):
76
if subdir is not None:
77
child = pathjoin(subdir, child)
78
fi = FileInfo(self.root, child)
81
for v in self.getmembers(child):
84
def extractfile(self, member):
85
return open(member.fullpath)
88
class FileInfo(object):
90
def __init__(self, root, filepath):
91
self.fullpath = pathjoin(root, filepath)
94
self.name = pathjoin(basename(root), filepath)
96
print 'root %r' % root
97
self.name = basename(root)
99
stat = os.lstat(self.fullpath)
100
self.mode = stat.st_mode
105
return 'FileInfo(%r)' % self.name
108
return stat.S_ISREG(self.mode)
111
return stat.S_ISDIR(self.mode)
114
if stat.S_ISLNK(self.mode):
115
self.linkname = os.readlink(self.fullpath)
21
122
"""Return the top directory given in a path."""
22
dirname = os.path.dirname(path)
23
last_dirname = dirname
25
dirname = os.path.dirname(dirname)
26
if dirname == '' or dirname == last_dirname:
28
last_dirname = dirname
123
components = splitpath(path)
124
if len(components) > 0:
31
130
def common_directory(names):
32
131
"""Determine a single directory prefix from a list of names"""
33
132
possible_prefix = None
34
133
for name in names:
35
name_top = top_directory(name)
134
name_top = top_path(name)
36
137
if possible_prefix is None:
37
138
possible_prefix = name_top
167
def should_ignore(relative_path):
168
return top_path(relative_path) == '.bzr'
66
171
def import_tar(tree, tar_input):
67
172
"""Replace the contents of a working directory with tarfile contents.
68
173
The tarfile may be a gzipped stream. File ids will be updated.
70
175
tar_file = tarfile.open('lala', 'r', tar_input)
71
prefix = common_directory(names_of_files(tar_file))
176
import_archive(tree, tar_file)
178
def import_zip(tree, zip_input):
179
zip_file = ZipFileWrapper(zip_input, 'r')
180
import_archive(tree, zip_file)
182
def import_dir(tree, dir_input):
183
dir_file = DirWrapper(dir_input)
184
import_archive(tree, dir_file)
187
def import_archive(tree, archive_file):
72
188
tt = TreeTransform(tree)
190
import_archive_to_transform(tree, archive_file, tt)
196
def import_archive_to_transform(tree, archive_file, tt):
197
prefix = common_directory(names_of_files(archive_file))
75
199
for path, entry in tree.inventory.iter_entries():
76
200
if entry.parent_id is None:
79
203
tt.delete_contents(trans_id)
83
207
implied_parents = set()
85
for member in tar_file.getmembers():
209
for member in archive_file.getmembers():
86
210
if member.type == 'g':
87
211
# type 'g' is a header
89
relative_path = member.name
213
# Inverse functionality in bzr uses utf-8. We could also
214
# interpret relative to fs encoding, which would match native
216
relative_path = member.name.decode('utf-8')
90
217
if prefix is not None:
91
218
relative_path = relative_path[len(prefix)+1:]
219
relative_path = relative_path.rstrip('/')
92
220
if relative_path == '':
222
if should_ignore(relative_path):
94
224
add_implied_parents(implied_parents, relative_path)
95
225
trans_id = tt.trans_id_tree_path(relative_path)
96
226
added.add(relative_path.rstrip('/'))
97
227
path = tree.abspath(relative_path)
98
228
if member.name in seen:
229
if tt.final_kind(trans_id) == 'file':
230
tt.set_executability(None, trans_id)
99
231
tt.cancel_creation(trans_id)
100
232
seen.add(member.name)
101
233
if member.isreg():
102
tt.create_file(file_iterator(tar_file.extractfile(member)),
234
tt.create_file(file_iterator(archive_file.extractfile(member)),
236
executable = (member.mode & 0111) != 0
237
tt.set_executability(executable, trans_id)
104
238
elif member.isdir():
105
239
do_directory(tt, trans_id, tree, relative_path, path)
106
240
elif member.issym():
107
241
tt.create_symlink(member.linkname, trans_id)
244
if tt.tree_file_id(trans_id) is None:
245
name = basename(member.name.rstrip('/'))
246
file_id = generate_ids.gen_file_id(name)
247
tt.version_file(file_id, trans_id)
109
249
for relative_path in implied_parents.difference(added):
110
250
if relative_path == "":
112
252
trans_id = tt.trans_id_tree_path(relative_path)
113
253
path = tree.abspath(relative_path)
114
254
do_directory(tt, trans_id, tree, relative_path, path)
255
if tt.tree_file_id(trans_id) is None:
256
tt.version_file(trans_id, trans_id)
115
257
added.add(relative_path)
259
for path in removed.difference(added):
260
tt.unversion_file(tt.trans_id_tree_path(path))
117
262
for conflict in cook_conflicts(resolve_conflicts(tt), tt):
118
263
warning(conflict)
120
update_ids(tree, added, removed)
123
def update_ids(tree, added, removed):
124
"""Make sure that all present files files have file_ids.
127
new = added.difference(removed)
128
deleted = removed.difference(added)
129
tree.add(sorted(new))
130
tree.remove(sorted(deleted, reverse=True))
133
266
def do_import(source, tree_directory=None):
147
280
if tree.changes_from(tree.basis_tree()).has_changed():
148
281
raise BzrCommandError("Working tree has uncommitted changes.")
150
if (source.endswith('.tar') or source.endswith('.tar.gz') or
151
source.endswith('.tar.bz2')) or source.endswith('.tgz'):
153
if source.endswith('.bz2'):
154
tar_input = BZ2File(source, 'r')
155
tar_input = StringIO(tar_input.read())
157
tar_input = file(source, 'rb')
159
if e.errno == errno.ENOENT:
160
raise NoSuchFile(source)
162
import_tar(tree, tar_input)
284
archive, external_compressor = get_archive_type(source)
285
except errors.NotArchiveType:
286
if file_kind(source) == 'directory':
291
raise BzrCommandError('Unhandled import source')
294
import_zip(tree, open_from_url(source))
295
elif archive == 'tar':
297
tar_input = open_from_url(source)
298
if external_compressor == 'bz2':
300
tar_input = StringIO(bz2.decompress(tar_input.read()))
301
elif external_compressor == 'lzma':
303
tar_input = StringIO(lzma.decompress(tar_input.read()))
305
if e.errno == errno.ENOENT:
306
raise NoSuchFile(source)
308
import_tar(tree, tar_input)
168
class TestImport(TestCaseInTempDir):
170
def make_tar(self, mode='w'):
172
tar_file = tarfile.open('project-0.1.tar', mode, result)
173
os.mkdir('project-0.1')
174
tar_file.add('project-0.1')
175
os.mkdir('project-0.1/junk')
176
tar_file.add('project-0.1/junk')
178
f = file('project-0.1/README', 'wb')
181
tar_file.add('project-0.1/README')
183
f = file('project-0.1/FEEDME', 'wb')
186
tar_file.add('project-0.1/FEEDME')
189
rmtree('project-0.1')
195
tar_file = tarfile.open('project-0.2.tar', 'w', result)
196
os.mkdir('project-0.2')
197
tar_file.add('project-0.2')
199
os.mkdir('project-0.2/junk')
200
tar_file.add('project-0.2/junk')
202
f = file('project-0.2/README', 'wb')
205
tar_file.add('project-0.2/README')
208
tar_file = tarfile.open('project-0.2.tar', 'a', result)
209
tar_file.add('project-0.2/README')
211
rmtree('project-0.2')
214
def make_messed_tar(self):
216
tar_file = tarfile.open('project-0.1.tar', 'w', result)
217
os.mkdir('project-0.1')
218
tar_file.add('project-0.1')
220
os.mkdir('project-0.2')
221
tar_file.add('project-0.2')
223
f = file('project-0.1/README', 'wb')
226
tar_file.add('project-0.1/README')
228
rmtree('project-0.1')
232
def test_top_directory(self):
233
self.assertEqual(top_directory('ab/b/c'), 'ab')
234
self.assertEqual(top_directory('/etc'), '/')
236
def test_common_directory(self):
237
self.assertEqual(common_directory(['ab/c/d', 'ab/c/e']), 'ab')
238
self.assertIs(common_directory(['ab/c/d', 'ac/c/e']), None)
240
def test_untar(self):
241
tar_file = self.make_tar()
242
tree = BzrDir.create_standalone_workingtree('tree')
243
import_tar(tree, tar_file)
244
self.assertTrue(tree.path2id('README') is not None)
245
self.assertTrue(tree.path2id('FEEDME') is not None)
246
self.assertTrue(os.path.isfile(tree.abspath('README')))
247
self.assertEqual(tree.inventory[tree.path2id('README')].kind, 'file')
248
self.assertEqual(tree.inventory[tree.path2id('FEEDME')].kind, 'file')
250
f = file(tree.abspath('junk/food'), 'wb')
251
f.write('I like food\n')
254
tar_file = self.make_tar2()
255
import_tar(tree, tar_file)
256
self.assertTrue(tree.path2id('README') is not None)
257
self.assertTrue(not os.path.exists(tree.abspath('FEEDME')))
260
def test_untar2(self):
261
tar_file = self.make_messed_tar()
262
tree = BzrDir.create_standalone_workingtree('tree')
263
import_tar(tree, tar_file)
264
self.assertTrue(tree.path2id('project-0.1/README') is not None)
266
def test_untar_gzip(self):
267
tar_file = self.make_tar(mode='w:gz')
268
tree = BzrDir.create_standalone_workingtree('tree')
269
import_tar(tree, tar_file)
270
self.assertTrue(tree.path2id('README') is not None)
274
return makeSuite(TestImport)
315
def get_archive_type(path):
316
"""Return the type of archive and compressor indicated by path name.
318
Only external compressors are returned, so zip files are only
319
('zip', None). .tgz is treated as ('tar', 'gz') and '.tar.xz' is treated
322
matches = re.match(r'.*\.(zip|tgz|tar(.(gz|bz2|lzma|xz))?)$', path)
324
raise errors.NotArchiveType(path)
325
external_compressor = None
326
if matches.group(3) is not None:
328
external_compressor = matches.group(3)
329
if external_compressor == 'xz':
330
external_compressor = 'lzma'
331
elif matches.group(1) == 'tgz':
334
archive = matches.group(1)
335
return archive, external_compressor