##// END OF EJS Templates
smartset: preserve istopo for baseset operations...
Jun Wu -
r31066:c962bb6a default
parent child Browse files
Show More
@@ -1,1047 +1,1056 b''
1 # smartset.py - data structure for revision set
1 # smartset.py - data structure for revision set
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from . import (
10 from . import (
11 util,
11 util,
12 )
12 )
13
13
14 def _formatsetrepr(r):
14 def _formatsetrepr(r):
15 """Format an optional printable representation of a set
15 """Format an optional printable representation of a set
16
16
17 ======== =================================
17 ======== =================================
18 type(r) example
18 type(r) example
19 ======== =================================
19 ======== =================================
20 tuple ('<not %r>', other)
20 tuple ('<not %r>', other)
21 str '<branch closed>'
21 str '<branch closed>'
22 callable lambda: '<branch %r>' % sorted(b)
22 callable lambda: '<branch %r>' % sorted(b)
23 object other
23 object other
24 ======== =================================
24 ======== =================================
25 """
25 """
26 if r is None:
26 if r is None:
27 return ''
27 return ''
28 elif isinstance(r, tuple):
28 elif isinstance(r, tuple):
29 return r[0] % r[1:]
29 return r[0] % r[1:]
30 elif isinstance(r, str):
30 elif isinstance(r, str):
31 return r
31 return r
32 elif callable(r):
32 elif callable(r):
33 return r()
33 return r()
34 else:
34 else:
35 return repr(r)
35 return repr(r)
36
36
37 class abstractsmartset(object):
37 class abstractsmartset(object):
38
38
39 def __nonzero__(self):
39 def __nonzero__(self):
40 """True if the smartset is not empty"""
40 """True if the smartset is not empty"""
41 raise NotImplementedError()
41 raise NotImplementedError()
42
42
43 def __contains__(self, rev):
43 def __contains__(self, rev):
44 """provide fast membership testing"""
44 """provide fast membership testing"""
45 raise NotImplementedError()
45 raise NotImplementedError()
46
46
47 def __iter__(self):
47 def __iter__(self):
48 """iterate the set in the order it is supposed to be iterated"""
48 """iterate the set in the order it is supposed to be iterated"""
49 raise NotImplementedError()
49 raise NotImplementedError()
50
50
51 # Attributes containing a function to perform a fast iteration in a given
51 # Attributes containing a function to perform a fast iteration in a given
52 # direction. A smartset can have none, one, or both defined.
52 # direction. A smartset can have none, one, or both defined.
53 #
53 #
54 # Default value is None instead of a function returning None to avoid
54 # Default value is None instead of a function returning None to avoid
55 # initializing an iterator just for testing if a fast method exists.
55 # initializing an iterator just for testing if a fast method exists.
56 fastasc = None
56 fastasc = None
57 fastdesc = None
57 fastdesc = None
58
58
59 def isascending(self):
59 def isascending(self):
60 """True if the set will iterate in ascending order"""
60 """True if the set will iterate in ascending order"""
61 raise NotImplementedError()
61 raise NotImplementedError()
62
62
63 def isdescending(self):
63 def isdescending(self):
64 """True if the set will iterate in descending order"""
64 """True if the set will iterate in descending order"""
65 raise NotImplementedError()
65 raise NotImplementedError()
66
66
67 def istopo(self):
67 def istopo(self):
68 """True if the set will iterate in topographical order"""
68 """True if the set will iterate in topographical order"""
69 raise NotImplementedError()
69 raise NotImplementedError()
70
70
71 def min(self):
71 def min(self):
72 """return the minimum element in the set"""
72 """return the minimum element in the set"""
73 if self.fastasc is None:
73 if self.fastasc is None:
74 v = min(self)
74 v = min(self)
75 else:
75 else:
76 for v in self.fastasc():
76 for v in self.fastasc():
77 break
77 break
78 else:
78 else:
79 raise ValueError('arg is an empty sequence')
79 raise ValueError('arg is an empty sequence')
80 self.min = lambda: v
80 self.min = lambda: v
81 return v
81 return v
82
82
83 def max(self):
83 def max(self):
84 """return the maximum element in the set"""
84 """return the maximum element in the set"""
85 if self.fastdesc is None:
85 if self.fastdesc is None:
86 return max(self)
86 return max(self)
87 else:
87 else:
88 for v in self.fastdesc():
88 for v in self.fastdesc():
89 break
89 break
90 else:
90 else:
91 raise ValueError('arg is an empty sequence')
91 raise ValueError('arg is an empty sequence')
92 self.max = lambda: v
92 self.max = lambda: v
93 return v
93 return v
94
94
95 def first(self):
95 def first(self):
96 """return the first element in the set (user iteration perspective)
96 """return the first element in the set (user iteration perspective)
97
97
98 Return None if the set is empty"""
98 Return None if the set is empty"""
99 raise NotImplementedError()
99 raise NotImplementedError()
100
100
101 def last(self):
101 def last(self):
102 """return the last element in the set (user iteration perspective)
102 """return the last element in the set (user iteration perspective)
103
103
104 Return None if the set is empty"""
104 Return None if the set is empty"""
105 raise NotImplementedError()
105 raise NotImplementedError()
106
106
107 def __len__(self):
107 def __len__(self):
108 """return the length of the smartsets
108 """return the length of the smartsets
109
109
110 This can be expensive on smartset that could be lazy otherwise."""
110 This can be expensive on smartset that could be lazy otherwise."""
111 raise NotImplementedError()
111 raise NotImplementedError()
112
112
113 def reverse(self):
113 def reverse(self):
114 """reverse the expected iteration order"""
114 """reverse the expected iteration order"""
115 raise NotImplementedError()
115 raise NotImplementedError()
116
116
117 def sort(self, reverse=True):
117 def sort(self, reverse=True):
118 """get the set to iterate in an ascending or descending order"""
118 """get the set to iterate in an ascending or descending order"""
119 raise NotImplementedError()
119 raise NotImplementedError()
120
120
121 def __and__(self, other):
121 def __and__(self, other):
122 """Returns a new object with the intersection of the two collections.
122 """Returns a new object with the intersection of the two collections.
123
123
124 This is part of the mandatory API for smartset."""
124 This is part of the mandatory API for smartset."""
125 if isinstance(other, fullreposet):
125 if isinstance(other, fullreposet):
126 return self
126 return self
127 return self.filter(other.__contains__, condrepr=other, cache=False)
127 return self.filter(other.__contains__, condrepr=other, cache=False)
128
128
129 def __add__(self, other):
129 def __add__(self, other):
130 """Returns a new object with the union of the two collections.
130 """Returns a new object with the union of the two collections.
131
131
132 This is part of the mandatory API for smartset."""
132 This is part of the mandatory API for smartset."""
133 return addset(self, other)
133 return addset(self, other)
134
134
135 def __sub__(self, other):
135 def __sub__(self, other):
136 """Returns a new object with the substraction of the two collections.
136 """Returns a new object with the substraction of the two collections.
137
137
138 This is part of the mandatory API for smartset."""
138 This is part of the mandatory API for smartset."""
139 c = other.__contains__
139 c = other.__contains__
140 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
140 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
141 cache=False)
141 cache=False)
142
142
143 def filter(self, condition, condrepr=None, cache=True):
143 def filter(self, condition, condrepr=None, cache=True):
144 """Returns this smartset filtered by condition as a new smartset.
144 """Returns this smartset filtered by condition as a new smartset.
145
145
146 `condition` is a callable which takes a revision number and returns a
146 `condition` is a callable which takes a revision number and returns a
147 boolean. Optional `condrepr` provides a printable representation of
147 boolean. Optional `condrepr` provides a printable representation of
148 the given `condition`.
148 the given `condition`.
149
149
150 This is part of the mandatory API for smartset."""
150 This is part of the mandatory API for smartset."""
151 # builtin cannot be cached. but do not needs to
151 # builtin cannot be cached. but do not needs to
152 if cache and util.safehasattr(condition, 'func_code'):
152 if cache and util.safehasattr(condition, 'func_code'):
153 condition = util.cachefunc(condition)
153 condition = util.cachefunc(condition)
154 return filteredset(self, condition, condrepr)
154 return filteredset(self, condition, condrepr)
155
155
156 class baseset(abstractsmartset):
156 class baseset(abstractsmartset):
157 """Basic data structure that represents a revset and contains the basic
157 """Basic data structure that represents a revset and contains the basic
158 operation that it should be able to perform.
158 operation that it should be able to perform.
159
159
160 Every method in this class should be implemented by any smartset class.
160 Every method in this class should be implemented by any smartset class.
161
161
162 This class could be constructed by an (unordered) set, or an (ordered)
162 This class could be constructed by an (unordered) set, or an (ordered)
163 list-like object. If a set is provided, it'll be sorted lazily.
163 list-like object. If a set is provided, it'll be sorted lazily.
164
164
165 >>> x = [4, 0, 7, 6]
165 >>> x = [4, 0, 7, 6]
166 >>> y = [5, 6, 7, 3]
166 >>> y = [5, 6, 7, 3]
167
167
168 Construct by a set:
168 Construct by a set:
169 >>> xs = baseset(set(x))
169 >>> xs = baseset(set(x))
170 >>> ys = baseset(set(y))
170 >>> ys = baseset(set(y))
171 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
171 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
172 [[0, 4, 6, 7, 3, 5], [6, 7], [0, 4]]
172 [[0, 4, 6, 7, 3, 5], [6, 7], [0, 4]]
173 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
173 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
174 ['addset', 'baseset', 'baseset']
174 ['addset', 'baseset', 'baseset']
175
175
176 Construct by a list-like:
176 Construct by a list-like:
177 >>> xs = baseset(x)
177 >>> xs = baseset(x)
178 >>> ys = baseset(i for i in y)
178 >>> ys = baseset(i for i in y)
179 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
179 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
180 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
180 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
181 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
181 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
182 ['addset', 'filteredset', 'filteredset']
182 ['addset', 'filteredset', 'filteredset']
183
183
184 Populate "_set" fields in the lists so set optimization may be used:
184 Populate "_set" fields in the lists so set optimization may be used:
185 >>> [1 in xs, 3 in ys]
185 >>> [1 in xs, 3 in ys]
186 [False, True]
186 [False, True]
187
187
188 Without sort(), results won't be changed:
188 Without sort(), results won't be changed:
189 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
189 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
190 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
190 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
191 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
191 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
192 ['addset', 'filteredset', 'filteredset']
192 ['addset', 'filteredset', 'filteredset']
193
193
194 With sort(), set optimization could be used:
194 With sort(), set optimization could be used:
195 >>> xs.sort(reverse=True)
195 >>> xs.sort(reverse=True)
196 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
196 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
197 [[7, 6, 4, 0, 5, 3], [7, 6], [4, 0]]
197 [[7, 6, 4, 0, 5, 3], [7, 6], [4, 0]]
198 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
198 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
199 ['addset', 'baseset', 'baseset']
199 ['addset', 'baseset', 'baseset']
200
200
201 >>> ys.sort()
201 >>> ys.sort()
202 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
202 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
203 [[7, 6, 4, 0, 3, 5], [7, 6], [4, 0]]
203 [[7, 6, 4, 0, 3, 5], [7, 6], [4, 0]]
204 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
204 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
205 ['addset', 'baseset', 'baseset']
205 ['addset', 'baseset', 'baseset']
206
207 istopo is preserved across set operations
208 >>> xs = baseset(set(x), istopo=True)
209 >>> rs = xs & ys
210 >>> type(rs).__name__
211 'baseset'
212 >>> rs._istopo
213 True
206 """
214 """
207 def __init__(self, data=(), datarepr=None, istopo=False):
215 def __init__(self, data=(), datarepr=None, istopo=False):
208 """
216 """
209 datarepr: a tuple of (format, obj, ...), a function or an object that
217 datarepr: a tuple of (format, obj, ...), a function or an object that
210 provides a printable representation of the given data.
218 provides a printable representation of the given data.
211 """
219 """
212 self._ascending = None
220 self._ascending = None
213 self._istopo = istopo
221 self._istopo = istopo
214 if not isinstance(data, list):
222 if not isinstance(data, list):
215 if isinstance(data, set):
223 if isinstance(data, set):
216 self._set = data
224 self._set = data
217 # set has no order we pick one for stability purpose
225 # set has no order we pick one for stability purpose
218 self._ascending = True
226 self._ascending = True
219 # converting set to list has a cost, do it lazily
227 # converting set to list has a cost, do it lazily
220 data = None
228 data = None
221 else:
229 else:
222 data = list(data)
230 data = list(data)
223 if data is not None:
231 if data is not None:
224 self._list = data
232 self._list = data
225 self._datarepr = datarepr
233 self._datarepr = datarepr
226
234
227 @util.propertycache
235 @util.propertycache
228 def _set(self):
236 def _set(self):
229 return set(self._list)
237 return set(self._list)
230
238
231 @util.propertycache
239 @util.propertycache
232 def _asclist(self):
240 def _asclist(self):
233 asclist = self._list[:]
241 asclist = self._list[:]
234 asclist.sort()
242 asclist.sort()
235 return asclist
243 return asclist
236
244
237 @util.propertycache
245 @util.propertycache
238 def _list(self):
246 def _list(self):
239 # _list is only lazily constructed if we have _set
247 # _list is only lazily constructed if we have _set
240 assert '_set' in self.__dict__
248 assert '_set' in self.__dict__
241 return list(self._set)
249 return list(self._set)
242
250
243 def __iter__(self):
251 def __iter__(self):
244 if self._ascending is None:
252 if self._ascending is None:
245 return iter(self._list)
253 return iter(self._list)
246 elif self._ascending:
254 elif self._ascending:
247 return iter(self._asclist)
255 return iter(self._asclist)
248 else:
256 else:
249 return reversed(self._asclist)
257 return reversed(self._asclist)
250
258
251 def fastasc(self):
259 def fastasc(self):
252 return iter(self._asclist)
260 return iter(self._asclist)
253
261
254 def fastdesc(self):
262 def fastdesc(self):
255 return reversed(self._asclist)
263 return reversed(self._asclist)
256
264
257 @util.propertycache
265 @util.propertycache
258 def __contains__(self):
266 def __contains__(self):
259 return self._set.__contains__
267 return self._set.__contains__
260
268
261 def __nonzero__(self):
269 def __nonzero__(self):
262 return bool(len(self))
270 return bool(len(self))
263
271
264 def sort(self, reverse=False):
272 def sort(self, reverse=False):
265 self._ascending = not bool(reverse)
273 self._ascending = not bool(reverse)
266 self._istopo = False
274 self._istopo = False
267
275
268 def reverse(self):
276 def reverse(self):
269 if self._ascending is None:
277 if self._ascending is None:
270 self._list.reverse()
278 self._list.reverse()
271 else:
279 else:
272 self._ascending = not self._ascending
280 self._ascending = not self._ascending
273 self._istopo = False
281 self._istopo = False
274
282
275 def __len__(self):
283 def __len__(self):
276 if '_list' in self.__dict__:
284 if '_list' in self.__dict__:
277 return len(self._list)
285 return len(self._list)
278 else:
286 else:
279 return len(self._set)
287 return len(self._set)
280
288
281 def isascending(self):
289 def isascending(self):
282 """Returns True if the collection is ascending order, False if not.
290 """Returns True if the collection is ascending order, False if not.
283
291
284 This is part of the mandatory API for smartset."""
292 This is part of the mandatory API for smartset."""
285 if len(self) <= 1:
293 if len(self) <= 1:
286 return True
294 return True
287 return self._ascending is not None and self._ascending
295 return self._ascending is not None and self._ascending
288
296
289 def isdescending(self):
297 def isdescending(self):
290 """Returns True if the collection is descending order, False if not.
298 """Returns True if the collection is descending order, False if not.
291
299
292 This is part of the mandatory API for smartset."""
300 This is part of the mandatory API for smartset."""
293 if len(self) <= 1:
301 if len(self) <= 1:
294 return True
302 return True
295 return self._ascending is not None and not self._ascending
303 return self._ascending is not None and not self._ascending
296
304
297 def istopo(self):
305 def istopo(self):
298 """Is the collection is in topographical order or not.
306 """Is the collection is in topographical order or not.
299
307
300 This is part of the mandatory API for smartset."""
308 This is part of the mandatory API for smartset."""
301 if len(self) <= 1:
309 if len(self) <= 1:
302 return True
310 return True
303 return self._istopo
311 return self._istopo
304
312
305 def first(self):
313 def first(self):
306 if self:
314 if self:
307 if self._ascending is None:
315 if self._ascending is None:
308 return self._list[0]
316 return self._list[0]
309 elif self._ascending:
317 elif self._ascending:
310 return self._asclist[0]
318 return self._asclist[0]
311 else:
319 else:
312 return self._asclist[-1]
320 return self._asclist[-1]
313 return None
321 return None
314
322
315 def last(self):
323 def last(self):
316 if self:
324 if self:
317 if self._ascending is None:
325 if self._ascending is None:
318 return self._list[-1]
326 return self._list[-1]
319 elif self._ascending:
327 elif self._ascending:
320 return self._asclist[-1]
328 return self._asclist[-1]
321 else:
329 else:
322 return self._asclist[0]
330 return self._asclist[0]
323 return None
331 return None
324
332
325 def _fastsetop(self, other, op):
333 def _fastsetop(self, other, op):
326 # try to use native set operations as fast paths
334 # try to use native set operations as fast paths
327 if (type(other) is baseset and '_set' in other.__dict__ and '_set' in
335 if (type(other) is baseset and '_set' in other.__dict__ and '_set' in
328 self.__dict__ and self._ascending is not None):
336 self.__dict__ and self._ascending is not None):
329 s = baseset(data=getattr(self._set, op)(other._set))
337 s = baseset(data=getattr(self._set, op)(other._set),
338 istopo=self._istopo)
330 s._ascending = self._ascending
339 s._ascending = self._ascending
331 else:
340 else:
332 s = getattr(super(baseset, self), op)(other)
341 s = getattr(super(baseset, self), op)(other)
333 return s
342 return s
334
343
335 def __and__(self, other):
344 def __and__(self, other):
336 return self._fastsetop(other, '__and__')
345 return self._fastsetop(other, '__and__')
337
346
338 def __sub__(self, other):
347 def __sub__(self, other):
339 return self._fastsetop(other, '__sub__')
348 return self._fastsetop(other, '__sub__')
340
349
341 def __repr__(self):
350 def __repr__(self):
342 d = {None: '', False: '-', True: '+'}[self._ascending]
351 d = {None: '', False: '-', True: '+'}[self._ascending]
343 s = _formatsetrepr(self._datarepr)
352 s = _formatsetrepr(self._datarepr)
344 if not s:
353 if not s:
345 l = self._list
354 l = self._list
346 # if _list has been built from a set, it might have a different
355 # if _list has been built from a set, it might have a different
347 # order from one python implementation to another.
356 # order from one python implementation to another.
348 # We fallback to the sorted version for a stable output.
357 # We fallback to the sorted version for a stable output.
349 if self._ascending is not None:
358 if self._ascending is not None:
350 l = self._asclist
359 l = self._asclist
351 s = repr(l)
360 s = repr(l)
352 return '<%s%s %s>' % (type(self).__name__, d, s)
361 return '<%s%s %s>' % (type(self).__name__, d, s)
353
362
354 class filteredset(abstractsmartset):
363 class filteredset(abstractsmartset):
355 """Duck type for baseset class which iterates lazily over the revisions in
364 """Duck type for baseset class which iterates lazily over the revisions in
356 the subset and contains a function which tests for membership in the
365 the subset and contains a function which tests for membership in the
357 revset
366 revset
358 """
367 """
359 def __init__(self, subset, condition=lambda x: True, condrepr=None):
368 def __init__(self, subset, condition=lambda x: True, condrepr=None):
360 """
369 """
361 condition: a function that decide whether a revision in the subset
370 condition: a function that decide whether a revision in the subset
362 belongs to the revset or not.
371 belongs to the revset or not.
363 condrepr: a tuple of (format, obj, ...), a function or an object that
372 condrepr: a tuple of (format, obj, ...), a function or an object that
364 provides a printable representation of the given condition.
373 provides a printable representation of the given condition.
365 """
374 """
366 self._subset = subset
375 self._subset = subset
367 self._condition = condition
376 self._condition = condition
368 self._condrepr = condrepr
377 self._condrepr = condrepr
369
378
370 def __contains__(self, x):
379 def __contains__(self, x):
371 return x in self._subset and self._condition(x)
380 return x in self._subset and self._condition(x)
372
381
373 def __iter__(self):
382 def __iter__(self):
374 return self._iterfilter(self._subset)
383 return self._iterfilter(self._subset)
375
384
376 def _iterfilter(self, it):
385 def _iterfilter(self, it):
377 cond = self._condition
386 cond = self._condition
378 for x in it:
387 for x in it:
379 if cond(x):
388 if cond(x):
380 yield x
389 yield x
381
390
382 @property
391 @property
383 def fastasc(self):
392 def fastasc(self):
384 it = self._subset.fastasc
393 it = self._subset.fastasc
385 if it is None:
394 if it is None:
386 return None
395 return None
387 return lambda: self._iterfilter(it())
396 return lambda: self._iterfilter(it())
388
397
389 @property
398 @property
390 def fastdesc(self):
399 def fastdesc(self):
391 it = self._subset.fastdesc
400 it = self._subset.fastdesc
392 if it is None:
401 if it is None:
393 return None
402 return None
394 return lambda: self._iterfilter(it())
403 return lambda: self._iterfilter(it())
395
404
396 def __nonzero__(self):
405 def __nonzero__(self):
397 fast = None
406 fast = None
398 candidates = [self.fastasc if self.isascending() else None,
407 candidates = [self.fastasc if self.isascending() else None,
399 self.fastdesc if self.isdescending() else None,
408 self.fastdesc if self.isdescending() else None,
400 self.fastasc,
409 self.fastasc,
401 self.fastdesc]
410 self.fastdesc]
402 for candidate in candidates:
411 for candidate in candidates:
403 if candidate is not None:
412 if candidate is not None:
404 fast = candidate
413 fast = candidate
405 break
414 break
406
415
407 if fast is not None:
416 if fast is not None:
408 it = fast()
417 it = fast()
409 else:
418 else:
410 it = self
419 it = self
411
420
412 for r in it:
421 for r in it:
413 return True
422 return True
414 return False
423 return False
415
424
416 def __len__(self):
425 def __len__(self):
417 # Basic implementation to be changed in future patches.
426 # Basic implementation to be changed in future patches.
418 # until this gets improved, we use generator expression
427 # until this gets improved, we use generator expression
419 # here, since list comprehensions are free to call __len__ again
428 # here, since list comprehensions are free to call __len__ again
420 # causing infinite recursion
429 # causing infinite recursion
421 l = baseset(r for r in self)
430 l = baseset(r for r in self)
422 return len(l)
431 return len(l)
423
432
424 def sort(self, reverse=False):
433 def sort(self, reverse=False):
425 self._subset.sort(reverse=reverse)
434 self._subset.sort(reverse=reverse)
426
435
427 def reverse(self):
436 def reverse(self):
428 self._subset.reverse()
437 self._subset.reverse()
429
438
430 def isascending(self):
439 def isascending(self):
431 return self._subset.isascending()
440 return self._subset.isascending()
432
441
433 def isdescending(self):
442 def isdescending(self):
434 return self._subset.isdescending()
443 return self._subset.isdescending()
435
444
436 def istopo(self):
445 def istopo(self):
437 return self._subset.istopo()
446 return self._subset.istopo()
438
447
439 def first(self):
448 def first(self):
440 for x in self:
449 for x in self:
441 return x
450 return x
442 return None
451 return None
443
452
444 def last(self):
453 def last(self):
445 it = None
454 it = None
446 if self.isascending():
455 if self.isascending():
447 it = self.fastdesc
456 it = self.fastdesc
448 elif self.isdescending():
457 elif self.isdescending():
449 it = self.fastasc
458 it = self.fastasc
450 if it is not None:
459 if it is not None:
451 for x in it():
460 for x in it():
452 return x
461 return x
453 return None #empty case
462 return None #empty case
454 else:
463 else:
455 x = None
464 x = None
456 for x in self:
465 for x in self:
457 pass
466 pass
458 return x
467 return x
459
468
460 def __repr__(self):
469 def __repr__(self):
461 xs = [repr(self._subset)]
470 xs = [repr(self._subset)]
462 s = _formatsetrepr(self._condrepr)
471 s = _formatsetrepr(self._condrepr)
463 if s:
472 if s:
464 xs.append(s)
473 xs.append(s)
465 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
474 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
466
475
467 def _iterordered(ascending, iter1, iter2):
476 def _iterordered(ascending, iter1, iter2):
468 """produce an ordered iteration from two iterators with the same order
477 """produce an ordered iteration from two iterators with the same order
469
478
470 The ascending is used to indicated the iteration direction.
479 The ascending is used to indicated the iteration direction.
471 """
480 """
472 choice = max
481 choice = max
473 if ascending:
482 if ascending:
474 choice = min
483 choice = min
475
484
476 val1 = None
485 val1 = None
477 val2 = None
486 val2 = None
478 try:
487 try:
479 # Consume both iterators in an ordered way until one is empty
488 # Consume both iterators in an ordered way until one is empty
480 while True:
489 while True:
481 if val1 is None:
490 if val1 is None:
482 val1 = next(iter1)
491 val1 = next(iter1)
483 if val2 is None:
492 if val2 is None:
484 val2 = next(iter2)
493 val2 = next(iter2)
485 n = choice(val1, val2)
494 n = choice(val1, val2)
486 yield n
495 yield n
487 if val1 == n:
496 if val1 == n:
488 val1 = None
497 val1 = None
489 if val2 == n:
498 if val2 == n:
490 val2 = None
499 val2 = None
491 except StopIteration:
500 except StopIteration:
492 # Flush any remaining values and consume the other one
501 # Flush any remaining values and consume the other one
493 it = iter2
502 it = iter2
494 if val1 is not None:
503 if val1 is not None:
495 yield val1
504 yield val1
496 it = iter1
505 it = iter1
497 elif val2 is not None:
506 elif val2 is not None:
498 # might have been equality and both are empty
507 # might have been equality and both are empty
499 yield val2
508 yield val2
500 for val in it:
509 for val in it:
501 yield val
510 yield val
502
511
503 class addset(abstractsmartset):
512 class addset(abstractsmartset):
504 """Represent the addition of two sets
513 """Represent the addition of two sets
505
514
506 Wrapper structure for lazily adding two structures without losing much
515 Wrapper structure for lazily adding two structures without losing much
507 performance on the __contains__ method
516 performance on the __contains__ method
508
517
509 If the ascending attribute is set, that means the two structures are
518 If the ascending attribute is set, that means the two structures are
510 ordered in either an ascending or descending way. Therefore, we can add
519 ordered in either an ascending or descending way. Therefore, we can add
511 them maintaining the order by iterating over both at the same time
520 them maintaining the order by iterating over both at the same time
512
521
513 >>> xs = baseset([0, 3, 2])
522 >>> xs = baseset([0, 3, 2])
514 >>> ys = baseset([5, 2, 4])
523 >>> ys = baseset([5, 2, 4])
515
524
516 >>> rs = addset(xs, ys)
525 >>> rs = addset(xs, ys)
517 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
526 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
518 (True, True, False, True, 0, 4)
527 (True, True, False, True, 0, 4)
519 >>> rs = addset(xs, baseset([]))
528 >>> rs = addset(xs, baseset([]))
520 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
529 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
521 (True, True, False, 0, 2)
530 (True, True, False, 0, 2)
522 >>> rs = addset(baseset([]), baseset([]))
531 >>> rs = addset(baseset([]), baseset([]))
523 >>> bool(rs), 0 in rs, rs.first(), rs.last()
532 >>> bool(rs), 0 in rs, rs.first(), rs.last()
524 (False, False, None, None)
533 (False, False, None, None)
525
534
526 iterate unsorted:
535 iterate unsorted:
527 >>> rs = addset(xs, ys)
536 >>> rs = addset(xs, ys)
528 >>> # (use generator because pypy could call len())
537 >>> # (use generator because pypy could call len())
529 >>> list(x for x in rs) # without _genlist
538 >>> list(x for x in rs) # without _genlist
530 [0, 3, 2, 5, 4]
539 [0, 3, 2, 5, 4]
531 >>> assert not rs._genlist
540 >>> assert not rs._genlist
532 >>> len(rs)
541 >>> len(rs)
533 5
542 5
534 >>> [x for x in rs] # with _genlist
543 >>> [x for x in rs] # with _genlist
535 [0, 3, 2, 5, 4]
544 [0, 3, 2, 5, 4]
536 >>> assert rs._genlist
545 >>> assert rs._genlist
537
546
538 iterate ascending:
547 iterate ascending:
539 >>> rs = addset(xs, ys, ascending=True)
548 >>> rs = addset(xs, ys, ascending=True)
540 >>> # (use generator because pypy could call len())
549 >>> # (use generator because pypy could call len())
541 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
550 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
542 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
551 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
543 >>> assert not rs._asclist
552 >>> assert not rs._asclist
544 >>> len(rs)
553 >>> len(rs)
545 5
554 5
546 >>> [x for x in rs], [x for x in rs.fastasc()]
555 >>> [x for x in rs], [x for x in rs.fastasc()]
547 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
556 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
548 >>> assert rs._asclist
557 >>> assert rs._asclist
549
558
550 iterate descending:
559 iterate descending:
551 >>> rs = addset(xs, ys, ascending=False)
560 >>> rs = addset(xs, ys, ascending=False)
552 >>> # (use generator because pypy could call len())
561 >>> # (use generator because pypy could call len())
553 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
562 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
554 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
563 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
555 >>> assert not rs._asclist
564 >>> assert not rs._asclist
556 >>> len(rs)
565 >>> len(rs)
557 5
566 5
558 >>> [x for x in rs], [x for x in rs.fastdesc()]
567 >>> [x for x in rs], [x for x in rs.fastdesc()]
559 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
568 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
560 >>> assert rs._asclist
569 >>> assert rs._asclist
561
570
562 iterate ascending without fastasc:
571 iterate ascending without fastasc:
563 >>> rs = addset(xs, generatorset(ys), ascending=True)
572 >>> rs = addset(xs, generatorset(ys), ascending=True)
564 >>> assert rs.fastasc is None
573 >>> assert rs.fastasc is None
565 >>> [x for x in rs]
574 >>> [x for x in rs]
566 [0, 2, 3, 4, 5]
575 [0, 2, 3, 4, 5]
567
576
568 iterate descending without fastdesc:
577 iterate descending without fastdesc:
569 >>> rs = addset(generatorset(xs), ys, ascending=False)
578 >>> rs = addset(generatorset(xs), ys, ascending=False)
570 >>> assert rs.fastdesc is None
579 >>> assert rs.fastdesc is None
571 >>> [x for x in rs]
580 >>> [x for x in rs]
572 [5, 4, 3, 2, 0]
581 [5, 4, 3, 2, 0]
573 """
582 """
574 def __init__(self, revs1, revs2, ascending=None):
583 def __init__(self, revs1, revs2, ascending=None):
575 self._r1 = revs1
584 self._r1 = revs1
576 self._r2 = revs2
585 self._r2 = revs2
577 self._iter = None
586 self._iter = None
578 self._ascending = ascending
587 self._ascending = ascending
579 self._genlist = None
588 self._genlist = None
580 self._asclist = None
589 self._asclist = None
581
590
582 def __len__(self):
591 def __len__(self):
583 return len(self._list)
592 return len(self._list)
584
593
585 def __nonzero__(self):
594 def __nonzero__(self):
586 return bool(self._r1) or bool(self._r2)
595 return bool(self._r1) or bool(self._r2)
587
596
588 @util.propertycache
597 @util.propertycache
589 def _list(self):
598 def _list(self):
590 if not self._genlist:
599 if not self._genlist:
591 self._genlist = baseset(iter(self))
600 self._genlist = baseset(iter(self))
592 return self._genlist
601 return self._genlist
593
602
594 def __iter__(self):
603 def __iter__(self):
595 """Iterate over both collections without repeating elements
604 """Iterate over both collections without repeating elements
596
605
597 If the ascending attribute is not set, iterate over the first one and
606 If the ascending attribute is not set, iterate over the first one and
598 then over the second one checking for membership on the first one so we
607 then over the second one checking for membership on the first one so we
599 dont yield any duplicates.
608 dont yield any duplicates.
600
609
601 If the ascending attribute is set, iterate over both collections at the
610 If the ascending attribute is set, iterate over both collections at the
602 same time, yielding only one value at a time in the given order.
611 same time, yielding only one value at a time in the given order.
603 """
612 """
604 if self._ascending is None:
613 if self._ascending is None:
605 if self._genlist:
614 if self._genlist:
606 return iter(self._genlist)
615 return iter(self._genlist)
607 def arbitraryordergen():
616 def arbitraryordergen():
608 for r in self._r1:
617 for r in self._r1:
609 yield r
618 yield r
610 inr1 = self._r1.__contains__
619 inr1 = self._r1.__contains__
611 for r in self._r2:
620 for r in self._r2:
612 if not inr1(r):
621 if not inr1(r):
613 yield r
622 yield r
614 return arbitraryordergen()
623 return arbitraryordergen()
615 # try to use our own fast iterator if it exists
624 # try to use our own fast iterator if it exists
616 self._trysetasclist()
625 self._trysetasclist()
617 if self._ascending:
626 if self._ascending:
618 attr = 'fastasc'
627 attr = 'fastasc'
619 else:
628 else:
620 attr = 'fastdesc'
629 attr = 'fastdesc'
621 it = getattr(self, attr)
630 it = getattr(self, attr)
622 if it is not None:
631 if it is not None:
623 return it()
632 return it()
624 # maybe half of the component supports fast
633 # maybe half of the component supports fast
625 # get iterator for _r1
634 # get iterator for _r1
626 iter1 = getattr(self._r1, attr)
635 iter1 = getattr(self._r1, attr)
627 if iter1 is None:
636 if iter1 is None:
628 # let's avoid side effect (not sure it matters)
637 # let's avoid side effect (not sure it matters)
629 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
638 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
630 else:
639 else:
631 iter1 = iter1()
640 iter1 = iter1()
632 # get iterator for _r2
641 # get iterator for _r2
633 iter2 = getattr(self._r2, attr)
642 iter2 = getattr(self._r2, attr)
634 if iter2 is None:
643 if iter2 is None:
635 # let's avoid side effect (not sure it matters)
644 # let's avoid side effect (not sure it matters)
636 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
645 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
637 else:
646 else:
638 iter2 = iter2()
647 iter2 = iter2()
639 return _iterordered(self._ascending, iter1, iter2)
648 return _iterordered(self._ascending, iter1, iter2)
640
649
641 def _trysetasclist(self):
650 def _trysetasclist(self):
642 """populate the _asclist attribute if possible and necessary"""
651 """populate the _asclist attribute if possible and necessary"""
643 if self._genlist is not None and self._asclist is None:
652 if self._genlist is not None and self._asclist is None:
644 self._asclist = sorted(self._genlist)
653 self._asclist = sorted(self._genlist)
645
654
646 @property
655 @property
647 def fastasc(self):
656 def fastasc(self):
648 self._trysetasclist()
657 self._trysetasclist()
649 if self._asclist is not None:
658 if self._asclist is not None:
650 return self._asclist.__iter__
659 return self._asclist.__iter__
651 iter1 = self._r1.fastasc
660 iter1 = self._r1.fastasc
652 iter2 = self._r2.fastasc
661 iter2 = self._r2.fastasc
653 if None in (iter1, iter2):
662 if None in (iter1, iter2):
654 return None
663 return None
655 return lambda: _iterordered(True, iter1(), iter2())
664 return lambda: _iterordered(True, iter1(), iter2())
656
665
657 @property
666 @property
658 def fastdesc(self):
667 def fastdesc(self):
659 self._trysetasclist()
668 self._trysetasclist()
660 if self._asclist is not None:
669 if self._asclist is not None:
661 return self._asclist.__reversed__
670 return self._asclist.__reversed__
662 iter1 = self._r1.fastdesc
671 iter1 = self._r1.fastdesc
663 iter2 = self._r2.fastdesc
672 iter2 = self._r2.fastdesc
664 if None in (iter1, iter2):
673 if None in (iter1, iter2):
665 return None
674 return None
666 return lambda: _iterordered(False, iter1(), iter2())
675 return lambda: _iterordered(False, iter1(), iter2())
667
676
668 def __contains__(self, x):
677 def __contains__(self, x):
669 return x in self._r1 or x in self._r2
678 return x in self._r1 or x in self._r2
670
679
671 def sort(self, reverse=False):
680 def sort(self, reverse=False):
672 """Sort the added set
681 """Sort the added set
673
682
674 For this we use the cached list with all the generated values and if we
683 For this we use the cached list with all the generated values and if we
675 know they are ascending or descending we can sort them in a smart way.
684 know they are ascending or descending we can sort them in a smart way.
676 """
685 """
677 self._ascending = not reverse
686 self._ascending = not reverse
678
687
679 def isascending(self):
688 def isascending(self):
680 return self._ascending is not None and self._ascending
689 return self._ascending is not None and self._ascending
681
690
682 def isdescending(self):
691 def isdescending(self):
683 return self._ascending is not None and not self._ascending
692 return self._ascending is not None and not self._ascending
684
693
685 def istopo(self):
694 def istopo(self):
686 # not worth the trouble asserting if the two sets combined are still
695 # not worth the trouble asserting if the two sets combined are still
687 # in topographical order. Use the sort() predicate to explicitly sort
696 # in topographical order. Use the sort() predicate to explicitly sort
688 # again instead.
697 # again instead.
689 return False
698 return False
690
699
691 def reverse(self):
700 def reverse(self):
692 if self._ascending is None:
701 if self._ascending is None:
693 self._list.reverse()
702 self._list.reverse()
694 else:
703 else:
695 self._ascending = not self._ascending
704 self._ascending = not self._ascending
696
705
697 def first(self):
706 def first(self):
698 for x in self:
707 for x in self:
699 return x
708 return x
700 return None
709 return None
701
710
702 def last(self):
711 def last(self):
703 self.reverse()
712 self.reverse()
704 val = self.first()
713 val = self.first()
705 self.reverse()
714 self.reverse()
706 return val
715 return val
707
716
708 def __repr__(self):
717 def __repr__(self):
709 d = {None: '', False: '-', True: '+'}[self._ascending]
718 d = {None: '', False: '-', True: '+'}[self._ascending]
710 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
719 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
711
720
712 class generatorset(abstractsmartset):
721 class generatorset(abstractsmartset):
713 """Wrap a generator for lazy iteration
722 """Wrap a generator for lazy iteration
714
723
715 Wrapper structure for generators that provides lazy membership and can
724 Wrapper structure for generators that provides lazy membership and can
716 be iterated more than once.
725 be iterated more than once.
717 When asked for membership it generates values until either it finds the
726 When asked for membership it generates values until either it finds the
718 requested one or has gone through all the elements in the generator
727 requested one or has gone through all the elements in the generator
719 """
728 """
720 def __init__(self, gen, iterasc=None):
729 def __init__(self, gen, iterasc=None):
721 """
730 """
722 gen: a generator producing the values for the generatorset.
731 gen: a generator producing the values for the generatorset.
723 """
732 """
724 self._gen = gen
733 self._gen = gen
725 self._asclist = None
734 self._asclist = None
726 self._cache = {}
735 self._cache = {}
727 self._genlist = []
736 self._genlist = []
728 self._finished = False
737 self._finished = False
729 self._ascending = True
738 self._ascending = True
730 if iterasc is not None:
739 if iterasc is not None:
731 if iterasc:
740 if iterasc:
732 self.fastasc = self._iterator
741 self.fastasc = self._iterator
733 self.__contains__ = self._asccontains
742 self.__contains__ = self._asccontains
734 else:
743 else:
735 self.fastdesc = self._iterator
744 self.fastdesc = self._iterator
736 self.__contains__ = self._desccontains
745 self.__contains__ = self._desccontains
737
746
738 def __nonzero__(self):
747 def __nonzero__(self):
739 # Do not use 'for r in self' because it will enforce the iteration
748 # Do not use 'for r in self' because it will enforce the iteration
740 # order (default ascending), possibly unrolling a whole descending
749 # order (default ascending), possibly unrolling a whole descending
741 # iterator.
750 # iterator.
742 if self._genlist:
751 if self._genlist:
743 return True
752 return True
744 for r in self._consumegen():
753 for r in self._consumegen():
745 return True
754 return True
746 return False
755 return False
747
756
748 def __contains__(self, x):
757 def __contains__(self, x):
749 if x in self._cache:
758 if x in self._cache:
750 return self._cache[x]
759 return self._cache[x]
751
760
752 # Use new values only, as existing values would be cached.
761 # Use new values only, as existing values would be cached.
753 for l in self._consumegen():
762 for l in self._consumegen():
754 if l == x:
763 if l == x:
755 return True
764 return True
756
765
757 self._cache[x] = False
766 self._cache[x] = False
758 return False
767 return False
759
768
760 def _asccontains(self, x):
769 def _asccontains(self, x):
761 """version of contains optimised for ascending generator"""
770 """version of contains optimised for ascending generator"""
762 if x in self._cache:
771 if x in self._cache:
763 return self._cache[x]
772 return self._cache[x]
764
773
765 # Use new values only, as existing values would be cached.
774 # Use new values only, as existing values would be cached.
766 for l in self._consumegen():
775 for l in self._consumegen():
767 if l == x:
776 if l == x:
768 return True
777 return True
769 if l > x:
778 if l > x:
770 break
779 break
771
780
772 self._cache[x] = False
781 self._cache[x] = False
773 return False
782 return False
774
783
775 def _desccontains(self, x):
784 def _desccontains(self, x):
776 """version of contains optimised for descending generator"""
785 """version of contains optimised for descending generator"""
777 if x in self._cache:
786 if x in self._cache:
778 return self._cache[x]
787 return self._cache[x]
779
788
780 # Use new values only, as existing values would be cached.
789 # Use new values only, as existing values would be cached.
781 for l in self._consumegen():
790 for l in self._consumegen():
782 if l == x:
791 if l == x:
783 return True
792 return True
784 if l < x:
793 if l < x:
785 break
794 break
786
795
787 self._cache[x] = False
796 self._cache[x] = False
788 return False
797 return False
789
798
790 def __iter__(self):
799 def __iter__(self):
791 if self._ascending:
800 if self._ascending:
792 it = self.fastasc
801 it = self.fastasc
793 else:
802 else:
794 it = self.fastdesc
803 it = self.fastdesc
795 if it is not None:
804 if it is not None:
796 return it()
805 return it()
797 # we need to consume the iterator
806 # we need to consume the iterator
798 for x in self._consumegen():
807 for x in self._consumegen():
799 pass
808 pass
800 # recall the same code
809 # recall the same code
801 return iter(self)
810 return iter(self)
802
811
803 def _iterator(self):
812 def _iterator(self):
804 if self._finished:
813 if self._finished:
805 return iter(self._genlist)
814 return iter(self._genlist)
806
815
807 # We have to use this complex iteration strategy to allow multiple
816 # We have to use this complex iteration strategy to allow multiple
808 # iterations at the same time. We need to be able to catch revision
817 # iterations at the same time. We need to be able to catch revision
809 # removed from _consumegen and added to genlist in another instance.
818 # removed from _consumegen and added to genlist in another instance.
810 #
819 #
811 # Getting rid of it would provide an about 15% speed up on this
820 # Getting rid of it would provide an about 15% speed up on this
812 # iteration.
821 # iteration.
813 genlist = self._genlist
822 genlist = self._genlist
814 nextrev = self._consumegen().next
823 nextrev = self._consumegen().next
815 _len = len # cache global lookup
824 _len = len # cache global lookup
816 def gen():
825 def gen():
817 i = 0
826 i = 0
818 while True:
827 while True:
819 if i < _len(genlist):
828 if i < _len(genlist):
820 yield genlist[i]
829 yield genlist[i]
821 else:
830 else:
822 yield nextrev()
831 yield nextrev()
823 i += 1
832 i += 1
824 return gen()
833 return gen()
825
834
826 def _consumegen(self):
835 def _consumegen(self):
827 cache = self._cache
836 cache = self._cache
828 genlist = self._genlist.append
837 genlist = self._genlist.append
829 for item in self._gen:
838 for item in self._gen:
830 cache[item] = True
839 cache[item] = True
831 genlist(item)
840 genlist(item)
832 yield item
841 yield item
833 if not self._finished:
842 if not self._finished:
834 self._finished = True
843 self._finished = True
835 asc = self._genlist[:]
844 asc = self._genlist[:]
836 asc.sort()
845 asc.sort()
837 self._asclist = asc
846 self._asclist = asc
838 self.fastasc = asc.__iter__
847 self.fastasc = asc.__iter__
839 self.fastdesc = asc.__reversed__
848 self.fastdesc = asc.__reversed__
840
849
841 def __len__(self):
850 def __len__(self):
842 for x in self._consumegen():
851 for x in self._consumegen():
843 pass
852 pass
844 return len(self._genlist)
853 return len(self._genlist)
845
854
846 def sort(self, reverse=False):
855 def sort(self, reverse=False):
847 self._ascending = not reverse
856 self._ascending = not reverse
848
857
849 def reverse(self):
858 def reverse(self):
850 self._ascending = not self._ascending
859 self._ascending = not self._ascending
851
860
852 def isascending(self):
861 def isascending(self):
853 return self._ascending
862 return self._ascending
854
863
855 def isdescending(self):
864 def isdescending(self):
856 return not self._ascending
865 return not self._ascending
857
866
858 def istopo(self):
867 def istopo(self):
859 # not worth the trouble asserting if the two sets combined are still
868 # not worth the trouble asserting if the two sets combined are still
860 # in topographical order. Use the sort() predicate to explicitly sort
869 # in topographical order. Use the sort() predicate to explicitly sort
861 # again instead.
870 # again instead.
862 return False
871 return False
863
872
864 def first(self):
873 def first(self):
865 if self._ascending:
874 if self._ascending:
866 it = self.fastasc
875 it = self.fastasc
867 else:
876 else:
868 it = self.fastdesc
877 it = self.fastdesc
869 if it is None:
878 if it is None:
870 # we need to consume all and try again
879 # we need to consume all and try again
871 for x in self._consumegen():
880 for x in self._consumegen():
872 pass
881 pass
873 return self.first()
882 return self.first()
874 return next(it(), None)
883 return next(it(), None)
875
884
876 def last(self):
885 def last(self):
877 if self._ascending:
886 if self._ascending:
878 it = self.fastdesc
887 it = self.fastdesc
879 else:
888 else:
880 it = self.fastasc
889 it = self.fastasc
881 if it is None:
890 if it is None:
882 # we need to consume all and try again
891 # we need to consume all and try again
883 for x in self._consumegen():
892 for x in self._consumegen():
884 pass
893 pass
885 return self.first()
894 return self.first()
886 return next(it(), None)
895 return next(it(), None)
887
896
888 def __repr__(self):
897 def __repr__(self):
889 d = {False: '-', True: '+'}[self._ascending]
898 d = {False: '-', True: '+'}[self._ascending]
890 return '<%s%s>' % (type(self).__name__, d)
899 return '<%s%s>' % (type(self).__name__, d)
891
900
892 class spanset(abstractsmartset):
901 class spanset(abstractsmartset):
893 """Duck type for baseset class which represents a range of revisions and
902 """Duck type for baseset class which represents a range of revisions and
894 can work lazily and without having all the range in memory
903 can work lazily and without having all the range in memory
895
904
896 Note that spanset(x, y) behave almost like xrange(x, y) except for two
905 Note that spanset(x, y) behave almost like xrange(x, y) except for two
897 notable points:
906 notable points:
898 - when x < y it will be automatically descending,
907 - when x < y it will be automatically descending,
899 - revision filtered with this repoview will be skipped.
908 - revision filtered with this repoview will be skipped.
900
909
901 """
910 """
902 def __init__(self, repo, start=0, end=None):
911 def __init__(self, repo, start=0, end=None):
903 """
912 """
904 start: first revision included the set
913 start: first revision included the set
905 (default to 0)
914 (default to 0)
906 end: first revision excluded (last+1)
915 end: first revision excluded (last+1)
907 (default to len(repo)
916 (default to len(repo)
908
917
909 Spanset will be descending if `end` < `start`.
918 Spanset will be descending if `end` < `start`.
910 """
919 """
911 if end is None:
920 if end is None:
912 end = len(repo)
921 end = len(repo)
913 self._ascending = start <= end
922 self._ascending = start <= end
914 if not self._ascending:
923 if not self._ascending:
915 start, end = end + 1, start +1
924 start, end = end + 1, start +1
916 self._start = start
925 self._start = start
917 self._end = end
926 self._end = end
918 self._hiddenrevs = repo.changelog.filteredrevs
927 self._hiddenrevs = repo.changelog.filteredrevs
919
928
920 def sort(self, reverse=False):
929 def sort(self, reverse=False):
921 self._ascending = not reverse
930 self._ascending = not reverse
922
931
923 def reverse(self):
932 def reverse(self):
924 self._ascending = not self._ascending
933 self._ascending = not self._ascending
925
934
926 def istopo(self):
935 def istopo(self):
927 # not worth the trouble asserting if the two sets combined are still
936 # not worth the trouble asserting if the two sets combined are still
928 # in topographical order. Use the sort() predicate to explicitly sort
937 # in topographical order. Use the sort() predicate to explicitly sort
929 # again instead.
938 # again instead.
930 return False
939 return False
931
940
932 def _iterfilter(self, iterrange):
941 def _iterfilter(self, iterrange):
933 s = self._hiddenrevs
942 s = self._hiddenrevs
934 for r in iterrange:
943 for r in iterrange:
935 if r not in s:
944 if r not in s:
936 yield r
945 yield r
937
946
938 def __iter__(self):
947 def __iter__(self):
939 if self._ascending:
948 if self._ascending:
940 return self.fastasc()
949 return self.fastasc()
941 else:
950 else:
942 return self.fastdesc()
951 return self.fastdesc()
943
952
944 def fastasc(self):
953 def fastasc(self):
945 iterrange = xrange(self._start, self._end)
954 iterrange = xrange(self._start, self._end)
946 if self._hiddenrevs:
955 if self._hiddenrevs:
947 return self._iterfilter(iterrange)
956 return self._iterfilter(iterrange)
948 return iter(iterrange)
957 return iter(iterrange)
949
958
950 def fastdesc(self):
959 def fastdesc(self):
951 iterrange = xrange(self._end - 1, self._start - 1, -1)
960 iterrange = xrange(self._end - 1, self._start - 1, -1)
952 if self._hiddenrevs:
961 if self._hiddenrevs:
953 return self._iterfilter(iterrange)
962 return self._iterfilter(iterrange)
954 return iter(iterrange)
963 return iter(iterrange)
955
964
956 def __contains__(self, rev):
965 def __contains__(self, rev):
957 hidden = self._hiddenrevs
966 hidden = self._hiddenrevs
958 return ((self._start <= rev < self._end)
967 return ((self._start <= rev < self._end)
959 and not (hidden and rev in hidden))
968 and not (hidden and rev in hidden))
960
969
961 def __nonzero__(self):
970 def __nonzero__(self):
962 for r in self:
971 for r in self:
963 return True
972 return True
964 return False
973 return False
965
974
966 def __len__(self):
975 def __len__(self):
967 if not self._hiddenrevs:
976 if not self._hiddenrevs:
968 return abs(self._end - self._start)
977 return abs(self._end - self._start)
969 else:
978 else:
970 count = 0
979 count = 0
971 start = self._start
980 start = self._start
972 end = self._end
981 end = self._end
973 for rev in self._hiddenrevs:
982 for rev in self._hiddenrevs:
974 if (end < rev <= start) or (start <= rev < end):
983 if (end < rev <= start) or (start <= rev < end):
975 count += 1
984 count += 1
976 return abs(self._end - self._start) - count
985 return abs(self._end - self._start) - count
977
986
978 def isascending(self):
987 def isascending(self):
979 return self._ascending
988 return self._ascending
980
989
981 def isdescending(self):
990 def isdescending(self):
982 return not self._ascending
991 return not self._ascending
983
992
984 def first(self):
993 def first(self):
985 if self._ascending:
994 if self._ascending:
986 it = self.fastasc
995 it = self.fastasc
987 else:
996 else:
988 it = self.fastdesc
997 it = self.fastdesc
989 for x in it():
998 for x in it():
990 return x
999 return x
991 return None
1000 return None
992
1001
993 def last(self):
1002 def last(self):
994 if self._ascending:
1003 if self._ascending:
995 it = self.fastdesc
1004 it = self.fastdesc
996 else:
1005 else:
997 it = self.fastasc
1006 it = self.fastasc
998 for x in it():
1007 for x in it():
999 return x
1008 return x
1000 return None
1009 return None
1001
1010
1002 def __repr__(self):
1011 def __repr__(self):
1003 d = {False: '-', True: '+'}[self._ascending]
1012 d = {False: '-', True: '+'}[self._ascending]
1004 return '<%s%s %d:%d>' % (type(self).__name__, d,
1013 return '<%s%s %d:%d>' % (type(self).__name__, d,
1005 self._start, self._end - 1)
1014 self._start, self._end - 1)
1006
1015
1007 class fullreposet(spanset):
1016 class fullreposet(spanset):
1008 """a set containing all revisions in the repo
1017 """a set containing all revisions in the repo
1009
1018
1010 This class exists to host special optimization and magic to handle virtual
1019 This class exists to host special optimization and magic to handle virtual
1011 revisions such as "null".
1020 revisions such as "null".
1012 """
1021 """
1013
1022
1014 def __init__(self, repo):
1023 def __init__(self, repo):
1015 super(fullreposet, self).__init__(repo)
1024 super(fullreposet, self).__init__(repo)
1016
1025
1017 def __and__(self, other):
1026 def __and__(self, other):
1018 """As self contains the whole repo, all of the other set should also be
1027 """As self contains the whole repo, all of the other set should also be
1019 in self. Therefore `self & other = other`.
1028 in self. Therefore `self & other = other`.
1020
1029
1021 This boldly assumes the other contains valid revs only.
1030 This boldly assumes the other contains valid revs only.
1022 """
1031 """
1023 # other not a smartset, make is so
1032 # other not a smartset, make is so
1024 if not util.safehasattr(other, 'isascending'):
1033 if not util.safehasattr(other, 'isascending'):
1025 # filter out hidden revision
1034 # filter out hidden revision
1026 # (this boldly assumes all smartset are pure)
1035 # (this boldly assumes all smartset are pure)
1027 #
1036 #
1028 # `other` was used with "&", let's assume this is a set like
1037 # `other` was used with "&", let's assume this is a set like
1029 # object.
1038 # object.
1030 other = baseset(other - self._hiddenrevs)
1039 other = baseset(other - self._hiddenrevs)
1031
1040
1032 other.sort(reverse=self.isdescending())
1041 other.sort(reverse=self.isdescending())
1033 return other
1042 return other
1034
1043
1035 def prettyformat(revs):
1044 def prettyformat(revs):
1036 lines = []
1045 lines = []
1037 rs = repr(revs)
1046 rs = repr(revs)
1038 p = 0
1047 p = 0
1039 while p < len(rs):
1048 while p < len(rs):
1040 q = rs.find('<', p + 1)
1049 q = rs.find('<', p + 1)
1041 if q < 0:
1050 if q < 0:
1042 q = len(rs)
1051 q = len(rs)
1043 l = rs.count('<', 0, p) - rs.count('>', 0, p)
1052 l = rs.count('<', 0, p) - rs.count('>', 0, p)
1044 assert l >= 0
1053 assert l >= 0
1045 lines.append((l, rs[p:q].rstrip()))
1054 lines.append((l, rs[p:q].rstrip()))
1046 p = q
1055 p = q
1047 return '\n'.join(' ' * l + s for l, s in lines)
1056 return '\n'.join(' ' * l + s for l, s in lines)
General Comments 0
You need to be logged in to leave comments. Login now