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