~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Jelmer Vernooij
  • Date: 2011-04-15 11:26:00 UTC
  • mfrom: (5757.7.7 knitpackrepo-6)
  • mto: This revision was merged to the branch mainline in revision 5801.
  • Revision ID: jelmer@samba.org-20110415112600-vrv2431lh3gi6wx2
MergeĀ knitpackrepo-6.

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
 
294
283
        """
295
284
        return osutils.split_lines(self.get_file_text(file_id, path))
296
285
 
 
286
    def get_file_sha1(self, file_id, path=None):
 
287
        """Return the SHA1 file for a file.
 
288
 
 
289
        :param file_id: The handle for this file.
 
290
        :param path: The path that this file can be found at.
 
291
            These must point to the same object.
 
292
        """
 
293
        raise NotImplementedError(self.get_file_sha1)
 
294
 
297
295
    def get_file_mtime(self, file_id, path=None):
298
296
        """Return the modification time for a file.
299
297
 
313
311
        raise NotImplementedError(self.get_file_size)
314
312
 
315
313
    def get_file_by_path(self, path):
316
 
        return self.get_file(self._inventory.path2id(path), path)
 
314
        raise NotImplementedError(self.get_file_by_path)
 
315
 
 
316
    def is_executable(self, file_id, path=None):
 
317
        """Check if a file is executable.
 
318
 
 
319
        :param file_id: The handle for this file.
 
320
        :param path: The path that this file can be found at.
 
321
            These must point to the same object.
 
322
        """
 
323
        raise NotImplementedError(self.is_executable)
317
324
 
318
325
    def iter_files_bytes(self, desired_files):
319
326
        """Iterate through file contents.
351
358
        """
352
359
        raise NotImplementedError(self.get_symlink_target)
353
360
 
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
361
 
432
362
    def get_root_id(self):
433
363
        """Return the file_id for the root of this tree."""
495
425
    @staticmethod
496
426
    def _file_revision(revision_tree, file_id):
497
427
        """Determine the revision associated with a file in a given tree."""
 
428
        # FIXME: Shouldn't this be a RevisionTree method?
498
429
        revision_tree.lock_read()
499
430
        try:
500
431
            return revision_tree.inventory[file_id].revision
519
450
            vf.fallback_versionedfiles.append(base_vf)
520
451
        return last_revision
521
452
 
522
 
    inventory = property(_get_inventory,
523
 
                         doc="Inventory of this Tree")
524
 
 
525
453
    def _check_retrieved(self, ie, f):
526
454
        if not __debug__:
527
455
            return
544
472
                     "file is actually %s" % fp['sha1'],
545
473
                     "store is probably damaged/corrupt"])
546
474
 
547
 
    @needs_read_lock
548
475
    def path2id(self, path):
549
476
        """Return the id for path in this tree."""
550
 
        return self._inventory.path2id(path)
 
477
        raise NotImplementedError(self.path2id)
551
478
 
552
479
    def paths2ids(self, paths, trees=[], require_versioned=True):
553
480
        """Return all the ids that can be reached by walking from paths.
610
537
 
611
538
        :return: set of paths.
612
539
        """
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)))
 
540
        raise NotImplementedError(self.filter_unversioned_files)
618
541
 
619
542
    def walkdirs(self, prefix=""):
620
543
        """Walk the contents of this tree from path down.
717
640
        return searcher
718
641
 
719
642
 
 
643
class InventoryTree(Tree):
 
644
    """A tree that relies on an inventory for its metadata.
 
645
 
 
646
    Trees contain an `Inventory` object, and also know how to retrieve
 
647
    file texts mentioned in the inventory, either from a working
 
648
    directory or from a store.
 
649
 
 
650
    It is possible for trees to contain files that are not described
 
651
    in their inventory or vice versa; for this use `filenames()`.
 
652
 
 
653
    Subclasses should set the _inventory attribute, which is considered
 
654
    private to external API users.
 
655
    """
 
656
 
 
657
    def get_canonical_inventory_paths(self, paths):
 
658
        """Like get_canonical_inventory_path() but works on multiple items.
 
659
 
 
660
        :param paths: A sequence of paths relative to the root of the tree.
 
661
        :return: A list of paths, with each item the corresponding input path
 
662
        adjusted to account for existing elements that match case
 
663
        insensitively.
 
664
        """
 
665
        return list(self._yield_canonical_inventory_paths(paths))
 
666
 
 
667
    def get_canonical_inventory_path(self, path):
 
668
        """Returns the first inventory item that case-insensitively matches path.
 
669
 
 
670
        If a path matches exactly, it is returned. If no path matches exactly
 
671
        but more than one path matches case-insensitively, it is implementation
 
672
        defined which is returned.
 
673
 
 
674
        If no path matches case-insensitively, the input path is returned, but
 
675
        with as many path entries that do exist changed to their canonical
 
676
        form.
 
677
 
 
678
        If you need to resolve many names from the same tree, you should
 
679
        use get_canonical_inventory_paths() to avoid O(N) behaviour.
 
680
 
 
681
        :param path: A paths relative to the root of the tree.
 
682
        :return: The input path adjusted to account for existing elements
 
683
        that match case insensitively.
 
684
        """
 
685
        return self._yield_canonical_inventory_paths([path]).next()
 
686
 
 
687
    def _yield_canonical_inventory_paths(self, paths):
 
688
        for path in paths:
 
689
            # First, if the path as specified exists exactly, just use it.
 
690
            if self.path2id(path) is not None:
 
691
                yield path
 
692
                continue
 
693
            # go walkin...
 
694
            cur_id = self.get_root_id()
 
695
            cur_path = ''
 
696
            bit_iter = iter(path.split("/"))
 
697
            for elt in bit_iter:
 
698
                lelt = elt.lower()
 
699
                new_path = None
 
700
                for child in self.iter_children(cur_id):
 
701
                    try:
 
702
                        # XXX: it seem like if the child is known to be in the
 
703
                        # tree, we shouldn't need to go from its id back to
 
704
                        # its path -- mbp 2010-02-11
 
705
                        #
 
706
                        # XXX: it seems like we could be more efficient
 
707
                        # by just directly looking up the original name and
 
708
                        # only then searching all children; also by not
 
709
                        # chopping paths so much. -- mbp 2010-02-11
 
710
                        child_base = os.path.basename(self.id2path(child))
 
711
                        if (child_base == elt):
 
712
                            # if we found an exact match, we can stop now; if
 
713
                            # we found an approximate match we need to keep
 
714
                            # searching because there might be an exact match
 
715
                            # later.  
 
716
                            cur_id = child
 
717
                            new_path = osutils.pathjoin(cur_path, child_base)
 
718
                            break
 
719
                        elif child_base.lower() == lelt:
 
720
                            cur_id = child
 
721
                            new_path = osutils.pathjoin(cur_path, child_base)
 
722
                    except errors.NoSuchId:
 
723
                        # before a change is committed we can see this error...
 
724
                        continue
 
725
                if new_path:
 
726
                    cur_path = new_path
 
727
                else:
 
728
                    # got to the end of this directory and no entries matched.
 
729
                    # Return what matched so far, plus the rest as specified.
 
730
                    cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
 
731
                    break
 
732
            yield cur_path
 
733
        # all done.
 
734
 
 
735
    def _get_inventory(self):
 
736
        return self._inventory
 
737
 
 
738
    inventory = property(_get_inventory,
 
739
                         doc="Inventory of this Tree")
 
740
 
 
741
    @needs_read_lock
 
742
    def path2id(self, path):
 
743
        """Return the id for path in this tree."""
 
744
        return self._inventory.path2id(path)
 
745
 
 
746
    def id2path(self, file_id):
 
747
        """Return the path for a file id.
 
748
 
 
749
        :raises NoSuchId:
 
750
        """
 
751
        return self.inventory.id2path(file_id)
 
752
 
 
753
    def has_id(self, file_id):
 
754
        return self.inventory.has_id(file_id)
 
755
 
 
756
    def has_or_had_id(self, file_id):
 
757
        return self.inventory.has_id(file_id)
 
758
 
 
759
    def __iter__(self):
 
760
        return iter(self.inventory)
 
761
 
 
762
    def filter_unversioned_files(self, paths):
 
763
        """Filter out paths that are versioned.
 
764
 
 
765
        :return: set of paths.
 
766
        """
 
767
        # NB: we specifically *don't* call self.has_filename, because for
 
768
        # WorkingTrees that can indicate files that exist on disk but that
 
769
        # are not versioned.
 
770
        pred = self.inventory.has_filename
 
771
        return set((p for p in paths if not pred(p)))
 
772
 
 
773
    @needs_read_lock
 
774
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
775
        """Walk the tree in 'by_dir' order.
 
776
 
 
777
        This will yield each entry in the tree as a (path, entry) tuple.
 
778
        The order that they are yielded is:
 
779
 
 
780
        See Tree.iter_entries_by_dir for details.
 
781
 
 
782
        :param yield_parents: If True, yield the parents from the root leading
 
783
            down to specific_file_ids that have been requested. This has no
 
784
            impact if specific_file_ids is None.
 
785
        """
 
786
        return self.inventory.iter_entries_by_dir(
 
787
            specific_file_ids=specific_file_ids, yield_parents=yield_parents)
 
788
 
 
789
    def get_file_by_path(self, path):
 
790
        return self.get_file(self._inventory.path2id(path), path)
 
791
 
 
792
 
720
793
######################################################################
721
794
# diff
722
795