checkout: Add/update type annotations

Change-Id: I5f51f469fdf5da9c2b5447ba0ecf962c34209cc5
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/194968
Reviewed-by: Ted Pudlik <tpudlik@google.com>
Pigweed-Auto-Submit: Rob Mohr <mohrr@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
diff --git a/recipe_modules/checkout/api.py b/recipe_modules/checkout/api.py
index 6320a40..52bfbaf 100644
--- a/recipe_modules/checkout/api.py
+++ b/recipe_modules/checkout/api.py
@@ -20,7 +20,7 @@
 import collections
 import contextlib
 import re
-from typing import Any, Optional
+from typing import Any, Sequence
 import urllib
 import xml.etree.ElementTree
 
@@ -65,7 +65,7 @@
     def __init__(self, url: str, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.url: str = url
-        self.https: Optional[str] = None
+        self.https: str | None = None
 
     def dict(self) -> dict[str, Any]:
         return to_dict(self)
@@ -77,9 +77,9 @@
 
     name: str
     fetch: Url
-    review: Optional[str] = None
-    revision: Optional[str] = None
-    alias: Optional[str] = None
+    review: str | None = None
+    revision: str | None = None
+    alias: str | None = None
 
     def dict(self) -> dict[str, Any]:
         res = to_dict(self)
@@ -96,7 +96,7 @@
     remote: str
     revision: str
     upstream: str
-    url: Optional[str] = None
+    url: str | None = None
 
     def path_object(self, root: config_types.Path) -> config_types.Path:
         return root.join(self.path)
@@ -105,13 +105,13 @@
         return to_dict(self)
 
 
-def _str_or_none(x: Optional[Any]) -> Optional[str]:
+def _str_or_none(x: Any | None) -> str | None:
     if x is None:
         return x
     return str(x)
 
 
-def _int_or_none(x: Optional[Any]) -> Optional[int]:
+def _int_or_none(x: Any | None) -> int | None:
     if x is None:
         return x
     return int(x)
@@ -123,19 +123,17 @@
 
     number: int = attrs.field(converter=int)
     bb_input: build_pb2.Build.Input = attrs.field(repr=False)
-    remote: Optional[str] = attrs.field(converter=_str_or_none)
-    ref: Optional[str] = attrs.field(converter=_str_or_none)
-    rebase: Optional[bool] = None
-    project: Optional[str] = None
-    branch: Optional[str] = attrs.field(converter=_str_or_none, default=None)
-    gerrit_name: Optional[str] = attrs.field(
-        converter=_str_or_none, default=None
-    )
+    remote: str | None = attrs.field(converter=_str_or_none)
+    ref: str | None = attrs.field(converter=_str_or_none)
+    rebase: bool | None = None
+    project: str | None = None
+    branch: str | None = attrs.field(converter=_str_or_none, default=None)
+    gerrit_name: str | None = attrs.field(converter=_str_or_none, default=None)
     submitted: bool = False
-    patchset: Optional[int] = attrs.field(converter=_int_or_none, default=None)
+    patchset: int | None = attrs.field(converter=_int_or_none, default=None)
     applied: bool = attrs.field(default=False, repr=False)
-    base: Optional[str] = attrs.field(converter=_str_or_none, default=None)
-    base_type: Optional[str] = attrs.field(converter=_str_or_none, default=None)
+    base: str | None = attrs.field(converter=_str_or_none, default=None)
+    base_type: str | None = attrs.field(converter=_str_or_none, default=None)
     is_merge: bool = attrs.field(default=False)
     commit_message: str = attrs.field(default='')
 
@@ -196,20 +194,18 @@
 class CheckoutContext:
     _api: recipe_api.RecipeApi = attrs.field(repr=False)
     options: Options = None
-    changes: Optional[list[Change]] = None  # List of triggering changes.
+    changes: list[Change] | None = None  # List of triggering changes.
     top: config_types.Path = None  # Actual checkout root.
     # Logical checkout root. Usually identical to 'top', but occasionally a
     # subdirectory instead.
     root: config_types.Path = None
     # Which triggering changes were applied or not applied.
-    status: Optional[StatusOfChanges] = None
+    status: StatusOfChanges | None = None
     # Remotes that should be treated identically.
-    equivalent_remotes: Optional[dict[str, list[str]]] = attrs.field(
-        factory=dict
-    )
-    manifest: Optional[Manifest] = None  # Parsed repo manifest.
+    equivalent_remotes: dict[str, list[str]] | None = attrs.field(factory=dict)
+    manifest: Manifest | None = None  # Parsed repo manifest.
     # Path to a JSON file containing metadata about the triggering changes.
-    changes_json: Optional[config_types.Path] = None
+    changes_json: config_types.Path | None = None
 
     # Current revision number.
     def revision(self) -> str:
@@ -325,7 +321,7 @@
 
     _REMOTE_REGEX = re.compile(r'^https://(?P<host>[^/]+)/(?P<project>.+)$')
 
-    def gerrit_host(self) -> Optional[str]:
+    def gerrit_host(self) -> str | None:
         match = self._REMOTE_REGEX.match(self.options.remote)
         if not match:
             return  # pragma: no cover
@@ -335,7 +331,7 @@
             gerrit_review_host = gerrit_review_host.replace('.', '-review.', 1)
         return gerrit_review_host
 
-    def gerrit_project(self) -> Optional[str]:
+    def gerrit_project(self) -> str | None:
         match = self._REMOTE_REGEX.match(self.options.remote)
         if not match:
             return  # pragma: no cover
@@ -685,7 +681,13 @@
 
             return tuple(results)
 
-    def _matching_branches(self, repo, branches, name='has branch', **kwargs):
+    def _matching_branches(
+        self,
+        repo: str,
+        branches: Sequence[str],
+        name: str = 'has branch',
+        **kwargs,
+    ):
         """Returns the subset of the given branches that exist on gitiles."""
         matches: set[str] = set()
         with self.m.step.nest(name), self.m.context(infra_steps=True):
@@ -776,8 +778,8 @@
                     )
 
                 # These remain unused if change.submitted is False.
-                remote: Optional[str] = None
-                remote_branch: Optional[str] = None
+                remote: str | None = None
+                remote_branch: str | None = None
 
                 with self.m.context(infra_steps=True):
                     # Change "https://foo.googlesource.com/bar"
@@ -868,7 +870,7 @@
         finally:
             pass
 
-    def _check_unapplied_changes(self, changes):
+    def _check_unapplied_changes(self, changes: Sequence[Change]):
         applied: list[Change] = []
         failed_to_apply: list[Change] = []
         if not changes:  # pragma: no cover
@@ -917,15 +919,15 @@
 
     def _cached_checkout(
         self,
-        remote,
-        path,
-        ref,
-        submodules,
-        included_submodules=None,
-        excluded_submodules=None,
-        submodule_timeout_sec=10 * 60,
-        cache=True,
-        use_packfiles=True,
+        remote: str,
+        path: config_types.Path,
+        ref: str,
+        submodules: bool,
+        included_submodules: Sequence[str] | None = None,
+        excluded_submodules: Sequence[str] | None = None,
+        submodule_timeout_sec: int = 10 * 60,
+        cache: bool = True,
+        use_packfiles: bool = True,
         **kwargs,
     ):
         submodule_paths = included_submodules = included_submodules or []
@@ -1072,7 +1074,7 @@
                     use_packfiles=use_packfiles,
                 )
 
-    def _git(self, ctx):
+    def _git(self, ctx: CheckoutContext):
         """Checkout code from git."""
 
         super_branch = self._matching_branch(ctx) or ctx.options.branch
@@ -1220,7 +1222,7 @@
                     # explain why got_revision is the value it is.
                     pres.properties['got_revision_type'] = got_revision_type
 
-    def _matching_branch(self, ctx):
+    def _matching_branch(self, ctx: CheckoutContext):
         """Return if there are manifest branches that match the triggering CLs.
 
         If the triggering change is on a branch name that is also present in the
@@ -1282,7 +1284,7 @@
         )
         return manifest_branch
 
-    def _repo(self, ctx):
+    def _repo(self, ctx: CheckoutContext):
         """Checkout code from an Android Repo Tool manifest.
 
         Args:
@@ -1390,7 +1392,7 @@
         if len(files) == 1:
             ctx.root = files.pop()
 
-    def _configure_insteadof(self, ctx):
+    def _configure_insteadof(self, ctx: CheckoutContext):
         """Configure git to use some urls in place of others."""
         if not ctx.options.rewrites:
             return
@@ -1408,7 +1410,7 @@
 
             self.m.git("rewrites", "config", "--get-regexp", "^url.*")
 
-    def _name(self, options):
+    def _name(self, options: Options):
         """Turn "https://foo/bar/baz.git" into "baz"."""
         name = options.remote.rstrip('/')
         if name.endswith('.git'):
@@ -1418,7 +1420,12 @@
             parts.pop(-1)
         return f'checkout {parts[-1]}'
 
-    def __call__(self, options, root=None, name=None):
+    def __call__(
+        self,
+        options: Options,
+        root: config_types.Path | None = None,
+        name: str = None,
+    ):
         """Checkout code."""
 
         checkout_name = name or self._name(options)
@@ -1485,7 +1492,12 @@
 
         return ctx
 
-    def get_revision(self, root, name='git log', test_data='HASH'):
+    def get_revision(
+        self,
+        root: config_types.Path,
+        name: str = 'git log',
+        test_data: str = 'HASH',
+    ):
         """Like self.revision, but works for secondary checkouts."""
         with self.m.context(cwd=root):
             step = self.m.git(