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