comparison mercurial/util.py @ 37118:5be286db5fb5

procutil: move process/executable management functions to new module std* files, pipe helpers, and findexe()s are moved as well since they are likely to be used with sub processes.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 24 Mar 2018 13:38:04 +0900
parents e7b517809ebc
children 24ab3381bf15
comparison
equal deleted inserted replaced
37117:e7b517809ebc 37118:5be286db5fb5
20 import collections 20 import collections
21 import contextlib 21 import contextlib
22 import errno 22 import errno
23 import gc 23 import gc
24 import hashlib 24 import hashlib
25 import imp
26 import io
27 import itertools 25 import itertools
28 import mmap 26 import mmap
29 import os 27 import os
30 import platform as pyplatform 28 import platform as pyplatform
31 import re as remod 29 import re as remod
32 import shutil 30 import shutil
33 import signal
34 import socket 31 import socket
35 import stat 32 import stat
36 import subprocess
37 import sys 33 import sys
38 import tempfile 34 import tempfile
39 import time 35 import time
40 import traceback 36 import traceback
41 import warnings 37 import warnings
50 pycompat, 46 pycompat,
51 urllibcompat, 47 urllibcompat,
52 ) 48 )
53 from .utils import ( 49 from .utils import (
54 dateutil, 50 dateutil,
51 procutil,
55 stringutil, 52 stringutil,
56 ) 53 )
57 54
58 base85 = policy.importmod(r'base85') 55 base85 = policy.importmod(r'base85')
59 osutil = policy.importmod(r'osutil') 56 osutil = policy.importmod(r'osutil')
67 httplib = pycompat.httplib 64 httplib = pycompat.httplib
68 pickle = pycompat.pickle 65 pickle = pycompat.pickle
69 queue = pycompat.queue 66 queue = pycompat.queue
70 safehasattr = pycompat.safehasattr 67 safehasattr = pycompat.safehasattr
71 socketserver = pycompat.socketserver 68 socketserver = pycompat.socketserver
72 stderr = pycompat.stderr
73 stdin = pycompat.stdin
74 stdout = pycompat.stdout
75 bytesio = pycompat.bytesio 69 bytesio = pycompat.bytesio
76 # TODO deprecate stringio name, as it is a lie on Python 3. 70 # TODO deprecate stringio name, as it is a lie on Python 3.
77 stringio = bytesio 71 stringio = bytesio
78 xmlrpclib = pycompat.xmlrpclib 72 xmlrpclib = pycompat.xmlrpclib
79 73
82 urlreq = urllibcompat.urlreq 76 urlreq = urllibcompat.urlreq
83 77
84 # workaround for win32mbcs 78 # workaround for win32mbcs
85 _filenamebytestr = pycompat.bytestr 79 _filenamebytestr = pycompat.bytestr
86 80
87 def isatty(fp):
88 try:
89 return fp.isatty()
90 except AttributeError:
91 return False
92
93 # glibc determines buffering on first write to stdout - if we replace a TTY
94 # destined stdout with a pipe destined stdout (e.g. pager), we want line
95 # buffering
96 if isatty(stdout):
97 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
98
99 if pycompat.iswindows: 81 if pycompat.iswindows:
100 from . import windows as platform 82 from . import windows as platform
101 stdout = platform.winstdout(stdout)
102 else: 83 else:
103 from . import posix as platform 84 from . import posix as platform
104 85
105 _ = i18n._ 86 _ = i18n._
106 87
108 cachestat = platform.cachestat 89 cachestat = platform.cachestat
109 checkexec = platform.checkexec 90 checkexec = platform.checkexec
110 checklink = platform.checklink 91 checklink = platform.checklink
111 copymode = platform.copymode 92 copymode = platform.copymode
112 expandglobs = platform.expandglobs 93 expandglobs = platform.expandglobs
113 explainexit = platform.explainexit
114 findexe = platform.findexe
115 getfsmountpoint = platform.getfsmountpoint 94 getfsmountpoint = platform.getfsmountpoint
116 getfstype = platform.getfstype 95 getfstype = platform.getfstype
117 _gethgcmd = platform.gethgcmd
118 getuser = platform.getuser
119 getpid = os.getpid
120 groupmembers = platform.groupmembers 96 groupmembers = platform.groupmembers
121 groupname = platform.groupname 97 groupname = platform.groupname
122 hidewindow = platform.hidewindow
123 isexec = platform.isexec 98 isexec = platform.isexec
124 isowner = platform.isowner 99 isowner = platform.isowner
125 listdir = osutil.listdir 100 listdir = osutil.listdir
126 localpath = platform.localpath 101 localpath = platform.localpath
127 lookupreg = platform.lookupreg 102 lookupreg = platform.lookupreg
134 openhardlinks = platform.openhardlinks 109 openhardlinks = platform.openhardlinks
135 oslink = platform.oslink 110 oslink = platform.oslink
136 parsepatchoutput = platform.parsepatchoutput 111 parsepatchoutput = platform.parsepatchoutput
137 pconvert = platform.pconvert 112 pconvert = platform.pconvert
138 poll = platform.poll 113 poll = platform.poll
139 popen = platform.popen
140 posixfile = platform.posixfile 114 posixfile = platform.posixfile
141 quotecommand = platform.quotecommand
142 readpipe = platform.readpipe
143 rename = platform.rename 115 rename = platform.rename
144 removedirs = platform.removedirs 116 removedirs = platform.removedirs
145 samedevice = platform.samedevice 117 samedevice = platform.samedevice
146 samefile = platform.samefile 118 samefile = platform.samefile
147 samestat = platform.samestat 119 samestat = platform.samestat
148 setbinary = platform.setbinary
149 setflags = platform.setflags 120 setflags = platform.setflags
150 setsignalhandler = platform.setsignalhandler
151 shellquote = platform.shellquote
152 shellsplit = platform.shellsplit
153 spawndetached = platform.spawndetached
154 split = platform.split 121 split = platform.split
155 sshargs = platform.sshargs
156 statfiles = getattr(osutil, 'statfiles', platform.statfiles) 122 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
157 statisexec = platform.statisexec 123 statisexec = platform.statisexec
158 statislink = platform.statislink 124 statislink = platform.statislink
159 testpid = platform.testpid
160 umask = platform.umask 125 umask = platform.umask
161 unlink = platform.unlink 126 unlink = platform.unlink
162 username = platform.username 127 username = platform.username
163 128
164 try: 129 try:
165 recvfds = osutil.recvfds 130 recvfds = osutil.recvfds
166 except AttributeError:
167 pass
168 try:
169 setprocname = osutil.setprocname
170 except AttributeError:
171 pass
172 try:
173 unblocksignal = osutil.unblocksignal
174 except AttributeError: 131 except AttributeError:
175 pass 132 pass
176 133
177 # Python compatibility 134 # Python compatibility
178 135
344 def buffer(sliceable, offset=0, length=None): 301 def buffer(sliceable, offset=0, length=None):
345 if length is not None: 302 if length is not None:
346 return memoryview(sliceable)[offset:offset + length] 303 return memoryview(sliceable)[offset:offset + length]
347 return memoryview(sliceable)[offset:] 304 return memoryview(sliceable)[offset:]
348 305
349 closefds = pycompat.isposix
350
351 _chunksize = 4096 306 _chunksize = 4096
352 307
353 class bufferedinputpipe(object): 308 class bufferedinputpipe(object):
354 """a manually buffered input pipe 309 """a manually buffered input pipe
355 310
461 # Empty files cannot be mmapped, but mmapread should still work. Check 416 # Empty files cannot be mmapped, but mmapread should still work. Check
462 # if the file is empty, and if so, return an empty buffer. 417 # if the file is empty, and if so, return an empty buffer.
463 if os.fstat(fd).st_size == 0: 418 if os.fstat(fd).st_size == 0:
464 return '' 419 return ''
465 raise 420 raise
466
467 def popen2(cmd, env=None, newlines=False):
468 # Setting bufsize to -1 lets the system decide the buffer size.
469 # The default for bufsize is 0, meaning unbuffered. This leads to
470 # poor performance on Mac OS X: http://bugs.python.org/issue4194
471 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
472 close_fds=closefds,
473 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
474 universal_newlines=newlines,
475 env=env)
476 return p.stdin, p.stdout
477
478 def popen3(cmd, env=None, newlines=False):
479 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
480 return stdin, stdout, stderr
481
482 def popen4(cmd, env=None, newlines=False, bufsize=-1):
483 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
484 close_fds=closefds,
485 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
486 stderr=subprocess.PIPE,
487 universal_newlines=newlines,
488 env=env)
489 return p.stdin, p.stdout, p.stderr, p
490 421
491 class fileobjectproxy(object): 422 class fileobjectproxy(object):
492 """A proxy around file objects that tells a watcher when events occur. 423 """A proxy around file objects that tells a watcher when events occur.
493 424
494 This type is intended to only be used for testing purposes. Think hard 425 This type is intended to only be used for testing purposes. Think hard
1498 def clearcachedproperty(obj, prop): 1429 def clearcachedproperty(obj, prop):
1499 '''clear a cached property value, if one has been set''' 1430 '''clear a cached property value, if one has been set'''
1500 if prop in obj.__dict__: 1431 if prop in obj.__dict__:
1501 del obj.__dict__[prop] 1432 del obj.__dict__[prop]
1502 1433
1503 def pipefilter(s, cmd):
1504 '''filter string S through command CMD, returning its output'''
1505 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1506 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1507 pout, perr = p.communicate(s)
1508 return pout
1509
1510 def tempfilter(s, cmd):
1511 '''filter string S through a pair of temporary files with CMD.
1512 CMD is used as a template to create the real command to be run,
1513 with the strings INFILE and OUTFILE replaced by the real names of
1514 the temporary files generated.'''
1515 inname, outname = None, None
1516 try:
1517 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
1518 fp = os.fdopen(infd, r'wb')
1519 fp.write(s)
1520 fp.close()
1521 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
1522 os.close(outfd)
1523 cmd = cmd.replace('INFILE', inname)
1524 cmd = cmd.replace('OUTFILE', outname)
1525 code = os.system(cmd)
1526 if pycompat.sysplatform == 'OpenVMS' and code & 1:
1527 code = 0
1528 if code:
1529 raise error.Abort(_("command '%s' failed: %s") %
1530 (cmd, explainexit(code)))
1531 with open(outname, 'rb') as fp:
1532 return fp.read()
1533 finally:
1534 try:
1535 if inname:
1536 os.unlink(inname)
1537 except OSError:
1538 pass
1539 try:
1540 if outname:
1541 os.unlink(outname)
1542 except OSError:
1543 pass
1544
1545 _filtertable = {
1546 'tempfile:': tempfilter,
1547 'pipe:': pipefilter,
1548 }
1549
1550 def filter(s, cmd):
1551 "filter a string through a command that transforms its input to its output"
1552 for name, fn in _filtertable.iteritems():
1553 if cmd.startswith(name):
1554 return fn(s, cmd[len(name):].lstrip())
1555 return pipefilter(s, cmd)
1556
1557 def increasingchunks(source, min=1024, max=65536): 1434 def increasingchunks(source, min=1024, max=65536):
1558 '''return no less than min bytes per chunk while data remains, 1435 '''return no less than min bytes per chunk while data remains,
1559 doubling min after each chunk until it reaches max''' 1436 doubling min after each chunk until it reaches max'''
1560 def log2(x): 1437 def log2(x):
1561 if not x: 1438 if not x:
1642 a.pop() 1519 a.pop()
1643 b.pop() 1520 b.pop()
1644 b.reverse() 1521 b.reverse()
1645 return pycompat.ossep.join((['..'] * len(a)) + b) or '.' 1522 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1646 1523
1647 def mainfrozen():
1648 """return True if we are a frozen executable.
1649
1650 The code supports py2exe (most common, Windows only) and tools/freeze
1651 (portable, not much used).
1652 """
1653 return (safehasattr(sys, "frozen") or # new py2exe
1654 safehasattr(sys, "importers") or # old py2exe
1655 imp.is_frozen(u"__main__")) # tools/freeze
1656
1657 # the location of data files matching the source code 1524 # the location of data files matching the source code
1658 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app': 1525 if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1659 # executable version (py2exe) doesn't support __file__ 1526 # executable version (py2exe) doesn't support __file__
1660 datapath = os.path.dirname(pycompat.sysexecutable) 1527 datapath = os.path.dirname(pycompat.sysexecutable)
1661 else: 1528 else:
1662 datapath = os.path.dirname(pycompat.fsencode(__file__)) 1529 datapath = os.path.dirname(pycompat.fsencode(__file__))
1663 1530
1664 i18n.setdatapath(datapath) 1531 i18n.setdatapath(datapath)
1665
1666 _hgexecutable = None
1667
1668 def hgexecutable():
1669 """return location of the 'hg' executable.
1670
1671 Defaults to $HG or 'hg' in the search path.
1672 """
1673 if _hgexecutable is None:
1674 hg = encoding.environ.get('HG')
1675 mainmod = sys.modules[r'__main__']
1676 if hg:
1677 _sethgexecutable(hg)
1678 elif mainfrozen():
1679 if getattr(sys, 'frozen', None) == 'macosx_app':
1680 # Env variable set by py2app
1681 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1682 else:
1683 _sethgexecutable(pycompat.sysexecutable)
1684 elif (os.path.basename(
1685 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1686 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1687 else:
1688 exe = findexe('hg') or os.path.basename(sys.argv[0])
1689 _sethgexecutable(exe)
1690 return _hgexecutable
1691
1692 def _sethgexecutable(path):
1693 """set location of the 'hg' executable"""
1694 global _hgexecutable
1695 _hgexecutable = path
1696
1697 def _testfileno(f, stdf):
1698 fileno = getattr(f, 'fileno', None)
1699 try:
1700 return fileno and fileno() == stdf.fileno()
1701 except io.UnsupportedOperation:
1702 return False # fileno() raised UnsupportedOperation
1703
1704 def isstdin(f):
1705 return _testfileno(f, sys.__stdin__)
1706
1707 def isstdout(f):
1708 return _testfileno(f, sys.__stdout__)
1709
1710 def shellenviron(environ=None):
1711 """return environ with optional override, useful for shelling out"""
1712 def py2shell(val):
1713 'convert python object into string that is useful to shell'
1714 if val is None or val is False:
1715 return '0'
1716 if val is True:
1717 return '1'
1718 return pycompat.bytestr(val)
1719 env = dict(encoding.environ)
1720 if environ:
1721 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1722 env['HG'] = hgexecutable()
1723 return env
1724
1725 def system(cmd, environ=None, cwd=None, out=None):
1726 '''enhanced shell command execution.
1727 run with environment maybe modified, maybe in different dir.
1728
1729 if out is specified, it is assumed to be a file-like object that has a
1730 write() method. stdout and stderr will be redirected to out.'''
1731 try:
1732 stdout.flush()
1733 except Exception:
1734 pass
1735 cmd = quotecommand(cmd)
1736 env = shellenviron(environ)
1737 if out is None or isstdout(out):
1738 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1739 env=env, cwd=cwd)
1740 else:
1741 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1742 env=env, cwd=cwd, stdout=subprocess.PIPE,
1743 stderr=subprocess.STDOUT)
1744 for line in iter(proc.stdout.readline, ''):
1745 out.write(line)
1746 proc.wait()
1747 rc = proc.returncode
1748 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1749 rc = 0
1750 return rc
1751 1532
1752 def checksignature(func): 1533 def checksignature(func):
1753 '''wrap a function with code to check for calling errors''' 1534 '''wrap a function with code to check for calling errors'''
1754 def check(*args, **kwargs): 1535 def check(*args, **kwargs):
1755 try: 1536 try:
2130 Note that this function does not use os.altsep because this is 1911 Note that this function does not use os.altsep because this is
2131 an alternative of simple "xxx.split(os.sep)". 1912 an alternative of simple "xxx.split(os.sep)".
2132 It is recommended to use os.path.normpath() before using this 1913 It is recommended to use os.path.normpath() before using this
2133 function if need.''' 1914 function if need.'''
2134 return path.split(pycompat.ossep) 1915 return path.split(pycompat.ossep)
2135
2136 def gui():
2137 '''Are we running in a GUI?'''
2138 if pycompat.isdarwin:
2139 if 'SSH_CONNECTION' in encoding.environ:
2140 # handle SSH access to a box where the user is logged in
2141 return False
2142 elif getattr(osutil, 'isgui', None):
2143 # check if a CoreGraphics session is available
2144 return osutil.isgui()
2145 else:
2146 # pure build; use a safe default
2147 return True
2148 else:
2149 return pycompat.iswindows or encoding.environ.get("DISPLAY")
2150 1916
2151 def mktempcopy(name, emptyok=False, createmode=None): 1917 def mktempcopy(name, emptyok=False, createmode=None):
2152 """Create a temporary file with the same contents from name 1918 """Create a temporary file with the same contents from name
2153 1919
2154 The permission bits are copied from the original file. 1920 The permission bits are copied from the original file.
2714 yield line 2480 yield line
2715 2481
2716 def expandpath(path): 2482 def expandpath(path):
2717 return os.path.expanduser(os.path.expandvars(path)) 2483 return os.path.expanduser(os.path.expandvars(path))
2718 2484
2719 def hgcmd():
2720 """Return the command used to execute current hg
2721
2722 This is different from hgexecutable() because on Windows we want
2723 to avoid things opening new shell windows like batch files, so we
2724 get either the python call or current executable.
2725 """
2726 if mainfrozen():
2727 if getattr(sys, 'frozen', None) == 'macosx_app':
2728 # Env variable set by py2app
2729 return [encoding.environ['EXECUTABLEPATH']]
2730 else:
2731 return [pycompat.sysexecutable]
2732 return _gethgcmd()
2733
2734 def rundetached(args, condfn):
2735 """Execute the argument list in a detached process.
2736
2737 condfn is a callable which is called repeatedly and should return
2738 True once the child process is known to have started successfully.
2739 At this point, the child process PID is returned. If the child
2740 process fails to start or finishes before condfn() evaluates to
2741 True, return -1.
2742 """
2743 # Windows case is easier because the child process is either
2744 # successfully starting and validating the condition or exiting
2745 # on failure. We just poll on its PID. On Unix, if the child
2746 # process fails to start, it will be left in a zombie state until
2747 # the parent wait on it, which we cannot do since we expect a long
2748 # running process on success. Instead we listen for SIGCHLD telling
2749 # us our child process terminated.
2750 terminated = set()
2751 def handler(signum, frame):
2752 terminated.add(os.wait())
2753 prevhandler = None
2754 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2755 if SIGCHLD is not None:
2756 prevhandler = signal.signal(SIGCHLD, handler)
2757 try:
2758 pid = spawndetached(args)
2759 while not condfn():
2760 if ((pid in terminated or not testpid(pid))
2761 and not condfn()):
2762 return -1
2763 time.sleep(0.1)
2764 return pid
2765 finally:
2766 if prevhandler is not None:
2767 signal.signal(signal.SIGCHLD, prevhandler)
2768
2769 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False): 2485 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2770 """Return the result of interpolating items in the mapping into string s. 2486 """Return the result of interpolating items in the mapping into string s.
2771 2487
2772 prefix is a single character string, or a two character string with 2488 prefix is a single character string, or a two character string with
2773 a backslash as the first character if the prefix needs to be escaped in 2489 a backslash as the first character if the prefix needs to be escaped in
3255 yield (fnmax, fnln, func) 2971 yield (fnmax, fnln, func)
3256 else: 2972 else:
3257 yield line % (fnmax, fnln, func) 2973 yield line % (fnmax, fnln, func)
3258 2974
3259 def debugstacktrace(msg='stacktrace', skip=0, 2975 def debugstacktrace(msg='stacktrace', skip=0,
3260 f=stderr, otherf=stdout, depth=0): 2976 f=procutil.stderr, otherf=procutil.stdout, depth=0):
3261 '''Writes a message to f (stderr) with a nicely formatted stacktrace. 2977 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3262 Skips the 'skip' entries closest to the call, then show 'depth' entries. 2978 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3263 By default it will flush stdout first. 2979 By default it will flush stdout first.
3264 It can be used everywhere and intentionally does not require an ui object. 2980 It can be used everywhere and intentionally does not require an ui object.
3265 Not be used in production code but very convenient while developing. 2981 Not be used in production code but very convenient while developing.
4074 parsetimezone = _deprecatedfunc(dateutil.parsetimezone, '4.6') 3790 parsetimezone = _deprecatedfunc(dateutil.parsetimezone, '4.6')
4075 strdate = _deprecatedfunc(dateutil.strdate, '4.6') 3791 strdate = _deprecatedfunc(dateutil.strdate, '4.6')
4076 parsedate = _deprecatedfunc(dateutil.parsedate, '4.6') 3792 parsedate = _deprecatedfunc(dateutil.parsedate, '4.6')
4077 matchdate = _deprecatedfunc(dateutil.matchdate, '4.6') 3793 matchdate = _deprecatedfunc(dateutil.matchdate, '4.6')
4078 3794
3795 stderr = procutil.stderr
3796 stdin = procutil.stdin
3797 stdout = procutil.stdout
3798 explainexit = procutil.explainexit
3799 findexe = procutil.findexe
3800 getuser = procutil.getuser
3801 getpid = procutil.getpid
3802 hidewindow = procutil.hidewindow
3803 popen = procutil.popen
3804 quotecommand = procutil.quotecommand
3805 readpipe = procutil.readpipe
3806 setbinary = procutil.setbinary
3807 setsignalhandler = procutil.setsignalhandler
3808 shellquote = procutil.shellquote
3809 shellsplit = procutil.shellsplit
3810 spawndetached = procutil.spawndetached
3811 sshargs = procutil.sshargs
3812 testpid = procutil.testpid
3813 try:
3814 setprocname = procutil.setprocname
3815 except AttributeError:
3816 pass
3817 try:
3818 unblocksignal = procutil.unblocksignal
3819 except AttributeError:
3820 pass
3821 closefds = procutil.closefds
3822 isatty = procutil.isatty
3823 popen2 = procutil.popen2
3824 popen3 = procutil.popen3
3825 popen4 = procutil.popen4
3826 pipefilter = procutil.pipefilter
3827 tempfilter = procutil.tempfilter
3828 filter = procutil.filter
3829 mainfrozen = procutil.mainfrozen
3830 hgexecutable = procutil.hgexecutable
3831 isstdin = procutil.isstdin
3832 isstdout = procutil.isstdout
3833 shellenviron = procutil.shellenviron
3834 system = procutil.system
3835 gui = procutil.gui
3836 hgcmd = procutil.hgcmd
3837 rundetached = procutil.rundetached
3838
4079 escapedata = _deprecatedfunc(stringutil.escapedata, '4.6') 3839 escapedata = _deprecatedfunc(stringutil.escapedata, '4.6')
4080 binary = _deprecatedfunc(stringutil.binary, '4.6') 3840 binary = _deprecatedfunc(stringutil.binary, '4.6')
4081 stringmatcher = _deprecatedfunc(stringutil.stringmatcher, '4.6') 3841 stringmatcher = _deprecatedfunc(stringutil.stringmatcher, '4.6')
4082 shortuser = _deprecatedfunc(stringutil.shortuser, '4.6') 3842 shortuser = _deprecatedfunc(stringutil.shortuser, '4.6')
4083 emailuser = _deprecatedfunc(stringutil.emailuser, '4.6') 3843 emailuser = _deprecatedfunc(stringutil.emailuser, '4.6')