[PATCH 2 of 2 RFC] RFC: switch to immutable configs
Jun Wu
quark at fb.com
Mon Mar 27 14:38:07 EDT 2017
# HG changeset patch
# User Jun Wu <quark at fb.com>
# Date 1490639836 25200
# Mon Mar 27 11:37:16 2017 -0700
# Node ID 13bee3e959f04f970f2fc0a01120f0b30d725b84
# Parent 4eb7c76340791f379a34f9df4ec42e0c8b9b2a2f
RFC: switch to immutable configs
This replaces "ui" so it's based on immutable config. Tests pass.
The config hierarchy is currently like:
mergedconfig (root config)
\_ mergedconfig (self.__class__._gcfgs, global configs across repos)
| \_ atomicconfig (added by setconfig(priority=3))
| \_ atomicconfig (added by setconfig(priority=3))
| \_ ...
\_ mergedconfig (self._ocfgs)
| \_ atomicconfig (added by setconfig)
| \_ atomicconfig (added by setconfig)
| \_ filteredconfig (if the subconfig has [paths], needs fix)
| | \_ atomicconfig (added by setconfig)
| \_ ...
\_ filteredconfig (filters out HGPLAIN stuff)
| \_ mergedconfig (self._tcfgs, or self._ucfgs, config files)
| \_ filteredconfig (if the subconfig has [paths], needs xi)
| | \_ fileconfig
| \_ fileconfig (added by readconfig)
| \_ fileconfig (added by readconfig)
| \_ ...
\_ atomicconfig (handles ui.compat, dos not exists yet, replaceable)
In the future we may want to let rcutil.py construct part of the configs,
and make systemrc and userrc separately accessible.
As a RFC, the patch is not split intentionally, to give an overview of what
needed to be done for switching to immutable configs. The current code is
done in a relatively quick & dirty way. If we feel good about the direction,
I'll clean things up into smaller patches.
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1652,5 +1652,5 @@ def _docommit(ui, repo, *pats, **opts):
raise error.Abort(_('cannot amend with --subrepos'))
# Let --subrepos on the command line override config setting.
- ui.setconfig('ui', 'commitsubrepos', True, 'commit')
+ ui.setconfig('ui', 'commitsubrepos', True, 'commit', priority=3)
cmdutil.checkunfinished(repo, commit=True)
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -109,5 +109,6 @@ def dispatch(req):
req.ui = uimod.ui.load()
if '--traceback' in req.args:
- req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
+ req.ui.setconfig('ui', 'traceback', 'on', '--traceback',
+ priority=2)
# set ui streams from the request
@@ -180,5 +181,6 @@ def _runcatch(req):
# the repo ui
for sec, name, val in cfgs:
- req.repo.ui.setconfig(sec, name, val, source='--config')
+ req.repo.ui.setconfig(sec, name, val, source='--config',
+ priority=2)
# developer config: ui.debugger
@@ -511,5 +513,5 @@ def _parseconfig(ui, config):
if not section or not name:
raise IndexError
- ui.setconfig(section, name, value, '--config')
+ ui.setconfig(section, name, value, '--config', priority=2)
configs.append((section, name, value))
except (IndexError, ValueError):
@@ -683,5 +685,6 @@ def _dispatch(req):
if '--profile' in args:
for ui_ in uis:
- ui_.setconfig('profiling', 'enabled', 'true', '--profile')
+ ui_.setconfig('profiling', 'enabled', 'true', '--profile',
+ priority=2)
with profiling.maybeprofile(lui):
@@ -755,13 +758,14 @@ def _dispatch(req):
val = val.encode('ascii')
for ui_ in uis:
- ui_.setconfig('ui', opt, val, '--' + opt)
+ ui_.setconfig('ui', opt, val, '--' + opt, priority=2)
if options['traceback']:
for ui_ in uis:
- ui_.setconfig('ui', 'traceback', 'on', '--traceback')
+ ui_.setconfig('ui', 'traceback', 'on', '--traceback',
+ priority=2)
if options['noninteractive']:
for ui_ in uis:
- ui_.setconfig('ui', 'interactive', 'off', '-y')
+ ui_.setconfig('ui', 'interactive', 'off', '-y', priority=2)
if util.parsebool(options['pager']):
@@ -778,5 +782,5 @@ def _dispatch(req):
for ui_ in uis:
if coloropt:
- ui_.setconfig('ui', 'color', coloropt, '--color')
+ ui_.setconfig('ui', 'color', coloropt, '--color', priority=2)
color.setup(ui_)
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -130,4 +130,115 @@ def _catchterm(*args):
raise error.SignalInterrupt
+def _fixpathsection(fileconfig, root):
+ """normalize paths in the [paths] section
+
+ fileconfig is an immutable config object. Return the fileconfig if it does
+ not have a "paths" section, or a "filteredconfig" if paths are normalized.
+ """
+ if 'paths' not in fileconfig.sections():
+ return fileconfig
+
+ def filterpaths(items):
+ result = util.sortdict()
+ for name, (path, source) in items.items():
+ # Only normalize it if path is non-empty and is not a sub-option
+ if ':' not in name and path:
+ path = util.expandpath(path)
+ if not util.hasscheme(path) and not os.path.isabs(path):
+ path = os.path.normpath(os.path.join(root, path))
+ result[name] = (path, source)
+ return result
+
+ filters = {'paths': filterpaths}
+ return config.filteredconfig(fileconfig.title, fileconfig, filters)
+
+def _isplain(feature=None):
+ '''is plain mode active?
+
+ Plain mode means that all configuration variables which affect
+ the behavior and output of Mercurial should be
+ ignored. Additionally, the output should be stable,
+ reproducible and suitable for use in scripts or applications.
+
+ The only way to trigger plain mode is by setting either the
+ `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
+
+ The return value can either be
+ - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
+ - True otherwise
+ '''
+ if ('HGPLAIN' not in encoding.environ and
+ 'HGPLAINEXCEPT' not in encoding.environ):
+ return False
+ exceptions = encoding.environ.get('HGPLAINEXCEPT',
+ '').strip().split(',')
+ if feature and exceptions:
+ return feature not in exceptions
+ return True
+
+def _filterconfig(subconfig):
+ '''remove configs according to HGPLAIN and HGPLAINEXCEPT
+
+ subconfig is an immutable config object. Returns an immutable config object
+ with related fields filtered.
+ '''
+ filters = {}
+ if _isplain():
+ def filterui(items):
+ result = util.sortdict(items)
+ for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
+ 'logtemplate', 'statuscopies', 'style',
+ 'traceback', 'verbose'):
+ if k in result:
+ del result[k]
+ return result
+
+ filters['ui'] = filterui
+ filters['defaults'] = {}
+ if _isplain('alias'):
+ filters['alias'] = {}
+ if _isplain('revsetalias'):
+ filters['revsetalias'] = {}
+ if _isplain('templatealias'):
+ filters['templatealias'] = {}
+ if _isplain('commands'):
+ filters['commands'] = {}
+
+ if filters:
+ return config.filteredconfig('filter', subconfig, filters)
+ else:
+ return subconfig
+
+def _getconfig(configroot, section, name, default=None, index=0):
+ '''get value (index=0) or source (index=-1) from an immutable config'''
+ value = configroot.getsection(section).get(name, (None,))[index]
+ if value is None:
+ value = default
+ return value
+
+def _buildconfigroot(cfg, ocfg, gcfg):
+ return config.mergedconfig('root', [cfg, ocfg, gcfg])
+
+def dependson(*fields):
+ '''cache result which gets invalidates if any field changes'''
+
+ def decorator(oldfunc):
+ cached = [[None], None] # cache key, result
+ def getcachekey(self):
+ return [getattr(self, f, None) for f in fields]
+
+ def newfunc(self):
+ newkey = getcachekey(self)
+ oldkey = cached[0]
+ if oldkey == newkey:
+ return cached[1]
+ result = oldfunc(self)
+ cached[:] = [newkey, result]
+ return result
+ newfunc.__name__ = oldfunc.__name__
+ newfunc.__doc__ = oldfunc.__doc__
+ return newfunc
+ return decorator
+
class ui(object):
def __init__(self, src=None):
@@ -146,16 +257,7 @@ class ui(object):
# This exists to prevent an extra list lookup.
self._bufferapplylabels = None
- self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
- self._reportuntrusted = True
- self._ocfg = config.config() # overlay
- self._tcfg = config.config() # trusted
- self._ucfg = config.config() # untrusted
- self._trustusers = set()
- self._trustgroups = set()
self.callhooks = True
# Insecure server connections requested.
self.insecureconnections = False
- # Blocked time
- self.logblockedtimes = False
# color mode: see mercurial/color.py for possible value
self._colormode = None
@@ -170,9 +272,9 @@ class ui(object):
self._disablepager = src._disablepager
- self._tcfg = src._tcfg.copy()
- self._ucfg = src._ucfg.copy()
- self._ocfg = src._ocfg.copy()
- self._trustusers = src._trustusers.copy()
- self._trustgroups = src._trustgroups.copy()
+ # immutable configs can be reused without copying
+ self._ocfgs = src._ocfgs
+ self._tcfgs = src._tcfgs
+ self._ucfgs = src._ucfgs
+
self.environ = src.environ
self.callhooks = src.callhooks
@@ -182,6 +284,4 @@ class ui(object):
self._styles = src._styles.copy()
- self.fixconfig()
-
self.httppasswordmgrdb = src.httppasswordmgrdb
self._blockedtimes = src._blockedtimes
@@ -199,4 +299,9 @@ class ui(object):
self._blockedtimes = collections.defaultdict(int)
+ # immutable configs
+ self._ocfgs = config.mergedconfig('setconfig', []) # overlay
+ self._tcfgs = config.mergedconfig('loaded', []) # trusted
+ self._ucfgs = config.mergedconfig('loaded', []) # trusted+untrusted
+
allowed = self.configlist('experimental', 'exportableenviron')
if '*' in allowed:
@@ -208,4 +313,7 @@ class ui(object):
self._exportableenviron[k] = self.environ[k]
+ # global overlay, for things like ui.commitsubrepos
+ _gcfgs = config.mergedconfig('global', [])
+
@classmethod
def load(cls):
@@ -217,13 +325,9 @@ class ui(object):
u.readconfig(f, trust=True)
elif t == 'items':
- sections = set()
- for section, name, value, source in f:
- # do not set u._ocfg
- # XXX clean this up once immutable config object is a thing
- u._tcfg.set(section, name, value, source)
- u._ucfg.set(section, name, value, source)
- sections.add(section)
- for section in sections:
- u.fixconfig(section=section)
+ acfg = config.atomicconfig(
+ 'load', ((s, n, (v, src)) for s, n, v, src in f))
+ u._tcfgs = u._tcfgs.append(acfg)
+ u._ucfgs = u._ucfgs.append(acfg)
+ u.readconfigitems(f)
else:
raise error.ProgrammingError('unknown rctype: %s' % t)
@@ -280,104 +384,141 @@ class ui(object):
raise
- cfg = config.config()
trusted = sections or trust or self._trusted(fp, filename)
try:
- cfg.read(filename, fp, sections=sections, remap=remap)
- fp.close()
+ fcfg = config.fileconfig(filename, fp, sections, remap)
except error.ConfigError as inst:
if trusted:
raise
self.warn(_("ignored: %s\n") % str(inst))
+ else:
+ fcfg = _fixpathsection(fcfg, root or os.path.expanduser('~'))
+ fcfg = _filterconfig(fcfg)
+ if trusted:
+ self._tcfgs = self._tcfgs.append(fcfg)
+ self._ucfgs = self._ucfgs.append(fcfg)
+ finally:
+ fp.close()
- if self.plain():
- for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
- 'logtemplate', 'statuscopies', 'style',
- 'traceback', 'verbose'):
- if k in cfg['ui']:
- del cfg['ui'][k]
- for k, v in cfg.items('defaults'):
- del cfg['defaults'][k]
- for k, v in cfg.items('commands'):
- del cfg['commands'][k]
- # Don't remove aliases from the configuration if in the exceptionlist
- if self.plain('alias'):
- for k, v in cfg.items('alias'):
- del cfg['alias'][k]
- if self.plain('revsetalias'):
- for k, v in cfg.items('revsetalias'):
- del cfg['revsetalias'][k]
- if self.plain('templatealias'):
- for k, v in cfg.items('templatealias'):
- del cfg['templatealias'][k]
+ def readconfigitems(self, items):
+ acfg = config.atomicconfig(
+ 'load', ((s, n, (v, src)) for s, n, v, src in items))
+ self._tcfgs = self._tcfgs.append(acfg)
+ self._ucfgs = self._ucfgs.append(acfg)
+
+ @property
+ @dependson('_roottcfg')
+ def debugflag(self):
+ return util.parsebool(_getconfig(self._roottcfg, 'ui', 'debug', '0'))
- if trusted:
- self._tcfg.update(cfg)
- self._tcfg.update(self._ocfg)
- self._ucfg.update(cfg)
- self._ucfg.update(self._ocfg)
+ @property
+ @dependson('_roottcfg')
+ def _verbosequiet(self):
+ verbose = self.debugflag or self.configbool('ui', 'verbose')
+ quiet = not self.debugflag and self.configbool('ui', 'quiet')
+ # they could cancel each other
+ if verbose and quiet:
+ verbose = quiet = False
+ return (verbose, quiet)
- if root is None:
- root = os.path.expanduser('~')
- self.fixconfig(root=root)
+ @property
+ def verbose(self):
+ return self._verbosequiet[0]
+
+ @property
+ def quiet(self):
+ return self._verbosequiet[1]
+
+ @quiet.setter
+ def quiet(self, value):
+ # this is needed by perf.py; new code shouldn't depend on this
+ assert value in (True, False)
+ if self.quiet == value:
+ return
+ self.setconfig('ui', 'quiet', str(value), 'quiet=')
+ assert self.quiet == value
- def fixconfig(self, root=None, section=None):
- if section in (None, 'paths'):
- # expand vars and ~
- # translate paths relative to root (or home) into absolute paths
- root = root or pycompat.getcwd()
- for c in self._tcfg, self._ucfg, self._ocfg:
- for n, p in c.items('paths'):
- # Ignore sub-options.
- if ':' in n:
- continue
- if not p:
- continue
- if '%%' in p:
- s = self.configsource('paths', n) or 'none'
- self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
- % (n, p, s))
- p = p.replace('%%', '%')
- p = util.expandpath(p)
- if not util.hasscheme(p) and not os.path.isabs(p):
- p = os.path.normpath(os.path.join(root, p))
- c.set("paths", n, p)
+ @verbose.setter
+ def verbose(self, value):
+ assert value in (True, False)
+ if self.verbose == value:
+ return
+ # this is needed by hgweb
+ self.setconfig('ui', 'verbose', str(value), 'verbose=')
+ if self.quiet:
+ self.setconfig('ui', 'quiet', '0', 'verbose=')
+ assert self.verbose == value
+
+ @property
+ @dependson('_roottcfg')
+ def tracebackflag(self):
+ return self.configbool('ui', 'traceback', False)
+
+ @property
+ @dependson('_roottcfg')
+ def logblockedtimes(self):
+ return self.configbool('ui', 'logblockedtimes')
+
+ @property
+ @dependson('_roottcfg')
+ def _reportuntrusted(self):
+ return self.debugflag or self.configbool("ui", "report_untrusted", True)
- if section in (None, 'ui'):
- # update ui options
- self.debugflag = self.configbool('ui', 'debug')
- self.verbose = self.debugflag or self.configbool('ui', 'verbose')
- self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
- if self.verbose and self.quiet:
- self.quiet = self.verbose = False
- self._reportuntrusted = self.debugflag or self.configbool("ui",
- "report_untrusted", True)
- self.tracebackflag = self.configbool('ui', 'traceback', False)
- self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
+ @property
+ @dependson('_tcfgs')
+ def _trustusers(self):
+ trustedusers = set()
+ for cfg in self._tcfgs.subconfigs('trusted'):
+ users = config.parselist(_getconfig(cfg, 'trusted', 'users', []))
+ trustedusers.update(users)
+ return trustedusers
+
+ @property
+ @dependson('_tcfgs')
+ def _trustgroups(self):
+ trustedgroups = set()
+ for cfg in self._tcfgs.subconfigs('trusted'):
+ groups = config.parselist(_getconfig(cfg, 'trusted', 'groups', []))
+ trustedgroups.update(groups)
+ return trustedgroups
- if section in (None, 'trusted'):
- # update trust information
- self._trustusers.update(self.configlist('trusted', 'users'))
- self._trustgroups.update(self.configlist('trusted', 'groups'))
-
- def backupconfig(self, section, item):
- return (self._ocfg.backup(section, item),
- self._tcfg.backup(section, item),
- self._ucfg.backup(section, item),)
- def restoreconfig(self, data):
- self._ocfg.restore(data[0])
- self._tcfg.restore(data[1])
- self._ucfg.restore(data[2])
-
- def setconfig(self, section, name, value, source=''):
- for cfg in (self._ocfg, self._tcfg, self._ucfg):
- cfg.set(section, name, value, source)
- self.fixconfig(section=section)
+ def setconfig(self, section, name, value, source='', priority=None):
+ title = source or 'setconfig'
+ acfg = config.atomicconfig(title, [(section, name, (value, source))])
+ try:
+ cwd = pycompat.getcwd()
+ except OSError:
+ pass
+ else:
+ acfg = _fixpathsection(acfg, cwd)
+ if priority == 3:
+ # global overlay
+ self.__class__._gcfgs = self.__class__._gcfgs.append(acfg)
+ elif priority == 2:
+ # change overlay in this ui
+ self._ocfgs = self._ocfgs.append(acfg)
+ elif priority == 1:
+ # change overlay in this ui, do not override existing overlays
+ self._ocfgs = self._ocfgs.prepend(acfg)
+ else:
+ self._tcfgs = self._tcfgs.append(acfg)
+ self._ucfgs = self._ucfgs.append(acfg)
def _data(self, untrusted):
- return untrusted and self._ucfg or self._tcfg
+ return untrusted and self._rootucfg or self._roottcfg
+
+ @property
+ @dependson('_ucfgs', '_ocfgs', '_gcfgs')
+ def _rootucfg(self):
+ return _buildconfigroot(self._ucfgs, self._ocfgs, self._gcfgs)
+
+ @property
+ @dependson('_tcfgs', '_ocfgs', '_gcfgs')
+ def _roottcfg(self):
+ return _buildconfigroot(self._tcfgs, self._ocfgs, self._gcfgs)
def configsource(self, section, name, untrusted=False):
- return self._data(untrusted).source(section, name)
+ root = self._data(untrusted)
+ return _getconfig(root, section, name, '', index=-1)
def config(self, section, name, default=None, untrusted=False):
@@ -387,6 +528,7 @@ class ui(object):
alternates = [name]
+ root = self._data(untrusted)
for n in alternates:
- value = self._data(untrusted).get(section, n, None)
+ value = _getconfig(root, section, n, default)
if value is not None:
name = n
@@ -396,6 +538,7 @@ class ui(object):
if self.debugflag and not untrusted and self._reportuntrusted:
+ uroot = self._data(untrusted=True)
for n in alternates:
- uvalue = self._ucfg.get(section, n)
+ uvalue = _getconfig(uroot, section, n)
if uvalue is not None and uvalue != value:
self.debug("ignoring untrusted configuration option "
@@ -413,25 +556,12 @@ class ui(object):
is a dict of defined sub-options where keys and values are strings.
"""
- data = self._data(untrusted)
- main = data.get(section, name, default)
- if self.debugflag and not untrusted and self._reportuntrusted:
- uvalue = self._ucfg.get(section, name)
- if uvalue is not None and uvalue != main:
- self.debug('ignoring untrusted configuration option '
- '%s.%s = %s\n' % (section, name, uvalue))
-
+ root = self._data(untrusted)
+ main = _getconfig(root, section, name, default)
sub = {}
prefix = '%s:' % name
- for k, v in data.items(section):
- if k.startswith(prefix):
- sub[k[len(prefix):]] = v
-
- if self.debugflag and not untrusted and self._reportuntrusted:
- for k, v in sub.items():
- uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
- if uvalue is not None and uvalue != v:
- self.debug('ignoring untrusted configuration option '
- '%s:%s.%s = %s\n' % (section, name, k, uvalue))
-
+ if section in root.sections():
+ for k, (v, src) in root.getsection(section).iteritems():
+ if k.startswith(prefix):
+ sub[k[len(prefix):]] = v
return main, sub
@@ -586,54 +716,30 @@ class ui(object):
def hasconfig(self, section, name, untrusted=False):
- return self._data(untrusted).hasitem(section, name)
+ return _getconfig(self._data(untrusted), section, name) is not None
def has_section(self, section, untrusted=False):
'''tell whether section exists in config.'''
- return section in self._data(untrusted)
+ return section in self._data(untrusted).sections()
def configitems(self, section, untrusted=False, ignoresub=False):
- items = self._data(untrusted).items(section)
- if ignoresub:
- newitems = {}
- for k, v in items:
- if ':' not in k:
- newitems[k] = v
- items = newitems.items()
- if self.debugflag and not untrusted and self._reportuntrusted:
- for k, v in self._ucfg.items(section):
- if self._tcfg.get(section, k) != v:
- self.debug("ignoring untrusted configuration option "
- "%s.%s = %s\n" % (section, k, v))
- return items
+ items = self._data(untrusted).getsection(section)
+ result = []
+ for k, (v, s) in items.iteritems():
+ if ignoresub and ':' in k:
+ continue
+ if v is not None:
+ result.append((k, v))
+ return result
def walkconfig(self, untrusted=False):
cfg = self._data(untrusted)
- for section in cfg.sections():
- for name, value in self.configitems(section, untrusted):
+ for section in sorted(cfg.sections()):
+ for name, (value, source) in cfg.getsection(section).iteritems():
+ if value is None:
+ continue
yield section, name, value
def plain(self, feature=None):
- '''is plain mode active?
-
- Plain mode means that all configuration variables which affect
- the behavior and output of Mercurial should be
- ignored. Additionally, the output should be stable,
- reproducible and suitable for use in scripts or applications.
-
- The only way to trigger plain mode is by setting either the
- `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
-
- The return value can either be
- - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
- - True otherwise
- '''
- if ('HGPLAIN' not in encoding.environ and
- 'HGPLAINEXCEPT' not in encoding.environ):
- return False
- exceptions = encoding.environ.get('HGPLAINEXCEPT',
- '').strip().split(',')
- if feature and exceptions:
- return feature not in exceptions
- return True
+ return _isplain(feature)
def username(self):
@@ -1446,17 +1552,12 @@ class ui(object):
`overrides` must be a dict of the following structure:
{(section, name) : value}"""
- backups = {}
+ bakocfgs = self._ocfgs
try:
- for (section, name), value in overrides.items():
- backups[(section, name)] = self.backupconfig(section, name)
- self.setconfig(section, name, value, source)
+ acfg = config.atomicconfig('configoverride',
+ ((s, n, (v, source)) for (s, n), v in overrides.items()))
+ self._ocfgs = self._ocfgs.append(acfg)
yield
finally:
- for __, backup in backups.items():
- self.restoreconfig(backup)
- # just restoring ui.quiet config to the previous value is not enough
- # as it does not update ui.quiet class member
- if ('ui', 'quiet') in overrides:
- self.fixconfig(section='ui')
+ self._ocfgs = bakocfgs
class paths(dict):
diff --git a/tests/test-config.t b/tests/test-config.t
--- a/tests/test-config.t
+++ b/tests/test-config.t
@@ -157,6 +157,6 @@ sub-options in [paths] aren't expanded
$ hg showconfig paths
+ paths.foo=$TESTTMP/foo
paths.foo:suboption=~/foo
- paths.foo=$TESTTMP/foo
edit failure
diff --git a/tests/test-trusted.py.out b/tests/test-trusted.py.out
--- a/tests/test-trusted.py.out
+++ b/tests/test-trusted.py.out
@@ -130,5 +130,4 @@ untrusted
not trusting file .hg/hgrc from untrusted user abc, group def
trusted
-ignoring untrusted configuration option paths.local = /another/path
global = /some/path
untrusted
@@ -149,5 +148,4 @@ untrusted
not trusting file .hg/hgrc from untrusted user abc, group def
trusted
-ignoring untrusted configuration option paths.local = /another/path
global = /some/path
untrusted
More information about the Mercurial-devel
mailing list