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
=== modified file 'breezy/bzrworkingtree.py'
--- breezy/bzrworkingtree.py 2017-06-10 12:56:18 +0000
+++ breezy/bzrworkingtree.py 2017-06-10 18:45:29 +0000
@@ -50,7 +50,6 @@
50 errors,50 errors,
51 graph as _mod_graph,51 graph as _mod_graph,
52 inventory,52 inventory,
53 mutabletree,
54 osutils,53 osutils,
55 revision as _mod_revision,54 revision as _mod_revision,
56 revisiontree,55 revisiontree,
@@ -63,6 +62,7 @@
6362
64from .decorators import needs_write_lock, needs_read_lock63from .decorators import needs_write_lock, needs_read_lock
65from .lock import _RelockDebugMixin, LogicalLockResult64from .lock import _RelockDebugMixin, LogicalLockResult
65from .inventorytree import InventoryRevisionTree, MutableInventoryTree
66from .mutabletree import needs_tree_write_lock66from .mutabletree import needs_tree_write_lock
67from .sixish import (67from .sixish import (
68 BytesIO,68 BytesIO,
@@ -85,8 +85,7 @@
85CONFLICT_HEADER_1 = "BZR conflict list format 1"85CONFLICT_HEADER_1 = "BZR conflict list format 1"
8686
8787
88class InventoryWorkingTree(WorkingTree,88class InventoryWorkingTree(WorkingTree,MutableInventoryTree):
89 mutabletree.MutableInventoryTree):
90 """Base class for working trees that are inventory-oriented.89 """Base class for working trees that are inventory-oriented.
9190
92 The inventory is held in the `Branch` working-inventory, and the91 The inventory is held in the `Branch` working-inventory, and the
@@ -591,7 +590,7 @@
591 # dont use the repository revision_tree api because we want590 # dont use the repository revision_tree api because we want
592 # to supply the inventory.591 # to supply the inventory.
593 if inv.revision_id == revision_id:592 if inv.revision_id == revision_id:
594 return revisiontree.InventoryRevisionTree(593 return InventoryRevisionTree(
595 self.branch.repository, inv, revision_id)594 self.branch.repository, inv, revision_id)
596 except errors.BadInventoryFormat:595 except errors.BadInventoryFormat:
597 pass596 pass
598597
=== added file 'breezy/inventorytree.py'
--- breezy/inventorytree.py 1970-01-01 00:00:00 +0000
+++ breezy/inventorytree.py 2017-06-10 18:45:29 +0000
@@ -0,0 +1,836 @@
1# Copyright (C) 2005-2011 Canonical Ltd
2#
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.
7#
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.
12#
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
16
17"""Tree classes, representing directory at point in time.
18"""
19
20from __future__ import absolute_import
21
22import os
23import re
24
25from . import (
26 errors,
27 lazy_import,
28 osutils,
29 revision,
30 )
31from .mutabletree import (
32 MutableTree,
33 needs_tree_write_lock,
34 )
35from .revisiontree import (
36 RevisionTree,
37 )
38lazy_import.lazy_import(globals(), """
39from breezy import (
40 add,
41 controldir,
42 inventory as _mod_inventory,
43 trace,
44 transport as _mod_transport,
45 )
46""")
47from .decorators import needs_read_lock
48from .sixish import (
49 viewvalues,
50 )
51from .tree import InterTree, Tree
52
53
54class InventoryTree(Tree):
55 """A tree that relies on an inventory for its metadata.
56
57 Trees contain an `Inventory` object, and also know how to retrieve
58 file texts mentioned in the inventory, either from a working
59 directory or from a store.
60
61 It is possible for trees to contain files that are not described
62 in their inventory or vice versa; for this use `filenames()`.
63
64 Subclasses should set the _inventory attribute, which is considered
65 private to external API users.
66 """
67
68 def get_canonical_inventory_paths(self, paths):
69 """Like get_canonical_inventory_path() but works on multiple items.
70
71 :param paths: A sequence of paths relative to the root of the tree.
72 :return: A list of paths, with each item the corresponding input path
73 adjusted to account for existing elements that match case
74 insensitively.
75 """
76 return list(self._yield_canonical_inventory_paths(paths))
77
78 def get_canonical_inventory_path(self, path):
79 """Returns the first inventory item that case-insensitively matches path.
80
81 If a path matches exactly, it is returned. If no path matches exactly
82 but more than one path matches case-insensitively, it is implementation
83 defined which is returned.
84
85 If no path matches case-insensitively, the input path is returned, but
86 with as many path entries that do exist changed to their canonical
87 form.
88
89 If you need to resolve many names from the same tree, you should
90 use get_canonical_inventory_paths() to avoid O(N) behaviour.
91
92 :param path: A paths relative to the root of the tree.
93 :return: The input path adjusted to account for existing elements
94 that match case insensitively.
95 """
96 return next(self._yield_canonical_inventory_paths([path]))
97
98 def _yield_canonical_inventory_paths(self, paths):
99 for path in paths:
100 # First, if the path as specified exists exactly, just use it.
101 if self.path2id(path) is not None:
102 yield path
103 continue
104 # go walkin...
105 cur_id = self.get_root_id()
106 cur_path = ''
107 bit_iter = iter(path.split("/"))
108 for elt in bit_iter:
109 lelt = elt.lower()
110 new_path = None
111 for child in self.iter_children(cur_id):
112 try:
113 # XXX: it seem like if the child is known to be in the
114 # tree, we shouldn't need to go from its id back to
115 # its path -- mbp 2010-02-11
116 #
117 # XXX: it seems like we could be more efficient
118 # by just directly looking up the original name and
119 # only then searching all children; also by not
120 # chopping paths so much. -- mbp 2010-02-11
121 child_base = os.path.basename(self.id2path(child))
122 if (child_base == elt):
123 # if we found an exact match, we can stop now; if
124 # we found an approximate match we need to keep
125 # searching because there might be an exact match
126 # later.
127 cur_id = child
128 new_path = osutils.pathjoin(cur_path, child_base)
129 break
130 elif child_base.lower() == lelt:
131 cur_id = child
132 new_path = osutils.pathjoin(cur_path, child_base)
133 except errors.NoSuchId:
134 # before a change is committed we can see this error...
135 continue
136 if new_path:
137 cur_path = new_path
138 else:
139 # got to the end of this directory and no entries matched.
140 # Return what matched so far, plus the rest as specified.
141 cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
142 break
143 yield cur_path
144 # all done.
145
146 def _get_root_inventory(self):
147 return self._inventory
148
149 root_inventory = property(_get_root_inventory,
150 doc="Root inventory of this tree")
151
152 def _unpack_file_id(self, file_id):
153 """Find the inventory and inventory file id for a tree file id.
154
155 :param file_id: The tree file id, as bytestring or tuple
156 :return: Inventory and inventory file id
157 """
158 if isinstance(file_id, tuple):
159 if len(file_id) != 1:
160 raise ValueError("nested trees not yet supported: %r" % file_id)
161 file_id = file_id[0]
162 return self.root_inventory, file_id
163
164 @needs_read_lock
165 def path2id(self, path):
166 """Return the id for path in this tree."""
167 return self._path2inv_file_id(path)[1]
168
169 def _path2inv_file_id(self, path):
170 """Lookup a inventory and inventory file id by path.
171
172 :param path: Path to look up
173 :return: tuple with inventory and inventory file id
174 """
175 # FIXME: Support nested trees
176 return self.root_inventory, self.root_inventory.path2id(path)
177
178 def id2path(self, file_id):
179 """Return the path for a file id.
180
181 :raises NoSuchId:
182 """
183 inventory, file_id = self._unpack_file_id(file_id)
184 return inventory.id2path(file_id)
185
186 def has_id(self, file_id):
187 inventory, file_id = self._unpack_file_id(file_id)
188 return inventory.has_id(file_id)
189
190 def has_or_had_id(self, file_id):
191 inventory, file_id = self._unpack_file_id(file_id)
192 return inventory.has_id(file_id)
193
194 def all_file_ids(self):
195 return {entry.file_id for path, entry in self.iter_entries_by_dir()}
196
197 def filter_unversioned_files(self, paths):
198 """Filter out paths that are versioned.
199
200 :return: set of paths.
201 """
202 # NB: we specifically *don't* call self.has_filename, because for
203 # WorkingTrees that can indicate files that exist on disk but that
204 # are not versioned.
205 return set((p for p in paths if self.path2id(p) is None))
206
207 @needs_read_lock
208 def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
209 """Walk the tree in 'by_dir' order.
210
211 This will yield each entry in the tree as a (path, entry) tuple.
212 The order that they are yielded is:
213
214 See Tree.iter_entries_by_dir for details.
215
216 :param yield_parents: If True, yield the parents from the root leading
217 down to specific_file_ids that have been requested. This has no
218 impact if specific_file_ids is None.
219 """
220 if specific_file_ids is None:
221 inventory_file_ids = None
222 else:
223 inventory_file_ids = []
224 for tree_file_id in specific_file_ids:
225 inventory, inv_file_id = self._unpack_file_id(tree_file_id)
226 if not inventory is self.root_inventory: # for now
227 raise AssertionError("%r != %r" % (
228 inventory, self.root_inventory))
229 inventory_file_ids.append(inv_file_id)
230 # FIXME: Handle nested trees
231 return self.root_inventory.iter_entries_by_dir(
232 specific_file_ids=inventory_file_ids, yield_parents=yield_parents)
233
234 @needs_read_lock
235 def iter_child_entries(self, file_id, path=None):
236 inv, inv_file_id = self._unpack_file_id(file_id)
237 return iter(viewvalues(inv[inv_file_id].children))
238
239 def iter_children(self, file_id, path=None):
240 """See Tree.iter_children."""
241 entry = self.iter_entries_by_dir([file_id]).next()[1]
242 for child in viewvalues(getattr(entry, 'children', {})):
243 yield child.file_id
244
245
246class MutableInventoryTree(MutableTree, InventoryTree):
247
248 @needs_tree_write_lock
249 def apply_inventory_delta(self, changes):
250 """Apply changes to the inventory as an atomic operation.
251
252 :param changes: An inventory delta to apply to the working tree's
253 inventory.
254 :return None:
255 :seealso Inventory.apply_delta: For details on the changes parameter.
256 """
257 self.flush()
258 inv = self.root_inventory
259 inv.apply_delta(changes)
260 self._write_inventory(inv)
261
262 def _fix_case_of_inventory_path(self, path):
263 """If our tree isn't case sensitive, return the canonical path"""
264 if not self.case_sensitive:
265 path = self.get_canonical_inventory_path(path)
266 return path
267
268 @needs_tree_write_lock
269 def smart_add(self, file_list, recurse=True, action=None, save=True):
270 """Version file_list, optionally recursing into directories.
271
272 This is designed more towards DWIM for humans than API clarity.
273 For the specific behaviour see the help for cmd_add().
274
275 :param file_list: List of zero or more paths. *NB: these are
276 interpreted relative to the process cwd, not relative to the
277 tree.* (Add and most other tree methods use tree-relative
278 paths.)
279 :param action: A reporter to be called with the inventory, parent_ie,
280 path and kind of the path being added. It may return a file_id if
281 a specific one should be used.
282 :param save: Save the inventory after completing the adds. If False
283 this provides dry-run functionality by doing the add and not saving
284 the inventory.
285 :return: A tuple - files_added, ignored_files. files_added is the count
286 of added files, and ignored_files is a dict mapping files that were
287 ignored to the rule that caused them to be ignored.
288 """
289 # Not all mutable trees can have conflicts
290 if getattr(self, 'conflicts', None) is not None:
291 # Collect all related files without checking whether they exist or
292 # are versioned. It's cheaper to do that once for all conflicts
293 # than trying to find the relevant conflict for each added file.
294 conflicts_related = set()
295 for c in self.conflicts():
296 conflicts_related.update(c.associated_filenames())
297 else:
298 conflicts_related = None
299 adder = _SmartAddHelper(self, action, conflicts_related)
300 adder.add(file_list, recurse=recurse)
301 if save:
302 invdelta = adder.get_inventory_delta()
303 self.apply_inventory_delta(invdelta)
304 return adder.added, adder.ignored
305
306 def update_basis_by_delta(self, new_revid, delta):
307 """Update the parents of this tree after a commit.
308
309 This gives the tree one parent, with revision id new_revid. The
310 inventory delta is applied to the current basis tree to generate the
311 inventory for the parent new_revid, and all other parent trees are
312 discarded.
313
314 All the changes in the delta should be changes synchronising the basis
315 tree with some or all of the working tree, with a change to a directory
316 requiring that its contents have been recursively included. That is,
317 this is not a general purpose tree modification routine, but a helper
318 for commit which is not required to handle situations that do not arise
319 outside of commit.
320
321 See the inventory developers documentation for the theory behind
322 inventory deltas.
323
324 :param new_revid: The new revision id for the trees parent.
325 :param delta: An inventory delta (see apply_inventory_delta) describing
326 the changes from the current left most parent revision to new_revid.
327 """
328 # if the tree is updated by a pull to the branch, as happens in
329 # WorkingTree2, when there was no separation between branch and tree,
330 # then just clear merges, efficiency is not a concern for now as this
331 # is legacy environments only, and they are slow regardless.
332 if self.last_revision() == new_revid:
333 self.set_parent_ids([new_revid])
334 return
335 # generic implementation based on Inventory manipulation. See
336 # WorkingTree classes for optimised versions for specific format trees.
337 basis = self.basis_tree()
338 basis.lock_read()
339 # TODO: Consider re-evaluating the need for this with CHKInventory
340 # we don't strictly need to mutate an inventory for this
341 # it only makes sense when apply_delta is cheaper than get_inventory()
342 inventory = _mod_inventory.mutable_inventory_from_tree(basis)
343 basis.unlock()
344 inventory.apply_delta(delta)
345 rev_tree = InventoryRevisionTree(self.branch.repository,
346 inventory, new_revid)
347 self.set_parent_trees([(new_revid, rev_tree)])
348
349
350class _SmartAddHelper(object):
351 """Helper for MutableTree.smart_add."""
352
353 def get_inventory_delta(self):
354 # GZ 2016-06-05: Returning view would probably be fine but currently
355 # Inventory.apply_delta is documented as requiring a list of changes.
356 return list(viewvalues(self._invdelta))
357
358 def _get_ie(self, inv_path):
359 """Retrieve the most up to date inventory entry for a path.
360
361 :param inv_path: Normalized inventory path
362 :return: Inventory entry (with possibly invalid .children for
363 directories)
364 """
365 entry = self._invdelta.get(inv_path)
366 if entry is not None:
367 return entry[3]
368 # Find a 'best fit' match if the filesystem is case-insensitive
369 inv_path = self.tree._fix_case_of_inventory_path(inv_path)
370 file_id = self.tree.path2id(inv_path)
371 if file_id is not None:
372 return self.tree.iter_entries_by_dir([file_id]).next()[1]
373 return None
374
375 def _convert_to_directory(self, this_ie, inv_path):
376 """Convert an entry to a directory.
377
378 :param this_ie: Inventory entry
379 :param inv_path: Normalized path for the inventory entry
380 :return: The new inventory entry
381 """
382 # Same as in _add_one below, if the inventory doesn't
383 # think this is a directory, update the inventory
384 this_ie = _mod_inventory.InventoryDirectory(
385 this_ie.file_id, this_ie.name, this_ie.parent_id)
386 self._invdelta[inv_path] = (inv_path, inv_path, this_ie.file_id,
387 this_ie)
388 return this_ie
389
390 def _add_one_and_parent(self, parent_ie, path, kind, inv_path):
391 """Add a new entry to the inventory and automatically add unversioned parents.
392
393 :param parent_ie: Parent inventory entry if known, or None. If
394 None, the parent is looked up by name and used if present, otherwise it
395 is recursively added.
396 :param path:
397 :param kind: Kind of new entry (file, directory, etc)
398 :param inv_path:
399 :return: Inventory entry for path and a list of paths which have been added.
400 """
401 # Nothing to do if path is already versioned.
402 # This is safe from infinite recursion because the tree root is
403 # always versioned.
404 inv_dirname = osutils.dirname(inv_path)
405 dirname, basename = osutils.split(path)
406 if parent_ie is None:
407 # slower but does not need parent_ie
408 this_ie = self._get_ie(inv_path)
409 if this_ie is not None:
410 return this_ie
411 # its really not there : add the parent
412 # note that the dirname use leads to some extra str copying etc but as
413 # there are a limited number of dirs we can be nested under, it should
414 # generally find it very fast and not recurse after that.
415 parent_ie = self._add_one_and_parent(None,
416 dirname, 'directory',
417 inv_dirname)
418 # if the parent exists, but isn't a directory, we have to do the
419 # kind change now -- really the inventory shouldn't pretend to know
420 # the kind of wt files, but it does.
421 if parent_ie.kind != 'directory':
422 # nb: this relies on someone else checking that the path we're using
423 # doesn't contain symlinks.
424 parent_ie = self._convert_to_directory(parent_ie, inv_dirname)
425 file_id = self.action(self.tree, parent_ie, path, kind)
426 entry = _mod_inventory.make_entry(kind, basename, parent_ie.file_id,
427 file_id=file_id)
428 self._invdelta[inv_path] = (None, inv_path, entry.file_id, entry)
429 self.added.append(inv_path)
430 return entry
431
432 def _gather_dirs_to_add(self, user_dirs):
433 # only walk the minimal parents needed: we have user_dirs to override
434 # ignores.
435 prev_dir = None
436
437 is_inside = osutils.is_inside_or_parent_of_any
438 for path in sorted(user_dirs):
439 if (prev_dir is None or not is_inside([prev_dir], path)):
440 inv_path, this_ie = user_dirs[path]
441 yield (path, inv_path, this_ie, None)
442 prev_dir = path
443
444 def __init__(self, tree, action, conflicts_related=None):
445 self.tree = tree
446 if action is None:
447 self.action = add.AddAction()
448 else:
449 self.action = action
450 self._invdelta = {}
451 self.added = []
452 self.ignored = {}
453 if conflicts_related is None:
454 self.conflicts_related = frozenset()
455 else:
456 self.conflicts_related = conflicts_related
457
458 def add(self, file_list, recurse=True):
459 from breezy.inventory import InventoryEntry
460 if not file_list:
461 # no paths supplied: add the entire tree.
462 # FIXME: this assumes we are running in a working tree subdir :-/
463 # -- vila 20100208
464 file_list = [u'.']
465
466 # expand any symlinks in the directory part, while leaving the
467 # filename alone
468 # only expanding if symlinks are supported avoids windows path bugs
469 if osutils.has_symlinks():
470 file_list = list(map(osutils.normalizepath, file_list))
471
472 user_dirs = {}
473 # validate user file paths and convert all paths to tree
474 # relative : it's cheaper to make a tree relative path an abspath
475 # than to convert an abspath to tree relative, and it's cheaper to
476 # perform the canonicalization in bulk.
477 for filepath in osutils.canonical_relpaths(self.tree.basedir, file_list):
478 # validate user parameters. Our recursive code avoids adding new
479 # files that need such validation
480 if self.tree.is_control_filename(filepath):
481 raise errors.ForbiddenControlFileError(filename=filepath)
482
483 abspath = self.tree.abspath(filepath)
484 kind = osutils.file_kind(abspath)
485 # ensure the named path is added, so that ignore rules in the later
486 # directory walk dont skip it.
487 # we dont have a parent ie known yet.: use the relatively slower
488 # inventory probing method
489 inv_path, _ = osutils.normalized_filename(filepath)
490 this_ie = self._get_ie(inv_path)
491 if this_ie is None:
492 this_ie = self._add_one_and_parent(None, filepath, kind, inv_path)
493 if kind == 'directory':
494 # schedule the dir for scanning
495 user_dirs[filepath] = (inv_path, this_ie)
496
497 if not recurse:
498 # no need to walk any directories at all.
499 return
500
501 things_to_add = list(self._gather_dirs_to_add(user_dirs))
502
503 illegalpath_re = re.compile(r'[\r\n]')
504 for directory, inv_path, this_ie, parent_ie in things_to_add:
505 # directory is tree-relative
506 abspath = self.tree.abspath(directory)
507
508 # get the contents of this directory.
509
510 # find the kind of the path being added, and save stat_value
511 # for reuse
512 stat_value = None
513 if this_ie is None:
514 stat_value = osutils.file_stat(abspath)
515 kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
516 else:
517 kind = this_ie.kind
518
519 # allow AddAction to skip this file
520 if self.action.skip_file(self.tree, abspath, kind, stat_value):
521 continue
522 if not InventoryEntry.versionable_kind(kind):
523 trace.warning("skipping %s (can't add file of kind '%s')",
524 abspath, kind)
525 continue
526 if illegalpath_re.search(directory):
527 trace.warning("skipping %r (contains \\n or \\r)" % abspath)
528 continue
529 if directory in self.conflicts_related:
530 # If the file looks like one generated for a conflict, don't
531 # add it.
532 trace.warning(
533 'skipping %s (generated to help resolve conflicts)',
534 abspath)
535 continue
536
537 if kind == 'directory' and directory != '':
538 try:
539 transport = _mod_transport.get_transport_from_path(abspath)
540 controldir.ControlDirFormat.find_format(transport)
541 sub_tree = True
542 except errors.NotBranchError:
543 sub_tree = False
544 except errors.UnsupportedFormatError:
545 sub_tree = True
546 else:
547 sub_tree = False
548
549 if this_ie is not None:
550 pass
551 elif sub_tree:
552 # XXX: This is wrong; people *might* reasonably be trying to
553 # add subtrees as subtrees. This should probably only be done
554 # in formats which can represent subtrees, and even then
555 # perhaps only when the user asked to add subtrees. At the
556 # moment you can add them specially through 'join --reference',
557 # which is perhaps reasonable: adding a new reference is a
558 # special operation and can have a special behaviour. mbp
559 # 20070306
560 trace.warning("skipping nested tree %r", abspath)
561 else:
562 this_ie = self._add_one_and_parent(parent_ie, directory, kind,
563 inv_path)
564
565 if kind == 'directory' and not sub_tree:
566 if this_ie.kind != 'directory':
567 this_ie = self._convert_to_directory(this_ie, inv_path)
568
569 for subf in sorted(os.listdir(abspath)):
570 inv_f, _ = osutils.normalized_filename(subf)
571 # here we could use TreeDirectory rather than
572 # string concatenation.
573 subp = osutils.pathjoin(directory, subf)
574 # TODO: is_control_filename is very slow. Make it faster.
575 # TreeDirectory.is_control_filename could also make this
576 # faster - its impossible for a non root dir to have a
577 # control file.
578 if self.tree.is_control_filename(subp):
579 trace.mutter("skip control directory %r", subp)
580 continue
581 sub_invp = osutils.pathjoin(inv_path, inv_f)
582 entry = self._invdelta.get(sub_invp)
583 if entry is not None:
584 sub_ie = entry[3]
585 else:
586 sub_ie = this_ie.children.get(inv_f)
587 if sub_ie is not None:
588 # recurse into this already versioned subdir.
589 things_to_add.append((subp, sub_invp, sub_ie, this_ie))
590 else:
591 # user selection overrides ignores
592 # ignore while selecting files - if we globbed in the
593 # outer loop we would ignore user files.
594 ignore_glob = self.tree.is_ignored(subp)
595 if ignore_glob is not None:
596 self.ignored.setdefault(ignore_glob, []).append(subp)
597 else:
598 things_to_add.append((subp, sub_invp, None, this_ie))
599
600
601class InventoryRevisionTree(RevisionTree,InventoryTree):
602
603 def __init__(self, repository, inv, revision_id):
604 RevisionTree.__init__(self, repository, revision_id)
605 self._inventory = inv
606
607 def get_file_mtime(self, file_id, path=None):
608 inv, inv_file_id = self._unpack_file_id(file_id)
609 ie = inv[inv_file_id]
610 try:
611 revision = self._repository.get_revision(ie.revision)
612 except errors.NoSuchRevision:
613 raise errors.FileTimestampUnavailable(self.id2path(file_id))
614 return revision.timestamp
615
616 def get_file_size(self, file_id):
617 inv, inv_file_id = self._unpack_file_id(file_id)
618 return inv[inv_file_id].text_size
619
620 def get_file_sha1(self, file_id, path=None, stat_value=None):
621 inv, inv_file_id = self._unpack_file_id(file_id)
622 ie = inv[inv_file_id]
623 if ie.kind == "file":
624 return ie.text_sha1
625 return None
626
627 def get_file_revision(self, file_id, path=None):
628 inv, inv_file_id = self._unpack_file_id(file_id)
629 ie = inv[inv_file_id]
630 return ie.revision
631
632 def is_executable(self, file_id, path=None):
633 inv, inv_file_id = self._unpack_file_id(file_id)
634 ie = inv[inv_file_id]
635 if ie.kind != "file":
636 return False
637 return ie.executable
638
639 def has_filename(self, filename):
640 return bool(self.path2id(filename))
641
642 def list_files(self, include_root=False, from_dir=None, recursive=True):
643 # The only files returned by this are those from the version
644 if from_dir is None:
645 from_dir_id = None
646 inv = self.root_inventory
647 else:
648 inv, from_dir_id = self._path2inv_file_id(from_dir)
649 if from_dir_id is None:
650 # Directory not versioned
651 return
652 entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
653 if inv.root is not None and not include_root and from_dir is None:
654 # skip the root for compatability with the current apis.
655 next(entries)
656 for path, entry in entries:
657 yield path, 'V', entry.kind, entry.file_id, entry
658
659 def get_symlink_target(self, file_id, path=None):
660 inv, inv_file_id = self._unpack_file_id(file_id)
661 ie = inv[inv_file_id]
662 # Inventories store symlink targets in unicode
663 return ie.symlink_target
664
665 def get_reference_revision(self, file_id, path=None):
666 inv, inv_file_id = self._unpack_file_id(file_id)
667 return inv[inv_file_id].reference_revision
668
669 def get_root_id(self):
670 if self.root_inventory.root:
671 return self.root_inventory.root.file_id
672
673 def kind(self, file_id):
674 inv, inv_file_id = self._unpack_file_id(file_id)
675 return inv[inv_file_id].kind
676
677 def path_content_summary(self, path):
678 """See Tree.path_content_summary."""
679 inv, file_id = self._path2inv_file_id(path)
680 if file_id is None:
681 return ('missing', None, None, None)
682 entry = inv[file_id]
683 kind = entry.kind
684 if kind == 'file':
685 return (kind, entry.text_size, entry.executable, entry.text_sha1)
686 elif kind == 'symlink':
687 return (kind, None, None, entry.symlink_target)
688 else:
689 return (kind, None, None, None)
690
691 def _comparison_data(self, entry, path):
692 if entry is None:
693 return None, False, None
694 return entry.kind, entry.executable, None
695
696 def _file_size(self, entry, stat_value):
697 return entry.text_size
698
699 def walkdirs(self, prefix=""):
700 _directory = 'directory'
701 inv, top_id = self._path2inv_file_id(prefix)
702 if top_id is None:
703 pending = []
704 else:
705 pending = [(prefix, '', _directory, None, top_id, None)]
706 while pending:
707 dirblock = []
708 currentdir = pending.pop()
709 # 0 - relpath, 1- basename, 2- kind, 3- stat, id, v-kind
710 if currentdir[0]:
711 relroot = currentdir[0] + '/'
712 else:
713 relroot = ""
714 # FIXME: stash the node in pending
715 entry = inv[currentdir[4]]
716 for name, child in entry.sorted_children():
717 toppath = relroot + name
718 dirblock.append((toppath, name, child.kind, None,
719 child.file_id, child.kind
720 ))
721 yield (currentdir[0], entry.file_id), dirblock
722 # push the user specified dirs from dirblock
723 for dir in reversed(dirblock):
724 if dir[2] == _directory:
725 pending.append(dir)
726
727 def iter_files_bytes(self, desired_files):
728 """See Tree.iter_files_bytes.
729
730 This version is implemented on top of Repository.iter_files_bytes"""
731 repo_desired_files = [(f, self.get_file_revision(f), i)
732 for f, i in desired_files]
733 try:
734 for result in self._repository.iter_files_bytes(repo_desired_files):
735 yield result
736 except errors.RevisionNotPresent as e:
737 raise errors.NoSuchFile(e.file_id)
738
739 def annotate_iter(self, file_id,
740 default_revision=revision.CURRENT_REVISION):
741 """See Tree.annotate_iter"""
742 text_key = (file_id, self.get_file_revision(file_id))
743 annotator = self._repository.texts.get_annotator()
744 annotations = annotator.annotate_flat(text_key)
745 return [(key[-1], line) for key, line in annotations]
746
747 def __eq__(self, other):
748 if self is other:
749 return True
750 if isinstance(other, InventoryRevisionTree):
751 return (self.root_inventory == other.root_inventory)
752 return False
753
754 def __ne__(self, other):
755 return not (self == other)
756
757 def __hash__(self):
758 raise ValueError('not hashable')
759
760
761class InterCHKRevisionTree(InterTree):
762 """Fast path optimiser for RevisionTrees with CHK inventories."""
763
764 @staticmethod
765 def is_compatible(source, target):
766 if (isinstance(source, RevisionTree)
767 and isinstance(target, RevisionTree)):
768 try:
769 # Only CHK inventories have id_to_entry attribute
770 source.root_inventory.id_to_entry
771 target.root_inventory.id_to_entry
772 return True
773 except AttributeError:
774 pass
775 return False
776
777 def iter_changes(self, include_unchanged=False,
778 specific_files=None, pb=None, extra_trees=[],
779 require_versioned=True, want_unversioned=False):
780 lookup_trees = [self.source]
781 if extra_trees:
782 lookup_trees.extend(extra_trees)
783 # The ids of items we need to examine to insure delta consistency.
784 precise_file_ids = set()
785 discarded_changes = {}
786 if specific_files == []:
787 specific_file_ids = []
788 else:
789 specific_file_ids = self.target.paths2ids(specific_files,
790 lookup_trees, require_versioned=require_versioned)
791 # FIXME: It should be possible to delegate include_unchanged handling
792 # to CHKInventory.iter_changes and do a better job there -- vila
793 # 20090304
794 changed_file_ids = set()
795 # FIXME: nested tree support
796 for result in self.target.root_inventory.iter_changes(
797 self.source.root_inventory):
798 if specific_file_ids is not None:
799 file_id = result[0]
800 if file_id not in specific_file_ids:
801 # A change from the whole tree that we don't want to show yet.
802 # We may find that we need to show it for delta consistency, so
803 # stash it.
804 discarded_changes[result[0]] = result
805 continue
806 new_parent_id = result[4][1]
807 precise_file_ids.add(new_parent_id)
808 yield result
809 changed_file_ids.add(result[0])
810 if specific_file_ids is not None:
811 for result in self._handle_precise_ids(precise_file_ids,
812 changed_file_ids, discarded_changes=discarded_changes):
813 yield result
814 if include_unchanged:
815 # CHKMap avoid being O(tree), so we go to O(tree) only if
816 # required to.
817 # Now walk the whole inventory, excluding the already yielded
818 # file ids
819 # FIXME: Support nested trees
820 changed_file_ids = set(changed_file_ids)
821 for relpath, entry in self.target.root_inventory.iter_entries():
822 if (specific_file_ids is not None
823 and not entry.file_id in specific_file_ids):
824 continue
825 if not entry.file_id in changed_file_ids:
826 yield (entry.file_id,
827 (relpath, relpath), # Not renamed
828 False, # Not modified
829 (True, True), # Still versioned
830 (entry.parent_id, entry.parent_id),
831 (entry.name, entry.name),
832 (entry.kind, entry.kind),
833 (entry.executable, entry.executable))
834
835
836InterTree.register_optimiser(InterCHKRevisionTree)
0837
=== modified file 'breezy/memorytree.py'
--- breezy/memorytree.py 2017-06-10 00:17:06 +0000
+++ breezy/memorytree.py 2017-06-10 18:45:29 +0000
@@ -30,12 +30,13 @@
30 )30 )
31from .decorators import needs_read_lock31from .decorators import needs_read_lock
32from .inventory import Inventory32from .inventory import Inventory
33from .inventorytree import MutableInventoryTree
33from .osutils import sha_file34from .osutils import sha_file
34from .mutabletree import needs_tree_write_lock35from .mutabletree import needs_tree_write_lock
35from .transport.memory import MemoryTransport36from .transport.memory import MemoryTransport
3637
3738
38class MemoryTree(mutabletree.MutableInventoryTree):39class MemoryTree(MutableInventoryTree):
39 """A MemoryTree is a specialisation of MutableTree.40 """A MemoryTree is a specialisation of MutableTree.
4041
41 It maintains nearly no state outside of read_lock and write_lock42 It maintains nearly no state outside of read_lock and write_lock
4243
=== modified file 'breezy/mutabletree.py'
--- breezy/mutabletree.py 2017-06-05 20:48:31 +0000
+++ breezy/mutabletree.py 2017-06-10 18:45:29 +0000
@@ -21,25 +21,15 @@
2121
22from __future__ import absolute_import22from __future__ import absolute_import
2323
24from .lazy_import import lazy_import
25lazy_import(globals(), """
26import operator24import operator
27import os25import os
28import re26from . import (
29
30from breezy import (
31 add,
32 controldir,
33 errors,27 errors,
34 hooks,28 hooks,
35 inventory as _mod_inventory,
36 osutils,29 osutils,
37 revisiontree,
38 trace,30 trace,
39 transport as _mod_transport,
40 tree,31 tree,
41 )32 )
42""")
4333
44from .decorators import needs_read_lock, needs_write_lock34from .decorators import needs_read_lock, needs_write_lock
45from .sixish import (35from .sixish import (
@@ -396,110 +386,6 @@
396 raise NotImplementedError(self.smart_add)386 raise NotImplementedError(self.smart_add)
397387
398388
399class MutableInventoryTree(MutableTree, tree.InventoryTree):
400
401 @needs_tree_write_lock
402 def apply_inventory_delta(self, changes):
403 """Apply changes to the inventory as an atomic operation.
404
405 :param changes: An inventory delta to apply to the working tree's
406 inventory.
407 :return None:
408 :seealso Inventory.apply_delta: For details on the changes parameter.
409 """
410 self.flush()
411 inv = self.root_inventory
412 inv.apply_delta(changes)
413 self._write_inventory(inv)
414
415 def _fix_case_of_inventory_path(self, path):
416 """If our tree isn't case sensitive, return the canonical path"""
417 if not self.case_sensitive:
418 path = self.get_canonical_inventory_path(path)
419 return path
420
421 @needs_tree_write_lock
422 def smart_add(self, file_list, recurse=True, action=None, save=True):
423 """Version file_list, optionally recursing into directories.
424
425 This is designed more towards DWIM for humans than API clarity.
426 For the specific behaviour see the help for cmd_add().
427
428 :param file_list: List of zero or more paths. *NB: these are
429 interpreted relative to the process cwd, not relative to the
430 tree.* (Add and most other tree methods use tree-relative
431 paths.)
432 :param action: A reporter to be called with the inventory, parent_ie,
433 path and kind of the path being added. It may return a file_id if
434 a specific one should be used.
435 :param save: Save the inventory after completing the adds. If False
436 this provides dry-run functionality by doing the add and not saving
437 the inventory.
438 :return: A tuple - files_added, ignored_files. files_added is the count
439 of added files, and ignored_files is a dict mapping files that were
440 ignored to the rule that caused them to be ignored.
441 """
442 # Not all mutable trees can have conflicts
443 if getattr(self, 'conflicts', None) is not None:
444 # Collect all related files without checking whether they exist or
445 # are versioned. It's cheaper to do that once for all conflicts
446 # than trying to find the relevant conflict for each added file.
447 conflicts_related = set()
448 for c in self.conflicts():
449 conflicts_related.update(c.associated_filenames())
450 else:
451 conflicts_related = None
452 adder = _SmartAddHelper(self, action, conflicts_related)
453 adder.add(file_list, recurse=recurse)
454 if save:
455 invdelta = adder.get_inventory_delta()
456 self.apply_inventory_delta(invdelta)
457 return adder.added, adder.ignored
458
459 def update_basis_by_delta(self, new_revid, delta):
460 """Update the parents of this tree after a commit.
461
462 This gives the tree one parent, with revision id new_revid. The
463 inventory delta is applied to the current basis tree to generate the
464 inventory for the parent new_revid, and all other parent trees are
465 discarded.
466
467 All the changes in the delta should be changes synchronising the basis
468 tree with some or all of the working tree, with a change to a directory
469 requiring that its contents have been recursively included. That is,
470 this is not a general purpose tree modification routine, but a helper
471 for commit which is not required to handle situations that do not arise
472 outside of commit.
473
474 See the inventory developers documentation for the theory behind
475 inventory deltas.
476
477 :param new_revid: The new revision id for the trees parent.
478 :param delta: An inventory delta (see apply_inventory_delta) describing
479 the changes from the current left most parent revision to new_revid.
480 """
481 # if the tree is updated by a pull to the branch, as happens in
482 # WorkingTree2, when there was no separation between branch and tree,
483 # then just clear merges, efficiency is not a concern for now as this
484 # is legacy environments only, and they are slow regardless.
485 if self.last_revision() == new_revid:
486 self.set_parent_ids([new_revid])
487 return
488 # generic implementation based on Inventory manipulation. See
489 # WorkingTree classes for optimised versions for specific format trees.
490 basis = self.basis_tree()
491 basis.lock_read()
492 # TODO: Consider re-evaluating the need for this with CHKInventory
493 # we don't strictly need to mutate an inventory for this
494 # it only makes sense when apply_delta is cheaper than get_inventory()
495 inventory = _mod_inventory.mutable_inventory_from_tree(basis)
496 basis.unlock()
497 inventory.apply_delta(delta)
498 rev_tree = revisiontree.InventoryRevisionTree(self.branch.repository,
499 inventory, new_revid)
500 self.set_parent_trees([(new_revid, rev_tree)])
501
502
503class MutableTreeHooks(hooks.Hooks):389class MutableTreeHooks(hooks.Hooks):
504 """A dictionary mapping a hook name to a list of callables for mutabletree390 """A dictionary mapping a hook name to a list of callables for mutabletree
505 hooks.391 hooks.
@@ -548,254 +434,3 @@
548 def __init__(self, mutable_tree):434 def __init__(self, mutable_tree):
549 """Create the parameters for the post_commit hook."""435 """Create the parameters for the post_commit hook."""
550 self.mutable_tree = mutable_tree436 self.mutable_tree = mutable_tree
551
552
553class _SmartAddHelper(object):
554 """Helper for MutableTree.smart_add."""
555
556 def get_inventory_delta(self):
557 # GZ 2016-06-05: Returning view would probably be fine but currently
558 # Inventory.apply_delta is documented as requiring a list of changes.
559 return list(viewvalues(self._invdelta))
560
561 def _get_ie(self, inv_path):
562 """Retrieve the most up to date inventory entry for a path.
563
564 :param inv_path: Normalized inventory path
565 :return: Inventory entry (with possibly invalid .children for
566 directories)
567 """
568 entry = self._invdelta.get(inv_path)
569 if entry is not None:
570 return entry[3]
571 # Find a 'best fit' match if the filesystem is case-insensitive
572 inv_path = self.tree._fix_case_of_inventory_path(inv_path)
573 file_id = self.tree.path2id(inv_path)
574 if file_id is not None:
575 return self.tree.iter_entries_by_dir([file_id]).next()[1]
576 return None
577
578 def _convert_to_directory(self, this_ie, inv_path):
579 """Convert an entry to a directory.
580
581 :param this_ie: Inventory entry
582 :param inv_path: Normalized path for the inventory entry
583 :return: The new inventory entry
584 """
585 # Same as in _add_one below, if the inventory doesn't
586 # think this is a directory, update the inventory
587 this_ie = _mod_inventory.InventoryDirectory(
588 this_ie.file_id, this_ie.name, this_ie.parent_id)
589 self._invdelta[inv_path] = (inv_path, inv_path, this_ie.file_id,
590 this_ie)
591 return this_ie
592
593 def _add_one_and_parent(self, parent_ie, path, kind, inv_path):
594 """Add a new entry to the inventory and automatically add unversioned parents.
595
596 :param parent_ie: Parent inventory entry if known, or None. If
597 None, the parent is looked up by name and used if present, otherwise it
598 is recursively added.
599 :param path:
600 :param kind: Kind of new entry (file, directory, etc)
601 :param inv_path:
602 :return: Inventory entry for path and a list of paths which have been added.
603 """
604 # Nothing to do if path is already versioned.
605 # This is safe from infinite recursion because the tree root is
606 # always versioned.
607 inv_dirname = osutils.dirname(inv_path)
608 dirname, basename = osutils.split(path)
609 if parent_ie is None:
610 # slower but does not need parent_ie
611 this_ie = self._get_ie(inv_path)
612 if this_ie is not None:
613 return this_ie
614 # its really not there : add the parent
615 # note that the dirname use leads to some extra str copying etc but as
616 # there are a limited number of dirs we can be nested under, it should
617 # generally find it very fast and not recurse after that.
618 parent_ie = self._add_one_and_parent(None,
619 dirname, 'directory',
620 inv_dirname)
621 # if the parent exists, but isn't a directory, we have to do the
622 # kind change now -- really the inventory shouldn't pretend to know
623 # the kind of wt files, but it does.
624 if parent_ie.kind != 'directory':
625 # nb: this relies on someone else checking that the path we're using
626 # doesn't contain symlinks.
627 parent_ie = self._convert_to_directory(parent_ie, inv_dirname)
628 file_id = self.action(self.tree, parent_ie, path, kind)
629 entry = _mod_inventory.make_entry(kind, basename, parent_ie.file_id,
630 file_id=file_id)
631 self._invdelta[inv_path] = (None, inv_path, entry.file_id, entry)
632 self.added.append(inv_path)
633 return entry
634
635 def _gather_dirs_to_add(self, user_dirs):
636 # only walk the minimal parents needed: we have user_dirs to override
637 # ignores.
638 prev_dir = None
639
640 is_inside = osutils.is_inside_or_parent_of_any
641 for path in sorted(user_dirs):
642 if (prev_dir is None or not is_inside([prev_dir], path)):
643 inv_path, this_ie = user_dirs[path]
644 yield (path, inv_path, this_ie, None)
645 prev_dir = path
646
647 def __init__(self, tree, action, conflicts_related=None):
648 self.tree = tree
649 if action is None:
650 self.action = add.AddAction()
651 else:
652 self.action = action
653 self._invdelta = {}
654 self.added = []
655 self.ignored = {}
656 if conflicts_related is None:
657 self.conflicts_related = frozenset()
658 else:
659 self.conflicts_related = conflicts_related
660
661 def add(self, file_list, recurse=True):
662 from breezy.inventory import InventoryEntry
663 if not file_list:
664 # no paths supplied: add the entire tree.
665 # FIXME: this assumes we are running in a working tree subdir :-/
666 # -- vila 20100208
667 file_list = [u'.']
668
669 # expand any symlinks in the directory part, while leaving the
670 # filename alone
671 # only expanding if symlinks are supported avoids windows path bugs
672 if osutils.has_symlinks():
673 file_list = list(map(osutils.normalizepath, file_list))
674
675 user_dirs = {}
676 # validate user file paths and convert all paths to tree
677 # relative : it's cheaper to make a tree relative path an abspath
678 # than to convert an abspath to tree relative, and it's cheaper to
679 # perform the canonicalization in bulk.
680 for filepath in osutils.canonical_relpaths(self.tree.basedir, file_list):
681 # validate user parameters. Our recursive code avoids adding new
682 # files that need such validation
683 if self.tree.is_control_filename(filepath):
684 raise errors.ForbiddenControlFileError(filename=filepath)
685
686 abspath = self.tree.abspath(filepath)
687 kind = osutils.file_kind(abspath)
688 # ensure the named path is added, so that ignore rules in the later
689 # directory walk dont skip it.
690 # we dont have a parent ie known yet.: use the relatively slower
691 # inventory probing method
692 inv_path, _ = osutils.normalized_filename(filepath)
693 this_ie = self._get_ie(inv_path)
694 if this_ie is None:
695 this_ie = self._add_one_and_parent(None, filepath, kind, inv_path)
696 if kind == 'directory':
697 # schedule the dir for scanning
698 user_dirs[filepath] = (inv_path, this_ie)
699
700 if not recurse:
701 # no need to walk any directories at all.
702 return
703
704 things_to_add = list(self._gather_dirs_to_add(user_dirs))
705
706 illegalpath_re = re.compile(r'[\r\n]')
707 for directory, inv_path, this_ie, parent_ie in things_to_add:
708 # directory is tree-relative
709 abspath = self.tree.abspath(directory)
710
711 # get the contents of this directory.
712
713 # find the kind of the path being added, and save stat_value
714 # for reuse
715 stat_value = None
716 if this_ie is None:
717 stat_value = osutils.file_stat(abspath)
718 kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
719 else:
720 kind = this_ie.kind
721
722 # allow AddAction to skip this file
723 if self.action.skip_file(self.tree, abspath, kind, stat_value):
724 continue
725 if not InventoryEntry.versionable_kind(kind):
726 trace.warning("skipping %s (can't add file of kind '%s')",
727 abspath, kind)
728 continue
729 if illegalpath_re.search(directory):
730 trace.warning("skipping %r (contains \\n or \\r)" % abspath)
731 continue
732 if directory in self.conflicts_related:
733 # If the file looks like one generated for a conflict, don't
734 # add it.
735 trace.warning(
736 'skipping %s (generated to help resolve conflicts)',
737 abspath)
738 continue
739
740 if kind == 'directory' and directory != '':
741 try:
742 transport = _mod_transport.get_transport_from_path(abspath)
743 controldir.ControlDirFormat.find_format(transport)
744 sub_tree = True
745 except errors.NotBranchError:
746 sub_tree = False
747 except errors.UnsupportedFormatError:
748 sub_tree = True
749 else:
750 sub_tree = False
751
752 if this_ie is not None:
753 pass
754 elif sub_tree:
755 # XXX: This is wrong; people *might* reasonably be trying to
756 # add subtrees as subtrees. This should probably only be done
757 # in formats which can represent subtrees, and even then
758 # perhaps only when the user asked to add subtrees. At the
759 # moment you can add them specially through 'join --reference',
760 # which is perhaps reasonable: adding a new reference is a
761 # special operation and can have a special behaviour. mbp
762 # 20070306
763 trace.warning("skipping nested tree %r", abspath)
764 else:
765 this_ie = self._add_one_and_parent(parent_ie, directory, kind,
766 inv_path)
767
768 if kind == 'directory' and not sub_tree:
769 if this_ie.kind != 'directory':
770 this_ie = self._convert_to_directory(this_ie, inv_path)
771
772 for subf in sorted(os.listdir(abspath)):
773 inv_f, _ = osutils.normalized_filename(subf)
774 # here we could use TreeDirectory rather than
775 # string concatenation.
776 subp = osutils.pathjoin(directory, subf)
777 # TODO: is_control_filename is very slow. Make it faster.
778 # TreeDirectory.is_control_filename could also make this
779 # faster - its impossible for a non root dir to have a
780 # control file.
781 if self.tree.is_control_filename(subp):
782 trace.mutter("skip control directory %r", subp)
783 continue
784 sub_invp = osutils.pathjoin(inv_path, inv_f)
785 entry = self._invdelta.get(sub_invp)
786 if entry is not None:
787 sub_ie = entry[3]
788 else:
789 sub_ie = this_ie.children.get(inv_f)
790 if sub_ie is not None:
791 # recurse into this already versioned subdir.
792 things_to_add.append((subp, sub_invp, sub_ie, this_ie))
793 else:
794 # user selection overrides ignores
795 # ignore while selecting files - if we globbed in the
796 # outer loop we would ignore user files.
797 ignore_glob = self.tree.is_ignored(subp)
798 if ignore_glob is not None:
799 self.ignored.setdefault(ignore_glob, []).append(subp)
800 else:
801 things_to_add.append((subp, sub_invp, None, this_ie))
802437
=== modified file 'breezy/remote.py'
--- breezy/remote.py 2017-06-10 00:52:08 +0000
+++ breezy/remote.py 2017-06-10 18:45:29 +0000
@@ -52,6 +52,7 @@
52 )52 )
53from .i18n import gettext53from .i18n import gettext
54from .inventory import Inventory54from .inventory import Inventory
55from .inventorytree import InventoryRevisionTree
55from .lockable_files import LockableFiles56from .lockable_files import LockableFiles
56from .sixish import (57from .sixish import (
57 viewitems,58 viewitems,
@@ -60,7 +61,6 @@
60from .smart import client, vfs, repository as smart_repo61from .smart import client, vfs, repository as smart_repo
61from .smart.client import _SmartClient62from .smart.client import _SmartClient
62from .revision import NULL_REVISION63from .revision import NULL_REVISION
63from .revisiontree import InventoryRevisionTree
64from .repository import RepositoryWriteLockResult, _LazyListJoin64from .repository import RepositoryWriteLockResult, _LazyListJoin
65from .serializer import format_registry as serializer_format_registry65from .serializer import format_registry as serializer_format_registry
66from .trace import mutter, note, warning, log_exception_quietly66from .trace import mutter, note, warning, log_exception_quietly
6767
=== modified file 'breezy/revisiontree.py'
--- breezy/revisiontree.py 2017-05-25 01:35:55 +0000
+++ breezy/revisiontree.py 2017-06-10 18:45:29 +0000
@@ -95,241 +95,3 @@
95 self._rules_searcher = super(RevisionTree,95 self._rules_searcher = super(RevisionTree,
96 self)._get_rules_searcher(default_searcher)96 self)._get_rules_searcher(default_searcher)
97 return self._rules_searcher97 return self._rules_searcher
98
99
100class InventoryRevisionTree(RevisionTree,tree.InventoryTree):
101
102 def __init__(self, repository, inv, revision_id):
103 RevisionTree.__init__(self, repository, revision_id)
104 self._inventory = inv
105
106 def get_file_mtime(self, file_id, path=None):
107 inv, inv_file_id = self._unpack_file_id(file_id)
108 ie = inv[inv_file_id]
109 try:
110 revision = self._repository.get_revision(ie.revision)
111 except errors.NoSuchRevision:
112 raise errors.FileTimestampUnavailable(self.id2path(file_id))
113 return revision.timestamp
114
115 def get_file_size(self, file_id):
116 inv, inv_file_id = self._unpack_file_id(file_id)
117 return inv[inv_file_id].text_size
118
119 def get_file_sha1(self, file_id, path=None, stat_value=None):
120 inv, inv_file_id = self._unpack_file_id(file_id)
121 ie = inv[inv_file_id]
122 if ie.kind == "file":
123 return ie.text_sha1
124 return None
125
126 def get_file_revision(self, file_id, path=None):
127 inv, inv_file_id = self._unpack_file_id(file_id)
128 ie = inv[inv_file_id]
129 return ie.revision
130
131 def is_executable(self, file_id, path=None):
132 inv, inv_file_id = self._unpack_file_id(file_id)
133 ie = inv[inv_file_id]
134 if ie.kind != "file":
135 return False
136 return ie.executable
137
138 def has_filename(self, filename):
139 return bool(self.path2id(filename))
140
141 def list_files(self, include_root=False, from_dir=None, recursive=True):
142 # The only files returned by this are those from the version
143 if from_dir is None:
144 from_dir_id = None
145 inv = self.root_inventory
146 else:
147 inv, from_dir_id = self._path2inv_file_id(from_dir)
148 if from_dir_id is None:
149 # Directory not versioned
150 return
151 entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
152 if inv.root is not None and not include_root and from_dir is None:
153 # skip the root for compatability with the current apis.
154 next(entries)
155 for path, entry in entries:
156 yield path, 'V', entry.kind, entry.file_id, entry
157
158 def get_symlink_target(self, file_id, path=None):
159 inv, inv_file_id = self._unpack_file_id(file_id)
160 ie = inv[inv_file_id]
161 # Inventories store symlink targets in unicode
162 return ie.symlink_target
163
164 def get_reference_revision(self, file_id, path=None):
165 inv, inv_file_id = self._unpack_file_id(file_id)
166 return inv[inv_file_id].reference_revision
167
168 def get_root_id(self):
169 if self.root_inventory.root:
170 return self.root_inventory.root.file_id
171
172 def kind(self, file_id):
173 inv, inv_file_id = self._unpack_file_id(file_id)
174 return inv[inv_file_id].kind
175
176 def path_content_summary(self, path):
177 """See Tree.path_content_summary."""
178 inv, file_id = self._path2inv_file_id(path)
179 if file_id is None:
180 return ('missing', None, None, None)
181 entry = inv[file_id]
182 kind = entry.kind
183 if kind == 'file':
184 return (kind, entry.text_size, entry.executable, entry.text_sha1)
185 elif kind == 'symlink':
186 return (kind, None, None, entry.symlink_target)
187 else:
188 return (kind, None, None, None)
189
190 def _comparison_data(self, entry, path):
191 if entry is None:
192 return None, False, None
193 return entry.kind, entry.executable, None
194
195 def _file_size(self, entry, stat_value):
196 return entry.text_size
197
198 def walkdirs(self, prefix=""):
199 _directory = 'directory'
200 inv, top_id = self._path2inv_file_id(prefix)
201 if top_id is None:
202 pending = []
203 else:
204 pending = [(prefix, '', _directory, None, top_id, None)]
205 while pending:
206 dirblock = []
207 currentdir = pending.pop()
208 # 0 - relpath, 1- basename, 2- kind, 3- stat, id, v-kind
209 if currentdir[0]:
210 relroot = currentdir[0] + '/'
211 else:
212 relroot = ""
213 # FIXME: stash the node in pending
214 entry = inv[currentdir[4]]
215 for name, child in entry.sorted_children():
216 toppath = relroot + name
217 dirblock.append((toppath, name, child.kind, None,
218 child.file_id, child.kind
219 ))
220 yield (currentdir[0], entry.file_id), dirblock
221 # push the user specified dirs from dirblock
222 for dir in reversed(dirblock):
223 if dir[2] == _directory:
224 pending.append(dir)
225
226 def iter_files_bytes(self, desired_files):
227 """See Tree.iter_files_bytes.
228
229 This version is implemented on top of Repository.iter_files_bytes"""
230 repo_desired_files = [(f, self.get_file_revision(f), i)
231 for f, i in desired_files]
232 try:
233 for result in self._repository.iter_files_bytes(repo_desired_files):
234 yield result
235 except errors.RevisionNotPresent as e:
236 raise errors.NoSuchFile(e.file_id)
237
238 def annotate_iter(self, file_id,
239 default_revision=revision.CURRENT_REVISION):
240 """See Tree.annotate_iter"""
241 text_key = (file_id, self.get_file_revision(file_id))
242 annotator = self._repository.texts.get_annotator()
243 annotations = annotator.annotate_flat(text_key)
244 return [(key[-1], line) for key, line in annotations]
245
246 def __eq__(self, other):
247 if self is other:
248 return True
249 if isinstance(other, InventoryRevisionTree):
250 return (self.root_inventory == other.root_inventory)
251 return False
252
253 def __ne__(self, other):
254 return not (self == other)
255
256 def __hash__(self):
257 raise ValueError('not hashable')
258
259
260class InterCHKRevisionTree(tree.InterTree):
261 """Fast path optimiser for RevisionTrees with CHK inventories."""
262
263 @staticmethod
264 def is_compatible(source, target):
265 if (isinstance(source, RevisionTree)
266 and isinstance(target, RevisionTree)):
267 try:
268 # Only CHK inventories have id_to_entry attribute
269 source.root_inventory.id_to_entry
270 target.root_inventory.id_to_entry
271 return True
272 except AttributeError:
273 pass
274 return False
275
276 def iter_changes(self, include_unchanged=False,
277 specific_files=None, pb=None, extra_trees=[],
278 require_versioned=True, want_unversioned=False):
279 lookup_trees = [self.source]
280 if extra_trees:
281 lookup_trees.extend(extra_trees)
282 # The ids of items we need to examine to insure delta consistency.
283 precise_file_ids = set()
284 discarded_changes = {}
285 if specific_files == []:
286 specific_file_ids = []
287 else:
288 specific_file_ids = self.target.paths2ids(specific_files,
289 lookup_trees, require_versioned=require_versioned)
290 # FIXME: It should be possible to delegate include_unchanged handling
291 # to CHKInventory.iter_changes and do a better job there -- vila
292 # 20090304
293 changed_file_ids = set()
294 # FIXME: nested tree support
295 for result in self.target.root_inventory.iter_changes(
296 self.source.root_inventory):
297 if specific_file_ids is not None:
298 file_id = result[0]
299 if file_id not in specific_file_ids:
300 # A change from the whole tree that we don't want to show yet.
301 # We may find that we need to show it for delta consistency, so
302 # stash it.
303 discarded_changes[result[0]] = result
304 continue
305 new_parent_id = result[4][1]
306 precise_file_ids.add(new_parent_id)
307 yield result
308 changed_file_ids.add(result[0])
309 if specific_file_ids is not None:
310 for result in self._handle_precise_ids(precise_file_ids,
311 changed_file_ids, discarded_changes=discarded_changes):
312 yield result
313 if include_unchanged:
314 # CHKMap avoid being O(tree), so we go to O(tree) only if
315 # required to.
316 # Now walk the whole inventory, excluding the already yielded
317 # file ids
318 # FIXME: Support nested trees
319 changed_file_ids = set(changed_file_ids)
320 for relpath, entry in self.target.root_inventory.iter_entries():
321 if (specific_file_ids is not None
322 and not entry.file_id in specific_file_ids):
323 continue
324 if not entry.file_id in changed_file_ids:
325 yield (entry.file_id,
326 (relpath, relpath), # Not renamed
327 False, # Not modified
328 (True, True), # Still versioned
329 (entry.parent_id, entry.parent_id),
330 (entry.name, entry.name),
331 (entry.kind, entry.kind),
332 (entry.executable, entry.executable))
333
334
335tree.InterTree.register_optimiser(InterCHKRevisionTree)
33698
=== modified file 'breezy/tests/per_intertree/__init__.py'
--- breezy/tests/per_intertree/__init__.py 2017-06-10 00:52:37 +0000
+++ breezy/tests/per_intertree/__init__.py 2017-06-10 18:45:29 +0000
@@ -24,6 +24,7 @@
2424
25import breezy25import breezy
26from breezy import (26from breezy import (
27 inventorytree,
27 revisiontree,28 revisiontree,
28 tests,29 tests,
29 )30 )
@@ -130,7 +131,7 @@
130 default_tree_format, default_tree_format,131 default_tree_format, default_tree_format,
131 return_provided_trees)]132 return_provided_trees)]
132 for optimiser in InterTree._optimisers:133 for optimiser in InterTree._optimisers:
133 if optimiser is revisiontree.InterCHKRevisionTree:134 if optimiser is inventorytree.InterCHKRevisionTree:
134 # XXX: we shouldn't use an Intertree object to detect inventories135 # XXX: we shouldn't use an Intertree object to detect inventories
135 # -- vila 20090311136 # -- vila 20090311
136 chk_tree_format = WorkingTreeFormat4()137 chk_tree_format = WorkingTreeFormat4()
137138
=== modified file 'breezy/tests/per_tree/test_inv.py'
--- breezy/tests/per_tree/test_inv.py 2017-05-22 00:56:52 +0000
+++ breezy/tests/per_tree/test_inv.py 2017-06-10 18:45:29 +0000
@@ -23,9 +23,9 @@
23from breezy.tests import (23from breezy.tests import (
24 per_tree,24 per_tree,
25 )25 )
26from breezy.inventorytree import InventoryTree
26from breezy.mutabletree import MutableTree27from breezy.mutabletree import MutableTree
27from breezy.tests import TestSkipped28from breezy.tests import TestSkipped
28from breezy.tree import InventoryTree
29from breezy.transform import _PreviewTree29from breezy.transform import _PreviewTree
30from breezy.uncommit import uncommit30from breezy.uncommit import uncommit
31from breezy.tests import (31from breezy.tests import (
3232
=== modified file 'breezy/tests/per_workingtree/test_parents.py'
--- breezy/tests/per_workingtree/test_parents.py 2017-06-10 00:52:08 +0000
+++ breezy/tests/per_workingtree/test_parents.py 2017-06-10 18:45:29 +0000
@@ -28,7 +28,7 @@
28 InventoryDirectory,28 InventoryDirectory,
29 InventoryLink,29 InventoryLink,
30 )30 )
31from ...revisiontree import InventoryRevisionTree31from ...inventorytree import InventoryRevisionTree
32from ...sixish import (32from ...sixish import (
33 BytesIO,33 BytesIO,
34 )34 )
3535
=== modified file 'breezy/tests/test_dirstate.py'
--- breezy/tests/test_dirstate.py 2017-06-10 00:52:37 +0000
+++ breezy/tests/test_dirstate.py 2017-06-10 18:45:29 +0000
@@ -24,6 +24,7 @@
24 dirstate,24 dirstate,
25 errors,25 errors,
26 inventory,26 inventory,
27 inventorytree,
27 memorytree,28 memorytree,
28 osutils,29 osutils,
29 revision as _mod_revision,30 revision as _mod_revision,
@@ -2492,7 +2493,7 @@
2492 dir_ids[''] = file_id2493 dir_ids[''] = file_id
2493 continue2494 continue
2494 inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))2495 inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
2495 return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)2496 return inventorytree.InventoryRevisionTree(_Repo(), inv, rev_id)
24962497
2497 def create_empty_dirstate(self):2498 def create_empty_dirstate(self):
2498 fd, path = tempfile.mkstemp(prefix='bzr-dirstate')2499 fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
24992500
=== modified file 'breezy/transform.py'
--- breezy/transform.py 2017-06-05 20:48:31 +0000
+++ breezy/transform.py 2017-06-10 18:45:29 +0000
@@ -24,6 +24,7 @@
24from . import (24from . import (
25 config as _mod_config,25 config as _mod_config,
26 errors,26 errors,
27 inventorytree,
27 lazy_import,28 lazy_import,
28 registry,29 registry,
29 trace,30 trace,
@@ -1971,7 +1972,7 @@
1971 raise NotImplementedError(self.new_orphan)1972 raise NotImplementedError(self.new_orphan)
19721973
19731974
1974class _PreviewTree(tree.InventoryTree):1975class _PreviewTree(inventorytree.InventoryTree):
1975 """Partial implementation of Tree to support show_diff_trees"""1976 """Partial implementation of Tree to support show_diff_trees"""
19761977
1977 def __init__(self, transform):1978 def __init__(self, transform):
19781979
=== modified file 'breezy/tree.py'
--- breezy/tree.py 2017-06-05 20:48:31 +0000
+++ breezy/tree.py 2017-06-10 18:45:29 +0000
@@ -684,198 +684,6 @@
684 return searcher684 return searcher
685685
686686
687class InventoryTree(Tree):
688 """A tree that relies on an inventory for its metadata.
689
690 Trees contain an `Inventory` object, and also know how to retrieve
691 file texts mentioned in the inventory, either from a working
692 directory or from a store.
693
694 It is possible for trees to contain files that are not described
695 in their inventory or vice versa; for this use `filenames()`.
696
697 Subclasses should set the _inventory attribute, which is considered
698 private to external API users.
699 """
700
701 def get_canonical_inventory_paths(self, paths):
702 """Like get_canonical_inventory_path() but works on multiple items.
703
704 :param paths: A sequence of paths relative to the root of the tree.
705 :return: A list of paths, with each item the corresponding input path
706 adjusted to account for existing elements that match case
707 insensitively.
708 """
709 return list(self._yield_canonical_inventory_paths(paths))
710
711 def get_canonical_inventory_path(self, path):
712 """Returns the first inventory item that case-insensitively matches path.
713
714 If a path matches exactly, it is returned. If no path matches exactly
715 but more than one path matches case-insensitively, it is implementation
716 defined which is returned.
717
718 If no path matches case-insensitively, the input path is returned, but
719 with as many path entries that do exist changed to their canonical
720 form.
721
722 If you need to resolve many names from the same tree, you should
723 use get_canonical_inventory_paths() to avoid O(N) behaviour.
724
725 :param path: A paths relative to the root of the tree.
726 :return: The input path adjusted to account for existing elements
727 that match case insensitively.
728 """
729 return next(self._yield_canonical_inventory_paths([path]))
730
731 def _yield_canonical_inventory_paths(self, paths):
732 for path in paths:
733 # First, if the path as specified exists exactly, just use it.
734 if self.path2id(path) is not None:
735 yield path
736 continue
737 # go walkin...
738 cur_id = self.get_root_id()
739 cur_path = ''
740 bit_iter = iter(path.split("/"))
741 for elt in bit_iter:
742 lelt = elt.lower()
743 new_path = None
744 for child in self.iter_children(cur_id):
745 try:
746 # XXX: it seem like if the child is known to be in the
747 # tree, we shouldn't need to go from its id back to
748 # its path -- mbp 2010-02-11
749 #
750 # XXX: it seems like we could be more efficient
751 # by just directly looking up the original name and
752 # only then searching all children; also by not
753 # chopping paths so much. -- mbp 2010-02-11
754 child_base = os.path.basename(self.id2path(child))
755 if (child_base == elt):
756 # if we found an exact match, we can stop now; if
757 # we found an approximate match we need to keep
758 # searching because there might be an exact match
759 # later.
760 cur_id = child
761 new_path = osutils.pathjoin(cur_path, child_base)
762 break
763 elif child_base.lower() == lelt:
764 cur_id = child
765 new_path = osutils.pathjoin(cur_path, child_base)
766 except errors.NoSuchId:
767 # before a change is committed we can see this error...
768 continue
769 if new_path:
770 cur_path = new_path
771 else:
772 # got to the end of this directory and no entries matched.
773 # Return what matched so far, plus the rest as specified.
774 cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
775 break
776 yield cur_path
777 # all done.
778
779 def _get_root_inventory(self):
780 return self._inventory
781
782 root_inventory = property(_get_root_inventory,
783 doc="Root inventory of this tree")
784
785 def _unpack_file_id(self, file_id):
786 """Find the inventory and inventory file id for a tree file id.
787
788 :param file_id: The tree file id, as bytestring or tuple
789 :return: Inventory and inventory file id
790 """
791 if isinstance(file_id, tuple):
792 if len(file_id) != 1:
793 raise ValueError("nested trees not yet supported: %r" % file_id)
794 file_id = file_id[0]
795 return self.root_inventory, file_id
796
797 @needs_read_lock
798 def path2id(self, path):
799 """Return the id for path in this tree."""
800 return self._path2inv_file_id(path)[1]
801
802 def _path2inv_file_id(self, path):
803 """Lookup a inventory and inventory file id by path.
804
805 :param path: Path to look up
806 :return: tuple with inventory and inventory file id
807 """
808 # FIXME: Support nested trees
809 return self.root_inventory, self.root_inventory.path2id(path)
810
811 def id2path(self, file_id):
812 """Return the path for a file id.
813
814 :raises NoSuchId:
815 """
816 inventory, file_id = self._unpack_file_id(file_id)
817 return inventory.id2path(file_id)
818
819 def has_id(self, file_id):
820 inventory, file_id = self._unpack_file_id(file_id)
821 return inventory.has_id(file_id)
822
823 def has_or_had_id(self, file_id):
824 inventory, file_id = self._unpack_file_id(file_id)
825 return inventory.has_id(file_id)
826
827 def all_file_ids(self):
828 return {entry.file_id for path, entry in self.iter_entries_by_dir()}
829
830 def filter_unversioned_files(self, paths):
831 """Filter out paths that are versioned.
832
833 :return: set of paths.
834 """
835 # NB: we specifically *don't* call self.has_filename, because for
836 # WorkingTrees that can indicate files that exist on disk but that
837 # are not versioned.
838 return set((p for p in paths if self.path2id(p) is None))
839
840 @needs_read_lock
841 def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
842 """Walk the tree in 'by_dir' order.
843
844 This will yield each entry in the tree as a (path, entry) tuple.
845 The order that they are yielded is:
846
847 See Tree.iter_entries_by_dir for details.
848
849 :param yield_parents: If True, yield the parents from the root leading
850 down to specific_file_ids that have been requested. This has no
851 impact if specific_file_ids is None.
852 """
853 if specific_file_ids is None:
854 inventory_file_ids = None
855 else:
856 inventory_file_ids = []
857 for tree_file_id in specific_file_ids:
858 inventory, inv_file_id = self._unpack_file_id(tree_file_id)
859 if not inventory is self.root_inventory: # for now
860 raise AssertionError("%r != %r" % (
861 inventory, self.root_inventory))
862 inventory_file_ids.append(inv_file_id)
863 # FIXME: Handle nested trees
864 return self.root_inventory.iter_entries_by_dir(
865 specific_file_ids=inventory_file_ids, yield_parents=yield_parents)
866
867 @needs_read_lock
868 def iter_child_entries(self, file_id, path=None):
869 inv, inv_file_id = self._unpack_file_id(file_id)
870 return iter(viewvalues(inv[inv_file_id].children))
871
872 def iter_children(self, file_id, path=None):
873 """See Tree.iter_children."""
874 entry = self.iter_entries_by_dir([file_id]).next()[1]
875 for child in viewvalues(getattr(entry, 'children', {})):
876 yield child.file_id
877
878
879def find_ids_across_trees(filenames, trees, require_versioned=True):687def find_ids_across_trees(filenames, trees, require_versioned=True):
880 """Find the ids corresponding to specified filenames.688 """Find the ids corresponding to specified filenames.
881689
882690
=== modified file 'breezy/vf_repository.py'
--- breezy/vf_repository.py 2017-06-10 00:52:08 +0000
+++ breezy/vf_repository.py 2017-06-10 18:45:29 +0000
@@ -44,7 +44,7 @@
44 )44 )
4545
46from breezy.recordcounter import RecordCounter46from breezy.recordcounter import RecordCounter
47from breezy.revisiontree import InventoryRevisionTree47from breezy.inventorytree import InventoryRevisionTree
48from breezy.testament import Testament48from breezy.testament import Testament
49from breezy.i18n import gettext49from breezy.i18n import gettext
50""")50""")
5151
=== modified file 'breezy/workingtree.py'
--- breezy/workingtree.py 2017-06-10 00:52:08 +0000
+++ breezy/workingtree.py 2017-06-10 18:45:29 +0000
@@ -18,7 +18,7 @@
1818
19A WorkingTree represents the editable working copy of a branch.19A WorkingTree represents the editable working copy of a branch.
20Operations which represent the WorkingTree are also done here,20Operations which represent the WorkingTree are also done here,
21such as renaming or adding files. 21such as renaming or adding files.
2222
23At the moment every WorkingTree has its own branch. Remote23At the moment every WorkingTree has its own branch. Remote
24WorkingTrees aren't supported.24WorkingTrees aren't supported.
2525
=== modified file 'breezy/workingtree_4.py'
--- breezy/workingtree_4.py 2017-06-10 12:56:18 +0000
+++ breezy/workingtree_4.py 2017-06-10 18:45:29 +0000
@@ -55,6 +55,10 @@
5555
56from .decorators import needs_read_lock, needs_write_lock56from .decorators import needs_read_lock, needs_write_lock
57from .inventory import Inventory, ROOT_ID, entry_factory57from .inventory import Inventory, ROOT_ID, entry_factory
58from .inventorytree import (
59 InventoryTree,
60 InventoryRevisionTree,
61 )
58from .lock import LogicalLockResult62from .lock import LogicalLockResult
59from .lockable_files import LockableFiles63from .lockable_files import LockableFiles
60from .lockdir import LockDir64from .lockdir import LockDir
@@ -76,7 +80,6 @@
76from .transport.local import LocalTransport80from .transport.local import LocalTransport
77from .tree import (81from .tree import (
78 InterTree,82 InterTree,
79 InventoryTree,
80 )83 )
81from .workingtree import (84from .workingtree import (
82 WorkingTree,85 WorkingTree,
@@ -1132,8 +1135,7 @@
1132 if (len(real_trees) == 11135 if (len(real_trees) == 1
1133 and not ghosts1136 and not ghosts
1134 and self.branch.repository._format.fast_deltas1137 and self.branch.repository._format.fast_deltas
1135 and isinstance(real_trees[0][1],1138 and isinstance(real_trees[0][1], InventoryRevisionTree)
1136 revisiontree.InventoryRevisionTree)
1137 and self.get_parent_ids()):1139 and self.get_parent_ids()):
1138 rev_id, rev_tree = real_trees[0]1140 rev_id, rev_tree = real_trees[0]
1139 basis_id = self.get_parent_ids()[0]1141 basis_id = self.get_parent_ids()[0]

Subscribers

People subscribed via source and target branches