comparison mercurial/ancestor.py @ 39473:b6db2e80a9ce

ancestors: actually iterate over ancestors in topological order (issue5979) This code previously used a dequeue logic, the first ancestors seen were the first ancestors to be emitted. In branching/merging situations, it can result in ancestors being yielded before their descendants, breaking the object contract. We got affected by this issue while working on the copy tracing code. At about the same time, Axel Hecht <axel@mozilla.com> reported the issue and provided the test case used in this changeset. Thanks Axel. Running `hg perfancestors` does not show a significant difference between the old and the new version.
author Boris Feld <boris.feld@octobus.net>
date Thu, 06 Sep 2018 17:00:28 -0400
parents e7aa113b14f7
children a60dae060bc8
comparison
equal deleted inserted replaced
39472:4e4fae1dda5c 39473:b6db2e80a9ce
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from __future__ import absolute_import 8 from __future__ import absolute_import
9 9
10 import collections
11 import heapq 10 import heapq
12 11
13 from .node import nullrev 12 from .node import nullrev
14 from . import ( 13 from . import (
15 pycompat, 14 pycompat,
303 302
304 def __iter__(self): 303 def __iter__(self):
305 """Generate the ancestors of _initrevs in reverse topological order. 304 """Generate the ancestors of _initrevs in reverse topological order.
306 305
307 If inclusive is False, yield a sequence of revision numbers starting 306 If inclusive is False, yield a sequence of revision numbers starting
308 with the parents of each revision in revs, i.e., each revision is *not* 307 with the parents of each revision in revs, i.e., each revision is
309 considered an ancestor of itself. Results are in breadth-first order: 308 *not* considered an ancestor of itself. Results are emitted in reverse
310 parents of each rev in revs, then parents of those, etc. 309 revision number order. That order is also topological: a child is
310 always emitted before its parent.
311 311
312 If inclusive is True, yield all the revs first (ignoring stoprev), 312 If inclusive is True, yield all the revs first (ignoring stoprev),
313 then yield all the ancestors of revs as when inclusive is False. 313 then yield all the ancestors of revs as when inclusive is False. If an
314 If an element in revs is an ancestor of a different rev it is not 314 element in revs is an ancestor of a different rev it is not yielded
315 yielded again.""" 315 again."""
316 seen = set() 316 seen = set()
317 revs = self._initrevs 317 revs = self._initrevs
318 if self._inclusive: 318 if self._inclusive:
319 for rev in revs: 319 for rev in revs:
320 yield rev 320 yield rev
321 seen.update(revs) 321 seen.update(revs)
322 322
323 parentrevs = self._parentrevs 323 parentrevs = self._parentrevs
324 stoprev = self._stoprev 324 stoprev = self._stoprev
325 visit = collections.deque(revs) 325 visit = []
326 326 heapq.heapify(visit)
327 schedule = heapq.heappush
328 nextitem = heapq.heappop
327 see = seen.add 329 see = seen.add
328 schedule = visit.append 330 see(nullrev)
331
332 for r in revs:
333 for parent in parentrevs(r):
334 if parent not in seen:
335 schedule(visit, -parent)
336 see(parent)
329 337
330 while visit: 338 while visit:
331 for parent in parentrevs(visit.popleft()): 339 current = -nextitem(visit)
332 if parent >= stoprev and parent not in seen: 340 if current >= stoprev:
333 schedule(parent) 341 yield current
334 see(parent) 342 for parent in parentrevs(current):
335 yield parent 343 if parent not in seen:
344 schedule(visit, -parent)
345 see(parent)
336 346
337 def __contains__(self, target): 347 def __contains__(self, target):
338 """Test whether target is an ancestor of self._initrevs.""" 348 """Test whether target is an ancestor of self._initrevs."""
339 # Trying to do both __iter__ and __contains__ using the same visit 349 # Trying to do both __iter__ and __contains__ using the same visit
340 # heap and seen set is complex enough that it slows down both. Keep 350 # heap and seen set is complex enough that it slows down both. Keep