##// END OF EJS Templates
pycompat: wrap xrange for py2 to provide efficient __contains__...
Joerg Sonnenberger -
r39474:45e05d39 default
parent child Browse files
Show More
@@ -1,563 +1,563 b''
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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 .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 bin,
12 bin,
13 hex,
13 hex,
14 nullid,
14 nullid,
15 )
15 )
16 from .thirdparty import (
16 from .thirdparty import (
17 attr,
17 attr,
18 )
18 )
19
19
20 from . import (
20 from . import (
21 encoding,
21 encoding,
22 error,
22 error,
23 pycompat,
23 pycompat,
24 revlog,
24 revlog,
25 )
25 )
26 from .utils import (
26 from .utils import (
27 dateutil,
27 dateutil,
28 stringutil,
28 stringutil,
29 )
29 )
30
30
31 _defaultextra = {'branch': 'default'}
31 _defaultextra = {'branch': 'default'}
32
32
33 def _string_escape(text):
33 def _string_escape(text):
34 """
34 """
35 >>> from .pycompat import bytechr as chr
35 >>> from .pycompat import bytechr as chr
36 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
36 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
37 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
37 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
38 >>> s
38 >>> s
39 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
39 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
40 >>> res = _string_escape(s)
40 >>> res = _string_escape(s)
41 >>> s == stringutil.unescapestr(res)
41 >>> s == stringutil.unescapestr(res)
42 True
42 True
43 """
43 """
44 # subset of the string_escape codec
44 # subset of the string_escape codec
45 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
45 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
46 return text.replace('\0', '\\0')
46 return text.replace('\0', '\\0')
47
47
48 def decodeextra(text):
48 def decodeextra(text):
49 """
49 """
50 >>> from .pycompat import bytechr as chr
50 >>> from .pycompat import bytechr as chr
51 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
51 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
52 ... ).items())
52 ... ).items())
53 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
53 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
54 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
54 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
55 ... b'baz': chr(92) + chr(0) + b'2'})
55 ... b'baz': chr(92) + chr(0) + b'2'})
56 ... ).items())
56 ... ).items())
57 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
57 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
58 """
58 """
59 extra = _defaultextra.copy()
59 extra = _defaultextra.copy()
60 for l in text.split('\0'):
60 for l in text.split('\0'):
61 if l:
61 if l:
62 if '\\0' in l:
62 if '\\0' in l:
63 # fix up \0 without getting into trouble with \\0
63 # fix up \0 without getting into trouble with \\0
64 l = l.replace('\\\\', '\\\\\n')
64 l = l.replace('\\\\', '\\\\\n')
65 l = l.replace('\\0', '\0')
65 l = l.replace('\\0', '\0')
66 l = l.replace('\n', '')
66 l = l.replace('\n', '')
67 k, v = stringutil.unescapestr(l).split(':', 1)
67 k, v = stringutil.unescapestr(l).split(':', 1)
68 extra[k] = v
68 extra[k] = v
69 return extra
69 return extra
70
70
71 def encodeextra(d):
71 def encodeextra(d):
72 # keys must be sorted to produce a deterministic changelog entry
72 # keys must be sorted to produce a deterministic changelog entry
73 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
73 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
74 return "\0".join(items)
74 return "\0".join(items)
75
75
76 def stripdesc(desc):
76 def stripdesc(desc):
77 """strip trailing whitespace and leading and trailing empty lines"""
77 """strip trailing whitespace and leading and trailing empty lines"""
78 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
78 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
79
79
80 class appender(object):
80 class appender(object):
81 '''the changelog index must be updated last on disk, so we use this class
81 '''the changelog index must be updated last on disk, so we use this class
82 to delay writes to it'''
82 to delay writes to it'''
83 def __init__(self, vfs, name, mode, buf):
83 def __init__(self, vfs, name, mode, buf):
84 self.data = buf
84 self.data = buf
85 fp = vfs(name, mode)
85 fp = vfs(name, mode)
86 self.fp = fp
86 self.fp = fp
87 self.offset = fp.tell()
87 self.offset = fp.tell()
88 self.size = vfs.fstat(fp).st_size
88 self.size = vfs.fstat(fp).st_size
89 self._end = self.size
89 self._end = self.size
90
90
91 def end(self):
91 def end(self):
92 return self._end
92 return self._end
93 def tell(self):
93 def tell(self):
94 return self.offset
94 return self.offset
95 def flush(self):
95 def flush(self):
96 pass
96 pass
97
97
98 @property
98 @property
99 def closed(self):
99 def closed(self):
100 return self.fp.closed
100 return self.fp.closed
101
101
102 def close(self):
102 def close(self):
103 self.fp.close()
103 self.fp.close()
104
104
105 def seek(self, offset, whence=0):
105 def seek(self, offset, whence=0):
106 '''virtual file offset spans real file and data'''
106 '''virtual file offset spans real file and data'''
107 if whence == 0:
107 if whence == 0:
108 self.offset = offset
108 self.offset = offset
109 elif whence == 1:
109 elif whence == 1:
110 self.offset += offset
110 self.offset += offset
111 elif whence == 2:
111 elif whence == 2:
112 self.offset = self.end() + offset
112 self.offset = self.end() + offset
113 if self.offset < self.size:
113 if self.offset < self.size:
114 self.fp.seek(self.offset)
114 self.fp.seek(self.offset)
115
115
116 def read(self, count=-1):
116 def read(self, count=-1):
117 '''only trick here is reads that span real file and data'''
117 '''only trick here is reads that span real file and data'''
118 ret = ""
118 ret = ""
119 if self.offset < self.size:
119 if self.offset < self.size:
120 s = self.fp.read(count)
120 s = self.fp.read(count)
121 ret = s
121 ret = s
122 self.offset += len(s)
122 self.offset += len(s)
123 if count > 0:
123 if count > 0:
124 count -= len(s)
124 count -= len(s)
125 if count != 0:
125 if count != 0:
126 doff = self.offset - self.size
126 doff = self.offset - self.size
127 self.data.insert(0, "".join(self.data))
127 self.data.insert(0, "".join(self.data))
128 del self.data[1:]
128 del self.data[1:]
129 s = self.data[0][doff:doff + count]
129 s = self.data[0][doff:doff + count]
130 self.offset += len(s)
130 self.offset += len(s)
131 ret += s
131 ret += s
132 return ret
132 return ret
133
133
134 def write(self, s):
134 def write(self, s):
135 self.data.append(bytes(s))
135 self.data.append(bytes(s))
136 self.offset += len(s)
136 self.offset += len(s)
137 self._end += len(s)
137 self._end += len(s)
138
138
139 def __enter__(self):
139 def __enter__(self):
140 self.fp.__enter__()
140 self.fp.__enter__()
141 return self
141 return self
142
142
143 def __exit__(self, *args):
143 def __exit__(self, *args):
144 return self.fp.__exit__(*args)
144 return self.fp.__exit__(*args)
145
145
146 def _divertopener(opener, target):
146 def _divertopener(opener, target):
147 """build an opener that writes in 'target.a' instead of 'target'"""
147 """build an opener that writes in 'target.a' instead of 'target'"""
148 def _divert(name, mode='r', checkambig=False):
148 def _divert(name, mode='r', checkambig=False):
149 if name != target:
149 if name != target:
150 return opener(name, mode)
150 return opener(name, mode)
151 return opener(name + ".a", mode)
151 return opener(name + ".a", mode)
152 return _divert
152 return _divert
153
153
154 def _delayopener(opener, target, buf):
154 def _delayopener(opener, target, buf):
155 """build an opener that stores chunks in 'buf' instead of 'target'"""
155 """build an opener that stores chunks in 'buf' instead of 'target'"""
156 def _delay(name, mode='r', checkambig=False):
156 def _delay(name, mode='r', checkambig=False):
157 if name != target:
157 if name != target:
158 return opener(name, mode)
158 return opener(name, mode)
159 return appender(opener, name, mode, buf)
159 return appender(opener, name, mode, buf)
160 return _delay
160 return _delay
161
161
162 @attr.s
162 @attr.s
163 class _changelogrevision(object):
163 class _changelogrevision(object):
164 # Extensions might modify _defaultextra, so let the constructor below pass
164 # Extensions might modify _defaultextra, so let the constructor below pass
165 # it in
165 # it in
166 extra = attr.ib()
166 extra = attr.ib()
167 manifest = attr.ib(default=nullid)
167 manifest = attr.ib(default=nullid)
168 user = attr.ib(default='')
168 user = attr.ib(default='')
169 date = attr.ib(default=(0, 0))
169 date = attr.ib(default=(0, 0))
170 files = attr.ib(default=attr.Factory(list))
170 files = attr.ib(default=attr.Factory(list))
171 description = attr.ib(default='')
171 description = attr.ib(default='')
172
172
173 class changelogrevision(object):
173 class changelogrevision(object):
174 """Holds results of a parsed changelog revision.
174 """Holds results of a parsed changelog revision.
175
175
176 Changelog revisions consist of multiple pieces of data, including
176 Changelog revisions consist of multiple pieces of data, including
177 the manifest node, user, and date. This object exposes a view into
177 the manifest node, user, and date. This object exposes a view into
178 the parsed object.
178 the parsed object.
179 """
179 """
180
180
181 __slots__ = (
181 __slots__ = (
182 u'_offsets',
182 u'_offsets',
183 u'_text',
183 u'_text',
184 )
184 )
185
185
186 def __new__(cls, text):
186 def __new__(cls, text):
187 if not text:
187 if not text:
188 return _changelogrevision(extra=_defaultextra)
188 return _changelogrevision(extra=_defaultextra)
189
189
190 self = super(changelogrevision, cls).__new__(cls)
190 self = super(changelogrevision, cls).__new__(cls)
191 # We could return here and implement the following as an __init__.
191 # We could return here and implement the following as an __init__.
192 # But doing it here is equivalent and saves an extra function call.
192 # But doing it here is equivalent and saves an extra function call.
193
193
194 # format used:
194 # format used:
195 # nodeid\n : manifest node in ascii
195 # nodeid\n : manifest node in ascii
196 # user\n : user, no \n or \r allowed
196 # user\n : user, no \n or \r allowed
197 # time tz extra\n : date (time is int or float, timezone is int)
197 # time tz extra\n : date (time is int or float, timezone is int)
198 # : extra is metadata, encoded and separated by '\0'
198 # : extra is metadata, encoded and separated by '\0'
199 # : older versions ignore it
199 # : older versions ignore it
200 # files\n\n : files modified by the cset, no \n or \r allowed
200 # files\n\n : files modified by the cset, no \n or \r allowed
201 # (.*) : comment (free text, ideally utf-8)
201 # (.*) : comment (free text, ideally utf-8)
202 #
202 #
203 # changelog v0 doesn't use extra
203 # changelog v0 doesn't use extra
204
204
205 nl1 = text.index('\n')
205 nl1 = text.index('\n')
206 nl2 = text.index('\n', nl1 + 1)
206 nl2 = text.index('\n', nl1 + 1)
207 nl3 = text.index('\n', nl2 + 1)
207 nl3 = text.index('\n', nl2 + 1)
208
208
209 # The list of files may be empty. Which means nl3 is the first of the
209 # The list of files may be empty. Which means nl3 is the first of the
210 # double newline that precedes the description.
210 # double newline that precedes the description.
211 if text[nl3 + 1:nl3 + 2] == '\n':
211 if text[nl3 + 1:nl3 + 2] == '\n':
212 doublenl = nl3
212 doublenl = nl3
213 else:
213 else:
214 doublenl = text.index('\n\n', nl3 + 1)
214 doublenl = text.index('\n\n', nl3 + 1)
215
215
216 self._offsets = (nl1, nl2, nl3, doublenl)
216 self._offsets = (nl1, nl2, nl3, doublenl)
217 self._text = text
217 self._text = text
218
218
219 return self
219 return self
220
220
221 @property
221 @property
222 def manifest(self):
222 def manifest(self):
223 return bin(self._text[0:self._offsets[0]])
223 return bin(self._text[0:self._offsets[0]])
224
224
225 @property
225 @property
226 def user(self):
226 def user(self):
227 off = self._offsets
227 off = self._offsets
228 return encoding.tolocal(self._text[off[0] + 1:off[1]])
228 return encoding.tolocal(self._text[off[0] + 1:off[1]])
229
229
230 @property
230 @property
231 def _rawdate(self):
231 def _rawdate(self):
232 off = self._offsets
232 off = self._offsets
233 dateextra = self._text[off[1] + 1:off[2]]
233 dateextra = self._text[off[1] + 1:off[2]]
234 return dateextra.split(' ', 2)[0:2]
234 return dateextra.split(' ', 2)[0:2]
235
235
236 @property
236 @property
237 def _rawextra(self):
237 def _rawextra(self):
238 off = self._offsets
238 off = self._offsets
239 dateextra = self._text[off[1] + 1:off[2]]
239 dateextra = self._text[off[1] + 1:off[2]]
240 fields = dateextra.split(' ', 2)
240 fields = dateextra.split(' ', 2)
241 if len(fields) != 3:
241 if len(fields) != 3:
242 return None
242 return None
243
243
244 return fields[2]
244 return fields[2]
245
245
246 @property
246 @property
247 def date(self):
247 def date(self):
248 raw = self._rawdate
248 raw = self._rawdate
249 time = float(raw[0])
249 time = float(raw[0])
250 # Various tools did silly things with the timezone.
250 # Various tools did silly things with the timezone.
251 try:
251 try:
252 timezone = int(raw[1])
252 timezone = int(raw[1])
253 except ValueError:
253 except ValueError:
254 timezone = 0
254 timezone = 0
255
255
256 return time, timezone
256 return time, timezone
257
257
258 @property
258 @property
259 def extra(self):
259 def extra(self):
260 raw = self._rawextra
260 raw = self._rawextra
261 if raw is None:
261 if raw is None:
262 return _defaultextra
262 return _defaultextra
263
263
264 return decodeextra(raw)
264 return decodeextra(raw)
265
265
266 @property
266 @property
267 def files(self):
267 def files(self):
268 off = self._offsets
268 off = self._offsets
269 if off[2] == off[3]:
269 if off[2] == off[3]:
270 return []
270 return []
271
271
272 return self._text[off[2] + 1:off[3]].split('\n')
272 return self._text[off[2] + 1:off[3]].split('\n')
273
273
274 @property
274 @property
275 def description(self):
275 def description(self):
276 return encoding.tolocal(self._text[self._offsets[3] + 2:])
276 return encoding.tolocal(self._text[self._offsets[3] + 2:])
277
277
278 class changelog(revlog.revlog):
278 class changelog(revlog.revlog):
279 def __init__(self, opener, trypending=False):
279 def __init__(self, opener, trypending=False):
280 """Load a changelog revlog using an opener.
280 """Load a changelog revlog using an opener.
281
281
282 If ``trypending`` is true, we attempt to load the index from a
282 If ``trypending`` is true, we attempt to load the index from a
283 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
283 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
284 The ``00changelog.i.a`` file contains index (and possibly inline
284 The ``00changelog.i.a`` file contains index (and possibly inline
285 revision) data for a transaction that hasn't been finalized yet.
285 revision) data for a transaction that hasn't been finalized yet.
286 It exists in a separate file to facilitate readers (such as
286 It exists in a separate file to facilitate readers (such as
287 hooks processes) accessing data before a transaction is finalized.
287 hooks processes) accessing data before a transaction is finalized.
288 """
288 """
289 if trypending and opener.exists('00changelog.i.a'):
289 if trypending and opener.exists('00changelog.i.a'):
290 indexfile = '00changelog.i.a'
290 indexfile = '00changelog.i.a'
291 else:
291 else:
292 indexfile = '00changelog.i'
292 indexfile = '00changelog.i'
293
293
294 datafile = '00changelog.d'
294 datafile = '00changelog.d'
295 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
295 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
296 checkambig=True, mmaplargeindex=True)
296 checkambig=True, mmaplargeindex=True)
297
297
298 if self._initempty:
298 if self._initempty:
299 # changelogs don't benefit from generaldelta
299 # changelogs don't benefit from generaldelta
300 self.version &= ~revlog.FLAG_GENERALDELTA
300 self.version &= ~revlog.FLAG_GENERALDELTA
301 self._generaldelta = False
301 self._generaldelta = False
302
302
303 # Delta chains for changelogs tend to be very small because entries
303 # Delta chains for changelogs tend to be very small because entries
304 # tend to be small and don't delta well with each. So disable delta
304 # tend to be small and don't delta well with each. So disable delta
305 # chains.
305 # chains.
306 self.storedeltachains = False
306 self.storedeltachains = False
307
307
308 self._realopener = opener
308 self._realopener = opener
309 self._delayed = False
309 self._delayed = False
310 self._delaybuf = None
310 self._delaybuf = None
311 self._divert = False
311 self._divert = False
312 self.filteredrevs = frozenset()
312 self.filteredrevs = frozenset()
313
313
314 def tiprev(self):
314 def tiprev(self):
315 for i in pycompat.xrange(len(self) -1, -2, -1):
315 for i in pycompat.xrange(len(self) -1, -2, -1):
316 if i not in self.filteredrevs:
316 if i not in self.filteredrevs:
317 return i
317 return i
318
318
319 def tip(self):
319 def tip(self):
320 """filtered version of revlog.tip"""
320 """filtered version of revlog.tip"""
321 return self.node(self.tiprev())
321 return self.node(self.tiprev())
322
322
323 def __contains__(self, rev):
323 def __contains__(self, rev):
324 """filtered version of revlog.__contains__"""
324 """filtered version of revlog.__contains__"""
325 return (0 <= rev < len(self)
325 return (0 <= rev < len(self)
326 and rev not in self.filteredrevs)
326 and rev not in self.filteredrevs)
327
327
328 def __iter__(self):
328 def __iter__(self):
329 """filtered version of revlog.__iter__"""
329 """filtered version of revlog.__iter__"""
330 if len(self.filteredrevs) == 0:
330 if len(self.filteredrevs) == 0:
331 return revlog.revlog.__iter__(self)
331 return revlog.revlog.__iter__(self)
332
332
333 def filterediter():
333 def filterediter():
334 for i in pycompat.xrange(len(self)):
334 for i in pycompat.xrange(len(self)):
335 if i not in self.filteredrevs:
335 if i not in self.filteredrevs:
336 yield i
336 yield i
337
337
338 return filterediter()
338 return filterediter()
339
339
340 def revs(self, start=0, stop=None):
340 def revs(self, start=0, stop=None):
341 """filtered version of revlog.revs"""
341 """filtered version of revlog.revs"""
342 for i in super(changelog, self).revs(start, stop):
342 for i in super(changelog, self).revs(start, stop):
343 if i not in self.filteredrevs:
343 if i not in self.filteredrevs:
344 yield i
344 yield i
345
345
346 def reachableroots(self, minroot, heads, roots, includepath=False):
346 def reachableroots(self, minroot, heads, roots, includepath=False):
347 return self.index.reachableroots2(minroot, heads, roots, includepath)
347 return self.index.reachableroots2(minroot, heads, roots, includepath)
348
348
349 def headrevs(self):
349 def headrevs(self):
350 if self.filteredrevs:
350 if self.filteredrevs:
351 try:
351 try:
352 return self.index.headrevsfiltered(self.filteredrevs)
352 return self.index.headrevsfiltered(self.filteredrevs)
353 # AttributeError covers non-c-extension environments and
353 # AttributeError covers non-c-extension environments and
354 # old c extensions without filter handling.
354 # old c extensions without filter handling.
355 except AttributeError:
355 except AttributeError:
356 return self._headrevs()
356 return self._headrevs()
357
357
358 return super(changelog, self).headrevs()
358 return super(changelog, self).headrevs()
359
359
360 def strip(self, *args, **kwargs):
360 def strip(self, *args, **kwargs):
361 # XXX make something better than assert
361 # XXX make something better than assert
362 # We can't expect proper strip behavior if we are filtered.
362 # We can't expect proper strip behavior if we are filtered.
363 assert not self.filteredrevs
363 assert not self.filteredrevs
364 super(changelog, self).strip(*args, **kwargs)
364 super(changelog, self).strip(*args, **kwargs)
365
365
366 def rev(self, node):
366 def rev(self, node):
367 """filtered version of revlog.rev"""
367 """filtered version of revlog.rev"""
368 r = super(changelog, self).rev(node)
368 r = super(changelog, self).rev(node)
369 if r in self.filteredrevs:
369 if r in self.filteredrevs:
370 raise error.FilteredLookupError(hex(node), self.indexfile,
370 raise error.FilteredLookupError(hex(node), self.indexfile,
371 _('filtered node'))
371 _('filtered node'))
372 return r
372 return r
373
373
374 def node(self, rev):
374 def node(self, rev):
375 """filtered version of revlog.node"""
375 """filtered version of revlog.node"""
376 if rev in self.filteredrevs:
376 if rev in self.filteredrevs:
377 raise error.FilteredIndexError(rev)
377 raise error.FilteredIndexError(rev)
378 return super(changelog, self).node(rev)
378 return super(changelog, self).node(rev)
379
379
380 def linkrev(self, rev):
380 def linkrev(self, rev):
381 """filtered version of revlog.linkrev"""
381 """filtered version of revlog.linkrev"""
382 if rev in self.filteredrevs:
382 if rev in self.filteredrevs:
383 raise error.FilteredIndexError(rev)
383 raise error.FilteredIndexError(rev)
384 return super(changelog, self).linkrev(rev)
384 return super(changelog, self).linkrev(rev)
385
385
386 def parentrevs(self, rev):
386 def parentrevs(self, rev):
387 """filtered version of revlog.parentrevs"""
387 """filtered version of revlog.parentrevs"""
388 if rev in self.filteredrevs:
388 if rev in self.filteredrevs:
389 raise error.FilteredIndexError(rev)
389 raise error.FilteredIndexError(rev)
390 return super(changelog, self).parentrevs(rev)
390 return super(changelog, self).parentrevs(rev)
391
391
392 def flags(self, rev):
392 def flags(self, rev):
393 """filtered version of revlog.flags"""
393 """filtered version of revlog.flags"""
394 if rev in self.filteredrevs:
394 if rev in self.filteredrevs:
395 raise error.FilteredIndexError(rev)
395 raise error.FilteredIndexError(rev)
396 return super(changelog, self).flags(rev)
396 return super(changelog, self).flags(rev)
397
397
398 def delayupdate(self, tr):
398 def delayupdate(self, tr):
399 "delay visibility of index updates to other readers"
399 "delay visibility of index updates to other readers"
400
400
401 if not self._delayed:
401 if not self._delayed:
402 if len(self) == 0:
402 if len(self) == 0:
403 self._divert = True
403 self._divert = True
404 if self._realopener.exists(self.indexfile + '.a'):
404 if self._realopener.exists(self.indexfile + '.a'):
405 self._realopener.unlink(self.indexfile + '.a')
405 self._realopener.unlink(self.indexfile + '.a')
406 self.opener = _divertopener(self._realopener, self.indexfile)
406 self.opener = _divertopener(self._realopener, self.indexfile)
407 else:
407 else:
408 self._delaybuf = []
408 self._delaybuf = []
409 self.opener = _delayopener(self._realopener, self.indexfile,
409 self.opener = _delayopener(self._realopener, self.indexfile,
410 self._delaybuf)
410 self._delaybuf)
411 self._delayed = True
411 self._delayed = True
412 tr.addpending('cl-%i' % id(self), self._writepending)
412 tr.addpending('cl-%i' % id(self), self._writepending)
413 tr.addfinalize('cl-%i' % id(self), self._finalize)
413 tr.addfinalize('cl-%i' % id(self), self._finalize)
414
414
415 def _finalize(self, tr):
415 def _finalize(self, tr):
416 "finalize index updates"
416 "finalize index updates"
417 self._delayed = False
417 self._delayed = False
418 self.opener = self._realopener
418 self.opener = self._realopener
419 # move redirected index data back into place
419 # move redirected index data back into place
420 if self._divert:
420 if self._divert:
421 assert not self._delaybuf
421 assert not self._delaybuf
422 tmpname = self.indexfile + ".a"
422 tmpname = self.indexfile + ".a"
423 nfile = self.opener.open(tmpname)
423 nfile = self.opener.open(tmpname)
424 nfile.close()
424 nfile.close()
425 self.opener.rename(tmpname, self.indexfile, checkambig=True)
425 self.opener.rename(tmpname, self.indexfile, checkambig=True)
426 elif self._delaybuf:
426 elif self._delaybuf:
427 fp = self.opener(self.indexfile, 'a', checkambig=True)
427 fp = self.opener(self.indexfile, 'a', checkambig=True)
428 fp.write("".join(self._delaybuf))
428 fp.write("".join(self._delaybuf))
429 fp.close()
429 fp.close()
430 self._delaybuf = None
430 self._delaybuf = None
431 self._divert = False
431 self._divert = False
432 # split when we're done
432 # split when we're done
433 self._enforceinlinesize(tr)
433 self._enforceinlinesize(tr)
434
434
435 def _writepending(self, tr):
435 def _writepending(self, tr):
436 "create a file containing the unfinalized state for pretxnchangegroup"
436 "create a file containing the unfinalized state for pretxnchangegroup"
437 if self._delaybuf:
437 if self._delaybuf:
438 # make a temporary copy of the index
438 # make a temporary copy of the index
439 fp1 = self._realopener(self.indexfile)
439 fp1 = self._realopener(self.indexfile)
440 pendingfilename = self.indexfile + ".a"
440 pendingfilename = self.indexfile + ".a"
441 # register as a temp file to ensure cleanup on failure
441 # register as a temp file to ensure cleanup on failure
442 tr.registertmp(pendingfilename)
442 tr.registertmp(pendingfilename)
443 # write existing data
443 # write existing data
444 fp2 = self._realopener(pendingfilename, "w")
444 fp2 = self._realopener(pendingfilename, "w")
445 fp2.write(fp1.read())
445 fp2.write(fp1.read())
446 # add pending data
446 # add pending data
447 fp2.write("".join(self._delaybuf))
447 fp2.write("".join(self._delaybuf))
448 fp2.close()
448 fp2.close()
449 # switch modes so finalize can simply rename
449 # switch modes so finalize can simply rename
450 self._delaybuf = None
450 self._delaybuf = None
451 self._divert = True
451 self._divert = True
452 self.opener = _divertopener(self._realopener, self.indexfile)
452 self.opener = _divertopener(self._realopener, self.indexfile)
453
453
454 if self._divert:
454 if self._divert:
455 return True
455 return True
456
456
457 return False
457 return False
458
458
459 def _enforceinlinesize(self, tr, fp=None):
459 def _enforceinlinesize(self, tr, fp=None):
460 if not self._delayed:
460 if not self._delayed:
461 revlog.revlog._enforceinlinesize(self, tr, fp)
461 revlog.revlog._enforceinlinesize(self, tr, fp)
462
462
463 def read(self, node):
463 def read(self, node):
464 """Obtain data from a parsed changelog revision.
464 """Obtain data from a parsed changelog revision.
465
465
466 Returns a 6-tuple of:
466 Returns a 6-tuple of:
467
467
468 - manifest node in binary
468 - manifest node in binary
469 - author/user as a localstr
469 - author/user as a localstr
470 - date as a 2-tuple of (time, timezone)
470 - date as a 2-tuple of (time, timezone)
471 - list of files
471 - list of files
472 - commit message as a localstr
472 - commit message as a localstr
473 - dict of extra metadata
473 - dict of extra metadata
474
474
475 Unless you need to access all fields, consider calling
475 Unless you need to access all fields, consider calling
476 ``changelogrevision`` instead, as it is faster for partial object
476 ``changelogrevision`` instead, as it is faster for partial object
477 access.
477 access.
478 """
478 """
479 c = changelogrevision(self.revision(node))
479 c = changelogrevision(self.revision(node))
480 return (
480 return (
481 c.manifest,
481 c.manifest,
482 c.user,
482 c.user,
483 c.date,
483 c.date,
484 c.files,
484 c.files,
485 c.description,
485 c.description,
486 c.extra
486 c.extra
487 )
487 )
488
488
489 def changelogrevision(self, nodeorrev):
489 def changelogrevision(self, nodeorrev):
490 """Obtain a ``changelogrevision`` for a node or revision."""
490 """Obtain a ``changelogrevision`` for a node or revision."""
491 return changelogrevision(self.revision(nodeorrev))
491 return changelogrevision(self.revision(nodeorrev))
492
492
493 def readfiles(self, node):
493 def readfiles(self, node):
494 """
494 """
495 short version of read that only returns the files modified by the cset
495 short version of read that only returns the files modified by the cset
496 """
496 """
497 text = self.revision(node)
497 text = self.revision(node)
498 if not text:
498 if not text:
499 return []
499 return []
500 last = text.index("\n\n")
500 last = text.index("\n\n")
501 l = text[:last].split('\n')
501 l = text[:last].split('\n')
502 return l[3:]
502 return l[3:]
503
503
504 def add(self, manifest, files, desc, transaction, p1, p2,
504 def add(self, manifest, files, desc, transaction, p1, p2,
505 user, date=None, extra=None):
505 user, date=None, extra=None):
506 # Convert to UTF-8 encoded bytestrings as the very first
506 # Convert to UTF-8 encoded bytestrings as the very first
507 # thing: calling any method on a localstr object will turn it
507 # thing: calling any method on a localstr object will turn it
508 # into a str object and the cached UTF-8 string is thus lost.
508 # into a str object and the cached UTF-8 string is thus lost.
509 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
509 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
510
510
511 user = user.strip()
511 user = user.strip()
512 # An empty username or a username with a "\n" will make the
512 # An empty username or a username with a "\n" will make the
513 # revision text contain two "\n\n" sequences -> corrupt
513 # revision text contain two "\n\n" sequences -> corrupt
514 # repository since read cannot unpack the revision.
514 # repository since read cannot unpack the revision.
515 if not user:
515 if not user:
516 raise error.RevlogError(_("empty username"))
516 raise error.RevlogError(_("empty username"))
517 if "\n" in user:
517 if "\n" in user:
518 raise error.RevlogError(_("username %r contains a newline")
518 raise error.RevlogError(_("username %r contains a newline")
519 % pycompat.bytestr(user))
519 % pycompat.bytestr(user))
520
520
521 desc = stripdesc(desc)
521 desc = stripdesc(desc)
522
522
523 if date:
523 if date:
524 parseddate = "%d %d" % dateutil.parsedate(date)
524 parseddate = "%d %d" % dateutil.parsedate(date)
525 else:
525 else:
526 parseddate = "%d %d" % dateutil.makedate()
526 parseddate = "%d %d" % dateutil.makedate()
527 if extra:
527 if extra:
528 branch = extra.get("branch")
528 branch = extra.get("branch")
529 if branch in ("default", ""):
529 if branch in ("default", ""):
530 del extra["branch"]
530 del extra["branch"]
531 elif branch in (".", "null", "tip"):
531 elif branch in (".", "null", "tip"):
532 raise error.RevlogError(_('the name \'%s\' is reserved')
532 raise error.RevlogError(_('the name \'%s\' is reserved')
533 % branch)
533 % branch)
534 if extra:
534 if extra:
535 extra = encodeextra(extra)
535 extra = encodeextra(extra)
536 parseddate = "%s %s" % (parseddate, extra)
536 parseddate = "%s %s" % (parseddate, extra)
537 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
537 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
538 text = "\n".join(l)
538 text = "\n".join(l)
539 return self.addrevision(text, transaction, len(self), p1, p2)
539 return self.addrevision(text, transaction, len(self), p1, p2)
540
540
541 def branchinfo(self, rev):
541 def branchinfo(self, rev):
542 """return the branch name and open/close state of a revision
542 """return the branch name and open/close state of a revision
543
543
544 This function exists because creating a changectx object
544 This function exists because creating a changectx object
545 just to access this is costly."""
545 just to access this is costly."""
546 extra = self.read(rev)[5]
546 extra = self.read(rev)[5]
547 return encoding.tolocal(extra.get("branch")), 'close' in extra
547 return encoding.tolocal(extra.get("branch")), 'close' in extra
548
548
549 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
549 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
550 # overlay over the standard revlog._addrevision to track the new
550 # overlay over the standard revlog._addrevision to track the new
551 # revision on the transaction.
551 # revision on the transaction.
552 rev = len(self)
552 rev = len(self)
553 node = super(changelog, self)._addrevision(node, rawtext, transaction,
553 node = super(changelog, self)._addrevision(node, rawtext, transaction,
554 *args, **kwargs)
554 *args, **kwargs)
555 revs = transaction.changes.get('revs')
555 revs = transaction.changes.get('revs')
556 if revs is not None:
556 if revs is not None:
557 if revs:
557 if revs:
558 assert revs[-1] + 1 == rev
558 assert revs[-1] + 1 == rev
559 revs = pycompat.xrange(revs[0], rev + 1)
559 revs = pycompat.membershiprange(revs[0], rev + 1)
560 else:
560 else:
561 revs = pycompat.xrange(rev, rev + 1)
561 revs = pycompat.membershiprange(rev, rev + 1)
562 transaction.changes['revs'] = revs
562 transaction.changes['revs'] = revs
563 return node
563 return node
@@ -1,429 +1,449 b''
1 # pycompat.py - portability shim for python 3
1 # pycompat.py - portability shim for python 3
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """Mercurial portability shim for python 3.
6 """Mercurial portability shim for python 3.
7
7
8 This contains aliases to hide python version-specific details from the core.
8 This contains aliases to hide python version-specific details from the core.
9 """
9 """
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import getopt
13 import getopt
14 import inspect
14 import inspect
15 import os
15 import os
16 import shlex
16 import shlex
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19
19
20 ispy3 = (sys.version_info[0] >= 3)
20 ispy3 = (sys.version_info[0] >= 3)
21 ispypy = (r'__pypy__' in sys.builtin_module_names)
21 ispypy = (r'__pypy__' in sys.builtin_module_names)
22
22
23 if not ispy3:
23 if not ispy3:
24 import cookielib
24 import cookielib
25 import cPickle as pickle
25 import cPickle as pickle
26 import httplib
26 import httplib
27 import Queue as queue
27 import Queue as queue
28 import SocketServer as socketserver
28 import SocketServer as socketserver
29 import xmlrpclib
29 import xmlrpclib
30
30
31 from .thirdparty.concurrent import futures
31 from .thirdparty.concurrent import futures
32
32
33 def future_set_exception_info(f, exc_info):
33 def future_set_exception_info(f, exc_info):
34 f.set_exception_info(*exc_info)
34 f.set_exception_info(*exc_info)
35 else:
35 else:
36 import concurrent.futures as futures
36 import concurrent.futures as futures
37 import http.cookiejar as cookielib
37 import http.cookiejar as cookielib
38 import http.client as httplib
38 import http.client as httplib
39 import pickle
39 import pickle
40 import queue as queue
40 import queue as queue
41 import socketserver
41 import socketserver
42 import xmlrpc.client as xmlrpclib
42 import xmlrpc.client as xmlrpclib
43
43
44 def future_set_exception_info(f, exc_info):
44 def future_set_exception_info(f, exc_info):
45 f.set_exception(exc_info[0])
45 f.set_exception(exc_info[0])
46
46
47 def identity(a):
47 def identity(a):
48 return a
48 return a
49
49
50 def _rapply(f, xs):
50 def _rapply(f, xs):
51 if xs is None:
51 if xs is None:
52 # assume None means non-value of optional data
52 # assume None means non-value of optional data
53 return xs
53 return xs
54 if isinstance(xs, (list, set, tuple)):
54 if isinstance(xs, (list, set, tuple)):
55 return type(xs)(_rapply(f, x) for x in xs)
55 return type(xs)(_rapply(f, x) for x in xs)
56 if isinstance(xs, dict):
56 if isinstance(xs, dict):
57 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
57 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
58 return f(xs)
58 return f(xs)
59
59
60 def rapply(f, xs):
60 def rapply(f, xs):
61 """Apply function recursively to every item preserving the data structure
61 """Apply function recursively to every item preserving the data structure
62
62
63 >>> def f(x):
63 >>> def f(x):
64 ... return 'f(%s)' % x
64 ... return 'f(%s)' % x
65 >>> rapply(f, None) is None
65 >>> rapply(f, None) is None
66 True
66 True
67 >>> rapply(f, 'a')
67 >>> rapply(f, 'a')
68 'f(a)'
68 'f(a)'
69 >>> rapply(f, {'a'}) == {'f(a)'}
69 >>> rapply(f, {'a'}) == {'f(a)'}
70 True
70 True
71 >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
71 >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
72 ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
72 ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
73
73
74 >>> xs = [object()]
74 >>> xs = [object()]
75 >>> rapply(identity, xs) is xs
75 >>> rapply(identity, xs) is xs
76 True
76 True
77 """
77 """
78 if f is identity:
78 if f is identity:
79 # fast path mainly for py2
79 # fast path mainly for py2
80 return xs
80 return xs
81 return _rapply(f, xs)
81 return _rapply(f, xs)
82
82
83 if ispy3:
83 if ispy3:
84 import builtins
84 import builtins
85 import functools
85 import functools
86 import io
86 import io
87 import struct
87 import struct
88
88
89 fsencode = os.fsencode
89 fsencode = os.fsencode
90 fsdecode = os.fsdecode
90 fsdecode = os.fsdecode
91 oscurdir = os.curdir.encode('ascii')
91 oscurdir = os.curdir.encode('ascii')
92 oslinesep = os.linesep.encode('ascii')
92 oslinesep = os.linesep.encode('ascii')
93 osname = os.name.encode('ascii')
93 osname = os.name.encode('ascii')
94 ospathsep = os.pathsep.encode('ascii')
94 ospathsep = os.pathsep.encode('ascii')
95 ospardir = os.pardir.encode('ascii')
95 ospardir = os.pardir.encode('ascii')
96 ossep = os.sep.encode('ascii')
96 ossep = os.sep.encode('ascii')
97 osaltsep = os.altsep
97 osaltsep = os.altsep
98 if osaltsep:
98 if osaltsep:
99 osaltsep = osaltsep.encode('ascii')
99 osaltsep = osaltsep.encode('ascii')
100 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
100 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
101 # returns bytes.
101 # returns bytes.
102 getcwd = os.getcwdb
102 getcwd = os.getcwdb
103 sysplatform = sys.platform.encode('ascii')
103 sysplatform = sys.platform.encode('ascii')
104 sysexecutable = sys.executable
104 sysexecutable = sys.executable
105 if sysexecutable:
105 if sysexecutable:
106 sysexecutable = os.fsencode(sysexecutable)
106 sysexecutable = os.fsencode(sysexecutable)
107 bytesio = io.BytesIO
107 bytesio = io.BytesIO
108 # TODO deprecate stringio name, as it is a lie on Python 3.
108 # TODO deprecate stringio name, as it is a lie on Python 3.
109 stringio = bytesio
109 stringio = bytesio
110
110
111 def maplist(*args):
111 def maplist(*args):
112 return list(map(*args))
112 return list(map(*args))
113
113
114 def rangelist(*args):
114 def rangelist(*args):
115 return list(range(*args))
115 return list(range(*args))
116
116
117 def ziplist(*args):
117 def ziplist(*args):
118 return list(zip(*args))
118 return list(zip(*args))
119
119
120 rawinput = input
120 rawinput = input
121 getargspec = inspect.getfullargspec
121 getargspec = inspect.getfullargspec
122
122
123 # TODO: .buffer might not exist if std streams were replaced; we'll need
123 # TODO: .buffer might not exist if std streams were replaced; we'll need
124 # a silly wrapper to make a bytes stream backed by a unicode one.
124 # a silly wrapper to make a bytes stream backed by a unicode one.
125 stdin = sys.stdin.buffer
125 stdin = sys.stdin.buffer
126 stdout = sys.stdout.buffer
126 stdout = sys.stdout.buffer
127 stderr = sys.stderr.buffer
127 stderr = sys.stderr.buffer
128
128
129 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
129 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
130 # we can use os.fsencode() to get back bytes argv.
130 # we can use os.fsencode() to get back bytes argv.
131 #
131 #
132 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
132 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
133 #
133 #
134 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
134 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
135 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
135 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
136 if getattr(sys, 'argv', None) is not None:
136 if getattr(sys, 'argv', None) is not None:
137 sysargv = list(map(os.fsencode, sys.argv))
137 sysargv = list(map(os.fsencode, sys.argv))
138
138
139 bytechr = struct.Struct('>B').pack
139 bytechr = struct.Struct('>B').pack
140 byterepr = b'%r'.__mod__
140 byterepr = b'%r'.__mod__
141
141
142 class bytestr(bytes):
142 class bytestr(bytes):
143 """A bytes which mostly acts as a Python 2 str
143 """A bytes which mostly acts as a Python 2 str
144
144
145 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
145 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
146 ('', 'foo', 'ascii', '1')
146 ('', 'foo', 'ascii', '1')
147 >>> s = bytestr(b'foo')
147 >>> s = bytestr(b'foo')
148 >>> assert s is bytestr(s)
148 >>> assert s is bytestr(s)
149
149
150 __bytes__() should be called if provided:
150 __bytes__() should be called if provided:
151
151
152 >>> class bytesable(object):
152 >>> class bytesable(object):
153 ... def __bytes__(self):
153 ... def __bytes__(self):
154 ... return b'bytes'
154 ... return b'bytes'
155 >>> bytestr(bytesable())
155 >>> bytestr(bytesable())
156 'bytes'
156 'bytes'
157
157
158 There's no implicit conversion from non-ascii str as its encoding is
158 There's no implicit conversion from non-ascii str as its encoding is
159 unknown:
159 unknown:
160
160
161 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
161 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
162 Traceback (most recent call last):
162 Traceback (most recent call last):
163 ...
163 ...
164 UnicodeEncodeError: ...
164 UnicodeEncodeError: ...
165
165
166 Comparison between bytestr and bytes should work:
166 Comparison between bytestr and bytes should work:
167
167
168 >>> assert bytestr(b'foo') == b'foo'
168 >>> assert bytestr(b'foo') == b'foo'
169 >>> assert b'foo' == bytestr(b'foo')
169 >>> assert b'foo' == bytestr(b'foo')
170 >>> assert b'f' in bytestr(b'foo')
170 >>> assert b'f' in bytestr(b'foo')
171 >>> assert bytestr(b'f') in b'foo'
171 >>> assert bytestr(b'f') in b'foo'
172
172
173 Sliced elements should be bytes, not integer:
173 Sliced elements should be bytes, not integer:
174
174
175 >>> s[1], s[:2]
175 >>> s[1], s[:2]
176 (b'o', b'fo')
176 (b'o', b'fo')
177 >>> list(s), list(reversed(s))
177 >>> list(s), list(reversed(s))
178 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
178 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
179
179
180 As bytestr type isn't propagated across operations, you need to cast
180 As bytestr type isn't propagated across operations, you need to cast
181 bytes to bytestr explicitly:
181 bytes to bytestr explicitly:
182
182
183 >>> s = bytestr(b'foo').upper()
183 >>> s = bytestr(b'foo').upper()
184 >>> t = bytestr(s)
184 >>> t = bytestr(s)
185 >>> s[0], t[0]
185 >>> s[0], t[0]
186 (70, b'F')
186 (70, b'F')
187
187
188 Be careful to not pass a bytestr object to a function which expects
188 Be careful to not pass a bytestr object to a function which expects
189 bytearray-like behavior.
189 bytearray-like behavior.
190
190
191 >>> t = bytes(t) # cast to bytes
191 >>> t = bytes(t) # cast to bytes
192 >>> assert type(t) is bytes
192 >>> assert type(t) is bytes
193 """
193 """
194
194
195 def __new__(cls, s=b''):
195 def __new__(cls, s=b''):
196 if isinstance(s, bytestr):
196 if isinstance(s, bytestr):
197 return s
197 return s
198 if (not isinstance(s, (bytes, bytearray))
198 if (not isinstance(s, (bytes, bytearray))
199 and not hasattr(s, u'__bytes__')): # hasattr-py3-only
199 and not hasattr(s, u'__bytes__')): # hasattr-py3-only
200 s = str(s).encode(u'ascii')
200 s = str(s).encode(u'ascii')
201 return bytes.__new__(cls, s)
201 return bytes.__new__(cls, s)
202
202
203 def __getitem__(self, key):
203 def __getitem__(self, key):
204 s = bytes.__getitem__(self, key)
204 s = bytes.__getitem__(self, key)
205 if not isinstance(s, bytes):
205 if not isinstance(s, bytes):
206 s = bytechr(s)
206 s = bytechr(s)
207 return s
207 return s
208
208
209 def __iter__(self):
209 def __iter__(self):
210 return iterbytestr(bytes.__iter__(self))
210 return iterbytestr(bytes.__iter__(self))
211
211
212 def __repr__(self):
212 def __repr__(self):
213 return bytes.__repr__(self)[1:] # drop b''
213 return bytes.__repr__(self)[1:] # drop b''
214
214
215 def iterbytestr(s):
215 def iterbytestr(s):
216 """Iterate bytes as if it were a str object of Python 2"""
216 """Iterate bytes as if it were a str object of Python 2"""
217 return map(bytechr, s)
217 return map(bytechr, s)
218
218
219 def maybebytestr(s):
219 def maybebytestr(s):
220 """Promote bytes to bytestr"""
220 """Promote bytes to bytestr"""
221 if isinstance(s, bytes):
221 if isinstance(s, bytes):
222 return bytestr(s)
222 return bytestr(s)
223 return s
223 return s
224
224
225 def sysbytes(s):
225 def sysbytes(s):
226 """Convert an internal str (e.g. keyword, __doc__) back to bytes
226 """Convert an internal str (e.g. keyword, __doc__) back to bytes
227
227
228 This never raises UnicodeEncodeError, but only ASCII characters
228 This never raises UnicodeEncodeError, but only ASCII characters
229 can be round-trip by sysstr(sysbytes(s)).
229 can be round-trip by sysstr(sysbytes(s)).
230 """
230 """
231 return s.encode(u'utf-8')
231 return s.encode(u'utf-8')
232
232
233 def sysstr(s):
233 def sysstr(s):
234 """Return a keyword str to be passed to Python functions such as
234 """Return a keyword str to be passed to Python functions such as
235 getattr() and str.encode()
235 getattr() and str.encode()
236
236
237 This never raises UnicodeDecodeError. Non-ascii characters are
237 This never raises UnicodeDecodeError. Non-ascii characters are
238 considered invalid and mapped to arbitrary but unique code points
238 considered invalid and mapped to arbitrary but unique code points
239 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
239 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
240 """
240 """
241 if isinstance(s, builtins.str):
241 if isinstance(s, builtins.str):
242 return s
242 return s
243 return s.decode(u'latin-1')
243 return s.decode(u'latin-1')
244
244
245 def strurl(url):
245 def strurl(url):
246 """Converts a bytes url back to str"""
246 """Converts a bytes url back to str"""
247 if isinstance(url, bytes):
247 if isinstance(url, bytes):
248 return url.decode(u'ascii')
248 return url.decode(u'ascii')
249 return url
249 return url
250
250
251 def bytesurl(url):
251 def bytesurl(url):
252 """Converts a str url to bytes by encoding in ascii"""
252 """Converts a str url to bytes by encoding in ascii"""
253 if isinstance(url, str):
253 if isinstance(url, str):
254 return url.encode(u'ascii')
254 return url.encode(u'ascii')
255 return url
255 return url
256
256
257 def raisewithtb(exc, tb):
257 def raisewithtb(exc, tb):
258 """Raise exception with the given traceback"""
258 """Raise exception with the given traceback"""
259 raise exc.with_traceback(tb)
259 raise exc.with_traceback(tb)
260
260
261 def getdoc(obj):
261 def getdoc(obj):
262 """Get docstring as bytes; may be None so gettext() won't confuse it
262 """Get docstring as bytes; may be None so gettext() won't confuse it
263 with _('')"""
263 with _('')"""
264 doc = getattr(obj, u'__doc__', None)
264 doc = getattr(obj, u'__doc__', None)
265 if doc is None:
265 if doc is None:
266 return doc
266 return doc
267 return sysbytes(doc)
267 return sysbytes(doc)
268
268
269 def _wrapattrfunc(f):
269 def _wrapattrfunc(f):
270 @functools.wraps(f)
270 @functools.wraps(f)
271 def w(object, name, *args):
271 def w(object, name, *args):
272 return f(object, sysstr(name), *args)
272 return f(object, sysstr(name), *args)
273 return w
273 return w
274
274
275 # these wrappers are automagically imported by hgloader
275 # these wrappers are automagically imported by hgloader
276 delattr = _wrapattrfunc(builtins.delattr)
276 delattr = _wrapattrfunc(builtins.delattr)
277 getattr = _wrapattrfunc(builtins.getattr)
277 getattr = _wrapattrfunc(builtins.getattr)
278 hasattr = _wrapattrfunc(builtins.hasattr)
278 hasattr = _wrapattrfunc(builtins.hasattr)
279 setattr = _wrapattrfunc(builtins.setattr)
279 setattr = _wrapattrfunc(builtins.setattr)
280 xrange = builtins.range
280 xrange = builtins.range
281 membershiprange = builtins.range
281 unicode = str
282 unicode = str
282
283
283 def open(name, mode='r', buffering=-1, encoding=None):
284 def open(name, mode='r', buffering=-1, encoding=None):
284 return builtins.open(name, sysstr(mode), buffering, encoding)
285 return builtins.open(name, sysstr(mode), buffering, encoding)
285
286
286 safehasattr = _wrapattrfunc(builtins.hasattr)
287 safehasattr = _wrapattrfunc(builtins.hasattr)
287
288
288 def _getoptbwrapper(orig, args, shortlist, namelist):
289 def _getoptbwrapper(orig, args, shortlist, namelist):
289 """
290 """
290 Takes bytes arguments, converts them to unicode, pass them to
291 Takes bytes arguments, converts them to unicode, pass them to
291 getopt.getopt(), convert the returned values back to bytes and then
292 getopt.getopt(), convert the returned values back to bytes and then
292 return them for Python 3 compatibility as getopt.getopt() don't accepts
293 return them for Python 3 compatibility as getopt.getopt() don't accepts
293 bytes on Python 3.
294 bytes on Python 3.
294 """
295 """
295 args = [a.decode('latin-1') for a in args]
296 args = [a.decode('latin-1') for a in args]
296 shortlist = shortlist.decode('latin-1')
297 shortlist = shortlist.decode('latin-1')
297 namelist = [a.decode('latin-1') for a in namelist]
298 namelist = [a.decode('latin-1') for a in namelist]
298 opts, args = orig(args, shortlist, namelist)
299 opts, args = orig(args, shortlist, namelist)
299 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
300 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
300 for a in opts]
301 for a in opts]
301 args = [a.encode('latin-1') for a in args]
302 args = [a.encode('latin-1') for a in args]
302 return opts, args
303 return opts, args
303
304
304 def strkwargs(dic):
305 def strkwargs(dic):
305 """
306 """
306 Converts the keys of a python dictonary to str i.e. unicodes so that
307 Converts the keys of a python dictonary to str i.e. unicodes so that
307 they can be passed as keyword arguments as dictonaries with bytes keys
308 they can be passed as keyword arguments as dictonaries with bytes keys
308 can't be passed as keyword arguments to functions on Python 3.
309 can't be passed as keyword arguments to functions on Python 3.
309 """
310 """
310 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
311 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
311 return dic
312 return dic
312
313
313 def byteskwargs(dic):
314 def byteskwargs(dic):
314 """
315 """
315 Converts keys of python dictonaries to bytes as they were converted to
316 Converts keys of python dictonaries to bytes as they were converted to
316 str to pass that dictonary as a keyword argument on Python 3.
317 str to pass that dictonary as a keyword argument on Python 3.
317 """
318 """
318 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
319 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
319 return dic
320 return dic
320
321
321 # TODO: handle shlex.shlex().
322 # TODO: handle shlex.shlex().
322 def shlexsplit(s, comments=False, posix=True):
323 def shlexsplit(s, comments=False, posix=True):
323 """
324 """
324 Takes bytes argument, convert it to str i.e. unicodes, pass that into
325 Takes bytes argument, convert it to str i.e. unicodes, pass that into
325 shlex.split(), convert the returned value to bytes and return that for
326 shlex.split(), convert the returned value to bytes and return that for
326 Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
327 Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
327 """
328 """
328 ret = shlex.split(s.decode('latin-1'), comments, posix)
329 ret = shlex.split(s.decode('latin-1'), comments, posix)
329 return [a.encode('latin-1') for a in ret]
330 return [a.encode('latin-1') for a in ret]
330
331
331 else:
332 else:
332 import cStringIO
333 import cStringIO
333
334
334 xrange = xrange
335 xrange = xrange
335 unicode = unicode
336 unicode = unicode
336 bytechr = chr
337 bytechr = chr
337 byterepr = repr
338 byterepr = repr
338 bytestr = str
339 bytestr = str
339 iterbytestr = iter
340 iterbytestr = iter
340 maybebytestr = identity
341 maybebytestr = identity
341 sysbytes = identity
342 sysbytes = identity
342 sysstr = identity
343 sysstr = identity
343 strurl = identity
344 strurl = identity
344 bytesurl = identity
345 bytesurl = identity
345
346
347 class membershiprange(object):
348 "Like xrange(a,b) but with constant-time membership test"
349 def __init__(self, a, b):
350 self._range = xrange(a, b)
351 def __getitem__(self, n):
352 return self._range[n]
353 def __hash__(self):
354 return hash(self._range)
355 def __iter__(self):
356 return iter(self._range)
357 def __len__(self):
358 return len(self._range)
359 def __reversed__(self):
360 return reversed(self._range)
361 def __contains__(self, n):
362 if not self._range:
363 return False
364 return n >= self._range[0] and n <= self._range[-1]
365
346 # this can't be parsed on Python 3
366 # this can't be parsed on Python 3
347 exec('def raisewithtb(exc, tb):\n'
367 exec('def raisewithtb(exc, tb):\n'
348 ' raise exc, None, tb\n')
368 ' raise exc, None, tb\n')
349
369
350 def fsencode(filename):
370 def fsencode(filename):
351 """
371 """
352 Partial backport from os.py in Python 3, which only accepts bytes.
372 Partial backport from os.py in Python 3, which only accepts bytes.
353 In Python 2, our paths should only ever be bytes, a unicode path
373 In Python 2, our paths should only ever be bytes, a unicode path
354 indicates a bug.
374 indicates a bug.
355 """
375 """
356 if isinstance(filename, str):
376 if isinstance(filename, str):
357 return filename
377 return filename
358 else:
378 else:
359 raise TypeError(
379 raise TypeError(
360 "expect str, not %s" % type(filename).__name__)
380 "expect str, not %s" % type(filename).__name__)
361
381
362 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
382 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
363 # better not to touch Python 2 part as it's already working fine.
383 # better not to touch Python 2 part as it's already working fine.
364 fsdecode = identity
384 fsdecode = identity
365
385
366 def getdoc(obj):
386 def getdoc(obj):
367 return getattr(obj, '__doc__', None)
387 return getattr(obj, '__doc__', None)
368
388
369 _notset = object()
389 _notset = object()
370
390
371 def safehasattr(thing, attr):
391 def safehasattr(thing, attr):
372 return getattr(thing, attr, _notset) is not _notset
392 return getattr(thing, attr, _notset) is not _notset
373
393
374 def _getoptbwrapper(orig, args, shortlist, namelist):
394 def _getoptbwrapper(orig, args, shortlist, namelist):
375 return orig(args, shortlist, namelist)
395 return orig(args, shortlist, namelist)
376
396
377 strkwargs = identity
397 strkwargs = identity
378 byteskwargs = identity
398 byteskwargs = identity
379
399
380 oscurdir = os.curdir
400 oscurdir = os.curdir
381 oslinesep = os.linesep
401 oslinesep = os.linesep
382 osname = os.name
402 osname = os.name
383 ospathsep = os.pathsep
403 ospathsep = os.pathsep
384 ospardir = os.pardir
404 ospardir = os.pardir
385 ossep = os.sep
405 ossep = os.sep
386 osaltsep = os.altsep
406 osaltsep = os.altsep
387 stdin = sys.stdin
407 stdin = sys.stdin
388 stdout = sys.stdout
408 stdout = sys.stdout
389 stderr = sys.stderr
409 stderr = sys.stderr
390 if getattr(sys, 'argv', None) is not None:
410 if getattr(sys, 'argv', None) is not None:
391 sysargv = sys.argv
411 sysargv = sys.argv
392 sysplatform = sys.platform
412 sysplatform = sys.platform
393 getcwd = os.getcwd
413 getcwd = os.getcwd
394 sysexecutable = sys.executable
414 sysexecutable = sys.executable
395 shlexsplit = shlex.split
415 shlexsplit = shlex.split
396 bytesio = cStringIO.StringIO
416 bytesio = cStringIO.StringIO
397 stringio = bytesio
417 stringio = bytesio
398 maplist = map
418 maplist = map
399 rangelist = range
419 rangelist = range
400 ziplist = zip
420 ziplist = zip
401 rawinput = raw_input
421 rawinput = raw_input
402 getargspec = inspect.getargspec
422 getargspec = inspect.getargspec
403
423
404 isjython = sysplatform.startswith('java')
424 isjython = sysplatform.startswith('java')
405
425
406 isdarwin = sysplatform == 'darwin'
426 isdarwin = sysplatform == 'darwin'
407 isposix = osname == 'posix'
427 isposix = osname == 'posix'
408 iswindows = osname == 'nt'
428 iswindows = osname == 'nt'
409
429
410 def getoptb(args, shortlist, namelist):
430 def getoptb(args, shortlist, namelist):
411 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
431 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
412
432
413 def gnugetoptb(args, shortlist, namelist):
433 def gnugetoptb(args, shortlist, namelist):
414 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
434 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
415
435
416 def mkdtemp(suffix=b'', prefix=b'tmp', dir=None):
436 def mkdtemp(suffix=b'', prefix=b'tmp', dir=None):
417 return tempfile.mkdtemp(suffix, prefix, dir)
437 return tempfile.mkdtemp(suffix, prefix, dir)
418
438
419 # text=True is not supported; use util.from/tonativeeol() instead
439 # text=True is not supported; use util.from/tonativeeol() instead
420 def mkstemp(suffix=b'', prefix=b'tmp', dir=None):
440 def mkstemp(suffix=b'', prefix=b'tmp', dir=None):
421 return tempfile.mkstemp(suffix, prefix, dir)
441 return tempfile.mkstemp(suffix, prefix, dir)
422
442
423 # mode must include 'b'ytes as encoding= is not supported
443 # mode must include 'b'ytes as encoding= is not supported
424 def namedtempfile(mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None,
444 def namedtempfile(mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None,
425 delete=True):
445 delete=True):
426 mode = sysstr(mode)
446 mode = sysstr(mode)
427 assert r'b' in mode
447 assert r'b' in mode
428 return tempfile.NamedTemporaryFile(mode, bufsize, suffix=suffix,
448 return tempfile.NamedTemporaryFile(mode, bufsize, suffix=suffix,
429 prefix=prefix, dir=dir, delete=delete)
449 prefix=prefix, dir=dir, delete=delete)
General Comments 0
You need to be logged in to leave comments. Login now