Merge lp:~jelmer/brz/move-invtree into lp:brz

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/move-invtree
Merge into: lp:brz
Diff against target: 1914 lines (+858/-812)
15 files modified
breezy/bzrworkingtree.py (+3/-4)
breezy/inventorytree.py (+836/-0)
breezy/memorytree.py (+2/-1)
breezy/mutabletree.py (+1/-366)
breezy/remote.py (+1/-1)
breezy/revisiontree.py (+0/-238)
breezy/tests/per_intertree/__init__.py (+2/-1)
breezy/tests/per_tree/test_inv.py (+1/-1)
breezy/tests/per_workingtree/test_parents.py (+1/-1)
breezy/tests/test_dirstate.py (+2/-1)
breezy/transform.py (+2/-1)
breezy/tree.py (+0/-192)
breezy/vf_repository.py (+1/-1)
breezy/workingtree.py (+1/-1)
breezy/workingtree_4.py (+5/-3)
To merge this branch: bzr merge lp:~jelmer/brz/move-invtree
Reviewer Review Type Date Requested Status
Martin Packman Approve
Review via email: mp+325444@code.launchpad.net

Commit message

Move inventory-related tree implementations to breezy.inventorytree.

Description of the change

Move inventory-related tree implementations to breezy.inventorytree.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Okay, change seems fine. We're going to have to draw a picture of where everything is...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/bzrworkingtree.py'
2--- breezy/bzrworkingtree.py 2017-06-10 12:56:18 +0000
3+++ breezy/bzrworkingtree.py 2017-06-10 18:45:29 +0000
4@@ -50,7 +50,6 @@
5 errors,
6 graph as _mod_graph,
7 inventory,
8- mutabletree,
9 osutils,
10 revision as _mod_revision,
11 revisiontree,
12@@ -63,6 +62,7 @@
13
14 from .decorators import needs_write_lock, needs_read_lock
15 from .lock import _RelockDebugMixin, LogicalLockResult
16+from .inventorytree import InventoryRevisionTree, MutableInventoryTree
17 from .mutabletree import needs_tree_write_lock
18 from .sixish import (
19 BytesIO,
20@@ -85,8 +85,7 @@
21 CONFLICT_HEADER_1 = "BZR conflict list format 1"
22
23
24-class InventoryWorkingTree(WorkingTree,
25- mutabletree.MutableInventoryTree):
26+class InventoryWorkingTree(WorkingTree,MutableInventoryTree):
27 """Base class for working trees that are inventory-oriented.
28
29 The inventory is held in the `Branch` working-inventory, and the
30@@ -591,7 +590,7 @@
31 # dont use the repository revision_tree api because we want
32 # to supply the inventory.
33 if inv.revision_id == revision_id:
34- return revisiontree.InventoryRevisionTree(
35+ return InventoryRevisionTree(
36 self.branch.repository, inv, revision_id)
37 except errors.BadInventoryFormat:
38 pass
39
40=== added file 'breezy/inventorytree.py'
41--- breezy/inventorytree.py 1970-01-01 00:00:00 +0000
42+++ breezy/inventorytree.py 2017-06-10 18:45:29 +0000
43@@ -0,0 +1,836 @@
44+# Copyright (C) 2005-2011 Canonical Ltd
45+#
46+# This program is free software; you can redistribute it and/or modify
47+# it under the terms of the GNU General Public License as published by
48+# the Free Software Foundation; either version 2 of the License, or
49+# (at your option) any later version.
50+#
51+# This program is distributed in the hope that it will be useful,
52+# but WITHOUT ANY WARRANTY; without even the implied warranty of
53+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54+# GNU General Public License for more details.
55+#
56+# You should have received a copy of the GNU General Public License
57+# along with this program; if not, write to the Free Software
58+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
59+
60+"""Tree classes, representing directory at point in time.
61+"""
62+
63+from __future__ import absolute_import
64+
65+import os
66+import re
67+
68+from . import (
69+ errors,
70+ lazy_import,
71+ osutils,
72+ revision,
73+ )
74+from .mutabletree import (
75+ MutableTree,
76+ needs_tree_write_lock,
77+ )
78+from .revisiontree import (
79+ RevisionTree,
80+ )
81+lazy_import.lazy_import(globals(), """
82+from breezy import (
83+ add,
84+ controldir,
85+ inventory as _mod_inventory,
86+ trace,
87+ transport as _mod_transport,
88+ )
89+""")
90+from .decorators import needs_read_lock
91+from .sixish import (
92+ viewvalues,
93+ )
94+from .tree import InterTree, Tree
95+
96+
97+class InventoryTree(Tree):
98+ """A tree that relies on an inventory for its metadata.
99+
100+ Trees contain an `Inventory` object, and also know how to retrieve
101+ file texts mentioned in the inventory, either from a working
102+ directory or from a store.
103+
104+ It is possible for trees to contain files that are not described
105+ in their inventory or vice versa; for this use `filenames()`.
106+
107+ Subclasses should set the _inventory attribute, which is considered
108+ private to external API users.
109+ """
110+
111+ def get_canonical_inventory_paths(self, paths):
112+ """Like get_canonical_inventory_path() but works on multiple items.
113+
114+ :param paths: A sequence of paths relative to the root of the tree.
115+ :return: A list of paths, with each item the corresponding input path
116+ adjusted to account for existing elements that match case
117+ insensitively.
118+ """
119+ return list(self._yield_canonical_inventory_paths(paths))
120+
121+ def get_canonical_inventory_path(self, path):
122+ """Returns the first inventory item that case-insensitively matches path.
123+
124+ If a path matches exactly, it is returned. If no path matches exactly
125+ but more than one path matches case-insensitively, it is implementation
126+ defined which is returned.
127+
128+ If no path matches case-insensitively, the input path is returned, but
129+ with as many path entries that do exist changed to their canonical
130+ form.
131+
132+ If you need to resolve many names from the same tree, you should
133+ use get_canonical_inventory_paths() to avoid O(N) behaviour.
134+
135+ :param path: A paths relative to the root of the tree.
136+ :return: The input path adjusted to account for existing elements
137+ that match case insensitively.
138+ """
139+ return next(self._yield_canonical_inventory_paths([path]))
140+
141+ def _yield_canonical_inventory_paths(self, paths):
142+ for path in paths:
143+ # First, if the path as specified exists exactly, just use it.
144+ if self.path2id(path) is not None:
145+ yield path
146+ continue
147+ # go walkin...
148+ cur_id = self.get_root_id()
149+ cur_path = ''
150+ bit_iter = iter(path.split("/"))
151+ for elt in bit_iter:
152+ lelt = elt.lower()
153+ new_path = None
154+ for child in self.iter_children(cur_id):
155+ try:
156+ # XXX: it seem like if the child is known to be in the
157+ # tree, we shouldn't need to go from its id back to
158+ # its path -- mbp 2010-02-11
159+ #
160+ # XXX: it seems like we could be more efficient
161+ # by just directly looking up the original name and
162+ # only then searching all children; also by not
163+ # chopping paths so much. -- mbp 2010-02-11
164+ child_base = os.path.basename(self.id2path(child))
165+ if (child_base == elt):
166+ # if we found an exact match, we can stop now; if
167+ # we found an approximate match we need to keep
168+ # searching because there might be an exact match
169+ # later.
170+ cur_id = child
171+ new_path = osutils.pathjoin(cur_path, child_base)
172+ break
173+ elif child_base.lower() == lelt:
174+ cur_id = child
175+ new_path = osutils.pathjoin(cur_path, child_base)
176+ except errors.NoSuchId:
177+ # before a change is committed we can see this error...
178+ continue
179+ if new_path:
180+ cur_path = new_path
181+ else:
182+ # got to the end of this directory and no entries matched.
183+ # Return what matched so far, plus the rest as specified.
184+ cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
185+ break
186+ yield cur_path
187+ # all done.
188+
189+ def _get_root_inventory(self):
190+ return self._inventory
191+
192+ root_inventory = property(_get_root_inventory,
193+ doc="Root inventory of this tree")
194+
195+ def _unpack_file_id(self, file_id):
196+ """Find the inventory and inventory file id for a tree file id.
197+
198+ :param file_id: The tree file id, as bytestring or tuple
199+ :return: Inventory and inventory file id
200+ """
201+ if isinstance(file_id, tuple):
202+ if len(file_id) != 1:
203+ raise ValueError("nested trees not yet supported: %r" % file_id)
204+ file_id = file_id[0]
205+ return self.root_inventory, file_id
206+
207+ @needs_read_lock
208+ def path2id(self, path):
209+ """Return the id for path in this tree."""
210+ return self._path2inv_file_id(path)[1]
211+
212+ def _path2inv_file_id(self, path):
213+ """Lookup a inventory and inventory file id by path.
214+
215+ :param path: Path to look up
216+ :return: tuple with inventory and inventory file id
217+ """
218+ # FIXME: Support nested trees
219+ return self.root_inventory, self.root_inventory.path2id(path)
220+
221+ def id2path(self, file_id):
222+ """Return the path for a file id.
223+
224+ :raises NoSuchId:
225+ """
226+ inventory, file_id = self._unpack_file_id(file_id)
227+ return inventory.id2path(file_id)
228+
229+ def has_id(self, file_id):
230+ inventory, file_id = self._unpack_file_id(file_id)
231+ return inventory.has_id(file_id)
232+
233+ def has_or_had_id(self, file_id):
234+ inventory, file_id = self._unpack_file_id(file_id)
235+ return inventory.has_id(file_id)
236+
237+ def all_file_ids(self):
238+ return {entry.file_id for path, entry in self.iter_entries_by_dir()}
239+
240+ def filter_unversioned_files(self, paths):
241+ """Filter out paths that are versioned.
242+
243+ :return: set of paths.
244+ """
245+ # NB: we specifically *don't* call self.has_filename, because for
246+ # WorkingTrees that can indicate files that exist on disk but that
247+ # are not versioned.
248+ return set((p for p in paths if self.path2id(p) is None))
249+
250+ @needs_read_lock
251+ def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
252+ """Walk the tree in 'by_dir' order.
253+
254+ This will yield each entry in the tree as a (path, entry) tuple.
255+ The order that they are yielded is:
256+
257+ See Tree.iter_entries_by_dir for details.
258+
259+ :param yield_parents: If True, yield the parents from the root leading
260+ down to specific_file_ids that have been requested. This has no
261+ impact if specific_file_ids is None.
262+ """
263+ if specific_file_ids is None:
264+ inventory_file_ids = None
265+ else:
266+ inventory_file_ids = []
267+ for tree_file_id in specific_file_ids:
268+ inventory, inv_file_id = self._unpack_file_id(tree_file_id)
269+ if not inventory is self.root_inventory: # for now
270+ raise AssertionError("%r != %r" % (
271+ inventory, self.root_inventory))
272+ inventory_file_ids.append(inv_file_id)
273+ # FIXME: Handle nested trees
274+ return self.root_inventory.iter_entries_by_dir(
275+ specific_file_ids=inventory_file_ids, yield_parents=yield_parents)
276+
277+ @needs_read_lock
278+ def iter_child_entries(self, file_id, path=None):
279+ inv, inv_file_id = self._unpack_file_id(file_id)
280+ return iter(viewvalues(inv[inv_file_id].children))
281+
282+ def iter_children(self, file_id, path=None):
283+ """See Tree.iter_children."""
284+ entry = self.iter_entries_by_dir([file_id]).next()[1]
285+ for child in viewvalues(getattr(entry, 'children', {})):
286+ yield child.file_id
287+
288+
289+class MutableInventoryTree(MutableTree, InventoryTree):
290+
291+ @needs_tree_write_lock
292+ def apply_inventory_delta(self, changes):
293+ """Apply changes to the inventory as an atomic operation.
294+
295+ :param changes: An inventory delta to apply to the working tree's
296+ inventory.
297+ :return None:
298+ :seealso Inventory.apply_delta: For details on the changes parameter.
299+ """
300+ self.flush()
301+ inv = self.root_inventory
302+ inv.apply_delta(changes)
303+ self._write_inventory(inv)
304+
305+ def _fix_case_of_inventory_path(self, path):
306+ """If our tree isn't case sensitive, return the canonical path"""
307+ if not self.case_sensitive:
308+ path = self.get_canonical_inventory_path(path)
309+ return path
310+
311+ @needs_tree_write_lock
312+ def smart_add(self, file_list, recurse=True, action=None, save=True):
313+ """Version file_list, optionally recursing into directories.
314+
315+ This is designed more towards DWIM for humans than API clarity.
316+ For the specific behaviour see the help for cmd_add().
317+
318+ :param file_list: List of zero or more paths. *NB: these are
319+ interpreted relative to the process cwd, not relative to the
320+ tree.* (Add and most other tree methods use tree-relative
321+ paths.)
322+ :param action: A reporter to be called with the inventory, parent_ie,
323+ path and kind of the path being added. It may return a file_id if
324+ a specific one should be used.
325+ :param save: Save the inventory after completing the adds. If False
326+ this provides dry-run functionality by doing the add and not saving
327+ the inventory.
328+ :return: A tuple - files_added, ignored_files. files_added is the count
329+ of added files, and ignored_files is a dict mapping files that were
330+ ignored to the rule that caused them to be ignored.
331+ """
332+ # Not all mutable trees can have conflicts
333+ if getattr(self, 'conflicts', None) is not None:
334+ # Collect all related files without checking whether they exist or
335+ # are versioned. It's cheaper to do that once for all conflicts
336+ # than trying to find the relevant conflict for each added file.
337+ conflicts_related = set()
338+ for c in self.conflicts():
339+ conflicts_related.update(c.associated_filenames())
340+ else:
341+ conflicts_related = None
342+ adder = _SmartAddHelper(self, action, conflicts_related)
343+ adder.add(file_list, recurse=recurse)
344+ if save:
345+ invdelta = adder.get_inventory_delta()
346+ self.apply_inventory_delta(invdelta)
347+ return adder.added, adder.ignored
348+
349+ def update_basis_by_delta(self, new_revid, delta):
350+ """Update the parents of this tree after a commit.
351+
352+ This gives the tree one parent, with revision id new_revid. The
353+ inventory delta is applied to the current basis tree to generate the
354+ inventory for the parent new_revid, and all other parent trees are
355+ discarded.
356+
357+ All the changes in the delta should be changes synchronising the basis
358+ tree with some or all of the working tree, with a change to a directory
359+ requiring that its contents have been recursively included. That is,
360+ this is not a general purpose tree modification routine, but a helper
361+ for commit which is not required to handle situations that do not arise
362+ outside of commit.
363+
364+ See the inventory developers documentation for the theory behind
365+ inventory deltas.
366+
367+ :param new_revid: The new revision id for the trees parent.
368+ :param delta: An inventory delta (see apply_inventory_delta) describing
369+ the changes from the current left most parent revision to new_revid.
370+ """
371+ # if the tree is updated by a pull to the branch, as happens in
372+ # WorkingTree2, when there was no separation between branch and tree,
373+ # then just clear merges, efficiency is not a concern for now as this
374+ # is legacy environments only, and they are slow regardless.
375+ if self.last_revision() == new_revid:
376+ self.set_parent_ids([new_revid])
377+ return
378+ # generic implementation based on Inventory manipulation. See
379+ # WorkingTree classes for optimised versions for specific format trees.
380+ basis = self.basis_tree()
381+ basis.lock_read()
382+ # TODO: Consider re-evaluating the need for this with CHKInventory
383+ # we don't strictly need to mutate an inventory for this
384+ # it only makes sense when apply_delta is cheaper than get_inventory()
385+ inventory = _mod_inventory.mutable_inventory_from_tree(basis)
386+ basis.unlock()
387+ inventory.apply_delta(delta)
388+ rev_tree = InventoryRevisionTree(self.branch.repository,
389+ inventory, new_revid)
390+ self.set_parent_trees([(new_revid, rev_tree)])
391+
392+
393+class _SmartAddHelper(object):
394+ """Helper for MutableTree.smart_add."""
395+
396+ def get_inventory_delta(self):
397+ # GZ 2016-06-05: Returning view would probably be fine but currently
398+ # Inventory.apply_delta is documented as requiring a list of changes.
399+ return list(viewvalues(self._invdelta))
400+
401+ def _get_ie(self, inv_path):
402+ """Retrieve the most up to date inventory entry for a path.
403+
404+ :param inv_path: Normalized inventory path
405+ :return: Inventory entry (with possibly invalid .children for
406+ directories)
407+ """
408+ entry = self._invdelta.get(inv_path)
409+ if entry is not None:
410+ return entry[3]
411+ # Find a 'best fit' match if the filesystem is case-insensitive
412+ inv_path = self.tree._fix_case_of_inventory_path(inv_path)
413+ file_id = self.tree.path2id(inv_path)
414+ if file_id is not None:
415+ return self.tree.iter_entries_by_dir([file_id]).next()[1]
416+ return None
417+
418+ def _convert_to_directory(self, this_ie, inv_path):
419+ """Convert an entry to a directory.
420+
421+ :param this_ie: Inventory entry
422+ :param inv_path: Normalized path for the inventory entry
423+ :return: The new inventory entry
424+ """
425+ # Same as in _add_one below, if the inventory doesn't
426+ # think this is a directory, update the inventory
427+ this_ie = _mod_inventory.InventoryDirectory(
428+ this_ie.file_id, this_ie.name, this_ie.parent_id)
429+ self._invdelta[inv_path] = (inv_path, inv_path, this_ie.file_id,
430+ this_ie)
431+ return this_ie
432+
433+ def _add_one_and_parent(self, parent_ie, path, kind, inv_path):
434+ """Add a new entry to the inventory and automatically add unversioned parents.
435+
436+ :param parent_ie: Parent inventory entry if known, or None. If
437+ None, the parent is looked up by name and used if present, otherwise it
438+ is recursively added.
439+ :param path:
440+ :param kind: Kind of new entry (file, directory, etc)
441+ :param inv_path:
442+ :return: Inventory entry for path and a list of paths which have been added.
443+ """
444+ # Nothing to do if path is already versioned.
445+ # This is safe from infinite recursion because the tree root is
446+ # always versioned.
447+ inv_dirname = osutils.dirname(inv_path)
448+ dirname, basename = osutils.split(path)
449+ if parent_ie is None:
450+ # slower but does not need parent_ie
451+ this_ie = self._get_ie(inv_path)
452+ if this_ie is not None:
453+ return this_ie
454+ # its really not there : add the parent
455+ # note that the dirname use leads to some extra str copying etc but as
456+ # there are a limited number of dirs we can be nested under, it should
457+ # generally find it very fast and not recurse after that.
458+ parent_ie = self._add_one_and_parent(None,
459+ dirname, 'directory',
460+ inv_dirname)
461+ # if the parent exists, but isn't a directory, we have to do the
462+ # kind change now -- really the inventory shouldn't pretend to know
463+ # the kind of wt files, but it does.
464+ if parent_ie.kind != 'directory':
465+ # nb: this relies on someone else checking that the path we're using
466+ # doesn't contain symlinks.
467+ parent_ie = self._convert_to_directory(parent_ie, inv_dirname)
468+ file_id = self.action(self.tree, parent_ie, path, kind)
469+ entry = _mod_inventory.make_entry(kind, basename, parent_ie.file_id,
470+ file_id=file_id)
471+ self._invdelta[inv_path] = (None, inv_path, entry.file_id, entry)
472+ self.added.append(inv_path)
473+ return entry
474+
475+ def _gather_dirs_to_add(self, user_dirs):
476+ # only walk the minimal parents needed: we have user_dirs to override
477+ # ignores.
478+ prev_dir = None
479+
480+ is_inside = osutils.is_inside_or_parent_of_any
481+ for path in sorted(user_dirs):
482+ if (prev_dir is None or not is_inside([prev_dir], path)):
483+ inv_path, this_ie = user_dirs[path]
484+ yield (path, inv_path, this_ie, None)
485+ prev_dir = path
486+
487+ def __init__(self, tree, action, conflicts_related=None):
488+ self.tree = tree
489+ if action is None:
490+ self.action = add.AddAction()
491+ else:
492+ self.action = action
493+ self._invdelta = {}
494+ self.added = []
495+ self.ignored = {}
496+ if conflicts_related is None:
497+ self.conflicts_related = frozenset()
498+ else:
499+ self.conflicts_related = conflicts_related
500+
501+ def add(self, file_list, recurse=True):
502+ from breezy.inventory import InventoryEntry
503+ if not file_list:
504+ # no paths supplied: add the entire tree.
505+ # FIXME: this assumes we are running in a working tree subdir :-/
506+ # -- vila 20100208
507+ file_list = [u'.']
508+
509+ # expand any symlinks in the directory part, while leaving the
510+ # filename alone
511+ # only expanding if symlinks are supported avoids windows path bugs
512+ if osutils.has_symlinks():
513+ file_list = list(map(osutils.normalizepath, file_list))
514+
515+ user_dirs = {}
516+ # validate user file paths and convert all paths to tree
517+ # relative : it's cheaper to make a tree relative path an abspath
518+ # than to convert an abspath to tree relative, and it's cheaper to
519+ # perform the canonicalization in bulk.
520+ for filepath in osutils.canonical_relpaths(self.tree.basedir, file_list):
521+ # validate user parameters. Our recursive code avoids adding new
522+ # files that need such validation
523+ if self.tree.is_control_filename(filepath):
524+ raise errors.ForbiddenControlFileError(filename=filepath)
525+
526+ abspath = self.tree.abspath(filepath)
527+ kind = osutils.file_kind(abspath)
528+ # ensure the named path is added, so that ignore rules in the later
529+ # directory walk dont skip it.
530+ # we dont have a parent ie known yet.: use the relatively slower
531+ # inventory probing method
532+ inv_path, _ = osutils.normalized_filename(filepath)
533+ this_ie = self._get_ie(inv_path)
534+ if this_ie is None:
535+ this_ie = self._add_one_and_parent(None, filepath, kind, inv_path)
536+ if kind == 'directory':
537+ # schedule the dir for scanning
538+ user_dirs[filepath] = (inv_path, this_ie)
539+
540+ if not recurse:
541+ # no need to walk any directories at all.
542+ return
543+
544+ things_to_add = list(self._gather_dirs_to_add(user_dirs))
545+
546+ illegalpath_re = re.compile(r'[\r\n]')
547+ for directory, inv_path, this_ie, parent_ie in things_to_add:
548+ # directory is tree-relative
549+ abspath = self.tree.abspath(directory)
550+
551+ # get the contents of this directory.
552+
553+ # find the kind of the path being added, and save stat_value
554+ # for reuse
555+ stat_value = None
556+ if this_ie is None:
557+ stat_value = osutils.file_stat(abspath)
558+ kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
559+ else:
560+ kind = this_ie.kind
561+
562+ # allow AddAction to skip this file
563+ if self.action.skip_file(self.tree, abspath, kind, stat_value):
564+ continue
565+ if not InventoryEntry.versionable_kind(kind):
566+ trace.warning("skipping %s (can't add file of kind '%s')",
567+ abspath, kind)
568+ continue
569+ if illegalpath_re.search(directory):
570+ trace.warning("skipping %r (contains \\n or \\r)" % abspath)
571+ continue
572+ if directory in self.conflicts_related:
573+ # If the file looks like one generated for a conflict, don't
574+ # add it.
575+ trace.warning(
576+ 'skipping %s (generated to help resolve conflicts)',
577+ abspath)
578+ continue
579+
580+ if kind == 'directory' and directory != '':
581+ try:
582+ transport = _mod_transport.get_transport_from_path(abspath)
583+ controldir.ControlDirFormat.find_format(transport)
584+ sub_tree = True
585+ except errors.NotBranchError:
586+ sub_tree = False
587+ except errors.UnsupportedFormatError:
588+ sub_tree = True
589+ else:
590+ sub_tree = False
591+
592+ if this_ie is not None:
593+ pass
594+ elif sub_tree:
595+ # XXX: This is wrong; people *might* reasonably be trying to
596+ # add subtrees as subtrees. This should probably only be done
597+ # in formats which can represent subtrees, and even then
598+ # perhaps only when the user asked to add subtrees. At the
599+ # moment you can add them specially through 'join --reference',
600+ # which is perhaps reasonable: adding a new reference is a
601+ # special operation and can have a special behaviour. mbp
602+ # 20070306
603+ trace.warning("skipping nested tree %r", abspath)
604+ else:
605+ this_ie = self._add_one_and_parent(parent_ie, directory, kind,
606+ inv_path)
607+
608+ if kind == 'directory' and not sub_tree:
609+ if this_ie.kind != 'directory':
610+ this_ie = self._convert_to_directory(this_ie, inv_path)
611+
612+ for subf in sorted(os.listdir(abspath)):
613+ inv_f, _ = osutils.normalized_filename(subf)
614+ # here we could use TreeDirectory rather than
615+ # string concatenation.
616+ subp = osutils.pathjoin(directory, subf)
617+ # TODO: is_control_filename is very slow. Make it faster.
618+ # TreeDirectory.is_control_filename could also make this
619+ # faster - its impossible for a non root dir to have a
620+ # control file.
621+ if self.tree.is_control_filename(subp):
622+ trace.mutter("skip control directory %r", subp)
623+ continue
624+ sub_invp = osutils.pathjoin(inv_path, inv_f)
625+ entry = self._invdelta.get(sub_invp)
626+ if entry is not None:
627+ sub_ie = entry[3]
628+ else:
629+ sub_ie = this_ie.children.get(inv_f)
630+ if sub_ie is not None:
631+ # recurse into this already versioned subdir.
632+ things_to_add.append((subp, sub_invp, sub_ie, this_ie))
633+ else:
634+ # user selection overrides ignores
635+ # ignore while selecting files - if we globbed in the
636+ # outer loop we would ignore user files.
637+ ignore_glob = self.tree.is_ignored(subp)
638+ if ignore_glob is not None:
639+ self.ignored.setdefault(ignore_glob, []).append(subp)
640+ else:
641+ things_to_add.append((subp, sub_invp, None, this_ie))
642+
643+
644+class InventoryRevisionTree(RevisionTree,InventoryTree):
645+
646+ def __init__(self, repository, inv, revision_id):
647+ RevisionTree.__init__(self, repository, revision_id)
648+ self._inventory = inv
649+
650+ def get_file_mtime(self, file_id, path=None):
651+ inv, inv_file_id = self._unpack_file_id(file_id)
652+ ie = inv[inv_file_id]
653+ try:
654+ revision = self._repository.get_revision(ie.revision)
655+ except errors.NoSuchRevision:
656+ raise errors.FileTimestampUnavailable(self.id2path(file_id))
657+ return revision.timestamp
658+
659+ def get_file_size(self, file_id):
660+ inv, inv_file_id = self._unpack_file_id(file_id)
661+ return inv[inv_file_id].text_size
662+
663+ def get_file_sha1(self, file_id, path=None, stat_value=None):
664+ inv, inv_file_id = self._unpack_file_id(file_id)
665+ ie = inv[inv_file_id]
666+ if ie.kind == "file":
667+ return ie.text_sha1
668+ return None
669+
670+ def get_file_revision(self, file_id, path=None):
671+ inv, inv_file_id = self._unpack_file_id(file_id)
672+ ie = inv[inv_file_id]
673+ return ie.revision
674+
675+ def is_executable(self, file_id, path=None):
676+ inv, inv_file_id = self._unpack_file_id(file_id)
677+ ie = inv[inv_file_id]
678+ if ie.kind != "file":
679+ return False
680+ return ie.executable
681+
682+ def has_filename(self, filename):
683+ return bool(self.path2id(filename))
684+
685+ def list_files(self, include_root=False, from_dir=None, recursive=True):
686+ # The only files returned by this are those from the version
687+ if from_dir is None:
688+ from_dir_id = None
689+ inv = self.root_inventory
690+ else:
691+ inv, from_dir_id = self._path2inv_file_id(from_dir)
692+ if from_dir_id is None:
693+ # Directory not versioned
694+ return
695+ entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
696+ if inv.root is not None and not include_root and from_dir is None:
697+ # skip the root for compatability with the current apis.
698+ next(entries)
699+ for path, entry in entries:
700+ yield path, 'V', entry.kind, entry.file_id, entry
701+
702+ def get_symlink_target(self, file_id, path=None):
703+ inv, inv_file_id = self._unpack_file_id(file_id)
704+ ie = inv[inv_file_id]
705+ # Inventories store symlink targets in unicode
706+ return ie.symlink_target
707+
708+ def get_reference_revision(self, file_id, path=None):
709+ inv, inv_file_id = self._unpack_file_id(file_id)
710+ return inv[inv_file_id].reference_revision
711+
712+ def get_root_id(self):
713+ if self.root_inventory.root:
714+ return self.root_inventory.root.file_id
715+
716+ def kind(self, file_id):
717+ inv, inv_file_id = self._unpack_file_id(file_id)
718+ return inv[inv_file_id].kind
719+
720+ def path_content_summary(self, path):
721+ """See Tree.path_content_summary."""
722+ inv, file_id = self._path2inv_file_id(path)
723+ if file_id is None:
724+ return ('missing', None, None, None)
725+ entry = inv[file_id]
726+ kind = entry.kind
727+ if kind == 'file':
728+ return (kind, entry.text_size, entry.executable, entry.text_sha1)
729+ elif kind == 'symlink':
730+ return (kind, None, None, entry.symlink_target)
731+ else:
732+ return (kind, None, None, None)
733+
734+ def _comparison_data(self, entry, path):
735+ if entry is None:
736+ return None, False, None
737+ return entry.kind, entry.executable, None
738+
739+ def _file_size(self, entry, stat_value):
740+ return entry.text_size
741+
742+ def walkdirs(self, prefix=""):
743+ _directory = 'directory'
744+ inv, top_id = self._path2inv_file_id(prefix)
745+ if top_id is None:
746+ pending = []
747+ else:
748+ pending = [(prefix, '', _directory, None, top_id, None)]
749+ while pending:
750+ dirblock = []
751+ currentdir = pending.pop()
752+ # 0 - relpath, 1- basename, 2- kind, 3- stat, id, v-kind
753+ if currentdir[0]:
754+ relroot = currentdir[0] + '/'
755+ else:
756+ relroot = ""
757+ # FIXME: stash the node in pending
758+ entry = inv[currentdir[4]]
759+ for name, child in entry.sorted_children():
760+ toppath = relroot + name
761+ dirblock.append((toppath, name, child.kind, None,
762+ child.file_id, child.kind
763+ ))
764+ yield (currentdir[0], entry.file_id), dirblock
765+ # push the user specified dirs from dirblock
766+ for dir in reversed(dirblock):
767+ if dir[2] == _directory:
768+ pending.append(dir)
769+
770+ def iter_files_bytes(self, desired_files):
771+ """See Tree.iter_files_bytes.
772+
773+ This version is implemented on top of Repository.iter_files_bytes"""
774+ repo_desired_files = [(f, self.get_file_revision(f), i)
775+ for f, i in desired_files]
776+ try:
777+ for result in self._repository.iter_files_bytes(repo_desired_files):
778+ yield result
779+ except errors.RevisionNotPresent as e:
780+ raise errors.NoSuchFile(e.file_id)
781+
782+ def annotate_iter(self, file_id,
783+ default_revision=revision.CURRENT_REVISION):
784+ """See Tree.annotate_iter"""
785+ text_key = (file_id, self.get_file_revision(file_id))
786+ annotator = self._repository.texts.get_annotator()
787+ annotations = annotator.annotate_flat(text_key)
788+ return [(key[-1], line) for key, line in annotations]
789+
790+ def __eq__(self, other):
791+ if self is other:
792+ return True
793+ if isinstance(other, InventoryRevisionTree):
794+ return (self.root_inventory == other.root_inventory)
795+ return False
796+
797+ def __ne__(self, other):
798+ return not (self == other)
799+
800+ def __hash__(self):
801+ raise ValueError('not hashable')
802+
803+
804+class InterCHKRevisionTree(InterTree):
805+ """Fast path optimiser for RevisionTrees with CHK inventories."""
806+
807+ @staticmethod
808+ def is_compatible(source, target):
809+ if (isinstance(source, RevisionTree)
810+ and isinstance(target, RevisionTree)):
811+ try:
812+ # Only CHK inventories have id_to_entry attribute
813+ source.root_inventory.id_to_entry
814+ target.root_inventory.id_to_entry
815+ return True
816+ except AttributeError:
817+ pass
818+ return False
819+
820+ def iter_changes(self, include_unchanged=False,
821+ specific_files=None, pb=None, extra_trees=[],
822+ require_versioned=True, want_unversioned=False):
823+ lookup_trees = [self.source]
824+ if extra_trees:
825+ lookup_trees.extend(extra_trees)
826+ # The ids of items we need to examine to insure delta consistency.
827+ precise_file_ids = set()
828+ discarded_changes = {}
829+ if specific_files == []:
830+ specific_file_ids = []
831+ else:
832+ specific_file_ids = self.target.paths2ids(specific_files,
833+ lookup_trees, require_versioned=require_versioned)
834+ # FIXME: It should be possible to delegate include_unchanged handling
835+ # to CHKInventory.iter_changes and do a better job there -- vila
836+ # 20090304
837+ changed_file_ids = set()
838+ # FIXME: nested tree support
839+ for result in self.target.root_inventory.iter_changes(
840+ self.source.root_inventory):
841+ if specific_file_ids is not None:
842+ file_id = result[0]
843+ if file_id not in specific_file_ids:
844+ # A change from the whole tree that we don't want to show yet.
845+ # We may find that we need to show it for delta consistency, so
846+ # stash it.
847+ discarded_changes[result[0]] = result
848+ continue
849+ new_parent_id = result[4][1]
850+ precise_file_ids.add(new_parent_id)
851+ yield result
852+ changed_file_ids.add(result[0])
853+ if specific_file_ids is not None:
854+ for result in self._handle_precise_ids(precise_file_ids,
855+ changed_file_ids, discarded_changes=discarded_changes):
856+ yield result
857+ if include_unchanged:
858+ # CHKMap avoid being O(tree), so we go to O(tree) only if
859+ # required to.
860+ # Now walk the whole inventory, excluding the already yielded
861+ # file ids
862+ # FIXME: Support nested trees
863+ changed_file_ids = set(changed_file_ids)
864+ for relpath, entry in self.target.root_inventory.iter_entries():
865+ if (specific_file_ids is not None
866+ and not entry.file_id in specific_file_ids):
867+ continue
868+ if not entry.file_id in changed_file_ids:
869+ yield (entry.file_id,
870+ (relpath, relpath), # Not renamed
871+ False, # Not modified
872+ (True, True), # Still versioned
873+ (entry.parent_id, entry.parent_id),
874+ (entry.name, entry.name),
875+ (entry.kind, entry.kind),
876+ (entry.executable, entry.executable))
877+
878+
879+InterTree.register_optimiser(InterCHKRevisionTree)
880
881=== modified file 'breezy/memorytree.py'
882--- breezy/memorytree.py 2017-06-10 00:17:06 +0000
883+++ breezy/memorytree.py 2017-06-10 18:45:29 +0000
884@@ -30,12 +30,13 @@
885 )
886 from .decorators import needs_read_lock
887 from .inventory import Inventory
888+from .inventorytree import MutableInventoryTree
889 from .osutils import sha_file
890 from .mutabletree import needs_tree_write_lock
891 from .transport.memory import MemoryTransport
892
893
894-class MemoryTree(mutabletree.MutableInventoryTree):
895+class MemoryTree(MutableInventoryTree):
896 """A MemoryTree is a specialisation of MutableTree.
897
898 It maintains nearly no state outside of read_lock and write_lock
899
900=== modified file 'breezy/mutabletree.py'
901--- breezy/mutabletree.py 2017-06-05 20:48:31 +0000
902+++ breezy/mutabletree.py 2017-06-10 18:45:29 +0000
903@@ -21,25 +21,15 @@
904
905 from __future__ import absolute_import
906
907-from .lazy_import import lazy_import
908-lazy_import(globals(), """
909 import operator
910 import os
911-import re
912-
913-from breezy import (
914- add,
915- controldir,
916+from . import (
917 errors,
918 hooks,
919- inventory as _mod_inventory,
920 osutils,
921- revisiontree,
922 trace,
923- transport as _mod_transport,
924 tree,
925 )
926-""")
927
928 from .decorators import needs_read_lock, needs_write_lock
929 from .sixish import (
930@@ -396,110 +386,6 @@
931 raise NotImplementedError(self.smart_add)
932
933
934-class MutableInventoryTree(MutableTree, tree.InventoryTree):
935-
936- @needs_tree_write_lock
937- def apply_inventory_delta(self, changes):
938- """Apply changes to the inventory as an atomic operation.
939-
940- :param changes: An inventory delta to apply to the working tree's
941- inventory.
942- :return None:
943- :seealso Inventory.apply_delta: For details on the changes parameter.
944- """
945- self.flush()
946- inv = self.root_inventory
947- inv.apply_delta(changes)
948- self._write_inventory(inv)
949-
950- def _fix_case_of_inventory_path(self, path):
951- """If our tree isn't case sensitive, return the canonical path"""
952- if not self.case_sensitive:
953- path = self.get_canonical_inventory_path(path)
954- return path
955-
956- @needs_tree_write_lock
957- def smart_add(self, file_list, recurse=True, action=None, save=True):
958- """Version file_list, optionally recursing into directories.
959-
960- This is designed more towards DWIM for humans than API clarity.
961- For the specific behaviour see the help for cmd_add().
962-
963- :param file_list: List of zero or more paths. *NB: these are
964- interpreted relative to the process cwd, not relative to the
965- tree.* (Add and most other tree methods use tree-relative
966- paths.)
967- :param action: A reporter to be called with the inventory, parent_ie,
968- path and kind of the path being added. It may return a file_id if
969- a specific one should be used.
970- :param save: Save the inventory after completing the adds. If False
971- this provides dry-run functionality by doing the add and not saving
972- the inventory.
973- :return: A tuple - files_added, ignored_files. files_added is the count
974- of added files, and ignored_files is a dict mapping files that were
975- ignored to the rule that caused them to be ignored.
976- """
977- # Not all mutable trees can have conflicts
978- if getattr(self, 'conflicts', None) is not None:
979- # Collect all related files without checking whether they exist or
980- # are versioned. It's cheaper to do that once for all conflicts
981- # than trying to find the relevant conflict for each added file.
982- conflicts_related = set()
983- for c in self.conflicts():
984- conflicts_related.update(c.associated_filenames())
985- else:
986- conflicts_related = None
987- adder = _SmartAddHelper(self, action, conflicts_related)
988- adder.add(file_list, recurse=recurse)
989- if save:
990- invdelta = adder.get_inventory_delta()
991- self.apply_inventory_delta(invdelta)
992- return adder.added, adder.ignored
993-
994- def update_basis_by_delta(self, new_revid, delta):
995- """Update the parents of this tree after a commit.
996-
997- This gives the tree one parent, with revision id new_revid. The
998- inventory delta is applied to the current basis tree to generate the
999- inventory for the parent new_revid, and all other parent trees are
1000- discarded.
1001-
1002- All the changes in the delta should be changes synchronising the basis
1003- tree with some or all of the working tree, with a change to a directory
1004- requiring that its contents have been recursively included. That is,
1005- this is not a general purpose tree modification routine, but a helper
1006- for commit which is not required to handle situations that do not arise
1007- outside of commit.
1008-
1009- See the inventory developers documentation for the theory behind
1010- inventory deltas.
1011-
1012- :param new_revid: The new revision id for the trees parent.
1013- :param delta: An inventory delta (see apply_inventory_delta) describing
1014- the changes from the current left most parent revision to new_revid.
1015- """
1016- # if the tree is updated by a pull to the branch, as happens in
1017- # WorkingTree2, when there was no separation between branch and tree,
1018- # then just clear merges, efficiency is not a concern for now as this
1019- # is legacy environments only, and they are slow regardless.
1020- if self.last_revision() == new_revid:
1021- self.set_parent_ids([new_revid])
1022- return
1023- # generic implementation based on Inventory manipulation. See
1024- # WorkingTree classes for optimised versions for specific format trees.
1025- basis = self.basis_tree()
1026- basis.lock_read()
1027- # TODO: Consider re-evaluating the need for this with CHKInventory
1028- # we don't strictly need to mutate an inventory for this
1029- # it only makes sense when apply_delta is cheaper than get_inventory()
1030- inventory = _mod_inventory.mutable_inventory_from_tree(basis)
1031- basis.unlock()
1032- inventory.apply_delta(delta)
1033- rev_tree = revisiontree.InventoryRevisionTree(self.branch.repository,
1034- inventory, new_revid)
1035- self.set_parent_trees([(new_revid, rev_tree)])
1036-
1037-
1038 class MutableTreeHooks(hooks.Hooks):
1039 """A dictionary mapping a hook name to a list of callables for mutabletree
1040 hooks.
1041@@ -548,254 +434,3 @@
1042 def __init__(self, mutable_tree):
1043 """Create the parameters for the post_commit hook."""
1044 self.mutable_tree = mutable_tree
1045-
1046-
1047-class _SmartAddHelper(object):
1048- """Helper for MutableTree.smart_add."""
1049-
1050- def get_inventory_delta(self):
1051- # GZ 2016-06-05: Returning view would probably be fine but currently
1052- # Inventory.apply_delta is documented as requiring a list of changes.
1053- return list(viewvalues(self._invdelta))
1054-
1055- def _get_ie(self, inv_path):
1056- """Retrieve the most up to date inventory entry for a path.
1057-
1058- :param inv_path: Normalized inventory path
1059- :return: Inventory entry (with possibly invalid .children for
1060- directories)
1061- """
1062- entry = self._invdelta.get(inv_path)
1063- if entry is not None:
1064- return entry[3]
1065- # Find a 'best fit' match if the filesystem is case-insensitive
1066- inv_path = self.tree._fix_case_of_inventory_path(inv_path)
1067- file_id = self.tree.path2id(inv_path)
1068- if file_id is not None:
1069- return self.tree.iter_entries_by_dir([file_id]).next()[1]
1070- return None
1071-
1072- def _convert_to_directory(self, this_ie, inv_path):
1073- """Convert an entry to a directory.
1074-
1075- :param this_ie: Inventory entry
1076- :param inv_path: Normalized path for the inventory entry
1077- :return: The new inventory entry
1078- """
1079- # Same as in _add_one below, if the inventory doesn't
1080- # think this is a directory, update the inventory
1081- this_ie = _mod_inventory.InventoryDirectory(
1082- this_ie.file_id, this_ie.name, this_ie.parent_id)
1083- self._invdelta[inv_path] = (inv_path, inv_path, this_ie.file_id,
1084- this_ie)
1085- return this_ie
1086-
1087- def _add_one_and_parent(self, parent_ie, path, kind, inv_path):
1088- """Add a new entry to the inventory and automatically add unversioned parents.
1089-
1090- :param parent_ie: Parent inventory entry if known, or None. If
1091- None, the parent is looked up by name and used if present, otherwise it
1092- is recursively added.
1093- :param path:
1094- :param kind: Kind of new entry (file, directory, etc)
1095- :param inv_path:
1096- :return: Inventory entry for path and a list of paths which have been added.
1097- """
1098- # Nothing to do if path is already versioned.
1099- # This is safe from infinite recursion because the tree root is
1100- # always versioned.
1101- inv_dirname = osutils.dirname(inv_path)
1102- dirname, basename = osutils.split(path)
1103- if parent_ie is None:
1104- # slower but does not need parent_ie
1105- this_ie = self._get_ie(inv_path)
1106- if this_ie is not None:
1107- return this_ie
1108- # its really not there : add the parent
1109- # note that the dirname use leads to some extra str copying etc but as
1110- # there are a limited number of dirs we can be nested under, it should
1111- # generally find it very fast and not recurse after that.
1112- parent_ie = self._add_one_and_parent(None,
1113- dirname, 'directory',
1114- inv_dirname)
1115- # if the parent exists, but isn't a directory, we have to do the
1116- # kind change now -- really the inventory shouldn't pretend to know
1117- # the kind of wt files, but it does.
1118- if parent_ie.kind != 'directory':
1119- # nb: this relies on someone else checking that the path we're using
1120- # doesn't contain symlinks.
1121- parent_ie = self._convert_to_directory(parent_ie, inv_dirname)
1122- file_id = self.action(self.tree, parent_ie, path, kind)
1123- entry = _mod_inventory.make_entry(kind, basename, parent_ie.file_id,
1124- file_id=file_id)
1125- self._invdelta[inv_path] = (None, inv_path, entry.file_id, entry)
1126- self.added.append(inv_path)
1127- return entry
1128-
1129- def _gather_dirs_to_add(self, user_dirs):
1130- # only walk the minimal parents needed: we have user_dirs to override
1131- # ignores.
1132- prev_dir = None
1133-
1134- is_inside = osutils.is_inside_or_parent_of_any
1135- for path in sorted(user_dirs):
1136- if (prev_dir is None or not is_inside([prev_dir], path)):
1137- inv_path, this_ie = user_dirs[path]
1138- yield (path, inv_path, this_ie, None)
1139- prev_dir = path
1140-
1141- def __init__(self, tree, action, conflicts_related=None):
1142- self.tree = tree
1143- if action is None:
1144- self.action = add.AddAction()
1145- else:
1146- self.action = action
1147- self._invdelta = {}
1148- self.added = []
1149- self.ignored = {}
1150- if conflicts_related is None:
1151- self.conflicts_related = frozenset()
1152- else:
1153- self.conflicts_related = conflicts_related
1154-
1155- def add(self, file_list, recurse=True):
1156- from breezy.inventory import InventoryEntry
1157- if not file_list:
1158- # no paths supplied: add the entire tree.
1159- # FIXME: this assumes we are running in a working tree subdir :-/
1160- # -- vila 20100208
1161- file_list = [u'.']
1162-
1163- # expand any symlinks in the directory part, while leaving the
1164- # filename alone
1165- # only expanding if symlinks are supported avoids windows path bugs
1166- if osutils.has_symlinks():
1167- file_list = list(map(osutils.normalizepath, file_list))
1168-
1169- user_dirs = {}
1170- # validate user file paths and convert all paths to tree
1171- # relative : it's cheaper to make a tree relative path an abspath
1172- # than to convert an abspath to tree relative, and it's cheaper to
1173- # perform the canonicalization in bulk.
1174- for filepath in osutils.canonical_relpaths(self.tree.basedir, file_list):
1175- # validate user parameters. Our recursive code avoids adding new
1176- # files that need such validation
1177- if self.tree.is_control_filename(filepath):
1178- raise errors.ForbiddenControlFileError(filename=filepath)
1179-
1180- abspath = self.tree.abspath(filepath)
1181- kind = osutils.file_kind(abspath)
1182- # ensure the named path is added, so that ignore rules in the later
1183- # directory walk dont skip it.
1184- # we dont have a parent ie known yet.: use the relatively slower
1185- # inventory probing method
1186- inv_path, _ = osutils.normalized_filename(filepath)
1187- this_ie = self._get_ie(inv_path)
1188- if this_ie is None:
1189- this_ie = self._add_one_and_parent(None, filepath, kind, inv_path)
1190- if kind == 'directory':
1191- # schedule the dir for scanning
1192- user_dirs[filepath] = (inv_path, this_ie)
1193-
1194- if not recurse:
1195- # no need to walk any directories at all.
1196- return
1197-
1198- things_to_add = list(self._gather_dirs_to_add(user_dirs))
1199-
1200- illegalpath_re = re.compile(r'[\r\n]')
1201- for directory, inv_path, this_ie, parent_ie in things_to_add:
1202- # directory is tree-relative
1203- abspath = self.tree.abspath(directory)
1204-
1205- # get the contents of this directory.
1206-
1207- # find the kind of the path being added, and save stat_value
1208- # for reuse
1209- stat_value = None
1210- if this_ie is None:
1211- stat_value = osutils.file_stat(abspath)
1212- kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
1213- else:
1214- kind = this_ie.kind
1215-
1216- # allow AddAction to skip this file
1217- if self.action.skip_file(self.tree, abspath, kind, stat_value):
1218- continue
1219- if not InventoryEntry.versionable_kind(kind):
1220- trace.warning("skipping %s (can't add file of kind '%s')",
1221- abspath, kind)
1222- continue
1223- if illegalpath_re.search(directory):
1224- trace.warning("skipping %r (contains \\n or \\r)" % abspath)
1225- continue
1226- if directory in self.conflicts_related:
1227- # If the file looks like one generated for a conflict, don't
1228- # add it.
1229- trace.warning(
1230- 'skipping %s (generated to help resolve conflicts)',
1231- abspath)
1232- continue
1233-
1234- if kind == 'directory' and directory != '':
1235- try:
1236- transport = _mod_transport.get_transport_from_path(abspath)
1237- controldir.ControlDirFormat.find_format(transport)
1238- sub_tree = True
1239- except errors.NotBranchError:
1240- sub_tree = False
1241- except errors.UnsupportedFormatError:
1242- sub_tree = True
1243- else:
1244- sub_tree = False
1245-
1246- if this_ie is not None:
1247- pass
1248- elif sub_tree:
1249- # XXX: This is wrong; people *might* reasonably be trying to
1250- # add subtrees as subtrees. This should probably only be done
1251- # in formats which can represent subtrees, and even then
1252- # perhaps only when the user asked to add subtrees. At the
1253- # moment you can add them specially through 'join --reference',
1254- # which is perhaps reasonable: adding a new reference is a
1255- # special operation and can have a special behaviour. mbp
1256- # 20070306
1257- trace.warning("skipping nested tree %r", abspath)
1258- else:
1259- this_ie = self._add_one_and_parent(parent_ie, directory, kind,
1260- inv_path)
1261-
1262- if kind == 'directory' and not sub_tree:
1263- if this_ie.kind != 'directory':
1264- this_ie = self._convert_to_directory(this_ie, inv_path)
1265-
1266- for subf in sorted(os.listdir(abspath)):
1267- inv_f, _ = osutils.normalized_filename(subf)
1268- # here we could use TreeDirectory rather than
1269- # string concatenation.
1270- subp = osutils.pathjoin(directory, subf)
1271- # TODO: is_control_filename is very slow. Make it faster.
1272- # TreeDirectory.is_control_filename could also make this
1273- # faster - its impossible for a non root dir to have a
1274- # control file.
1275- if self.tree.is_control_filename(subp):
1276- trace.mutter("skip control directory %r", subp)
1277- continue
1278- sub_invp = osutils.pathjoin(inv_path, inv_f)
1279- entry = self._invdelta.get(sub_invp)
1280- if entry is not None:
1281- sub_ie = entry[3]
1282- else:
1283- sub_ie = this_ie.children.get(inv_f)
1284- if sub_ie is not None:
1285- # recurse into this already versioned subdir.
1286- things_to_add.append((subp, sub_invp, sub_ie, this_ie))
1287- else:
1288- # user selection overrides ignores
1289- # ignore while selecting files - if we globbed in the
1290- # outer loop we would ignore user files.
1291- ignore_glob = self.tree.is_ignored(subp)
1292- if ignore_glob is not None:
1293- self.ignored.setdefault(ignore_glob, []).append(subp)
1294- else:
1295- things_to_add.append((subp, sub_invp, None, this_ie))
1296
1297=== modified file 'breezy/remote.py'
1298--- breezy/remote.py 2017-06-10 00:52:08 +0000
1299+++ breezy/remote.py 2017-06-10 18:45:29 +0000
1300@@ -52,6 +52,7 @@
1301 )
1302 from .i18n import gettext
1303 from .inventory import Inventory
1304+from .inventorytree import InventoryRevisionTree
1305 from .lockable_files import LockableFiles
1306 from .sixish import (
1307 viewitems,
1308@@ -60,7 +61,6 @@
1309 from .smart import client, vfs, repository as smart_repo
1310 from .smart.client import _SmartClient
1311 from .revision import NULL_REVISION
1312-from .revisiontree import InventoryRevisionTree
1313 from .repository import RepositoryWriteLockResult, _LazyListJoin
1314 from .serializer import format_registry as serializer_format_registry
1315 from .trace import mutter, note, warning, log_exception_quietly
1316
1317=== modified file 'breezy/revisiontree.py'
1318--- breezy/revisiontree.py 2017-05-25 01:35:55 +0000
1319+++ breezy/revisiontree.py 2017-06-10 18:45:29 +0000
1320@@ -95,241 +95,3 @@
1321 self._rules_searcher = super(RevisionTree,
1322 self)._get_rules_searcher(default_searcher)
1323 return self._rules_searcher
1324-
1325-
1326-class InventoryRevisionTree(RevisionTree,tree.InventoryTree):
1327-
1328- def __init__(self, repository, inv, revision_id):
1329- RevisionTree.__init__(self, repository, revision_id)
1330- self._inventory = inv
1331-
1332- def get_file_mtime(self, file_id, path=None):
1333- inv, inv_file_id = self._unpack_file_id(file_id)
1334- ie = inv[inv_file_id]
1335- try:
1336- revision = self._repository.get_revision(ie.revision)
1337- except errors.NoSuchRevision:
1338- raise errors.FileTimestampUnavailable(self.id2path(file_id))
1339- return revision.timestamp
1340-
1341- def get_file_size(self, file_id):
1342- inv, inv_file_id = self._unpack_file_id(file_id)
1343- return inv[inv_file_id].text_size
1344-
1345- def get_file_sha1(self, file_id, path=None, stat_value=None):
1346- inv, inv_file_id = self._unpack_file_id(file_id)
1347- ie = inv[inv_file_id]
1348- if ie.kind == "file":
1349- return ie.text_sha1
1350- return None
1351-
1352- def get_file_revision(self, file_id, path=None):
1353- inv, inv_file_id = self._unpack_file_id(file_id)
1354- ie = inv[inv_file_id]
1355- return ie.revision
1356-
1357- def is_executable(self, file_id, path=None):
1358- inv, inv_file_id = self._unpack_file_id(file_id)
1359- ie = inv[inv_file_id]
1360- if ie.kind != "file":
1361- return False
1362- return ie.executable
1363-
1364- def has_filename(self, filename):
1365- return bool(self.path2id(filename))
1366-
1367- def list_files(self, include_root=False, from_dir=None, recursive=True):
1368- # The only files returned by this are those from the version
1369- if from_dir is None:
1370- from_dir_id = None
1371- inv = self.root_inventory
1372- else:
1373- inv, from_dir_id = self._path2inv_file_id(from_dir)
1374- if from_dir_id is None:
1375- # Directory not versioned
1376- return
1377- entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
1378- if inv.root is not None and not include_root and from_dir is None:
1379- # skip the root for compatability with the current apis.
1380- next(entries)
1381- for path, entry in entries:
1382- yield path, 'V', entry.kind, entry.file_id, entry
1383-
1384- def get_symlink_target(self, file_id, path=None):
1385- inv, inv_file_id = self._unpack_file_id(file_id)
1386- ie = inv[inv_file_id]
1387- # Inventories store symlink targets in unicode
1388- return ie.symlink_target
1389-
1390- def get_reference_revision(self, file_id, path=None):
1391- inv, inv_file_id = self._unpack_file_id(file_id)
1392- return inv[inv_file_id].reference_revision
1393-
1394- def get_root_id(self):
1395- if self.root_inventory.root:
1396- return self.root_inventory.root.file_id
1397-
1398- def kind(self, file_id):
1399- inv, inv_file_id = self._unpack_file_id(file_id)
1400- return inv[inv_file_id].kind
1401-
1402- def path_content_summary(self, path):
1403- """See Tree.path_content_summary."""
1404- inv, file_id = self._path2inv_file_id(path)
1405- if file_id is None:
1406- return ('missing', None, None, None)
1407- entry = inv[file_id]
1408- kind = entry.kind
1409- if kind == 'file':
1410- return (kind, entry.text_size, entry.executable, entry.text_sha1)
1411- elif kind == 'symlink':
1412- return (kind, None, None, entry.symlink_target)
1413- else:
1414- return (kind, None, None, None)
1415-
1416- def _comparison_data(self, entry, path):
1417- if entry is None:
1418- return None, False, None
1419- return entry.kind, entry.executable, None
1420-
1421- def _file_size(self, entry, stat_value):
1422- return entry.text_size
1423-
1424- def walkdirs(self, prefix=""):
1425- _directory = 'directory'
1426- inv, top_id = self._path2inv_file_id(prefix)
1427- if top_id is None:
1428- pending = []
1429- else:
1430- pending = [(prefix, '', _directory, None, top_id, None)]
1431- while pending:
1432- dirblock = []
1433- currentdir = pending.pop()
1434- # 0 - relpath, 1- basename, 2- kind, 3- stat, id, v-kind
1435- if currentdir[0]:
1436- relroot = currentdir[0] + '/'
1437- else:
1438- relroot = ""
1439- # FIXME: stash the node in pending
1440- entry = inv[currentdir[4]]
1441- for name, child in entry.sorted_children():
1442- toppath = relroot + name
1443- dirblock.append((toppath, name, child.kind, None,
1444- child.file_id, child.kind
1445- ))
1446- yield (currentdir[0], entry.file_id), dirblock
1447- # push the user specified dirs from dirblock
1448- for dir in reversed(dirblock):
1449- if dir[2] == _directory:
1450- pending.append(dir)
1451-
1452- def iter_files_bytes(self, desired_files):
1453- """See Tree.iter_files_bytes.
1454-
1455- This version is implemented on top of Repository.iter_files_bytes"""
1456- repo_desired_files = [(f, self.get_file_revision(f), i)
1457- for f, i in desired_files]
1458- try:
1459- for result in self._repository.iter_files_bytes(repo_desired_files):
1460- yield result
1461- except errors.RevisionNotPresent as e:
1462- raise errors.NoSuchFile(e.file_id)
1463-
1464- def annotate_iter(self, file_id,
1465- default_revision=revision.CURRENT_REVISION):
1466- """See Tree.annotate_iter"""
1467- text_key = (file_id, self.get_file_revision(file_id))
1468- annotator = self._repository.texts.get_annotator()
1469- annotations = annotator.annotate_flat(text_key)
1470- return [(key[-1], line) for key, line in annotations]
1471-
1472- def __eq__(self, other):
1473- if self is other:
1474- return True
1475- if isinstance(other, InventoryRevisionTree):
1476- return (self.root_inventory == other.root_inventory)
1477- return False
1478-
1479- def __ne__(self, other):
1480- return not (self == other)
1481-
1482- def __hash__(self):
1483- raise ValueError('not hashable')
1484-
1485-
1486-class InterCHKRevisionTree(tree.InterTree):
1487- """Fast path optimiser for RevisionTrees with CHK inventories."""
1488-
1489- @staticmethod
1490- def is_compatible(source, target):
1491- if (isinstance(source, RevisionTree)
1492- and isinstance(target, RevisionTree)):
1493- try:
1494- # Only CHK inventories have id_to_entry attribute
1495- source.root_inventory.id_to_entry
1496- target.root_inventory.id_to_entry
1497- return True
1498- except AttributeError:
1499- pass
1500- return False
1501-
1502- def iter_changes(self, include_unchanged=False,
1503- specific_files=None, pb=None, extra_trees=[],
1504- require_versioned=True, want_unversioned=False):
1505- lookup_trees = [self.source]
1506- if extra_trees:
1507- lookup_trees.extend(extra_trees)
1508- # The ids of items we need to examine to insure delta consistency.
1509- precise_file_ids = set()
1510- discarded_changes = {}
1511- if specific_files == []:
1512- specific_file_ids = []
1513- else:
1514- specific_file_ids = self.target.paths2ids(specific_files,
1515- lookup_trees, require_versioned=require_versioned)
1516- # FIXME: It should be possible to delegate include_unchanged handling
1517- # to CHKInventory.iter_changes and do a better job there -- vila
1518- # 20090304
1519- changed_file_ids = set()
1520- # FIXME: nested tree support
1521- for result in self.target.root_inventory.iter_changes(
1522- self.source.root_inventory):
1523- if specific_file_ids is not None:
1524- file_id = result[0]
1525- if file_id not in specific_file_ids:
1526- # A change from the whole tree that we don't want to show yet.
1527- # We may find that we need to show it for delta consistency, so
1528- # stash it.
1529- discarded_changes[result[0]] = result
1530- continue
1531- new_parent_id = result[4][1]
1532- precise_file_ids.add(new_parent_id)
1533- yield result
1534- changed_file_ids.add(result[0])
1535- if specific_file_ids is not None:
1536- for result in self._handle_precise_ids(precise_file_ids,
1537- changed_file_ids, discarded_changes=discarded_changes):
1538- yield result
1539- if include_unchanged:
1540- # CHKMap avoid being O(tree), so we go to O(tree) only if
1541- # required to.
1542- # Now walk the whole inventory, excluding the already yielded
1543- # file ids
1544- # FIXME: Support nested trees
1545- changed_file_ids = set(changed_file_ids)
1546- for relpath, entry in self.target.root_inventory.iter_entries():
1547- if (specific_file_ids is not None
1548- and not entry.file_id in specific_file_ids):
1549- continue
1550- if not entry.file_id in changed_file_ids:
1551- yield (entry.file_id,
1552- (relpath, relpath), # Not renamed
1553- False, # Not modified
1554- (True, True), # Still versioned
1555- (entry.parent_id, entry.parent_id),
1556- (entry.name, entry.name),
1557- (entry.kind, entry.kind),
1558- (entry.executable, entry.executable))
1559-
1560-
1561-tree.InterTree.register_optimiser(InterCHKRevisionTree)
1562
1563=== modified file 'breezy/tests/per_intertree/__init__.py'
1564--- breezy/tests/per_intertree/__init__.py 2017-06-10 00:52:37 +0000
1565+++ breezy/tests/per_intertree/__init__.py 2017-06-10 18:45:29 +0000
1566@@ -24,6 +24,7 @@
1567
1568 import breezy
1569 from breezy import (
1570+ inventorytree,
1571 revisiontree,
1572 tests,
1573 )
1574@@ -130,7 +131,7 @@
1575 default_tree_format, default_tree_format,
1576 return_provided_trees)]
1577 for optimiser in InterTree._optimisers:
1578- if optimiser is revisiontree.InterCHKRevisionTree:
1579+ if optimiser is inventorytree.InterCHKRevisionTree:
1580 # XXX: we shouldn't use an Intertree object to detect inventories
1581 # -- vila 20090311
1582 chk_tree_format = WorkingTreeFormat4()
1583
1584=== modified file 'breezy/tests/per_tree/test_inv.py'
1585--- breezy/tests/per_tree/test_inv.py 2017-05-22 00:56:52 +0000
1586+++ breezy/tests/per_tree/test_inv.py 2017-06-10 18:45:29 +0000
1587@@ -23,9 +23,9 @@
1588 from breezy.tests import (
1589 per_tree,
1590 )
1591+from breezy.inventorytree import InventoryTree
1592 from breezy.mutabletree import MutableTree
1593 from breezy.tests import TestSkipped
1594-from breezy.tree import InventoryTree
1595 from breezy.transform import _PreviewTree
1596 from breezy.uncommit import uncommit
1597 from breezy.tests import (
1598
1599=== modified file 'breezy/tests/per_workingtree/test_parents.py'
1600--- breezy/tests/per_workingtree/test_parents.py 2017-06-10 00:52:08 +0000
1601+++ breezy/tests/per_workingtree/test_parents.py 2017-06-10 18:45:29 +0000
1602@@ -28,7 +28,7 @@
1603 InventoryDirectory,
1604 InventoryLink,
1605 )
1606-from ...revisiontree import InventoryRevisionTree
1607+from ...inventorytree import InventoryRevisionTree
1608 from ...sixish import (
1609 BytesIO,
1610 )
1611
1612=== modified file 'breezy/tests/test_dirstate.py'
1613--- breezy/tests/test_dirstate.py 2017-06-10 00:52:37 +0000
1614+++ breezy/tests/test_dirstate.py 2017-06-10 18:45:29 +0000
1615@@ -24,6 +24,7 @@
1616 dirstate,
1617 errors,
1618 inventory,
1619+ inventorytree,
1620 memorytree,
1621 osutils,
1622 revision as _mod_revision,
1623@@ -2492,7 +2493,7 @@
1624 dir_ids[''] = file_id
1625 continue
1626 inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
1627- return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
1628+ return inventorytree.InventoryRevisionTree(_Repo(), inv, rev_id)
1629
1630 def create_empty_dirstate(self):
1631 fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
1632
1633=== modified file 'breezy/transform.py'
1634--- breezy/transform.py 2017-06-05 20:48:31 +0000
1635+++ breezy/transform.py 2017-06-10 18:45:29 +0000
1636@@ -24,6 +24,7 @@
1637 from . import (
1638 config as _mod_config,
1639 errors,
1640+ inventorytree,
1641 lazy_import,
1642 registry,
1643 trace,
1644@@ -1971,7 +1972,7 @@
1645 raise NotImplementedError(self.new_orphan)
1646
1647
1648-class _PreviewTree(tree.InventoryTree):
1649+class _PreviewTree(inventorytree.InventoryTree):
1650 """Partial implementation of Tree to support show_diff_trees"""
1651
1652 def __init__(self, transform):
1653
1654=== modified file 'breezy/tree.py'
1655--- breezy/tree.py 2017-06-05 20:48:31 +0000
1656+++ breezy/tree.py 2017-06-10 18:45:29 +0000
1657@@ -684,198 +684,6 @@
1658 return searcher
1659
1660
1661-class InventoryTree(Tree):
1662- """A tree that relies on an inventory for its metadata.
1663-
1664- Trees contain an `Inventory` object, and also know how to retrieve
1665- file texts mentioned in the inventory, either from a working
1666- directory or from a store.
1667-
1668- It is possible for trees to contain files that are not described
1669- in their inventory or vice versa; for this use `filenames()`.
1670-
1671- Subclasses should set the _inventory attribute, which is considered
1672- private to external API users.
1673- """
1674-
1675- def get_canonical_inventory_paths(self, paths):
1676- """Like get_canonical_inventory_path() but works on multiple items.
1677-
1678- :param paths: A sequence of paths relative to the root of the tree.
1679- :return: A list of paths, with each item the corresponding input path
1680- adjusted to account for existing elements that match case
1681- insensitively.
1682- """
1683- return list(self._yield_canonical_inventory_paths(paths))
1684-
1685- def get_canonical_inventory_path(self, path):
1686- """Returns the first inventory item that case-insensitively matches path.
1687-
1688- If a path matches exactly, it is returned. If no path matches exactly
1689- but more than one path matches case-insensitively, it is implementation
1690- defined which is returned.
1691-
1692- If no path matches case-insensitively, the input path is returned, but
1693- with as many path entries that do exist changed to their canonical
1694- form.
1695-
1696- If you need to resolve many names from the same tree, you should
1697- use get_canonical_inventory_paths() to avoid O(N) behaviour.
1698-
1699- :param path: A paths relative to the root of the tree.
1700- :return: The input path adjusted to account for existing elements
1701- that match case insensitively.
1702- """
1703- return next(self._yield_canonical_inventory_paths([path]))
1704-
1705- def _yield_canonical_inventory_paths(self, paths):
1706- for path in paths:
1707- # First, if the path as specified exists exactly, just use it.
1708- if self.path2id(path) is not None:
1709- yield path
1710- continue
1711- # go walkin...
1712- cur_id = self.get_root_id()
1713- cur_path = ''
1714- bit_iter = iter(path.split("/"))
1715- for elt in bit_iter:
1716- lelt = elt.lower()
1717- new_path = None
1718- for child in self.iter_children(cur_id):
1719- try:
1720- # XXX: it seem like if the child is known to be in the
1721- # tree, we shouldn't need to go from its id back to
1722- # its path -- mbp 2010-02-11
1723- #
1724- # XXX: it seems like we could be more efficient
1725- # by just directly looking up the original name and
1726- # only then searching all children; also by not
1727- # chopping paths so much. -- mbp 2010-02-11
1728- child_base = os.path.basename(self.id2path(child))
1729- if (child_base == elt):
1730- # if we found an exact match, we can stop now; if
1731- # we found an approximate match we need to keep
1732- # searching because there might be an exact match
1733- # later.
1734- cur_id = child
1735- new_path = osutils.pathjoin(cur_path, child_base)
1736- break
1737- elif child_base.lower() == lelt:
1738- cur_id = child
1739- new_path = osutils.pathjoin(cur_path, child_base)
1740- except errors.NoSuchId:
1741- # before a change is committed we can see this error...
1742- continue
1743- if new_path:
1744- cur_path = new_path
1745- else:
1746- # got to the end of this directory and no entries matched.
1747- # Return what matched so far, plus the rest as specified.
1748- cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
1749- break
1750- yield cur_path
1751- # all done.
1752-
1753- def _get_root_inventory(self):
1754- return self._inventory
1755-
1756- root_inventory = property(_get_root_inventory,
1757- doc="Root inventory of this tree")
1758-
1759- def _unpack_file_id(self, file_id):
1760- """Find the inventory and inventory file id for a tree file id.
1761-
1762- :param file_id: The tree file id, as bytestring or tuple
1763- :return: Inventory and inventory file id
1764- """
1765- if isinstance(file_id, tuple):
1766- if len(file_id) != 1:
1767- raise ValueError("nested trees not yet supported: %r" % file_id)
1768- file_id = file_id[0]
1769- return self.root_inventory, file_id
1770-
1771- @needs_read_lock
1772- def path2id(self, path):
1773- """Return the id for path in this tree."""
1774- return self._path2inv_file_id(path)[1]
1775-
1776- def _path2inv_file_id(self, path):
1777- """Lookup a inventory and inventory file id by path.
1778-
1779- :param path: Path to look up
1780- :return: tuple with inventory and inventory file id
1781- """
1782- # FIXME: Support nested trees
1783- return self.root_inventory, self.root_inventory.path2id(path)
1784-
1785- def id2path(self, file_id):
1786- """Return the path for a file id.
1787-
1788- :raises NoSuchId:
1789- """
1790- inventory, file_id = self._unpack_file_id(file_id)
1791- return inventory.id2path(file_id)
1792-
1793- def has_id(self, file_id):
1794- inventory, file_id = self._unpack_file_id(file_id)
1795- return inventory.has_id(file_id)
1796-
1797- def has_or_had_id(self, file_id):
1798- inventory, file_id = self._unpack_file_id(file_id)
1799- return inventory.has_id(file_id)
1800-
1801- def all_file_ids(self):
1802- return {entry.file_id for path, entry in self.iter_entries_by_dir()}
1803-
1804- def filter_unversioned_files(self, paths):
1805- """Filter out paths that are versioned.
1806-
1807- :return: set of paths.
1808- """
1809- # NB: we specifically *don't* call self.has_filename, because for
1810- # WorkingTrees that can indicate files that exist on disk but that
1811- # are not versioned.
1812- return set((p for p in paths if self.path2id(p) is None))
1813-
1814- @needs_read_lock
1815- def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1816- """Walk the tree in 'by_dir' order.
1817-
1818- This will yield each entry in the tree as a (path, entry) tuple.
1819- The order that they are yielded is:
1820-
1821- See Tree.iter_entries_by_dir for details.
1822-
1823- :param yield_parents: If True, yield the parents from the root leading
1824- down to specific_file_ids that have been requested. This has no
1825- impact if specific_file_ids is None.
1826- """
1827- if specific_file_ids is None:
1828- inventory_file_ids = None
1829- else:
1830- inventory_file_ids = []
1831- for tree_file_id in specific_file_ids:
1832- inventory, inv_file_id = self._unpack_file_id(tree_file_id)
1833- if not inventory is self.root_inventory: # for now
1834- raise AssertionError("%r != %r" % (
1835- inventory, self.root_inventory))
1836- inventory_file_ids.append(inv_file_id)
1837- # FIXME: Handle nested trees
1838- return self.root_inventory.iter_entries_by_dir(
1839- specific_file_ids=inventory_file_ids, yield_parents=yield_parents)
1840-
1841- @needs_read_lock
1842- def iter_child_entries(self, file_id, path=None):
1843- inv, inv_file_id = self._unpack_file_id(file_id)
1844- return iter(viewvalues(inv[inv_file_id].children))
1845-
1846- def iter_children(self, file_id, path=None):
1847- """See Tree.iter_children."""
1848- entry = self.iter_entries_by_dir([file_id]).next()[1]
1849- for child in viewvalues(getattr(entry, 'children', {})):
1850- yield child.file_id
1851-
1852-
1853 def find_ids_across_trees(filenames, trees, require_versioned=True):
1854 """Find the ids corresponding to specified filenames.
1855
1856
1857=== modified file 'breezy/vf_repository.py'
1858--- breezy/vf_repository.py 2017-06-10 00:52:08 +0000
1859+++ breezy/vf_repository.py 2017-06-10 18:45:29 +0000
1860@@ -44,7 +44,7 @@
1861 )
1862
1863 from breezy.recordcounter import RecordCounter
1864-from breezy.revisiontree import InventoryRevisionTree
1865+from breezy.inventorytree import InventoryRevisionTree
1866 from breezy.testament import Testament
1867 from breezy.i18n import gettext
1868 """)
1869
1870=== modified file 'breezy/workingtree.py'
1871--- breezy/workingtree.py 2017-06-10 00:52:08 +0000
1872+++ breezy/workingtree.py 2017-06-10 18:45:29 +0000
1873@@ -18,7 +18,7 @@
1874
1875 A WorkingTree represents the editable working copy of a branch.
1876 Operations which represent the WorkingTree are also done here,
1877-such as renaming or adding files.
1878+such as renaming or adding files.
1879
1880 At the moment every WorkingTree has its own branch. Remote
1881 WorkingTrees aren't supported.
1882
1883=== modified file 'breezy/workingtree_4.py'
1884--- breezy/workingtree_4.py 2017-06-10 12:56:18 +0000
1885+++ breezy/workingtree_4.py 2017-06-10 18:45:29 +0000
1886@@ -55,6 +55,10 @@
1887
1888 from .decorators import needs_read_lock, needs_write_lock
1889 from .inventory import Inventory, ROOT_ID, entry_factory
1890+from .inventorytree import (
1891+ InventoryTree,
1892+ InventoryRevisionTree,
1893+ )
1894 from .lock import LogicalLockResult
1895 from .lockable_files import LockableFiles
1896 from .lockdir import LockDir
1897@@ -76,7 +80,6 @@
1898 from .transport.local import LocalTransport
1899 from .tree import (
1900 InterTree,
1901- InventoryTree,
1902 )
1903 from .workingtree import (
1904 WorkingTree,
1905@@ -1132,8 +1135,7 @@
1906 if (len(real_trees) == 1
1907 and not ghosts
1908 and self.branch.repository._format.fast_deltas
1909- and isinstance(real_trees[0][1],
1910- revisiontree.InventoryRevisionTree)
1911+ and isinstance(real_trees[0][1], InventoryRevisionTree)
1912 and self.get_parent_ids()):
1913 rev_id, rev_tree = real_trees[0]
1914 basis_id = self.get_parent_ids()[0]

Subscribers

People subscribed via source and target branches