changeset 35111:62539b425347 stable

merge with i18n
author Augie Fackler <augie@google.com>
date Wed, 29 Nov 2017 17:49:08 -0500
parents 281214150561 (diff) 3f2e49d9802e (current diff)
children ee64e677c3cf 27196b7fc1ac
files
diffstat 18 files changed, 643 insertions(+), 191 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/convert/__init__.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/hgext/convert/__init__.py	Wed Nov 29 17:49:08 2017 -0500
@@ -28,103 +28,6 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
-configtable = {}
-configitem = registrar.configitem(configtable)
-
-configitem('convert', 'cvsps.cache',
-    default=True,
-)
-configitem('convert', 'cvsps.fuzz',
-    default=60,
-)
-configitem('convert', 'cvsps.logencoding',
-    default=None,
-)
-configitem('convert', 'cvsps.mergefrom',
-    default=None,
-)
-configitem('convert', 'cvsps.mergeto',
-    default=None,
-)
-configitem('convert', 'git.committeractions',
-    default=lambda: ['messagedifferent'],
-)
-configitem('convert', 'git.extrakeys',
-    default=list,
-)
-configitem('convert', 'git.findcopiesharder',
-    default=False,
-)
-configitem('convert', 'git.remoteprefix',
-    default='remote',
-)
-configitem('convert', 'git.renamelimit',
-    default=400,
-)
-configitem('convert', 'git.saverev',
-    default=True,
-)
-configitem('convert', 'git.similarity',
-    default=50,
-)
-configitem('convert', 'git.skipsubmodules',
-    default=False,
-)
-configitem('convert', 'hg.clonebranches',
-    default=False,
-)
-configitem('convert', 'hg.ignoreerrors',
-    default=False,
-)
-configitem('convert', 'hg.revs',
-    default=None,
-)
-configitem('convert', 'hg.saverev',
-    default=False,
-)
-configitem('convert', 'hg.sourcename',
-    default=None,
-)
-configitem('convert', 'hg.startrev',
-    default=None,
-)
-configitem('convert', 'hg.tagsbranch',
-    default='default',
-)
-configitem('convert', 'hg.usebranchnames',
-    default=True,
-)
-configitem('convert', 'ignoreancestorcheck',
-    default=False,
-)
-configitem('convert', 'localtimezone',
-    default=False,
-)
-configitem('convert', 'p4.encoding',
-    default=lambda: convcmd.orig_encoding,
-)
-configitem('convert', 'p4.startrev',
-    default=0,
-)
-configitem('convert', 'skiptags',
-    default=False,
-)
-configitem('convert', 'svn.debugsvnlog',
-    default=True,
-)
-configitem('convert', 'svn.trunk',
-    default=None,
-)
-configitem('convert', 'svn.tags',
-    default=None,
-)
-configitem('convert', 'svn.branches',
-    default=None,
-)
-configitem('convert', 'svn.startrev',
-    default=0,
-)
-
 # Commands definition was moved elsewhere to ease demandload job.
 
 @command('convert',
--- a/hgext/convert/p4.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/hgext/convert/p4.py	Wed Nov 29 17:49:08 2017 -0500
@@ -44,6 +44,9 @@
 
 class p4_source(common.converter_source):
     def __init__(self, ui, path, revs=None):
+        # avoid import cycle
+        from . import convcmd
+
         super(p4_source, self).__init__(ui, path, revs=revs)
 
         if "/" in path and not path.startswith('//'):
@@ -53,7 +56,8 @@
         common.checktool('p4', abort=False)
 
         self.revmap = {}
-        self.encoding = self.ui.config('convert', 'p4.encoding')
+        self.encoding = self.ui.config('convert', 'p4.encoding',
+                                       convcmd.orig_encoding)
         self.re_type = re.compile(
             "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
             "(\+\w+)?$")
--- a/mercurial/chgserver.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/mercurial/chgserver.py	Wed Nov 29 17:49:08 2017 -0500
@@ -235,6 +235,7 @@
     cwds = dispatch._earlygetopt(['--cwd'], args)
     cwd = cwds and os.path.realpath(cwds[-1]) or None
     rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
+    rpath = rpath and rpath[-1] or ''
     path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
 
     return (newui, newlui)
--- a/mercurial/cmdutil.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/mercurial/cmdutil.py	Wed Nov 29 17:49:08 2017 -0500
@@ -3153,6 +3153,18 @@
             raise error.Abort(
                 _("failed to mark all new/missing files as added/removed"))
 
+        # Check subrepos. This depends on in-place wctx._status update in
+        # subrepo.precommit(). To minimize the risk of this hack, we do
+        # nothing if .hgsub does not exist.
+        if '.hgsub' in wctx or '.hgsub' in old:
+            from . import subrepo  # avoid cycle: cmdutil -> subrepo -> cmdutil
+            subs, commitsubs, newsubstate = subrepo.precommit(
+                ui, wctx, wctx._status, matcher)
+            # amend should abort if commitsubrepos is enabled
+            assert not commitsubs
+            if subs:
+                subrepo.writestate(repo, newsubstate)
+
         filestoamend = set(f for f in wctx.files() if matcher(f))
 
         changes = (len(filestoamend) > 0)
@@ -3166,9 +3178,11 @@
             # introduced file X and the file was renamed in the working
             # copy, then those two files are the same and
             # we can discard X from our list of files. Likewise if X
-            # was deleted, it's no longer relevant
+            # was removed, it's no longer relevant. If X is missing (aka
+            # deleted), old X must be preserved.
             files.update(filestoamend)
-            files = [f for f in files if not samefile(f, wctx, base)]
+            files = [f for f in files if (not samefile(f, wctx, base)
+                                          or f in wctx.deleted())]
 
             def filectxfn(repo, ctx_, path):
                 try:
@@ -3180,12 +3194,11 @@
                     if path not in filestoamend:
                         return old.filectx(path)
 
+                    # Return None for removed files.
+                    if path in wctx.removed():
+                        return None
+
                     fctx = wctx[path]
-
-                    # Return None for removed files.
-                    if not fctx.exists():
-                        return None
-
                     flags = fctx.flags()
                     mctx = context.memfilectx(repo,
                                               fctx.path(), fctx.data(),
--- a/mercurial/commands.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/mercurial/commands.py	Wed Nov 29 17:49:08 2017 -0500
@@ -102,6 +102,10 @@
      _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
 ]
 
+# options which must be pre-parsed before loading configs and extensions
+# TODO: perhaps --debugger should be included
+earlyoptflags = ("--cwd", "-R", "--repository", "--repo", "--config")
+
 dryrunopts = cmdutil.dryrunopts
 remoteopts = cmdutil.remoteopts
 walkopts = cmdutil.walkopts
--- a/mercurial/configitems.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/mercurial/configitems.py	Wed Nov 29 17:49:08 2017 -0500
@@ -208,6 +208,99 @@
     default=None,
     generic=True,
 )
+coreconfigitem('convert', 'cvsps.cache',
+    default=True,
+)
+coreconfigitem('convert', 'cvsps.fuzz',
+    default=60,
+)
+coreconfigitem('convert', 'cvsps.logencoding',
+    default=None,
+)
+coreconfigitem('convert', 'cvsps.mergefrom',
+    default=None,
+)
+coreconfigitem('convert', 'cvsps.mergeto',
+    default=None,
+)
+coreconfigitem('convert', 'git.committeractions',
+    default=lambda: ['messagedifferent'],
+)
+coreconfigitem('convert', 'git.extrakeys',
+    default=list,
+)
+coreconfigitem('convert', 'git.findcopiesharder',
+    default=False,
+)
+coreconfigitem('convert', 'git.remoteprefix',
+    default='remote',
+)
+coreconfigitem('convert', 'git.renamelimit',
+    default=400,
+)
+coreconfigitem('convert', 'git.saverev',
+    default=True,
+)
+coreconfigitem('convert', 'git.similarity',
+    default=50,
+)
+coreconfigitem('convert', 'git.skipsubmodules',
+    default=False,
+)
+coreconfigitem('convert', 'hg.clonebranches',
+    default=False,
+)
+coreconfigitem('convert', 'hg.ignoreerrors',
+    default=False,
+)
+coreconfigitem('convert', 'hg.revs',
+    default=None,
+)
+coreconfigitem('convert', 'hg.saverev',
+    default=False,
+)
+coreconfigitem('convert', 'hg.sourcename',
+    default=None,
+)
+coreconfigitem('convert', 'hg.startrev',
+    default=None,
+)
+coreconfigitem('convert', 'hg.tagsbranch',
+    default='default',
+)
+coreconfigitem('convert', 'hg.usebranchnames',
+    default=True,
+)
+coreconfigitem('convert', 'ignoreancestorcheck',
+    default=False,
+)
+coreconfigitem('convert', 'localtimezone',
+    default=False,
+)
+coreconfigitem('convert', 'p4.encoding',
+    default=dynamicdefault,
+)
+coreconfigitem('convert', 'p4.startrev',
+    default=0,
+)
+coreconfigitem('convert', 'skiptags',
+    default=False,
+)
+coreconfigitem('convert', 'svn.debugsvnlog',
+    default=True,
+)
+coreconfigitem('convert', 'svn.trunk',
+    default=None,
+)
+coreconfigitem('convert', 'svn.tags',
+    default=None,
+)
+coreconfigitem('convert', 'svn.branches',
+    default=None,
+)
+coreconfigitem('convert', 'svn.startrev',
+    default=0,
+)
 coreconfigitem('debug', 'dirstate.delaywrite',
     default=0,
 )
--- a/mercurial/dispatch.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/mercurial/dispatch.py	Wed Nov 29 17:49:08 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:
--- a/mercurial/localrepo.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/mercurial/localrepo.py	Wed Nov 29 17:49:08 2017 -0500
@@ -1845,58 +1845,8 @@
                 status.modified.extend(status.clean) # mq may commit clean files
 
             # check subrepos
-            subs = []
-            commitsubs = set()
-            newstate = wctx.substate.copy()
-            # only manage subrepos and .hgsubstate if .hgsub is present
-            if '.hgsub' in wctx:
-                # we'll decide whether to track this ourselves, thanks
-                for c in status.modified, status.added, status.removed:
-                    if '.hgsubstate' in c:
-                        c.remove('.hgsubstate')
-
-                # compare current state to last committed state
-                # build new substate based on last committed state
-                oldstate = wctx.p1().substate
-                for s in sorted(newstate.keys()):
-                    if not match(s):
-                        # ignore working copy, use old state if present
-                        if s in oldstate:
-                            newstate[s] = oldstate[s]
-                            continue
-                        if not force:
-                            raise error.Abort(
-                                _("commit with new subrepo %s excluded") % s)
-                    dirtyreason = wctx.sub(s).dirtyreason(True)
-                    if dirtyreason:
-                        if not self.ui.configbool('ui', 'commitsubrepos'):
-                            raise error.Abort(dirtyreason,
-                                hint=_("use --subrepos for recursive commit"))
-                        subs.append(s)
-                        commitsubs.add(s)
-                    else:
-                        bs = wctx.sub(s).basestate()
-                        newstate[s] = (newstate[s][0], bs, newstate[s][2])
-                        if oldstate.get(s, (None, None, None))[1] != bs:
-                            subs.append(s)
-
-                # check for removed subrepos
-                for p in wctx.parents():
-                    r = [s for s in p.substate if s not in newstate]
-                    subs += [s for s in r if match(s)]
-                if subs:
-                    if (not match('.hgsub') and
-                        '.hgsub' in (wctx.modified() + wctx.added())):
-                        raise error.Abort(
-                            _("can't commit subrepos without .hgsub"))
-                    status.modified.insert(0, '.hgsubstate')
-
-            elif '.hgsub' in status.removed:
-                # clean up .hgsubstate when .hgsub is removed
-                if ('.hgsubstate' in wctx and
-                    '.hgsubstate' not in (status.modified + status.added +
-                                          status.removed)):
-                    status.removed.insert(0, '.hgsubstate')
+            subs, commitsubs, newstate = subrepo.precommit(
+                self.ui, wctx, status, match, force=force)
 
             # make sure all explicit patterns are matched
             if not force:
--- a/mercurial/scmutil.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/mercurial/scmutil.py	Wed Nov 29 17:49:08 2017 -0500
@@ -1233,9 +1233,17 @@
 
     def reportsummary(func):
         """decorator for report callbacks."""
-        reporef = weakref.ref(repo)
+        # The repoview life cycle is shorter than the one of the actual
+        # underlying repository. So the filtered object can die before the
+        # weakref is used leading to troubles. We keep a reference to the
+        # unfiltered object and restore the filtering when retrieving the
+        # repository through the weakref.
+        filtername = repo.filtername
+        reporef = weakref.ref(repo.unfiltered())
         def wrapped(tr):
             repo = reporef()
+            if filtername:
+                repo = repo.filtered(filtername)
             func(repo, tr)
         newcat = '%2i-txnreport' % len(categories)
         otr.addpostclose(newcat, wrapped)
--- a/mercurial/subrepo.py	Tue Nov 21 13:50:25 2017 -0200
+++ b/mercurial/subrepo.py	Wed Nov 29 17:49:08 2017 -0500
@@ -293,6 +293,71 @@
     writestate(repo, sm)
     return sm
 
+def precommit(ui, wctx, status, match, force=False):
+    """Calculate .hgsubstate changes that should be applied before committing
+
+    Returns (subs, commitsubs, newstate) where
+    - subs: changed subrepos (including dirty ones)
+    - commitsubs: dirty subrepos which the caller needs to commit recursively
+    - newstate: new state dict which the caller must write to .hgsubstate
+
+    This also updates the given status argument.
+    """
+    subs = []
+    commitsubs = set()
+    newstate = wctx.substate.copy()
+
+    # only manage subrepos and .hgsubstate if .hgsub is present
+    if '.hgsub' in wctx:
+        # we'll decide whether to track this ourselves, thanks
+        for c in status.modified, status.added, status.removed:
+            if '.hgsubstate' in c:
+                c.remove('.hgsubstate')
+
+        # compare current state to last committed state
+        # build new substate based on last committed state
+        oldstate = wctx.p1().substate
+        for s in sorted(newstate.keys()):
+            if not match(s):
+                # ignore working copy, use old state if present
+                if s in oldstate:
+                    newstate[s] = oldstate[s]
+                    continue
+                if not force:
+                    raise error.Abort(
+                        _("commit with new subrepo %s excluded") % s)
+            dirtyreason = wctx.sub(s).dirtyreason(True)
+            if dirtyreason:
+                if not ui.configbool('ui', 'commitsubrepos'):
+                    raise error.Abort(dirtyreason,
+                        hint=_("use --subrepos for recursive commit"))
+                subs.append(s)
+                commitsubs.add(s)
+            else:
+                bs = wctx.sub(s).basestate()
+                newstate[s] = (newstate[s][0], bs, newstate[s][2])
+                if oldstate.get(s, (None, None, None))[1] != bs:
+                    subs.append(s)
+
+        # check for removed subrepos
+        for p in wctx.parents():
+            r = [s for s in p.substate if s not in newstate]
+            subs += [s for s in r if match(s)]
+        if subs:
+            if (not match('.hgsub') and
+                '.hgsub' in (wctx.modified() + wctx.added())):
+                raise error.Abort(_("can't commit subrepos without .hgsub"))
+            status.modified.insert(0, '.hgsubstate')
+
+    elif '.hgsub' in status.removed:
+        # clean up .hgsubstate when .hgsub is removed
+        if ('.hgsubstate' in wctx and
+            '.hgsubstate' not in (status.modified + status.added +
+                                  status.removed)):
+            status.removed.insert(0, '.hgsubstate')
+
+    return subs, commitsubs, newstate
+
 def _updateprompt(ui, sub, dirty, local, remote):
     if dirty:
         msg = (_(' subrepository sources for %s differ\n'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-amend-subrepo.t	Wed Nov 29 17:49:08 2017 -0500
@@ -0,0 +1,154 @@
+#testcases obsstore-off obsstore-on
+
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > amend =
+  > EOF
+
+#if obsstore-on
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > evolution.createmarkers = True
+  > EOF
+#endif
+
+Prepare parent repo
+-------------------
+
+  $ hg init r
+  $ cd r
+
+  $ echo a > a
+  $ hg ci -Am0
+  adding a
+
+Link first subrepo
+------------------
+
+  $ echo 's = s' >> .hgsub
+  $ hg add .hgsub
+  $ hg init s
+
+amend without .hgsub
+
+  $ hg amend s
+  abort: can't commit subrepos without .hgsub
+  [255]
+
+amend with subrepo
+
+  $ hg amend
+  saved backup bundle to * (glob) (obsstore-off !)
+  $ hg status --change .
+  A .hgsub
+  A .hgsubstate
+  A a
+  $ cat .hgsubstate
+  0000000000000000000000000000000000000000 s
+
+Update subrepo
+--------------
+
+add new commit to be amended
+
+  $ echo a >> a
+  $ hg ci -m1
+
+amend with dirty subrepo
+
+  $ echo a >> s/a
+  $ hg add -R s
+  adding s/a
+  $ hg amend
+  abort: uncommitted changes in subrepository "s"
+  (use --subrepos for recursive commit)
+  [255]
+
+amend with modified subrepo
+
+  $ hg ci -R s -m0
+  $ hg amend
+  saved backup bundle to * (glob) (obsstore-off !)
+  $ hg status --change .
+  M .hgsubstate
+  M a
+  $ cat .hgsubstate
+  f7b1eb17ad24730a1651fccd46c43826d1bbc2ac s
+
+revert subrepo change
+
+  $ hg up -R s -q null
+  $ hg amend
+  saved backup bundle to * (glob) (obsstore-off !)
+  $ hg status --change .
+  M a
+
+Link another subrepo
+--------------------
+
+add new commit to be amended
+
+  $ echo b >> b
+  $ hg ci -qAm2
+
+also checks if non-subrepo change is included
+
+  $ echo a >> a
+
+amend with another subrepo
+
+  $ hg init t
+  $ echo b >> t/b
+  $ hg ci -R t -Am0
+  adding b
+  $ echo 't = t' >> .hgsub
+  $ hg amend
+  saved backup bundle to * (glob) (obsstore-off !)
+  $ hg status --change .
+  M .hgsub
+  M .hgsubstate
+  M a
+  A b
+  $ cat .hgsubstate
+  0000000000000000000000000000000000000000 s
+  bfb1a4fb358498a9533dabf4f2043d94162f1fcd t
+
+Unlink one subrepo
+------------------
+
+add new commit to be amended
+
+  $ echo a >> a
+  $ hg ci -m3
+
+amend with one subrepo dropped
+
+  $ echo 't = t' > .hgsub
+  $ hg amend
+  saved backup bundle to * (glob) (obsstore-off !)
+  $ hg status --change .
+  M .hgsub
+  M .hgsubstate
+  M a
+  $ cat .hgsubstate
+  bfb1a4fb358498a9533dabf4f2043d94162f1fcd t
+
+Unlink subrepos completely
+--------------------------
+
+add new commit to be amended
+
+  $ echo a >> a
+  $ hg ci -m3
+
+amend with .hgsub removed
+
+  $ hg rm .hgsub
+  $ hg amend
+  saved backup bundle to * (glob) (obsstore-off !)
+  $ hg status --change .
+  M a
+  R .hgsub
+  R .hgsubstate
+
+  $ cd ..
--- a/tests/test-amend.t	Tue Nov 21 13:50:25 2017 -0200
+++ b/tests/test-amend.t	Wed Nov 29 17:49:08 2017 -0500
@@ -235,3 +235,97 @@
   |
   o  A
   
+
+More complete test for status changes (issue5732)
+-------------------------------------------------
+
+Generates history of files having 3 states, r0_r1_wc:
+
+ r0: ground (content/missing)
+ r1: old state to be amended (content/missing, where missing means removed)
+ wc: changes to be included in r1 (content/missing-tracked/untracked)
+
+  $ hg init $TESTTMP/wcstates
+  $ cd $TESTTMP/wcstates
+
+  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 1
+  $ hg addremove -q --similarity 0
+  $ hg commit -m0
+
+  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 2
+  $ hg addremove -q --similarity 0
+  $ hg commit -m1
+
+  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 wc
+  $ hg addremove -q --similarity 0
+  $ hg forget *_*_*-untracked
+  $ rm *_*_missing-*
+
+amend r1 to include wc changes
+
+  $ hg amend
+  saved backup bundle to * (glob) (obsstore-off !)
+
+clean/modified/removed/added states of the amended revision
+
+  $ hg status --all --change . 'glob:content1_*_content1-tracked'
+  C content1_content1_content1-tracked
+  C content1_content2_content1-tracked
+  C content1_missing_content1-tracked
+  $ hg status --all --change . 'glob:content1_*_content[23]-tracked'
+  M content1_content1_content3-tracked
+  M content1_content2_content2-tracked
+  M content1_content2_content3-tracked
+  M content1_missing_content3-tracked
+  $ hg status --all --change . 'glob:content1_*_missing-tracked'
+  M content1_content2_missing-tracked
+  R content1_missing_missing-tracked
+  C content1_content1_missing-tracked
+  $ hg status --all --change . 'glob:content1_*_*-untracked'
+  R content1_content1_content1-untracked
+  R content1_content1_content3-untracked
+  R content1_content1_missing-untracked
+  R content1_content2_content1-untracked
+  R content1_content2_content2-untracked
+  R content1_content2_content3-untracked
+  R content1_content2_missing-untracked
+  R content1_missing_content1-untracked
+  R content1_missing_content3-untracked
+  R content1_missing_missing-untracked
+  $ hg status --all --change . 'glob:missing_content2_*'
+  A missing_content2_content2-tracked
+  A missing_content2_content3-tracked
+  A missing_content2_missing-tracked
+  $ hg status --all --change . 'glob:missing_missing_*'
+  A missing_missing_content3-tracked
+
+working directory should be all clean (with some missing/untracked files)
+
+  $ hg status --all 'glob:*_content?-tracked'
+  C content1_content1_content1-tracked
+  C content1_content1_content3-tracked
+  C content1_content2_content1-tracked
+  C content1_content2_content2-tracked
+  C content1_content2_content3-tracked
+  C content1_missing_content1-tracked
+  C content1_missing_content3-tracked
+  C missing_content2_content2-tracked
+  C missing_content2_content3-tracked
+  C missing_missing_content3-tracked
+  $ hg status --all 'glob:*_missing-tracked'
+  ! content1_content1_missing-tracked
+  ! content1_content2_missing-tracked
+  ! content1_missing_missing-tracked
+  ! missing_content2_missing-tracked
+  ! missing_missing_missing-tracked
+  $ hg status --all 'glob:*-untracked'
+  ? content1_content1_content1-untracked
+  ? content1_content1_content3-untracked
+  ? content1_content2_content1-untracked
+  ? content1_content2_content2-untracked
+  ? content1_content2_content3-untracked
+  ? content1_missing_content1-untracked
+  ? content1_missing_content3-untracked
+  ? missing_content2_content2-untracked
+  ? missing_content2_content3-untracked
+  ? missing_missing_content3-untracked
--- a/tests/test-blackbox.t	Tue Nov 21 13:50:25 2017 -0200
+++ b/tests/test-blackbox.t	Wed Nov 29 17:49:08 2017 -0500
@@ -19,7 +19,7 @@
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* (glob)
 
 alias expansion is logged
   $ rm ./.hg/blackbox.log
--- a/tests/test-bookmarks-pushpull.t	Tue Nov 21 13:50:25 2017 -0200
+++ b/tests/test-bookmarks-pushpull.t	Wed Nov 29 17:49:08 2017 -0500
@@ -175,7 +175,7 @@
      Z                         1:0d2164f0ce0d
 
   $ cd ../b
-  $ hg up --config
+  $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark foobar
   $ echo c2 > f2
--- a/tests/test-dispatch.t	Tue Nov 21 13:50:25 2017 -0200
+++ b/tests/test-dispatch.t	Wed Nov 29 17:49:08 2017 -0500
@@ -30,6 +30,89 @@
   (use 'hg cat -h' to show more help)
   [255]
 
+Missing parameter for early option:
+
+  $ hg log -R 2>&1 | grep 'hg log'
+  hg log: option -R requires argument
+  hg log [OPTION]... [FILE]
+  (use 'hg log -h' to show more help)
+
+  $ hg log -R -- 2>&1 | grep 'hg log'
+  hg log: option -R requires argument
+  hg log [OPTION]... [FILE]
+  (use 'hg log -h' to show more help)
+
+Parsing of early options should stop at "--":
+
+  $ hg cat -- --config=hooks.pre-cat=false
+  --config=hooks.pre-cat=false: no such file in rev cb9a9f314b8b
+  [1]
+  $ hg cat -- --debugger
+  --debugger: no such file in rev cb9a9f314b8b
+  [1]
+
+Unparsable form of early options:
+
+  $ hg cat --debugg
+  abort: option --debugger may not be abbreviated!
+  [255]
+
+Parsing failure of early options should be detected before executing the
+command:
+
+  $ hg log -b '--config=hooks.pre-log=false' default
+  abort: option --config may not be abbreviated!
+  [255]
+  $ hg log -b -R. default
+  abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
+  [255]
+  $ hg log --cwd .. -b --cwd=. default
+  abort: option --cwd may not be abbreviated!
+  [255]
+
+However, we can't prevent it from loading extensions and configs:
+
+  $ cat <<EOF > bad.py
+  > raise Exception('bad')
+  > EOF
+  $ hg log -b '--config=extensions.bad=bad.py' default
+  *** failed to import extension bad from bad.py: bad
+  abort: option --config may not be abbreviated!
+  [255]
+
+  $ mkdir -p badrepo/.hg
+  $ echo 'invalid-syntax' > badrepo/.hg/hgrc
+  $ hg log -b -Rbadrepo default
+  hg: parse error at badrepo/.hg/hgrc:1: invalid-syntax
+  [255]
+
+  $ hg log -b --cwd=inexistent default
+  abort: No such file or directory: 'inexistent'
+  [255]
+
+  $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
+  Traceback (most recent call last):
+  $ hg log -b '--config=profiling.enabled=yes' 2>&1 | grep -i sample
+  Sample count: .*|No samples recorded\. (re)
+
+Early options can't be specified in [aliases] and [defaults] because they are
+applied before the command name is resolved:
+
+  $ hg log -b '--config=alias.log=log --config=hooks.pre-log=false'
+  hg log: option -b not recognized
+  error in definition for alias 'log': --config may only be given on the command
+  line
+  [255]
+
+  $ hg log -b '--config=defaults.log=--config=hooks.pre-log=false'
+  abort: option --config may not be abbreviated!
+  [255]
+
+Shell aliases bypass any command parsing rules but for the early one:
+
+  $ hg log -b '--config=alias.log=!echo howdy'
+  howdy
+
 [defaults]
 
   $ hg cat a
--- a/tests/test-lfconvert.t	Tue Nov 21 13:50:25 2017 -0200
+++ b/tests/test-lfconvert.t	Wed Nov 29 17:49:08 2017 -0500
@@ -233,9 +233,10 @@
   $ cd ..
 
 round-trip: converting back to a normal (non-largefiles) repo with
-"lfconvert --to-normal" should give the same as ../bigfile-repo
+"lfconvert --to-normal" should give the same as ../bigfile-repo.  The
+convert extension is disabled to show config items can be loaded without it.
   $ cd largefiles-repo
-  $ hg lfconvert --to-normal . ../normal-repo
+  $ hg --config extensions.convert=! lfconvert --to-normal . ../normal-repo
   initializing destination ../normal-repo
   0 additional largefiles cached
   scanning source...
--- a/tests/test-merge-subrepos.t	Tue Nov 21 13:50:25 2017 -0200
+++ b/tests/test-merge-subrepos.t	Wed Nov 29 17:49:08 2017 -0500
@@ -61,7 +61,7 @@
   9bfe45a197d7+ tip
   $ cat .hg/blackbox.log
   * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> serve --cmdserver chgunix * (glob) (chg !)
-  * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id (glob)
+  * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id --config *extensions.blackbox=* --config *blackbox.dirty=True* (glob)
   * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id --config *extensions.blackbox=* --config *blackbox.dirty=True* exited 0 * (glob)
 
 TODO: a deleted file should be listed as such, like the top level repo
--- a/tests/test-setdiscovery.t	Tue Nov 21 13:50:25 2017 -0200
+++ b/tests/test-setdiscovery.t	Wed Nov 29 17:49:08 2017 -0500
@@ -17,7 +17,7 @@
   >     hg -R a debugdiscovery b --verbose --debug --config progress.debug=true
   >     echo
   >     echo "% -- b -> a tree"
-  >     hg -R b debugdiscovery a --verbose --old --config
+  >     hg -R b debugdiscovery a --verbose --old
   >     echo
   >     echo "% -- b -> a set"
   >     hg -R b debugdiscovery a --verbose --debug --config progress.debug=true
@@ -406,8 +406,8 @@
   101 102 103 104 105 106 107 108 109 110  (no-eol)
   $ hg -R r1 --config extensions.blackbox= blackbox
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> serve --cmdserver chgunix * (glob) (chg !)
-  * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> outgoing r2 *-T{rev} * (glob)
+  * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 outgoing r2 *-T{rev} * --config *extensions.blackbox=* (glob)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> found 101 common and 1 unknown server heads, 2 roundtrips in *.????s (glob)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 outgoing r2 *-T{rev} * --config *extensions.blackbox=* exited 0 after *.?? seconds (glob)
-  * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> blackbox (glob)
+  * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 --config *extensions.blackbox=* blackbox (glob)
   $ cd ..