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