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