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