1 # util.py - Mercurial utility functions and platform specfic implementations
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>
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.
10 """Mercurial utility functions and platform specfic implementations.
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, random, textwrap
22 # Python compatibility
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.
32 from hashlib import sha1 as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
40 closefds = os.name == 'posix'
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,
47 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
48 return p.stdin, p.stdout
50 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
52 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
53 stderr=subprocess.PIPE)
54 return p.stdin, p.stdout, p.stderr
57 """Return version information if available."""
60 return __version__.version
65 defaultdateformats = (
67 '%Y-%m-%d %I:%M:%S%p',
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"
79 '%b %d %I:%M:%S%p %Y',
92 extendeddateformats = defaultdateformats + (
100 '''cache the result of function calls'''
101 # XXX doesn't handle keywords args
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
108 cache[arg] = func(arg)
112 if args not in cache:
113 cache[args] = func(*args)
118 def lrucachefunc(func):
119 '''cache most recent results of function calls'''
122 if func.func_code.co_argcount == 1:
126 del cache[order.pop(0)]
127 cache[arg] = func(arg)
134 if args not in cache:
136 del cache[order.pop(0)]
137 cache[args] = func(*args)
145 class propertycache(object):
146 def __init__(self, func):
148 self.name = func.__name__
149 def __get__(self, obj, type=None):
150 result = self.func(obj)
151 setattr(obj, self.name, result)
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)
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
168 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
169 fp = os.fdopen(infd, 'wb')
172 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
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:
179 if code: raise Abort(_("command '%s' failed: %s") %
180 (cmd, explain_exit(code)))
181 return open(outname, 'rb').read()
184 if inname: os.unlink(inname)
187 if outname: os.unlink(outname)
191 'tempfile:': tempfilter,
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)
203 """return true if a string is binary data"""
204 return bool(s and '\0' in s)
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'''
226 nmin = 1 << log2(blen)
239 def always(fn): return True
240 def never(fn): return False
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.
249 If n1 is a relative path, it's assumed it's
251 n2 should always be relative to root.
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('/')
261 while a and b and a[-1] == b[-1]:
265 return os.sep.join((['..'] * len(a)) + b) or '.'
267 def canonpath(root, cwd, myname):
268 """return the canonical path of myname, given cwd and root"""
271 elif endswithsep(root):
274 rootsep = root + os.sep
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):]
283 return pconvert(name)
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
293 root_st = os.stat(root)
297 name_st = os.stat(name)
300 if samestat(name_st, root_st):
302 # name was actually the same as root (maybe a symlink)
305 name = os.path.join(*rel)
307 return pconvert(name)
308 dirname, basename = os.path.split(name)
314 raise Abort('%s not under root' % myname)
318 def main_is_frozen():
319 """return True if we are a frozen executable.
321 The code supports py2exe (most common, Windows only) and tools/freeze
322 (portable, not much used).
324 return (hasattr(sys, "frozen") or # new py2exe
325 hasattr(sys, "importers") or # old py2exe
326 imp.is_frozen("__main__")) # tools/freeze
329 """return location of the 'hg' executable.
331 Defaults to $HG or 'hg' in the search path.
333 if _hgexecutable is None:
334 hg = os.environ.get('HG')
337 elif main_is_frozen():
338 set_hgexecutable(sys.executable)
340 set_hgexecutable(find_exe('hg') or 'hg')
343 def set_hgexecutable(path):
344 """set location of the 'hg' executable"""
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.
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
356 'convert python object into string that is useful to shell'
357 if val is None or val is False:
364 oldenv[k] = os.environ.get(k)
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:
377 if sys.platform == 'OpenVMS' and rc & 1:
380 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
383 errmsg = '%s: %s' % (errprefix, errmsg)
385 onerr.warn(errmsg + '\n')
386 except AttributeError:
390 for k, v in oldenv.iteritems():
395 if cwd is not None and oldcwd != cwd:
398 def checksignature(func):
399 '''wrap a function with code to check for calling errors'''
400 def check(*args, **kwargs):
402 return func(*args, **kwargs)
404 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
405 raise error.SignatureError
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"
419 def rename(src, dst):
420 """forcibly rename a file"""
423 except OSError, err: # FIXME: check err (EEXIST ?)
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.
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):
443 raise IOError, (errno.EEXIST, "No usable temporary filename found")
451 """unlink and remove the directory if it is empty"""
453 # try removing directories that might now be empty
455 os.removedirs(os.path.dirname(f))
459 def copyfile(src, dest):
460 "copy a file, preserving mode and atime/mtime"
461 if os.path.islink(src):
466 os.symlink(os.readlink(src), dest)
469 shutil.copyfile(src, dest)
470 shutil.copystat(src, dest)
471 except shutil.Error, inst:
472 raise Abort(str(inst))
474 def copyfiles(src, dst, hardlink=None):
475 """Copy a directory tree using hardlinks if possible"""
478 hardlink = (os.stat(src).st_dev ==
479 os.stat(os.path.dirname(dst)).st_dev)
481 if os.path.isdir(src):
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)
491 except (IOError, OSError):
493 shutil.copy(src, dst)
495 shutil.copy(src, dst)
497 class path_auditor(object):
498 '''ensure that a filesystem path contains no banned components.
499 the following properties of a path are checked:
501 - under top-level .hg
502 - starts at the root of a windows drive
504 - traverses a symlink (e.g. a/symlink_here/b)
505 - inside a nested repository'''
507 def __init__(self, root):
509 self.auditeddir = set()
512 def __call__(self, path):
513 if path in self.audited:
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.':
525 pos = lparts.index(p)
526 base = os.path.join(*parts[:pos])
527 raise Abort(_('path %r is inside repo %r') % (path, base))
529 curpath = os.path.join(self.root, prefix)
531 st = os.lstat(curpath)
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):
538 if stat.S_ISLNK(st.st_mode):
539 raise Abort(_('path %r traverses symbolic link %r') %
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') %
548 prefix = os.sep.join(parts)
549 if prefix in self.auditeddir:
552 prefixes.append(prefix)
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)
560 def nlinks(pathname):
561 """Return number of hardlinks for the given file."""
562 return os.lstat(pathname).st_nlink
564 if hasattr(os, 'link'):
567 def os_link(src, dst):
568 raise OSError(0, _("Hardlinks not supported"))
570 def lookup_reg(key, name=None, scope=None):
574 from windows import *
578 def makelock(info, pathname):
580 return os.symlink(info, pathname)
582 if why.errno == errno.EEXIST:
584 except AttributeError: # no symlink in os
587 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
591 def readlock(pathname):
593 return os.readlink(pathname)
595 if why.errno not in (errno.EINVAL, errno.ENOSYS):
597 except AttributeError: # no symlink in os
599 return posixfile(pathname).read()
602 '''stat file object that may not have fileno method.'''
604 return os.fstat(fp.fileno())
605 except AttributeError:
606 return os.stat(fp.name)
608 # File system features
612 Check whether the given path is on a case-sensitive filesystem
614 Requires a path (like /foo/.hg) ending with a foldable final
618 d, b = os.path.split(path)
619 p2 = os.path.join(d, b.upper())
621 p2 = os.path.join(d, b.lower())
631 def fspath(name, root):
632 '''Get name in the case stored in the filesystem
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).
638 # If name is absolute, make it relative
639 if name.lower().startswith(root.lower()):
641 if name[l] == os.sep or name[l] == os.altsep:
645 if not os.path.exists(os.path.join(root, name)):
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))
656 for part, sep in pattern.findall(name):
661 if dir not in _fspathcache:
662 _fspathcache[dir] = os.listdir(dir)
663 contents = _fspathcache[dir]
667 if n.lower() == lpart:
671 # Cannot happen, as the file exists!
673 dir = os.path.join(dir, lpart)
675 return ''.join(result)
679 Check whether the given path is on a filesystem with UNIX-like exec flags
681 Requires a directory (like /foo/.hg)
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
689 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
690 fh, fn = tempfile.mkstemp("", "", path)
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)
699 except (IOError, OSError):
700 # we don't care, the user probably won't be able to commit anyway
702 return not (new_file_has_exec or exec_flags_cannot_flip)
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)
710 os.symlink(".", name)
713 except (OSError, AttributeError):
716 def needbinarypatch():
717 """return True if patches should be applied in binary mode by default."""
718 return os.name == 'nt'
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)
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
730 return path.split(os.sep)
733 '''Are we running in a GUI?'''
734 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
736 def mktempcopy(name, emptyok=False, createmode=None):
737 """Create a temporary file with the same contents from name
739 The permission bits are copied from the original file.
741 If the temporary file is going to be truncated immediately, you
742 can use emptyok=True as an optimization.
744 Returns the name of the temporary file.
746 d, fn = os.path.split(name)
747 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
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.
753 st_mode = os.lstat(name).st_mode & 0777
754 except OSError, inst:
755 if inst.errno != errno.ENOENT:
761 os.chmod(temp, st_mode)
766 ifp = posixfile(name, "rb")
767 except IOError, inst:
768 if inst.errno == errno.ENOENT:
770 if not getattr(inst, 'filename', None):
773 ofp = posixfile(temp, "wb")
774 for chunk in filechunkiter(ifp):
784 class atomictempfile(object):
785 """file-like object that atomically updates a file
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.
791 def __init__(self, name, mode, createmode):
794 self.temp = mktempcopy(name, emptyok=('w' in mode),
795 createmode=createmode)
796 self._fp = posixfile(self.temp, mode)
798 def __getattr__(self, name):
799 return getattr(self._fp, name)
802 if not self._fp.closed:
804 rename(self.temp, localpath(self.__name))
809 if not self._fp.closed:
815 def makedirs(name, mode=None):
816 """recursive directory creation with parent mode inheritance"""
823 if err.errno == errno.EEXIST:
825 if err.errno != errno.ENOENT:
827 parent = os.path.abspath(os.path.dirname(name))
828 makedirs(parent, mode)
831 class opener(object):
832 """Open files relative to a base directory
834 This class is used to hide the details of COW semantics and
835 remote file access from higher level code.
837 def __init__(self, base, audit=True):
840 self.audit_path = path_auditor(base)
842 self.audit_path = always
843 self.createmode = None
846 def _can_symlink(self):
847 return checklink(self.base)
849 def _fixfilemode(self, name):
850 if self.createmode is None:
852 os.chmod(name, self.createmode & 0666)
854 def __call__(self, path, mode="r", text=False, atomictemp=False):
855 self.audit_path(path)
856 f = os.path.join(self.base, path)
858 if not text and "b" not in mode:
859 mode += "b" # for that other OS
862 if mode not in ("r", "rb"):
867 d = os.path.dirname(f)
868 if not os.path.isdir(d):
869 makedirs(d, self.createmode)
871 return atomictempfile(f, mode, self.createmode)
873 rename(mktempcopy(f), f)
874 fp = posixfile(f, mode)
879 def symlink(self, src, dst):
881 linkname = os.path.join(self.base, dst)
887 dirname = os.path.dirname(linkname)
888 if not os.path.exists(dirname):
889 makedirs(dirname, self.createmode)
891 if self._can_symlink:
893 os.symlink(src, linkname)
895 raise OSError(err.errno, _('could not symlink to %r: %s') %
896 (src, err.strerror), linkname)
901 self._fixfilemode(dst)
903 class chunkbuffer(object):
904 """Allow arbitrary sized chunks of data to be efficiently read from an
905 iterator over chunks of arbitrary size."""
907 def __init__(self, in_iter):
908 """in_iter is the iterator that's iterating over the input chunks.
909 targetsize is how big a buffer to try to maintain."""
910 self.iter = iter(in_iter)
912 self.targetsize = 2**16
915 """Read L bytes of data from the iterator of chunks of data.
916 Returns less than L bytes if the iterator runs dry."""
917 if l > len(self.buf) and self.iter:
918 # Clamp to a multiple of self.targetsize
919 targetsize = max(l, self.targetsize)
920 collector = cStringIO.StringIO()
921 collector.write(self.buf)
922 collected = len(self.buf)
923 for chunk in self.iter:
924 collector.write(chunk)
925 collected += len(chunk)
926 if collected >= targetsize:
928 if collected < targetsize:
930 self.buf = collector.getvalue()
931 if len(self.buf) == l:
932 s, self.buf = str(self.buf), ''
934 s, self.buf = self.buf[:l], buffer(self.buf, l)
937 def filechunkiter(f, size=65536, limit=None):
938 """Create a generator that produces the data in the file size
939 (default 65536) bytes at a time, up to optional limit (default is
940 to read all data). Chunks may be less than size bytes if the
941 chunk is the last chunk in the file, or the file is a socket or
942 some other type of file that sometimes reads less data than is
945 assert limit is None or limit >= 0
947 if limit is None: nbytes = size
948 else: nbytes = min(limit, size)
949 s = nbytes and f.read(nbytes)
951 if limit: limit -= len(s)
955 lt = time.localtime()
956 if lt[8] == 1 and time.daylight:
960 return time.mktime(lt), tz
962 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
963 """represent a (unixtime, offset) tuple as a localized time.
964 unixtime is seconds since the epoch, and offset is the time zone's
965 number of seconds away from UTC. if timezone is false, do not
966 append time zone to string."""
967 t, tz = date or makedate()
968 if "%1" in format or "%2" in format:
969 sign = (tz > 0) and "-" or "+"
970 minutes = abs(tz) // 60
971 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
972 format = format.replace("%2", "%02d" % (minutes % 60))
973 s = time.strftime(format, time.gmtime(float(t) - tz))
976 def shortdate(date=None):
977 """turn (timestamp, tzoff) tuple into iso 8631 date."""
978 return datestr(date, format='%Y-%m-%d')
980 def strdate(string, format, defaults=[]):
981 """parse a localized time string and return a (unixtime, offset) tuple.
982 if the string cannot be parsed, ValueError is raised."""
983 def timezone(string):
984 tz = string.split()[-1]
985 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
986 sign = (tz[0] == "+") and 1 or -1
988 minutes = int(tz[3:5])
989 return -sign * (hours * 60 + minutes) * 60
990 if tz == "GMT" or tz == "UTC":
994 # NOTE: unixtime = localunixtime + offset
995 offset, date = timezone(string), string
997 date = " ".join(string.split()[:-1])
999 # add missing elements from defaults
1000 for part in defaults:
1001 found = [True for p in part if ("%"+p) in format]
1003 date += "@" + defaults[part]
1004 format += "@%" + part[0]
1006 timetuple = time.strptime(date, format)
1007 localunixtime = int(calendar.timegm(timetuple))
1010 unixtime = int(time.mktime(timetuple))
1011 offset = unixtime - localunixtime
1013 unixtime = localunixtime + offset
1014 return unixtime, offset
1016 def parsedate(date, formats=None, defaults=None):
1017 """parse a localized date/time string and return a (unixtime, offset) tuple.
1019 The date may be a "unixtime offset" string or in one of the specified
1020 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1024 if isinstance(date, tuple) and len(date) == 2:
1027 formats = defaultdateformats
1030 when, offset = map(int, date.split(' '))
1036 for part in "d mb yY HI M S".split():
1037 if part not in defaults:
1038 if part[0] in "HMS":
1039 defaults[part] = "00"
1041 defaults[part] = datestr(now, "%" + part[0])
1043 for format in formats:
1045 when, offset = strdate(date, format, defaults)
1046 except (ValueError, OverflowError):
1051 raise Abort(_('invalid date: %r ') % date)
1052 # validate explicit (probably user-specified) date and
1053 # time zone offset. values must fit in signed 32 bits for
1054 # current 32-bit linux runtimes. timezones go from UTC-12
1056 if abs(when) > 0x7fffffff:
1057 raise Abort(_('date exceeds 32 bits: %d') % when)
1058 if offset < -50400 or offset > 43200:
1059 raise Abort(_('impossible time zone offset: %d') % offset)
1062 def matchdate(date):
1063 """Return a function that matches a given date match specifier
1067 '{date}' match a given date to the accuracy provided
1069 '<{date}' on or before a given date
1071 '>{date}' on or after a given date
1076 d = dict(mb="1", d="1")
1077 return parsedate(date, extendeddateformats, d)[0]
1080 d = dict(mb="12", HI="23", M="59", S="59")
1081 for days in "31 30 29".split():
1084 return parsedate(date, extendeddateformats, d)[0]
1088 return parsedate(date, extendeddateformats, d)[0]
1092 when = upper(date[1:])
1093 return lambda x: x <= when
1094 elif date[0] == ">":
1095 when = lower(date[1:])
1096 return lambda x: x >= when
1097 elif date[0] == "-":
1099 days = int(date[1:])
1101 raise Abort(_("invalid day spec: %s") % date[1:])
1102 when = makedate()[0] - days * 3600 * 24
1103 return lambda x: x >= when
1104 elif " to " in date:
1105 a, b = date.split(" to ")
1106 start, stop = lower(a), upper(b)
1107 return lambda x: x >= start and x <= stop
1109 start, stop = lower(date), upper(date)
1110 return lambda x: x >= start and x <= stop
1112 def shortuser(user):
1113 """Return a short representation of a user name or email address."""
1129 '''get email of author.'''
1130 r = author.find('>')
1131 if r == -1: r = None
1132 return author[author.find('<')+1:r]
1134 def ellipsis(text, maxlength=400):
1135 """Trim string to at most maxlength (default: 400) characters."""
1136 if len(text) <= maxlength:
1139 return "%s..." % (text[:maxlength-3])
1141 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1142 '''yield every hg repository under path, recursively.'''
1143 def errhandler(err):
1144 if err.filename == path:
1146 if followsym and hasattr(os.path, 'samestat'):
1147 def _add_dir_if_not_there(dirlst, dirname):
1149 samestat = os.path.samestat
1150 dirstat = os.stat(dirname)
1151 for lstdirstat in dirlst:
1152 if samestat(dirstat, lstdirstat):
1156 dirlst.append(dirstat)
1161 if (seen_dirs is None) and followsym:
1163 _add_dir_if_not_there(seen_dirs, path)
1164 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1166 yield root # found a repository
1167 qroot = os.path.join(root, '.hg', 'patches')
1168 if os.path.isdir(os.path.join(qroot, '.hg')):
1169 yield qroot # we have a patch queue repo here
1171 # avoid recursing inside the .hg directory
1174 dirs[:] = [] # don't descend further
1178 fname = os.path.join(root, d)
1179 if _add_dir_if_not_there(seen_dirs, fname):
1180 if os.path.islink(fname):
1181 for hgname in walkrepos(fname, True, seen_dirs):
1190 '''return default os-specific hgrc search path'''
1191 path = system_rcpath()
1192 path.extend(user_rcpath())
1193 path = [os.path.normpath(f) for f in path]
1197 '''return hgrc search path. if env var HGRCPATH is set, use it.
1198 for each item in path, if directory, use files ending in .rc,
1200 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1201 if no HGRCPATH, use default os-specific path.'''
1204 if 'HGRCPATH' in os.environ:
1206 for p in os.environ['HGRCPATH'].split(os.pathsep):
1208 if os.path.isdir(p):
1209 for f, kind in osutil.listdir(p):
1210 if f.endswith('.rc'):
1211 _rcpath.append(os.path.join(p, f))
1215 _rcpath = os_rcpath()
1218 def bytecount(nbytes):
1219 '''return byte count formatted as readable string, with units'''
1222 (100, 1<<30, _('%.0f GB')),
1223 (10, 1<<30, _('%.1f GB')),
1224 (1, 1<<30, _('%.2f GB')),
1225 (100, 1<<20, _('%.0f MB')),
1226 (10, 1<<20, _('%.1f MB')),
1227 (1, 1<<20, _('%.2f MB')),
1228 (100, 1<<10, _('%.0f KB')),
1229 (10, 1<<10, _('%.1f KB')),
1230 (1, 1<<10, _('%.2f KB')),
1231 (1, 1, _('%.0f bytes')),
1234 for multiplier, divisor, format in units:
1235 if nbytes >= divisor * multiplier:
1236 return format % (nbytes / float(divisor))
1237 return units[-1][2] % nbytes
1239 def drop_scheme(scheme, path):
1241 if path.startswith(sc):
1242 path = path[len(sc):]
1243 if path.startswith('//'):
1248 # Avoid double backslash in Windows path repr()
1249 return repr(s).replace('\\\\', '\\')
1252 if 'COLUMNS' in os.environ:
1254 return int(os.environ['COLUMNS'])
1258 import termios, array, fcntl
1259 for dev in (sys.stdout, sys.stdin):
1263 except AttributeError:
1265 if not os.isatty(fd):
1267 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1268 return array.array('h', arri)[1]
1275 def wrap(line, hangindent, width=None):
1277 width = termwidth() - 2
1278 padding = '\n' + ' ' * hangindent
1279 return padding.join(textwrap.wrap(line, width=width - hangindent))
1281 def iterlines(iterator):
1282 for chunk in iterator:
1283 for line in chunk.splitlines():