/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Martin
  • Date: 2011-04-15 21:22:52 UTC
  • mto: This revision was merged to the branch mainline in revision 5797.
  • Revision ID: gzlist@googlemail.com-20110415212252-lhqulomwg2y538xj
Add user encoding name to argv decoding error message per poolie in review

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
"""Tree classes, representing directory at point in time.
 
18
"""
 
19
 
 
20
import os
 
21
 
 
22
from bzrlib.lazy_import import lazy_import
 
23
lazy_import(globals(), """
 
24
import collections
 
25
 
 
26
from bzrlib import (
 
27
    conflicts as _mod_conflicts,
 
28
    debug,
 
29
    delta,
 
30
    errors,
 
31
    filters,
 
32
    inventory,
 
33
    osutils,
 
34
    revision as _mod_revision,
 
35
    rules,
 
36
    trace,
 
37
    )
 
38
""")
 
39
 
 
40
from bzrlib.decorators import needs_read_lock
 
41
from bzrlib.inter import InterObject
 
42
 
 
43
 
 
44
class Tree(object):
 
45
    """Abstract file tree.
 
46
 
 
47
    There are several subclasses:
 
48
 
 
49
    * `WorkingTree` exists as files on disk editable by the user.
 
50
 
 
51
    * `RevisionTree` is a tree as recorded at some point in the past.
 
52
 
 
53
    Trees can be compared, etc, regardless of whether they are working
 
54
    trees or versioned trees.
 
55
    """
 
56
 
 
57
    def changes_from(self, other, want_unchanged=False, specific_files=None,
 
58
        extra_trees=None, require_versioned=False, include_root=False,
 
59
        want_unversioned=False):
 
60
        """Return a TreeDelta of the changes from other to this tree.
 
61
 
 
62
        :param other: A tree to compare with.
 
63
        :param specific_files: An optional list of file paths to restrict the
 
64
            comparison to. When mapping filenames to ids, all matches in all
 
65
            trees (including optional extra_trees) are used, and all children of
 
66
            matched directories are included.
 
67
        :param want_unchanged: An optional boolean requesting the inclusion of
 
68
            unchanged entries in the result.
 
69
        :param extra_trees: An optional list of additional trees to use when
 
70
            mapping the contents of specific_files (paths) to file_ids.
 
71
        :param require_versioned: An optional boolean (defaults to False). When
 
72
            supplied and True all the 'specific_files' must be versioned, or
 
73
            a PathsNotVersionedError will be thrown.
 
74
        :param want_unversioned: Scan for unversioned paths.
 
75
 
 
76
        The comparison will be performed by an InterTree object looked up on
 
77
        self and other.
 
78
        """
 
79
        # Martin observes that Tree.changes_from returns a TreeDelta and this
 
80
        # may confuse people, because the class name of the returned object is
 
81
        # a synonym of the object referenced in the method name.
 
82
        return InterTree.get(other, self).compare(
 
83
            want_unchanged=want_unchanged,
 
84
            specific_files=specific_files,
 
85
            extra_trees=extra_trees,
 
86
            require_versioned=require_versioned,
 
87
            include_root=include_root,
 
88
            want_unversioned=want_unversioned,
 
89
            )
 
90
 
 
91
    def iter_changes(self, from_tree, include_unchanged=False,
 
92
                     specific_files=None, pb=None, extra_trees=None,
 
93
                     require_versioned=True, want_unversioned=False):
 
94
        """See InterTree.iter_changes"""
 
95
        intertree = InterTree.get(from_tree, self)
 
96
        return intertree.iter_changes(include_unchanged, specific_files, pb,
 
97
            extra_trees, require_versioned, want_unversioned=want_unversioned)
 
98
 
 
99
    def conflicts(self):
 
100
        """Get a list of the conflicts in the tree.
 
101
 
 
102
        Each conflict is an instance of bzrlib.conflicts.Conflict.
 
103
        """
 
104
        return _mod_conflicts.ConflictList()
 
105
 
 
106
    def extras(self):
 
107
        """For trees that can have unversioned files, return all such paths."""
 
108
        return []
 
109
 
 
110
    def get_parent_ids(self):
 
111
        """Get the parent ids for this tree.
 
112
 
 
113
        :return: a list of parent ids. [] is returned to indicate
 
114
        a tree with no parents.
 
115
        :raises: BzrError if the parents are not known.
 
116
        """
 
117
        raise NotImplementedError(self.get_parent_ids)
 
118
 
 
119
    def has_filename(self, filename):
 
120
        """True if the tree has given filename."""
 
121
        raise NotImplementedError(self.has_filename)
 
122
 
 
123
    def has_id(self, file_id):
 
124
        raise NotImplementedError(self.has_id)
 
125
 
 
126
    def __contains__(self, file_id):
 
127
        return self.has_id(file_id)
 
128
 
 
129
    def has_or_had_id(self, file_id):
 
130
        raise NotImplementedError(self.has_or_had_id)
 
131
 
 
132
    def is_ignored(self, filename):
 
133
        """Check whether the filename is ignored by this tree.
 
134
 
 
135
        :param filename: The relative filename within the tree.
 
136
        :return: True if the filename is ignored.
 
137
        """
 
138
        return False
 
139
 
 
140
    def __iter__(self):
 
141
        """Yield all file ids in this tree."""
 
142
        raise NotImplementedError(self.__iter__)
 
143
 
 
144
    def all_file_ids(self):
 
145
        """Iterate through all file ids, including ids for missing files."""
 
146
        return set(self.inventory)
 
147
 
 
148
    def id2path(self, file_id):
 
149
        """Return the path for a file id.
 
150
 
 
151
        :raises NoSuchId:
 
152
        """
 
153
        raise NotImplementedError(self.id2path)
 
154
 
 
155
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
156
        """Walk the tree in 'by_dir' order.
 
157
 
 
158
        This will yield each entry in the tree as a (path, entry) tuple.
 
159
        The order that they are yielded is:
 
160
 
 
161
        Directories are walked in a depth-first lexicographical order,
 
162
        however, whenever a directory is reached, all of its direct child
 
163
        nodes are yielded in  lexicographical order before yielding the
 
164
        grandchildren.
 
165
 
 
166
        For example, in the tree::
 
167
 
 
168
           a/
 
169
             b/
 
170
               c
 
171
             d/
 
172
               e
 
173
           f/
 
174
             g
 
175
 
 
176
        The yield order (ignoring root) would be::
 
177
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
 
178
 
 
179
        :param yield_parents: If True, yield the parents from the root leading
 
180
            down to specific_file_ids that have been requested. This has no
 
181
            impact if specific_file_ids is None.
 
182
        """
 
183
        raise NotImplementedError(self.iter_entries_by_dir)
 
184
 
 
185
    def iter_references(self):
 
186
        if self.supports_tree_reference():
 
187
            for path, entry in self.iter_entries_by_dir():
 
188
                if entry.kind == 'tree-reference':
 
189
                    yield path, entry.file_id
 
190
 
 
191
    def kind(self, file_id):
 
192
        raise NotImplementedError("Tree subclass %s must implement kind"
 
193
            % self.__class__.__name__)
 
194
 
 
195
    def stored_kind(self, file_id):
 
196
        """File kind stored for this file_id.
 
197
 
 
198
        May not match kind on disk for working trees.  Always available
 
199
        for versioned files, even when the file itself is missing.
 
200
        """
 
201
        return self.kind(file_id)
 
202
 
 
203
    def path_content_summary(self, path):
 
204
        """Get a summary of the information about path.
 
205
 
 
206
        All the attributes returned are for the canonical form, not the
 
207
        convenient form (if content filters are in use.)
 
208
 
 
209
        :param path: A relative path within the tree.
 
210
        :return: A tuple containing kind, size, exec, sha1-or-link.
 
211
            Kind is always present (see tree.kind()).
 
212
            size is present if kind is file and the size of the 
 
213
                canonical form can be cheaply determined, None otherwise.
 
214
            exec is None unless kind is file and the platform supports the 'x'
 
215
                bit.
 
216
            sha1-or-link is the link target if kind is symlink, or the sha1 if
 
217
                it can be obtained without reading the file.
 
218
        """
 
219
        raise NotImplementedError(self.path_content_summary)
 
220
 
 
221
    def get_reference_revision(self, file_id, path=None):
 
222
        raise NotImplementedError("Tree subclass %s must implement "
 
223
                                  "get_reference_revision"
 
224
            % self.__class__.__name__)
 
225
 
 
226
    def _comparison_data(self, entry, path):
 
227
        """Return a tuple of kind, executable, stat_value for a file.
 
228
 
 
229
        entry may be None if there is no inventory entry for the file, but
 
230
        path must always be supplied.
 
231
 
 
232
        kind is None if there is no file present (even if an inventory id is
 
233
        present).  executable is False for non-file entries.
 
234
        """
 
235
        raise NotImplementedError(self._comparison_data)
 
236
 
 
237
    def _file_size(self, entry, stat_value):
 
238
        raise NotImplementedError(self._file_size)
 
239
 
 
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
        """
 
246
        raise NotImplementedError(self.get_file)
 
247
 
 
248
    def get_file_with_stat(self, file_id, path=None):
 
249
        """Get a file handle and stat object for file_id.
 
250
 
 
251
        The default implementation returns (self.get_file, None) for backwards
 
252
        compatibility.
 
253
 
 
254
        :param file_id: The file id to read.
 
255
        :param path: The path of the file, if it is known.
 
256
        :return: A tuple (file_handle, stat_value_or_None). If the tree has
 
257
            no stat facility, or need for a stat cache feedback during commit,
 
258
            it may return None for the second element of the tuple.
 
259
        """
 
260
        return (self.get_file(file_id, path), None)
 
261
 
 
262
    def get_file_text(self, file_id, path=None):
 
263
        """Return the byte content of a file.
 
264
 
 
265
        :param file_id: The file_id of the file.
 
266
        :param path: The path of the file.
 
267
        If both file_id and path are supplied, an implementation may use
 
268
        either one.
 
269
        """
 
270
        my_file = self.get_file(file_id, path)
 
271
        try:
 
272
            return my_file.read()
 
273
        finally:
 
274
            my_file.close()
 
275
 
 
276
    def get_file_lines(self, file_id, path=None):
 
277
        """Return the content of a file, as lines.
 
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
        return osutils.split_lines(self.get_file_text(file_id, path))
 
285
 
 
286
    def get_file_sha1(self, file_id, path=None):
 
287
        """Return the SHA1 file for a file.
 
288
 
 
289
        :param file_id: The handle for this file.
 
290
        :param path: The path that this file can be found at.
 
291
            These must point to the same object.
 
292
        """
 
293
        raise NotImplementedError(self.get_file_sha1)
 
294
 
 
295
    def get_file_mtime(self, file_id, path=None):
 
296
        """Return the modification time for a file.
 
297
 
 
298
        :param file_id: The handle for this file.
 
299
        :param path: The path that this file can be found at.
 
300
            These must point to the same object.
 
301
        """
 
302
        raise NotImplementedError(self.get_file_mtime)
 
303
 
 
304
    def get_file_size(self, file_id):
 
305
        """Return the size of a file in bytes.
 
306
 
 
307
        This applies only to regular files.  If invoked on directories or
 
308
        symlinks, it will return None.
 
309
        :param file_id: The file-id of the file
 
310
        """
 
311
        raise NotImplementedError(self.get_file_size)
 
312
 
 
313
    def get_file_by_path(self, path):
 
314
        raise NotImplementedError(self.get_file_by_path)
 
315
 
 
316
    def is_executable(self, file_id, path=None):
 
317
        """Check if a file is executable.
 
318
 
 
319
        :param file_id: The handle for this file.
 
320
        :param path: The path that this file can be found at.
 
321
            These must point to the same object.
 
322
        """
 
323
        raise NotImplementedError(self.is_executable)
 
324
 
 
325
    def iter_files_bytes(self, desired_files):
 
326
        """Iterate through file contents.
 
327
 
 
328
        Files will not necessarily be returned in the order they occur in
 
329
        desired_files.  No specific order is guaranteed.
 
330
 
 
331
        Yields pairs of identifier, bytes_iterator.  identifier is an opaque
 
332
        value supplied by the caller as part of desired_files.  It should
 
333
        uniquely identify the file version in the caller's context.  (Examples:
 
334
        an index number or a TreeTransform trans_id.)
 
335
 
 
336
        bytes_iterator is an iterable of bytestrings for the file.  The
 
337
        kind of iterable and length of the bytestrings are unspecified, but for
 
338
        this implementation, it is a tuple containing a single bytestring with
 
339
        the complete text of the file.
 
340
 
 
341
        :param desired_files: a list of (file_id, identifier) pairs
 
342
        """
 
343
        for file_id, identifier in desired_files:
 
344
            # We wrap the string in a tuple so that we can return an iterable
 
345
            # of bytestrings.  (Technically, a bytestring is also an iterable
 
346
            # of bytestrings, but iterating through each character is not
 
347
            # performant.)
 
348
            cur_file = (self.get_file_text(file_id),)
 
349
            yield identifier, cur_file
 
350
 
 
351
    def get_symlink_target(self, file_id):
 
352
        """Get the target for a given file_id.
 
353
 
 
354
        It is assumed that the caller already knows that file_id is referencing
 
355
        a symlink.
 
356
        :param file_id: Handle for the symlink entry.
 
357
        :return: The path the symlink points to.
 
358
        """
 
359
        raise NotImplementedError(self.get_symlink_target)
 
360
 
 
361
 
 
362
    def get_root_id(self):
 
363
        """Return the file_id for the root of this tree."""
 
364
        raise NotImplementedError(self.get_root_id)
 
365
 
 
366
    def annotate_iter(self, file_id,
 
367
                      default_revision=_mod_revision.CURRENT_REVISION):
 
368
        """Return an iterator of revision_id, line tuples.
 
369
 
 
370
        For working trees (and mutable trees in general), the special
 
371
        revision_id 'current:' will be used for lines that are new in this
 
372
        tree, e.g. uncommitted changes.
 
373
        :param file_id: The file to produce an annotated version from
 
374
        :param default_revision: For lines that don't match a basis, mark them
 
375
            with this revision id. Not all implementations will make use of
 
376
            this value.
 
377
        """
 
378
        raise NotImplementedError(self.annotate_iter)
 
379
 
 
380
    def _get_plan_merge_data(self, file_id, other, base):
 
381
        from bzrlib import versionedfile
 
382
        vf = versionedfile._PlanMergeVersionedFile(file_id)
 
383
        last_revision_a = self._get_file_revision(file_id, vf, 'this:')
 
384
        last_revision_b = other._get_file_revision(file_id, vf, 'other:')
 
385
        if base is None:
 
386
            last_revision_base = None
 
387
        else:
 
388
            last_revision_base = base._get_file_revision(file_id, vf, 'base:')
 
389
        return vf, last_revision_a, last_revision_b, last_revision_base
 
390
 
 
391
    def plan_file_merge(self, file_id, other, base=None):
 
392
        """Generate a merge plan based on annotations.
 
393
 
 
394
        If the file contains uncommitted changes in this tree, they will be
 
395
        attributed to the 'current:' pseudo-revision.  If the file contains
 
396
        uncommitted changes in the other tree, they will be assigned to the
 
397
        'other:' pseudo-revision.
 
398
        """
 
399
        data = self._get_plan_merge_data(file_id, other, base)
 
400
        vf, last_revision_a, last_revision_b, last_revision_base = data
 
401
        return vf.plan_merge(last_revision_a, last_revision_b,
 
402
                             last_revision_base)
 
403
 
 
404
    def plan_file_lca_merge(self, file_id, other, base=None):
 
405
        """Generate a merge plan based lca-newness.
 
406
 
 
407
        If the file contains uncommitted changes in this tree, they will be
 
408
        attributed to the 'current:' pseudo-revision.  If the file contains
 
409
        uncommitted changes in the other tree, they will be assigned to the
 
410
        'other:' pseudo-revision.
 
411
        """
 
412
        data = self._get_plan_merge_data(file_id, other, base)
 
413
        vf, last_revision_a, last_revision_b, last_revision_base = data
 
414
        return vf.plan_lca_merge(last_revision_a, last_revision_b,
 
415
                                 last_revision_base)
 
416
 
 
417
    def _iter_parent_trees(self):
 
418
        """Iterate through parent trees, defaulting to Tree.revision_tree."""
 
419
        for revision_id in self.get_parent_ids():
 
420
            try:
 
421
                yield self.revision_tree(revision_id)
 
422
            except errors.NoSuchRevisionInTree:
 
423
                yield self.repository.revision_tree(revision_id)
 
424
 
 
425
    @staticmethod
 
426
    def _file_revision(revision_tree, file_id):
 
427
        """Determine the revision associated with a file in a given tree."""
 
428
        # FIXME: Shouldn't this be a RevisionTree method?
 
429
        revision_tree.lock_read()
 
430
        try:
 
431
            return revision_tree.inventory[file_id].revision
 
432
        finally:
 
433
            revision_tree.unlock()
 
434
 
 
435
    def _get_file_revision(self, file_id, vf, tree_revision):
 
436
        """Ensure that file_id, tree_revision is in vf to plan the merge."""
 
437
 
 
438
        if getattr(self, '_repository', None) is None:
 
439
            last_revision = tree_revision
 
440
            parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
 
441
                self._iter_parent_trees()]
 
442
            vf.add_lines((file_id, last_revision), parent_keys,
 
443
                         self.get_file_lines(file_id))
 
444
            repo = self.branch.repository
 
445
            base_vf = repo.texts
 
446
        else:
 
447
            last_revision = self._file_revision(self, file_id)
 
448
            base_vf = self._repository.texts
 
449
        if base_vf not in vf.fallback_versionedfiles:
 
450
            vf.fallback_versionedfiles.append(base_vf)
 
451
        return last_revision
 
452
 
 
453
    def _check_retrieved(self, ie, f):
 
454
        if not __debug__:
 
455
            return
 
456
        fp = osutils.fingerprint_file(f)
 
457
        f.seek(0)
 
458
 
 
459
        if ie.text_size is not None:
 
460
            if ie.text_size != fp['size']:
 
461
                raise errors.BzrError(
 
462
                        "mismatched size for file %r in %r" %
 
463
                        (ie.file_id, self._store),
 
464
                        ["inventory expects %d bytes" % ie.text_size,
 
465
                         "file is actually %d bytes" % fp['size'],
 
466
                         "store is probably damaged/corrupt"])
 
467
 
 
468
        if ie.text_sha1 != fp['sha1']:
 
469
            raise errors.BzrError("wrong SHA-1 for file %r in %r" %
 
470
                    (ie.file_id, self._store),
 
471
                    ["inventory expects %s" % ie.text_sha1,
 
472
                     "file is actually %s" % fp['sha1'],
 
473
                     "store is probably damaged/corrupt"])
 
474
 
 
475
    def path2id(self, path):
 
476
        """Return the id for path in this tree."""
 
477
        raise NotImplementedError(self.path2id)
 
478
 
 
479
    def paths2ids(self, paths, trees=[], require_versioned=True):
 
480
        """Return all the ids that can be reached by walking from paths.
 
481
 
 
482
        Each path is looked up in this tree and any extras provided in
 
483
        trees, and this is repeated recursively: the children in an extra tree
 
484
        of a directory that has been renamed under a provided path in this tree
 
485
        are all returned, even if none exist under a provided path in this
 
486
        tree, and vice versa.
 
487
 
 
488
        :param paths: An iterable of paths to start converting to ids from.
 
489
            Alternatively, if paths is None, no ids should be calculated and None
 
490
            will be returned. This is offered to make calling the api unconditional
 
491
            for code that *might* take a list of files.
 
492
        :param trees: Additional trees to consider.
 
493
        :param require_versioned: If False, do not raise NotVersionedError if
 
494
            an element of paths is not versioned in this tree and all of trees.
 
495
        """
 
496
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
 
497
 
 
498
    def iter_children(self, file_id):
 
499
        entry = self.iter_entries_by_dir([file_id]).next()[1]
 
500
        for child in getattr(entry, 'children', {}).itervalues():
 
501
            yield child.file_id
 
502
 
 
503
    def lock_read(self):
 
504
        """Lock this tree for multiple read only operations.
 
505
        
 
506
        :return: A bzrlib.lock.LogicalLockResult.
 
507
        """
 
508
        pass
 
509
 
 
510
    def revision_tree(self, revision_id):
 
511
        """Obtain a revision tree for the revision revision_id.
 
512
 
 
513
        The intention of this method is to allow access to possibly cached
 
514
        tree data. Implementors of this method should raise NoSuchRevision if
 
515
        the tree is not locally available, even if they could obtain the
 
516
        tree via a repository or some other means. Callers are responsible
 
517
        for finding the ultimate source for a revision tree.
 
518
 
 
519
        :param revision_id: The revision_id of the requested tree.
 
520
        :return: A Tree.
 
521
        :raises: NoSuchRevision if the tree cannot be obtained.
 
522
        """
 
523
        raise errors.NoSuchRevisionInTree(self, revision_id)
 
524
 
 
525
    def unknowns(self):
 
526
        """What files are present in this tree and unknown.
 
527
 
 
528
        :return: an iterator over the unknown files.
 
529
        """
 
530
        return iter([])
 
531
 
 
532
    def unlock(self):
 
533
        pass
 
534
 
 
535
    def filter_unversioned_files(self, paths):
 
536
        """Filter out paths that are versioned.
 
537
 
 
538
        :return: set of paths.
 
539
        """
 
540
        raise NotImplementedError(self.filter_unversioned_files)
 
541
 
 
542
    def walkdirs(self, prefix=""):
 
543
        """Walk the contents of this tree from path down.
 
544
 
 
545
        This yields all the data about the contents of a directory at a time.
 
546
        After each directory has been yielded, if the caller has mutated the
 
547
        list to exclude some directories, they are then not descended into.
 
548
 
 
549
        The data yielded is of the form:
 
550
        ((directory-relpath, directory-path-from-root, directory-fileid),
 
551
        [(relpath, basename, kind, lstat, path_from_tree_root, file_id,
 
552
          versioned_kind), ...]),
 
553
         - directory-relpath is the containing dirs relpath from prefix
 
554
         - directory-path-from-root is the containing dirs path from /
 
555
         - directory-fileid is the id of the directory if it is versioned.
 
556
         - relpath is the relative path within the subtree being walked.
 
557
         - basename is the basename
 
558
         - kind is the kind of the file now. If unknonwn then the file is not
 
559
           present within the tree - but it may be recorded as versioned. See
 
560
           versioned_kind.
 
561
         - lstat is the stat data *if* the file was statted.
 
562
         - path_from_tree_root is the path from the root of the tree.
 
563
         - file_id is the file_id if the entry is versioned.
 
564
         - versioned_kind is the kind of the file as last recorded in the
 
565
           versioning system. If 'unknown' the file is not versioned.
 
566
        One of 'kind' and 'versioned_kind' must not be 'unknown'.
 
567
 
 
568
        :param prefix: Start walking from prefix within the tree rather than
 
569
        at the root. This allows one to walk a subtree but get paths that are
 
570
        relative to a tree rooted higher up.
 
571
        :return: an iterator over the directory data.
 
572
        """
 
573
        raise NotImplementedError(self.walkdirs)
 
574
 
 
575
    def supports_content_filtering(self):
 
576
        return False
 
577
 
 
578
    def _content_filter_stack(self, path=None, file_id=None):
 
579
        """The stack of content filters for a path if filtering is supported.
 
580
 
 
581
        Readers will be applied in first-to-last order.
 
582
        Writers will be applied in last-to-first order.
 
583
        Either the path or the file-id needs to be provided.
 
584
 
 
585
        :param path: path relative to the root of the tree
 
586
            or None if unknown
 
587
        :param file_id: file_id or None if unknown
 
588
        :return: the list of filters - [] if there are none
 
589
        """
 
590
        filter_pref_names = filters._get_registered_names()
 
591
        if len(filter_pref_names) == 0:
 
592
            return []
 
593
        if path is None:
 
594
            path = self.id2path(file_id)
 
595
        prefs = self.iter_search_rules([path], filter_pref_names).next()
 
596
        stk = filters._get_filter_stack_for(prefs)
 
597
        if 'filters' in debug.debug_flags:
 
598
            trace.note("*** %s content-filter: %s => %r" % (path,prefs,stk))
 
599
        return stk
 
600
 
 
601
    def _content_filter_stack_provider(self):
 
602
        """A function that returns a stack of ContentFilters.
 
603
 
 
604
        The function takes a path (relative to the top of the tree) and a
 
605
        file-id as parameters.
 
606
 
 
607
        :return: None if content filtering is not supported by this tree.
 
608
        """
 
609
        if self.supports_content_filtering():
 
610
            return lambda path, file_id: \
 
611
                    self._content_filter_stack(path, file_id)
 
612
        else:
 
613
            return None
 
614
 
 
615
    def iter_search_rules(self, path_names, pref_names=None,
 
616
        _default_searcher=None):
 
617
        """Find the preferences for filenames in a tree.
 
618
 
 
619
        :param path_names: an iterable of paths to find attributes for.
 
620
          Paths are given relative to the root of the tree.
 
621
        :param pref_names: the list of preferences to lookup - None for all
 
622
        :param _default_searcher: private parameter to assist testing - don't use
 
623
        :return: an iterator of tuple sequences, one per path-name.
 
624
          See _RulesSearcher.get_items for details on the tuple sequence.
 
625
        """
 
626
        if _default_searcher is None:
 
627
            _default_searcher = rules._per_user_searcher
 
628
        searcher = self._get_rules_searcher(_default_searcher)
 
629
        if searcher is not None:
 
630
            if pref_names is not None:
 
631
                for path in path_names:
 
632
                    yield searcher.get_selected_items(path, pref_names)
 
633
            else:
 
634
                for path in path_names:
 
635
                    yield searcher.get_items(path)
 
636
 
 
637
    def _get_rules_searcher(self, default_searcher):
 
638
        """Get the RulesSearcher for this tree given the default one."""
 
639
        searcher = default_searcher
 
640
        return searcher
 
641
 
 
642
 
 
643
class InventoryTree(Tree):
 
644
    """A tree that relies on an inventory for its metadata.
 
645
 
 
646
    Trees contain an `Inventory` object, and also know how to retrieve
 
647
    file texts mentioned in the inventory, either from a working
 
648
    directory or from a store.
 
649
 
 
650
    It is possible for trees to contain files that are not described
 
651
    in their inventory or vice versa; for this use `filenames()`.
 
652
 
 
653
    Subclasses should set the _inventory attribute, which is considered
 
654
    private to external API users.
 
655
    """
 
656
 
 
657
    def get_canonical_inventory_paths(self, paths):
 
658
        """Like get_canonical_inventory_path() but works on multiple items.
 
659
 
 
660
        :param paths: A sequence of paths relative to the root of the tree.
 
661
        :return: A list of paths, with each item the corresponding input path
 
662
        adjusted to account for existing elements that match case
 
663
        insensitively.
 
664
        """
 
665
        return list(self._yield_canonical_inventory_paths(paths))
 
666
 
 
667
    def get_canonical_inventory_path(self, path):
 
668
        """Returns the first inventory item that case-insensitively matches path.
 
669
 
 
670
        If a path matches exactly, it is returned. If no path matches exactly
 
671
        but more than one path matches case-insensitively, it is implementation
 
672
        defined which is returned.
 
673
 
 
674
        If no path matches case-insensitively, the input path is returned, but
 
675
        with as many path entries that do exist changed to their canonical
 
676
        form.
 
677
 
 
678
        If you need to resolve many names from the same tree, you should
 
679
        use get_canonical_inventory_paths() to avoid O(N) behaviour.
 
680
 
 
681
        :param path: A paths relative to the root of the tree.
 
682
        :return: The input path adjusted to account for existing elements
 
683
        that match case insensitively.
 
684
        """
 
685
        return self._yield_canonical_inventory_paths([path]).next()
 
686
 
 
687
    def _yield_canonical_inventory_paths(self, paths):
 
688
        for path in paths:
 
689
            # First, if the path as specified exists exactly, just use it.
 
690
            if self.path2id(path) is not None:
 
691
                yield path
 
692
                continue
 
693
            # go walkin...
 
694
            cur_id = self.get_root_id()
 
695
            cur_path = ''
 
696
            bit_iter = iter(path.split("/"))
 
697
            for elt in bit_iter:
 
698
                lelt = elt.lower()
 
699
                new_path = None
 
700
                for child in self.iter_children(cur_id):
 
701
                    try:
 
702
                        # XXX: it seem like if the child is known to be in the
 
703
                        # tree, we shouldn't need to go from its id back to
 
704
                        # its path -- mbp 2010-02-11
 
705
                        #
 
706
                        # XXX: it seems like we could be more efficient
 
707
                        # by just directly looking up the original name and
 
708
                        # only then searching all children; also by not
 
709
                        # chopping paths so much. -- mbp 2010-02-11
 
710
                        child_base = os.path.basename(self.id2path(child))
 
711
                        if (child_base == elt):
 
712
                            # if we found an exact match, we can stop now; if
 
713
                            # we found an approximate match we need to keep
 
714
                            # searching because there might be an exact match
 
715
                            # later.  
 
716
                            cur_id = child
 
717
                            new_path = osutils.pathjoin(cur_path, child_base)
 
718
                            break
 
719
                        elif child_base.lower() == lelt:
 
720
                            cur_id = child
 
721
                            new_path = osutils.pathjoin(cur_path, child_base)
 
722
                    except errors.NoSuchId:
 
723
                        # before a change is committed we can see this error...
 
724
                        continue
 
725
                if new_path:
 
726
                    cur_path = new_path
 
727
                else:
 
728
                    # got to the end of this directory and no entries matched.
 
729
                    # Return what matched so far, plus the rest as specified.
 
730
                    cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
 
731
                    break
 
732
            yield cur_path
 
733
        # all done.
 
734
 
 
735
    def _get_inventory(self):
 
736
        return self._inventory
 
737
 
 
738
    inventory = property(_get_inventory,
 
739
                         doc="Inventory of this Tree")
 
740
 
 
741
    @needs_read_lock
 
742
    def path2id(self, path):
 
743
        """Return the id for path in this tree."""
 
744
        return self._inventory.path2id(path)
 
745
 
 
746
    def id2path(self, file_id):
 
747
        """Return the path for a file id.
 
748
 
 
749
        :raises NoSuchId:
 
750
        """
 
751
        return self.inventory.id2path(file_id)
 
752
 
 
753
    def has_id(self, file_id):
 
754
        return self.inventory.has_id(file_id)
 
755
 
 
756
    def has_or_had_id(self, file_id):
 
757
        return self.inventory.has_id(file_id)
 
758
 
 
759
    def __iter__(self):
 
760
        return iter(self.inventory)
 
761
 
 
762
    def filter_unversioned_files(self, paths):
 
763
        """Filter out paths that are versioned.
 
764
 
 
765
        :return: set of paths.
 
766
        """
 
767
        # NB: we specifically *don't* call self.has_filename, because for
 
768
        # WorkingTrees that can indicate files that exist on disk but that
 
769
        # are not versioned.
 
770
        pred = self.inventory.has_filename
 
771
        return set((p for p in paths if not pred(p)))
 
772
 
 
773
    @needs_read_lock
 
774
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
775
        """Walk the tree in 'by_dir' order.
 
776
 
 
777
        This will yield each entry in the tree as a (path, entry) tuple.
 
778
        The order that they are yielded is:
 
779
 
 
780
        See Tree.iter_entries_by_dir for details.
 
781
 
 
782
        :param yield_parents: If True, yield the parents from the root leading
 
783
            down to specific_file_ids that have been requested. This has no
 
784
            impact if specific_file_ids is None.
 
785
        """
 
786
        return self.inventory.iter_entries_by_dir(
 
787
            specific_file_ids=specific_file_ids, yield_parents=yield_parents)
 
788
 
 
789
    def get_file_by_path(self, path):
 
790
        return self.get_file(self._inventory.path2id(path), path)
 
791
 
 
792
 
 
793
######################################################################
 
794
# diff
 
795
 
 
796
# TODO: Merge these two functions into a single one that can operate
 
797
# on either a whole tree or a set of files.
 
798
 
 
799
# TODO: Return the diff in order by filename, not by category or in
 
800
# random order.  Can probably be done by lock-stepping through the
 
801
# filenames from both trees.
 
802
 
 
803
 
 
804
def file_status(filename, old_tree, new_tree):
 
805
    """Return single-letter status, old and new names for a file.
 
806
 
 
807
    The complexity here is in deciding how to represent renames;
 
808
    many complex cases are possible.
 
809
    """
 
810
    old_inv = old_tree.inventory
 
811
    new_inv = new_tree.inventory
 
812
    new_id = new_inv.path2id(filename)
 
813
    old_id = old_inv.path2id(filename)
 
814
 
 
815
    if not new_id and not old_id:
 
816
        # easy: doesn't exist in either; not versioned at all
 
817
        if new_tree.is_ignored(filename):
 
818
            return 'I', None, None
 
819
        else:
 
820
            return '?', None, None
 
821
    elif new_id:
 
822
        # There is now a file of this name, great.
 
823
        pass
 
824
    else:
 
825
        # There is no longer a file of this name, but we can describe
 
826
        # what happened to the file that used to have
 
827
        # this name.  There are two possibilities: either it was
 
828
        # deleted entirely, or renamed.
 
829
        if new_inv.has_id(old_id):
 
830
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
 
831
        else:
 
832
            return 'D', old_inv.id2path(old_id), None
 
833
 
 
834
    # if the file_id is new in this revision, it is added
 
835
    if new_id and not old_inv.has_id(new_id):
 
836
        return 'A'
 
837
 
 
838
    # if there used to be a file of this name, but that ID has now
 
839
    # disappeared, it is deleted
 
840
    if old_id and not new_inv.has_id(old_id):
 
841
        return 'D'
 
842
 
 
843
    return 'wtf?'
 
844
 
 
845
 
 
846
def find_ids_across_trees(filenames, trees, require_versioned=True):
 
847
    """Find the ids corresponding to specified filenames.
 
848
 
 
849
    All matches in all trees will be used, and all children of matched
 
850
    directories will be used.
 
851
 
 
852
    :param filenames: The filenames to find file_ids for (if None, returns
 
853
        None)
 
854
    :param trees: The trees to find file_ids within
 
855
    :param require_versioned: if true, all specified filenames must occur in
 
856
    at least one tree.
 
857
    :return: a set of file ids for the specified filenames and their children.
 
858
    """
 
859
    if not filenames:
 
860
        return None
 
861
    specified_path_ids = _find_ids_across_trees(filenames, trees,
 
862
        require_versioned)
 
863
    return _find_children_across_trees(specified_path_ids, trees)
 
864
 
 
865
 
 
866
def _find_ids_across_trees(filenames, trees, require_versioned):
 
867
    """Find the ids corresponding to specified filenames.
 
868
 
 
869
    All matches in all trees will be used, but subdirectories are not scanned.
 
870
 
 
871
    :param filenames: The filenames to find file_ids for
 
872
    :param trees: The trees to find file_ids within
 
873
    :param require_versioned: if true, all specified filenames must occur in
 
874
        at least one tree.
 
875
    :return: a set of file ids for the specified filenames
 
876
    """
 
877
    not_versioned = []
 
878
    interesting_ids = set()
 
879
    for tree_path in filenames:
 
880
        not_found = True
 
881
        for tree in trees:
 
882
            file_id = tree.path2id(tree_path)
 
883
            if file_id is not None:
 
884
                interesting_ids.add(file_id)
 
885
                not_found = False
 
886
        if not_found:
 
887
            not_versioned.append(tree_path)
 
888
    if len(not_versioned) > 0 and require_versioned:
 
889
        raise errors.PathsNotVersionedError(not_versioned)
 
890
    return interesting_ids
 
891
 
 
892
 
 
893
def _find_children_across_trees(specified_ids, trees):
 
894
    """Return a set including specified ids and their children.
 
895
 
 
896
    All matches in all trees will be used.
 
897
 
 
898
    :param trees: The trees to find file_ids within
 
899
    :return: a set containing all specified ids and their children
 
900
    """
 
901
    interesting_ids = set(specified_ids)
 
902
    pending = interesting_ids
 
903
    # now handle children of interesting ids
 
904
    # we loop so that we handle all children of each id in both trees
 
905
    while len(pending) > 0:
 
906
        new_pending = set()
 
907
        for file_id in pending:
 
908
            for tree in trees:
 
909
                if not tree.has_or_had_id(file_id):
 
910
                    continue
 
911
                for child_id in tree.iter_children(file_id):
 
912
                    if child_id not in interesting_ids:
 
913
                        new_pending.add(child_id)
 
914
        interesting_ids.update(new_pending)
 
915
        pending = new_pending
 
916
    return interesting_ids
 
917
 
 
918
 
 
919
class InterTree(InterObject):
 
920
    """This class represents operations taking place between two Trees.
 
921
 
 
922
    Its instances have methods like 'compare' and contain references to the
 
923
    source and target trees these operations are to be carried out on.
 
924
 
 
925
    Clients of bzrlib should not need to use InterTree directly, rather they
 
926
    should use the convenience methods on Tree such as 'Tree.compare()' which
 
927
    will pass through to InterTree as appropriate.
 
928
    """
 
929
 
 
930
    # Formats that will be used to test this InterTree. If both are
 
931
    # None, this InterTree will not be tested (e.g. because a complex
 
932
    # setup is required)
 
933
    _matching_from_tree_format = None
 
934
    _matching_to_tree_format = None
 
935
 
 
936
    _optimisers = []
 
937
 
 
938
    def _changes_from_entries(self, source_entry, target_entry,
 
939
        source_path=None, target_path=None):
 
940
        """Generate a iter_changes tuple between source_entry and target_entry.
 
941
 
 
942
        :param source_entry: An inventory entry from self.source, or None.
 
943
        :param target_entry: An inventory entry from self.target, or None.
 
944
        :param source_path: The path of source_entry, if known. If not known
 
945
            it will be looked up.
 
946
        :param target_path: The path of target_entry, if known. If not known
 
947
            it will be looked up.
 
948
        :return: A tuple, item 0 of which is an iter_changes result tuple, and
 
949
            item 1 is True if there are any changes in the result tuple.
 
950
        """
 
951
        if source_entry is None:
 
952
            if target_entry is None:
 
953
                return None
 
954
            file_id = target_entry.file_id
 
955
        else:
 
956
            file_id = source_entry.file_id
 
957
        if source_entry is not None:
 
958
            source_versioned = True
 
959
            source_name = source_entry.name
 
960
            source_parent = source_entry.parent_id
 
961
            if source_path is None:
 
962
                source_path = self.source.id2path(file_id)
 
963
            source_kind, source_executable, source_stat = \
 
964
                self.source._comparison_data(source_entry, source_path)
 
965
        else:
 
966
            source_versioned = False
 
967
            source_name = None
 
968
            source_parent = None
 
969
            source_kind = None
 
970
            source_executable = None
 
971
        if target_entry is not None:
 
972
            target_versioned = True
 
973
            target_name = target_entry.name
 
974
            target_parent = target_entry.parent_id
 
975
            if target_path is None:
 
976
                target_path = self.target.id2path(file_id)
 
977
            target_kind, target_executable, target_stat = \
 
978
                self.target._comparison_data(target_entry, target_path)
 
979
        else:
 
980
            target_versioned = False
 
981
            target_name = None
 
982
            target_parent = None
 
983
            target_kind = None
 
984
            target_executable = None
 
985
        versioned = (source_versioned, target_versioned)
 
986
        kind = (source_kind, target_kind)
 
987
        changed_content = False
 
988
        if source_kind != target_kind:
 
989
            changed_content = True
 
990
        elif source_kind == 'file':
 
991
            if (self.source.get_file_sha1(file_id, source_path, source_stat) !=
 
992
                self.target.get_file_sha1(file_id, target_path, target_stat)):
 
993
                changed_content = True
 
994
        elif source_kind == 'symlink':
 
995
            if (self.source.get_symlink_target(file_id) !=
 
996
                self.target.get_symlink_target(file_id)):
 
997
                changed_content = True
 
998
            # XXX: Yes, the indentation below is wrong. But fixing it broke
 
999
            # test_merge.TestMergerEntriesLCAOnDisk.
 
1000
            # test_nested_tree_subtree_renamed_and_modified. We'll wait for
 
1001
            # the fix from bzr.dev -- vila 2009026
 
1002
            elif source_kind == 'tree-reference':
 
1003
                if (self.source.get_reference_revision(file_id, source_path)
 
1004
                    != self.target.get_reference_revision(file_id, target_path)):
 
1005
                    changed_content = True
 
1006
        parent = (source_parent, target_parent)
 
1007
        name = (source_name, target_name)
 
1008
        executable = (source_executable, target_executable)
 
1009
        if (changed_content is not False or versioned[0] != versioned[1]
 
1010
            or parent[0] != parent[1] or name[0] != name[1] or
 
1011
            executable[0] != executable[1]):
 
1012
            changes = True
 
1013
        else:
 
1014
            changes = False
 
1015
        return (file_id, (source_path, target_path), changed_content,
 
1016
                versioned, parent, name, kind, executable), changes
 
1017
 
 
1018
    @needs_read_lock
 
1019
    def compare(self, want_unchanged=False, specific_files=None,
 
1020
        extra_trees=None, require_versioned=False, include_root=False,
 
1021
        want_unversioned=False):
 
1022
        """Return the changes from source to target.
 
1023
 
 
1024
        :return: A TreeDelta.
 
1025
        :param specific_files: An optional list of file paths to restrict the
 
1026
            comparison to. When mapping filenames to ids, all matches in all
 
1027
            trees (including optional extra_trees) are used, and all children of
 
1028
            matched directories are included.
 
1029
        :param want_unchanged: An optional boolean requesting the inclusion of
 
1030
            unchanged entries in the result.
 
1031
        :param extra_trees: An optional list of additional trees to use when
 
1032
            mapping the contents of specific_files (paths) to file_ids.
 
1033
        :param require_versioned: An optional boolean (defaults to False). When
 
1034
            supplied and True all the 'specific_files' must be versioned, or
 
1035
            a PathsNotVersionedError will be thrown.
 
1036
        :param want_unversioned: Scan for unversioned paths.
 
1037
        """
 
1038
        trees = (self.source,)
 
1039
        if extra_trees is not None:
 
1040
            trees = trees + tuple(extra_trees)
 
1041
        # target is usually the newer tree:
 
1042
        specific_file_ids = self.target.paths2ids(specific_files, trees,
 
1043
            require_versioned=require_versioned)
 
1044
        if specific_files and not specific_file_ids:
 
1045
            # All files are unversioned, so just return an empty delta
 
1046
            # _compare_trees would think we want a complete delta
 
1047
            result = delta.TreeDelta()
 
1048
            fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
 
1049
            result.unversioned = [(path, None,
 
1050
                self.target._comparison_data(fake_entry, path)[0]) for path in
 
1051
                specific_files]
 
1052
            return result
 
1053
        return delta._compare_trees(self.source, self.target, want_unchanged,
 
1054
            specific_files, include_root, extra_trees=extra_trees,
 
1055
            require_versioned=require_versioned,
 
1056
            want_unversioned=want_unversioned)
 
1057
 
 
1058
    def iter_changes(self, include_unchanged=False,
 
1059
                      specific_files=None, pb=None, extra_trees=[],
 
1060
                      require_versioned=True, want_unversioned=False):
 
1061
        """Generate an iterator of changes between trees.
 
1062
 
 
1063
        A tuple is returned:
 
1064
        (file_id, (path_in_source, path_in_target),
 
1065
         changed_content, versioned, parent, name, kind,
 
1066
         executable)
 
1067
 
 
1068
        Changed_content is True if the file's content has changed.  This
 
1069
        includes changes to its kind, and to a symlink's target.
 
1070
 
 
1071
        versioned, parent, name, kind, executable are tuples of (from, to).
 
1072
        If a file is missing in a tree, its kind is None.
 
1073
 
 
1074
        Iteration is done in parent-to-child order, relative to the target
 
1075
        tree.
 
1076
 
 
1077
        There is no guarantee that all paths are in sorted order: the
 
1078
        requirement to expand the search due to renames may result in children
 
1079
        that should be found early being found late in the search, after
 
1080
        lexically later results have been returned.
 
1081
        :param require_versioned: Raise errors.PathsNotVersionedError if a
 
1082
            path in the specific_files list is not versioned in one of
 
1083
            source, target or extra_trees.
 
1084
        :param specific_files: An optional list of file paths to restrict the
 
1085
            comparison to. When mapping filenames to ids, all matches in all
 
1086
            trees (including optional extra_trees) are used, and all children
 
1087
            of matched directories are included. The parents in the target tree
 
1088
            of the specific files up to and including the root of the tree are
 
1089
            always evaluated for changes too.
 
1090
        :param want_unversioned: Should unversioned files be returned in the
 
1091
            output. An unversioned file is defined as one with (False, False)
 
1092
            for the versioned pair.
 
1093
        """
 
1094
        lookup_trees = [self.source]
 
1095
        if extra_trees:
 
1096
             lookup_trees.extend(extra_trees)
 
1097
        # The ids of items we need to examine to insure delta consistency.
 
1098
        precise_file_ids = set()
 
1099
        changed_file_ids = []
 
1100
        if specific_files == []:
 
1101
            specific_file_ids = []
 
1102
        else:
 
1103
            specific_file_ids = self.target.paths2ids(specific_files,
 
1104
                lookup_trees, require_versioned=require_versioned)
 
1105
        if specific_files is not None:
 
1106
            # reparented or added entries must have their parents included
 
1107
            # so that valid deltas can be created. The seen_parents set
 
1108
            # tracks the parents that we need to have.
 
1109
            # The seen_dirs set tracks directory entries we've yielded.
 
1110
            # After outputting version object in to_entries we set difference
 
1111
            # the two seen sets and start checking parents.
 
1112
            seen_parents = set()
 
1113
            seen_dirs = set()
 
1114
        if want_unversioned:
 
1115
            all_unversioned = sorted([(p.split('/'), p) for p in
 
1116
                                     self.target.extras()
 
1117
                if specific_files is None or
 
1118
                    osutils.is_inside_any(specific_files, p)])
 
1119
            all_unversioned = collections.deque(all_unversioned)
 
1120
        else:
 
1121
            all_unversioned = collections.deque()
 
1122
        to_paths = {}
 
1123
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
 
1124
            specific_file_ids=specific_file_ids))
 
1125
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
 
1126
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
 
1127
            specific_file_ids=specific_file_ids))
 
1128
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
 
1129
        entry_count = 0
 
1130
        # the unversioned path lookup only occurs on real trees - where there
 
1131
        # can be extras. So the fake_entry is solely used to look up
 
1132
        # executable it values when execute is not supported.
 
1133
        fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
 
1134
        for target_path, target_entry in to_entries_by_dir:
 
1135
            while (all_unversioned and
 
1136
                all_unversioned[0][0] < target_path.split('/')):
 
1137
                unversioned_path = all_unversioned.popleft()
 
1138
                target_kind, target_executable, target_stat = \
 
1139
                    self.target._comparison_data(fake_entry, unversioned_path[1])
 
1140
                yield (None, (None, unversioned_path[1]), True, (False, False),
 
1141
                    (None, None),
 
1142
                    (None, unversioned_path[0][-1]),
 
1143
                    (None, target_kind),
 
1144
                    (None, target_executable))
 
1145
            source_path, source_entry = from_data.get(target_entry.file_id,
 
1146
                (None, None))
 
1147
            result, changes = self._changes_from_entries(source_entry,
 
1148
                target_entry, source_path=source_path, target_path=target_path)
 
1149
            to_paths[result[0]] = result[1][1]
 
1150
            entry_count += 1
 
1151
            if result[3][0]:
 
1152
                entry_count += 1
 
1153
            if pb is not None:
 
1154
                pb.update('comparing files', entry_count, num_entries)
 
1155
            if changes or include_unchanged:
 
1156
                if specific_file_ids is not None:
 
1157
                    new_parent_id = result[4][1]
 
1158
                    precise_file_ids.add(new_parent_id)
 
1159
                    changed_file_ids.append(result[0])
 
1160
                yield result
 
1161
            # Ensure correct behaviour for reparented/added specific files.
 
1162
            if specific_files is not None:
 
1163
                # Record output dirs
 
1164
                if result[6][1] == 'directory':
 
1165
                    seen_dirs.add(result[0])
 
1166
                # Record parents of reparented/added entries.
 
1167
                versioned = result[3]
 
1168
                parents = result[4]
 
1169
                if not versioned[0] or parents[0] != parents[1]:
 
1170
                    seen_parents.add(parents[1])
 
1171
        while all_unversioned:
 
1172
            # yield any trailing unversioned paths
 
1173
            unversioned_path = all_unversioned.popleft()
 
1174
            to_kind, to_executable, to_stat = \
 
1175
                self.target._comparison_data(fake_entry, unversioned_path[1])
 
1176
            yield (None, (None, unversioned_path[1]), True, (False, False),
 
1177
                (None, None),
 
1178
                (None, unversioned_path[0][-1]),
 
1179
                (None, to_kind),
 
1180
                (None, to_executable))
 
1181
        # Yield all remaining source paths
 
1182
        for path, from_entry in from_entries_by_dir:
 
1183
            file_id = from_entry.file_id
 
1184
            if file_id in to_paths:
 
1185
                # already returned
 
1186
                continue
 
1187
            if not self.target.has_id(file_id):
 
1188
                # common case - paths we have not emitted are not present in
 
1189
                # target.
 
1190
                to_path = None
 
1191
            else:
 
1192
                to_path = self.target.id2path(file_id)
 
1193
            entry_count += 1
 
1194
            if pb is not None:
 
1195
                pb.update('comparing files', entry_count, num_entries)
 
1196
            versioned = (True, False)
 
1197
            parent = (from_entry.parent_id, None)
 
1198
            name = (from_entry.name, None)
 
1199
            from_kind, from_executable, stat_value = \
 
1200
                self.source._comparison_data(from_entry, path)
 
1201
            kind = (from_kind, None)
 
1202
            executable = (from_executable, None)
 
1203
            changed_content = from_kind is not None
 
1204
            # the parent's path is necessarily known at this point.
 
1205
            changed_file_ids.append(file_id)
 
1206
            yield(file_id, (path, to_path), changed_content, versioned, parent,
 
1207
                  name, kind, executable)
 
1208
        changed_file_ids = set(changed_file_ids)
 
1209
        if specific_file_ids is not None:
 
1210
            for result in self._handle_precise_ids(precise_file_ids,
 
1211
                changed_file_ids):
 
1212
                yield result
 
1213
 
 
1214
    def _get_entry(self, tree, file_id):
 
1215
        """Get an inventory entry from a tree, with missing entries as None.
 
1216
 
 
1217
        If the tree raises NotImplementedError on accessing .inventory, then
 
1218
        this is worked around using iter_entries_by_dir on just the file id
 
1219
        desired.
 
1220
 
 
1221
        :param tree: The tree to lookup the entry in.
 
1222
        :param file_id: The file_id to lookup.
 
1223
        """
 
1224
        try:
 
1225
            inventory = tree.inventory
 
1226
        except NotImplementedError:
 
1227
            # No inventory available.
 
1228
            try:
 
1229
                iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
 
1230
                return iterator.next()[1]
 
1231
            except StopIteration:
 
1232
                return None
 
1233
        else:
 
1234
            try:
 
1235
                return inventory[file_id]
 
1236
            except errors.NoSuchId:
 
1237
                return None
 
1238
 
 
1239
    def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
 
1240
        discarded_changes=None):
 
1241
        """Fill out a partial iter_changes to be consistent.
 
1242
 
 
1243
        :param precise_file_ids: The file ids of parents that were seen during
 
1244
            the iter_changes.
 
1245
        :param changed_file_ids: The file ids of already emitted items.
 
1246
        :param discarded_changes: An optional dict of precalculated
 
1247
            iter_changes items which the partial iter_changes had not output
 
1248
            but had calculated.
 
1249
        :return: A generator of iter_changes items to output.
 
1250
        """
 
1251
        # process parents of things that had changed under the users
 
1252
        # requested paths to prevent incorrect paths or parent ids which
 
1253
        # aren't in the tree.
 
1254
        while precise_file_ids:
 
1255
            precise_file_ids.discard(None)
 
1256
            # Don't emit file_ids twice
 
1257
            precise_file_ids.difference_update(changed_file_ids)
 
1258
            if not precise_file_ids:
 
1259
                break
 
1260
            # If the there was something at a given output path in source, we
 
1261
            # have to include the entry from source in the delta, or we would
 
1262
            # be putting this entry into a used path.
 
1263
            paths = []
 
1264
            for parent_id in precise_file_ids:
 
1265
                try:
 
1266
                    paths.append(self.target.id2path(parent_id))
 
1267
                except errors.NoSuchId:
 
1268
                    # This id has been dragged in from the source by delta
 
1269
                    # expansion and isn't present in target at all: we don't
 
1270
                    # need to check for path collisions on it.
 
1271
                    pass
 
1272
            for path in paths:
 
1273
                old_id = self.source.path2id(path)
 
1274
                precise_file_ids.add(old_id)
 
1275
            precise_file_ids.discard(None)
 
1276
            current_ids = precise_file_ids
 
1277
            precise_file_ids = set()
 
1278
            # We have to emit all of precise_file_ids that have been altered.
 
1279
            # We may have to output the children of some of those ids if any
 
1280
            # directories have stopped being directories.
 
1281
            for file_id in current_ids:
 
1282
                # Examine file_id
 
1283
                if discarded_changes:
 
1284
                    result = discarded_changes.get(file_id)
 
1285
                    old_entry = None
 
1286
                else:
 
1287
                    result = None
 
1288
                if result is None:
 
1289
                    old_entry = self._get_entry(self.source, file_id)
 
1290
                    new_entry = self._get_entry(self.target, file_id)
 
1291
                    result, changes = self._changes_from_entries(
 
1292
                        old_entry, new_entry)
 
1293
                else:
 
1294
                    changes = True
 
1295
                # Get this parents parent to examine.
 
1296
                new_parent_id = result[4][1]
 
1297
                precise_file_ids.add(new_parent_id)
 
1298
                if changes:
 
1299
                    if (result[6][0] == 'directory' and
 
1300
                        result[6][1] != 'directory'):
 
1301
                        # This stopped being a directory, the old children have
 
1302
                        # to be included.
 
1303
                        if old_entry is None:
 
1304
                            # Reusing a discarded change.
 
1305
                            old_entry = self._get_entry(self.source, file_id)
 
1306
                        for child in old_entry.children.values():
 
1307
                            precise_file_ids.add(child.file_id)
 
1308
                    changed_file_ids.add(result[0])
 
1309
                    yield result
 
1310
 
 
1311
 
 
1312
class MultiWalker(object):
 
1313
    """Walk multiple trees simultaneously, getting combined results."""
 
1314
 
 
1315
    # Note: This could be written to not assume you can do out-of-order
 
1316
    #       lookups. Instead any nodes that don't match in all trees could be
 
1317
    #       marked as 'deferred', and then returned in the final cleanup loop.
 
1318
    #       For now, I think it is "nicer" to return things as close to the
 
1319
    #       "master_tree" order as we can.
 
1320
 
 
1321
    def __init__(self, master_tree, other_trees):
 
1322
        """Create a new MultiWalker.
 
1323
 
 
1324
        All trees being walked must implement "iter_entries_by_dir()", such
 
1325
        that they yield (path, object) tuples, where that object will have a
 
1326
        '.file_id' member, that can be used to check equality.
 
1327
 
 
1328
        :param master_tree: All trees will be 'slaved' to the master_tree such
 
1329
            that nodes in master_tree will be used as 'first-pass' sync points.
 
1330
            Any nodes that aren't in master_tree will be merged in a second
 
1331
            pass.
 
1332
        :param other_trees: A list of other trees to walk simultaneously.
 
1333
        """
 
1334
        self._master_tree = master_tree
 
1335
        self._other_trees = other_trees
 
1336
 
 
1337
        # Keep track of any nodes that were properly processed just out of
 
1338
        # order, that way we don't return them at the end, we don't have to
 
1339
        # track *all* processed file_ids, just the out-of-order ones
 
1340
        self._out_of_order_processed = set()
 
1341
 
 
1342
    @staticmethod
 
1343
    def _step_one(iterator):
 
1344
        """Step an iter_entries_by_dir iterator.
 
1345
 
 
1346
        :return: (has_more, path, ie)
 
1347
            If has_more is False, path and ie will be None.
 
1348
        """
 
1349
        try:
 
1350
            path, ie = iterator.next()
 
1351
        except StopIteration:
 
1352
            return False, None, None
 
1353
        else:
 
1354
            return True, path, ie
 
1355
 
 
1356
    @staticmethod
 
1357
    def _cmp_path_by_dirblock(path1, path2):
 
1358
        """Compare two paths based on what directory they are in.
 
1359
 
 
1360
        This generates a sort order, such that all children of a directory are
 
1361
        sorted together, and grandchildren are in the same order as the
 
1362
        children appear. But all grandchildren come after all children.
 
1363
 
 
1364
        :param path1: first path
 
1365
        :param path2: the second path
 
1366
        :return: negative number if ``path1`` comes first,
 
1367
            0 if paths are equal
 
1368
            and a positive number if ``path2`` sorts first
 
1369
        """
 
1370
        # Shortcut this special case
 
1371
        if path1 == path2:
 
1372
            return 0
 
1373
        # This is stolen from _dirstate_helpers_py.py, only switching it to
 
1374
        # Unicode objects. Consider using encode_utf8() and then using the
 
1375
        # optimized versions, or maybe writing optimized unicode versions.
 
1376
        if not isinstance(path1, unicode):
 
1377
            raise TypeError("'path1' must be a unicode string, not %s: %r"
 
1378
                            % (type(path1), path1))
 
1379
        if not isinstance(path2, unicode):
 
1380
            raise TypeError("'path2' must be a unicode string, not %s: %r"
 
1381
                            % (type(path2), path2))
 
1382
        return cmp(MultiWalker._path_to_key(path1),
 
1383
                   MultiWalker._path_to_key(path2))
 
1384
 
 
1385
    @staticmethod
 
1386
    def _path_to_key(path):
 
1387
        dirname, basename = osutils.split(path)
 
1388
        return (dirname.split(u'/'), basename)
 
1389
 
 
1390
    def _lookup_by_file_id(self, extra_entries, other_tree, file_id):
 
1391
        """Lookup an inventory entry by file_id.
 
1392
 
 
1393
        This is called when an entry is missing in the normal order.
 
1394
        Generally this is because a file was either renamed, or it was
 
1395
        deleted/added. If the entry was found in the inventory and not in
 
1396
        extra_entries, it will be added to self._out_of_order_processed
 
1397
 
 
1398
        :param extra_entries: A dictionary of {file_id: (path, ie)}.  This
 
1399
            should be filled with entries that were found before they were
 
1400
            used. If file_id is present, it will be removed from the
 
1401
            dictionary.
 
1402
        :param other_tree: The Tree to search, in case we didn't find the entry
 
1403
            yet.
 
1404
        :param file_id: The file_id to look for
 
1405
        :return: (path, ie) if found or (None, None) if not present.
 
1406
        """
 
1407
        if file_id in extra_entries:
 
1408
            return extra_entries.pop(file_id)
 
1409
        # TODO: Is id2path better as the first call, or is
 
1410
        #       inventory[file_id] better as a first check?
 
1411
        try:
 
1412
            cur_path = other_tree.id2path(file_id)
 
1413
        except errors.NoSuchId:
 
1414
            cur_path = None
 
1415
        if cur_path is None:
 
1416
            return (None, None)
 
1417
        else:
 
1418
            self._out_of_order_processed.add(file_id)
 
1419
            cur_ie = other_tree.inventory[file_id]
 
1420
            return (cur_path, cur_ie)
 
1421
 
 
1422
    def iter_all(self):
 
1423
        """Match up the values in the different trees."""
 
1424
        for result in self._walk_master_tree():
 
1425
            yield result
 
1426
        self._finish_others()
 
1427
        for result in self._walk_others():
 
1428
            yield result
 
1429
 
 
1430
    def _walk_master_tree(self):
 
1431
        """First pass, walk all trees in lock-step.
 
1432
 
 
1433
        When we are done, all nodes in the master_tree will have been
 
1434
        processed. _other_walkers, _other_entries, and _others_extra will be
 
1435
        set on 'self' for future processing.
 
1436
        """
 
1437
        # This iterator has the most "inlining" done, because it tends to touch
 
1438
        # every file in the tree, while the others only hit nodes that don't
 
1439
        # match.
 
1440
        master_iterator = self._master_tree.iter_entries_by_dir()
 
1441
 
 
1442
        other_walkers = [other.iter_entries_by_dir()
 
1443
                         for other in self._other_trees]
 
1444
        other_entries = [self._step_one(walker) for walker in other_walkers]
 
1445
        # Track extra nodes in the other trees
 
1446
        others_extra = [{} for i in xrange(len(self._other_trees))]
 
1447
 
 
1448
        master_has_more = True
 
1449
        step_one = self._step_one
 
1450
        lookup_by_file_id = self._lookup_by_file_id
 
1451
        out_of_order_processed = self._out_of_order_processed
 
1452
 
 
1453
        while master_has_more:
 
1454
            (master_has_more, path, master_ie) = step_one(master_iterator)
 
1455
            if not master_has_more:
 
1456
                break
 
1457
 
 
1458
            file_id = master_ie.file_id
 
1459
            other_values = []
 
1460
            other_values_append = other_values.append
 
1461
            next_other_entries = []
 
1462
            next_other_entries_append = next_other_entries.append
 
1463
            for idx, (other_has_more, other_path, other_ie) in enumerate(other_entries):
 
1464
                if not other_has_more:
 
1465
                    other_values_append(lookup_by_file_id(
 
1466
                        others_extra[idx], self._other_trees[idx], file_id))
 
1467
                    next_other_entries_append((False, None, None))
 
1468
                elif file_id == other_ie.file_id:
 
1469
                    # This is the critical code path, as most of the entries
 
1470
                    # should match between most trees.
 
1471
                    other_values_append((other_path, other_ie))
 
1472
                    next_other_entries_append(step_one(other_walkers[idx]))
 
1473
                else:
 
1474
                    # This walker did not match, step it until it either
 
1475
                    # matches, or we know we are past the current walker.
 
1476
                    other_walker = other_walkers[idx]
 
1477
                    other_extra = others_extra[idx]
 
1478
                    while (other_has_more and
 
1479
                           self._cmp_path_by_dirblock(other_path, path) < 0):
 
1480
                        other_file_id = other_ie.file_id
 
1481
                        if other_file_id not in out_of_order_processed:
 
1482
                            other_extra[other_file_id] = (other_path, other_ie)
 
1483
                        other_has_more, other_path, other_ie = \
 
1484
                            step_one(other_walker)
 
1485
                    if other_has_more and other_ie.file_id == file_id:
 
1486
                        # We ended up walking to this point, match and step
 
1487
                        # again
 
1488
                        other_values_append((other_path, other_ie))
 
1489
                        other_has_more, other_path, other_ie = \
 
1490
                            step_one(other_walker)
 
1491
                    else:
 
1492
                        # This record isn't in the normal order, see if it
 
1493
                        # exists at all.
 
1494
                        other_values_append(lookup_by_file_id(
 
1495
                            other_extra, self._other_trees[idx], file_id))
 
1496
                    next_other_entries_append((other_has_more, other_path,
 
1497
                                               other_ie))
 
1498
            other_entries = next_other_entries
 
1499
 
 
1500
            # We've matched all the walkers, yield this datapoint
 
1501
            yield path, file_id, master_ie, other_values
 
1502
        self._other_walkers = other_walkers
 
1503
        self._other_entries = other_entries
 
1504
        self._others_extra = others_extra
 
1505
 
 
1506
    def _finish_others(self):
 
1507
        """Finish walking the other iterators, so we get all entries."""
 
1508
        for idx, info in enumerate(self._other_entries):
 
1509
            other_extra = self._others_extra[idx]
 
1510
            (other_has_more, other_path, other_ie) = info
 
1511
            while other_has_more:
 
1512
                other_file_id = other_ie.file_id
 
1513
                if other_file_id not in self._out_of_order_processed:
 
1514
                    other_extra[other_file_id] = (other_path, other_ie)
 
1515
                other_has_more, other_path, other_ie = \
 
1516
                    self._step_one(self._other_walkers[idx])
 
1517
        del self._other_entries
 
1518
 
 
1519
    def _walk_others(self):
 
1520
        """Finish up by walking all the 'deferred' nodes."""
 
1521
        # TODO: One alternative would be to grab all possible unprocessed
 
1522
        #       file_ids, and then sort by path, and then yield them. That
 
1523
        #       might ensure better ordering, in case a caller strictly
 
1524
        #       requires parents before children.
 
1525
        for idx, other_extra in enumerate(self._others_extra):
 
1526
            others = sorted(other_extra.itervalues(),
 
1527
                            key=lambda x: self._path_to_key(x[0]))
 
1528
            for other_path, other_ie in others:
 
1529
                file_id = other_ie.file_id
 
1530
                # We don't need to check out_of_order_processed here, because
 
1531
                # the lookup_by_file_id will be removing anything processed
 
1532
                # from the extras cache
 
1533
                other_extra.pop(file_id)
 
1534
                other_values = [(None, None) for i in xrange(idx)]
 
1535
                other_values.append((other_path, other_ie))
 
1536
                for alt_idx, alt_extra in enumerate(self._others_extra[idx+1:]):
 
1537
                    alt_idx = alt_idx + idx + 1
 
1538
                    alt_extra = self._others_extra[alt_idx]
 
1539
                    alt_tree = self._other_trees[alt_idx]
 
1540
                    other_values.append(self._lookup_by_file_id(
 
1541
                                            alt_extra, alt_tree, file_id))
 
1542
                yield other_path, file_id, None, other_values