1# Copyright (C) 2005-2011 Canonical Ltd 2# 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. 7# 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. 12# 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 16 17"""Tree classes, representing directory at point in time. 18""" 19 20from . import ( 21 errors, 22 lock, 23 osutils, 24 revision as _mod_revision, 25 trace, 26 ) 27from .inter import InterObject 28 29 30class FileTimestampUnavailable(errors.BzrError): 31 32 _fmt = "The filestamp for %(path)s is not available." 33 34 internal_error = True 35 36 def __init__(self, path): 37 self.path = path 38 39 40class MissingNestedTree(errors.BzrError): 41 42 _fmt = "The nested tree for %(path)s can not be resolved.""" 43 44 def __init__(self, path): 45 self.path = path 46 47 48class TreeEntry(object): 49 """An entry that implements the minimum interface used by commands. 50 """ 51 52 __slots__ = [] 53 54 def __eq__(self, other): 55 # yes, this is ugly, TODO: best practice __eq__ style. 56 return (isinstance(other, TreeEntry) 57 and other.__class__ == self.__class__) 58 59 kind = None 60 61 def kind_character(self): 62 return "???" 63 64 def is_unmodified(self, other): 65 """Does this entry reference the same entry? 66 67 This is mostly the same as __eq__, but returns False 68 for entries without enough information (i.e. revision is None) 69 """ 70 return False 71 72 73class TreeDirectory(TreeEntry): 74 """See TreeEntry. This is a directory in a working tree.""" 75 76 __slots__ = [] 77 78 kind = 'directory' 79 80 def kind_character(self): 81 return "/" 82 83 84class TreeFile(TreeEntry): 85 """See TreeEntry. This is a regular file in a working tree.""" 86 87 __slots__ = [] 88 89 kind = 'file' 90 91 def kind_character(self): 92 return '' 93 94 95class TreeLink(TreeEntry): 96 """See TreeEntry. This is a symlink in a working tree.""" 97 98 __slots__ = [] 99 100 kind = 'symlink' 101 102 def kind_character(self): 103 return '' 104 105 106class TreeReference(TreeEntry): 107 """See TreeEntry. This is a reference to a nested tree in a working tree.""" 108 109 __slots__ = [] 110 111 kind = 'tree-reference' 112 113 def kind_character(self): 114 return '+' 115 116 117class TreeChange(object): 118 """Describes the changes between the same item in two different trees.""" 119 120 __slots__ = ['path', 'changed_content', 'versioned', 121 'name', 'kind', 'executable', 'copied'] 122 123 def __init__(self, path, changed_content, versioned, 124 name, kind, executable, copied=False): 125 self.path = path 126 self.changed_content = changed_content 127 self.versioned = versioned 128 self.name = name 129 self.kind = kind 130 self.executable = executable 131 self.copied = copied 132 133 def __repr__(self): 134 return "%s%r" % (self.__class__.__name__, self._as_tuple()) 135 136 def _as_tuple(self): 137 return (self.path, self.changed_content, self.versioned, 138 self.name, self.kind, self.executable, self.copied) 139 140 def __eq__(self, other): 141 if isinstance(other, TreeChange): 142 return self._as_tuple() == other._as_tuple() 143 if isinstance(other, tuple): 144 return self._as_tuple() == other 145 return False 146 147 def __lt__(self, other): 148 return self._as_tuple() < other._as_tuple() 149 150 def meta_modified(self): 151 if self.versioned == (True, True): 152 return (self.executable[0] != self.executable[1]) 153 return False 154 155 @property 156 def renamed(self): 157 return ( 158 not self.copied and 159 None not in self.name and 160 self.path[0] != self.path[1]) 161 162 def is_reparented(self): 163 return os.path.dirname(self.path[0]) != os.path.dirname(self.path[1]) 164 165 def discard_new(self): 166 return self.__class__( 167 (self.path[0], None), self.changed_content, 168 (self.versioned[0], None), 169 (self.name[0], None), (self.kind[0], None), 170 (self.executable[0], None), 171 copied=False) 172 173 174class Tree(object): 175 """Abstract file tree. 176 177 There are several subclasses: 178 179 * `WorkingTree` exists as files on disk editable by the user. 180 181 * `RevisionTree` is a tree as recorded at some point in the past. 182 183 Trees can be compared, etc, regardless of whether they are working 184 trees or versioned trees. 185 """ 186 187 def supports_rename_tracking(self): 188 """Whether this tree supports rename tracking. 189 190 This defaults to True, but some implementations may want to override 191 it. 192 """ 193 return True 194 195 def has_versioned_directories(self): 196 """Whether this tree can contain explicitly versioned directories. 197 198 This defaults to True, but some implementations may want to override 199 it. 200 """ 201 return True 202 203 def supports_symlinks(self): 204 """Does this tree support symbolic links? 205 """ 206 return osutils.has_symlinks() 207 208 def changes_from(self, other, want_unchanged=False, specific_files=None, 209 extra_trees=None, require_versioned=False, include_root=False, 210 want_unversioned=False): 211 """Return a TreeDelta of the changes from other to this tree. 212 213 :param other: A tree to compare with. 214 :param specific_files: An optional list of file paths to restrict the 215 comparison to. When mapping filenames to ids, all matches in all 216 trees (including optional extra_trees) are used, and all children of 217 matched directories are included. 218 :param want_unchanged: An optional boolean requesting the inclusion of 219 unchanged entries in the result. 220 :param extra_trees: An optional list of additional trees to use when 221 mapping the contents of specific_files (paths) to their identities. 222 :param require_versioned: An optional boolean (defaults to False). When 223 supplied and True all the 'specific_files' must be versioned, or 224 a PathsNotVersionedError will be thrown. 225 :param want_unversioned: Scan for unversioned paths. 226 227 The comparison will be performed by an InterTree object looked up on 228 self and other. 229 """ 230 # Martin observes that Tree.changes_from returns a TreeDelta and this 231 # may confuse people, because the class name of the returned object is 232 # a synonym of the object referenced in the method name. 233 return InterTree.get(other, self).compare( 234 want_unchanged=want_unchanged, 235 specific_files=specific_files, 236 extra_trees=extra_trees, 237 require_versioned=require_versioned, 238 include_root=include_root, 239 want_unversioned=want_unversioned, 240 ) 241 242 def iter_changes(self, from_tree, include_unchanged=False, 243 specific_files=None, pb=None, extra_trees=None, 244 require_versioned=True, want_unversioned=False): 245 """See InterTree.iter_changes""" 246 intertree = InterTree.get(from_tree, self) 247 return intertree.iter_changes(include_unchanged, specific_files, pb, 248 extra_trees, require_versioned, 249 want_unversioned=want_unversioned) 250 251 def conflicts(self): 252 """Get a list of the conflicts in the tree. 253 254 Each conflict is an instance of breezy.conflicts.Conflict. 255 """ 256 from . import conflicts as _mod_conflicts 257 return _mod_conflicts.ConflictList() 258 259 def extras(self): 260 """For trees that can have unversioned files, return all such paths.""" 261 return [] 262 263 def get_parent_ids(self): 264 """Get the parent ids for this tree. 265 266 :return: a list of parent ids. [] is returned to indicate 267 a tree with no parents. 268 :raises: BzrError if the parents are not known. 269 """ 270 raise NotImplementedError(self.get_parent_ids) 271 272 def has_filename(self, filename): 273 """True if the tree has given filename.""" 274 raise NotImplementedError(self.has_filename) 275 276 def is_ignored(self, filename): 277 """Check whether the filename is ignored by this tree. 278 279 :param filename: The relative filename within the tree. 280 :return: True if the filename is ignored. 281 """ 282 return False 283 284 def all_file_ids(self): 285 """Iterate through all file ids, including ids for missing files.""" 286 raise NotImplementedError(self.all_file_ids) 287 288 def all_versioned_paths(self): 289 """Iterate through all paths, including paths for missing files.""" 290 raise NotImplementedError(self.all_versioned_paths) 291 292 def id2path(self, file_id, recurse='down'): 293 """Return the path for a file id. 294 295 :raises NoSuchId: 296 """ 297 raise NotImplementedError(self.id2path) 298 299 def iter_entries_by_dir(self, specific_files=None, recurse_nested=False): 300 """Walk the tree in 'by_dir' order. 301 302 This will yield each entry in the tree as a (path, entry) tuple. 303 The order that they are yielded is: 304 305 Directories are walked in a depth-first lexicographical order, 306 however, whenever a directory is reached, all of its direct child 307 nodes are yielded in lexicographical order before yielding the 308 grandchildren. 309 310 For example, in the tree:: 311 312 a/ 313 b/ 314 c 315 d/ 316 e 317 f/ 318 g 319 320 The yield order (ignoring root) would be:: 321 322 a, f, a/b, a/d, a/b/c, a/d/e, f/g 323 324 If recurse_nested is enabled then nested trees are included as if 325 they were a part of the tree. If is disabled then TreeReference 326 objects (without any children) are yielded. 327 """ 328 raise NotImplementedError(self.iter_entries_by_dir) 329 330 def iter_child_entries(self, path): 331 """Iterate over the children of a directory or tree reference. 332 333 :param path: Path of the directory 334 :raise NoSuchFile: When the path does not exist 335 :return: Iterator over entries in the directory 336 """ 337 raise NotImplementedError(self.iter_child_entries) 338 339 def list_files(self, include_root=False, from_dir=None, recursive=True, 340 recurse_nested=False): 341 """List all files in this tree. 342 343 :param include_root: Whether to include the entry for the tree root 344 :param from_dir: Directory under which to list files 345 :param recursive: Whether to list files recursively 346 :param recurse_nested: enter nested trees 347 :return: iterator over tuples of 348 (path, versioned, kind, inventory entry) 349 """ 350 raise NotImplementedError(self.list_files) 351 352 def iter_references(self): 353 if self.supports_tree_reference(): 354 for path, entry in self.iter_entries_by_dir(): 355 if entry.kind == 'tree-reference': 356 yield path 357 358 def get_containing_nested_tree(self, path): 359 """Find the nested tree that contains a path. 360 361 :return: tuple with (nested tree and path inside the nested tree) 362 """ 363 for nested_path in self.iter_references(): 364 nested_path += '/' 365 if path.startswith(nested_path): 366 nested_tree = self.get_nested_tree(nested_path) 367 return nested_tree, path[len(nested_path):] 368 else: 369 return None, None 370 371 def get_nested_tree(self, path): 372 """Open the nested tree at the specified path. 373 374 :param path: Path from which to resolve tree reference. 375 :return: A Tree object for the nested tree 376 :raise MissingNestedTree: If the nested tree can not be resolved 377 """ 378 raise NotImplementedError(self.get_nested_tree) 379 380 def kind(self, path): 381 raise NotImplementedError("Tree subclass %s must implement kind" 382 % self.__class__.__name__) 383 384 def stored_kind(self, path): 385 """File kind stored for this path. 386 387 May not match kind on disk for working trees. Always available 388 for versioned files, even when the file itself is missing. 389 """ 390 return self.kind(path) 391 392 def path_content_summary(self, path): 393 """Get a summary of the information about path. 394 395 All the attributes returned are for the canonical form, not the 396 convenient form (if content filters are in use.) 397 398 :param path: A relative path within the tree. 399 :return: A tuple containing kind, size, exec, sha1-or-link. 400 Kind is always present (see tree.kind()). 401 size is present if kind is file and the size of the 402 canonical form can be cheaply determined, None otherwise. 403 exec is None unless kind is file and the platform supports the 'x' 404 bit. 405 sha1-or-link is the link target if kind is symlink, or the sha1 if 406 it can be obtained without reading the file. 407 """ 408 raise NotImplementedError(self.path_content_summary) 409 410 def get_reference_revision(self, path, branch=None): 411 raise NotImplementedError("Tree subclass %s must implement " 412 "get_reference_revision" 413 % self.__class__.__name__) 414 415 def _comparison_data(self, entry, path): 416 """Return a tuple of kind, executable, stat_value for a file. 417 418 entry may be None if there is no inventory entry for the file, but 419 path must always be supplied. 420 421 kind is None if there is no file present (even if an inventory id is 422 present). executable is False for non-file entries. 423 """ 424 raise NotImplementedError(self._comparison_data) 425 426 def get_file(self, path): 427 """Return a file object for the file path in the tree. 428 """ 429 raise NotImplementedError(self.get_file) 430 431 def get_file_with_stat(self, path): 432 """Get a file handle and stat object for path. 433 434 The default implementation returns (self.get_file, None) for backwards 435 compatibility. 436 437 :param path: The path of the file. 438 :return: A tuple (file_handle, stat_value_or_None). If the tree has 439 no stat facility, or need for a stat cache feedback during commit, 440 it may return None for the second element of the tuple. 441 """ 442 return (self.get_file(path), None) 443 444 def get_file_text(self, path): 445 """Return the byte content of a file. 446 447 :param path: The path of the file. 448 449 :returns: A single byte string for the whole file. 450 """ 451 with self.get_file(path) as my_file: 452 return my_file.read() 453 454 def get_file_lines(self, path): 455 """Return the content of a file, as lines. 456 457 :param path: The path of the file. 458 """ 459 return osutils.split_lines(self.get_file_text(path)) 460 461 def get_file_verifier(self, path, stat_value=None): 462 """Return a verifier for a file. 463 464 The default implementation returns a sha1. 465 466 :param path: The path that this file can be found at. 467 These must point to the same object. 468 :param stat_value: Optional stat value for the object 469 :return: Tuple with verifier name and verifier data 470 """ 471 return ("SHA1", self.get_file_sha1(path, stat_value=stat_value)) 472 473 def get_file_sha1(self, path, stat_value=None): 474 """Return the SHA1 file for a file. 475 476 :note: callers should use get_file_verifier instead 477 where possible, as the underlying repository implementation may 478 have quicker access to a non-sha1 verifier. 479 480 :param path: The path that this file can be found at. 481 :param stat_value: Optional stat value for the object 482 """ 483 raise NotImplementedError(self.get_file_sha1) 484 485 def get_file_mtime(self, path): 486 """Return the modification time for a file. 487 488 :param path: The path that this file can be found at. 489 """ 490 raise NotImplementedError(self.get_file_mtime) 491 492 def get_file_size(self, path): 493 """Return the size of a file in bytes. 494 495 This applies only to regular files. If invoked on directories or 496 symlinks, it will return None. 497 """ 498 raise NotImplementedError(self.get_file_size) 499 500 def is_executable(self, path): 501 """Check if a file is executable. 502 503 :param path: The path that this file can be found at. 504 """ 505 raise NotImplementedError(self.is_executable) 506 507 def iter_files_bytes(self, desired_files): 508 """Iterate through file contents. 509 510 Files will not necessarily be returned in the order they occur in 511 desired_files. No specific order is guaranteed. 512 513 Yields pairs of identifier, bytes_iterator. identifier is an opaque 514 value supplied by the caller as part of desired_files. It should 515 uniquely identify the file version in the caller's context. (Examples: 516 an index number or a TreeTransform trans_id.) 517 518 bytes_iterator is an iterable of bytestrings for the file. The 519 kind of iterable and length of the bytestrings are unspecified, but for 520 this implementation, it is a tuple containing a single bytestring with 521 the complete text of the file. 522 523 :param desired_files: a list of (path, identifier) pairs 524 """ 525 for path, identifier in desired_files: 526 # We wrap the string in a tuple so that we can return an iterable 527 # of bytestrings. (Technically, a bytestring is also an iterable 528 # of bytestrings, but iterating through each character is not 529 # performant.) 530 cur_file = (self.get_file_text(path),) 531 yield identifier, cur_file 532 533 def get_symlink_target(self, path): 534 """Get the target for a given path. 535 536 It is assumed that the caller already knows that path is referencing 537 a symlink. 538 :param path: The path of the file. 539 :return: The path the symlink points to. 540 """ 541 raise NotImplementedError(self.get_symlink_target) 542 543 def annotate_iter(self, path, 544 default_revision=_mod_revision.CURRENT_REVISION): 545 """Return an iterator of revision_id, line tuples. 546 547 For working trees (and mutable trees in general), the special 548 revision_id 'current:' will be used for lines that are new in this 549 tree, e.g. uncommitted changes. 550 :param path: The file to produce an annotated version from 551 :param default_revision: For lines that don't match a basis, mark them 552 with this revision id. Not all implementations will make use of 553 this value. 554 """ 555 raise NotImplementedError(self.annotate_iter) 556 557 def path2id(self, path): 558 """Return the id for path in this tree.""" 559 raise NotImplementedError(self.path2id) 560 561 def is_versioned(self, path): 562 """Check whether path is versioned. 563 564 :param path: Path to check 565 :return: boolean 566 """ 567 return self.path2id(path) is not None 568 569 def find_related_paths_across_trees(self, paths, trees=[], 570 require_versioned=True): 571 """Find related paths in tree corresponding to specified filenames in any 572 of `lookup_trees`. 573 574 All matches in all trees will be used, and all children of matched 575 directories will be used. 576 577 :param paths: The filenames to find related paths for (if None, returns 578 None) 579 :param trees: The trees to find file_ids within 580 :param require_versioned: if true, all specified filenames must occur in 581 at least one tree. 582 :return: a set of paths for the specified filenames and their children 583 in `tree` 584 """ 585 raise NotImplementedError(self.find_related_paths_across_trees) 586 587 def lock_read(self): 588 """Lock this tree for multiple read only operations. 589 590 :return: A breezy.lock.LogicalLockResult. 591 """ 592 return lock.LogicalLockResult(self.unlock) 593 594 def revision_tree(self, revision_id): 595 """Obtain a revision tree for the revision revision_id. 596 597 The intention of this method is to allow access to possibly cached 598 tree data. Implementors of this method should raise NoSuchRevision if 599 the tree is not locally available, even if they could obtain the 600 tree via a repository or some other means. Callers are responsible 601 for finding the ultimate source for a revision tree. 602 603 :param revision_id: The revision_id of the requested tree. 604 :return: A Tree. 605 :raises: NoSuchRevision if the tree cannot be obtained. 606 """ 607 raise errors.NoSuchRevisionInTree(self, revision_id) 608 609 def unknowns(self): 610 """What files are present in this tree and unknown. 611 612 :return: an iterator over the unknown files. 613 """ 614 return iter([]) 615 616 def unlock(self): 617 pass 618 619 def filter_unversioned_files(self, paths): 620 """Filter out paths that are versioned. 621 622 :return: set of paths. 623 """ 624 # NB: we specifically *don't* call self.has_filename, because for 625 # WorkingTrees that can indicate files that exist on disk but that 626 # are not versioned. 627 return set(p for p in paths if not self.is_versioned(p)) 628 629 def walkdirs(self, prefix=""): 630 """Walk the contents of this tree from path down. 631 632 This yields all the data about the contents of a directory at a time. 633 After each directory has been yielded, if the caller has mutated the 634 list to exclude some directories, they are then not descended into. 635 636 The data yielded is of the form: 637 (directory-relpath, 638 [(relpath, basename, kind, lstat, path_from_tree_root, 639 versioned_kind), ...]), 640 - directory-path-from-root is the containing dirs path from / 641 - relpath is the relative path within the subtree being walked. 642 - basename is the basename 643 - kind is the kind of the file now. If unknonwn then the file is not 644 present within the tree - but it may be recorded as versioned. See 645 versioned_kind. 646 - lstat is the stat data *if* the file was statted. 647 - path_from_tree_root is the path from the root of the tree. 648 - versioned_kind is the kind of the file as last recorded in the 649 versioning system. If 'unknown' the file is not versioned. 650 One of 'kind' and 'versioned_kind' must not be 'unknown'. 651 652 :param prefix: Start walking from prefix within the tree rather than 653 at the root. This allows one to walk a subtree but get paths that are 654 relative to a tree rooted higher up. 655 :return: an iterator over the directory data. 656 """ 657 raise NotImplementedError(self.walkdirs) 658 659 def supports_content_filtering(self): 660 return False 661 662 def _content_filter_stack(self, path=None): 663 """The stack of content filters for a path if filtering is supported. 664 665 Readers will be applied in first-to-last order. 666 Writers will be applied in last-to-first order. 667 Either the path or the file-id needs to be provided. 668 669 :param path: path relative to the root of the tree 670 or None if unknown 671 :return: the list of filters - [] if there are none 672 """ 673 from . import debug, filters 674 filter_pref_names = filters._get_registered_names() 675 if len(filter_pref_names) == 0: 676 return [] 677 prefs = next(self.iter_search_rules([path], filter_pref_names)) 678 stk = filters._get_filter_stack_for(prefs) 679 if 'filters' in debug.debug_flags: 680 trace.note("*** {0} content-filter: {1} => {2!r}").format(path, prefs, stk) 681 return stk 682 683 def _content_filter_stack_provider(self): 684 """A function that returns a stack of ContentFilters. 685 686 The function takes a path (relative to the top of the tree) and a 687 file-id as parameters. 688 689 :return: None if content filtering is not supported by this tree. 690 """ 691 if self.supports_content_filtering(): 692 return self._content_filter_stack 693 else: 694 return None 695 696 def iter_search_rules(self, path_names, pref_names=None, 697 _default_searcher=None): 698 """Find the preferences for filenames in a tree. 699 700 :param path_names: an iterable of paths to find attributes for. 701 Paths are given relative to the root of the tree. 702 :param pref_names: the list of preferences to lookup - None for all 703 :param _default_searcher: private parameter to assist testing - don't use 704 :return: an iterator of tuple sequences, one per path-name. 705 See _RulesSearcher.get_items for details on the tuple sequence. 706 """ 707 from . import rules 708 if _default_searcher is None: 709 _default_searcher = rules._per_user_searcher 710 searcher = self._get_rules_searcher(_default_searcher) 711 if searcher is not None: 712 if pref_names is not None: 713 for path in path_names: 714 yield searcher.get_selected_items(path, pref_names) 715 else: 716 for path in path_names: 717 yield searcher.get_items(path) 718 719 def _get_rules_searcher(self, default_searcher): 720 """Get the RulesSearcher for this tree given the default one.""" 721 searcher = default_searcher 722 return searcher 723 724 def archive(self, format, name, root='', subdir=None, 725 force_mtime=None): 726 """Create an archive of this tree. 727 728 :param format: Format name (e.g. 'tar') 729 :param name: target file name 730 :param root: Root directory name (or None) 731 :param subdir: Subdirectory to export (or None) 732 :return: Iterator over archive chunks 733 """ 734 from .archive import create_archive 735 with self.lock_read(): 736 return create_archive(format, self, name, root, 737 subdir, force_mtime=force_mtime) 738 739 @classmethod 740 def versionable_kind(cls, kind): 741 """Check if this tree support versioning a specific file kind.""" 742 return (kind in ('file', 'directory', 'symlink', 'tree-reference')) 743 744 def preview_transform(self, pb=None): 745 """Obtain a transform object.""" 746 raise NotImplementedError(self.preview_transform) 747 748 749class InterTree(InterObject): 750 """This class represents operations taking place between two Trees. 751 752 Its instances have methods like 'compare' and contain references to the 753 source and target trees these operations are to be carried out on. 754 755 Clients of breezy should not need to use InterTree directly, rather they 756 should use the convenience methods on Tree such as 'Tree.compare()' which 757 will pass through to InterTree as appropriate. 758 """ 759 760 # Formats that will be used to test this InterTree. If both are 761 # None, this InterTree will not be tested (e.g. because a complex 762 # setup is required) 763 _matching_from_tree_format = None 764 _matching_to_tree_format = None 765 766 _optimisers = [] 767 768 @classmethod 769 def is_compatible(kls, source, target): 770 # The default implementation is naive and uses the public API, so 771 # it works for all trees. 772 return True 773 774 def compare(self, want_unchanged=False, specific_files=None, 775 extra_trees=None, require_versioned=False, include_root=False, 776 want_unversioned=False): 777 """Return the changes from source to target. 778 779 :return: A TreeDelta. 780 :param specific_files: An optional list of file paths to restrict the 781 comparison to. When mapping filenames to ids, all matches in all 782 trees (including optional extra_trees) are used, and all children of 783 matched directories are included. 784 :param want_unchanged: An optional boolean requesting the inclusion of 785 unchanged entries in the result. 786 :param extra_trees: An optional list of additional trees to use when 787 mapping the contents of specific_files (paths) to file_ids. 788 :param require_versioned: An optional boolean (defaults to False). When 789 supplied and True all the 'specific_files' must be versioned, or 790 a PathsNotVersionedError will be thrown. 791 :param want_unversioned: Scan for unversioned paths. 792 """ 793 from . import delta 794 trees = (self.source,) 795 if extra_trees is not None: 796 trees = trees + tuple(extra_trees) 797 with self.lock_read(): 798 return delta._compare_trees(self.source, self.target, want_unchanged, 799 specific_files, include_root, extra_trees=extra_trees, 800 require_versioned=require_versioned, 801 want_unversioned=want_unversioned) 802 803 def iter_changes(self, include_unchanged=False, 804 specific_files=None, pb=None, extra_trees=[], 805 require_versioned=True, want_unversioned=False): 806 """Generate an iterator of changes between trees. 807 808 A tuple is returned: 809 (file_id, (path_in_source, path_in_target), 810 changed_content, versioned, parent, name, kind, 811 executable) 812 813 Changed_content is True if the file's content has changed. This 814 includes changes to its kind, and to a symlink's target. 815 816 versioned, parent, name, kind, executable are tuples of (from, to). 817 If a file is missing in a tree, its kind is None. 818 819 Iteration is done in parent-to-child order, relative to the target 820 tree. 821 822 There is no guarantee that all paths are in sorted order: the 823 requirement to expand the search due to renames may result in children 824 that should be found early being found late in the search, after 825 lexically later results have been returned. 826 :param require_versioned: Raise errors.PathsNotVersionedError if a 827 path in the specific_files list is not versioned in one of 828 source, target or extra_trees. 829 :param specific_files: An optional list of file paths to restrict the 830 comparison to. When mapping filenames to ids, all matches in all 831 trees (including optional extra_trees) are used, and all children 832 of matched directories are included. The parents in the target tree 833 of the specific files up to and including the root of the tree are 834 always evaluated for changes too. 835 :param want_unversioned: Should unversioned files be returned in the 836 output. An unversioned file is defined as one with (False, False) 837 for the versioned pair. 838 """ 839 raise NotImplementedError(self.iter_changes) 840 841 def file_content_matches( 842 self, source_path, target_path, 843 source_stat=None, target_stat=None): 844 """Check if two files are the same in the source and target trees. 845 846 This only checks that the contents of the files are the same, 847 it does not touch anything else. 848 849 :param source_path: Path of the file in the source tree 850 :param target_path: Path of the file in the target tree 851 :param source_stat: Optional stat value of the file in the source tree 852 :param target_stat: Optional stat value of the file in the target tree 853 :return: Boolean indicating whether the files have the same contents 854 """ 855 with self.lock_read(): 856 source_verifier_kind, source_verifier_data = ( 857 self.source.get_file_verifier(source_path, source_stat)) 858 target_verifier_kind, target_verifier_data = ( 859 self.target.get_file_verifier( 860 target_path, target_stat)) 861 if source_verifier_kind == target_verifier_kind: 862 return (source_verifier_data == target_verifier_data) 863 # Fall back to SHA1 for now 864 if source_verifier_kind != "SHA1": 865 source_sha1 = self.source.get_file_sha1( 866 source_path, source_stat) 867 else: 868 source_sha1 = source_verifier_data 869 if target_verifier_kind != "SHA1": 870 target_sha1 = self.target.get_file_sha1( 871 target_path, target_stat) 872 else: 873 target_sha1 = target_verifier_data 874 return (source_sha1 == target_sha1) 875 876 def find_target_path(self, path, recurse='none'): 877 """Find target tree path. 878 879 :param path: Path to search for (exists in source) 880 :return: path in target, or None if there is no equivalent path. 881 :raise NoSuchFile: If the path doesn't exist in source 882 """ 883 raise NotImplementedError(self.find_target_path) 884 885 def find_source_path(self, path, recurse='none'): 886 """Find the source tree path. 887 888 :param path: Path to search for (exists in target) 889 :return: path in source, or None if there is no equivalent path. 890 :raise NoSuchFile: if the path doesn't exist in target 891 """ 892 raise NotImplementedError(self.find_source_path) 893 894 def find_target_paths(self, paths, recurse='none'): 895 """Find target tree paths. 896 897 :param paths: Iterable over paths in target to search for 898 :return: Dictionary mapping from source paths to paths in target , or 899 None if there is no equivalent path. 900 """ 901 ret = {} 902 for path in paths: 903 ret[path] = self.find_target_path(path, recurse=recurse) 904 return ret 905 906 def find_source_paths(self, paths, recurse='none'): 907 """Find source tree paths. 908 909 :param paths: Iterable over paths in target to search for 910 :return: Dictionary mapping from target paths to paths in source, or 911 None if there is no equivalent path. 912 """ 913 ret = {} 914 for path in paths: 915 ret[path] = self.find_source_path(path, recurse=recurse) 916 return ret 917 918 919def find_previous_paths(from_tree, to_tree, paths, recurse='none'): 920 """Find previous tree paths. 921 922 :param from_tree: From tree 923 :param to_tree: To tree 924 :param paths: Iterable over paths in from_tree to search for 925 :return: Dictionary mapping from from_tree paths to paths in to_tree, or 926 None if there is no equivalent path. 927 """ 928 return InterTree.get(to_tree, from_tree).find_source_paths(paths, recurse=recurse) 929 930 931def find_previous_path(from_tree, to_tree, path, recurse='none'): 932 """Find previous tree path. 933 934 :param from_tree: From tree 935 :param to_tree: To tree 936 :param path: Path to search for (exists in from_tree) 937 :return: path in to_tree, or None if there is no equivalent path. 938 :raise NoSuchFile: If the path doesn't exist in from_tree 939 """ 940 return InterTree.get(to_tree, from_tree).find_source_path( 941 path, recurse=recurse) 942 943 944def get_canonical_path(tree, path, normalize): 945 """Find the canonical path of an item, ignoring case. 946 947 :param tree: Tree to traverse 948 :param path: Case-insensitive path to look up 949 :param normalize: Function to normalize a filename for comparison 950 :return: The canonical path 951 """ 952 # go walkin... 953 cur_path = '' 954 bit_iter = iter(path.split("/")) 955 for elt in bit_iter: 956 lelt = normalize(elt) 957 new_path = None 958 try: 959 for child in tree.iter_child_entries(cur_path): 960 try: 961 if child.name == elt: 962 # if we found an exact match, we can stop now; if 963 # we found an approximate match we need to keep 964 # searching because there might be an exact match 965 # later. 966 new_path = osutils.pathjoin(cur_path, child.name) 967 break 968 elif normalize(child.name) == lelt: 969 new_path = osutils.pathjoin(cur_path, child.name) 970 except errors.NoSuchId: 971 # before a change is committed we can see this error... 972 continue 973 except errors.NotADirectory: 974 pass 975 if new_path: 976 cur_path = new_path 977 else: 978 # got to the end of this directory and no entries matched. 979 # Return what matched so far, plus the rest as specified. 980 cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter)) 981 break 982 return cur_path 983