~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Martin Pool
  • Date: 2008-04-30 08:04:11 UTC
  • mto: This revision was merged to the branch mainline in revision 3396.
  • Revision ID: mbp@sourcefrog.net-20080430080411-imrex2wtwpb9eivj
_format_version_tuple can take a 3-tuple

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
"""
19
19
 
20
20
import os
 
21
from collections import deque
21
22
from cStringIO import StringIO
22
23
 
23
24
import bzrlib
24
25
from bzrlib import (
25
26
    delta,
26
27
    osutils,
 
28
    revision as _mod_revision,
 
29
    conflicts as _mod_conflicts,
27
30
    symbol_versioning,
28
31
    )
29
32
from bzrlib.decorators import needs_read_lock
30
33
from bzrlib.errors import BzrError, BzrCheckError
31
34
from bzrlib import errors
32
 
from bzrlib.inventory import Inventory
 
35
from bzrlib.inventory import Inventory, InventoryFile
33
36
from bzrlib.inter import InterObject
34
37
from bzrlib.osutils import fingerprint_file
35
38
import bzrlib.revision
57
60
    """
58
61
    
59
62
    def changes_from(self, other, want_unchanged=False, specific_files=None,
60
 
        extra_trees=None, require_versioned=False, include_root=False):
 
63
        extra_trees=None, require_versioned=False, include_root=False,
 
64
        want_unversioned=False):
61
65
        """Return a TreeDelta of the changes from other to this tree.
62
66
 
63
67
        :param other: A tree to compare with.
72
76
        :param require_versioned: An optional boolean (defaults to False). When
73
77
            supplied and True all the 'specific_files' must be versioned, or
74
78
            a PathsNotVersionedError will be thrown.
 
79
        :param want_unversioned: Scan for unversioned paths.
75
80
 
76
81
        The comparison will be performed by an InterTree object looked up on 
77
82
        self and other.
84
89
            specific_files=specific_files,
85
90
            extra_trees=extra_trees,
86
91
            require_versioned=require_versioned,
87
 
            include_root=include_root
 
92
            include_root=include_root,
 
93
            want_unversioned=want_unversioned,
88
94
            )
89
95
 
90
 
    def _iter_changes(self, from_tree, include_unchanged=False, 
91
 
                     specific_file_ids=None, pb=None):
 
96
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
 
97
    def _iter_changes(self, *args, **kwargs):
 
98
        return self.iter_changes(*args, **kwargs)
 
99
 
 
100
    def iter_changes(self, from_tree, include_unchanged=False,
 
101
                     specific_files=None, pb=None, extra_trees=None,
 
102
                     require_versioned=True, want_unversioned=False):
92
103
        intertree = InterTree.get(from_tree, self)
93
 
        return intertree._iter_changes(from_tree, self, include_unchanged, 
94
 
                                       specific_file_ids, pb)
 
104
        return intertree.iter_changes(include_unchanged, specific_files, pb,
 
105
            extra_trees, require_versioned, want_unversioned=want_unversioned)
95
106
    
96
107
    def conflicts(self):
97
108
        """Get a list of the conflicts in the tree.
98
109
 
99
110
        Each conflict is an instance of bzrlib.conflicts.Conflict.
100
111
        """
 
112
        return _mod_conflicts.ConflictList()
 
113
 
 
114
    def extras(self):
 
115
        """For trees that can have unversioned files, return all such paths."""
101
116
        return []
102
117
 
103
118
    def get_parent_ids(self):
111
126
    
112
127
    def has_filename(self, filename):
113
128
        """True if the tree has given filename."""
114
 
        raise NotImplementedError()
 
129
        raise NotImplementedError(self.has_filename)
115
130
 
116
131
    def has_id(self, file_id):
117
132
        return self.inventory.has_id(file_id)
123
138
            return True
124
139
        return self.inventory.has_id(file_id)
125
140
 
 
141
    def is_ignored(self, filename):
 
142
        """Check whether the filename is ignored by this tree.
 
143
 
 
144
        :param filename: The relative filename within the tree.
 
145
        :return: True if the filename is ignored.
 
146
        """
 
147
        return False
 
148
 
126
149
    def __iter__(self):
127
150
        return iter(self.inventory)
128
151
 
 
152
    def all_file_ids(self):
 
153
        """Iterate through all file ids, including ids for missing files."""
 
154
        return set(self.inventory)
 
155
 
129
156
    def id2path(self, file_id):
 
157
        """Return the path for a file id.
 
158
 
 
159
        :raises NoSuchId:
 
160
        """
130
161
        return self.inventory.id2path(file_id)
131
162
 
132
163
    def is_control_filename(self, filename):
153
184
        return self.inventory.iter_entries_by_dir(
154
185
            specific_file_ids=specific_file_ids)
155
186
 
 
187
    def iter_references(self):
 
188
        for path, entry in self.iter_entries_by_dir():
 
189
            if entry.kind == 'tree-reference':
 
190
                yield path, entry.file_id
 
191
 
156
192
    def kind(self, file_id):
157
 
        raise NotImplementedError("subclasses must implement kind")
 
193
        raise NotImplementedError("Tree subclass %s must implement kind"
 
194
            % self.__class__.__name__)
 
195
 
 
196
    def stored_kind(self, file_id):
 
197
        """File kind stored for this file_id.
 
198
 
 
199
        May not match kind on disk for working trees.  Always available
 
200
        for versioned files, even when the file itself is missing.
 
201
        """
 
202
        return self.kind(file_id)
 
203
 
 
204
    def path_content_summary(self, path):
 
205
        """Get a summary of the information about path.
 
206
        
 
207
        :param path: A relative path within the tree.
 
208
        :return: A tuple containing kind, size, exec, sha1-or-link.
 
209
            Kind is always present (see tree.kind()).
 
210
            size is present if kind is file, None otherwise.
 
211
            exec is None unless kind is file and the platform supports the 'x'
 
212
                bit.
 
213
            sha1-or-link is the link target if kind is symlink, or the sha1 if
 
214
                it can be obtained without reading the file.
 
215
        """
 
216
        raise NotImplementedError(self.path_content_summary)
 
217
 
 
218
    def get_reference_revision(self, file_id, path=None):
 
219
        raise NotImplementedError("Tree subclass %s must implement "
 
220
                                  "get_reference_revision"
 
221
            % self.__class__.__name__)
158
222
 
159
223
    def _comparison_data(self, entry, path):
160
224
        """Return a tuple of kind, executable, stat_value for a file.
173
237
    def _get_inventory(self):
174
238
        return self._inventory
175
239
    
176
 
    def get_file(self, file_id):
177
 
        """Return a file object for the file file_id in the tree."""
 
240
    def get_file(self, file_id, path=None):
 
241
        """Return a file object for the file file_id in the tree.
 
242
        
 
243
        If both file_id and path are defined, it is implementation defined as
 
244
        to which one is used.
 
245
        """
178
246
        raise NotImplementedError(self.get_file)
179
 
    
 
247
 
 
248
    def get_file_mtime(self, file_id, path=None):
 
249
        """Return the modification time for a file.
 
250
 
 
251
        :param file_id: The handle for this file.
 
252
        :param path: The path that this file can be found at.
 
253
            These must point to the same object.
 
254
        """
 
255
        raise NotImplementedError(self.get_file_mtime)
 
256
 
 
257
    def get_file_size(self, file_id):
 
258
        """Return the size of a file in bytes.
 
259
 
 
260
        This applies only to regular files.  If invoked on directories or
 
261
        symlinks, it will return None.
 
262
        :param file_id: The file-id of the file
 
263
        """
 
264
        raise NotImplementedError(self.get_file_size)
 
265
 
180
266
    def get_file_by_path(self, path):
181
 
        return self.get_file(self._inventory.path2id(path))
182
 
 
183
 
    def annotate_iter(self, file_id):
184
 
        """Return an iterator of revision_id, line tuples
 
267
        return self.get_file(self._inventory.path2id(path), path)
 
268
 
 
269
    def iter_files_bytes(self, desired_files):
 
270
        """Iterate through file contents.
 
271
 
 
272
        Files will not necessarily be returned in the order they occur in
 
273
        desired_files.  No specific order is guaranteed.
 
274
 
 
275
        Yields pairs of identifier, bytes_iterator.  identifier is an opaque
 
276
        value supplied by the caller as part of desired_files.  It should
 
277
        uniquely identify the file version in the caller's context.  (Examples:
 
278
        an index number or a TreeTransform trans_id.)
 
279
 
 
280
        bytes_iterator is an iterable of bytestrings for the file.  The
 
281
        kind of iterable and length of the bytestrings are unspecified, but for
 
282
        this implementation, it is a tuple containing a single bytestring with
 
283
        the complete text of the file.
 
284
 
 
285
        :param desired_files: a list of (file_id, identifier) pairs
 
286
        """
 
287
        for file_id, identifier in desired_files:
 
288
            # We wrap the string in a tuple so that we can return an iterable
 
289
            # of bytestrings.  (Technically, a bytestring is also an iterable
 
290
            # of bytestrings, but iterating through each character is not
 
291
            # performant.)
 
292
            cur_file = (self.get_file_text(file_id),)
 
293
            yield identifier, cur_file
 
294
 
 
295
    def get_symlink_target(self, file_id):
 
296
        """Get the target for a given file_id.
 
297
 
 
298
        It is assumed that the caller already knows that file_id is referencing
 
299
        a symlink.
 
300
        :param file_id: Handle for the symlink entry.
 
301
        :return: The path the symlink points to.
 
302
        """
 
303
        raise NotImplementedError(self.get_symlink_target)
 
304
 
 
305
    def get_root_id(self):
 
306
        """Return the file_id for the root of this tree."""
 
307
        raise NotImplementedError(self.get_root_id)
 
308
 
 
309
    def annotate_iter(self, file_id,
 
310
                      default_revision=_mod_revision.CURRENT_REVISION):
 
311
        """Return an iterator of revision_id, line tuples.
185
312
 
186
313
        For working trees (and mutable trees in general), the special
187
314
        revision_id 'current:' will be used for lines that are new in this
188
315
        tree, e.g. uncommitted changes.
189
316
        :param file_id: The file to produce an annotated version from
 
317
        :param default_revision: For lines that don't match a basis, mark them
 
318
            with this revision id. Not all implementations will make use of
 
319
            this value.
190
320
        """
191
321
        raise NotImplementedError(self.annotate_iter)
192
322
 
 
323
    def _get_plan_merge_data(self, file_id, other, base):
 
324
        from bzrlib import merge, versionedfile
 
325
        vf = versionedfile._PlanMergeVersionedFile(file_id)
 
326
        last_revision_a = self._get_file_revision(file_id, vf, 'this:')
 
327
        last_revision_b = other._get_file_revision(file_id, vf, 'other:')
 
328
        if base is None:
 
329
            last_revision_base = None
 
330
        else:
 
331
            last_revision_base = base._get_file_revision(file_id, vf, 'base:')
 
332
        return vf, last_revision_a, last_revision_b, last_revision_base
 
333
 
 
334
    def plan_file_merge(self, file_id, other, base=None):
 
335
        """Generate a merge plan based on annotations.
 
336
 
 
337
        If the file contains uncommitted changes in this tree, they will be
 
338
        attributed to the 'current:' pseudo-revision.  If the file contains
 
339
        uncommitted changes in the other tree, they will be assigned to the
 
340
        'other:' pseudo-revision.
 
341
        """
 
342
        data = self._get_plan_merge_data(file_id, other, base)
 
343
        vf, last_revision_a, last_revision_b, last_revision_base = data
 
344
        return vf.plan_merge(last_revision_a, last_revision_b,
 
345
                             last_revision_base)
 
346
 
 
347
    def plan_file_lca_merge(self, file_id, other, base=None):
 
348
        """Generate a merge plan based lca-newness.
 
349
 
 
350
        If the file contains uncommitted changes in this tree, they will be
 
351
        attributed to the 'current:' pseudo-revision.  If the file contains
 
352
        uncommitted changes in the other tree, they will be assigned to the
 
353
        'other:' pseudo-revision.
 
354
        """
 
355
        data = self._get_plan_merge_data(file_id, other, base)
 
356
        vf, last_revision_a, last_revision_b, last_revision_base = data
 
357
        return vf.plan_lca_merge(last_revision_a, last_revision_b,
 
358
                                 last_revision_base)
 
359
 
 
360
    def _get_file_revision(self, file_id, vf, tree_revision):
 
361
        def file_revision(revision_tree):
 
362
            revision_tree.lock_read()
 
363
            try:
 
364
                return revision_tree.inventory[file_id].revision
 
365
            finally:
 
366
                revision_tree.unlock()
 
367
 
 
368
        def iter_parent_trees():
 
369
            for revision_id in self.get_parent_ids():
 
370
                try:
 
371
                    yield self.revision_tree(revision_id)
 
372
                except:
 
373
                    yield self.repository.revision_tree(revision_id)
 
374
 
 
375
        if getattr(self, '_get_weave', None) is None:
 
376
            last_revision = tree_revision
 
377
            parent_revisions = [file_revision(t) for t in iter_parent_trees()]
 
378
            vf.add_lines(last_revision, parent_revisions,
 
379
                         self.get_file(file_id).readlines())
 
380
            repo = self.branch.repository
 
381
            transaction = repo.get_transaction()
 
382
            base_vf = repo.weave_store.get_weave(file_id, transaction)
 
383
        else:
 
384
            last_revision = file_revision(self)
 
385
            base_vf = self._get_weave(file_id)
 
386
        vf.fallback_versionedfiles.append(base_vf)
 
387
        return last_revision
 
388
 
193
389
    inventory = property(_get_inventory,
194
390
                         doc="Inventory of this Tree")
195
391
 
212
408
                     "file is actually %s" % fp['sha1'],
213
409
                     "store is probably damaged/corrupt"])
214
410
 
 
411
    @needs_read_lock
215
412
    def path2id(self, path):
216
413
        """Return the id for path in this tree."""
217
414
        return self._inventory.path2id(path)
218
415
 
 
416
    def paths2ids(self, paths, trees=[], require_versioned=True):
 
417
        """Return all the ids that can be reached by walking from paths.
 
418
        
 
419
        Each path is looked up in this tree and any extras provided in
 
420
        trees, and this is repeated recursively: the children in an extra tree
 
421
        of a directory that has been renamed under a provided path in this tree
 
422
        are all returned, even if none exist under a provided path in this
 
423
        tree, and vice versa.
 
424
 
 
425
        :param paths: An iterable of paths to start converting to ids from.
 
426
            Alternatively, if paths is None, no ids should be calculated and None
 
427
            will be returned. This is offered to make calling the api unconditional
 
428
            for code that *might* take a list of files.
 
429
        :param trees: Additional trees to consider.
 
430
        :param require_versioned: If False, do not raise NotVersionedError if
 
431
            an element of paths is not versioned in this tree and all of trees.
 
432
        """
 
433
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
 
434
 
219
435
    def print_file(self, file_id):
220
436
        """Print file with id `file_id` to stdout."""
221
437
        import sys
250
466
        pass
251
467
 
252
468
    def filter_unversioned_files(self, paths):
253
 
        """Filter out paths that are not versioned.
 
469
        """Filter out paths that are versioned.
254
470
 
255
471
        :return: set of paths.
256
472
        """
281
497
           versioned_kind.
282
498
         - lstat is the stat data *if* the file was statted.
283
499
         - path_from_tree_root is the path from the root of the tree.
284
 
         - file_id is the file_id is the entry is versioned.
 
500
         - file_id is the file_id if the entry is versioned.
285
501
         - versioned_kind is the kind of the file as last recorded in the 
286
502
           versioning system. If 'unknown' the file is not versioned.
287
503
        One of 'kind' and 'versioned_kind' must not be 'unknown'.
408
624
    specified_path_ids = _find_ids_across_trees(filenames, trees,
409
625
        require_versioned)
410
626
    return _find_children_across_trees(specified_path_ids, trees)
411
 
#    specified_ids = [id for path, id in _find_path_ids_across_trees(filenames, trees, require_versioned)]
412
 
#    return _find_children_across_trees(specified_ids, trees)
413
 
 
414
 
def find_path_ids_across_trees(filenames, trees, require_versioned=True):
415
 
    """Find the paths and ids corresponding to specified filenames.
416
 
    
417
 
    All matches in all trees will be used, and all children of matched
418
 
    directories will be included
419
 
 
420
 
    :param filenames: The filenames to find file_ids for
421
 
    :param trees: The trees to find file_ids within
422
 
    :param require_versioned: if true, all specified filenames must occur in
423
 
        at least one tree.
424
 
    :return: a set of (path, file ids) for the specified filenames and their
425
 
        children. The returned path is the path of the id in the first tree
426
 
        that contains it. This matters when files have been moved 
427
 
    """
428
 
    if not filenames:
429
 
        return set()
430
 
    # This function needs to know the ids for filenames in all trees, then
431
 
    # search for those same files and children in all the other trees.
432
 
    # it is complicated by the same path in two trees being able to have
433
 
    # different ids, which might both be present in both trees.
434
 
    # consider two trees, which have had 'mv foo bar' and 'mv baz foo' done
435
 
    # in this case, a diff of 'foo' should should changes to both the current
436
 
    # 'bar' and the current 'foo' which was baz. Its arguable that if 
437
 
    # the situation is 'mv parent/foo bar' and 'mv baz parent/foo', that 
438
 
    # we should return the current bar and the current parent/foo' - at the 
439
 
    # moment we do, but we loop around all ids and all trees: I*T checks.
440
 
    
441
 
    # Updating this algorithm to be fast in the common case:
442
 
    # nothing has moved, all files have the same id in parent, child and there
443
 
    # are only two trees (or one is working tree and the others are parents).
444
 
    # walk the dirstate. as we find each path, gather the paths of that
445
 
    # id in all trees. add a mapping from the id to the path in those trees.
446
 
    # now lookup children by id, again in all trees; for these trees that
447
 
    # nothing has moved in, the id->path mapping will allow us to find the
448
 
    # parent trivially. To answer 'has anything been moved' in one of the
449
 
    # dirstate parent trees though, we will need to stare harder at it.
450
 
 
451
 
    #  Now, given a path index, that is trivial for any one tree, and given
452
 
    #  that we can ask for additional data from a dirstate tree, its a single
453
 
    #  pass, though it will require scanning the entire tree to find paths
454
 
    #  that were at the current location.
455
 
    # ideal results?: There are three things: tree, path, id. Pathologically
456
 
    # we can have completely disjoint ids for each tree; but we cannot have 
457
 
    # disjoin paths for each tree, except if we scan each tree for the 
458
 
    # different ids from other trees.
459
 
 
460
 
    specified_path_ids = _find_ids_across_trees(filenames, trees,
461
 
        require_versioned)
462
 
    return _find_path_id_children_across_trees(specified_path_ids, trees)
463
627
 
464
628
 
465
629
def _find_ids_across_trees(filenames, trees, require_versioned):
471
635
    :param trees: The trees to find file_ids within
472
636
    :param require_versioned: if true, all specified filenames must occur in
473
637
        at least one tree.
474
 
    :return: a set of (path, file ids) for the specified filenames
 
638
    :return: a set of file ids for the specified filenames
475
639
    """
476
640
    not_versioned = []
477
641
    interesting_ids = set()
490
654
 
491
655
 
492
656
def _find_children_across_trees(specified_ids, trees):
493
 
    """Return a set including specified ids and their children
 
657
    """Return a set including specified ids and their children.
494
658
    
495
659
    All matches in all trees will be used.
496
660
 
522
686
    Its instances have methods like 'compare' and contain references to the
523
687
    source and target trees these operations are to be carried out on.
524
688
 
525
 
    clients of bzrlib should not need to use InterTree directly, rather they
 
689
    Clients of bzrlib should not need to use InterTree directly, rather they
526
690
    should use the convenience methods on Tree such as 'Tree.compare()' which
527
691
    will pass through to InterTree as appropriate.
528
692
    """
531
695
 
532
696
    @needs_read_lock
533
697
    def compare(self, want_unchanged=False, specific_files=None,
534
 
        extra_trees=None, require_versioned=False, include_root=False):
 
698
        extra_trees=None, require_versioned=False, include_root=False,
 
699
        want_unversioned=False):
535
700
        """Return the changes from source to target.
536
701
 
537
702
        :return: A TreeDelta.
546
711
        :param require_versioned: An optional boolean (defaults to False). When
547
712
            supplied and True all the 'specific_files' must be versioned, or
548
713
            a PathsNotVersionedError will be thrown.
 
714
        :param want_unversioned: Scan for unversioned paths.
549
715
        """
550
716
        # NB: show_status depends on being able to pass in non-versioned files
551
717
        # and report them as unknown
552
 
        trees = (self.source, self.target)
 
718
        trees = (self.source,)
553
719
        if extra_trees is not None:
554
720
            trees = trees + tuple(extra_trees)
555
 
        specific_file_ids = find_ids_across_trees(specific_files,
556
 
            trees, require_versioned=require_versioned)
 
721
        # target is usually the newer tree:
 
722
        specific_file_ids = self.target.paths2ids(specific_files, trees,
 
723
            require_versioned=require_versioned)
557
724
        if specific_files and not specific_file_ids:
558
725
            # All files are unversioned, so just return an empty delta
559
726
            # _compare_trees would think we want a complete delta
560
 
            return delta.TreeDelta()
 
727
            result = delta.TreeDelta()
 
728
            fake_entry = InventoryFile('unused', 'unused', 'unused')
 
729
            result.unversioned = [(path, None,
 
730
                self.target._comparison_data(fake_entry, path)[0]) for path in
 
731
                specific_files]
 
732
            return result
561
733
        return delta._compare_trees(self.source, self.target, want_unchanged,
562
 
            specific_file_ids, include_root)
 
734
            specific_files, include_root, extra_trees=extra_trees,
 
735
            require_versioned=require_versioned,
 
736
            want_unversioned=want_unversioned)
563
737
 
564
 
    def _iter_changes(self, from_tree, to_tree, include_unchanged, 
565
 
                      specific_file_ids, pb):
 
738
    def iter_changes(self, include_unchanged=False,
 
739
                      specific_files=None, pb=None, extra_trees=[],
 
740
                      require_versioned=True, want_unversioned=False):
566
741
        """Generate an iterator of changes between trees.
567
742
 
568
743
        A tuple is returned:
569
 
        (file_id, path, changed_content, versioned, parent, name, kind,
 
744
        (file_id, (path_in_source, path_in_target),
 
745
         changed_content, versioned, parent, name, kind,
570
746
         executable)
571
747
 
572
 
        Path is relative to the to_tree.  changed_content is True if the file's
573
 
        content has changed.  This includes changes to its kind, and to
574
 
        a symlink's target.
 
748
        Changed_content is True if the file's content has changed.  This
 
749
        includes changes to its kind, and to a symlink's target.
575
750
 
576
751
        versioned, parent, name, kind, executable are tuples of (from, to).
577
752
        If a file is missing in a tree, its kind is None.
578
753
 
579
 
        Iteration is done in parent-to-child order, relative to the to_tree.
 
754
        Iteration is done in parent-to-child order, relative to the target
 
755
        tree.
 
756
 
 
757
        There is no guarantee that all paths are in sorted order: the
 
758
        requirement to expand the search due to renames may result in children
 
759
        that should be found early being found late in the search, after
 
760
        lexically later results have been returned.
 
761
        :param require_versioned: Raise errors.PathsNotVersionedError if a
 
762
            path in the specific_files list is not versioned in one of
 
763
            source, target or extra_trees.
 
764
        :param want_unversioned: Should unversioned files be returned in the
 
765
            output. An unversioned file is defined as one with (False, False)
 
766
            for the versioned pair.
580
767
        """
 
768
        result = []
 
769
        lookup_trees = [self.source]
 
770
        if extra_trees:
 
771
             lookup_trees.extend(extra_trees)
 
772
        if specific_files == []:
 
773
            specific_file_ids = []
 
774
        else:
 
775
            specific_file_ids = self.target.paths2ids(specific_files,
 
776
                lookup_trees, require_versioned=require_versioned)
 
777
        if want_unversioned:
 
778
            all_unversioned = sorted([(p.split('/'), p) for p in
 
779
                                     self.target.extras()
 
780
                if specific_files is None or
 
781
                    osutils.is_inside_any(specific_files, p)])
 
782
            all_unversioned = deque(all_unversioned)
 
783
        else:
 
784
            all_unversioned = deque()
581
785
        to_paths = {}
582
 
        from_entries_by_dir = list(from_tree.inventory.iter_entries_by_dir(
 
786
        from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
583
787
            specific_file_ids=specific_file_ids))
584
788
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
585
 
        to_entries_by_dir = list(to_tree.inventory.iter_entries_by_dir(
 
789
        to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
586
790
            specific_file_ids=specific_file_ids))
587
791
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
588
792
        entry_count = 0
 
793
        # the unversioned path lookup only occurs on real trees - where there 
 
794
        # can be extras. So the fake_entry is solely used to look up
 
795
        # executable it values when execute is not supported.
 
796
        fake_entry = InventoryFile('unused', 'unused', 'unused')
589
797
        for to_path, to_entry in to_entries_by_dir:
 
798
            while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
 
799
                unversioned_path = all_unversioned.popleft()
 
800
                to_kind, to_executable, to_stat = \
 
801
                    self.target._comparison_data(fake_entry, unversioned_path[1])
 
802
                yield (None, (None, unversioned_path[1]), True, (False, False),
 
803
                    (None, None),
 
804
                    (None, unversioned_path[0][-1]),
 
805
                    (None, to_kind),
 
806
                    (None, to_executable))
590
807
            file_id = to_entry.file_id
591
808
            to_paths[file_id] = to_path
592
809
            entry_count += 1
598
815
                from_name = from_entry.name
599
816
                from_parent = from_entry.parent_id
600
817
                from_kind, from_executable, from_stat = \
601
 
                    from_tree._comparison_data(from_entry, from_path)
 
818
                    self.source._comparison_data(from_entry, from_path)
602
819
                entry_count += 1
603
820
            else:
604
821
                from_versioned = False
608
825
                from_executable = None
609
826
            versioned = (from_versioned, True)
610
827
            to_kind, to_executable, to_stat = \
611
 
                to_tree._comparison_data(to_entry, to_path)
 
828
                self.target._comparison_data(to_entry, to_path)
612
829
            kind = (from_kind, to_kind)
613
830
            if kind[0] != kind[1]:
614
831
                changed_content = True
615
832
            elif from_kind == 'file':
616
 
                from_size = from_tree._file_size(from_entry, from_stat)
617
 
                to_size = to_tree._file_size(to_entry, to_stat)
 
833
                from_size = self.source._file_size(from_entry, from_stat)
 
834
                to_size = self.target._file_size(to_entry, to_stat)
618
835
                if from_size != to_size:
619
836
                    changed_content = True
620
 
                elif (from_tree.get_file_sha1(file_id, from_path, from_stat) !=
621
 
                    to_tree.get_file_sha1(file_id, to_path, to_stat)):
 
837
                elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
 
838
                    self.target.get_file_sha1(file_id, to_path, to_stat)):
622
839
                    changed_content = True
623
840
            elif from_kind == 'symlink':
624
 
                if (from_tree.get_symlink_target(file_id) != 
625
 
                    to_tree.get_symlink_target(file_id)):
 
841
                if (self.source.get_symlink_target(file_id) !=
 
842
                    self.target.get_symlink_target(file_id)):
626
843
                    changed_content = True
 
844
                elif from_kind == 'tree-reference':
 
845
                    if (self.source.get_reference_revision(file_id, from_path)
 
846
                        != self.target.get_reference_revision(file_id, to_path)):
 
847
                        changed_content = True 
627
848
            parent = (from_parent, to_entry.parent_id)
628
849
            name = (from_name, to_entry.name)
629
850
            executable = (from_executable, to_executable)
630
851
            if pb is not None:
631
852
                pb.update('comparing files', entry_count, num_entries)
632
 
            if (changed_content is not False or versioned[0] != versioned[1] 
 
853
            if (changed_content is not False or versioned[0] != versioned[1]
633
854
                or parent[0] != parent[1] or name[0] != name[1] or 
634
855
                executable[0] != executable[1] or include_unchanged):
635
 
                yield (file_id, to_path, changed_content, versioned, parent,
636
 
                       name, kind, executable)
637
 
 
638
 
        def get_to_path(from_entry):
639
 
            if from_entry.parent_id is None:
640
 
                to_path = ''
 
856
                yield (file_id, (from_path, to_path), changed_content,
 
857
                    versioned, parent, name, kind, executable)
 
858
 
 
859
        while all_unversioned:
 
860
            # yield any trailing unversioned paths
 
861
            unversioned_path = all_unversioned.popleft()
 
862
            to_kind, to_executable, to_stat = \
 
863
                self.target._comparison_data(fake_entry, unversioned_path[1])
 
864
            yield (None, (None, unversioned_path[1]), True, (False, False),
 
865
                (None, None),
 
866
                (None, unversioned_path[0][-1]),
 
867
                (None, to_kind),
 
868
                (None, to_executable))
 
869
 
 
870
        def get_to_path(to_entry):
 
871
            if to_entry.parent_id is None:
 
872
                to_path = '' # the root
641
873
            else:
642
 
                if from_entry.parent_id not in to_paths:
643
 
                    get_to_path(from_tree.inventory[from_entry.parent_id])
644
 
                to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
645
 
                                           from_entry.name)
646
 
            to_paths[from_entry.file_id] = to_path
 
874
                if to_entry.parent_id not in to_paths:
 
875
                    # recurse up
 
876
                    return get_to_path(self.target.inventory[to_entry.parent_id])
 
877
                to_path = osutils.pathjoin(to_paths[to_entry.parent_id],
 
878
                                           to_entry.name)
 
879
            to_paths[to_entry.file_id] = to_path
647
880
            return to_path
648
881
 
649
882
        for path, from_entry in from_entries_by_dir:
650
883
            file_id = from_entry.file_id
651
884
            if file_id in to_paths:
 
885
                # already returned
652
886
                continue
653
 
            to_path = get_to_path(from_entry)
 
887
            if not file_id in self.target.inventory:
 
888
                # common case - paths we have not emitted are not present in
 
889
                # target.
 
890
                to_path = None
 
891
            else:
 
892
                to_path = get_to_path(self.target.inventory[file_id])
654
893
            entry_count += 1
655
894
            if pb is not None:
656
895
                pb.update('comparing files', entry_count, num_entries)
658
897
            parent = (from_entry.parent_id, None)
659
898
            name = (from_entry.name, None)
660
899
            from_kind, from_executable, stat_value = \
661
 
                from_tree._comparison_data(from_entry, path)
 
900
                self.source._comparison_data(from_entry, path)
662
901
            kind = (from_kind, None)
663
902
            executable = (from_executable, None)
664
903
            changed_content = True
665
904
            # the parent's path is necessarily known at this point.
666
 
            yield(file_id, to_path, changed_content, versioned, parent,
 
905
            yield(file_id, (path, to_path), changed_content, versioned, parent,
667
906
                  name, kind, executable)
668
907
 
669
908