##// END OF EJS Templates
util: work around behavior change in Python 2.7.1
Steve Borho -
r13128:dbc54681 stable
parent child Browse files
Show More
@@ -1,1484 +1,1485 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic 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 specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, textwrap, unicodedata, signal
19 import os, stat, time, calendar, textwrap, unicodedata, signal
20 import imp, socket
20 import imp, socket
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 if sys.version_info >= (2, 5):
31 if sys.version_info >= (2, 5):
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 else:
33 else:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 if sys.version_info[0] < 3:
41 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
42 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
43 return sliceable[offset:]
44 else:
44 else:
45 def fakebuffer(sliceable, offset=0):
45 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
46 return memoryview(sliceable)[offset:]
47 try:
47 try:
48 buffer
48 buffer
49 except NameError:
49 except NameError:
50 __builtin__.buffer = fakebuffer
50 __builtin__.buffer = fakebuffer
51
51
52 import subprocess
52 import subprocess
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54
54
55 def popen2(cmd, env=None, newlines=False):
55 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
56 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
62 universal_newlines=newlines,
63 env=env)
63 env=env)
64 return p.stdin, p.stdout
64 return p.stdin, p.stdout
65
65
66 def popen3(cmd, env=None, newlines=False):
66 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
70 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
71 universal_newlines=newlines,
72 env=env)
72 env=env)
73 return p.stdin, p.stdout, p.stderr
73 return p.stdin, p.stdout, p.stderr
74
74
75 def version():
75 def version():
76 """Return version information if available."""
76 """Return version information if available."""
77 try:
77 try:
78 import __version__
78 import __version__
79 return __version__.version
79 return __version__.version
80 except ImportError:
80 except ImportError:
81 return 'unknown'
81 return 'unknown'
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
97 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
98 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
99 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
100 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
101 '%b %d %H:%M',
102 '%b %d %I:%M%p',
102 '%b %d %I:%M%p',
103 '%b %d %Y',
103 '%b %d %Y',
104 '%b %d',
104 '%b %d',
105 '%H:%M:%S',
105 '%H:%M:%S',
106 '%I:%M:%S%p',
106 '%I:%M:%S%p',
107 '%H:%M',
107 '%H:%M',
108 '%I:%M%p',
108 '%I:%M%p',
109 )
109 )
110
110
111 extendeddateformats = defaultdateformats + (
111 extendeddateformats = defaultdateformats + (
112 "%Y",
112 "%Y",
113 "%Y-%m",
113 "%Y-%m",
114 "%b",
114 "%b",
115 "%b %Y",
115 "%b %Y",
116 )
116 )
117
117
118 def cachefunc(func):
118 def cachefunc(func):
119 '''cache the result of function calls'''
119 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
120 # XXX doesn't handle keywords args
121 cache = {}
121 cache = {}
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
123 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
124 # we don't need to pack/unpack the list
125 def f(arg):
125 def f(arg):
126 if arg not in cache:
126 if arg not in cache:
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 return cache[arg]
128 return cache[arg]
129 else:
129 else:
130 def f(*args):
130 def f(*args):
131 if args not in cache:
131 if args not in cache:
132 cache[args] = func(*args)
132 cache[args] = func(*args)
133 return cache[args]
133 return cache[args]
134
134
135 return f
135 return f
136
136
137 def lrucachefunc(func):
137 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
138 '''cache most recent results of function calls'''
139 cache = {}
139 cache = {}
140 order = []
140 order = []
141 if func.func_code.co_argcount == 1:
141 if func.func_code.co_argcount == 1:
142 def f(arg):
142 def f(arg):
143 if arg not in cache:
143 if arg not in cache:
144 if len(cache) > 20:
144 if len(cache) > 20:
145 del cache[order.pop(0)]
145 del cache[order.pop(0)]
146 cache[arg] = func(arg)
146 cache[arg] = func(arg)
147 else:
147 else:
148 order.remove(arg)
148 order.remove(arg)
149 order.append(arg)
149 order.append(arg)
150 return cache[arg]
150 return cache[arg]
151 else:
151 else:
152 def f(*args):
152 def f(*args):
153 if args not in cache:
153 if args not in cache:
154 if len(cache) > 20:
154 if len(cache) > 20:
155 del cache[order.pop(0)]
155 del cache[order.pop(0)]
156 cache[args] = func(*args)
156 cache[args] = func(*args)
157 else:
157 else:
158 order.remove(args)
158 order.remove(args)
159 order.append(args)
159 order.append(args)
160 return cache[args]
160 return cache[args]
161
161
162 return f
162 return f
163
163
164 class propertycache(object):
164 class propertycache(object):
165 def __init__(self, func):
165 def __init__(self, func):
166 self.func = func
166 self.func = func
167 self.name = func.__name__
167 self.name = func.__name__
168 def __get__(self, obj, type=None):
168 def __get__(self, obj, type=None):
169 result = self.func(obj)
169 result = self.func(obj)
170 setattr(obj, self.name, result)
170 setattr(obj, self.name, result)
171 return result
171 return result
172
172
173 def pipefilter(s, cmd):
173 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
174 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
177 pout, perr = p.communicate(s)
178 return pout
178 return pout
179
179
180 def tempfilter(s, cmd):
180 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
181 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
182 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
183 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
184 the temporary files generated.'''
185 inname, outname = None, None
185 inname, outname = None, None
186 try:
186 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
188 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
189 fp.write(s)
190 fp.close()
190 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
192 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
193 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
194 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
195 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
196 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
197 code = 0
198 if code:
198 if code:
199 raise Abort(_("command '%s' failed: %s") %
199 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explain_exit(code)))
200 (cmd, explain_exit(code)))
201 return open(outname, 'rb').read()
201 return open(outname, 'rb').read()
202 finally:
202 finally:
203 try:
203 try:
204 if inname:
204 if inname:
205 os.unlink(inname)
205 os.unlink(inname)
206 except:
206 except:
207 pass
207 pass
208 try:
208 try:
209 if outname:
209 if outname:
210 os.unlink(outname)
210 os.unlink(outname)
211 except:
211 except:
212 pass
212 pass
213
213
214 filtertable = {
214 filtertable = {
215 'tempfile:': tempfilter,
215 'tempfile:': tempfilter,
216 'pipe:': pipefilter,
216 'pipe:': pipefilter,
217 }
217 }
218
218
219 def filter(s, cmd):
219 def filter(s, cmd):
220 "filter a string through a command that transforms its input to its output"
220 "filter a string through a command that transforms its input to its output"
221 for name, fn in filtertable.iteritems():
221 for name, fn in filtertable.iteritems():
222 if cmd.startswith(name):
222 if cmd.startswith(name):
223 return fn(s, cmd[len(name):].lstrip())
223 return fn(s, cmd[len(name):].lstrip())
224 return pipefilter(s, cmd)
224 return pipefilter(s, cmd)
225
225
226 def binary(s):
226 def binary(s):
227 """return true if a string is binary data"""
227 """return true if a string is binary data"""
228 return bool(s and '\0' in s)
228 return bool(s and '\0' in s)
229
229
230 def increasingchunks(source, min=1024, max=65536):
230 def increasingchunks(source, min=1024, max=65536):
231 '''return no less than min bytes per chunk while data remains,
231 '''return no less than min bytes per chunk while data remains,
232 doubling min after each chunk until it reaches max'''
232 doubling min after each chunk until it reaches max'''
233 def log2(x):
233 def log2(x):
234 if not x:
234 if not x:
235 return 0
235 return 0
236 i = 0
236 i = 0
237 while x:
237 while x:
238 x >>= 1
238 x >>= 1
239 i += 1
239 i += 1
240 return i - 1
240 return i - 1
241
241
242 buf = []
242 buf = []
243 blen = 0
243 blen = 0
244 for chunk in source:
244 for chunk in source:
245 buf.append(chunk)
245 buf.append(chunk)
246 blen += len(chunk)
246 blen += len(chunk)
247 if blen >= min:
247 if blen >= min:
248 if min < max:
248 if min < max:
249 min = min << 1
249 min = min << 1
250 nmin = 1 << log2(blen)
250 nmin = 1 << log2(blen)
251 if nmin > min:
251 if nmin > min:
252 min = nmin
252 min = nmin
253 if min > max:
253 if min > max:
254 min = max
254 min = max
255 yield ''.join(buf)
255 yield ''.join(buf)
256 blen = 0
256 blen = 0
257 buf = []
257 buf = []
258 if buf:
258 if buf:
259 yield ''.join(buf)
259 yield ''.join(buf)
260
260
261 Abort = error.Abort
261 Abort = error.Abort
262
262
263 def always(fn):
263 def always(fn):
264 return True
264 return True
265
265
266 def never(fn):
266 def never(fn):
267 return False
267 return False
268
268
269 def pathto(root, n1, n2):
269 def pathto(root, n1, n2):
270 '''return the relative path from one place to another.
270 '''return the relative path from one place to another.
271 root should use os.sep to separate directories
271 root should use os.sep to separate directories
272 n1 should use os.sep to separate directories
272 n1 should use os.sep to separate directories
273 n2 should use "/" to separate directories
273 n2 should use "/" to separate directories
274 returns an os.sep-separated path.
274 returns an os.sep-separated path.
275
275
276 If n1 is a relative path, it's assumed it's
276 If n1 is a relative path, it's assumed it's
277 relative to root.
277 relative to root.
278 n2 should always be relative to root.
278 n2 should always be relative to root.
279 '''
279 '''
280 if not n1:
280 if not n1:
281 return localpath(n2)
281 return localpath(n2)
282 if os.path.isabs(n1):
282 if os.path.isabs(n1):
283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
284 return os.path.join(root, localpath(n2))
284 return os.path.join(root, localpath(n2))
285 n2 = '/'.join((pconvert(root), n2))
285 n2 = '/'.join((pconvert(root), n2))
286 a, b = splitpath(n1), n2.split('/')
286 a, b = splitpath(n1), n2.split('/')
287 a.reverse()
287 a.reverse()
288 b.reverse()
288 b.reverse()
289 while a and b and a[-1] == b[-1]:
289 while a and b and a[-1] == b[-1]:
290 a.pop()
290 a.pop()
291 b.pop()
291 b.pop()
292 b.reverse()
292 b.reverse()
293 return os.sep.join((['..'] * len(a)) + b) or '.'
293 return os.sep.join((['..'] * len(a)) + b) or '.'
294
294
295 def canonpath(root, cwd, myname, auditor=None):
295 def canonpath(root, cwd, myname, auditor=None):
296 """return the canonical path of myname, given cwd and root"""
296 """return the canonical path of myname, given cwd and root"""
297 if endswithsep(root):
297 if endswithsep(root):
298 rootsep = root
298 rootsep = root
299 else:
299 else:
300 rootsep = root + os.sep
300 rootsep = root + os.sep
301 name = myname
301 name = myname
302 if not os.path.isabs(name):
302 if not os.path.isabs(name):
303 name = os.path.join(root, cwd, name)
303 name = os.path.join(root, cwd, name)
304 name = os.path.normpath(name)
304 name = os.path.normpath(name)
305 if auditor is None:
305 if auditor is None:
306 auditor = path_auditor(root)
306 auditor = path_auditor(root)
307 if name != rootsep and name.startswith(rootsep):
307 if name != rootsep and name.startswith(rootsep):
308 name = name[len(rootsep):]
308 name = name[len(rootsep):]
309 auditor(name)
309 auditor(name)
310 return pconvert(name)
310 return pconvert(name)
311 elif name == root:
311 elif name == root:
312 return ''
312 return ''
313 else:
313 else:
314 # Determine whether `name' is in the hierarchy at or beneath `root',
314 # Determine whether `name' is in the hierarchy at or beneath `root',
315 # by iterating name=dirname(name) until that causes no change (can't
315 # by iterating name=dirname(name) until that causes no change (can't
316 # check name == '/', because that doesn't work on windows). For each
316 # check name == '/', because that doesn't work on windows). For each
317 # `name', compare dev/inode numbers. If they match, the list `rel'
317 # `name', compare dev/inode numbers. If they match, the list `rel'
318 # holds the reversed list of components making up the relative file
318 # holds the reversed list of components making up the relative file
319 # name we want.
319 # name we want.
320 root_st = os.stat(root)
320 root_st = os.stat(root)
321 rel = []
321 rel = []
322 while True:
322 while True:
323 try:
323 try:
324 name_st = os.stat(name)
324 name_st = os.stat(name)
325 except OSError:
325 except OSError:
326 break
326 break
327 if samestat(name_st, root_st):
327 if samestat(name_st, root_st):
328 if not rel:
328 if not rel:
329 # name was actually the same as root (maybe a symlink)
329 # name was actually the same as root (maybe a symlink)
330 return ''
330 return ''
331 rel.reverse()
331 rel.reverse()
332 name = os.path.join(*rel)
332 name = os.path.join(*rel)
333 auditor(name)
333 auditor(name)
334 return pconvert(name)
334 return pconvert(name)
335 dirname, basename = os.path.split(name)
335 dirname, basename = os.path.split(name)
336 rel.append(basename)
336 rel.append(basename)
337 if dirname == name:
337 if dirname == name:
338 break
338 break
339 name = dirname
339 name = dirname
340
340
341 raise Abort('%s not under root' % myname)
341 raise Abort('%s not under root' % myname)
342
342
343 _hgexecutable = None
343 _hgexecutable = None
344
344
345 def main_is_frozen():
345 def main_is_frozen():
346 """return True if we are a frozen executable.
346 """return True if we are a frozen executable.
347
347
348 The code supports py2exe (most common, Windows only) and tools/freeze
348 The code supports py2exe (most common, Windows only) and tools/freeze
349 (portable, not much used).
349 (portable, not much used).
350 """
350 """
351 return (hasattr(sys, "frozen") or # new py2exe
351 return (hasattr(sys, "frozen") or # new py2exe
352 hasattr(sys, "importers") or # old py2exe
352 hasattr(sys, "importers") or # old py2exe
353 imp.is_frozen("__main__")) # tools/freeze
353 imp.is_frozen("__main__")) # tools/freeze
354
354
355 def hgexecutable():
355 def hgexecutable():
356 """return location of the 'hg' executable.
356 """return location of the 'hg' executable.
357
357
358 Defaults to $HG or 'hg' in the search path.
358 Defaults to $HG or 'hg' in the search path.
359 """
359 """
360 if _hgexecutable is None:
360 if _hgexecutable is None:
361 hg = os.environ.get('HG')
361 hg = os.environ.get('HG')
362 if hg:
362 if hg:
363 set_hgexecutable(hg)
363 set_hgexecutable(hg)
364 elif main_is_frozen():
364 elif main_is_frozen():
365 set_hgexecutable(sys.executable)
365 set_hgexecutable(sys.executable)
366 else:
366 else:
367 exe = find_exe('hg') or os.path.basename(sys.argv[0])
367 exe = find_exe('hg') or os.path.basename(sys.argv[0])
368 set_hgexecutable(exe)
368 set_hgexecutable(exe)
369 return _hgexecutable
369 return _hgexecutable
370
370
371 def set_hgexecutable(path):
371 def set_hgexecutable(path):
372 """set location of the 'hg' executable"""
372 """set location of the 'hg' executable"""
373 global _hgexecutable
373 global _hgexecutable
374 _hgexecutable = path
374 _hgexecutable = path
375
375
376 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
376 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
377 '''enhanced shell command execution.
377 '''enhanced shell command execution.
378 run with environment maybe modified, maybe in different dir.
378 run with environment maybe modified, maybe in different dir.
379
379
380 if command fails and onerr is None, return status. if ui object,
380 if command fails and onerr is None, return status. if ui object,
381 print error message and return status, else raise onerr object as
381 print error message and return status, else raise onerr object as
382 exception.
382 exception.
383
383
384 if out is specified, it is assumed to be a file-like object that has a
384 if out is specified, it is assumed to be a file-like object that has a
385 write() method. stdout and stderr will be redirected to out.'''
385 write() method. stdout and stderr will be redirected to out.'''
386 def py2shell(val):
386 def py2shell(val):
387 'convert python object into string that is useful to shell'
387 'convert python object into string that is useful to shell'
388 if val is None or val is False:
388 if val is None or val is False:
389 return '0'
389 return '0'
390 if val is True:
390 if val is True:
391 return '1'
391 return '1'
392 return str(val)
392 return str(val)
393 origcmd = cmd
393 origcmd = cmd
394 if os.name == 'nt':
394 if os.name == 'nt' and sys.version_info < (2, 7, 1):
395 # Python versions since 2.7.1 do this extra quoting themselves
395 cmd = '"%s"' % cmd
396 cmd = '"%s"' % cmd
396 env = dict(os.environ)
397 env = dict(os.environ)
397 env.update((k, py2shell(v)) for k, v in environ.iteritems())
398 env.update((k, py2shell(v)) for k, v in environ.iteritems())
398 env['HG'] = hgexecutable()
399 env['HG'] = hgexecutable()
399 if out is None:
400 if out is None:
400 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
401 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
401 env=env, cwd=cwd)
402 env=env, cwd=cwd)
402 else:
403 else:
403 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
404 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
404 env=env, cwd=cwd, stdout=subprocess.PIPE,
405 env=env, cwd=cwd, stdout=subprocess.PIPE,
405 stderr=subprocess.STDOUT)
406 stderr=subprocess.STDOUT)
406 for line in proc.stdout:
407 for line in proc.stdout:
407 out.write(line)
408 out.write(line)
408 proc.wait()
409 proc.wait()
409 rc = proc.returncode
410 rc = proc.returncode
410 if sys.platform == 'OpenVMS' and rc & 1:
411 if sys.platform == 'OpenVMS' and rc & 1:
411 rc = 0
412 rc = 0
412 if rc and onerr:
413 if rc and onerr:
413 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
414 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
414 explain_exit(rc)[0])
415 explain_exit(rc)[0])
415 if errprefix:
416 if errprefix:
416 errmsg = '%s: %s' % (errprefix, errmsg)
417 errmsg = '%s: %s' % (errprefix, errmsg)
417 try:
418 try:
418 onerr.warn(errmsg + '\n')
419 onerr.warn(errmsg + '\n')
419 except AttributeError:
420 except AttributeError:
420 raise onerr(errmsg)
421 raise onerr(errmsg)
421 return rc
422 return rc
422
423
423 def checksignature(func):
424 def checksignature(func):
424 '''wrap a function with code to check for calling errors'''
425 '''wrap a function with code to check for calling errors'''
425 def check(*args, **kwargs):
426 def check(*args, **kwargs):
426 try:
427 try:
427 return func(*args, **kwargs)
428 return func(*args, **kwargs)
428 except TypeError:
429 except TypeError:
429 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
430 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
430 raise error.SignatureError
431 raise error.SignatureError
431 raise
432 raise
432
433
433 return check
434 return check
434
435
435 def unlink(f):
436 def unlink(f):
436 """unlink and remove the directory if it is empty"""
437 """unlink and remove the directory if it is empty"""
437 os.unlink(f)
438 os.unlink(f)
438 # try removing directories that might now be empty
439 # try removing directories that might now be empty
439 try:
440 try:
440 os.removedirs(os.path.dirname(f))
441 os.removedirs(os.path.dirname(f))
441 except OSError:
442 except OSError:
442 pass
443 pass
443
444
444 def copyfile(src, dest):
445 def copyfile(src, dest):
445 "copy a file, preserving mode and atime/mtime"
446 "copy a file, preserving mode and atime/mtime"
446 if os.path.islink(src):
447 if os.path.islink(src):
447 try:
448 try:
448 os.unlink(dest)
449 os.unlink(dest)
449 except:
450 except:
450 pass
451 pass
451 os.symlink(os.readlink(src), dest)
452 os.symlink(os.readlink(src), dest)
452 else:
453 else:
453 try:
454 try:
454 shutil.copyfile(src, dest)
455 shutil.copyfile(src, dest)
455 shutil.copystat(src, dest)
456 shutil.copystat(src, dest)
456 except shutil.Error, inst:
457 except shutil.Error, inst:
457 raise Abort(str(inst))
458 raise Abort(str(inst))
458
459
459 def copyfiles(src, dst, hardlink=None):
460 def copyfiles(src, dst, hardlink=None):
460 """Copy a directory tree using hardlinks if possible"""
461 """Copy a directory tree using hardlinks if possible"""
461
462
462 if hardlink is None:
463 if hardlink is None:
463 hardlink = (os.stat(src).st_dev ==
464 hardlink = (os.stat(src).st_dev ==
464 os.stat(os.path.dirname(dst)).st_dev)
465 os.stat(os.path.dirname(dst)).st_dev)
465
466
466 num = 0
467 num = 0
467 if os.path.isdir(src):
468 if os.path.isdir(src):
468 os.mkdir(dst)
469 os.mkdir(dst)
469 for name, kind in osutil.listdir(src):
470 for name, kind in osutil.listdir(src):
470 srcname = os.path.join(src, name)
471 srcname = os.path.join(src, name)
471 dstname = os.path.join(dst, name)
472 dstname = os.path.join(dst, name)
472 hardlink, n = copyfiles(srcname, dstname, hardlink)
473 hardlink, n = copyfiles(srcname, dstname, hardlink)
473 num += n
474 num += n
474 else:
475 else:
475 if hardlink:
476 if hardlink:
476 try:
477 try:
477 os_link(src, dst)
478 os_link(src, dst)
478 except (IOError, OSError):
479 except (IOError, OSError):
479 hardlink = False
480 hardlink = False
480 shutil.copy(src, dst)
481 shutil.copy(src, dst)
481 else:
482 else:
482 shutil.copy(src, dst)
483 shutil.copy(src, dst)
483 num += 1
484 num += 1
484
485
485 return hardlink, num
486 return hardlink, num
486
487
487 class path_auditor(object):
488 class path_auditor(object):
488 '''ensure that a filesystem path contains no banned components.
489 '''ensure that a filesystem path contains no banned components.
489 the following properties of a path are checked:
490 the following properties of a path are checked:
490
491
491 - under top-level .hg
492 - under top-level .hg
492 - starts at the root of a windows drive
493 - starts at the root of a windows drive
493 - contains ".."
494 - contains ".."
494 - traverses a symlink (e.g. a/symlink_here/b)
495 - traverses a symlink (e.g. a/symlink_here/b)
495 - inside a nested repository (a callback can be used to approve
496 - inside a nested repository (a callback can be used to approve
496 some nested repositories, e.g., subrepositories)
497 some nested repositories, e.g., subrepositories)
497 '''
498 '''
498
499
499 def __init__(self, root, callback=None):
500 def __init__(self, root, callback=None):
500 self.audited = set()
501 self.audited = set()
501 self.auditeddir = set()
502 self.auditeddir = set()
502 self.root = root
503 self.root = root
503 self.callback = callback
504 self.callback = callback
504
505
505 def __call__(self, path):
506 def __call__(self, path):
506 if path in self.audited:
507 if path in self.audited:
507 return
508 return
508 normpath = os.path.normcase(path)
509 normpath = os.path.normcase(path)
509 parts = splitpath(normpath)
510 parts = splitpath(normpath)
510 if (os.path.splitdrive(path)[0]
511 if (os.path.splitdrive(path)[0]
511 or parts[0].lower() in ('.hg', '.hg.', '')
512 or parts[0].lower() in ('.hg', '.hg.', '')
512 or os.pardir in parts):
513 or os.pardir in parts):
513 raise Abort(_("path contains illegal component: %s") % path)
514 raise Abort(_("path contains illegal component: %s") % path)
514 if '.hg' in path.lower():
515 if '.hg' in path.lower():
515 lparts = [p.lower() for p in parts]
516 lparts = [p.lower() for p in parts]
516 for p in '.hg', '.hg.':
517 for p in '.hg', '.hg.':
517 if p in lparts[1:]:
518 if p in lparts[1:]:
518 pos = lparts.index(p)
519 pos = lparts.index(p)
519 base = os.path.join(*parts[:pos])
520 base = os.path.join(*parts[:pos])
520 raise Abort(_('path %r is inside repo %r') % (path, base))
521 raise Abort(_('path %r is inside repo %r') % (path, base))
521 def check(prefix):
522 def check(prefix):
522 curpath = os.path.join(self.root, prefix)
523 curpath = os.path.join(self.root, prefix)
523 try:
524 try:
524 st = os.lstat(curpath)
525 st = os.lstat(curpath)
525 except OSError, err:
526 except OSError, err:
526 # EINVAL can be raised as invalid path syntax under win32.
527 # EINVAL can be raised as invalid path syntax under win32.
527 # They must be ignored for patterns can be checked too.
528 # They must be ignored for patterns can be checked too.
528 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
529 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
529 raise
530 raise
530 else:
531 else:
531 if stat.S_ISLNK(st.st_mode):
532 if stat.S_ISLNK(st.st_mode):
532 raise Abort(_('path %r traverses symbolic link %r') %
533 raise Abort(_('path %r traverses symbolic link %r') %
533 (path, prefix))
534 (path, prefix))
534 elif (stat.S_ISDIR(st.st_mode) and
535 elif (stat.S_ISDIR(st.st_mode) and
535 os.path.isdir(os.path.join(curpath, '.hg'))):
536 os.path.isdir(os.path.join(curpath, '.hg'))):
536 if not self.callback or not self.callback(curpath):
537 if not self.callback or not self.callback(curpath):
537 raise Abort(_('path %r is inside repo %r') %
538 raise Abort(_('path %r is inside repo %r') %
538 (path, prefix))
539 (path, prefix))
539 parts.pop()
540 parts.pop()
540 prefixes = []
541 prefixes = []
541 while parts:
542 while parts:
542 prefix = os.sep.join(parts)
543 prefix = os.sep.join(parts)
543 if prefix in self.auditeddir:
544 if prefix in self.auditeddir:
544 break
545 break
545 check(prefix)
546 check(prefix)
546 prefixes.append(prefix)
547 prefixes.append(prefix)
547 parts.pop()
548 parts.pop()
548
549
549 self.audited.add(path)
550 self.audited.add(path)
550 # only add prefixes to the cache after checking everything: we don't
551 # only add prefixes to the cache after checking everything: we don't
551 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
552 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
552 self.auditeddir.update(prefixes)
553 self.auditeddir.update(prefixes)
553
554
554 def nlinks(pathname):
555 def nlinks(pathname):
555 """Return number of hardlinks for the given file."""
556 """Return number of hardlinks for the given file."""
556 return os.lstat(pathname).st_nlink
557 return os.lstat(pathname).st_nlink
557
558
558 if hasattr(os, 'link'):
559 if hasattr(os, 'link'):
559 os_link = os.link
560 os_link = os.link
560 else:
561 else:
561 def os_link(src, dst):
562 def os_link(src, dst):
562 raise OSError(0, _("Hardlinks not supported"))
563 raise OSError(0, _("Hardlinks not supported"))
563
564
564 def lookup_reg(key, name=None, scope=None):
565 def lookup_reg(key, name=None, scope=None):
565 return None
566 return None
566
567
567 def hidewindow():
568 def hidewindow():
568 """Hide current shell window.
569 """Hide current shell window.
569
570
570 Used to hide the window opened when starting asynchronous
571 Used to hide the window opened when starting asynchronous
571 child process under Windows, unneeded on other systems.
572 child process under Windows, unneeded on other systems.
572 """
573 """
573 pass
574 pass
574
575
575 if os.name == 'nt':
576 if os.name == 'nt':
576 from windows import *
577 from windows import *
577 else:
578 else:
578 from posix import *
579 from posix import *
579
580
580 def makelock(info, pathname):
581 def makelock(info, pathname):
581 try:
582 try:
582 return os.symlink(info, pathname)
583 return os.symlink(info, pathname)
583 except OSError, why:
584 except OSError, why:
584 if why.errno == errno.EEXIST:
585 if why.errno == errno.EEXIST:
585 raise
586 raise
586 except AttributeError: # no symlink in os
587 except AttributeError: # no symlink in os
587 pass
588 pass
588
589
589 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
590 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
590 os.write(ld, info)
591 os.write(ld, info)
591 os.close(ld)
592 os.close(ld)
592
593
593 def readlock(pathname):
594 def readlock(pathname):
594 try:
595 try:
595 return os.readlink(pathname)
596 return os.readlink(pathname)
596 except OSError, why:
597 except OSError, why:
597 if why.errno not in (errno.EINVAL, errno.ENOSYS):
598 if why.errno not in (errno.EINVAL, errno.ENOSYS):
598 raise
599 raise
599 except AttributeError: # no symlink in os
600 except AttributeError: # no symlink in os
600 pass
601 pass
601 return posixfile(pathname).read()
602 return posixfile(pathname).read()
602
603
603 def fstat(fp):
604 def fstat(fp):
604 '''stat file object that may not have fileno method.'''
605 '''stat file object that may not have fileno method.'''
605 try:
606 try:
606 return os.fstat(fp.fileno())
607 return os.fstat(fp.fileno())
607 except AttributeError:
608 except AttributeError:
608 return os.stat(fp.name)
609 return os.stat(fp.name)
609
610
610 # File system features
611 # File system features
611
612
612 def checkcase(path):
613 def checkcase(path):
613 """
614 """
614 Check whether the given path is on a case-sensitive filesystem
615 Check whether the given path is on a case-sensitive filesystem
615
616
616 Requires a path (like /foo/.hg) ending with a foldable final
617 Requires a path (like /foo/.hg) ending with a foldable final
617 directory component.
618 directory component.
618 """
619 """
619 s1 = os.stat(path)
620 s1 = os.stat(path)
620 d, b = os.path.split(path)
621 d, b = os.path.split(path)
621 p2 = os.path.join(d, b.upper())
622 p2 = os.path.join(d, b.upper())
622 if path == p2:
623 if path == p2:
623 p2 = os.path.join(d, b.lower())
624 p2 = os.path.join(d, b.lower())
624 try:
625 try:
625 s2 = os.stat(p2)
626 s2 = os.stat(p2)
626 if s2 == s1:
627 if s2 == s1:
627 return False
628 return False
628 return True
629 return True
629 except:
630 except:
630 return True
631 return True
631
632
632 _fspathcache = {}
633 _fspathcache = {}
633 def fspath(name, root):
634 def fspath(name, root):
634 '''Get name in the case stored in the filesystem
635 '''Get name in the case stored in the filesystem
635
636
636 The name is either relative to root, or it is an absolute path starting
637 The name is either relative to root, or it is an absolute path starting
637 with root. Note that this function is unnecessary, and should not be
638 with root. Note that this function is unnecessary, and should not be
638 called, for case-sensitive filesystems (simply because it's expensive).
639 called, for case-sensitive filesystems (simply because it's expensive).
639 '''
640 '''
640 # If name is absolute, make it relative
641 # If name is absolute, make it relative
641 if name.lower().startswith(root.lower()):
642 if name.lower().startswith(root.lower()):
642 l = len(root)
643 l = len(root)
643 if name[l] == os.sep or name[l] == os.altsep:
644 if name[l] == os.sep or name[l] == os.altsep:
644 l = l + 1
645 l = l + 1
645 name = name[l:]
646 name = name[l:]
646
647
647 if not os.path.lexists(os.path.join(root, name)):
648 if not os.path.lexists(os.path.join(root, name)):
648 return None
649 return None
649
650
650 seps = os.sep
651 seps = os.sep
651 if os.altsep:
652 if os.altsep:
652 seps = seps + os.altsep
653 seps = seps + os.altsep
653 # Protect backslashes. This gets silly very quickly.
654 # Protect backslashes. This gets silly very quickly.
654 seps.replace('\\','\\\\')
655 seps.replace('\\','\\\\')
655 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
656 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
656 dir = os.path.normcase(os.path.normpath(root))
657 dir = os.path.normcase(os.path.normpath(root))
657 result = []
658 result = []
658 for part, sep in pattern.findall(name):
659 for part, sep in pattern.findall(name):
659 if sep:
660 if sep:
660 result.append(sep)
661 result.append(sep)
661 continue
662 continue
662
663
663 if dir not in _fspathcache:
664 if dir not in _fspathcache:
664 _fspathcache[dir] = os.listdir(dir)
665 _fspathcache[dir] = os.listdir(dir)
665 contents = _fspathcache[dir]
666 contents = _fspathcache[dir]
666
667
667 lpart = part.lower()
668 lpart = part.lower()
668 lenp = len(part)
669 lenp = len(part)
669 for n in contents:
670 for n in contents:
670 if lenp == len(n) and n.lower() == lpart:
671 if lenp == len(n) and n.lower() == lpart:
671 result.append(n)
672 result.append(n)
672 break
673 break
673 else:
674 else:
674 # Cannot happen, as the file exists!
675 # Cannot happen, as the file exists!
675 result.append(part)
676 result.append(part)
676 dir = os.path.join(dir, lpart)
677 dir = os.path.join(dir, lpart)
677
678
678 return ''.join(result)
679 return ''.join(result)
679
680
680 def checkexec(path):
681 def checkexec(path):
681 """
682 """
682 Check whether the given path is on a filesystem with UNIX-like exec flags
683 Check whether the given path is on a filesystem with UNIX-like exec flags
683
684
684 Requires a directory (like /foo/.hg)
685 Requires a directory (like /foo/.hg)
685 """
686 """
686
687
687 # VFAT on some Linux versions can flip mode but it doesn't persist
688 # VFAT on some Linux versions can flip mode but it doesn't persist
688 # a FS remount. Frequently we can detect it if files are created
689 # a FS remount. Frequently we can detect it if files are created
689 # with exec bit on.
690 # with exec bit on.
690
691
691 try:
692 try:
692 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
693 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
693 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
694 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
694 try:
695 try:
695 os.close(fh)
696 os.close(fh)
696 m = os.stat(fn).st_mode & 0777
697 m = os.stat(fn).st_mode & 0777
697 new_file_has_exec = m & EXECFLAGS
698 new_file_has_exec = m & EXECFLAGS
698 os.chmod(fn, m ^ EXECFLAGS)
699 os.chmod(fn, m ^ EXECFLAGS)
699 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
700 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
700 finally:
701 finally:
701 os.unlink(fn)
702 os.unlink(fn)
702 except (IOError, OSError):
703 except (IOError, OSError):
703 # we don't care, the user probably won't be able to commit anyway
704 # we don't care, the user probably won't be able to commit anyway
704 return False
705 return False
705 return not (new_file_has_exec or exec_flags_cannot_flip)
706 return not (new_file_has_exec or exec_flags_cannot_flip)
706
707
707 def checklink(path):
708 def checklink(path):
708 """check whether the given path is on a symlink-capable filesystem"""
709 """check whether the given path is on a symlink-capable filesystem"""
709 # mktemp is not racy because symlink creation will fail if the
710 # mktemp is not racy because symlink creation will fail if the
710 # file already exists
711 # file already exists
711 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
712 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
712 try:
713 try:
713 os.symlink(".", name)
714 os.symlink(".", name)
714 os.unlink(name)
715 os.unlink(name)
715 return True
716 return True
716 except (OSError, AttributeError):
717 except (OSError, AttributeError):
717 return False
718 return False
718
719
719 def checknlink(testfile):
720 def checknlink(testfile):
720 '''check whether hardlink count reporting works properly'''
721 '''check whether hardlink count reporting works properly'''
721 f = testfile + ".hgtmp"
722 f = testfile + ".hgtmp"
722
723
723 try:
724 try:
724 os_link(testfile, f)
725 os_link(testfile, f)
725 except OSError:
726 except OSError:
726 return False
727 return False
727
728
728 try:
729 try:
729 # nlinks() may behave differently for files on Windows shares if
730 # nlinks() may behave differently for files on Windows shares if
730 # the file is open.
731 # the file is open.
731 fd = open(f)
732 fd = open(f)
732 return nlinks(f) > 1
733 return nlinks(f) > 1
733 finally:
734 finally:
734 fd.close()
735 fd.close()
735 os.unlink(f)
736 os.unlink(f)
736
737
737 return False
738 return False
738
739
739 def endswithsep(path):
740 def endswithsep(path):
740 '''Check path ends with os.sep or os.altsep.'''
741 '''Check path ends with os.sep or os.altsep.'''
741 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
742 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
742
743
743 def splitpath(path):
744 def splitpath(path):
744 '''Split path by os.sep.
745 '''Split path by os.sep.
745 Note that this function does not use os.altsep because this is
746 Note that this function does not use os.altsep because this is
746 an alternative of simple "xxx.split(os.sep)".
747 an alternative of simple "xxx.split(os.sep)".
747 It is recommended to use os.path.normpath() before using this
748 It is recommended to use os.path.normpath() before using this
748 function if need.'''
749 function if need.'''
749 return path.split(os.sep)
750 return path.split(os.sep)
750
751
751 def gui():
752 def gui():
752 '''Are we running in a GUI?'''
753 '''Are we running in a GUI?'''
753 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
754 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
754
755
755 def mktempcopy(name, emptyok=False, createmode=None):
756 def mktempcopy(name, emptyok=False, createmode=None):
756 """Create a temporary file with the same contents from name
757 """Create a temporary file with the same contents from name
757
758
758 The permission bits are copied from the original file.
759 The permission bits are copied from the original file.
759
760
760 If the temporary file is going to be truncated immediately, you
761 If the temporary file is going to be truncated immediately, you
761 can use emptyok=True as an optimization.
762 can use emptyok=True as an optimization.
762
763
763 Returns the name of the temporary file.
764 Returns the name of the temporary file.
764 """
765 """
765 d, fn = os.path.split(name)
766 d, fn = os.path.split(name)
766 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
767 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
767 os.close(fd)
768 os.close(fd)
768 # Temporary files are created with mode 0600, which is usually not
769 # Temporary files are created with mode 0600, which is usually not
769 # what we want. If the original file already exists, just copy
770 # what we want. If the original file already exists, just copy
770 # its mode. Otherwise, manually obey umask.
771 # its mode. Otherwise, manually obey umask.
771 try:
772 try:
772 st_mode = os.lstat(name).st_mode & 0777
773 st_mode = os.lstat(name).st_mode & 0777
773 except OSError, inst:
774 except OSError, inst:
774 if inst.errno != errno.ENOENT:
775 if inst.errno != errno.ENOENT:
775 raise
776 raise
776 st_mode = createmode
777 st_mode = createmode
777 if st_mode is None:
778 if st_mode is None:
778 st_mode = ~umask
779 st_mode = ~umask
779 st_mode &= 0666
780 st_mode &= 0666
780 os.chmod(temp, st_mode)
781 os.chmod(temp, st_mode)
781 if emptyok:
782 if emptyok:
782 return temp
783 return temp
783 try:
784 try:
784 try:
785 try:
785 ifp = posixfile(name, "rb")
786 ifp = posixfile(name, "rb")
786 except IOError, inst:
787 except IOError, inst:
787 if inst.errno == errno.ENOENT:
788 if inst.errno == errno.ENOENT:
788 return temp
789 return temp
789 if not getattr(inst, 'filename', None):
790 if not getattr(inst, 'filename', None):
790 inst.filename = name
791 inst.filename = name
791 raise
792 raise
792 ofp = posixfile(temp, "wb")
793 ofp = posixfile(temp, "wb")
793 for chunk in filechunkiter(ifp):
794 for chunk in filechunkiter(ifp):
794 ofp.write(chunk)
795 ofp.write(chunk)
795 ifp.close()
796 ifp.close()
796 ofp.close()
797 ofp.close()
797 except:
798 except:
798 try: os.unlink(temp)
799 try: os.unlink(temp)
799 except: pass
800 except: pass
800 raise
801 raise
801 return temp
802 return temp
802
803
803 class atomictempfile(object):
804 class atomictempfile(object):
804 """file-like object that atomically updates a file
805 """file-like object that atomically updates a file
805
806
806 All writes will be redirected to a temporary copy of the original
807 All writes will be redirected to a temporary copy of the original
807 file. When rename is called, the copy is renamed to the original
808 file. When rename is called, the copy is renamed to the original
808 name, making the changes visible.
809 name, making the changes visible.
809 """
810 """
810 def __init__(self, name, mode='w+b', createmode=None):
811 def __init__(self, name, mode='w+b', createmode=None):
811 self.__name = name
812 self.__name = name
812 self._fp = None
813 self._fp = None
813 self.temp = mktempcopy(name, emptyok=('w' in mode),
814 self.temp = mktempcopy(name, emptyok=('w' in mode),
814 createmode=createmode)
815 createmode=createmode)
815 self._fp = posixfile(self.temp, mode)
816 self._fp = posixfile(self.temp, mode)
816
817
817 def __getattr__(self, name):
818 def __getattr__(self, name):
818 return getattr(self._fp, name)
819 return getattr(self._fp, name)
819
820
820 def rename(self):
821 def rename(self):
821 if not self._fp.closed:
822 if not self._fp.closed:
822 self._fp.close()
823 self._fp.close()
823 rename(self.temp, localpath(self.__name))
824 rename(self.temp, localpath(self.__name))
824
825
825 def __del__(self):
826 def __del__(self):
826 if not self._fp:
827 if not self._fp:
827 return
828 return
828 if not self._fp.closed:
829 if not self._fp.closed:
829 try:
830 try:
830 os.unlink(self.temp)
831 os.unlink(self.temp)
831 except: pass
832 except: pass
832 self._fp.close()
833 self._fp.close()
833
834
834 def makedirs(name, mode=None):
835 def makedirs(name, mode=None):
835 """recursive directory creation with parent mode inheritance"""
836 """recursive directory creation with parent mode inheritance"""
836 parent = os.path.abspath(os.path.dirname(name))
837 parent = os.path.abspath(os.path.dirname(name))
837 try:
838 try:
838 os.mkdir(name)
839 os.mkdir(name)
839 if mode is not None:
840 if mode is not None:
840 os.chmod(name, mode)
841 os.chmod(name, mode)
841 return
842 return
842 except OSError, err:
843 except OSError, err:
843 if err.errno == errno.EEXIST:
844 if err.errno == errno.EEXIST:
844 return
845 return
845 if not name or parent == name or err.errno != errno.ENOENT:
846 if not name or parent == name or err.errno != errno.ENOENT:
846 raise
847 raise
847 makedirs(parent, mode)
848 makedirs(parent, mode)
848 makedirs(name, mode)
849 makedirs(name, mode)
849
850
850 class opener(object):
851 class opener(object):
851 """Open files relative to a base directory
852 """Open files relative to a base directory
852
853
853 This class is used to hide the details of COW semantics and
854 This class is used to hide the details of COW semantics and
854 remote file access from higher level code.
855 remote file access from higher level code.
855 """
856 """
856 def __init__(self, base, audit=True):
857 def __init__(self, base, audit=True):
857 self.base = base
858 self.base = base
858 if audit:
859 if audit:
859 self.auditor = path_auditor(base)
860 self.auditor = path_auditor(base)
860 else:
861 else:
861 self.auditor = always
862 self.auditor = always
862 self.createmode = None
863 self.createmode = None
863 self._trustnlink = None
864 self._trustnlink = None
864
865
865 @propertycache
866 @propertycache
866 def _can_symlink(self):
867 def _can_symlink(self):
867 return checklink(self.base)
868 return checklink(self.base)
868
869
869 def _fixfilemode(self, name):
870 def _fixfilemode(self, name):
870 if self.createmode is None:
871 if self.createmode is None:
871 return
872 return
872 os.chmod(name, self.createmode & 0666)
873 os.chmod(name, self.createmode & 0666)
873
874
874 def __call__(self, path, mode="r", text=False, atomictemp=False):
875 def __call__(self, path, mode="r", text=False, atomictemp=False):
875 self.auditor(path)
876 self.auditor(path)
876 f = os.path.join(self.base, path)
877 f = os.path.join(self.base, path)
877
878
878 if not text and "b" not in mode:
879 if not text and "b" not in mode:
879 mode += "b" # for that other OS
880 mode += "b" # for that other OS
880
881
881 nlink = -1
882 nlink = -1
882 st_mode = None
883 st_mode = None
883 dirname, basename = os.path.split(f)
884 dirname, basename = os.path.split(f)
884 # If basename is empty, then the path is malformed because it points
885 # If basename is empty, then the path is malformed because it points
885 # to a directory. Let the posixfile() call below raise IOError.
886 # to a directory. Let the posixfile() call below raise IOError.
886 if basename and mode not in ('r', 'rb'):
887 if basename and mode not in ('r', 'rb'):
887 if atomictemp:
888 if atomictemp:
888 if not os.path.isdir(dirname):
889 if not os.path.isdir(dirname):
889 makedirs(dirname, self.createmode)
890 makedirs(dirname, self.createmode)
890 return atomictempfile(f, mode, self.createmode)
891 return atomictempfile(f, mode, self.createmode)
891 try:
892 try:
892 if 'w' in mode:
893 if 'w' in mode:
893 st_mode = os.lstat(f).st_mode & 0777
894 st_mode = os.lstat(f).st_mode & 0777
894 os.unlink(f)
895 os.unlink(f)
895 nlink = 0
896 nlink = 0
896 else:
897 else:
897 # nlinks() may behave differently for files on Windows
898 # nlinks() may behave differently for files on Windows
898 # shares if the file is open.
899 # shares if the file is open.
899 fd = open(f)
900 fd = open(f)
900 nlink = nlinks(f)
901 nlink = nlinks(f)
901 fd.close()
902 fd.close()
902 except (OSError, IOError):
903 except (OSError, IOError):
903 nlink = 0
904 nlink = 0
904 if not os.path.isdir(dirname):
905 if not os.path.isdir(dirname):
905 makedirs(dirname, self.createmode)
906 makedirs(dirname, self.createmode)
906 if nlink > 0:
907 if nlink > 0:
907 if self._trustnlink is None:
908 if self._trustnlink is None:
908 self._trustnlink = nlink > 1 or checknlink(f)
909 self._trustnlink = nlink > 1 or checknlink(f)
909 if nlink > 1 or not self._trustnlink:
910 if nlink > 1 or not self._trustnlink:
910 rename(mktempcopy(f), f)
911 rename(mktempcopy(f), f)
911 fp = posixfile(f, mode)
912 fp = posixfile(f, mode)
912 if nlink == 0:
913 if nlink == 0:
913 if st_mode is None:
914 if st_mode is None:
914 self._fixfilemode(f)
915 self._fixfilemode(f)
915 else:
916 else:
916 os.chmod(f, st_mode)
917 os.chmod(f, st_mode)
917 return fp
918 return fp
918
919
919 def symlink(self, src, dst):
920 def symlink(self, src, dst):
920 self.auditor(dst)
921 self.auditor(dst)
921 linkname = os.path.join(self.base, dst)
922 linkname = os.path.join(self.base, dst)
922 try:
923 try:
923 os.unlink(linkname)
924 os.unlink(linkname)
924 except OSError:
925 except OSError:
925 pass
926 pass
926
927
927 dirname = os.path.dirname(linkname)
928 dirname = os.path.dirname(linkname)
928 if not os.path.exists(dirname):
929 if not os.path.exists(dirname):
929 makedirs(dirname, self.createmode)
930 makedirs(dirname, self.createmode)
930
931
931 if self._can_symlink:
932 if self._can_symlink:
932 try:
933 try:
933 os.symlink(src, linkname)
934 os.symlink(src, linkname)
934 except OSError, err:
935 except OSError, err:
935 raise OSError(err.errno, _('could not symlink to %r: %s') %
936 raise OSError(err.errno, _('could not symlink to %r: %s') %
936 (src, err.strerror), linkname)
937 (src, err.strerror), linkname)
937 else:
938 else:
938 f = self(dst, "w")
939 f = self(dst, "w")
939 f.write(src)
940 f.write(src)
940 f.close()
941 f.close()
941 self._fixfilemode(dst)
942 self._fixfilemode(dst)
942
943
943 class chunkbuffer(object):
944 class chunkbuffer(object):
944 """Allow arbitrary sized chunks of data to be efficiently read from an
945 """Allow arbitrary sized chunks of data to be efficiently read from an
945 iterator over chunks of arbitrary size."""
946 iterator over chunks of arbitrary size."""
946
947
947 def __init__(self, in_iter):
948 def __init__(self, in_iter):
948 """in_iter is the iterator that's iterating over the input chunks.
949 """in_iter is the iterator that's iterating over the input chunks.
949 targetsize is how big a buffer to try to maintain."""
950 targetsize is how big a buffer to try to maintain."""
950 def splitbig(chunks):
951 def splitbig(chunks):
951 for chunk in chunks:
952 for chunk in chunks:
952 if len(chunk) > 2**20:
953 if len(chunk) > 2**20:
953 pos = 0
954 pos = 0
954 while pos < len(chunk):
955 while pos < len(chunk):
955 end = pos + 2 ** 18
956 end = pos + 2 ** 18
956 yield chunk[pos:end]
957 yield chunk[pos:end]
957 pos = end
958 pos = end
958 else:
959 else:
959 yield chunk
960 yield chunk
960 self.iter = splitbig(in_iter)
961 self.iter = splitbig(in_iter)
961 self._queue = []
962 self._queue = []
962
963
963 def read(self, l):
964 def read(self, l):
964 """Read L bytes of data from the iterator of chunks of data.
965 """Read L bytes of data from the iterator of chunks of data.
965 Returns less than L bytes if the iterator runs dry."""
966 Returns less than L bytes if the iterator runs dry."""
966 left = l
967 left = l
967 buf = ''
968 buf = ''
968 queue = self._queue
969 queue = self._queue
969 while left > 0:
970 while left > 0:
970 # refill the queue
971 # refill the queue
971 if not queue:
972 if not queue:
972 target = 2**18
973 target = 2**18
973 for chunk in self.iter:
974 for chunk in self.iter:
974 queue.append(chunk)
975 queue.append(chunk)
975 target -= len(chunk)
976 target -= len(chunk)
976 if target <= 0:
977 if target <= 0:
977 break
978 break
978 if not queue:
979 if not queue:
979 break
980 break
980
981
981 chunk = queue.pop(0)
982 chunk = queue.pop(0)
982 left -= len(chunk)
983 left -= len(chunk)
983 if left < 0:
984 if left < 0:
984 queue.insert(0, chunk[left:])
985 queue.insert(0, chunk[left:])
985 buf += chunk[:left]
986 buf += chunk[:left]
986 else:
987 else:
987 buf += chunk
988 buf += chunk
988
989
989 return buf
990 return buf
990
991
991 def filechunkiter(f, size=65536, limit=None):
992 def filechunkiter(f, size=65536, limit=None):
992 """Create a generator that produces the data in the file size
993 """Create a generator that produces the data in the file size
993 (default 65536) bytes at a time, up to optional limit (default is
994 (default 65536) bytes at a time, up to optional limit (default is
994 to read all data). Chunks may be less than size bytes if the
995 to read all data). Chunks may be less than size bytes if the
995 chunk is the last chunk in the file, or the file is a socket or
996 chunk is the last chunk in the file, or the file is a socket or
996 some other type of file that sometimes reads less data than is
997 some other type of file that sometimes reads less data than is
997 requested."""
998 requested."""
998 assert size >= 0
999 assert size >= 0
999 assert limit is None or limit >= 0
1000 assert limit is None or limit >= 0
1000 while True:
1001 while True:
1001 if limit is None:
1002 if limit is None:
1002 nbytes = size
1003 nbytes = size
1003 else:
1004 else:
1004 nbytes = min(limit, size)
1005 nbytes = min(limit, size)
1005 s = nbytes and f.read(nbytes)
1006 s = nbytes and f.read(nbytes)
1006 if not s:
1007 if not s:
1007 break
1008 break
1008 if limit:
1009 if limit:
1009 limit -= len(s)
1010 limit -= len(s)
1010 yield s
1011 yield s
1011
1012
1012 def makedate():
1013 def makedate():
1013 lt = time.localtime()
1014 lt = time.localtime()
1014 if lt[8] == 1 and time.daylight:
1015 if lt[8] == 1 and time.daylight:
1015 tz = time.altzone
1016 tz = time.altzone
1016 else:
1017 else:
1017 tz = time.timezone
1018 tz = time.timezone
1018 t = time.mktime(lt)
1019 t = time.mktime(lt)
1019 if t < 0:
1020 if t < 0:
1020 hint = _("check your clock")
1021 hint = _("check your clock")
1021 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1022 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1022 return t, tz
1023 return t, tz
1023
1024
1024 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1025 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1025 """represent a (unixtime, offset) tuple as a localized time.
1026 """represent a (unixtime, offset) tuple as a localized time.
1026 unixtime is seconds since the epoch, and offset is the time zone's
1027 unixtime is seconds since the epoch, and offset is the time zone's
1027 number of seconds away from UTC. if timezone is false, do not
1028 number of seconds away from UTC. if timezone is false, do not
1028 append time zone to string."""
1029 append time zone to string."""
1029 t, tz = date or makedate()
1030 t, tz = date or makedate()
1030 if t < 0:
1031 if t < 0:
1031 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1032 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1032 tz = 0
1033 tz = 0
1033 if "%1" in format or "%2" in format:
1034 if "%1" in format or "%2" in format:
1034 sign = (tz > 0) and "-" or "+"
1035 sign = (tz > 0) and "-" or "+"
1035 minutes = abs(tz) // 60
1036 minutes = abs(tz) // 60
1036 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1037 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1037 format = format.replace("%2", "%02d" % (minutes % 60))
1038 format = format.replace("%2", "%02d" % (minutes % 60))
1038 s = time.strftime(format, time.gmtime(float(t) - tz))
1039 s = time.strftime(format, time.gmtime(float(t) - tz))
1039 return s
1040 return s
1040
1041
1041 def shortdate(date=None):
1042 def shortdate(date=None):
1042 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1043 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1043 return datestr(date, format='%Y-%m-%d')
1044 return datestr(date, format='%Y-%m-%d')
1044
1045
1045 def strdate(string, format, defaults=[]):
1046 def strdate(string, format, defaults=[]):
1046 """parse a localized time string and return a (unixtime, offset) tuple.
1047 """parse a localized time string and return a (unixtime, offset) tuple.
1047 if the string cannot be parsed, ValueError is raised."""
1048 if the string cannot be parsed, ValueError is raised."""
1048 def timezone(string):
1049 def timezone(string):
1049 tz = string.split()[-1]
1050 tz = string.split()[-1]
1050 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1051 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1051 sign = (tz[0] == "+") and 1 or -1
1052 sign = (tz[0] == "+") and 1 or -1
1052 hours = int(tz[1:3])
1053 hours = int(tz[1:3])
1053 minutes = int(tz[3:5])
1054 minutes = int(tz[3:5])
1054 return -sign * (hours * 60 + minutes) * 60
1055 return -sign * (hours * 60 + minutes) * 60
1055 if tz == "GMT" or tz == "UTC":
1056 if tz == "GMT" or tz == "UTC":
1056 return 0
1057 return 0
1057 return None
1058 return None
1058
1059
1059 # NOTE: unixtime = localunixtime + offset
1060 # NOTE: unixtime = localunixtime + offset
1060 offset, date = timezone(string), string
1061 offset, date = timezone(string), string
1061 if offset != None:
1062 if offset != None:
1062 date = " ".join(string.split()[:-1])
1063 date = " ".join(string.split()[:-1])
1063
1064
1064 # add missing elements from defaults
1065 # add missing elements from defaults
1065 for part in defaults:
1066 for part in defaults:
1066 found = [True for p in part if ("%"+p) in format]
1067 found = [True for p in part if ("%"+p) in format]
1067 if not found:
1068 if not found:
1068 date += "@" + defaults[part]
1069 date += "@" + defaults[part]
1069 format += "@%" + part[0]
1070 format += "@%" + part[0]
1070
1071
1071 timetuple = time.strptime(date, format)
1072 timetuple = time.strptime(date, format)
1072 localunixtime = int(calendar.timegm(timetuple))
1073 localunixtime = int(calendar.timegm(timetuple))
1073 if offset is None:
1074 if offset is None:
1074 # local timezone
1075 # local timezone
1075 unixtime = int(time.mktime(timetuple))
1076 unixtime = int(time.mktime(timetuple))
1076 offset = unixtime - localunixtime
1077 offset = unixtime - localunixtime
1077 else:
1078 else:
1078 unixtime = localunixtime + offset
1079 unixtime = localunixtime + offset
1079 return unixtime, offset
1080 return unixtime, offset
1080
1081
1081 def parsedate(date, formats=None, defaults=None):
1082 def parsedate(date, formats=None, defaults=None):
1082 """parse a localized date/time string and return a (unixtime, offset) tuple.
1083 """parse a localized date/time string and return a (unixtime, offset) tuple.
1083
1084
1084 The date may be a "unixtime offset" string or in one of the specified
1085 The date may be a "unixtime offset" string or in one of the specified
1085 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1086 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1086 """
1087 """
1087 if not date:
1088 if not date:
1088 return 0, 0
1089 return 0, 0
1089 if isinstance(date, tuple) and len(date) == 2:
1090 if isinstance(date, tuple) and len(date) == 2:
1090 return date
1091 return date
1091 if not formats:
1092 if not formats:
1092 formats = defaultdateformats
1093 formats = defaultdateformats
1093 date = date.strip()
1094 date = date.strip()
1094 try:
1095 try:
1095 when, offset = map(int, date.split(' '))
1096 when, offset = map(int, date.split(' '))
1096 except ValueError:
1097 except ValueError:
1097 # fill out defaults
1098 # fill out defaults
1098 if not defaults:
1099 if not defaults:
1099 defaults = {}
1100 defaults = {}
1100 now = makedate()
1101 now = makedate()
1101 for part in "d mb yY HI M S".split():
1102 for part in "d mb yY HI M S".split():
1102 if part not in defaults:
1103 if part not in defaults:
1103 if part[0] in "HMS":
1104 if part[0] in "HMS":
1104 defaults[part] = "00"
1105 defaults[part] = "00"
1105 else:
1106 else:
1106 defaults[part] = datestr(now, "%" + part[0])
1107 defaults[part] = datestr(now, "%" + part[0])
1107
1108
1108 for format in formats:
1109 for format in formats:
1109 try:
1110 try:
1110 when, offset = strdate(date, format, defaults)
1111 when, offset = strdate(date, format, defaults)
1111 except (ValueError, OverflowError):
1112 except (ValueError, OverflowError):
1112 pass
1113 pass
1113 else:
1114 else:
1114 break
1115 break
1115 else:
1116 else:
1116 raise Abort(_('invalid date: %r') % date)
1117 raise Abort(_('invalid date: %r') % date)
1117 # validate explicit (probably user-specified) date and
1118 # validate explicit (probably user-specified) date and
1118 # time zone offset. values must fit in signed 32 bits for
1119 # time zone offset. values must fit in signed 32 bits for
1119 # current 32-bit linux runtimes. timezones go from UTC-12
1120 # current 32-bit linux runtimes. timezones go from UTC-12
1120 # to UTC+14
1121 # to UTC+14
1121 if abs(when) > 0x7fffffff:
1122 if abs(when) > 0x7fffffff:
1122 raise Abort(_('date exceeds 32 bits: %d') % when)
1123 raise Abort(_('date exceeds 32 bits: %d') % when)
1123 if when < 0:
1124 if when < 0:
1124 raise Abort(_('negative date value: %d') % when)
1125 raise Abort(_('negative date value: %d') % when)
1125 if offset < -50400 or offset > 43200:
1126 if offset < -50400 or offset > 43200:
1126 raise Abort(_('impossible time zone offset: %d') % offset)
1127 raise Abort(_('impossible time zone offset: %d') % offset)
1127 return when, offset
1128 return when, offset
1128
1129
1129 def matchdate(date):
1130 def matchdate(date):
1130 """Return a function that matches a given date match specifier
1131 """Return a function that matches a given date match specifier
1131
1132
1132 Formats include:
1133 Formats include:
1133
1134
1134 '{date}' match a given date to the accuracy provided
1135 '{date}' match a given date to the accuracy provided
1135
1136
1136 '<{date}' on or before a given date
1137 '<{date}' on or before a given date
1137
1138
1138 '>{date}' on or after a given date
1139 '>{date}' on or after a given date
1139
1140
1140 """
1141 """
1141
1142
1142 def lower(date):
1143 def lower(date):
1143 d = dict(mb="1", d="1")
1144 d = dict(mb="1", d="1")
1144 return parsedate(date, extendeddateformats, d)[0]
1145 return parsedate(date, extendeddateformats, d)[0]
1145
1146
1146 def upper(date):
1147 def upper(date):
1147 d = dict(mb="12", HI="23", M="59", S="59")
1148 d = dict(mb="12", HI="23", M="59", S="59")
1148 for days in "31 30 29".split():
1149 for days in "31 30 29".split():
1149 try:
1150 try:
1150 d["d"] = days
1151 d["d"] = days
1151 return parsedate(date, extendeddateformats, d)[0]
1152 return parsedate(date, extendeddateformats, d)[0]
1152 except:
1153 except:
1153 pass
1154 pass
1154 d["d"] = "28"
1155 d["d"] = "28"
1155 return parsedate(date, extendeddateformats, d)[0]
1156 return parsedate(date, extendeddateformats, d)[0]
1156
1157
1157 date = date.strip()
1158 date = date.strip()
1158 if date[0] == "<":
1159 if date[0] == "<":
1159 when = upper(date[1:])
1160 when = upper(date[1:])
1160 return lambda x: x <= when
1161 return lambda x: x <= when
1161 elif date[0] == ">":
1162 elif date[0] == ">":
1162 when = lower(date[1:])
1163 when = lower(date[1:])
1163 return lambda x: x >= when
1164 return lambda x: x >= when
1164 elif date[0] == "-":
1165 elif date[0] == "-":
1165 try:
1166 try:
1166 days = int(date[1:])
1167 days = int(date[1:])
1167 except ValueError:
1168 except ValueError:
1168 raise Abort(_("invalid day spec: %s") % date[1:])
1169 raise Abort(_("invalid day spec: %s") % date[1:])
1169 when = makedate()[0] - days * 3600 * 24
1170 when = makedate()[0] - days * 3600 * 24
1170 return lambda x: x >= when
1171 return lambda x: x >= when
1171 elif " to " in date:
1172 elif " to " in date:
1172 a, b = date.split(" to ")
1173 a, b = date.split(" to ")
1173 start, stop = lower(a), upper(b)
1174 start, stop = lower(a), upper(b)
1174 return lambda x: x >= start and x <= stop
1175 return lambda x: x >= start and x <= stop
1175 else:
1176 else:
1176 start, stop = lower(date), upper(date)
1177 start, stop = lower(date), upper(date)
1177 return lambda x: x >= start and x <= stop
1178 return lambda x: x >= start and x <= stop
1178
1179
1179 def shortuser(user):
1180 def shortuser(user):
1180 """Return a short representation of a user name or email address."""
1181 """Return a short representation of a user name or email address."""
1181 f = user.find('@')
1182 f = user.find('@')
1182 if f >= 0:
1183 if f >= 0:
1183 user = user[:f]
1184 user = user[:f]
1184 f = user.find('<')
1185 f = user.find('<')
1185 if f >= 0:
1186 if f >= 0:
1186 user = user[f + 1:]
1187 user = user[f + 1:]
1187 f = user.find(' ')
1188 f = user.find(' ')
1188 if f >= 0:
1189 if f >= 0:
1189 user = user[:f]
1190 user = user[:f]
1190 f = user.find('.')
1191 f = user.find('.')
1191 if f >= 0:
1192 if f >= 0:
1192 user = user[:f]
1193 user = user[:f]
1193 return user
1194 return user
1194
1195
1195 def email(author):
1196 def email(author):
1196 '''get email of author.'''
1197 '''get email of author.'''
1197 r = author.find('>')
1198 r = author.find('>')
1198 if r == -1:
1199 if r == -1:
1199 r = None
1200 r = None
1200 return author[author.find('<') + 1:r]
1201 return author[author.find('<') + 1:r]
1201
1202
1202 def ellipsis(text, maxlength=400):
1203 def ellipsis(text, maxlength=400):
1203 """Trim string to at most maxlength (default: 400) characters."""
1204 """Trim string to at most maxlength (default: 400) characters."""
1204 if len(text) <= maxlength:
1205 if len(text) <= maxlength:
1205 return text
1206 return text
1206 else:
1207 else:
1207 return "%s..." % (text[:maxlength - 3])
1208 return "%s..." % (text[:maxlength - 3])
1208
1209
1209 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1210 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1210 '''yield every hg repository under path, recursively.'''
1211 '''yield every hg repository under path, recursively.'''
1211 def errhandler(err):
1212 def errhandler(err):
1212 if err.filename == path:
1213 if err.filename == path:
1213 raise err
1214 raise err
1214 if followsym and hasattr(os.path, 'samestat'):
1215 if followsym and hasattr(os.path, 'samestat'):
1215 def _add_dir_if_not_there(dirlst, dirname):
1216 def _add_dir_if_not_there(dirlst, dirname):
1216 match = False
1217 match = False
1217 samestat = os.path.samestat
1218 samestat = os.path.samestat
1218 dirstat = os.stat(dirname)
1219 dirstat = os.stat(dirname)
1219 for lstdirstat in dirlst:
1220 for lstdirstat in dirlst:
1220 if samestat(dirstat, lstdirstat):
1221 if samestat(dirstat, lstdirstat):
1221 match = True
1222 match = True
1222 break
1223 break
1223 if not match:
1224 if not match:
1224 dirlst.append(dirstat)
1225 dirlst.append(dirstat)
1225 return not match
1226 return not match
1226 else:
1227 else:
1227 followsym = False
1228 followsym = False
1228
1229
1229 if (seen_dirs is None) and followsym:
1230 if (seen_dirs is None) and followsym:
1230 seen_dirs = []
1231 seen_dirs = []
1231 _add_dir_if_not_there(seen_dirs, path)
1232 _add_dir_if_not_there(seen_dirs, path)
1232 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1233 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1233 dirs.sort()
1234 dirs.sort()
1234 if '.hg' in dirs:
1235 if '.hg' in dirs:
1235 yield root # found a repository
1236 yield root # found a repository
1236 qroot = os.path.join(root, '.hg', 'patches')
1237 qroot = os.path.join(root, '.hg', 'patches')
1237 if os.path.isdir(os.path.join(qroot, '.hg')):
1238 if os.path.isdir(os.path.join(qroot, '.hg')):
1238 yield qroot # we have a patch queue repo here
1239 yield qroot # we have a patch queue repo here
1239 if recurse:
1240 if recurse:
1240 # avoid recursing inside the .hg directory
1241 # avoid recursing inside the .hg directory
1241 dirs.remove('.hg')
1242 dirs.remove('.hg')
1242 else:
1243 else:
1243 dirs[:] = [] # don't descend further
1244 dirs[:] = [] # don't descend further
1244 elif followsym:
1245 elif followsym:
1245 newdirs = []
1246 newdirs = []
1246 for d in dirs:
1247 for d in dirs:
1247 fname = os.path.join(root, d)
1248 fname = os.path.join(root, d)
1248 if _add_dir_if_not_there(seen_dirs, fname):
1249 if _add_dir_if_not_there(seen_dirs, fname):
1249 if os.path.islink(fname):
1250 if os.path.islink(fname):
1250 for hgname in walkrepos(fname, True, seen_dirs):
1251 for hgname in walkrepos(fname, True, seen_dirs):
1251 yield hgname
1252 yield hgname
1252 else:
1253 else:
1253 newdirs.append(d)
1254 newdirs.append(d)
1254 dirs[:] = newdirs
1255 dirs[:] = newdirs
1255
1256
1256 _rcpath = None
1257 _rcpath = None
1257
1258
1258 def os_rcpath():
1259 def os_rcpath():
1259 '''return default os-specific hgrc search path'''
1260 '''return default os-specific hgrc search path'''
1260 path = system_rcpath()
1261 path = system_rcpath()
1261 path.extend(user_rcpath())
1262 path.extend(user_rcpath())
1262 path = [os.path.normpath(f) for f in path]
1263 path = [os.path.normpath(f) for f in path]
1263 return path
1264 return path
1264
1265
1265 def rcpath():
1266 def rcpath():
1266 '''return hgrc search path. if env var HGRCPATH is set, use it.
1267 '''return hgrc search path. if env var HGRCPATH is set, use it.
1267 for each item in path, if directory, use files ending in .rc,
1268 for each item in path, if directory, use files ending in .rc,
1268 else use item.
1269 else use item.
1269 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1270 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1270 if no HGRCPATH, use default os-specific path.'''
1271 if no HGRCPATH, use default os-specific path.'''
1271 global _rcpath
1272 global _rcpath
1272 if _rcpath is None:
1273 if _rcpath is None:
1273 if 'HGRCPATH' in os.environ:
1274 if 'HGRCPATH' in os.environ:
1274 _rcpath = []
1275 _rcpath = []
1275 for p in os.environ['HGRCPATH'].split(os.pathsep):
1276 for p in os.environ['HGRCPATH'].split(os.pathsep):
1276 if not p:
1277 if not p:
1277 continue
1278 continue
1278 p = expandpath(p)
1279 p = expandpath(p)
1279 if os.path.isdir(p):
1280 if os.path.isdir(p):
1280 for f, kind in osutil.listdir(p):
1281 for f, kind in osutil.listdir(p):
1281 if f.endswith('.rc'):
1282 if f.endswith('.rc'):
1282 _rcpath.append(os.path.join(p, f))
1283 _rcpath.append(os.path.join(p, f))
1283 else:
1284 else:
1284 _rcpath.append(p)
1285 _rcpath.append(p)
1285 else:
1286 else:
1286 _rcpath = os_rcpath()
1287 _rcpath = os_rcpath()
1287 return _rcpath
1288 return _rcpath
1288
1289
1289 def bytecount(nbytes):
1290 def bytecount(nbytes):
1290 '''return byte count formatted as readable string, with units'''
1291 '''return byte count formatted as readable string, with units'''
1291
1292
1292 units = (
1293 units = (
1293 (100, 1 << 30, _('%.0f GB')),
1294 (100, 1 << 30, _('%.0f GB')),
1294 (10, 1 << 30, _('%.1f GB')),
1295 (10, 1 << 30, _('%.1f GB')),
1295 (1, 1 << 30, _('%.2f GB')),
1296 (1, 1 << 30, _('%.2f GB')),
1296 (100, 1 << 20, _('%.0f MB')),
1297 (100, 1 << 20, _('%.0f MB')),
1297 (10, 1 << 20, _('%.1f MB')),
1298 (10, 1 << 20, _('%.1f MB')),
1298 (1, 1 << 20, _('%.2f MB')),
1299 (1, 1 << 20, _('%.2f MB')),
1299 (100, 1 << 10, _('%.0f KB')),
1300 (100, 1 << 10, _('%.0f KB')),
1300 (10, 1 << 10, _('%.1f KB')),
1301 (10, 1 << 10, _('%.1f KB')),
1301 (1, 1 << 10, _('%.2f KB')),
1302 (1, 1 << 10, _('%.2f KB')),
1302 (1, 1, _('%.0f bytes')),
1303 (1, 1, _('%.0f bytes')),
1303 )
1304 )
1304
1305
1305 for multiplier, divisor, format in units:
1306 for multiplier, divisor, format in units:
1306 if nbytes >= divisor * multiplier:
1307 if nbytes >= divisor * multiplier:
1307 return format % (nbytes / float(divisor))
1308 return format % (nbytes / float(divisor))
1308 return units[-1][2] % nbytes
1309 return units[-1][2] % nbytes
1309
1310
1310 def drop_scheme(scheme, path):
1311 def drop_scheme(scheme, path):
1311 sc = scheme + ':'
1312 sc = scheme + ':'
1312 if path.startswith(sc):
1313 if path.startswith(sc):
1313 path = path[len(sc):]
1314 path = path[len(sc):]
1314 if path.startswith('//'):
1315 if path.startswith('//'):
1315 if scheme == 'file':
1316 if scheme == 'file':
1316 i = path.find('/', 2)
1317 i = path.find('/', 2)
1317 if i == -1:
1318 if i == -1:
1318 return ''
1319 return ''
1319 # On Windows, absolute paths are rooted at the current drive
1320 # On Windows, absolute paths are rooted at the current drive
1320 # root. On POSIX they are rooted at the file system root.
1321 # root. On POSIX they are rooted at the file system root.
1321 if os.name == 'nt':
1322 if os.name == 'nt':
1322 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1323 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1323 path = os.path.join(droot, path[i + 1:])
1324 path = os.path.join(droot, path[i + 1:])
1324 else:
1325 else:
1325 path = path[i:]
1326 path = path[i:]
1326 else:
1327 else:
1327 path = path[2:]
1328 path = path[2:]
1328 return path
1329 return path
1329
1330
1330 def uirepr(s):
1331 def uirepr(s):
1331 # Avoid double backslash in Windows path repr()
1332 # Avoid double backslash in Windows path repr()
1332 return repr(s).replace('\\\\', '\\')
1333 return repr(s).replace('\\\\', '\\')
1333
1334
1334 #### naming convention of below implementation follows 'textwrap' module
1335 #### naming convention of below implementation follows 'textwrap' module
1335
1336
1336 class MBTextWrapper(textwrap.TextWrapper):
1337 class MBTextWrapper(textwrap.TextWrapper):
1337 def __init__(self, **kwargs):
1338 def __init__(self, **kwargs):
1338 textwrap.TextWrapper.__init__(self, **kwargs)
1339 textwrap.TextWrapper.__init__(self, **kwargs)
1339
1340
1340 def _cutdown(self, str, space_left):
1341 def _cutdown(self, str, space_left):
1341 l = 0
1342 l = 0
1342 ucstr = unicode(str, encoding.encoding)
1343 ucstr = unicode(str, encoding.encoding)
1343 w = unicodedata.east_asian_width
1344 w = unicodedata.east_asian_width
1344 for i in xrange(len(ucstr)):
1345 for i in xrange(len(ucstr)):
1345 l += w(ucstr[i]) in 'WFA' and 2 or 1
1346 l += w(ucstr[i]) in 'WFA' and 2 or 1
1346 if space_left < l:
1347 if space_left < l:
1347 return (ucstr[:i].encode(encoding.encoding),
1348 return (ucstr[:i].encode(encoding.encoding),
1348 ucstr[i:].encode(encoding.encoding))
1349 ucstr[i:].encode(encoding.encoding))
1349 return str, ''
1350 return str, ''
1350
1351
1351 # ----------------------------------------
1352 # ----------------------------------------
1352 # overriding of base class
1353 # overriding of base class
1353
1354
1354 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1355 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1355 space_left = max(width - cur_len, 1)
1356 space_left = max(width - cur_len, 1)
1356
1357
1357 if self.break_long_words:
1358 if self.break_long_words:
1358 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1359 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1359 cur_line.append(cut)
1360 cur_line.append(cut)
1360 reversed_chunks[-1] = res
1361 reversed_chunks[-1] = res
1361 elif not cur_line:
1362 elif not cur_line:
1362 cur_line.append(reversed_chunks.pop())
1363 cur_line.append(reversed_chunks.pop())
1363
1364
1364 #### naming convention of above implementation follows 'textwrap' module
1365 #### naming convention of above implementation follows 'textwrap' module
1365
1366
1366 def wrap(line, width, initindent='', hangindent=''):
1367 def wrap(line, width, initindent='', hangindent=''):
1367 maxindent = max(len(hangindent), len(initindent))
1368 maxindent = max(len(hangindent), len(initindent))
1368 if width <= maxindent:
1369 if width <= maxindent:
1369 # adjust for weird terminal size
1370 # adjust for weird terminal size
1370 width = max(78, maxindent + 1)
1371 width = max(78, maxindent + 1)
1371 wrapper = MBTextWrapper(width=width,
1372 wrapper = MBTextWrapper(width=width,
1372 initial_indent=initindent,
1373 initial_indent=initindent,
1373 subsequent_indent=hangindent)
1374 subsequent_indent=hangindent)
1374 return wrapper.fill(line)
1375 return wrapper.fill(line)
1375
1376
1376 def iterlines(iterator):
1377 def iterlines(iterator):
1377 for chunk in iterator:
1378 for chunk in iterator:
1378 for line in chunk.splitlines():
1379 for line in chunk.splitlines():
1379 yield line
1380 yield line
1380
1381
1381 def expandpath(path):
1382 def expandpath(path):
1382 return os.path.expanduser(os.path.expandvars(path))
1383 return os.path.expanduser(os.path.expandvars(path))
1383
1384
1384 def hgcmd():
1385 def hgcmd():
1385 """Return the command used to execute current hg
1386 """Return the command used to execute current hg
1386
1387
1387 This is different from hgexecutable() because on Windows we want
1388 This is different from hgexecutable() because on Windows we want
1388 to avoid things opening new shell windows like batch files, so we
1389 to avoid things opening new shell windows like batch files, so we
1389 get either the python call or current executable.
1390 get either the python call or current executable.
1390 """
1391 """
1391 if main_is_frozen():
1392 if main_is_frozen():
1392 return [sys.executable]
1393 return [sys.executable]
1393 return gethgcmd()
1394 return gethgcmd()
1394
1395
1395 def rundetached(args, condfn):
1396 def rundetached(args, condfn):
1396 """Execute the argument list in a detached process.
1397 """Execute the argument list in a detached process.
1397
1398
1398 condfn is a callable which is called repeatedly and should return
1399 condfn is a callable which is called repeatedly and should return
1399 True once the child process is known to have started successfully.
1400 True once the child process is known to have started successfully.
1400 At this point, the child process PID is returned. If the child
1401 At this point, the child process PID is returned. If the child
1401 process fails to start or finishes before condfn() evaluates to
1402 process fails to start or finishes before condfn() evaluates to
1402 True, return -1.
1403 True, return -1.
1403 """
1404 """
1404 # Windows case is easier because the child process is either
1405 # Windows case is easier because the child process is either
1405 # successfully starting and validating the condition or exiting
1406 # successfully starting and validating the condition or exiting
1406 # on failure. We just poll on its PID. On Unix, if the child
1407 # on failure. We just poll on its PID. On Unix, if the child
1407 # process fails to start, it will be left in a zombie state until
1408 # process fails to start, it will be left in a zombie state until
1408 # the parent wait on it, which we cannot do since we expect a long
1409 # the parent wait on it, which we cannot do since we expect a long
1409 # running process on success. Instead we listen for SIGCHLD telling
1410 # running process on success. Instead we listen for SIGCHLD telling
1410 # us our child process terminated.
1411 # us our child process terminated.
1411 terminated = set()
1412 terminated = set()
1412 def handler(signum, frame):
1413 def handler(signum, frame):
1413 terminated.add(os.wait())
1414 terminated.add(os.wait())
1414 prevhandler = None
1415 prevhandler = None
1415 if hasattr(signal, 'SIGCHLD'):
1416 if hasattr(signal, 'SIGCHLD'):
1416 prevhandler = signal.signal(signal.SIGCHLD, handler)
1417 prevhandler = signal.signal(signal.SIGCHLD, handler)
1417 try:
1418 try:
1418 pid = spawndetached(args)
1419 pid = spawndetached(args)
1419 while not condfn():
1420 while not condfn():
1420 if ((pid in terminated or not testpid(pid))
1421 if ((pid in terminated or not testpid(pid))
1421 and not condfn()):
1422 and not condfn()):
1422 return -1
1423 return -1
1423 time.sleep(0.1)
1424 time.sleep(0.1)
1424 return pid
1425 return pid
1425 finally:
1426 finally:
1426 if prevhandler is not None:
1427 if prevhandler is not None:
1427 signal.signal(signal.SIGCHLD, prevhandler)
1428 signal.signal(signal.SIGCHLD, prevhandler)
1428
1429
1429 try:
1430 try:
1430 any, all = any, all
1431 any, all = any, all
1431 except NameError:
1432 except NameError:
1432 def any(iterable):
1433 def any(iterable):
1433 for i in iterable:
1434 for i in iterable:
1434 if i:
1435 if i:
1435 return True
1436 return True
1436 return False
1437 return False
1437
1438
1438 def all(iterable):
1439 def all(iterable):
1439 for i in iterable:
1440 for i in iterable:
1440 if not i:
1441 if not i:
1441 return False
1442 return False
1442 return True
1443 return True
1443
1444
1444 def interpolate(prefix, mapping, s, fn=None):
1445 def interpolate(prefix, mapping, s, fn=None):
1445 """Return the result of interpolating items in the mapping into string s.
1446 """Return the result of interpolating items in the mapping into string s.
1446
1447
1447 prefix is a single character string, or a two character string with
1448 prefix is a single character string, or a two character string with
1448 a backslash as the first character if the prefix needs to be escaped in
1449 a backslash as the first character if the prefix needs to be escaped in
1449 a regular expression.
1450 a regular expression.
1450
1451
1451 fn is an optional function that will be applied to the replacement text
1452 fn is an optional function that will be applied to the replacement text
1452 just before replacement.
1453 just before replacement.
1453 """
1454 """
1454 fn = fn or (lambda s: s)
1455 fn = fn or (lambda s: s)
1455 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1456 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1456 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1457 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1457
1458
1458 def getport(port):
1459 def getport(port):
1459 """Return the port for a given network service.
1460 """Return the port for a given network service.
1460
1461
1461 If port is an integer, it's returned as is. If it's a string, it's
1462 If port is an integer, it's returned as is. If it's a string, it's
1462 looked up using socket.getservbyname(). If there's no matching
1463 looked up using socket.getservbyname(). If there's no matching
1463 service, util.Abort is raised.
1464 service, util.Abort is raised.
1464 """
1465 """
1465 try:
1466 try:
1466 return int(port)
1467 return int(port)
1467 except ValueError:
1468 except ValueError:
1468 pass
1469 pass
1469
1470
1470 try:
1471 try:
1471 return socket.getservbyname(port)
1472 return socket.getservbyname(port)
1472 except socket.error:
1473 except socket.error:
1473 raise Abort(_("no port number associated with service '%s'") % port)
1474 raise Abort(_("no port number associated with service '%s'") % port)
1474
1475
1475 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1476 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1476 '0': False, 'no': False, 'false': False, 'off': False,
1477 '0': False, 'no': False, 'false': False, 'off': False,
1477 'never': False}
1478 'never': False}
1478
1479
1479 def parsebool(s):
1480 def parsebool(s):
1480 """Parse s into a boolean.
1481 """Parse s into a boolean.
1481
1482
1482 If s is not a valid boolean, returns None.
1483 If s is not a valid boolean, returns None.
1483 """
1484 """
1484 return _booleans.get(s.lower(), None)
1485 return _booleans.get(s.lower(), None)
General Comments 0
You need to be logged in to leave comments. Login now