~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Jelmer Vernooij
  • Date: 2011-04-09 19:25:42 UTC
  • mto: (5777.5.1 inventoryworkingtree)
  • mto: This revision was merged to the branch mainline in revision 5781.
  • Revision ID: jelmer@samba.org-20110409192542-8bbedp36s7nj928e
Split InventoryTree out of Tree.

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
    rules,
36
36
    trace,
37
37
    )
38
 
from bzrlib.i18n import gettext
39
38
""")
40
39
 
41
40
from bzrlib.decorators import needs_read_lock
42
41
from bzrlib.inter import InterObject
43
 
from bzrlib.symbol_versioning import (
44
 
    deprecated_in,
45
 
    deprecated_method,
46
 
    )
47
42
 
48
43
 
49
44
class Tree(object):
59
54
    trees or versioned trees.
60
55
    """
61
56
 
62
 
    def has_versioned_directories(self):
63
 
        """Whether this tree can contain explicitly versioned directories.
64
 
 
65
 
        This defaults to True, but some implementations may want to override
66
 
        it.
67
 
        """
68
 
        return True
69
 
 
70
57
    def changes_from(self, other, want_unchanged=False, specific_files=None,
71
58
        extra_trees=None, require_versioned=False, include_root=False,
72
59
        want_unversioned=False):
136
123
    def has_id(self, file_id):
137
124
        raise NotImplementedError(self.has_id)
138
125
 
139
 
    @deprecated_method(deprecated_in((2, 4, 0)))
140
126
    def __contains__(self, file_id):
141
127
        return self.has_id(file_id)
142
128
 
151
137
        """
152
138
        return False
153
139
 
 
140
    def __iter__(self):
 
141
        """Yield all file ids in this tree."""
 
142
        raise NotImplementedError(self.__iter__)
 
143
 
154
144
    def all_file_ids(self):
155
145
        """Iterate through all file ids, including ids for missing files."""
156
 
        raise NotImplementedError(self.all_file_ids)
 
146
        return set(self.inventory)
157
147
 
158
148
    def id2path(self, file_id):
159
149
        """Return the path for a file id.
184
174
             g
185
175
 
186
176
        The yield order (ignoring root) would be::
187
 
 
188
177
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
189
178
 
190
179
        :param yield_parents: If True, yield the parents from the root leading
193
182
        """
194
183
        raise NotImplementedError(self.iter_entries_by_dir)
195
184
 
196
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
197
 
        """List all files in this tree.
198
 
 
199
 
        :param include_root: Whether to include the entry for the tree root
200
 
        :param from_dir: Directory under which to list files
201
 
        :param recursive: Whether to list files recursively
202
 
        :return: iterator over tuples of (path, versioned, kind, file_id,
203
 
            inventory entry)
204
 
        """
205
 
        raise NotImplementedError(self.list_files)
206
 
 
207
185
    def iter_references(self):
208
186
        if self.supports_tree_reference():
209
187
            for path, entry in self.iter_entries_by_dir():
286
264
 
287
265
        :param file_id: The file_id of the file.
288
266
        :param path: The path of the file.
289
 
 
290
267
        If both file_id and path are supplied, an implementation may use
291
268
        either one.
292
 
 
293
 
        :returns: A single byte string for the whole file.
294
269
        """
295
270
        my_file = self.get_file(file_id, path)
296
271
        try:
303
278
 
304
279
        :param file_id: The file_id of the file.
305
280
        :param path: The path of the file.
306
 
 
307
281
        If both file_id and path are supplied, an implementation may use
308
282
        either one.
309
283
        """
310
284
        return osutils.split_lines(self.get_file_text(file_id, path))
311
285
 
312
 
    def get_file_verifier(self, file_id, path=None, stat_value=None):
313
 
        """Return a verifier for a file.
314
 
 
315
 
        The default implementation returns a sha1.
316
 
 
317
 
        :param file_id: The handle for this file.
318
 
        :param path: The path that this file can be found at.
319
 
            These must point to the same object.
320
 
        :param stat_value: Optional stat value for the object
321
 
        :return: Tuple with verifier name and verifier data
322
 
        """
323
 
        return ("SHA1", self.get_file_sha1(file_id, path=path,
324
 
            stat_value=stat_value))
325
 
 
326
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
327
 
        """Return the SHA1 file for a file.
328
 
 
329
 
        :note: callers should use get_file_verifier instead
330
 
            where possible, as the underlying repository implementation may
331
 
            have quicker access to a non-sha1 verifier.
332
 
 
333
 
        :param file_id: The handle for this file.
334
 
        :param path: The path that this file can be found at.
335
 
            These must point to the same object.
336
 
        :param stat_value: Optional stat value for the object
337
 
        """
338
 
        raise NotImplementedError(self.get_file_sha1)
339
 
 
340
286
    def get_file_mtime(self, file_id, path=None):
341
287
        """Return the modification time for a file.
342
288
 
358
304
    def get_file_by_path(self, path):
359
305
        raise NotImplementedError(self.get_file_by_path)
360
306
 
361
 
    def is_executable(self, file_id, path=None):
362
 
        """Check if a file is executable.
363
 
 
364
 
        :param file_id: The handle for this file.
365
 
        :param path: The path that this file can be found at.
366
 
            These must point to the same object.
367
 
        """
368
 
        raise NotImplementedError(self.is_executable)
369
 
 
370
307
    def iter_files_bytes(self, desired_files):
371
308
        """Iterate through file contents.
372
309
 
393
330
            cur_file = (self.get_file_text(file_id),)
394
331
            yield identifier, cur_file
395
332
 
396
 
    def get_symlink_target(self, file_id, path=None):
 
333
    def get_symlink_target(self, file_id):
397
334
        """Get the target for a given file_id.
398
335
 
399
336
        It is assumed that the caller already knows that file_id is referencing
400
337
        a symlink.
401
338
        :param file_id: Handle for the symlink entry.
402
 
        :param path: The path of the file.
403
 
        If both file_id and path are supplied, an implementation may use
404
 
        either one.
405
339
        :return: The path the symlink points to.
406
340
        """
407
341
        raise NotImplementedError(self.get_symlink_target)
408
342
 
 
343
 
409
344
    def get_root_id(self):
410
345
        """Return the file_id for the root of this tree."""
411
346
        raise NotImplementedError(self.get_root_id)
469
404
            except errors.NoSuchRevisionInTree:
470
405
                yield self.repository.revision_tree(revision_id)
471
406
 
 
407
    @staticmethod
 
408
    def _file_revision(revision_tree, file_id):
 
409
        """Determine the revision associated with a file in a given tree."""
 
410
        # FIXME: Shouldn't this be a RevisionTree method?
 
411
        revision_tree.lock_read()
 
412
        try:
 
413
            return revision_tree.inventory[file_id].revision
 
414
        finally:
 
415
            revision_tree.unlock()
 
416
 
472
417
    def _get_file_revision(self, file_id, vf, tree_revision):
473
418
        """Ensure that file_id, tree_revision is in vf to plan the merge."""
474
419
 
475
420
        if getattr(self, '_repository', None) is None:
476
421
            last_revision = tree_revision
477
 
            parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
 
422
            parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
478
423
                self._iter_parent_trees()]
479
424
            vf.add_lines((file_id, last_revision), parent_keys,
480
425
                         self.get_file_lines(file_id))
481
426
            repo = self.branch.repository
482
427
            base_vf = repo.texts
483
428
        else:
484
 
            last_revision = self.get_file_revision(file_id)
 
429
            last_revision = self._file_revision(self, file_id)
485
430
            base_vf = self._repository.texts
486
431
        if base_vf not in vf.fallback_versionedfiles:
487
432
            vf.fallback_versionedfiles.append(base_vf)
632
577
        prefs = self.iter_search_rules([path], filter_pref_names).next()
633
578
        stk = filters._get_filter_stack_for(prefs)
634
579
        if 'filters' in debug.debug_flags:
635
 
            trace.note(gettext("*** {0} content-filter: {1} => {2!r}").format(path,prefs,stk))
 
580
            trace.note("*** %s content-filter: %s => %r" % (path,prefs,stk))
636
581
        return stk
637
582
 
638
583
    def _content_filter_stack_provider(self):
686
631
 
687
632
    It is possible for trees to contain files that are not described
688
633
    in their inventory or vice versa; for this use `filenames()`.
689
 
 
690
 
    Subclasses should set the _inventory attribute, which is considered
691
 
    private to external API users.
692
634
    """
693
635
 
694
636
    def get_canonical_inventory_paths(self, paths):
793
735
    def has_or_had_id(self, file_id):
794
736
        return self.inventory.has_id(file_id)
795
737
 
796
 
    def all_file_ids(self):
797
 
        return set(self.inventory)
798
 
 
799
 
    @deprecated_method(deprecated_in((2, 4, 0)))
800
738
    def __iter__(self):
801
739
        return iter(self.inventory)
802
740
 
831
769
        return self.get_file(self._inventory.path2id(path), path)
832
770
 
833
771
 
 
772
######################################################################
 
773
# diff
 
774
 
 
775
# TODO: Merge these two functions into a single one that can operate
 
776
# on either a whole tree or a set of files.
 
777
 
 
778
# TODO: Return the diff in order by filename, not by category or in
 
779
# random order.  Can probably be done by lock-stepping through the
 
780
# filenames from both trees.
 
781
 
 
782
 
 
783
def file_status(filename, old_tree, new_tree):
 
784
    """Return single-letter status, old and new names for a file.
 
785
 
 
786
    The complexity here is in deciding how to represent renames;
 
787
    many complex cases are possible.
 
788
    """
 
789
    old_inv = old_tree.inventory
 
790
    new_inv = new_tree.inventory
 
791
    new_id = new_inv.path2id(filename)
 
792
    old_id = old_inv.path2id(filename)
 
793
 
 
794
    if not new_id and not old_id:
 
795
        # easy: doesn't exist in either; not versioned at all
 
796
        if new_tree.is_ignored(filename):
 
797
            return 'I', None, None
 
798
        else:
 
799
            return '?', None, None
 
800
    elif new_id:
 
801
        # There is now a file of this name, great.
 
802
        pass
 
803
    else:
 
804
        # There is no longer a file of this name, but we can describe
 
805
        # what happened to the file that used to have
 
806
        # this name.  There are two possibilities: either it was
 
807
        # deleted entirely, or renamed.
 
808
        if new_inv.has_id(old_id):
 
809
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
 
810
        else:
 
811
            return 'D', old_inv.id2path(old_id), None
 
812
 
 
813
    # if the file_id is new in this revision, it is added
 
814
    if new_id and not old_inv.has_id(new_id):
 
815
        return 'A'
 
816
 
 
817
    # if there used to be a file of this name, but that ID has now
 
818
    # disappeared, it is deleted
 
819
    if old_id and not new_inv.has_id(old_id):
 
820
        return 'D'
 
821
 
 
822
    return 'wtf?'
 
823
 
 
824
 
834
825
def find_ids_across_trees(filenames, trees, require_versioned=True):
835
826
    """Find the ids corresponding to specified filenames.
836
827
 
841
832
        None)
842
833
    :param trees: The trees to find file_ids within
843
834
    :param require_versioned: if true, all specified filenames must occur in
844
 
        at least one tree.
 
835
    at least one tree.
845
836
    :return: a set of file ids for the specified filenames and their children.
846
837
    """
847
838
    if not filenames:
923
914
 
924
915
    _optimisers = []
925
916
 
926
 
    @classmethod
927
 
    def is_compatible(kls, source, target):
928
 
        # The default implementation is naive and uses the public API, so
929
 
        # it works for all trees.
930
 
        return True
931
 
 
932
917
    def _changes_from_entries(self, source_entry, target_entry,
933
918
        source_path=None, target_path=None):
934
919
        """Generate a iter_changes tuple between source_entry and target_entry.
982
967
        if source_kind != target_kind:
983
968
            changed_content = True
984
969
        elif source_kind == 'file':
985
 
            if not self.file_content_matches(file_id, file_id, source_path,
986
 
                    target_path, source_stat, target_stat):
 
970
            if (self.source.get_file_sha1(file_id, source_path, source_stat) !=
 
971
                self.target.get_file_sha1(file_id, target_path, target_stat)):
987
972
                changed_content = True
988
973
        elif source_kind == 'symlink':
989
974
            if (self.source.get_symlink_target(file_id) !=
1302
1287
                    changed_file_ids.add(result[0])
1303
1288
                    yield result
1304
1289
 
1305
 
    @needs_read_lock
1306
 
    def file_content_matches(self, source_file_id, target_file_id,
1307
 
            source_path=None, target_path=None, source_stat=None, target_stat=None):
1308
 
        """Check if two files are the same in the source and target trees.
1309
 
 
1310
 
        This only checks that the contents of the files are the same,
1311
 
        it does not touch anything else.
1312
 
 
1313
 
        :param source_file_id: File id of the file in the source tree
1314
 
        :param target_file_id: File id of the file in the target tree
1315
 
        :param source_path: Path of the file in the source tree
1316
 
        :param target_path: Path of the file in the target tree
1317
 
        :param source_stat: Optional stat value of the file in the source tree
1318
 
        :param target_stat: Optional stat value of the file in the target tree
1319
 
        :return: Boolean indicating whether the files have the same contents
1320
 
        """
1321
 
        source_verifier_kind, source_verifier_data = self.source.get_file_verifier(
1322
 
            source_file_id, source_path, source_stat)
1323
 
        target_verifier_kind, target_verifier_data = self.target.get_file_verifier(
1324
 
            target_file_id, target_path, target_stat)
1325
 
        if source_verifier_kind == target_verifier_kind:
1326
 
            return (source_verifier_data == target_verifier_data)
1327
 
        # Fall back to SHA1 for now
1328
 
        if source_verifier_kind != "SHA1":
1329
 
            source_sha1 = self.source.get_file_sha1(source_file_id,
1330
 
                    source_path, source_stat)
1331
 
        else:
1332
 
            source_sha1 = source_verifier_data
1333
 
        if target_verifier_kind != "SHA1":
1334
 
            target_sha1 = self.target.get_file_sha1(target_file_id,
1335
 
                    target_path, target_stat)
1336
 
        else:
1337
 
            target_sha1 = target_verifier_data
1338
 
        return (source_sha1 == target_sha1)
1339
 
 
1340
 
InterTree.register_optimiser(InterTree)
1341
 
 
1342
1290
 
1343
1291
class MultiWalker(object):
1344
1292
    """Walk multiple trees simultaneously, getting combined results."""