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