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