merge with stable
authorMatt Harbison <matt_harbison@yahoo.com>
Thu, 18 Mar 2021 18:24:59 -0400
changeset 46794 e2f7b2695ba1
parent 46782 6b52cffd8d0a (current diff)
parent 46793 86b47ec1960a (diff)
child 46795 048beb0167a7
merge with stable
mercurial/branchmap.py
mercurial/changegroup.py
mercurial/cmdutil.py
mercurial/commands.py
mercurial/commit.py
mercurial/context.py
mercurial/debugcommands.py
mercurial/dirstate.py
mercurial/error.py
mercurial/hg.py
mercurial/localrepo.py
mercurial/logcmdutil.py
mercurial/merge.py
mercurial/revlogutils/nodemap.py
mercurial/subrepo.py
mercurial/upgrade_utils/engine.py
tests/test-rebase-conflicts.t
--- a/mercurial/branchmap.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/branchmap.py	Thu Mar 18 18:24:59 2021 -0400
@@ -39,6 +39,7 @@
         Tuple,
         Union,
     )
+    from . import localrepo
 
     assert any(
         (
@@ -51,6 +52,7 @@
             Set,
             Tuple,
             Union,
+            localrepo,
         )
     )
 
@@ -193,7 +195,7 @@
         closednodes=None,
         hasnode=None,
     ):
-        # type: (Union[Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]], bytes,  int, Optional[bytes], Optional[Set[bytes]], Optional[Callable[[bytes], bool]]) -> None
+        # type: (localrepo.localrepository, Union[Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]], bytes,  int, Optional[bytes], Optional[Set[bytes]], Optional[Callable[[bytes], bool]]) -> None
         """hasnode is a function which can be used to verify whether changelog
         has a given node or not. If it's not provided, we assume that every node
         we have exists in changelog"""
@@ -303,9 +305,7 @@
                     msg
                     % (
                         _branchcachedesc(repo),
-                        pycompat.bytestr(
-                            inst
-                        ),  # pytype: disable=wrong-arg-types
+                        stringutil.forcebytestr(inst),
                     )
                 )
             bcache = None
--- a/mercurial/cffi/bdiff.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/cffi/bdiff.py	Thu Mar 18 18:24:59 2021 -0400
@@ -10,7 +10,7 @@
 import struct
 
 from ..pure.bdiff import *
-from . import _bdiff
+from . import _bdiff  # pytype: disable=import-error
 
 ffi = _bdiff.ffi
 lib = _bdiff.lib
--- a/mercurial/cffi/mpatch.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/cffi/mpatch.py	Thu Mar 18 18:24:59 2021 -0400
@@ -9,7 +9,7 @@
 
 from ..pure.mpatch import *
 from ..pure.mpatch import mpatchError  # silence pyflakes
-from . import _mpatch
+from . import _mpatch  # pytype: disable=import-error
 
 ffi = _mpatch.ffi
 lib = _mpatch.lib
--- a/mercurial/cffi/osutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/cffi/osutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -15,7 +15,7 @@
 from .. import pycompat
 
 if pycompat.isdarwin:
-    from . import _osutil
+    from . import _osutil  # pytype: disable=import-error
 
     ffi = _osutil.ffi
     lib = _osutil.lib
--- a/mercurial/changegroup.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/changegroup.py	Thu Mar 18 18:24:59 2021 -0400
@@ -910,7 +910,7 @@
 
     configtarget = repo.ui.config(b'devel', b'bundle.delta')
     if configtarget not in (b'', b'p1', b'full'):
-        msg = _("""config "devel.bundle.delta" as unknown value: %s""")
+        msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
         repo.ui.warn(msg % configtarget)
 
     deltamode = repository.CG_DELTAMODE_STD
@@ -1311,9 +1311,10 @@
         def makelookupmflinknode(tree, nodes):
             if fastpathlinkrev:
                 assert not tree
-                return (
-                    manifests.__getitem__
-                )  # pytype: disable=unsupported-operands
+
+                # pytype: disable=unsupported-operands
+                return manifests.__getitem__
+                # pytype: enable=unsupported-operands
 
             def lookupmflinknode(x):
                 """Callback for looking up the linknode for manifests.
--- a/mercurial/cmdutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/cmdutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -869,7 +869,7 @@
             )
             msg = (
                 _(
-                    '''Unresolved merge conflicts:
+                    b'''Unresolved merge conflicts:
 
 %s
 
--- a/mercurial/commands.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/commands.py	Thu Mar 18 18:24:59 2021 -0400
@@ -1083,7 +1083,7 @@
         if rev:
             if not nodes:
                 raise error.Abort(_(b'empty revision set'))
-            node = repo[nodes.last()].node()
+            node = repo[nodes[-1]].node()
         with hbisect.restore_state(repo, state, node):
             while changesets:
                 # update state
@@ -3452,7 +3452,8 @@
         regexp = util.re.compile(pattern, reflags)
     except re.error as inst:
         ui.warn(
-            _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
+            _(b"grep: invalid match pattern: %s\n")
+            % stringutil.forcebytestr(inst)
         )
         return 1
     sep, eol = b':', b'\n'
--- a/mercurial/commit.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/commit.py	Thu Mar 18 18:24:59 2021 -0400
@@ -359,19 +359,15 @@
     elif fparent1 == nullid:
         fparent1, fparent2 = fparent2, nullid
     elif fparent2 != nullid:
-        # is one parent an ancestor of the other?
-        fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
-        if fparent1 in fparentancestors:
+        if ms.active() and ms.extras(fname).get(b'filenode-source') == b'other':
             fparent1, fparent2 = fparent2, nullid
-        elif fparent2 in fparentancestors:
-            fparent2 = nullid
-        elif not fparentancestors:
-            # TODO: this whole if-else might be simplified much more
-            if (
-                ms.active()
-                and ms.extras(fname).get(b'filenode-source') == b'other'
-            ):
+        # is one parent an ancestor of the other?
+        else:
+            fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
+            if fparent1 in fparentancestors:
                 fparent1, fparent2 = fparent2, nullid
+            elif fparent2 in fparentancestors:
+                fparent2 = nullid
 
     force_new_node = False
     # The file might have been deleted by merge code and user explicitly choose
--- a/mercurial/context.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/context.py	Thu Mar 18 18:24:59 2021 -0400
@@ -993,8 +993,10 @@
                 # if file data starts with '\1\n', empty metadata block is
                 # prepended, which adds 4 bytes to filelog.size().
                 return self._filelog.cmp(self._filenode, fctx.data())
-        if self.size() == fctx.size():
+        if self.size() == fctx.size() or self.flags() == b'l':
             # size() matches: need to compare content
+            # issue6456: Always compare symlinks because size can represent
+            # encrypted string for EXT-4 encryption(fscrypt).
             return self._filelog.cmp(self._filenode, fctx.data())
 
         # size() differs
--- a/mercurial/crecord.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/crecord.py	Thu Mar 18 18:24:59 2021 -0400
@@ -34,14 +34,14 @@
 
 # patch comments based on the git one
 diffhelptext = _(
-    """# To remove '-' lines, make them ' ' lines (context).
+    b"""# To remove '-' lines, make them ' ' lines (context).
 # To remove '+' lines, delete them.
 # Lines starting with # will be removed from the patch.
 """
 )
 
 hunkhelptext = _(
-    """#
+    b"""#
 # If the patch applies cleanly, the edited hunk will immediately be
 # added to the record list. If it does not apply cleanly, a rejects file
 # will be generated. You can use that when you try again. If all lines
@@ -51,7 +51,7 @@
 )
 
 patchhelptext = _(
-    """#
+    b"""#
 # If the patch applies cleanly, the edited patch will immediately
 # be finalised. If it does not apply cleanly, rejects files will be
 # generated. You can use those when you try again.
@@ -64,7 +64,7 @@
 
     curses.error
 except (ImportError, AttributeError):
-    curses = False
+    curses = None
 
 
 class fallbackerror(error.Abort):
@@ -611,7 +611,8 @@
 
     chunkselector.stdscr = dummystdscr()
     if testfn and os.path.exists(testfn):
-        testf = open(testfn, 'r')
+        testf = open(testfn, b'r')
+        # TODO: open in binary mode?
         testcommands = [x.rstrip('\n') for x in testf.readlines()]
         testf.close()
         while True:
@@ -1151,7 +1152,7 @@
             numtrailingspaces = origlen - strippedlen
 
         if towin:
-            window.addstr(text, colorpair)
+            window.addstr(encoding.strfromlocal(text), colorpair)
         t += text
 
         if showwhtspc:
@@ -1621,7 +1622,7 @@
     def helpwindow(self):
         """print a help window to the screen.  exit after any keypress."""
         helptext = _(
-            """            [press any key to return to the patch-display]
+            b"""            [press any key to return to the patch-display]
 
 The curses hunk selector allows you to interactively choose among the
 changes you have made, and confirm only those changes you select for
@@ -1745,7 +1746,7 @@
         """ask for 'y' to be pressed to confirm selected. return True if
         confirmed."""
         confirmtext = _(
-            """If you answer yes to the following, your currently chosen patch chunks
+            b"""If you answer yes to the following, your currently chosen patch chunks
 will be loaded into an editor. To modify the patch, make the changes in your
 editor and save. To accept the current patch as-is, close the editor without
 saving.
--- a/mercurial/dagparser.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/dagparser.py	Thu Mar 18 18:24:59 2021 -0400
@@ -168,9 +168,9 @@
     if not desc:
         return
 
-    wordchars = pycompat.bytestr(
-        string.ascii_letters + string.digits
-    )  # pytype: disable=wrong-arg-types
+    # pytype: disable=wrong-arg-types
+    wordchars = pycompat.bytestr(string.ascii_letters + string.digits)
+    # pytype: enable=wrong-arg-types
 
     labels = {}
     p1 = -1
@@ -179,9 +179,9 @@
     def resolve(ref):
         if not ref:
             return p1
-        elif ref[0] in pycompat.bytestr(
-            string.digits
-        ):  # pytype: disable=wrong-arg-types
+        # pytype: disable=wrong-arg-types
+        elif ref[0] in pycompat.bytestr(string.digits):
+            # pytype: enable=wrong-arg-types
             return r - int(ref)
         else:
             return labels[ref]
@@ -215,9 +215,9 @@
 
     c = nextch()
     while c != b'\0':
-        while c in pycompat.bytestr(
-            string.whitespace
-        ):  # pytype: disable=wrong-arg-types
+        # pytype: disable=wrong-arg-types
+        while c in pycompat.bytestr(string.whitespace):
+            # pytype: enable=wrong-arg-types
             c = nextch()
         if c == b'.':
             yield b'n', (r, [p1])
@@ -225,9 +225,9 @@
             r += 1
             c = nextch()
         elif c == b'+':
-            c, digs = nextrun(
-                nextch(), pycompat.bytestr(string.digits)
-            )  # pytype: disable=wrong-arg-types
+            # pytype: disable=wrong-arg-types
+            c, digs = nextrun(nextch(), pycompat.bytestr(string.digits))
+            # pytype: enable=wrong-arg-types
             n = int(digs)
             for i in pycompat.xrange(0, n):
                 yield b'n', (r, [p1])
--- a/mercurial/debugcommands.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/debugcommands.py	Thu Mar 18 18:24:59 2021 -0400
@@ -1702,7 +1702,7 @@
     )
 
     try:
-        from . import rustext
+        from . import rustext  # pytype: disable=import-error
 
         rustext.__doc__  # trigger lazy import
     except ImportError:
@@ -2121,7 +2121,9 @@
                 try:
                     manifest = m[store.lookup(n)]
                 except error.LookupError as e:
-                    raise error.Abort(e, hint=b"Check your manifest node id")
+                    raise error.Abort(
+                        bytes(e), hint=b"Check your manifest node id"
+                    )
                 manifest.read()  # stores revisision in cache too
             return
 
@@ -2456,7 +2458,7 @@
                 tr.close()
             except ValueError as exc:
                 raise error.Abort(
-                    _(b'bad obsmarker input: %s') % pycompat.bytestr(exc)
+                    _(b'bad obsmarker input: %s') % stringutil.forcebytestr(exc)
                 )
             finally:
                 tr.release()
--- a/mercurial/dirstate.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/dirstate.py	Thu Mar 18 18:24:59 2021 -0400
@@ -1284,7 +1284,12 @@
                     or size == -2  # other parent
                     or fn in copymap
                 ):
-                    madd(fn)
+                    if stat.S_ISLNK(st.st_mode) and size != st.st_size:
+                        # issue6456: Size returned may be longer due to
+                        # encryption on EXT-4 fscrypt, undecided.
+                        ladd(fn)
+                    else:
+                        madd(fn)
                 elif (
                     time != st[stat.ST_MTIME]
                     and time != st[stat.ST_MTIME] & _rangemask
--- a/mercurial/dispatch.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/dispatch.py	Thu Mar 18 18:24:59 2021 -0400
@@ -173,7 +173,9 @@
                 "line_buffering": sys.stdout.line_buffering,
             }
             if util.safehasattr(sys.stdout, "write_through"):
+                # pytype: disable=attribute-error
                 kwargs["write_through"] = sys.stdout.write_through
+                # pytype: enable=attribute-error
             sys.stdout = io.TextIOWrapper(
                 sys.stdout.buffer,
                 sys.stdout.encoding,
@@ -187,7 +189,9 @@
                 "line_buffering": sys.stderr.line_buffering,
             }
             if util.safehasattr(sys.stderr, "write_through"):
+                # pytype: disable=attribute-error
                 kwargs["write_through"] = sys.stderr.write_through
+                # pytype: enable=attribute-error
             sys.stderr = io.TextIOWrapper(
                 sys.stderr.buffer,
                 sys.stderr.encoding,
--- a/mercurial/error.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/error.py	Thu Mar 18 18:24:59 2021 -0400
@@ -18,6 +18,11 @@
 # Do not import anything but pycompat here, please
 from . import pycompat
 
+if pycompat.TYPE_CHECKING:
+    from typing import (
+        Optional,
+    )
+
 
 def _tobytes(exc):
     """Byte-stringify exception in the same way as BaseException_str()"""
@@ -170,6 +175,7 @@
     """Raised if a command needs to print an error and exit."""
 
     def __init__(self, message, hint=None):
+        # type: (bytes, Optional[bytes]) -> None
         self.message = message
         self.hint = hint
         # Pass the message into the Exception constructor to help extensions
--- a/mercurial/extensions.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/extensions.py	Thu Mar 18 18:24:59 2021 -0400
@@ -912,6 +912,7 @@
     exts = {}
     for ename, ext in extensions():
         doc = gettext(ext.__doc__) or _(b'(no help text available)')
+        assert doc is not None  # help pytype
         if shortname:
             ename = ename.split(b'.')[-1]
         exts[ename] = doc.splitlines()[0].strip()
--- a/mercurial/hg.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/hg.py	Thu Mar 18 18:24:59 2021 -0400
@@ -41,7 +41,6 @@
     mergestate as mergestatemod,
     narrowspec,
     phases,
-    pycompat,
     requirements,
     scmutil,
     sshpeer,
@@ -53,7 +52,11 @@
     verify as verifymod,
     vfs as vfsmod,
 )
-from .utils import hashutil
+from .utils import (
+    hashutil,
+    stringutil,
+)
+
 
 release = lock.release
 
@@ -74,7 +77,7 @@
     # Python 2 raises TypeError, Python 3 ValueError.
     except (TypeError, ValueError) as e:
         raise error.Abort(
-            _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
+            _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
         )
     except OSError:
         isfile = False
--- a/mercurial/hgweb/webcommands.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/hgweb/webcommands.py	Thu Mar 18 18:24:59 2021 -0400
@@ -1156,6 +1156,7 @@
 
     linerange = None
     if lrange is not None:
+        assert lrange is not None  # help pytype (!?)
         linerange = webutil.formatlinerange(*lrange)
         # deactivate numeric nav links when linerange is specified as this
         # would required a dedicated "revnav" class
--- a/mercurial/hgweb/wsgicgi.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/hgweb/wsgicgi.py	Thu Mar 18 18:24:59 2021 -0400
@@ -25,7 +25,7 @@
     procutil.setbinary(procutil.stdout)
 
     environ = dict(pycompat.iteritems(os.environ))  # re-exports
-    environ.setdefault('PATH_INFO', b'')
+    environ.setdefault('PATH_INFO', '')
     if environ.get('SERVER_SOFTWARE', '').startswith('Microsoft-IIS'):
         # IIS includes script_name in PATH_INFO
         scriptname = environ['SCRIPT_NAME']
--- a/mercurial/localrepo.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/localrepo.py	Thu Mar 18 18:24:59 2021 -0400
@@ -2336,6 +2336,7 @@
 
             def tracktags(tr2):
                 repo = reporef()
+                assert repo is not None  # help pytype
                 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
                 newheads = repo.changelog.headrevs()
                 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
@@ -2372,6 +2373,7 @@
             # gating.
             tracktags(tr2)
             repo = reporef()
+            assert repo is not None  # help pytype
 
             singleheadopt = (b'experimental', b'single-head-per-branch')
             singlehead = repo.ui.configbool(*singleheadopt)
@@ -2475,6 +2477,8 @@
 
             def hookfunc(unused_success):
                 repo = reporef()
+                assert repo is not None  # help pytype
+
                 if hook.hashook(repo.ui, b'txnclose-bookmark'):
                     bmchanges = sorted(tr.changes[b'bookmarks'].items())
                     for name, (old, new) in bmchanges:
@@ -2506,7 +2510,9 @@
                     b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
                 )
 
-            reporef()._afterlock(hookfunc)
+            repo = reporef()
+            assert repo is not None  # help pytype
+            repo._afterlock(hookfunc)
 
         tr.addfinalize(b'txnclose-hook', txnclosehook)
         # Include a leading "-" to make it happen before the transaction summary
@@ -2517,7 +2523,9 @@
 
         def txnaborthook(tr2):
             """To be run if transaction is aborted"""
-            reporef().hook(
+            repo = reporef()
+            assert repo is not None  # help pytype
+            repo.hook(
                 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
             )
 
@@ -2700,6 +2708,7 @@
 
         def updater(tr):
             repo = reporef()
+            assert repo is not None  # help pytype
             repo.updatecaches(tr)
 
         return updater
@@ -2915,7 +2924,7 @@
 
         If both 'lock' and 'wlock' must be acquired, ensure you always acquires
         'wlock' first to avoid a dead-lock hazard."""
-        l = self._wlockref and self._wlockref()
+        l = self._wlockref() if self._wlockref else None
         if l is not None and l.held:
             l.lock()
             return l
--- a/mercurial/logcmdutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/logcmdutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -52,6 +52,7 @@
         Dict,
         List,
         Optional,
+        Sequence,
         Tuple,
     )
 
@@ -754,7 +755,7 @@
 
 
 def parseopts(ui, pats, opts):
-    # type: (Any, List[bytes], Dict[bytes, Any]) -> walkopts
+    # type: (Any, Sequence[bytes], Dict[bytes, Any]) -> walkopts
     """Parse log command options into walkopts
 
     The returned walkopts will be passed in to getrevs() or makewalker().
--- a/mercurial/mail.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/mail.py	Thu Mar 18 18:24:59 2021 -0400
@@ -165,7 +165,7 @@
         try:
             s.login(username, password)
         except smtplib.SMTPException as inst:
-            raise error.Abort(inst)
+            raise error.Abort(stringutil.forcebytestr(inst))
 
     def send(sender, recipients, msg):
         try:
--- a/mercurial/merge.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/merge.py	Thu Mar 18 18:24:59 2021 -0400
@@ -20,6 +20,7 @@
     nullrev,
 )
 from .thirdparty import attr
+from .utils import stringutil
 from . import (
     copies,
     encoding,
@@ -1343,7 +1344,7 @@
         except OSError as inst:
             repo.ui.warn(
                 _(b"update failed to remove %s: %s!\n")
-                % (f, pycompat.bytestr(inst.strerror))
+                % (f, stringutil.forcebytestr(inst.strerror))
             )
         if i == 100:
             yield i, f
--- a/mercurial/obsutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/obsutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -782,7 +782,7 @@
     # closestsuccessors returns an empty list for pruned revisions, remap it
     # into a list containing an empty list for future processing
     if ssets == []:
-        ssets = [[]]
+        ssets = [_succs()]
 
     # Try to recover pruned markers
     succsmap = repo.obsstore.successors
--- a/mercurial/patch.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/patch.py	Thu Mar 18 18:24:59 2021 -0400
@@ -1210,7 +1210,7 @@
                 # Patch comment based on the Git one (based on comment at end of
                 # https://mercurial-scm.org/wiki/RecordExtension)
                 phelp = b'---' + _(
-                    """
+                    b"""
 To remove '-' lines, make them ' ' lines (context).
 To remove '+' lines, delete them.
 Lines starting with # will be removed from the patch.
--- a/mercurial/posix.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/posix.py	Thu Mar 18 18:24:59 2021 -0400
@@ -321,9 +321,10 @@
                     fullpath = os.path.join(cachedir, target)
                     open(fullpath, b'w').close()
                 except IOError as inst:
-                    if (
-                        inst[0] == errno.EACCES
-                    ):  # pytype: disable=unsupported-operands
+                    # pytype: disable=unsupported-operands
+                    if inst[0] == errno.EACCES:
+                        # pytype: enable=unsupported-operands
+
                         # If we can't write to cachedir, just pretend
                         # that the fs is readonly and by association
                         # that the fs won't support symlinks. This
--- a/mercurial/pure/osutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/pure/osutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -172,7 +172,7 @@
 else:
     import msvcrt
 
-    _kernel32 = ctypes.windll.kernel32
+    _kernel32 = ctypes.windll.kernel32  # pytype: disable=module-attr
 
     _DWORD = ctypes.c_ulong
     _LPCSTR = _LPSTR = ctypes.c_char_p
@@ -216,7 +216,7 @@
     _kernel32.CreateFileA.restype = _HANDLE
 
     def _raiseioerror(name):
-        err = ctypes.WinError()
+        err = ctypes.WinError()  # pytype: disable=module-attr
         raise IOError(
             err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
         )
@@ -271,7 +271,7 @@
             if fh == _INVALID_HANDLE_VALUE:
                 _raiseioerror(name)
 
-            fd = msvcrt.open_osfhandle(fh, flags)
+            fd = msvcrt.open_osfhandle(fh, flags)  # pytype: disable=module-attr
             if fd == -1:
                 _kernel32.CloseHandle(fh)
                 _raiseioerror(name)
--- a/mercurial/revlogutils/nodemap.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/revlogutils/nodemap.py	Thu Mar 18 18:24:59 2021 -0400
@@ -570,7 +570,7 @@
 def parse_data(data):
     """parse parse nodemap data into a nodemap Trie"""
     if (len(data) % S_BLOCK.size) != 0:
-        msg = "nodemap data size is not a multiple of block size (%d): %d"
+        msg = b"nodemap data size is not a multiple of block size (%d): %d"
         raise error.Abort(msg % (S_BLOCK.size, len(data)))
     if not data:
         return Block(), None
--- a/mercurial/sslutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/sslutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -269,7 +269,7 @@
 
     if b'SSLKEYLOGFILE' in encoding.environ:
         try:
-            import sslkeylog
+            import sslkeylog  # pytype: disable=import-error
 
             sslkeylog.set_keylog(
                 pycompat.fsdecode(encoding.environ[b'SSLKEYLOGFILE'])
@@ -543,7 +543,9 @@
     # Use the list of more secure ciphers if found in the ssl module.
     if util.safehasattr(ssl, b'_RESTRICTED_SERVER_CIPHERS'):
         sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
+        # pytype: disable=module-attr
         sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
+        # pytype: enable=module-attr
 
     if requireclientcert:
         sslcontext.verify_mode = ssl.CERT_REQUIRED
--- a/mercurial/subrepo.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/subrepo.py	Thu Mar 18 18:24:59 2021 -0400
@@ -1891,7 +1891,12 @@
             if info.issym():
                 data = info.linkname
             else:
-                data = tar.extractfile(info).read()
+                f = tar.extractfile(info)
+                if f:
+                    data = f.read()
+                else:
+                    self.ui.warn(_(b'skipping "%s" (unknown type)') % bname)
+                    continue
             archiver.addfile(prefix + bname, info.mode, info.issym(), data)
             total += 1
             progress.increment()
--- a/mercurial/testing/revlog.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/testing/revlog.py	Thu Mar 18 18:24:59 2021 -0400
@@ -24,7 +24,7 @@
 
 
 try:
-    from ..cext import parsers as cparsers
+    from ..cext import parsers as cparsers  # pytype: disable=import-error
 except ImportError:
     cparsers = None
 
--- a/mercurial/upgrade_utils/engine.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/upgrade_utils/engine.py	Thu Mar 18 18:24:59 2021 -0400
@@ -581,6 +581,7 @@
             # reference to its new location. So clean it up manually. Alternatively, we
             # could update srcrepo.svfs and other variables to point to the new
             # location. This is simpler.
+            assert backupvfs is not None  # help pytype
             backupvfs.unlink(b'store/lock')
 
     return backuppath
--- a/mercurial/urllibcompat.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/urllibcompat.py	Thu Mar 18 18:24:59 2021 -0400
@@ -148,6 +148,7 @@
 
 
 else:
+    # pytype: disable=import-error
     import BaseHTTPServer
     import CGIHTTPServer
     import SimpleHTTPServer
@@ -155,6 +156,8 @@
     import urllib
     import urlparse
 
+    # pytype: enable=import-error
+
     urlreq._registeraliases(
         urllib,
         (
--- a/mercurial/utils/hashutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/utils/hashutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -3,7 +3,7 @@
 import hashlib
 
 try:
-    from ..thirdparty import sha1dc
+    from ..thirdparty import sha1dc  # pytype: disable=import-error
 
     sha1 = sha1dc.sha1
 except (ImportError, AttributeError):
--- a/mercurial/utils/procutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/utils/procutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -152,8 +152,8 @@
 
     if pycompat.iswindows:
         # Work around Windows bugs.
-        stdout = platform.winstdout(stdout)
-        stderr = platform.winstdout(stderr)
+        stdout = platform.winstdout(stdout)  # pytype: disable=module-attr
+        stderr = platform.winstdout(stderr)  # pytype: disable=module-attr
     if isatty(stdout):
         # The standard library doesn't offer line-buffered binary streams.
         stdout = make_line_buffered(stdout)
@@ -164,8 +164,8 @@
     stderr = sys.stderr
     if pycompat.iswindows:
         # Work around Windows bugs.
-        stdout = platform.winstdout(stdout)
-        stderr = platform.winstdout(stderr)
+        stdout = platform.winstdout(stdout)  # pytype: disable=module-attr
+        stderr = platform.winstdout(stderr)  # pytype: disable=module-attr
     if isatty(stdout):
         if pycompat.iswindows:
             # The Windows C runtime library doesn't support line buffering.
--- a/mercurial/utils/resourceutil.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/utils/resourceutil.py	Thu Mar 18 18:24:59 2021 -0400
@@ -70,12 +70,14 @@
         )
 
     def is_resource(package, name):
-        return resources.is_resource(
+        return resources.is_resource(  # pytype: disable=module-attr
             pycompat.sysstr(package), encoding.strfromlocal(name)
         )
 
     def contents(package):
+        # pytype: disable=module-attr
         for r in resources.contents(pycompat.sysstr(package)):
+            # pytype: enable=module-attr
             yield encoding.strtolocal(r)
 
 
--- a/mercurial/verify.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/verify.py	Thu Mar 18 18:24:59 2021 -0400
@@ -14,6 +14,9 @@
     nullid,
     short,
 )
+from .utils import (
+    stringutil,
+)
 
 from . import (
     error,
@@ -81,7 +84,7 @@
 
     def _exc(self, linkrev, msg, inst, filename=None):
         """record exception raised during the verify process"""
-        fmsg = pycompat.bytestr(inst)
+        fmsg = stringutil.forcebytestr(inst)
         if not fmsg:
             fmsg = pycompat.byterepr(inst)
         self._err(linkrev, b"%s: %s" % (msg, fmsg), filename)
@@ -431,6 +434,7 @@
                 filenodes.setdefault(f, {}).update(onefilenodes)
 
         if not dir and subdirnodes:
+            assert subdirprogress is not None  # help pytype
             subdirprogress.complete()
             if self.warnorphanstorefiles:
                 for f in sorted(storefiles):
--- a/mercurial/win32.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/win32.py	Thu Mar 18 18:24:59 2021 -0400
@@ -20,10 +20,12 @@
     pycompat,
 )
 
+# pytype: disable=module-attr
 _kernel32 = ctypes.windll.kernel32
 _advapi32 = ctypes.windll.advapi32
 _user32 = ctypes.windll.user32
 _crypt32 = ctypes.windll.crypt32
+# pytype: enable=module-attr
 
 _BOOL = ctypes.c_long
 _WORD = ctypes.c_ushort
@@ -311,7 +313,9 @@
 _kernel32.GetCurrentProcessId.argtypes = []
 _kernel32.GetCurrentProcessId.restype = _DWORD
 
+# pytype: disable=module-attr
 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
+# pytype: enable=module-attr
 _kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL]
 _kernel32.SetConsoleCtrlHandler.restype = _BOOL
 
@@ -336,7 +340,9 @@
 _user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int]
 _user32.ShowWindow.restype = _BOOL
 
+# pytype: disable=module-attr
 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
+# pytype: enable=module-attr
 _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM]
 _user32.EnumWindows.restype = _BOOL
 
@@ -357,7 +363,7 @@
     code = _kernel32.GetLastError()
     if code > 0x7FFFFFFF:
         code -= 2 ** 32
-    err = ctypes.WinError(code=code)
+    err = ctypes.WinError(code=code)  # pytype: disable=module-attr
     raise OSError(
         err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
     )
@@ -466,7 +472,7 @@
 
 
 def peekpipe(pipe):
-    handle = msvcrt.get_osfhandle(pipe.fileno())
+    handle = msvcrt.get_osfhandle(pipe.fileno())  # pytype: disable=module-attr
     avail = _DWORD()
 
     if not _kernel32.PeekNamedPipe(
@@ -475,7 +481,7 @@
         err = _kernel32.GetLastError()
         if err == _ERROR_BROKEN_PIPE:
             return 0
-        raise ctypes.WinError(err)
+        raise ctypes.WinError(err)  # pytype: disable=module-attr
 
     return avail.value
 
@@ -506,10 +512,12 @@
     size = 600
     buf = ctypes.create_string_buffer(size + 1)
     len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
+    # pytype: disable=module-attr
     if len == 0:
         raise ctypes.WinError()  # Note: WinError is a function
     elif len == size:
         raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
+    # pytype: enable=module-attr
     return buf.value
 
 
@@ -528,7 +536,8 @@
     buf = ctypes.create_string_buffer(size)
 
     if not _kernel32.GetVolumePathNameA(realpath, ctypes.byref(buf), size):
-        raise ctypes.WinError()  # Note: WinError is a function
+        # Note: WinError is a function
+        raise ctypes.WinError()  # pytype: disable=module-attr
 
     return buf.value
 
@@ -558,7 +567,8 @@
     if not _kernel32.GetVolumeInformationA(
         volume, None, 0, None, None, None, ctypes.byref(name), size
     ):
-        raise ctypes.WinError()  # Note: WinError is a function
+        # Note: WinError is a function
+        raise ctypes.WinError()  # pytype: disable=module-attr
 
     return name.value
 
@@ -568,7 +578,7 @@
     size = _DWORD(300)
     buf = ctypes.create_string_buffer(size.value + 1)
     if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
-        raise ctypes.WinError()
+        raise ctypes.WinError()  # pytype: disable=module-attr
     return buf.value
 
 
@@ -589,7 +599,7 @@
     h = _SIGNAL_HANDLER(handler)
     _signalhandler.append(h)  # needed to prevent garbage collection
     if not _kernel32.SetConsoleCtrlHandler(h, True):
-        raise ctypes.WinError()
+        raise ctypes.WinError()  # pytype: disable=module-attr
 
 
 def hidewindow():
@@ -686,7 +696,7 @@
         ctypes.byref(pi),
     )
     if not res:
-        raise ctypes.WinError()
+        raise ctypes.WinError()  # pytype: disable=module-attr
 
     _kernel32.CloseHandle(pi.hProcess)
     _kernel32.CloseHandle(pi.hThread)
--- a/mercurial/wireprotoserver.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/wireprotoserver.py	Thu Mar 18 18:24:59 2021 -0400
@@ -24,6 +24,7 @@
 from .utils import (
     cborutil,
     compression,
+    stringutil,
 )
 
 stringio = util.stringio
@@ -233,10 +234,12 @@
     except hgwebcommon.ErrorResponse as e:
         for k, v in e.headers:
             res.headers[k] = v
-        res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
+        res.status = hgwebcommon.statusmessage(
+            e.code, stringutil.forcebytestr(e)
+        )
         # TODO This response body assumes the failed command was
         # "unbundle." That assumption is not always valid.
-        res.setbodybytes(b'0\n%s\n' % pycompat.bytestr(e))
+        res.setbodybytes(b'0\n%s\n' % stringutil.forcebytestr(e))
 
     return True
 
--- a/mercurial/wireprotov2server.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/wireprotov2server.py	Thu Mar 18 18:24:59 2021 -0400
@@ -88,7 +88,9 @@
     try:
         checkperm(rctx, req, b'pull' if permission == b'ro' else b'push')
     except hgwebcommon.ErrorResponse as e:
-        res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
+        res.status = hgwebcommon.statusmessage(
+            e.code, stringutil.forcebytestr(e)
+        )
         for k, v in e.headers:
             res.headers[k] = v
         res.setbodybytes(b'permission denied')
--- a/mercurial/worker.py	Tue Mar 02 00:05:22 2021 +0100
+++ b/mercurial/worker.py	Thu Mar 18 18:24:59 2021 -0400
@@ -104,7 +104,9 @@
 else:
 
     def ismainthread():
+        # pytype: disable=module-attr
         return isinstance(threading.current_thread(), threading._MainThread)
+        # pytype: enable=module-attr
 
     def _blockingreader(wrapped):
         return wrapped
--- a/tests/test-rebase-conflicts.t	Tue Mar 02 00:05:22 2021 +0100
+++ b/tests/test-rebase-conflicts.t	Thu Mar 18 18:24:59 2021 -0400
@@ -276,13 +276,13 @@
   committing manifest
   committing changelog
   updating the branch cache
-  rebased as 2a7f09cac94c
+  rebased as c1ffa3b5274e
   rebase status stored
   rebase merging completed
   update back to initial working directory parent
   resolving manifests
    branchmerge: False, force: False, partial: False
-   ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0
+   ancestor: c1ffa3b5274e, local: c1ffa3b5274e+, remote: d79e2059b5c0
    f1.txt: other deleted -> r
   removing f1.txt
    f2.txt: remote created -> g
@@ -300,7 +300,7 @@
   list of changesets:
   4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c
   19c888675e133ab5dff84516926a65672eaf04d9
-  2a7f09cac94c7f4b73ebd5cd1a62d3b2e8e336bf
+  c1ffa3b5274e92a9388fe782854e295d2e8d0443
   bundle2-output-bundle: "HG20", 3 parts total
   bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
   bundle2-output-part: "cache:rev-branch-cache" (advisory) streamed payload
@@ -311,7 +311,7 @@
   adding changesets
   add changeset 4c9fbe56a16f
   add changeset 19c888675e13
-  add changeset 2a7f09cac94c
+  add changeset c1ffa3b5274e
   adding manifests
   adding file changes
   adding f1.txt revisions