mercurial/dispatch.py
changeset 35184 ee64e677c3cf
parent 35170 e6487522ef92
parent 34986 02845f7441af
child 35197 aef2b98d9352
--- a/mercurial/dispatch.py	Wed Nov 22 22:18:06 2017 +0800
+++ b/mercurial/dispatch.py	Thu Nov 30 15:48:42 2017 -0500
@@ -55,6 +55,9 @@
         self.fout = fout
         self.ferr = ferr
 
+        # remember options pre-parsed by _earlyreqopt*()
+        self.earlyoptions = {}
+
         # reposetups which run before extensions, useful for chg to pre-fill
         # low-level repo state (for example, changelog) before extensions.
         self.prereposetups = prereposetups or []
@@ -147,7 +150,7 @@
     try:
         if not req.ui:
             req.ui = uimod.ui.load()
-        if '--traceback' in req.args:
+        if _earlyreqoptbool(req, 'traceback', ['--traceback']):
             req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
 
         # set ui streams from the request
@@ -261,7 +264,8 @@
 
             # read --config before doing anything else
             # (e.g. to change trust settings for reading .hg/hgrc)
-            cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
+            cfgs = _parseconfig(req.ui,
+                                _earlyreqopt(req, 'config', ['--config']))
 
             if req.repo:
                 # copy configs that were passed on the cmdline (--config) to
@@ -275,7 +279,7 @@
             if not debugger or ui.plain():
                 # if we are in HGPLAIN mode, then disable custom debugging
                 debugger = 'pdb'
-            elif '--debugger' in req.args:
+            elif _earlyreqoptbool(req, 'debugger', ['--debugger']):
                 # This import can be slow for fancy debuggers, so only
                 # do it when absolutely necessary, i.e. when actual
                 # debugging has been requested
@@ -289,7 +293,7 @@
             debugmortem[debugger] = debugmod.post_mortem
 
             # enter the debugger before command execution
-            if '--debugger' in req.args:
+            if _earlyreqoptbool(req, 'debugger', ['--debugger']):
                 ui.warn(_("entering debugger - "
                         "type c to continue starting hg or h for help\n"))
 
@@ -305,7 +309,7 @@
                 ui.flush()
         except: # re-raises
             # enter the debugger when we hit an exception
-            if '--debugger' in req.args:
+            if _earlyreqoptbool(req, 'debugger', ['--debugger']):
                 traceback.print_exc()
                 debugmortem[debugger](sys.exc_info()[2])
             raise
@@ -465,7 +469,7 @@
         self.cmdname = cmd = args.pop(0)
         self.givenargs = args
 
-        for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
+        for invalidarg in commands.earlyoptflags:
             if _earlygetopt([invalidarg], args):
                 self.badalias = (_("error in definition for alias '%s': %s may "
                                    "only be given on the command line")
@@ -640,11 +644,11 @@
 
     return configs
 
-def _earlygetopt(aliases, args):
+def _earlygetopt(aliases, args, strip=True):
     """Return list of values for an option (or aliases).
 
     The values are listed in the order they appear in args.
-    The options and values are removed from args.
+    The options and values are removed from args if strip=True.
 
     >>> args = [b'x', b'--cwd', b'foo', b'y']
     >>> _earlygetopt([b'--cwd'], args), args
@@ -654,13 +658,33 @@
     >>> _earlygetopt([b'--cwd'], args), args
     (['bar'], ['x', 'y'])
 
+    >>> args = [b'x', b'--cwd=bar', b'y']
+    >>> _earlygetopt([b'--cwd'], args, strip=False), args
+    (['bar'], ['x', '--cwd=bar', 'y'])
+
     >>> args = [b'x', b'-R', b'foo', b'y']
     >>> _earlygetopt([b'-R'], args), args
     (['foo'], ['x', 'y'])
 
+    >>> args = [b'x', b'-R', b'foo', b'y']
+    >>> _earlygetopt([b'-R'], args, strip=False), args
+    (['foo'], ['x', '-R', 'foo', 'y'])
+
     >>> args = [b'x', b'-Rbar', b'y']
     >>> _earlygetopt([b'-R'], args), args
     (['bar'], ['x', 'y'])
+
+    >>> args = [b'x', b'-Rbar', b'y']
+    >>> _earlygetopt([b'-R'], args, strip=False), args
+    (['bar'], ['x', '-Rbar', 'y'])
+
+    >>> args = [b'x', b'-R=bar', b'y']
+    >>> _earlygetopt([b'-R'], args), args
+    (['=bar'], ['x', 'y'])
+
+    >>> args = [b'x', b'-R', b'--', b'y']
+    >>> _earlygetopt([b'-R'], args), args
+    ([], ['x', '-R', '--', 'y'])
     """
     try:
         argcount = args.index("--")
@@ -671,28 +695,77 @@
     pos = 0
     while pos < argcount:
         fullarg = arg = args[pos]
-        equals = arg.find('=')
+        equals = -1
+        if arg.startswith('--'):
+            equals = arg.find('=')
         if equals > -1:
             arg = arg[:equals]
         if arg in aliases:
-            del args[pos]
             if equals > -1:
                 values.append(fullarg[equals + 1:])
-                argcount -= 1
+                if strip:
+                    del args[pos]
+                    argcount -= 1
+                else:
+                    pos += 1
             else:
                 if pos + 1 >= argcount:
                     # ignore and let getopt report an error if there is no value
                     break
-                values.append(args.pop(pos))
-                argcount -= 2
+                values.append(args[pos + 1])
+                if strip:
+                    del args[pos:pos + 2]
+                    argcount -= 2
+                else:
+                    pos += 2
         elif arg[:2] in shortopts:
             # short option can have no following space, e.g. hg log -Rfoo
-            values.append(args.pop(pos)[2:])
-            argcount -= 1
+            values.append(args[pos][2:])
+            if strip:
+                del args[pos]
+                argcount -= 1
+            else:
+                pos += 1
         else:
             pos += 1
     return values
 
+def _earlyreqopt(req, name, aliases):
+    """Peek a list option without using a full options table"""
+    values = _earlygetopt(aliases, req.args, strip=False)
+    req.earlyoptions[name] = values
+    return values
+
+def _earlyreqoptstr(req, name, aliases):
+    """Peek a string option without using a full options table"""
+    value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
+    req.earlyoptions[name] = value
+    return value
+
+def _earlyreqoptbool(req, name, aliases):
+    """Peek a boolean option without using a full options table
+
+    >>> req = request([b'x', b'--debugger'])
+    >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
+    True
+
+    >>> req = request([b'x', b'--', b'--debugger'])
+    >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
+    """
+    try:
+        argcount = req.args.index("--")
+    except ValueError:
+        argcount = len(req.args)
+    value = None
+    pos = 0
+    while pos < argcount:
+        arg = req.args[pos]
+        if arg in aliases:
+            value = True
+        pos += 1
+    req.earlyoptions[name] = value
+    return value
+
 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
     # run pre-hook, and abort if it fails
     hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
@@ -727,8 +800,8 @@
         lui = ui.copy()
         lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
 
-    if rpath and rpath[-1]:
-        path = lui.expandpath(rpath[-1])
+    if rpath:
+        path = lui.expandpath(rpath)
         lui = ui.copy()
         lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
 
@@ -759,6 +832,9 @@
     fn = entry[0]
 
     if cmd and util.safehasattr(fn, 'shell'):
+        # shell alias shouldn't receive early options which are consumed by hg
+        args = args[:]
+        _earlygetopt(commands.earlyoptflags, args, strip=True)
         d = lambda: fn(ui, *args[1:])
         return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
                                   [], {})
@@ -768,11 +844,11 @@
     ui = req.ui
 
     # check for cwd
-    cwd = _earlygetopt(['--cwd'], args)
+    cwd = _earlyreqoptstr(req, 'cwd', ['--cwd'])
     if cwd:
-        os.chdir(cwd[-1])
+        os.chdir(cwd)
 
-    rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
+    rpath = _earlyreqoptstr(req, 'repository', ["-R", "--repository", "--repo"])
     path, lui = _getlocal(ui, rpath)
 
     uis = {ui, lui}
@@ -780,7 +856,7 @@
     if req.repo:
         uis.add(req.repo.ui)
 
-    if '--profile' in args:
+    if _earlyreqoptbool(req, 'profile', ['--profile']):
         for ui_ in uis:
             ui_.setconfig('profiling', 'enabled', 'true', '--profile')
 
@@ -812,14 +888,17 @@
         fullargs = args
         cmd, func, args, options, cmdoptions = _parse(lui, args)
 
-        if options["config"]:
+        if options["config"] != req.earlyoptions["config"]:
             raise error.Abort(_("option --config may not be abbreviated!"))
-        if options["cwd"]:
+        if options["cwd"] != req.earlyoptions["cwd"]:
             raise error.Abort(_("option --cwd may not be abbreviated!"))
-        if options["repository"]:
+        if options["repository"] != req.earlyoptions["repository"]:
             raise error.Abort(_(
                 "option -R has to be separated from other options (e.g. not "
                 "-qR) and --repository may only be abbreviated as --repo!"))
+        if options["debugger"] != req.earlyoptions["debugger"]:
+            raise error.Abort(_("option --debugger may not be abbreviated!"))
+        # don't validate --profile/--traceback, which can be enabled from now
 
         if options["encoding"]:
             encoding.encoding = options["encoding"]
@@ -908,7 +987,7 @@
                 except error.RequirementError:
                     raise
                 except error.RepoError:
-                    if rpath and rpath[-1]: # invalid -R path
+                    if rpath: # invalid -R path
                         raise
                     if not func.optionalrepo:
                         if func.inferrepo and args and not path: