Mercurial > hg
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 |