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