comparison mercurial/bundle2.py @ 51667:6fc31e7bd5db

typing: add some type hints for bundle2 capabilities Somewhere between hg 3dbc7b1ecaba and hg 8e3f6b5bf720, pytype determined the signature of `bundle20.capabilities` changed from `Dict[bytes, Tuple[bytes]]` to `Dict[bytes, Union[List[bytes], Tuple[bytes]]]`. First, I did try to simply be explicit about the previously inferred type, but it does seem to mix and match list/tuple now (e.g. in `writenewbundle()`). I tried changing the new list usage to tuple, but a couple of things complained, (and I think lists of one item are a little more clear to read anyway). So then I typed the dict value as `Sequence[bytes]`, which worked fine. But there's also a module level `capabilities` field, and when that's typed, pytype complains about `Sequence[bytes]` lacking `__add__`[1]. So I gave up, and just assigned it the type it wanted, with an alias. If somebody feels motivated to make the type consistent, it's simple enough to change the alias. The mutable default value to the constructor was removed to appease PyCharm's type checking on the field. (I didn't bother running the code through pytype prior to changing it, because we've previously made an effort to remove this pattern anyway.) I'm not sure why `getrepocaps()` has a default value for `role` that apparently raises an exception. It's just flagged for now so this series can land without risking additional problems. [1] https://foss.heptapod.net/mercurial/mercurial-devel/-/jobs/2466903
author Matt Harbison <matt_harbison@yahoo.com>
date Wed, 10 Jul 2024 17:09:34 -0400
parents a0f1378b932e
children 493034cc3265
comparison
equal deleted inserted replaced
51666:a09435c0eb14 51667:6fc31e7bd5db
151 import os 151 import os
152 import re 152 import re
153 import string 153 import string
154 import struct 154 import struct
155 import sys 155 import sys
156 import typing
156 157
157 from .i18n import _ 158 from .i18n import _
158 from .node import ( 159 from .node import (
159 hex, 160 hex,
160 short, 161 short,
178 from .utils import ( 179 from .utils import (
179 stringutil, 180 stringutil,
180 urlutil, 181 urlutil,
181 ) 182 )
182 from .interfaces import repository 183 from .interfaces import repository
184
185 if typing.TYPE_CHECKING:
186 from typing import (
187 Dict,
188 List,
189 Optional,
190 Tuple,
191 Union,
192 )
193
194 Capabilities = Dict[bytes, Union[List[bytes], Tuple[bytes, ...]]]
183 195
184 urlerr = util.urlerr 196 urlerr = util.urlerr
185 urlreq = util.urlreq 197 urlreq = util.urlreq
186 198
187 _pack = struct.pack 199 _pack = struct.pack
600 outpart.addparam( 612 outpart.addparam(
601 b'in-reply-to', pycompat.bytestr(part.id), mandatory=False 613 b'in-reply-to', pycompat.bytestr(part.id), mandatory=False
602 ) 614 )
603 615
604 616
605 def decodecaps(blob): 617 def decodecaps(blob: bytes) -> "Capabilities":
606 """decode a bundle2 caps bytes blob into a dictionary 618 """decode a bundle2 caps bytes blob into a dictionary
607 619
608 The blob is a list of capabilities (one per line) 620 The blob is a list of capabilities (one per line)
609 Capabilities may have values using a line of the form:: 621 Capabilities may have values using a line of the form::
610 622
660 populate it. Then call `getchunks` to retrieve all the binary chunks of 672 populate it. Then call `getchunks` to retrieve all the binary chunks of
661 data that compose the bundle2 container.""" 673 data that compose the bundle2 container."""
662 674
663 _magicstring = b'HG20' 675 _magicstring = b'HG20'
664 676
665 def __init__(self, ui, capabilities=()): 677 def __init__(self, ui, capabilities: "Optional[Capabilities]" = None):
678 if capabilities is None:
679 capabilities = {}
680
666 self.ui = ui 681 self.ui = ui
667 self._params = [] 682 self._params = []
668 self._parts = [] 683 self._parts = []
669 self.capabilities = dict(capabilities) 684 self.capabilities: "Capabilities" = dict(capabilities)
670 self._compengine = util.compengines.forbundletype(b'UN') 685 self._compengine = util.compengines.forbundletype(b'UN')
671 self._compopts = None 686 self._compopts = None
672 # If compression is being handled by a consumer of the raw 687 # If compression is being handled by a consumer of the raw
673 # data (e.g. the wire protocol), unsetting this flag tells 688 # data (e.g. the wire protocol), unsetting this flag tells
674 # consumers that the bundle is best left uncompressed. 689 # consumers that the bundle is best left uncompressed.
1610 return None 1625 return None
1611 1626
1612 1627
1613 # These are only the static capabilities. 1628 # These are only the static capabilities.
1614 # Check the 'getrepocaps' function for the rest. 1629 # Check the 'getrepocaps' function for the rest.
1615 capabilities = { 1630 capabilities: "Capabilities" = {
1616 b'HG20': (), 1631 b'HG20': (),
1617 b'bookmarks': (), 1632 b'bookmarks': (),
1618 b'error': (b'abort', b'unsupportedcontent', b'pushraced', b'pushkey'), 1633 b'error': (b'abort', b'unsupportedcontent', b'pushraced', b'pushkey'),
1619 b'listkeys': (), 1634 b'listkeys': (),
1620 b'pushkey': (), 1635 b'pushkey': (),
1624 b'phases': (b'heads',), 1639 b'phases': (b'heads',),
1625 b'stream': (b'v2',), 1640 b'stream': (b'v2',),
1626 } 1641 }
1627 1642
1628 1643
1629 def getrepocaps(repo, allowpushback=False, role=None): 1644 # TODO: drop the default value for 'role'
1645 def getrepocaps(repo, allowpushback: bool = False, role=None) -> "Capabilities":
1630 """return the bundle2 capabilities for a given repo 1646 """return the bundle2 capabilities for a given repo
1631 1647
1632 Exists to allow extensions (like evolution) to mutate the capabilities. 1648 Exists to allow extensions (like evolution) to mutate the capabilities.
1633 1649
1634 The returned value is used for servers advertising their capabilities as 1650 The returned value is used for servers advertising their capabilities as
1673 # for legacy clients. 1689 # for legacy clients.
1674 1690
1675 return caps 1691 return caps
1676 1692
1677 1693
1678 def bundle2caps(remote): 1694 def bundle2caps(remote) -> "Capabilities":
1679 """return the bundle capabilities of a peer as dict""" 1695 """return the bundle capabilities of a peer as dict"""
1680 raw = remote.capable(b'bundle2') 1696 raw = remote.capable(b'bundle2')
1681 if not raw and raw != b'': 1697 if not raw and raw != b'':
1682 return {} 1698 return {}
1683 capsblob = urlreq.unquote(remote.capable(b'bundle2')) 1699 capsblob = urlreq.unquote(remote.capable(b'bundle2'))
1684 return decodecaps(capsblob) 1700 return decodecaps(capsblob)
1685 1701
1686 1702
1687 def obsmarkersversion(caps): 1703 def obsmarkersversion(caps: "Capabilities"):
1688 """extract the list of supported obsmarkers versions from a bundle2caps dict""" 1704 """extract the list of supported obsmarkers versions from a bundle2caps dict"""
1689 obscaps = caps.get(b'obsmarkers', ()) 1705 obscaps = caps.get(b'obsmarkers', ())
1690 return [int(c[1:]) for c in obscaps if c.startswith(b'V')] 1706 return [int(c[1:]) for c in obscaps if c.startswith(b'V')]
1691 1707
1692 1708
1723 count = len(repo.revs(b'%ln and _internal()', outgoing.missing)) 1739 count = len(repo.revs(b'%ln and _internal()', outgoing.missing))
1724 msg = "backup bundle would contains %d internal changesets" 1740 msg = "backup bundle would contains %d internal changesets"
1725 msg %= count 1741 msg %= count
1726 raise error.ProgrammingError(msg) 1742 raise error.ProgrammingError(msg)
1727 1743
1728 caps = {} 1744 caps: "Capabilities" = {}
1729 if opts.get(b'obsolescence', False): 1745 if opts.get(b'obsolescence', False):
1730 caps[b'obsmarkers'] = (b'V1',) 1746 caps[b'obsmarkers'] = (b'V1',)
1731 stream_version = opts.get(b'stream', b"") 1747 stream_version = opts.get(b'stream', b"")
1732 if stream_version == b"v2": 1748 if stream_version == b"v2":
1733 caps[b'stream'] = [b'v2'] 1749 caps[b'stream'] = [b'v2']