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