99
122
"""Print file with id `file_id` to stdout."""
101
124
sys.stdout.write(self.get_file_text(file_id))
104
def export(self, dest, format='dir', root=None):
105
"""Export this tree."""
107
exporter = exporters[format]
109
from bzrlib.errors import BzrCommandError
110
raise BzrCommandError("export format %r not supported" % format)
111
exporter(self, dest, root)
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)))
115
151
class RevisionTree(Tree):
116
152
"""Tree viewing a previous revision.
122
158
or at least passing a description to the constructor.
125
def __init__(self, weave_store, inv, revision_id):
126
self._weave_store = weave_store
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
127
169
self._inventory = inv
128
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
130
184
def get_weave(self, file_id):
131
return self._weave_store.get_weave(file_id)
185
return self._weave_store.get_weave(file_id,
186
self._repository.get_transaction())
134
188
def get_file_lines(self, file_id):
135
189
ie = self._inventory[file_id]
136
190
weave = self.get_weave(file_id)
137
return weave.get(ie.revision)
191
return weave.get_lines(ie.revision)
140
193
def get_file_text(self, file_id):
141
194
return ''.join(self.get_file_lines(file_id))
144
196
def get_file(self, file_id):
145
197
return StringIO(self.get_file_text(file_id))
147
199
def get_file_size(self, file_id):
148
200
return self._inventory[file_id].text_size
150
def get_file_sha1(self, file_id):
202
def get_file_sha1(self, file_id, path=None):
151
203
ie = self._inventory[file_id]
152
204
if ie.kind == "file":
153
205
return ie.text_sha1
155
def is_executable(self, file_id):
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":
156
217
return self._inventory[file_id].executable
158
219
def has_filename(self, filename):
167
228
ie = self._inventory[file_id]
168
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()
171
241
class EmptyTree(Tree):
172
243
def __init__(self):
173
244
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.
175
253
def get_symlink_target(self, file_id):
178
256
def has_filename(self, filename):
259
def kind(self, file_id):
260
assert self._inventory[file_id].kind == "root_directory"
261
return "root_directory"
181
263
def list_files(self):
184
266
def __contains__(self, file_id):
185
267
return file_id in self._inventory
187
def get_file_sha1(self, file_id):
269
def get_file_sha1(self, file_id, path=None):
188
270
assert self._inventory[file_id].kind == "root_directory"
254
336
yield (old_name, new_name)
258
######################################################################
261
def dir_exporter(tree, dest, root):
262
"""Export this tree to a new directory.
264
`dest` should not exist, and will be created holding the
265
contents of this tree.
267
TODO: To handle subdirectories we need to create the
270
:note: If the export fails, the destination directory will be
271
left in a half-assed state.
275
mutter('export version %r' % tree)
277
for dp, ie in inv.iter_entries():
278
ie.put_on_disk(dest, dp, tree)
280
exporters['dir'] = dir_exporter
287
def get_root_name(dest):
288
"""Get just the root name for a tarball.
290
>>> get_root_name('mytar.tar')
292
>>> get_root_name('mytar.tar.bz2')
294
>>> get_root_name('tar.tar.tar.tgz')
296
>>> get_root_name('bzr-0.0.5.tar.gz')
298
>>> get_root_name('a/long/path/mytar.tgz')
300
>>> get_root_name('../parent/../dir/other.tbz2')
303
endings = ['.tar', '.tar.gz', '.tgz', '.tar.bz2', '.tbz2']
304
dest = os.path.basename(dest)
306
if dest.endswith(end):
307
return dest[:-len(end)]
309
def tar_exporter(tree, dest, root, compression=None):
310
"""Export this tree to a new tar file.
312
`dest` will be created holding the contents of this tree; if it
313
already exists, it will be clobbered, like with "tar -c".
315
from time import time
317
compression = str(compression or '')
319
root = get_root_name(dest)
321
ball = tarfile.open(dest, 'w:' + compression)
322
except tarfile.CompressionError, e:
323
raise BzrError(str(e))
324
mutter('export version %r' % tree)
326
for dp, ie in inv.iter_entries():
327
mutter(" export {%s} kind %s to %s" % (ie.file_id, ie.kind, dest))
328
item, fileobj = ie.get_tar_item(root, dp, now, tree)
329
ball.addfile(item, fileobj)
332
exporters['tar'] = tar_exporter
334
def tgz_exporter(tree, dest, root):
335
tar_exporter(tree, dest, root, compression='gz')
336
exporters['tgz'] = tgz_exporter
338
def tbz_exporter(tree, dest, root):
339
tar_exporter(tree, dest, root, compression='bz2')
340
exporters['tbz2'] = tbz_exporter
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