##// END OF EJS Templates
util: add __ne__ to filestat class for consistency...
FUJIWARA Katsunori -
r29298:82f6193f default
parent child Browse files
Show More
@@ -1,2832 +1,2835 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from __future__ import absolute_import
16 from __future__ import absolute_import
17
17
18 import bz2
18 import bz2
19 import calendar
19 import calendar
20 import collections
20 import collections
21 import datetime
21 import datetime
22 import errno
22 import errno
23 import gc
23 import gc
24 import hashlib
24 import hashlib
25 import imp
25 import imp
26 import os
26 import os
27 import re as remod
27 import re as remod
28 import shutil
28 import shutil
29 import signal
29 import signal
30 import socket
30 import socket
31 import subprocess
31 import subprocess
32 import sys
32 import sys
33 import tempfile
33 import tempfile
34 import textwrap
34 import textwrap
35 import time
35 import time
36 import traceback
36 import traceback
37 import zlib
37 import zlib
38
38
39 from . import (
39 from . import (
40 encoding,
40 encoding,
41 error,
41 error,
42 i18n,
42 i18n,
43 osutil,
43 osutil,
44 parsers,
44 parsers,
45 pycompat,
45 pycompat,
46 )
46 )
47
47
48 for attr in (
48 for attr in (
49 'empty',
49 'empty',
50 'queue',
50 'queue',
51 'urlerr',
51 'urlerr',
52 # we do import urlreq, but we do it outside the loop
52 # we do import urlreq, but we do it outside the loop
53 #'urlreq',
53 #'urlreq',
54 'stringio',
54 'stringio',
55 ):
55 ):
56 globals()[attr] = getattr(pycompat, attr)
56 globals()[attr] = getattr(pycompat, attr)
57
57
58 # This line is to make pyflakes happy:
58 # This line is to make pyflakes happy:
59 urlreq = pycompat.urlreq
59 urlreq = pycompat.urlreq
60
60
61 if os.name == 'nt':
61 if os.name == 'nt':
62 from . import windows as platform
62 from . import windows as platform
63 else:
63 else:
64 from . import posix as platform
64 from . import posix as platform
65
65
66 md5 = hashlib.md5
66 md5 = hashlib.md5
67 sha1 = hashlib.sha1
67 sha1 = hashlib.sha1
68 sha256 = hashlib.sha256
68 sha256 = hashlib.sha256
69 sha512 = hashlib.sha512
69 sha512 = hashlib.sha512
70 _ = i18n._
70 _ = i18n._
71
71
72 cachestat = platform.cachestat
72 cachestat = platform.cachestat
73 checkexec = platform.checkexec
73 checkexec = platform.checkexec
74 checklink = platform.checklink
74 checklink = platform.checklink
75 copymode = platform.copymode
75 copymode = platform.copymode
76 executablepath = platform.executablepath
76 executablepath = platform.executablepath
77 expandglobs = platform.expandglobs
77 expandglobs = platform.expandglobs
78 explainexit = platform.explainexit
78 explainexit = platform.explainexit
79 findexe = platform.findexe
79 findexe = platform.findexe
80 gethgcmd = platform.gethgcmd
80 gethgcmd = platform.gethgcmd
81 getuser = platform.getuser
81 getuser = platform.getuser
82 getpid = os.getpid
82 getpid = os.getpid
83 groupmembers = platform.groupmembers
83 groupmembers = platform.groupmembers
84 groupname = platform.groupname
84 groupname = platform.groupname
85 hidewindow = platform.hidewindow
85 hidewindow = platform.hidewindow
86 isexec = platform.isexec
86 isexec = platform.isexec
87 isowner = platform.isowner
87 isowner = platform.isowner
88 localpath = platform.localpath
88 localpath = platform.localpath
89 lookupreg = platform.lookupreg
89 lookupreg = platform.lookupreg
90 makedir = platform.makedir
90 makedir = platform.makedir
91 nlinks = platform.nlinks
91 nlinks = platform.nlinks
92 normpath = platform.normpath
92 normpath = platform.normpath
93 normcase = platform.normcase
93 normcase = platform.normcase
94 normcasespec = platform.normcasespec
94 normcasespec = platform.normcasespec
95 normcasefallback = platform.normcasefallback
95 normcasefallback = platform.normcasefallback
96 openhardlinks = platform.openhardlinks
96 openhardlinks = platform.openhardlinks
97 oslink = platform.oslink
97 oslink = platform.oslink
98 parsepatchoutput = platform.parsepatchoutput
98 parsepatchoutput = platform.parsepatchoutput
99 pconvert = platform.pconvert
99 pconvert = platform.pconvert
100 poll = platform.poll
100 poll = platform.poll
101 popen = platform.popen
101 popen = platform.popen
102 posixfile = platform.posixfile
102 posixfile = platform.posixfile
103 quotecommand = platform.quotecommand
103 quotecommand = platform.quotecommand
104 readpipe = platform.readpipe
104 readpipe = platform.readpipe
105 rename = platform.rename
105 rename = platform.rename
106 removedirs = platform.removedirs
106 removedirs = platform.removedirs
107 samedevice = platform.samedevice
107 samedevice = platform.samedevice
108 samefile = platform.samefile
108 samefile = platform.samefile
109 samestat = platform.samestat
109 samestat = platform.samestat
110 setbinary = platform.setbinary
110 setbinary = platform.setbinary
111 setflags = platform.setflags
111 setflags = platform.setflags
112 setsignalhandler = platform.setsignalhandler
112 setsignalhandler = platform.setsignalhandler
113 shellquote = platform.shellquote
113 shellquote = platform.shellquote
114 spawndetached = platform.spawndetached
114 spawndetached = platform.spawndetached
115 split = platform.split
115 split = platform.split
116 sshargs = platform.sshargs
116 sshargs = platform.sshargs
117 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
117 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
118 statisexec = platform.statisexec
118 statisexec = platform.statisexec
119 statislink = platform.statislink
119 statislink = platform.statislink
120 termwidth = platform.termwidth
120 termwidth = platform.termwidth
121 testpid = platform.testpid
121 testpid = platform.testpid
122 umask = platform.umask
122 umask = platform.umask
123 unlink = platform.unlink
123 unlink = platform.unlink
124 unlinkpath = platform.unlinkpath
124 unlinkpath = platform.unlinkpath
125 username = platform.username
125 username = platform.username
126
126
127 # Python compatibility
127 # Python compatibility
128
128
129 _notset = object()
129 _notset = object()
130
130
131 # disable Python's problematic floating point timestamps (issue4836)
131 # disable Python's problematic floating point timestamps (issue4836)
132 # (Python hypocritically says you shouldn't change this behavior in
132 # (Python hypocritically says you shouldn't change this behavior in
133 # libraries, and sure enough Mercurial is not a library.)
133 # libraries, and sure enough Mercurial is not a library.)
134 os.stat_float_times(False)
134 os.stat_float_times(False)
135
135
136 def safehasattr(thing, attr):
136 def safehasattr(thing, attr):
137 return getattr(thing, attr, _notset) is not _notset
137 return getattr(thing, attr, _notset) is not _notset
138
138
139 DIGESTS = {
139 DIGESTS = {
140 'md5': md5,
140 'md5': md5,
141 'sha1': sha1,
141 'sha1': sha1,
142 'sha512': sha512,
142 'sha512': sha512,
143 }
143 }
144 # List of digest types from strongest to weakest
144 # List of digest types from strongest to weakest
145 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
145 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
146
146
147 for k in DIGESTS_BY_STRENGTH:
147 for k in DIGESTS_BY_STRENGTH:
148 assert k in DIGESTS
148 assert k in DIGESTS
149
149
150 class digester(object):
150 class digester(object):
151 """helper to compute digests.
151 """helper to compute digests.
152
152
153 This helper can be used to compute one or more digests given their name.
153 This helper can be used to compute one or more digests given their name.
154
154
155 >>> d = digester(['md5', 'sha1'])
155 >>> d = digester(['md5', 'sha1'])
156 >>> d.update('foo')
156 >>> d.update('foo')
157 >>> [k for k in sorted(d)]
157 >>> [k for k in sorted(d)]
158 ['md5', 'sha1']
158 ['md5', 'sha1']
159 >>> d['md5']
159 >>> d['md5']
160 'acbd18db4cc2f85cedef654fccc4a4d8'
160 'acbd18db4cc2f85cedef654fccc4a4d8'
161 >>> d['sha1']
161 >>> d['sha1']
162 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
162 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
163 >>> digester.preferred(['md5', 'sha1'])
163 >>> digester.preferred(['md5', 'sha1'])
164 'sha1'
164 'sha1'
165 """
165 """
166
166
167 def __init__(self, digests, s=''):
167 def __init__(self, digests, s=''):
168 self._hashes = {}
168 self._hashes = {}
169 for k in digests:
169 for k in digests:
170 if k not in DIGESTS:
170 if k not in DIGESTS:
171 raise Abort(_('unknown digest type: %s') % k)
171 raise Abort(_('unknown digest type: %s') % k)
172 self._hashes[k] = DIGESTS[k]()
172 self._hashes[k] = DIGESTS[k]()
173 if s:
173 if s:
174 self.update(s)
174 self.update(s)
175
175
176 def update(self, data):
176 def update(self, data):
177 for h in self._hashes.values():
177 for h in self._hashes.values():
178 h.update(data)
178 h.update(data)
179
179
180 def __getitem__(self, key):
180 def __getitem__(self, key):
181 if key not in DIGESTS:
181 if key not in DIGESTS:
182 raise Abort(_('unknown digest type: %s') % k)
182 raise Abort(_('unknown digest type: %s') % k)
183 return self._hashes[key].hexdigest()
183 return self._hashes[key].hexdigest()
184
184
185 def __iter__(self):
185 def __iter__(self):
186 return iter(self._hashes)
186 return iter(self._hashes)
187
187
188 @staticmethod
188 @staticmethod
189 def preferred(supported):
189 def preferred(supported):
190 """returns the strongest digest type in both supported and DIGESTS."""
190 """returns the strongest digest type in both supported and DIGESTS."""
191
191
192 for k in DIGESTS_BY_STRENGTH:
192 for k in DIGESTS_BY_STRENGTH:
193 if k in supported:
193 if k in supported:
194 return k
194 return k
195 return None
195 return None
196
196
197 class digestchecker(object):
197 class digestchecker(object):
198 """file handle wrapper that additionally checks content against a given
198 """file handle wrapper that additionally checks content against a given
199 size and digests.
199 size and digests.
200
200
201 d = digestchecker(fh, size, {'md5': '...'})
201 d = digestchecker(fh, size, {'md5': '...'})
202
202
203 When multiple digests are given, all of them are validated.
203 When multiple digests are given, all of them are validated.
204 """
204 """
205
205
206 def __init__(self, fh, size, digests):
206 def __init__(self, fh, size, digests):
207 self._fh = fh
207 self._fh = fh
208 self._size = size
208 self._size = size
209 self._got = 0
209 self._got = 0
210 self._digests = dict(digests)
210 self._digests = dict(digests)
211 self._digester = digester(self._digests.keys())
211 self._digester = digester(self._digests.keys())
212
212
213 def read(self, length=-1):
213 def read(self, length=-1):
214 content = self._fh.read(length)
214 content = self._fh.read(length)
215 self._digester.update(content)
215 self._digester.update(content)
216 self._got += len(content)
216 self._got += len(content)
217 return content
217 return content
218
218
219 def validate(self):
219 def validate(self):
220 if self._size != self._got:
220 if self._size != self._got:
221 raise Abort(_('size mismatch: expected %d, got %d') %
221 raise Abort(_('size mismatch: expected %d, got %d') %
222 (self._size, self._got))
222 (self._size, self._got))
223 for k, v in self._digests.items():
223 for k, v in self._digests.items():
224 if v != self._digester[k]:
224 if v != self._digester[k]:
225 # i18n: first parameter is a digest name
225 # i18n: first parameter is a digest name
226 raise Abort(_('%s mismatch: expected %s, got %s') %
226 raise Abort(_('%s mismatch: expected %s, got %s') %
227 (k, v, self._digester[k]))
227 (k, v, self._digester[k]))
228
228
229 try:
229 try:
230 buffer = buffer
230 buffer = buffer
231 except NameError:
231 except NameError:
232 if sys.version_info[0] < 3:
232 if sys.version_info[0] < 3:
233 def buffer(sliceable, offset=0):
233 def buffer(sliceable, offset=0):
234 return sliceable[offset:]
234 return sliceable[offset:]
235 else:
235 else:
236 def buffer(sliceable, offset=0):
236 def buffer(sliceable, offset=0):
237 return memoryview(sliceable)[offset:]
237 return memoryview(sliceable)[offset:]
238
238
239 closefds = os.name == 'posix'
239 closefds = os.name == 'posix'
240
240
241 _chunksize = 4096
241 _chunksize = 4096
242
242
243 class bufferedinputpipe(object):
243 class bufferedinputpipe(object):
244 """a manually buffered input pipe
244 """a manually buffered input pipe
245
245
246 Python will not let us use buffered IO and lazy reading with 'polling' at
246 Python will not let us use buffered IO and lazy reading with 'polling' at
247 the same time. We cannot probe the buffer state and select will not detect
247 the same time. We cannot probe the buffer state and select will not detect
248 that data are ready to read if they are already buffered.
248 that data are ready to read if they are already buffered.
249
249
250 This class let us work around that by implementing its own buffering
250 This class let us work around that by implementing its own buffering
251 (allowing efficient readline) while offering a way to know if the buffer is
251 (allowing efficient readline) while offering a way to know if the buffer is
252 empty from the output (allowing collaboration of the buffer with polling).
252 empty from the output (allowing collaboration of the buffer with polling).
253
253
254 This class lives in the 'util' module because it makes use of the 'os'
254 This class lives in the 'util' module because it makes use of the 'os'
255 module from the python stdlib.
255 module from the python stdlib.
256 """
256 """
257
257
258 def __init__(self, input):
258 def __init__(self, input):
259 self._input = input
259 self._input = input
260 self._buffer = []
260 self._buffer = []
261 self._eof = False
261 self._eof = False
262 self._lenbuf = 0
262 self._lenbuf = 0
263
263
264 @property
264 @property
265 def hasbuffer(self):
265 def hasbuffer(self):
266 """True is any data is currently buffered
266 """True is any data is currently buffered
267
267
268 This will be used externally a pre-step for polling IO. If there is
268 This will be used externally a pre-step for polling IO. If there is
269 already data then no polling should be set in place."""
269 already data then no polling should be set in place."""
270 return bool(self._buffer)
270 return bool(self._buffer)
271
271
272 @property
272 @property
273 def closed(self):
273 def closed(self):
274 return self._input.closed
274 return self._input.closed
275
275
276 def fileno(self):
276 def fileno(self):
277 return self._input.fileno()
277 return self._input.fileno()
278
278
279 def close(self):
279 def close(self):
280 return self._input.close()
280 return self._input.close()
281
281
282 def read(self, size):
282 def read(self, size):
283 while (not self._eof) and (self._lenbuf < size):
283 while (not self._eof) and (self._lenbuf < size):
284 self._fillbuffer()
284 self._fillbuffer()
285 return self._frombuffer(size)
285 return self._frombuffer(size)
286
286
287 def readline(self, *args, **kwargs):
287 def readline(self, *args, **kwargs):
288 if 1 < len(self._buffer):
288 if 1 < len(self._buffer):
289 # this should not happen because both read and readline end with a
289 # this should not happen because both read and readline end with a
290 # _frombuffer call that collapse it.
290 # _frombuffer call that collapse it.
291 self._buffer = [''.join(self._buffer)]
291 self._buffer = [''.join(self._buffer)]
292 self._lenbuf = len(self._buffer[0])
292 self._lenbuf = len(self._buffer[0])
293 lfi = -1
293 lfi = -1
294 if self._buffer:
294 if self._buffer:
295 lfi = self._buffer[-1].find('\n')
295 lfi = self._buffer[-1].find('\n')
296 while (not self._eof) and lfi < 0:
296 while (not self._eof) and lfi < 0:
297 self._fillbuffer()
297 self._fillbuffer()
298 if self._buffer:
298 if self._buffer:
299 lfi = self._buffer[-1].find('\n')
299 lfi = self._buffer[-1].find('\n')
300 size = lfi + 1
300 size = lfi + 1
301 if lfi < 0: # end of file
301 if lfi < 0: # end of file
302 size = self._lenbuf
302 size = self._lenbuf
303 elif 1 < len(self._buffer):
303 elif 1 < len(self._buffer):
304 # we need to take previous chunks into account
304 # we need to take previous chunks into account
305 size += self._lenbuf - len(self._buffer[-1])
305 size += self._lenbuf - len(self._buffer[-1])
306 return self._frombuffer(size)
306 return self._frombuffer(size)
307
307
308 def _frombuffer(self, size):
308 def _frombuffer(self, size):
309 """return at most 'size' data from the buffer
309 """return at most 'size' data from the buffer
310
310
311 The data are removed from the buffer."""
311 The data are removed from the buffer."""
312 if size == 0 or not self._buffer:
312 if size == 0 or not self._buffer:
313 return ''
313 return ''
314 buf = self._buffer[0]
314 buf = self._buffer[0]
315 if 1 < len(self._buffer):
315 if 1 < len(self._buffer):
316 buf = ''.join(self._buffer)
316 buf = ''.join(self._buffer)
317
317
318 data = buf[:size]
318 data = buf[:size]
319 buf = buf[len(data):]
319 buf = buf[len(data):]
320 if buf:
320 if buf:
321 self._buffer = [buf]
321 self._buffer = [buf]
322 self._lenbuf = len(buf)
322 self._lenbuf = len(buf)
323 else:
323 else:
324 self._buffer = []
324 self._buffer = []
325 self._lenbuf = 0
325 self._lenbuf = 0
326 return data
326 return data
327
327
328 def _fillbuffer(self):
328 def _fillbuffer(self):
329 """read data to the buffer"""
329 """read data to the buffer"""
330 data = os.read(self._input.fileno(), _chunksize)
330 data = os.read(self._input.fileno(), _chunksize)
331 if not data:
331 if not data:
332 self._eof = True
332 self._eof = True
333 else:
333 else:
334 self._lenbuf += len(data)
334 self._lenbuf += len(data)
335 self._buffer.append(data)
335 self._buffer.append(data)
336
336
337 def popen2(cmd, env=None, newlines=False):
337 def popen2(cmd, env=None, newlines=False):
338 # Setting bufsize to -1 lets the system decide the buffer size.
338 # Setting bufsize to -1 lets the system decide the buffer size.
339 # The default for bufsize is 0, meaning unbuffered. This leads to
339 # The default for bufsize is 0, meaning unbuffered. This leads to
340 # poor performance on Mac OS X: http://bugs.python.org/issue4194
340 # poor performance on Mac OS X: http://bugs.python.org/issue4194
341 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
341 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
342 close_fds=closefds,
342 close_fds=closefds,
343 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
343 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
344 universal_newlines=newlines,
344 universal_newlines=newlines,
345 env=env)
345 env=env)
346 return p.stdin, p.stdout
346 return p.stdin, p.stdout
347
347
348 def popen3(cmd, env=None, newlines=False):
348 def popen3(cmd, env=None, newlines=False):
349 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
349 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
350 return stdin, stdout, stderr
350 return stdin, stdout, stderr
351
351
352 def popen4(cmd, env=None, newlines=False, bufsize=-1):
352 def popen4(cmd, env=None, newlines=False, bufsize=-1):
353 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
353 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
354 close_fds=closefds,
354 close_fds=closefds,
355 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
355 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
356 stderr=subprocess.PIPE,
356 stderr=subprocess.PIPE,
357 universal_newlines=newlines,
357 universal_newlines=newlines,
358 env=env)
358 env=env)
359 return p.stdin, p.stdout, p.stderr, p
359 return p.stdin, p.stdout, p.stderr, p
360
360
361 def version():
361 def version():
362 """Return version information if available."""
362 """Return version information if available."""
363 try:
363 try:
364 from . import __version__
364 from . import __version__
365 return __version__.version
365 return __version__.version
366 except ImportError:
366 except ImportError:
367 return 'unknown'
367 return 'unknown'
368
368
369 def versiontuple(v=None, n=4):
369 def versiontuple(v=None, n=4):
370 """Parses a Mercurial version string into an N-tuple.
370 """Parses a Mercurial version string into an N-tuple.
371
371
372 The version string to be parsed is specified with the ``v`` argument.
372 The version string to be parsed is specified with the ``v`` argument.
373 If it isn't defined, the current Mercurial version string will be parsed.
373 If it isn't defined, the current Mercurial version string will be parsed.
374
374
375 ``n`` can be 2, 3, or 4. Here is how some version strings map to
375 ``n`` can be 2, 3, or 4. Here is how some version strings map to
376 returned values:
376 returned values:
377
377
378 >>> v = '3.6.1+190-df9b73d2d444'
378 >>> v = '3.6.1+190-df9b73d2d444'
379 >>> versiontuple(v, 2)
379 >>> versiontuple(v, 2)
380 (3, 6)
380 (3, 6)
381 >>> versiontuple(v, 3)
381 >>> versiontuple(v, 3)
382 (3, 6, 1)
382 (3, 6, 1)
383 >>> versiontuple(v, 4)
383 >>> versiontuple(v, 4)
384 (3, 6, 1, '190-df9b73d2d444')
384 (3, 6, 1, '190-df9b73d2d444')
385
385
386 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
386 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
387 (3, 6, 1, '190-df9b73d2d444+20151118')
387 (3, 6, 1, '190-df9b73d2d444+20151118')
388
388
389 >>> v = '3.6'
389 >>> v = '3.6'
390 >>> versiontuple(v, 2)
390 >>> versiontuple(v, 2)
391 (3, 6)
391 (3, 6)
392 >>> versiontuple(v, 3)
392 >>> versiontuple(v, 3)
393 (3, 6, None)
393 (3, 6, None)
394 >>> versiontuple(v, 4)
394 >>> versiontuple(v, 4)
395 (3, 6, None, None)
395 (3, 6, None, None)
396 """
396 """
397 if not v:
397 if not v:
398 v = version()
398 v = version()
399 parts = v.split('+', 1)
399 parts = v.split('+', 1)
400 if len(parts) == 1:
400 if len(parts) == 1:
401 vparts, extra = parts[0], None
401 vparts, extra = parts[0], None
402 else:
402 else:
403 vparts, extra = parts
403 vparts, extra = parts
404
404
405 vints = []
405 vints = []
406 for i in vparts.split('.'):
406 for i in vparts.split('.'):
407 try:
407 try:
408 vints.append(int(i))
408 vints.append(int(i))
409 except ValueError:
409 except ValueError:
410 break
410 break
411 # (3, 6) -> (3, 6, None)
411 # (3, 6) -> (3, 6, None)
412 while len(vints) < 3:
412 while len(vints) < 3:
413 vints.append(None)
413 vints.append(None)
414
414
415 if n == 2:
415 if n == 2:
416 return (vints[0], vints[1])
416 return (vints[0], vints[1])
417 if n == 3:
417 if n == 3:
418 return (vints[0], vints[1], vints[2])
418 return (vints[0], vints[1], vints[2])
419 if n == 4:
419 if n == 4:
420 return (vints[0], vints[1], vints[2], extra)
420 return (vints[0], vints[1], vints[2], extra)
421
421
422 # used by parsedate
422 # used by parsedate
423 defaultdateformats = (
423 defaultdateformats = (
424 '%Y-%m-%d %H:%M:%S',
424 '%Y-%m-%d %H:%M:%S',
425 '%Y-%m-%d %I:%M:%S%p',
425 '%Y-%m-%d %I:%M:%S%p',
426 '%Y-%m-%d %H:%M',
426 '%Y-%m-%d %H:%M',
427 '%Y-%m-%d %I:%M%p',
427 '%Y-%m-%d %I:%M%p',
428 '%Y-%m-%d',
428 '%Y-%m-%d',
429 '%m-%d',
429 '%m-%d',
430 '%m/%d',
430 '%m/%d',
431 '%m/%d/%y',
431 '%m/%d/%y',
432 '%m/%d/%Y',
432 '%m/%d/%Y',
433 '%a %b %d %H:%M:%S %Y',
433 '%a %b %d %H:%M:%S %Y',
434 '%a %b %d %I:%M:%S%p %Y',
434 '%a %b %d %I:%M:%S%p %Y',
435 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
435 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
436 '%b %d %H:%M:%S %Y',
436 '%b %d %H:%M:%S %Y',
437 '%b %d %I:%M:%S%p %Y',
437 '%b %d %I:%M:%S%p %Y',
438 '%b %d %H:%M:%S',
438 '%b %d %H:%M:%S',
439 '%b %d %I:%M:%S%p',
439 '%b %d %I:%M:%S%p',
440 '%b %d %H:%M',
440 '%b %d %H:%M',
441 '%b %d %I:%M%p',
441 '%b %d %I:%M%p',
442 '%b %d %Y',
442 '%b %d %Y',
443 '%b %d',
443 '%b %d',
444 '%H:%M:%S',
444 '%H:%M:%S',
445 '%I:%M:%S%p',
445 '%I:%M:%S%p',
446 '%H:%M',
446 '%H:%M',
447 '%I:%M%p',
447 '%I:%M%p',
448 )
448 )
449
449
450 extendeddateformats = defaultdateformats + (
450 extendeddateformats = defaultdateformats + (
451 "%Y",
451 "%Y",
452 "%Y-%m",
452 "%Y-%m",
453 "%b",
453 "%b",
454 "%b %Y",
454 "%b %Y",
455 )
455 )
456
456
457 def cachefunc(func):
457 def cachefunc(func):
458 '''cache the result of function calls'''
458 '''cache the result of function calls'''
459 # XXX doesn't handle keywords args
459 # XXX doesn't handle keywords args
460 if func.__code__.co_argcount == 0:
460 if func.__code__.co_argcount == 0:
461 cache = []
461 cache = []
462 def f():
462 def f():
463 if len(cache) == 0:
463 if len(cache) == 0:
464 cache.append(func())
464 cache.append(func())
465 return cache[0]
465 return cache[0]
466 return f
466 return f
467 cache = {}
467 cache = {}
468 if func.__code__.co_argcount == 1:
468 if func.__code__.co_argcount == 1:
469 # we gain a small amount of time because
469 # we gain a small amount of time because
470 # we don't need to pack/unpack the list
470 # we don't need to pack/unpack the list
471 def f(arg):
471 def f(arg):
472 if arg not in cache:
472 if arg not in cache:
473 cache[arg] = func(arg)
473 cache[arg] = func(arg)
474 return cache[arg]
474 return cache[arg]
475 else:
475 else:
476 def f(*args):
476 def f(*args):
477 if args not in cache:
477 if args not in cache:
478 cache[args] = func(*args)
478 cache[args] = func(*args)
479 return cache[args]
479 return cache[args]
480
480
481 return f
481 return f
482
482
483 class sortdict(dict):
483 class sortdict(dict):
484 '''a simple sorted dictionary'''
484 '''a simple sorted dictionary'''
485 def __init__(self, data=None):
485 def __init__(self, data=None):
486 self._list = []
486 self._list = []
487 if data:
487 if data:
488 self.update(data)
488 self.update(data)
489 def copy(self):
489 def copy(self):
490 return sortdict(self)
490 return sortdict(self)
491 def __setitem__(self, key, val):
491 def __setitem__(self, key, val):
492 if key in self:
492 if key in self:
493 self._list.remove(key)
493 self._list.remove(key)
494 self._list.append(key)
494 self._list.append(key)
495 dict.__setitem__(self, key, val)
495 dict.__setitem__(self, key, val)
496 def __iter__(self):
496 def __iter__(self):
497 return self._list.__iter__()
497 return self._list.__iter__()
498 def update(self, src):
498 def update(self, src):
499 if isinstance(src, dict):
499 if isinstance(src, dict):
500 src = src.iteritems()
500 src = src.iteritems()
501 for k, v in src:
501 for k, v in src:
502 self[k] = v
502 self[k] = v
503 def clear(self):
503 def clear(self):
504 dict.clear(self)
504 dict.clear(self)
505 self._list = []
505 self._list = []
506 def items(self):
506 def items(self):
507 return [(k, self[k]) for k in self._list]
507 return [(k, self[k]) for k in self._list]
508 def __delitem__(self, key):
508 def __delitem__(self, key):
509 dict.__delitem__(self, key)
509 dict.__delitem__(self, key)
510 self._list.remove(key)
510 self._list.remove(key)
511 def pop(self, key, *args, **kwargs):
511 def pop(self, key, *args, **kwargs):
512 dict.pop(self, key, *args, **kwargs)
512 dict.pop(self, key, *args, **kwargs)
513 try:
513 try:
514 self._list.remove(key)
514 self._list.remove(key)
515 except ValueError:
515 except ValueError:
516 pass
516 pass
517 def keys(self):
517 def keys(self):
518 return self._list
518 return self._list
519 def iterkeys(self):
519 def iterkeys(self):
520 return self._list.__iter__()
520 return self._list.__iter__()
521 def iteritems(self):
521 def iteritems(self):
522 for k in self._list:
522 for k in self._list:
523 yield k, self[k]
523 yield k, self[k]
524 def insert(self, index, key, val):
524 def insert(self, index, key, val):
525 self._list.insert(index, key)
525 self._list.insert(index, key)
526 dict.__setitem__(self, key, val)
526 dict.__setitem__(self, key, val)
527
527
528 class _lrucachenode(object):
528 class _lrucachenode(object):
529 """A node in a doubly linked list.
529 """A node in a doubly linked list.
530
530
531 Holds a reference to nodes on either side as well as a key-value
531 Holds a reference to nodes on either side as well as a key-value
532 pair for the dictionary entry.
532 pair for the dictionary entry.
533 """
533 """
534 __slots__ = ('next', 'prev', 'key', 'value')
534 __slots__ = ('next', 'prev', 'key', 'value')
535
535
536 def __init__(self):
536 def __init__(self):
537 self.next = None
537 self.next = None
538 self.prev = None
538 self.prev = None
539
539
540 self.key = _notset
540 self.key = _notset
541 self.value = None
541 self.value = None
542
542
543 def markempty(self):
543 def markempty(self):
544 """Mark the node as emptied."""
544 """Mark the node as emptied."""
545 self.key = _notset
545 self.key = _notset
546
546
547 class lrucachedict(object):
547 class lrucachedict(object):
548 """Dict that caches most recent accesses and sets.
548 """Dict that caches most recent accesses and sets.
549
549
550 The dict consists of an actual backing dict - indexed by original
550 The dict consists of an actual backing dict - indexed by original
551 key - and a doubly linked circular list defining the order of entries in
551 key - and a doubly linked circular list defining the order of entries in
552 the cache.
552 the cache.
553
553
554 The head node is the newest entry in the cache. If the cache is full,
554 The head node is the newest entry in the cache. If the cache is full,
555 we recycle head.prev and make it the new head. Cache accesses result in
555 we recycle head.prev and make it the new head. Cache accesses result in
556 the node being moved to before the existing head and being marked as the
556 the node being moved to before the existing head and being marked as the
557 new head node.
557 new head node.
558 """
558 """
559 def __init__(self, max):
559 def __init__(self, max):
560 self._cache = {}
560 self._cache = {}
561
561
562 self._head = head = _lrucachenode()
562 self._head = head = _lrucachenode()
563 head.prev = head
563 head.prev = head
564 head.next = head
564 head.next = head
565 self._size = 1
565 self._size = 1
566 self._capacity = max
566 self._capacity = max
567
567
568 def __len__(self):
568 def __len__(self):
569 return len(self._cache)
569 return len(self._cache)
570
570
571 def __contains__(self, k):
571 def __contains__(self, k):
572 return k in self._cache
572 return k in self._cache
573
573
574 def __iter__(self):
574 def __iter__(self):
575 # We don't have to iterate in cache order, but why not.
575 # We don't have to iterate in cache order, but why not.
576 n = self._head
576 n = self._head
577 for i in range(len(self._cache)):
577 for i in range(len(self._cache)):
578 yield n.key
578 yield n.key
579 n = n.next
579 n = n.next
580
580
581 def __getitem__(self, k):
581 def __getitem__(self, k):
582 node = self._cache[k]
582 node = self._cache[k]
583 self._movetohead(node)
583 self._movetohead(node)
584 return node.value
584 return node.value
585
585
586 def __setitem__(self, k, v):
586 def __setitem__(self, k, v):
587 node = self._cache.get(k)
587 node = self._cache.get(k)
588 # Replace existing value and mark as newest.
588 # Replace existing value and mark as newest.
589 if node is not None:
589 if node is not None:
590 node.value = v
590 node.value = v
591 self._movetohead(node)
591 self._movetohead(node)
592 return
592 return
593
593
594 if self._size < self._capacity:
594 if self._size < self._capacity:
595 node = self._addcapacity()
595 node = self._addcapacity()
596 else:
596 else:
597 # Grab the last/oldest item.
597 # Grab the last/oldest item.
598 node = self._head.prev
598 node = self._head.prev
599
599
600 # At capacity. Kill the old entry.
600 # At capacity. Kill the old entry.
601 if node.key is not _notset:
601 if node.key is not _notset:
602 del self._cache[node.key]
602 del self._cache[node.key]
603
603
604 node.key = k
604 node.key = k
605 node.value = v
605 node.value = v
606 self._cache[k] = node
606 self._cache[k] = node
607 # And mark it as newest entry. No need to adjust order since it
607 # And mark it as newest entry. No need to adjust order since it
608 # is already self._head.prev.
608 # is already self._head.prev.
609 self._head = node
609 self._head = node
610
610
611 def __delitem__(self, k):
611 def __delitem__(self, k):
612 node = self._cache.pop(k)
612 node = self._cache.pop(k)
613 node.markempty()
613 node.markempty()
614
614
615 # Temporarily mark as newest item before re-adjusting head to make
615 # Temporarily mark as newest item before re-adjusting head to make
616 # this node the oldest item.
616 # this node the oldest item.
617 self._movetohead(node)
617 self._movetohead(node)
618 self._head = node.next
618 self._head = node.next
619
619
620 # Additional dict methods.
620 # Additional dict methods.
621
621
622 def get(self, k, default=None):
622 def get(self, k, default=None):
623 try:
623 try:
624 return self._cache[k]
624 return self._cache[k]
625 except KeyError:
625 except KeyError:
626 return default
626 return default
627
627
628 def clear(self):
628 def clear(self):
629 n = self._head
629 n = self._head
630 while n.key is not _notset:
630 while n.key is not _notset:
631 n.markempty()
631 n.markempty()
632 n = n.next
632 n = n.next
633
633
634 self._cache.clear()
634 self._cache.clear()
635
635
636 def copy(self):
636 def copy(self):
637 result = lrucachedict(self._capacity)
637 result = lrucachedict(self._capacity)
638 n = self._head.prev
638 n = self._head.prev
639 # Iterate in oldest-to-newest order, so the copy has the right ordering
639 # Iterate in oldest-to-newest order, so the copy has the right ordering
640 for i in range(len(self._cache)):
640 for i in range(len(self._cache)):
641 result[n.key] = n.value
641 result[n.key] = n.value
642 n = n.prev
642 n = n.prev
643 return result
643 return result
644
644
645 def _movetohead(self, node):
645 def _movetohead(self, node):
646 """Mark a node as the newest, making it the new head.
646 """Mark a node as the newest, making it the new head.
647
647
648 When a node is accessed, it becomes the freshest entry in the LRU
648 When a node is accessed, it becomes the freshest entry in the LRU
649 list, which is denoted by self._head.
649 list, which is denoted by self._head.
650
650
651 Visually, let's make ``N`` the new head node (* denotes head):
651 Visually, let's make ``N`` the new head node (* denotes head):
652
652
653 previous/oldest <-> head <-> next/next newest
653 previous/oldest <-> head <-> next/next newest
654
654
655 ----<->--- A* ---<->-----
655 ----<->--- A* ---<->-----
656 | |
656 | |
657 E <-> D <-> N <-> C <-> B
657 E <-> D <-> N <-> C <-> B
658
658
659 To:
659 To:
660
660
661 ----<->--- N* ---<->-----
661 ----<->--- N* ---<->-----
662 | |
662 | |
663 E <-> D <-> C <-> B <-> A
663 E <-> D <-> C <-> B <-> A
664
664
665 This requires the following moves:
665 This requires the following moves:
666
666
667 C.next = D (node.prev.next = node.next)
667 C.next = D (node.prev.next = node.next)
668 D.prev = C (node.next.prev = node.prev)
668 D.prev = C (node.next.prev = node.prev)
669 E.next = N (head.prev.next = node)
669 E.next = N (head.prev.next = node)
670 N.prev = E (node.prev = head.prev)
670 N.prev = E (node.prev = head.prev)
671 N.next = A (node.next = head)
671 N.next = A (node.next = head)
672 A.prev = N (head.prev = node)
672 A.prev = N (head.prev = node)
673 """
673 """
674 head = self._head
674 head = self._head
675 # C.next = D
675 # C.next = D
676 node.prev.next = node.next
676 node.prev.next = node.next
677 # D.prev = C
677 # D.prev = C
678 node.next.prev = node.prev
678 node.next.prev = node.prev
679 # N.prev = E
679 # N.prev = E
680 node.prev = head.prev
680 node.prev = head.prev
681 # N.next = A
681 # N.next = A
682 # It is tempting to do just "head" here, however if node is
682 # It is tempting to do just "head" here, however if node is
683 # adjacent to head, this will do bad things.
683 # adjacent to head, this will do bad things.
684 node.next = head.prev.next
684 node.next = head.prev.next
685 # E.next = N
685 # E.next = N
686 node.next.prev = node
686 node.next.prev = node
687 # A.prev = N
687 # A.prev = N
688 node.prev.next = node
688 node.prev.next = node
689
689
690 self._head = node
690 self._head = node
691
691
692 def _addcapacity(self):
692 def _addcapacity(self):
693 """Add a node to the circular linked list.
693 """Add a node to the circular linked list.
694
694
695 The new node is inserted before the head node.
695 The new node is inserted before the head node.
696 """
696 """
697 head = self._head
697 head = self._head
698 node = _lrucachenode()
698 node = _lrucachenode()
699 head.prev.next = node
699 head.prev.next = node
700 node.prev = head.prev
700 node.prev = head.prev
701 node.next = head
701 node.next = head
702 head.prev = node
702 head.prev = node
703 self._size += 1
703 self._size += 1
704 return node
704 return node
705
705
706 def lrucachefunc(func):
706 def lrucachefunc(func):
707 '''cache most recent results of function calls'''
707 '''cache most recent results of function calls'''
708 cache = {}
708 cache = {}
709 order = collections.deque()
709 order = collections.deque()
710 if func.__code__.co_argcount == 1:
710 if func.__code__.co_argcount == 1:
711 def f(arg):
711 def f(arg):
712 if arg not in cache:
712 if arg not in cache:
713 if len(cache) > 20:
713 if len(cache) > 20:
714 del cache[order.popleft()]
714 del cache[order.popleft()]
715 cache[arg] = func(arg)
715 cache[arg] = func(arg)
716 else:
716 else:
717 order.remove(arg)
717 order.remove(arg)
718 order.append(arg)
718 order.append(arg)
719 return cache[arg]
719 return cache[arg]
720 else:
720 else:
721 def f(*args):
721 def f(*args):
722 if args not in cache:
722 if args not in cache:
723 if len(cache) > 20:
723 if len(cache) > 20:
724 del cache[order.popleft()]
724 del cache[order.popleft()]
725 cache[args] = func(*args)
725 cache[args] = func(*args)
726 else:
726 else:
727 order.remove(args)
727 order.remove(args)
728 order.append(args)
728 order.append(args)
729 return cache[args]
729 return cache[args]
730
730
731 return f
731 return f
732
732
733 class propertycache(object):
733 class propertycache(object):
734 def __init__(self, func):
734 def __init__(self, func):
735 self.func = func
735 self.func = func
736 self.name = func.__name__
736 self.name = func.__name__
737 def __get__(self, obj, type=None):
737 def __get__(self, obj, type=None):
738 result = self.func(obj)
738 result = self.func(obj)
739 self.cachevalue(obj, result)
739 self.cachevalue(obj, result)
740 return result
740 return result
741
741
742 def cachevalue(self, obj, value):
742 def cachevalue(self, obj, value):
743 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
743 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
744 obj.__dict__[self.name] = value
744 obj.__dict__[self.name] = value
745
745
746 def pipefilter(s, cmd):
746 def pipefilter(s, cmd):
747 '''filter string S through command CMD, returning its output'''
747 '''filter string S through command CMD, returning its output'''
748 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
748 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
749 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
749 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
750 pout, perr = p.communicate(s)
750 pout, perr = p.communicate(s)
751 return pout
751 return pout
752
752
753 def tempfilter(s, cmd):
753 def tempfilter(s, cmd):
754 '''filter string S through a pair of temporary files with CMD.
754 '''filter string S through a pair of temporary files with CMD.
755 CMD is used as a template to create the real command to be run,
755 CMD is used as a template to create the real command to be run,
756 with the strings INFILE and OUTFILE replaced by the real names of
756 with the strings INFILE and OUTFILE replaced by the real names of
757 the temporary files generated.'''
757 the temporary files generated.'''
758 inname, outname = None, None
758 inname, outname = None, None
759 try:
759 try:
760 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
760 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
761 fp = os.fdopen(infd, 'wb')
761 fp = os.fdopen(infd, 'wb')
762 fp.write(s)
762 fp.write(s)
763 fp.close()
763 fp.close()
764 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
764 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
765 os.close(outfd)
765 os.close(outfd)
766 cmd = cmd.replace('INFILE', inname)
766 cmd = cmd.replace('INFILE', inname)
767 cmd = cmd.replace('OUTFILE', outname)
767 cmd = cmd.replace('OUTFILE', outname)
768 code = os.system(cmd)
768 code = os.system(cmd)
769 if sys.platform == 'OpenVMS' and code & 1:
769 if sys.platform == 'OpenVMS' and code & 1:
770 code = 0
770 code = 0
771 if code:
771 if code:
772 raise Abort(_("command '%s' failed: %s") %
772 raise Abort(_("command '%s' failed: %s") %
773 (cmd, explainexit(code)))
773 (cmd, explainexit(code)))
774 return readfile(outname)
774 return readfile(outname)
775 finally:
775 finally:
776 try:
776 try:
777 if inname:
777 if inname:
778 os.unlink(inname)
778 os.unlink(inname)
779 except OSError:
779 except OSError:
780 pass
780 pass
781 try:
781 try:
782 if outname:
782 if outname:
783 os.unlink(outname)
783 os.unlink(outname)
784 except OSError:
784 except OSError:
785 pass
785 pass
786
786
787 filtertable = {
787 filtertable = {
788 'tempfile:': tempfilter,
788 'tempfile:': tempfilter,
789 'pipe:': pipefilter,
789 'pipe:': pipefilter,
790 }
790 }
791
791
792 def filter(s, cmd):
792 def filter(s, cmd):
793 "filter a string through a command that transforms its input to its output"
793 "filter a string through a command that transforms its input to its output"
794 for name, fn in filtertable.iteritems():
794 for name, fn in filtertable.iteritems():
795 if cmd.startswith(name):
795 if cmd.startswith(name):
796 return fn(s, cmd[len(name):].lstrip())
796 return fn(s, cmd[len(name):].lstrip())
797 return pipefilter(s, cmd)
797 return pipefilter(s, cmd)
798
798
799 def binary(s):
799 def binary(s):
800 """return true if a string is binary data"""
800 """return true if a string is binary data"""
801 return bool(s and '\0' in s)
801 return bool(s and '\0' in s)
802
802
803 def increasingchunks(source, min=1024, max=65536):
803 def increasingchunks(source, min=1024, max=65536):
804 '''return no less than min bytes per chunk while data remains,
804 '''return no less than min bytes per chunk while data remains,
805 doubling min after each chunk until it reaches max'''
805 doubling min after each chunk until it reaches max'''
806 def log2(x):
806 def log2(x):
807 if not x:
807 if not x:
808 return 0
808 return 0
809 i = 0
809 i = 0
810 while x:
810 while x:
811 x >>= 1
811 x >>= 1
812 i += 1
812 i += 1
813 return i - 1
813 return i - 1
814
814
815 buf = []
815 buf = []
816 blen = 0
816 blen = 0
817 for chunk in source:
817 for chunk in source:
818 buf.append(chunk)
818 buf.append(chunk)
819 blen += len(chunk)
819 blen += len(chunk)
820 if blen >= min:
820 if blen >= min:
821 if min < max:
821 if min < max:
822 min = min << 1
822 min = min << 1
823 nmin = 1 << log2(blen)
823 nmin = 1 << log2(blen)
824 if nmin > min:
824 if nmin > min:
825 min = nmin
825 min = nmin
826 if min > max:
826 if min > max:
827 min = max
827 min = max
828 yield ''.join(buf)
828 yield ''.join(buf)
829 blen = 0
829 blen = 0
830 buf = []
830 buf = []
831 if buf:
831 if buf:
832 yield ''.join(buf)
832 yield ''.join(buf)
833
833
834 Abort = error.Abort
834 Abort = error.Abort
835
835
836 def always(fn):
836 def always(fn):
837 return True
837 return True
838
838
839 def never(fn):
839 def never(fn):
840 return False
840 return False
841
841
842 def nogc(func):
842 def nogc(func):
843 """disable garbage collector
843 """disable garbage collector
844
844
845 Python's garbage collector triggers a GC each time a certain number of
845 Python's garbage collector triggers a GC each time a certain number of
846 container objects (the number being defined by gc.get_threshold()) are
846 container objects (the number being defined by gc.get_threshold()) are
847 allocated even when marked not to be tracked by the collector. Tracking has
847 allocated even when marked not to be tracked by the collector. Tracking has
848 no effect on when GCs are triggered, only on what objects the GC looks
848 no effect on when GCs are triggered, only on what objects the GC looks
849 into. As a workaround, disable GC while building complex (huge)
849 into. As a workaround, disable GC while building complex (huge)
850 containers.
850 containers.
851
851
852 This garbage collector issue have been fixed in 2.7.
852 This garbage collector issue have been fixed in 2.7.
853 """
853 """
854 def wrapper(*args, **kwargs):
854 def wrapper(*args, **kwargs):
855 gcenabled = gc.isenabled()
855 gcenabled = gc.isenabled()
856 gc.disable()
856 gc.disable()
857 try:
857 try:
858 return func(*args, **kwargs)
858 return func(*args, **kwargs)
859 finally:
859 finally:
860 if gcenabled:
860 if gcenabled:
861 gc.enable()
861 gc.enable()
862 return wrapper
862 return wrapper
863
863
864 def pathto(root, n1, n2):
864 def pathto(root, n1, n2):
865 '''return the relative path from one place to another.
865 '''return the relative path from one place to another.
866 root should use os.sep to separate directories
866 root should use os.sep to separate directories
867 n1 should use os.sep to separate directories
867 n1 should use os.sep to separate directories
868 n2 should use "/" to separate directories
868 n2 should use "/" to separate directories
869 returns an os.sep-separated path.
869 returns an os.sep-separated path.
870
870
871 If n1 is a relative path, it's assumed it's
871 If n1 is a relative path, it's assumed it's
872 relative to root.
872 relative to root.
873 n2 should always be relative to root.
873 n2 should always be relative to root.
874 '''
874 '''
875 if not n1:
875 if not n1:
876 return localpath(n2)
876 return localpath(n2)
877 if os.path.isabs(n1):
877 if os.path.isabs(n1):
878 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
878 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
879 return os.path.join(root, localpath(n2))
879 return os.path.join(root, localpath(n2))
880 n2 = '/'.join((pconvert(root), n2))
880 n2 = '/'.join((pconvert(root), n2))
881 a, b = splitpath(n1), n2.split('/')
881 a, b = splitpath(n1), n2.split('/')
882 a.reverse()
882 a.reverse()
883 b.reverse()
883 b.reverse()
884 while a and b and a[-1] == b[-1]:
884 while a and b and a[-1] == b[-1]:
885 a.pop()
885 a.pop()
886 b.pop()
886 b.pop()
887 b.reverse()
887 b.reverse()
888 return os.sep.join((['..'] * len(a)) + b) or '.'
888 return os.sep.join((['..'] * len(a)) + b) or '.'
889
889
890 def mainfrozen():
890 def mainfrozen():
891 """return True if we are a frozen executable.
891 """return True if we are a frozen executable.
892
892
893 The code supports py2exe (most common, Windows only) and tools/freeze
893 The code supports py2exe (most common, Windows only) and tools/freeze
894 (portable, not much used).
894 (portable, not much used).
895 """
895 """
896 return (safehasattr(sys, "frozen") or # new py2exe
896 return (safehasattr(sys, "frozen") or # new py2exe
897 safehasattr(sys, "importers") or # old py2exe
897 safehasattr(sys, "importers") or # old py2exe
898 imp.is_frozen("__main__")) # tools/freeze
898 imp.is_frozen("__main__")) # tools/freeze
899
899
900 # the location of data files matching the source code
900 # the location of data files matching the source code
901 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
901 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
902 # executable version (py2exe) doesn't support __file__
902 # executable version (py2exe) doesn't support __file__
903 datapath = os.path.dirname(sys.executable)
903 datapath = os.path.dirname(sys.executable)
904 else:
904 else:
905 datapath = os.path.dirname(__file__)
905 datapath = os.path.dirname(__file__)
906
906
907 i18n.setdatapath(datapath)
907 i18n.setdatapath(datapath)
908
908
909 _hgexecutable = None
909 _hgexecutable = None
910
910
911 def hgexecutable():
911 def hgexecutable():
912 """return location of the 'hg' executable.
912 """return location of the 'hg' executable.
913
913
914 Defaults to $HG or 'hg' in the search path.
914 Defaults to $HG or 'hg' in the search path.
915 """
915 """
916 if _hgexecutable is None:
916 if _hgexecutable is None:
917 hg = os.environ.get('HG')
917 hg = os.environ.get('HG')
918 mainmod = sys.modules['__main__']
918 mainmod = sys.modules['__main__']
919 if hg:
919 if hg:
920 _sethgexecutable(hg)
920 _sethgexecutable(hg)
921 elif mainfrozen():
921 elif mainfrozen():
922 if getattr(sys, 'frozen', None) == 'macosx_app':
922 if getattr(sys, 'frozen', None) == 'macosx_app':
923 # Env variable set by py2app
923 # Env variable set by py2app
924 _sethgexecutable(os.environ['EXECUTABLEPATH'])
924 _sethgexecutable(os.environ['EXECUTABLEPATH'])
925 else:
925 else:
926 _sethgexecutable(sys.executable)
926 _sethgexecutable(sys.executable)
927 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
927 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
928 _sethgexecutable(mainmod.__file__)
928 _sethgexecutable(mainmod.__file__)
929 else:
929 else:
930 exe = findexe('hg') or os.path.basename(sys.argv[0])
930 exe = findexe('hg') or os.path.basename(sys.argv[0])
931 _sethgexecutable(exe)
931 _sethgexecutable(exe)
932 return _hgexecutable
932 return _hgexecutable
933
933
934 def _sethgexecutable(path):
934 def _sethgexecutable(path):
935 """set location of the 'hg' executable"""
935 """set location of the 'hg' executable"""
936 global _hgexecutable
936 global _hgexecutable
937 _hgexecutable = path
937 _hgexecutable = path
938
938
939 def _isstdout(f):
939 def _isstdout(f):
940 fileno = getattr(f, 'fileno', None)
940 fileno = getattr(f, 'fileno', None)
941 return fileno and fileno() == sys.__stdout__.fileno()
941 return fileno and fileno() == sys.__stdout__.fileno()
942
942
943 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
943 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
944 '''enhanced shell command execution.
944 '''enhanced shell command execution.
945 run with environment maybe modified, maybe in different dir.
945 run with environment maybe modified, maybe in different dir.
946
946
947 if command fails and onerr is None, return status, else raise onerr
947 if command fails and onerr is None, return status, else raise onerr
948 object as exception.
948 object as exception.
949
949
950 if out is specified, it is assumed to be a file-like object that has a
950 if out is specified, it is assumed to be a file-like object that has a
951 write() method. stdout and stderr will be redirected to out.'''
951 write() method. stdout and stderr will be redirected to out.'''
952 if environ is None:
952 if environ is None:
953 environ = {}
953 environ = {}
954 try:
954 try:
955 sys.stdout.flush()
955 sys.stdout.flush()
956 except Exception:
956 except Exception:
957 pass
957 pass
958 def py2shell(val):
958 def py2shell(val):
959 'convert python object into string that is useful to shell'
959 'convert python object into string that is useful to shell'
960 if val is None or val is False:
960 if val is None or val is False:
961 return '0'
961 return '0'
962 if val is True:
962 if val is True:
963 return '1'
963 return '1'
964 return str(val)
964 return str(val)
965 origcmd = cmd
965 origcmd = cmd
966 cmd = quotecommand(cmd)
966 cmd = quotecommand(cmd)
967 if sys.platform == 'plan9' and (sys.version_info[0] == 2
967 if sys.platform == 'plan9' and (sys.version_info[0] == 2
968 and sys.version_info[1] < 7):
968 and sys.version_info[1] < 7):
969 # subprocess kludge to work around issues in half-baked Python
969 # subprocess kludge to work around issues in half-baked Python
970 # ports, notably bichued/python:
970 # ports, notably bichued/python:
971 if not cwd is None:
971 if not cwd is None:
972 os.chdir(cwd)
972 os.chdir(cwd)
973 rc = os.system(cmd)
973 rc = os.system(cmd)
974 else:
974 else:
975 env = dict(os.environ)
975 env = dict(os.environ)
976 env.update((k, py2shell(v)) for k, v in environ.iteritems())
976 env.update((k, py2shell(v)) for k, v in environ.iteritems())
977 env['HG'] = hgexecutable()
977 env['HG'] = hgexecutable()
978 if out is None or _isstdout(out):
978 if out is None or _isstdout(out):
979 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
979 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
980 env=env, cwd=cwd)
980 env=env, cwd=cwd)
981 else:
981 else:
982 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
982 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
983 env=env, cwd=cwd, stdout=subprocess.PIPE,
983 env=env, cwd=cwd, stdout=subprocess.PIPE,
984 stderr=subprocess.STDOUT)
984 stderr=subprocess.STDOUT)
985 while True:
985 while True:
986 line = proc.stdout.readline()
986 line = proc.stdout.readline()
987 if not line:
987 if not line:
988 break
988 break
989 out.write(line)
989 out.write(line)
990 proc.wait()
990 proc.wait()
991 rc = proc.returncode
991 rc = proc.returncode
992 if sys.platform == 'OpenVMS' and rc & 1:
992 if sys.platform == 'OpenVMS' and rc & 1:
993 rc = 0
993 rc = 0
994 if rc and onerr:
994 if rc and onerr:
995 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
995 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
996 explainexit(rc)[0])
996 explainexit(rc)[0])
997 if errprefix:
997 if errprefix:
998 errmsg = '%s: %s' % (errprefix, errmsg)
998 errmsg = '%s: %s' % (errprefix, errmsg)
999 raise onerr(errmsg)
999 raise onerr(errmsg)
1000 return rc
1000 return rc
1001
1001
1002 def checksignature(func):
1002 def checksignature(func):
1003 '''wrap a function with code to check for calling errors'''
1003 '''wrap a function with code to check for calling errors'''
1004 def check(*args, **kwargs):
1004 def check(*args, **kwargs):
1005 try:
1005 try:
1006 return func(*args, **kwargs)
1006 return func(*args, **kwargs)
1007 except TypeError:
1007 except TypeError:
1008 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1008 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1009 raise error.SignatureError
1009 raise error.SignatureError
1010 raise
1010 raise
1011
1011
1012 return check
1012 return check
1013
1013
1014 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1014 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1015 '''copy a file, preserving mode and optionally other stat info like
1015 '''copy a file, preserving mode and optionally other stat info like
1016 atime/mtime'''
1016 atime/mtime'''
1017 assert not (copystat and checkambig)
1017 assert not (copystat and checkambig)
1018 oldstat = None
1018 oldstat = None
1019 if os.path.lexists(dest):
1019 if os.path.lexists(dest):
1020 if checkambig:
1020 if checkambig:
1021 oldstat = checkambig and filestat(dest)
1021 oldstat = checkambig and filestat(dest)
1022 unlink(dest)
1022 unlink(dest)
1023 # hardlinks are problematic on CIFS, quietly ignore this flag
1023 # hardlinks are problematic on CIFS, quietly ignore this flag
1024 # until we find a way to work around it cleanly (issue4546)
1024 # until we find a way to work around it cleanly (issue4546)
1025 if False and hardlink:
1025 if False and hardlink:
1026 try:
1026 try:
1027 oslink(src, dest)
1027 oslink(src, dest)
1028 return
1028 return
1029 except (IOError, OSError):
1029 except (IOError, OSError):
1030 pass # fall back to normal copy
1030 pass # fall back to normal copy
1031 if os.path.islink(src):
1031 if os.path.islink(src):
1032 os.symlink(os.readlink(src), dest)
1032 os.symlink(os.readlink(src), dest)
1033 # copytime is ignored for symlinks, but in general copytime isn't needed
1033 # copytime is ignored for symlinks, but in general copytime isn't needed
1034 # for them anyway
1034 # for them anyway
1035 else:
1035 else:
1036 try:
1036 try:
1037 shutil.copyfile(src, dest)
1037 shutil.copyfile(src, dest)
1038 if copystat:
1038 if copystat:
1039 # copystat also copies mode
1039 # copystat also copies mode
1040 shutil.copystat(src, dest)
1040 shutil.copystat(src, dest)
1041 else:
1041 else:
1042 shutil.copymode(src, dest)
1042 shutil.copymode(src, dest)
1043 if oldstat and oldstat.stat:
1043 if oldstat and oldstat.stat:
1044 newstat = filestat(dest)
1044 newstat = filestat(dest)
1045 if newstat.isambig(oldstat):
1045 if newstat.isambig(oldstat):
1046 # stat of copied file is ambiguous to original one
1046 # stat of copied file is ambiguous to original one
1047 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1047 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1048 os.utime(dest, (advanced, advanced))
1048 os.utime(dest, (advanced, advanced))
1049 except shutil.Error as inst:
1049 except shutil.Error as inst:
1050 raise Abort(str(inst))
1050 raise Abort(str(inst))
1051
1051
1052 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1052 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1053 """Copy a directory tree using hardlinks if possible."""
1053 """Copy a directory tree using hardlinks if possible."""
1054 num = 0
1054 num = 0
1055
1055
1056 if hardlink is None:
1056 if hardlink is None:
1057 hardlink = (os.stat(src).st_dev ==
1057 hardlink = (os.stat(src).st_dev ==
1058 os.stat(os.path.dirname(dst)).st_dev)
1058 os.stat(os.path.dirname(dst)).st_dev)
1059 if hardlink:
1059 if hardlink:
1060 topic = _('linking')
1060 topic = _('linking')
1061 else:
1061 else:
1062 topic = _('copying')
1062 topic = _('copying')
1063
1063
1064 if os.path.isdir(src):
1064 if os.path.isdir(src):
1065 os.mkdir(dst)
1065 os.mkdir(dst)
1066 for name, kind in osutil.listdir(src):
1066 for name, kind in osutil.listdir(src):
1067 srcname = os.path.join(src, name)
1067 srcname = os.path.join(src, name)
1068 dstname = os.path.join(dst, name)
1068 dstname = os.path.join(dst, name)
1069 def nprog(t, pos):
1069 def nprog(t, pos):
1070 if pos is not None:
1070 if pos is not None:
1071 return progress(t, pos + num)
1071 return progress(t, pos + num)
1072 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1072 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1073 num += n
1073 num += n
1074 else:
1074 else:
1075 if hardlink:
1075 if hardlink:
1076 try:
1076 try:
1077 oslink(src, dst)
1077 oslink(src, dst)
1078 except (IOError, OSError):
1078 except (IOError, OSError):
1079 hardlink = False
1079 hardlink = False
1080 shutil.copy(src, dst)
1080 shutil.copy(src, dst)
1081 else:
1081 else:
1082 shutil.copy(src, dst)
1082 shutil.copy(src, dst)
1083 num += 1
1083 num += 1
1084 progress(topic, num)
1084 progress(topic, num)
1085 progress(topic, None)
1085 progress(topic, None)
1086
1086
1087 return hardlink, num
1087 return hardlink, num
1088
1088
1089 _winreservednames = '''con prn aux nul
1089 _winreservednames = '''con prn aux nul
1090 com1 com2 com3 com4 com5 com6 com7 com8 com9
1090 com1 com2 com3 com4 com5 com6 com7 com8 com9
1091 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1091 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1092 _winreservedchars = ':*?"<>|'
1092 _winreservedchars = ':*?"<>|'
1093 def checkwinfilename(path):
1093 def checkwinfilename(path):
1094 r'''Check that the base-relative path is a valid filename on Windows.
1094 r'''Check that the base-relative path is a valid filename on Windows.
1095 Returns None if the path is ok, or a UI string describing the problem.
1095 Returns None if the path is ok, or a UI string describing the problem.
1096
1096
1097 >>> checkwinfilename("just/a/normal/path")
1097 >>> checkwinfilename("just/a/normal/path")
1098 >>> checkwinfilename("foo/bar/con.xml")
1098 >>> checkwinfilename("foo/bar/con.xml")
1099 "filename contains 'con', which is reserved on Windows"
1099 "filename contains 'con', which is reserved on Windows"
1100 >>> checkwinfilename("foo/con.xml/bar")
1100 >>> checkwinfilename("foo/con.xml/bar")
1101 "filename contains 'con', which is reserved on Windows"
1101 "filename contains 'con', which is reserved on Windows"
1102 >>> checkwinfilename("foo/bar/xml.con")
1102 >>> checkwinfilename("foo/bar/xml.con")
1103 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1103 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1104 "filename contains 'AUX', which is reserved on Windows"
1104 "filename contains 'AUX', which is reserved on Windows"
1105 >>> checkwinfilename("foo/bar/bla:.txt")
1105 >>> checkwinfilename("foo/bar/bla:.txt")
1106 "filename contains ':', which is reserved on Windows"
1106 "filename contains ':', which is reserved on Windows"
1107 >>> checkwinfilename("foo/bar/b\07la.txt")
1107 >>> checkwinfilename("foo/bar/b\07la.txt")
1108 "filename contains '\\x07', which is invalid on Windows"
1108 "filename contains '\\x07', which is invalid on Windows"
1109 >>> checkwinfilename("foo/bar/bla ")
1109 >>> checkwinfilename("foo/bar/bla ")
1110 "filename ends with ' ', which is not allowed on Windows"
1110 "filename ends with ' ', which is not allowed on Windows"
1111 >>> checkwinfilename("../bar")
1111 >>> checkwinfilename("../bar")
1112 >>> checkwinfilename("foo\\")
1112 >>> checkwinfilename("foo\\")
1113 "filename ends with '\\', which is invalid on Windows"
1113 "filename ends with '\\', which is invalid on Windows"
1114 >>> checkwinfilename("foo\\/bar")
1114 >>> checkwinfilename("foo\\/bar")
1115 "directory name ends with '\\', which is invalid on Windows"
1115 "directory name ends with '\\', which is invalid on Windows"
1116 '''
1116 '''
1117 if path.endswith('\\'):
1117 if path.endswith('\\'):
1118 return _("filename ends with '\\', which is invalid on Windows")
1118 return _("filename ends with '\\', which is invalid on Windows")
1119 if '\\/' in path:
1119 if '\\/' in path:
1120 return _("directory name ends with '\\', which is invalid on Windows")
1120 return _("directory name ends with '\\', which is invalid on Windows")
1121 for n in path.replace('\\', '/').split('/'):
1121 for n in path.replace('\\', '/').split('/'):
1122 if not n:
1122 if not n:
1123 continue
1123 continue
1124 for c in n:
1124 for c in n:
1125 if c in _winreservedchars:
1125 if c in _winreservedchars:
1126 return _("filename contains '%s', which is reserved "
1126 return _("filename contains '%s', which is reserved "
1127 "on Windows") % c
1127 "on Windows") % c
1128 if ord(c) <= 31:
1128 if ord(c) <= 31:
1129 return _("filename contains %r, which is invalid "
1129 return _("filename contains %r, which is invalid "
1130 "on Windows") % c
1130 "on Windows") % c
1131 base = n.split('.')[0]
1131 base = n.split('.')[0]
1132 if base and base.lower() in _winreservednames:
1132 if base and base.lower() in _winreservednames:
1133 return _("filename contains '%s', which is reserved "
1133 return _("filename contains '%s', which is reserved "
1134 "on Windows") % base
1134 "on Windows") % base
1135 t = n[-1]
1135 t = n[-1]
1136 if t in '. ' and n not in '..':
1136 if t in '. ' and n not in '..':
1137 return _("filename ends with '%s', which is not allowed "
1137 return _("filename ends with '%s', which is not allowed "
1138 "on Windows") % t
1138 "on Windows") % t
1139
1139
1140 if os.name == 'nt':
1140 if os.name == 'nt':
1141 checkosfilename = checkwinfilename
1141 checkosfilename = checkwinfilename
1142 else:
1142 else:
1143 checkosfilename = platform.checkosfilename
1143 checkosfilename = platform.checkosfilename
1144
1144
1145 def makelock(info, pathname):
1145 def makelock(info, pathname):
1146 try:
1146 try:
1147 return os.symlink(info, pathname)
1147 return os.symlink(info, pathname)
1148 except OSError as why:
1148 except OSError as why:
1149 if why.errno == errno.EEXIST:
1149 if why.errno == errno.EEXIST:
1150 raise
1150 raise
1151 except AttributeError: # no symlink in os
1151 except AttributeError: # no symlink in os
1152 pass
1152 pass
1153
1153
1154 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1154 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1155 os.write(ld, info)
1155 os.write(ld, info)
1156 os.close(ld)
1156 os.close(ld)
1157
1157
1158 def readlock(pathname):
1158 def readlock(pathname):
1159 try:
1159 try:
1160 return os.readlink(pathname)
1160 return os.readlink(pathname)
1161 except OSError as why:
1161 except OSError as why:
1162 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1162 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1163 raise
1163 raise
1164 except AttributeError: # no symlink in os
1164 except AttributeError: # no symlink in os
1165 pass
1165 pass
1166 fp = posixfile(pathname)
1166 fp = posixfile(pathname)
1167 r = fp.read()
1167 r = fp.read()
1168 fp.close()
1168 fp.close()
1169 return r
1169 return r
1170
1170
1171 def fstat(fp):
1171 def fstat(fp):
1172 '''stat file object that may not have fileno method.'''
1172 '''stat file object that may not have fileno method.'''
1173 try:
1173 try:
1174 return os.fstat(fp.fileno())
1174 return os.fstat(fp.fileno())
1175 except AttributeError:
1175 except AttributeError:
1176 return os.stat(fp.name)
1176 return os.stat(fp.name)
1177
1177
1178 # File system features
1178 # File system features
1179
1179
1180 def checkcase(path):
1180 def checkcase(path):
1181 """
1181 """
1182 Return true if the given path is on a case-sensitive filesystem
1182 Return true if the given path is on a case-sensitive filesystem
1183
1183
1184 Requires a path (like /foo/.hg) ending with a foldable final
1184 Requires a path (like /foo/.hg) ending with a foldable final
1185 directory component.
1185 directory component.
1186 """
1186 """
1187 s1 = os.lstat(path)
1187 s1 = os.lstat(path)
1188 d, b = os.path.split(path)
1188 d, b = os.path.split(path)
1189 b2 = b.upper()
1189 b2 = b.upper()
1190 if b == b2:
1190 if b == b2:
1191 b2 = b.lower()
1191 b2 = b.lower()
1192 if b == b2:
1192 if b == b2:
1193 return True # no evidence against case sensitivity
1193 return True # no evidence against case sensitivity
1194 p2 = os.path.join(d, b2)
1194 p2 = os.path.join(d, b2)
1195 try:
1195 try:
1196 s2 = os.lstat(p2)
1196 s2 = os.lstat(p2)
1197 if s2 == s1:
1197 if s2 == s1:
1198 return False
1198 return False
1199 return True
1199 return True
1200 except OSError:
1200 except OSError:
1201 return True
1201 return True
1202
1202
1203 try:
1203 try:
1204 import re2
1204 import re2
1205 _re2 = None
1205 _re2 = None
1206 except ImportError:
1206 except ImportError:
1207 _re2 = False
1207 _re2 = False
1208
1208
1209 class _re(object):
1209 class _re(object):
1210 def _checkre2(self):
1210 def _checkre2(self):
1211 global _re2
1211 global _re2
1212 try:
1212 try:
1213 # check if match works, see issue3964
1213 # check if match works, see issue3964
1214 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1214 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1215 except ImportError:
1215 except ImportError:
1216 _re2 = False
1216 _re2 = False
1217
1217
1218 def compile(self, pat, flags=0):
1218 def compile(self, pat, flags=0):
1219 '''Compile a regular expression, using re2 if possible
1219 '''Compile a regular expression, using re2 if possible
1220
1220
1221 For best performance, use only re2-compatible regexp features. The
1221 For best performance, use only re2-compatible regexp features. The
1222 only flags from the re module that are re2-compatible are
1222 only flags from the re module that are re2-compatible are
1223 IGNORECASE and MULTILINE.'''
1223 IGNORECASE and MULTILINE.'''
1224 if _re2 is None:
1224 if _re2 is None:
1225 self._checkre2()
1225 self._checkre2()
1226 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1226 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1227 if flags & remod.IGNORECASE:
1227 if flags & remod.IGNORECASE:
1228 pat = '(?i)' + pat
1228 pat = '(?i)' + pat
1229 if flags & remod.MULTILINE:
1229 if flags & remod.MULTILINE:
1230 pat = '(?m)' + pat
1230 pat = '(?m)' + pat
1231 try:
1231 try:
1232 return re2.compile(pat)
1232 return re2.compile(pat)
1233 except re2.error:
1233 except re2.error:
1234 pass
1234 pass
1235 return remod.compile(pat, flags)
1235 return remod.compile(pat, flags)
1236
1236
1237 @propertycache
1237 @propertycache
1238 def escape(self):
1238 def escape(self):
1239 '''Return the version of escape corresponding to self.compile.
1239 '''Return the version of escape corresponding to self.compile.
1240
1240
1241 This is imperfect because whether re2 or re is used for a particular
1241 This is imperfect because whether re2 or re is used for a particular
1242 function depends on the flags, etc, but it's the best we can do.
1242 function depends on the flags, etc, but it's the best we can do.
1243 '''
1243 '''
1244 global _re2
1244 global _re2
1245 if _re2 is None:
1245 if _re2 is None:
1246 self._checkre2()
1246 self._checkre2()
1247 if _re2:
1247 if _re2:
1248 return re2.escape
1248 return re2.escape
1249 else:
1249 else:
1250 return remod.escape
1250 return remod.escape
1251
1251
1252 re = _re()
1252 re = _re()
1253
1253
1254 _fspathcache = {}
1254 _fspathcache = {}
1255 def fspath(name, root):
1255 def fspath(name, root):
1256 '''Get name in the case stored in the filesystem
1256 '''Get name in the case stored in the filesystem
1257
1257
1258 The name should be relative to root, and be normcase-ed for efficiency.
1258 The name should be relative to root, and be normcase-ed for efficiency.
1259
1259
1260 Note that this function is unnecessary, and should not be
1260 Note that this function is unnecessary, and should not be
1261 called, for case-sensitive filesystems (simply because it's expensive).
1261 called, for case-sensitive filesystems (simply because it's expensive).
1262
1262
1263 The root should be normcase-ed, too.
1263 The root should be normcase-ed, too.
1264 '''
1264 '''
1265 def _makefspathcacheentry(dir):
1265 def _makefspathcacheentry(dir):
1266 return dict((normcase(n), n) for n in os.listdir(dir))
1266 return dict((normcase(n), n) for n in os.listdir(dir))
1267
1267
1268 seps = os.sep
1268 seps = os.sep
1269 if os.altsep:
1269 if os.altsep:
1270 seps = seps + os.altsep
1270 seps = seps + os.altsep
1271 # Protect backslashes. This gets silly very quickly.
1271 # Protect backslashes. This gets silly very quickly.
1272 seps.replace('\\','\\\\')
1272 seps.replace('\\','\\\\')
1273 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1273 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1274 dir = os.path.normpath(root)
1274 dir = os.path.normpath(root)
1275 result = []
1275 result = []
1276 for part, sep in pattern.findall(name):
1276 for part, sep in pattern.findall(name):
1277 if sep:
1277 if sep:
1278 result.append(sep)
1278 result.append(sep)
1279 continue
1279 continue
1280
1280
1281 if dir not in _fspathcache:
1281 if dir not in _fspathcache:
1282 _fspathcache[dir] = _makefspathcacheentry(dir)
1282 _fspathcache[dir] = _makefspathcacheentry(dir)
1283 contents = _fspathcache[dir]
1283 contents = _fspathcache[dir]
1284
1284
1285 found = contents.get(part)
1285 found = contents.get(part)
1286 if not found:
1286 if not found:
1287 # retry "once per directory" per "dirstate.walk" which
1287 # retry "once per directory" per "dirstate.walk" which
1288 # may take place for each patches of "hg qpush", for example
1288 # may take place for each patches of "hg qpush", for example
1289 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1289 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1290 found = contents.get(part)
1290 found = contents.get(part)
1291
1291
1292 result.append(found or part)
1292 result.append(found or part)
1293 dir = os.path.join(dir, part)
1293 dir = os.path.join(dir, part)
1294
1294
1295 return ''.join(result)
1295 return ''.join(result)
1296
1296
1297 def checknlink(testfile):
1297 def checknlink(testfile):
1298 '''check whether hardlink count reporting works properly'''
1298 '''check whether hardlink count reporting works properly'''
1299
1299
1300 # testfile may be open, so we need a separate file for checking to
1300 # testfile may be open, so we need a separate file for checking to
1301 # work around issue2543 (or testfile may get lost on Samba shares)
1301 # work around issue2543 (or testfile may get lost on Samba shares)
1302 f1 = testfile + ".hgtmp1"
1302 f1 = testfile + ".hgtmp1"
1303 if os.path.lexists(f1):
1303 if os.path.lexists(f1):
1304 return False
1304 return False
1305 try:
1305 try:
1306 posixfile(f1, 'w').close()
1306 posixfile(f1, 'w').close()
1307 except IOError:
1307 except IOError:
1308 return False
1308 return False
1309
1309
1310 f2 = testfile + ".hgtmp2"
1310 f2 = testfile + ".hgtmp2"
1311 fd = None
1311 fd = None
1312 try:
1312 try:
1313 oslink(f1, f2)
1313 oslink(f1, f2)
1314 # nlinks() may behave differently for files on Windows shares if
1314 # nlinks() may behave differently for files on Windows shares if
1315 # the file is open.
1315 # the file is open.
1316 fd = posixfile(f2)
1316 fd = posixfile(f2)
1317 return nlinks(f2) > 1
1317 return nlinks(f2) > 1
1318 except OSError:
1318 except OSError:
1319 return False
1319 return False
1320 finally:
1320 finally:
1321 if fd is not None:
1321 if fd is not None:
1322 fd.close()
1322 fd.close()
1323 for f in (f1, f2):
1323 for f in (f1, f2):
1324 try:
1324 try:
1325 os.unlink(f)
1325 os.unlink(f)
1326 except OSError:
1326 except OSError:
1327 pass
1327 pass
1328
1328
1329 def endswithsep(path):
1329 def endswithsep(path):
1330 '''Check path ends with os.sep or os.altsep.'''
1330 '''Check path ends with os.sep or os.altsep.'''
1331 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1331 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1332
1332
1333 def splitpath(path):
1333 def splitpath(path):
1334 '''Split path by os.sep.
1334 '''Split path by os.sep.
1335 Note that this function does not use os.altsep because this is
1335 Note that this function does not use os.altsep because this is
1336 an alternative of simple "xxx.split(os.sep)".
1336 an alternative of simple "xxx.split(os.sep)".
1337 It is recommended to use os.path.normpath() before using this
1337 It is recommended to use os.path.normpath() before using this
1338 function if need.'''
1338 function if need.'''
1339 return path.split(os.sep)
1339 return path.split(os.sep)
1340
1340
1341 def gui():
1341 def gui():
1342 '''Are we running in a GUI?'''
1342 '''Are we running in a GUI?'''
1343 if sys.platform == 'darwin':
1343 if sys.platform == 'darwin':
1344 if 'SSH_CONNECTION' in os.environ:
1344 if 'SSH_CONNECTION' in os.environ:
1345 # handle SSH access to a box where the user is logged in
1345 # handle SSH access to a box where the user is logged in
1346 return False
1346 return False
1347 elif getattr(osutil, 'isgui', None):
1347 elif getattr(osutil, 'isgui', None):
1348 # check if a CoreGraphics session is available
1348 # check if a CoreGraphics session is available
1349 return osutil.isgui()
1349 return osutil.isgui()
1350 else:
1350 else:
1351 # pure build; use a safe default
1351 # pure build; use a safe default
1352 return True
1352 return True
1353 else:
1353 else:
1354 return os.name == "nt" or os.environ.get("DISPLAY")
1354 return os.name == "nt" or os.environ.get("DISPLAY")
1355
1355
1356 def mktempcopy(name, emptyok=False, createmode=None):
1356 def mktempcopy(name, emptyok=False, createmode=None):
1357 """Create a temporary file with the same contents from name
1357 """Create a temporary file with the same contents from name
1358
1358
1359 The permission bits are copied from the original file.
1359 The permission bits are copied from the original file.
1360
1360
1361 If the temporary file is going to be truncated immediately, you
1361 If the temporary file is going to be truncated immediately, you
1362 can use emptyok=True as an optimization.
1362 can use emptyok=True as an optimization.
1363
1363
1364 Returns the name of the temporary file.
1364 Returns the name of the temporary file.
1365 """
1365 """
1366 d, fn = os.path.split(name)
1366 d, fn = os.path.split(name)
1367 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1367 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1368 os.close(fd)
1368 os.close(fd)
1369 # Temporary files are created with mode 0600, which is usually not
1369 # Temporary files are created with mode 0600, which is usually not
1370 # what we want. If the original file already exists, just copy
1370 # what we want. If the original file already exists, just copy
1371 # its mode. Otherwise, manually obey umask.
1371 # its mode. Otherwise, manually obey umask.
1372 copymode(name, temp, createmode)
1372 copymode(name, temp, createmode)
1373 if emptyok:
1373 if emptyok:
1374 return temp
1374 return temp
1375 try:
1375 try:
1376 try:
1376 try:
1377 ifp = posixfile(name, "rb")
1377 ifp = posixfile(name, "rb")
1378 except IOError as inst:
1378 except IOError as inst:
1379 if inst.errno == errno.ENOENT:
1379 if inst.errno == errno.ENOENT:
1380 return temp
1380 return temp
1381 if not getattr(inst, 'filename', None):
1381 if not getattr(inst, 'filename', None):
1382 inst.filename = name
1382 inst.filename = name
1383 raise
1383 raise
1384 ofp = posixfile(temp, "wb")
1384 ofp = posixfile(temp, "wb")
1385 for chunk in filechunkiter(ifp):
1385 for chunk in filechunkiter(ifp):
1386 ofp.write(chunk)
1386 ofp.write(chunk)
1387 ifp.close()
1387 ifp.close()
1388 ofp.close()
1388 ofp.close()
1389 except: # re-raises
1389 except: # re-raises
1390 try: os.unlink(temp)
1390 try: os.unlink(temp)
1391 except OSError: pass
1391 except OSError: pass
1392 raise
1392 raise
1393 return temp
1393 return temp
1394
1394
1395 class filestat(object):
1395 class filestat(object):
1396 """help to exactly detect change of a file
1396 """help to exactly detect change of a file
1397
1397
1398 'stat' attribute is result of 'os.stat()' if specified 'path'
1398 'stat' attribute is result of 'os.stat()' if specified 'path'
1399 exists. Otherwise, it is None. This can avoid preparative
1399 exists. Otherwise, it is None. This can avoid preparative
1400 'exists()' examination on client side of this class.
1400 'exists()' examination on client side of this class.
1401 """
1401 """
1402 def __init__(self, path):
1402 def __init__(self, path):
1403 try:
1403 try:
1404 self.stat = os.stat(path)
1404 self.stat = os.stat(path)
1405 except OSError as err:
1405 except OSError as err:
1406 if err.errno != errno.ENOENT:
1406 if err.errno != errno.ENOENT:
1407 raise
1407 raise
1408 self.stat = None
1408 self.stat = None
1409
1409
1410 __hash__ = object.__hash__
1410 __hash__ = object.__hash__
1411
1411
1412 def __eq__(self, old):
1412 def __eq__(self, old):
1413 try:
1413 try:
1414 # if ambiguity between stat of new and old file is
1414 # if ambiguity between stat of new and old file is
1415 # avoided, comparision of size, ctime and mtime is enough
1415 # avoided, comparision of size, ctime and mtime is enough
1416 # to exactly detect change of a file regardless of platform
1416 # to exactly detect change of a file regardless of platform
1417 return (self.stat.st_size == old.stat.st_size and
1417 return (self.stat.st_size == old.stat.st_size and
1418 self.stat.st_ctime == old.stat.st_ctime and
1418 self.stat.st_ctime == old.stat.st_ctime and
1419 self.stat.st_mtime == old.stat.st_mtime)
1419 self.stat.st_mtime == old.stat.st_mtime)
1420 except AttributeError:
1420 except AttributeError:
1421 return False
1421 return False
1422
1422
1423 def isambig(self, old):
1423 def isambig(self, old):
1424 """Examine whether new (= self) stat is ambiguous against old one
1424 """Examine whether new (= self) stat is ambiguous against old one
1425
1425
1426 "S[N]" below means stat of a file at N-th change:
1426 "S[N]" below means stat of a file at N-th change:
1427
1427
1428 - S[n-1].ctime < S[n].ctime: can detect change of a file
1428 - S[n-1].ctime < S[n].ctime: can detect change of a file
1429 - S[n-1].ctime == S[n].ctime
1429 - S[n-1].ctime == S[n].ctime
1430 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1430 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1431 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1431 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1432 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1432 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1433 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1433 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1434
1434
1435 Case (*2) above means that a file was changed twice or more at
1435 Case (*2) above means that a file was changed twice or more at
1436 same time in sec (= S[n-1].ctime), and comparison of timestamp
1436 same time in sec (= S[n-1].ctime), and comparison of timestamp
1437 is ambiguous.
1437 is ambiguous.
1438
1438
1439 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1439 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1440 timestamp is ambiguous".
1440 timestamp is ambiguous".
1441
1441
1442 But advancing mtime only in case (*2) doesn't work as
1442 But advancing mtime only in case (*2) doesn't work as
1443 expected, because naturally advanced S[n].mtime in case (*1)
1443 expected, because naturally advanced S[n].mtime in case (*1)
1444 might be equal to manually advanced S[n-1 or earlier].mtime.
1444 might be equal to manually advanced S[n-1 or earlier].mtime.
1445
1445
1446 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1446 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1447 treated as ambiguous regardless of mtime, to avoid overlooking
1447 treated as ambiguous regardless of mtime, to avoid overlooking
1448 by confliction between such mtime.
1448 by confliction between such mtime.
1449
1449
1450 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1450 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1451 S[n].mtime", even if size of a file isn't changed.
1451 S[n].mtime", even if size of a file isn't changed.
1452 """
1452 """
1453 try:
1453 try:
1454 return (self.stat.st_ctime == old.stat.st_ctime)
1454 return (self.stat.st_ctime == old.stat.st_ctime)
1455 except AttributeError:
1455 except AttributeError:
1456 return False
1456 return False
1457
1457
1458 def __ne__(self, other):
1459 return not self == other
1460
1458 class atomictempfile(object):
1461 class atomictempfile(object):
1459 '''writable file object that atomically updates a file
1462 '''writable file object that atomically updates a file
1460
1463
1461 All writes will go to a temporary copy of the original file. Call
1464 All writes will go to a temporary copy of the original file. Call
1462 close() when you are done writing, and atomictempfile will rename
1465 close() when you are done writing, and atomictempfile will rename
1463 the temporary copy to the original name, making the changes
1466 the temporary copy to the original name, making the changes
1464 visible. If the object is destroyed without being closed, all your
1467 visible. If the object is destroyed without being closed, all your
1465 writes are discarded.
1468 writes are discarded.
1466 '''
1469 '''
1467 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1470 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1468 self.__name = name # permanent name
1471 self.__name = name # permanent name
1469 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1472 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1470 createmode=createmode)
1473 createmode=createmode)
1471 self._fp = posixfile(self._tempname, mode)
1474 self._fp = posixfile(self._tempname, mode)
1472 self._checkambig = checkambig
1475 self._checkambig = checkambig
1473
1476
1474 # delegated methods
1477 # delegated methods
1475 self.write = self._fp.write
1478 self.write = self._fp.write
1476 self.seek = self._fp.seek
1479 self.seek = self._fp.seek
1477 self.tell = self._fp.tell
1480 self.tell = self._fp.tell
1478 self.fileno = self._fp.fileno
1481 self.fileno = self._fp.fileno
1479
1482
1480 def close(self):
1483 def close(self):
1481 if not self._fp.closed:
1484 if not self._fp.closed:
1482 self._fp.close()
1485 self._fp.close()
1483 filename = localpath(self.__name)
1486 filename = localpath(self.__name)
1484 oldstat = self._checkambig and filestat(filename)
1487 oldstat = self._checkambig and filestat(filename)
1485 if oldstat and oldstat.stat:
1488 if oldstat and oldstat.stat:
1486 rename(self._tempname, filename)
1489 rename(self._tempname, filename)
1487 newstat = filestat(filename)
1490 newstat = filestat(filename)
1488 if newstat.isambig(oldstat):
1491 if newstat.isambig(oldstat):
1489 # stat of changed file is ambiguous to original one
1492 # stat of changed file is ambiguous to original one
1490 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1493 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1491 os.utime(filename, (advanced, advanced))
1494 os.utime(filename, (advanced, advanced))
1492 else:
1495 else:
1493 rename(self._tempname, filename)
1496 rename(self._tempname, filename)
1494
1497
1495 def discard(self):
1498 def discard(self):
1496 if not self._fp.closed:
1499 if not self._fp.closed:
1497 try:
1500 try:
1498 os.unlink(self._tempname)
1501 os.unlink(self._tempname)
1499 except OSError:
1502 except OSError:
1500 pass
1503 pass
1501 self._fp.close()
1504 self._fp.close()
1502
1505
1503 def __del__(self):
1506 def __del__(self):
1504 if safehasattr(self, '_fp'): # constructor actually did something
1507 if safehasattr(self, '_fp'): # constructor actually did something
1505 self.discard()
1508 self.discard()
1506
1509
1507 def makedirs(name, mode=None, notindexed=False):
1510 def makedirs(name, mode=None, notindexed=False):
1508 """recursive directory creation with parent mode inheritance
1511 """recursive directory creation with parent mode inheritance
1509
1512
1510 Newly created directories are marked as "not to be indexed by
1513 Newly created directories are marked as "not to be indexed by
1511 the content indexing service", if ``notindexed`` is specified
1514 the content indexing service", if ``notindexed`` is specified
1512 for "write" mode access.
1515 for "write" mode access.
1513 """
1516 """
1514 try:
1517 try:
1515 makedir(name, notindexed)
1518 makedir(name, notindexed)
1516 except OSError as err:
1519 except OSError as err:
1517 if err.errno == errno.EEXIST:
1520 if err.errno == errno.EEXIST:
1518 return
1521 return
1519 if err.errno != errno.ENOENT or not name:
1522 if err.errno != errno.ENOENT or not name:
1520 raise
1523 raise
1521 parent = os.path.dirname(os.path.abspath(name))
1524 parent = os.path.dirname(os.path.abspath(name))
1522 if parent == name:
1525 if parent == name:
1523 raise
1526 raise
1524 makedirs(parent, mode, notindexed)
1527 makedirs(parent, mode, notindexed)
1525 try:
1528 try:
1526 makedir(name, notindexed)
1529 makedir(name, notindexed)
1527 except OSError as err:
1530 except OSError as err:
1528 # Catch EEXIST to handle races
1531 # Catch EEXIST to handle races
1529 if err.errno == errno.EEXIST:
1532 if err.errno == errno.EEXIST:
1530 return
1533 return
1531 raise
1534 raise
1532 if mode is not None:
1535 if mode is not None:
1533 os.chmod(name, mode)
1536 os.chmod(name, mode)
1534
1537
1535 def readfile(path):
1538 def readfile(path):
1536 with open(path, 'rb') as fp:
1539 with open(path, 'rb') as fp:
1537 return fp.read()
1540 return fp.read()
1538
1541
1539 def writefile(path, text):
1542 def writefile(path, text):
1540 with open(path, 'wb') as fp:
1543 with open(path, 'wb') as fp:
1541 fp.write(text)
1544 fp.write(text)
1542
1545
1543 def appendfile(path, text):
1546 def appendfile(path, text):
1544 with open(path, 'ab') as fp:
1547 with open(path, 'ab') as fp:
1545 fp.write(text)
1548 fp.write(text)
1546
1549
1547 class chunkbuffer(object):
1550 class chunkbuffer(object):
1548 """Allow arbitrary sized chunks of data to be efficiently read from an
1551 """Allow arbitrary sized chunks of data to be efficiently read from an
1549 iterator over chunks of arbitrary size."""
1552 iterator over chunks of arbitrary size."""
1550
1553
1551 def __init__(self, in_iter):
1554 def __init__(self, in_iter):
1552 """in_iter is the iterator that's iterating over the input chunks.
1555 """in_iter is the iterator that's iterating over the input chunks.
1553 targetsize is how big a buffer to try to maintain."""
1556 targetsize is how big a buffer to try to maintain."""
1554 def splitbig(chunks):
1557 def splitbig(chunks):
1555 for chunk in chunks:
1558 for chunk in chunks:
1556 if len(chunk) > 2**20:
1559 if len(chunk) > 2**20:
1557 pos = 0
1560 pos = 0
1558 while pos < len(chunk):
1561 while pos < len(chunk):
1559 end = pos + 2 ** 18
1562 end = pos + 2 ** 18
1560 yield chunk[pos:end]
1563 yield chunk[pos:end]
1561 pos = end
1564 pos = end
1562 else:
1565 else:
1563 yield chunk
1566 yield chunk
1564 self.iter = splitbig(in_iter)
1567 self.iter = splitbig(in_iter)
1565 self._queue = collections.deque()
1568 self._queue = collections.deque()
1566 self._chunkoffset = 0
1569 self._chunkoffset = 0
1567
1570
1568 def read(self, l=None):
1571 def read(self, l=None):
1569 """Read L bytes of data from the iterator of chunks of data.
1572 """Read L bytes of data from the iterator of chunks of data.
1570 Returns less than L bytes if the iterator runs dry.
1573 Returns less than L bytes if the iterator runs dry.
1571
1574
1572 If size parameter is omitted, read everything"""
1575 If size parameter is omitted, read everything"""
1573 if l is None:
1576 if l is None:
1574 return ''.join(self.iter)
1577 return ''.join(self.iter)
1575
1578
1576 left = l
1579 left = l
1577 buf = []
1580 buf = []
1578 queue = self._queue
1581 queue = self._queue
1579 while left > 0:
1582 while left > 0:
1580 # refill the queue
1583 # refill the queue
1581 if not queue:
1584 if not queue:
1582 target = 2**18
1585 target = 2**18
1583 for chunk in self.iter:
1586 for chunk in self.iter:
1584 queue.append(chunk)
1587 queue.append(chunk)
1585 target -= len(chunk)
1588 target -= len(chunk)
1586 if target <= 0:
1589 if target <= 0:
1587 break
1590 break
1588 if not queue:
1591 if not queue:
1589 break
1592 break
1590
1593
1591 # The easy way to do this would be to queue.popleft(), modify the
1594 # The easy way to do this would be to queue.popleft(), modify the
1592 # chunk (if necessary), then queue.appendleft(). However, for cases
1595 # chunk (if necessary), then queue.appendleft(). However, for cases
1593 # where we read partial chunk content, this incurs 2 dequeue
1596 # where we read partial chunk content, this incurs 2 dequeue
1594 # mutations and creates a new str for the remaining chunk in the
1597 # mutations and creates a new str for the remaining chunk in the
1595 # queue. Our code below avoids this overhead.
1598 # queue. Our code below avoids this overhead.
1596
1599
1597 chunk = queue[0]
1600 chunk = queue[0]
1598 chunkl = len(chunk)
1601 chunkl = len(chunk)
1599 offset = self._chunkoffset
1602 offset = self._chunkoffset
1600
1603
1601 # Use full chunk.
1604 # Use full chunk.
1602 if offset == 0 and left >= chunkl:
1605 if offset == 0 and left >= chunkl:
1603 left -= chunkl
1606 left -= chunkl
1604 queue.popleft()
1607 queue.popleft()
1605 buf.append(chunk)
1608 buf.append(chunk)
1606 # self._chunkoffset remains at 0.
1609 # self._chunkoffset remains at 0.
1607 continue
1610 continue
1608
1611
1609 chunkremaining = chunkl - offset
1612 chunkremaining = chunkl - offset
1610
1613
1611 # Use all of unconsumed part of chunk.
1614 # Use all of unconsumed part of chunk.
1612 if left >= chunkremaining:
1615 if left >= chunkremaining:
1613 left -= chunkremaining
1616 left -= chunkremaining
1614 queue.popleft()
1617 queue.popleft()
1615 # offset == 0 is enabled by block above, so this won't merely
1618 # offset == 0 is enabled by block above, so this won't merely
1616 # copy via ``chunk[0:]``.
1619 # copy via ``chunk[0:]``.
1617 buf.append(chunk[offset:])
1620 buf.append(chunk[offset:])
1618 self._chunkoffset = 0
1621 self._chunkoffset = 0
1619
1622
1620 # Partial chunk needed.
1623 # Partial chunk needed.
1621 else:
1624 else:
1622 buf.append(chunk[offset:offset + left])
1625 buf.append(chunk[offset:offset + left])
1623 self._chunkoffset += left
1626 self._chunkoffset += left
1624 left -= chunkremaining
1627 left -= chunkremaining
1625
1628
1626 return ''.join(buf)
1629 return ''.join(buf)
1627
1630
1628 def filechunkiter(f, size=65536, limit=None):
1631 def filechunkiter(f, size=65536, limit=None):
1629 """Create a generator that produces the data in the file size
1632 """Create a generator that produces the data in the file size
1630 (default 65536) bytes at a time, up to optional limit (default is
1633 (default 65536) bytes at a time, up to optional limit (default is
1631 to read all data). Chunks may be less than size bytes if the
1634 to read all data). Chunks may be less than size bytes if the
1632 chunk is the last chunk in the file, or the file is a socket or
1635 chunk is the last chunk in the file, or the file is a socket or
1633 some other type of file that sometimes reads less data than is
1636 some other type of file that sometimes reads less data than is
1634 requested."""
1637 requested."""
1635 assert size >= 0
1638 assert size >= 0
1636 assert limit is None or limit >= 0
1639 assert limit is None or limit >= 0
1637 while True:
1640 while True:
1638 if limit is None:
1641 if limit is None:
1639 nbytes = size
1642 nbytes = size
1640 else:
1643 else:
1641 nbytes = min(limit, size)
1644 nbytes = min(limit, size)
1642 s = nbytes and f.read(nbytes)
1645 s = nbytes and f.read(nbytes)
1643 if not s:
1646 if not s:
1644 break
1647 break
1645 if limit:
1648 if limit:
1646 limit -= len(s)
1649 limit -= len(s)
1647 yield s
1650 yield s
1648
1651
1649 def makedate(timestamp=None):
1652 def makedate(timestamp=None):
1650 '''Return a unix timestamp (or the current time) as a (unixtime,
1653 '''Return a unix timestamp (or the current time) as a (unixtime,
1651 offset) tuple based off the local timezone.'''
1654 offset) tuple based off the local timezone.'''
1652 if timestamp is None:
1655 if timestamp is None:
1653 timestamp = time.time()
1656 timestamp = time.time()
1654 if timestamp < 0:
1657 if timestamp < 0:
1655 hint = _("check your clock")
1658 hint = _("check your clock")
1656 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1659 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1657 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1660 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1658 datetime.datetime.fromtimestamp(timestamp))
1661 datetime.datetime.fromtimestamp(timestamp))
1659 tz = delta.days * 86400 + delta.seconds
1662 tz = delta.days * 86400 + delta.seconds
1660 return timestamp, tz
1663 return timestamp, tz
1661
1664
1662 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1665 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1663 """represent a (unixtime, offset) tuple as a localized time.
1666 """represent a (unixtime, offset) tuple as a localized time.
1664 unixtime is seconds since the epoch, and offset is the time zone's
1667 unixtime is seconds since the epoch, and offset is the time zone's
1665 number of seconds away from UTC.
1668 number of seconds away from UTC.
1666
1669
1667 >>> datestr((0, 0))
1670 >>> datestr((0, 0))
1668 'Thu Jan 01 00:00:00 1970 +0000'
1671 'Thu Jan 01 00:00:00 1970 +0000'
1669 >>> datestr((42, 0))
1672 >>> datestr((42, 0))
1670 'Thu Jan 01 00:00:42 1970 +0000'
1673 'Thu Jan 01 00:00:42 1970 +0000'
1671 >>> datestr((-42, 0))
1674 >>> datestr((-42, 0))
1672 'Wed Dec 31 23:59:18 1969 +0000'
1675 'Wed Dec 31 23:59:18 1969 +0000'
1673 >>> datestr((0x7fffffff, 0))
1676 >>> datestr((0x7fffffff, 0))
1674 'Tue Jan 19 03:14:07 2038 +0000'
1677 'Tue Jan 19 03:14:07 2038 +0000'
1675 >>> datestr((-0x80000000, 0))
1678 >>> datestr((-0x80000000, 0))
1676 'Fri Dec 13 20:45:52 1901 +0000'
1679 'Fri Dec 13 20:45:52 1901 +0000'
1677 """
1680 """
1678 t, tz = date or makedate()
1681 t, tz = date or makedate()
1679 if "%1" in format or "%2" in format or "%z" in format:
1682 if "%1" in format or "%2" in format or "%z" in format:
1680 sign = (tz > 0) and "-" or "+"
1683 sign = (tz > 0) and "-" or "+"
1681 minutes = abs(tz) // 60
1684 minutes = abs(tz) // 60
1682 q, r = divmod(minutes, 60)
1685 q, r = divmod(minutes, 60)
1683 format = format.replace("%z", "%1%2")
1686 format = format.replace("%z", "%1%2")
1684 format = format.replace("%1", "%c%02d" % (sign, q))
1687 format = format.replace("%1", "%c%02d" % (sign, q))
1685 format = format.replace("%2", "%02d" % r)
1688 format = format.replace("%2", "%02d" % r)
1686 d = t - tz
1689 d = t - tz
1687 if d > 0x7fffffff:
1690 if d > 0x7fffffff:
1688 d = 0x7fffffff
1691 d = 0x7fffffff
1689 elif d < -0x80000000:
1692 elif d < -0x80000000:
1690 d = -0x80000000
1693 d = -0x80000000
1691 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1694 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1692 # because they use the gmtime() system call which is buggy on Windows
1695 # because they use the gmtime() system call which is buggy on Windows
1693 # for negative values.
1696 # for negative values.
1694 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1697 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1695 s = t.strftime(format)
1698 s = t.strftime(format)
1696 return s
1699 return s
1697
1700
1698 def shortdate(date=None):
1701 def shortdate(date=None):
1699 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1702 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1700 return datestr(date, format='%Y-%m-%d')
1703 return datestr(date, format='%Y-%m-%d')
1701
1704
1702 def parsetimezone(tz):
1705 def parsetimezone(tz):
1703 """parse a timezone string and return an offset integer"""
1706 """parse a timezone string and return an offset integer"""
1704 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1707 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1705 sign = (tz[0] == "+") and 1 or -1
1708 sign = (tz[0] == "+") and 1 or -1
1706 hours = int(tz[1:3])
1709 hours = int(tz[1:3])
1707 minutes = int(tz[3:5])
1710 minutes = int(tz[3:5])
1708 return -sign * (hours * 60 + minutes) * 60
1711 return -sign * (hours * 60 + minutes) * 60
1709 if tz == "GMT" or tz == "UTC":
1712 if tz == "GMT" or tz == "UTC":
1710 return 0
1713 return 0
1711 return None
1714 return None
1712
1715
1713 def strdate(string, format, defaults=[]):
1716 def strdate(string, format, defaults=[]):
1714 """parse a localized time string and return a (unixtime, offset) tuple.
1717 """parse a localized time string and return a (unixtime, offset) tuple.
1715 if the string cannot be parsed, ValueError is raised."""
1718 if the string cannot be parsed, ValueError is raised."""
1716 # NOTE: unixtime = localunixtime + offset
1719 # NOTE: unixtime = localunixtime + offset
1717 offset, date = parsetimezone(string.split()[-1]), string
1720 offset, date = parsetimezone(string.split()[-1]), string
1718 if offset is not None:
1721 if offset is not None:
1719 date = " ".join(string.split()[:-1])
1722 date = " ".join(string.split()[:-1])
1720
1723
1721 # add missing elements from defaults
1724 # add missing elements from defaults
1722 usenow = False # default to using biased defaults
1725 usenow = False # default to using biased defaults
1723 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1726 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1724 found = [True for p in part if ("%"+p) in format]
1727 found = [True for p in part if ("%"+p) in format]
1725 if not found:
1728 if not found:
1726 date += "@" + defaults[part][usenow]
1729 date += "@" + defaults[part][usenow]
1727 format += "@%" + part[0]
1730 format += "@%" + part[0]
1728 else:
1731 else:
1729 # We've found a specific time element, less specific time
1732 # We've found a specific time element, less specific time
1730 # elements are relative to today
1733 # elements are relative to today
1731 usenow = True
1734 usenow = True
1732
1735
1733 timetuple = time.strptime(date, format)
1736 timetuple = time.strptime(date, format)
1734 localunixtime = int(calendar.timegm(timetuple))
1737 localunixtime = int(calendar.timegm(timetuple))
1735 if offset is None:
1738 if offset is None:
1736 # local timezone
1739 # local timezone
1737 unixtime = int(time.mktime(timetuple))
1740 unixtime = int(time.mktime(timetuple))
1738 offset = unixtime - localunixtime
1741 offset = unixtime - localunixtime
1739 else:
1742 else:
1740 unixtime = localunixtime + offset
1743 unixtime = localunixtime + offset
1741 return unixtime, offset
1744 return unixtime, offset
1742
1745
1743 def parsedate(date, formats=None, bias=None):
1746 def parsedate(date, formats=None, bias=None):
1744 """parse a localized date/time and return a (unixtime, offset) tuple.
1747 """parse a localized date/time and return a (unixtime, offset) tuple.
1745
1748
1746 The date may be a "unixtime offset" string or in one of the specified
1749 The date may be a "unixtime offset" string or in one of the specified
1747 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1750 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1748
1751
1749 >>> parsedate(' today ') == parsedate(\
1752 >>> parsedate(' today ') == parsedate(\
1750 datetime.date.today().strftime('%b %d'))
1753 datetime.date.today().strftime('%b %d'))
1751 True
1754 True
1752 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1755 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1753 datetime.timedelta(days=1)\
1756 datetime.timedelta(days=1)\
1754 ).strftime('%b %d'))
1757 ).strftime('%b %d'))
1755 True
1758 True
1756 >>> now, tz = makedate()
1759 >>> now, tz = makedate()
1757 >>> strnow, strtz = parsedate('now')
1760 >>> strnow, strtz = parsedate('now')
1758 >>> (strnow - now) < 1
1761 >>> (strnow - now) < 1
1759 True
1762 True
1760 >>> tz == strtz
1763 >>> tz == strtz
1761 True
1764 True
1762 """
1765 """
1763 if bias is None:
1766 if bias is None:
1764 bias = {}
1767 bias = {}
1765 if not date:
1768 if not date:
1766 return 0, 0
1769 return 0, 0
1767 if isinstance(date, tuple) and len(date) == 2:
1770 if isinstance(date, tuple) and len(date) == 2:
1768 return date
1771 return date
1769 if not formats:
1772 if not formats:
1770 formats = defaultdateformats
1773 formats = defaultdateformats
1771 date = date.strip()
1774 date = date.strip()
1772
1775
1773 if date == 'now' or date == _('now'):
1776 if date == 'now' or date == _('now'):
1774 return makedate()
1777 return makedate()
1775 if date == 'today' or date == _('today'):
1778 if date == 'today' or date == _('today'):
1776 date = datetime.date.today().strftime('%b %d')
1779 date = datetime.date.today().strftime('%b %d')
1777 elif date == 'yesterday' or date == _('yesterday'):
1780 elif date == 'yesterday' or date == _('yesterday'):
1778 date = (datetime.date.today() -
1781 date = (datetime.date.today() -
1779 datetime.timedelta(days=1)).strftime('%b %d')
1782 datetime.timedelta(days=1)).strftime('%b %d')
1780
1783
1781 try:
1784 try:
1782 when, offset = map(int, date.split(' '))
1785 when, offset = map(int, date.split(' '))
1783 except ValueError:
1786 except ValueError:
1784 # fill out defaults
1787 # fill out defaults
1785 now = makedate()
1788 now = makedate()
1786 defaults = {}
1789 defaults = {}
1787 for part in ("d", "mb", "yY", "HI", "M", "S"):
1790 for part in ("d", "mb", "yY", "HI", "M", "S"):
1788 # this piece is for rounding the specific end of unknowns
1791 # this piece is for rounding the specific end of unknowns
1789 b = bias.get(part)
1792 b = bias.get(part)
1790 if b is None:
1793 if b is None:
1791 if part[0] in "HMS":
1794 if part[0] in "HMS":
1792 b = "00"
1795 b = "00"
1793 else:
1796 else:
1794 b = "0"
1797 b = "0"
1795
1798
1796 # this piece is for matching the generic end to today's date
1799 # this piece is for matching the generic end to today's date
1797 n = datestr(now, "%" + part[0])
1800 n = datestr(now, "%" + part[0])
1798
1801
1799 defaults[part] = (b, n)
1802 defaults[part] = (b, n)
1800
1803
1801 for format in formats:
1804 for format in formats:
1802 try:
1805 try:
1803 when, offset = strdate(date, format, defaults)
1806 when, offset = strdate(date, format, defaults)
1804 except (ValueError, OverflowError):
1807 except (ValueError, OverflowError):
1805 pass
1808 pass
1806 else:
1809 else:
1807 break
1810 break
1808 else:
1811 else:
1809 raise Abort(_('invalid date: %r') % date)
1812 raise Abort(_('invalid date: %r') % date)
1810 # validate explicit (probably user-specified) date and
1813 # validate explicit (probably user-specified) date and
1811 # time zone offset. values must fit in signed 32 bits for
1814 # time zone offset. values must fit in signed 32 bits for
1812 # current 32-bit linux runtimes. timezones go from UTC-12
1815 # current 32-bit linux runtimes. timezones go from UTC-12
1813 # to UTC+14
1816 # to UTC+14
1814 if when < -0x80000000 or when > 0x7fffffff:
1817 if when < -0x80000000 or when > 0x7fffffff:
1815 raise Abort(_('date exceeds 32 bits: %d') % when)
1818 raise Abort(_('date exceeds 32 bits: %d') % when)
1816 if offset < -50400 or offset > 43200:
1819 if offset < -50400 or offset > 43200:
1817 raise Abort(_('impossible time zone offset: %d') % offset)
1820 raise Abort(_('impossible time zone offset: %d') % offset)
1818 return when, offset
1821 return when, offset
1819
1822
1820 def matchdate(date):
1823 def matchdate(date):
1821 """Return a function that matches a given date match specifier
1824 """Return a function that matches a given date match specifier
1822
1825
1823 Formats include:
1826 Formats include:
1824
1827
1825 '{date}' match a given date to the accuracy provided
1828 '{date}' match a given date to the accuracy provided
1826
1829
1827 '<{date}' on or before a given date
1830 '<{date}' on or before a given date
1828
1831
1829 '>{date}' on or after a given date
1832 '>{date}' on or after a given date
1830
1833
1831 >>> p1 = parsedate("10:29:59")
1834 >>> p1 = parsedate("10:29:59")
1832 >>> p2 = parsedate("10:30:00")
1835 >>> p2 = parsedate("10:30:00")
1833 >>> p3 = parsedate("10:30:59")
1836 >>> p3 = parsedate("10:30:59")
1834 >>> p4 = parsedate("10:31:00")
1837 >>> p4 = parsedate("10:31:00")
1835 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1838 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1836 >>> f = matchdate("10:30")
1839 >>> f = matchdate("10:30")
1837 >>> f(p1[0])
1840 >>> f(p1[0])
1838 False
1841 False
1839 >>> f(p2[0])
1842 >>> f(p2[0])
1840 True
1843 True
1841 >>> f(p3[0])
1844 >>> f(p3[0])
1842 True
1845 True
1843 >>> f(p4[0])
1846 >>> f(p4[0])
1844 False
1847 False
1845 >>> f(p5[0])
1848 >>> f(p5[0])
1846 False
1849 False
1847 """
1850 """
1848
1851
1849 def lower(date):
1852 def lower(date):
1850 d = {'mb': "1", 'd': "1"}
1853 d = {'mb': "1", 'd': "1"}
1851 return parsedate(date, extendeddateformats, d)[0]
1854 return parsedate(date, extendeddateformats, d)[0]
1852
1855
1853 def upper(date):
1856 def upper(date):
1854 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1857 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1855 for days in ("31", "30", "29"):
1858 for days in ("31", "30", "29"):
1856 try:
1859 try:
1857 d["d"] = days
1860 d["d"] = days
1858 return parsedate(date, extendeddateformats, d)[0]
1861 return parsedate(date, extendeddateformats, d)[0]
1859 except Abort:
1862 except Abort:
1860 pass
1863 pass
1861 d["d"] = "28"
1864 d["d"] = "28"
1862 return parsedate(date, extendeddateformats, d)[0]
1865 return parsedate(date, extendeddateformats, d)[0]
1863
1866
1864 date = date.strip()
1867 date = date.strip()
1865
1868
1866 if not date:
1869 if not date:
1867 raise Abort(_("dates cannot consist entirely of whitespace"))
1870 raise Abort(_("dates cannot consist entirely of whitespace"))
1868 elif date[0] == "<":
1871 elif date[0] == "<":
1869 if not date[1:]:
1872 if not date[1:]:
1870 raise Abort(_("invalid day spec, use '<DATE'"))
1873 raise Abort(_("invalid day spec, use '<DATE'"))
1871 when = upper(date[1:])
1874 when = upper(date[1:])
1872 return lambda x: x <= when
1875 return lambda x: x <= when
1873 elif date[0] == ">":
1876 elif date[0] == ">":
1874 if not date[1:]:
1877 if not date[1:]:
1875 raise Abort(_("invalid day spec, use '>DATE'"))
1878 raise Abort(_("invalid day spec, use '>DATE'"))
1876 when = lower(date[1:])
1879 when = lower(date[1:])
1877 return lambda x: x >= when
1880 return lambda x: x >= when
1878 elif date[0] == "-":
1881 elif date[0] == "-":
1879 try:
1882 try:
1880 days = int(date[1:])
1883 days = int(date[1:])
1881 except ValueError:
1884 except ValueError:
1882 raise Abort(_("invalid day spec: %s") % date[1:])
1885 raise Abort(_("invalid day spec: %s") % date[1:])
1883 if days < 0:
1886 if days < 0:
1884 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1887 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1885 % date[1:])
1888 % date[1:])
1886 when = makedate()[0] - days * 3600 * 24
1889 when = makedate()[0] - days * 3600 * 24
1887 return lambda x: x >= when
1890 return lambda x: x >= when
1888 elif " to " in date:
1891 elif " to " in date:
1889 a, b = date.split(" to ")
1892 a, b = date.split(" to ")
1890 start, stop = lower(a), upper(b)
1893 start, stop = lower(a), upper(b)
1891 return lambda x: x >= start and x <= stop
1894 return lambda x: x >= start and x <= stop
1892 else:
1895 else:
1893 start, stop = lower(date), upper(date)
1896 start, stop = lower(date), upper(date)
1894 return lambda x: x >= start and x <= stop
1897 return lambda x: x >= start and x <= stop
1895
1898
1896 def stringmatcher(pattern):
1899 def stringmatcher(pattern):
1897 """
1900 """
1898 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1901 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1899 returns the matcher name, pattern, and matcher function.
1902 returns the matcher name, pattern, and matcher function.
1900 missing or unknown prefixes are treated as literal matches.
1903 missing or unknown prefixes are treated as literal matches.
1901
1904
1902 helper for tests:
1905 helper for tests:
1903 >>> def test(pattern, *tests):
1906 >>> def test(pattern, *tests):
1904 ... kind, pattern, matcher = stringmatcher(pattern)
1907 ... kind, pattern, matcher = stringmatcher(pattern)
1905 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1908 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1906
1909
1907 exact matching (no prefix):
1910 exact matching (no prefix):
1908 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1911 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1909 ('literal', 'abcdefg', [False, False, True])
1912 ('literal', 'abcdefg', [False, False, True])
1910
1913
1911 regex matching ('re:' prefix)
1914 regex matching ('re:' prefix)
1912 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1915 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1913 ('re', 'a.+b', [False, False, True])
1916 ('re', 'a.+b', [False, False, True])
1914
1917
1915 force exact matches ('literal:' prefix)
1918 force exact matches ('literal:' prefix)
1916 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1919 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1917 ('literal', 're:foobar', [False, True])
1920 ('literal', 're:foobar', [False, True])
1918
1921
1919 unknown prefixes are ignored and treated as literals
1922 unknown prefixes are ignored and treated as literals
1920 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1923 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1921 ('literal', 'foo:bar', [False, False, True])
1924 ('literal', 'foo:bar', [False, False, True])
1922 """
1925 """
1923 if pattern.startswith('re:'):
1926 if pattern.startswith('re:'):
1924 pattern = pattern[3:]
1927 pattern = pattern[3:]
1925 try:
1928 try:
1926 regex = remod.compile(pattern)
1929 regex = remod.compile(pattern)
1927 except remod.error as e:
1930 except remod.error as e:
1928 raise error.ParseError(_('invalid regular expression: %s')
1931 raise error.ParseError(_('invalid regular expression: %s')
1929 % e)
1932 % e)
1930 return 're', pattern, regex.search
1933 return 're', pattern, regex.search
1931 elif pattern.startswith('literal:'):
1934 elif pattern.startswith('literal:'):
1932 pattern = pattern[8:]
1935 pattern = pattern[8:]
1933 return 'literal', pattern, pattern.__eq__
1936 return 'literal', pattern, pattern.__eq__
1934
1937
1935 def shortuser(user):
1938 def shortuser(user):
1936 """Return a short representation of a user name or email address."""
1939 """Return a short representation of a user name or email address."""
1937 f = user.find('@')
1940 f = user.find('@')
1938 if f >= 0:
1941 if f >= 0:
1939 user = user[:f]
1942 user = user[:f]
1940 f = user.find('<')
1943 f = user.find('<')
1941 if f >= 0:
1944 if f >= 0:
1942 user = user[f + 1:]
1945 user = user[f + 1:]
1943 f = user.find(' ')
1946 f = user.find(' ')
1944 if f >= 0:
1947 if f >= 0:
1945 user = user[:f]
1948 user = user[:f]
1946 f = user.find('.')
1949 f = user.find('.')
1947 if f >= 0:
1950 if f >= 0:
1948 user = user[:f]
1951 user = user[:f]
1949 return user
1952 return user
1950
1953
1951 def emailuser(user):
1954 def emailuser(user):
1952 """Return the user portion of an email address."""
1955 """Return the user portion of an email address."""
1953 f = user.find('@')
1956 f = user.find('@')
1954 if f >= 0:
1957 if f >= 0:
1955 user = user[:f]
1958 user = user[:f]
1956 f = user.find('<')
1959 f = user.find('<')
1957 if f >= 0:
1960 if f >= 0:
1958 user = user[f + 1:]
1961 user = user[f + 1:]
1959 return user
1962 return user
1960
1963
1961 def email(author):
1964 def email(author):
1962 '''get email of author.'''
1965 '''get email of author.'''
1963 r = author.find('>')
1966 r = author.find('>')
1964 if r == -1:
1967 if r == -1:
1965 r = None
1968 r = None
1966 return author[author.find('<') + 1:r]
1969 return author[author.find('<') + 1:r]
1967
1970
1968 def ellipsis(text, maxlength=400):
1971 def ellipsis(text, maxlength=400):
1969 """Trim string to at most maxlength (default: 400) columns in display."""
1972 """Trim string to at most maxlength (default: 400) columns in display."""
1970 return encoding.trim(text, maxlength, ellipsis='...')
1973 return encoding.trim(text, maxlength, ellipsis='...')
1971
1974
1972 def unitcountfn(*unittable):
1975 def unitcountfn(*unittable):
1973 '''return a function that renders a readable count of some quantity'''
1976 '''return a function that renders a readable count of some quantity'''
1974
1977
1975 def go(count):
1978 def go(count):
1976 for multiplier, divisor, format in unittable:
1979 for multiplier, divisor, format in unittable:
1977 if count >= divisor * multiplier:
1980 if count >= divisor * multiplier:
1978 return format % (count / float(divisor))
1981 return format % (count / float(divisor))
1979 return unittable[-1][2] % count
1982 return unittable[-1][2] % count
1980
1983
1981 return go
1984 return go
1982
1985
1983 bytecount = unitcountfn(
1986 bytecount = unitcountfn(
1984 (100, 1 << 30, _('%.0f GB')),
1987 (100, 1 << 30, _('%.0f GB')),
1985 (10, 1 << 30, _('%.1f GB')),
1988 (10, 1 << 30, _('%.1f GB')),
1986 (1, 1 << 30, _('%.2f GB')),
1989 (1, 1 << 30, _('%.2f GB')),
1987 (100, 1 << 20, _('%.0f MB')),
1990 (100, 1 << 20, _('%.0f MB')),
1988 (10, 1 << 20, _('%.1f MB')),
1991 (10, 1 << 20, _('%.1f MB')),
1989 (1, 1 << 20, _('%.2f MB')),
1992 (1, 1 << 20, _('%.2f MB')),
1990 (100, 1 << 10, _('%.0f KB')),
1993 (100, 1 << 10, _('%.0f KB')),
1991 (10, 1 << 10, _('%.1f KB')),
1994 (10, 1 << 10, _('%.1f KB')),
1992 (1, 1 << 10, _('%.2f KB')),
1995 (1, 1 << 10, _('%.2f KB')),
1993 (1, 1, _('%.0f bytes')),
1996 (1, 1, _('%.0f bytes')),
1994 )
1997 )
1995
1998
1996 def uirepr(s):
1999 def uirepr(s):
1997 # Avoid double backslash in Windows path repr()
2000 # Avoid double backslash in Windows path repr()
1998 return repr(s).replace('\\\\', '\\')
2001 return repr(s).replace('\\\\', '\\')
1999
2002
2000 # delay import of textwrap
2003 # delay import of textwrap
2001 def MBTextWrapper(**kwargs):
2004 def MBTextWrapper(**kwargs):
2002 class tw(textwrap.TextWrapper):
2005 class tw(textwrap.TextWrapper):
2003 """
2006 """
2004 Extend TextWrapper for width-awareness.
2007 Extend TextWrapper for width-awareness.
2005
2008
2006 Neither number of 'bytes' in any encoding nor 'characters' is
2009 Neither number of 'bytes' in any encoding nor 'characters' is
2007 appropriate to calculate terminal columns for specified string.
2010 appropriate to calculate terminal columns for specified string.
2008
2011
2009 Original TextWrapper implementation uses built-in 'len()' directly,
2012 Original TextWrapper implementation uses built-in 'len()' directly,
2010 so overriding is needed to use width information of each characters.
2013 so overriding is needed to use width information of each characters.
2011
2014
2012 In addition, characters classified into 'ambiguous' width are
2015 In addition, characters classified into 'ambiguous' width are
2013 treated as wide in East Asian area, but as narrow in other.
2016 treated as wide in East Asian area, but as narrow in other.
2014
2017
2015 This requires use decision to determine width of such characters.
2018 This requires use decision to determine width of such characters.
2016 """
2019 """
2017 def _cutdown(self, ucstr, space_left):
2020 def _cutdown(self, ucstr, space_left):
2018 l = 0
2021 l = 0
2019 colwidth = encoding.ucolwidth
2022 colwidth = encoding.ucolwidth
2020 for i in xrange(len(ucstr)):
2023 for i in xrange(len(ucstr)):
2021 l += colwidth(ucstr[i])
2024 l += colwidth(ucstr[i])
2022 if space_left < l:
2025 if space_left < l:
2023 return (ucstr[:i], ucstr[i:])
2026 return (ucstr[:i], ucstr[i:])
2024 return ucstr, ''
2027 return ucstr, ''
2025
2028
2026 # overriding of base class
2029 # overriding of base class
2027 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2030 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2028 space_left = max(width - cur_len, 1)
2031 space_left = max(width - cur_len, 1)
2029
2032
2030 if self.break_long_words:
2033 if self.break_long_words:
2031 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2034 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2032 cur_line.append(cut)
2035 cur_line.append(cut)
2033 reversed_chunks[-1] = res
2036 reversed_chunks[-1] = res
2034 elif not cur_line:
2037 elif not cur_line:
2035 cur_line.append(reversed_chunks.pop())
2038 cur_line.append(reversed_chunks.pop())
2036
2039
2037 # this overriding code is imported from TextWrapper of Python 2.6
2040 # this overriding code is imported from TextWrapper of Python 2.6
2038 # to calculate columns of string by 'encoding.ucolwidth()'
2041 # to calculate columns of string by 'encoding.ucolwidth()'
2039 def _wrap_chunks(self, chunks):
2042 def _wrap_chunks(self, chunks):
2040 colwidth = encoding.ucolwidth
2043 colwidth = encoding.ucolwidth
2041
2044
2042 lines = []
2045 lines = []
2043 if self.width <= 0:
2046 if self.width <= 0:
2044 raise ValueError("invalid width %r (must be > 0)" % self.width)
2047 raise ValueError("invalid width %r (must be > 0)" % self.width)
2045
2048
2046 # Arrange in reverse order so items can be efficiently popped
2049 # Arrange in reverse order so items can be efficiently popped
2047 # from a stack of chucks.
2050 # from a stack of chucks.
2048 chunks.reverse()
2051 chunks.reverse()
2049
2052
2050 while chunks:
2053 while chunks:
2051
2054
2052 # Start the list of chunks that will make up the current line.
2055 # Start the list of chunks that will make up the current line.
2053 # cur_len is just the length of all the chunks in cur_line.
2056 # cur_len is just the length of all the chunks in cur_line.
2054 cur_line = []
2057 cur_line = []
2055 cur_len = 0
2058 cur_len = 0
2056
2059
2057 # Figure out which static string will prefix this line.
2060 # Figure out which static string will prefix this line.
2058 if lines:
2061 if lines:
2059 indent = self.subsequent_indent
2062 indent = self.subsequent_indent
2060 else:
2063 else:
2061 indent = self.initial_indent
2064 indent = self.initial_indent
2062
2065
2063 # Maximum width for this line.
2066 # Maximum width for this line.
2064 width = self.width - len(indent)
2067 width = self.width - len(indent)
2065
2068
2066 # First chunk on line is whitespace -- drop it, unless this
2069 # First chunk on line is whitespace -- drop it, unless this
2067 # is the very beginning of the text (i.e. no lines started yet).
2070 # is the very beginning of the text (i.e. no lines started yet).
2068 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2071 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2069 del chunks[-1]
2072 del chunks[-1]
2070
2073
2071 while chunks:
2074 while chunks:
2072 l = colwidth(chunks[-1])
2075 l = colwidth(chunks[-1])
2073
2076
2074 # Can at least squeeze this chunk onto the current line.
2077 # Can at least squeeze this chunk onto the current line.
2075 if cur_len + l <= width:
2078 if cur_len + l <= width:
2076 cur_line.append(chunks.pop())
2079 cur_line.append(chunks.pop())
2077 cur_len += l
2080 cur_len += l
2078
2081
2079 # Nope, this line is full.
2082 # Nope, this line is full.
2080 else:
2083 else:
2081 break
2084 break
2082
2085
2083 # The current line is full, and the next chunk is too big to
2086 # The current line is full, and the next chunk is too big to
2084 # fit on *any* line (not just this one).
2087 # fit on *any* line (not just this one).
2085 if chunks and colwidth(chunks[-1]) > width:
2088 if chunks and colwidth(chunks[-1]) > width:
2086 self._handle_long_word(chunks, cur_line, cur_len, width)
2089 self._handle_long_word(chunks, cur_line, cur_len, width)
2087
2090
2088 # If the last chunk on this line is all whitespace, drop it.
2091 # If the last chunk on this line is all whitespace, drop it.
2089 if (self.drop_whitespace and
2092 if (self.drop_whitespace and
2090 cur_line and cur_line[-1].strip() == ''):
2093 cur_line and cur_line[-1].strip() == ''):
2091 del cur_line[-1]
2094 del cur_line[-1]
2092
2095
2093 # Convert current line back to a string and store it in list
2096 # Convert current line back to a string and store it in list
2094 # of all lines (return value).
2097 # of all lines (return value).
2095 if cur_line:
2098 if cur_line:
2096 lines.append(indent + ''.join(cur_line))
2099 lines.append(indent + ''.join(cur_line))
2097
2100
2098 return lines
2101 return lines
2099
2102
2100 global MBTextWrapper
2103 global MBTextWrapper
2101 MBTextWrapper = tw
2104 MBTextWrapper = tw
2102 return tw(**kwargs)
2105 return tw(**kwargs)
2103
2106
2104 def wrap(line, width, initindent='', hangindent=''):
2107 def wrap(line, width, initindent='', hangindent=''):
2105 maxindent = max(len(hangindent), len(initindent))
2108 maxindent = max(len(hangindent), len(initindent))
2106 if width <= maxindent:
2109 if width <= maxindent:
2107 # adjust for weird terminal size
2110 # adjust for weird terminal size
2108 width = max(78, maxindent + 1)
2111 width = max(78, maxindent + 1)
2109 line = line.decode(encoding.encoding, encoding.encodingmode)
2112 line = line.decode(encoding.encoding, encoding.encodingmode)
2110 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
2113 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
2111 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
2114 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
2112 wrapper = MBTextWrapper(width=width,
2115 wrapper = MBTextWrapper(width=width,
2113 initial_indent=initindent,
2116 initial_indent=initindent,
2114 subsequent_indent=hangindent)
2117 subsequent_indent=hangindent)
2115 return wrapper.fill(line).encode(encoding.encoding)
2118 return wrapper.fill(line).encode(encoding.encoding)
2116
2119
2117 def iterlines(iterator):
2120 def iterlines(iterator):
2118 for chunk in iterator:
2121 for chunk in iterator:
2119 for line in chunk.splitlines():
2122 for line in chunk.splitlines():
2120 yield line
2123 yield line
2121
2124
2122 def expandpath(path):
2125 def expandpath(path):
2123 return os.path.expanduser(os.path.expandvars(path))
2126 return os.path.expanduser(os.path.expandvars(path))
2124
2127
2125 def hgcmd():
2128 def hgcmd():
2126 """Return the command used to execute current hg
2129 """Return the command used to execute current hg
2127
2130
2128 This is different from hgexecutable() because on Windows we want
2131 This is different from hgexecutable() because on Windows we want
2129 to avoid things opening new shell windows like batch files, so we
2132 to avoid things opening new shell windows like batch files, so we
2130 get either the python call or current executable.
2133 get either the python call or current executable.
2131 """
2134 """
2132 if mainfrozen():
2135 if mainfrozen():
2133 if getattr(sys, 'frozen', None) == 'macosx_app':
2136 if getattr(sys, 'frozen', None) == 'macosx_app':
2134 # Env variable set by py2app
2137 # Env variable set by py2app
2135 return [os.environ['EXECUTABLEPATH']]
2138 return [os.environ['EXECUTABLEPATH']]
2136 else:
2139 else:
2137 return [sys.executable]
2140 return [sys.executable]
2138 return gethgcmd()
2141 return gethgcmd()
2139
2142
2140 def rundetached(args, condfn):
2143 def rundetached(args, condfn):
2141 """Execute the argument list in a detached process.
2144 """Execute the argument list in a detached process.
2142
2145
2143 condfn is a callable which is called repeatedly and should return
2146 condfn is a callable which is called repeatedly and should return
2144 True once the child process is known to have started successfully.
2147 True once the child process is known to have started successfully.
2145 At this point, the child process PID is returned. If the child
2148 At this point, the child process PID is returned. If the child
2146 process fails to start or finishes before condfn() evaluates to
2149 process fails to start or finishes before condfn() evaluates to
2147 True, return -1.
2150 True, return -1.
2148 """
2151 """
2149 # Windows case is easier because the child process is either
2152 # Windows case is easier because the child process is either
2150 # successfully starting and validating the condition or exiting
2153 # successfully starting and validating the condition or exiting
2151 # on failure. We just poll on its PID. On Unix, if the child
2154 # on failure. We just poll on its PID. On Unix, if the child
2152 # process fails to start, it will be left in a zombie state until
2155 # process fails to start, it will be left in a zombie state until
2153 # the parent wait on it, which we cannot do since we expect a long
2156 # the parent wait on it, which we cannot do since we expect a long
2154 # running process on success. Instead we listen for SIGCHLD telling
2157 # running process on success. Instead we listen for SIGCHLD telling
2155 # us our child process terminated.
2158 # us our child process terminated.
2156 terminated = set()
2159 terminated = set()
2157 def handler(signum, frame):
2160 def handler(signum, frame):
2158 terminated.add(os.wait())
2161 terminated.add(os.wait())
2159 prevhandler = None
2162 prevhandler = None
2160 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2163 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2161 if SIGCHLD is not None:
2164 if SIGCHLD is not None:
2162 prevhandler = signal.signal(SIGCHLD, handler)
2165 prevhandler = signal.signal(SIGCHLD, handler)
2163 try:
2166 try:
2164 pid = spawndetached(args)
2167 pid = spawndetached(args)
2165 while not condfn():
2168 while not condfn():
2166 if ((pid in terminated or not testpid(pid))
2169 if ((pid in terminated or not testpid(pid))
2167 and not condfn()):
2170 and not condfn()):
2168 return -1
2171 return -1
2169 time.sleep(0.1)
2172 time.sleep(0.1)
2170 return pid
2173 return pid
2171 finally:
2174 finally:
2172 if prevhandler is not None:
2175 if prevhandler is not None:
2173 signal.signal(signal.SIGCHLD, prevhandler)
2176 signal.signal(signal.SIGCHLD, prevhandler)
2174
2177
2175 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2178 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2176 """Return the result of interpolating items in the mapping into string s.
2179 """Return the result of interpolating items in the mapping into string s.
2177
2180
2178 prefix is a single character string, or a two character string with
2181 prefix is a single character string, or a two character string with
2179 a backslash as the first character if the prefix needs to be escaped in
2182 a backslash as the first character if the prefix needs to be escaped in
2180 a regular expression.
2183 a regular expression.
2181
2184
2182 fn is an optional function that will be applied to the replacement text
2185 fn is an optional function that will be applied to the replacement text
2183 just before replacement.
2186 just before replacement.
2184
2187
2185 escape_prefix is an optional flag that allows using doubled prefix for
2188 escape_prefix is an optional flag that allows using doubled prefix for
2186 its escaping.
2189 its escaping.
2187 """
2190 """
2188 fn = fn or (lambda s: s)
2191 fn = fn or (lambda s: s)
2189 patterns = '|'.join(mapping.keys())
2192 patterns = '|'.join(mapping.keys())
2190 if escape_prefix:
2193 if escape_prefix:
2191 patterns += '|' + prefix
2194 patterns += '|' + prefix
2192 if len(prefix) > 1:
2195 if len(prefix) > 1:
2193 prefix_char = prefix[1:]
2196 prefix_char = prefix[1:]
2194 else:
2197 else:
2195 prefix_char = prefix
2198 prefix_char = prefix
2196 mapping[prefix_char] = prefix_char
2199 mapping[prefix_char] = prefix_char
2197 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2200 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2198 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2201 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2199
2202
2200 def getport(port):
2203 def getport(port):
2201 """Return the port for a given network service.
2204 """Return the port for a given network service.
2202
2205
2203 If port is an integer, it's returned as is. If it's a string, it's
2206 If port is an integer, it's returned as is. If it's a string, it's
2204 looked up using socket.getservbyname(). If there's no matching
2207 looked up using socket.getservbyname(). If there's no matching
2205 service, error.Abort is raised.
2208 service, error.Abort is raised.
2206 """
2209 """
2207 try:
2210 try:
2208 return int(port)
2211 return int(port)
2209 except ValueError:
2212 except ValueError:
2210 pass
2213 pass
2211
2214
2212 try:
2215 try:
2213 return socket.getservbyname(port)
2216 return socket.getservbyname(port)
2214 except socket.error:
2217 except socket.error:
2215 raise Abort(_("no port number associated with service '%s'") % port)
2218 raise Abort(_("no port number associated with service '%s'") % port)
2216
2219
2217 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2220 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2218 '0': False, 'no': False, 'false': False, 'off': False,
2221 '0': False, 'no': False, 'false': False, 'off': False,
2219 'never': False}
2222 'never': False}
2220
2223
2221 def parsebool(s):
2224 def parsebool(s):
2222 """Parse s into a boolean.
2225 """Parse s into a boolean.
2223
2226
2224 If s is not a valid boolean, returns None.
2227 If s is not a valid boolean, returns None.
2225 """
2228 """
2226 return _booleans.get(s.lower(), None)
2229 return _booleans.get(s.lower(), None)
2227
2230
2228 _hexdig = '0123456789ABCDEFabcdef'
2231 _hexdig = '0123456789ABCDEFabcdef'
2229 _hextochr = dict((a + b, chr(int(a + b, 16)))
2232 _hextochr = dict((a + b, chr(int(a + b, 16)))
2230 for a in _hexdig for b in _hexdig)
2233 for a in _hexdig for b in _hexdig)
2231
2234
2232 def _urlunquote(s):
2235 def _urlunquote(s):
2233 """Decode HTTP/HTML % encoding.
2236 """Decode HTTP/HTML % encoding.
2234
2237
2235 >>> _urlunquote('abc%20def')
2238 >>> _urlunquote('abc%20def')
2236 'abc def'
2239 'abc def'
2237 """
2240 """
2238 res = s.split('%')
2241 res = s.split('%')
2239 # fastpath
2242 # fastpath
2240 if len(res) == 1:
2243 if len(res) == 1:
2241 return s
2244 return s
2242 s = res[0]
2245 s = res[0]
2243 for item in res[1:]:
2246 for item in res[1:]:
2244 try:
2247 try:
2245 s += _hextochr[item[:2]] + item[2:]
2248 s += _hextochr[item[:2]] + item[2:]
2246 except KeyError:
2249 except KeyError:
2247 s += '%' + item
2250 s += '%' + item
2248 except UnicodeDecodeError:
2251 except UnicodeDecodeError:
2249 s += unichr(int(item[:2], 16)) + item[2:]
2252 s += unichr(int(item[:2], 16)) + item[2:]
2250 return s
2253 return s
2251
2254
2252 class url(object):
2255 class url(object):
2253 r"""Reliable URL parser.
2256 r"""Reliable URL parser.
2254
2257
2255 This parses URLs and provides attributes for the following
2258 This parses URLs and provides attributes for the following
2256 components:
2259 components:
2257
2260
2258 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2261 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2259
2262
2260 Missing components are set to None. The only exception is
2263 Missing components are set to None. The only exception is
2261 fragment, which is set to '' if present but empty.
2264 fragment, which is set to '' if present but empty.
2262
2265
2263 If parsefragment is False, fragment is included in query. If
2266 If parsefragment is False, fragment is included in query. If
2264 parsequery is False, query is included in path. If both are
2267 parsequery is False, query is included in path. If both are
2265 False, both fragment and query are included in path.
2268 False, both fragment and query are included in path.
2266
2269
2267 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2270 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2268
2271
2269 Note that for backward compatibility reasons, bundle URLs do not
2272 Note that for backward compatibility reasons, bundle URLs do not
2270 take host names. That means 'bundle://../' has a path of '../'.
2273 take host names. That means 'bundle://../' has a path of '../'.
2271
2274
2272 Examples:
2275 Examples:
2273
2276
2274 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2277 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2275 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2278 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2276 >>> url('ssh://[::1]:2200//home/joe/repo')
2279 >>> url('ssh://[::1]:2200//home/joe/repo')
2277 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2280 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2278 >>> url('file:///home/joe/repo')
2281 >>> url('file:///home/joe/repo')
2279 <url scheme: 'file', path: '/home/joe/repo'>
2282 <url scheme: 'file', path: '/home/joe/repo'>
2280 >>> url('file:///c:/temp/foo/')
2283 >>> url('file:///c:/temp/foo/')
2281 <url scheme: 'file', path: 'c:/temp/foo/'>
2284 <url scheme: 'file', path: 'c:/temp/foo/'>
2282 >>> url('bundle:foo')
2285 >>> url('bundle:foo')
2283 <url scheme: 'bundle', path: 'foo'>
2286 <url scheme: 'bundle', path: 'foo'>
2284 >>> url('bundle://../foo')
2287 >>> url('bundle://../foo')
2285 <url scheme: 'bundle', path: '../foo'>
2288 <url scheme: 'bundle', path: '../foo'>
2286 >>> url(r'c:\foo\bar')
2289 >>> url(r'c:\foo\bar')
2287 <url path: 'c:\\foo\\bar'>
2290 <url path: 'c:\\foo\\bar'>
2288 >>> url(r'\\blah\blah\blah')
2291 >>> url(r'\\blah\blah\blah')
2289 <url path: '\\\\blah\\blah\\blah'>
2292 <url path: '\\\\blah\\blah\\blah'>
2290 >>> url(r'\\blah\blah\blah#baz')
2293 >>> url(r'\\blah\blah\blah#baz')
2291 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2294 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2292 >>> url(r'file:///C:\users\me')
2295 >>> url(r'file:///C:\users\me')
2293 <url scheme: 'file', path: 'C:\\users\\me'>
2296 <url scheme: 'file', path: 'C:\\users\\me'>
2294
2297
2295 Authentication credentials:
2298 Authentication credentials:
2296
2299
2297 >>> url('ssh://joe:xyz@x/repo')
2300 >>> url('ssh://joe:xyz@x/repo')
2298 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2301 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2299 >>> url('ssh://joe@x/repo')
2302 >>> url('ssh://joe@x/repo')
2300 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2303 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2301
2304
2302 Query strings and fragments:
2305 Query strings and fragments:
2303
2306
2304 >>> url('http://host/a?b#c')
2307 >>> url('http://host/a?b#c')
2305 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2308 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2306 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2309 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2307 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2310 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2308 """
2311 """
2309
2312
2310 _safechars = "!~*'()+"
2313 _safechars = "!~*'()+"
2311 _safepchars = "/!~*'()+:\\"
2314 _safepchars = "/!~*'()+:\\"
2312 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2315 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2313
2316
2314 def __init__(self, path, parsequery=True, parsefragment=True):
2317 def __init__(self, path, parsequery=True, parsefragment=True):
2315 # We slowly chomp away at path until we have only the path left
2318 # We slowly chomp away at path until we have only the path left
2316 self.scheme = self.user = self.passwd = self.host = None
2319 self.scheme = self.user = self.passwd = self.host = None
2317 self.port = self.path = self.query = self.fragment = None
2320 self.port = self.path = self.query = self.fragment = None
2318 self._localpath = True
2321 self._localpath = True
2319 self._hostport = ''
2322 self._hostport = ''
2320 self._origpath = path
2323 self._origpath = path
2321
2324
2322 if parsefragment and '#' in path:
2325 if parsefragment and '#' in path:
2323 path, self.fragment = path.split('#', 1)
2326 path, self.fragment = path.split('#', 1)
2324 if not path:
2327 if not path:
2325 path = None
2328 path = None
2326
2329
2327 # special case for Windows drive letters and UNC paths
2330 # special case for Windows drive letters and UNC paths
2328 if hasdriveletter(path) or path.startswith(r'\\'):
2331 if hasdriveletter(path) or path.startswith(r'\\'):
2329 self.path = path
2332 self.path = path
2330 return
2333 return
2331
2334
2332 # For compatibility reasons, we can't handle bundle paths as
2335 # For compatibility reasons, we can't handle bundle paths as
2333 # normal URLS
2336 # normal URLS
2334 if path.startswith('bundle:'):
2337 if path.startswith('bundle:'):
2335 self.scheme = 'bundle'
2338 self.scheme = 'bundle'
2336 path = path[7:]
2339 path = path[7:]
2337 if path.startswith('//'):
2340 if path.startswith('//'):
2338 path = path[2:]
2341 path = path[2:]
2339 self.path = path
2342 self.path = path
2340 return
2343 return
2341
2344
2342 if self._matchscheme(path):
2345 if self._matchscheme(path):
2343 parts = path.split(':', 1)
2346 parts = path.split(':', 1)
2344 if parts[0]:
2347 if parts[0]:
2345 self.scheme, path = parts
2348 self.scheme, path = parts
2346 self._localpath = False
2349 self._localpath = False
2347
2350
2348 if not path:
2351 if not path:
2349 path = None
2352 path = None
2350 if self._localpath:
2353 if self._localpath:
2351 self.path = ''
2354 self.path = ''
2352 return
2355 return
2353 else:
2356 else:
2354 if self._localpath:
2357 if self._localpath:
2355 self.path = path
2358 self.path = path
2356 return
2359 return
2357
2360
2358 if parsequery and '?' in path:
2361 if parsequery and '?' in path:
2359 path, self.query = path.split('?', 1)
2362 path, self.query = path.split('?', 1)
2360 if not path:
2363 if not path:
2361 path = None
2364 path = None
2362 if not self.query:
2365 if not self.query:
2363 self.query = None
2366 self.query = None
2364
2367
2365 # // is required to specify a host/authority
2368 # // is required to specify a host/authority
2366 if path and path.startswith('//'):
2369 if path and path.startswith('//'):
2367 parts = path[2:].split('/', 1)
2370 parts = path[2:].split('/', 1)
2368 if len(parts) > 1:
2371 if len(parts) > 1:
2369 self.host, path = parts
2372 self.host, path = parts
2370 else:
2373 else:
2371 self.host = parts[0]
2374 self.host = parts[0]
2372 path = None
2375 path = None
2373 if not self.host:
2376 if not self.host:
2374 self.host = None
2377 self.host = None
2375 # path of file:///d is /d
2378 # path of file:///d is /d
2376 # path of file:///d:/ is d:/, not /d:/
2379 # path of file:///d:/ is d:/, not /d:/
2377 if path and not hasdriveletter(path):
2380 if path and not hasdriveletter(path):
2378 path = '/' + path
2381 path = '/' + path
2379
2382
2380 if self.host and '@' in self.host:
2383 if self.host and '@' in self.host:
2381 self.user, self.host = self.host.rsplit('@', 1)
2384 self.user, self.host = self.host.rsplit('@', 1)
2382 if ':' in self.user:
2385 if ':' in self.user:
2383 self.user, self.passwd = self.user.split(':', 1)
2386 self.user, self.passwd = self.user.split(':', 1)
2384 if not self.host:
2387 if not self.host:
2385 self.host = None
2388 self.host = None
2386
2389
2387 # Don't split on colons in IPv6 addresses without ports
2390 # Don't split on colons in IPv6 addresses without ports
2388 if (self.host and ':' in self.host and
2391 if (self.host and ':' in self.host and
2389 not (self.host.startswith('[') and self.host.endswith(']'))):
2392 not (self.host.startswith('[') and self.host.endswith(']'))):
2390 self._hostport = self.host
2393 self._hostport = self.host
2391 self.host, self.port = self.host.rsplit(':', 1)
2394 self.host, self.port = self.host.rsplit(':', 1)
2392 if not self.host:
2395 if not self.host:
2393 self.host = None
2396 self.host = None
2394
2397
2395 if (self.host and self.scheme == 'file' and
2398 if (self.host and self.scheme == 'file' and
2396 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2399 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2397 raise Abort(_('file:// URLs can only refer to localhost'))
2400 raise Abort(_('file:// URLs can only refer to localhost'))
2398
2401
2399 self.path = path
2402 self.path = path
2400
2403
2401 # leave the query string escaped
2404 # leave the query string escaped
2402 for a in ('user', 'passwd', 'host', 'port',
2405 for a in ('user', 'passwd', 'host', 'port',
2403 'path', 'fragment'):
2406 'path', 'fragment'):
2404 v = getattr(self, a)
2407 v = getattr(self, a)
2405 if v is not None:
2408 if v is not None:
2406 setattr(self, a, _urlunquote(v))
2409 setattr(self, a, _urlunquote(v))
2407
2410
2408 def __repr__(self):
2411 def __repr__(self):
2409 attrs = []
2412 attrs = []
2410 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2413 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2411 'query', 'fragment'):
2414 'query', 'fragment'):
2412 v = getattr(self, a)
2415 v = getattr(self, a)
2413 if v is not None:
2416 if v is not None:
2414 attrs.append('%s: %r' % (a, v))
2417 attrs.append('%s: %r' % (a, v))
2415 return '<url %s>' % ', '.join(attrs)
2418 return '<url %s>' % ', '.join(attrs)
2416
2419
2417 def __str__(self):
2420 def __str__(self):
2418 r"""Join the URL's components back into a URL string.
2421 r"""Join the URL's components back into a URL string.
2419
2422
2420 Examples:
2423 Examples:
2421
2424
2422 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2425 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2423 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2426 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2424 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2427 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2425 'http://user:pw@host:80/?foo=bar&baz=42'
2428 'http://user:pw@host:80/?foo=bar&baz=42'
2426 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2429 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2427 'http://user:pw@host:80/?foo=bar%3dbaz'
2430 'http://user:pw@host:80/?foo=bar%3dbaz'
2428 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2431 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2429 'ssh://user:pw@[::1]:2200//home/joe#'
2432 'ssh://user:pw@[::1]:2200//home/joe#'
2430 >>> str(url('http://localhost:80//'))
2433 >>> str(url('http://localhost:80//'))
2431 'http://localhost:80//'
2434 'http://localhost:80//'
2432 >>> str(url('http://localhost:80/'))
2435 >>> str(url('http://localhost:80/'))
2433 'http://localhost:80/'
2436 'http://localhost:80/'
2434 >>> str(url('http://localhost:80'))
2437 >>> str(url('http://localhost:80'))
2435 'http://localhost:80/'
2438 'http://localhost:80/'
2436 >>> str(url('bundle:foo'))
2439 >>> str(url('bundle:foo'))
2437 'bundle:foo'
2440 'bundle:foo'
2438 >>> str(url('bundle://../foo'))
2441 >>> str(url('bundle://../foo'))
2439 'bundle:../foo'
2442 'bundle:../foo'
2440 >>> str(url('path'))
2443 >>> str(url('path'))
2441 'path'
2444 'path'
2442 >>> str(url('file:///tmp/foo/bar'))
2445 >>> str(url('file:///tmp/foo/bar'))
2443 'file:///tmp/foo/bar'
2446 'file:///tmp/foo/bar'
2444 >>> str(url('file:///c:/tmp/foo/bar'))
2447 >>> str(url('file:///c:/tmp/foo/bar'))
2445 'file:///c:/tmp/foo/bar'
2448 'file:///c:/tmp/foo/bar'
2446 >>> print url(r'bundle:foo\bar')
2449 >>> print url(r'bundle:foo\bar')
2447 bundle:foo\bar
2450 bundle:foo\bar
2448 >>> print url(r'file:///D:\data\hg')
2451 >>> print url(r'file:///D:\data\hg')
2449 file:///D:\data\hg
2452 file:///D:\data\hg
2450 """
2453 """
2451 if self._localpath:
2454 if self._localpath:
2452 s = self.path
2455 s = self.path
2453 if self.scheme == 'bundle':
2456 if self.scheme == 'bundle':
2454 s = 'bundle:' + s
2457 s = 'bundle:' + s
2455 if self.fragment:
2458 if self.fragment:
2456 s += '#' + self.fragment
2459 s += '#' + self.fragment
2457 return s
2460 return s
2458
2461
2459 s = self.scheme + ':'
2462 s = self.scheme + ':'
2460 if self.user or self.passwd or self.host:
2463 if self.user or self.passwd or self.host:
2461 s += '//'
2464 s += '//'
2462 elif self.scheme and (not self.path or self.path.startswith('/')
2465 elif self.scheme and (not self.path or self.path.startswith('/')
2463 or hasdriveletter(self.path)):
2466 or hasdriveletter(self.path)):
2464 s += '//'
2467 s += '//'
2465 if hasdriveletter(self.path):
2468 if hasdriveletter(self.path):
2466 s += '/'
2469 s += '/'
2467 if self.user:
2470 if self.user:
2468 s += urlreq.quote(self.user, safe=self._safechars)
2471 s += urlreq.quote(self.user, safe=self._safechars)
2469 if self.passwd:
2472 if self.passwd:
2470 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2473 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2471 if self.user or self.passwd:
2474 if self.user or self.passwd:
2472 s += '@'
2475 s += '@'
2473 if self.host:
2476 if self.host:
2474 if not (self.host.startswith('[') and self.host.endswith(']')):
2477 if not (self.host.startswith('[') and self.host.endswith(']')):
2475 s += urlreq.quote(self.host)
2478 s += urlreq.quote(self.host)
2476 else:
2479 else:
2477 s += self.host
2480 s += self.host
2478 if self.port:
2481 if self.port:
2479 s += ':' + urlreq.quote(self.port)
2482 s += ':' + urlreq.quote(self.port)
2480 if self.host:
2483 if self.host:
2481 s += '/'
2484 s += '/'
2482 if self.path:
2485 if self.path:
2483 # TODO: similar to the query string, we should not unescape the
2486 # TODO: similar to the query string, we should not unescape the
2484 # path when we store it, the path might contain '%2f' = '/',
2487 # path when we store it, the path might contain '%2f' = '/',
2485 # which we should *not* escape.
2488 # which we should *not* escape.
2486 s += urlreq.quote(self.path, safe=self._safepchars)
2489 s += urlreq.quote(self.path, safe=self._safepchars)
2487 if self.query:
2490 if self.query:
2488 # we store the query in escaped form.
2491 # we store the query in escaped form.
2489 s += '?' + self.query
2492 s += '?' + self.query
2490 if self.fragment is not None:
2493 if self.fragment is not None:
2491 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2494 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2492 return s
2495 return s
2493
2496
2494 def authinfo(self):
2497 def authinfo(self):
2495 user, passwd = self.user, self.passwd
2498 user, passwd = self.user, self.passwd
2496 try:
2499 try:
2497 self.user, self.passwd = None, None
2500 self.user, self.passwd = None, None
2498 s = str(self)
2501 s = str(self)
2499 finally:
2502 finally:
2500 self.user, self.passwd = user, passwd
2503 self.user, self.passwd = user, passwd
2501 if not self.user:
2504 if not self.user:
2502 return (s, None)
2505 return (s, None)
2503 # authinfo[1] is passed to urllib2 password manager, and its
2506 # authinfo[1] is passed to urllib2 password manager, and its
2504 # URIs must not contain credentials. The host is passed in the
2507 # URIs must not contain credentials. The host is passed in the
2505 # URIs list because Python < 2.4.3 uses only that to search for
2508 # URIs list because Python < 2.4.3 uses only that to search for
2506 # a password.
2509 # a password.
2507 return (s, (None, (s, self.host),
2510 return (s, (None, (s, self.host),
2508 self.user, self.passwd or ''))
2511 self.user, self.passwd or ''))
2509
2512
2510 def isabs(self):
2513 def isabs(self):
2511 if self.scheme and self.scheme != 'file':
2514 if self.scheme and self.scheme != 'file':
2512 return True # remote URL
2515 return True # remote URL
2513 if hasdriveletter(self.path):
2516 if hasdriveletter(self.path):
2514 return True # absolute for our purposes - can't be joined()
2517 return True # absolute for our purposes - can't be joined()
2515 if self.path.startswith(r'\\'):
2518 if self.path.startswith(r'\\'):
2516 return True # Windows UNC path
2519 return True # Windows UNC path
2517 if self.path.startswith('/'):
2520 if self.path.startswith('/'):
2518 return True # POSIX-style
2521 return True # POSIX-style
2519 return False
2522 return False
2520
2523
2521 def localpath(self):
2524 def localpath(self):
2522 if self.scheme == 'file' or self.scheme == 'bundle':
2525 if self.scheme == 'file' or self.scheme == 'bundle':
2523 path = self.path or '/'
2526 path = self.path or '/'
2524 # For Windows, we need to promote hosts containing drive
2527 # For Windows, we need to promote hosts containing drive
2525 # letters to paths with drive letters.
2528 # letters to paths with drive letters.
2526 if hasdriveletter(self._hostport):
2529 if hasdriveletter(self._hostport):
2527 path = self._hostport + '/' + self.path
2530 path = self._hostport + '/' + self.path
2528 elif (self.host is not None and self.path
2531 elif (self.host is not None and self.path
2529 and not hasdriveletter(path)):
2532 and not hasdriveletter(path)):
2530 path = '/' + path
2533 path = '/' + path
2531 return path
2534 return path
2532 return self._origpath
2535 return self._origpath
2533
2536
2534 def islocal(self):
2537 def islocal(self):
2535 '''whether localpath will return something that posixfile can open'''
2538 '''whether localpath will return something that posixfile can open'''
2536 return (not self.scheme or self.scheme == 'file'
2539 return (not self.scheme or self.scheme == 'file'
2537 or self.scheme == 'bundle')
2540 or self.scheme == 'bundle')
2538
2541
2539 def hasscheme(path):
2542 def hasscheme(path):
2540 return bool(url(path).scheme)
2543 return bool(url(path).scheme)
2541
2544
2542 def hasdriveletter(path):
2545 def hasdriveletter(path):
2543 return path and path[1:2] == ':' and path[0:1].isalpha()
2546 return path and path[1:2] == ':' and path[0:1].isalpha()
2544
2547
2545 def urllocalpath(path):
2548 def urllocalpath(path):
2546 return url(path, parsequery=False, parsefragment=False).localpath()
2549 return url(path, parsequery=False, parsefragment=False).localpath()
2547
2550
2548 def hidepassword(u):
2551 def hidepassword(u):
2549 '''hide user credential in a url string'''
2552 '''hide user credential in a url string'''
2550 u = url(u)
2553 u = url(u)
2551 if u.passwd:
2554 if u.passwd:
2552 u.passwd = '***'
2555 u.passwd = '***'
2553 return str(u)
2556 return str(u)
2554
2557
2555 def removeauth(u):
2558 def removeauth(u):
2556 '''remove all authentication information from a url string'''
2559 '''remove all authentication information from a url string'''
2557 u = url(u)
2560 u = url(u)
2558 u.user = u.passwd = None
2561 u.user = u.passwd = None
2559 return str(u)
2562 return str(u)
2560
2563
2561 def isatty(fp):
2564 def isatty(fp):
2562 try:
2565 try:
2563 return fp.isatty()
2566 return fp.isatty()
2564 except AttributeError:
2567 except AttributeError:
2565 return False
2568 return False
2566
2569
2567 timecount = unitcountfn(
2570 timecount = unitcountfn(
2568 (1, 1e3, _('%.0f s')),
2571 (1, 1e3, _('%.0f s')),
2569 (100, 1, _('%.1f s')),
2572 (100, 1, _('%.1f s')),
2570 (10, 1, _('%.2f s')),
2573 (10, 1, _('%.2f s')),
2571 (1, 1, _('%.3f s')),
2574 (1, 1, _('%.3f s')),
2572 (100, 0.001, _('%.1f ms')),
2575 (100, 0.001, _('%.1f ms')),
2573 (10, 0.001, _('%.2f ms')),
2576 (10, 0.001, _('%.2f ms')),
2574 (1, 0.001, _('%.3f ms')),
2577 (1, 0.001, _('%.3f ms')),
2575 (100, 0.000001, _('%.1f us')),
2578 (100, 0.000001, _('%.1f us')),
2576 (10, 0.000001, _('%.2f us')),
2579 (10, 0.000001, _('%.2f us')),
2577 (1, 0.000001, _('%.3f us')),
2580 (1, 0.000001, _('%.3f us')),
2578 (100, 0.000000001, _('%.1f ns')),
2581 (100, 0.000000001, _('%.1f ns')),
2579 (10, 0.000000001, _('%.2f ns')),
2582 (10, 0.000000001, _('%.2f ns')),
2580 (1, 0.000000001, _('%.3f ns')),
2583 (1, 0.000000001, _('%.3f ns')),
2581 )
2584 )
2582
2585
2583 _timenesting = [0]
2586 _timenesting = [0]
2584
2587
2585 def timed(func):
2588 def timed(func):
2586 '''Report the execution time of a function call to stderr.
2589 '''Report the execution time of a function call to stderr.
2587
2590
2588 During development, use as a decorator when you need to measure
2591 During development, use as a decorator when you need to measure
2589 the cost of a function, e.g. as follows:
2592 the cost of a function, e.g. as follows:
2590
2593
2591 @util.timed
2594 @util.timed
2592 def foo(a, b, c):
2595 def foo(a, b, c):
2593 pass
2596 pass
2594 '''
2597 '''
2595
2598
2596 def wrapper(*args, **kwargs):
2599 def wrapper(*args, **kwargs):
2597 start = time.time()
2600 start = time.time()
2598 indent = 2
2601 indent = 2
2599 _timenesting[0] += indent
2602 _timenesting[0] += indent
2600 try:
2603 try:
2601 return func(*args, **kwargs)
2604 return func(*args, **kwargs)
2602 finally:
2605 finally:
2603 elapsed = time.time() - start
2606 elapsed = time.time() - start
2604 _timenesting[0] -= indent
2607 _timenesting[0] -= indent
2605 sys.stderr.write('%s%s: %s\n' %
2608 sys.stderr.write('%s%s: %s\n' %
2606 (' ' * _timenesting[0], func.__name__,
2609 (' ' * _timenesting[0], func.__name__,
2607 timecount(elapsed)))
2610 timecount(elapsed)))
2608 return wrapper
2611 return wrapper
2609
2612
2610 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2613 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2611 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2614 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2612
2615
2613 def sizetoint(s):
2616 def sizetoint(s):
2614 '''Convert a space specifier to a byte count.
2617 '''Convert a space specifier to a byte count.
2615
2618
2616 >>> sizetoint('30')
2619 >>> sizetoint('30')
2617 30
2620 30
2618 >>> sizetoint('2.2kb')
2621 >>> sizetoint('2.2kb')
2619 2252
2622 2252
2620 >>> sizetoint('6M')
2623 >>> sizetoint('6M')
2621 6291456
2624 6291456
2622 '''
2625 '''
2623 t = s.strip().lower()
2626 t = s.strip().lower()
2624 try:
2627 try:
2625 for k, u in _sizeunits:
2628 for k, u in _sizeunits:
2626 if t.endswith(k):
2629 if t.endswith(k):
2627 return int(float(t[:-len(k)]) * u)
2630 return int(float(t[:-len(k)]) * u)
2628 return int(t)
2631 return int(t)
2629 except ValueError:
2632 except ValueError:
2630 raise error.ParseError(_("couldn't parse size: %s") % s)
2633 raise error.ParseError(_("couldn't parse size: %s") % s)
2631
2634
2632 class hooks(object):
2635 class hooks(object):
2633 '''A collection of hook functions that can be used to extend a
2636 '''A collection of hook functions that can be used to extend a
2634 function's behavior. Hooks are called in lexicographic order,
2637 function's behavior. Hooks are called in lexicographic order,
2635 based on the names of their sources.'''
2638 based on the names of their sources.'''
2636
2639
2637 def __init__(self):
2640 def __init__(self):
2638 self._hooks = []
2641 self._hooks = []
2639
2642
2640 def add(self, source, hook):
2643 def add(self, source, hook):
2641 self._hooks.append((source, hook))
2644 self._hooks.append((source, hook))
2642
2645
2643 def __call__(self, *args):
2646 def __call__(self, *args):
2644 self._hooks.sort(key=lambda x: x[0])
2647 self._hooks.sort(key=lambda x: x[0])
2645 results = []
2648 results = []
2646 for source, hook in self._hooks:
2649 for source, hook in self._hooks:
2647 results.append(hook(*args))
2650 results.append(hook(*args))
2648 return results
2651 return results
2649
2652
2650 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s'):
2653 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s'):
2651 '''Yields lines for a nicely formatted stacktrace.
2654 '''Yields lines for a nicely formatted stacktrace.
2652 Skips the 'skip' last entries.
2655 Skips the 'skip' last entries.
2653 Each file+linenumber is formatted according to fileline.
2656 Each file+linenumber is formatted according to fileline.
2654 Each line is formatted according to line.
2657 Each line is formatted according to line.
2655 If line is None, it yields:
2658 If line is None, it yields:
2656 length of longest filepath+line number,
2659 length of longest filepath+line number,
2657 filepath+linenumber,
2660 filepath+linenumber,
2658 function
2661 function
2659
2662
2660 Not be used in production code but very convenient while developing.
2663 Not be used in production code but very convenient while developing.
2661 '''
2664 '''
2662 entries = [(fileline % (fn, ln), func)
2665 entries = [(fileline % (fn, ln), func)
2663 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2666 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2664 if entries:
2667 if entries:
2665 fnmax = max(len(entry[0]) for entry in entries)
2668 fnmax = max(len(entry[0]) for entry in entries)
2666 for fnln, func in entries:
2669 for fnln, func in entries:
2667 if line is None:
2670 if line is None:
2668 yield (fnmax, fnln, func)
2671 yield (fnmax, fnln, func)
2669 else:
2672 else:
2670 yield line % (fnmax, fnln, func)
2673 yield line % (fnmax, fnln, func)
2671
2674
2672 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2675 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2673 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2676 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2674 Skips the 'skip' last entries. By default it will flush stdout first.
2677 Skips the 'skip' last entries. By default it will flush stdout first.
2675 It can be used everywhere and intentionally does not require an ui object.
2678 It can be used everywhere and intentionally does not require an ui object.
2676 Not be used in production code but very convenient while developing.
2679 Not be used in production code but very convenient while developing.
2677 '''
2680 '''
2678 if otherf:
2681 if otherf:
2679 otherf.flush()
2682 otherf.flush()
2680 f.write('%s at:\n' % msg)
2683 f.write('%s at:\n' % msg)
2681 for line in getstackframes(skip + 1):
2684 for line in getstackframes(skip + 1):
2682 f.write(line)
2685 f.write(line)
2683 f.flush()
2686 f.flush()
2684
2687
2685 class dirs(object):
2688 class dirs(object):
2686 '''a multiset of directory names from a dirstate or manifest'''
2689 '''a multiset of directory names from a dirstate or manifest'''
2687
2690
2688 def __init__(self, map, skip=None):
2691 def __init__(self, map, skip=None):
2689 self._dirs = {}
2692 self._dirs = {}
2690 addpath = self.addpath
2693 addpath = self.addpath
2691 if safehasattr(map, 'iteritems') and skip is not None:
2694 if safehasattr(map, 'iteritems') and skip is not None:
2692 for f, s in map.iteritems():
2695 for f, s in map.iteritems():
2693 if s[0] != skip:
2696 if s[0] != skip:
2694 addpath(f)
2697 addpath(f)
2695 else:
2698 else:
2696 for f in map:
2699 for f in map:
2697 addpath(f)
2700 addpath(f)
2698
2701
2699 def addpath(self, path):
2702 def addpath(self, path):
2700 dirs = self._dirs
2703 dirs = self._dirs
2701 for base in finddirs(path):
2704 for base in finddirs(path):
2702 if base in dirs:
2705 if base in dirs:
2703 dirs[base] += 1
2706 dirs[base] += 1
2704 return
2707 return
2705 dirs[base] = 1
2708 dirs[base] = 1
2706
2709
2707 def delpath(self, path):
2710 def delpath(self, path):
2708 dirs = self._dirs
2711 dirs = self._dirs
2709 for base in finddirs(path):
2712 for base in finddirs(path):
2710 if dirs[base] > 1:
2713 if dirs[base] > 1:
2711 dirs[base] -= 1
2714 dirs[base] -= 1
2712 return
2715 return
2713 del dirs[base]
2716 del dirs[base]
2714
2717
2715 def __iter__(self):
2718 def __iter__(self):
2716 return self._dirs.iterkeys()
2719 return self._dirs.iterkeys()
2717
2720
2718 def __contains__(self, d):
2721 def __contains__(self, d):
2719 return d in self._dirs
2722 return d in self._dirs
2720
2723
2721 if safehasattr(parsers, 'dirs'):
2724 if safehasattr(parsers, 'dirs'):
2722 dirs = parsers.dirs
2725 dirs = parsers.dirs
2723
2726
2724 def finddirs(path):
2727 def finddirs(path):
2725 pos = path.rfind('/')
2728 pos = path.rfind('/')
2726 while pos != -1:
2729 while pos != -1:
2727 yield path[:pos]
2730 yield path[:pos]
2728 pos = path.rfind('/', 0, pos)
2731 pos = path.rfind('/', 0, pos)
2729
2732
2730 # compression utility
2733 # compression utility
2731
2734
2732 class nocompress(object):
2735 class nocompress(object):
2733 def compress(self, x):
2736 def compress(self, x):
2734 return x
2737 return x
2735 def flush(self):
2738 def flush(self):
2736 return ""
2739 return ""
2737
2740
2738 compressors = {
2741 compressors = {
2739 None: nocompress,
2742 None: nocompress,
2740 # lambda to prevent early import
2743 # lambda to prevent early import
2741 'BZ': lambda: bz2.BZ2Compressor(),
2744 'BZ': lambda: bz2.BZ2Compressor(),
2742 'GZ': lambda: zlib.compressobj(),
2745 'GZ': lambda: zlib.compressobj(),
2743 }
2746 }
2744 # also support the old form by courtesies
2747 # also support the old form by courtesies
2745 compressors['UN'] = compressors[None]
2748 compressors['UN'] = compressors[None]
2746
2749
2747 def _makedecompressor(decompcls):
2750 def _makedecompressor(decompcls):
2748 def generator(f):
2751 def generator(f):
2749 d = decompcls()
2752 d = decompcls()
2750 for chunk in filechunkiter(f):
2753 for chunk in filechunkiter(f):
2751 yield d.decompress(chunk)
2754 yield d.decompress(chunk)
2752 def func(fh):
2755 def func(fh):
2753 return chunkbuffer(generator(fh))
2756 return chunkbuffer(generator(fh))
2754 return func
2757 return func
2755
2758
2756 class ctxmanager(object):
2759 class ctxmanager(object):
2757 '''A context manager for use in 'with' blocks to allow multiple
2760 '''A context manager for use in 'with' blocks to allow multiple
2758 contexts to be entered at once. This is both safer and more
2761 contexts to be entered at once. This is both safer and more
2759 flexible than contextlib.nested.
2762 flexible than contextlib.nested.
2760
2763
2761 Once Mercurial supports Python 2.7+, this will become mostly
2764 Once Mercurial supports Python 2.7+, this will become mostly
2762 unnecessary.
2765 unnecessary.
2763 '''
2766 '''
2764
2767
2765 def __init__(self, *args):
2768 def __init__(self, *args):
2766 '''Accepts a list of no-argument functions that return context
2769 '''Accepts a list of no-argument functions that return context
2767 managers. These will be invoked at __call__ time.'''
2770 managers. These will be invoked at __call__ time.'''
2768 self._pending = args
2771 self._pending = args
2769 self._atexit = []
2772 self._atexit = []
2770
2773
2771 def __enter__(self):
2774 def __enter__(self):
2772 return self
2775 return self
2773
2776
2774 def enter(self):
2777 def enter(self):
2775 '''Create and enter context managers in the order in which they were
2778 '''Create and enter context managers in the order in which they were
2776 passed to the constructor.'''
2779 passed to the constructor.'''
2777 values = []
2780 values = []
2778 for func in self._pending:
2781 for func in self._pending:
2779 obj = func()
2782 obj = func()
2780 values.append(obj.__enter__())
2783 values.append(obj.__enter__())
2781 self._atexit.append(obj.__exit__)
2784 self._atexit.append(obj.__exit__)
2782 del self._pending
2785 del self._pending
2783 return values
2786 return values
2784
2787
2785 def atexit(self, func, *args, **kwargs):
2788 def atexit(self, func, *args, **kwargs):
2786 '''Add a function to call when this context manager exits. The
2789 '''Add a function to call when this context manager exits. The
2787 ordering of multiple atexit calls is unspecified, save that
2790 ordering of multiple atexit calls is unspecified, save that
2788 they will happen before any __exit__ functions.'''
2791 they will happen before any __exit__ functions.'''
2789 def wrapper(exc_type, exc_val, exc_tb):
2792 def wrapper(exc_type, exc_val, exc_tb):
2790 func(*args, **kwargs)
2793 func(*args, **kwargs)
2791 self._atexit.append(wrapper)
2794 self._atexit.append(wrapper)
2792 return func
2795 return func
2793
2796
2794 def __exit__(self, exc_type, exc_val, exc_tb):
2797 def __exit__(self, exc_type, exc_val, exc_tb):
2795 '''Context managers are exited in the reverse order from which
2798 '''Context managers are exited in the reverse order from which
2796 they were created.'''
2799 they were created.'''
2797 received = exc_type is not None
2800 received = exc_type is not None
2798 suppressed = False
2801 suppressed = False
2799 pending = None
2802 pending = None
2800 self._atexit.reverse()
2803 self._atexit.reverse()
2801 for exitfunc in self._atexit:
2804 for exitfunc in self._atexit:
2802 try:
2805 try:
2803 if exitfunc(exc_type, exc_val, exc_tb):
2806 if exitfunc(exc_type, exc_val, exc_tb):
2804 suppressed = True
2807 suppressed = True
2805 exc_type = None
2808 exc_type = None
2806 exc_val = None
2809 exc_val = None
2807 exc_tb = None
2810 exc_tb = None
2808 except BaseException:
2811 except BaseException:
2809 pending = sys.exc_info()
2812 pending = sys.exc_info()
2810 exc_type, exc_val, exc_tb = pending = sys.exc_info()
2813 exc_type, exc_val, exc_tb = pending = sys.exc_info()
2811 del self._atexit
2814 del self._atexit
2812 if pending:
2815 if pending:
2813 raise exc_val
2816 raise exc_val
2814 return received and suppressed
2817 return received and suppressed
2815
2818
2816 def _bz2():
2819 def _bz2():
2817 d = bz2.BZ2Decompressor()
2820 d = bz2.BZ2Decompressor()
2818 # Bzip2 stream start with BZ, but we stripped it.
2821 # Bzip2 stream start with BZ, but we stripped it.
2819 # we put it back for good measure.
2822 # we put it back for good measure.
2820 d.decompress('BZ')
2823 d.decompress('BZ')
2821 return d
2824 return d
2822
2825
2823 decompressors = {None: lambda fh: fh,
2826 decompressors = {None: lambda fh: fh,
2824 '_truncatedBZ': _makedecompressor(_bz2),
2827 '_truncatedBZ': _makedecompressor(_bz2),
2825 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2828 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2826 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2829 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2827 }
2830 }
2828 # also support the old form by courtesies
2831 # also support the old form by courtesies
2829 decompressors['UN'] = decompressors[None]
2832 decompressors['UN'] = decompressors[None]
2830
2833
2831 # convenient shortcut
2834 # convenient shortcut
2832 dst = debugstacktrace
2835 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now