13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Tree classes, representing directory at point in time.
22
from bzrlib.lazy_import import lazy_import
23
lazy_import(globals(), """
21
from collections import deque
22
from cStringIO import StringIO
26
25
from bzrlib import (
27
26
conflicts as _mod_conflicts,
34
29
revision as _mod_revision,
40
33
from bzrlib.decorators import needs_read_lock
34
from bzrlib.errors import BzrError, BzrCheckError
35
from bzrlib import errors
36
from bzrlib.inventory import Inventory, InventoryFile
41
37
from bzrlib.inter import InterObject
42
from bzrlib.symbol_versioning import (
38
from bzrlib.osutils import fingerprint_file
39
import bzrlib.revision
40
from bzrlib.trace import mutter, note
48
43
class Tree(object):
49
44
"""Abstract file tree.
51
46
There are several subclasses:
53
48
* `WorkingTree` exists as files on disk editable by the user.
55
50
* `RevisionTree` is a tree as recorded at some point in the past.
52
Trees contain an `Inventory` object, and also know how to retrieve
53
file texts mentioned in the inventory, either from a working
54
directory or from a store.
56
It is possible for trees to contain files that are not described
57
in their inventory or vice versa; for this use `filenames()`.
57
59
Trees can be compared, etc, regardless of whether they are working
58
60
trees or versioned trees.
61
63
def changes_from(self, other, want_unchanged=False, specific_files=None,
62
64
extra_trees=None, require_versioned=False, include_root=False,
63
65
want_unversioned=False):
177
199
The yield order (ignoring root) would be::
179
200
a, f, a/b, a/d, a/b/c, a/d/e, f/g
181
:param yield_parents: If True, yield the parents from the root leading
182
down to specific_file_ids that have been requested. This has no
183
impact if specific_file_ids is None.
185
raise NotImplementedError(self.iter_entries_by_dir)
187
def list_files(self, include_root=False, from_dir=None, recursive=True):
188
"""List all files in this tree.
190
:param include_root: Whether to include the entry for the tree root
191
:param from_dir: Directory under which to list files
192
:param recursive: Whether to list files recursively
193
:return: iterator over tuples of (path, versioned, kind, file_id,
196
raise NotImplementedError(self.list_files)
202
return self.inventory.iter_entries_by_dir(
203
specific_file_ids=specific_file_ids)
198
205
def iter_references(self):
199
if self.supports_tree_reference():
200
for path, entry in self.iter_entries_by_dir():
201
if entry.kind == 'tree-reference':
202
yield path, entry.file_id
206
for path, entry in self.iter_entries_by_dir():
207
if entry.kind == 'tree-reference':
208
yield path, entry.file_id
204
210
def kind(self, file_id):
205
211
raise NotImplementedError("Tree subclass %s must implement kind"
250
252
def _file_size(self, entry, stat_value):
251
253
raise NotImplementedError(self._file_size)
255
def _get_inventory(self):
256
return self._inventory
253
258
def get_file(self, file_id, path=None):
254
259
"""Return a file object for the file file_id in the tree.
256
261
If both file_id and path are defined, it is implementation defined as
257
262
to which one is used.
259
264
raise NotImplementedError(self.get_file)
261
def get_file_with_stat(self, file_id, path=None):
262
"""Get a file handle and stat object for file_id.
264
The default implementation returns (self.get_file, None) for backwards
267
:param file_id: The file id to read.
268
:param path: The path of the file, if it is known.
269
:return: A tuple (file_handle, stat_value_or_None). If the tree has
270
no stat facility, or need for a stat cache feedback during commit,
271
it may return None for the second element of the tuple.
273
return (self.get_file(file_id, path), None)
275
def get_file_text(self, file_id, path=None):
276
"""Return the byte content of a file.
278
:param file_id: The file_id of the file.
279
:param path: The path of the file.
281
If both file_id and path are supplied, an implementation may use
284
:returns: A single byte string for the whole file.
286
my_file = self.get_file(file_id, path)
288
return my_file.read()
292
def get_file_lines(self, file_id, path=None):
293
"""Return the content of a file, as lines.
295
:param file_id: The file_id of the file.
296
:param path: The path of the file.
298
If both file_id and path are supplied, an implementation may use
301
return osutils.split_lines(self.get_file_text(file_id, path))
303
def get_file_verifier(self, file_id, path=None, stat_value=None):
304
"""Return a verifier for a file.
306
The default implementation returns a sha1.
308
:param file_id: The handle for this file.
309
:param path: The path that this file can be found at.
310
These must point to the same object.
311
:param stat_value: Optional stat value for the object
312
:return: Tuple with verifier name and verifier data
314
return ("SHA1", self.get_file_sha1(file_id, path=path,
315
stat_value=stat_value))
317
def get_file_sha1(self, file_id, path=None, stat_value=None):
318
"""Return the SHA1 file for a file.
320
:note: callers should use get_file_verifier instead
321
where possible, as the underlying repository implementation may
322
have quicker access to a non-sha1 verifier.
324
:param file_id: The handle for this file.
325
:param path: The path that this file can be found at.
326
These must point to the same object.
327
:param stat_value: Optional stat value for the object
329
raise NotImplementedError(self.get_file_sha1)
331
266
def get_file_mtime(self, file_id, path=None):
332
267
"""Return the modification time for a file.
460
383
except errors.NoSuchRevisionInTree:
461
384
yield self.repository.revision_tree(revision_id)
387
def _file_revision(revision_tree, file_id):
388
"""Determine the revision associated with a file in a given tree."""
389
revision_tree.lock_read()
391
return revision_tree.inventory[file_id].revision
393
revision_tree.unlock()
463
395
def _get_file_revision(self, file_id, vf, tree_revision):
464
396
"""Ensure that file_id, tree_revision is in vf to plan the merge."""
466
398
if getattr(self, '_repository', None) is None:
467
399
last_revision = tree_revision
468
parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
400
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
469
401
self._iter_parent_trees()]
470
402
vf.add_lines((file_id, last_revision), parent_keys,
471
self.get_file_lines(file_id))
403
self.get_file(file_id).readlines())
472
404
repo = self.branch.repository
473
405
base_vf = repo.texts
475
last_revision = self.get_file_revision(file_id)
407
last_revision = self._file_revision(self, file_id)
476
408
base_vf = self._repository.texts
477
409
if base_vf not in vf.fallback_versionedfiles:
478
410
vf.fallback_versionedfiles.append(base_vf)
479
411
return last_revision
413
inventory = property(_get_inventory,
414
doc="Inventory of this Tree")
481
416
def _check_retrieved(self, ie, f):
482
417
if not __debug__:
484
fp = osutils.fingerprint_file(f)
419
fp = fingerprint_file(f)
487
422
if ie.text_size is not None:
488
423
if ie.text_size != fp['size']:
489
raise errors.BzrError(
490
"mismatched size for file %r in %r" %
491
(ie.file_id, self._store),
424
raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
492
425
["inventory expects %d bytes" % ie.text_size,
493
426
"file is actually %d bytes" % fp['size'],
494
427
"store is probably damaged/corrupt"])
496
429
if ie.text_sha1 != fp['sha1']:
497
raise errors.BzrError("wrong SHA-1 for file %r in %r" %
498
(ie.file_id, self._store),
430
raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
499
431
["inventory expects %s" % ie.text_sha1,
500
432
"file is actually %s" % fp['sha1'],
501
433
"store is probably damaged/corrupt"])
503
436
def path2id(self, path):
504
437
"""Return the id for path in this tree."""
505
raise NotImplementedError(self.path2id)
438
return self._inventory.path2id(path)
507
440
def paths2ids(self, paths, trees=[], require_versioned=True):
508
441
"""Return all the ids that can be reached by walking from paths.
510
443
Each path is looked up in this tree and any extras provided in
511
444
trees, and this is repeated recursively: the children in an extra tree
512
445
of a directory that has been renamed under a provided path in this tree
601
540
raise NotImplementedError(self.walkdirs)
603
def supports_content_filtering(self):
606
def _content_filter_stack(self, path=None, file_id=None):
607
"""The stack of content filters for a path if filtering is supported.
609
Readers will be applied in first-to-last order.
610
Writers will be applied in last-to-first order.
611
Either the path or the file-id needs to be provided.
613
:param path: path relative to the root of the tree
615
:param file_id: file_id or None if unknown
616
:return: the list of filters - [] if there are none
618
filter_pref_names = filters._get_registered_names()
619
if len(filter_pref_names) == 0:
622
path = self.id2path(file_id)
623
prefs = self.iter_search_rules([path], filter_pref_names).next()
624
stk = filters._get_filter_stack_for(prefs)
625
if 'filters' in debug.debug_flags:
626
trace.note("*** %s content-filter: %s => %r" % (path,prefs,stk))
629
def _content_filter_stack_provider(self):
630
"""A function that returns a stack of ContentFilters.
632
The function takes a path (relative to the top of the tree) and a
633
file-id as parameters.
635
:return: None if content filtering is not supported by this tree.
637
if self.supports_content_filtering():
638
return lambda path, file_id: \
639
self._content_filter_stack(path, file_id)
643
542
def iter_search_rules(self, path_names, pref_names=None,
644
_default_searcher=None):
543
_default_searcher=rules._per_user_searcher):
645
544
"""Find the preferences for filenames in a tree.
647
546
:param path_names: an iterable of paths to find attributes for.
662
559
for path in path_names:
663
560
yield searcher.get_items(path)
665
563
def _get_rules_searcher(self, default_searcher):
666
564
"""Get the RulesSearcher for this tree given the default one."""
667
565
searcher = default_searcher
671
class InventoryTree(Tree):
672
"""A tree that relies on an inventory for its metadata.
674
Trees contain an `Inventory` object, and also know how to retrieve
675
file texts mentioned in the inventory, either from a working
676
directory or from a store.
678
It is possible for trees to contain files that are not described
679
in their inventory or vice versa; for this use `filenames()`.
681
Subclasses should set the _inventory attribute, which is considered
682
private to external API users.
569
class EmptyTree(Tree):
572
self._inventory = Inventory(root_id=None)
573
symbol_versioning.warn('EmptyTree is deprecated as of bzr 0.9 please'
574
' use repository.revision_tree instead.',
575
DeprecationWarning, stacklevel=2)
577
def get_parent_ids(self):
580
def get_symlink_target(self, file_id):
583
def has_filename(self, filename):
586
def kind(self, file_id):
589
def list_files(self, include_root=False):
592
def __contains__(self, file_id):
593
return (file_id in self._inventory)
595
def get_file_sha1(self, file_id, path=None, stat_value=None):
599
######################################################################
602
# TODO: Merge these two functions into a single one that can operate
603
# on either a whole tree or a set of files.
605
# TODO: Return the diff in order by filename, not by category or in
606
# random order. Can probably be done by lock-stepping through the
607
# filenames from both trees.
610
def file_status(filename, old_tree, new_tree):
611
"""Return single-letter status, old and new names for a file.
613
The complexity here is in deciding how to represent renames;
614
many complex cases are possible.
685
def get_canonical_inventory_paths(self, paths):
686
"""Like get_canonical_inventory_path() but works on multiple items.
688
:param paths: A sequence of paths relative to the root of the tree.
689
:return: A list of paths, with each item the corresponding input path
690
adjusted to account for existing elements that match case
693
return list(self._yield_canonical_inventory_paths(paths))
695
def get_canonical_inventory_path(self, path):
696
"""Returns the first inventory item that case-insensitively matches path.
698
If a path matches exactly, it is returned. If no path matches exactly
699
but more than one path matches case-insensitively, it is implementation
700
defined which is returned.
702
If no path matches case-insensitively, the input path is returned, but
703
with as many path entries that do exist changed to their canonical
706
If you need to resolve many names from the same tree, you should
707
use get_canonical_inventory_paths() to avoid O(N) behaviour.
709
:param path: A paths relative to the root of the tree.
710
:return: The input path adjusted to account for existing elements
711
that match case insensitively.
713
return self._yield_canonical_inventory_paths([path]).next()
715
def _yield_canonical_inventory_paths(self, paths):
717
# First, if the path as specified exists exactly, just use it.
718
if self.path2id(path) is not None:
722
cur_id = self.get_root_id()
724
bit_iter = iter(path.split("/"))
728
for child in self.iter_children(cur_id):
730
# XXX: it seem like if the child is known to be in the
731
# tree, we shouldn't need to go from its id back to
732
# its path -- mbp 2010-02-11
734
# XXX: it seems like we could be more efficient
735
# by just directly looking up the original name and
736
# only then searching all children; also by not
737
# chopping paths so much. -- mbp 2010-02-11
738
child_base = os.path.basename(self.id2path(child))
739
if (child_base == elt):
740
# if we found an exact match, we can stop now; if
741
# we found an approximate match we need to keep
742
# searching because there might be an exact match
745
new_path = osutils.pathjoin(cur_path, child_base)
747
elif child_base.lower() == lelt:
749
new_path = osutils.pathjoin(cur_path, child_base)
750
except errors.NoSuchId:
751
# before a change is committed we can see this error...
756
# got to the end of this directory and no entries matched.
757
# Return what matched so far, plus the rest as specified.
758
cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
763
def _get_inventory(self):
764
return self._inventory
766
inventory = property(_get_inventory,
767
doc="Inventory of this Tree")
770
def path2id(self, path):
771
"""Return the id for path in this tree."""
772
return self._inventory.path2id(path)
774
def id2path(self, file_id):
775
"""Return the path for a file id.
779
return self.inventory.id2path(file_id)
781
def has_id(self, file_id):
782
return self.inventory.has_id(file_id)
784
def has_or_had_id(self, file_id):
785
return self.inventory.has_id(file_id)
787
def all_file_ids(self):
788
return set(self.inventory)
790
@deprecated_method(deprecated_in((2, 4, 0)))
792
return iter(self.inventory)
794
def filter_unversioned_files(self, paths):
795
"""Filter out paths that are versioned.
797
:return: set of paths.
799
# NB: we specifically *don't* call self.has_filename, because for
800
# WorkingTrees that can indicate files that exist on disk but that
802
pred = self.inventory.has_filename
803
return set((p for p in paths if not pred(p)))
806
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
807
"""Walk the tree in 'by_dir' order.
809
This will yield each entry in the tree as a (path, entry) tuple.
810
The order that they are yielded is:
812
See Tree.iter_entries_by_dir for details.
814
:param yield_parents: If True, yield the parents from the root leading
815
down to specific_file_ids that have been requested. This has no
816
impact if specific_file_ids is None.
818
return self.inventory.iter_entries_by_dir(
819
specific_file_ids=specific_file_ids, yield_parents=yield_parents)
821
def get_file_by_path(self, path):
822
return self.get_file(self._inventory.path2id(path), path)
616
old_inv = old_tree.inventory
617
new_inv = new_tree.inventory
618
new_id = new_inv.path2id(filename)
619
old_id = old_inv.path2id(filename)
621
if not new_id and not old_id:
622
# easy: doesn't exist in either; not versioned at all
623
if new_tree.is_ignored(filename):
624
return 'I', None, None
626
return '?', None, None
628
# There is now a file of this name, great.
631
# There is no longer a file of this name, but we can describe
632
# what happened to the file that used to have
633
# this name. There are two possibilities: either it was
634
# deleted entirely, or renamed.
635
if new_inv.has_id(old_id):
636
return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
638
return 'D', old_inv.id2path(old_id), None
640
# if the file_id is new in this revision, it is added
641
if new_id and not old_inv.has_id(new_id):
644
# if there used to be a file of this name, but that ID has now
645
# disappeared, it is deleted
646
if old_id and not new_inv.has_id(old_id):
653
def find_renames(old_inv, new_inv):
654
for file_id in old_inv:
655
if file_id not in new_inv:
657
old_name = old_inv.id2path(file_id)
658
new_name = new_inv.id2path(file_id)
659
if old_name != new_name:
660
yield (old_name, new_name)
825
663
def find_ids_across_trees(filenames, trees, require_versioned=True):
826
664
"""Find the ids corresponding to specified filenames.
828
666
All matches in all trees will be used, and all children of matched
829
667
directories will be used.
906
744
will pass through to InterTree as appropriate.
909
# Formats that will be used to test this InterTree. If both are
910
# None, this InterTree will not be tested (e.g. because a complex
912
_matching_from_tree_format = None
913
_matching_to_tree_format = None
918
def is_compatible(kls, source, target):
919
# The default implementation is naive and uses the public API, so
920
# it works for all trees.
923
def _changes_from_entries(self, source_entry, target_entry,
924
source_path=None, target_path=None):
925
"""Generate a iter_changes tuple between source_entry and target_entry.
927
:param source_entry: An inventory entry from self.source, or None.
928
:param target_entry: An inventory entry from self.target, or None.
929
:param source_path: The path of source_entry, if known. If not known
930
it will be looked up.
931
:param target_path: The path of target_entry, if known. If not known
932
it will be looked up.
933
:return: A tuple, item 0 of which is an iter_changes result tuple, and
934
item 1 is True if there are any changes in the result tuple.
936
if source_entry is None:
937
if target_entry is None:
939
file_id = target_entry.file_id
941
file_id = source_entry.file_id
942
if source_entry is not None:
943
source_versioned = True
944
source_name = source_entry.name
945
source_parent = source_entry.parent_id
946
if source_path is None:
947
source_path = self.source.id2path(file_id)
948
source_kind, source_executable, source_stat = \
949
self.source._comparison_data(source_entry, source_path)
951
source_versioned = False
955
source_executable = None
956
if target_entry is not None:
957
target_versioned = True
958
target_name = target_entry.name
959
target_parent = target_entry.parent_id
960
if target_path is None:
961
target_path = self.target.id2path(file_id)
962
target_kind, target_executable, target_stat = \
963
self.target._comparison_data(target_entry, target_path)
965
target_versioned = False
969
target_executable = None
970
versioned = (source_versioned, target_versioned)
971
kind = (source_kind, target_kind)
972
changed_content = False
973
if source_kind != target_kind:
974
changed_content = True
975
elif source_kind == 'file':
976
if not self.file_content_matches(file_id, file_id, source_path,
977
target_path, source_stat, target_stat):
978
changed_content = True
979
elif source_kind == 'symlink':
980
if (self.source.get_symlink_target(file_id) !=
981
self.target.get_symlink_target(file_id)):
982
changed_content = True
983
# XXX: Yes, the indentation below is wrong. But fixing it broke
984
# test_merge.TestMergerEntriesLCAOnDisk.
985
# test_nested_tree_subtree_renamed_and_modified. We'll wait for
986
# the fix from bzr.dev -- vila 2009026
987
elif source_kind == 'tree-reference':
988
if (self.source.get_reference_revision(file_id, source_path)
989
!= self.target.get_reference_revision(file_id, target_path)):
990
changed_content = True
991
parent = (source_parent, target_parent)
992
name = (source_name, target_name)
993
executable = (source_executable, target_executable)
994
if (changed_content is not False or versioned[0] != versioned[1]
995
or parent[0] != parent[1] or name[0] != name[1] or
996
executable[0] != executable[1]):
1000
return (file_id, (source_path, target_path), changed_content,
1001
versioned, parent, name, kind, executable), changes
1003
749
@needs_read_lock
1004
750
def compare(self, want_unchanged=False, specific_files=None,
1005
751
extra_trees=None, require_versioned=False, include_root=False,
1066
814
:param require_versioned: Raise errors.PathsNotVersionedError if a
1067
815
path in the specific_files list is not versioned in one of
1068
816
source, target or extra_trees.
1069
:param specific_files: An optional list of file paths to restrict the
1070
comparison to. When mapping filenames to ids, all matches in all
1071
trees (including optional extra_trees) are used, and all children
1072
of matched directories are included. The parents in the target tree
1073
of the specific files up to and including the root of the tree are
1074
always evaluated for changes too.
1075
817
:param want_unversioned: Should unversioned files be returned in the
1076
818
output. An unversioned file is defined as one with (False, False)
1077
819
for the versioned pair.
1079
822
lookup_trees = [self.source]
1081
824
lookup_trees.extend(extra_trees)
1082
# The ids of items we need to examine to insure delta consistency.
1083
precise_file_ids = set()
1084
changed_file_ids = []
1085
825
if specific_files == []:
1086
826
specific_file_ids = []
1088
828
specific_file_ids = self.target.paths2ids(specific_files,
1089
829
lookup_trees, require_versioned=require_versioned)
1090
if specific_files is not None:
1091
# reparented or added entries must have their parents included
1092
# so that valid deltas can be created. The seen_parents set
1093
# tracks the parents that we need to have.
1094
# The seen_dirs set tracks directory entries we've yielded.
1095
# After outputting version object in to_entries we set difference
1096
# the two seen sets and start checking parents.
1097
seen_parents = set()
1099
830
if want_unversioned:
1100
831
all_unversioned = sorted([(p.split('/'), p) for p in
1101
832
self.target.extras()
1102
833
if specific_files is None or
1103
834
osutils.is_inside_any(specific_files, p)])
1104
all_unversioned = collections.deque(all_unversioned)
835
all_unversioned = deque(all_unversioned)
1106
all_unversioned = collections.deque()
837
all_unversioned = deque()
1108
839
from_entries_by_dir = list(self.source.iter_entries_by_dir(
1109
840
specific_file_ids=specific_file_ids))
1112
843
specific_file_ids=specific_file_ids))
1113
844
num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
1115
# the unversioned path lookup only occurs on real trees - where there
846
# the unversioned path lookup only occurs on real trees - where there
1116
847
# can be extras. So the fake_entry is solely used to look up
1117
848
# executable it values when execute is not supported.
1118
fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
1119
for target_path, target_entry in to_entries_by_dir:
1120
while (all_unversioned and
1121
all_unversioned[0][0] < target_path.split('/')):
849
fake_entry = InventoryFile('unused', 'unused', 'unused')
850
for to_path, to_entry in to_entries_by_dir:
851
while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
1122
852
unversioned_path = all_unversioned.popleft()
1123
target_kind, target_executable, target_stat = \
853
to_kind, to_executable, to_stat = \
1124
854
self.target._comparison_data(fake_entry, unversioned_path[1])
1125
855
yield (None, (None, unversioned_path[1]), True, (False, False),
1127
857
(None, unversioned_path[0][-1]),
1128
(None, target_kind),
1129
(None, target_executable))
1130
source_path, source_entry = from_data.get(target_entry.file_id,
1132
result, changes = self._changes_from_entries(source_entry,
1133
target_entry, source_path=source_path, target_path=target_path)
1134
to_paths[result[0]] = result[1][1]
859
(None, to_executable))
860
file_id = to_entry.file_id
861
to_paths[file_id] = to_path
1135
862
entry_count += 1
863
changed_content = False
864
from_path, from_entry = from_data.get(file_id, (None, None))
865
from_versioned = (from_entry is not None)
866
if from_entry is not None:
867
from_versioned = True
868
from_name = from_entry.name
869
from_parent = from_entry.parent_id
870
from_kind, from_executable, from_stat = \
871
self.source._comparison_data(from_entry, from_path)
1137
872
entry_count += 1
874
from_versioned = False
878
from_executable = None
879
versioned = (from_versioned, True)
880
to_kind, to_executable, to_stat = \
881
self.target._comparison_data(to_entry, to_path)
882
kind = (from_kind, to_kind)
883
if kind[0] != kind[1]:
884
changed_content = True
885
elif from_kind == 'file':
886
from_size = self.source._file_size(from_entry, from_stat)
887
to_size = self.target._file_size(to_entry, to_stat)
888
if from_size != to_size:
889
changed_content = True
890
elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
891
self.target.get_file_sha1(file_id, to_path, to_stat)):
892
changed_content = True
893
elif from_kind == 'symlink':
894
if (self.source.get_symlink_target(file_id) !=
895
self.target.get_symlink_target(file_id)):
896
changed_content = True
897
elif from_kind == 'tree-reference':
898
if (self.source.get_reference_revision(file_id, from_path)
899
!= self.target.get_reference_revision(file_id, to_path)):
900
changed_content = True
901
parent = (from_parent, to_entry.parent_id)
902
name = (from_name, to_entry.name)
903
executable = (from_executable, to_executable)
1138
904
if pb is not None:
1139
905
pb.update('comparing files', entry_count, num_entries)
1140
if changes or include_unchanged:
1141
if specific_file_ids is not None:
1142
new_parent_id = result[4][1]
1143
precise_file_ids.add(new_parent_id)
1144
changed_file_ids.append(result[0])
1146
# Ensure correct behaviour for reparented/added specific files.
1147
if specific_files is not None:
1148
# Record output dirs
1149
if result[6][1] == 'directory':
1150
seen_dirs.add(result[0])
1151
# Record parents of reparented/added entries.
1152
versioned = result[3]
1154
if not versioned[0] or parents[0] != parents[1]:
1155
seen_parents.add(parents[1])
906
if (changed_content is not False or versioned[0] != versioned[1]
907
or parent[0] != parent[1] or name[0] != name[1] or
908
executable[0] != executable[1] or include_unchanged):
909
yield (file_id, (from_path, to_path), changed_content,
910
versioned, parent, name, kind, executable)
1156
912
while all_unversioned:
1157
913
# yield any trailing unversioned paths
1158
914
unversioned_path = all_unversioned.popleft()
1187
955
executable = (from_executable, None)
1188
956
changed_content = from_kind is not None
1189
957
# the parent's path is necessarily known at this point.
1190
changed_file_ids.append(file_id)
1191
958
yield(file_id, (path, to_path), changed_content, versioned, parent,
1192
959
name, kind, executable)
1193
changed_file_ids = set(changed_file_ids)
1194
if specific_file_ids is not None:
1195
for result in self._handle_precise_ids(precise_file_ids,
1199
def _get_entry(self, tree, file_id):
1200
"""Get an inventory entry from a tree, with missing entries as None.
1202
If the tree raises NotImplementedError on accessing .inventory, then
1203
this is worked around using iter_entries_by_dir on just the file id
1206
:param tree: The tree to lookup the entry in.
1207
:param file_id: The file_id to lookup.
1210
inventory = tree.inventory
1211
except NotImplementedError:
1212
# No inventory available.
1214
iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
1215
return iterator.next()[1]
1216
except StopIteration:
1220
return inventory[file_id]
1221
except errors.NoSuchId:
1224
def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
1225
discarded_changes=None):
1226
"""Fill out a partial iter_changes to be consistent.
1228
:param precise_file_ids: The file ids of parents that were seen during
1230
:param changed_file_ids: The file ids of already emitted items.
1231
:param discarded_changes: An optional dict of precalculated
1232
iter_changes items which the partial iter_changes had not output
1234
:return: A generator of iter_changes items to output.
1236
# process parents of things that had changed under the users
1237
# requested paths to prevent incorrect paths or parent ids which
1238
# aren't in the tree.
1239
while precise_file_ids:
1240
precise_file_ids.discard(None)
1241
# Don't emit file_ids twice
1242
precise_file_ids.difference_update(changed_file_ids)
1243
if not precise_file_ids:
1245
# If the there was something at a given output path in source, we
1246
# have to include the entry from source in the delta, or we would
1247
# be putting this entry into a used path.
1249
for parent_id in precise_file_ids:
1251
paths.append(self.target.id2path(parent_id))
1252
except errors.NoSuchId:
1253
# This id has been dragged in from the source by delta
1254
# expansion and isn't present in target at all: we don't
1255
# need to check for path collisions on it.
1258
old_id = self.source.path2id(path)
1259
precise_file_ids.add(old_id)
1260
precise_file_ids.discard(None)
1261
current_ids = precise_file_ids
1262
precise_file_ids = set()
1263
# We have to emit all of precise_file_ids that have been altered.
1264
# We may have to output the children of some of those ids if any
1265
# directories have stopped being directories.
1266
for file_id in current_ids:
1268
if discarded_changes:
1269
result = discarded_changes.get(file_id)
1274
old_entry = self._get_entry(self.source, file_id)
1275
new_entry = self._get_entry(self.target, file_id)
1276
result, changes = self._changes_from_entries(
1277
old_entry, new_entry)
1280
# Get this parents parent to examine.
1281
new_parent_id = result[4][1]
1282
precise_file_ids.add(new_parent_id)
1284
if (result[6][0] == 'directory' and
1285
result[6][1] != 'directory'):
1286
# This stopped being a directory, the old children have
1288
if old_entry is None:
1289
# Reusing a discarded change.
1290
old_entry = self._get_entry(self.source, file_id)
1291
for child in old_entry.children.values():
1292
precise_file_ids.add(child.file_id)
1293
changed_file_ids.add(result[0])
1297
def file_content_matches(self, source_file_id, target_file_id,
1298
source_path=None, target_path=None, source_stat=None, target_stat=None):
1299
"""Check if two files are the same in the source and target trees.
1301
This only checks that the contents of the files are the same,
1302
it does not touch anything else.
1304
:param source_file_id: File id of the file in the source tree
1305
:param target_file_id: File id of the file in the target tree
1306
:param source_path: Path of the file in the source tree
1307
:param target_path: Path of the file in the target tree
1308
:param source_stat: Optional stat value of the file in the source tree
1309
:param target_stat: Optional stat value of the file in the target tree
1310
:return: Boolean indicating whether the files have the same contents
1312
source_verifier_kind, source_verifier_data = self.source.get_file_verifier(
1313
source_file_id, source_path, source_stat)
1314
target_verifier_kind, target_verifier_data = self.target.get_file_verifier(
1315
target_file_id, target_path, target_stat)
1316
if source_verifier_kind == target_verifier_kind:
1317
return (source_verifier_data == target_verifier_data)
1318
# Fall back to SHA1 for now
1319
if source_verifier_kind != "SHA1":
1320
source_sha1 = self.source.get_file_sha1(source_file_id,
1321
source_path, source_stat)
1323
source_sha1 = source_verifier_data
1324
if target_verifier_kind != "SHA1":
1325
target_sha1 = self.target.get_file_sha1(target_file_id,
1326
target_path, target_stat)
1328
target_sha1 = target_verifier_data
1329
return (source_sha1 == target_sha1)
1331
InterTree.register_optimiser(InterTree)
1334
962
class MultiWalker(object):