##// END OF EJS Templates
ancestor: unroll loop of parents in _lazyancestorsiter()...
Yuya Nishihara -
r39571:b9ee9c2e default
parent child Browse files
Show More
@@ -1,368 +1,374 b''
1 # ancestor.py - generic DAG ancestor algorithm for mercurial
1 # ancestor.py - generic DAG ancestor algorithm for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
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 heapq
10 import heapq
11
11
12 from .node import nullrev
12 from .node import nullrev
13 from . import (
13 from . import (
14 pycompat,
14 pycompat,
15 )
15 )
16
16
17 def commonancestorsheads(pfunc, *nodes):
17 def commonancestorsheads(pfunc, *nodes):
18 """Returns a set with the heads of all common ancestors of all nodes,
18 """Returns a set with the heads of all common ancestors of all nodes,
19 heads(::nodes[0] and ::nodes[1] and ...) .
19 heads(::nodes[0] and ::nodes[1] and ...) .
20
20
21 pfunc must return a list of parent vertices for a given vertex.
21 pfunc must return a list of parent vertices for a given vertex.
22 """
22 """
23 if not isinstance(nodes, set):
23 if not isinstance(nodes, set):
24 nodes = set(nodes)
24 nodes = set(nodes)
25 if nullrev in nodes:
25 if nullrev in nodes:
26 return set()
26 return set()
27 if len(nodes) <= 1:
27 if len(nodes) <= 1:
28 return nodes
28 return nodes
29
29
30 allseen = (1 << len(nodes)) - 1
30 allseen = (1 << len(nodes)) - 1
31 seen = [0] * (max(nodes) + 1)
31 seen = [0] * (max(nodes) + 1)
32 for i, n in enumerate(nodes):
32 for i, n in enumerate(nodes):
33 seen[n] = 1 << i
33 seen[n] = 1 << i
34 poison = 1 << (i + 1)
34 poison = 1 << (i + 1)
35
35
36 gca = set()
36 gca = set()
37 interesting = len(nodes)
37 interesting = len(nodes)
38 nv = len(seen) - 1
38 nv = len(seen) - 1
39 while nv >= 0 and interesting:
39 while nv >= 0 and interesting:
40 v = nv
40 v = nv
41 nv -= 1
41 nv -= 1
42 if not seen[v]:
42 if not seen[v]:
43 continue
43 continue
44 sv = seen[v]
44 sv = seen[v]
45 if sv < poison:
45 if sv < poison:
46 interesting -= 1
46 interesting -= 1
47 if sv == allseen:
47 if sv == allseen:
48 gca.add(v)
48 gca.add(v)
49 sv |= poison
49 sv |= poison
50 if v in nodes:
50 if v in nodes:
51 # history is linear
51 # history is linear
52 return {v}
52 return {v}
53 if sv < poison:
53 if sv < poison:
54 for p in pfunc(v):
54 for p in pfunc(v):
55 sp = seen[p]
55 sp = seen[p]
56 if p == nullrev:
56 if p == nullrev:
57 continue
57 continue
58 if sp == 0:
58 if sp == 0:
59 seen[p] = sv
59 seen[p] = sv
60 interesting += 1
60 interesting += 1
61 elif sp != sv:
61 elif sp != sv:
62 seen[p] |= sv
62 seen[p] |= sv
63 else:
63 else:
64 for p in pfunc(v):
64 for p in pfunc(v):
65 if p == nullrev:
65 if p == nullrev:
66 continue
66 continue
67 sp = seen[p]
67 sp = seen[p]
68 if sp and sp < poison:
68 if sp and sp < poison:
69 interesting -= 1
69 interesting -= 1
70 seen[p] = sv
70 seen[p] = sv
71 return gca
71 return gca
72
72
73 def ancestors(pfunc, *orignodes):
73 def ancestors(pfunc, *orignodes):
74 """
74 """
75 Returns the common ancestors of a and b that are furthest from a
75 Returns the common ancestors of a and b that are furthest from a
76 root (as measured by longest path).
76 root (as measured by longest path).
77
77
78 pfunc must return a list of parent vertices for a given vertex.
78 pfunc must return a list of parent vertices for a given vertex.
79 """
79 """
80 def deepest(nodes):
80 def deepest(nodes):
81 interesting = {}
81 interesting = {}
82 count = max(nodes) + 1
82 count = max(nodes) + 1
83 depth = [0] * count
83 depth = [0] * count
84 seen = [0] * count
84 seen = [0] * count
85 mapping = []
85 mapping = []
86 for (i, n) in enumerate(sorted(nodes)):
86 for (i, n) in enumerate(sorted(nodes)):
87 depth[n] = 1
87 depth[n] = 1
88 b = 1 << i
88 b = 1 << i
89 seen[n] = b
89 seen[n] = b
90 interesting[b] = 1
90 interesting[b] = 1
91 mapping.append((b, n))
91 mapping.append((b, n))
92 nv = count - 1
92 nv = count - 1
93 while nv >= 0 and len(interesting) > 1:
93 while nv >= 0 and len(interesting) > 1:
94 v = nv
94 v = nv
95 nv -= 1
95 nv -= 1
96 dv = depth[v]
96 dv = depth[v]
97 if dv == 0:
97 if dv == 0:
98 continue
98 continue
99 sv = seen[v]
99 sv = seen[v]
100 for p in pfunc(v):
100 for p in pfunc(v):
101 if p == nullrev:
101 if p == nullrev:
102 continue
102 continue
103 dp = depth[p]
103 dp = depth[p]
104 nsp = sp = seen[p]
104 nsp = sp = seen[p]
105 if dp <= dv:
105 if dp <= dv:
106 depth[p] = dv + 1
106 depth[p] = dv + 1
107 if sp != sv:
107 if sp != sv:
108 interesting[sv] += 1
108 interesting[sv] += 1
109 nsp = seen[p] = sv
109 nsp = seen[p] = sv
110 if sp:
110 if sp:
111 interesting[sp] -= 1
111 interesting[sp] -= 1
112 if interesting[sp] == 0:
112 if interesting[sp] == 0:
113 del interesting[sp]
113 del interesting[sp]
114 elif dv == dp - 1:
114 elif dv == dp - 1:
115 nsp = sp | sv
115 nsp = sp | sv
116 if nsp == sp:
116 if nsp == sp:
117 continue
117 continue
118 seen[p] = nsp
118 seen[p] = nsp
119 interesting.setdefault(nsp, 0)
119 interesting.setdefault(nsp, 0)
120 interesting[nsp] += 1
120 interesting[nsp] += 1
121 interesting[sp] -= 1
121 interesting[sp] -= 1
122 if interesting[sp] == 0:
122 if interesting[sp] == 0:
123 del interesting[sp]
123 del interesting[sp]
124 interesting[sv] -= 1
124 interesting[sv] -= 1
125 if interesting[sv] == 0:
125 if interesting[sv] == 0:
126 del interesting[sv]
126 del interesting[sv]
127
127
128 if len(interesting) != 1:
128 if len(interesting) != 1:
129 return []
129 return []
130
130
131 k = 0
131 k = 0
132 for i in interesting:
132 for i in interesting:
133 k |= i
133 k |= i
134 return set(n for (i, n) in mapping if k & i)
134 return set(n for (i, n) in mapping if k & i)
135
135
136 gca = commonancestorsheads(pfunc, *orignodes)
136 gca = commonancestorsheads(pfunc, *orignodes)
137
137
138 if len(gca) <= 1:
138 if len(gca) <= 1:
139 return gca
139 return gca
140 return deepest(gca)
140 return deepest(gca)
141
141
142 class incrementalmissingancestors(object):
142 class incrementalmissingancestors(object):
143 '''persistent state used to calculate missing ancestors incrementally
143 '''persistent state used to calculate missing ancestors incrementally
144
144
145 Although similar in spirit to lazyancestors below, this is a separate class
145 Although similar in spirit to lazyancestors below, this is a separate class
146 because trying to support contains and missingancestors operations with the
146 because trying to support contains and missingancestors operations with the
147 same internal data structures adds needless complexity.'''
147 same internal data structures adds needless complexity.'''
148 def __init__(self, pfunc, bases):
148 def __init__(self, pfunc, bases):
149 self.bases = set(bases)
149 self.bases = set(bases)
150 if not self.bases:
150 if not self.bases:
151 self.bases.add(nullrev)
151 self.bases.add(nullrev)
152 self.pfunc = pfunc
152 self.pfunc = pfunc
153
153
154 def hasbases(self):
154 def hasbases(self):
155 '''whether the common set has any non-trivial bases'''
155 '''whether the common set has any non-trivial bases'''
156 return self.bases and self.bases != {nullrev}
156 return self.bases and self.bases != {nullrev}
157
157
158 def addbases(self, newbases):
158 def addbases(self, newbases):
159 '''grow the ancestor set by adding new bases'''
159 '''grow the ancestor set by adding new bases'''
160 self.bases.update(newbases)
160 self.bases.update(newbases)
161
161
162 def removeancestorsfrom(self, revs):
162 def removeancestorsfrom(self, revs):
163 '''remove all ancestors of bases from the set revs (in place)'''
163 '''remove all ancestors of bases from the set revs (in place)'''
164 bases = self.bases
164 bases = self.bases
165 pfunc = self.pfunc
165 pfunc = self.pfunc
166 revs.difference_update(bases)
166 revs.difference_update(bases)
167 # nullrev is always an ancestor
167 # nullrev is always an ancestor
168 revs.discard(nullrev)
168 revs.discard(nullrev)
169 if not revs:
169 if not revs:
170 return
170 return
171 # anything in revs > start is definitely not an ancestor of bases
171 # anything in revs > start is definitely not an ancestor of bases
172 # revs <= start needs to be investigated
172 # revs <= start needs to be investigated
173 start = max(bases)
173 start = max(bases)
174 keepcount = sum(1 for r in revs if r > start)
174 keepcount = sum(1 for r in revs if r > start)
175 if len(revs) == keepcount:
175 if len(revs) == keepcount:
176 # no revs to consider
176 # no revs to consider
177 return
177 return
178
178
179 for curr in pycompat.xrange(start, min(revs) - 1, -1):
179 for curr in pycompat.xrange(start, min(revs) - 1, -1):
180 if curr not in bases:
180 if curr not in bases:
181 continue
181 continue
182 revs.discard(curr)
182 revs.discard(curr)
183 bases.update(pfunc(curr))
183 bases.update(pfunc(curr))
184 if len(revs) == keepcount:
184 if len(revs) == keepcount:
185 # no more potential revs to discard
185 # no more potential revs to discard
186 break
186 break
187
187
188 def missingancestors(self, revs):
188 def missingancestors(self, revs):
189 '''return all the ancestors of revs that are not ancestors of self.bases
189 '''return all the ancestors of revs that are not ancestors of self.bases
190
190
191 This may include elements from revs.
191 This may include elements from revs.
192
192
193 Equivalent to the revset (::revs - ::self.bases). Revs are returned in
193 Equivalent to the revset (::revs - ::self.bases). Revs are returned in
194 revision number order, which is a topological order.'''
194 revision number order, which is a topological order.'''
195 revsvisit = set(revs)
195 revsvisit = set(revs)
196 basesvisit = self.bases
196 basesvisit = self.bases
197 pfunc = self.pfunc
197 pfunc = self.pfunc
198 bothvisit = revsvisit.intersection(basesvisit)
198 bothvisit = revsvisit.intersection(basesvisit)
199 revsvisit.difference_update(bothvisit)
199 revsvisit.difference_update(bothvisit)
200 if not revsvisit:
200 if not revsvisit:
201 return []
201 return []
202
202
203 start = max(max(revsvisit), max(basesvisit))
203 start = max(max(revsvisit), max(basesvisit))
204 # At this point, we hold the invariants that:
204 # At this point, we hold the invariants that:
205 # - revsvisit is the set of nodes we know are an ancestor of at least
205 # - revsvisit is the set of nodes we know are an ancestor of at least
206 # one of the nodes in revs
206 # one of the nodes in revs
207 # - basesvisit is the same for bases
207 # - basesvisit is the same for bases
208 # - bothvisit is the set of nodes we know are ancestors of at least one
208 # - bothvisit is the set of nodes we know are ancestors of at least one
209 # of the nodes in revs and one of the nodes in bases. bothvisit and
209 # of the nodes in revs and one of the nodes in bases. bothvisit and
210 # revsvisit are mutually exclusive, but bothvisit is a subset of
210 # revsvisit are mutually exclusive, but bothvisit is a subset of
211 # basesvisit.
211 # basesvisit.
212 # Now we walk down in reverse topo order, adding parents of nodes
212 # Now we walk down in reverse topo order, adding parents of nodes
213 # already visited to the sets while maintaining the invariants. When a
213 # already visited to the sets while maintaining the invariants. When a
214 # node is found in both revsvisit and basesvisit, it is removed from
214 # node is found in both revsvisit and basesvisit, it is removed from
215 # revsvisit and added to bothvisit. When revsvisit becomes empty, there
215 # revsvisit and added to bothvisit. When revsvisit becomes empty, there
216 # are no more ancestors of revs that aren't also ancestors of bases, so
216 # are no more ancestors of revs that aren't also ancestors of bases, so
217 # exit.
217 # exit.
218
218
219 missing = []
219 missing = []
220 for curr in pycompat.xrange(start, nullrev, -1):
220 for curr in pycompat.xrange(start, nullrev, -1):
221 if not revsvisit:
221 if not revsvisit:
222 break
222 break
223
223
224 if curr in bothvisit:
224 if curr in bothvisit:
225 bothvisit.remove(curr)
225 bothvisit.remove(curr)
226 # curr's parents might have made it into revsvisit through
226 # curr's parents might have made it into revsvisit through
227 # another path
227 # another path
228 for p in pfunc(curr):
228 for p in pfunc(curr):
229 revsvisit.discard(p)
229 revsvisit.discard(p)
230 basesvisit.add(p)
230 basesvisit.add(p)
231 bothvisit.add(p)
231 bothvisit.add(p)
232 continue
232 continue
233
233
234 if curr in revsvisit:
234 if curr in revsvisit:
235 missing.append(curr)
235 missing.append(curr)
236 revsvisit.remove(curr)
236 revsvisit.remove(curr)
237 thisvisit = revsvisit
237 thisvisit = revsvisit
238 othervisit = basesvisit
238 othervisit = basesvisit
239 elif curr in basesvisit:
239 elif curr in basesvisit:
240 thisvisit = basesvisit
240 thisvisit = basesvisit
241 othervisit = revsvisit
241 othervisit = revsvisit
242 else:
242 else:
243 # not an ancestor of revs or bases: ignore
243 # not an ancestor of revs or bases: ignore
244 continue
244 continue
245
245
246 for p in pfunc(curr):
246 for p in pfunc(curr):
247 if p == nullrev:
247 if p == nullrev:
248 pass
248 pass
249 elif p in othervisit or p in bothvisit:
249 elif p in othervisit or p in bothvisit:
250 # p is implicitly in thisvisit. This means p is or should be
250 # p is implicitly in thisvisit. This means p is or should be
251 # in bothvisit
251 # in bothvisit
252 revsvisit.discard(p)
252 revsvisit.discard(p)
253 basesvisit.add(p)
253 basesvisit.add(p)
254 bothvisit.add(p)
254 bothvisit.add(p)
255 else:
255 else:
256 # visit later
256 # visit later
257 thisvisit.add(p)
257 thisvisit.add(p)
258
258
259 missing.reverse()
259 missing.reverse()
260 return missing
260 return missing
261
261
262 # Extracted from lazyancestors.__iter__ to avoid a reference cycle
262 # Extracted from lazyancestors.__iter__ to avoid a reference cycle
263 def _lazyancestorsiter(parentrevs, initrevs, stoprev, inclusive):
263 def _lazyancestorsiter(parentrevs, initrevs, stoprev, inclusive):
264 seen = {nullrev}
264 seen = {nullrev}
265 schedule = heapq.heappush
265 schedule = heapq.heappush
266 nextitem = heapq.heappop
266 nextitem = heapq.heappop
267 see = seen.add
267 see = seen.add
268
268
269 if inclusive:
269 if inclusive:
270 visit = [-r for r in initrevs]
270 visit = [-r for r in initrevs]
271 seen.update(initrevs)
271 seen.update(initrevs)
272 heapq.heapify(visit)
272 heapq.heapify(visit)
273 else:
273 else:
274 visit = []
274 visit = []
275 heapq.heapify(visit)
275 heapq.heapify(visit)
276 for r in initrevs:
276 for r in initrevs:
277 for parent in parentrevs(r):
277 p1, p2 = parentrevs(r)
278 if parent not in seen:
278 if p1 not in seen:
279 schedule(visit, -parent)
279 schedule(visit, -p1)
280 see(parent)
280 see(p1)
281 if p2 not in seen:
282 schedule(visit, -p2)
283 see(p2)
281
284
282 while visit:
285 while visit:
283 current = -nextitem(visit)
286 current = -nextitem(visit)
284 if current < stoprev:
287 if current < stoprev:
285 break
288 break
286 yield current
289 yield current
287 for parent in parentrevs(current):
290 p1, p2 = parentrevs(current)
288 if parent not in seen:
291 if p1 not in seen:
289 schedule(visit, -parent)
292 schedule(visit, -p1)
290 see(parent)
293 see(p1)
294 if p2 not in seen:
295 schedule(visit, -p2)
296 see(p2)
291
297
292 class lazyancestors(object):
298 class lazyancestors(object):
293 def __init__(self, pfunc, revs, stoprev=0, inclusive=False):
299 def __init__(self, pfunc, revs, stoprev=0, inclusive=False):
294 """Create a new object generating ancestors for the given revs. Does
300 """Create a new object generating ancestors for the given revs. Does
295 not generate revs lower than stoprev.
301 not generate revs lower than stoprev.
296
302
297 This is computed lazily starting from revs. The object supports
303 This is computed lazily starting from revs. The object supports
298 iteration and membership.
304 iteration and membership.
299
305
300 cl should be a changelog and revs should be an iterable. inclusive is
306 cl should be a changelog and revs should be an iterable. inclusive is
301 a boolean that indicates whether revs should be included. Revs lower
307 a boolean that indicates whether revs should be included. Revs lower
302 than stoprev will not be generated.
308 than stoprev will not be generated.
303
309
304 Result does not include the null revision."""
310 Result does not include the null revision."""
305 self._parentrevs = pfunc
311 self._parentrevs = pfunc
306 self._initrevs = revs = [r for r in revs if r >= stoprev]
312 self._initrevs = revs = [r for r in revs if r >= stoprev]
307 self._stoprev = stoprev
313 self._stoprev = stoprev
308 self._inclusive = inclusive
314 self._inclusive = inclusive
309
315
310 self._containsseen = set()
316 self._containsseen = set()
311 self._containsiter = _lazyancestorsiter(self._parentrevs,
317 self._containsiter = _lazyancestorsiter(self._parentrevs,
312 self._initrevs,
318 self._initrevs,
313 self._stoprev,
319 self._stoprev,
314 self._inclusive)
320 self._inclusive)
315
321
316 def __nonzero__(self):
322 def __nonzero__(self):
317 """False if the set is empty, True otherwise."""
323 """False if the set is empty, True otherwise."""
318 try:
324 try:
319 next(iter(self))
325 next(iter(self))
320 return True
326 return True
321 except StopIteration:
327 except StopIteration:
322 return False
328 return False
323
329
324 __bool__ = __nonzero__
330 __bool__ = __nonzero__
325
331
326 def __iter__(self):
332 def __iter__(self):
327 """Generate the ancestors of _initrevs in reverse topological order.
333 """Generate the ancestors of _initrevs in reverse topological order.
328
334
329 If inclusive is False, yield a sequence of revision numbers starting
335 If inclusive is False, yield a sequence of revision numbers starting
330 with the parents of each revision in revs, i.e., each revision is
336 with the parents of each revision in revs, i.e., each revision is
331 *not* considered an ancestor of itself. Results are emitted in reverse
337 *not* considered an ancestor of itself. Results are emitted in reverse
332 revision number order. That order is also topological: a child is
338 revision number order. That order is also topological: a child is
333 always emitted before its parent.
339 always emitted before its parent.
334
340
335 If inclusive is True, the source revisions are also yielded. The
341 If inclusive is True, the source revisions are also yielded. The
336 reverse revision number order is still enforced."""
342 reverse revision number order is still enforced."""
337 for rev in _lazyancestorsiter(self._parentrevs, self._initrevs,
343 for rev in _lazyancestorsiter(self._parentrevs, self._initrevs,
338 self._stoprev, self._inclusive):
344 self._stoprev, self._inclusive):
339 yield rev
345 yield rev
340
346
341 def __contains__(self, target):
347 def __contains__(self, target):
342 """Test whether target is an ancestor of self._initrevs."""
348 """Test whether target is an ancestor of self._initrevs."""
343 seen = self._containsseen
349 seen = self._containsseen
344 if target in seen:
350 if target in seen:
345 return True
351 return True
346 iter = self._containsiter
352 iter = self._containsiter
347 if iter is None:
353 if iter is None:
348 # Iterator exhausted
354 # Iterator exhausted
349 return False
355 return False
350 # Only integer target is valid, but some callers expect 'None in self'
356 # Only integer target is valid, but some callers expect 'None in self'
351 # to be False. So we explicitly allow it.
357 # to be False. So we explicitly allow it.
352 if target is None:
358 if target is None:
353 return False
359 return False
354
360
355 see = seen.add
361 see = seen.add
356 try:
362 try:
357 while True:
363 while True:
358 rev = next(iter)
364 rev = next(iter)
359 see(rev)
365 see(rev)
360 if rev == target:
366 if rev == target:
361 return True
367 return True
362 if rev < target:
368 if rev < target:
363 return False
369 return False
364 except StopIteration:
370 except StopIteration:
365 # Set to None to indicate fast-path can be used next time, and to
371 # Set to None to indicate fast-path can be used next time, and to
366 # free up memory.
372 # free up memory.
367 self._containsiter = None
373 self._containsiter = None
368 return False
374 return False
@@ -1,280 +1,280 b''
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import binascii
3 import binascii
4 import getopt
4 import getopt
5 import math
5 import math
6 import os
6 import os
7 import random
7 import random
8 import sys
8 import sys
9 import time
9 import time
10
10
11 from mercurial.node import nullrev
11 from mercurial.node import nullrev
12 from mercurial import (
12 from mercurial import (
13 ancestor,
13 ancestor,
14 debugcommands,
14 debugcommands,
15 hg,
15 hg,
16 pycompat,
16 pycompat,
17 ui as uimod,
17 ui as uimod,
18 util,
18 util,
19 )
19 )
20
20
21 if pycompat.ispy3:
21 if pycompat.ispy3:
22 long = int
22 long = int
23 xrange = range
23 xrange = range
24
24
25 def buildgraph(rng, nodes=100, rootprob=0.05, mergeprob=0.2, prevprob=0.7):
25 def buildgraph(rng, nodes=100, rootprob=0.05, mergeprob=0.2, prevprob=0.7):
26 '''nodes: total number of nodes in the graph
26 '''nodes: total number of nodes in the graph
27 rootprob: probability that a new node (not 0) will be a root
27 rootprob: probability that a new node (not 0) will be a root
28 mergeprob: probability that, excluding a root a node will be a merge
28 mergeprob: probability that, excluding a root a node will be a merge
29 prevprob: probability that p1 will be the previous node
29 prevprob: probability that p1 will be the previous node
30
30
31 return value is a graph represented as an adjacency list.
31 return value is a graph represented as an adjacency list.
32 '''
32 '''
33 graph = [None] * nodes
33 graph = [None] * nodes
34 for i in xrange(nodes):
34 for i in xrange(nodes):
35 if i == 0 or rng.random() < rootprob:
35 if i == 0 or rng.random() < rootprob:
36 graph[i] = [nullrev]
36 graph[i] = [nullrev]
37 elif i == 1:
37 elif i == 1:
38 graph[i] = [0]
38 graph[i] = [0]
39 elif rng.random() < mergeprob:
39 elif rng.random() < mergeprob:
40 if i == 2 or rng.random() < prevprob:
40 if i == 2 or rng.random() < prevprob:
41 # p1 is prev
41 # p1 is prev
42 p1 = i - 1
42 p1 = i - 1
43 else:
43 else:
44 p1 = rng.randrange(i - 1)
44 p1 = rng.randrange(i - 1)
45 p2 = rng.choice(list(range(0, p1)) + list(range(p1 + 1, i)))
45 p2 = rng.choice(list(range(0, p1)) + list(range(p1 + 1, i)))
46 graph[i] = [p1, p2]
46 graph[i] = [p1, p2]
47 elif rng.random() < prevprob:
47 elif rng.random() < prevprob:
48 graph[i] = [i - 1]
48 graph[i] = [i - 1]
49 else:
49 else:
50 graph[i] = [rng.randrange(i - 1)]
50 graph[i] = [rng.randrange(i - 1)]
51
51
52 return graph
52 return graph
53
53
54 def buildancestorsets(graph):
54 def buildancestorsets(graph):
55 ancs = [None] * len(graph)
55 ancs = [None] * len(graph)
56 for i in xrange(len(graph)):
56 for i in xrange(len(graph)):
57 ancs[i] = {i}
57 ancs[i] = {i}
58 if graph[i] == [nullrev]:
58 if graph[i] == [nullrev]:
59 continue
59 continue
60 for p in graph[i]:
60 for p in graph[i]:
61 ancs[i].update(ancs[p])
61 ancs[i].update(ancs[p])
62 return ancs
62 return ancs
63
63
64 class naiveincrementalmissingancestors(object):
64 class naiveincrementalmissingancestors(object):
65 def __init__(self, ancs, bases):
65 def __init__(self, ancs, bases):
66 self.ancs = ancs
66 self.ancs = ancs
67 self.bases = set(bases)
67 self.bases = set(bases)
68 def addbases(self, newbases):
68 def addbases(self, newbases):
69 self.bases.update(newbases)
69 self.bases.update(newbases)
70 def removeancestorsfrom(self, revs):
70 def removeancestorsfrom(self, revs):
71 for base in self.bases:
71 for base in self.bases:
72 if base != nullrev:
72 if base != nullrev:
73 revs.difference_update(self.ancs[base])
73 revs.difference_update(self.ancs[base])
74 revs.discard(nullrev)
74 revs.discard(nullrev)
75 def missingancestors(self, revs):
75 def missingancestors(self, revs):
76 res = set()
76 res = set()
77 for rev in revs:
77 for rev in revs:
78 if rev != nullrev:
78 if rev != nullrev:
79 res.update(self.ancs[rev])
79 res.update(self.ancs[rev])
80 for base in self.bases:
80 for base in self.bases:
81 if base != nullrev:
81 if base != nullrev:
82 res.difference_update(self.ancs[base])
82 res.difference_update(self.ancs[base])
83 return sorted(res)
83 return sorted(res)
84
84
85 def test_missingancestors(seed, rng):
85 def test_missingancestors(seed, rng):
86 # empirically observed to take around 1 second
86 # empirically observed to take around 1 second
87 graphcount = 100
87 graphcount = 100
88 testcount = 10
88 testcount = 10
89 inccount = 10
89 inccount = 10
90 nerrs = [0]
90 nerrs = [0]
91 # the default mu and sigma give us a nice distribution of mostly
91 # the default mu and sigma give us a nice distribution of mostly
92 # single-digit counts (including 0) with some higher ones
92 # single-digit counts (including 0) with some higher ones
93 def lognormrandom(mu, sigma):
93 def lognormrandom(mu, sigma):
94 return int(math.floor(rng.lognormvariate(mu, sigma)))
94 return int(math.floor(rng.lognormvariate(mu, sigma)))
95
95
96 def samplerevs(nodes, mu=1.1, sigma=0.8):
96 def samplerevs(nodes, mu=1.1, sigma=0.8):
97 count = min(lognormrandom(mu, sigma), len(nodes))
97 count = min(lognormrandom(mu, sigma), len(nodes))
98 return rng.sample(nodes, count)
98 return rng.sample(nodes, count)
99
99
100 def err(seed, graph, bases, seq, output, expected):
100 def err(seed, graph, bases, seq, output, expected):
101 if nerrs[0] == 0:
101 if nerrs[0] == 0:
102 print('seed:', hex(seed)[:-1], file=sys.stderr)
102 print('seed:', hex(seed)[:-1], file=sys.stderr)
103 if gerrs[0] == 0:
103 if gerrs[0] == 0:
104 print('graph:', graph, file=sys.stderr)
104 print('graph:', graph, file=sys.stderr)
105 print('* bases:', bases, file=sys.stderr)
105 print('* bases:', bases, file=sys.stderr)
106 print('* seq: ', seq, file=sys.stderr)
106 print('* seq: ', seq, file=sys.stderr)
107 print('* output: ', output, file=sys.stderr)
107 print('* output: ', output, file=sys.stderr)
108 print('* expected:', expected, file=sys.stderr)
108 print('* expected:', expected, file=sys.stderr)
109 nerrs[0] += 1
109 nerrs[0] += 1
110 gerrs[0] += 1
110 gerrs[0] += 1
111
111
112 for g in xrange(graphcount):
112 for g in xrange(graphcount):
113 graph = buildgraph(rng)
113 graph = buildgraph(rng)
114 ancs = buildancestorsets(graph)
114 ancs = buildancestorsets(graph)
115 gerrs = [0]
115 gerrs = [0]
116 for _ in xrange(testcount):
116 for _ in xrange(testcount):
117 # start from nullrev to include it as a possibility
117 # start from nullrev to include it as a possibility
118 graphnodes = range(nullrev, len(graph))
118 graphnodes = range(nullrev, len(graph))
119 bases = samplerevs(graphnodes)
119 bases = samplerevs(graphnodes)
120
120
121 # fast algorithm
121 # fast algorithm
122 inc = ancestor.incrementalmissingancestors(graph.__getitem__, bases)
122 inc = ancestor.incrementalmissingancestors(graph.__getitem__, bases)
123 # reference slow algorithm
123 # reference slow algorithm
124 naiveinc = naiveincrementalmissingancestors(ancs, bases)
124 naiveinc = naiveincrementalmissingancestors(ancs, bases)
125 seq = []
125 seq = []
126 revs = []
126 revs = []
127 for _ in xrange(inccount):
127 for _ in xrange(inccount):
128 if rng.random() < 0.2:
128 if rng.random() < 0.2:
129 newbases = samplerevs(graphnodes)
129 newbases = samplerevs(graphnodes)
130 seq.append(('addbases', newbases))
130 seq.append(('addbases', newbases))
131 inc.addbases(newbases)
131 inc.addbases(newbases)
132 naiveinc.addbases(newbases)
132 naiveinc.addbases(newbases)
133 if rng.random() < 0.4:
133 if rng.random() < 0.4:
134 # larger set so that there are more revs to remove from
134 # larger set so that there are more revs to remove from
135 revs = samplerevs(graphnodes, mu=1.5)
135 revs = samplerevs(graphnodes, mu=1.5)
136 seq.append(('removeancestorsfrom', revs))
136 seq.append(('removeancestorsfrom', revs))
137 hrevs = set(revs)
137 hrevs = set(revs)
138 rrevs = set(revs)
138 rrevs = set(revs)
139 inc.removeancestorsfrom(hrevs)
139 inc.removeancestorsfrom(hrevs)
140 naiveinc.removeancestorsfrom(rrevs)
140 naiveinc.removeancestorsfrom(rrevs)
141 if hrevs != rrevs:
141 if hrevs != rrevs:
142 err(seed, graph, bases, seq, sorted(hrevs),
142 err(seed, graph, bases, seq, sorted(hrevs),
143 sorted(rrevs))
143 sorted(rrevs))
144 else:
144 else:
145 revs = samplerevs(graphnodes)
145 revs = samplerevs(graphnodes)
146 seq.append(('missingancestors', revs))
146 seq.append(('missingancestors', revs))
147 h = inc.missingancestors(revs)
147 h = inc.missingancestors(revs)
148 r = naiveinc.missingancestors(revs)
148 r = naiveinc.missingancestors(revs)
149 if h != r:
149 if h != r:
150 err(seed, graph, bases, seq, h, r)
150 err(seed, graph, bases, seq, h, r)
151
151
152 # graph is a dict of child->parent adjacency lists for this graph:
152 # graph is a dict of child->parent adjacency lists for this graph:
153 # o 13
153 # o 13
154 # |
154 # |
155 # | o 12
155 # | o 12
156 # | |
156 # | |
157 # | | o 11
157 # | | o 11
158 # | | |\
158 # | | |\
159 # | | | | o 10
159 # | | | | o 10
160 # | | | | |
160 # | | | | |
161 # | o---+ | 9
161 # | o---+ | 9
162 # | | | | |
162 # | | | | |
163 # o | | | | 8
163 # o | | | | 8
164 # / / / /
164 # / / / /
165 # | | o | 7
165 # | | o | 7
166 # | | | |
166 # | | | |
167 # o---+ | 6
167 # o---+ | 6
168 # / / /
168 # / / /
169 # | | o 5
169 # | | o 5
170 # | |/
170 # | |/
171 # | o 4
171 # | o 4
172 # | |
172 # | |
173 # o | 3
173 # o | 3
174 # | |
174 # | |
175 # | o 2
175 # | o 2
176 # |/
176 # |/
177 # o 1
177 # o 1
178 # |
178 # |
179 # o 0
179 # o 0
180
180
181 graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
181 graph = {0: [-1, -1], 1: [0, -1], 2: [1, -1], 3: [1, -1], 4: [2, -1],
182 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
182 5: [4, -1], 6: [4, -1], 7: [4, -1], 8: [-1, -1], 9: [6, 7],
183 13: [8]}
183 10: [5, -1], 11: [3, 7], 12: [9, -1], 13: [8, -1]}
184
184
185 def genlazyancestors(revs, stoprev=0, inclusive=False):
185 def genlazyancestors(revs, stoprev=0, inclusive=False):
186 print(("%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" %
186 print(("%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" %
187 (revs, stoprev, inclusive)))
187 (revs, stoprev, inclusive)))
188 return ancestor.lazyancestors(graph.get, revs, stoprev=stoprev,
188 return ancestor.lazyancestors(graph.get, revs, stoprev=stoprev,
189 inclusive=inclusive)
189 inclusive=inclusive)
190
190
191 def printlazyancestors(s, l):
191 def printlazyancestors(s, l):
192 print('membership: %r' % [n for n in l if n in s])
192 print('membership: %r' % [n for n in l if n in s])
193 print('iteration: %r' % list(s))
193 print('iteration: %r' % list(s))
194
194
195 def test_lazyancestors():
195 def test_lazyancestors():
196 # Empty revs
196 # Empty revs
197 s = genlazyancestors([])
197 s = genlazyancestors([])
198 printlazyancestors(s, [3, 0, -1])
198 printlazyancestors(s, [3, 0, -1])
199
199
200 # Standard example
200 # Standard example
201 s = genlazyancestors([11, 13])
201 s = genlazyancestors([11, 13])
202 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
202 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
203
203
204 # Standard with ancestry in the initial set (1 is ancestor of 3)
204 # Standard with ancestry in the initial set (1 is ancestor of 3)
205 s = genlazyancestors([1, 3])
205 s = genlazyancestors([1, 3])
206 printlazyancestors(s, [1, -1, 0])
206 printlazyancestors(s, [1, -1, 0])
207
207
208 # Including revs
208 # Including revs
209 s = genlazyancestors([11, 13], inclusive=True)
209 s = genlazyancestors([11, 13], inclusive=True)
210 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
210 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
211
211
212 # Test with stoprev
212 # Test with stoprev
213 s = genlazyancestors([11, 13], stoprev=6)
213 s = genlazyancestors([11, 13], stoprev=6)
214 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
214 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
215 s = genlazyancestors([11, 13], stoprev=6, inclusive=True)
215 s = genlazyancestors([11, 13], stoprev=6, inclusive=True)
216 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
216 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
217
217
218 # Test with stoprev >= min(initrevs)
218 # Test with stoprev >= min(initrevs)
219 s = genlazyancestors([11, 13], stoprev=11, inclusive=True)
219 s = genlazyancestors([11, 13], stoprev=11, inclusive=True)
220 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
220 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
221 s = genlazyancestors([11, 13], stoprev=12, inclusive=True)
221 s = genlazyancestors([11, 13], stoprev=12, inclusive=True)
222 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
222 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
223
223
224 # The C gca algorithm requires a real repo. These are textual descriptions of
224 # The C gca algorithm requires a real repo. These are textual descriptions of
225 # DAGs that have been known to be problematic, and, optionally, known pairs
225 # DAGs that have been known to be problematic, and, optionally, known pairs
226 # of revisions and their expected ancestor list.
226 # of revisions and their expected ancestor list.
227 dagtests = [
227 dagtests = [
228 (b'+2*2*2/*3/2', {}),
228 (b'+2*2*2/*3/2', {}),
229 (b'+3*3/*2*2/*4*4/*4/2*4/2*2', {}),
229 (b'+3*3/*2*2/*4*4/*4/2*4/2*2', {}),
230 (b'+2*2*/2*4*/4*/3*2/4', {(6, 7): [3, 5]}),
230 (b'+2*2*/2*4*/4*/3*2/4', {(6, 7): [3, 5]}),
231 ]
231 ]
232 def test_gca():
232 def test_gca():
233 u = uimod.ui.load()
233 u = uimod.ui.load()
234 for i, (dag, tests) in enumerate(dagtests):
234 for i, (dag, tests) in enumerate(dagtests):
235 repo = hg.repository(u, b'gca%d' % i, create=1)
235 repo = hg.repository(u, b'gca%d' % i, create=1)
236 cl = repo.changelog
236 cl = repo.changelog
237 if not util.safehasattr(cl.index, 'ancestors'):
237 if not util.safehasattr(cl.index, 'ancestors'):
238 # C version not available
238 # C version not available
239 return
239 return
240
240
241 debugcommands.debugbuilddag(u, repo, dag)
241 debugcommands.debugbuilddag(u, repo, dag)
242 # Compare the results of the Python and C versions. This does not
242 # Compare the results of the Python and C versions. This does not
243 # include choosing a winner when more than one gca exists -- we make
243 # include choosing a winner when more than one gca exists -- we make
244 # sure both return exactly the same set of gcas.
244 # sure both return exactly the same set of gcas.
245 # Also compare against expected results, if available.
245 # Also compare against expected results, if available.
246 for a in cl:
246 for a in cl:
247 for b in cl:
247 for b in cl:
248 cgcas = sorted(cl.index.ancestors(a, b))
248 cgcas = sorted(cl.index.ancestors(a, b))
249 pygcas = sorted(ancestor.ancestors(cl.parentrevs, a, b))
249 pygcas = sorted(ancestor.ancestors(cl.parentrevs, a, b))
250 expected = None
250 expected = None
251 if (a, b) in tests:
251 if (a, b) in tests:
252 expected = tests[(a, b)]
252 expected = tests[(a, b)]
253 if cgcas != pygcas or (expected and cgcas != expected):
253 if cgcas != pygcas or (expected and cgcas != expected):
254 print("test_gca: for dag %s, gcas for %d, %d:"
254 print("test_gca: for dag %s, gcas for %d, %d:"
255 % (dag, a, b))
255 % (dag, a, b))
256 print(" C returned: %s" % cgcas)
256 print(" C returned: %s" % cgcas)
257 print(" Python returned: %s" % pygcas)
257 print(" Python returned: %s" % pygcas)
258 if expected:
258 if expected:
259 print(" expected: %s" % expected)
259 print(" expected: %s" % expected)
260
260
261 def main():
261 def main():
262 seed = None
262 seed = None
263 opts, args = getopt.getopt(sys.argv[1:], 's:', ['seed='])
263 opts, args = getopt.getopt(sys.argv[1:], 's:', ['seed='])
264 for o, a in opts:
264 for o, a in opts:
265 if o in ('-s', '--seed'):
265 if o in ('-s', '--seed'):
266 seed = long(a, base=0) # accepts base 10 or 16 strings
266 seed = long(a, base=0) # accepts base 10 or 16 strings
267
267
268 if seed is None:
268 if seed is None:
269 try:
269 try:
270 seed = long(binascii.hexlify(os.urandom(16)), 16)
270 seed = long(binascii.hexlify(os.urandom(16)), 16)
271 except AttributeError:
271 except AttributeError:
272 seed = long(time.time() * 1000)
272 seed = long(time.time() * 1000)
273
273
274 rng = random.Random(seed)
274 rng = random.Random(seed)
275 test_missingancestors(seed, rng)
275 test_missingancestors(seed, rng)
276 test_lazyancestors()
276 test_lazyancestors()
277 test_gca()
277 test_gca()
278
278
279 if __name__ == '__main__':
279 if __name__ == '__main__':
280 main()
280 main()
General Comments 0
You need to be logged in to leave comments. Login now