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 (
33
needs_tree_write_lock,
35
from ..revisiontree import (
38
lazy_import.lazy_import(globals(), """
43
transport as _mod_transport,
45
from breezy.bzr import (
46
inventory as _mod_inventory,
49
from ..decorators import needs_read_lock
50
from ..sixish import (
54
FileTimestampUnavailable,
60
class InventoryTree(Tree):
61
"""A tree that relies on an inventory for its metadata.
63
Trees contain an `Inventory` object, and also know how to retrieve
64
file texts mentioned in the inventory, either from a working
65
directory or from a store.
67
It is possible for trees to contain files that are not described
68
in their inventory or vice versa; for this use `filenames()`.
70
Subclasses should set the _inventory attribute, which is considered
71
private to external API users.
74
def get_canonical_inventory_paths(self, paths):
75
"""Like get_canonical_inventory_path() but works on multiple items.
77
:param paths: A sequence of paths relative to the root of the tree.
78
:return: A list of paths, with each item the corresponding input path
79
adjusted to account for existing elements that match case
82
return list(self._yield_canonical_inventory_paths(paths))
84
def get_canonical_inventory_path(self, path):
85
"""Returns the first inventory item that case-insensitively matches path.
87
If a path matches exactly, it is returned. If no path matches exactly
88
but more than one path matches case-insensitively, it is implementation
89
defined which is returned.
91
If no path matches case-insensitively, the input path is returned, but
92
with as many path entries that do exist changed to their canonical
95
If you need to resolve many names from the same tree, you should
96
use get_canonical_inventory_paths() to avoid O(N) behaviour.
98
:param path: A paths relative to the root of the tree.
99
:return: The input path adjusted to account for existing elements
100
that match case insensitively.
102
return next(self._yield_canonical_inventory_paths([path]))
104
def _yield_canonical_inventory_paths(self, paths):
106
# First, if the path as specified exists exactly, just use it.
107
if self.path2id(path) is not None:
111
cur_id = self.get_root_id()
113
bit_iter = iter(path.split("/"))
117
for child in self.iter_children(cur_id):
119
# XXX: it seem like if the child is known to be in the
120
# tree, we shouldn't need to go from its id back to
121
# its path -- mbp 2010-02-11
123
# XXX: it seems like we could be more efficient
124
# by just directly looking up the original name and
125
# only then searching all children; also by not
126
# chopping paths so much. -- mbp 2010-02-11
127
child_base = os.path.basename(self.id2path(child))
128
if (child_base == elt):
129
# if we found an exact match, we can stop now; if
130
# we found an approximate match we need to keep
131
# searching because there might be an exact match
134
new_path = osutils.pathjoin(cur_path, child_base)
136
elif child_base.lower() == lelt:
138
new_path = osutils.pathjoin(cur_path, child_base)
139
except errors.NoSuchId:
140
# before a change is committed we can see this error...
145
# got to the end of this directory and no entries matched.
146
# Return what matched so far, plus the rest as specified.
147
cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
152
def _get_root_inventory(self):
153
return self._inventory
155
root_inventory = property(_get_root_inventory,
156
doc="Root inventory of this tree")
158
def _unpack_file_id(self, file_id):
159
"""Find the inventory and inventory file id for a tree file id.
161
:param file_id: The tree file id, as bytestring or tuple
162
:return: Inventory and inventory file id
164
if isinstance(file_id, tuple):
165
if len(file_id) != 1:
166
raise ValueError("nested trees not yet supported: %r" % file_id)
168
return self.root_inventory, file_id
171
def path2id(self, path):
172
"""Return the id for path in this tree."""
173
return self._path2inv_file_id(path)[1]
175
def _path2inv_file_id(self, path):
176
"""Lookup a inventory and inventory file id by path.
178
:param path: Path to look up
179
:return: tuple with inventory and inventory file id
181
# FIXME: Support nested trees
182
return self.root_inventory, self.root_inventory.path2id(path)
184
def id2path(self, file_id):
185
"""Return the path for a file id.
189
inventory, file_id = self._unpack_file_id(file_id)
190
return inventory.id2path(file_id)
192
def has_id(self, file_id):
193
inventory, file_id = self._unpack_file_id(file_id)
194
return inventory.has_id(file_id)
196
def has_or_had_id(self, file_id):
197
inventory, file_id = self._unpack_file_id(file_id)
198
return inventory.has_id(file_id)
200
def all_file_ids(self):
201
return {entry.file_id for path, entry in self.iter_entries_by_dir()}
203
def filter_unversioned_files(self, paths):
204
"""Filter out paths that are versioned.
206
:return: set of paths.
208
# NB: we specifically *don't* call self.has_filename, because for
209
# WorkingTrees that can indicate files that exist on disk but that
211
return set((p for p in paths if self.path2id(p) is None))
214
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
215
"""Walk the tree in 'by_dir' order.
217
This will yield each entry in the tree as a (path, entry) tuple.
218
The order that they are yielded is:
220
See Tree.iter_entries_by_dir for details.
222
:param yield_parents: If True, yield the parents from the root leading
223
down to specific_file_ids that have been requested. This has no
224
impact if specific_file_ids is None.
226
if specific_file_ids is None:
227
inventory_file_ids = None
229
inventory_file_ids = []
230
for tree_file_id in specific_file_ids:
231
inventory, inv_file_id = self._unpack_file_id(tree_file_id)
232
if not inventory is self.root_inventory: # for now
233
raise AssertionError("%r != %r" % (
234
inventory, self.root_inventory))
235
inventory_file_ids.append(inv_file_id)
236
# FIXME: Handle nested trees
237
return self.root_inventory.iter_entries_by_dir(
238
specific_file_ids=inventory_file_ids, yield_parents=yield_parents)
241
def iter_child_entries(self, file_id, path=None):
242
inv, inv_file_id = self._unpack_file_id(file_id)
243
return iter(viewvalues(inv[inv_file_id].children))
245
def iter_children(self, file_id, path=None):
246
"""See Tree.iter_children."""
247
entry = self.iter_entries_by_dir([file_id]).next()[1]
248
for child in viewvalues(getattr(entry, 'children', {})):
252
class MutableInventoryTree(MutableTree, InventoryTree):
254
@needs_tree_write_lock
255
def apply_inventory_delta(self, changes):
256
"""Apply changes to the inventory as an atomic operation.
258
:param changes: An inventory delta to apply to the working tree's
261
:seealso Inventory.apply_delta: For details on the changes parameter.
264
inv = self.root_inventory
265
inv.apply_delta(changes)
266
self._write_inventory(inv)
268
def _fix_case_of_inventory_path(self, path):
269
"""If our tree isn't case sensitive, return the canonical path"""
270
if not self.case_sensitive:
271
path = self.get_canonical_inventory_path(path)
274
@needs_tree_write_lock
275
def smart_add(self, file_list, recurse=True, action=None, save=True):
276
"""Version file_list, optionally recursing into directories.
278
This is designed more towards DWIM for humans than API clarity.
279
For the specific behaviour see the help for cmd_add().
281
:param file_list: List of zero or more paths. *NB: these are
282
interpreted relative to the process cwd, not relative to the
283
tree.* (Add and most other tree methods use tree-relative
285
:param action: A reporter to be called with the inventory, parent_ie,
286
path and kind of the path being added. It may return a file_id if
287
a specific one should be used.
288
:param save: Save the inventory after completing the adds. If False
289
this provides dry-run functionality by doing the add and not saving
291
:return: A tuple - files_added, ignored_files. files_added is the count
292
of added files, and ignored_files is a dict mapping files that were
293
ignored to the rule that caused them to be ignored.
295
# Not all mutable trees can have conflicts
296
if getattr(self, 'conflicts', None) is not None:
297
# Collect all related files without checking whether they exist or
298
# are versioned. It's cheaper to do that once for all conflicts
299
# than trying to find the relevant conflict for each added file.
300
conflicts_related = set()
301
for c in self.conflicts():
302
conflicts_related.update(c.associated_filenames())
304
conflicts_related = None
305
adder = _SmartAddHelper(self, action, conflicts_related)
306
adder.add(file_list, recurse=recurse)
308
invdelta = adder.get_inventory_delta()
309
self.apply_inventory_delta(invdelta)
310
return adder.added, adder.ignored
312
def update_basis_by_delta(self, new_revid, delta):
313
"""Update the parents of this tree after a commit.
315
This gives the tree one parent, with revision id new_revid. The
316
inventory delta is applied to the current basis tree to generate the
317
inventory for the parent new_revid, and all other parent trees are
320
All the changes in the delta should be changes synchronising the basis
321
tree with some or all of the working tree, with a change to a directory
322
requiring that its contents have been recursively included. That is,
323
this is not a general purpose tree modification routine, but a helper
324
for commit which is not required to handle situations that do not arise
327
See the inventory developers documentation for the theory behind
330
:param new_revid: The new revision id for the trees parent.
331
:param delta: An inventory delta (see apply_inventory_delta) describing
332
the changes from the current left most parent revision to new_revid.
334
# if the tree is updated by a pull to the branch, as happens in
335
# WorkingTree2, when there was no separation between branch and tree,
336
# then just clear merges, efficiency is not a concern for now as this
337
# is legacy environments only, and they are slow regardless.
338
if self.last_revision() == new_revid:
339
self.set_parent_ids([new_revid])
341
# generic implementation based on Inventory manipulation. See
342
# WorkingTree classes for optimised versions for specific format trees.
343
basis = self.basis_tree()
345
# TODO: Consider re-evaluating the need for this with CHKInventory
346
# we don't strictly need to mutate an inventory for this
347
# it only makes sense when apply_delta is cheaper than get_inventory()
348
inventory = _mod_inventory.mutable_inventory_from_tree(basis)
350
inventory.apply_delta(delta)
351
rev_tree = InventoryRevisionTree(self.branch.repository,
352
inventory, new_revid)
353
self.set_parent_trees([(new_revid, rev_tree)])
356
class _SmartAddHelper(object):
357
"""Helper for MutableTree.smart_add."""
359
def get_inventory_delta(self):
360
# GZ 2016-06-05: Returning view would probably be fine but currently
361
# Inventory.apply_delta is documented as requiring a list of changes.
362
return list(viewvalues(self._invdelta))
364
def _get_ie(self, inv_path):
365
"""Retrieve the most up to date inventory entry for a path.
367
:param inv_path: Normalized inventory path
368
:return: Inventory entry (with possibly invalid .children for
371
entry = self._invdelta.get(inv_path)
372
if entry is not None:
374
# Find a 'best fit' match if the filesystem is case-insensitive
375
inv_path = self.tree._fix_case_of_inventory_path(inv_path)
376
file_id = self.tree.path2id(inv_path)
377
if file_id is not None:
378
return self.tree.iter_entries_by_dir([file_id]).next()[1]
381
def _convert_to_directory(self, this_ie, inv_path):
382
"""Convert an entry to a directory.
384
:param this_ie: Inventory entry
385
:param inv_path: Normalized path for the inventory entry
386
:return: The new inventory entry
388
# Same as in _add_one below, if the inventory doesn't
389
# think this is a directory, update the inventory
390
this_ie = _mod_inventory.InventoryDirectory(
391
this_ie.file_id, this_ie.name, this_ie.parent_id)
392
self._invdelta[inv_path] = (inv_path, inv_path, this_ie.file_id,
396
def _add_one_and_parent(self, parent_ie, path, kind, inv_path):
397
"""Add a new entry to the inventory and automatically add unversioned parents.
399
:param parent_ie: Parent inventory entry if known, or None. If
400
None, the parent is looked up by name and used if present, otherwise it
401
is recursively added.
403
:param kind: Kind of new entry (file, directory, etc)
405
:return: Inventory entry for path and a list of paths which have been added.
407
# Nothing to do if path is already versioned.
408
# This is safe from infinite recursion because the tree root is
410
inv_dirname = osutils.dirname(inv_path)
411
dirname, basename = osutils.split(path)
412
if parent_ie is None:
413
# slower but does not need parent_ie
414
this_ie = self._get_ie(inv_path)
415
if this_ie is not None:
417
# its really not there : add the parent
418
# note that the dirname use leads to some extra str copying etc but as
419
# there are a limited number of dirs we can be nested under, it should
420
# generally find it very fast and not recurse after that.
421
parent_ie = self._add_one_and_parent(None,
422
dirname, 'directory',
424
# if the parent exists, but isn't a directory, we have to do the
425
# kind change now -- really the inventory shouldn't pretend to know
426
# the kind of wt files, but it does.
427
if parent_ie.kind != 'directory':
428
# nb: this relies on someone else checking that the path we're using
429
# doesn't contain symlinks.
430
parent_ie = self._convert_to_directory(parent_ie, inv_dirname)
431
file_id = self.action(self.tree, parent_ie, path, kind)
432
entry = _mod_inventory.make_entry(kind, basename, parent_ie.file_id,
434
self._invdelta[inv_path] = (None, inv_path, entry.file_id, entry)
435
self.added.append(inv_path)
438
def _gather_dirs_to_add(self, user_dirs):
439
# only walk the minimal parents needed: we have user_dirs to override
443
is_inside = osutils.is_inside_or_parent_of_any
444
for path in sorted(user_dirs):
445
if (prev_dir is None or not is_inside([prev_dir], path)):
446
inv_path, this_ie = user_dirs[path]
447
yield (path, inv_path, this_ie, None)
450
def __init__(self, tree, action, conflicts_related=None):
453
self.action = add.AddAction()
459
if conflicts_related is None:
460
self.conflicts_related = frozenset()
462
self.conflicts_related = conflicts_related
464
def add(self, file_list, recurse=True):
466
# no paths supplied: add the entire tree.
467
# FIXME: this assumes we are running in a working tree subdir :-/
471
# expand any symlinks in the directory part, while leaving the
473
# only expanding if symlinks are supported avoids windows path bugs
474
if osutils.has_symlinks():
475
file_list = list(map(osutils.normalizepath, file_list))
478
# validate user file paths and convert all paths to tree
479
# relative : it's cheaper to make a tree relative path an abspath
480
# than to convert an abspath to tree relative, and it's cheaper to
481
# perform the canonicalization in bulk.
482
for filepath in osutils.canonical_relpaths(self.tree.basedir, file_list):
483
# validate user parameters. Our recursive code avoids adding new
484
# files that need such validation
485
if self.tree.is_control_filename(filepath):
486
raise errors.ForbiddenControlFileError(filename=filepath)
488
abspath = self.tree.abspath(filepath)
489
kind = osutils.file_kind(abspath)
490
# ensure the named path is added, so that ignore rules in the later
491
# directory walk dont skip it.
492
# we dont have a parent ie known yet.: use the relatively slower
493
# inventory probing method
494
inv_path, _ = osutils.normalized_filename(filepath)
495
this_ie = self._get_ie(inv_path)
497
this_ie = self._add_one_and_parent(None, filepath, kind, inv_path)
498
if kind == 'directory':
499
# schedule the dir for scanning
500
user_dirs[filepath] = (inv_path, this_ie)
503
# no need to walk any directories at all.
506
things_to_add = list(self._gather_dirs_to_add(user_dirs))
508
illegalpath_re = re.compile(r'[\r\n]')
509
for directory, inv_path, this_ie, parent_ie in things_to_add:
510
# directory is tree-relative
511
abspath = self.tree.abspath(directory)
513
# get the contents of this directory.
515
# find the kind of the path being added, and save stat_value
519
stat_value = osutils.file_stat(abspath)
520
kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
524
# allow AddAction to skip this file
525
if self.action.skip_file(self.tree, abspath, kind, stat_value):
527
if not _mod_inventory.InventoryEntry.versionable_kind(kind):
528
trace.warning("skipping %s (can't add file of kind '%s')",
531
if illegalpath_re.search(directory):
532
trace.warning("skipping %r (contains \\n or \\r)" % abspath)
534
if directory in self.conflicts_related:
535
# If the file looks like one generated for a conflict, don't
538
'skipping %s (generated to help resolve conflicts)',
542
if kind == 'directory' and directory != '':
544
transport = _mod_transport.get_transport_from_path(abspath)
545
controldir.ControlDirFormat.find_format(transport)
547
except errors.NotBranchError:
549
except errors.UnsupportedFormatError:
554
if this_ie is not None:
557
# XXX: This is wrong; people *might* reasonably be trying to
558
# add subtrees as subtrees. This should probably only be done
559
# in formats which can represent subtrees, and even then
560
# perhaps only when the user asked to add subtrees. At the
561
# moment you can add them specially through 'join --reference',
562
# which is perhaps reasonable: adding a new reference is a
563
# special operation and can have a special behaviour. mbp
565
trace.warning("skipping nested tree %r", abspath)
567
this_ie = self._add_one_and_parent(parent_ie, directory, kind,
570
if kind == 'directory' and not sub_tree:
571
if this_ie.kind != 'directory':
572
this_ie = self._convert_to_directory(this_ie, inv_path)
574
for subf in sorted(os.listdir(abspath)):
575
inv_f, _ = osutils.normalized_filename(subf)
576
# here we could use TreeDirectory rather than
577
# string concatenation.
578
subp = osutils.pathjoin(directory, subf)
579
# TODO: is_control_filename is very slow. Make it faster.
580
# TreeDirectory.is_control_filename could also make this
581
# faster - its impossible for a non root dir to have a
583
if self.tree.is_control_filename(subp):
584
trace.mutter("skip control directory %r", subp)
586
sub_invp = osutils.pathjoin(inv_path, inv_f)
587
entry = self._invdelta.get(sub_invp)
588
if entry is not None:
591
sub_ie = this_ie.children.get(inv_f)
592
if sub_ie is not None:
593
# recurse into this already versioned subdir.
594
things_to_add.append((subp, sub_invp, sub_ie, this_ie))
596
# user selection overrides ignores
597
# ignore while selecting files - if we globbed in the
598
# outer loop we would ignore user files.
599
ignore_glob = self.tree.is_ignored(subp)
600
if ignore_glob is not None:
601
self.ignored.setdefault(ignore_glob, []).append(subp)
603
things_to_add.append((subp, sub_invp, None, this_ie))
606
class InventoryRevisionTree(RevisionTree,InventoryTree):
608
def __init__(self, repository, inv, revision_id):
609
RevisionTree.__init__(self, repository, revision_id)
610
self._inventory = inv
612
def get_file_mtime(self, file_id, path=None):
613
inv, inv_file_id = self._unpack_file_id(file_id)
614
ie = inv[inv_file_id]
616
revision = self._repository.get_revision(ie.revision)
617
except errors.NoSuchRevision:
618
raise FileTimestampUnavailable(self.id2path(file_id))
619
return revision.timestamp
621
def get_file_size(self, file_id):
622
inv, inv_file_id = self._unpack_file_id(file_id)
623
return inv[inv_file_id].text_size
625
def get_file_sha1(self, file_id, path=None, stat_value=None):
626
inv, inv_file_id = self._unpack_file_id(file_id)
627
ie = inv[inv_file_id]
628
if ie.kind == "file":
632
def get_file_revision(self, file_id, path=None):
633
inv, inv_file_id = self._unpack_file_id(file_id)
634
ie = inv[inv_file_id]
637
def is_executable(self, file_id, path=None):
638
inv, inv_file_id = self._unpack_file_id(file_id)
639
ie = inv[inv_file_id]
640
if ie.kind != "file":
644
def has_filename(self, filename):
645
return bool(self.path2id(filename))
647
def list_files(self, include_root=False, from_dir=None, recursive=True):
648
# The only files returned by this are those from the version
651
inv = self.root_inventory
653
inv, from_dir_id = self._path2inv_file_id(from_dir)
654
if from_dir_id is None:
655
# Directory not versioned
657
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
658
if inv.root is not None and not include_root and from_dir is None:
659
# skip the root for compatability with the current apis.
661
for path, entry in entries:
662
yield path, 'V', entry.kind, entry.file_id, entry
664
def get_symlink_target(self, file_id, path=None):
665
inv, inv_file_id = self._unpack_file_id(file_id)
666
ie = inv[inv_file_id]
667
# Inventories store symlink targets in unicode
668
return ie.symlink_target
670
def get_reference_revision(self, file_id, path=None):
671
inv, inv_file_id = self._unpack_file_id(file_id)
672
return inv[inv_file_id].reference_revision
674
def get_root_id(self):
675
if self.root_inventory.root:
676
return self.root_inventory.root.file_id
678
def kind(self, file_id):
679
inv, inv_file_id = self._unpack_file_id(file_id)
680
return inv[inv_file_id].kind
682
def path_content_summary(self, path):
683
"""See Tree.path_content_summary."""
684
inv, file_id = self._path2inv_file_id(path)
686
return ('missing', None, None, None)
690
return (kind, entry.text_size, entry.executable, entry.text_sha1)
691
elif kind == 'symlink':
692
return (kind, None, None, entry.symlink_target)
694
return (kind, None, None, None)
696
def _comparison_data(self, entry, path):
698
return None, False, None
699
return entry.kind, entry.executable, None
701
def _file_size(self, entry, stat_value):
702
return entry.text_size
704
def walkdirs(self, prefix=""):
705
_directory = 'directory'
706
inv, top_id = self._path2inv_file_id(prefix)
710
pending = [(prefix, '', _directory, None, top_id, None)]
713
currentdir = pending.pop()
714
# 0 - relpath, 1- basename, 2- kind, 3- stat, id, v-kind
716
relroot = currentdir[0] + '/'
719
# FIXME: stash the node in pending
720
entry = inv[currentdir[4]]
721
for name, child in entry.sorted_children():
722
toppath = relroot + name
723
dirblock.append((toppath, name, child.kind, None,
724
child.file_id, child.kind
726
yield (currentdir[0], entry.file_id), dirblock
727
# push the user specified dirs from dirblock
728
for dir in reversed(dirblock):
729
if dir[2] == _directory:
732
def iter_files_bytes(self, desired_files):
733
"""See Tree.iter_files_bytes.
735
This version is implemented on top of Repository.iter_files_bytes"""
736
repo_desired_files = [(f, self.get_file_revision(f), i)
737
for f, i in desired_files]
739
for result in self._repository.iter_files_bytes(repo_desired_files):
741
except errors.RevisionNotPresent as e:
742
raise errors.NoSuchFile(e.file_id)
744
def annotate_iter(self, file_id,
745
default_revision=revision.CURRENT_REVISION):
746
"""See Tree.annotate_iter"""
747
text_key = (file_id, self.get_file_revision(file_id))
748
annotator = self._repository.texts.get_annotator()
749
annotations = annotator.annotate_flat(text_key)
750
return [(key[-1], line) for key, line in annotations]
752
def __eq__(self, other):
755
if isinstance(other, InventoryRevisionTree):
756
return (self.root_inventory == other.root_inventory)
759
def __ne__(self, other):
760
return not (self == other)
763
raise ValueError('not hashable')
766
class InterCHKRevisionTree(InterTree):
767
"""Fast path optimiser for RevisionTrees with CHK inventories."""
770
def is_compatible(source, target):
771
if (isinstance(source, RevisionTree)
772
and isinstance(target, RevisionTree)):
774
# Only CHK inventories have id_to_entry attribute
775
source.root_inventory.id_to_entry
776
target.root_inventory.id_to_entry
778
except AttributeError:
782
def iter_changes(self, include_unchanged=False,
783
specific_files=None, pb=None, extra_trees=[],
784
require_versioned=True, want_unversioned=False):
785
lookup_trees = [self.source]
787
lookup_trees.extend(extra_trees)
788
# The ids of items we need to examine to insure delta consistency.
789
precise_file_ids = set()
790
discarded_changes = {}
791
if specific_files == []:
792
specific_file_ids = []
794
specific_file_ids = self.target.paths2ids(specific_files,
795
lookup_trees, require_versioned=require_versioned)
796
# FIXME: It should be possible to delegate include_unchanged handling
797
# to CHKInventory.iter_changes and do a better job there -- vila
799
changed_file_ids = set()
800
# FIXME: nested tree support
801
for result in self.target.root_inventory.iter_changes(
802
self.source.root_inventory):
803
if specific_file_ids is not None:
805
if file_id not in specific_file_ids:
806
# A change from the whole tree that we don't want to show yet.
807
# We may find that we need to show it for delta consistency, so
809
discarded_changes[result[0]] = result
811
new_parent_id = result[4][1]
812
precise_file_ids.add(new_parent_id)
814
changed_file_ids.add(result[0])
815
if specific_file_ids is not None:
816
for result in self._handle_precise_ids(precise_file_ids,
817
changed_file_ids, discarded_changes=discarded_changes):
819
if include_unchanged:
820
# CHKMap avoid being O(tree), so we go to O(tree) only if
822
# Now walk the whole inventory, excluding the already yielded
824
# FIXME: Support nested trees
825
changed_file_ids = set(changed_file_ids)
826
for relpath, entry in self.target.root_inventory.iter_entries():
827
if (specific_file_ids is not None
828
and not entry.file_id in specific_file_ids):
830
if not entry.file_id in changed_file_ids:
831
yield (entry.file_id,
832
(relpath, relpath), # Not renamed
833
False, # Not modified
834
(True, True), # Still versioned
835
(entry.parent_id, entry.parent_id),
836
(entry.name, entry.name),
837
(entry.kind, entry.kind),
838
(entry.executable, entry.executable))
841
InterTree.register_optimiser(InterCHKRevisionTree)