Merge lp:~jelmer/brz/move-invtree into lp:brz
- move-invtree
- Merge into trunk
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 |
Related bugs: |
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.
Description of the change
Move inventory-related tree implementations to breezy.
To post a comment you must log in.
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] |
Okay, change seems fine. We're going to have to draw a picture of where everything is...