comparison mercurial/cmdutil.py @ 50093:ce60c8d4ac87

typing: add type hints to argument checking functions in cmdutil These might be surprising, since they can take strings instead of bytes. The way `AnyStr` works is that it must be all bytes or all str for any given invocation. The wildcard here will be the `opts` that get passed in- if the type is unknown and defaults to `Any`, there's no enforcement that the dict key type matches the additional args. But a lot of uses should be using `**opts` from the command method, which has a str key. The uses of these methods in this module are now typed because their internals force a specific type, and it can't just be inferred from the caller.
author Matt Harbison <matt_harbison@yahoo.com>
date Tue, 14 Feb 2023 12:40:59 -0500
parents 237e9d2e1c71
children fef5bca96513
comparison
equal deleted inserted replaced
50091:a8d71a6ba205 50093:ce60c8d4ac87
8 8
9 import copy as copymod 9 import copy as copymod
10 import errno 10 import errno
11 import os 11 import os
12 import re 12 import re
13
14 from typing import (
15 Any,
16 AnyStr,
17 Dict,
18 Iterable,
19 Optional,
20 cast,
21 )
13 22
14 from .i18n import _ 23 from .i18n import _
15 from .node import ( 24 from .node import (
16 hex, 25 hex,
17 nullrev, 26 nullrev,
62 from .revlogutils import ( 71 from .revlogutils import (
63 constants as revlog_constants, 72 constants as revlog_constants,
64 ) 73 )
65 74
66 if pycompat.TYPE_CHECKING: 75 if pycompat.TYPE_CHECKING:
67 from typing import ( 76 from . import (
68 Any, 77 ui as uimod,
69 Dict,
70 ) 78 )
71
72 for t in (Any, Dict):
73 assert t
74 79
75 stringio = util.stringio 80 stringio = util.stringio
76 81
77 # templates of common command options 82 # templates of common command options
78 83
266 # special string such that everything below this line will be ingored in the 271 # special string such that everything below this line will be ingored in the
267 # editor text 272 # editor text
268 _linebelow = b"^HG: ------------------------ >8 ------------------------$" 273 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
269 274
270 275
271 def check_at_most_one_arg(opts, *args): 276 def check_at_most_one_arg(
277 opts: Dict[AnyStr, Any],
278 *args: AnyStr,
279 ) -> Optional[AnyStr]:
272 """abort if more than one of the arguments are in opts 280 """abort if more than one of the arguments are in opts
273 281
274 Returns the unique argument or None if none of them were specified. 282 Returns the unique argument or None if none of them were specified.
275 """ 283 """
276 284
277 def to_display(name): 285 def to_display(name: AnyStr) -> bytes:
278 return pycompat.sysbytes(name).replace(b'_', b'-') 286 return pycompat.sysbytes(name).replace(b'_', b'-')
279 287
280 previous = None 288 previous = None
281 for x in args: 289 for x in args:
282 if opts.get(x): 290 if opts.get(x):
287 ) 295 )
288 previous = x 296 previous = x
289 return previous 297 return previous
290 298
291 299
292 def check_incompatible_arguments(opts, first, others): 300 def check_incompatible_arguments(
301 opts: Dict[AnyStr, Any],
302 first: AnyStr,
303 others: Iterable[AnyStr],
304 ) -> None:
293 """abort if the first argument is given along with any of the others 305 """abort if the first argument is given along with any of the others
294 306
295 Unlike check_at_most_one_arg(), `others` are not mutually exclusive 307 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
296 among themselves, and they're passed as a single collection. 308 among themselves, and they're passed as a single collection.
297 """ 309 """
298 for other in others: 310 for other in others:
299 check_at_most_one_arg(opts, first, other) 311 check_at_most_one_arg(opts, first, other)
300 312
301 313
302 def resolve_commit_options(ui, opts): 314 def resolve_commit_options(ui: "uimod.ui", opts: Dict[str, Any]) -> bool:
303 """modify commit options dict to handle related options 315 """modify commit options dict to handle related options
304 316
305 The return value indicates that ``rewrite.update-timestamp`` is the reason 317 The return value indicates that ``rewrite.update-timestamp`` is the reason
306 the ``date`` option is set. 318 the ``date`` option is set.
307 """ 319 """
324 opts['user'] = ui.username() 336 opts['user'] = ui.username()
325 337
326 return datemaydiffer 338 return datemaydiffer
327 339
328 340
329 def check_note_size(opts): 341 def check_note_size(opts: Dict[str, Any]) -> None:
330 """make sure note is of valid format""" 342 """make sure note is of valid format"""
331 343
332 note = opts.get('note') 344 note = opts.get('note')
333 if not note: 345 if not note:
334 return 346 return
1112 ctx = repo[None] 1124 ctx = repo[None]
1113 for s in sorted(ctx.substate): 1125 for s in sorted(ctx.substate):
1114 ctx.sub(s).bailifchanged(hint=hint) 1126 ctx.sub(s).bailifchanged(hint=hint)
1115 1127
1116 1128
1117 def logmessage(ui, opts): 1129 def logmessage(ui: "uimod.ui", opts: Dict[bytes, Any]) -> Optional[bytes]:
1118 """get the log message according to -m and -l option""" 1130 """get the log message according to -m and -l option"""
1119 1131
1120 check_at_most_one_arg(opts, b'message', b'logfile') 1132 check_at_most_one_arg(opts, b'message', b'logfile')
1121 1133
1122 message = opts.get(b'message') 1134 message = cast(Optional[bytes], opts.get(b'message'))
1123 logfile = opts.get(b'logfile') 1135 logfile = opts.get(b'logfile')
1124 1136
1125 if not message and logfile: 1137 if not message and logfile:
1126 try: 1138 try:
1127 if isstdiofilename(logfile): 1139 if isstdiofilename(logfile):
1462 commands. 1474 commands.
1463 """ 1475 """
1464 return openstorage(repo, cmd, file_, opts, returnrevlog=True) 1476 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1465 1477
1466 1478
1467 def copy(ui, repo, pats, opts, rename=False): 1479 def copy(ui, repo, pats, opts: Dict[bytes, Any], rename=False):
1468 check_incompatible_arguments(opts, b'forget', [b'dry_run']) 1480 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1469 1481
1470 # called with the repo lock held 1482 # called with the repo lock held
1471 # 1483 #
1472 # hgsep => pathname that uses "/" to separate directories 1484 # hgsep => pathname that uses "/" to separate directories
2775 if not sub.cat( 2787 if not sub.cat(
2776 submatch, 2788 submatch,
2777 basefm, 2789 basefm,
2778 fntemplate, 2790 fntemplate,
2779 subprefix, 2791 subprefix,
2780 **pycompat.strkwargs(opts) 2792 **pycompat.strkwargs(opts),
2781 ): 2793 ):
2782 err = 0 2794 err = 0
2783 except error.RepoLookupError: 2795 except error.RepoLookupError:
2784 ui.status( 2796 ui.status(
2785 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath) 2797 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2929 return False 2941 return False
2930 else: 2942 else:
2931 return f not in ctx2.manifest() 2943 return f not in ctx2.manifest()
2932 2944
2933 2945
2934 def amend(ui, repo, old, extra, pats, opts): 2946 def amend(ui, repo, old, extra, pats, opts: Dict[str, Any]):
2935 # avoid cycle context -> subrepo -> cmdutil 2947 # avoid cycle context -> subrepo -> cmdutil
2936 from . import context 2948 from . import context
2937 2949
2938 # amend will reuse the existing user if not specified, but the obsolete 2950 # amend will reuse the existing user if not specified, but the obsolete
2939 # marker creation requires that the current user's name is specified. 2951 # marker creation requires that the current user's name is specified.