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