mercurial/dispatch.py
changeset 52744 25b344f2aeef
parent 52743 1ccbca64610a
child 52745 8780d5707812
--- a/mercurial/dispatch.py	Wed Jan 29 16:04:39 2025 -0500
+++ b/mercurial/dispatch.py	Wed Jan 29 16:09:06 2025 -0500
@@ -17,6 +17,9 @@
 import sys
 import traceback
 
+from typing import (
+    Iterable,
+)
 
 from .i18n import _
 
@@ -26,6 +29,7 @@
     cmdutil,
     color,
     commands,
+    config as configmod,
     demandimport,
     encoding,
     error,
@@ -387,13 +391,22 @@
                 debugtrace = {b'pdb': pdb.set_trace}
                 debugmortem = {b'pdb': pdb.post_mortem}
 
-                # read --config before doing anything else
-                # (e.g. to change trust settings for reading .hg/hgrc)
+                # read --config-file and --config before doing anything else
+                # (e.g. to change trust settings for reading .hg/hgrc).
+
+                # cmdargs may not have been initialized here (in the case of an
+                # error), so use pycompat.sysargv instead.
+                file_cfgs = _parse_config_files(
+                    req.ui, pycompat.sysargv, req.earlyoptions[b'config_file']
+                )
                 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
 
                 if req.repo:
                     # copy configs that were passed on the cmdline (--config) to
                     # the repo ui
+                    for sec, name, val, source in file_cfgs:
+                        req.repo.ui.setconfig(sec, name, val, source=source)
+
                     for sec, name, val in cfgs:
                         req.repo.ui.setconfig(
                             sec, name, val, source=b'--config'
@@ -875,6 +888,48 @@
     return configs
 
 
+def _parse_config_files(
+    ui, cmdargs: list[bytes], config_files: Iterable[bytes]
+) -> list[tuple[bytes, bytes, bytes, bytes]]:
+    """parse the --config-file options from the command line
+
+    A list of tuples containing (section, name, value, source) is returned,
+    in the order they were read.
+    """
+
+    configs: list[tuple[bytes, bytes, bytes, bytes]] = []
+
+    cfg = configmod.config()
+
+    for file in config_files:
+        try:
+            cfg.read(file)
+        except error.ConfigError as e:
+            raise error.InputError(
+                _(b'invalid --config-file content at %s') % e.location,
+                hint=e.message,
+            )
+        except FileNotFoundError:
+            hint = None
+            if b'--cwd' in cmdargs:
+                hint = _(b"this file is resolved before --cwd is processed")
+
+            raise error.InputError(
+                _(b'missing file "%s" for --config-file') % file, hint=hint
+            )
+
+    for section in cfg.sections():
+        for item in cfg.items(section):
+            name = item[0]
+            value = item[1]
+            src = cfg.source(section, name)
+
+            ui.setconfig(section, name, value, src)
+            configs.append((section, name, value, src))
+
+    return configs
+
+
 def _earlyparseopts(ui, args):
     options = {}
     fancyopts.fancyopts(
@@ -892,7 +947,13 @@
     """Split args into a list of possible early options and remainder args"""
     shortoptions = b'R:'
     # TODO: perhaps 'debugger' should be included
-    longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
+    longoptions = [
+        b'cwd=',
+        b'repository=',
+        b'repo=',
+        b'config=',
+        b'config-file=',
+    ]
     return fancyopts.earlygetopt(
         args, shortoptions, longoptions, gnu=True, keepsep=True
     )
@@ -1097,6 +1158,10 @@
 
         if options[b"config"] != req.earlyoptions[b"config"]:
             raise error.InputError(_(b"option --config may not be abbreviated"))
+        if options[b"config_file"] != req.earlyoptions[b"config_file"]:
+            raise error.InputError(
+                _(b"option --config-file may not be abbreviated")
+            )
         if options[b"cwd"] != req.earlyoptions[b"cwd"]:
             raise error.InputError(_(b"option --cwd may not be abbreviated"))
         if options[b"repository"] != req.earlyoptions[b"repository"]: