~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Aaron Bentley
  • Date: 2007-02-06 14:52:16 UTC
  • mfrom: (2266 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20070206145216-fcpi8o3ufvuzwbp9
Merge bzr.dev

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
22
21
from cStringIO import StringIO
23
22
 
24
23
import bzrlib
25
24
from bzrlib import (
26
 
    conflicts as _mod_conflicts,
27
25
    delta,
28
26
    osutils,
29
 
    revision as _mod_revision,
30
 
    rules,
31
27
    symbol_versioning,
32
28
    )
33
29
from bzrlib.decorators import needs_read_lock
34
30
from bzrlib.errors import BzrError, BzrCheckError
35
31
from bzrlib import errors
36
 
from bzrlib.inventory import Inventory, InventoryFile
 
32
from bzrlib.inventory import Inventory
37
33
from bzrlib.inter import InterObject
38
34
from bzrlib.osutils import fingerprint_file
39
35
import bzrlib.revision
61
57
    """
62
58
    
63
59
    def changes_from(self, other, want_unchanged=False, specific_files=None,
64
 
        extra_trees=None, require_versioned=False, include_root=False,
65
 
        want_unversioned=False):
 
60
        extra_trees=None, require_versioned=False, include_root=False):
66
61
        """Return a TreeDelta of the changes from other to this tree.
67
62
 
68
63
        :param other: A tree to compare with.
77
72
        :param require_versioned: An optional boolean (defaults to False). When
78
73
            supplied and True all the 'specific_files' must be versioned, or
79
74
            a PathsNotVersionedError will be thrown.
80
 
        :param want_unversioned: Scan for unversioned paths.
81
75
 
82
76
        The comparison will be performed by an InterTree object looked up on 
83
77
        self and other.
90
84
            specific_files=specific_files,
91
85
            extra_trees=extra_trees,
92
86
            require_versioned=require_versioned,
93
 
            include_root=include_root,
94
 
            want_unversioned=want_unversioned,
 
87
            include_root=include_root
95
88
            )
96
89
 
97
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
98
 
    def _iter_changes(self, *args, **kwargs):
99
 
        return self.iter_changes(*args, **kwargs)
100
 
 
101
 
    def iter_changes(self, from_tree, include_unchanged=False,
102
 
                     specific_files=None, pb=None, extra_trees=None,
103
 
                     require_versioned=True, want_unversioned=False):
 
90
    def _iter_changes(self, from_tree, include_unchanged=False, 
 
91
                     specific_file_ids=None, pb=None):
104
92
        intertree = InterTree.get(from_tree, self)
105
 
        return intertree.iter_changes(include_unchanged, specific_files, pb,
106
 
            extra_trees, require_versioned, want_unversioned=want_unversioned)
 
93
        return intertree._iter_changes(from_tree, self, include_unchanged, 
 
94
                                       specific_file_ids, pb)
107
95
    
108
96
    def conflicts(self):
109
97
        """Get a list of the conflicts in the tree.
110
98
 
111
99
        Each conflict is an instance of bzrlib.conflicts.Conflict.
112
100
        """
113
 
        return _mod_conflicts.ConflictList()
114
 
 
115
 
    def extras(self):
116
 
        """For trees that can have unversioned files, return all such paths."""
117
101
        return []
118
102
 
119
103
    def get_parent_ids(self):
127
111
    
128
112
    def has_filename(self, filename):
129
113
        """True if the tree has given filename."""
130
 
        raise NotImplementedError(self.has_filename)
 
114
        raise NotImplementedError()
131
115
 
132
116
    def has_id(self, file_id):
133
117
        return self.inventory.has_id(file_id)
139
123
            return True
140
124
        return self.inventory.has_id(file_id)
141
125
 
142
 
    def is_ignored(self, filename):
143
 
        """Check whether the filename is ignored by this tree.
144
 
 
145
 
        :param filename: The relative filename within the tree.
146
 
        :return: True if the filename is ignored.
147
 
        """
148
 
        return False
149
 
 
150
126
    def __iter__(self):
151
127
        return iter(self.inventory)
152
128
 
153
 
    def all_file_ids(self):
154
 
        """Iterate through all file ids, including ids for missing files."""
155
 
        return set(self.inventory)
156
 
 
157
129
    def id2path(self, file_id):
158
 
        """Return the path for a file id.
159
 
 
160
 
        :raises NoSuchId:
161
 
        """
162
130
        return self.inventory.id2path(file_id)
163
131
 
164
132
    def is_control_filename(self, filename):
173
141
        """
174
142
        return self.bzrdir.is_control_filename(filename)
175
143
 
176
 
    @needs_read_lock
177
144
    def iter_entries_by_dir(self, specific_file_ids=None):
178
145
        """Walk the tree in 'by_dir' order.
179
146
 
180
 
        This will yield each entry in the tree as a (path, entry) tuple.
181
 
        The order that they are yielded is:
182
 
 
183
 
        Directories are walked in a depth-first lexicographical order,
184
 
        however, whenever a directory is reached, all of its direct child
185
 
        nodes are yielded in  lexicographical order before yielding the
186
 
        grandchildren.
187
 
 
188
 
        For example, in the tree::
189
 
 
190
 
           a/
191
 
             b/
192
 
               c
193
 
             d/
194
 
               e
195
 
           f/
196
 
             g
197
 
 
198
 
        The yield order (ignoring root) would be::
199
 
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
 
147
        This will yield each entry in the tree as a (path, entry) tuple. The
 
148
        order that they are yielded is: the contents of a directory are 
 
149
        preceeded by the parent of a directory, and all the contents of a 
 
150
        directory are grouped together.
200
151
        """
201
152
        return self.inventory.iter_entries_by_dir(
202
153
            specific_file_ids=specific_file_ids)
203
154
 
204
 
    def iter_references(self):
205
 
        for path, entry in self.iter_entries_by_dir():
206
 
            if entry.kind == 'tree-reference':
207
 
                yield path, entry.file_id
208
 
 
209
155
    def kind(self, file_id):
210
 
        raise NotImplementedError("Tree subclass %s must implement kind"
211
 
            % self.__class__.__name__)
212
 
 
213
 
    def stored_kind(self, file_id):
214
 
        """File kind stored for this file_id.
215
 
 
216
 
        May not match kind on disk for working trees.  Always available
217
 
        for versioned files, even when the file itself is missing.
218
 
        """
219
 
        return self.kind(file_id)
220
 
 
221
 
    def path_content_summary(self, path):
222
 
        """Get a summary of the information about path.
223
 
        
224
 
        :param path: A relative path within the tree.
225
 
        :return: A tuple containing kind, size, exec, sha1-or-link.
226
 
            Kind is always present (see tree.kind()).
227
 
            size is present if kind is file, None otherwise.
228
 
            exec is None unless kind is file and the platform supports the 'x'
229
 
                bit.
230
 
            sha1-or-link is the link target if kind is symlink, or the sha1 if
231
 
                it can be obtained without reading the file.
232
 
        """
233
 
        raise NotImplementedError(self.path_content_summary)
234
 
 
235
 
    def get_reference_revision(self, file_id, path=None):
236
 
        raise NotImplementedError("Tree subclass %s must implement "
237
 
                                  "get_reference_revision"
238
 
            % self.__class__.__name__)
 
156
        raise NotImplementedError("subclasses must implement kind")
239
157
 
240
158
    def _comparison_data(self, entry, path):
241
159
        """Return a tuple of kind, executable, stat_value for a file.
248
166
        """
249
167
        raise NotImplementedError(self._comparison_data)
250
168
 
251
 
    def _file_size(self, entry, stat_value):
 
169
    def _file_size(entry, stat_value):
252
170
        raise NotImplementedError(self._file_size)
253
171
 
254
172
    def _get_inventory(self):
255
173
        return self._inventory
256
174
    
257
 
    def get_file(self, file_id, path=None):
258
 
        """Return a file object for the file file_id in the tree.
259
 
        
260
 
        If both file_id and path are defined, it is implementation defined as
261
 
        to which one is used.
262
 
        """
 
175
    def get_file(self, file_id):
 
176
        """Return a file object for the file file_id in the tree."""
263
177
        raise NotImplementedError(self.get_file)
264
 
 
265
 
    def get_file_mtime(self, file_id, path=None):
266
 
        """Return the modification time for a file.
267
 
 
268
 
        :param file_id: The handle for this file.
269
 
        :param path: The path that this file can be found at.
270
 
            These must point to the same object.
271
 
        """
272
 
        raise NotImplementedError(self.get_file_mtime)
273
 
 
274
 
    def get_file_size(self, file_id):
275
 
        """Return the size of a file in bytes.
276
 
 
277
 
        This applies only to regular files.  If invoked on directories or
278
 
        symlinks, it will return None.
279
 
        :param file_id: The file-id of the file
280
 
        """
281
 
        raise NotImplementedError(self.get_file_size)
282
 
 
 
178
    
283
179
    def get_file_by_path(self, path):
284
 
        return self.get_file(self._inventory.path2id(path), path)
285
 
 
286
 
    def iter_files_bytes(self, desired_files):
287
 
        """Iterate through file contents.
288
 
 
289
 
        Files will not necessarily be returned in the order they occur in
290
 
        desired_files.  No specific order is guaranteed.
291
 
 
292
 
        Yields pairs of identifier, bytes_iterator.  identifier is an opaque
293
 
        value supplied by the caller as part of desired_files.  It should
294
 
        uniquely identify the file version in the caller's context.  (Examples:
295
 
        an index number or a TreeTransform trans_id.)
296
 
 
297
 
        bytes_iterator is an iterable of bytestrings for the file.  The
298
 
        kind of iterable and length of the bytestrings are unspecified, but for
299
 
        this implementation, it is a tuple containing a single bytestring with
300
 
        the complete text of the file.
301
 
 
302
 
        :param desired_files: a list of (file_id, identifier) pairs
303
 
        """
304
 
        for file_id, identifier in desired_files:
305
 
            # We wrap the string in a tuple so that we can return an iterable
306
 
            # of bytestrings.  (Technically, a bytestring is also an iterable
307
 
            # of bytestrings, but iterating through each character is not
308
 
            # performant.)
309
 
            cur_file = (self.get_file_text(file_id),)
310
 
            yield identifier, cur_file
311
 
 
312
 
    def get_symlink_target(self, file_id):
313
 
        """Get the target for a given file_id.
314
 
 
315
 
        It is assumed that the caller already knows that file_id is referencing
316
 
        a symlink.
317
 
        :param file_id: Handle for the symlink entry.
318
 
        :return: The path the symlink points to.
319
 
        """
320
 
        raise NotImplementedError(self.get_symlink_target)
321
 
 
322
 
    def get_root_id(self):
323
 
        """Return the file_id for the root of this tree."""
324
 
        raise NotImplementedError(self.get_root_id)
325
 
 
326
 
    def annotate_iter(self, file_id,
327
 
                      default_revision=_mod_revision.CURRENT_REVISION):
328
 
        """Return an iterator of revision_id, line tuples.
 
180
        return self.get_file(self._inventory.path2id(path))
 
181
 
 
182
    def annotate_iter(self, file_id):
 
183
        """Return an iterator of revision_id, line tuples
329
184
 
330
185
        For working trees (and mutable trees in general), the special
331
186
        revision_id 'current:' will be used for lines that are new in this
332
187
        tree, e.g. uncommitted changes.
333
188
        :param file_id: The file to produce an annotated version from
334
 
        :param default_revision: For lines that don't match a basis, mark them
335
 
            with this revision id. Not all implementations will make use of
336
 
            this value.
337
189
        """
338
190
        raise NotImplementedError(self.annotate_iter)
339
191
 
340
 
    def _get_plan_merge_data(self, file_id, other, base):
341
 
        from bzrlib import merge, versionedfile
342
 
        vf = versionedfile._PlanMergeVersionedFile(file_id)
343
 
        last_revision_a = self._get_file_revision(file_id, vf, 'this:')
344
 
        last_revision_b = other._get_file_revision(file_id, vf, 'other:')
345
 
        if base is None:
346
 
            last_revision_base = None
347
 
        else:
348
 
            last_revision_base = base._get_file_revision(file_id, vf, 'base:')
349
 
        return vf, last_revision_a, last_revision_b, last_revision_base
350
 
 
351
 
    def plan_file_merge(self, file_id, other, base=None):
352
 
        """Generate a merge plan based on annotations.
353
 
 
354
 
        If the file contains uncommitted changes in this tree, they will be
355
 
        attributed to the 'current:' pseudo-revision.  If the file contains
356
 
        uncommitted changes in the other tree, they will be assigned to the
357
 
        'other:' pseudo-revision.
358
 
        """
359
 
        data = self._get_plan_merge_data(file_id, other, base)
360
 
        vf, last_revision_a, last_revision_b, last_revision_base = data
361
 
        return vf.plan_merge(last_revision_a, last_revision_b,
362
 
                             last_revision_base)
363
 
 
364
 
    def plan_file_lca_merge(self, file_id, other, base=None):
365
 
        """Generate a merge plan based lca-newness.
366
 
 
367
 
        If the file contains uncommitted changes in this tree, they will be
368
 
        attributed to the 'current:' pseudo-revision.  If the file contains
369
 
        uncommitted changes in the other tree, they will be assigned to the
370
 
        'other:' pseudo-revision.
371
 
        """
372
 
        data = self._get_plan_merge_data(file_id, other, base)
373
 
        vf, last_revision_a, last_revision_b, last_revision_base = data
374
 
        return vf.plan_lca_merge(last_revision_a, last_revision_b,
375
 
                                 last_revision_base)
376
 
 
377
 
    def _iter_parent_trees(self):
378
 
        """Iterate through parent trees, defaulting to Tree.revision_tree."""
379
 
        for revision_id in self.get_parent_ids():
380
 
            try:
381
 
                yield self.revision_tree(revision_id)
382
 
            except errors.NoSuchRevisionInTree:
383
 
                yield self.repository.revision_tree(revision_id)
384
 
 
385
 
    @staticmethod
386
 
    def _file_revision(revision_tree, file_id):
387
 
        """Determine the revision associated with a file in a given tree."""
388
 
        revision_tree.lock_read()
389
 
        try:
390
 
            return revision_tree.inventory[file_id].revision
391
 
        finally:
392
 
            revision_tree.unlock()
393
 
 
394
 
    def _get_file_revision(self, file_id, vf, tree_revision):
395
 
        """Ensure that file_id, tree_revision is in vf to plan the merge."""
396
 
 
397
 
        if getattr(self, '_repository', None) is None:
398
 
            last_revision = tree_revision
399
 
            parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
400
 
                self._iter_parent_trees()]
401
 
            vf.add_lines((file_id, last_revision), parent_keys,
402
 
                         self.get_file(file_id).readlines())
403
 
            repo = self.branch.repository
404
 
            base_vf = repo.texts
405
 
        else:
406
 
            last_revision = self._file_revision(self, file_id)
407
 
            base_vf = self._repository.texts
408
 
        if base_vf not in vf.fallback_versionedfiles:
409
 
            vf.fallback_versionedfiles.append(base_vf)
410
 
        return last_revision
411
 
 
412
192
    inventory = property(_get_inventory,
413
193
                         doc="Inventory of this Tree")
414
194
 
431
211
                     "file is actually %s" % fp['sha1'],
432
212
                     "store is probably damaged/corrupt"])
433
213
 
434
 
    @needs_read_lock
435
214
    def path2id(self, path):
436
215
        """Return the id for path in this tree."""
437
216
        return self._inventory.path2id(path)
438
217
 
439
 
    def paths2ids(self, paths, trees=[], require_versioned=True):
440
 
        """Return all the ids that can be reached by walking from paths.
441
 
        
442
 
        Each path is looked up in this tree and any extras provided in
443
 
        trees, and this is repeated recursively: the children in an extra tree
444
 
        of a directory that has been renamed under a provided path in this tree
445
 
        are all returned, even if none exist under a provided path in this
446
 
        tree, and vice versa.
447
 
 
448
 
        :param paths: An iterable of paths to start converting to ids from.
449
 
            Alternatively, if paths is None, no ids should be calculated and None
450
 
            will be returned. This is offered to make calling the api unconditional
451
 
            for code that *might* take a list of files.
452
 
        :param trees: Additional trees to consider.
453
 
        :param require_versioned: If False, do not raise NotVersionedError if
454
 
            an element of paths is not versioned in this tree and all of trees.
455
 
        """
456
 
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
457
 
 
458
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_six)
459
218
    def print_file(self, file_id):
460
219
        """Print file with id `file_id` to stdout."""
461
220
        import sys
464
223
    def lock_read(self):
465
224
        pass
466
225
 
467
 
    def revision_tree(self, revision_id):
468
 
        """Obtain a revision tree for the revision revision_id.
469
 
 
470
 
        The intention of this method is to allow access to possibly cached
471
 
        tree data. Implementors of this method should raise NoSuchRevision if
472
 
        the tree is not locally available, even if they could obtain the 
473
 
        tree via a repository or some other means. Callers are responsible 
474
 
        for finding the ultimate source for a revision tree.
475
 
 
476
 
        :param revision_id: The revision_id of the requested tree.
477
 
        :return: A Tree.
478
 
        :raises: NoSuchRevision if the tree cannot be obtained.
479
 
        """
480
 
        raise errors.NoSuchRevisionInTree(self, revision_id)
481
 
 
482
226
    def unknowns(self):
483
227
        """What files are present in this tree and unknown.
484
228
        
490
234
        pass
491
235
 
492
236
    def filter_unversioned_files(self, paths):
493
 
        """Filter out paths that are versioned.
 
237
        """Filter out paths that are not versioned.
494
238
 
495
239
        :return: set of paths.
496
240
        """
500
244
        pred = self.inventory.has_filename
501
245
        return set((p for p in paths if not pred(p)))
502
246
 
503
 
    def walkdirs(self, prefix=""):
504
 
        """Walk the contents of this tree from path down.
505
 
 
506
 
        This yields all the data about the contents of a directory at a time.
507
 
        After each directory has been yielded, if the caller has mutated the
508
 
        list to exclude some directories, they are then not descended into.
509
 
        
510
 
        The data yielded is of the form:
511
 
        ((directory-relpath, directory-path-from-root, directory-fileid),
512
 
        [(relpath, basename, kind, lstat, path_from_tree_root, file_id, 
513
 
          versioned_kind), ...]),
514
 
         - directory-relpath is the containing dirs relpath from prefix
515
 
         - directory-path-from-root is the containing dirs path from /
516
 
         - directory-fileid is the id of the directory if it is versioned.
517
 
         - relpath is the relative path within the subtree being walked.
518
 
         - basename is the basename
519
 
         - kind is the kind of the file now. If unknonwn then the file is not
520
 
           present within the tree - but it may be recorded as versioned. See
521
 
           versioned_kind.
522
 
         - lstat is the stat data *if* the file was statted.
523
 
         - path_from_tree_root is the path from the root of the tree.
524
 
         - file_id is the file_id if the entry is versioned.
525
 
         - versioned_kind is the kind of the file as last recorded in the 
526
 
           versioning system. If 'unknown' the file is not versioned.
527
 
        One of 'kind' and 'versioned_kind' must not be 'unknown'.
528
 
 
529
 
        :param prefix: Start walking from prefix within the tree rather than
530
 
        at the root. This allows one to walk a subtree but get paths that are
531
 
        relative to a tree rooted higher up.
532
 
        :return: an iterator over the directory data.
533
 
        """
534
 
        raise NotImplementedError(self.walkdirs)
535
 
 
536
 
    def iter_search_rules(self, path_names, pref_names=None,
537
 
        _default_searcher=rules._per_user_searcher):
538
 
        """Find the preferences for filenames in a tree.
539
 
 
540
 
        :param path_names: an iterable of paths to find attributes for.
541
 
          Paths are given relative to the root of the tree.
542
 
        :param pref_names: the list of preferences to lookup - None for all
543
 
        :param _default_searcher: private parameter to assist testing - don't use
544
 
        :return: an iterator of tuple sequences, one per path-name.
545
 
          See _RulesSearcher.get_items for details on the tuple sequence.
546
 
        """
547
 
        searcher = self._get_rules_searcher(_default_searcher)
548
 
        if searcher is not None:
549
 
            if pref_names is not None:
550
 
                for path in path_names:
551
 
                    yield searcher.get_selected_items(path, pref_names)
552
 
            else:
553
 
                for path in path_names:
554
 
                    yield searcher.get_items(path)
555
 
 
556
 
    @needs_read_lock
557
 
    def _get_rules_searcher(self, default_searcher):
558
 
        """Get the RulesSearcher for this tree given the default one."""
559
 
        searcher = default_searcher
560
 
        file_id = self.path2id(rules.RULES_TREE_FILENAME)
561
 
        if file_id is not None:
562
 
            ini_file = self.get_file(file_id)
563
 
            searcher = rules._StackedRulesSearcher(
564
 
                [rules._IniBasedRulesSearcher(ini_file), default_searcher])
565
 
        return searcher
566
 
 
567
247
 
568
248
class EmptyTree(Tree):
569
249
 
583
263
        return False
584
264
 
585
265
    def kind(self, file_id):
 
266
        assert self._inventory[file_id].kind == "directory"
586
267
        return "directory"
587
268
 
588
269
    def list_files(self, include_root=False):
631
312
        # what happened to the file that used to have
632
313
        # this name.  There are two possibilities: either it was
633
314
        # deleted entirely, or renamed.
 
315
        assert old_id
634
316
        if new_inv.has_id(old_id):
635
317
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
636
318
        else:
665
347
    All matches in all trees will be used, and all children of matched
666
348
    directories will be used.
667
349
 
668
 
    :param filenames: The filenames to find file_ids for (if None, returns
669
 
        None)
 
350
    :param filenames: The filenames to find file_ids for
670
351
    :param trees: The trees to find file_ids within
671
352
    :param require_versioned: if true, all specified filenames must occur in
672
353
    at least one tree.
674
355
    """
675
356
    if not filenames:
676
357
        return None
677
 
    specified_path_ids = _find_ids_across_trees(filenames, trees,
678
 
        require_versioned)
679
 
    return _find_children_across_trees(specified_path_ids, trees)
680
 
 
681
 
 
682
 
def _find_ids_across_trees(filenames, trees, require_versioned):
 
358
    specified_ids = _find_filename_ids_across_trees(filenames, trees, 
 
359
                                                    require_versioned)
 
360
    return _find_children_across_trees(specified_ids, trees)
 
361
 
 
362
 
 
363
def _find_filename_ids_across_trees(filenames, trees, require_versioned):
683
364
    """Find the ids corresponding to specified filenames.
684
365
    
685
 
    All matches in all trees will be used, but subdirectories are not scanned.
 
366
    All matches in all trees will be used.
686
367
 
687
368
    :param filenames: The filenames to find file_ids for
688
369
    :param trees: The trees to find file_ids within
689
370
    :param require_versioned: if true, all specified filenames must occur in
690
 
        at least one tree.
 
371
    at least one tree.
691
372
    :return: a set of file ids for the specified filenames
692
373
    """
693
374
    not_versioned = []
695
376
    for tree_path in filenames:
696
377
        not_found = True
697
378
        for tree in trees:
698
 
            file_id = tree.path2id(tree_path)
 
379
            file_id = tree.inventory.path2id(tree_path)
699
380
            if file_id is not None:
700
381
                interesting_ids.add(file_id)
701
382
                not_found = False
707
388
 
708
389
 
709
390
def _find_children_across_trees(specified_ids, trees):
710
 
    """Return a set including specified ids and their children.
 
391
    """Return a set including specified ids and their children
711
392
    
712
393
    All matches in all trees will be used.
713
394
 
722
403
        new_pending = set()
723
404
        for file_id in pending:
724
405
            for tree in trees:
725
 
                if not tree.has_id(file_id):
 
406
                if file_id not in tree:
726
407
                    continue
727
408
                entry = tree.inventory[file_id]
728
409
                for child in getattr(entry, 'children', {}).itervalues():
739
420
    Its instances have methods like 'compare' and contain references to the
740
421
    source and target trees these operations are to be carried out on.
741
422
 
742
 
    Clients of bzrlib should not need to use InterTree directly, rather they
 
423
    clients of bzrlib should not need to use InterTree directly, rather they
743
424
    should use the convenience methods on Tree such as 'Tree.compare()' which
744
425
    will pass through to InterTree as appropriate.
745
426
    """
748
429
 
749
430
    @needs_read_lock
750
431
    def compare(self, want_unchanged=False, specific_files=None,
751
 
        extra_trees=None, require_versioned=False, include_root=False,
752
 
        want_unversioned=False):
 
432
        extra_trees=None, require_versioned=False, include_root=False):
753
433
        """Return the changes from source to target.
754
434
 
755
435
        :return: A TreeDelta.
764
444
        :param require_versioned: An optional boolean (defaults to False). When
765
445
            supplied and True all the 'specific_files' must be versioned, or
766
446
            a PathsNotVersionedError will be thrown.
767
 
        :param want_unversioned: Scan for unversioned paths.
768
447
        """
769
 
        # NB: show_status depends on being able to pass in non-versioned files
770
 
        # and report them as unknown
771
 
        trees = (self.source,)
 
448
        # NB: show_status depends on being able to pass in non-versioned files and
 
449
        # report them as unknown
 
450
        trees = (self.source, self.target)
772
451
        if extra_trees is not None:
773
452
            trees = trees + tuple(extra_trees)
774
 
        # target is usually the newer tree:
775
 
        specific_file_ids = self.target.paths2ids(specific_files, trees,
776
 
            require_versioned=require_versioned)
 
453
        specific_file_ids = find_ids_across_trees(specific_files,
 
454
            trees, require_versioned=require_versioned)
777
455
        if specific_files and not specific_file_ids:
778
456
            # All files are unversioned, so just return an empty delta
779
457
            # _compare_trees would think we want a complete delta
780
 
            result = delta.TreeDelta()
781
 
            fake_entry = InventoryFile('unused', 'unused', 'unused')
782
 
            result.unversioned = [(path, None,
783
 
                self.target._comparison_data(fake_entry, path)[0]) for path in
784
 
                specific_files]
785
 
            return result
 
458
            return delta.TreeDelta()
786
459
        return delta._compare_trees(self.source, self.target, want_unchanged,
787
 
            specific_files, include_root, extra_trees=extra_trees,
788
 
            require_versioned=require_versioned,
789
 
            want_unversioned=want_unversioned)
 
460
            specific_file_ids, include_root)
790
461
 
791
 
    def iter_changes(self, include_unchanged=False,
792
 
                      specific_files=None, pb=None, extra_trees=[],
793
 
                      require_versioned=True, want_unversioned=False):
 
462
    def _iter_changes(self, from_tree, to_tree, include_unchanged, 
 
463
                      specific_file_ids, pb):
794
464
        """Generate an iterator of changes between trees.
795
465
 
796
466
        A tuple is returned:
797
 
        (file_id, (path_in_source, path_in_target),
798
 
         changed_content, versioned, parent, name, kind,
 
467
        (file_id, path, changed_content, versioned, parent, name, kind,
799
468
         executable)
800
469
 
801
 
        Changed_content is True if the file's content has changed.  This
802
 
        includes changes to its kind, and to a symlink's target.
 
470
        Path is relative to the to_tree.  changed_content is True if the file's
 
471
        content has changed.  This includes changes to its kind, and to
 
472
        a symlink's target.
803
473
 
804
474
        versioned, parent, name, kind, executable are tuples of (from, to).
805
475
        If a file is missing in a tree, its kind is None.
806
476
 
807
 
        Iteration is done in parent-to-child order, relative to the target
808
 
        tree.
809
 
 
810
 
        There is no guarantee that all paths are in sorted order: the
811
 
        requirement to expand the search due to renames may result in children
812
 
        that should be found early being found late in the search, after
813
 
        lexically later results have been returned.
814
 
        :param require_versioned: Raise errors.PathsNotVersionedError if a
815
 
            path in the specific_files list is not versioned in one of
816
 
            source, target or extra_trees.
817
 
        :param want_unversioned: Should unversioned files be returned in the
818
 
            output. An unversioned file is defined as one with (False, False)
819
 
            for the versioned pair.
 
477
        Iteration is done in parent-to-child order, relative to the to_tree.
820
478
        """
821
 
        result = []
822
 
        lookup_trees = [self.source]
823
 
        if extra_trees:
824
 
             lookup_trees.extend(extra_trees)
825
 
        if specific_files == []:
826
 
            specific_file_ids = []
827
 
        else:
828
 
            specific_file_ids = self.target.paths2ids(specific_files,
829
 
                lookup_trees, require_versioned=require_versioned)
830
 
        if want_unversioned:
831
 
            all_unversioned = sorted([(p.split('/'), p) for p in
832
 
                                     self.target.extras()
833
 
                if specific_files is None or
834
 
                    osutils.is_inside_any(specific_files, p)])
835
 
            all_unversioned = deque(all_unversioned)
836
 
        else:
837
 
            all_unversioned = deque()
838
479
        to_paths = {}
839
 
        from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
 
480
        from_entries_by_dir = list(from_tree.inventory.iter_entries_by_dir(
840
481
            specific_file_ids=specific_file_ids))
841
482
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
842
 
        to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
 
483
        to_entries_by_dir = list(to_tree.inventory.iter_entries_by_dir(
843
484
            specific_file_ids=specific_file_ids))
844
485
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
845
486
        entry_count = 0
846
 
        # the unversioned path lookup only occurs on real trees - where there 
847
 
        # can be extras. So the fake_entry is solely used to look up
848
 
        # executable it values when execute is not supported.
849
 
        fake_entry = InventoryFile('unused', 'unused', 'unused')
850
487
        for to_path, to_entry in to_entries_by_dir:
851
 
            while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
852
 
                unversioned_path = all_unversioned.popleft()
853
 
                to_kind, to_executable, to_stat = \
854
 
                    self.target._comparison_data(fake_entry, unversioned_path[1])
855
 
                yield (None, (None, unversioned_path[1]), True, (False, False),
856
 
                    (None, None),
857
 
                    (None, unversioned_path[0][-1]),
858
 
                    (None, to_kind),
859
 
                    (None, to_executable))
860
488
            file_id = to_entry.file_id
861
489
            to_paths[file_id] = to_path
862
490
            entry_count += 1
868
496
                from_name = from_entry.name
869
497
                from_parent = from_entry.parent_id
870
498
                from_kind, from_executable, from_stat = \
871
 
                    self.source._comparison_data(from_entry, from_path)
 
499
                    from_tree._comparison_data(from_entry, from_path)
872
500
                entry_count += 1
873
501
            else:
874
502
                from_versioned = False
878
506
                from_executable = None
879
507
            versioned = (from_versioned, True)
880
508
            to_kind, to_executable, to_stat = \
881
 
                self.target._comparison_data(to_entry, to_path)
 
509
                to_tree._comparison_data(to_entry, to_path)
882
510
            kind = (from_kind, to_kind)
883
511
            if kind[0] != kind[1]:
884
512
                changed_content = True
885
513
            elif from_kind == 'file':
886
 
                from_size = self.source._file_size(from_entry, from_stat)
887
 
                to_size = self.target._file_size(to_entry, to_stat)
 
514
                from_size = from_tree._file_size(from_entry, from_stat)
 
515
                to_size = to_tree._file_size(to_entry, to_stat)
888
516
                if from_size != to_size:
889
517
                    changed_content = True
890
 
                elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
891
 
                    self.target.get_file_sha1(file_id, to_path, to_stat)):
 
518
                elif (from_tree.get_file_sha1(file_id, from_path, from_stat) !=
 
519
                    to_tree.get_file_sha1(file_id, to_path, to_stat)):
892
520
                    changed_content = True
893
521
            elif from_kind == 'symlink':
894
 
                if (self.source.get_symlink_target(file_id) !=
895
 
                    self.target.get_symlink_target(file_id)):
 
522
                if (from_tree.get_symlink_target(file_id) != 
 
523
                    to_tree.get_symlink_target(file_id)):
896
524
                    changed_content = True
897
 
                elif from_kind == 'tree-reference':
898
 
                    if (self.source.get_reference_revision(file_id, from_path)
899
 
                        != self.target.get_reference_revision(file_id, to_path)):
900
 
                        changed_content = True 
901
525
            parent = (from_parent, to_entry.parent_id)
902
526
            name = (from_name, to_entry.name)
903
527
            executable = (from_executable, to_executable)
904
528
            if pb is not None:
905
529
                pb.update('comparing files', entry_count, num_entries)
906
 
            if (changed_content is not False or versioned[0] != versioned[1]
 
530
            if (changed_content is not False or versioned[0] != versioned[1] 
907
531
                or parent[0] != parent[1] or name[0] != name[1] or 
908
532
                executable[0] != executable[1] or include_unchanged):
909
 
                yield (file_id, (from_path, to_path), changed_content,
910
 
                    versioned, parent, name, kind, executable)
911
 
 
912
 
        while all_unversioned:
913
 
            # yield any trailing unversioned paths
914
 
            unversioned_path = all_unversioned.popleft()
915
 
            to_kind, to_executable, to_stat = \
916
 
                self.target._comparison_data(fake_entry, unversioned_path[1])
917
 
            yield (None, (None, unversioned_path[1]), True, (False, False),
918
 
                (None, None),
919
 
                (None, unversioned_path[0][-1]),
920
 
                (None, to_kind),
921
 
                (None, to_executable))
922
 
 
923
 
        def get_to_path(to_entry):
924
 
            if to_entry.parent_id is None:
925
 
                to_path = '' # the root
 
533
                yield (file_id, to_path, changed_content, versioned, parent,
 
534
                       name, kind, executable)
 
535
 
 
536
        def get_to_path(from_entry):
 
537
            if from_entry.parent_id is None:
 
538
                to_path = ''
926
539
            else:
927
 
                if to_entry.parent_id not in to_paths:
928
 
                    # recurse up
929
 
                    return get_to_path(self.target.inventory[to_entry.parent_id])
930
 
                to_path = osutils.pathjoin(to_paths[to_entry.parent_id],
931
 
                                           to_entry.name)
932
 
            to_paths[to_entry.file_id] = to_path
 
540
                if from_entry.parent_id not in to_paths:
 
541
                    get_to_path(from_tree.inventory[from_entry.parent_id])
 
542
                to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
 
543
                                           from_entry.name)
 
544
            to_paths[from_entry.file_id] = to_path
933
545
            return to_path
934
546
 
935
547
        for path, from_entry in from_entries_by_dir:
936
548
            file_id = from_entry.file_id
937
549
            if file_id in to_paths:
938
 
                # already returned
939
550
                continue
940
 
            if not file_id in self.target.inventory:
941
 
                # common case - paths we have not emitted are not present in
942
 
                # target.
943
 
                to_path = None
944
 
            else:
945
 
                to_path = get_to_path(self.target.inventory[file_id])
 
551
            to_path = get_to_path(from_entry)
946
552
            entry_count += 1
947
553
            if pb is not None:
948
554
                pb.update('comparing files', entry_count, num_entries)
950
556
            parent = (from_entry.parent_id, None)
951
557
            name = (from_entry.name, None)
952
558
            from_kind, from_executable, stat_value = \
953
 
                self.source._comparison_data(from_entry, path)
 
559
                from_tree._comparison_data(from_entry, path)
954
560
            kind = (from_kind, None)
955
561
            executable = (from_executable, None)
956
562
            changed_content = True
957
563
            # the parent's path is necessarily known at this point.
958
 
            yield(file_id, (path, to_path), changed_content, versioned, parent,
 
564
            yield(file_id, to_path, changed_content, versioned, parent,
959
565
                  name, kind, executable)
 
566
 
 
567
 
 
568
# This was deprecated before 0.12, but did not have an official warning
 
569
@symbol_versioning.deprecated_function(symbol_versioning.zero_twelve)
 
570
def RevisionTree(*args, **kwargs):
 
571
    """RevisionTree has moved to bzrlib.revisiontree.RevisionTree()
 
572
 
 
573
    Accessing it as bzrlib.tree.RevisionTree has been deprecated as of
 
574
    bzr 0.12.
 
575
    """
 
576
    from bzrlib.revisiontree import RevisionTree as _RevisionTree
 
577
    return _RevisionTree(*args, **kwargs)
 
578
 
 
579