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