~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Jelmer Vernooij
  • Date: 2011-04-09 19:25:42 UTC
  • mto: (5777.5.1 inventoryworkingtree)
  • mto: This revision was merged to the branch mainline in revision 5781.
  • Revision ID: jelmer@samba.org-20110409192542-8bbedp36s7nj928e
Split InventoryTree out of Tree.

Show diffs side-by-side

added added

removed removed

Lines of Context:
50
50
 
51
51
    * `RevisionTree` is a tree as recorded at some point in the past.
52
52
 
53
 
    Trees contain an `Inventory` object, and also know how to retrieve
54
 
    file texts mentioned in the inventory, either from a working
55
 
    directory or from a store.
56
 
 
57
 
    It is possible for trees to contain files that are not described
58
 
    in their inventory or vice versa; for this use `filenames()`.
59
 
 
60
53
    Trees can be compared, etc, regardless of whether they are working
61
54
    trees or versioned trees.
62
55
    """
128
121
        raise NotImplementedError(self.has_filename)
129
122
 
130
123
    def has_id(self, file_id):
131
 
        return self.inventory.has_id(file_id)
 
124
        raise NotImplementedError(self.has_id)
132
125
 
133
126
    def __contains__(self, file_id):
134
127
        return self.has_id(file_id)
135
128
 
136
129
    def has_or_had_id(self, file_id):
137
 
        return self.inventory.has_id(file_id)
 
130
        raise NotImplementedError(self.has_or_had_id)
138
131
 
139
132
    def is_ignored(self, filename):
140
133
        """Check whether the filename is ignored by this tree.
145
138
        return False
146
139
 
147
140
    def __iter__(self):
148
 
        return iter(self.inventory)
 
141
        """Yield all file ids in this tree."""
 
142
        raise NotImplementedError(self.__iter__)
149
143
 
150
144
    def all_file_ids(self):
151
145
        """Iterate through all file ids, including ids for missing files."""
156
150
 
157
151
        :raises NoSuchId:
158
152
        """
159
 
        return self.inventory.id2path(file_id)
 
153
        raise NotImplementedError(self.id2path)
160
154
 
161
 
    @needs_read_lock
162
155
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
163
156
        """Walk the tree in 'by_dir' order.
164
157
 
187
180
            down to specific_file_ids that have been requested. This has no
188
181
            impact if specific_file_ids is None.
189
182
        """
190
 
        return self.inventory.iter_entries_by_dir(
191
 
            specific_file_ids=specific_file_ids, yield_parents=yield_parents)
 
183
        raise NotImplementedError(self.iter_entries_by_dir)
192
184
 
193
185
    def iter_references(self):
194
186
        if self.supports_tree_reference():
245
237
    def _file_size(self, entry, stat_value):
246
238
        raise NotImplementedError(self._file_size)
247
239
 
248
 
    def _get_inventory(self):
249
 
        return self._inventory
250
 
 
251
240
    def get_file(self, file_id, path=None):
252
241
        """Return a file object for the file file_id in the tree.
253
242
 
313
302
        raise NotImplementedError(self.get_file_size)
314
303
 
315
304
    def get_file_by_path(self, path):
316
 
        return self.get_file(self._inventory.path2id(path), path)
 
305
        raise NotImplementedError(self.get_file_by_path)
317
306
 
318
307
    def iter_files_bytes(self, desired_files):
319
308
        """Iterate through file contents.
351
340
        """
352
341
        raise NotImplementedError(self.get_symlink_target)
353
342
 
354
 
    def get_canonical_inventory_paths(self, paths):
355
 
        """Like get_canonical_inventory_path() but works on multiple items.
356
 
 
357
 
        :param paths: A sequence of paths relative to the root of the tree.
358
 
        :return: A list of paths, with each item the corresponding input path
359
 
        adjusted to account for existing elements that match case
360
 
        insensitively.
361
 
        """
362
 
        return list(self._yield_canonical_inventory_paths(paths))
363
 
 
364
 
    def get_canonical_inventory_path(self, path):
365
 
        """Returns the first inventory item that case-insensitively matches path.
366
 
 
367
 
        If a path matches exactly, it is returned. If no path matches exactly
368
 
        but more than one path matches case-insensitively, it is implementation
369
 
        defined which is returned.
370
 
 
371
 
        If no path matches case-insensitively, the input path is returned, but
372
 
        with as many path entries that do exist changed to their canonical
373
 
        form.
374
 
 
375
 
        If you need to resolve many names from the same tree, you should
376
 
        use get_canonical_inventory_paths() to avoid O(N) behaviour.
377
 
 
378
 
        :param path: A paths relative to the root of the tree.
379
 
        :return: The input path adjusted to account for existing elements
380
 
        that match case insensitively.
381
 
        """
382
 
        return self._yield_canonical_inventory_paths([path]).next()
383
 
 
384
 
    def _yield_canonical_inventory_paths(self, paths):
385
 
        for path in paths:
386
 
            # First, if the path as specified exists exactly, just use it.
387
 
            if self.path2id(path) is not None:
388
 
                yield path
389
 
                continue
390
 
            # go walkin...
391
 
            cur_id = self.get_root_id()
392
 
            cur_path = ''
393
 
            bit_iter = iter(path.split("/"))
394
 
            for elt in bit_iter:
395
 
                lelt = elt.lower()
396
 
                new_path = None
397
 
                for child in self.iter_children(cur_id):
398
 
                    try:
399
 
                        # XXX: it seem like if the child is known to be in the
400
 
                        # tree, we shouldn't need to go from its id back to
401
 
                        # its path -- mbp 2010-02-11
402
 
                        #
403
 
                        # XXX: it seems like we could be more efficient
404
 
                        # by just directly looking up the original name and
405
 
                        # only then searching all children; also by not
406
 
                        # chopping paths so much. -- mbp 2010-02-11
407
 
                        child_base = os.path.basename(self.id2path(child))
408
 
                        if (child_base == elt):
409
 
                            # if we found an exact match, we can stop now; if
410
 
                            # we found an approximate match we need to keep
411
 
                            # searching because there might be an exact match
412
 
                            # later.  
413
 
                            cur_id = child
414
 
                            new_path = osutils.pathjoin(cur_path, child_base)
415
 
                            break
416
 
                        elif child_base.lower() == lelt:
417
 
                            cur_id = child
418
 
                            new_path = osutils.pathjoin(cur_path, child_base)
419
 
                    except errors.NoSuchId:
420
 
                        # before a change is committed we can see this error...
421
 
                        continue
422
 
                if new_path:
423
 
                    cur_path = new_path
424
 
                else:
425
 
                    # got to the end of this directory and no entries matched.
426
 
                    # Return what matched so far, plus the rest as specified.
427
 
                    cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
428
 
                    break
429
 
            yield cur_path
430
 
        # all done.
431
343
 
432
344
    def get_root_id(self):
433
345
        """Return the file_id for the root of this tree."""
495
407
    @staticmethod
496
408
    def _file_revision(revision_tree, file_id):
497
409
        """Determine the revision associated with a file in a given tree."""
 
410
        # FIXME: Shouldn't this be a RevisionTree method?
498
411
        revision_tree.lock_read()
499
412
        try:
500
413
            return revision_tree.inventory[file_id].revision
519
432
            vf.fallback_versionedfiles.append(base_vf)
520
433
        return last_revision
521
434
 
522
 
    inventory = property(_get_inventory,
523
 
                         doc="Inventory of this Tree")
524
 
 
525
435
    def _check_retrieved(self, ie, f):
526
436
        if not __debug__:
527
437
            return
544
454
                     "file is actually %s" % fp['sha1'],
545
455
                     "store is probably damaged/corrupt"])
546
456
 
547
 
    @needs_read_lock
548
457
    def path2id(self, path):
549
458
        """Return the id for path in this tree."""
550
 
        return self._inventory.path2id(path)
 
459
        raise NotImplementedError(self.path2id)
551
460
 
552
461
    def paths2ids(self, paths, trees=[], require_versioned=True):
553
462
        """Return all the ids that can be reached by walking from paths.
610
519
 
611
520
        :return: set of paths.
612
521
        """
613
 
        # NB: we specifically *don't* call self.has_filename, because for
614
 
        # WorkingTrees that can indicate files that exist on disk but that
615
 
        # are not versioned.
616
 
        pred = self.inventory.has_filename
617
 
        return set((p for p in paths if not pred(p)))
 
522
        raise NotImplementedError(self.filter_unversioned_files)
618
523
 
619
524
    def walkdirs(self, prefix=""):
620
525
        """Walk the contents of this tree from path down.
717
622
        return searcher
718
623
 
719
624
 
 
625
class InventoryTree(Tree):
 
626
    """A tree that relies on an inventory for its metadata.
 
627
 
 
628
    Trees contain an `Inventory` object, and also know how to retrieve
 
629
    file texts mentioned in the inventory, either from a working
 
630
    directory or from a store.
 
631
 
 
632
    It is possible for trees to contain files that are not described
 
633
    in their inventory or vice versa; for this use `filenames()`.
 
634
    """
 
635
 
 
636
    def get_canonical_inventory_paths(self, paths):
 
637
        """Like get_canonical_inventory_path() but works on multiple items.
 
638
 
 
639
        :param paths: A sequence of paths relative to the root of the tree.
 
640
        :return: A list of paths, with each item the corresponding input path
 
641
        adjusted to account for existing elements that match case
 
642
        insensitively.
 
643
        """
 
644
        return list(self._yield_canonical_inventory_paths(paths))
 
645
 
 
646
    def get_canonical_inventory_path(self, path):
 
647
        """Returns the first inventory item that case-insensitively matches path.
 
648
 
 
649
        If a path matches exactly, it is returned. If no path matches exactly
 
650
        but more than one path matches case-insensitively, it is implementation
 
651
        defined which is returned.
 
652
 
 
653
        If no path matches case-insensitively, the input path is returned, but
 
654
        with as many path entries that do exist changed to their canonical
 
655
        form.
 
656
 
 
657
        If you need to resolve many names from the same tree, you should
 
658
        use get_canonical_inventory_paths() to avoid O(N) behaviour.
 
659
 
 
660
        :param path: A paths relative to the root of the tree.
 
661
        :return: The input path adjusted to account for existing elements
 
662
        that match case insensitively.
 
663
        """
 
664
        return self._yield_canonical_inventory_paths([path]).next()
 
665
 
 
666
    def _yield_canonical_inventory_paths(self, paths):
 
667
        for path in paths:
 
668
            # First, if the path as specified exists exactly, just use it.
 
669
            if self.path2id(path) is not None:
 
670
                yield path
 
671
                continue
 
672
            # go walkin...
 
673
            cur_id = self.get_root_id()
 
674
            cur_path = ''
 
675
            bit_iter = iter(path.split("/"))
 
676
            for elt in bit_iter:
 
677
                lelt = elt.lower()
 
678
                new_path = None
 
679
                for child in self.iter_children(cur_id):
 
680
                    try:
 
681
                        # XXX: it seem like if the child is known to be in the
 
682
                        # tree, we shouldn't need to go from its id back to
 
683
                        # its path -- mbp 2010-02-11
 
684
                        #
 
685
                        # XXX: it seems like we could be more efficient
 
686
                        # by just directly looking up the original name and
 
687
                        # only then searching all children; also by not
 
688
                        # chopping paths so much. -- mbp 2010-02-11
 
689
                        child_base = os.path.basename(self.id2path(child))
 
690
                        if (child_base == elt):
 
691
                            # if we found an exact match, we can stop now; if
 
692
                            # we found an approximate match we need to keep
 
693
                            # searching because there might be an exact match
 
694
                            # later.  
 
695
                            cur_id = child
 
696
                            new_path = osutils.pathjoin(cur_path, child_base)
 
697
                            break
 
698
                        elif child_base.lower() == lelt:
 
699
                            cur_id = child
 
700
                            new_path = osutils.pathjoin(cur_path, child_base)
 
701
                    except errors.NoSuchId:
 
702
                        # before a change is committed we can see this error...
 
703
                        continue
 
704
                if new_path:
 
705
                    cur_path = new_path
 
706
                else:
 
707
                    # got to the end of this directory and no entries matched.
 
708
                    # Return what matched so far, plus the rest as specified.
 
709
                    cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
 
710
                    break
 
711
            yield cur_path
 
712
        # all done.
 
713
 
 
714
    def _get_inventory(self):
 
715
        return self._inventory
 
716
 
 
717
    inventory = property(_get_inventory,
 
718
                         doc="Inventory of this Tree")
 
719
 
 
720
    @needs_read_lock
 
721
    def path2id(self, path):
 
722
        """Return the id for path in this tree."""
 
723
        return self._inventory.path2id(path)
 
724
 
 
725
    def id2path(self, file_id):
 
726
        """Return the path for a file id.
 
727
 
 
728
        :raises NoSuchId:
 
729
        """
 
730
        return self.inventory.id2path(file_id)
 
731
 
 
732
    def has_id(self, file_id):
 
733
        return self.inventory.has_id(file_id)
 
734
 
 
735
    def has_or_had_id(self, file_id):
 
736
        return self.inventory.has_id(file_id)
 
737
 
 
738
    def __iter__(self):
 
739
        return iter(self.inventory)
 
740
 
 
741
    def filter_unversioned_files(self, paths):
 
742
        """Filter out paths that are versioned.
 
743
 
 
744
        :return: set of paths.
 
745
        """
 
746
        # NB: we specifically *don't* call self.has_filename, because for
 
747
        # WorkingTrees that can indicate files that exist on disk but that
 
748
        # are not versioned.
 
749
        pred = self.inventory.has_filename
 
750
        return set((p for p in paths if not pred(p)))
 
751
 
 
752
    @needs_read_lock
 
753
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
754
        """Walk the tree in 'by_dir' order.
 
755
 
 
756
        This will yield each entry in the tree as a (path, entry) tuple.
 
757
        The order that they are yielded is:
 
758
 
 
759
        See Tree.iter_entries_by_dir for details.
 
760
 
 
761
        :param yield_parents: If True, yield the parents from the root leading
 
762
            down to specific_file_ids that have been requested. This has no
 
763
            impact if specific_file_ids is None.
 
764
        """
 
765
        return self.inventory.iter_entries_by_dir(
 
766
            specific_file_ids=specific_file_ids, yield_parents=yield_parents)
 
767
 
 
768
    def get_file_by_path(self, path):
 
769
        return self.get_file(self._inventory.path2id(path), path)
 
770
 
 
771
 
720
772
######################################################################
721
773
# diff
722
774