1
# Copyright (C) 2005-2011 Canonical Ltd
1
# Copyright (C) 2005 Canonical Ltd
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.
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.
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
17
17
"""Tree classes, representing directory at point in time.
20
from .lazy_import import lazy_import
21
lazy_import(globals(), """
22
from breezy.i18n import gettext
29
revision as _mod_revision,
32
from .inter import InterObject
35
class FileTimestampUnavailable(errors.BzrError):
37
_fmt = "The filestamp for %(path)s is not available."
41
def __init__(self, path):
45
class MissingNestedTree(errors.BzrError):
47
_fmt = "The nested tree for %(path)s can not be resolved."""
49
def __init__(self, path):
53
class TreeEntry(object):
54
"""An entry that implements the minimum interface used by commands.
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__)
66
def kind_character(self):
69
def is_unmodified(self, other):
70
"""Does this entry reference the same entry?
72
This is mostly the same as __eq__, but returns False
73
for entries without enough information (i.e. revision is None)
78
class TreeDirectory(TreeEntry):
79
"""See TreeEntry. This is a directory in a working tree."""
85
def kind_character(self):
89
class TreeFile(TreeEntry):
90
"""See TreeEntry. This is a regular file in a working tree."""
96
def kind_character(self):
100
class TreeLink(TreeEntry):
101
"""See TreeEntry. This is a symlink in a working tree."""
107
def kind_character(self):
111
class TreeReference(TreeEntry):
112
"""See TreeEntry. This is a reference to a nested tree in a working tree."""
116
kind = 'tree-reference'
118
def kind_character(self):
122
class TreeChange(object):
123
"""Describes the changes between the same item in two different trees."""
125
__slots__ = ['file_id', 'path', 'changed_content', 'versioned', 'parent_id',
126
'name', 'kind', 'executable', 'copied']
128
def __init__(self, file_id, path, changed_content, versioned, parent_id,
129
name, kind, executable, copied=False):
130
self.file_id = file_id
132
self.changed_content = changed_content
133
self.versioned = versioned
134
self.parent_id = parent_id
137
self.executable = executable
141
return "%s%r" % (self.__class__.__name__, self._as_tuple())
144
return len(self.__slots__)
147
return (self.file_id, self.path, self.changed_content, self.versioned,
148
self.parent_id, self.name, self.kind, self.executable, self.copied)
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
157
def __lt__(self, other):
158
return self._as_tuple() < other._as_tuple()
160
def meta_modified(self):
161
if self.versioned == (True, True):
162
return (self.executable[0] != self.executable[1])
165
def is_reparented(self):
166
return self.parent_id[0] != self.parent_id[1]
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),
21
from cStringIO import StringIO
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
177
29
class Tree(object):
178
30
"""Abstract file tree.
180
32
There are several subclasses:
182
34
* `WorkingTree` exists as files on disk editable by the user.
184
36
* `RevisionTree` is a tree as recorded at some point in the past.
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.
44
It is possible for trees to contain files that are not described
45
in their inventory or vice versa; for this use `filenames()`.
186
47
Trees can be compared, etc, regardless of whether they are working
187
48
trees or versioned trees.
190
def supports_rename_tracking(self):
191
"""Whether this tree supports rename tracking.
193
This defaults to True, but some implementations may want to override
198
def has_versioned_directories(self):
199
"""Whether this tree can contain explicitly versioned directories.
201
This defaults to True, but some implementations may want to override
206
def supports_symlinks(self):
207
"""Does this tree support symbolic links?
209
return osutils.has_symlinks()
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.
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.
230
The comparison will be performed by an InterTree object looked up on
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,
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)
255
"""Get a list of the conflicts in the tree.
257
Each conflict is an instance of breezy.conflicts.Conflict.
259
from . import conflicts as _mod_conflicts
260
return _mod_conflicts.ConflictList()
263
"""For trees that can have unversioned files, return all such paths."""
266
def get_parent_ids(self):
267
"""Get the parent ids for this tree.
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.
273
raise NotImplementedError(self.get_parent_ids)
275
51
def has_filename(self, filename):
276
52
"""True if the tree has given filename."""
277
raise NotImplementedError(self.has_filename)
279
def is_ignored(self, filename):
280
"""Check whether the filename is ignored by this tree.
282
:param filename: The relative filename within the tree.
283
:return: True if the filename is ignored.
53
raise NotImplementedError()
55
def has_id(self, file_id):
56
return self.inventory.has_id(file_id)
58
def has_or_had_id(self, file_id):
59
if file_id == self.inventory.root.file_id:
61
return self.inventory.has_id(file_id)
66
return iter(self.inventory)
68
def id2path(self, file_id):
69
return self.inventory.id2path(file_id)
71
def kind(self, file_id):
72
raise NotImplementedError("subclasses must implement kind")
74
def _get_inventory(self):
75
return self._inventory
77
def get_file_by_path(self, path):
78
return self.get_file(self._inventory.path2id(path))
80
inventory = property(_get_inventory,
81
doc="Inventory of this Tree")
83
def _check_retrieved(self, ie, f):
86
fp = fingerprint_file(f)
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"])
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"])
103
def print_file(self, file_id):
104
"""Print file with id `file_id` to stdout."""
106
sys.stdout.write(self.get_file_text(file_id))
109
class RevisionTree(Tree):
110
"""Tree viewing a previous revision.
112
File text can be retrieved from the text store.
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.
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
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())
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)
138
def get_file_text(self, file_id):
139
return ''.join(self.get_file_lines(file_id))
142
def get_file(self, file_id):
143
return StringIO(self.get_file_text(file_id))
145
def get_file_size(self, file_id):
146
return self._inventory[file_id].text_size
148
def get_file_sha1(self, file_id):
149
ie = self._inventory[file_id]
150
if ie.kind == "file":
153
def is_executable(self, file_id):
154
ie = self._inventory[file_id]
155
if ie.kind != "file":
157
return self._inventory[file_id].executable
159
def has_filename(self, filename):
160
return bool(self.inventory.path2id(filename))
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
167
def get_symlink_target(self, file_id):
168
ie = self._inventory[file_id]
169
return ie.symlink_target;
171
def kind(self, file_id):
172
return self._inventory[file_id].kind
175
class EmptyTree(Tree):
177
self._inventory = Inventory()
179
def get_symlink_target(self, file_id):
182
def has_filename(self, filename):
287
def all_file_ids(self):
288
"""Iterate through all file ids, including ids for missing files."""
289
raise NotImplementedError(self.all_file_ids)
291
def all_versioned_paths(self):
292
"""Iterate through all paths, including paths for missing files."""
293
raise NotImplementedError(self.all_versioned_paths)
295
def id2path(self, file_id, recurse='down'):
296
"""Return the path for a file id.
300
raise NotImplementedError(self.id2path)
302
def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
303
"""Walk the tree in 'by_dir' order.
305
This will yield each entry in the tree as a (path, entry) tuple.
306
The order that they are yielded is:
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
313
For example, in the tree::
323
The yield order (ignoring root) would be::
325
a, f, a/b, a/d, a/b/c, a/d/e, f/g
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.
331
raise NotImplementedError(self.iter_entries_by_dir)
333
def iter_child_entries(self, path):
334
"""Iterate over the children of a directory or tree reference.
336
:param path: Path of the directory
337
:raise NoSuchFile: When the path does not exist
338
:return: Iterator over entries in the directory
340
raise NotImplementedError(self.iter_child_entries)
342
def list_files(self, include_root=False, from_dir=None, recursive=True,
343
recurse_nested=False):
344
"""List all files in this tree.
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)
353
raise NotImplementedError(self.list_files)
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':
361
def get_containing_nested_tree(self, path):
362
"""Find the nested tree that contains a path.
364
:return: tuple with (nested tree and path inside the nested tree)
366
for nested_path in self.iter_references():
368
if path.startswith(nested_path):
369
nested_tree = self.get_nested_tree(nested_path)
370
return nested_tree, path[len(nested_path):]
374
def get_nested_tree(self, path):
375
"""Open the nested tree at the specified path.
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
381
raise NotImplementedError(self.get_nested_tree)
383
def kind(self, path):
384
raise NotImplementedError("Tree subclass %s must implement kind"
385
% self.__class__.__name__)
387
def stored_kind(self, path):
388
"""File kind stored for this path.
390
May not match kind on disk for working trees. Always available
391
for versioned files, even when the file itself is missing.
393
return self.kind(path)
395
def path_content_summary(self, path):
396
"""Get a summary of the information about path.
398
All the attributes returned are for the canonical form, not the
399
convenient form (if content filters are in use.)
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'
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.
411
raise NotImplementedError(self.path_content_summary)
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__)
418
def _comparison_data(self, entry, path):
419
"""Return a tuple of kind, executable, stat_value for a file.
421
entry may be None if there is no inventory entry for the file, but
422
path must always be supplied.
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.
427
raise NotImplementedError(self._comparison_data)
429
def get_file(self, path):
430
"""Return a file object for the file path in the tree.
432
raise NotImplementedError(self.get_file)
434
def get_file_with_stat(self, path):
435
"""Get a file handle and stat object for path.
437
The default implementation returns (self.get_file, None) for backwards
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.
445
return (self.get_file(path), None)
447
def get_file_text(self, path):
448
"""Return the byte content of a file.
450
:param path: The path of the file.
452
:returns: A single byte string for the whole file.
454
with self.get_file(path) as my_file:
455
return my_file.read()
457
def get_file_lines(self, path):
458
"""Return the content of a file, as lines.
460
:param path: The path of the file.
462
return osutils.split_lines(self.get_file_text(path))
464
def get_file_verifier(self, path, stat_value=None):
465
"""Return a verifier for a file.
467
The default implementation returns a sha1.
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
474
return ("SHA1", self.get_file_sha1(path, stat_value=stat_value))
476
def get_file_sha1(self, path, stat_value=None):
477
"""Return the SHA1 file for a file.
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.
483
:param path: The path that this file can be found at.
484
:param stat_value: Optional stat value for the object
486
raise NotImplementedError(self.get_file_sha1)
488
def get_file_mtime(self, path):
489
"""Return the modification time for a file.
491
:param path: The path that this file can be found at.
493
raise NotImplementedError(self.get_file_mtime)
495
def get_file_size(self, path):
496
"""Return the size of a file in bytes.
498
This applies only to regular files. If invoked on directories or
499
symlinks, it will return None.
501
raise NotImplementedError(self.get_file_size)
503
def is_executable(self, path):
504
"""Check if a file is executable.
506
:param path: The path that this file can be found at.
508
raise NotImplementedError(self.is_executable)
510
def iter_files_bytes(self, desired_files):
511
"""Iterate through file contents.
513
Files will not necessarily be returned in the order they occur in
514
desired_files. No specific order is guaranteed.
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.)
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.
526
:param desired_files: a list of (path, identifier) pairs
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
533
cur_file = (self.get_file_text(path),)
534
yield identifier, cur_file
536
def get_symlink_target(self, path):
537
"""Get the target for a given path.
539
It is assumed that the caller already knows that path is referencing
541
:param path: The path of the file.
542
:return: The path the symlink points to.
544
raise NotImplementedError(self.get_symlink_target)
546
def annotate_iter(self, path,
547
default_revision=_mod_revision.CURRENT_REVISION):
548
"""Return an iterator of revision_id, line tuples.
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
558
raise NotImplementedError(self.annotate_iter)
560
def path2id(self, path):
561
"""Return the id for path in this tree."""
562
raise NotImplementedError(self.path2id)
564
def is_versioned(self, path):
565
"""Check whether path is versioned.
567
:param path: Path to check
570
return self.path2id(path) is not None
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
577
All matches in all trees will be used, and all children of matched
578
directories will be used.
580
:param paths: The filenames to find related paths for (if None, returns
582
:param trees: The trees to find file_ids within
583
:param require_versioned: if true, all specified filenames must occur in
585
:return: a set of paths for the specified filenames and their children
588
raise NotImplementedError(self.find_related_paths_across_trees)
591
"""Lock this tree for multiple read only operations.
593
:return: A breezy.lock.LogicalLockResult.
595
return lock.LogicalLockResult(self.unlock)
597
def revision_tree(self, revision_id):
598
"""Obtain a revision tree for the revision revision_id.
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.
606
:param revision_id: The revision_id of the requested tree.
608
:raises: NoSuchRevision if the tree cannot be obtained.
610
raise errors.NoSuchRevisionInTree(self, revision_id)
613
"""What files are present in this tree and unknown.
615
:return: an iterator over the unknown files.
185
def kind(self, file_id):
186
assert self._inventory[file_id].kind == "root_directory"
187
return "root_directory"
189
def list_files(self):
192
def __contains__(self, file_id):
193
return file_id in self._inventory
195
def get_file_sha1(self, file_id):
196
assert self._inventory[file_id].kind == "root_directory"
200
######################################################################
203
# TODO: Merge these two functions into a single one that can operate
204
# on either a whole tree or a set of files.
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.
211
def file_status(filename, old_tree, new_tree):
212
"""Return single-letter status, old and new names for a file.
214
The complexity here is in deciding how to represent renames;
215
many complex cases are possible.
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)
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
227
return '?', None, None
229
# There is now a file of this name, great.
622
def filter_unversioned_files(self, paths):
623
"""Filter out paths that are versioned.
625
:return: set of paths.
627
# NB: we specifically *don't* call self.has_filename, because for
628
# WorkingTrees that can indicate files that exist on disk but that
630
return set(p for p in paths if not self.is_versioned(p))
632
def walkdirs(self, prefix=""):
633
"""Walk the contents of this tree from path down.
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.
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
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'.
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.
663
raise NotImplementedError(self.walkdirs)
665
def supports_content_filtering(self):
668
def _content_filter_stack(self, path=None):
669
"""The stack of content filters for a path if filtering is supported.
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.
675
:param path: path relative to the root of the tree
677
:return: the list of filters - [] if there are none
679
from . import debug, filters
680
filter_pref_names = filters._get_registered_names()
681
if len(filter_pref_names) == 0:
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:
687
gettext("*** {0} content-filter: {1} => {2!r}").format(path, prefs, stk))
690
def _content_filter_stack_provider(self):
691
"""A function that returns a stack of ContentFilters.
693
The function takes a path (relative to the top of the tree) and a
694
file-id as parameters.
696
:return: None if content filtering is not supported by this tree.
698
if self.supports_content_filtering():
699
return self._content_filter_stack
703
def iter_search_rules(self, path_names, pref_names=None,
704
_default_searcher=None):
705
"""Find the preferences for filenames in a tree.
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.
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)
723
for path in path_names:
724
yield searcher.get_items(path)
726
def _get_rules_searcher(self, default_searcher):
727
"""Get the RulesSearcher for this tree given the default one."""
728
searcher = default_searcher
731
def archive(self, format, name, root='', subdir=None,
733
"""Create an archive of this tree.
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
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)
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'))
751
def preview_transform(self, pb=None):
752
"""Obtain a transform object."""
753
raise NotImplementedError(self.preview_transform)
756
class InterTree(InterObject):
757
"""This class represents operations taking place between two Trees.
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.
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.
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
770
_matching_from_tree_format = None
771
_matching_to_tree_format = None
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.
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.
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.
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)
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.
816
(file_id, (path_in_source, path_in_target),
817
changed_content, versioned, parent, name, kind,
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.
823
versioned, parent, name, kind, executable are tuples of (from, to).
824
If a file is missing in a tree, its kind is None.
826
Iteration is done in parent-to-child order, relative to the target
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.
846
raise NotImplementedError(self.iter_changes)
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.
853
This only checks that the contents of the files are the same,
854
it does not touch anything else.
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
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)
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)
880
target_sha1 = target_verifier_data
881
return (source_sha1 == target_sha1)
883
def find_target_path(self, path, recurse='none'):
884
"""Find target tree path.
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
890
raise NotImplementedError(self.find_target_path)
892
def find_source_path(self, path, recurse='none'):
893
"""Find the source tree path.
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
899
raise NotImplementedError(self.find_source_path)
901
def find_target_paths(self, paths, recurse='none'):
902
"""Find target tree paths.
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.
910
ret[path] = self.find_target_path(path, recurse=recurse)
913
def find_source_paths(self, paths, recurse='none'):
914
"""Find source tree paths.
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.
922
ret[path] = self.find_source_path(path, recurse=recurse)
926
def find_previous_paths(from_tree, to_tree, paths, recurse='none'):
927
"""Find previous tree paths.
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.
935
return InterTree.get(to_tree, from_tree).find_source_paths(paths, recurse=recurse)
938
def find_previous_path(from_tree, to_tree, path, recurse='none'):
939
"""Find previous tree path.
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
947
return InterTree.get(to_tree, from_tree).find_source_path(
948
path, recurse=recurse)
951
def get_canonical_path(tree, path, normalize):
952
"""Find the canonical path of an item, ignoring case.
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
961
bit_iter = iter(path.split("/"))
963
lelt = normalize(elt)
966
for child in tree.iter_child_entries(cur_path):
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
973
new_path = osutils.pathjoin(cur_path, child.name)
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...
980
except errors.NotADirectory:
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))
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.
237
if new_inv.has_id(old_id):
238
return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
240
return 'D', old_inv.id2path(old_id), None
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):
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):
255
def find_renames(old_inv, new_inv):
256
for file_id in old_inv:
257
if file_id not in new_inv:
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)