118
92
"store is probably damaged/corrupt"])
121
def print_file(self, file_id):
122
"""Print file with id `file_id` to stdout."""
95
def print_file(self, fileid):
96
"""Print file with id `fileid` to stdout."""
124
sys.stdout.write(self.get_file_text(file_id))
130
"""What files are present in this tree and unknown.
132
:return: an iterator over the unknown files.
139
def filter_unversioned_files(self, paths):
140
"""Filter out paths that are not versioned.
142
:return: set of paths.
144
# NB: we specifically *don't* call self.has_filename, because for
145
# WorkingTrees that can indicate files that exist on disk but that
147
pred = self.inventory.has_filename
148
return set((p for p in paths if not pred(p)))
98
pumpfile(self.get_file(fileid), sys.stdout)
101
def export(self, dest, format='dir', root=None):
102
"""Export this tree."""
104
exporter = exporters[format]
106
from bzrlib.errors import BzrCommandError
107
raise BzrCommandError("export format %r not supported" % format)
108
exporter(self, dest, root)
151
112
class RevisionTree(Tree):
152
113
"""Tree viewing a previous revision.
158
119
or at least passing a description to the constructor.
161
def __init__(self, branch, inv, revision_id):
162
# for compatability the 'branch' parameter has not been renamed to
163
# repository at this point. However, we should change RevisionTree's
164
# construction to always be via Repository and not via direct
165
# construction - this will mean that we can change the constructor
166
# with much less chance of breaking client code.
167
self._repository = branch
168
self._weave_store = branch.weave_store
122
def __init__(self, store, inv):
169
124
self._inventory = inv
170
self._revision_id = revision_id
172
def get_parent_ids(self):
173
"""See Tree.get_parent_ids.
175
A RevisionTree's parents match the revision graph.
177
parent_ids = self._repository.get_revision(self._revision_id).parent_ids
180
def get_revision_id(self):
181
"""Return the revision id associated with this tree."""
182
return self._revision_id
184
def get_weave(self, file_id):
185
return self._weave_store.get_weave(file_id,
186
self._repository.get_transaction())
188
def get_file_lines(self, file_id):
189
ie = self._inventory[file_id]
190
weave = self.get_weave(file_id)
191
return weave.get_lines(ie.revision)
193
def get_file_text(self, file_id):
194
return ''.join(self.get_file_lines(file_id))
196
126
def get_file(self, file_id):
197
return StringIO(self.get_file_text(file_id))
127
ie = self._inventory[file_id]
128
f = self._store[ie.text_id]
129
mutter(" get fileid{%s} from %r" % (file_id, self))
130
self._check_retrieved(ie, f)
199
133
def get_file_size(self, file_id):
200
134
return self._inventory[file_id].text_size
202
def get_file_sha1(self, file_id, path=None):
136
def get_file_sha1(self, file_id):
203
137
ie = self._inventory[file_id]
204
138
if ie.kind == "file":
205
139
return ie.text_sha1
208
def get_file_mtime(self, file_id, path=None):
209
ie = self._inventory[file_id]
210
revision = self._repository.get_revision(ie.revision)
211
return revision.timestamp
213
def is_executable(self, file_id, path=None):
214
ie = self._inventory[file_id]
215
if ie.kind != "file":
217
return self._inventory[file_id].executable
219
141
def has_filename(self, filename):
220
142
return bool(self.inventory.path2id(filename))
222
144
def list_files(self):
223
145
# The only files returned by this are those from the version
224
146
for path, entry in self.inventory.iter_entries():
225
yield path, 'V', entry.kind, entry.file_id, entry
227
def get_symlink_target(self, file_id):
228
ie = self._inventory[file_id]
229
return ie.symlink_target;
231
def kind(self, file_id):
232
return self._inventory[file_id].kind
235
self._repository.lock_read()
238
self._repository.unlock()
147
yield path, 'V', entry.kind, entry.file_id
241
150
class EmptyTree(Tree):
243
151
def __init__(self):
244
152
self._inventory = Inventory()
246
def get_parent_ids(self):
247
"""See Tree.get_parent_ids.
249
An EmptyTree always has NULL_REVISION as the only parent.
253
def get_symlink_target(self, file_id):
256
154
def has_filename(self, filename):
259
def kind(self, file_id):
260
assert self._inventory[file_id].kind == "root_directory"
261
return "root_directory"
263
157
def list_files(self):
158
if False: # just to make it a generator
266
161
def __contains__(self, file_id):
267
162
return file_id in self._inventory
269
def get_file_sha1(self, file_id, path=None):
164
def get_file_sha1(self, file_id):
270
165
assert self._inventory[file_id].kind == "root_directory"
274
171
######################################################################
336
233
yield (old_name, new_name)
339
def find_ids_across_trees(filenames, trees, require_versioned=True):
340
"""Find the ids corresponding to specified filenames.
342
All matches in all trees will be used, and all children of matched
343
directories will be used.
345
:param filenames: The filenames to find file_ids for
346
:param trees: The trees to find file_ids within
347
:param require_versioned: if true, all specified filenames must occur in
349
:return: a set of file ids for the specified filenames and their children.
353
specified_ids = _find_filename_ids_across_trees(filenames, trees,
355
return _find_children_across_trees(specified_ids, trees)
358
def _find_filename_ids_across_trees(filenames, trees, require_versioned):
359
"""Find the ids corresponding to specified filenames.
361
All matches in all trees will be used.
363
:param filenames: The filenames to find file_ids for
364
:param trees: The trees to find file_ids within
365
:param require_versioned: if true, all specified filenames must occur in
367
:return: a set of file ids for the specified filenames
370
interesting_ids = set()
371
for tree_path in filenames:
374
file_id = tree.inventory.path2id(tree_path)
375
if file_id is not None:
376
interesting_ids.add(file_id)
379
not_versioned.append(tree_path)
380
if len(not_versioned) > 0 and require_versioned:
381
raise errors.PathsNotVersionedError(not_versioned)
382
return interesting_ids
385
def _find_children_across_trees(specified_ids, trees):
386
"""Return a set including specified ids and their children
388
All matches in all trees will be used.
390
:param trees: The trees to find file_ids within
391
:return: a set containing all specified ids and their children
393
interesting_ids = set(specified_ids)
394
pending = interesting_ids
395
# now handle children of interesting ids
396
# we loop so that we handle all children of each id in both trees
397
while len(pending) > 0:
399
for file_id in pending:
401
if file_id not in tree:
403
entry = tree.inventory[file_id]
404
for child in getattr(entry, 'children', {}).itervalues():
405
if child.file_id not in interesting_ids:
406
new_pending.add(child.file_id)
407
interesting_ids.update(new_pending)
408
pending = new_pending
409
return interesting_ids
237
######################################################################
240
def dir_exporter(tree, dest, root):
241
"""Export this tree to a new directory.
243
`dest` should not exist, and will be created holding the
244
contents of this tree.
246
TODO: To handle subdirectories we need to create the
249
:note: If the export fails, the destination directory will be
250
left in a half-assed state.
254
mutter('export version %r' % tree)
256
for dp, ie in inv.iter_entries():
258
fullpath = appendpath(dest, dp)
259
if kind == 'directory':
262
pumpfile(tree.get_file(ie.file_id), file(fullpath, 'wb'))
264
raise BzrError("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
265
mutter(" export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
266
exporters['dir'] = dir_exporter
273
def get_root_name(dest):
274
"""Get just the root name for a tarball.
276
>>> get_root_name('mytar.tar')
278
>>> get_root_name('mytar.tar.bz2')
280
>>> get_root_name('tar.tar.tar.tgz')
282
>>> get_root_name('bzr-0.0.5.tar.gz')
284
>>> get_root_name('a/long/path/mytar.tgz')
286
>>> get_root_name('../parent/../dir/other.tbz2')
289
endings = ['.tar', '.tar.gz', '.tgz', '.tar.bz2', '.tbz2']
290
dest = os.path.basename(dest)
292
if dest.endswith(end):
293
return dest[:-len(end)]
295
def tar_exporter(tree, dest, root, compression=None):
296
"""Export this tree to a new tar file.
298
`dest` will be created holding the contents of this tree; if it
299
already exists, it will be clobbered, like with "tar -c".
301
from time import time
303
compression = str(compression or '')
305
root = get_root_name(dest)
307
ball = tarfile.open(dest, 'w:' + compression)
308
except tarfile.CompressionError, e:
309
raise BzrError(str(e))
310
mutter('export version %r' % tree)
312
for dp, ie in inv.iter_entries():
313
mutter(" export {%s} kind %s to %s" % (ie.file_id, ie.kind, dest))
314
item = tarfile.TarInfo(os.path.join(root, dp))
315
# TODO: would be cool to actually set it to the timestamp of the
316
# revision it was last changed
318
if ie.kind == 'directory':
319
item.type = tarfile.DIRTYPE
324
elif ie.kind == 'file':
325
item.type = tarfile.REGTYPE
326
fileobj = tree.get_file(ie.file_id)
327
item.size = _find_file_size(fileobj)
330
raise BzrError("don't know how to export {%s} of kind %r" %
331
(ie.file_id, ie.kind))
333
ball.addfile(item, fileobj)
335
exporters['tar'] = tar_exporter
337
def tgz_exporter(tree, dest, root):
338
tar_exporter(tree, dest, root, compression='gz')
339
exporters['tgz'] = tgz_exporter
341
def tbz_exporter(tree, dest, root):
342
tar_exporter(tree, dest, root, compression='bz2')
343
exporters['tbz2'] = tbz_exporter
346
def _find_file_size(fileobj):
347
offset = fileobj.tell()
350
size = fileobj.tell()
352
# gzip doesn't accept second argument to seek()
356
nread = len(fileobj.read())