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