##// END OF EJS Templates
dagutil: fix off-by-one in inverserevlogdag buildup
Peter Arrenbrecht -
r15052:06c3667c stable
parent child Browse files
Show More
@@ -1,277 +1,277 b''
1 1 # dagutil.py - dag utilities for mercurial
2 2 #
3 3 # Copyright 2010 Benoit Boissinot <bboissin@gmail.com>
4 4 # and Peter Arrenbrecht <peter@arrenbrecht.ch>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from node import nullrev
10 10 from i18n import _
11 11
12 12
13 13 class basedag(object):
14 14 '''generic interface for DAGs
15 15
16 16 terms:
17 17 "ix" (short for index) identifies a nodes internally,
18 18 "id" identifies one externally.
19 19
20 20 All params are ixs unless explicitly suffixed otherwise.
21 21 Pluralized params are lists or sets.
22 22 '''
23 23
24 24 def __init__(self):
25 25 self._inverse = None
26 26
27 27 def nodeset(self):
28 28 '''set of all node idxs'''
29 29 raise NotImplementedError()
30 30
31 31 def heads(self):
32 32 '''list of head ixs'''
33 33 raise NotImplementedError()
34 34
35 35 def parents(self, ix):
36 36 '''list of parents ixs of ix'''
37 37 raise NotImplementedError()
38 38
39 39 def inverse(self):
40 40 '''inverse DAG, where parents becomes children, etc.'''
41 41 raise NotImplementedError()
42 42
43 43 def ancestorset(self, starts, stops=None):
44 44 '''
45 45 set of all ancestors of starts (incl), but stop walk at stops (excl)
46 46 '''
47 47 raise NotImplementedError()
48 48
49 49 def descendantset(self, starts, stops=None):
50 50 '''
51 51 set of all descendants of starts (incl), but stop walk at stops (excl)
52 52 '''
53 53 return self.inverse().ancestorset(starts, stops)
54 54
55 55 def headsetofconnecteds(self, ixs):
56 56 '''
57 57 subset of connected list of ixs so that no node has a descendant in it
58 58
59 59 By "connected list" we mean that if an ancestor and a descendant are in
60 60 the list, then so is at least one path connecting them.
61 61 '''
62 62 raise NotImplementedError()
63 63
64 64 def externalize(self, ix):
65 65 '''return a list of (or set if given a set) of node ids'''
66 66 return self._externalize(ix)
67 67
68 68 def externalizeall(self, ixs):
69 69 '''return a list of (or set if given a set) of node ids'''
70 70 ids = self._externalizeall(ixs)
71 71 if isinstance(ixs, set):
72 72 return set(ids)
73 73 return list(ids)
74 74
75 75 def internalize(self, id):
76 76 '''return a list of (or set if given a set) of node ixs'''
77 77 return self._internalize(id)
78 78
79 79 def internalizeall(self, ids, filterunknown=False):
80 80 '''return a list of (or set if given a set) of node ids'''
81 81 ixs = self._internalizeall(ids, filterunknown)
82 82 if isinstance(ids, set):
83 83 return set(ixs)
84 84 return list(ixs)
85 85
86 86
87 87 class genericdag(basedag):
88 88 '''generic implementations for DAGs'''
89 89
90 90 def ancestorset(self, starts, stops=None):
91 91 stops = stops and set(stops) or set()
92 92 seen = set()
93 93 pending = list(starts)
94 94 while pending:
95 95 n = pending.pop()
96 96 if n not in seen and n not in stops:
97 97 seen.add(n)
98 98 pending.extend(self.parents(n))
99 99 return seen
100 100
101 101 def headsetofconnecteds(self, ixs):
102 102 hds = set(ixs)
103 103 if not hds:
104 104 return hds
105 105 for n in ixs:
106 106 for p in self.parents(n):
107 107 hds.discard(p)
108 108 assert hds
109 109 return hds
110 110
111 111
112 112 class revlogbaseddag(basedag):
113 113 '''generic dag interface to a revlog'''
114 114
115 115 def __init__(self, revlog, nodeset):
116 116 basedag.__init__(self)
117 117 self._revlog = revlog
118 118 self._heads = None
119 119 self._nodeset = nodeset
120 120
121 121 def nodeset(self):
122 122 return self._nodeset
123 123
124 124 def heads(self):
125 125 if self._heads is None:
126 126 self._heads = self._getheads()
127 127 return self._heads
128 128
129 129 def _externalize(self, ix):
130 130 return self._revlog.index[ix][7]
131 131 def _externalizeall(self, ixs):
132 132 idx = self._revlog.index
133 133 return [idx[i][7] for i in ixs]
134 134
135 135 def _internalize(self, id):
136 136 ix = self._revlog.rev(id)
137 137 if ix == nullrev:
138 138 raise LookupError(id, self._revlog.indexfile, _('nullid'))
139 139 return ix
140 140 def _internalizeall(self, ids, filterunknown):
141 141 rl = self._revlog
142 142 if filterunknown:
143 143 return [r for r in map(rl.nodemap.get, ids)
144 144 if r is not None and r != nullrev]
145 145 return map(self._internalize, ids)
146 146
147 147
148 148 class revlogdag(revlogbaseddag):
149 149 '''dag interface to a revlog'''
150 150
151 151 def __init__(self, revlog):
152 152 revlogbaseddag.__init__(self, revlog, set(xrange(len(revlog))))
153 153
154 154 def _getheads(self):
155 155 return [r for r in self._revlog.headrevs() if r != nullrev]
156 156
157 157 def parents(self, ix):
158 158 rlog = self._revlog
159 159 idx = rlog.index
160 160 revdata = idx[ix]
161 161 prev = revdata[5]
162 162 if prev != nullrev:
163 163 prev2 = revdata[6]
164 164 if prev2 == nullrev:
165 165 return [prev]
166 166 return [prev, prev2]
167 167 prev2 = revdata[6]
168 168 if prev2 != nullrev:
169 169 return [prev2]
170 170 return []
171 171
172 172 def inverse(self):
173 173 if self._inverse is None:
174 174 self._inverse = inverserevlogdag(self)
175 175 return self._inverse
176 176
177 177 def ancestorset(self, starts, stops=None):
178 178 rlog = self._revlog
179 179 idx = rlog.index
180 180 stops = stops and set(stops) or set()
181 181 seen = set()
182 182 pending = list(starts)
183 183 while pending:
184 184 rev = pending.pop()
185 185 if rev not in seen and rev not in stops:
186 186 seen.add(rev)
187 187 revdata = idx[rev]
188 188 for i in [5, 6]:
189 189 prev = revdata[i]
190 190 if prev != nullrev:
191 191 pending.append(prev)
192 192 return seen
193 193
194 194 def headsetofconnecteds(self, ixs):
195 195 if not ixs:
196 196 return set()
197 197 rlog = self._revlog
198 198 idx = rlog.index
199 199 headrevs = set(ixs)
200 200 for rev in ixs:
201 201 revdata = idx[rev]
202 202 for i in [5, 6]:
203 203 prev = revdata[i]
204 204 if prev != nullrev:
205 205 headrevs.discard(prev)
206 206 assert headrevs
207 207 return headrevs
208 208
209 209 def linearize(self, ixs):
210 210 '''linearize and topologically sort a list of revisions
211 211
212 212 The linearization process tries to create long runs of revs where
213 213 a child rev comes immediately after its first parent. This is done by
214 214 visiting the heads of the given revs in inverse topological order,
215 215 and for each visited rev, visiting its second parent, then its first
216 216 parent, then adding the rev itself to the output list.
217 217 '''
218 218 sorted = []
219 219 visit = list(self.headsetofconnecteds(ixs))
220 220 visit.sort(reverse=True)
221 221 finished = set()
222 222
223 223 while visit:
224 224 cur = visit.pop()
225 225 if cur < 0:
226 226 cur = -cur - 1
227 227 if cur not in finished:
228 228 sorted.append(cur)
229 229 finished.add(cur)
230 230 else:
231 231 visit.append(-cur - 1)
232 232 visit += [p for p in self.parents(cur)
233 233 if p in ixs and p not in finished]
234 234 assert len(sorted) == len(ixs)
235 235 return sorted
236 236
237 237
238 238 class inverserevlogdag(revlogbaseddag, genericdag):
239 239 '''inverse of an existing revlog dag; see revlogdag.inverse()'''
240 240
241 241 def __init__(self, orig):
242 242 revlogbaseddag.__init__(self, orig._revlog, orig._nodeset)
243 243 self._orig = orig
244 244 self._children = {}
245 245 self._roots = []
246 246 self._walkfrom = len(self._revlog) - 1
247 247
248 248 def _walkto(self, walkto):
249 249 rev = self._walkfrom
250 250 cs = self._children
251 251 roots = self._roots
252 252 idx = self._revlog.index
253 253 while rev >= walkto:
254 254 data = idx[rev]
255 255 isroot = True
256 256 for prev in [data[5], data[6]]: # parent revs
257 257 if prev != nullrev:
258 258 cs.setdefault(prev, []).append(rev)
259 259 isroot = False
260 260 if isroot:
261 261 roots.append(rev)
262 262 rev -= 1
263 self._walkfrom = rev - 1
263 self._walkfrom = rev
264 264
265 265 def _getheads(self):
266 266 self._walkto(nullrev)
267 267 return self._roots
268 268
269 269 def parents(self, ix):
270 270 if ix is None:
271 271 return []
272 272 if ix <= self._walkfrom:
273 273 self._walkto(ix)
274 274 return self._children.get(ix, [])
275 275
276 276 def inverse(self):
277 277 return self._orig
@@ -1,268 +1,268 b''
1 1
2 2 Function to test discovery between two repos in both directions, using both the local shortcut
3 3 (which is currently not activated by default) and the full remotable protocol:
4 4
5 5 $ testdesc() { # revs_a, revs_b, dagdesc
6 6 > if [ -d foo ]; then rm -rf foo; fi
7 7 > hg init foo
8 8 > cd foo
9 9 > hg debugbuilddag "$3"
10 10 > hg clone . a $1 --quiet
11 11 > hg clone . b $2 --quiet
12 12 > echo
13 13 > echo "% -- a -> b tree"
14 14 > hg -R a debugdiscovery b --verbose --old
15 15 > echo
16 16 > echo "% -- a -> b set"
17 17 > hg -R a debugdiscovery b --verbose --debug
18 18 > echo
19 19 > echo "% -- b -> a tree"
20 20 > hg -R b debugdiscovery a --verbose --old
21 21 > echo
22 22 > echo "% -- b -> a set"
23 23 > hg -R b debugdiscovery a --verbose --debug
24 24 > cd ..
25 25 > }
26 26
27 27
28 28 Small superset:
29 29
30 30 $ testdesc '-ra1 -ra2' '-rb1 -rb2 -rb3' '
31 31 > +2:f +1:a1:b1
32 32 > <f +4 :a2
33 33 > +5 :b2
34 34 > <f +3 :b3'
35 35
36 36 % -- a -> b tree
37 37 comparing with b
38 38 searching for changes
39 39 unpruned common: b5714e113bc0 66f7d451a68b 01241442b3c2
40 40 common heads: b5714e113bc0 01241442b3c2
41 41 local is subset
42 42
43 43 % -- a -> b set
44 44 comparing with b
45 45 query 1; heads
46 46 searching for changes
47 47 all local heads known remotely
48 48 common heads: b5714e113bc0 01241442b3c2
49 49 local is subset
50 50
51 51 % -- b -> a tree
52 52 comparing with a
53 53 searching for changes
54 54 unpruned common: b5714e113bc0 01241442b3c2
55 55 common heads: b5714e113bc0 01241442b3c2
56 56 remote is subset
57 57
58 58 % -- b -> a set
59 59 comparing with a
60 60 query 1; heads
61 61 searching for changes
62 62 all remote heads known locally
63 63 common heads: b5714e113bc0 01241442b3c2
64 64 remote is subset
65 65
66 66
67 67 Many new:
68 68
69 69 $ testdesc '-ra1 -ra2' '-rb' '
70 70 > +2:f +3:a1 +3:b
71 71 > <f +30 :a2'
72 72
73 73 % -- a -> b tree
74 74 comparing with b
75 75 searching for changes
76 76 unpruned common: bebd167eb94d
77 77 common heads: bebd167eb94d
78 78
79 79 % -- a -> b set
80 80 comparing with b
81 81 query 1; heads
82 82 searching for changes
83 83 taking initial sample
84 84 searching: 2 queries
85 85 query 2; still undecided: 29, sample size is: 29
86 86 2 total queries
87 87 common heads: bebd167eb94d
88 88
89 89 % -- b -> a tree
90 90 comparing with a
91 91 searching for changes
92 92 unpruned common: bebd167eb94d 66f7d451a68b
93 93 common heads: bebd167eb94d
94 94
95 95 % -- b -> a set
96 96 comparing with a
97 97 query 1; heads
98 98 searching for changes
99 99 taking initial sample
100 100 searching: 2 queries
101 101 query 2; still undecided: 2, sample size is: 2
102 102 2 total queries
103 103 common heads: bebd167eb94d
104 104
105 105
106 106 Both sides many new with stub:
107 107
108 108 $ testdesc '-ra1 -ra2' '-rb' '
109 109 > +2:f +2:a1 +30 :b
110 110 > <f +30 :a2'
111 111
112 112 % -- a -> b tree
113 113 comparing with b
114 114 searching for changes
115 115 unpruned common: 2dc09a01254d
116 116 common heads: 2dc09a01254d
117 117
118 118 % -- a -> b set
119 119 comparing with b
120 120 query 1; heads
121 121 searching for changes
122 122 taking initial sample
123 123 searching: 2 queries
124 124 query 2; still undecided: 29, sample size is: 29
125 125 2 total queries
126 126 common heads: 2dc09a01254d
127 127
128 128 % -- b -> a tree
129 129 comparing with a
130 130 searching for changes
131 131 unpruned common: 66f7d451a68b 2dc09a01254d
132 132 common heads: 2dc09a01254d
133 133
134 134 % -- b -> a set
135 135 comparing with a
136 136 query 1; heads
137 137 searching for changes
138 138 taking initial sample
139 139 searching: 2 queries
140 140 query 2; still undecided: 29, sample size is: 29
141 141 2 total queries
142 142 common heads: 2dc09a01254d
143 143
144 144
145 145 Both many new:
146 146
147 147 $ testdesc '-ra' '-rb' '
148 148 > +2:f +30 :b
149 149 > <f +30 :a'
150 150
151 151 % -- a -> b tree
152 152 comparing with b
153 153 searching for changes
154 154 unpruned common: 66f7d451a68b
155 155 common heads: 66f7d451a68b
156 156
157 157 % -- a -> b set
158 158 comparing with b
159 159 query 1; heads
160 160 searching for changes
161 161 taking quick initial sample
162 162 searching: 2 queries
163 163 query 2; still undecided: 31, sample size is: 31
164 164 2 total queries
165 165 common heads: 66f7d451a68b
166 166
167 167 % -- b -> a tree
168 168 comparing with a
169 169 searching for changes
170 170 unpruned common: 66f7d451a68b
171 171 common heads: 66f7d451a68b
172 172
173 173 % -- b -> a set
174 174 comparing with a
175 175 query 1; heads
176 176 searching for changes
177 177 taking quick initial sample
178 178 searching: 2 queries
179 179 query 2; still undecided: 31, sample size is: 31
180 180 2 total queries
181 181 common heads: 66f7d451a68b
182 182
183 183
184 184 Both many new skewed:
185 185
186 186 $ testdesc '-ra' '-rb' '
187 187 > +2:f +30 :b
188 188 > <f +50 :a'
189 189
190 190 % -- a -> b tree
191 191 comparing with b
192 192 searching for changes
193 193 unpruned common: 66f7d451a68b
194 194 common heads: 66f7d451a68b
195 195
196 196 % -- a -> b set
197 197 comparing with b
198 198 query 1; heads
199 199 searching for changes
200 200 taking quick initial sample
201 201 searching: 2 queries
202 202 query 2; still undecided: 51, sample size is: 51
203 203 2 total queries
204 204 common heads: 66f7d451a68b
205 205
206 206 % -- b -> a tree
207 207 comparing with a
208 208 searching for changes
209 209 unpruned common: 66f7d451a68b
210 210 common heads: 66f7d451a68b
211 211
212 212 % -- b -> a set
213 213 comparing with a
214 214 query 1; heads
215 215 searching for changes
216 216 taking quick initial sample
217 217 searching: 2 queries
218 218 query 2; still undecided: 31, sample size is: 31
219 219 2 total queries
220 220 common heads: 66f7d451a68b
221 221
222 222
223 223 Both many new on top of long history:
224 224
225 225 $ testdesc '-ra' '-rb' '
226 226 > +1000:f +30 :b
227 227 > <f +50 :a'
228 228
229 229 % -- a -> b tree
230 230 comparing with b
231 231 searching for changes
232 232 unpruned common: 7ead0cba2838
233 233 common heads: 7ead0cba2838
234 234
235 235 % -- a -> b set
236 236 comparing with b
237 237 query 1; heads
238 238 searching for changes
239 239 taking quick initial sample
240 240 searching: 2 queries
241 241 query 2; still undecided: 1049, sample size is: 11
242 242 sampling from both directions
243 243 searching: 3 queries
244 244 query 3; still undecided: 31, sample size is: 31
245 245 3 total queries
246 246 common heads: 7ead0cba2838
247 247
248 248 % -- b -> a tree
249 249 comparing with a
250 250 searching for changes
251 251 unpruned common: 7ead0cba2838
252 252 common heads: 7ead0cba2838
253 253
254 254 % -- b -> a set
255 255 comparing with a
256 256 query 1; heads
257 257 searching for changes
258 258 taking quick initial sample
259 259 searching: 2 queries
260 260 query 2; still undecided: 1029, sample size is: 11
261 261 sampling from both directions
262 262 searching: 3 queries
263 query 3; still undecided: 16, sample size is: 16
263 query 3; still undecided: 15, sample size is: 15
264 264 3 total queries
265 265 common heads: 7ead0cba2838
266 266
267 267
268 268
General Comments 0
You need to be logged in to leave comments. Login now