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