/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: Robert Collins
  • Date: 2005-12-24 02:20:45 UTC
  • mto: (1185.50.57 bzr-jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: robertc@robertcollins.net-20051224022045-14efc8dfa0e1a4e9
Start tests for api usage.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Tree classes, representing directory at point in time.
18
18
"""
19
19
 
20
 
from .lazy_import import lazy_import
21
 
lazy_import(globals(), """
22
 
from breezy.i18n import gettext
23
 
""")
24
 
 
25
 
from . import (
26
 
    errors,
27
 
    lock,
28
 
    osutils,
29
 
    revision as _mod_revision,
30
 
    trace,
31
 
    )
32
 
from .inter import InterObject
33
 
 
34
 
 
35
 
class FileTimestampUnavailable(errors.BzrError):
36
 
 
37
 
    _fmt = "The filestamp for %(path)s is not available."
38
 
 
39
 
    internal_error = True
40
 
 
41
 
    def __init__(self, path):
42
 
        self.path = path
43
 
 
44
 
 
45
 
class MissingNestedTree(errors.BzrError):
46
 
 
47
 
    _fmt = "The nested tree for %(path)s can not be resolved."""
48
 
 
49
 
    def __init__(self, path):
50
 
        self.path = path
51
 
 
52
 
 
53
 
class TreeEntry(object):
54
 
    """An entry that implements the minimum interface used by commands.
55
 
    """
56
 
 
57
 
    __slots__ = []
58
 
 
59
 
    def __eq__(self, other):
60
 
        # yes, this is ugly, TODO: best practice __eq__ style.
61
 
        return (isinstance(other, TreeEntry)
62
 
                and other.__class__ == self.__class__)
63
 
 
64
 
    kind = None
65
 
 
66
 
    def kind_character(self):
67
 
        return "???"
68
 
 
69
 
    def is_unmodified(self, other):
70
 
        """Does this entry reference the same entry?
71
 
 
72
 
        This is mostly the same as __eq__, but returns False
73
 
        for entries without enough information (i.e. revision is None)
74
 
        """
75
 
        return False
76
 
 
77
 
 
78
 
class TreeDirectory(TreeEntry):
79
 
    """See TreeEntry. This is a directory in a working tree."""
80
 
 
81
 
    __slots__ = []
82
 
 
83
 
    kind = 'directory'
84
 
 
85
 
    def kind_character(self):
86
 
        return "/"
87
 
 
88
 
 
89
 
class TreeFile(TreeEntry):
90
 
    """See TreeEntry. This is a regular file in a working tree."""
91
 
 
92
 
    __slots__ = []
93
 
 
94
 
    kind = 'file'
95
 
 
96
 
    def kind_character(self):
97
 
        return ''
98
 
 
99
 
 
100
 
class TreeLink(TreeEntry):
101
 
    """See TreeEntry. This is a symlink in a working tree."""
102
 
 
103
 
    __slots__ = []
104
 
 
105
 
    kind = 'symlink'
106
 
 
107
 
    def kind_character(self):
108
 
        return ''
109
 
 
110
 
 
111
 
class TreeReference(TreeEntry):
112
 
    """See TreeEntry. This is a reference to a nested tree in a working tree."""
113
 
 
114
 
    __slots__ = []
115
 
 
116
 
    kind = 'tree-reference'
117
 
 
118
 
    def kind_character(self):
119
 
        return '+'
120
 
 
121
 
 
122
 
class TreeChange(object):
123
 
    """Describes the changes between the same item in two different trees."""
124
 
 
125
 
    __slots__ = ['file_id', 'path', 'changed_content', 'versioned', 'parent_id',
126
 
                 'name', 'kind', 'executable', 'copied']
127
 
 
128
 
    def __init__(self, file_id, path, changed_content, versioned, parent_id,
129
 
                 name, kind, executable, copied=False):
130
 
        self.file_id = file_id
131
 
        self.path = path
132
 
        self.changed_content = changed_content
133
 
        self.versioned = versioned
134
 
        self.parent_id = parent_id
135
 
        self.name = name
136
 
        self.kind = kind
137
 
        self.executable = executable
138
 
        self.copied = copied
139
 
 
140
 
    def __repr__(self):
141
 
        return "%s%r" % (self.__class__.__name__, self._as_tuple())
142
 
 
143
 
    def __len__(self):
144
 
        return len(self.__slots__)
145
 
 
146
 
    def _as_tuple(self):
147
 
        return (self.file_id, self.path, self.changed_content, self.versioned,
148
 
                self.parent_id, self.name, self.kind, self.executable, self.copied)
149
 
 
150
 
    def __eq__(self, other):
151
 
        if isinstance(other, TreeChange):
152
 
            return self._as_tuple() == other._as_tuple()
153
 
        if isinstance(other, tuple):
154
 
            return self._as_tuple() == other
155
 
        return False
156
 
 
157
 
    def __lt__(self, other):
158
 
        return self._as_tuple() < other._as_tuple()
159
 
 
160
 
    def meta_modified(self):
161
 
        if self.versioned == (True, True):
162
 
            return (self.executable[0] != self.executable[1])
163
 
        return False
164
 
 
165
 
    def is_reparented(self):
166
 
        return self.parent_id[0] != self.parent_id[1]
167
 
 
168
 
    def discard_new(self):
169
 
        return self.__class__(
170
 
            self.file_id, (self.path[0], None), self.changed_content,
171
 
            (self.versioned[0], None), (self.parent_id[0], None),
172
 
            (self.name[0], None), (self.kind[0], None),
173
 
            (self.executable[0], None),
174
 
            copied=False)
175
 
 
 
20
import os
 
21
from cStringIO import StringIO
 
22
 
 
23
import bzrlib
 
24
from bzrlib.trace import mutter, note
 
25
from bzrlib.errors import BzrError, BzrCheckError
 
26
from bzrlib.inventory import Inventory
 
27
from bzrlib.osutils import appendpath, fingerprint_file
176
28
 
177
29
class Tree(object):
178
30
    """Abstract file tree.
179
31
 
180
32
    There are several subclasses:
181
 
 
 
33
    
182
34
    * `WorkingTree` exists as files on disk editable by the user.
183
35
 
184
36
    * `RevisionTree` is a tree as recorded at some point in the past.
185
37
 
 
38
    * `EmptyTree`
 
39
 
 
40
    Trees contain an `Inventory` object, and also know how to retrieve
 
41
    file texts mentioned in the inventory, either from a working
 
42
    directory or from a store.
 
43
 
 
44
    It is possible for trees to contain files that are not described
 
45
    in their inventory or vice versa; for this use `filenames()`.
 
46
 
186
47
    Trees can be compared, etc, regardless of whether they are working
187
48
    trees or versioned trees.
188
49
    """
189
 
 
190
 
    def supports_rename_tracking(self):
191
 
        """Whether this tree supports rename tracking.
192
 
 
193
 
        This defaults to True, but some implementations may want to override
194
 
        it.
195
 
        """
196
 
        return True
197
 
 
198
 
    def has_versioned_directories(self):
199
 
        """Whether this tree can contain explicitly versioned directories.
200
 
 
201
 
        This defaults to True, but some implementations may want to override
202
 
        it.
203
 
        """
204
 
        return True
205
 
 
206
 
    def supports_symlinks(self):
207
 
        """Does this tree support symbolic links?
208
 
        """
209
 
        return osutils.has_symlinks()
210
 
 
211
 
    def changes_from(self, other, want_unchanged=False, specific_files=None,
212
 
                     extra_trees=None, require_versioned=False, include_root=False,
213
 
                     want_unversioned=False):
214
 
        """Return a TreeDelta of the changes from other to this tree.
215
 
 
216
 
        :param other: A tree to compare with.
217
 
        :param specific_files: An optional list of file paths to restrict the
218
 
            comparison to. When mapping filenames to ids, all matches in all
219
 
            trees (including optional extra_trees) are used, and all children of
220
 
            matched directories are included.
221
 
        :param want_unchanged: An optional boolean requesting the inclusion of
222
 
            unchanged entries in the result.
223
 
        :param extra_trees: An optional list of additional trees to use when
224
 
            mapping the contents of specific_files (paths) to their identities.
225
 
        :param require_versioned: An optional boolean (defaults to False). When
226
 
            supplied and True all the 'specific_files' must be versioned, or
227
 
            a PathsNotVersionedError will be thrown.
228
 
        :param want_unversioned: Scan for unversioned paths.
229
 
 
230
 
        The comparison will be performed by an InterTree object looked up on
231
 
        self and other.
232
 
        """
233
 
        # Martin observes that Tree.changes_from returns a TreeDelta and this
234
 
        # may confuse people, because the class name of the returned object is
235
 
        # a synonym of the object referenced in the method name.
236
 
        return InterTree.get(other, self).compare(
237
 
            want_unchanged=want_unchanged,
238
 
            specific_files=specific_files,
239
 
            extra_trees=extra_trees,
240
 
            require_versioned=require_versioned,
241
 
            include_root=include_root,
242
 
            want_unversioned=want_unversioned,
243
 
            )
244
 
 
245
 
    def iter_changes(self, from_tree, include_unchanged=False,
246
 
                     specific_files=None, pb=None, extra_trees=None,
247
 
                     require_versioned=True, want_unversioned=False):
248
 
        """See InterTree.iter_changes"""
249
 
        intertree = InterTree.get(from_tree, self)
250
 
        return intertree.iter_changes(include_unchanged, specific_files, pb,
251
 
                                      extra_trees, require_versioned,
252
 
                                      want_unversioned=want_unversioned)
253
 
 
254
 
    def conflicts(self):
255
 
        """Get a list of the conflicts in the tree.
256
 
 
257
 
        Each conflict is an instance of breezy.conflicts.Conflict.
258
 
        """
259
 
        from . import conflicts as _mod_conflicts
260
 
        return _mod_conflicts.ConflictList()
261
 
 
262
 
    def extras(self):
263
 
        """For trees that can have unversioned files, return all such paths."""
264
 
        return []
265
 
 
266
 
    def get_parent_ids(self):
267
 
        """Get the parent ids for this tree.
268
 
 
269
 
        :return: a list of parent ids. [] is returned to indicate
270
 
        a tree with no parents.
271
 
        :raises: BzrError if the parents are not known.
272
 
        """
273
 
        raise NotImplementedError(self.get_parent_ids)
274
 
 
 
50
    
275
51
    def has_filename(self, filename):
276
52
        """True if the tree has given filename."""
277
 
        raise NotImplementedError(self.has_filename)
278
 
 
279
 
    def is_ignored(self, filename):
280
 
        """Check whether the filename is ignored by this tree.
281
 
 
282
 
        :param filename: The relative filename within the tree.
283
 
        :return: True if the filename is ignored.
284
 
        """
 
53
        raise NotImplementedError()
 
54
 
 
55
    def has_id(self, file_id):
 
56
        return self.inventory.has_id(file_id)
 
57
 
 
58
    def has_or_had_id(self, file_id):
 
59
        if file_id == self.inventory.root.file_id:
 
60
            return True
 
61
        return self.inventory.has_id(file_id)
 
62
 
 
63
    __contains__ = has_id
 
64
 
 
65
    def __iter__(self):
 
66
        return iter(self.inventory)
 
67
 
 
68
    def id2path(self, file_id):
 
69
        return self.inventory.id2path(file_id)
 
70
 
 
71
    def kind(self, file_id):
 
72
        raise NotImplementedError("subclasses must implement kind")
 
73
 
 
74
    def _get_inventory(self):
 
75
        return self._inventory
 
76
    
 
77
    def get_file_by_path(self, path):
 
78
        return self.get_file(self._inventory.path2id(path))
 
79
 
 
80
    inventory = property(_get_inventory,
 
81
                         doc="Inventory of this Tree")
 
82
 
 
83
    def _check_retrieved(self, ie, f):
 
84
        if not __debug__:
 
85
            return  
 
86
        fp = fingerprint_file(f)
 
87
        f.seek(0)
 
88
        
 
89
        if ie.text_size != None:
 
90
            if ie.text_size != fp['size']:
 
91
                raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
 
92
                        ["inventory expects %d bytes" % ie.text_size,
 
93
                         "file is actually %d bytes" % fp['size'],
 
94
                         "store is probably damaged/corrupt"])
 
95
 
 
96
        if ie.text_sha1 != fp['sha1']:
 
97
            raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
 
98
                    ["inventory expects %s" % ie.text_sha1,
 
99
                     "file is actually %s" % fp['sha1'],
 
100
                     "store is probably damaged/corrupt"])
 
101
 
 
102
 
 
103
    def print_file(self, file_id):
 
104
        """Print file with id `file_id` to stdout."""
 
105
        import sys
 
106
        sys.stdout.write(self.get_file_text(file_id))
 
107
        
 
108
        
 
109
class RevisionTree(Tree):
 
110
    """Tree viewing a previous revision.
 
111
 
 
112
    File text can be retrieved from the text store.
 
113
 
 
114
    TODO: Some kind of `__repr__` method, but a good one
 
115
           probably means knowing the branch and revision number,
 
116
           or at least passing a description to the constructor.
 
117
    """
 
118
    
 
119
    def __init__(self, weave_store, inv, revision_id):
 
120
        self._weave_store = weave_store
 
121
        self._inventory = inv
 
122
        self._revision_id = revision_id
 
123
 
 
124
    def get_weave(self, file_id):
 
125
        # FIXME: RevisionTree should be given a branch
 
126
        # not a store, or the store should know the branch.
 
127
        import bzrlib.transactions as transactions
 
128
        return self._weave_store.get_weave(file_id,
 
129
            transactions.PassThroughTransaction())
 
130
 
 
131
 
 
132
    def get_file_lines(self, file_id):
 
133
        ie = self._inventory[file_id]
 
134
        weave = self.get_weave(file_id)
 
135
        return weave.get(ie.revision)
 
136
        
 
137
 
 
138
    def get_file_text(self, file_id):
 
139
        return ''.join(self.get_file_lines(file_id))
 
140
 
 
141
 
 
142
    def get_file(self, file_id):
 
143
        return StringIO(self.get_file_text(file_id))
 
144
 
 
145
    def get_file_size(self, file_id):
 
146
        return self._inventory[file_id].text_size
 
147
 
 
148
    def get_file_sha1(self, file_id):
 
149
        ie = self._inventory[file_id]
 
150
        if ie.kind == "file":
 
151
            return ie.text_sha1
 
152
 
 
153
    def is_executable(self, file_id):
 
154
        ie = self._inventory[file_id]
 
155
        if ie.kind != "file":
 
156
            return None 
 
157
        return self._inventory[file_id].executable
 
158
 
 
159
    def has_filename(self, filename):
 
160
        return bool(self.inventory.path2id(filename))
 
161
 
 
162
    def list_files(self):
 
163
        # The only files returned by this are those from the version
 
164
        for path, entry in self.inventory.iter_entries():
 
165
            yield path, 'V', entry.kind, entry.file_id, entry
 
166
 
 
167
    def get_symlink_target(self, file_id):
 
168
        ie = self._inventory[file_id]
 
169
        return ie.symlink_target;
 
170
 
 
171
    def kind(self, file_id):
 
172
        return self._inventory[file_id].kind
 
173
 
 
174
 
 
175
class EmptyTree(Tree):
 
176
    def __init__(self):
 
177
        self._inventory = Inventory()
 
178
 
 
179
    def get_symlink_target(self, file_id):
 
180
        return None
 
181
 
 
182
    def has_filename(self, filename):
285
183
        return False
286
184
 
287
 
    def all_file_ids(self):
288
 
        """Iterate through all file ids, including ids for missing files."""
289
 
        raise NotImplementedError(self.all_file_ids)
290
 
 
291
 
    def all_versioned_paths(self):
292
 
        """Iterate through all paths, including paths for missing files."""
293
 
        raise NotImplementedError(self.all_versioned_paths)
294
 
 
295
 
    def id2path(self, file_id, recurse='down'):
296
 
        """Return the path for a file id.
297
 
 
298
 
        :raises NoSuchId:
299
 
        """
300
 
        raise NotImplementedError(self.id2path)
301
 
 
302
 
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
303
 
        """Walk the tree in 'by_dir' order.
304
 
 
305
 
        This will yield each entry in the tree as a (path, entry) tuple.
306
 
        The order that they are yielded is:
307
 
 
308
 
        Directories are walked in a depth-first lexicographical order,
309
 
        however, whenever a directory is reached, all of its direct child
310
 
        nodes are yielded in  lexicographical order before yielding the
311
 
        grandchildren.
312
 
 
313
 
        For example, in the tree::
314
 
 
315
 
           a/
316
 
             b/
317
 
               c
318
 
             d/
319
 
               e
320
 
           f/
321
 
             g
322
 
 
323
 
        The yield order (ignoring root) would be::
324
 
 
325
 
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
326
 
 
327
 
        If recurse_nested is enabled then nested trees are included as if
328
 
        they were a part of the tree. If is disabled then TreeReference
329
 
        objects (without any children) are yielded.
330
 
        """
331
 
        raise NotImplementedError(self.iter_entries_by_dir)
332
 
 
333
 
    def iter_child_entries(self, path):
334
 
        """Iterate over the children of a directory or tree reference.
335
 
 
336
 
        :param path: Path of the directory
337
 
        :raise NoSuchFile: When the path does not exist
338
 
        :return: Iterator over entries in the directory
339
 
        """
340
 
        raise NotImplementedError(self.iter_child_entries)
341
 
 
342
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
343
 
                   recurse_nested=False):
344
 
        """List all files in this tree.
345
 
 
346
 
        :param include_root: Whether to include the entry for the tree root
347
 
        :param from_dir: Directory under which to list files
348
 
        :param recursive: Whether to list files recursively
349
 
        :param recurse_nested: enter nested trees
350
 
        :return: iterator over tuples of
351
 
            (path, versioned, kind, inventory entry)
352
 
        """
353
 
        raise NotImplementedError(self.list_files)
354
 
 
355
 
    def iter_references(self):
356
 
        if self.supports_tree_reference():
357
 
            for path, entry in self.iter_entries_by_dir():
358
 
                if entry.kind == 'tree-reference':
359
 
                    yield path
360
 
 
361
 
    def get_containing_nested_tree(self, path):
362
 
        """Find the nested tree that contains a path.
363
 
 
364
 
        :return: tuple with (nested tree and path inside the nested tree)
365
 
        """
366
 
        for nested_path in self.iter_references():
367
 
            nested_path += '/'
368
 
            if path.startswith(nested_path):
369
 
                nested_tree = self.get_nested_tree(nested_path)
370
 
                return nested_tree, path[len(nested_path):]
371
 
        else:
372
 
            return None, None
373
 
 
374
 
    def get_nested_tree(self, path):
375
 
        """Open the nested tree at the specified path.
376
 
 
377
 
        :param path: Path from which to resolve tree reference.
378
 
        :return: A Tree object for the nested tree
379
 
        :raise MissingNestedTree: If the nested tree can not be resolved
380
 
        """
381
 
        raise NotImplementedError(self.get_nested_tree)
382
 
 
383
 
    def kind(self, path):
384
 
        raise NotImplementedError("Tree subclass %s must implement kind"
385
 
                                  % self.__class__.__name__)
386
 
 
387
 
    def stored_kind(self, path):
388
 
        """File kind stored for this path.
389
 
 
390
 
        May not match kind on disk for working trees.  Always available
391
 
        for versioned files, even when the file itself is missing.
392
 
        """
393
 
        return self.kind(path)
394
 
 
395
 
    def path_content_summary(self, path):
396
 
        """Get a summary of the information about path.
397
 
 
398
 
        All the attributes returned are for the canonical form, not the
399
 
        convenient form (if content filters are in use.)
400
 
 
401
 
        :param path: A relative path within the tree.
402
 
        :return: A tuple containing kind, size, exec, sha1-or-link.
403
 
            Kind is always present (see tree.kind()).
404
 
            size is present if kind is file and the size of the
405
 
                canonical form can be cheaply determined, None otherwise.
406
 
            exec is None unless kind is file and the platform supports the 'x'
407
 
                bit.
408
 
            sha1-or-link is the link target if kind is symlink, or the sha1 if
409
 
                it can be obtained without reading the file.
410
 
        """
411
 
        raise NotImplementedError(self.path_content_summary)
412
 
 
413
 
    def get_reference_revision(self, path, branch=None):
414
 
        raise NotImplementedError("Tree subclass %s must implement "
415
 
                                  "get_reference_revision"
416
 
                                  % self.__class__.__name__)
417
 
 
418
 
    def _comparison_data(self, entry, path):
419
 
        """Return a tuple of kind, executable, stat_value for a file.
420
 
 
421
 
        entry may be None if there is no inventory entry for the file, but
422
 
        path must always be supplied.
423
 
 
424
 
        kind is None if there is no file present (even if an inventory id is
425
 
        present).  executable is False for non-file entries.
426
 
        """
427
 
        raise NotImplementedError(self._comparison_data)
428
 
 
429
 
    def get_file(self, path):
430
 
        """Return a file object for the file path in the tree.
431
 
        """
432
 
        raise NotImplementedError(self.get_file)
433
 
 
434
 
    def get_file_with_stat(self, path):
435
 
        """Get a file handle and stat object for path.
436
 
 
437
 
        The default implementation returns (self.get_file, None) for backwards
438
 
        compatibility.
439
 
 
440
 
        :param path: The path of the file.
441
 
        :return: A tuple (file_handle, stat_value_or_None). If the tree has
442
 
            no stat facility, or need for a stat cache feedback during commit,
443
 
            it may return None for the second element of the tuple.
444
 
        """
445
 
        return (self.get_file(path), None)
446
 
 
447
 
    def get_file_text(self, path):
448
 
        """Return the byte content of a file.
449
 
 
450
 
        :param path: The path of the file.
451
 
 
452
 
        :returns: A single byte string for the whole file.
453
 
        """
454
 
        with self.get_file(path) as my_file:
455
 
            return my_file.read()
456
 
 
457
 
    def get_file_lines(self, path):
458
 
        """Return the content of a file, as lines.
459
 
 
460
 
        :param path: The path of the file.
461
 
        """
462
 
        return osutils.split_lines(self.get_file_text(path))
463
 
 
464
 
    def get_file_verifier(self, path, stat_value=None):
465
 
        """Return a verifier for a file.
466
 
 
467
 
        The default implementation returns a sha1.
468
 
 
469
 
        :param path: The path that this file can be found at.
470
 
            These must point to the same object.
471
 
        :param stat_value: Optional stat value for the object
472
 
        :return: Tuple with verifier name and verifier data
473
 
        """
474
 
        return ("SHA1", self.get_file_sha1(path, stat_value=stat_value))
475
 
 
476
 
    def get_file_sha1(self, path, stat_value=None):
477
 
        """Return the SHA1 file for a file.
478
 
 
479
 
        :note: callers should use get_file_verifier instead
480
 
            where possible, as the underlying repository implementation may
481
 
            have quicker access to a non-sha1 verifier.
482
 
 
483
 
        :param path: The path that this file can be found at.
484
 
        :param stat_value: Optional stat value for the object
485
 
        """
486
 
        raise NotImplementedError(self.get_file_sha1)
487
 
 
488
 
    def get_file_mtime(self, path):
489
 
        """Return the modification time for a file.
490
 
 
491
 
        :param path: The path that this file can be found at.
492
 
        """
493
 
        raise NotImplementedError(self.get_file_mtime)
494
 
 
495
 
    def get_file_size(self, path):
496
 
        """Return the size of a file in bytes.
497
 
 
498
 
        This applies only to regular files.  If invoked on directories or
499
 
        symlinks, it will return None.
500
 
        """
501
 
        raise NotImplementedError(self.get_file_size)
502
 
 
503
 
    def is_executable(self, path):
504
 
        """Check if a file is executable.
505
 
 
506
 
        :param path: The path that this file can be found at.
507
 
        """
508
 
        raise NotImplementedError(self.is_executable)
509
 
 
510
 
    def iter_files_bytes(self, desired_files):
511
 
        """Iterate through file contents.
512
 
 
513
 
        Files will not necessarily be returned in the order they occur in
514
 
        desired_files.  No specific order is guaranteed.
515
 
 
516
 
        Yields pairs of identifier, bytes_iterator.  identifier is an opaque
517
 
        value supplied by the caller as part of desired_files.  It should
518
 
        uniquely identify the file version in the caller's context.  (Examples:
519
 
        an index number or a TreeTransform trans_id.)
520
 
 
521
 
        bytes_iterator is an iterable of bytestrings for the file.  The
522
 
        kind of iterable and length of the bytestrings are unspecified, but for
523
 
        this implementation, it is a tuple containing a single bytestring with
524
 
        the complete text of the file.
525
 
 
526
 
        :param desired_files: a list of (path, identifier) pairs
527
 
        """
528
 
        for path, identifier in desired_files:
529
 
            # We wrap the string in a tuple so that we can return an iterable
530
 
            # of bytestrings.  (Technically, a bytestring is also an iterable
531
 
            # of bytestrings, but iterating through each character is not
532
 
            # performant.)
533
 
            cur_file = (self.get_file_text(path),)
534
 
            yield identifier, cur_file
535
 
 
536
 
    def get_symlink_target(self, path):
537
 
        """Get the target for a given path.
538
 
 
539
 
        It is assumed that the caller already knows that path is referencing
540
 
        a symlink.
541
 
        :param path: The path of the file.
542
 
        :return: The path the symlink points to.
543
 
        """
544
 
        raise NotImplementedError(self.get_symlink_target)
545
 
 
546
 
    def annotate_iter(self, path,
547
 
                      default_revision=_mod_revision.CURRENT_REVISION):
548
 
        """Return an iterator of revision_id, line tuples.
549
 
 
550
 
        For working trees (and mutable trees in general), the special
551
 
        revision_id 'current:' will be used for lines that are new in this
552
 
        tree, e.g. uncommitted changes.
553
 
        :param path: The file to produce an annotated version from
554
 
        :param default_revision: For lines that don't match a basis, mark them
555
 
            with this revision id. Not all implementations will make use of
556
 
            this value.
557
 
        """
558
 
        raise NotImplementedError(self.annotate_iter)
559
 
 
560
 
    def path2id(self, path):
561
 
        """Return the id for path in this tree."""
562
 
        raise NotImplementedError(self.path2id)
563
 
 
564
 
    def is_versioned(self, path):
565
 
        """Check whether path is versioned.
566
 
 
567
 
        :param path: Path to check
568
 
        :return: boolean
569
 
        """
570
 
        return self.path2id(path) is not None
571
 
 
572
 
    def find_related_paths_across_trees(self, paths, trees=[],
573
 
                                        require_versioned=True):
574
 
        """Find related paths in tree corresponding to specified filenames in any
575
 
        of `lookup_trees`.
576
 
 
577
 
        All matches in all trees will be used, and all children of matched
578
 
        directories will be used.
579
 
 
580
 
        :param paths: The filenames to find related paths for (if None, returns
581
 
            None)
582
 
        :param trees: The trees to find file_ids within
583
 
        :param require_versioned: if true, all specified filenames must occur in
584
 
            at least one tree.
585
 
        :return: a set of paths for the specified filenames and their children
586
 
            in `tree`
587
 
        """
588
 
        raise NotImplementedError(self.find_related_paths_across_trees)
589
 
 
590
 
    def lock_read(self):
591
 
        """Lock this tree for multiple read only operations.
592
 
 
593
 
        :return: A breezy.lock.LogicalLockResult.
594
 
        """
595
 
        return lock.LogicalLockResult(self.unlock)
596
 
 
597
 
    def revision_tree(self, revision_id):
598
 
        """Obtain a revision tree for the revision revision_id.
599
 
 
600
 
        The intention of this method is to allow access to possibly cached
601
 
        tree data. Implementors of this method should raise NoSuchRevision if
602
 
        the tree is not locally available, even if they could obtain the
603
 
        tree via a repository or some other means. Callers are responsible
604
 
        for finding the ultimate source for a revision tree.
605
 
 
606
 
        :param revision_id: The revision_id of the requested tree.
607
 
        :return: A Tree.
608
 
        :raises: NoSuchRevision if the tree cannot be obtained.
609
 
        """
610
 
        raise errors.NoSuchRevisionInTree(self, revision_id)
611
 
 
612
 
    def unknowns(self):
613
 
        """What files are present in this tree and unknown.
614
 
 
615
 
        :return: an iterator over the unknown files.
616
 
        """
 
185
    def kind(self, file_id):
 
186
        assert self._inventory[file_id].kind == "root_directory"
 
187
        return "root_directory"
 
188
 
 
189
    def list_files(self):
617
190
        return iter([])
618
 
 
619
 
    def unlock(self):
 
191
    
 
192
    def __contains__(self, file_id):
 
193
        return file_id in self._inventory
 
194
 
 
195
    def get_file_sha1(self, file_id):
 
196
        assert self._inventory[file_id].kind == "root_directory"
 
197
        return None
 
198
 
 
199
 
 
200
######################################################################
 
201
# diff
 
202
 
 
203
# TODO: Merge these two functions into a single one that can operate
 
204
# on either a whole tree or a set of files.
 
205
 
 
206
# TODO: Return the diff in order by filename, not by category or in
 
207
# random order.  Can probably be done by lock-stepping through the
 
208
# filenames from both trees.
 
209
 
 
210
 
 
211
def file_status(filename, old_tree, new_tree):
 
212
    """Return single-letter status, old and new names for a file.
 
213
 
 
214
    The complexity here is in deciding how to represent renames;
 
215
    many complex cases are possible.
 
216
    """
 
217
    old_inv = old_tree.inventory
 
218
    new_inv = new_tree.inventory
 
219
    new_id = new_inv.path2id(filename)
 
220
    old_id = old_inv.path2id(filename)
 
221
 
 
222
    if not new_id and not old_id:
 
223
        # easy: doesn't exist in either; not versioned at all
 
224
        if new_tree.is_ignored(filename):
 
225
            return 'I', None, None
 
226
        else:
 
227
            return '?', None, None
 
228
    elif new_id:
 
229
        # There is now a file of this name, great.
620
230
        pass
621
 
 
622
 
    def filter_unversioned_files(self, paths):
623
 
        """Filter out paths that are versioned.
624
 
 
625
 
        :return: set of paths.
626
 
        """
627
 
        # NB: we specifically *don't* call self.has_filename, because for
628
 
        # WorkingTrees that can indicate files that exist on disk but that
629
 
        # are not versioned.
630
 
        return set(p for p in paths if not self.is_versioned(p))
631
 
 
632
 
    def walkdirs(self, prefix=""):
633
 
        """Walk the contents of this tree from path down.
634
 
 
635
 
        This yields all the data about the contents of a directory at a time.
636
 
        After each directory has been yielded, if the caller has mutated the
637
 
        list to exclude some directories, they are then not descended into.
638
 
 
639
 
        The data yielded is of the form:
640
 
        ((directory-relpath, directory-path-from-root, directory-fileid),
641
 
        [(relpath, basename, kind, lstat, path_from_tree_root, file_id,
642
 
          versioned_kind), ...]),
643
 
         - directory-relpath is the containing dirs relpath from prefix
644
 
         - directory-path-from-root is the containing dirs path from /
645
 
         - directory-fileid is the id of the directory if it is versioned.
646
 
         - relpath is the relative path within the subtree being walked.
647
 
         - basename is the basename
648
 
         - kind is the kind of the file now. If unknonwn then the file is not
649
 
           present within the tree - but it may be recorded as versioned. See
650
 
           versioned_kind.
651
 
         - lstat is the stat data *if* the file was statted.
652
 
         - path_from_tree_root is the path from the root of the tree.
653
 
         - file_id is the file_id if the entry is versioned.
654
 
         - versioned_kind is the kind of the file as last recorded in the
655
 
           versioning system. If 'unknown' the file is not versioned.
656
 
        One of 'kind' and 'versioned_kind' must not be 'unknown'.
657
 
 
658
 
        :param prefix: Start walking from prefix within the tree rather than
659
 
        at the root. This allows one to walk a subtree but get paths that are
660
 
        relative to a tree rooted higher up.
661
 
        :return: an iterator over the directory data.
662
 
        """
663
 
        raise NotImplementedError(self.walkdirs)
664
 
 
665
 
    def supports_content_filtering(self):
666
 
        return False
667
 
 
668
 
    def _content_filter_stack(self, path=None):
669
 
        """The stack of content filters for a path if filtering is supported.
670
 
 
671
 
        Readers will be applied in first-to-last order.
672
 
        Writers will be applied in last-to-first order.
673
 
        Either the path or the file-id needs to be provided.
674
 
 
675
 
        :param path: path relative to the root of the tree
676
 
            or None if unknown
677
 
        :return: the list of filters - [] if there are none
678
 
        """
679
 
        from . import debug, filters
680
 
        filter_pref_names = filters._get_registered_names()
681
 
        if len(filter_pref_names) == 0:
682
 
            return []
683
 
        prefs = next(self.iter_search_rules([path], filter_pref_names))
684
 
        stk = filters._get_filter_stack_for(prefs)
685
 
        if 'filters' in debug.debug_flags:
686
 
            trace.note(
687
 
                gettext("*** {0} content-filter: {1} => {2!r}").format(path, prefs, stk))
688
 
        return stk
689
 
 
690
 
    def _content_filter_stack_provider(self):
691
 
        """A function that returns a stack of ContentFilters.
692
 
 
693
 
        The function takes a path (relative to the top of the tree) and a
694
 
        file-id as parameters.
695
 
 
696
 
        :return: None if content filtering is not supported by this tree.
697
 
        """
698
 
        if self.supports_content_filtering():
699
 
            return self._content_filter_stack
700
 
        else:
701
 
            return None
702
 
 
703
 
    def iter_search_rules(self, path_names, pref_names=None,
704
 
                          _default_searcher=None):
705
 
        """Find the preferences for filenames in a tree.
706
 
 
707
 
        :param path_names: an iterable of paths to find attributes for.
708
 
          Paths are given relative to the root of the tree.
709
 
        :param pref_names: the list of preferences to lookup - None for all
710
 
        :param _default_searcher: private parameter to assist testing - don't use
711
 
        :return: an iterator of tuple sequences, one per path-name.
712
 
          See _RulesSearcher.get_items for details on the tuple sequence.
713
 
        """
714
 
        from . import rules
715
 
        if _default_searcher is None:
716
 
            _default_searcher = rules._per_user_searcher
717
 
        searcher = self._get_rules_searcher(_default_searcher)
718
 
        if searcher is not None:
719
 
            if pref_names is not None:
720
 
                for path in path_names:
721
 
                    yield searcher.get_selected_items(path, pref_names)
722
 
            else:
723
 
                for path in path_names:
724
 
                    yield searcher.get_items(path)
725
 
 
726
 
    def _get_rules_searcher(self, default_searcher):
727
 
        """Get the RulesSearcher for this tree given the default one."""
728
 
        searcher = default_searcher
729
 
        return searcher
730
 
 
731
 
    def archive(self, format, name, root='', subdir=None,
732
 
                force_mtime=None):
733
 
        """Create an archive of this tree.
734
 
 
735
 
        :param format: Format name (e.g. 'tar')
736
 
        :param name: target file name
737
 
        :param root: Root directory name (or None)
738
 
        :param subdir: Subdirectory to export (or None)
739
 
        :return: Iterator over archive chunks
740
 
        """
741
 
        from .archive import create_archive
742
 
        with self.lock_read():
743
 
            return create_archive(format, self, name, root,
744
 
                                  subdir, force_mtime=force_mtime)
745
 
 
746
 
    @classmethod
747
 
    def versionable_kind(cls, kind):
748
 
        """Check if this tree support versioning a specific file kind."""
749
 
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
750
 
 
751
 
    def preview_transform(self, pb=None):
752
 
        """Obtain a transform object."""
753
 
        raise NotImplementedError(self.preview_transform)
754
 
 
755
 
 
756
 
class InterTree(InterObject):
757
 
    """This class represents operations taking place between two Trees.
758
 
 
759
 
    Its instances have methods like 'compare' and contain references to the
760
 
    source and target trees these operations are to be carried out on.
761
 
 
762
 
    Clients of breezy should not need to use InterTree directly, rather they
763
 
    should use the convenience methods on Tree such as 'Tree.compare()' which
764
 
    will pass through to InterTree as appropriate.
765
 
    """
766
 
 
767
 
    # Formats that will be used to test this InterTree. If both are
768
 
    # None, this InterTree will not be tested (e.g. because a complex
769
 
    # setup is required)
770
 
    _matching_from_tree_format = None
771
 
    _matching_to_tree_format = None
772
 
 
773
 
    _optimisers = []
774
 
 
775
 
    @classmethod
776
 
    def is_compatible(kls, source, target):
777
 
        # The default implementation is naive and uses the public API, so
778
 
        # it works for all trees.
779
 
        return True
780
 
 
781
 
    def compare(self, want_unchanged=False, specific_files=None,
782
 
                extra_trees=None, require_versioned=False, include_root=False,
783
 
                want_unversioned=False):
784
 
        """Return the changes from source to target.
785
 
 
786
 
        :return: A TreeDelta.
787
 
        :param specific_files: An optional list of file paths to restrict the
788
 
            comparison to. When mapping filenames to ids, all matches in all
789
 
            trees (including optional extra_trees) are used, and all children of
790
 
            matched directories are included.
791
 
        :param want_unchanged: An optional boolean requesting the inclusion of
792
 
            unchanged entries in the result.
793
 
        :param extra_trees: An optional list of additional trees to use when
794
 
            mapping the contents of specific_files (paths) to file_ids.
795
 
        :param require_versioned: An optional boolean (defaults to False). When
796
 
            supplied and True all the 'specific_files' must be versioned, or
797
 
            a PathsNotVersionedError will be thrown.
798
 
        :param want_unversioned: Scan for unversioned paths.
799
 
        """
800
 
        from . import delta
801
 
        trees = (self.source,)
802
 
        if extra_trees is not None:
803
 
            trees = trees + tuple(extra_trees)
804
 
        with self.lock_read():
805
 
            return delta._compare_trees(self.source, self.target, want_unchanged,
806
 
                                        specific_files, include_root, extra_trees=extra_trees,
807
 
                                        require_versioned=require_versioned,
808
 
                                        want_unversioned=want_unversioned)
809
 
 
810
 
    def iter_changes(self, include_unchanged=False,
811
 
                     specific_files=None, pb=None, extra_trees=[],
812
 
                     require_versioned=True, want_unversioned=False):
813
 
        """Generate an iterator of changes between trees.
814
 
 
815
 
        A tuple is returned:
816
 
        (file_id, (path_in_source, path_in_target),
817
 
         changed_content, versioned, parent, name, kind,
818
 
         executable)
819
 
 
820
 
        Changed_content is True if the file's content has changed.  This
821
 
        includes changes to its kind, and to a symlink's target.
822
 
 
823
 
        versioned, parent, name, kind, executable are tuples of (from, to).
824
 
        If a file is missing in a tree, its kind is None.
825
 
 
826
 
        Iteration is done in parent-to-child order, relative to the target
827
 
        tree.
828
 
 
829
 
        There is no guarantee that all paths are in sorted order: the
830
 
        requirement to expand the search due to renames may result in children
831
 
        that should be found early being found late in the search, after
832
 
        lexically later results have been returned.
833
 
        :param require_versioned: Raise errors.PathsNotVersionedError if a
834
 
            path in the specific_files list is not versioned in one of
835
 
            source, target or extra_trees.
836
 
        :param specific_files: An optional list of file paths to restrict the
837
 
            comparison to. When mapping filenames to ids, all matches in all
838
 
            trees (including optional extra_trees) are used, and all children
839
 
            of matched directories are included. The parents in the target tree
840
 
            of the specific files up to and including the root of the tree are
841
 
            always evaluated for changes too.
842
 
        :param want_unversioned: Should unversioned files be returned in the
843
 
            output. An unversioned file is defined as one with (False, False)
844
 
            for the versioned pair.
845
 
        """
846
 
        raise NotImplementedError(self.iter_changes)
847
 
 
848
 
    def file_content_matches(
849
 
            self, source_path, target_path,
850
 
            source_stat=None, target_stat=None):
851
 
        """Check if two files are the same in the source and target trees.
852
 
 
853
 
        This only checks that the contents of the files are the same,
854
 
        it does not touch anything else.
855
 
 
856
 
        :param source_path: Path of the file in the source tree
857
 
        :param target_path: Path of the file in the target tree
858
 
        :param source_stat: Optional stat value of the file in the source tree
859
 
        :param target_stat: Optional stat value of the file in the target tree
860
 
        :return: Boolean indicating whether the files have the same contents
861
 
        """
862
 
        with self.lock_read():
863
 
            source_verifier_kind, source_verifier_data = (
864
 
                self.source.get_file_verifier(source_path, source_stat))
865
 
            target_verifier_kind, target_verifier_data = (
866
 
                self.target.get_file_verifier(
867
 
                    target_path, target_stat))
868
 
            if source_verifier_kind == target_verifier_kind:
869
 
                return (source_verifier_data == target_verifier_data)
870
 
            # Fall back to SHA1 for now
871
 
            if source_verifier_kind != "SHA1":
872
 
                source_sha1 = self.source.get_file_sha1(
873
 
                    source_path, source_stat)
874
 
            else:
875
 
                source_sha1 = source_verifier_data
876
 
            if target_verifier_kind != "SHA1":
877
 
                target_sha1 = self.target.get_file_sha1(
878
 
                    target_path, target_stat)
879
 
            else:
880
 
                target_sha1 = target_verifier_data
881
 
            return (source_sha1 == target_sha1)
882
 
 
883
 
    def find_target_path(self, path, recurse='none'):
884
 
        """Find target tree path.
885
 
 
886
 
        :param path: Path to search for (exists in source)
887
 
        :return: path in target, or None if there is no equivalent path.
888
 
        :raise NoSuchFile: If the path doesn't exist in source
889
 
        """
890
 
        raise NotImplementedError(self.find_target_path)
891
 
 
892
 
    def find_source_path(self, path, recurse='none'):
893
 
        """Find the source tree path.
894
 
 
895
 
        :param path: Path to search for (exists in target)
896
 
        :return: path in source, or None if there is no equivalent path.
897
 
        :raise NoSuchFile: if the path doesn't exist in target
898
 
        """
899
 
        raise NotImplementedError(self.find_source_path)
900
 
 
901
 
    def find_target_paths(self, paths, recurse='none'):
902
 
        """Find target tree paths.
903
 
 
904
 
        :param paths: Iterable over paths in target to search for
905
 
        :return: Dictionary mapping from source paths to paths in target , or
906
 
            None if there is no equivalent path.
907
 
        """
908
 
        ret = {}
909
 
        for path in paths:
910
 
            ret[path] = self.find_target_path(path, recurse=recurse)
911
 
        return ret
912
 
 
913
 
    def find_source_paths(self, paths, recurse='none'):
914
 
        """Find source tree paths.
915
 
 
916
 
        :param paths: Iterable over paths in target to search for
917
 
        :return: Dictionary mapping from target paths to paths in source, or
918
 
            None if there is no equivalent path.
919
 
        """
920
 
        ret = {}
921
 
        for path in paths:
922
 
            ret[path] = self.find_source_path(path, recurse=recurse)
923
 
        return ret
924
 
 
925
 
 
926
 
def find_previous_paths(from_tree, to_tree, paths, recurse='none'):
927
 
    """Find previous tree paths.
928
 
 
929
 
    :param from_tree: From tree
930
 
    :param to_tree: To tree
931
 
    :param paths: Iterable over paths in from_tree to search for
932
 
    :return: Dictionary mapping from from_tree paths to paths in to_tree, or
933
 
        None if there is no equivalent path.
934
 
    """
935
 
    return InterTree.get(to_tree, from_tree).find_source_paths(paths, recurse=recurse)
936
 
 
937
 
 
938
 
def find_previous_path(from_tree, to_tree, path, recurse='none'):
939
 
    """Find previous tree path.
940
 
 
941
 
    :param from_tree: From tree
942
 
    :param to_tree: To tree
943
 
    :param path: Path to search for (exists in from_tree)
944
 
    :return: path in to_tree, or None if there is no equivalent path.
945
 
    :raise NoSuchFile: If the path doesn't exist in from_tree
946
 
    """
947
 
    return InterTree.get(to_tree, from_tree).find_source_path(
948
 
        path, recurse=recurse)
949
 
 
950
 
 
951
 
def get_canonical_path(tree, path, normalize):
952
 
    """Find the canonical path of an item, ignoring case.
953
 
 
954
 
    :param tree: Tree to traverse
955
 
    :param path: Case-insensitive path to look up
956
 
    :param normalize: Function to normalize a filename for comparison
957
 
    :return: The canonical path
958
 
    """
959
 
    # go walkin...
960
 
    cur_path = ''
961
 
    bit_iter = iter(path.split("/"))
962
 
    for elt in bit_iter:
963
 
        lelt = normalize(elt)
964
 
        new_path = None
965
 
        try:
966
 
            for child in tree.iter_child_entries(cur_path):
967
 
                try:
968
 
                    if child.name == elt:
969
 
                        # if we found an exact match, we can stop now; if
970
 
                        # we found an approximate match we need to keep
971
 
                        # searching because there might be an exact match
972
 
                        # later.
973
 
                        new_path = osutils.pathjoin(cur_path, child.name)
974
 
                        break
975
 
                    elif normalize(child.name) == lelt:
976
 
                        new_path = osutils.pathjoin(cur_path, child.name)
977
 
                except errors.NoSuchId:
978
 
                    # before a change is committed we can see this error...
979
 
                    continue
980
 
        except errors.NotADirectory:
981
 
            pass
982
 
        if new_path:
983
 
            cur_path = new_path
984
 
        else:
985
 
            # got to the end of this directory and no entries matched.
986
 
            # Return what matched so far, plus the rest as specified.
987
 
            cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
988
 
            break
989
 
    return cur_path
 
231
    else:
 
232
        # There is no longer a file of this name, but we can describe
 
233
        # what happened to the file that used to have
 
234
        # this name.  There are two possibilities: either it was
 
235
        # deleted entirely, or renamed.
 
236
        assert old_id
 
237
        if new_inv.has_id(old_id):
 
238
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
 
239
        else:
 
240
            return 'D', old_inv.id2path(old_id), None
 
241
 
 
242
    # if the file_id is new in this revision, it is added
 
243
    if new_id and not old_inv.has_id(new_id):
 
244
        return 'A'
 
245
 
 
246
    # if there used to be a file of this name, but that ID has now
 
247
    # disappeared, it is deleted
 
248
    if old_id and not new_inv.has_id(old_id):
 
249
        return 'D'
 
250
 
 
251
    return 'wtf?'
 
252
 
 
253
    
 
254
 
 
255
def find_renames(old_inv, new_inv):
 
256
    for file_id in old_inv:
 
257
        if file_id not in new_inv:
 
258
            continue
 
259
        old_name = old_inv.id2path(file_id)
 
260
        new_name = new_inv.id2path(file_id)
 
261
        if old_name != new_name:
 
262
            yield (old_name, new_name)
 
263
            
 
264
 
 
265