Mercurial > public > mercurial-scm > hg
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. |