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