1
# Copyright (C) 2005-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
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
17
"""Tree classes, representing directory at point in time.
20
from __future__ import absolute_import
26
revision as _mod_revision,
29
from .inter import InterObject
36
class FileTimestampUnavailable(errors.BzrError):
38
_fmt = "The filestamp for %(path)s is not available."
42
def __init__(self, path):
46
class MissingNestedTree(errors.BzrError):
48
_fmt = "The nested tree for %(path)s can not be resolved."""
50
def __init__(self, path):
54
class TreeEntry(object):
55
"""An entry that implements the minimum interface used by commands.
60
def __eq__(self, other):
61
# yes, this is ugly, TODO: best practice __eq__ style.
62
return (isinstance(other, TreeEntry)
63
and other.__class__ == self.__class__)
67
def kind_character(self):
70
def is_unmodified(self, other):
71
"""Does this entry reference the same entry?
73
This is mostly the same as __eq__, but returns False
74
for entries without enough information (i.e. revision is None)
79
class TreeDirectory(TreeEntry):
80
"""See TreeEntry. This is a directory in a working tree."""
86
def kind_character(self):
90
class TreeFile(TreeEntry):
91
"""See TreeEntry. This is a regular file in a working tree."""
97
def kind_character(self):
101
class TreeLink(TreeEntry):
102
"""See TreeEntry. This is a symlink in a working tree."""
108
def kind_character(self):
112
class TreeReference(TreeEntry):
113
"""See TreeEntry. This is a reference to a nested tree in a working tree."""
117
kind = 'tree-reference'
119
def kind_character(self):
123
class TreeChange(object):
124
"""Describes the changes between the same item in two different trees."""
126
__slots__ = ['path', 'changed_content', 'versioned',
127
'name', 'kind', 'executable', 'copied']
129
def __init__(self, path, changed_content, versioned,
130
name, kind, executable, copied=False):
132
self.changed_content = changed_content
133
self.versioned = versioned
136
self.executable = executable
140
return "%s%r" % (self.__class__.__name__, self._as_tuple())
143
return (self.path, self.changed_content, self.versioned,
144
self.name, self.kind, self.executable, self.copied)
146
def __eq__(self, other):
147
if isinstance(other, TreeChange):
148
return self._as_tuple() == other._as_tuple()
149
if isinstance(other, tuple):
150
return self._as_tuple() == other
153
def __lt__(self, other):
154
return self._as_tuple() < other._as_tuple()
156
def meta_modified(self):
157
if self.versioned == (True, True):
158
return (self.executable[0] != self.executable[1])
165
None not in self.name and
166
self.path[0] != self.path[1])
168
def is_reparented(self):
169
return os.path.dirname(self.path[0]) != os.path.dirname(self.path[1])
171
def discard_new(self):
172
return self.__class__(
173
(self.path[0], None), self.changed_content,
174
(self.versioned[0], None),
175
(self.name[0], None), (self.kind[0], None),
176
(self.executable[0], None),
181
"""Abstract file tree.
183
There are several subclasses:
185
* `WorkingTree` exists as files on disk editable by the user.
187
* `RevisionTree` is a tree as recorded at some point in the past.
189
Trees can be compared, etc, regardless of whether they are working
190
trees or versioned trees.
193
def supports_rename_tracking(self):
194
"""Whether this tree supports rename tracking.
196
This defaults to True, but some implementations may want to override
201
def has_versioned_directories(self):
202
"""Whether this tree can contain explicitly versioned directories.
204
This defaults to True, but some implementations may want to override
209
def supports_symlinks(self):
210
"""Does this tree support symbolic links?
212
return osutils.has_symlinks()
214
def changes_from(self, other, want_unchanged=False, specific_files=None,
215
extra_trees=None, require_versioned=False, include_root=False,
216
want_unversioned=False):
217
"""Return a TreeDelta of the changes from other to this tree.
219
:param other: A tree to compare with.
220
:param specific_files: An optional list of file paths to restrict the
221
comparison to. When mapping filenames to ids, all matches in all
222
trees (including optional extra_trees) are used, and all children of
223
matched directories are included.
224
:param want_unchanged: An optional boolean requesting the inclusion of
225
unchanged entries in the result.
226
:param extra_trees: An optional list of additional trees to use when
227
mapping the contents of specific_files (paths) to their identities.
228
:param require_versioned: An optional boolean (defaults to False). When
229
supplied and True all the 'specific_files' must be versioned, or
230
a PathsNotVersionedError will be thrown.
231
:param want_unversioned: Scan for unversioned paths.
233
The comparison will be performed by an InterTree object looked up on
236
# Martin observes that Tree.changes_from returns a TreeDelta and this
237
# may confuse people, because the class name of the returned object is
238
# a synonym of the object referenced in the method name.
239
return InterTree.get(other, self).compare(
240
want_unchanged=want_unchanged,
241
specific_files=specific_files,
242
extra_trees=extra_trees,
243
require_versioned=require_versioned,
244
include_root=include_root,
245
want_unversioned=want_unversioned,
248
def iter_changes(self, from_tree, include_unchanged=False,
249
specific_files=None, pb=None, extra_trees=None,
250
require_versioned=True, want_unversioned=False):
251
"""See InterTree.iter_changes"""
252
intertree = InterTree.get(from_tree, self)
253
return intertree.iter_changes(include_unchanged, specific_files, pb,
254
extra_trees, require_versioned,
255
want_unversioned=want_unversioned)
258
"""Get a list of the conflicts in the tree.
260
Each conflict is an instance of breezy.conflicts.Conflict.
262
from . import conflicts as _mod_conflicts
263
return _mod_conflicts.ConflictList()
266
"""For trees that can have unversioned files, return all such paths."""
269
def get_parent_ids(self):
270
"""Get the parent ids for this tree.
272
:return: a list of parent ids. [] is returned to indicate
273
a tree with no parents.
274
:raises: BzrError if the parents are not known.
276
raise NotImplementedError(self.get_parent_ids)
278
def has_filename(self, filename):
279
"""True if the tree has given filename."""
280
raise NotImplementedError(self.has_filename)
282
def is_ignored(self, filename):
283
"""Check whether the filename is ignored by this tree.
285
:param filename: The relative filename within the tree.
286
:return: True if the filename is ignored.
290
def all_file_ids(self):
291
"""Iterate through all file ids, including ids for missing files."""
292
raise NotImplementedError(self.all_file_ids)
294
def all_versioned_paths(self):
295
"""Iterate through all paths, including paths for missing files."""
296
raise NotImplementedError(self.all_versioned_paths)
298
def id2path(self, file_id, recurse='down'):
299
"""Return the path for a file id.
303
raise NotImplementedError(self.id2path)
305
def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
306
"""Walk the tree in 'by_dir' order.
308
This will yield each entry in the tree as a (path, entry) tuple.
309
The order that they are yielded is:
311
Directories are walked in a depth-first lexicographical order,
312
however, whenever a directory is reached, all of its direct child
313
nodes are yielded in lexicographical order before yielding the
316
For example, in the tree::
326
The yield order (ignoring root) would be::
328
a, f, a/b, a/d, a/b/c, a/d/e, f/g
330
If recurse_nested is enabled then nested trees are included as if
331
they were a part of the tree. If is disabled then TreeReference
332
objects (without any children) are yielded.
334
raise NotImplementedError(self.iter_entries_by_dir)
336
def iter_child_entries(self, path):
337
"""Iterate over the children of a directory or tree reference.
339
:param path: Path of the directory
340
:raise NoSuchFile: When the path does not exist
341
:return: Iterator over entries in the directory
343
raise NotImplementedError(self.iter_child_entries)
345
def list_files(self, include_root=False, from_dir=None, recursive=True,
346
recurse_nested=False):
347
"""List all files in this tree.
349
:param include_root: Whether to include the entry for the tree root
350
:param from_dir: Directory under which to list files
351
:param recursive: Whether to list files recursively
352
:param recurse_nested: enter nested trees
353
:return: iterator over tuples of
354
(path, versioned, kind, inventory entry)
356
raise NotImplementedError(self.list_files)
358
def iter_references(self):
359
if self.supports_tree_reference():
360
for path, entry in self.iter_entries_by_dir():
361
if entry.kind == 'tree-reference':
364
def get_containing_nested_tree(self, path):
365
"""Find the nested tree that contains a path.
367
:return: tuple with (nested tree and path inside the nested tree)
369
for nested_path in self.iter_references():
371
if path.startswith(nested_path):
372
nested_tree = self.get_nested_tree(nested_path)
373
return nested_tree, path[len(nested_path):]
377
def get_nested_tree(self, path):
378
"""Open the nested tree at the specified path.
380
:param path: Path from which to resolve tree reference.
381
:return: A Tree object for the nested tree
382
:raise MissingNestedTree: If the nested tree can not be resolved
384
raise NotImplementedError(self.get_nested_tree)
386
def kind(self, path):
387
raise NotImplementedError("Tree subclass %s must implement kind"
388
% self.__class__.__name__)
390
def stored_kind(self, path):
391
"""File kind stored for this path.
393
May not match kind on disk for working trees. Always available
394
for versioned files, even when the file itself is missing.
396
return self.kind(path)
398
def path_content_summary(self, path):
399
"""Get a summary of the information about path.
401
All the attributes returned are for the canonical form, not the
402
convenient form (if content filters are in use.)
404
:param path: A relative path within the tree.
405
:return: A tuple containing kind, size, exec, sha1-or-link.
406
Kind is always present (see tree.kind()).
407
size is present if kind is file and the size of the
408
canonical form can be cheaply determined, None otherwise.
409
exec is None unless kind is file and the platform supports the 'x'
411
sha1-or-link is the link target if kind is symlink, or the sha1 if
412
it can be obtained without reading the file.
414
raise NotImplementedError(self.path_content_summary)
416
def get_reference_revision(self, path, branch=None):
417
raise NotImplementedError("Tree subclass %s must implement "
418
"get_reference_revision"
419
% self.__class__.__name__)
421
def _comparison_data(self, entry, path):
422
"""Return a tuple of kind, executable, stat_value for a file.
424
entry may be None if there is no inventory entry for the file, but
425
path must always be supplied.
427
kind is None if there is no file present (even if an inventory id is
428
present). executable is False for non-file entries.
430
raise NotImplementedError(self._comparison_data)
432
def get_file(self, path):
433
"""Return a file object for the file path in the tree.
435
raise NotImplementedError(self.get_file)
437
def get_file_with_stat(self, path):
438
"""Get a file handle and stat object for path.
440
The default implementation returns (self.get_file, None) for backwards
443
:param path: The path of the file.
444
:return: A tuple (file_handle, stat_value_or_None). If the tree has
445
no stat facility, or need for a stat cache feedback during commit,
446
it may return None for the second element of the tuple.
448
return (self.get_file(path), None)
450
def get_file_text(self, path):
451
"""Return the byte content of a file.
453
:param path: The path of the file.
455
:returns: A single byte string for the whole file.
457
with self.get_file(path) as my_file:
458
return my_file.read()
460
def get_file_lines(self, path):
461
"""Return the content of a file, as lines.
463
:param path: The path of the file.
465
return osutils.split_lines(self.get_file_text(path))
467
def get_file_verifier(self, path, stat_value=None):
468
"""Return a verifier for a file.
470
The default implementation returns a sha1.
472
:param path: The path that this file can be found at.
473
These must point to the same object.
474
:param stat_value: Optional stat value for the object
475
:return: Tuple with verifier name and verifier data
477
return ("SHA1", self.get_file_sha1(path, stat_value=stat_value))
479
def get_file_sha1(self, path, stat_value=None):
480
"""Return the SHA1 file for a file.
482
:note: callers should use get_file_verifier instead
483
where possible, as the underlying repository implementation may
484
have quicker access to a non-sha1 verifier.
486
:param path: The path that this file can be found at.
487
:param stat_value: Optional stat value for the object
489
raise NotImplementedError(self.get_file_sha1)
491
def get_file_mtime(self, path):
492
"""Return the modification time for a file.
494
:param path: The path that this file can be found at.
496
raise NotImplementedError(self.get_file_mtime)
498
def get_file_size(self, path):
499
"""Return the size of a file in bytes.
501
This applies only to regular files. If invoked on directories or
502
symlinks, it will return None.
504
raise NotImplementedError(self.get_file_size)
506
def is_executable(self, path):
507
"""Check if a file is executable.
509
:param path: The path that this file can be found at.
511
raise NotImplementedError(self.is_executable)
513
def iter_files_bytes(self, desired_files):
514
"""Iterate through file contents.
516
Files will not necessarily be returned in the order they occur in
517
desired_files. No specific order is guaranteed.
519
Yields pairs of identifier, bytes_iterator. identifier is an opaque
520
value supplied by the caller as part of desired_files. It should
521
uniquely identify the file version in the caller's context. (Examples:
522
an index number or a TreeTransform trans_id.)
524
bytes_iterator is an iterable of bytestrings for the file. The
525
kind of iterable and length of the bytestrings are unspecified, but for
526
this implementation, it is a tuple containing a single bytestring with
527
the complete text of the file.
529
:param desired_files: a list of (path, identifier) pairs
531
for path, identifier in desired_files:
532
# We wrap the string in a tuple so that we can return an iterable
533
# of bytestrings. (Technically, a bytestring is also an iterable
534
# of bytestrings, but iterating through each character is not
536
cur_file = (self.get_file_text(path),)
537
yield identifier, cur_file
539
def get_symlink_target(self, path):
540
"""Get the target for a given path.
542
It is assumed that the caller already knows that path is referencing
544
:param path: The path of the file.
545
:return: The path the symlink points to.
547
raise NotImplementedError(self.get_symlink_target)
549
def annotate_iter(self, path,
550
default_revision=_mod_revision.CURRENT_REVISION):
551
"""Return an iterator of revision_id, line tuples.
553
For working trees (and mutable trees in general), the special
554
revision_id 'current:' will be used for lines that are new in this
555
tree, e.g. uncommitted changes.
556
:param path: The file to produce an annotated version from
557
:param default_revision: For lines that don't match a basis, mark them
558
with this revision id. Not all implementations will make use of
561
raise NotImplementedError(self.annotate_iter)
563
def path2id(self, path):
564
"""Return the id for path in this tree."""
565
raise NotImplementedError(self.path2id)
567
def is_versioned(self, path):
568
"""Check whether path is versioned.
570
:param path: Path to check
573
return self.path2id(path) is not None
575
def find_related_paths_across_trees(self, paths, trees=[],
576
require_versioned=True):
577
"""Find related paths in tree corresponding to specified filenames in any
580
All matches in all trees will be used, and all children of matched
581
directories will be used.
583
:param paths: The filenames to find related paths for (if None, returns
585
:param trees: The trees to find file_ids within
586
:param require_versioned: if true, all specified filenames must occur in
588
:return: a set of paths for the specified filenames and their children
591
raise NotImplementedError(self.find_related_paths_across_trees)
594
"""Lock this tree for multiple read only operations.
596
:return: A breezy.lock.LogicalLockResult.
598
return lock.LogicalLockResult(self.unlock)
600
def revision_tree(self, revision_id):
601
"""Obtain a revision tree for the revision revision_id.
603
The intention of this method is to allow access to possibly cached
604
tree data. Implementors of this method should raise NoSuchRevision if
605
the tree is not locally available, even if they could obtain the
606
tree via a repository or some other means. Callers are responsible
607
for finding the ultimate source for a revision tree.
609
:param revision_id: The revision_id of the requested tree.
611
:raises: NoSuchRevision if the tree cannot be obtained.
613
raise errors.NoSuchRevisionInTree(self, revision_id)
616
"""What files are present in this tree and unknown.
618
:return: an iterator over the unknown files.
625
def filter_unversioned_files(self, paths):
626
"""Filter out paths that are versioned.
628
:return: set of paths.
630
# NB: we specifically *don't* call self.has_filename, because for
631
# WorkingTrees that can indicate files that exist on disk but that
633
return set(p for p in paths if not self.is_versioned(p))
635
def walkdirs(self, prefix=""):
636
"""Walk the contents of this tree from path down.
638
This yields all the data about the contents of a directory at a time.
639
After each directory has been yielded, if the caller has mutated the
640
list to exclude some directories, they are then not descended into.
642
The data yielded is of the form:
644
[(relpath, basename, kind, lstat, path_from_tree_root,
645
versioned_kind), ...]),
646
- directory-path-from-root is the containing dirs path from /
647
- relpath is the relative path within the subtree being walked.
648
- basename is the basename
649
- kind is the kind of the file now. If unknonwn then the file is not
650
present within the tree - but it may be recorded as versioned. See
652
- lstat is the stat data *if* the file was statted.
653
- path_from_tree_root is the path from the root of the tree.
654
- versioned_kind is the kind of the file as last recorded in the
655
versioning system. If 'unknown' the file is not versioned.
656
One of 'kind' and 'versioned_kind' must not be 'unknown'.
658
:param prefix: Start walking from prefix within the tree rather than
659
at the root. This allows one to walk a subtree but get paths that are
660
relative to a tree rooted higher up.
661
:return: an iterator over the directory data.
663
raise NotImplementedError(self.walkdirs)
665
def supports_content_filtering(self):
668
def _content_filter_stack(self, path=None):
669
"""The stack of content filters for a path if filtering is supported.
671
Readers will be applied in first-to-last order.
672
Writers will be applied in last-to-first order.
673
Either the path or the file-id needs to be provided.
675
:param path: path relative to the root of the tree
677
:return: the list of filters - [] if there are none
679
from . import debug, filters
680
filter_pref_names = filters._get_registered_names()
681
if len(filter_pref_names) == 0:
683
prefs = next(self.iter_search_rules([path], filter_pref_names))
684
stk = filters._get_filter_stack_for(prefs)
685
if 'filters' in debug.debug_flags:
686
trace.note("*** {0} content-filter: {1} => {2!r}").format(path, prefs, stk)
689
def _content_filter_stack_provider(self):
690
"""A function that returns a stack of ContentFilters.
692
The function takes a path (relative to the top of the tree) and a
693
file-id as parameters.
695
:return: None if content filtering is not supported by this tree.
697
if self.supports_content_filtering():
698
return self._content_filter_stack
702
def iter_search_rules(self, path_names, pref_names=None,
703
_default_searcher=None):
704
"""Find the preferences for filenames in a tree.
706
:param path_names: an iterable of paths to find attributes for.
707
Paths are given relative to the root of the tree.
708
:param pref_names: the list of preferences to lookup - None for all
709
:param _default_searcher: private parameter to assist testing - don't use
710
:return: an iterator of tuple sequences, one per path-name.
711
See _RulesSearcher.get_items for details on the tuple sequence.
714
if _default_searcher is None:
715
_default_searcher = rules._per_user_searcher
716
searcher = self._get_rules_searcher(_default_searcher)
717
if searcher is not None:
718
if pref_names is not None:
719
for path in path_names:
720
yield searcher.get_selected_items(path, pref_names)
722
for path in path_names:
723
yield searcher.get_items(path)
725
def _get_rules_searcher(self, default_searcher):
726
"""Get the RulesSearcher for this tree given the default one."""
727
searcher = default_searcher
730
def archive(self, format, name, root='', subdir=None,
732
"""Create an archive of this tree.
734
:param format: Format name (e.g. 'tar')
735
:param name: target file name
736
:param root: Root directory name (or None)
737
:param subdir: Subdirectory to export (or None)
738
:return: Iterator over archive chunks
740
from .archive import create_archive
741
with self.lock_read():
742
return create_archive(format, self, name, root,
743
subdir, force_mtime=force_mtime)
746
def versionable_kind(cls, kind):
747
"""Check if this tree support versioning a specific file kind."""
748
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
750
def preview_transform(self, pb=None):
751
"""Obtain a transform object."""
752
raise NotImplementedError(self.preview_transform)
755
class InterTree(InterObject):
756
"""This class represents operations taking place between two Trees.
758
Its instances have methods like 'compare' and contain references to the
759
source and target trees these operations are to be carried out on.
761
Clients of breezy should not need to use InterTree directly, rather they
762
should use the convenience methods on Tree such as 'Tree.compare()' which
763
will pass through to InterTree as appropriate.
766
# Formats that will be used to test this InterTree. If both are
767
# None, this InterTree will not be tested (e.g. because a complex
769
_matching_from_tree_format = None
770
_matching_to_tree_format = None
775
def is_compatible(kls, source, target):
776
# The default implementation is naive and uses the public API, so
777
# it works for all trees.
780
def compare(self, want_unchanged=False, specific_files=None,
781
extra_trees=None, require_versioned=False, include_root=False,
782
want_unversioned=False):
783
"""Return the changes from source to target.
785
:return: A TreeDelta.
786
:param specific_files: An optional list of file paths to restrict the
787
comparison to. When mapping filenames to ids, all matches in all
788
trees (including optional extra_trees) are used, and all children of
789
matched directories are included.
790
:param want_unchanged: An optional boolean requesting the inclusion of
791
unchanged entries in the result.
792
:param extra_trees: An optional list of additional trees to use when
793
mapping the contents of specific_files (paths) to file_ids.
794
:param require_versioned: An optional boolean (defaults to False). When
795
supplied and True all the 'specific_files' must be versioned, or
796
a PathsNotVersionedError will be thrown.
797
:param want_unversioned: Scan for unversioned paths.
800
trees = (self.source,)
801
if extra_trees is not None:
802
trees = trees + tuple(extra_trees)
803
with self.lock_read():
804
return delta._compare_trees(self.source, self.target, want_unchanged,
805
specific_files, include_root, extra_trees=extra_trees,
806
require_versioned=require_versioned,
807
want_unversioned=want_unversioned)
809
def iter_changes(self, include_unchanged=False,
810
specific_files=None, pb=None, extra_trees=[],
811
require_versioned=True, want_unversioned=False):
812
"""Generate an iterator of changes between trees.
815
(file_id, (path_in_source, path_in_target),
816
changed_content, versioned, parent, name, kind,
819
Changed_content is True if the file's content has changed. This
820
includes changes to its kind, and to a symlink's target.
822
versioned, parent, name, kind, executable are tuples of (from, to).
823
If a file is missing in a tree, its kind is None.
825
Iteration is done in parent-to-child order, relative to the target
828
There is no guarantee that all paths are in sorted order: the
829
requirement to expand the search due to renames may result in children
830
that should be found early being found late in the search, after
831
lexically later results have been returned.
832
:param require_versioned: Raise errors.PathsNotVersionedError if a
833
path in the specific_files list is not versioned in one of
834
source, target or extra_trees.
835
:param specific_files: An optional list of file paths to restrict the
836
comparison to. When mapping filenames to ids, all matches in all
837
trees (including optional extra_trees) are used, and all children
838
of matched directories are included. The parents in the target tree
839
of the specific files up to and including the root of the tree are
840
always evaluated for changes too.
841
:param want_unversioned: Should unversioned files be returned in the
842
output. An unversioned file is defined as one with (False, False)
843
for the versioned pair.
845
raise NotImplementedError(self.iter_changes)
847
def file_content_matches(
848
self, source_path, target_path,
849
source_stat=None, target_stat=None):
850
"""Check if two files are the same in the source and target trees.
852
This only checks that the contents of the files are the same,
853
it does not touch anything else.
855
:param source_path: Path of the file in the source tree
856
:param target_path: Path of the file in the target tree
857
:param source_stat: Optional stat value of the file in the source tree
858
:param target_stat: Optional stat value of the file in the target tree
859
:return: Boolean indicating whether the files have the same contents
861
with self.lock_read():
862
source_verifier_kind, source_verifier_data = (
863
self.source.get_file_verifier(source_path, source_stat))
864
target_verifier_kind, target_verifier_data = (
865
self.target.get_file_verifier(
866
target_path, target_stat))
867
if source_verifier_kind == target_verifier_kind:
868
return (source_verifier_data == target_verifier_data)
869
# Fall back to SHA1 for now
870
if source_verifier_kind != "SHA1":
871
source_sha1 = self.source.get_file_sha1(
872
source_path, source_stat)
874
source_sha1 = source_verifier_data
875
if target_verifier_kind != "SHA1":
876
target_sha1 = self.target.get_file_sha1(
877
target_path, target_stat)
879
target_sha1 = target_verifier_data
880
return (source_sha1 == target_sha1)
882
def find_target_path(self, path, recurse='none'):
883
"""Find target tree path.
885
:param path: Path to search for (exists in source)
886
:return: path in target, or None if there is no equivalent path.
887
:raise NoSuchFile: If the path doesn't exist in source
889
raise NotImplementedError(self.find_target_path)
891
def find_source_path(self, path, recurse='none'):
892
"""Find the source tree path.
894
:param path: Path to search for (exists in target)
895
:return: path in source, or None if there is no equivalent path.
896
:raise NoSuchFile: if the path doesn't exist in target
898
raise NotImplementedError(self.find_source_path)
900
def find_target_paths(self, paths, recurse='none'):
901
"""Find target tree paths.
903
:param paths: Iterable over paths in target to search for
904
:return: Dictionary mapping from source paths to paths in target , or
905
None if there is no equivalent path.
909
ret[path] = self.find_target_path(path, recurse=recurse)
912
def find_source_paths(self, paths, recurse='none'):
913
"""Find source tree paths.
915
:param paths: Iterable over paths in target to search for
916
:return: Dictionary mapping from target paths to paths in source, or
917
None if there is no equivalent path.
921
ret[path] = self.find_source_path(path, recurse=recurse)
925
def find_previous_paths(from_tree, to_tree, paths, recurse='none'):
926
"""Find previous tree paths.
928
:param from_tree: From tree
929
:param to_tree: To tree
930
:param paths: Iterable over paths in from_tree to search for
931
:return: Dictionary mapping from from_tree paths to paths in to_tree, or
932
None if there is no equivalent path.
934
return InterTree.get(to_tree, from_tree).find_source_paths(paths, recurse=recurse)
937
def find_previous_path(from_tree, to_tree, path, recurse='none'):
938
"""Find previous tree path.
940
:param from_tree: From tree
941
:param to_tree: To tree
942
:param path: Path to search for (exists in from_tree)
943
:return: path in to_tree, or None if there is no equivalent path.
944
:raise NoSuchFile: If the path doesn't exist in from_tree
946
return InterTree.get(to_tree, from_tree).find_source_path(
947
path, recurse=recurse)
950
def get_canonical_path(tree, path, normalize):
951
"""Find the canonical path of an item, ignoring case.
953
:param tree: Tree to traverse
954
:param path: Case-insensitive path to look up
955
:param normalize: Function to normalize a filename for comparison
956
:return: The canonical path
960
bit_iter = iter(path.split("/"))
962
lelt = normalize(elt)
965
for child in tree.iter_child_entries(cur_path):
967
if child.name == elt:
968
# if we found an exact match, we can stop now; if
969
# we found an approximate match we need to keep
970
# searching because there might be an exact match
972
new_path = osutils.pathjoin(cur_path, child.name)
974
elif normalize(child.name) == lelt:
975
new_path = osutils.pathjoin(cur_path, child.name)
976
except errors.NoSuchId:
977
# before a change is committed we can see this error...
979
except errors.NotADirectory:
984
# got to the end of this directory and no entries matched.
985
# Return what matched so far, plus the rest as specified.
986
cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))