##// END OF EJS Templates
util: simplify range expression...
Martin Geisler -
r8631:a87c41f6 default
parent child Browse files
Show More
@@ -1,1247 +1,1247 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, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
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
17 import error, osutil
18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, random
19 import os, stat, time, calendar, random
20 import imp
20 import imp
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 try:
31 try:
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 except ImportError:
33 except ImportError:
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 subprocess
39 import subprocess
40 closefds = os.name == 'posix'
40 closefds = os.name == 'posix'
41 def popen2(cmd):
41 def popen2(cmd):
42 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
42 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
43 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
43 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
44 return p.stdin, p.stdout
44 return p.stdin, p.stdout
45 def popen3(cmd):
45 def popen3(cmd):
46 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
46 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
47 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
47 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
48 stderr=subprocess.PIPE)
48 stderr=subprocess.PIPE)
49 return p.stdin, p.stdout, p.stderr
49 return p.stdin, p.stdout, p.stderr
50
50
51 def version():
51 def version():
52 """Return version information if available."""
52 """Return version information if available."""
53 try:
53 try:
54 import __version__
54 import __version__
55 return __version__.version
55 return __version__.version
56 except ImportError:
56 except ImportError:
57 return 'unknown'
57 return 'unknown'
58
58
59 # used by parsedate
59 # used by parsedate
60 defaultdateformats = (
60 defaultdateformats = (
61 '%Y-%m-%d %H:%M:%S',
61 '%Y-%m-%d %H:%M:%S',
62 '%Y-%m-%d %I:%M:%S%p',
62 '%Y-%m-%d %I:%M:%S%p',
63 '%Y-%m-%d %H:%M',
63 '%Y-%m-%d %H:%M',
64 '%Y-%m-%d %I:%M%p',
64 '%Y-%m-%d %I:%M%p',
65 '%Y-%m-%d',
65 '%Y-%m-%d',
66 '%m-%d',
66 '%m-%d',
67 '%m/%d',
67 '%m/%d',
68 '%m/%d/%y',
68 '%m/%d/%y',
69 '%m/%d/%Y',
69 '%m/%d/%Y',
70 '%a %b %d %H:%M:%S %Y',
70 '%a %b %d %H:%M:%S %Y',
71 '%a %b %d %I:%M:%S%p %Y',
71 '%a %b %d %I:%M:%S%p %Y',
72 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
72 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
73 '%b %d %H:%M:%S %Y',
73 '%b %d %H:%M:%S %Y',
74 '%b %d %I:%M:%S%p %Y',
74 '%b %d %I:%M:%S%p %Y',
75 '%b %d %H:%M:%S',
75 '%b %d %H:%M:%S',
76 '%b %d %I:%M:%S%p',
76 '%b %d %I:%M:%S%p',
77 '%b %d %H:%M',
77 '%b %d %H:%M',
78 '%b %d %I:%M%p',
78 '%b %d %I:%M%p',
79 '%b %d %Y',
79 '%b %d %Y',
80 '%b %d',
80 '%b %d',
81 '%H:%M:%S',
81 '%H:%M:%S',
82 '%I:%M:%SP',
82 '%I:%M:%SP',
83 '%H:%M',
83 '%H:%M',
84 '%I:%M%p',
84 '%I:%M%p',
85 )
85 )
86
86
87 extendeddateformats = defaultdateformats + (
87 extendeddateformats = defaultdateformats + (
88 "%Y",
88 "%Y",
89 "%Y-%m",
89 "%Y-%m",
90 "%b",
90 "%b",
91 "%b %Y",
91 "%b %Y",
92 )
92 )
93
93
94 def cachefunc(func):
94 def cachefunc(func):
95 '''cache the result of function calls'''
95 '''cache the result of function calls'''
96 # XXX doesn't handle keywords args
96 # XXX doesn't handle keywords args
97 cache = {}
97 cache = {}
98 if func.func_code.co_argcount == 1:
98 if func.func_code.co_argcount == 1:
99 # we gain a small amount of time because
99 # we gain a small amount of time because
100 # we don't need to pack/unpack the list
100 # we don't need to pack/unpack the list
101 def f(arg):
101 def f(arg):
102 if arg not in cache:
102 if arg not in cache:
103 cache[arg] = func(arg)
103 cache[arg] = func(arg)
104 return cache[arg]
104 return cache[arg]
105 else:
105 else:
106 def f(*args):
106 def f(*args):
107 if args not in cache:
107 if args not in cache:
108 cache[args] = func(*args)
108 cache[args] = func(*args)
109 return cache[args]
109 return cache[args]
110
110
111 return f
111 return f
112
112
113 class propertycache(object):
113 class propertycache(object):
114 def __init__(self, func):
114 def __init__(self, func):
115 self.func = func
115 self.func = func
116 self.name = func.__name__
116 self.name = func.__name__
117 def __get__(self, obj, type=None):
117 def __get__(self, obj, type=None):
118 result = self.func(obj)
118 result = self.func(obj)
119 setattr(obj, self.name, result)
119 setattr(obj, self.name, result)
120 return result
120 return result
121
121
122 def pipefilter(s, cmd):
122 def pipefilter(s, cmd):
123 '''filter string S through command CMD, returning its output'''
123 '''filter string S through command CMD, returning its output'''
124 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
124 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
125 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
125 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
126 pout, perr = p.communicate(s)
126 pout, perr = p.communicate(s)
127 return pout
127 return pout
128
128
129 def tempfilter(s, cmd):
129 def tempfilter(s, cmd):
130 '''filter string S through a pair of temporary files with CMD.
130 '''filter string S through a pair of temporary files with CMD.
131 CMD is used as a template to create the real command to be run,
131 CMD is used as a template to create the real command to be run,
132 with the strings INFILE and OUTFILE replaced by the real names of
132 with the strings INFILE and OUTFILE replaced by the real names of
133 the temporary files generated.'''
133 the temporary files generated.'''
134 inname, outname = None, None
134 inname, outname = None, None
135 try:
135 try:
136 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
136 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
137 fp = os.fdopen(infd, 'wb')
137 fp = os.fdopen(infd, 'wb')
138 fp.write(s)
138 fp.write(s)
139 fp.close()
139 fp.close()
140 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
140 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
141 os.close(outfd)
141 os.close(outfd)
142 cmd = cmd.replace('INFILE', inname)
142 cmd = cmd.replace('INFILE', inname)
143 cmd = cmd.replace('OUTFILE', outname)
143 cmd = cmd.replace('OUTFILE', outname)
144 code = os.system(cmd)
144 code = os.system(cmd)
145 if sys.platform == 'OpenVMS' and code & 1:
145 if sys.platform == 'OpenVMS' and code & 1:
146 code = 0
146 code = 0
147 if code: raise Abort(_("command '%s' failed: %s") %
147 if code: raise Abort(_("command '%s' failed: %s") %
148 (cmd, explain_exit(code)))
148 (cmd, explain_exit(code)))
149 return open(outname, 'rb').read()
149 return open(outname, 'rb').read()
150 finally:
150 finally:
151 try:
151 try:
152 if inname: os.unlink(inname)
152 if inname: os.unlink(inname)
153 except: pass
153 except: pass
154 try:
154 try:
155 if outname: os.unlink(outname)
155 if outname: os.unlink(outname)
156 except: pass
156 except: pass
157
157
158 filtertable = {
158 filtertable = {
159 'tempfile:': tempfilter,
159 'tempfile:': tempfilter,
160 'pipe:': pipefilter,
160 'pipe:': pipefilter,
161 }
161 }
162
162
163 def filter(s, cmd):
163 def filter(s, cmd):
164 "filter a string through a command that transforms its input to its output"
164 "filter a string through a command that transforms its input to its output"
165 for name, fn in filtertable.iteritems():
165 for name, fn in filtertable.iteritems():
166 if cmd.startswith(name):
166 if cmd.startswith(name):
167 return fn(s, cmd[len(name):].lstrip())
167 return fn(s, cmd[len(name):].lstrip())
168 return pipefilter(s, cmd)
168 return pipefilter(s, cmd)
169
169
170 def binary(s):
170 def binary(s):
171 """return true if a string is binary data"""
171 """return true if a string is binary data"""
172 return bool(s and '\0' in s)
172 return bool(s and '\0' in s)
173
173
174 def increasingchunks(source, min=1024, max=65536):
174 def increasingchunks(source, min=1024, max=65536):
175 '''return no less than min bytes per chunk while data remains,
175 '''return no less than min bytes per chunk while data remains,
176 doubling min after each chunk until it reaches max'''
176 doubling min after each chunk until it reaches max'''
177 def log2(x):
177 def log2(x):
178 if not x:
178 if not x:
179 return 0
179 return 0
180 i = 0
180 i = 0
181 while x:
181 while x:
182 x >>= 1
182 x >>= 1
183 i += 1
183 i += 1
184 return i - 1
184 return i - 1
185
185
186 buf = []
186 buf = []
187 blen = 0
187 blen = 0
188 for chunk in source:
188 for chunk in source:
189 buf.append(chunk)
189 buf.append(chunk)
190 blen += len(chunk)
190 blen += len(chunk)
191 if blen >= min:
191 if blen >= min:
192 if min < max:
192 if min < max:
193 min = min << 1
193 min = min << 1
194 nmin = 1 << log2(blen)
194 nmin = 1 << log2(blen)
195 if nmin > min:
195 if nmin > min:
196 min = nmin
196 min = nmin
197 if min > max:
197 if min > max:
198 min = max
198 min = max
199 yield ''.join(buf)
199 yield ''.join(buf)
200 blen = 0
200 blen = 0
201 buf = []
201 buf = []
202 if buf:
202 if buf:
203 yield ''.join(buf)
203 yield ''.join(buf)
204
204
205 Abort = error.Abort
205 Abort = error.Abort
206
206
207 def always(fn): return True
207 def always(fn): return True
208 def never(fn): return False
208 def never(fn): return False
209
209
210 def pathto(root, n1, n2):
210 def pathto(root, n1, n2):
211 '''return the relative path from one place to another.
211 '''return the relative path from one place to another.
212 root should use os.sep to separate directories
212 root should use os.sep to separate directories
213 n1 should use os.sep to separate directories
213 n1 should use os.sep to separate directories
214 n2 should use "/" to separate directories
214 n2 should use "/" to separate directories
215 returns an os.sep-separated path.
215 returns an os.sep-separated path.
216
216
217 If n1 is a relative path, it's assumed it's
217 If n1 is a relative path, it's assumed it's
218 relative to root.
218 relative to root.
219 n2 should always be relative to root.
219 n2 should always be relative to root.
220 '''
220 '''
221 if not n1: return localpath(n2)
221 if not n1: return localpath(n2)
222 if os.path.isabs(n1):
222 if os.path.isabs(n1):
223 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
223 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
224 return os.path.join(root, localpath(n2))
224 return os.path.join(root, localpath(n2))
225 n2 = '/'.join((pconvert(root), n2))
225 n2 = '/'.join((pconvert(root), n2))
226 a, b = splitpath(n1), n2.split('/')
226 a, b = splitpath(n1), n2.split('/')
227 a.reverse()
227 a.reverse()
228 b.reverse()
228 b.reverse()
229 while a and b and a[-1] == b[-1]:
229 while a and b and a[-1] == b[-1]:
230 a.pop()
230 a.pop()
231 b.pop()
231 b.pop()
232 b.reverse()
232 b.reverse()
233 return os.sep.join((['..'] * len(a)) + b) or '.'
233 return os.sep.join((['..'] * len(a)) + b) or '.'
234
234
235 def canonpath(root, cwd, myname):
235 def canonpath(root, cwd, myname):
236 """return the canonical path of myname, given cwd and root"""
236 """return the canonical path of myname, given cwd and root"""
237 if root == os.sep:
237 if root == os.sep:
238 rootsep = os.sep
238 rootsep = os.sep
239 elif endswithsep(root):
239 elif endswithsep(root):
240 rootsep = root
240 rootsep = root
241 else:
241 else:
242 rootsep = root + os.sep
242 rootsep = root + os.sep
243 name = myname
243 name = myname
244 if not os.path.isabs(name):
244 if not os.path.isabs(name):
245 name = os.path.join(root, cwd, name)
245 name = os.path.join(root, cwd, name)
246 name = os.path.normpath(name)
246 name = os.path.normpath(name)
247 audit_path = path_auditor(root)
247 audit_path = path_auditor(root)
248 if name != rootsep and name.startswith(rootsep):
248 if name != rootsep and name.startswith(rootsep):
249 name = name[len(rootsep):]
249 name = name[len(rootsep):]
250 audit_path(name)
250 audit_path(name)
251 return pconvert(name)
251 return pconvert(name)
252 elif name == root:
252 elif name == root:
253 return ''
253 return ''
254 else:
254 else:
255 # Determine whether `name' is in the hierarchy at or beneath `root',
255 # Determine whether `name' is in the hierarchy at or beneath `root',
256 # by iterating name=dirname(name) until that causes no change (can't
256 # by iterating name=dirname(name) until that causes no change (can't
257 # check name == '/', because that doesn't work on windows). For each
257 # check name == '/', because that doesn't work on windows). For each
258 # `name', compare dev/inode numbers. If they match, the list `rel'
258 # `name', compare dev/inode numbers. If they match, the list `rel'
259 # holds the reversed list of components making up the relative file
259 # holds the reversed list of components making up the relative file
260 # name we want.
260 # name we want.
261 root_st = os.stat(root)
261 root_st = os.stat(root)
262 rel = []
262 rel = []
263 while True:
263 while True:
264 try:
264 try:
265 name_st = os.stat(name)
265 name_st = os.stat(name)
266 except OSError:
266 except OSError:
267 break
267 break
268 if samestat(name_st, root_st):
268 if samestat(name_st, root_st):
269 if not rel:
269 if not rel:
270 # name was actually the same as root (maybe a symlink)
270 # name was actually the same as root (maybe a symlink)
271 return ''
271 return ''
272 rel.reverse()
272 rel.reverse()
273 name = os.path.join(*rel)
273 name = os.path.join(*rel)
274 audit_path(name)
274 audit_path(name)
275 return pconvert(name)
275 return pconvert(name)
276 dirname, basename = os.path.split(name)
276 dirname, basename = os.path.split(name)
277 rel.append(basename)
277 rel.append(basename)
278 if dirname == name:
278 if dirname == name:
279 break
279 break
280 name = dirname
280 name = dirname
281
281
282 raise Abort('%s not under root' % myname)
282 raise Abort('%s not under root' % myname)
283
283
284 _hgexecutable = None
284 _hgexecutable = None
285
285
286 def main_is_frozen():
286 def main_is_frozen():
287 """return True if we are a frozen executable.
287 """return True if we are a frozen executable.
288
288
289 The code supports py2exe (most common, Windows only) and tools/freeze
289 The code supports py2exe (most common, Windows only) and tools/freeze
290 (portable, not much used).
290 (portable, not much used).
291 """
291 """
292 return (hasattr(sys, "frozen") or # new py2exe
292 return (hasattr(sys, "frozen") or # new py2exe
293 hasattr(sys, "importers") or # old py2exe
293 hasattr(sys, "importers") or # old py2exe
294 imp.is_frozen("__main__")) # tools/freeze
294 imp.is_frozen("__main__")) # tools/freeze
295
295
296 def hgexecutable():
296 def hgexecutable():
297 """return location of the 'hg' executable.
297 """return location of the 'hg' executable.
298
298
299 Defaults to $HG or 'hg' in the search path.
299 Defaults to $HG or 'hg' in the search path.
300 """
300 """
301 if _hgexecutable is None:
301 if _hgexecutable is None:
302 hg = os.environ.get('HG')
302 hg = os.environ.get('HG')
303 if hg:
303 if hg:
304 set_hgexecutable(hg)
304 set_hgexecutable(hg)
305 elif main_is_frozen():
305 elif main_is_frozen():
306 set_hgexecutable(sys.executable)
306 set_hgexecutable(sys.executable)
307 else:
307 else:
308 set_hgexecutable(find_exe('hg') or 'hg')
308 set_hgexecutable(find_exe('hg') or 'hg')
309 return _hgexecutable
309 return _hgexecutable
310
310
311 def set_hgexecutable(path):
311 def set_hgexecutable(path):
312 """set location of the 'hg' executable"""
312 """set location of the 'hg' executable"""
313 global _hgexecutable
313 global _hgexecutable
314 _hgexecutable = path
314 _hgexecutable = path
315
315
316 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
316 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
317 '''enhanced shell command execution.
317 '''enhanced shell command execution.
318 run with environment maybe modified, maybe in different dir.
318 run with environment maybe modified, maybe in different dir.
319
319
320 if command fails and onerr is None, return status. if ui object,
320 if command fails and onerr is None, return status. if ui object,
321 print error message and return status, else raise onerr object as
321 print error message and return status, else raise onerr object as
322 exception.'''
322 exception.'''
323 def py2shell(val):
323 def py2shell(val):
324 'convert python object into string that is useful to shell'
324 'convert python object into string that is useful to shell'
325 if val is None or val is False:
325 if val is None or val is False:
326 return '0'
326 return '0'
327 if val is True:
327 if val is True:
328 return '1'
328 return '1'
329 return str(val)
329 return str(val)
330 oldenv = {}
330 oldenv = {}
331 for k in environ:
331 for k in environ:
332 oldenv[k] = os.environ.get(k)
332 oldenv[k] = os.environ.get(k)
333 if cwd is not None:
333 if cwd is not None:
334 oldcwd = os.getcwd()
334 oldcwd = os.getcwd()
335 origcmd = cmd
335 origcmd = cmd
336 if os.name == 'nt':
336 if os.name == 'nt':
337 cmd = '"%s"' % cmd
337 cmd = '"%s"' % cmd
338 try:
338 try:
339 for k, v in environ.iteritems():
339 for k, v in environ.iteritems():
340 os.environ[k] = py2shell(v)
340 os.environ[k] = py2shell(v)
341 os.environ['HG'] = hgexecutable()
341 os.environ['HG'] = hgexecutable()
342 if cwd is not None and oldcwd != cwd:
342 if cwd is not None and oldcwd != cwd:
343 os.chdir(cwd)
343 os.chdir(cwd)
344 rc = os.system(cmd)
344 rc = os.system(cmd)
345 if sys.platform == 'OpenVMS' and rc & 1:
345 if sys.platform == 'OpenVMS' and rc & 1:
346 rc = 0
346 rc = 0
347 if rc and onerr:
347 if rc and onerr:
348 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
348 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
349 explain_exit(rc)[0])
349 explain_exit(rc)[0])
350 if errprefix:
350 if errprefix:
351 errmsg = '%s: %s' % (errprefix, errmsg)
351 errmsg = '%s: %s' % (errprefix, errmsg)
352 try:
352 try:
353 onerr.warn(errmsg + '\n')
353 onerr.warn(errmsg + '\n')
354 except AttributeError:
354 except AttributeError:
355 raise onerr(errmsg)
355 raise onerr(errmsg)
356 return rc
356 return rc
357 finally:
357 finally:
358 for k, v in oldenv.iteritems():
358 for k, v in oldenv.iteritems():
359 if v is None:
359 if v is None:
360 del os.environ[k]
360 del os.environ[k]
361 else:
361 else:
362 os.environ[k] = v
362 os.environ[k] = v
363 if cwd is not None and oldcwd != cwd:
363 if cwd is not None and oldcwd != cwd:
364 os.chdir(oldcwd)
364 os.chdir(oldcwd)
365
365
366 def checksignature(func):
366 def checksignature(func):
367 '''wrap a function with code to check for calling errors'''
367 '''wrap a function with code to check for calling errors'''
368 def check(*args, **kwargs):
368 def check(*args, **kwargs):
369 try:
369 try:
370 return func(*args, **kwargs)
370 return func(*args, **kwargs)
371 except TypeError:
371 except TypeError:
372 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
372 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
373 raise error.SignatureError
373 raise error.SignatureError
374 raise
374 raise
375
375
376 return check
376 return check
377
377
378 # os.path.lexists is not available on python2.3
378 # os.path.lexists is not available on python2.3
379 def lexists(filename):
379 def lexists(filename):
380 "test whether a file with this name exists. does not follow symlinks"
380 "test whether a file with this name exists. does not follow symlinks"
381 try:
381 try:
382 os.lstat(filename)
382 os.lstat(filename)
383 except:
383 except:
384 return False
384 return False
385 return True
385 return True
386
386
387 def rename(src, dst):
387 def rename(src, dst):
388 """forcibly rename a file"""
388 """forcibly rename a file"""
389 try:
389 try:
390 os.rename(src, dst)
390 os.rename(src, dst)
391 except OSError, err: # FIXME: check err (EEXIST ?)
391 except OSError, err: # FIXME: check err (EEXIST ?)
392
392
393 # On windows, rename to existing file is not allowed, so we
393 # On windows, rename to existing file is not allowed, so we
394 # must delete destination first. But if a file is open, unlink
394 # must delete destination first. But if a file is open, unlink
395 # schedules it for delete but does not delete it. Rename
395 # schedules it for delete but does not delete it. Rename
396 # happens immediately even for open files, so we rename
396 # happens immediately even for open files, so we rename
397 # destination to a temporary name, then delete that. Then
397 # destination to a temporary name, then delete that. Then
398 # rename is safe to do.
398 # rename is safe to do.
399 # The temporary name is chosen at random to avoid the situation
399 # The temporary name is chosen at random to avoid the situation
400 # where a file is left lying around from a previous aborted run.
400 # where a file is left lying around from a previous aborted run.
401 # The usual race condition this introduces can't be avoided as
401 # The usual race condition this introduces can't be avoided as
402 # we need the name to rename into, and not the file itself. Due
402 # we need the name to rename into, and not the file itself. Due
403 # to the nature of the operation however, any races will at worst
403 # to the nature of the operation however, any races will at worst
404 # lead to the rename failing and the current operation aborting.
404 # lead to the rename failing and the current operation aborting.
405
405
406 def tempname(prefix):
406 def tempname(prefix):
407 for tries in xrange(10):
407 for tries in xrange(10):
408 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
408 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
409 if not os.path.exists(temp):
409 if not os.path.exists(temp):
410 return temp
410 return temp
411 raise IOError, (errno.EEXIST, "No usable temporary filename found")
411 raise IOError, (errno.EEXIST, "No usable temporary filename found")
412
412
413 temp = tempname(dst)
413 temp = tempname(dst)
414 os.rename(dst, temp)
414 os.rename(dst, temp)
415 os.unlink(temp)
415 os.unlink(temp)
416 os.rename(src, dst)
416 os.rename(src, dst)
417
417
418 def unlink(f):
418 def unlink(f):
419 """unlink and remove the directory if it is empty"""
419 """unlink and remove the directory if it is empty"""
420 os.unlink(f)
420 os.unlink(f)
421 # try removing directories that might now be empty
421 # try removing directories that might now be empty
422 try:
422 try:
423 os.removedirs(os.path.dirname(f))
423 os.removedirs(os.path.dirname(f))
424 except OSError:
424 except OSError:
425 pass
425 pass
426
426
427 def copyfile(src, dest):
427 def copyfile(src, dest):
428 "copy a file, preserving mode and atime/mtime"
428 "copy a file, preserving mode and atime/mtime"
429 if os.path.islink(src):
429 if os.path.islink(src):
430 try:
430 try:
431 os.unlink(dest)
431 os.unlink(dest)
432 except:
432 except:
433 pass
433 pass
434 os.symlink(os.readlink(src), dest)
434 os.symlink(os.readlink(src), dest)
435 else:
435 else:
436 try:
436 try:
437 shutil.copyfile(src, dest)
437 shutil.copyfile(src, dest)
438 shutil.copystat(src, dest)
438 shutil.copystat(src, dest)
439 except shutil.Error, inst:
439 except shutil.Error, inst:
440 raise Abort(str(inst))
440 raise Abort(str(inst))
441
441
442 def copyfiles(src, dst, hardlink=None):
442 def copyfiles(src, dst, hardlink=None):
443 """Copy a directory tree using hardlinks if possible"""
443 """Copy a directory tree using hardlinks if possible"""
444
444
445 if hardlink is None:
445 if hardlink is None:
446 hardlink = (os.stat(src).st_dev ==
446 hardlink = (os.stat(src).st_dev ==
447 os.stat(os.path.dirname(dst)).st_dev)
447 os.stat(os.path.dirname(dst)).st_dev)
448
448
449 if os.path.isdir(src):
449 if os.path.isdir(src):
450 os.mkdir(dst)
450 os.mkdir(dst)
451 for name, kind in osutil.listdir(src):
451 for name, kind in osutil.listdir(src):
452 srcname = os.path.join(src, name)
452 srcname = os.path.join(src, name)
453 dstname = os.path.join(dst, name)
453 dstname = os.path.join(dst, name)
454 copyfiles(srcname, dstname, hardlink)
454 copyfiles(srcname, dstname, hardlink)
455 else:
455 else:
456 if hardlink:
456 if hardlink:
457 try:
457 try:
458 os_link(src, dst)
458 os_link(src, dst)
459 except (IOError, OSError):
459 except (IOError, OSError):
460 hardlink = False
460 hardlink = False
461 shutil.copy(src, dst)
461 shutil.copy(src, dst)
462 else:
462 else:
463 shutil.copy(src, dst)
463 shutil.copy(src, dst)
464
464
465 class path_auditor(object):
465 class path_auditor(object):
466 '''ensure that a filesystem path contains no banned components.
466 '''ensure that a filesystem path contains no banned components.
467 the following properties of a path are checked:
467 the following properties of a path are checked:
468
468
469 - under top-level .hg
469 - under top-level .hg
470 - starts at the root of a windows drive
470 - starts at the root of a windows drive
471 - contains ".."
471 - contains ".."
472 - traverses a symlink (e.g. a/symlink_here/b)
472 - traverses a symlink (e.g. a/symlink_here/b)
473 - inside a nested repository'''
473 - inside a nested repository'''
474
474
475 def __init__(self, root):
475 def __init__(self, root):
476 self.audited = set()
476 self.audited = set()
477 self.auditeddir = set()
477 self.auditeddir = set()
478 self.root = root
478 self.root = root
479
479
480 def __call__(self, path):
480 def __call__(self, path):
481 if path in self.audited:
481 if path in self.audited:
482 return
482 return
483 normpath = os.path.normcase(path)
483 normpath = os.path.normcase(path)
484 parts = splitpath(normpath)
484 parts = splitpath(normpath)
485 if (os.path.splitdrive(path)[0]
485 if (os.path.splitdrive(path)[0]
486 or parts[0].lower() in ('.hg', '.hg.', '')
486 or parts[0].lower() in ('.hg', '.hg.', '')
487 or os.pardir in parts):
487 or os.pardir in parts):
488 raise Abort(_("path contains illegal component: %s") % path)
488 raise Abort(_("path contains illegal component: %s") % path)
489 if '.hg' in path.lower():
489 if '.hg' in path.lower():
490 lparts = [p.lower() for p in parts]
490 lparts = [p.lower() for p in parts]
491 for p in '.hg', '.hg.':
491 for p in '.hg', '.hg.':
492 if p in lparts[1:]:
492 if p in lparts[1:]:
493 pos = lparts.index(p)
493 pos = lparts.index(p)
494 base = os.path.join(*parts[:pos])
494 base = os.path.join(*parts[:pos])
495 raise Abort(_('path %r is inside repo %r') % (path, base))
495 raise Abort(_('path %r is inside repo %r') % (path, base))
496 def check(prefix):
496 def check(prefix):
497 curpath = os.path.join(self.root, prefix)
497 curpath = os.path.join(self.root, prefix)
498 try:
498 try:
499 st = os.lstat(curpath)
499 st = os.lstat(curpath)
500 except OSError, err:
500 except OSError, err:
501 # EINVAL can be raised as invalid path syntax under win32.
501 # EINVAL can be raised as invalid path syntax under win32.
502 # They must be ignored for patterns can be checked too.
502 # They must be ignored for patterns can be checked too.
503 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
503 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
504 raise
504 raise
505 else:
505 else:
506 if stat.S_ISLNK(st.st_mode):
506 if stat.S_ISLNK(st.st_mode):
507 raise Abort(_('path %r traverses symbolic link %r') %
507 raise Abort(_('path %r traverses symbolic link %r') %
508 (path, prefix))
508 (path, prefix))
509 elif (stat.S_ISDIR(st.st_mode) and
509 elif (stat.S_ISDIR(st.st_mode) and
510 os.path.isdir(os.path.join(curpath, '.hg'))):
510 os.path.isdir(os.path.join(curpath, '.hg'))):
511 raise Abort(_('path %r is inside repo %r') %
511 raise Abort(_('path %r is inside repo %r') %
512 (path, prefix))
512 (path, prefix))
513 parts.pop()
513 parts.pop()
514 prefixes = []
514 prefixes = []
515 for n in range(len(parts)):
515 while parts:
516 prefix = os.sep.join(parts)
516 prefix = os.sep.join(parts)
517 if prefix in self.auditeddir:
517 if prefix in self.auditeddir:
518 break
518 break
519 check(prefix)
519 check(prefix)
520 prefixes.append(prefix)
520 prefixes.append(prefix)
521 parts.pop()
521 parts.pop()
522
522
523 self.audited.add(path)
523 self.audited.add(path)
524 # only add prefixes to the cache after checking everything: we don't
524 # only add prefixes to the cache after checking everything: we don't
525 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
525 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
526 self.auditeddir.update(prefixes)
526 self.auditeddir.update(prefixes)
527
527
528 def nlinks(pathname):
528 def nlinks(pathname):
529 """Return number of hardlinks for the given file."""
529 """Return number of hardlinks for the given file."""
530 return os.lstat(pathname).st_nlink
530 return os.lstat(pathname).st_nlink
531
531
532 if hasattr(os, 'link'):
532 if hasattr(os, 'link'):
533 os_link = os.link
533 os_link = os.link
534 else:
534 else:
535 def os_link(src, dst):
535 def os_link(src, dst):
536 raise OSError(0, _("Hardlinks not supported"))
536 raise OSError(0, _("Hardlinks not supported"))
537
537
538 def lookup_reg(key, name=None, scope=None):
538 def lookup_reg(key, name=None, scope=None):
539 return None
539 return None
540
540
541 if os.name == 'nt':
541 if os.name == 'nt':
542 from windows import *
542 from windows import *
543 else:
543 else:
544 from posix import *
544 from posix import *
545
545
546 def makelock(info, pathname):
546 def makelock(info, pathname):
547 try:
547 try:
548 return os.symlink(info, pathname)
548 return os.symlink(info, pathname)
549 except OSError, why:
549 except OSError, why:
550 if why.errno == errno.EEXIST:
550 if why.errno == errno.EEXIST:
551 raise
551 raise
552 except AttributeError: # no symlink in os
552 except AttributeError: # no symlink in os
553 pass
553 pass
554
554
555 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
555 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
556 os.write(ld, info)
556 os.write(ld, info)
557 os.close(ld)
557 os.close(ld)
558
558
559 def readlock(pathname):
559 def readlock(pathname):
560 try:
560 try:
561 return os.readlink(pathname)
561 return os.readlink(pathname)
562 except OSError, why:
562 except OSError, why:
563 if why.errno not in (errno.EINVAL, errno.ENOSYS):
563 if why.errno not in (errno.EINVAL, errno.ENOSYS):
564 raise
564 raise
565 except AttributeError: # no symlink in os
565 except AttributeError: # no symlink in os
566 pass
566 pass
567 return posixfile(pathname).read()
567 return posixfile(pathname).read()
568
568
569 def fstat(fp):
569 def fstat(fp):
570 '''stat file object that may not have fileno method.'''
570 '''stat file object that may not have fileno method.'''
571 try:
571 try:
572 return os.fstat(fp.fileno())
572 return os.fstat(fp.fileno())
573 except AttributeError:
573 except AttributeError:
574 return os.stat(fp.name)
574 return os.stat(fp.name)
575
575
576 # File system features
576 # File system features
577
577
578 def checkcase(path):
578 def checkcase(path):
579 """
579 """
580 Check whether the given path is on a case-sensitive filesystem
580 Check whether the given path is on a case-sensitive filesystem
581
581
582 Requires a path (like /foo/.hg) ending with a foldable final
582 Requires a path (like /foo/.hg) ending with a foldable final
583 directory component.
583 directory component.
584 """
584 """
585 s1 = os.stat(path)
585 s1 = os.stat(path)
586 d, b = os.path.split(path)
586 d, b = os.path.split(path)
587 p2 = os.path.join(d, b.upper())
587 p2 = os.path.join(d, b.upper())
588 if path == p2:
588 if path == p2:
589 p2 = os.path.join(d, b.lower())
589 p2 = os.path.join(d, b.lower())
590 try:
590 try:
591 s2 = os.stat(p2)
591 s2 = os.stat(p2)
592 if s2 == s1:
592 if s2 == s1:
593 return False
593 return False
594 return True
594 return True
595 except:
595 except:
596 return True
596 return True
597
597
598 _fspathcache = {}
598 _fspathcache = {}
599 def fspath(name, root):
599 def fspath(name, root):
600 '''Get name in the case stored in the filesystem
600 '''Get name in the case stored in the filesystem
601
601
602 The name is either relative to root, or it is an absolute path starting
602 The name is either relative to root, or it is an absolute path starting
603 with root. Note that this function is unnecessary, and should not be
603 with root. Note that this function is unnecessary, and should not be
604 called, for case-sensitive filesystems (simply because it's expensive).
604 called, for case-sensitive filesystems (simply because it's expensive).
605 '''
605 '''
606 # If name is absolute, make it relative
606 # If name is absolute, make it relative
607 if name.lower().startswith(root.lower()):
607 if name.lower().startswith(root.lower()):
608 l = len(root)
608 l = len(root)
609 if name[l] == os.sep or name[l] == os.altsep:
609 if name[l] == os.sep or name[l] == os.altsep:
610 l = l + 1
610 l = l + 1
611 name = name[l:]
611 name = name[l:]
612
612
613 if not os.path.exists(os.path.join(root, name)):
613 if not os.path.exists(os.path.join(root, name)):
614 return None
614 return None
615
615
616 seps = os.sep
616 seps = os.sep
617 if os.altsep:
617 if os.altsep:
618 seps = seps + os.altsep
618 seps = seps + os.altsep
619 # Protect backslashes. This gets silly very quickly.
619 # Protect backslashes. This gets silly very quickly.
620 seps.replace('\\','\\\\')
620 seps.replace('\\','\\\\')
621 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
621 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
622 dir = os.path.normcase(os.path.normpath(root))
622 dir = os.path.normcase(os.path.normpath(root))
623 result = []
623 result = []
624 for part, sep in pattern.findall(name):
624 for part, sep in pattern.findall(name):
625 if sep:
625 if sep:
626 result.append(sep)
626 result.append(sep)
627 continue
627 continue
628
628
629 if dir not in _fspathcache:
629 if dir not in _fspathcache:
630 _fspathcache[dir] = os.listdir(dir)
630 _fspathcache[dir] = os.listdir(dir)
631 contents = _fspathcache[dir]
631 contents = _fspathcache[dir]
632
632
633 lpart = part.lower()
633 lpart = part.lower()
634 for n in contents:
634 for n in contents:
635 if n.lower() == lpart:
635 if n.lower() == lpart:
636 result.append(n)
636 result.append(n)
637 break
637 break
638 else:
638 else:
639 # Cannot happen, as the file exists!
639 # Cannot happen, as the file exists!
640 result.append(part)
640 result.append(part)
641 dir = os.path.join(dir, lpart)
641 dir = os.path.join(dir, lpart)
642
642
643 return ''.join(result)
643 return ''.join(result)
644
644
645 def checkexec(path):
645 def checkexec(path):
646 """
646 """
647 Check whether the given path is on a filesystem with UNIX-like exec flags
647 Check whether the given path is on a filesystem with UNIX-like exec flags
648
648
649 Requires a directory (like /foo/.hg)
649 Requires a directory (like /foo/.hg)
650 """
650 """
651
651
652 # VFAT on some Linux versions can flip mode but it doesn't persist
652 # VFAT on some Linux versions can flip mode but it doesn't persist
653 # a FS remount. Frequently we can detect it if files are created
653 # a FS remount. Frequently we can detect it if files are created
654 # with exec bit on.
654 # with exec bit on.
655
655
656 try:
656 try:
657 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
657 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
658 fh, fn = tempfile.mkstemp("", "", path)
658 fh, fn = tempfile.mkstemp("", "", path)
659 try:
659 try:
660 os.close(fh)
660 os.close(fh)
661 m = os.stat(fn).st_mode & 0777
661 m = os.stat(fn).st_mode & 0777
662 new_file_has_exec = m & EXECFLAGS
662 new_file_has_exec = m & EXECFLAGS
663 os.chmod(fn, m ^ EXECFLAGS)
663 os.chmod(fn, m ^ EXECFLAGS)
664 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
664 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
665 finally:
665 finally:
666 os.unlink(fn)
666 os.unlink(fn)
667 except (IOError, OSError):
667 except (IOError, OSError):
668 # we don't care, the user probably won't be able to commit anyway
668 # we don't care, the user probably won't be able to commit anyway
669 return False
669 return False
670 return not (new_file_has_exec or exec_flags_cannot_flip)
670 return not (new_file_has_exec or exec_flags_cannot_flip)
671
671
672 def checklink(path):
672 def checklink(path):
673 """check whether the given path is on a symlink-capable filesystem"""
673 """check whether the given path is on a symlink-capable filesystem"""
674 # mktemp is not racy because symlink creation will fail if the
674 # mktemp is not racy because symlink creation will fail if the
675 # file already exists
675 # file already exists
676 name = tempfile.mktemp(dir=path)
676 name = tempfile.mktemp(dir=path)
677 try:
677 try:
678 os.symlink(".", name)
678 os.symlink(".", name)
679 os.unlink(name)
679 os.unlink(name)
680 return True
680 return True
681 except (OSError, AttributeError):
681 except (OSError, AttributeError):
682 return False
682 return False
683
683
684 def needbinarypatch():
684 def needbinarypatch():
685 """return True if patches should be applied in binary mode by default."""
685 """return True if patches should be applied in binary mode by default."""
686 return os.name == 'nt'
686 return os.name == 'nt'
687
687
688 def endswithsep(path):
688 def endswithsep(path):
689 '''Check path ends with os.sep or os.altsep.'''
689 '''Check path ends with os.sep or os.altsep.'''
690 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
690 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
691
691
692 def splitpath(path):
692 def splitpath(path):
693 '''Split path by os.sep.
693 '''Split path by os.sep.
694 Note that this function does not use os.altsep because this is
694 Note that this function does not use os.altsep because this is
695 an alternative of simple "xxx.split(os.sep)".
695 an alternative of simple "xxx.split(os.sep)".
696 It is recommended to use os.path.normpath() before using this
696 It is recommended to use os.path.normpath() before using this
697 function if need.'''
697 function if need.'''
698 return path.split(os.sep)
698 return path.split(os.sep)
699
699
700 def gui():
700 def gui():
701 '''Are we running in a GUI?'''
701 '''Are we running in a GUI?'''
702 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
702 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
703
703
704 def mktempcopy(name, emptyok=False, createmode=None):
704 def mktempcopy(name, emptyok=False, createmode=None):
705 """Create a temporary file with the same contents from name
705 """Create a temporary file with the same contents from name
706
706
707 The permission bits are copied from the original file.
707 The permission bits are copied from the original file.
708
708
709 If the temporary file is going to be truncated immediately, you
709 If the temporary file is going to be truncated immediately, you
710 can use emptyok=True as an optimization.
710 can use emptyok=True as an optimization.
711
711
712 Returns the name of the temporary file.
712 Returns the name of the temporary file.
713 """
713 """
714 d, fn = os.path.split(name)
714 d, fn = os.path.split(name)
715 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
715 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
716 os.close(fd)
716 os.close(fd)
717 # Temporary files are created with mode 0600, which is usually not
717 # Temporary files are created with mode 0600, which is usually not
718 # what we want. If the original file already exists, just copy
718 # what we want. If the original file already exists, just copy
719 # its mode. Otherwise, manually obey umask.
719 # its mode. Otherwise, manually obey umask.
720 try:
720 try:
721 st_mode = os.lstat(name).st_mode & 0777
721 st_mode = os.lstat(name).st_mode & 0777
722 except OSError, inst:
722 except OSError, inst:
723 if inst.errno != errno.ENOENT:
723 if inst.errno != errno.ENOENT:
724 raise
724 raise
725 st_mode = createmode
725 st_mode = createmode
726 if st_mode is None:
726 if st_mode is None:
727 st_mode = ~umask
727 st_mode = ~umask
728 st_mode &= 0666
728 st_mode &= 0666
729 os.chmod(temp, st_mode)
729 os.chmod(temp, st_mode)
730 if emptyok:
730 if emptyok:
731 return temp
731 return temp
732 try:
732 try:
733 try:
733 try:
734 ifp = posixfile(name, "rb")
734 ifp = posixfile(name, "rb")
735 except IOError, inst:
735 except IOError, inst:
736 if inst.errno == errno.ENOENT:
736 if inst.errno == errno.ENOENT:
737 return temp
737 return temp
738 if not getattr(inst, 'filename', None):
738 if not getattr(inst, 'filename', None):
739 inst.filename = name
739 inst.filename = name
740 raise
740 raise
741 ofp = posixfile(temp, "wb")
741 ofp = posixfile(temp, "wb")
742 for chunk in filechunkiter(ifp):
742 for chunk in filechunkiter(ifp):
743 ofp.write(chunk)
743 ofp.write(chunk)
744 ifp.close()
744 ifp.close()
745 ofp.close()
745 ofp.close()
746 except:
746 except:
747 try: os.unlink(temp)
747 try: os.unlink(temp)
748 except: pass
748 except: pass
749 raise
749 raise
750 return temp
750 return temp
751
751
752 class atomictempfile:
752 class atomictempfile:
753 """file-like object that atomically updates a file
753 """file-like object that atomically updates a file
754
754
755 All writes will be redirected to a temporary copy of the original
755 All writes will be redirected to a temporary copy of the original
756 file. When rename is called, the copy is renamed to the original
756 file. When rename is called, the copy is renamed to the original
757 name, making the changes visible.
757 name, making the changes visible.
758 """
758 """
759 def __init__(self, name, mode, createmode):
759 def __init__(self, name, mode, createmode):
760 self.__name = name
760 self.__name = name
761 self._fp = None
761 self._fp = None
762 self.temp = mktempcopy(name, emptyok=('w' in mode),
762 self.temp = mktempcopy(name, emptyok=('w' in mode),
763 createmode=createmode)
763 createmode=createmode)
764 self._fp = posixfile(self.temp, mode)
764 self._fp = posixfile(self.temp, mode)
765
765
766 def __getattr__(self, name):
766 def __getattr__(self, name):
767 return getattr(self._fp, name)
767 return getattr(self._fp, name)
768
768
769 def rename(self):
769 def rename(self):
770 if not self.closed:
770 if not self.closed:
771 self._fp.close()
771 self._fp.close()
772 rename(self.temp, localpath(self.__name))
772 rename(self.temp, localpath(self.__name))
773
773
774 def __del__(self):
774 def __del__(self):
775 if not self.closed:
775 if not self.closed:
776 try:
776 try:
777 os.unlink(self.temp)
777 os.unlink(self.temp)
778 except: pass
778 except: pass
779 if self._fp:
779 if self._fp:
780 self._fp.close()
780 self._fp.close()
781
781
782 def makedirs(name, mode=None):
782 def makedirs(name, mode=None):
783 """recursive directory creation with parent mode inheritance"""
783 """recursive directory creation with parent mode inheritance"""
784 try:
784 try:
785 os.mkdir(name)
785 os.mkdir(name)
786 if mode is not None:
786 if mode is not None:
787 os.chmod(name, mode)
787 os.chmod(name, mode)
788 return
788 return
789 except OSError, err:
789 except OSError, err:
790 if err.errno == errno.EEXIST:
790 if err.errno == errno.EEXIST:
791 return
791 return
792 if err.errno != errno.ENOENT:
792 if err.errno != errno.ENOENT:
793 raise
793 raise
794 parent = os.path.abspath(os.path.dirname(name))
794 parent = os.path.abspath(os.path.dirname(name))
795 makedirs(parent, mode)
795 makedirs(parent, mode)
796 makedirs(name, mode)
796 makedirs(name, mode)
797
797
798 class opener(object):
798 class opener(object):
799 """Open files relative to a base directory
799 """Open files relative to a base directory
800
800
801 This class is used to hide the details of COW semantics and
801 This class is used to hide the details of COW semantics and
802 remote file access from higher level code.
802 remote file access from higher level code.
803 """
803 """
804 def __init__(self, base, audit=True):
804 def __init__(self, base, audit=True):
805 self.base = base
805 self.base = base
806 if audit:
806 if audit:
807 self.audit_path = path_auditor(base)
807 self.audit_path = path_auditor(base)
808 else:
808 else:
809 self.audit_path = always
809 self.audit_path = always
810 self.createmode = None
810 self.createmode = None
811
811
812 def __getattr__(self, name):
812 def __getattr__(self, name):
813 if name == '_can_symlink':
813 if name == '_can_symlink':
814 self._can_symlink = checklink(self.base)
814 self._can_symlink = checklink(self.base)
815 return self._can_symlink
815 return self._can_symlink
816 raise AttributeError(name)
816 raise AttributeError(name)
817
817
818 def _fixfilemode(self, name):
818 def _fixfilemode(self, name):
819 if self.createmode is None:
819 if self.createmode is None:
820 return
820 return
821 os.chmod(name, self.createmode & 0666)
821 os.chmod(name, self.createmode & 0666)
822
822
823 def __call__(self, path, mode="r", text=False, atomictemp=False):
823 def __call__(self, path, mode="r", text=False, atomictemp=False):
824 self.audit_path(path)
824 self.audit_path(path)
825 f = os.path.join(self.base, path)
825 f = os.path.join(self.base, path)
826
826
827 if not text and "b" not in mode:
827 if not text and "b" not in mode:
828 mode += "b" # for that other OS
828 mode += "b" # for that other OS
829
829
830 nlink = -1
830 nlink = -1
831 if mode not in ("r", "rb"):
831 if mode not in ("r", "rb"):
832 try:
832 try:
833 nlink = nlinks(f)
833 nlink = nlinks(f)
834 except OSError:
834 except OSError:
835 nlink = 0
835 nlink = 0
836 d = os.path.dirname(f)
836 d = os.path.dirname(f)
837 if not os.path.isdir(d):
837 if not os.path.isdir(d):
838 makedirs(d, self.createmode)
838 makedirs(d, self.createmode)
839 if atomictemp:
839 if atomictemp:
840 return atomictempfile(f, mode, self.createmode)
840 return atomictempfile(f, mode, self.createmode)
841 if nlink > 1:
841 if nlink > 1:
842 rename(mktempcopy(f), f)
842 rename(mktempcopy(f), f)
843 fp = posixfile(f, mode)
843 fp = posixfile(f, mode)
844 if nlink == 0:
844 if nlink == 0:
845 self._fixfilemode(f)
845 self._fixfilemode(f)
846 return fp
846 return fp
847
847
848 def symlink(self, src, dst):
848 def symlink(self, src, dst):
849 self.audit_path(dst)
849 self.audit_path(dst)
850 linkname = os.path.join(self.base, dst)
850 linkname = os.path.join(self.base, dst)
851 try:
851 try:
852 os.unlink(linkname)
852 os.unlink(linkname)
853 except OSError:
853 except OSError:
854 pass
854 pass
855
855
856 dirname = os.path.dirname(linkname)
856 dirname = os.path.dirname(linkname)
857 if not os.path.exists(dirname):
857 if not os.path.exists(dirname):
858 makedirs(dirname, self.createmode)
858 makedirs(dirname, self.createmode)
859
859
860 if self._can_symlink:
860 if self._can_symlink:
861 try:
861 try:
862 os.symlink(src, linkname)
862 os.symlink(src, linkname)
863 except OSError, err:
863 except OSError, err:
864 raise OSError(err.errno, _('could not symlink to %r: %s') %
864 raise OSError(err.errno, _('could not symlink to %r: %s') %
865 (src, err.strerror), linkname)
865 (src, err.strerror), linkname)
866 else:
866 else:
867 f = self(dst, "w")
867 f = self(dst, "w")
868 f.write(src)
868 f.write(src)
869 f.close()
869 f.close()
870 self._fixfilemode(dst)
870 self._fixfilemode(dst)
871
871
872 class chunkbuffer(object):
872 class chunkbuffer(object):
873 """Allow arbitrary sized chunks of data to be efficiently read from an
873 """Allow arbitrary sized chunks of data to be efficiently read from an
874 iterator over chunks of arbitrary size."""
874 iterator over chunks of arbitrary size."""
875
875
876 def __init__(self, in_iter):
876 def __init__(self, in_iter):
877 """in_iter is the iterator that's iterating over the input chunks.
877 """in_iter is the iterator that's iterating over the input chunks.
878 targetsize is how big a buffer to try to maintain."""
878 targetsize is how big a buffer to try to maintain."""
879 self.iter = iter(in_iter)
879 self.iter = iter(in_iter)
880 self.buf = ''
880 self.buf = ''
881 self.targetsize = 2**16
881 self.targetsize = 2**16
882
882
883 def read(self, l):
883 def read(self, l):
884 """Read L bytes of data from the iterator of chunks of data.
884 """Read L bytes of data from the iterator of chunks of data.
885 Returns less than L bytes if the iterator runs dry."""
885 Returns less than L bytes if the iterator runs dry."""
886 if l > len(self.buf) and self.iter:
886 if l > len(self.buf) and self.iter:
887 # Clamp to a multiple of self.targetsize
887 # Clamp to a multiple of self.targetsize
888 targetsize = max(l, self.targetsize)
888 targetsize = max(l, self.targetsize)
889 collector = cStringIO.StringIO()
889 collector = cStringIO.StringIO()
890 collector.write(self.buf)
890 collector.write(self.buf)
891 collected = len(self.buf)
891 collected = len(self.buf)
892 for chunk in self.iter:
892 for chunk in self.iter:
893 collector.write(chunk)
893 collector.write(chunk)
894 collected += len(chunk)
894 collected += len(chunk)
895 if collected >= targetsize:
895 if collected >= targetsize:
896 break
896 break
897 if collected < targetsize:
897 if collected < targetsize:
898 self.iter = False
898 self.iter = False
899 self.buf = collector.getvalue()
899 self.buf = collector.getvalue()
900 if len(self.buf) == l:
900 if len(self.buf) == l:
901 s, self.buf = str(self.buf), ''
901 s, self.buf = str(self.buf), ''
902 else:
902 else:
903 s, self.buf = self.buf[:l], buffer(self.buf, l)
903 s, self.buf = self.buf[:l], buffer(self.buf, l)
904 return s
904 return s
905
905
906 def filechunkiter(f, size=65536, limit=None):
906 def filechunkiter(f, size=65536, limit=None):
907 """Create a generator that produces the data in the file size
907 """Create a generator that produces the data in the file size
908 (default 65536) bytes at a time, up to optional limit (default is
908 (default 65536) bytes at a time, up to optional limit (default is
909 to read all data). Chunks may be less than size bytes if the
909 to read all data). Chunks may be less than size bytes if the
910 chunk is the last chunk in the file, or the file is a socket or
910 chunk is the last chunk in the file, or the file is a socket or
911 some other type of file that sometimes reads less data than is
911 some other type of file that sometimes reads less data than is
912 requested."""
912 requested."""
913 assert size >= 0
913 assert size >= 0
914 assert limit is None or limit >= 0
914 assert limit is None or limit >= 0
915 while True:
915 while True:
916 if limit is None: nbytes = size
916 if limit is None: nbytes = size
917 else: nbytes = min(limit, size)
917 else: nbytes = min(limit, size)
918 s = nbytes and f.read(nbytes)
918 s = nbytes and f.read(nbytes)
919 if not s: break
919 if not s: break
920 if limit: limit -= len(s)
920 if limit: limit -= len(s)
921 yield s
921 yield s
922
922
923 def makedate():
923 def makedate():
924 lt = time.localtime()
924 lt = time.localtime()
925 if lt[8] == 1 and time.daylight:
925 if lt[8] == 1 and time.daylight:
926 tz = time.altzone
926 tz = time.altzone
927 else:
927 else:
928 tz = time.timezone
928 tz = time.timezone
929 return time.mktime(lt), tz
929 return time.mktime(lt), tz
930
930
931 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
931 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
932 """represent a (unixtime, offset) tuple as a localized time.
932 """represent a (unixtime, offset) tuple as a localized time.
933 unixtime is seconds since the epoch, and offset is the time zone's
933 unixtime is seconds since the epoch, and offset is the time zone's
934 number of seconds away from UTC. if timezone is false, do not
934 number of seconds away from UTC. if timezone is false, do not
935 append time zone to string."""
935 append time zone to string."""
936 t, tz = date or makedate()
936 t, tz = date or makedate()
937 if "%1" in format or "%2" in format:
937 if "%1" in format or "%2" in format:
938 sign = (tz > 0) and "-" or "+"
938 sign = (tz > 0) and "-" or "+"
939 minutes = abs(tz) / 60
939 minutes = abs(tz) / 60
940 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
940 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
941 format = format.replace("%2", "%02d" % (minutes % 60))
941 format = format.replace("%2", "%02d" % (minutes % 60))
942 s = time.strftime(format, time.gmtime(float(t) - tz))
942 s = time.strftime(format, time.gmtime(float(t) - tz))
943 return s
943 return s
944
944
945 def shortdate(date=None):
945 def shortdate(date=None):
946 """turn (timestamp, tzoff) tuple into iso 8631 date."""
946 """turn (timestamp, tzoff) tuple into iso 8631 date."""
947 return datestr(date, format='%Y-%m-%d')
947 return datestr(date, format='%Y-%m-%d')
948
948
949 def strdate(string, format, defaults=[]):
949 def strdate(string, format, defaults=[]):
950 """parse a localized time string and return a (unixtime, offset) tuple.
950 """parse a localized time string and return a (unixtime, offset) tuple.
951 if the string cannot be parsed, ValueError is raised."""
951 if the string cannot be parsed, ValueError is raised."""
952 def timezone(string):
952 def timezone(string):
953 tz = string.split()[-1]
953 tz = string.split()[-1]
954 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
954 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
955 sign = (tz[0] == "+") and 1 or -1
955 sign = (tz[0] == "+") and 1 or -1
956 hours = int(tz[1:3])
956 hours = int(tz[1:3])
957 minutes = int(tz[3:5])
957 minutes = int(tz[3:5])
958 return -sign * (hours * 60 + minutes) * 60
958 return -sign * (hours * 60 + minutes) * 60
959 if tz == "GMT" or tz == "UTC":
959 if tz == "GMT" or tz == "UTC":
960 return 0
960 return 0
961 return None
961 return None
962
962
963 # NOTE: unixtime = localunixtime + offset
963 # NOTE: unixtime = localunixtime + offset
964 offset, date = timezone(string), string
964 offset, date = timezone(string), string
965 if offset != None:
965 if offset != None:
966 date = " ".join(string.split()[:-1])
966 date = " ".join(string.split()[:-1])
967
967
968 # add missing elements from defaults
968 # add missing elements from defaults
969 for part in defaults:
969 for part in defaults:
970 found = [True for p in part if ("%"+p) in format]
970 found = [True for p in part if ("%"+p) in format]
971 if not found:
971 if not found:
972 date += "@" + defaults[part]
972 date += "@" + defaults[part]
973 format += "@%" + part[0]
973 format += "@%" + part[0]
974
974
975 timetuple = time.strptime(date, format)
975 timetuple = time.strptime(date, format)
976 localunixtime = int(calendar.timegm(timetuple))
976 localunixtime = int(calendar.timegm(timetuple))
977 if offset is None:
977 if offset is None:
978 # local timezone
978 # local timezone
979 unixtime = int(time.mktime(timetuple))
979 unixtime = int(time.mktime(timetuple))
980 offset = unixtime - localunixtime
980 offset = unixtime - localunixtime
981 else:
981 else:
982 unixtime = localunixtime + offset
982 unixtime = localunixtime + offset
983 return unixtime, offset
983 return unixtime, offset
984
984
985 def parsedate(date, formats=None, defaults=None):
985 def parsedate(date, formats=None, defaults=None):
986 """parse a localized date/time string and return a (unixtime, offset) tuple.
986 """parse a localized date/time string and return a (unixtime, offset) tuple.
987
987
988 The date may be a "unixtime offset" string or in one of the specified
988 The date may be a "unixtime offset" string or in one of the specified
989 formats. If the date already is a (unixtime, offset) tuple, it is returned.
989 formats. If the date already is a (unixtime, offset) tuple, it is returned.
990 """
990 """
991 if not date:
991 if not date:
992 return 0, 0
992 return 0, 0
993 if isinstance(date, tuple) and len(date) == 2:
993 if isinstance(date, tuple) and len(date) == 2:
994 return date
994 return date
995 if not formats:
995 if not formats:
996 formats = defaultdateformats
996 formats = defaultdateformats
997 date = date.strip()
997 date = date.strip()
998 try:
998 try:
999 when, offset = map(int, date.split(' '))
999 when, offset = map(int, date.split(' '))
1000 except ValueError:
1000 except ValueError:
1001 # fill out defaults
1001 # fill out defaults
1002 if not defaults:
1002 if not defaults:
1003 defaults = {}
1003 defaults = {}
1004 now = makedate()
1004 now = makedate()
1005 for part in "d mb yY HI M S".split():
1005 for part in "d mb yY HI M S".split():
1006 if part not in defaults:
1006 if part not in defaults:
1007 if part[0] in "HMS":
1007 if part[0] in "HMS":
1008 defaults[part] = "00"
1008 defaults[part] = "00"
1009 else:
1009 else:
1010 defaults[part] = datestr(now, "%" + part[0])
1010 defaults[part] = datestr(now, "%" + part[0])
1011
1011
1012 for format in formats:
1012 for format in formats:
1013 try:
1013 try:
1014 when, offset = strdate(date, format, defaults)
1014 when, offset = strdate(date, format, defaults)
1015 except (ValueError, OverflowError):
1015 except (ValueError, OverflowError):
1016 pass
1016 pass
1017 else:
1017 else:
1018 break
1018 break
1019 else:
1019 else:
1020 raise Abort(_('invalid date: %r ') % date)
1020 raise Abort(_('invalid date: %r ') % date)
1021 # validate explicit (probably user-specified) date and
1021 # validate explicit (probably user-specified) date and
1022 # time zone offset. values must fit in signed 32 bits for
1022 # time zone offset. values must fit in signed 32 bits for
1023 # current 32-bit linux runtimes. timezones go from UTC-12
1023 # current 32-bit linux runtimes. timezones go from UTC-12
1024 # to UTC+14
1024 # to UTC+14
1025 if abs(when) > 0x7fffffff:
1025 if abs(when) > 0x7fffffff:
1026 raise Abort(_('date exceeds 32 bits: %d') % when)
1026 raise Abort(_('date exceeds 32 bits: %d') % when)
1027 if offset < -50400 or offset > 43200:
1027 if offset < -50400 or offset > 43200:
1028 raise Abort(_('impossible time zone offset: %d') % offset)
1028 raise Abort(_('impossible time zone offset: %d') % offset)
1029 return when, offset
1029 return when, offset
1030
1030
1031 def matchdate(date):
1031 def matchdate(date):
1032 """Return a function that matches a given date match specifier
1032 """Return a function that matches a given date match specifier
1033
1033
1034 Formats include:
1034 Formats include:
1035
1035
1036 '{date}' match a given date to the accuracy provided
1036 '{date}' match a given date to the accuracy provided
1037
1037
1038 '<{date}' on or before a given date
1038 '<{date}' on or before a given date
1039
1039
1040 '>{date}' on or after a given date
1040 '>{date}' on or after a given date
1041
1041
1042 """
1042 """
1043
1043
1044 def lower(date):
1044 def lower(date):
1045 d = dict(mb="1", d="1")
1045 d = dict(mb="1", d="1")
1046 return parsedate(date, extendeddateformats, d)[0]
1046 return parsedate(date, extendeddateformats, d)[0]
1047
1047
1048 def upper(date):
1048 def upper(date):
1049 d = dict(mb="12", HI="23", M="59", S="59")
1049 d = dict(mb="12", HI="23", M="59", S="59")
1050 for days in "31 30 29".split():
1050 for days in "31 30 29".split():
1051 try:
1051 try:
1052 d["d"] = days
1052 d["d"] = days
1053 return parsedate(date, extendeddateformats, d)[0]
1053 return parsedate(date, extendeddateformats, d)[0]
1054 except:
1054 except:
1055 pass
1055 pass
1056 d["d"] = "28"
1056 d["d"] = "28"
1057 return parsedate(date, extendeddateformats, d)[0]
1057 return parsedate(date, extendeddateformats, d)[0]
1058
1058
1059 date = date.strip()
1059 date = date.strip()
1060 if date[0] == "<":
1060 if date[0] == "<":
1061 when = upper(date[1:])
1061 when = upper(date[1:])
1062 return lambda x: x <= when
1062 return lambda x: x <= when
1063 elif date[0] == ">":
1063 elif date[0] == ">":
1064 when = lower(date[1:])
1064 when = lower(date[1:])
1065 return lambda x: x >= when
1065 return lambda x: x >= when
1066 elif date[0] == "-":
1066 elif date[0] == "-":
1067 try:
1067 try:
1068 days = int(date[1:])
1068 days = int(date[1:])
1069 except ValueError:
1069 except ValueError:
1070 raise Abort(_("invalid day spec: %s") % date[1:])
1070 raise Abort(_("invalid day spec: %s") % date[1:])
1071 when = makedate()[0] - days * 3600 * 24
1071 when = makedate()[0] - days * 3600 * 24
1072 return lambda x: x >= when
1072 return lambda x: x >= when
1073 elif " to " in date:
1073 elif " to " in date:
1074 a, b = date.split(" to ")
1074 a, b = date.split(" to ")
1075 start, stop = lower(a), upper(b)
1075 start, stop = lower(a), upper(b)
1076 return lambda x: x >= start and x <= stop
1076 return lambda x: x >= start and x <= stop
1077 else:
1077 else:
1078 start, stop = lower(date), upper(date)
1078 start, stop = lower(date), upper(date)
1079 return lambda x: x >= start and x <= stop
1079 return lambda x: x >= start and x <= stop
1080
1080
1081 def shortuser(user):
1081 def shortuser(user):
1082 """Return a short representation of a user name or email address."""
1082 """Return a short representation of a user name or email address."""
1083 f = user.find('@')
1083 f = user.find('@')
1084 if f >= 0:
1084 if f >= 0:
1085 user = user[:f]
1085 user = user[:f]
1086 f = user.find('<')
1086 f = user.find('<')
1087 if f >= 0:
1087 if f >= 0:
1088 user = user[f+1:]
1088 user = user[f+1:]
1089 f = user.find(' ')
1089 f = user.find(' ')
1090 if f >= 0:
1090 if f >= 0:
1091 user = user[:f]
1091 user = user[:f]
1092 f = user.find('.')
1092 f = user.find('.')
1093 if f >= 0:
1093 if f >= 0:
1094 user = user[:f]
1094 user = user[:f]
1095 return user
1095 return user
1096
1096
1097 def email(author):
1097 def email(author):
1098 '''get email of author.'''
1098 '''get email of author.'''
1099 r = author.find('>')
1099 r = author.find('>')
1100 if r == -1: r = None
1100 if r == -1: r = None
1101 return author[author.find('<')+1:r]
1101 return author[author.find('<')+1:r]
1102
1102
1103 def ellipsis(text, maxlength=400):
1103 def ellipsis(text, maxlength=400):
1104 """Trim string to at most maxlength (default: 400) characters."""
1104 """Trim string to at most maxlength (default: 400) characters."""
1105 if len(text) <= maxlength:
1105 if len(text) <= maxlength:
1106 return text
1106 return text
1107 else:
1107 else:
1108 return "%s..." % (text[:maxlength-3])
1108 return "%s..." % (text[:maxlength-3])
1109
1109
1110 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1110 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1111 '''yield every hg repository under path, recursively.'''
1111 '''yield every hg repository under path, recursively.'''
1112 def errhandler(err):
1112 def errhandler(err):
1113 if err.filename == path:
1113 if err.filename == path:
1114 raise err
1114 raise err
1115 if followsym and hasattr(os.path, 'samestat'):
1115 if followsym and hasattr(os.path, 'samestat'):
1116 def _add_dir_if_not_there(dirlst, dirname):
1116 def _add_dir_if_not_there(dirlst, dirname):
1117 match = False
1117 match = False
1118 samestat = os.path.samestat
1118 samestat = os.path.samestat
1119 dirstat = os.stat(dirname)
1119 dirstat = os.stat(dirname)
1120 for lstdirstat in dirlst:
1120 for lstdirstat in dirlst:
1121 if samestat(dirstat, lstdirstat):
1121 if samestat(dirstat, lstdirstat):
1122 match = True
1122 match = True
1123 break
1123 break
1124 if not match:
1124 if not match:
1125 dirlst.append(dirstat)
1125 dirlst.append(dirstat)
1126 return not match
1126 return not match
1127 else:
1127 else:
1128 followsym = False
1128 followsym = False
1129
1129
1130 if (seen_dirs is None) and followsym:
1130 if (seen_dirs is None) and followsym:
1131 seen_dirs = []
1131 seen_dirs = []
1132 _add_dir_if_not_there(seen_dirs, path)
1132 _add_dir_if_not_there(seen_dirs, path)
1133 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1133 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1134 if '.hg' in dirs:
1134 if '.hg' in dirs:
1135 yield root # found a repository
1135 yield root # found a repository
1136 qroot = os.path.join(root, '.hg', 'patches')
1136 qroot = os.path.join(root, '.hg', 'patches')
1137 if os.path.isdir(os.path.join(qroot, '.hg')):
1137 if os.path.isdir(os.path.join(qroot, '.hg')):
1138 yield qroot # we have a patch queue repo here
1138 yield qroot # we have a patch queue repo here
1139 if recurse:
1139 if recurse:
1140 # avoid recursing inside the .hg directory
1140 # avoid recursing inside the .hg directory
1141 dirs.remove('.hg')
1141 dirs.remove('.hg')
1142 else:
1142 else:
1143 dirs[:] = [] # don't descend further
1143 dirs[:] = [] # don't descend further
1144 elif followsym:
1144 elif followsym:
1145 newdirs = []
1145 newdirs = []
1146 for d in dirs:
1146 for d in dirs:
1147 fname = os.path.join(root, d)
1147 fname = os.path.join(root, d)
1148 if _add_dir_if_not_there(seen_dirs, fname):
1148 if _add_dir_if_not_there(seen_dirs, fname):
1149 if os.path.islink(fname):
1149 if os.path.islink(fname):
1150 for hgname in walkrepos(fname, True, seen_dirs):
1150 for hgname in walkrepos(fname, True, seen_dirs):
1151 yield hgname
1151 yield hgname
1152 else:
1152 else:
1153 newdirs.append(d)
1153 newdirs.append(d)
1154 dirs[:] = newdirs
1154 dirs[:] = newdirs
1155
1155
1156 _rcpath = None
1156 _rcpath = None
1157
1157
1158 def os_rcpath():
1158 def os_rcpath():
1159 '''return default os-specific hgrc search path'''
1159 '''return default os-specific hgrc search path'''
1160 path = system_rcpath()
1160 path = system_rcpath()
1161 path.extend(user_rcpath())
1161 path.extend(user_rcpath())
1162 path = [os.path.normpath(f) for f in path]
1162 path = [os.path.normpath(f) for f in path]
1163 return path
1163 return path
1164
1164
1165 def rcpath():
1165 def rcpath():
1166 '''return hgrc search path. if env var HGRCPATH is set, use it.
1166 '''return hgrc search path. if env var HGRCPATH is set, use it.
1167 for each item in path, if directory, use files ending in .rc,
1167 for each item in path, if directory, use files ending in .rc,
1168 else use item.
1168 else use item.
1169 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1169 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1170 if no HGRCPATH, use default os-specific path.'''
1170 if no HGRCPATH, use default os-specific path.'''
1171 global _rcpath
1171 global _rcpath
1172 if _rcpath is None:
1172 if _rcpath is None:
1173 if 'HGRCPATH' in os.environ:
1173 if 'HGRCPATH' in os.environ:
1174 _rcpath = []
1174 _rcpath = []
1175 for p in os.environ['HGRCPATH'].split(os.pathsep):
1175 for p in os.environ['HGRCPATH'].split(os.pathsep):
1176 if not p: continue
1176 if not p: continue
1177 if os.path.isdir(p):
1177 if os.path.isdir(p):
1178 for f, kind in osutil.listdir(p):
1178 for f, kind in osutil.listdir(p):
1179 if f.endswith('.rc'):
1179 if f.endswith('.rc'):
1180 _rcpath.append(os.path.join(p, f))
1180 _rcpath.append(os.path.join(p, f))
1181 else:
1181 else:
1182 _rcpath.append(p)
1182 _rcpath.append(p)
1183 else:
1183 else:
1184 _rcpath = os_rcpath()
1184 _rcpath = os_rcpath()
1185 return _rcpath
1185 return _rcpath
1186
1186
1187 def bytecount(nbytes):
1187 def bytecount(nbytes):
1188 '''return byte count formatted as readable string, with units'''
1188 '''return byte count formatted as readable string, with units'''
1189
1189
1190 units = (
1190 units = (
1191 (100, 1<<30, _('%.0f GB')),
1191 (100, 1<<30, _('%.0f GB')),
1192 (10, 1<<30, _('%.1f GB')),
1192 (10, 1<<30, _('%.1f GB')),
1193 (1, 1<<30, _('%.2f GB')),
1193 (1, 1<<30, _('%.2f GB')),
1194 (100, 1<<20, _('%.0f MB')),
1194 (100, 1<<20, _('%.0f MB')),
1195 (10, 1<<20, _('%.1f MB')),
1195 (10, 1<<20, _('%.1f MB')),
1196 (1, 1<<20, _('%.2f MB')),
1196 (1, 1<<20, _('%.2f MB')),
1197 (100, 1<<10, _('%.0f KB')),
1197 (100, 1<<10, _('%.0f KB')),
1198 (10, 1<<10, _('%.1f KB')),
1198 (10, 1<<10, _('%.1f KB')),
1199 (1, 1<<10, _('%.2f KB')),
1199 (1, 1<<10, _('%.2f KB')),
1200 (1, 1, _('%.0f bytes')),
1200 (1, 1, _('%.0f bytes')),
1201 )
1201 )
1202
1202
1203 for multiplier, divisor, format in units:
1203 for multiplier, divisor, format in units:
1204 if nbytes >= divisor * multiplier:
1204 if nbytes >= divisor * multiplier:
1205 return format % (nbytes / float(divisor))
1205 return format % (nbytes / float(divisor))
1206 return units[-1][2] % nbytes
1206 return units[-1][2] % nbytes
1207
1207
1208 def drop_scheme(scheme, path):
1208 def drop_scheme(scheme, path):
1209 sc = scheme + ':'
1209 sc = scheme + ':'
1210 if path.startswith(sc):
1210 if path.startswith(sc):
1211 path = path[len(sc):]
1211 path = path[len(sc):]
1212 if path.startswith('//'):
1212 if path.startswith('//'):
1213 path = path[2:]
1213 path = path[2:]
1214 return path
1214 return path
1215
1215
1216 def uirepr(s):
1216 def uirepr(s):
1217 # Avoid double backslash in Windows path repr()
1217 # Avoid double backslash in Windows path repr()
1218 return repr(s).replace('\\\\', '\\')
1218 return repr(s).replace('\\\\', '\\')
1219
1219
1220 def termwidth():
1220 def termwidth():
1221 if 'COLUMNS' in os.environ:
1221 if 'COLUMNS' in os.environ:
1222 try:
1222 try:
1223 return int(os.environ['COLUMNS'])
1223 return int(os.environ['COLUMNS'])
1224 except ValueError:
1224 except ValueError:
1225 pass
1225 pass
1226 try:
1226 try:
1227 import termios, array, fcntl
1227 import termios, array, fcntl
1228 for dev in (sys.stdout, sys.stdin):
1228 for dev in (sys.stdout, sys.stdin):
1229 try:
1229 try:
1230 try:
1230 try:
1231 fd = dev.fileno()
1231 fd = dev.fileno()
1232 except AttributeError:
1232 except AttributeError:
1233 continue
1233 continue
1234 if not os.isatty(fd):
1234 if not os.isatty(fd):
1235 continue
1235 continue
1236 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1236 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1237 return array.array('h', arri)[1]
1237 return array.array('h', arri)[1]
1238 except ValueError:
1238 except ValueError:
1239 pass
1239 pass
1240 except ImportError:
1240 except ImportError:
1241 pass
1241 pass
1242 return 80
1242 return 80
1243
1243
1244 def iterlines(iterator):
1244 def iterlines(iterator):
1245 for chunk in iterator:
1245 for chunk in iterator:
1246 for line in chunk.splitlines():
1246 for line in chunk.splitlines():
1247 yield line
1247 yield line
General Comments 0
You need to be logged in to leave comments. Login now