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