50
56
trees or versioned trees.
59
def changes_from(self, other, want_unchanged=False, specific_files=None,
60
extra_trees=None, require_versioned=False, include_root=False):
61
"""Return a TreeDelta of the changes from other to this tree.
63
:param other: A tree to compare with.
64
:param specific_files: An optional list of file paths to restrict the
65
comparison to. When mapping filenames to ids, all matches in all
66
trees (including optional extra_trees) are used, and all children of
67
matched directories are included.
68
:param want_unchanged: An optional boolean requesting the inclusion of
69
unchanged entries in the result.
70
:param extra_trees: An optional list of additional trees to use when
71
mapping the contents of specific_files (paths) to file_ids.
72
:param require_versioned: An optional boolean (defaults to False). When
73
supplied and True all the 'specific_files' must be versioned, or
74
a PathsNotVersionedError will be thrown.
76
The comparison will be performed by an InterTree object looked up on
79
# Martin observes that Tree.changes_from returns a TreeDelta and this
80
# may confuse people, because the class name of the returned object is
81
# a synonym of the object referenced in the method name.
82
return InterTree.get(other, self).compare(
83
want_unchanged=want_unchanged,
84
specific_files=specific_files,
85
extra_trees=extra_trees,
86
require_versioned=require_versioned,
87
include_root=include_root
90
def _iter_changes(self, from_tree, include_unchanged=False,
91
specific_file_ids=None, pb=None):
92
intertree = InterTree.get(from_tree, self)
93
return intertree._iter_changes(from_tree, self, include_unchanged,
94
specific_file_ids, pb)
53
96
def conflicts(self):
54
97
"""Get a list of the conflicts in the tree.
73
116
def has_id(self, file_id):
74
117
return self.inventory.has_id(file_id)
119
__contains__ = has_id
76
121
def has_or_had_id(self, file_id):
77
122
if file_id == self.inventory.root.file_id:
79
124
return self.inventory.has_id(file_id)
83
126
def __iter__(self):
84
127
return iter(self.inventory)
86
129
def id2path(self, file_id):
87
130
return self.inventory.id2path(file_id)
132
def is_control_filename(self, filename):
133
"""True if filename is the name of a control file in this tree.
135
:param filename: A filename within the tree. This is a relative path
136
from the root of this tree.
138
This is true IF and ONLY IF the filename is part of the meta data
139
that bzr controls in this tree. I.E. a random .bzr directory placed
140
on disk will not be a control file for this tree.
142
return self.bzrdir.is_control_filename(filename)
144
def iter_entries_by_dir(self, specific_file_ids=None):
145
"""Walk the tree in 'by_dir' order.
147
This will yield each entry in the tree as a (path, entry) tuple. The
148
order that they are yielded is: the contents of a directory are
149
preceeded by the parent of a directory, and all the contents of a
150
directory are grouped together.
152
return self.inventory.iter_entries_by_dir(
153
specific_file_ids=specific_file_ids)
89
155
def kind(self, file_id):
90
156
raise NotImplementedError("subclasses must implement kind")
158
def _comparison_data(self, entry, path):
159
"""Return a tuple of kind, executable, stat_value for a file.
161
entry may be None if there is no inventory entry for the file, but
162
path must always be supplied.
164
kind is None if there is no file present (even if an inventory id is
165
present). executable is False for non-file entries.
167
raise NotImplementedError(self._comparison_data)
169
def _file_size(entry, stat_value):
170
raise NotImplementedError(self._file_size)
92
172
def _get_inventory(self):
93
173
return self._inventory
175
def get_file(self, file_id):
176
"""Return a file object for the file file_id in the tree."""
177
raise NotImplementedError(self.get_file)
95
179
def get_file_by_path(self, path):
96
180
return self.get_file(self._inventory.path2id(path))
182
def annotate_iter(self, file_id):
183
"""Return an iterator of revision_id, line tuples
185
For working trees (and mutable trees in general), the special
186
revision_id 'current:' will be used for lines that are new in this
187
tree, e.g. uncommitted changes.
188
:param file_id: The file to produce an annotated version from
190
raise NotImplementedError(self.annotate_iter)
98
192
inventory = property(_get_inventory,
99
193
doc="Inventory of this Tree")
146
243
# are not versioned.
147
244
pred = self.inventory.has_filename
148
245
return set((p for p in paths if not pred(p)))
151
class RevisionTree(Tree):
152
"""Tree viewing a previous revision.
154
File text can be retrieved from the text store.
156
TODO: Some kind of `__repr__` method, but a good one
157
probably means knowing the branch and revision number,
158
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
169
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
def get_file(self, file_id):
197
return StringIO(self.get_file_text(file_id))
199
def get_file_size(self, file_id):
200
return self._inventory[file_id].text_size
202
def get_file_sha1(self, file_id, path=None):
203
ie = self._inventory[file_id]
204
if ie.kind == "file":
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
def has_filename(self, filename):
220
return bool(self.inventory.path2id(filename))
222
def list_files(self):
223
# The only files returned by this are those from the version
224
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()
241
248
class EmptyTree(Tree):
243
250
def __init__(self):
244
self._inventory = Inventory()
251
self._inventory = Inventory(root_id=None)
252
symbol_versioning.warn('EmptyTree is deprecated as of bzr 0.9 please'
253
' use repository.revision_tree instead.',
254
DeprecationWarning, stacklevel=2)
246
256
def get_parent_ids(self):
247
"""See Tree.get_parent_ids.
249
An EmptyTree always has NULL_REVISION as the only parent.
253
259
def get_symlink_target(self, file_id):
407
412
interesting_ids.update(new_pending)
408
413
pending = new_pending
409
414
return interesting_ids
417
class InterTree(InterObject):
418
"""This class represents operations taking place between two Trees.
420
Its instances have methods like 'compare' and contain references to the
421
source and target trees these operations are to be carried out on.
423
clients of bzrlib should not need to use InterTree directly, rather they
424
should use the convenience methods on Tree such as 'Tree.compare()' which
425
will pass through to InterTree as appropriate.
431
def compare(self, want_unchanged=False, specific_files=None,
432
extra_trees=None, require_versioned=False, include_root=False):
433
"""Return the changes from source to target.
435
:return: A TreeDelta.
436
:param specific_files: An optional list of file paths to restrict the
437
comparison to. When mapping filenames to ids, all matches in all
438
trees (including optional extra_trees) are used, and all children of
439
matched directories are included.
440
:param want_unchanged: An optional boolean requesting the inclusion of
441
unchanged entries in the result.
442
:param extra_trees: An optional list of additional trees to use when
443
mapping the contents of specific_files (paths) to file_ids.
444
:param require_versioned: An optional boolean (defaults to False). When
445
supplied and True all the 'specific_files' must be versioned, or
446
a PathsNotVersionedError will be thrown.
448
# NB: show_status depends on being able to pass in non-versioned files and
449
# report them as unknown
450
trees = (self.source, self.target)
451
if extra_trees is not None:
452
trees = trees + tuple(extra_trees)
453
specific_file_ids = find_ids_across_trees(specific_files,
454
trees, require_versioned=require_versioned)
455
if specific_files and not specific_file_ids:
456
# All files are unversioned, so just return an empty delta
457
# _compare_trees would think we want a complete delta
458
return delta.TreeDelta()
459
return delta._compare_trees(self.source, self.target, want_unchanged,
460
specific_file_ids, include_root)
462
def _iter_changes(self, from_tree, to_tree, include_unchanged,
463
specific_file_ids, pb):
464
"""Generate an iterator of changes between trees.
467
(file_id, path, changed_content, versioned, parent, name, kind,
470
Path is relative to the to_tree. changed_content is True if the file's
471
content has changed. This includes changes to its kind, and to
474
versioned, parent, name, kind, executable are tuples of (from, to).
475
If a file is missing in a tree, its kind is None.
477
Iteration is done in parent-to-child order, relative to the to_tree.
480
from_entries_by_dir = list(from_tree.inventory.iter_entries_by_dir(
481
specific_file_ids=specific_file_ids))
482
from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
483
to_entries_by_dir = list(to_tree.inventory.iter_entries_by_dir(specific_file_ids=specific_file_ids))
484
num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
486
for to_path, to_entry in to_entries_by_dir:
487
file_id = to_entry.file_id
488
to_paths[file_id] = to_path
490
changed_content = False
491
from_path, from_entry = from_data.get(file_id, (None, None))
492
from_versioned = (from_entry is not None)
493
if from_entry is not None:
494
from_versioned = True
495
from_name = from_entry.name
496
from_parent = from_entry.parent_id
497
from_kind, from_executable, from_stat = \
498
from_tree._comparison_data(from_entry, from_path)
501
from_versioned = False
505
from_executable = None
506
versioned = (from_versioned, True)
507
to_kind, to_executable, to_stat = \
508
to_tree._comparison_data(to_entry, to_path)
509
kind = (from_kind, to_kind)
510
if kind[0] != kind[1]:
511
changed_content = True
512
elif from_kind == 'file':
513
from_size = from_tree._file_size(from_entry, from_stat)
514
to_size = to_tree._file_size(to_entry, to_stat)
515
if from_size != to_size:
516
changed_content = True
517
elif (from_tree.get_file_sha1(file_id, from_path, from_stat) !=
518
to_tree.get_file_sha1(file_id, to_path, to_stat)):
519
changed_content = True
520
elif from_kind == 'symlink':
521
if (from_tree.get_symlink_target(file_id) !=
522
to_tree.get_symlink_target(file_id)):
523
changed_content = True
524
parent = (from_parent, to_entry.parent_id)
525
name = (from_name, to_entry.name)
526
executable = (from_executable, to_executable)
528
pb.update('comparing files', entry_count, num_entries)
529
if (changed_content is not False or versioned[0] != versioned[1]
530
or parent[0] != parent[1] or name[0] != name[1] or
531
executable[0] != executable[1] or include_unchanged):
532
yield (file_id, to_path, changed_content, versioned, parent,
533
name, kind, executable)
535
for path, from_entry in from_entries_by_dir:
536
file_id = from_entry.file_id
537
if file_id in to_paths:
539
if from_entry.parent_id is None:
542
to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
544
to_paths[file_id] = to_path
547
pb.update('comparing files', entry_count, num_entries)
548
versioned = (True, False)
549
parent = (from_entry.parent_id, None)
550
name = (from_entry.name, None)
551
from_kind, from_executable, stat_value = \
552
from_tree._comparison_data(from_entry, path)
553
kind = (from_kind, None)
554
executable = (from_executable, None)
555
changed_content = True
556
# the parent's path is necessarily known at this point.
557
yield(file_id, to_path, changed_content, versioned, parent,
558
name, kind, executable)
561
# This was deprecated before 0.12, but did not have an official warning
562
@symbol_versioning.deprecated_function(symbol_versioning.zero_twelve)
563
def RevisionTree(*args, **kwargs):
564
"""RevisionTree has moved to bzrlib.revisiontree.RevisionTree()
566
Accessing it as bzrlib.tree.RevisionTree has been deprecated as of
569
from bzrlib.revisiontree import RevisionTree as _RevisionTree
570
return _RevisionTree(*args, **kwargs)