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