mercurial/ui.py
branchstable
changeset 46993 f67b8946bb1b
parent 46965 b03527bdac01
child 47029 9ea75ea23534
child 47079 5b3513177f2b
equal deleted inserted replaced
46810:bc268ea9f984 46993:f67b8946bb1b
     1 # ui.py - user interface bits for mercurial
     1 # ui.py - user interface bits for mercurial
     2 #
     2 #
     3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
     3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
     4 #
     4 #
     5 # This software may be used and distributed according to the terms of the
     5 # This software may be used and distributed according to the terms of the
     6 # GNU General Public License version 2 or any later version.
     6 # GNU General Public License version 2 or any later version.
     7 
     7 
     8 from __future__ import absolute_import
     8 from __future__ import absolute_import
    24 from .i18n import _
    24 from .i18n import _
    25 from .node import hex
    25 from .node import hex
    26 from .pycompat import (
    26 from .pycompat import (
    27     getattr,
    27     getattr,
    28     open,
    28     open,
    29     setattr,
       
    30 )
    29 )
    31 
    30 
    32 from . import (
    31 from . import (
    33     color,
    32     color,
    34     config,
    33     config,
    46 from .utils import (
    45 from .utils import (
    47     dateutil,
    46     dateutil,
    48     procutil,
    47     procutil,
    49     resourceutil,
    48     resourceutil,
    50     stringutil,
    49     stringutil,
       
    50     urlutil,
    51 )
    51 )
    52 
    52 
    53 urlreq = util.urlreq
    53 urlreq = util.urlreq
    54 
    54 
    55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
    55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
   300             self._exportableenviron = {}
   300             self._exportableenviron = {}
   301             for k in allowed:
   301             for k in allowed:
   302                 if k in self.environ:
   302                 if k in self.environ:
   303                     self._exportableenviron[k] = self.environ[k]
   303                     self._exportableenviron[k] = self.environ[k]
   304 
   304 
       
   305     def _new_source(self):
       
   306         self._ocfg.new_source()
       
   307         self._tcfg.new_source()
       
   308         self._ucfg.new_source()
       
   309 
   305     @classmethod
   310     @classmethod
   306     def load(cls):
   311     def load(cls):
   307         """Create a ui and load global and user configs"""
   312         """Create a ui and load global and user configs"""
   308         u = cls()
   313         u = cls()
   309         # we always trust global config files and environment variables
   314         # we always trust global config files and environment variables
   311             if t == b'path':
   316             if t == b'path':
   312                 u.readconfig(f, trust=True)
   317                 u.readconfig(f, trust=True)
   313             elif t == b'resource':
   318             elif t == b'resource':
   314                 u.read_resource_config(f, trust=True)
   319                 u.read_resource_config(f, trust=True)
   315             elif t == b'items':
   320             elif t == b'items':
       
   321                 u._new_source()
   316                 sections = set()
   322                 sections = set()
   317                 for section, name, value, source in f:
   323                 for section, name, value, source in f:
   318                     # do not set u._ocfg
   324                     # do not set u._ocfg
   319                     # XXX clean this up once immutable config object is a thing
   325                     # XXX clean this up once immutable config object is a thing
   320                     u._tcfg.set(section, name, value, source)
   326                     u._tcfg.set(section, name, value, source)
   323                 for section in sections:
   329                 for section in sections:
   324                     u.fixconfig(section=section)
   330                     u.fixconfig(section=section)
   325             else:
   331             else:
   326                 raise error.ProgrammingError(b'unknown rctype: %s' % t)
   332                 raise error.ProgrammingError(b'unknown rctype: %s' % t)
   327         u._maybetweakdefaults()
   333         u._maybetweakdefaults()
       
   334         u._new_source()  # anything after that is a different level
   328         return u
   335         return u
   329 
   336 
   330     def _maybetweakdefaults(self):
   337     def _maybetweakdefaults(self):
   331         if not self.configbool(b'ui', b'tweakdefaults'):
   338         if not self.configbool(b'ui', b'tweakdefaults'):
   332             return
   339             return
   550                             _(b"(deprecated '%%' in path %s=%s from %s)\n")
   557                             _(b"(deprecated '%%' in path %s=%s from %s)\n")
   551                             % (n, p, s)
   558                             % (n, p, s)
   552                         )
   559                         )
   553                         p = p.replace(b'%%', b'%')
   560                         p = p.replace(b'%%', b'%')
   554                     p = util.expandpath(p)
   561                     p = util.expandpath(p)
   555                     if not util.hasscheme(p) and not os.path.isabs(p):
   562                     if not urlutil.hasscheme(p) and not os.path.isabs(p):
   556                         p = os.path.normpath(os.path.join(root, p))
   563                         p = os.path.normpath(os.path.join(root, p))
   557                     c.set(b"paths", n, p)
   564                     c.alter(b"paths", n, p)
   558 
   565 
   559         if section in (None, b'ui'):
   566         if section in (None, b'ui'):
   560             # update ui options
   567             # update ui options
   561             self._fmsgout, self._fmsgerr = _selectmsgdests(self)
   568             self._fmsgout, self._fmsgerr = _selectmsgdests(self)
   562             self.debugflag = self.configbool(b'ui', b'debug')
   569             self.debugflag = self.configbool(b'ui', b'debug')
   653                 b"config item: '%s.%s' '%s'"
   660                 b"config item: '%s.%s' '%s'"
   654             )
   661             )
   655             msg %= (section, name, pycompat.bytestr(default))
   662             msg %= (section, name, pycompat.bytestr(default))
   656             self.develwarn(msg, 2, b'warn-config-default')
   663             self.develwarn(msg, 2, b'warn-config-default')
   657 
   664 
       
   665         candidates = []
       
   666         config = self._data(untrusted)
   658         for s, n in alternates:
   667         for s, n in alternates:
   659             candidate = self._data(untrusted).get(s, n, None)
   668             candidate = config.get(s, n, None)
   660             if candidate is not None:
   669             if candidate is not None:
   661                 value = candidate
   670                 candidates.append((s, n, candidate))
   662                 break
   671         if candidates:
       
   672 
       
   673             def level(x):
       
   674                 return config.level(x[0], x[1])
       
   675 
       
   676             value = max(candidates, key=level)[2]
   663 
   677 
   664         if self.debugflag and not untrusted and self._reportuntrusted:
   678         if self.debugflag and not untrusted and self._reportuntrusted:
   665             for s, n in alternates:
   679             for s, n in alternates:
   666                 uvalue = self._ucfg.get(s, n)
   680                 uvalue = self._ucfg.get(s, n)
   667                 if uvalue is not None and uvalue != value:
   681                 if uvalue is not None and uvalue != value:
  1014             user = stringutil.shortuser(user)
  1028             user = stringutil.shortuser(user)
  1015         return user
  1029         return user
  1016 
  1030 
  1017     def expandpath(self, loc, default=None):
  1031     def expandpath(self, loc, default=None):
  1018         """Return repository location relative to cwd or from [paths]"""
  1032         """Return repository location relative to cwd or from [paths]"""
       
  1033         msg = b'ui.expandpath is deprecated, use `get_*` functions from urlutil'
       
  1034         self.deprecwarn(msg, b'6.0')
  1019         try:
  1035         try:
  1020             p = self.paths.getpath(loc)
  1036             p = self.getpath(loc)
  1021             if p:
  1037             if p:
  1022                 return p.rawloc
  1038                 return p.rawloc
  1023         except error.RepoError:
  1039         except error.RepoError:
  1024             pass
  1040             pass
  1025 
  1041 
  1026         if default:
  1042         if default:
  1027             try:
  1043             try:
  1028                 p = self.paths.getpath(default)
  1044                 p = self.getpath(default)
  1029                 if p:
  1045                 if p:
  1030                     return p.rawloc
  1046                     return p.rawloc
  1031             except error.RepoError:
  1047             except error.RepoError:
  1032                 pass
  1048                 pass
  1033 
  1049 
  1034         return loc
  1050         return loc
  1035 
  1051 
  1036     @util.propertycache
  1052     @util.propertycache
  1037     def paths(self):
  1053     def paths(self):
  1038         return paths(self)
  1054         return urlutil.paths(self)
       
  1055 
       
  1056     def getpath(self, *args, **kwargs):
       
  1057         """see paths.getpath for details
       
  1058 
       
  1059         This method exist as `getpath` need a ui for potential warning message.
       
  1060         """
       
  1061         return self.paths.getpath(self, *args, **kwargs)
  1039 
  1062 
  1040     @property
  1063     @property
  1041     def fout(self):
  1064     def fout(self):
  1042         return self._fout
  1065         return self._fout
  1043 
  1066 
  2157                     _(b"ui.available-memory value is invalid ('%s')") % value
  2180                     _(b"ui.available-memory value is invalid ('%s')") % value
  2158                 )
  2181                 )
  2159         return util._estimatememory()
  2182         return util._estimatememory()
  2160 
  2183 
  2161 
  2184 
  2162 class paths(dict):
       
  2163     """Represents a collection of paths and their configs.
       
  2164 
       
  2165     Data is initially derived from ui instances and the config files they have
       
  2166     loaded.
       
  2167     """
       
  2168 
       
  2169     def __init__(self, ui):
       
  2170         dict.__init__(self)
       
  2171 
       
  2172         for name, loc in ui.configitems(b'paths', ignoresub=True):
       
  2173             # No location is the same as not existing.
       
  2174             if not loc:
       
  2175                 continue
       
  2176             loc, sub = ui.configsuboptions(b'paths', name)
       
  2177             self[name] = path(ui, name, rawloc=loc, suboptions=sub)
       
  2178 
       
  2179     def getpath(self, name, default=None):
       
  2180         """Return a ``path`` from a string, falling back to default.
       
  2181 
       
  2182         ``name`` can be a named path or locations. Locations are filesystem
       
  2183         paths or URIs.
       
  2184 
       
  2185         Returns None if ``name`` is not a registered path, a URI, or a local
       
  2186         path to a repo.
       
  2187         """
       
  2188         # Only fall back to default if no path was requested.
       
  2189         if name is None:
       
  2190             if not default:
       
  2191                 default = ()
       
  2192             elif not isinstance(default, (tuple, list)):
       
  2193                 default = (default,)
       
  2194             for k in default:
       
  2195                 try:
       
  2196                     return self[k]
       
  2197                 except KeyError:
       
  2198                     continue
       
  2199             return None
       
  2200 
       
  2201         # Most likely empty string.
       
  2202         # This may need to raise in the future.
       
  2203         if not name:
       
  2204             return None
       
  2205 
       
  2206         try:
       
  2207             return self[name]
       
  2208         except KeyError:
       
  2209             # Try to resolve as a local path or URI.
       
  2210             try:
       
  2211                 # We don't pass sub-options in, so no need to pass ui instance.
       
  2212                 return path(None, None, rawloc=name)
       
  2213             except ValueError:
       
  2214                 raise error.RepoError(_(b'repository %s does not exist') % name)
       
  2215 
       
  2216 
       
  2217 _pathsuboptions = {}
       
  2218 
       
  2219 
       
  2220 def pathsuboption(option, attr):
       
  2221     """Decorator used to declare a path sub-option.
       
  2222 
       
  2223     Arguments are the sub-option name and the attribute it should set on
       
  2224     ``path`` instances.
       
  2225 
       
  2226     The decorated function will receive as arguments a ``ui`` instance,
       
  2227     ``path`` instance, and the string value of this option from the config.
       
  2228     The function should return the value that will be set on the ``path``
       
  2229     instance.
       
  2230 
       
  2231     This decorator can be used to perform additional verification of
       
  2232     sub-options and to change the type of sub-options.
       
  2233     """
       
  2234 
       
  2235     def register(func):
       
  2236         _pathsuboptions[option] = (attr, func)
       
  2237         return func
       
  2238 
       
  2239     return register
       
  2240 
       
  2241 
       
  2242 @pathsuboption(b'pushurl', b'pushloc')
       
  2243 def pushurlpathoption(ui, path, value):
       
  2244     u = util.url(value)
       
  2245     # Actually require a URL.
       
  2246     if not u.scheme:
       
  2247         ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
       
  2248         return None
       
  2249 
       
  2250     # Don't support the #foo syntax in the push URL to declare branch to
       
  2251     # push.
       
  2252     if u.fragment:
       
  2253         ui.warn(
       
  2254             _(
       
  2255                 b'("#fragment" in paths.%s:pushurl not supported; '
       
  2256                 b'ignoring)\n'
       
  2257             )
       
  2258             % path.name
       
  2259         )
       
  2260         u.fragment = None
       
  2261 
       
  2262     return bytes(u)
       
  2263 
       
  2264 
       
  2265 @pathsuboption(b'pushrev', b'pushrev')
       
  2266 def pushrevpathoption(ui, path, value):
       
  2267     return value
       
  2268 
       
  2269 
       
  2270 class path(object):
       
  2271     """Represents an individual path and its configuration."""
       
  2272 
       
  2273     def __init__(self, ui, name, rawloc=None, suboptions=None):
       
  2274         """Construct a path from its config options.
       
  2275 
       
  2276         ``ui`` is the ``ui`` instance the path is coming from.
       
  2277         ``name`` is the symbolic name of the path.
       
  2278         ``rawloc`` is the raw location, as defined in the config.
       
  2279         ``pushloc`` is the raw locations pushes should be made to.
       
  2280 
       
  2281         If ``name`` is not defined, we require that the location be a) a local
       
  2282         filesystem path with a .hg directory or b) a URL. If not,
       
  2283         ``ValueError`` is raised.
       
  2284         """
       
  2285         if not rawloc:
       
  2286             raise ValueError(b'rawloc must be defined')
       
  2287 
       
  2288         # Locations may define branches via syntax <base>#<branch>.
       
  2289         u = util.url(rawloc)
       
  2290         branch = None
       
  2291         if u.fragment:
       
  2292             branch = u.fragment
       
  2293             u.fragment = None
       
  2294 
       
  2295         self.url = u
       
  2296         self.branch = branch
       
  2297 
       
  2298         self.name = name
       
  2299         self.rawloc = rawloc
       
  2300         self.loc = b'%s' % u
       
  2301 
       
  2302         # When given a raw location but not a symbolic name, validate the
       
  2303         # location is valid.
       
  2304         if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
       
  2305             raise ValueError(
       
  2306                 b'location is not a URL or path to a local '
       
  2307                 b'repo: %s' % rawloc
       
  2308             )
       
  2309 
       
  2310         suboptions = suboptions or {}
       
  2311 
       
  2312         # Now process the sub-options. If a sub-option is registered, its
       
  2313         # attribute will always be present. The value will be None if there
       
  2314         # was no valid sub-option.
       
  2315         for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
       
  2316             if suboption not in suboptions:
       
  2317                 setattr(self, attr, None)
       
  2318                 continue
       
  2319 
       
  2320             value = func(ui, self, suboptions[suboption])
       
  2321             setattr(self, attr, value)
       
  2322 
       
  2323     def _isvalidlocalpath(self, path):
       
  2324         """Returns True if the given path is a potentially valid repository.
       
  2325         This is its own function so that extensions can change the definition of
       
  2326         'valid' in this case (like when pulling from a git repo into a hg
       
  2327         one)."""
       
  2328         try:
       
  2329             return os.path.isdir(os.path.join(path, b'.hg'))
       
  2330         # Python 2 may return TypeError. Python 3, ValueError.
       
  2331         except (TypeError, ValueError):
       
  2332             return False
       
  2333 
       
  2334     @property
       
  2335     def suboptions(self):
       
  2336         """Return sub-options and their values for this path.
       
  2337 
       
  2338         This is intended to be used for presentation purposes.
       
  2339         """
       
  2340         d = {}
       
  2341         for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
       
  2342             value = getattr(self, attr)
       
  2343             if value is not None:
       
  2344                 d[subopt] = value
       
  2345         return d
       
  2346 
       
  2347 
       
  2348 # we instantiate one globally shared progress bar to avoid
  2185 # we instantiate one globally shared progress bar to avoid
  2349 # competing progress bars when multiple UI objects get created
  2186 # competing progress bars when multiple UI objects get created
  2350 _progresssingleton = None
  2187 _progresssingleton = None
  2351 
  2188 
  2352 
  2189