Merge lp:~jelmer/brz/split-multiwalker into lp:brz

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
Reviewer Review Type Date Requested Status
Breezy developers Pending
Review via email: mp+367316@code.launchpad.net

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

Subscribers

People subscribed via source and target branches