1
1
"""Import upstream source into a branch"""
3
from bz2 import BZ2File
6
from shutil import rmtree
6
7
from StringIO import StringIO
9
from unittest import makeSuite
11
from bzrlib import generate_ids
12
11
from bzrlib.bzrdir import BzrDir
12
from bzrlib.delta import compare_trees
13
13
from bzrlib.errors import NoSuchFile, BzrCommandError, NotBranchError
14
from bzrlib.osutils import (pathjoin, isdir, file_iterator, basename,
14
from bzrlib.osutils import pathjoin, isdir, file_iterator
15
from bzrlib.tests import TestCaseInTempDir
16
16
from bzrlib.trace import warning
17
17
from bzrlib.transform import TreeTransform, resolve_conflicts, cook_conflicts
18
18
from bzrlib.workingtree import WorkingTree
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
def top_directory(path):
122
22
"""Return the top directory given in a path."""
123
components = splitpath(path)
124
if len(components) > 0:
23
dirname = os.path.dirname(path)
24
last_dirname = dirname
26
dirname = os.path.dirname(dirname)
27
if dirname == '' or dirname == last_dirname:
29
last_dirname = dirname
130
32
def common_directory(names):
131
33
"""Determine a single directory prefix from a list of names"""
132
34
possible_prefix = None
133
35
for name in names:
134
name_top = top_path(name)
36
name_top = top_directory(name)
137
37
if possible_prefix is None:
138
38
possible_prefix = name_top
167
def should_ignore(relative_path):
168
return top_path(relative_path) == '.bzr'
171
67
def import_tar(tree, tar_input):
172
68
"""Replace the contents of a working directory with tarfile contents.
173
69
The tarfile may be a gzipped stream. File ids will be updated.
175
71
tar_file = tarfile.open('lala', 'r', tar_input)
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
prefix = common_directory(names_of_files(tar_file))
188
73
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))
199
for path, entry in tree.iter_entries_by_dir():
200
if entry.parent_id is None:
76
for path, entry in tree.inventory.iter_entries():
202
77
trans_id = tt.trans_id_tree_path(path)
203
78
tt.delete_contents(trans_id)
207
82
implied_parents = set()
209
for member in archive_file.getmembers():
84
for member in tar_file.getmembers():
210
85
if member.type == 'g':
211
86
# type 'g' is a header
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')
88
relative_path = member.name
217
89
if prefix is not None:
218
90
relative_path = relative_path[len(prefix)+1:]
219
relative_path = relative_path.rstrip('/')
220
91
if relative_path == '':
222
if should_ignore(relative_path):
224
93
add_implied_parents(implied_parents, relative_path)
225
94
trans_id = tt.trans_id_tree_path(relative_path)
226
95
added.add(relative_path.rstrip('/'))
227
96
path = tree.abspath(relative_path)
228
97
if member.name in seen:
229
if tt.final_kind(trans_id) == 'file':
230
tt.set_executability(None, trans_id)
231
98
tt.cancel_creation(trans_id)
232
99
seen.add(member.name)
233
100
if member.isreg():
234
tt.create_file(file_iterator(archive_file.extractfile(member)),
101
tt.create_file(file_iterator(tar_file.extractfile(member)),
236
executable = (member.mode & 0111) != 0
237
tt.set_executability(executable, trans_id)
238
103
elif member.isdir():
239
104
do_directory(tt, trans_id, tree, relative_path, path)
240
105
elif member.issym():
241
106
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)
249
108
for relative_path in implied_parents.difference(added):
250
109
if relative_path == "":
252
111
trans_id = tt.trans_id_tree_path(relative_path)
253
112
path = tree.abspath(relative_path)
254
113
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)
257
114
added.add(relative_path)
259
for path in removed.difference(added):
260
tt.unversion_file(tt.trans_id_tree_path(path))
262
116
for conflict in cook_conflicts(resolve_conflicts(tt), tt):
263
117
warning(conflict)
119
update_ids(tree, added, removed)
122
def update_ids(tree, added, removed):
123
"""Make sure that all present files files have file_ids.
126
new = added.difference(removed)
127
deleted = removed.difference(added)
128
tree.add(sorted(new))
129
tree.remove(sorted(deleted, reverse=True))
266
132
def do_import(source, tree_directory=None):
277
143
tree = WorkingTree.open_containing('.')[0]
278
144
tree.lock_write()
280
if tree.changes_from(tree.basis_tree()).has_changed():
146
if compare_trees(tree, tree.basis_tree()).has_changed():
281
147
raise BzrCommandError("Working tree has uncommitted changes.")
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)
149
if (source.endswith('.tar') or source.endswith('.tar.gz') or
150
source.endswith('.tar.bz2')) or source.endswith('.tgz'):
152
if source.endswith('.bz2'):
153
tar_input = BZ2File(source, 'r')
154
tar_input = StringIO(tar_input.read())
156
tar_input = file(source, 'rb')
158
if e.errno == errno.ENOENT:
159
raise NoSuchFile(source)
161
import_tar(tree, tar_input)
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
167
class TestImport(TestCaseInTempDir):
169
def make_tar(self, mode='w'):
171
tar_file = tarfile.open('project-0.1.tar', mode, result)
172
os.mkdir('project-0.1')
173
tar_file.add('project-0.1')
174
os.mkdir('project-0.1/junk')
175
tar_file.add('project-0.1/junk')
177
f = file('project-0.1/README', 'wb')
180
tar_file.add('project-0.1/README')
182
f = file('project-0.1/FEEDME', 'wb')
185
tar_file.add('project-0.1/FEEDME')
188
rmtree('project-0.1')
194
tar_file = tarfile.open('project-0.2.tar', 'w', result)
195
os.mkdir('project-0.2')
196
tar_file.add('project-0.2')
198
os.mkdir('project-0.2/junk')
199
tar_file.add('project-0.2/junk')
201
f = file('project-0.2/README', 'wb')
204
tar_file.add('project-0.2/README')
207
tar_file = tarfile.open('project-0.2.tar', 'a', result)
208
tar_file.add('project-0.2/README')
210
rmtree('project-0.2')
213
def make_messed_tar(self):
215
tar_file = tarfile.open('project-0.1.tar', 'w', result)
216
os.mkdir('project-0.1')
217
tar_file.add('project-0.1')
219
os.mkdir('project-0.2')
220
tar_file.add('project-0.2')
222
f = file('project-0.1/README', 'wb')
225
tar_file.add('project-0.1/README')
227
rmtree('project-0.1')
231
def test_top_directory(self):
232
self.assertEqual(top_directory('ab/b/c'), 'ab')
233
self.assertEqual(top_directory('/etc'), '/')
235
def test_common_directory(self):
236
self.assertEqual(common_directory(['ab/c/d', 'ab/c/e']), 'ab')
237
self.assertIs(common_directory(['ab/c/d', 'ac/c/e']), None)
239
def test_untar(self):
240
tar_file = self.make_tar()
241
tree = BzrDir.create_standalone_workingtree('tree')
242
import_tar(tree, tar_file)
243
self.assertTrue(tree.path2id('README') is not None)
244
self.assertTrue(tree.path2id('FEEDME') is not None)
245
self.assertTrue(os.path.isfile(tree.abspath('README')))
246
self.assertEqual(tree.inventory[tree.path2id('README')].kind, 'file')
247
self.assertEqual(tree.inventory[tree.path2id('FEEDME')].kind, 'file')
249
f = file(tree.abspath('junk/food'), 'wb')
250
f.write('I like food\n')
253
tar_file = self.make_tar2()
254
import_tar(tree, tar_file)
255
self.assertTrue(tree.path2id('README') is not None)
256
self.assertTrue(not os.path.exists(tree.abspath('FEEDME')))
259
def test_untar2(self):
260
tar_file = self.make_messed_tar()
261
tree = BzrDir.create_standalone_workingtree('tree')
262
import_tar(tree, tar_file)
263
self.assertTrue(tree.path2id('project-0.1/README') is not None)
265
def test_untar_gzip(self):
266
tar_file = self.make_tar(mode='w:gz')
267
tree = BzrDir.create_standalone_workingtree('tree')
268
import_tar(tree, tar_file)
269
self.assertTrue(tree.path2id('README') is not None)
273
return makeSuite(TestImport)