Merge lp:~jelmer/brz/split-multiwalker into lp:brz
- split-multiwalker
- Merge into trunk
Proposed by
Jelmer Vernooij
Status: | Merged |
---|---|
Merged at revision: | 7314 |
Proposed branch: | lp:~jelmer/brz/split-multiwalker |
Merge into: | lp:brz |
Diff against target: |
1160 lines (+577/-517) 6 files modified
breezy/merge.py (+3/-4) breezy/multiwalker.py (+266/-0) breezy/tests/__init__.py (+1/-0) breezy/tests/test_multiwalker.py (+305/-0) breezy/tests/test_tree.py (+0/-279) breezy/tree.py (+2/-234) |
To merge this branch: | bzr merge lp:~jelmer/brz/split-multiwalker |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Breezy developers | Pending | ||
Review via email: mp+367316@code.launchpad.net |
Commit message
Description of the change
Move MultiWalker out into its own module.
It's only used by LCA.
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/merge.py' |
2 | --- breezy/merge.py 2019-02-14 21:45:23 +0000 |
3 | +++ breezy/merge.py 2019-05-12 10:12:09 +0000 |
4 | @@ -904,17 +904,16 @@ |
5 | else: |
6 | interesting_files = None |
7 | result = [] |
8 | - walker = _mod_tree.MultiWalker(self.other_tree, self._lca_trees) |
9 | + from .multiwalker import MultiWalker |
10 | + walker = MultiWalker(self.other_tree, self._lca_trees) |
11 | |
12 | base_inventory = self.base_tree.root_inventory |
13 | this_inventory = self.this_tree.root_inventory |
14 | - for path, file_id, other_ie, lca_values in walker.iter_all(): |
15 | + for other_path, file_id, other_ie, lca_values in walker.iter_all(): |
16 | # Is this modified at all from any of the other trees? |
17 | if other_ie is None: |
18 | other_ie = _none_entry |
19 | other_path = None |
20 | - else: |
21 | - other_path = self.other_tree.id2path(file_id) |
22 | if interesting_files is not None and other_path not in interesting_files: |
23 | continue |
24 | |
25 | |
26 | === added file 'breezy/multiwalker.py' |
27 | --- breezy/multiwalker.py 1970-01-01 00:00:00 +0000 |
28 | +++ breezy/multiwalker.py 2019-05-12 10:12:09 +0000 |
29 | @@ -0,0 +1,266 @@ |
30 | +# Copyright (C) 2005-2011 Canonical Ltd |
31 | +# |
32 | +# This program is free software; you can redistribute it and/or modify |
33 | +# it under the terms of the GNU General Public License as published by |
34 | +# the Free Software Foundation; either version 2 of the License, or |
35 | +# (at your option) any later version. |
36 | +# |
37 | +# This program is distributed in the hope that it will be useful, |
38 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
39 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
40 | +# GNU General Public License for more details. |
41 | +# |
42 | +# You should have received a copy of the GNU General Public License |
43 | +# along with this program; if not, write to the Free Software |
44 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
45 | + |
46 | +"""Walk multiple trees simultaneously. |
47 | +""" |
48 | + |
49 | +from __future__ import absolute_import |
50 | + |
51 | +from . import ( |
52 | + errors, |
53 | + osutils, |
54 | + ) |
55 | + |
56 | +from .sixish import ( |
57 | + text_type, |
58 | + viewvalues, |
59 | + ) |
60 | + |
61 | + |
62 | +class MultiWalker(object): |
63 | + """Walk multiple trees simultaneously, getting combined results.""" |
64 | + |
65 | + # Note: This could be written to not assume you can do out-of-order |
66 | + # lookups. Instead any nodes that don't match in all trees could be |
67 | + # marked as 'deferred', and then returned in the final cleanup loop. |
68 | + # For now, I think it is "nicer" to return things as close to the |
69 | + # "master_tree" order as we can. |
70 | + |
71 | + def __init__(self, master_tree, other_trees): |
72 | + """Create a new MultiWalker. |
73 | + |
74 | + All trees being walked must implement "iter_entries_by_dir()", such |
75 | + that they yield (path, object) tuples, where that object will have a |
76 | + '.file_id' member, that can be used to check equality. |
77 | + |
78 | + :param master_tree: All trees will be 'slaved' to the master_tree such |
79 | + that nodes in master_tree will be used as 'first-pass' sync points. |
80 | + Any nodes that aren't in master_tree will be merged in a second |
81 | + pass. |
82 | + :param other_trees: A list of other trees to walk simultaneously. |
83 | + """ |
84 | + self._master_tree = master_tree |
85 | + self._other_trees = other_trees |
86 | + |
87 | + # Keep track of any nodes that were properly processed just out of |
88 | + # order, that way we don't return them at the end, we don't have to |
89 | + # track *all* processed file_ids, just the out-of-order ones |
90 | + self._out_of_order_processed = set() |
91 | + |
92 | + @staticmethod |
93 | + def _step_one(iterator): |
94 | + """Step an iter_entries_by_dir iterator. |
95 | + |
96 | + :return: (has_more, path, ie) |
97 | + If has_more is False, path and ie will be None. |
98 | + """ |
99 | + try: |
100 | + path, ie = next(iterator) |
101 | + except StopIteration: |
102 | + return False, None, None |
103 | + else: |
104 | + return True, path, ie |
105 | + |
106 | + @staticmethod |
107 | + def _lt_path_by_dirblock(path1, path2): |
108 | + """Compare two paths based on what directory they are in. |
109 | + |
110 | + This generates a sort order, such that all children of a directory are |
111 | + sorted together, and grandchildren are in the same order as the |
112 | + children appear. But all grandchildren come after all children. |
113 | + |
114 | + :param path1: first path |
115 | + :param path2: the second path |
116 | + :return: negative number if ``path1`` comes first, |
117 | + 0 if paths are equal |
118 | + and a positive number if ``path2`` sorts first |
119 | + """ |
120 | + # Shortcut this special case |
121 | + if path1 == path2: |
122 | + return False |
123 | + # This is stolen from _dirstate_helpers_py.py, only switching it to |
124 | + # Unicode objects. Consider using encode_utf8() and then using the |
125 | + # optimized versions, or maybe writing optimized unicode versions. |
126 | + if not isinstance(path1, text_type): |
127 | + raise TypeError("'path1' must be a unicode string, not %s: %r" |
128 | + % (type(path1), path1)) |
129 | + if not isinstance(path2, text_type): |
130 | + raise TypeError("'path2' must be a unicode string, not %s: %r" |
131 | + % (type(path2), path2)) |
132 | + return (MultiWalker._path_to_key(path1) < |
133 | + MultiWalker._path_to_key(path2)) |
134 | + |
135 | + @staticmethod |
136 | + def _path_to_key(path): |
137 | + dirname, basename = osutils.split(path) |
138 | + return (dirname.split(u'/'), basename) |
139 | + |
140 | + def _lookup_by_file_id(self, extra_entries, other_tree, file_id): |
141 | + """Lookup an inventory entry by file_id. |
142 | + |
143 | + This is called when an entry is missing in the normal order. |
144 | + Generally this is because a file was either renamed, or it was |
145 | + deleted/added. If the entry was found in the inventory and not in |
146 | + extra_entries, it will be added to self._out_of_order_processed |
147 | + |
148 | + :param extra_entries: A dictionary of {file_id: (path, ie)}. This |
149 | + should be filled with entries that were found before they were |
150 | + used. If file_id is present, it will be removed from the |
151 | + dictionary. |
152 | + :param other_tree: The Tree to search, in case we didn't find the entry |
153 | + yet. |
154 | + :param file_id: The file_id to look for |
155 | + :return: (path, ie) if found or (None, None) if not present. |
156 | + """ |
157 | + if file_id in extra_entries: |
158 | + return extra_entries.pop(file_id) |
159 | + # TODO: Is id2path better as the first call, or is |
160 | + # inventory[file_id] better as a first check? |
161 | + try: |
162 | + cur_path = other_tree.id2path(file_id) |
163 | + except errors.NoSuchId: |
164 | + cur_path = None |
165 | + if cur_path is None: |
166 | + return (None, None) |
167 | + else: |
168 | + self._out_of_order_processed.add(file_id) |
169 | + cur_ie = other_tree.root_inventory.get_entry(file_id) |
170 | + return (cur_path, cur_ie) |
171 | + |
172 | + def iter_all(self): |
173 | + """Match up the values in the different trees.""" |
174 | + for result in self._walk_master_tree(): |
175 | + yield result |
176 | + self._finish_others() |
177 | + for result in self._walk_others(): |
178 | + yield result |
179 | + |
180 | + def _walk_master_tree(self): |
181 | + """First pass, walk all trees in lock-step. |
182 | + |
183 | + When we are done, all nodes in the master_tree will have been |
184 | + processed. _other_walkers, _other_entries, and _others_extra will be |
185 | + set on 'self' for future processing. |
186 | + """ |
187 | + # This iterator has the most "inlining" done, because it tends to touch |
188 | + # every file in the tree, while the others only hit nodes that don't |
189 | + # match. |
190 | + master_iterator = self._master_tree.iter_entries_by_dir() |
191 | + |
192 | + other_walkers = [other.iter_entries_by_dir() |
193 | + for other in self._other_trees] |
194 | + other_entries = [self._step_one(walker) for walker in other_walkers] |
195 | + # Track extra nodes in the other trees |
196 | + others_extra = [{} for _ in range(len(self._other_trees))] |
197 | + |
198 | + master_has_more = True |
199 | + step_one = self._step_one |
200 | + lookup_by_file_id = self._lookup_by_file_id |
201 | + out_of_order_processed = self._out_of_order_processed |
202 | + |
203 | + while master_has_more: |
204 | + (master_has_more, path, master_ie) = step_one(master_iterator) |
205 | + if not master_has_more: |
206 | + break |
207 | + |
208 | + file_id = master_ie.file_id |
209 | + other_values = [] |
210 | + other_values_append = other_values.append |
211 | + next_other_entries = [] |
212 | + next_other_entries_append = next_other_entries.append |
213 | + for idx, (other_has_more, other_path, other_ie) in enumerate(other_entries): |
214 | + if not other_has_more: |
215 | + other_values_append(lookup_by_file_id( |
216 | + others_extra[idx], self._other_trees[idx], file_id)) |
217 | + next_other_entries_append((False, None, None)) |
218 | + elif file_id == other_ie.file_id: |
219 | + # This is the critical code path, as most of the entries |
220 | + # should match between most trees. |
221 | + other_values_append((other_path, other_ie)) |
222 | + next_other_entries_append(step_one(other_walkers[idx])) |
223 | + else: |
224 | + # This walker did not match, step it until it either |
225 | + # matches, or we know we are past the current walker. |
226 | + other_walker = other_walkers[idx] |
227 | + other_extra = others_extra[idx] |
228 | + while (other_has_more and |
229 | + self._lt_path_by_dirblock(other_path, path)): |
230 | + other_file_id = other_ie.file_id |
231 | + if other_file_id not in out_of_order_processed: |
232 | + other_extra[other_file_id] = (other_path, other_ie) |
233 | + other_has_more, other_path, other_ie = \ |
234 | + step_one(other_walker) |
235 | + if other_has_more and other_ie.file_id == file_id: |
236 | + # We ended up walking to this point, match and step |
237 | + # again |
238 | + other_values_append((other_path, other_ie)) |
239 | + other_has_more, other_path, other_ie = \ |
240 | + step_one(other_walker) |
241 | + else: |
242 | + # This record isn't in the normal order, see if it |
243 | + # exists at all. |
244 | + other_values_append(lookup_by_file_id( |
245 | + other_extra, self._other_trees[idx], file_id)) |
246 | + next_other_entries_append((other_has_more, other_path, |
247 | + other_ie)) |
248 | + other_entries = next_other_entries |
249 | + |
250 | + # We've matched all the walkers, yield this datapoint |
251 | + yield path, file_id, master_ie, other_values |
252 | + self._other_walkers = other_walkers |
253 | + self._other_entries = other_entries |
254 | + self._others_extra = others_extra |
255 | + |
256 | + def _finish_others(self): |
257 | + """Finish walking the other iterators, so we get all entries.""" |
258 | + for idx, info in enumerate(self._other_entries): |
259 | + other_extra = self._others_extra[idx] |
260 | + (other_has_more, other_path, other_ie) = info |
261 | + while other_has_more: |
262 | + other_file_id = other_ie.file_id |
263 | + if other_file_id not in self._out_of_order_processed: |
264 | + other_extra[other_file_id] = (other_path, other_ie) |
265 | + other_has_more, other_path, other_ie = \ |
266 | + self._step_one(self._other_walkers[idx]) |
267 | + del self._other_entries |
268 | + |
269 | + def _walk_others(self): |
270 | + """Finish up by walking all the 'deferred' nodes.""" |
271 | + # TODO: One alternative would be to grab all possible unprocessed |
272 | + # file_ids, and then sort by path, and then yield them. That |
273 | + # might ensure better ordering, in case a caller strictly |
274 | + # requires parents before children. |
275 | + for idx, other_extra in enumerate(self._others_extra): |
276 | + others = sorted(viewvalues(other_extra), |
277 | + key=lambda x: self._path_to_key(x[0])) |
278 | + for other_path, other_ie in others: |
279 | + file_id = other_ie.file_id |
280 | + # We don't need to check out_of_order_processed here, because |
281 | + # the lookup_by_file_id will be removing anything processed |
282 | + # from the extras cache |
283 | + other_extra.pop(file_id) |
284 | + other_values = [(None, None)] * idx |
285 | + other_values.append((other_path, other_ie)) |
286 | + for alt_idx, alt_extra in enumerate(self._others_extra[idx + 1:]): |
287 | + alt_idx = alt_idx + idx + 1 |
288 | + alt_extra = self._others_extra[alt_idx] |
289 | + alt_tree = self._other_trees[alt_idx] |
290 | + other_values.append(self._lookup_by_file_id( |
291 | + alt_extra, alt_tree, file_id)) |
292 | + yield other_path, file_id, None, other_values |
293 | + |
294 | + |
295 | + |
296 | |
297 | === modified file 'breezy/tests/__init__.py' |
298 | --- breezy/tests/__init__.py 2019-02-03 23:43:20 +0000 |
299 | +++ breezy/tests/__init__.py 2019-05-12 10:12:09 +0000 |
300 | @@ -4146,6 +4146,7 @@ |
301 | 'breezy.tests.test_missing', |
302 | 'breezy.tests.test_msgeditor', |
303 | 'breezy.tests.test_multiparent', |
304 | + 'breezy.tests.test_multiwalker', |
305 | 'breezy.tests.test_mutabletree', |
306 | 'breezy.tests.test_nonascii', |
307 | 'breezy.tests.test_options', |
308 | |
309 | === added file 'breezy/tests/test_multiwalker.py' |
310 | --- breezy/tests/test_multiwalker.py 1970-01-01 00:00:00 +0000 |
311 | +++ breezy/tests/test_multiwalker.py 2019-05-12 10:12:09 +0000 |
312 | @@ -0,0 +1,305 @@ |
313 | +# Copyright (C) 2006-2009, 2011 Canonical Ltd |
314 | +# |
315 | +# This program is free software; you can redistribute it and/or modify |
316 | +# it under the terms of the GNU General Public License as published by |
317 | +# the Free Software Foundation; either version 2 of the License, or |
318 | +# (at your option) any later version. |
319 | +# |
320 | +# This program is distributed in the hope that it will be useful, |
321 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
322 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
323 | +# GNU General Public License for more details. |
324 | +# |
325 | +# You should have received a copy of the GNU General Public License |
326 | +# along with this program; if not, write to the Free Software |
327 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
328 | + |
329 | +"""Tests for MultiWalker.""" |
330 | + |
331 | +from breezy import ( |
332 | + revision, |
333 | + multiwalker, |
334 | + tree as _mod_tree, |
335 | + ) |
336 | +from breezy.tests import ( |
337 | + TestCaseWithTransport, |
338 | + ) |
339 | + |
340 | + |
341 | +class TestMultiWalker(TestCaseWithTransport): |
342 | + |
343 | + def assertStepOne(self, has_more, path, file_id, iterator): |
344 | + retval = multiwalker.MultiWalker._step_one(iterator) |
345 | + if not has_more: |
346 | + self.assertIs(None, path) |
347 | + self.assertIs(None, file_id) |
348 | + self.assertEqual((False, None, None), retval) |
349 | + else: |
350 | + self.assertEqual((has_more, path, file_id), |
351 | + (retval[0], retval[1], retval[2].file_id)) |
352 | + |
353 | + def test__step_one_empty(self): |
354 | + tree = self.make_branch_and_tree('empty') |
355 | + repo = tree.branch.repository |
356 | + empty_tree = repo.revision_tree(revision.NULL_REVISION) |
357 | + |
358 | + iterator = empty_tree.iter_entries_by_dir() |
359 | + self.assertStepOne(False, None, None, iterator) |
360 | + self.assertStepOne(False, None, None, iterator) |
361 | + |
362 | + def test__step_one(self): |
363 | + tree = self.make_branch_and_tree('tree') |
364 | + self.build_tree(['tree/a', 'tree/b/', 'tree/b/c']) |
365 | + tree.add(['a', 'b', 'b/c'], [b'a-id', b'b-id', b'c-id']) |
366 | + |
367 | + iterator = tree.iter_entries_by_dir() |
368 | + tree.lock_read() |
369 | + self.addCleanup(tree.unlock) |
370 | + |
371 | + root_id = tree.path2id('') |
372 | + self.assertStepOne(True, '', root_id, iterator) |
373 | + self.assertStepOne(True, 'a', b'a-id', iterator) |
374 | + self.assertStepOne(True, 'b', b'b-id', iterator) |
375 | + self.assertStepOne(True, 'b/c', b'c-id', iterator) |
376 | + self.assertStepOne(False, None, None, iterator) |
377 | + self.assertStepOne(False, None, None, iterator) |
378 | + |
379 | + def assertWalkerNext(self, exp_path, exp_file_id, master_has_node, |
380 | + exp_other_paths, iterator): |
381 | + """Check what happens when we step the iterator. |
382 | + |
383 | + :param path: The path for this entry |
384 | + :param file_id: The file_id for this entry |
385 | + :param master_has_node: Does the master tree have this entry? |
386 | + :param exp_other_paths: A list of other_path values. |
387 | + :param iterator: The iterator to step |
388 | + """ |
389 | + path, file_id, master_ie, other_values = next(iterator) |
390 | + self.assertEqual((exp_path, exp_file_id), (path, file_id), |
391 | + 'Master entry did not match') |
392 | + if master_has_node: |
393 | + self.assertIsNot(None, master_ie, 'master should have an entry') |
394 | + else: |
395 | + self.assertIs(None, master_ie, 'master should not have an entry') |
396 | + self.assertEqual(len(exp_other_paths), len(other_values), |
397 | + 'Wrong number of other entries') |
398 | + other_paths = [] |
399 | + other_file_ids = [] |
400 | + for path, ie in other_values: |
401 | + other_paths.append(path) |
402 | + if ie is None: |
403 | + other_file_ids.append(None) |
404 | + else: |
405 | + other_file_ids.append(ie.file_id) |
406 | + |
407 | + exp_file_ids = [] |
408 | + for path in exp_other_paths: |
409 | + if path is None: |
410 | + exp_file_ids.append(None) |
411 | + else: |
412 | + exp_file_ids.append(file_id) |
413 | + self.assertEqual(exp_other_paths, other_paths, "Other paths incorrect") |
414 | + self.assertEqual(exp_file_ids, other_file_ids, |
415 | + "Other file_ids incorrect") |
416 | + |
417 | + def lock_and_get_basis_and_root_id(self, tree): |
418 | + tree.lock_read() |
419 | + self.addCleanup(tree.unlock) |
420 | + basis_tree = tree.basis_tree() |
421 | + basis_tree.lock_read() |
422 | + self.addCleanup(basis_tree.unlock) |
423 | + root_id = tree.path2id('') |
424 | + return basis_tree, root_id |
425 | + |
426 | + def test_simple_stepping(self): |
427 | + tree = self.make_branch_and_tree('tree') |
428 | + self.build_tree(['tree/a', 'tree/b/', 'tree/b/c']) |
429 | + tree.add(['a', 'b', 'b/c'], [b'a-id', b'b-id', b'c-id']) |
430 | + |
431 | + tree.commit('first', rev_id=b'first-rev-id') |
432 | + |
433 | + basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
434 | + |
435 | + walker = multiwalker.MultiWalker(tree, [basis_tree]) |
436 | + iterator = walker.iter_all() |
437 | + self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
438 | + self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
439 | + self.assertWalkerNext(u'b', b'b-id', True, [u'b'], iterator) |
440 | + self.assertWalkerNext(u'b/c', b'c-id', True, [u'b/c'], iterator) |
441 | + self.assertRaises(StopIteration, next, iterator) |
442 | + |
443 | + def test_master_has_extra(self): |
444 | + tree = self.make_branch_and_tree('tree') |
445 | + self.build_tree(['tree/a', 'tree/b/', 'tree/c', 'tree/d']) |
446 | + tree.add(['a', 'b', 'd'], [b'a-id', b'b-id', b'd-id']) |
447 | + |
448 | + tree.commit('first', rev_id=b'first-rev-id') |
449 | + |
450 | + tree.add(['c'], [b'c-id']) |
451 | + basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
452 | + |
453 | + walker = multiwalker.MultiWalker(tree, [basis_tree]) |
454 | + iterator = walker.iter_all() |
455 | + self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
456 | + self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
457 | + self.assertWalkerNext(u'b', b'b-id', True, [u'b'], iterator) |
458 | + self.assertWalkerNext(u'c', b'c-id', True, [None], iterator) |
459 | + self.assertWalkerNext(u'd', b'd-id', True, [u'd'], iterator) |
460 | + self.assertRaises(StopIteration, next, iterator) |
461 | + |
462 | + def test_master_renamed_to_earlier(self): |
463 | + """The record is still present, it just shows up early.""" |
464 | + tree = self.make_branch_and_tree('tree') |
465 | + self.build_tree(['tree/a', 'tree/c', 'tree/d']) |
466 | + tree.add(['a', 'c', 'd'], [b'a-id', b'c-id', b'd-id']) |
467 | + tree.commit('first', rev_id=b'first-rev-id') |
468 | + tree.rename_one('d', 'b') |
469 | + |
470 | + basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
471 | + |
472 | + walker = multiwalker.MultiWalker(tree, [basis_tree]) |
473 | + iterator = walker.iter_all() |
474 | + self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
475 | + self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
476 | + self.assertWalkerNext(u'b', b'd-id', True, [u'd'], iterator) |
477 | + self.assertWalkerNext(u'c', b'c-id', True, [u'c'], iterator) |
478 | + self.assertRaises(StopIteration, next, iterator) |
479 | + |
480 | + def test_master_renamed_to_later(self): |
481 | + tree = self.make_branch_and_tree('tree') |
482 | + self.build_tree(['tree/a', 'tree/b', 'tree/d']) |
483 | + tree.add(['a', 'b', 'd'], [b'a-id', b'b-id', b'd-id']) |
484 | + tree.commit('first', rev_id=b'first-rev-id') |
485 | + tree.rename_one('b', 'e') |
486 | + |
487 | + basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
488 | + |
489 | + walker = multiwalker.MultiWalker(tree, [basis_tree]) |
490 | + iterator = walker.iter_all() |
491 | + self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
492 | + self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
493 | + self.assertWalkerNext(u'd', b'd-id', True, [u'd'], iterator) |
494 | + self.assertWalkerNext(u'e', b'b-id', True, [u'b'], iterator) |
495 | + self.assertRaises(StopIteration, next, iterator) |
496 | + |
497 | + def test_other_extra_in_middle(self): |
498 | + tree = self.make_branch_and_tree('tree') |
499 | + self.build_tree(['tree/a', 'tree/b', 'tree/d']) |
500 | + tree.add(['a', 'b', 'd'], [b'a-id', b'b-id', b'd-id']) |
501 | + tree.commit('first', rev_id=b'first-rev-id') |
502 | + tree.remove(['b']) |
503 | + |
504 | + basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
505 | + walker = multiwalker.MultiWalker(tree, [basis_tree]) |
506 | + iterator = walker.iter_all() |
507 | + self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
508 | + self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
509 | + self.assertWalkerNext(u'd', b'd-id', True, [u'd'], iterator) |
510 | + self.assertWalkerNext(u'b', b'b-id', False, [u'b'], iterator) |
511 | + self.assertRaises(StopIteration, next, iterator) |
512 | + |
513 | + def test_other_extra_at_end(self): |
514 | + tree = self.make_branch_and_tree('tree') |
515 | + self.build_tree(['tree/a', 'tree/b', 'tree/d']) |
516 | + tree.add(['a', 'b', 'd'], [b'a-id', b'b-id', b'd-id']) |
517 | + tree.commit('first', rev_id=b'first-rev-id') |
518 | + tree.remove(['d']) |
519 | + |
520 | + basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
521 | + walker = multiwalker.MultiWalker(tree, [basis_tree]) |
522 | + iterator = walker.iter_all() |
523 | + self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
524 | + self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
525 | + self.assertWalkerNext(u'b', b'b-id', True, [u'b'], iterator) |
526 | + self.assertWalkerNext(u'd', b'd-id', False, [u'd'], iterator) |
527 | + self.assertRaises(StopIteration, next, iterator) |
528 | + |
529 | + def test_others_extra_at_end(self): |
530 | + tree = self.make_branch_and_tree('tree') |
531 | + self.build_tree(['tree/a', 'tree/b', 'tree/c', 'tree/d', 'tree/e']) |
532 | + tree.add(['a', 'b', 'c', 'd', 'e'], |
533 | + [b'a-id', b'b-id', b'c-id', b'd-id', b'e-id']) |
534 | + tree.commit('first', rev_id=b'first-rev-id') |
535 | + tree.remove(['e']) |
536 | + tree.commit('second', rev_id=b'second-rev-id') |
537 | + tree.remove(['d']) |
538 | + tree.commit('third', rev_id=b'third-rev-id') |
539 | + tree.remove(['c']) |
540 | + |
541 | + basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
542 | + first_tree = tree.branch.repository.revision_tree(b'first-rev-id') |
543 | + second_tree = tree.branch.repository.revision_tree(b'second-rev-id') |
544 | + walker = multiwalker.MultiWalker(tree, [basis_tree, first_tree, |
545 | + second_tree]) |
546 | + iterator = walker.iter_all() |
547 | + self.assertWalkerNext(u'', root_id, True, [u'', u'', u''], iterator) |
548 | + self.assertWalkerNext(u'a', b'a-id', True, |
549 | + [u'a', u'a', u'a'], iterator) |
550 | + self.assertWalkerNext(u'b', b'b-id', True, |
551 | + [u'b', u'b', u'b'], iterator) |
552 | + self.assertWalkerNext(u'c', b'c-id', False, |
553 | + [u'c', u'c', u'c'], iterator) |
554 | + self.assertWalkerNext(u'd', b'd-id', False, |
555 | + [None, u'd', u'd'], iterator) |
556 | + self.assertWalkerNext(u'e', b'e-id', False, |
557 | + [None, u'e', None], iterator) |
558 | + self.assertRaises(StopIteration, next, iterator) |
559 | + |
560 | + def test_different_file_id_in_others(self): |
561 | + tree = self.make_branch_and_tree('tree') |
562 | + self.build_tree(['tree/a', 'tree/b', 'tree/c/']) |
563 | + tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id']) |
564 | + tree.commit('first', rev_id=b'first-rev-id') |
565 | + |
566 | + tree.rename_one('b', 'c/d') |
567 | + self.build_tree(['tree/b']) |
568 | + tree.add(['b'], [b'b2-id']) |
569 | + tree.commit('second', rev_id=b'second-rev-id') |
570 | + |
571 | + tree.rename_one('a', 'c/e') |
572 | + self.build_tree(['tree/a']) |
573 | + tree.add(['a'], [b'a2-id']) |
574 | + |
575 | + basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
576 | + first_tree = tree.branch.repository.revision_tree(b'first-rev-id') |
577 | + walker = multiwalker.MultiWalker(tree, [basis_tree, first_tree]) |
578 | + |
579 | + iterator = walker.iter_all() |
580 | + self.assertWalkerNext(u'', root_id, True, [u'', u''], iterator) |
581 | + self.assertWalkerNext(u'a', b'a2-id', True, [None, None], iterator) |
582 | + self.assertWalkerNext(u'b', b'b2-id', True, [u'b', None], iterator) |
583 | + self.assertWalkerNext(u'c', b'c-id', True, [u'c', u'c'], iterator) |
584 | + self.assertWalkerNext(u'c/d', b'b-id', True, [u'c/d', u'b'], iterator) |
585 | + self.assertWalkerNext(u'c/e', b'a-id', True, [u'a', u'a'], iterator) |
586 | + self.assertRaises(StopIteration, next, iterator) |
587 | + |
588 | + def assertLtByDirblock(self, lt_val, path1, path2): |
589 | + self.assertEqual( |
590 | + lt_val, multiwalker.MultiWalker._lt_path_by_dirblock(path1, path2)) |
591 | + |
592 | + def test__lt_path_by_dirblock(self): |
593 | + # We only support Unicode strings at this point |
594 | + self.assertRaises( |
595 | + TypeError, multiwalker.MultiWalker._lt_path_by_dirblock, b'', b'b') |
596 | + self.assertLtByDirblock(False, u'', u'') |
597 | + self.assertLtByDirblock(False, u'a', u'a') |
598 | + self.assertLtByDirblock(False, u'a/b', u'a/b') |
599 | + self.assertLtByDirblock(False, u'a/b/c', u'a/b/c') |
600 | + self.assertLtByDirblock(False, u'a-a', u'a') |
601 | + self.assertLtByDirblock(True, u'a-a', u'a/a') |
602 | + self.assertLtByDirblock(True, u'a=a', u'a/a') |
603 | + self.assertLtByDirblock(False, u'a-a/a', u'a/a') |
604 | + self.assertLtByDirblock(False, u'a=a/a', u'a/a') |
605 | + self.assertLtByDirblock(False, u'a-a/a', u'a/a/a') |
606 | + self.assertLtByDirblock(False, u'a=a/a', u'a/a/a') |
607 | + self.assertLtByDirblock(False, u'a-a/a/a', u'a/a/a') |
608 | + self.assertLtByDirblock(False, u'a=a/a/a', u'a/a/a') |
609 | + |
610 | + def assertPathToKey(self, expected, path): |
611 | + self.assertEqual(expected, multiwalker.MultiWalker._path_to_key(path)) |
612 | + |
613 | + def test__path_to_key(self): |
614 | + self.assertPathToKey(([u''], u''), u'') |
615 | + self.assertPathToKey(([u''], u'a'), u'a') |
616 | + self.assertPathToKey(([u'a'], u'b'), u'a/b') |
617 | + self.assertPathToKey(([u'a', u'b'], u'c'), u'a/b/c') |
618 | |
619 | === modified file 'breezy/tests/test_tree.py' |
620 | --- breezy/tests/test_tree.py 2018-11-12 01:41:38 +0000 |
621 | +++ breezy/tests/test_tree.py 2019-05-12 10:12:09 +0000 |
622 | @@ -163,285 +163,6 @@ |
623 | self.assertEqual(len(delta.added), 1) |
624 | |
625 | |
626 | -class TestMultiWalker(TestCaseWithTransport): |
627 | - |
628 | - def assertStepOne(self, has_more, path, file_id, iterator): |
629 | - retval = _mod_tree.MultiWalker._step_one(iterator) |
630 | - if not has_more: |
631 | - self.assertIs(None, path) |
632 | - self.assertIs(None, file_id) |
633 | - self.assertEqual((False, None, None), retval) |
634 | - else: |
635 | - self.assertEqual((has_more, path, file_id), |
636 | - (retval[0], retval[1], retval[2].file_id)) |
637 | - |
638 | - def test__step_one_empty(self): |
639 | - tree = self.make_branch_and_tree('empty') |
640 | - repo = tree.branch.repository |
641 | - empty_tree = repo.revision_tree(revision.NULL_REVISION) |
642 | - |
643 | - iterator = empty_tree.iter_entries_by_dir() |
644 | - self.assertStepOne(False, None, None, iterator) |
645 | - self.assertStepOne(False, None, None, iterator) |
646 | - |
647 | - def test__step_one(self): |
648 | - tree = self.make_branch_and_tree('tree') |
649 | - self.build_tree(['tree/a', 'tree/b/', 'tree/b/c']) |
650 | - tree.add(['a', 'b', 'b/c'], [b'a-id', b'b-id', b'c-id']) |
651 | - |
652 | - iterator = tree.iter_entries_by_dir() |
653 | - tree.lock_read() |
654 | - self.addCleanup(tree.unlock) |
655 | - |
656 | - root_id = tree.path2id('') |
657 | - self.assertStepOne(True, '', root_id, iterator) |
658 | - self.assertStepOne(True, 'a', b'a-id', iterator) |
659 | - self.assertStepOne(True, 'b', b'b-id', iterator) |
660 | - self.assertStepOne(True, 'b/c', b'c-id', iterator) |
661 | - self.assertStepOne(False, None, None, iterator) |
662 | - self.assertStepOne(False, None, None, iterator) |
663 | - |
664 | - def assertWalkerNext(self, exp_path, exp_file_id, master_has_node, |
665 | - exp_other_paths, iterator): |
666 | - """Check what happens when we step the iterator. |
667 | - |
668 | - :param path: The path for this entry |
669 | - :param file_id: The file_id for this entry |
670 | - :param master_has_node: Does the master tree have this entry? |
671 | - :param exp_other_paths: A list of other_path values. |
672 | - :param iterator: The iterator to step |
673 | - """ |
674 | - path, file_id, master_ie, other_values = next(iterator) |
675 | - self.assertEqual((exp_path, exp_file_id), (path, file_id), |
676 | - 'Master entry did not match') |
677 | - if master_has_node: |
678 | - self.assertIsNot(None, master_ie, 'master should have an entry') |
679 | - else: |
680 | - self.assertIs(None, master_ie, 'master should not have an entry') |
681 | - self.assertEqual(len(exp_other_paths), len(other_values), |
682 | - 'Wrong number of other entries') |
683 | - other_paths = [] |
684 | - other_file_ids = [] |
685 | - for path, ie in other_values: |
686 | - other_paths.append(path) |
687 | - if ie is None: |
688 | - other_file_ids.append(None) |
689 | - else: |
690 | - other_file_ids.append(ie.file_id) |
691 | - |
692 | - exp_file_ids = [] |
693 | - for path in exp_other_paths: |
694 | - if path is None: |
695 | - exp_file_ids.append(None) |
696 | - else: |
697 | - exp_file_ids.append(file_id) |
698 | - self.assertEqual(exp_other_paths, other_paths, "Other paths incorrect") |
699 | - self.assertEqual(exp_file_ids, other_file_ids, |
700 | - "Other file_ids incorrect") |
701 | - |
702 | - def lock_and_get_basis_and_root_id(self, tree): |
703 | - tree.lock_read() |
704 | - self.addCleanup(tree.unlock) |
705 | - basis_tree = tree.basis_tree() |
706 | - basis_tree.lock_read() |
707 | - self.addCleanup(basis_tree.unlock) |
708 | - root_id = tree.path2id('') |
709 | - return basis_tree, root_id |
710 | - |
711 | - def test_simple_stepping(self): |
712 | - tree = self.make_branch_and_tree('tree') |
713 | - self.build_tree(['tree/a', 'tree/b/', 'tree/b/c']) |
714 | - tree.add(['a', 'b', 'b/c'], [b'a-id', b'b-id', b'c-id']) |
715 | - |
716 | - tree.commit('first', rev_id=b'first-rev-id') |
717 | - |
718 | - basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
719 | - |
720 | - walker = _mod_tree.MultiWalker(tree, [basis_tree]) |
721 | - iterator = walker.iter_all() |
722 | - self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
723 | - self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
724 | - self.assertWalkerNext(u'b', b'b-id', True, [u'b'], iterator) |
725 | - self.assertWalkerNext(u'b/c', b'c-id', True, [u'b/c'], iterator) |
726 | - self.assertRaises(StopIteration, next, iterator) |
727 | - |
728 | - def test_master_has_extra(self): |
729 | - tree = self.make_branch_and_tree('tree') |
730 | - self.build_tree(['tree/a', 'tree/b/', 'tree/c', 'tree/d']) |
731 | - tree.add(['a', 'b', 'd'], [b'a-id', b'b-id', b'd-id']) |
732 | - |
733 | - tree.commit('first', rev_id=b'first-rev-id') |
734 | - |
735 | - tree.add(['c'], [b'c-id']) |
736 | - basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
737 | - |
738 | - walker = _mod_tree.MultiWalker(tree, [basis_tree]) |
739 | - iterator = walker.iter_all() |
740 | - self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
741 | - self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
742 | - self.assertWalkerNext(u'b', b'b-id', True, [u'b'], iterator) |
743 | - self.assertWalkerNext(u'c', b'c-id', True, [None], iterator) |
744 | - self.assertWalkerNext(u'd', b'd-id', True, [u'd'], iterator) |
745 | - self.assertRaises(StopIteration, next, iterator) |
746 | - |
747 | - def test_master_renamed_to_earlier(self): |
748 | - """The record is still present, it just shows up early.""" |
749 | - tree = self.make_branch_and_tree('tree') |
750 | - self.build_tree(['tree/a', 'tree/c', 'tree/d']) |
751 | - tree.add(['a', 'c', 'd'], [b'a-id', b'c-id', b'd-id']) |
752 | - tree.commit('first', rev_id=b'first-rev-id') |
753 | - tree.rename_one('d', 'b') |
754 | - |
755 | - basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
756 | - |
757 | - walker = _mod_tree.MultiWalker(tree, [basis_tree]) |
758 | - iterator = walker.iter_all() |
759 | - self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
760 | - self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
761 | - self.assertWalkerNext(u'b', b'd-id', True, [u'd'], iterator) |
762 | - self.assertWalkerNext(u'c', b'c-id', True, [u'c'], iterator) |
763 | - self.assertRaises(StopIteration, next, iterator) |
764 | - |
765 | - def test_master_renamed_to_later(self): |
766 | - tree = self.make_branch_and_tree('tree') |
767 | - self.build_tree(['tree/a', 'tree/b', 'tree/d']) |
768 | - tree.add(['a', 'b', 'd'], [b'a-id', b'b-id', b'd-id']) |
769 | - tree.commit('first', rev_id=b'first-rev-id') |
770 | - tree.rename_one('b', 'e') |
771 | - |
772 | - basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
773 | - |
774 | - walker = _mod_tree.MultiWalker(tree, [basis_tree]) |
775 | - iterator = walker.iter_all() |
776 | - self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
777 | - self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
778 | - self.assertWalkerNext(u'd', b'd-id', True, [u'd'], iterator) |
779 | - self.assertWalkerNext(u'e', b'b-id', True, [u'b'], iterator) |
780 | - self.assertRaises(StopIteration, next, iterator) |
781 | - |
782 | - def test_other_extra_in_middle(self): |
783 | - tree = self.make_branch_and_tree('tree') |
784 | - self.build_tree(['tree/a', 'tree/b', 'tree/d']) |
785 | - tree.add(['a', 'b', 'd'], [b'a-id', b'b-id', b'd-id']) |
786 | - tree.commit('first', rev_id=b'first-rev-id') |
787 | - tree.remove(['b']) |
788 | - |
789 | - basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
790 | - walker = _mod_tree.MultiWalker(tree, [basis_tree]) |
791 | - iterator = walker.iter_all() |
792 | - self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
793 | - self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
794 | - self.assertWalkerNext(u'd', b'd-id', True, [u'd'], iterator) |
795 | - self.assertWalkerNext(u'b', b'b-id', False, [u'b'], iterator) |
796 | - self.assertRaises(StopIteration, next, iterator) |
797 | - |
798 | - def test_other_extra_at_end(self): |
799 | - tree = self.make_branch_and_tree('tree') |
800 | - self.build_tree(['tree/a', 'tree/b', 'tree/d']) |
801 | - tree.add(['a', 'b', 'd'], [b'a-id', b'b-id', b'd-id']) |
802 | - tree.commit('first', rev_id=b'first-rev-id') |
803 | - tree.remove(['d']) |
804 | - |
805 | - basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
806 | - walker = _mod_tree.MultiWalker(tree, [basis_tree]) |
807 | - iterator = walker.iter_all() |
808 | - self.assertWalkerNext(u'', root_id, True, [u''], iterator) |
809 | - self.assertWalkerNext(u'a', b'a-id', True, [u'a'], iterator) |
810 | - self.assertWalkerNext(u'b', b'b-id', True, [u'b'], iterator) |
811 | - self.assertWalkerNext(u'd', b'd-id', False, [u'd'], iterator) |
812 | - self.assertRaises(StopIteration, next, iterator) |
813 | - |
814 | - def test_others_extra_at_end(self): |
815 | - tree = self.make_branch_and_tree('tree') |
816 | - self.build_tree(['tree/a', 'tree/b', 'tree/c', 'tree/d', 'tree/e']) |
817 | - tree.add(['a', 'b', 'c', 'd', 'e'], |
818 | - [b'a-id', b'b-id', b'c-id', b'd-id', b'e-id']) |
819 | - tree.commit('first', rev_id=b'first-rev-id') |
820 | - tree.remove(['e']) |
821 | - tree.commit('second', rev_id=b'second-rev-id') |
822 | - tree.remove(['d']) |
823 | - tree.commit('third', rev_id=b'third-rev-id') |
824 | - tree.remove(['c']) |
825 | - |
826 | - basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
827 | - first_tree = tree.branch.repository.revision_tree(b'first-rev-id') |
828 | - second_tree = tree.branch.repository.revision_tree(b'second-rev-id') |
829 | - walker = _mod_tree.MultiWalker(tree, [basis_tree, first_tree, |
830 | - second_tree]) |
831 | - iterator = walker.iter_all() |
832 | - self.assertWalkerNext(u'', root_id, True, [u'', u'', u''], iterator) |
833 | - self.assertWalkerNext(u'a', b'a-id', True, |
834 | - [u'a', u'a', u'a'], iterator) |
835 | - self.assertWalkerNext(u'b', b'b-id', True, |
836 | - [u'b', u'b', u'b'], iterator) |
837 | - self.assertWalkerNext(u'c', b'c-id', False, |
838 | - [u'c', u'c', u'c'], iterator) |
839 | - self.assertWalkerNext(u'd', b'd-id', False, |
840 | - [None, u'd', u'd'], iterator) |
841 | - self.assertWalkerNext(u'e', b'e-id', False, |
842 | - [None, u'e', None], iterator) |
843 | - self.assertRaises(StopIteration, next, iterator) |
844 | - |
845 | - def test_different_file_id_in_others(self): |
846 | - tree = self.make_branch_and_tree('tree') |
847 | - self.build_tree(['tree/a', 'tree/b', 'tree/c/']) |
848 | - tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id']) |
849 | - tree.commit('first', rev_id=b'first-rev-id') |
850 | - |
851 | - tree.rename_one('b', 'c/d') |
852 | - self.build_tree(['tree/b']) |
853 | - tree.add(['b'], [b'b2-id']) |
854 | - tree.commit('second', rev_id=b'second-rev-id') |
855 | - |
856 | - tree.rename_one('a', 'c/e') |
857 | - self.build_tree(['tree/a']) |
858 | - tree.add(['a'], [b'a2-id']) |
859 | - |
860 | - basis_tree, root_id = self.lock_and_get_basis_and_root_id(tree) |
861 | - first_tree = tree.branch.repository.revision_tree(b'first-rev-id') |
862 | - walker = _mod_tree.MultiWalker(tree, [basis_tree, first_tree]) |
863 | - |
864 | - iterator = walker.iter_all() |
865 | - self.assertWalkerNext(u'', root_id, True, [u'', u''], iterator) |
866 | - self.assertWalkerNext(u'a', b'a2-id', True, [None, None], iterator) |
867 | - self.assertWalkerNext(u'b', b'b2-id', True, [u'b', None], iterator) |
868 | - self.assertWalkerNext(u'c', b'c-id', True, [u'c', u'c'], iterator) |
869 | - self.assertWalkerNext(u'c/d', b'b-id', True, [u'c/d', u'b'], iterator) |
870 | - self.assertWalkerNext(u'c/e', b'a-id', True, [u'a', u'a'], iterator) |
871 | - self.assertRaises(StopIteration, next, iterator) |
872 | - |
873 | - def assertLtByDirblock(self, lt_val, path1, path2): |
874 | - self.assertEqual( |
875 | - lt_val, _mod_tree.MultiWalker._lt_path_by_dirblock(path1, path2)) |
876 | - |
877 | - def test__lt_path_by_dirblock(self): |
878 | - # We only support Unicode strings at this point |
879 | - self.assertRaises( |
880 | - TypeError, _mod_tree.MultiWalker._lt_path_by_dirblock, b'', b'b') |
881 | - self.assertLtByDirblock(False, u'', u'') |
882 | - self.assertLtByDirblock(False, u'a', u'a') |
883 | - self.assertLtByDirblock(False, u'a/b', u'a/b') |
884 | - self.assertLtByDirblock(False, u'a/b/c', u'a/b/c') |
885 | - self.assertLtByDirblock(False, u'a-a', u'a') |
886 | - self.assertLtByDirblock(True, u'a-a', u'a/a') |
887 | - self.assertLtByDirblock(True, u'a=a', u'a/a') |
888 | - self.assertLtByDirblock(False, u'a-a/a', u'a/a') |
889 | - self.assertLtByDirblock(False, u'a=a/a', u'a/a') |
890 | - self.assertLtByDirblock(False, u'a-a/a', u'a/a/a') |
891 | - self.assertLtByDirblock(False, u'a=a/a', u'a/a/a') |
892 | - self.assertLtByDirblock(False, u'a-a/a/a', u'a/a/a') |
893 | - self.assertLtByDirblock(False, u'a=a/a/a', u'a/a/a') |
894 | - |
895 | - def assertPathToKey(self, expected, path): |
896 | - self.assertEqual(expected, _mod_tree.MultiWalker._path_to_key(path)) |
897 | - |
898 | - def test__path_to_key(self): |
899 | - self.assertPathToKey(([u''], u''), u'') |
900 | - self.assertPathToKey(([u''], u'a'), u'a') |
901 | - self.assertPathToKey(([u'a'], u'b'), u'a/b') |
902 | - self.assertPathToKey(([u'a', u'b'], u'c'), u'a/b/c') |
903 | - |
904 | - |
905 | class FindPreviousPathsTests(TestCaseWithTransport): |
906 | |
907 | def test_new(self): |
908 | |
909 | === modified file 'breezy/tree.py' |
910 | --- breezy/tree.py 2019-02-02 15:13:30 +0000 |
911 | +++ breezy/tree.py 2019-05-12 10:12:09 +0000 |
912 | @@ -945,7 +945,8 @@ |
913 | changed_file_ids): |
914 | yield result |
915 | |
916 | - def _get_entry(self, tree, path): |
917 | + @staticmethod |
918 | + def _get_entry(tree, path): |
919 | """Get an inventory entry from a tree, with missing entries as None. |
920 | |
921 | If the tree raises NotImplementedError on accessing .inventory, then |
922 | @@ -1091,239 +1092,6 @@ |
923 | InterTree.register_optimiser(InterTree) |
924 | |
925 | |
926 | -class MultiWalker(object): |
927 | - """Walk multiple trees simultaneously, getting combined results.""" |
928 | - |
929 | - # Note: This could be written to not assume you can do out-of-order |
930 | - # lookups. Instead any nodes that don't match in all trees could be |
931 | - # marked as 'deferred', and then returned in the final cleanup loop. |
932 | - # For now, I think it is "nicer" to return things as close to the |
933 | - # "master_tree" order as we can. |
934 | - |
935 | - def __init__(self, master_tree, other_trees): |
936 | - """Create a new MultiWalker. |
937 | - |
938 | - All trees being walked must implement "iter_entries_by_dir()", such |
939 | - that they yield (path, object) tuples, where that object will have a |
940 | - '.file_id' member, that can be used to check equality. |
941 | - |
942 | - :param master_tree: All trees will be 'slaved' to the master_tree such |
943 | - that nodes in master_tree will be used as 'first-pass' sync points. |
944 | - Any nodes that aren't in master_tree will be merged in a second |
945 | - pass. |
946 | - :param other_trees: A list of other trees to walk simultaneously. |
947 | - """ |
948 | - self._master_tree = master_tree |
949 | - self._other_trees = other_trees |
950 | - |
951 | - # Keep track of any nodes that were properly processed just out of |
952 | - # order, that way we don't return them at the end, we don't have to |
953 | - # track *all* processed file_ids, just the out-of-order ones |
954 | - self._out_of_order_processed = set() |
955 | - |
956 | - @staticmethod |
957 | - def _step_one(iterator): |
958 | - """Step an iter_entries_by_dir iterator. |
959 | - |
960 | - :return: (has_more, path, ie) |
961 | - If has_more is False, path and ie will be None. |
962 | - """ |
963 | - try: |
964 | - path, ie = next(iterator) |
965 | - except StopIteration: |
966 | - return False, None, None |
967 | - else: |
968 | - return True, path, ie |
969 | - |
970 | - @staticmethod |
971 | - def _lt_path_by_dirblock(path1, path2): |
972 | - """Compare two paths based on what directory they are in. |
973 | - |
974 | - This generates a sort order, such that all children of a directory are |
975 | - sorted together, and grandchildren are in the same order as the |
976 | - children appear. But all grandchildren come after all children. |
977 | - |
978 | - :param path1: first path |
979 | - :param path2: the second path |
980 | - :return: negative number if ``path1`` comes first, |
981 | - 0 if paths are equal |
982 | - and a positive number if ``path2`` sorts first |
983 | - """ |
984 | - # Shortcut this special case |
985 | - if path1 == path2: |
986 | - return False |
987 | - # This is stolen from _dirstate_helpers_py.py, only switching it to |
988 | - # Unicode objects. Consider using encode_utf8() and then using the |
989 | - # optimized versions, or maybe writing optimized unicode versions. |
990 | - if not isinstance(path1, text_type): |
991 | - raise TypeError("'path1' must be a unicode string, not %s: %r" |
992 | - % (type(path1), path1)) |
993 | - if not isinstance(path2, text_type): |
994 | - raise TypeError("'path2' must be a unicode string, not %s: %r" |
995 | - % (type(path2), path2)) |
996 | - return (MultiWalker._path_to_key(path1) < |
997 | - MultiWalker._path_to_key(path2)) |
998 | - |
999 | - @staticmethod |
1000 | - def _path_to_key(path): |
1001 | - dirname, basename = osutils.split(path) |
1002 | - return (dirname.split(u'/'), basename) |
1003 | - |
1004 | - def _lookup_by_file_id(self, extra_entries, other_tree, file_id): |
1005 | - """Lookup an inventory entry by file_id. |
1006 | - |
1007 | - This is called when an entry is missing in the normal order. |
1008 | - Generally this is because a file was either renamed, or it was |
1009 | - deleted/added. If the entry was found in the inventory and not in |
1010 | - extra_entries, it will be added to self._out_of_order_processed |
1011 | - |
1012 | - :param extra_entries: A dictionary of {file_id: (path, ie)}. This |
1013 | - should be filled with entries that were found before they were |
1014 | - used. If file_id is present, it will be removed from the |
1015 | - dictionary. |
1016 | - :param other_tree: The Tree to search, in case we didn't find the entry |
1017 | - yet. |
1018 | - :param file_id: The file_id to look for |
1019 | - :return: (path, ie) if found or (None, None) if not present. |
1020 | - """ |
1021 | - if file_id in extra_entries: |
1022 | - return extra_entries.pop(file_id) |
1023 | - # TODO: Is id2path better as the first call, or is |
1024 | - # inventory[file_id] better as a first check? |
1025 | - try: |
1026 | - cur_path = other_tree.id2path(file_id) |
1027 | - except errors.NoSuchId: |
1028 | - cur_path = None |
1029 | - if cur_path is None: |
1030 | - return (None, None) |
1031 | - else: |
1032 | - self._out_of_order_processed.add(file_id) |
1033 | - cur_ie = other_tree.root_inventory.get_entry(file_id) |
1034 | - return (cur_path, cur_ie) |
1035 | - |
1036 | - def iter_all(self): |
1037 | - """Match up the values in the different trees.""" |
1038 | - for result in self._walk_master_tree(): |
1039 | - yield result |
1040 | - self._finish_others() |
1041 | - for result in self._walk_others(): |
1042 | - yield result |
1043 | - |
1044 | - def _walk_master_tree(self): |
1045 | - """First pass, walk all trees in lock-step. |
1046 | - |
1047 | - When we are done, all nodes in the master_tree will have been |
1048 | - processed. _other_walkers, _other_entries, and _others_extra will be |
1049 | - set on 'self' for future processing. |
1050 | - """ |
1051 | - # This iterator has the most "inlining" done, because it tends to touch |
1052 | - # every file in the tree, while the others only hit nodes that don't |
1053 | - # match. |
1054 | - master_iterator = self._master_tree.iter_entries_by_dir() |
1055 | - |
1056 | - other_walkers = [other.iter_entries_by_dir() |
1057 | - for other in self._other_trees] |
1058 | - other_entries = [self._step_one(walker) for walker in other_walkers] |
1059 | - # Track extra nodes in the other trees |
1060 | - others_extra = [{} for _ in range(len(self._other_trees))] |
1061 | - |
1062 | - master_has_more = True |
1063 | - step_one = self._step_one |
1064 | - lookup_by_file_id = self._lookup_by_file_id |
1065 | - out_of_order_processed = self._out_of_order_processed |
1066 | - |
1067 | - while master_has_more: |
1068 | - (master_has_more, path, master_ie) = step_one(master_iterator) |
1069 | - if not master_has_more: |
1070 | - break |
1071 | - |
1072 | - file_id = master_ie.file_id |
1073 | - other_values = [] |
1074 | - other_values_append = other_values.append |
1075 | - next_other_entries = [] |
1076 | - next_other_entries_append = next_other_entries.append |
1077 | - for idx, (other_has_more, other_path, other_ie) in enumerate(other_entries): |
1078 | - if not other_has_more: |
1079 | - other_values_append(lookup_by_file_id( |
1080 | - others_extra[idx], self._other_trees[idx], file_id)) |
1081 | - next_other_entries_append((False, None, None)) |
1082 | - elif file_id == other_ie.file_id: |
1083 | - # This is the critical code path, as most of the entries |
1084 | - # should match between most trees. |
1085 | - other_values_append((other_path, other_ie)) |
1086 | - next_other_entries_append(step_one(other_walkers[idx])) |
1087 | - else: |
1088 | - # This walker did not match, step it until it either |
1089 | - # matches, or we know we are past the current walker. |
1090 | - other_walker = other_walkers[idx] |
1091 | - other_extra = others_extra[idx] |
1092 | - while (other_has_more and |
1093 | - self._lt_path_by_dirblock(other_path, path)): |
1094 | - other_file_id = other_ie.file_id |
1095 | - if other_file_id not in out_of_order_processed: |
1096 | - other_extra[other_file_id] = (other_path, other_ie) |
1097 | - other_has_more, other_path, other_ie = \ |
1098 | - step_one(other_walker) |
1099 | - if other_has_more and other_ie.file_id == file_id: |
1100 | - # We ended up walking to this point, match and step |
1101 | - # again |
1102 | - other_values_append((other_path, other_ie)) |
1103 | - other_has_more, other_path, other_ie = \ |
1104 | - step_one(other_walker) |
1105 | - else: |
1106 | - # This record isn't in the normal order, see if it |
1107 | - # exists at all. |
1108 | - other_values_append(lookup_by_file_id( |
1109 | - other_extra, self._other_trees[idx], file_id)) |
1110 | - next_other_entries_append((other_has_more, other_path, |
1111 | - other_ie)) |
1112 | - other_entries = next_other_entries |
1113 | - |
1114 | - # We've matched all the walkers, yield this datapoint |
1115 | - yield path, file_id, master_ie, other_values |
1116 | - self._other_walkers = other_walkers |
1117 | - self._other_entries = other_entries |
1118 | - self._others_extra = others_extra |
1119 | - |
1120 | - def _finish_others(self): |
1121 | - """Finish walking the other iterators, so we get all entries.""" |
1122 | - for idx, info in enumerate(self._other_entries): |
1123 | - other_extra = self._others_extra[idx] |
1124 | - (other_has_more, other_path, other_ie) = info |
1125 | - while other_has_more: |
1126 | - other_file_id = other_ie.file_id |
1127 | - if other_file_id not in self._out_of_order_processed: |
1128 | - other_extra[other_file_id] = (other_path, other_ie) |
1129 | - other_has_more, other_path, other_ie = \ |
1130 | - self._step_one(self._other_walkers[idx]) |
1131 | - del self._other_entries |
1132 | - |
1133 | - def _walk_others(self): |
1134 | - """Finish up by walking all the 'deferred' nodes.""" |
1135 | - # TODO: One alternative would be to grab all possible unprocessed |
1136 | - # file_ids, and then sort by path, and then yield them. That |
1137 | - # might ensure better ordering, in case a caller strictly |
1138 | - # requires parents before children. |
1139 | - for idx, other_extra in enumerate(self._others_extra): |
1140 | - others = sorted(viewvalues(other_extra), |
1141 | - key=lambda x: self._path_to_key(x[0])) |
1142 | - for other_path, other_ie in others: |
1143 | - file_id = other_ie.file_id |
1144 | - # We don't need to check out_of_order_processed here, because |
1145 | - # the lookup_by_file_id will be removing anything processed |
1146 | - # from the extras cache |
1147 | - other_extra.pop(file_id) |
1148 | - other_values = [(None, None)] * idx |
1149 | - other_values.append((other_path, other_ie)) |
1150 | - for alt_idx, alt_extra in enumerate(self._others_extra[idx + 1:]): |
1151 | - alt_idx = alt_idx + idx + 1 |
1152 | - alt_extra = self._others_extra[alt_idx] |
1153 | - alt_tree = self._other_trees[alt_idx] |
1154 | - other_values.append(self._lookup_by_file_id( |
1155 | - alt_extra, alt_tree, file_id)) |
1156 | - yield other_path, file_id, None, other_values |
1157 | - |
1158 | - |
1159 | def find_previous_paths(from_tree, to_tree, paths): |
1160 | """Find previous tree paths. |
1161 |