##// END OF EJS Templates
ancestor: fix a comment (followup to 0b03454abae7)
Siddharth Agarwal -
r17976:09d5681d default
parent child Browse files
Show More
@@ -1,255 +1,255
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 from node import nullrev
9 from node import nullrev
10
10
11 def ancestor(a, b, pfunc):
11 def ancestor(a, b, pfunc):
12 """
12 """
13 Returns the common ancestor of a and b that is furthest from a
13 Returns the common ancestor of a and b that is furthest from a
14 root (as measured by longest path) or None if no ancestor is
14 root (as measured by longest path) or None if no ancestor is
15 found. If there are multiple common ancestors at the same
15 found. If there are multiple common ancestors at the same
16 distance, the first one found is returned.
16 distance, the first one found is returned.
17
17
18 pfunc must return a list of parent vertices for a given vertex
18 pfunc must return a list of parent vertices for a given vertex
19 """
19 """
20
20
21 if a == b:
21 if a == b:
22 return a
22 return a
23
23
24 a, b = sorted([a, b])
24 a, b = sorted([a, b])
25
25
26 # find depth from root of all ancestors
26 # find depth from root of all ancestors
27 # depth is stored as a negative for heapq
27 # depth is stored as a negative for heapq
28 parentcache = {}
28 parentcache = {}
29 visit = [a, b]
29 visit = [a, b]
30 depth = {}
30 depth = {}
31 while visit:
31 while visit:
32 vertex = visit[-1]
32 vertex = visit[-1]
33 pl = pfunc(vertex)
33 pl = pfunc(vertex)
34 parentcache[vertex] = pl
34 parentcache[vertex] = pl
35 if not pl:
35 if not pl:
36 depth[vertex] = 0
36 depth[vertex] = 0
37 visit.pop()
37 visit.pop()
38 else:
38 else:
39 for p in pl:
39 for p in pl:
40 if p == a or p == b: # did we find a or b as a parent?
40 if p == a or p == b: # did we find a or b as a parent?
41 return p # we're done
41 return p # we're done
42 if p not in depth:
42 if p not in depth:
43 visit.append(p)
43 visit.append(p)
44 if visit[-1] == vertex:
44 if visit[-1] == vertex:
45 # -(maximum distance of parents + 1)
45 # -(maximum distance of parents + 1)
46 depth[vertex] = min([depth[p] for p in pl]) - 1
46 depth[vertex] = min([depth[p] for p in pl]) - 1
47 visit.pop()
47 visit.pop()
48
48
49 # traverse ancestors in order of decreasing distance from root
49 # traverse ancestors in order of decreasing distance from root
50 def ancestors(vertex):
50 def ancestors(vertex):
51 h = [(depth[vertex], vertex)]
51 h = [(depth[vertex], vertex)]
52 seen = set()
52 seen = set()
53 while h:
53 while h:
54 d, n = heapq.heappop(h)
54 d, n = heapq.heappop(h)
55 if n not in seen:
55 if n not in seen:
56 seen.add(n)
56 seen.add(n)
57 yield (d, n)
57 yield (d, n)
58 for p in parentcache[n]:
58 for p in parentcache[n]:
59 heapq.heappush(h, (depth[p], p))
59 heapq.heappush(h, (depth[p], p))
60
60
61 def generations(vertex):
61 def generations(vertex):
62 sg, s = None, set()
62 sg, s = None, set()
63 for g, v in ancestors(vertex):
63 for g, v in ancestors(vertex):
64 if g != sg:
64 if g != sg:
65 if sg:
65 if sg:
66 yield sg, s
66 yield sg, s
67 sg, s = g, set((v,))
67 sg, s = g, set((v,))
68 else:
68 else:
69 s.add(v)
69 s.add(v)
70 yield sg, s
70 yield sg, s
71
71
72 x = generations(a)
72 x = generations(a)
73 y = generations(b)
73 y = generations(b)
74 gx = x.next()
74 gx = x.next()
75 gy = y.next()
75 gy = y.next()
76
76
77 # increment each ancestor list until it is closer to root than
77 # increment each ancestor list until it is closer to root than
78 # the other, or they match
78 # the other, or they match
79 try:
79 try:
80 while True:
80 while True:
81 if gx[0] == gy[0]:
81 if gx[0] == gy[0]:
82 for v in gx[1]:
82 for v in gx[1]:
83 if v in gy[1]:
83 if v in gy[1]:
84 return v
84 return v
85 gy = y.next()
85 gy = y.next()
86 gx = x.next()
86 gx = x.next()
87 elif gx[0] > gy[0]:
87 elif gx[0] > gy[0]:
88 gy = y.next()
88 gy = y.next()
89 else:
89 else:
90 gx = x.next()
90 gx = x.next()
91 except StopIteration:
91 except StopIteration:
92 return None
92 return None
93
93
94 def missingancestors(revs, bases, pfunc):
94 def missingancestors(revs, bases, pfunc):
95 """Return all the ancestors of revs that are not ancestors of bases.
95 """Return all the ancestors of revs that are not ancestors of bases.
96
96
97 This may include elements from revs.
97 This may include elements from revs.
98
98
99 Equivalent to the revset (::revs - ::bases). Revs are returned in
99 Equivalent to the revset (::revs - ::bases). Revs are returned in
100 revision number order, which is a topological order.
100 revision number order, which is a topological order.
101
101
102 revs and bases should both be iterables. pfunc must return a list of
102 revs and bases should both be iterables. pfunc must return a list of
103 parent revs for a given revs.
103 parent revs for a given revs.
104
104
105 graph is a dict of child->parent adjacency lists for this graph:
105 graph is a dict of child->parent adjacency lists for this graph:
106 o 13
106 o 13
107 |
107 |
108 | o 12
108 | o 12
109 | |
109 | |
110 | | o 11
110 | | o 11
111 | | |\
111 | | |\
112 | | | | o 10
112 | | | | o 10
113 | | | | |
113 | | | | |
114 | o---+ | 9
114 | o---+ | 9
115 | | | | |
115 | | | | |
116 o | | | | 8
116 o | | | | 8
117 / / / /
117 / / / /
118 | | o | 7
118 | | o | 7
119 | | | |
119 | | | |
120 o---+ | 6
120 o---+ | 6
121 / / /
121 / / /
122 | | o 5
122 | | o 5
123 | |/
123 | |/
124 | o 4
124 | o 4
125 | |
125 | |
126 o | 3
126 o | 3
127 | |
127 | |
128 | o 2
128 | o 2
129 |/
129 |/
130 o 1
130 o 1
131 |
131 |
132 o 0
132 o 0
133 >>> graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
133 >>> graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
134 ... 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
134 ... 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
135 ... 13: [8]}
135 ... 13: [8]}
136 >>> pfunc = graph.get
136 >>> pfunc = graph.get
137
137
138 Empty revs
138 Empty revs
139 >>> missingancestors([], [1], pfunc)
139 >>> missingancestors([], [1], pfunc)
140 []
140 []
141 >>> missingancestors([], [], pfunc)
141 >>> missingancestors([], [], pfunc)
142 []
142 []
143
143
144 If bases is empty, it's the same as if it were [nullrev]
144 If bases is empty, it's the same as if it were [nullrev]
145 >>> missingancestors([12], [], pfunc)
145 >>> missingancestors([12], [], pfunc)
146 [0, 1, 2, 4, 6, 7, 9, 12]
146 [0, 1, 2, 4, 6, 7, 9, 12]
147
147
148 Trivial case: revs == bases
148 Trivial case: revs == bases
149 >>> missingancestors([0], [0], pfunc)
149 >>> missingancestors([0], [0], pfunc)
150 []
150 []
151 >>> missingancestors([4, 5, 6], [6, 5, 4], pfunc)
151 >>> missingancestors([4, 5, 6], [6, 5, 4], pfunc)
152 []
152 []
153
153
154 With nullrev
154 With nullrev
155 >>> missingancestors([-1], [12], pfunc)
155 >>> missingancestors([-1], [12], pfunc)
156 []
156 []
157 >>> missingancestors([12], [-1], pfunc)
157 >>> missingancestors([12], [-1], pfunc)
158 [0, 1, 2, 4, 6, 7, 9, 12]
158 [0, 1, 2, 4, 6, 7, 9, 12]
159
159
160 9 is a parent of 12. 7 is a parent of 9, so an ancestor of 12. 6 is an
160 9 is a parent of 12. 7 is a parent of 9, so an ancestor of 12. 6 is an
161 ancestor of 12 but not of 7.
161 ancestor of 12 but not of 7.
162 >>> missingancestors([12], [9], pfunc)
162 >>> missingancestors([12], [9], pfunc)
163 [12]
163 [12]
164 >>> missingancestors([9], [12], pfunc)
164 >>> missingancestors([9], [12], pfunc)
165 []
165 []
166 >>> missingancestors([12, 9], [7], pfunc)
166 >>> missingancestors([12, 9], [7], pfunc)
167 [6, 9, 12]
167 [6, 9, 12]
168 >>> missingancestors([7, 6], [12], pfunc)
168 >>> missingancestors([7, 6], [12], pfunc)
169 []
169 []
170
170
171 More complex cases
171 More complex cases
172 >>> missingancestors([10], [11, 12], pfunc)
172 >>> missingancestors([10], [11, 12], pfunc)
173 [5, 10]
173 [5, 10]
174 >>> missingancestors([11], [10], pfunc)
174 >>> missingancestors([11], [10], pfunc)
175 [3, 7, 11]
175 [3, 7, 11]
176 >>> missingancestors([11], [10, 12], pfunc)
176 >>> missingancestors([11], [10, 12], pfunc)
177 [3, 11]
177 [3, 11]
178 >>> missingancestors([12], [10], pfunc)
178 >>> missingancestors([12], [10], pfunc)
179 [6, 7, 9, 12]
179 [6, 7, 9, 12]
180 >>> missingancestors([12], [11], pfunc)
180 >>> missingancestors([12], [11], pfunc)
181 [6, 9, 12]
181 [6, 9, 12]
182 >>> missingancestors([10, 11, 12], [13], pfunc)
182 >>> missingancestors([10, 11, 12], [13], pfunc)
183 [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12]
183 [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12]
184 >>> missingancestors([13], [10, 11, 12], pfunc)
184 >>> missingancestors([13], [10, 11, 12], pfunc)
185 [8, 13]
185 [8, 13]
186 """
186 """
187
187
188 revsvisit = set(revs)
188 revsvisit = set(revs)
189 basesvisit = set(bases)
189 basesvisit = set(bases)
190 if not revsvisit:
190 if not revsvisit:
191 return []
191 return []
192 if not basesvisit:
192 if not basesvisit:
193 basesvisit.add(nullrev)
193 basesvisit.add(nullrev)
194 start = max(max(revsvisit), max(basesvisit))
194 start = max(max(revsvisit), max(basesvisit))
195 bothvisit = revsvisit.intersection(basesvisit)
195 bothvisit = revsvisit.intersection(basesvisit)
196 revsvisit.difference_update(bothvisit)
196 revsvisit.difference_update(bothvisit)
197 basesvisit.difference_update(bothvisit)
197 basesvisit.difference_update(bothvisit)
198 # At this point, we hold the invariants that:
198 # At this point, we hold the invariants that:
199 # - revsvisit is the set of nodes we know are an ancestor of at least one
199 # - revsvisit is the set of nodes we know are an ancestor of at least one
200 # of the nodes in revs
200 # of the nodes in revs
201 # - basesvisit is the same for bases
201 # - basesvisit is the same for bases
202 # - bothvisit is the set of nodes we know are ancestors of at least one of
202 # - bothvisit is the set of nodes we know are ancestors of at least one of
203 # the nodes in revs and one of the nodes in bases
203 # the nodes in revs and one of the nodes in bases
204 # - a node may be in none or one, but not more, of revsvisit, basesvisit
204 # - a node may be in none or one, but not more, of revsvisit, basesvisit
205 # and bothvisit at any given time
205 # and bothvisit at any given time
206 # Now we walk down in reverse topo order, adding parents of nodes already
206 # Now we walk down in reverse topo order, adding parents of nodes already
207 # visited to the sets while maintaining the invariants. When a node is
207 # visited to the sets while maintaining the invariants. When a node is
208 # found in both revsvisit and basesvisit, it is removed from them and
208 # found in both revsvisit and basesvisit, it is removed from them and
209 # added to bothvisit instead. When revsvisit becomes empty, there are no
209 # added to bothvisit instead. When revsvisit becomes empty, there are no
210 # more ancestors of revs that aren't also ancestors of bases, so exit.
210 # more ancestors of revs that aren't also ancestors of bases, so exit.
211
211
212 missing = []
212 missing = []
213 for curr in xrange(start, nullrev, -1):
213 for curr in xrange(start, nullrev, -1):
214 if not revsvisit:
214 if not revsvisit:
215 break
215 break
216
216
217 if curr in bothvisit:
217 if curr in bothvisit:
218 bothvisit.remove(curr)
218 bothvisit.remove(curr)
219 # curr's parents might have made it into revsvisit or basesvisit
219 # curr's parents might have made it into revsvisit or basesvisit
220 # through another path
220 # through another path
221 for p in pfunc(curr):
221 for p in pfunc(curr):
222 revsvisit.discard(p)
222 revsvisit.discard(p)
223 basesvisit.discard(p)
223 basesvisit.discard(p)
224 bothvisit.add(p)
224 bothvisit.add(p)
225 continue
225 continue
226
226
227 # curr will never be in both revsvisit and basesvisit, since if it
227 # curr will never be in both revsvisit and basesvisit, since if it
228 # were it'd have been pushed to bothvisit
228 # were it'd have been pushed to bothvisit
229 if curr in revsvisit:
229 if curr in revsvisit:
230 missing.append(curr)
230 missing.append(curr)
231 thisvisit = revsvisit
231 thisvisit = revsvisit
232 othervisit = basesvisit
232 othervisit = basesvisit
233 elif curr in basesvisit:
233 elif curr in basesvisit:
234 thisvisit = basesvisit
234 thisvisit = basesvisit
235 othervisit = revsvisit
235 othervisit = revsvisit
236 else:
236 else:
237 # not an ancestor of a or b: ignore
237 # not an ancestor of revs or bases: ignore
238 continue
238 continue
239
239
240 thisvisit.remove(curr)
240 thisvisit.remove(curr)
241 for p in pfunc(curr):
241 for p in pfunc(curr):
242 if p == nullrev:
242 if p == nullrev:
243 pass
243 pass
244 elif p in othervisit or p in bothvisit:
244 elif p in othervisit or p in bothvisit:
245 # 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
246 # in bothvisit
246 # in bothvisit
247 revsvisit.discard(p)
247 revsvisit.discard(p)
248 basesvisit.discard(p)
248 basesvisit.discard(p)
249 bothvisit.add(p)
249 bothvisit.add(p)
250 else:
250 else:
251 # visit later
251 # visit later
252 thisvisit.add(p)
252 thisvisit.add(p)
253
253
254 missing.reverse()
254 missing.reverse()
255 return missing
255 return missing
General Comments 0
You need to be logged in to leave comments. Login now