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