~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

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