~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Dmitry Vasiliev
  • Date: 2009-06-29 11:02:31 UTC
  • mto: (4517.1.1 integration2)
  • mto: This revision was merged to the branch mainline in revision 4520.
  • Revision ID: dima@hlabs.spb.ru-20090629110231-tsy00fwr6aud4en3
Optimize configuration for build documentation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tree classes, representing directory at point in time.
18
18
"""
24
24
import bzrlib
25
25
from bzrlib import (
26
26
    conflicts as _mod_conflicts,
 
27
    debug,
27
28
    delta,
 
29
    filters,
28
30
    osutils,
29
31
    revision as _mod_revision,
30
32
    rules,
31
33
    symbol_versioning,
32
34
    )
33
35
from bzrlib.decorators import needs_read_lock
34
 
from bzrlib.errors import BzrError, BzrCheckError
 
36
from bzrlib.errors import BzrError, BzrCheckError, NoSuchId
35
37
from bzrlib import errors
36
38
from bzrlib.inventory import Inventory, InventoryFile
37
39
from bzrlib.inter import InterObject
38
40
from bzrlib.osutils import fingerprint_file
39
41
import bzrlib.revision
 
42
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
40
43
from bzrlib.trace import mutter, note
41
44
 
42
45
 
44
47
    """Abstract file tree.
45
48
 
46
49
    There are several subclasses:
47
 
    
 
50
 
48
51
    * `WorkingTree` exists as files on disk editable by the user.
49
52
 
50
53
    * `RevisionTree` is a tree as recorded at some point in the past.
59
62
    Trees can be compared, etc, regardless of whether they are working
60
63
    trees or versioned trees.
61
64
    """
62
 
    
 
65
 
63
66
    def changes_from(self, other, want_unchanged=False, specific_files=None,
64
67
        extra_trees=None, require_versioned=False, include_root=False,
65
68
        want_unversioned=False):
79
82
            a PathsNotVersionedError will be thrown.
80
83
        :param want_unversioned: Scan for unversioned paths.
81
84
 
82
 
        The comparison will be performed by an InterTree object looked up on 
 
85
        The comparison will be performed by an InterTree object looked up on
83
86
        self and other.
84
87
        """
85
88
        # Martin observes that Tree.changes_from returns a TreeDelta and this
94
97
            want_unversioned=want_unversioned,
95
98
            )
96
99
 
97
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
98
 
    def _iter_changes(self, *args, **kwargs):
99
 
        return self.iter_changes(*args, **kwargs)
100
 
 
101
100
    def iter_changes(self, from_tree, include_unchanged=False,
102
101
                     specific_files=None, pb=None, extra_trees=None,
103
102
                     require_versioned=True, want_unversioned=False):
104
103
        intertree = InterTree.get(from_tree, self)
105
104
        return intertree.iter_changes(include_unchanged, specific_files, pb,
106
105
            extra_trees, require_versioned, want_unversioned=want_unversioned)
107
 
    
 
106
 
108
107
    def conflicts(self):
109
108
        """Get a list of the conflicts in the tree.
110
109
 
117
116
        return []
118
117
 
119
118
    def get_parent_ids(self):
120
 
        """Get the parent ids for this tree. 
 
119
        """Get the parent ids for this tree.
121
120
 
122
121
        :return: a list of parent ids. [] is returned to indicate
123
122
        a tree with no parents.
124
123
        :raises: BzrError if the parents are not known.
125
124
        """
126
125
        raise NotImplementedError(self.get_parent_ids)
127
 
    
 
126
 
128
127
    def has_filename(self, filename):
129
128
        """True if the tree has given filename."""
130
129
        raise NotImplementedError(self.has_filename)
132
131
    def has_id(self, file_id):
133
132
        return self.inventory.has_id(file_id)
134
133
 
135
 
    __contains__ = has_id
 
134
    def __contains__(self, file_id):
 
135
        return self.has_id(file_id)
136
136
 
137
137
    def has_or_had_id(self, file_id):
138
138
        if file_id == self.inventory.root.file_id:
163
163
 
164
164
    def is_control_filename(self, filename):
165
165
        """True if filename is the name of a control file in this tree.
166
 
        
 
166
 
167
167
        :param filename: A filename within the tree. This is a relative path
168
168
        from the root of this tree.
169
169
 
202
202
            specific_file_ids=specific_file_ids)
203
203
 
204
204
    def iter_references(self):
205
 
        for path, entry in self.iter_entries_by_dir():
206
 
            if entry.kind == 'tree-reference':
207
 
                yield path, entry.file_id
 
205
        if self.supports_tree_reference():
 
206
            for path, entry in self.iter_entries_by_dir():
 
207
                if entry.kind == 'tree-reference':
 
208
                    yield path, entry.file_id
208
209
 
209
210
    def kind(self, file_id):
210
211
        raise NotImplementedError("Tree subclass %s must implement kind"
220
221
 
221
222
    def path_content_summary(self, path):
222
223
        """Get a summary of the information about path.
223
 
        
 
224
 
224
225
        :param path: A relative path within the tree.
225
226
        :return: A tuple containing kind, size, exec, sha1-or-link.
226
227
            Kind is always present (see tree.kind()).
253
254
 
254
255
    def _get_inventory(self):
255
256
        return self._inventory
256
 
    
 
257
 
257
258
    def get_file(self, file_id, path=None):
258
259
        """Return a file object for the file file_id in the tree.
259
 
        
 
260
 
260
261
        If both file_id and path are defined, it is implementation defined as
261
262
        to which one is used.
262
263
        """
263
264
        raise NotImplementedError(self.get_file)
264
265
 
 
266
    def get_file_with_stat(self, file_id, path=None):
 
267
        """Get a file handle and stat object for file_id.
 
268
 
 
269
        The default implementation returns (self.get_file, None) for backwards
 
270
        compatibility.
 
271
 
 
272
        :param file_id: The file id to read.
 
273
        :param path: The path of the file, if it is known.
 
274
        :return: A tuple (file_handle, stat_value_or_None). If the tree has
 
275
            no stat facility, or need for a stat cache feedback during commit,
 
276
            it may return None for the second element of the tuple.
 
277
        """
 
278
        return (self.get_file(file_id, path), None)
 
279
 
 
280
    def get_file_text(self, file_id, path=None):
 
281
        """Return the byte content of a file.
 
282
 
 
283
        :param file_id: The file_id of the file.
 
284
        :param path: The path of the file.
 
285
        If both file_id and path are supplied, an implementation may use
 
286
        either one.
 
287
        """
 
288
        my_file = self.get_file(file_id, path)
 
289
        try:
 
290
            return my_file.read()
 
291
        finally:
 
292
            my_file.close()
 
293
 
 
294
    def get_file_lines(self, file_id, path=None):
 
295
        """Return the content of a file, as lines.
 
296
 
 
297
        :param file_id: The file_id of the file.
 
298
        :param path: The path of the file.
 
299
        If both file_id and path are supplied, an implementation may use
 
300
        either one.
 
301
        """
 
302
        return osutils.split_lines(self.get_file_text(file_id, path))
 
303
 
265
304
    def get_file_mtime(self, file_id, path=None):
266
305
        """Return the modification time for a file.
267
306
 
319
358
        """
320
359
        raise NotImplementedError(self.get_symlink_target)
321
360
 
 
361
    def get_canonical_inventory_paths(self, paths):
 
362
        """Like get_canonical_inventory_path() but works on multiple items.
 
363
 
 
364
        :param paths: A sequence of paths relative to the root of the tree.
 
365
        :return: A list of paths, with each item the corresponding input path
 
366
        adjusted to account for existing elements that match case
 
367
        insensitively.
 
368
        """
 
369
        return list(self._yield_canonical_inventory_paths(paths))
 
370
 
 
371
    def get_canonical_inventory_path(self, path):
 
372
        """Returns the first inventory item that case-insensitively matches path.
 
373
 
 
374
        If a path matches exactly, it is returned. If no path matches exactly
 
375
        but more than one path matches case-insensitively, it is implementation
 
376
        defined which is returned.
 
377
 
 
378
        If no path matches case-insensitively, the input path is returned, but
 
379
        with as many path entries that do exist changed to their canonical
 
380
        form.
 
381
 
 
382
        If you need to resolve many names from the same tree, you should
 
383
        use get_canonical_inventory_paths() to avoid O(N) behaviour.
 
384
 
 
385
        :param path: A paths relative to the root of the tree.
 
386
        :return: The input path adjusted to account for existing elements
 
387
        that match case insensitively.
 
388
        """
 
389
        return self._yield_canonical_inventory_paths([path]).next()
 
390
 
 
391
    def _yield_canonical_inventory_paths(self, paths):
 
392
        for path in paths:
 
393
            # First, if the path as specified exists exactly, just use it.
 
394
            if self.path2id(path) is not None:
 
395
                yield path
 
396
                continue
 
397
            # go walkin...
 
398
            cur_id = self.get_root_id()
 
399
            cur_path = ''
 
400
            bit_iter = iter(path.split("/"))
 
401
            for elt in bit_iter:
 
402
                lelt = elt.lower()
 
403
                for child in self.iter_children(cur_id):
 
404
                    try:
 
405
                        child_base = os.path.basename(self.id2path(child))
 
406
                        if child_base.lower() == lelt:
 
407
                            cur_id = child
 
408
                            cur_path = osutils.pathjoin(cur_path, child_base)
 
409
                            break
 
410
                    except NoSuchId:
 
411
                        # before a change is committed we can see this error...
 
412
                        continue
 
413
                else:
 
414
                    # got to the end of this directory and no entries matched.
 
415
                    # Return what matched so far, plus the rest as specified.
 
416
                    cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
 
417
                    break
 
418
            yield cur_path
 
419
        # all done.
 
420
 
322
421
    def get_root_id(self):
323
422
        """Return the file_id for the root of this tree."""
324
423
        raise NotImplementedError(self.get_root_id)
414
513
 
415
514
    def _check_retrieved(self, ie, f):
416
515
        if not __debug__:
417
 
            return  
 
516
            return
418
517
        fp = fingerprint_file(f)
419
518
        f.seek(0)
420
 
        
 
519
 
421
520
        if ie.text_size is not None:
422
521
            if ie.text_size != fp['size']:
423
522
                raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
438
537
 
439
538
    def paths2ids(self, paths, trees=[], require_versioned=True):
440
539
        """Return all the ids that can be reached by walking from paths.
441
 
        
 
540
 
442
541
        Each path is looked up in this tree and any extras provided in
443
542
        trees, and this is repeated recursively: the children in an extra tree
444
543
        of a directory that has been renamed under a provided path in this tree
455
554
        """
456
555
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
457
556
 
458
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_six)
459
 
    def print_file(self, file_id):
460
 
        """Print file with id `file_id` to stdout."""
461
 
        import sys
462
 
        sys.stdout.write(self.get_file_text(file_id))
 
557
    def iter_children(self, file_id):
 
558
        entry = self.iter_entries_by_dir([file_id]).next()[1]
 
559
        for child in getattr(entry, 'children', {}).itervalues():
 
560
            yield child.file_id
463
561
 
464
562
    def lock_read(self):
465
563
        pass
469
567
 
470
568
        The intention of this method is to allow access to possibly cached
471
569
        tree data. Implementors of this method should raise NoSuchRevision if
472
 
        the tree is not locally available, even if they could obtain the 
473
 
        tree via a repository or some other means. Callers are responsible 
 
570
        the tree is not locally available, even if they could obtain the
 
571
        tree via a repository or some other means. Callers are responsible
474
572
        for finding the ultimate source for a revision tree.
475
573
 
476
574
        :param revision_id: The revision_id of the requested tree.
481
579
 
482
580
    def unknowns(self):
483
581
        """What files are present in this tree and unknown.
484
 
        
 
582
 
485
583
        :return: an iterator over the unknown files.
486
584
        """
487
585
        return iter([])
495
593
        :return: set of paths.
496
594
        """
497
595
        # NB: we specifically *don't* call self.has_filename, because for
498
 
        # WorkingTrees that can indicate files that exist on disk but that 
 
596
        # WorkingTrees that can indicate files that exist on disk but that
499
597
        # are not versioned.
500
598
        pred = self.inventory.has_filename
501
599
        return set((p for p in paths if not pred(p)))
506
604
        This yields all the data about the contents of a directory at a time.
507
605
        After each directory has been yielded, if the caller has mutated the
508
606
        list to exclude some directories, they are then not descended into.
509
 
        
 
607
 
510
608
        The data yielded is of the form:
511
609
        ((directory-relpath, directory-path-from-root, directory-fileid),
512
 
        [(relpath, basename, kind, lstat, path_from_tree_root, file_id, 
 
610
        [(relpath, basename, kind, lstat, path_from_tree_root, file_id,
513
611
          versioned_kind), ...]),
514
612
         - directory-relpath is the containing dirs relpath from prefix
515
613
         - directory-path-from-root is the containing dirs path from /
522
620
         - lstat is the stat data *if* the file was statted.
523
621
         - path_from_tree_root is the path from the root of the tree.
524
622
         - file_id is the file_id if the entry is versioned.
525
 
         - versioned_kind is the kind of the file as last recorded in the 
 
623
         - versioned_kind is the kind of the file as last recorded in the
526
624
           versioning system. If 'unknown' the file is not versioned.
527
625
        One of 'kind' and 'versioned_kind' must not be 'unknown'.
528
626
 
533
631
        """
534
632
        raise NotImplementedError(self.walkdirs)
535
633
 
 
634
    def supports_content_filtering(self):
 
635
        return False
 
636
 
 
637
    def _content_filter_stack(self, path=None, file_id=None):
 
638
        """The stack of content filters for a path if filtering is supported.
 
639
 
 
640
        Readers will be applied in first-to-last order.
 
641
        Writers will be applied in last-to-first order.
 
642
        Either the path or the file-id needs to be provided.
 
643
 
 
644
        :param path: path relative to the root of the tree
 
645
            or None if unknown
 
646
        :param file_id: file_id or None if unknown
 
647
        :return: the list of filters - [] if there are none
 
648
        """
 
649
        filter_pref_names = filters._get_registered_names()
 
650
        if len(filter_pref_names) == 0:
 
651
            return []
 
652
        if path is None:
 
653
            path = self.id2path(file_id)
 
654
        prefs = self.iter_search_rules([path], filter_pref_names).next()
 
655
        stk = filters._get_filter_stack_for(prefs)
 
656
        if 'filters' in debug.debug_flags:
 
657
            note("*** %s content-filter: %s => %r" % (path,prefs,stk))
 
658
        return stk
 
659
 
 
660
    def _content_filter_stack_provider(self):
 
661
        """A function that returns a stack of ContentFilters.
 
662
 
 
663
        The function takes a path (relative to the top of the tree) and a
 
664
        file-id as parameters.
 
665
 
 
666
        :return: None if content filtering is not supported by this tree.
 
667
        """
 
668
        if self.supports_content_filtering():
 
669
            return lambda path, file_id: \
 
670
                    self._content_filter_stack(path, file_id)
 
671
        else:
 
672
            return None
 
673
 
536
674
    def iter_search_rules(self, path_names, pref_names=None,
537
 
        _default_searcher=rules._per_user_searcher):
 
675
        _default_searcher=None):
538
676
        """Find the preferences for filenames in a tree.
539
677
 
540
678
        :param path_names: an iterable of paths to find attributes for.
544
682
        :return: an iterator of tuple sequences, one per path-name.
545
683
          See _RulesSearcher.get_items for details on the tuple sequence.
546
684
        """
 
685
        if _default_searcher is None:
 
686
            _default_searcher = rules._per_user_searcher
547
687
        searcher = self._get_rules_searcher(_default_searcher)
548
688
        if searcher is not None:
549
689
            if pref_names is not None:
560
700
        return searcher
561
701
 
562
702
 
563
 
class EmptyTree(Tree):
564
 
 
565
 
    def __init__(self):
566
 
        self._inventory = Inventory(root_id=None)
567
 
        symbol_versioning.warn('EmptyTree is deprecated as of bzr 0.9 please'
568
 
                               ' use repository.revision_tree instead.',
569
 
                               DeprecationWarning, stacklevel=2)
570
 
 
571
 
    def get_parent_ids(self):
572
 
        return []
573
 
 
574
 
    def get_symlink_target(self, file_id):
575
 
        return None
576
 
 
577
 
    def has_filename(self, filename):
578
 
        return False
579
 
 
580
 
    def kind(self, file_id):
581
 
        return "directory"
582
 
 
583
 
    def list_files(self, include_root=False):
584
 
        return iter([])
585
 
    
586
 
    def __contains__(self, file_id):
587
 
        return (file_id in self._inventory)
588
 
 
589
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
590
 
        return None
591
 
 
592
 
 
593
703
######################################################################
594
704
# diff
595
705
 
642
752
 
643
753
    return 'wtf?'
644
754
 
645
 
    
646
755
 
 
756
@deprecated_function(deprecated_in((1, 9, 0)))
647
757
def find_renames(old_inv, new_inv):
648
758
    for file_id in old_inv:
649
759
        if file_id not in new_inv:
652
762
        new_name = new_inv.id2path(file_id)
653
763
        if old_name != new_name:
654
764
            yield (old_name, new_name)
655
 
            
 
765
 
656
766
 
657
767
def find_ids_across_trees(filenames, trees, require_versioned=True):
658
768
    """Find the ids corresponding to specified filenames.
659
 
    
 
769
 
660
770
    All matches in all trees will be used, and all children of matched
661
771
    directories will be used.
662
772
 
676
786
 
677
787
def _find_ids_across_trees(filenames, trees, require_versioned):
678
788
    """Find the ids corresponding to specified filenames.
679
 
    
 
789
 
680
790
    All matches in all trees will be used, but subdirectories are not scanned.
681
791
 
682
792
    :param filenames: The filenames to find file_ids for
703
813
 
704
814
def _find_children_across_trees(specified_ids, trees):
705
815
    """Return a set including specified ids and their children.
706
 
    
 
816
 
707
817
    All matches in all trees will be used.
708
818
 
709
819
    :param trees: The trees to find file_ids within
710
 
    :return: a set containing all specified ids and their children 
 
820
    :return: a set containing all specified ids and their children
711
821
    """
712
822
    interesting_ids = set(specified_ids)
713
823
    pending = interesting_ids
719
829
            for tree in trees:
720
830
                if not tree.has_id(file_id):
721
831
                    continue
722
 
                entry = tree.inventory[file_id]
723
 
                for child in getattr(entry, 'children', {}).itervalues():
724
 
                    if child.file_id not in interesting_ids:
725
 
                        new_pending.add(child.file_id)
 
832
                for child_id in tree.iter_children(file_id):
 
833
                    if child_id not in interesting_ids:
 
834
                        new_pending.add(child_id)
726
835
        interesting_ids.update(new_pending)
727
836
        pending = new_pending
728
837
    return interesting_ids
813
922
            output. An unversioned file is defined as one with (False, False)
814
923
            for the versioned pair.
815
924
        """
816
 
        result = []
817
925
        lookup_trees = [self.source]
818
926
        if extra_trees:
819
927
             lookup_trees.extend(extra_trees)
831
939
        else:
832
940
            all_unversioned = deque()
833
941
        to_paths = {}
834
 
        from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
 
942
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
835
943
            specific_file_ids=specific_file_ids))
836
944
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
837
 
        to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
 
945
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
838
946
            specific_file_ids=specific_file_ids))
839
947
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
840
948
        entry_count = 0
841
 
        # the unversioned path lookup only occurs on real trees - where there 
 
949
        # the unversioned path lookup only occurs on real trees - where there
842
950
        # can be extras. So the fake_entry is solely used to look up
843
951
        # executable it values when execute is not supported.
844
952
        fake_entry = InventoryFile('unused', 'unused', 'unused')
878
986
            if kind[0] != kind[1]:
879
987
                changed_content = True
880
988
            elif from_kind == 'file':
881
 
                from_size = self.source._file_size(from_entry, from_stat)
882
 
                to_size = self.target._file_size(to_entry, to_stat)
883
 
                if from_size != to_size:
884
 
                    changed_content = True
885
 
                elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
 
989
                if (self.source.get_file_sha1(file_id, from_path, from_stat) !=
886
990
                    self.target.get_file_sha1(file_id, to_path, to_stat)):
887
991
                    changed_content = True
888
992
            elif from_kind == 'symlink':
889
993
                if (self.source.get_symlink_target(file_id) !=
890
994
                    self.target.get_symlink_target(file_id)):
891
995
                    changed_content = True
 
996
                # XXX: Yes, the indentation below is wrong. But fixing it broke
 
997
                # test_merge.TestMergerEntriesLCAOnDisk.
 
998
                # test_nested_tree_subtree_renamed_and_modified. We'll wait for
 
999
                # the fix from bzr.dev -- vila 2009026
892
1000
                elif from_kind == 'tree-reference':
893
1001
                    if (self.source.get_reference_revision(file_id, from_path)
894
1002
                        != self.target.get_reference_revision(file_id, to_path)):
895
 
                        changed_content = True 
 
1003
                        changed_content = True
896
1004
            parent = (from_parent, to_entry.parent_id)
897
1005
            name = (from_name, to_entry.name)
898
1006
            executable = (from_executable, to_executable)
899
1007
            if pb is not None:
900
1008
                pb.update('comparing files', entry_count, num_entries)
901
1009
            if (changed_content is not False or versioned[0] != versioned[1]
902
 
                or parent[0] != parent[1] or name[0] != name[1] or 
 
1010
                or parent[0] != parent[1] or name[0] != name[1] or
903
1011
                executable[0] != executable[1] or include_unchanged):
904
1012
                yield (file_id, (from_path, to_path), changed_content,
905
1013
                    versioned, parent, name, kind, executable)
932
1040
            if file_id in to_paths:
933
1041
                # already returned
934
1042
                continue
935
 
            if not file_id in self.target.inventory:
 
1043
            if not file_id in self.target.all_file_ids():
936
1044
                # common case - paths we have not emitted are not present in
937
1045
                # target.
938
1046
                to_path = None
1074
1182
 
1075
1183
    def _walk_master_tree(self):
1076
1184
        """First pass, walk all trees in lock-step.
1077
 
        
 
1185
 
1078
1186
        When we are done, all nodes in the master_tree will have been
1079
1187
        processed. _other_walkers, _other_entries, and _others_extra will be
1080
1188
        set on 'self' for future processing.