##// END OF EJS Templates
util.datestr: do not crash on revisions with negative timestamp (issue2513)...
Adrian Buehlmann -
r13039:dd24f3e7 stable
parent child Browse files
Show More
@@ -1,1475 +1,1478 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':
395 cmd = '"%s"' % cmd
395 cmd = '"%s"' % cmd
396 env = dict(os.environ)
396 env = dict(os.environ)
397 env.update((k, py2shell(v)) for k, v in environ.iteritems())
397 env.update((k, py2shell(v)) for k, v in environ.iteritems())
398 env['HG'] = hgexecutable()
398 env['HG'] = hgexecutable()
399 if out is None:
399 if out is None:
400 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
400 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
401 env=env, cwd=cwd)
401 env=env, cwd=cwd)
402 else:
402 else:
403 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
403 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
404 env=env, cwd=cwd, stdout=subprocess.PIPE,
404 env=env, cwd=cwd, stdout=subprocess.PIPE,
405 stderr=subprocess.STDOUT)
405 stderr=subprocess.STDOUT)
406 for line in proc.stdout:
406 for line in proc.stdout:
407 out.write(line)
407 out.write(line)
408 proc.wait()
408 proc.wait()
409 rc = proc.returncode
409 rc = proc.returncode
410 if sys.platform == 'OpenVMS' and rc & 1:
410 if sys.platform == 'OpenVMS' and rc & 1:
411 rc = 0
411 rc = 0
412 if rc and onerr:
412 if rc and onerr:
413 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
413 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
414 explain_exit(rc)[0])
414 explain_exit(rc)[0])
415 if errprefix:
415 if errprefix:
416 errmsg = '%s: %s' % (errprefix, errmsg)
416 errmsg = '%s: %s' % (errprefix, errmsg)
417 try:
417 try:
418 onerr.warn(errmsg + '\n')
418 onerr.warn(errmsg + '\n')
419 except AttributeError:
419 except AttributeError:
420 raise onerr(errmsg)
420 raise onerr(errmsg)
421 return rc
421 return rc
422
422
423 def checksignature(func):
423 def checksignature(func):
424 '''wrap a function with code to check for calling errors'''
424 '''wrap a function with code to check for calling errors'''
425 def check(*args, **kwargs):
425 def check(*args, **kwargs):
426 try:
426 try:
427 return func(*args, **kwargs)
427 return func(*args, **kwargs)
428 except TypeError:
428 except TypeError:
429 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
429 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
430 raise error.SignatureError
430 raise error.SignatureError
431 raise
431 raise
432
432
433 return check
433 return check
434
434
435 def unlink(f):
435 def unlink(f):
436 """unlink and remove the directory if it is empty"""
436 """unlink and remove the directory if it is empty"""
437 os.unlink(f)
437 os.unlink(f)
438 # try removing directories that might now be empty
438 # try removing directories that might now be empty
439 try:
439 try:
440 os.removedirs(os.path.dirname(f))
440 os.removedirs(os.path.dirname(f))
441 except OSError:
441 except OSError:
442 pass
442 pass
443
443
444 def copyfile(src, dest):
444 def copyfile(src, dest):
445 "copy a file, preserving mode and atime/mtime"
445 "copy a file, preserving mode and atime/mtime"
446 if os.path.islink(src):
446 if os.path.islink(src):
447 try:
447 try:
448 os.unlink(dest)
448 os.unlink(dest)
449 except:
449 except:
450 pass
450 pass
451 os.symlink(os.readlink(src), dest)
451 os.symlink(os.readlink(src), dest)
452 else:
452 else:
453 try:
453 try:
454 shutil.copyfile(src, dest)
454 shutil.copyfile(src, dest)
455 shutil.copystat(src, dest)
455 shutil.copystat(src, dest)
456 except shutil.Error, inst:
456 except shutil.Error, inst:
457 raise Abort(str(inst))
457 raise Abort(str(inst))
458
458
459 def copyfiles(src, dst, hardlink=None):
459 def copyfiles(src, dst, hardlink=None):
460 """Copy a directory tree using hardlinks if possible"""
460 """Copy a directory tree using hardlinks if possible"""
461
461
462 if hardlink is None:
462 if hardlink is None:
463 hardlink = (os.stat(src).st_dev ==
463 hardlink = (os.stat(src).st_dev ==
464 os.stat(os.path.dirname(dst)).st_dev)
464 os.stat(os.path.dirname(dst)).st_dev)
465
465
466 num = 0
466 num = 0
467 if os.path.isdir(src):
467 if os.path.isdir(src):
468 os.mkdir(dst)
468 os.mkdir(dst)
469 for name, kind in osutil.listdir(src):
469 for name, kind in osutil.listdir(src):
470 srcname = os.path.join(src, name)
470 srcname = os.path.join(src, name)
471 dstname = os.path.join(dst, name)
471 dstname = os.path.join(dst, name)
472 hardlink, n = copyfiles(srcname, dstname, hardlink)
472 hardlink, n = copyfiles(srcname, dstname, hardlink)
473 num += n
473 num += n
474 else:
474 else:
475 if hardlink:
475 if hardlink:
476 try:
476 try:
477 os_link(src, dst)
477 os_link(src, dst)
478 except (IOError, OSError):
478 except (IOError, OSError):
479 hardlink = False
479 hardlink = False
480 shutil.copy(src, dst)
480 shutil.copy(src, dst)
481 else:
481 else:
482 shutil.copy(src, dst)
482 shutil.copy(src, dst)
483 num += 1
483 num += 1
484
484
485 return hardlink, num
485 return hardlink, num
486
486
487 class path_auditor(object):
487 class path_auditor(object):
488 '''ensure that a filesystem path contains no banned components.
488 '''ensure that a filesystem path contains no banned components.
489 the following properties of a path are checked:
489 the following properties of a path are checked:
490
490
491 - under top-level .hg
491 - under top-level .hg
492 - starts at the root of a windows drive
492 - starts at the root of a windows drive
493 - contains ".."
493 - contains ".."
494 - traverses a symlink (e.g. a/symlink_here/b)
494 - traverses a symlink (e.g. a/symlink_here/b)
495 - inside a nested repository (a callback can be used to approve
495 - inside a nested repository (a callback can be used to approve
496 some nested repositories, e.g., subrepositories)
496 some nested repositories, e.g., subrepositories)
497 '''
497 '''
498
498
499 def __init__(self, root, callback=None):
499 def __init__(self, root, callback=None):
500 self.audited = set()
500 self.audited = set()
501 self.auditeddir = set()
501 self.auditeddir = set()
502 self.root = root
502 self.root = root
503 self.callback = callback
503 self.callback = callback
504
504
505 def __call__(self, path):
505 def __call__(self, path):
506 if path in self.audited:
506 if path in self.audited:
507 return
507 return
508 normpath = os.path.normcase(path)
508 normpath = os.path.normcase(path)
509 parts = splitpath(normpath)
509 parts = splitpath(normpath)
510 if (os.path.splitdrive(path)[0]
510 if (os.path.splitdrive(path)[0]
511 or parts[0].lower() in ('.hg', '.hg.', '')
511 or parts[0].lower() in ('.hg', '.hg.', '')
512 or os.pardir in parts):
512 or os.pardir in parts):
513 raise Abort(_("path contains illegal component: %s") % path)
513 raise Abort(_("path contains illegal component: %s") % path)
514 if '.hg' in path.lower():
514 if '.hg' in path.lower():
515 lparts = [p.lower() for p in parts]
515 lparts = [p.lower() for p in parts]
516 for p in '.hg', '.hg.':
516 for p in '.hg', '.hg.':
517 if p in lparts[1:]:
517 if p in lparts[1:]:
518 pos = lparts.index(p)
518 pos = lparts.index(p)
519 base = os.path.join(*parts[:pos])
519 base = os.path.join(*parts[:pos])
520 raise Abort(_('path %r is inside repo %r') % (path, base))
520 raise Abort(_('path %r is inside repo %r') % (path, base))
521 def check(prefix):
521 def check(prefix):
522 curpath = os.path.join(self.root, prefix)
522 curpath = os.path.join(self.root, prefix)
523 try:
523 try:
524 st = os.lstat(curpath)
524 st = os.lstat(curpath)
525 except OSError, err:
525 except OSError, err:
526 # EINVAL can be raised as invalid path syntax under win32.
526 # EINVAL can be raised as invalid path syntax under win32.
527 # They must be ignored for patterns can be checked too.
527 # They must be ignored for patterns can be checked too.
528 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
528 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
529 raise
529 raise
530 else:
530 else:
531 if stat.S_ISLNK(st.st_mode):
531 if stat.S_ISLNK(st.st_mode):
532 raise Abort(_('path %r traverses symbolic link %r') %
532 raise Abort(_('path %r traverses symbolic link %r') %
533 (path, prefix))
533 (path, prefix))
534 elif (stat.S_ISDIR(st.st_mode) and
534 elif (stat.S_ISDIR(st.st_mode) and
535 os.path.isdir(os.path.join(curpath, '.hg'))):
535 os.path.isdir(os.path.join(curpath, '.hg'))):
536 if not self.callback or not self.callback(curpath):
536 if not self.callback or not self.callback(curpath):
537 raise Abort(_('path %r is inside repo %r') %
537 raise Abort(_('path %r is inside repo %r') %
538 (path, prefix))
538 (path, prefix))
539 parts.pop()
539 parts.pop()
540 prefixes = []
540 prefixes = []
541 while parts:
541 while parts:
542 prefix = os.sep.join(parts)
542 prefix = os.sep.join(parts)
543 if prefix in self.auditeddir:
543 if prefix in self.auditeddir:
544 break
544 break
545 check(prefix)
545 check(prefix)
546 prefixes.append(prefix)
546 prefixes.append(prefix)
547 parts.pop()
547 parts.pop()
548
548
549 self.audited.add(path)
549 self.audited.add(path)
550 # only add prefixes to the cache after checking everything: we don't
550 # 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"
551 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
552 self.auditeddir.update(prefixes)
552 self.auditeddir.update(prefixes)
553
553
554 def nlinks(pathname):
554 def nlinks(pathname):
555 """Return number of hardlinks for the given file."""
555 """Return number of hardlinks for the given file."""
556 return os.lstat(pathname).st_nlink
556 return os.lstat(pathname).st_nlink
557
557
558 if hasattr(os, 'link'):
558 if hasattr(os, 'link'):
559 os_link = os.link
559 os_link = os.link
560 else:
560 else:
561 def os_link(src, dst):
561 def os_link(src, dst):
562 raise OSError(0, _("Hardlinks not supported"))
562 raise OSError(0, _("Hardlinks not supported"))
563
563
564 def lookup_reg(key, name=None, scope=None):
564 def lookup_reg(key, name=None, scope=None):
565 return None
565 return None
566
566
567 def hidewindow():
567 def hidewindow():
568 """Hide current shell window.
568 """Hide current shell window.
569
569
570 Used to hide the window opened when starting asynchronous
570 Used to hide the window opened when starting asynchronous
571 child process under Windows, unneeded on other systems.
571 child process under Windows, unneeded on other systems.
572 """
572 """
573 pass
573 pass
574
574
575 if os.name == 'nt':
575 if os.name == 'nt':
576 from windows import *
576 from windows import *
577 else:
577 else:
578 from posix import *
578 from posix import *
579
579
580 def makelock(info, pathname):
580 def makelock(info, pathname):
581 try:
581 try:
582 return os.symlink(info, pathname)
582 return os.symlink(info, pathname)
583 except OSError, why:
583 except OSError, why:
584 if why.errno == errno.EEXIST:
584 if why.errno == errno.EEXIST:
585 raise
585 raise
586 except AttributeError: # no symlink in os
586 except AttributeError: # no symlink in os
587 pass
587 pass
588
588
589 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
589 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
590 os.write(ld, info)
590 os.write(ld, info)
591 os.close(ld)
591 os.close(ld)
592
592
593 def readlock(pathname):
593 def readlock(pathname):
594 try:
594 try:
595 return os.readlink(pathname)
595 return os.readlink(pathname)
596 except OSError, why:
596 except OSError, why:
597 if why.errno not in (errno.EINVAL, errno.ENOSYS):
597 if why.errno not in (errno.EINVAL, errno.ENOSYS):
598 raise
598 raise
599 except AttributeError: # no symlink in os
599 except AttributeError: # no symlink in os
600 pass
600 pass
601 return posixfile(pathname).read()
601 return posixfile(pathname).read()
602
602
603 def fstat(fp):
603 def fstat(fp):
604 '''stat file object that may not have fileno method.'''
604 '''stat file object that may not have fileno method.'''
605 try:
605 try:
606 return os.fstat(fp.fileno())
606 return os.fstat(fp.fileno())
607 except AttributeError:
607 except AttributeError:
608 return os.stat(fp.name)
608 return os.stat(fp.name)
609
609
610 # File system features
610 # File system features
611
611
612 def checkcase(path):
612 def checkcase(path):
613 """
613 """
614 Check whether the given path is on a case-sensitive filesystem
614 Check whether the given path is on a case-sensitive filesystem
615
615
616 Requires a path (like /foo/.hg) ending with a foldable final
616 Requires a path (like /foo/.hg) ending with a foldable final
617 directory component.
617 directory component.
618 """
618 """
619 s1 = os.stat(path)
619 s1 = os.stat(path)
620 d, b = os.path.split(path)
620 d, b = os.path.split(path)
621 p2 = os.path.join(d, b.upper())
621 p2 = os.path.join(d, b.upper())
622 if path == p2:
622 if path == p2:
623 p2 = os.path.join(d, b.lower())
623 p2 = os.path.join(d, b.lower())
624 try:
624 try:
625 s2 = os.stat(p2)
625 s2 = os.stat(p2)
626 if s2 == s1:
626 if s2 == s1:
627 return False
627 return False
628 return True
628 return True
629 except:
629 except:
630 return True
630 return True
631
631
632 _fspathcache = {}
632 _fspathcache = {}
633 def fspath(name, root):
633 def fspath(name, root):
634 '''Get name in the case stored in the filesystem
634 '''Get name in the case stored in the filesystem
635
635
636 The name is either relative to root, or it is an absolute path starting
636 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
637 with root. Note that this function is unnecessary, and should not be
638 called, for case-sensitive filesystems (simply because it's expensive).
638 called, for case-sensitive filesystems (simply because it's expensive).
639 '''
639 '''
640 # If name is absolute, make it relative
640 # If name is absolute, make it relative
641 if name.lower().startswith(root.lower()):
641 if name.lower().startswith(root.lower()):
642 l = len(root)
642 l = len(root)
643 if name[l] == os.sep or name[l] == os.altsep:
643 if name[l] == os.sep or name[l] == os.altsep:
644 l = l + 1
644 l = l + 1
645 name = name[l:]
645 name = name[l:]
646
646
647 if not os.path.lexists(os.path.join(root, name)):
647 if not os.path.lexists(os.path.join(root, name)):
648 return None
648 return None
649
649
650 seps = os.sep
650 seps = os.sep
651 if os.altsep:
651 if os.altsep:
652 seps = seps + os.altsep
652 seps = seps + os.altsep
653 # Protect backslashes. This gets silly very quickly.
653 # Protect backslashes. This gets silly very quickly.
654 seps.replace('\\','\\\\')
654 seps.replace('\\','\\\\')
655 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
655 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
656 dir = os.path.normcase(os.path.normpath(root))
656 dir = os.path.normcase(os.path.normpath(root))
657 result = []
657 result = []
658 for part, sep in pattern.findall(name):
658 for part, sep in pattern.findall(name):
659 if sep:
659 if sep:
660 result.append(sep)
660 result.append(sep)
661 continue
661 continue
662
662
663 if dir not in _fspathcache:
663 if dir not in _fspathcache:
664 _fspathcache[dir] = os.listdir(dir)
664 _fspathcache[dir] = os.listdir(dir)
665 contents = _fspathcache[dir]
665 contents = _fspathcache[dir]
666
666
667 lpart = part.lower()
667 lpart = part.lower()
668 lenp = len(part)
668 lenp = len(part)
669 for n in contents:
669 for n in contents:
670 if lenp == len(n) and n.lower() == lpart:
670 if lenp == len(n) and n.lower() == lpart:
671 result.append(n)
671 result.append(n)
672 break
672 break
673 else:
673 else:
674 # Cannot happen, as the file exists!
674 # Cannot happen, as the file exists!
675 result.append(part)
675 result.append(part)
676 dir = os.path.join(dir, lpart)
676 dir = os.path.join(dir, lpart)
677
677
678 return ''.join(result)
678 return ''.join(result)
679
679
680 def checkexec(path):
680 def checkexec(path):
681 """
681 """
682 Check whether the given path is on a filesystem with UNIX-like exec flags
682 Check whether the given path is on a filesystem with UNIX-like exec flags
683
683
684 Requires a directory (like /foo/.hg)
684 Requires a directory (like /foo/.hg)
685 """
685 """
686
686
687 # VFAT on some Linux versions can flip mode but it doesn't persist
687 # 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
688 # a FS remount. Frequently we can detect it if files are created
689 # with exec bit on.
689 # with exec bit on.
690
690
691 try:
691 try:
692 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
692 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
693 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
693 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
694 try:
694 try:
695 os.close(fh)
695 os.close(fh)
696 m = os.stat(fn).st_mode & 0777
696 m = os.stat(fn).st_mode & 0777
697 new_file_has_exec = m & EXECFLAGS
697 new_file_has_exec = m & EXECFLAGS
698 os.chmod(fn, m ^ EXECFLAGS)
698 os.chmod(fn, m ^ EXECFLAGS)
699 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
699 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
700 finally:
700 finally:
701 os.unlink(fn)
701 os.unlink(fn)
702 except (IOError, OSError):
702 except (IOError, OSError):
703 # we don't care, the user probably won't be able to commit anyway
703 # we don't care, the user probably won't be able to commit anyway
704 return False
704 return False
705 return not (new_file_has_exec or exec_flags_cannot_flip)
705 return not (new_file_has_exec or exec_flags_cannot_flip)
706
706
707 def checklink(path):
707 def checklink(path):
708 """check whether the given path is on a symlink-capable filesystem"""
708 """check whether the given path is on a symlink-capable filesystem"""
709 # mktemp is not racy because symlink creation will fail if the
709 # mktemp is not racy because symlink creation will fail if the
710 # file already exists
710 # file already exists
711 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
711 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
712 try:
712 try:
713 os.symlink(".", name)
713 os.symlink(".", name)
714 os.unlink(name)
714 os.unlink(name)
715 return True
715 return True
716 except (OSError, AttributeError):
716 except (OSError, AttributeError):
717 return False
717 return False
718
718
719 def checknlink(testfile):
719 def checknlink(testfile):
720 '''check whether hardlink count reporting works properly'''
720 '''check whether hardlink count reporting works properly'''
721 f = testfile + ".hgtmp"
721 f = testfile + ".hgtmp"
722
722
723 try:
723 try:
724 os_link(testfile, f)
724 os_link(testfile, f)
725 except OSError:
725 except OSError:
726 return False
726 return False
727
727
728 try:
728 try:
729 # nlinks() may behave differently for files on Windows shares if
729 # nlinks() may behave differently for files on Windows shares if
730 # the file is open.
730 # the file is open.
731 fd = open(f)
731 fd = open(f)
732 return nlinks(f) > 1
732 return nlinks(f) > 1
733 finally:
733 finally:
734 fd.close()
734 fd.close()
735 os.unlink(f)
735 os.unlink(f)
736
736
737 return False
737 return False
738
738
739 def endswithsep(path):
739 def endswithsep(path):
740 '''Check path ends with os.sep or os.altsep.'''
740 '''Check path ends with os.sep or os.altsep.'''
741 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
741 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
742
742
743 def splitpath(path):
743 def splitpath(path):
744 '''Split path by os.sep.
744 '''Split path by os.sep.
745 Note that this function does not use os.altsep because this is
745 Note that this function does not use os.altsep because this is
746 an alternative of simple "xxx.split(os.sep)".
746 an alternative of simple "xxx.split(os.sep)".
747 It is recommended to use os.path.normpath() before using this
747 It is recommended to use os.path.normpath() before using this
748 function if need.'''
748 function if need.'''
749 return path.split(os.sep)
749 return path.split(os.sep)
750
750
751 def gui():
751 def gui():
752 '''Are we running in a GUI?'''
752 '''Are we running in a GUI?'''
753 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
753 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
754
754
755 def mktempcopy(name, emptyok=False, createmode=None):
755 def mktempcopy(name, emptyok=False, createmode=None):
756 """Create a temporary file with the same contents from name
756 """Create a temporary file with the same contents from name
757
757
758 The permission bits are copied from the original file.
758 The permission bits are copied from the original file.
759
759
760 If the temporary file is going to be truncated immediately, you
760 If the temporary file is going to be truncated immediately, you
761 can use emptyok=True as an optimization.
761 can use emptyok=True as an optimization.
762
762
763 Returns the name of the temporary file.
763 Returns the name of the temporary file.
764 """
764 """
765 d, fn = os.path.split(name)
765 d, fn = os.path.split(name)
766 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
766 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
767 os.close(fd)
767 os.close(fd)
768 # Temporary files are created with mode 0600, which is usually not
768 # Temporary files are created with mode 0600, which is usually not
769 # what we want. If the original file already exists, just copy
769 # what we want. If the original file already exists, just copy
770 # its mode. Otherwise, manually obey umask.
770 # its mode. Otherwise, manually obey umask.
771 try:
771 try:
772 st_mode = os.lstat(name).st_mode & 0777
772 st_mode = os.lstat(name).st_mode & 0777
773 except OSError, inst:
773 except OSError, inst:
774 if inst.errno != errno.ENOENT:
774 if inst.errno != errno.ENOENT:
775 raise
775 raise
776 st_mode = createmode
776 st_mode = createmode
777 if st_mode is None:
777 if st_mode is None:
778 st_mode = ~umask
778 st_mode = ~umask
779 st_mode &= 0666
779 st_mode &= 0666
780 os.chmod(temp, st_mode)
780 os.chmod(temp, st_mode)
781 if emptyok:
781 if emptyok:
782 return temp
782 return temp
783 try:
783 try:
784 try:
784 try:
785 ifp = posixfile(name, "rb")
785 ifp = posixfile(name, "rb")
786 except IOError, inst:
786 except IOError, inst:
787 if inst.errno == errno.ENOENT:
787 if inst.errno == errno.ENOENT:
788 return temp
788 return temp
789 if not getattr(inst, 'filename', None):
789 if not getattr(inst, 'filename', None):
790 inst.filename = name
790 inst.filename = name
791 raise
791 raise
792 ofp = posixfile(temp, "wb")
792 ofp = posixfile(temp, "wb")
793 for chunk in filechunkiter(ifp):
793 for chunk in filechunkiter(ifp):
794 ofp.write(chunk)
794 ofp.write(chunk)
795 ifp.close()
795 ifp.close()
796 ofp.close()
796 ofp.close()
797 except:
797 except:
798 try: os.unlink(temp)
798 try: os.unlink(temp)
799 except: pass
799 except: pass
800 raise
800 raise
801 return temp
801 return temp
802
802
803 class atomictempfile(object):
803 class atomictempfile(object):
804 """file-like object that atomically updates a file
804 """file-like object that atomically updates a file
805
805
806 All writes will be redirected to a temporary copy of the original
806 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
807 file. When rename is called, the copy is renamed to the original
808 name, making the changes visible.
808 name, making the changes visible.
809 """
809 """
810 def __init__(self, name, mode='w+b', createmode=None):
810 def __init__(self, name, mode='w+b', createmode=None):
811 self.__name = name
811 self.__name = name
812 self._fp = None
812 self._fp = None
813 self.temp = mktempcopy(name, emptyok=('w' in mode),
813 self.temp = mktempcopy(name, emptyok=('w' in mode),
814 createmode=createmode)
814 createmode=createmode)
815 self._fp = posixfile(self.temp, mode)
815 self._fp = posixfile(self.temp, mode)
816
816
817 def __getattr__(self, name):
817 def __getattr__(self, name):
818 return getattr(self._fp, name)
818 return getattr(self._fp, name)
819
819
820 def rename(self):
820 def rename(self):
821 if not self._fp.closed:
821 if not self._fp.closed:
822 self._fp.close()
822 self._fp.close()
823 rename(self.temp, localpath(self.__name))
823 rename(self.temp, localpath(self.__name))
824
824
825 def __del__(self):
825 def __del__(self):
826 if not self._fp:
826 if not self._fp:
827 return
827 return
828 if not self._fp.closed:
828 if not self._fp.closed:
829 try:
829 try:
830 os.unlink(self.temp)
830 os.unlink(self.temp)
831 except: pass
831 except: pass
832 self._fp.close()
832 self._fp.close()
833
833
834 def makedirs(name, mode=None):
834 def makedirs(name, mode=None):
835 """recursive directory creation with parent mode inheritance"""
835 """recursive directory creation with parent mode inheritance"""
836 try:
836 try:
837 os.mkdir(name)
837 os.mkdir(name)
838 if mode is not None:
838 if mode is not None:
839 os.chmod(name, mode)
839 os.chmod(name, mode)
840 return
840 return
841 except OSError, err:
841 except OSError, err:
842 if err.errno == errno.EEXIST:
842 if err.errno == errno.EEXIST:
843 return
843 return
844 if err.errno != errno.ENOENT:
844 if err.errno != errno.ENOENT:
845 raise
845 raise
846 parent = os.path.abspath(os.path.dirname(name))
846 parent = os.path.abspath(os.path.dirname(name))
847 makedirs(parent, mode)
847 makedirs(parent, mode)
848 makedirs(name, mode)
848 makedirs(name, mode)
849
849
850 class opener(object):
850 class opener(object):
851 """Open files relative to a base directory
851 """Open files relative to a base directory
852
852
853 This class is used to hide the details of COW semantics and
853 This class is used to hide the details of COW semantics and
854 remote file access from higher level code.
854 remote file access from higher level code.
855 """
855 """
856 def __init__(self, base, audit=True):
856 def __init__(self, base, audit=True):
857 self.base = base
857 self.base = base
858 if audit:
858 if audit:
859 self.auditor = path_auditor(base)
859 self.auditor = path_auditor(base)
860 else:
860 else:
861 self.auditor = always
861 self.auditor = always
862 self.createmode = None
862 self.createmode = None
863 self._trustnlink = None
863 self._trustnlink = None
864
864
865 @propertycache
865 @propertycache
866 def _can_symlink(self):
866 def _can_symlink(self):
867 return checklink(self.base)
867 return checklink(self.base)
868
868
869 def _fixfilemode(self, name):
869 def _fixfilemode(self, name):
870 if self.createmode is None:
870 if self.createmode is None:
871 return
871 return
872 os.chmod(name, self.createmode & 0666)
872 os.chmod(name, self.createmode & 0666)
873
873
874 def __call__(self, path, mode="r", text=False, atomictemp=False):
874 def __call__(self, path, mode="r", text=False, atomictemp=False):
875 self.auditor(path)
875 self.auditor(path)
876 f = os.path.join(self.base, path)
876 f = os.path.join(self.base, path)
877
877
878 if not text and "b" not in mode:
878 if not text and "b" not in mode:
879 mode += "b" # for that other OS
879 mode += "b" # for that other OS
880
880
881 nlink = -1
881 nlink = -1
882 st_mode = None
882 st_mode = None
883 dirname, basename = os.path.split(f)
883 dirname, basename = os.path.split(f)
884 # If basename is empty, then the path is malformed because it points
884 # If basename is empty, then the path is malformed because it points
885 # to a directory. Let the posixfile() call below raise IOError.
885 # to a directory. Let the posixfile() call below raise IOError.
886 if basename and mode not in ('r', 'rb'):
886 if basename and mode not in ('r', 'rb'):
887 if atomictemp:
887 if atomictemp:
888 if not os.path.isdir(dirname):
888 if not os.path.isdir(dirname):
889 makedirs(dirname, self.createmode)
889 makedirs(dirname, self.createmode)
890 return atomictempfile(f, mode, self.createmode)
890 return atomictempfile(f, mode, self.createmode)
891 try:
891 try:
892 if 'w' in mode:
892 if 'w' in mode:
893 st_mode = os.lstat(f).st_mode & 0777
893 st_mode = os.lstat(f).st_mode & 0777
894 os.unlink(f)
894 os.unlink(f)
895 nlink = 0
895 nlink = 0
896 else:
896 else:
897 # nlinks() may behave differently for files on Windows
897 # nlinks() may behave differently for files on Windows
898 # shares if the file is open.
898 # shares if the file is open.
899 fd = open(f)
899 fd = open(f)
900 nlink = nlinks(f)
900 nlink = nlinks(f)
901 fd.close()
901 fd.close()
902 except (OSError, IOError):
902 except (OSError, IOError):
903 nlink = 0
903 nlink = 0
904 if not os.path.isdir(dirname):
904 if not os.path.isdir(dirname):
905 makedirs(dirname, self.createmode)
905 makedirs(dirname, self.createmode)
906 if nlink > 0:
906 if nlink > 0:
907 if self._trustnlink is None:
907 if self._trustnlink is None:
908 self._trustnlink = nlink > 1 or checknlink(f)
908 self._trustnlink = nlink > 1 or checknlink(f)
909 if nlink > 1 or not self._trustnlink:
909 if nlink > 1 or not self._trustnlink:
910 rename(mktempcopy(f), f)
910 rename(mktempcopy(f), f)
911 fp = posixfile(f, mode)
911 fp = posixfile(f, mode)
912 if nlink == 0:
912 if nlink == 0:
913 if st_mode is None:
913 if st_mode is None:
914 self._fixfilemode(f)
914 self._fixfilemode(f)
915 else:
915 else:
916 os.chmod(f, st_mode)
916 os.chmod(f, st_mode)
917 return fp
917 return fp
918
918
919 def symlink(self, src, dst):
919 def symlink(self, src, dst):
920 self.auditor(dst)
920 self.auditor(dst)
921 linkname = os.path.join(self.base, dst)
921 linkname = os.path.join(self.base, dst)
922 try:
922 try:
923 os.unlink(linkname)
923 os.unlink(linkname)
924 except OSError:
924 except OSError:
925 pass
925 pass
926
926
927 dirname = os.path.dirname(linkname)
927 dirname = os.path.dirname(linkname)
928 if not os.path.exists(dirname):
928 if not os.path.exists(dirname):
929 makedirs(dirname, self.createmode)
929 makedirs(dirname, self.createmode)
930
930
931 if self._can_symlink:
931 if self._can_symlink:
932 try:
932 try:
933 os.symlink(src, linkname)
933 os.symlink(src, linkname)
934 except OSError, err:
934 except OSError, err:
935 raise OSError(err.errno, _('could not symlink to %r: %s') %
935 raise OSError(err.errno, _('could not symlink to %r: %s') %
936 (src, err.strerror), linkname)
936 (src, err.strerror), linkname)
937 else:
937 else:
938 f = self(dst, "w")
938 f = self(dst, "w")
939 f.write(src)
939 f.write(src)
940 f.close()
940 f.close()
941 self._fixfilemode(dst)
941 self._fixfilemode(dst)
942
942
943 class chunkbuffer(object):
943 class chunkbuffer(object):
944 """Allow arbitrary sized chunks of data to be efficiently read from an
944 """Allow arbitrary sized chunks of data to be efficiently read from an
945 iterator over chunks of arbitrary size."""
945 iterator over chunks of arbitrary size."""
946
946
947 def __init__(self, in_iter):
947 def __init__(self, in_iter):
948 """in_iter is the iterator that's iterating over the input chunks.
948 """in_iter is the iterator that's iterating over the input chunks.
949 targetsize is how big a buffer to try to maintain."""
949 targetsize is how big a buffer to try to maintain."""
950 def splitbig(chunks):
950 def splitbig(chunks):
951 for chunk in chunks:
951 for chunk in chunks:
952 if len(chunk) > 2**20:
952 if len(chunk) > 2**20:
953 pos = 0
953 pos = 0
954 while pos < len(chunk):
954 while pos < len(chunk):
955 end = pos + 2 ** 18
955 end = pos + 2 ** 18
956 yield chunk[pos:end]
956 yield chunk[pos:end]
957 pos = end
957 pos = end
958 else:
958 else:
959 yield chunk
959 yield chunk
960 self.iter = splitbig(in_iter)
960 self.iter = splitbig(in_iter)
961 self._queue = []
961 self._queue = []
962
962
963 def read(self, l):
963 def read(self, l):
964 """Read L bytes of data from the iterator of chunks of data.
964 """Read L bytes of data from the iterator of chunks of data.
965 Returns less than L bytes if the iterator runs dry."""
965 Returns less than L bytes if the iterator runs dry."""
966 left = l
966 left = l
967 buf = ''
967 buf = ''
968 queue = self._queue
968 queue = self._queue
969 while left > 0:
969 while left > 0:
970 # refill the queue
970 # refill the queue
971 if not queue:
971 if not queue:
972 target = 2**18
972 target = 2**18
973 for chunk in self.iter:
973 for chunk in self.iter:
974 queue.append(chunk)
974 queue.append(chunk)
975 target -= len(chunk)
975 target -= len(chunk)
976 if target <= 0:
976 if target <= 0:
977 break
977 break
978 if not queue:
978 if not queue:
979 break
979 break
980
980
981 chunk = queue.pop(0)
981 chunk = queue.pop(0)
982 left -= len(chunk)
982 left -= len(chunk)
983 if left < 0:
983 if left < 0:
984 queue.insert(0, chunk[left:])
984 queue.insert(0, chunk[left:])
985 buf += chunk[:left]
985 buf += chunk[:left]
986 else:
986 else:
987 buf += chunk
987 buf += chunk
988
988
989 return buf
989 return buf
990
990
991 def filechunkiter(f, size=65536, limit=None):
991 def filechunkiter(f, size=65536, limit=None):
992 """Create a generator that produces the data in the file size
992 """Create a generator that produces the data in the file size
993 (default 65536) bytes at a time, up to optional limit (default is
993 (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
994 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
995 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
996 some other type of file that sometimes reads less data than is
997 requested."""
997 requested."""
998 assert size >= 0
998 assert size >= 0
999 assert limit is None or limit >= 0
999 assert limit is None or limit >= 0
1000 while True:
1000 while True:
1001 if limit is None:
1001 if limit is None:
1002 nbytes = size
1002 nbytes = size
1003 else:
1003 else:
1004 nbytes = min(limit, size)
1004 nbytes = min(limit, size)
1005 s = nbytes and f.read(nbytes)
1005 s = nbytes and f.read(nbytes)
1006 if not s:
1006 if not s:
1007 break
1007 break
1008 if limit:
1008 if limit:
1009 limit -= len(s)
1009 limit -= len(s)
1010 yield s
1010 yield s
1011
1011
1012 def makedate():
1012 def makedate():
1013 lt = time.localtime()
1013 lt = time.localtime()
1014 if lt[8] == 1 and time.daylight:
1014 if lt[8] == 1 and time.daylight:
1015 tz = time.altzone
1015 tz = time.altzone
1016 else:
1016 else:
1017 tz = time.timezone
1017 tz = time.timezone
1018 return time.mktime(lt), tz
1018 return time.mktime(lt), tz
1019
1019
1020 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1020 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1021 """represent a (unixtime, offset) tuple as a localized time.
1021 """represent a (unixtime, offset) tuple as a localized time.
1022 unixtime is seconds since the epoch, and offset is the time zone's
1022 unixtime is seconds since the epoch, and offset is the time zone's
1023 number of seconds away from UTC. if timezone is false, do not
1023 number of seconds away from UTC. if timezone is false, do not
1024 append time zone to string."""
1024 append time zone to string."""
1025 t, tz = date or makedate()
1025 t, tz = date or makedate()
1026 if t < 0:
1027 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1028 tz = 0
1026 if "%1" in format or "%2" in format:
1029 if "%1" in format or "%2" in format:
1027 sign = (tz > 0) and "-" or "+"
1030 sign = (tz > 0) and "-" or "+"
1028 minutes = abs(tz) // 60
1031 minutes = abs(tz) // 60
1029 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1032 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1030 format = format.replace("%2", "%02d" % (minutes % 60))
1033 format = format.replace("%2", "%02d" % (minutes % 60))
1031 s = time.strftime(format, time.gmtime(float(t) - tz))
1034 s = time.strftime(format, time.gmtime(float(t) - tz))
1032 return s
1035 return s
1033
1036
1034 def shortdate(date=None):
1037 def shortdate(date=None):
1035 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1038 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1036 return datestr(date, format='%Y-%m-%d')
1039 return datestr(date, format='%Y-%m-%d')
1037
1040
1038 def strdate(string, format, defaults=[]):
1041 def strdate(string, format, defaults=[]):
1039 """parse a localized time string and return a (unixtime, offset) tuple.
1042 """parse a localized time string and return a (unixtime, offset) tuple.
1040 if the string cannot be parsed, ValueError is raised."""
1043 if the string cannot be parsed, ValueError is raised."""
1041 def timezone(string):
1044 def timezone(string):
1042 tz = string.split()[-1]
1045 tz = string.split()[-1]
1043 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1046 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1044 sign = (tz[0] == "+") and 1 or -1
1047 sign = (tz[0] == "+") and 1 or -1
1045 hours = int(tz[1:3])
1048 hours = int(tz[1:3])
1046 minutes = int(tz[3:5])
1049 minutes = int(tz[3:5])
1047 return -sign * (hours * 60 + minutes) * 60
1050 return -sign * (hours * 60 + minutes) * 60
1048 if tz == "GMT" or tz == "UTC":
1051 if tz == "GMT" or tz == "UTC":
1049 return 0
1052 return 0
1050 return None
1053 return None
1051
1054
1052 # NOTE: unixtime = localunixtime + offset
1055 # NOTE: unixtime = localunixtime + offset
1053 offset, date = timezone(string), string
1056 offset, date = timezone(string), string
1054 if offset != None:
1057 if offset != None:
1055 date = " ".join(string.split()[:-1])
1058 date = " ".join(string.split()[:-1])
1056
1059
1057 # add missing elements from defaults
1060 # add missing elements from defaults
1058 for part in defaults:
1061 for part in defaults:
1059 found = [True for p in part if ("%"+p) in format]
1062 found = [True for p in part if ("%"+p) in format]
1060 if not found:
1063 if not found:
1061 date += "@" + defaults[part]
1064 date += "@" + defaults[part]
1062 format += "@%" + part[0]
1065 format += "@%" + part[0]
1063
1066
1064 timetuple = time.strptime(date, format)
1067 timetuple = time.strptime(date, format)
1065 localunixtime = int(calendar.timegm(timetuple))
1068 localunixtime = int(calendar.timegm(timetuple))
1066 if offset is None:
1069 if offset is None:
1067 # local timezone
1070 # local timezone
1068 unixtime = int(time.mktime(timetuple))
1071 unixtime = int(time.mktime(timetuple))
1069 offset = unixtime - localunixtime
1072 offset = unixtime - localunixtime
1070 else:
1073 else:
1071 unixtime = localunixtime + offset
1074 unixtime = localunixtime + offset
1072 return unixtime, offset
1075 return unixtime, offset
1073
1076
1074 def parsedate(date, formats=None, defaults=None):
1077 def parsedate(date, formats=None, defaults=None):
1075 """parse a localized date/time string and return a (unixtime, offset) tuple.
1078 """parse a localized date/time string and return a (unixtime, offset) tuple.
1076
1079
1077 The date may be a "unixtime offset" string or in one of the specified
1080 The date may be a "unixtime offset" string or in one of the specified
1078 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1081 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1079 """
1082 """
1080 if not date:
1083 if not date:
1081 return 0, 0
1084 return 0, 0
1082 if isinstance(date, tuple) and len(date) == 2:
1085 if isinstance(date, tuple) and len(date) == 2:
1083 return date
1086 return date
1084 if not formats:
1087 if not formats:
1085 formats = defaultdateformats
1088 formats = defaultdateformats
1086 date = date.strip()
1089 date = date.strip()
1087 try:
1090 try:
1088 when, offset = map(int, date.split(' '))
1091 when, offset = map(int, date.split(' '))
1089 except ValueError:
1092 except ValueError:
1090 # fill out defaults
1093 # fill out defaults
1091 if not defaults:
1094 if not defaults:
1092 defaults = {}
1095 defaults = {}
1093 now = makedate()
1096 now = makedate()
1094 for part in "d mb yY HI M S".split():
1097 for part in "d mb yY HI M S".split():
1095 if part not in defaults:
1098 if part not in defaults:
1096 if part[0] in "HMS":
1099 if part[0] in "HMS":
1097 defaults[part] = "00"
1100 defaults[part] = "00"
1098 else:
1101 else:
1099 defaults[part] = datestr(now, "%" + part[0])
1102 defaults[part] = datestr(now, "%" + part[0])
1100
1103
1101 for format in formats:
1104 for format in formats:
1102 try:
1105 try:
1103 when, offset = strdate(date, format, defaults)
1106 when, offset = strdate(date, format, defaults)
1104 except (ValueError, OverflowError):
1107 except (ValueError, OverflowError):
1105 pass
1108 pass
1106 else:
1109 else:
1107 break
1110 break
1108 else:
1111 else:
1109 raise Abort(_('invalid date: %r') % date)
1112 raise Abort(_('invalid date: %r') % date)
1110 # validate explicit (probably user-specified) date and
1113 # validate explicit (probably user-specified) date and
1111 # time zone offset. values must fit in signed 32 bits for
1114 # time zone offset. values must fit in signed 32 bits for
1112 # current 32-bit linux runtimes. timezones go from UTC-12
1115 # current 32-bit linux runtimes. timezones go from UTC-12
1113 # to UTC+14
1116 # to UTC+14
1114 if abs(when) > 0x7fffffff:
1117 if abs(when) > 0x7fffffff:
1115 raise Abort(_('date exceeds 32 bits: %d') % when)
1118 raise Abort(_('date exceeds 32 bits: %d') % when)
1116 if offset < -50400 or offset > 43200:
1119 if offset < -50400 or offset > 43200:
1117 raise Abort(_('impossible time zone offset: %d') % offset)
1120 raise Abort(_('impossible time zone offset: %d') % offset)
1118 return when, offset
1121 return when, offset
1119
1122
1120 def matchdate(date):
1123 def matchdate(date):
1121 """Return a function that matches a given date match specifier
1124 """Return a function that matches a given date match specifier
1122
1125
1123 Formats include:
1126 Formats include:
1124
1127
1125 '{date}' match a given date to the accuracy provided
1128 '{date}' match a given date to the accuracy provided
1126
1129
1127 '<{date}' on or before a given date
1130 '<{date}' on or before a given date
1128
1131
1129 '>{date}' on or after a given date
1132 '>{date}' on or after a given date
1130
1133
1131 """
1134 """
1132
1135
1133 def lower(date):
1136 def lower(date):
1134 d = dict(mb="1", d="1")
1137 d = dict(mb="1", d="1")
1135 return parsedate(date, extendeddateformats, d)[0]
1138 return parsedate(date, extendeddateformats, d)[0]
1136
1139
1137 def upper(date):
1140 def upper(date):
1138 d = dict(mb="12", HI="23", M="59", S="59")
1141 d = dict(mb="12", HI="23", M="59", S="59")
1139 for days in "31 30 29".split():
1142 for days in "31 30 29".split():
1140 try:
1143 try:
1141 d["d"] = days
1144 d["d"] = days
1142 return parsedate(date, extendeddateformats, d)[0]
1145 return parsedate(date, extendeddateformats, d)[0]
1143 except:
1146 except:
1144 pass
1147 pass
1145 d["d"] = "28"
1148 d["d"] = "28"
1146 return parsedate(date, extendeddateformats, d)[0]
1149 return parsedate(date, extendeddateformats, d)[0]
1147
1150
1148 date = date.strip()
1151 date = date.strip()
1149 if date[0] == "<":
1152 if date[0] == "<":
1150 when = upper(date[1:])
1153 when = upper(date[1:])
1151 return lambda x: x <= when
1154 return lambda x: x <= when
1152 elif date[0] == ">":
1155 elif date[0] == ">":
1153 when = lower(date[1:])
1156 when = lower(date[1:])
1154 return lambda x: x >= when
1157 return lambda x: x >= when
1155 elif date[0] == "-":
1158 elif date[0] == "-":
1156 try:
1159 try:
1157 days = int(date[1:])
1160 days = int(date[1:])
1158 except ValueError:
1161 except ValueError:
1159 raise Abort(_("invalid day spec: %s") % date[1:])
1162 raise Abort(_("invalid day spec: %s") % date[1:])
1160 when = makedate()[0] - days * 3600 * 24
1163 when = makedate()[0] - days * 3600 * 24
1161 return lambda x: x >= when
1164 return lambda x: x >= when
1162 elif " to " in date:
1165 elif " to " in date:
1163 a, b = date.split(" to ")
1166 a, b = date.split(" to ")
1164 start, stop = lower(a), upper(b)
1167 start, stop = lower(a), upper(b)
1165 return lambda x: x >= start and x <= stop
1168 return lambda x: x >= start and x <= stop
1166 else:
1169 else:
1167 start, stop = lower(date), upper(date)
1170 start, stop = lower(date), upper(date)
1168 return lambda x: x >= start and x <= stop
1171 return lambda x: x >= start and x <= stop
1169
1172
1170 def shortuser(user):
1173 def shortuser(user):
1171 """Return a short representation of a user name or email address."""
1174 """Return a short representation of a user name or email address."""
1172 f = user.find('@')
1175 f = user.find('@')
1173 if f >= 0:
1176 if f >= 0:
1174 user = user[:f]
1177 user = user[:f]
1175 f = user.find('<')
1178 f = user.find('<')
1176 if f >= 0:
1179 if f >= 0:
1177 user = user[f + 1:]
1180 user = user[f + 1:]
1178 f = user.find(' ')
1181 f = user.find(' ')
1179 if f >= 0:
1182 if f >= 0:
1180 user = user[:f]
1183 user = user[:f]
1181 f = user.find('.')
1184 f = user.find('.')
1182 if f >= 0:
1185 if f >= 0:
1183 user = user[:f]
1186 user = user[:f]
1184 return user
1187 return user
1185
1188
1186 def email(author):
1189 def email(author):
1187 '''get email of author.'''
1190 '''get email of author.'''
1188 r = author.find('>')
1191 r = author.find('>')
1189 if r == -1:
1192 if r == -1:
1190 r = None
1193 r = None
1191 return author[author.find('<') + 1:r]
1194 return author[author.find('<') + 1:r]
1192
1195
1193 def ellipsis(text, maxlength=400):
1196 def ellipsis(text, maxlength=400):
1194 """Trim string to at most maxlength (default: 400) characters."""
1197 """Trim string to at most maxlength (default: 400) characters."""
1195 if len(text) <= maxlength:
1198 if len(text) <= maxlength:
1196 return text
1199 return text
1197 else:
1200 else:
1198 return "%s..." % (text[:maxlength - 3])
1201 return "%s..." % (text[:maxlength - 3])
1199
1202
1200 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1203 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1201 '''yield every hg repository under path, recursively.'''
1204 '''yield every hg repository under path, recursively.'''
1202 def errhandler(err):
1205 def errhandler(err):
1203 if err.filename == path:
1206 if err.filename == path:
1204 raise err
1207 raise err
1205 if followsym and hasattr(os.path, 'samestat'):
1208 if followsym and hasattr(os.path, 'samestat'):
1206 def _add_dir_if_not_there(dirlst, dirname):
1209 def _add_dir_if_not_there(dirlst, dirname):
1207 match = False
1210 match = False
1208 samestat = os.path.samestat
1211 samestat = os.path.samestat
1209 dirstat = os.stat(dirname)
1212 dirstat = os.stat(dirname)
1210 for lstdirstat in dirlst:
1213 for lstdirstat in dirlst:
1211 if samestat(dirstat, lstdirstat):
1214 if samestat(dirstat, lstdirstat):
1212 match = True
1215 match = True
1213 break
1216 break
1214 if not match:
1217 if not match:
1215 dirlst.append(dirstat)
1218 dirlst.append(dirstat)
1216 return not match
1219 return not match
1217 else:
1220 else:
1218 followsym = False
1221 followsym = False
1219
1222
1220 if (seen_dirs is None) and followsym:
1223 if (seen_dirs is None) and followsym:
1221 seen_dirs = []
1224 seen_dirs = []
1222 _add_dir_if_not_there(seen_dirs, path)
1225 _add_dir_if_not_there(seen_dirs, path)
1223 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1226 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1224 dirs.sort()
1227 dirs.sort()
1225 if '.hg' in dirs:
1228 if '.hg' in dirs:
1226 yield root # found a repository
1229 yield root # found a repository
1227 qroot = os.path.join(root, '.hg', 'patches')
1230 qroot = os.path.join(root, '.hg', 'patches')
1228 if os.path.isdir(os.path.join(qroot, '.hg')):
1231 if os.path.isdir(os.path.join(qroot, '.hg')):
1229 yield qroot # we have a patch queue repo here
1232 yield qroot # we have a patch queue repo here
1230 if recurse:
1233 if recurse:
1231 # avoid recursing inside the .hg directory
1234 # avoid recursing inside the .hg directory
1232 dirs.remove('.hg')
1235 dirs.remove('.hg')
1233 else:
1236 else:
1234 dirs[:] = [] # don't descend further
1237 dirs[:] = [] # don't descend further
1235 elif followsym:
1238 elif followsym:
1236 newdirs = []
1239 newdirs = []
1237 for d in dirs:
1240 for d in dirs:
1238 fname = os.path.join(root, d)
1241 fname = os.path.join(root, d)
1239 if _add_dir_if_not_there(seen_dirs, fname):
1242 if _add_dir_if_not_there(seen_dirs, fname):
1240 if os.path.islink(fname):
1243 if os.path.islink(fname):
1241 for hgname in walkrepos(fname, True, seen_dirs):
1244 for hgname in walkrepos(fname, True, seen_dirs):
1242 yield hgname
1245 yield hgname
1243 else:
1246 else:
1244 newdirs.append(d)
1247 newdirs.append(d)
1245 dirs[:] = newdirs
1248 dirs[:] = newdirs
1246
1249
1247 _rcpath = None
1250 _rcpath = None
1248
1251
1249 def os_rcpath():
1252 def os_rcpath():
1250 '''return default os-specific hgrc search path'''
1253 '''return default os-specific hgrc search path'''
1251 path = system_rcpath()
1254 path = system_rcpath()
1252 path.extend(user_rcpath())
1255 path.extend(user_rcpath())
1253 path = [os.path.normpath(f) for f in path]
1256 path = [os.path.normpath(f) for f in path]
1254 return path
1257 return path
1255
1258
1256 def rcpath():
1259 def rcpath():
1257 '''return hgrc search path. if env var HGRCPATH is set, use it.
1260 '''return hgrc search path. if env var HGRCPATH is set, use it.
1258 for each item in path, if directory, use files ending in .rc,
1261 for each item in path, if directory, use files ending in .rc,
1259 else use item.
1262 else use item.
1260 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1263 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1261 if no HGRCPATH, use default os-specific path.'''
1264 if no HGRCPATH, use default os-specific path.'''
1262 global _rcpath
1265 global _rcpath
1263 if _rcpath is None:
1266 if _rcpath is None:
1264 if 'HGRCPATH' in os.environ:
1267 if 'HGRCPATH' in os.environ:
1265 _rcpath = []
1268 _rcpath = []
1266 for p in os.environ['HGRCPATH'].split(os.pathsep):
1269 for p in os.environ['HGRCPATH'].split(os.pathsep):
1267 if not p:
1270 if not p:
1268 continue
1271 continue
1269 p = expandpath(p)
1272 p = expandpath(p)
1270 if os.path.isdir(p):
1273 if os.path.isdir(p):
1271 for f, kind in osutil.listdir(p):
1274 for f, kind in osutil.listdir(p):
1272 if f.endswith('.rc'):
1275 if f.endswith('.rc'):
1273 _rcpath.append(os.path.join(p, f))
1276 _rcpath.append(os.path.join(p, f))
1274 else:
1277 else:
1275 _rcpath.append(p)
1278 _rcpath.append(p)
1276 else:
1279 else:
1277 _rcpath = os_rcpath()
1280 _rcpath = os_rcpath()
1278 return _rcpath
1281 return _rcpath
1279
1282
1280 def bytecount(nbytes):
1283 def bytecount(nbytes):
1281 '''return byte count formatted as readable string, with units'''
1284 '''return byte count formatted as readable string, with units'''
1282
1285
1283 units = (
1286 units = (
1284 (100, 1 << 30, _('%.0f GB')),
1287 (100, 1 << 30, _('%.0f GB')),
1285 (10, 1 << 30, _('%.1f GB')),
1288 (10, 1 << 30, _('%.1f GB')),
1286 (1, 1 << 30, _('%.2f GB')),
1289 (1, 1 << 30, _('%.2f GB')),
1287 (100, 1 << 20, _('%.0f MB')),
1290 (100, 1 << 20, _('%.0f MB')),
1288 (10, 1 << 20, _('%.1f MB')),
1291 (10, 1 << 20, _('%.1f MB')),
1289 (1, 1 << 20, _('%.2f MB')),
1292 (1, 1 << 20, _('%.2f MB')),
1290 (100, 1 << 10, _('%.0f KB')),
1293 (100, 1 << 10, _('%.0f KB')),
1291 (10, 1 << 10, _('%.1f KB')),
1294 (10, 1 << 10, _('%.1f KB')),
1292 (1, 1 << 10, _('%.2f KB')),
1295 (1, 1 << 10, _('%.2f KB')),
1293 (1, 1, _('%.0f bytes')),
1296 (1, 1, _('%.0f bytes')),
1294 )
1297 )
1295
1298
1296 for multiplier, divisor, format in units:
1299 for multiplier, divisor, format in units:
1297 if nbytes >= divisor * multiplier:
1300 if nbytes >= divisor * multiplier:
1298 return format % (nbytes / float(divisor))
1301 return format % (nbytes / float(divisor))
1299 return units[-1][2] % nbytes
1302 return units[-1][2] % nbytes
1300
1303
1301 def drop_scheme(scheme, path):
1304 def drop_scheme(scheme, path):
1302 sc = scheme + ':'
1305 sc = scheme + ':'
1303 if path.startswith(sc):
1306 if path.startswith(sc):
1304 path = path[len(sc):]
1307 path = path[len(sc):]
1305 if path.startswith('//'):
1308 if path.startswith('//'):
1306 if scheme == 'file':
1309 if scheme == 'file':
1307 i = path.find('/', 2)
1310 i = path.find('/', 2)
1308 if i == -1:
1311 if i == -1:
1309 return ''
1312 return ''
1310 # On Windows, absolute paths are rooted at the current drive
1313 # On Windows, absolute paths are rooted at the current drive
1311 # root. On POSIX they are rooted at the file system root.
1314 # root. On POSIX they are rooted at the file system root.
1312 if os.name == 'nt':
1315 if os.name == 'nt':
1313 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1316 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1314 path = os.path.join(droot, path[i + 1:])
1317 path = os.path.join(droot, path[i + 1:])
1315 else:
1318 else:
1316 path = path[i:]
1319 path = path[i:]
1317 else:
1320 else:
1318 path = path[2:]
1321 path = path[2:]
1319 return path
1322 return path
1320
1323
1321 def uirepr(s):
1324 def uirepr(s):
1322 # Avoid double backslash in Windows path repr()
1325 # Avoid double backslash in Windows path repr()
1323 return repr(s).replace('\\\\', '\\')
1326 return repr(s).replace('\\\\', '\\')
1324
1327
1325 #### naming convention of below implementation follows 'textwrap' module
1328 #### naming convention of below implementation follows 'textwrap' module
1326
1329
1327 class MBTextWrapper(textwrap.TextWrapper):
1330 class MBTextWrapper(textwrap.TextWrapper):
1328 def __init__(self, **kwargs):
1331 def __init__(self, **kwargs):
1329 textwrap.TextWrapper.__init__(self, **kwargs)
1332 textwrap.TextWrapper.__init__(self, **kwargs)
1330
1333
1331 def _cutdown(self, str, space_left):
1334 def _cutdown(self, str, space_left):
1332 l = 0
1335 l = 0
1333 ucstr = unicode(str, encoding.encoding)
1336 ucstr = unicode(str, encoding.encoding)
1334 w = unicodedata.east_asian_width
1337 w = unicodedata.east_asian_width
1335 for i in xrange(len(ucstr)):
1338 for i in xrange(len(ucstr)):
1336 l += w(ucstr[i]) in 'WFA' and 2 or 1
1339 l += w(ucstr[i]) in 'WFA' and 2 or 1
1337 if space_left < l:
1340 if space_left < l:
1338 return (ucstr[:i].encode(encoding.encoding),
1341 return (ucstr[:i].encode(encoding.encoding),
1339 ucstr[i:].encode(encoding.encoding))
1342 ucstr[i:].encode(encoding.encoding))
1340 return str, ''
1343 return str, ''
1341
1344
1342 # ----------------------------------------
1345 # ----------------------------------------
1343 # overriding of base class
1346 # overriding of base class
1344
1347
1345 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1348 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1346 space_left = max(width - cur_len, 1)
1349 space_left = max(width - cur_len, 1)
1347
1350
1348 if self.break_long_words:
1351 if self.break_long_words:
1349 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1352 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1350 cur_line.append(cut)
1353 cur_line.append(cut)
1351 reversed_chunks[-1] = res
1354 reversed_chunks[-1] = res
1352 elif not cur_line:
1355 elif not cur_line:
1353 cur_line.append(reversed_chunks.pop())
1356 cur_line.append(reversed_chunks.pop())
1354
1357
1355 #### naming convention of above implementation follows 'textwrap' module
1358 #### naming convention of above implementation follows 'textwrap' module
1356
1359
1357 def wrap(line, width, initindent='', hangindent=''):
1360 def wrap(line, width, initindent='', hangindent=''):
1358 maxindent = max(len(hangindent), len(initindent))
1361 maxindent = max(len(hangindent), len(initindent))
1359 if width <= maxindent:
1362 if width <= maxindent:
1360 # adjust for weird terminal size
1363 # adjust for weird terminal size
1361 width = max(78, maxindent + 1)
1364 width = max(78, maxindent + 1)
1362 wrapper = MBTextWrapper(width=width,
1365 wrapper = MBTextWrapper(width=width,
1363 initial_indent=initindent,
1366 initial_indent=initindent,
1364 subsequent_indent=hangindent)
1367 subsequent_indent=hangindent)
1365 return wrapper.fill(line)
1368 return wrapper.fill(line)
1366
1369
1367 def iterlines(iterator):
1370 def iterlines(iterator):
1368 for chunk in iterator:
1371 for chunk in iterator:
1369 for line in chunk.splitlines():
1372 for line in chunk.splitlines():
1370 yield line
1373 yield line
1371
1374
1372 def expandpath(path):
1375 def expandpath(path):
1373 return os.path.expanduser(os.path.expandvars(path))
1376 return os.path.expanduser(os.path.expandvars(path))
1374
1377
1375 def hgcmd():
1378 def hgcmd():
1376 """Return the command used to execute current hg
1379 """Return the command used to execute current hg
1377
1380
1378 This is different from hgexecutable() because on Windows we want
1381 This is different from hgexecutable() because on Windows we want
1379 to avoid things opening new shell windows like batch files, so we
1382 to avoid things opening new shell windows like batch files, so we
1380 get either the python call or current executable.
1383 get either the python call or current executable.
1381 """
1384 """
1382 if main_is_frozen():
1385 if main_is_frozen():
1383 return [sys.executable]
1386 return [sys.executable]
1384 return gethgcmd()
1387 return gethgcmd()
1385
1388
1386 def rundetached(args, condfn):
1389 def rundetached(args, condfn):
1387 """Execute the argument list in a detached process.
1390 """Execute the argument list in a detached process.
1388
1391
1389 condfn is a callable which is called repeatedly and should return
1392 condfn is a callable which is called repeatedly and should return
1390 True once the child process is known to have started successfully.
1393 True once the child process is known to have started successfully.
1391 At this point, the child process PID is returned. If the child
1394 At this point, the child process PID is returned. If the child
1392 process fails to start or finishes before condfn() evaluates to
1395 process fails to start or finishes before condfn() evaluates to
1393 True, return -1.
1396 True, return -1.
1394 """
1397 """
1395 # Windows case is easier because the child process is either
1398 # Windows case is easier because the child process is either
1396 # successfully starting and validating the condition or exiting
1399 # successfully starting and validating the condition or exiting
1397 # on failure. We just poll on its PID. On Unix, if the child
1400 # on failure. We just poll on its PID. On Unix, if the child
1398 # process fails to start, it will be left in a zombie state until
1401 # process fails to start, it will be left in a zombie state until
1399 # the parent wait on it, which we cannot do since we expect a long
1402 # the parent wait on it, which we cannot do since we expect a long
1400 # running process on success. Instead we listen for SIGCHLD telling
1403 # running process on success. Instead we listen for SIGCHLD telling
1401 # us our child process terminated.
1404 # us our child process terminated.
1402 terminated = set()
1405 terminated = set()
1403 def handler(signum, frame):
1406 def handler(signum, frame):
1404 terminated.add(os.wait())
1407 terminated.add(os.wait())
1405 prevhandler = None
1408 prevhandler = None
1406 if hasattr(signal, 'SIGCHLD'):
1409 if hasattr(signal, 'SIGCHLD'):
1407 prevhandler = signal.signal(signal.SIGCHLD, handler)
1410 prevhandler = signal.signal(signal.SIGCHLD, handler)
1408 try:
1411 try:
1409 pid = spawndetached(args)
1412 pid = spawndetached(args)
1410 while not condfn():
1413 while not condfn():
1411 if ((pid in terminated or not testpid(pid))
1414 if ((pid in terminated or not testpid(pid))
1412 and not condfn()):
1415 and not condfn()):
1413 return -1
1416 return -1
1414 time.sleep(0.1)
1417 time.sleep(0.1)
1415 return pid
1418 return pid
1416 finally:
1419 finally:
1417 if prevhandler is not None:
1420 if prevhandler is not None:
1418 signal.signal(signal.SIGCHLD, prevhandler)
1421 signal.signal(signal.SIGCHLD, prevhandler)
1419
1422
1420 try:
1423 try:
1421 any, all = any, all
1424 any, all = any, all
1422 except NameError:
1425 except NameError:
1423 def any(iterable):
1426 def any(iterable):
1424 for i in iterable:
1427 for i in iterable:
1425 if i:
1428 if i:
1426 return True
1429 return True
1427 return False
1430 return False
1428
1431
1429 def all(iterable):
1432 def all(iterable):
1430 for i in iterable:
1433 for i in iterable:
1431 if not i:
1434 if not i:
1432 return False
1435 return False
1433 return True
1436 return True
1434
1437
1435 def interpolate(prefix, mapping, s, fn=None):
1438 def interpolate(prefix, mapping, s, fn=None):
1436 """Return the result of interpolating items in the mapping into string s.
1439 """Return the result of interpolating items in the mapping into string s.
1437
1440
1438 prefix is a single character string, or a two character string with
1441 prefix is a single character string, or a two character string with
1439 a backslash as the first character if the prefix needs to be escaped in
1442 a backslash as the first character if the prefix needs to be escaped in
1440 a regular expression.
1443 a regular expression.
1441
1444
1442 fn is an optional function that will be applied to the replacement text
1445 fn is an optional function that will be applied to the replacement text
1443 just before replacement.
1446 just before replacement.
1444 """
1447 """
1445 fn = fn or (lambda s: s)
1448 fn = fn or (lambda s: s)
1446 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1449 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1447 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1450 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1448
1451
1449 def getport(port):
1452 def getport(port):
1450 """Return the port for a given network service.
1453 """Return the port for a given network service.
1451
1454
1452 If port is an integer, it's returned as is. If it's a string, it's
1455 If port is an integer, it's returned as is. If it's a string, it's
1453 looked up using socket.getservbyname(). If there's no matching
1456 looked up using socket.getservbyname(). If there's no matching
1454 service, util.Abort is raised.
1457 service, util.Abort is raised.
1455 """
1458 """
1456 try:
1459 try:
1457 return int(port)
1460 return int(port)
1458 except ValueError:
1461 except ValueError:
1459 pass
1462 pass
1460
1463
1461 try:
1464 try:
1462 return socket.getservbyname(port)
1465 return socket.getservbyname(port)
1463 except socket.error:
1466 except socket.error:
1464 raise Abort(_("no port number associated with service '%s'") % port)
1467 raise Abort(_("no port number associated with service '%s'") % port)
1465
1468
1466 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1469 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1467 '0': False, 'no': False, 'false': False, 'off': False,
1470 '0': False, 'no': False, 'false': False, 'off': False,
1468 'never': False}
1471 'never': False}
1469
1472
1470 def parsebool(s):
1473 def parsebool(s):
1471 """Parse s into a boolean.
1474 """Parse s into a boolean.
1472
1475
1473 If s is not a valid boolean, returns None.
1476 If s is not a valid boolean, returns None.
1474 """
1477 """
1475 return _booleans.get(s.lower(), None)
1478 return _booleans.get(s.lower(), None)
General Comments 0
You need to be logged in to leave comments. Login now