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