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