1
# Copyright (C) 2005-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tree classes, representing directory at point in time.
20
from __future__ import absolute_import
31
from ..mutabletree import (
34
from ..revisiontree import (
37
lazy_import.lazy_import(globals(), """
42
transport as _mod_transport,
44
from breezy.bzr import (
45
inventory as _mod_inventory,
48
from ..sixish import (
52
FileTimestampUnavailable,
58
class InventoryTree(Tree):
59
"""A tree that relies on an inventory for its metadata.
61
Trees contain an `Inventory` object, and also know how to retrieve
62
file texts mentioned in the inventory, either from a working
63
directory or from a store.
65
It is possible for trees to contain files that are not described
66
in their inventory or vice versa; for this use `filenames()`.
68
Subclasses should set the _inventory attribute, which is considered
69
private to external API users.
72
def get_canonical_inventory_paths(self, paths):
73
"""Like get_canonical_inventory_path() but works on multiple items.
75
:param paths: A sequence of paths relative to the root of the tree.
76
:return: A list of paths, with each item the corresponding input path
77
adjusted to account for existing elements that match case
80
return list(self._yield_canonical_inventory_paths(paths))
82
def get_canonical_inventory_path(self, path):
83
"""Returns the first inventory item that case-insensitively matches path.
85
If a path matches exactly, it is returned. If no path matches exactly
86
but more than one path matches case-insensitively, it is implementation
87
defined which is returned.
89
If no path matches case-insensitively, the input path is returned, but
90
with as many path entries that do exist changed to their canonical
93
If you need to resolve many names from the same tree, you should
94
use get_canonical_inventory_paths() to avoid O(N) behaviour.
96
:param path: A paths relative to the root of the tree.
97
:return: The input path adjusted to account for existing elements
98
that match case insensitively.
100
return next(self._yield_canonical_inventory_paths([path]))
102
def _yield_canonical_inventory_paths(self, paths):
104
# First, if the path as specified exists exactly, just use it.
105
if self.path2id(path) is not None:
109
cur_id = self.get_root_id()
111
bit_iter = iter(path.split("/"))
115
for child in self.iter_children(cur_id):
117
# XXX: it seem like if the child is known to be in the
118
# tree, we shouldn't need to go from its id back to
119
# its path -- mbp 2010-02-11
121
# XXX: it seems like we could be more efficient
122
# by just directly looking up the original name and
123
# only then searching all children; also by not
124
# chopping paths so much. -- mbp 2010-02-11
125
child_base = os.path.basename(self.id2path(child))
126
if (child_base == elt):
127
# if we found an exact match, we can stop now; if
128
# we found an approximate match we need to keep
129
# searching because there might be an exact match
132
new_path = osutils.pathjoin(cur_path, child_base)
134
elif child_base.lower() == lelt:
136
new_path = osutils.pathjoin(cur_path, child_base)
137
except errors.NoSuchId:
138
# before a change is committed we can see this error...
143
# got to the end of this directory and no entries matched.
144
# Return what matched so far, plus the rest as specified.
145
cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
150
def _get_root_inventory(self):
151
return self._inventory
153
root_inventory = property(_get_root_inventory,
154
doc="Root inventory of this tree")
156
def _unpack_file_id(self, file_id):
157
"""Find the inventory and inventory file id for a tree file id.
159
:param file_id: The tree file id, as bytestring or tuple
160
:return: Inventory and inventory file id
162
if isinstance(file_id, tuple):
163
if len(file_id) != 1:
164
raise ValueError("nested trees not yet supported: %r" % file_id)
166
return self.root_inventory, file_id
168
def path2id(self, path):
169
"""Return the id for path in this tree."""
170
with self.lock_read():
171
return self._path2inv_file_id(path)[1]
173
def _path2inv_file_id(self, path):
174
"""Lookup a inventory and inventory file id by path.
176
:param path: Path to look up
177
:return: tuple with inventory and inventory file id
179
# FIXME: Support nested trees
180
return self.root_inventory, self.root_inventory.path2id(path)
182
def id2path(self, file_id):
183
"""Return the path for a file id.
187
inventory, file_id = self._unpack_file_id(file_id)
188
return inventory.id2path(file_id)
190
def has_id(self, file_id):
191
inventory, file_id = self._unpack_file_id(file_id)
192
return inventory.has_id(file_id)
194
def has_or_had_id(self, file_id):
195
inventory, file_id = self._unpack_file_id(file_id)
196
return inventory.has_id(file_id)
198
def all_file_ids(self):
199
return {entry.file_id for path, entry in self.iter_entries_by_dir()}
201
def filter_unversioned_files(self, paths):
202
"""Filter out paths that are versioned.
204
:return: set of paths.
206
# NB: we specifically *don't* call self.has_filename, because for
207
# WorkingTrees that can indicate files that exist on disk but that
209
return set((p for p in paths if self.path2id(p) is None))
211
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
212
"""Walk the tree in 'by_dir' order.
214
This will yield each entry in the tree as a (path, entry) tuple.
215
The order that they are yielded is:
217
See Tree.iter_entries_by_dir for details.
219
:param yield_parents: If True, yield the parents from the root leading
220
down to specific_file_ids that have been requested. This has no
221
impact if specific_file_ids is None.
223
with self.lock_read():
224
if specific_file_ids is None:
225
inventory_file_ids = None
227
inventory_file_ids = []
228
for tree_file_id in specific_file_ids:
229
inventory, inv_file_id = self._unpack_file_id(tree_file_id)
230
if not inventory is self.root_inventory: # for now
231
raise AssertionError("%r != %r" % (
232
inventory, self.root_inventory))
233
inventory_file_ids.append(inv_file_id)
234
# FIXME: Handle nested trees
235
return self.root_inventory.iter_entries_by_dir(
236
specific_file_ids=inventory_file_ids, yield_parents=yield_parents)
238
def iter_child_entries(self, file_id, path=None):
239
with self.lock_read():
240
inv, inv_file_id = self._unpack_file_id(file_id)
241
return iter(viewvalues(inv[inv_file_id].children))
243
def iter_children(self, file_id, path=None):
244
"""See Tree.iter_children."""
245
entry = self.iter_entries_by_dir([file_id]).next()[1]
246
for child in viewvalues(getattr(entry, 'children', {})):
250
class MutableInventoryTree(MutableTree, InventoryTree):
252
def apply_inventory_delta(self, changes):
253
"""Apply changes to the inventory as an atomic operation.
255
:param changes: An inventory delta to apply to the working tree's
258
:seealso Inventory.apply_delta: For details on the changes parameter.
260
with self.lock_tree_write():
262
inv = self.root_inventory
263
inv.apply_delta(changes)
264
self._write_inventory(inv)
266
def _fix_case_of_inventory_path(self, path):
267
"""If our tree isn't case sensitive, return the canonical path"""
268
if not self.case_sensitive:
269
path = self.get_canonical_inventory_path(path)
272
def smart_add(self, file_list, recurse=True, action=None, save=True):
273
"""Version file_list, optionally recursing into directories.
275
This is designed more towards DWIM for humans than API clarity.
276
For the specific behaviour see the help for cmd_add().
278
:param file_list: List of zero or more paths. *NB: these are
279
interpreted relative to the process cwd, not relative to the
280
tree.* (Add and most other tree methods use tree-relative
282
:param action: A reporter to be called with the inventory, parent_ie,
283
path and kind of the path being added. It may return a file_id if
284
a specific one should be used.
285
:param save: Save the inventory after completing the adds. If False
286
this provides dry-run functionality by doing the add and not saving
288
:return: A tuple - files_added, ignored_files. files_added is the count
289
of added files, and ignored_files is a dict mapping files that were
290
ignored to the rule that caused them to be ignored.
292
with self.lock_tree_write():
293
# Not all mutable trees can have conflicts
294
if getattr(self, 'conflicts', None) is not None:
295
# Collect all related files without checking whether they exist or
296
# are versioned. It's cheaper to do that once for all conflicts
297
# than trying to find the relevant conflict for each added file.
298
conflicts_related = set()
299
for c in self.conflicts():
300
conflicts_related.update(c.associated_filenames())
302
conflicts_related = None
303
adder = _SmartAddHelper(self, action, conflicts_related)
304
adder.add(file_list, recurse=recurse)
306
invdelta = adder.get_inventory_delta()
307
self.apply_inventory_delta(invdelta)
308
return adder.added, adder.ignored
310
def update_basis_by_delta(self, new_revid, delta):
311
"""Update the parents of this tree after a commit.
313
This gives the tree one parent, with revision id new_revid. The
314
inventory delta is applied to the current basis tree to generate the
315
inventory for the parent new_revid, and all other parent trees are
318
All the changes in the delta should be changes synchronising the basis
319
tree with some or all of the working tree, with a change to a directory
320
requiring that its contents have been recursively included. That is,
321
this is not a general purpose tree modification routine, but a helper
322
for commit which is not required to handle situations that do not arise
325
See the inventory developers documentation for the theory behind
328
:param new_revid: The new revision id for the trees parent.
329
:param delta: An inventory delta (see apply_inventory_delta) describing
330
the changes from the current left most parent revision to new_revid.
332
# if the tree is updated by a pull to the branch, as happens in
333
# WorkingTree2, when there was no separation between branch and tree,
334
# then just clear merges, efficiency is not a concern for now as this
335
# is legacy environments only, and they are slow regardless.
336
if self.last_revision() == new_revid:
337
self.set_parent_ids([new_revid])
339
# generic implementation based on Inventory manipulation. See
340
# WorkingTree classes for optimised versions for specific format trees.
341
basis = self.basis_tree()
342
with basis.lock_read():
343
# TODO: Consider re-evaluating the need for this with CHKInventory
344
# we don't strictly need to mutate an inventory for this
345
# it only makes sense when apply_delta is cheaper than get_inventory()
346
inventory = _mod_inventory.mutable_inventory_from_tree(basis)
347
inventory.apply_delta(delta)
348
rev_tree = InventoryRevisionTree(self.branch.repository,
349
inventory, new_revid)
350
self.set_parent_trees([(new_revid, rev_tree)])
353
class _SmartAddHelper(object):
354
"""Helper for MutableTree.smart_add."""
356
def get_inventory_delta(self):
357
# GZ 2016-06-05: Returning view would probably be fine but currently
358
# Inventory.apply_delta is documented as requiring a list of changes.
359
return list(viewvalues(self._invdelta))
361
def _get_ie(self, inv_path):
362
"""Retrieve the most up to date inventory entry for a path.
364
:param inv_path: Normalized inventory path
365
:return: Inventory entry (with possibly invalid .children for
368
entry = self._invdelta.get(inv_path)
369
if entry is not None:
371
# Find a 'best fit' match if the filesystem is case-insensitive
372
inv_path = self.tree._fix_case_of_inventory_path(inv_path)
373
file_id = self.tree.path2id(inv_path)
374
if file_id is not None:
375
return self.tree.iter_entries_by_dir([file_id]).next()[1]
378
def _convert_to_directory(self, this_ie, inv_path):
379
"""Convert an entry to a directory.
381
:param this_ie: Inventory entry
382
:param inv_path: Normalized path for the inventory entry
383
:return: The new inventory entry
385
# Same as in _add_one below, if the inventory doesn't
386
# think this is a directory, update the inventory
387
this_ie = _mod_inventory.InventoryDirectory(
388
this_ie.file_id, this_ie.name, this_ie.parent_id)
389
self._invdelta[inv_path] = (inv_path, inv_path, this_ie.file_id,
393
def _add_one_and_parent(self, parent_ie, path, kind, inv_path):
394
"""Add a new entry to the inventory and automatically add unversioned parents.
396
:param parent_ie: Parent inventory entry if known, or None. If
397
None, the parent is looked up by name and used if present, otherwise it
398
is recursively added.
400
:param kind: Kind of new entry (file, directory, etc)
402
:return: Inventory entry for path and a list of paths which have been added.
404
# Nothing to do if path is already versioned.
405
# This is safe from infinite recursion because the tree root is
407
inv_dirname = osutils.dirname(inv_path)
408
dirname, basename = osutils.split(path)
409
if parent_ie is None:
410
# slower but does not need parent_ie
411
this_ie = self._get_ie(inv_path)
412
if this_ie is not None:
414
# its really not there : add the parent
415
# note that the dirname use leads to some extra str copying etc but as
416
# there are a limited number of dirs we can be nested under, it should
417
# generally find it very fast and not recurse after that.
418
parent_ie = self._add_one_and_parent(None,
419
dirname, 'directory',
421
# if the parent exists, but isn't a directory, we have to do the
422
# kind change now -- really the inventory shouldn't pretend to know
423
# the kind of wt files, but it does.
424
if parent_ie.kind != 'directory':
425
# nb: this relies on someone else checking that the path we're using
426
# doesn't contain symlinks.
427
parent_ie = self._convert_to_directory(parent_ie, inv_dirname)
428
file_id = self.action(self.tree, parent_ie, path, kind)
429
entry = _mod_inventory.make_entry(kind, basename, parent_ie.file_id,
431
self._invdelta[inv_path] = (None, inv_path, entry.file_id, entry)
432
self.added.append(inv_path)
435
def _gather_dirs_to_add(self, user_dirs):
436
# only walk the minimal parents needed: we have user_dirs to override
440
is_inside = osutils.is_inside_or_parent_of_any
441
for path in sorted(user_dirs):
442
if (prev_dir is None or not is_inside([prev_dir], path)):
443
inv_path, this_ie = user_dirs[path]
444
yield (path, inv_path, this_ie, None)
447
def __init__(self, tree, action, conflicts_related=None):
450
self.action = add.AddAction()
456
if conflicts_related is None:
457
self.conflicts_related = frozenset()
459
self.conflicts_related = conflicts_related
461
def add(self, file_list, recurse=True):
463
# no paths supplied: add the entire tree.
464
# FIXME: this assumes we are running in a working tree subdir :-/
468
# expand any symlinks in the directory part, while leaving the
470
# only expanding if symlinks are supported avoids windows path bugs
471
if osutils.has_symlinks():
472
file_list = list(map(osutils.normalizepath, file_list))
475
# validate user file paths and convert all paths to tree
476
# relative : it's cheaper to make a tree relative path an abspath
477
# than to convert an abspath to tree relative, and it's cheaper to
478
# perform the canonicalization in bulk.
479
for filepath in osutils.canonical_relpaths(self.tree.basedir, file_list):
480
# validate user parameters. Our recursive code avoids adding new
481
# files that need such validation
482
if self.tree.is_control_filename(filepath):
483
raise errors.ForbiddenControlFileError(filename=filepath)
485
abspath = self.tree.abspath(filepath)
486
kind = osutils.file_kind(abspath)
487
# ensure the named path is added, so that ignore rules in the later
488
# directory walk dont skip it.
489
# we dont have a parent ie known yet.: use the relatively slower
490
# inventory probing method
491
inv_path, _ = osutils.normalized_filename(filepath)
492
this_ie = self._get_ie(inv_path)
494
this_ie = self._add_one_and_parent(None, filepath, kind, inv_path)
495
if kind == 'directory':
496
# schedule the dir for scanning
497
user_dirs[filepath] = (inv_path, this_ie)
500
# no need to walk any directories at all.
503
things_to_add = list(self._gather_dirs_to_add(user_dirs))
505
illegalpath_re = re.compile(r'[\r\n]')
506
for directory, inv_path, this_ie, parent_ie in things_to_add:
507
# directory is tree-relative
508
abspath = self.tree.abspath(directory)
510
# get the contents of this directory.
512
# find the kind of the path being added, and save stat_value
516
stat_value = osutils.file_stat(abspath)
517
kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
521
# allow AddAction to skip this file
522
if self.action.skip_file(self.tree, abspath, kind, stat_value):
524
if not _mod_inventory.InventoryEntry.versionable_kind(kind):
525
trace.warning("skipping %s (can't add file of kind '%s')",
528
if illegalpath_re.search(directory):
529
trace.warning("skipping %r (contains \\n or \\r)" % abspath)
531
if directory in self.conflicts_related:
532
# If the file looks like one generated for a conflict, don't
535
'skipping %s (generated to help resolve conflicts)',
539
if kind == 'directory' and directory != '':
541
transport = _mod_transport.get_transport_from_path(abspath)
542
controldir.ControlDirFormat.find_format(transport)
544
except errors.NotBranchError:
546
except errors.UnsupportedFormatError:
551
if this_ie is not None:
554
# XXX: This is wrong; people *might* reasonably be trying to
555
# add subtrees as subtrees. This should probably only be done
556
# in formats which can represent subtrees, and even then
557
# perhaps only when the user asked to add subtrees. At the
558
# moment you can add them specially through 'join --reference',
559
# which is perhaps reasonable: adding a new reference is a
560
# special operation and can have a special behaviour. mbp
562
trace.warning("skipping nested tree %r", abspath)
564
this_ie = self._add_one_and_parent(parent_ie, directory, kind,
567
if kind == 'directory' and not sub_tree:
568
if this_ie.kind != 'directory':
569
this_ie = self._convert_to_directory(this_ie, inv_path)
571
for subf in sorted(os.listdir(abspath)):
572
inv_f, _ = osutils.normalized_filename(subf)
573
# here we could use TreeDirectory rather than
574
# string concatenation.
575
subp = osutils.pathjoin(directory, subf)
576
# TODO: is_control_filename is very slow. Make it faster.
577
# TreeDirectory.is_control_filename could also make this
578
# faster - its impossible for a non root dir to have a
580
if self.tree.is_control_filename(subp):
581
trace.mutter("skip control directory %r", subp)
583
sub_invp = osutils.pathjoin(inv_path, inv_f)
584
entry = self._invdelta.get(sub_invp)
585
if entry is not None:
588
sub_ie = this_ie.children.get(inv_f)
589
if sub_ie is not None:
590
# recurse into this already versioned subdir.
591
things_to_add.append((subp, sub_invp, sub_ie, this_ie))
593
# user selection overrides ignores
594
# ignore while selecting files - if we globbed in the
595
# outer loop we would ignore user files.
596
ignore_glob = self.tree.is_ignored(subp)
597
if ignore_glob is not None:
598
self.ignored.setdefault(ignore_glob, []).append(subp)
600
things_to_add.append((subp, sub_invp, None, this_ie))
603
class InventoryRevisionTree(RevisionTree,InventoryTree):
605
def __init__(self, repository, inv, revision_id):
606
RevisionTree.__init__(self, repository, revision_id)
607
self._inventory = inv
609
def get_file_mtime(self, file_id, path=None):
610
inv, inv_file_id = self._unpack_file_id(file_id)
611
ie = inv[inv_file_id]
613
revision = self._repository.get_revision(ie.revision)
614
except errors.NoSuchRevision:
615
raise FileTimestampUnavailable(self.id2path(file_id))
616
return revision.timestamp
618
def get_file_size(self, file_id):
619
inv, inv_file_id = self._unpack_file_id(file_id)
620
return inv[inv_file_id].text_size
622
def get_file_sha1(self, file_id, path=None, stat_value=None):
623
inv, inv_file_id = self._unpack_file_id(file_id)
624
ie = inv[inv_file_id]
625
if ie.kind == "file":
629
def get_file_revision(self, file_id, path=None):
630
inv, inv_file_id = self._unpack_file_id(file_id)
631
ie = inv[inv_file_id]
634
def is_executable(self, file_id, path=None):
635
inv, inv_file_id = self._unpack_file_id(file_id)
636
ie = inv[inv_file_id]
637
if ie.kind != "file":
641
def has_filename(self, filename):
642
return bool(self.path2id(filename))
644
def list_files(self, include_root=False, from_dir=None, recursive=True):
645
# The only files returned by this are those from the version
648
inv = self.root_inventory
650
inv, from_dir_id = self._path2inv_file_id(from_dir)
651
if from_dir_id is None:
652
# Directory not versioned
654
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
655
if inv.root is not None and not include_root and from_dir is None:
656
# skip the root for compatability with the current apis.
658
for path, entry in entries:
659
yield path, 'V', entry.kind, entry.file_id, entry
661
def get_symlink_target(self, file_id, path=None):
662
inv, inv_file_id = self._unpack_file_id(file_id)
663
ie = inv[inv_file_id]
664
# Inventories store symlink targets in unicode
665
return ie.symlink_target
667
def get_reference_revision(self, file_id, path=None):
668
inv, inv_file_id = self._unpack_file_id(file_id)
669
return inv[inv_file_id].reference_revision
671
def get_root_id(self):
672
if self.root_inventory.root:
673
return self.root_inventory.root.file_id
675
def kind(self, file_id):
676
inv, inv_file_id = self._unpack_file_id(file_id)
677
return inv[inv_file_id].kind
679
def path_content_summary(self, path):
680
"""See Tree.path_content_summary."""
681
inv, file_id = self._path2inv_file_id(path)
683
return ('missing', None, None, None)
687
return (kind, entry.text_size, entry.executable, entry.text_sha1)
688
elif kind == 'symlink':
689
return (kind, None, None, entry.symlink_target)
691
return (kind, None, None, None)
693
def _comparison_data(self, entry, path):
695
return None, False, None
696
return entry.kind, entry.executable, None
698
def _file_size(self, entry, stat_value):
699
return entry.text_size
701
def walkdirs(self, prefix=""):
702
_directory = 'directory'
703
inv, top_id = self._path2inv_file_id(prefix)
707
pending = [(prefix, '', _directory, None, top_id, None)]
710
currentdir = pending.pop()
711
# 0 - relpath, 1- basename, 2- kind, 3- stat, id, v-kind
713
relroot = currentdir[0] + '/'
716
# FIXME: stash the node in pending
717
entry = inv[currentdir[4]]
718
for name, child in entry.sorted_children():
719
toppath = relroot + name
720
dirblock.append((toppath, name, child.kind, None,
721
child.file_id, child.kind
723
yield (currentdir[0], entry.file_id), dirblock
724
# push the user specified dirs from dirblock
725
for dir in reversed(dirblock):
726
if dir[2] == _directory:
729
def iter_files_bytes(self, desired_files):
730
"""See Tree.iter_files_bytes.
732
This version is implemented on top of Repository.iter_files_bytes"""
733
repo_desired_files = [(f, self.get_file_revision(f), i)
734
for f, i in desired_files]
736
for result in self._repository.iter_files_bytes(repo_desired_files):
738
except errors.RevisionNotPresent as e:
739
raise errors.NoSuchFile(e.file_id)
741
def annotate_iter(self, file_id,
742
default_revision=revision.CURRENT_REVISION):
743
"""See Tree.annotate_iter"""
744
text_key = (file_id, self.get_file_revision(file_id))
745
annotator = self._repository.texts.get_annotator()
746
annotations = annotator.annotate_flat(text_key)
747
return [(key[-1], line) for key, line in annotations]
749
def __eq__(self, other):
752
if isinstance(other, InventoryRevisionTree):
753
return (self.root_inventory == other.root_inventory)
756
def __ne__(self, other):
757
return not (self == other)
760
raise ValueError('not hashable')
763
class InterCHKRevisionTree(InterTree):
764
"""Fast path optimiser for RevisionTrees with CHK inventories."""
767
def is_compatible(source, target):
768
if (isinstance(source, RevisionTree)
769
and isinstance(target, RevisionTree)):
771
# Only CHK inventories have id_to_entry attribute
772
source.root_inventory.id_to_entry
773
target.root_inventory.id_to_entry
775
except AttributeError:
779
def iter_changes(self, include_unchanged=False,
780
specific_files=None, pb=None, extra_trees=[],
781
require_versioned=True, want_unversioned=False):
782
lookup_trees = [self.source]
784
lookup_trees.extend(extra_trees)
785
# The ids of items we need to examine to insure delta consistency.
786
precise_file_ids = set()
787
discarded_changes = {}
788
if specific_files == []:
789
specific_file_ids = []
791
specific_file_ids = self.target.paths2ids(specific_files,
792
lookup_trees, require_versioned=require_versioned)
793
# FIXME: It should be possible to delegate include_unchanged handling
794
# to CHKInventory.iter_changes and do a better job there -- vila
796
changed_file_ids = set()
797
# FIXME: nested tree support
798
for result in self.target.root_inventory.iter_changes(
799
self.source.root_inventory):
800
if specific_file_ids is not None:
802
if file_id not in specific_file_ids:
803
# A change from the whole tree that we don't want to show yet.
804
# We may find that we need to show it for delta consistency, so
806
discarded_changes[result[0]] = result
808
new_parent_id = result[4][1]
809
precise_file_ids.add(new_parent_id)
811
changed_file_ids.add(result[0])
812
if specific_file_ids is not None:
813
for result in self._handle_precise_ids(precise_file_ids,
814
changed_file_ids, discarded_changes=discarded_changes):
816
if include_unchanged:
817
# CHKMap avoid being O(tree), so we go to O(tree) only if
819
# Now walk the whole inventory, excluding the already yielded
821
# FIXME: Support nested trees
822
changed_file_ids = set(changed_file_ids)
823
for relpath, entry in self.target.root_inventory.iter_entries():
824
if (specific_file_ids is not None
825
and not entry.file_id in specific_file_ids):
827
if not entry.file_id in changed_file_ids:
828
yield (entry.file_id,
829
(relpath, relpath), # Not renamed
830
False, # Not modified
831
(True, True), # Still versioned
832
(entry.parent_id, entry.parent_id),
833
(entry.name, entry.name),
834
(entry.kind, entry.kind),
835
(entry.executable, entry.executable))
838
InterTree.register_optimiser(InterCHKRevisionTree)