[PATCH 1 of 2] config: introduce load order tracking

Boris Feld boris.feld at octobus.net
Mon Jul 9 10:12:21 UTC 2018


# HG changeset patch
# User Boris Feld <boris.feld at octobus.net>
# Date 1530887625 -7200
#      Fri Jul 06 16:33:45 2018 +0200
# Node ID 1019d8a4f6b810aaa63651ed56b29668650f590e
# Parent  8c38d29482177cd40243a008057d6762c7d23c6f
# EXP-Topic config-order
# Available At https://bitbucket.org/octobus/mercurial-devel/
#              hg pull https://bitbucket.org/octobus/mercurial-devel/ -r 1019d8a4f6b8
config: introduce load order tracking

Configuration values reads from disk are now associated with the index of the
file they came from. (First loaded file has index 0, second file index 1,
etc…).

This will ultimately allow us to use the alias value of a configuration item
if it was defined in a higher priority file than the configuration item value
itself.

See next changeset for details.

Value set directly through the code or through the command line have the highest
priority.

diff --git a/mercurial/config.py b/mercurial/config.py
--- a/mercurial/config.py
+++ b/mercurial/config.py
@@ -26,8 +26,11 @@ class config(object):
             for k in data._data:
                 self._data[k] = data[k].copy()
             self._source = data._source.copy()
+            self._level = data._level.copy()
         else:
             self._source = util.cowdict()
+            self._level = util.cowdict()
+
     def copy(self):
         return config(self)
     def __contains__(self, section):
@@ -47,6 +50,7 @@ class config(object):
                 self._data[s] = ds.preparewrite()
                 del self._data[s][n]
                 del self._source[(s, n)]
+                self._level.pop((s, n), None)
         for s in src:
             ds = self._data.get(s, None)
             if ds:
@@ -55,6 +59,7 @@ class config(object):
                 self._data[s] = util.cowsortdict()
             self._data[s].update(src._data[s])
         self._source.update(src._source)
+        self._level.update(src._level)
     def get(self, section, item, default=None):
         return self._data.get(section, {}).get(item, default)
 
@@ -66,17 +71,20 @@ class config(object):
         try:
             value = self._data[section][item]
             source = self.source(section, item)
-            return (section, item, value, source)
+            level = self.level(section, item)
+            return (section, item, value, source, level)
         except KeyError:
             return (section, item)
 
     def source(self, section, item):
         return self._source.get((section, item), "")
+    def level(self, section, item):
+        return self._level.get((section, item), None)
     def sections(self):
         return sorted(self._data.keys())
     def items(self, section):
         return list(self._data.get(section, {}).iteritems())
-    def set(self, section, item, value, source=""):
+    def set(self, section, item, value, source="", level=None):
         if pycompat.ispy3:
             assert not isinstance(value, str), (
                 'config values may not be unicode strings on Python 3')
@@ -88,24 +96,30 @@ class config(object):
         if source:
             self._source = self._source.preparewrite()
             self._source[(section, item)] = source
+        if level is not None:
+            self._level[(section, item)] = level
 
     def restore(self, data):
         """restore data returned by self.backup"""
         self._source = self._source.preparewrite()
-        if len(data) == 4:
+        self._level = self._level.preparewrite()
+        if len(data) == 5:
             # restore old data
-            section, item, value, source = data
+            section, item, value, source, level = data
             self._data[section] = self._data[section].preparewrite()
             self._data[section][item] = value
             self._source[(section, item)] = source
+            self._source[(section, item)] = level
         else:
             # no data before, remove everything
             section, item = data
             if section in self._data:
                 self._data[section].pop(item, None)
             self._source.pop((section, item), None)
+            self._level.pop((section, item), None)
 
-    def parse(self, src, data, sections=None, remap=None, include=None):
+    def parse(self, src, data, sections=None, remap=None, include=None,
+            level=None):
         sectionre = util.re.compile(br'\[([^\[]+)\]')
         itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
         contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
@@ -134,7 +148,8 @@ class config(object):
                     if sections and section not in sections:
                         continue
                     v = self.get(section, item) + "\n" + m.group(1)
-                    self.set(section, item, v, "%s:%d" % (src, line))
+                    self.set(section, item, v, "%s:%d" % (src, line),
+                             level=level)
                     continue
                 item = None
                 cont = False
@@ -172,7 +187,8 @@ class config(object):
                 cont = True
                 if sections and section not in sections:
                     continue
-                self.set(section, item, m.group(2), "%s:%d" % (src, line))
+                self.set(section, item, m.group(2), "%s:%d" % (src, line),
+                         level=level)
                 continue
             m = unsetre.match(l)
             if m:
@@ -187,14 +203,14 @@ class config(object):
 
             raise error.ParseError(l.rstrip(), ("%s:%d" % (src, line)))
 
-    def read(self, path, fp=None, sections=None, remap=None):
+    def read(self, path, fp=None, sections=None, remap=None, level=None):
         if not fp:
             fp = util.posixfile(path, 'rb')
         assert getattr(fp, 'mode', r'rb') == r'rb', (
             'config files must be opened in binary mode, got fp=%r mode=%r' % (
                 fp, fp.mode))
-        self.parse(path, fp.read(),
-                   sections=sections, remap=remap, include=self.read)
+        self.parse(path, fp.read(), sections=sections, remap=remap,
+                   include=self.read, level=level)
 
 def parselist(value):
     """parse a configuration value as a list of comma/space separated strings
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -191,6 +191,9 @@ def _catchterm(*args):
 # _reqexithandlers: callbacks run at the end of a request
 _reqexithandlers = []
 
+# config level set directly have higher level than those from disk
+directconfig = sys.maxint
+
 class ui(object):
     def __init__(self, src=None):
         """Create a fresh new ui object if no src given
@@ -210,6 +213,7 @@ class ui(object):
         self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
         self._reportuntrusted = True
         self._knownconfig = configitems.coreitems
+        self._configlevel = 0
         self._ocfg = config.config() # overlay
         self._tcfg = config.config() # trusted
         self._ucfg = config.config() # untrusted
@@ -234,6 +238,7 @@ class ui(object):
             self._disablepager = src._disablepager
             self._tweaked = src._tweaked
 
+            self._configlevel = src._configlevel
             self._tcfg = src._tcfg.copy()
             self._ucfg = src._ucfg.copy()
             self._ocfg = src._ocfg.copy()
@@ -282,12 +287,14 @@ class ui(object):
             if t == 'path':
                 u.readconfig(f, trust=True)
             elif t == 'items':
+                level = u._configlevel
+                u._configlevel += 1
                 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)
+                    u._tcfg.set(section, name, value, source, level=level)
+                    u._ucfg.set(section, name, value, source, level=level)
                     sections.add(section)
                 for section in sections:
                     u.fixconfig(section=section)
@@ -314,7 +321,9 @@ class ui(object):
         for section in tmpcfg:
             for name, value in tmpcfg.items(section):
                 if not self.hasconfig(section, name):
-                    self.setconfig(section, name, value, "<tweakdefaults>")
+                    self.setconfig(section, name, value, "<tweakdefaults>",
+                                   level=self._configlevel)
+        self._configlevel += 1
 
     def copy(self):
         return self.__class__(self)
@@ -390,6 +399,8 @@ class ui(object):
 
     def readconfig(self, filename, root=None, trust=False,
                    sections=None, remap=None):
+        level = self._configlevel
+        self._configlevel += 1
         try:
             fp = open(filename, u'rb')
         except IOError:
@@ -401,7 +412,7 @@ class ui(object):
         trusted = sections or trust or self._trusted(fp, filename)
 
         try:
-            cfg.read(filename, fp, sections=sections, remap=remap)
+            cfg.read(filename, fp, sections=sections, remap=remap, level=level)
             fp.close()
         except error.ConfigError as inst:
             if trusted:
@@ -459,7 +470,7 @@ class ui(object):
                     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)
+                    c.set("paths", n, p, level=c.level("paths", n))
 
         if section in (None, 'ui'):
             # update ui options
@@ -487,9 +498,9 @@ class ui(object):
         self._tcfg.restore(data[1])
         self._ucfg.restore(data[2])
 
-    def setconfig(self, section, name, value, source=''):
+    def setconfig(self, section, name, value, source='', level=directconfig):
         for cfg in (self._ocfg, self._tcfg, self._ucfg):
-            cfg.set(section, name, value, source)
+            cfg.set(section, name, value, source, level=level)
         self.fixconfig(section=section)
         self._maybetweakdefaults()
 


More information about the Mercurial-devel mailing list