diff hgext/git/manifest.py @ 52521:48cdbd4d5443

typing: align the signatures of `repository.imanifestdict` overrides This is the same procedure as 048c11993d6a, where we bounce around to the various subclasses and steal their type hints to keep everything happy when the interface is explicitly subclassed.
author Matt Harbison <matt_harbison@yahoo.com>
date Wed, 23 Oct 2024 15:56:48 -0400
parents c855943e334b
children db6efd74cf14
line wrap: on
line diff
--- a/hgext/git/manifest.py	Thu Oct 24 22:24:46 2024 -0400
+++ b/hgext/git/manifest.py	Wed Oct 23 15:56:48 2024 -0400
@@ -1,5 +1,13 @@
 from __future__ import annotations
 
+import typing
+
+from typing import (
+    Any,
+    Iterator,
+    Set,
+)
+
 from mercurial import (
     match as matchmod,
     pathutil,
@@ -12,6 +20,10 @@
 )
 from . import gitutil
 
+if typing.TYPE_CHECKING:
+    from typing import (
+        ByteString,  # TODO: change to Buffer for 3.14
+    )
 
 pygit2 = gitutil.get_pygit2()
 
@@ -71,16 +83,16 @@
             raise ValueError('unsupported mode %s' % oct(ent.filemode))
         return ent.id.raw, flags
 
-    def __getitem__(self, path):
+    def __getitem__(self, path: bytes) -> bytes:
         return self._resolve_entry(path)[0]
 
-    def find(self, path):
+    def find(self, path: bytes) -> tuple[bytes, bytes]:
         return self._resolve_entry(path)
 
-    def __len__(self):
+    def __len__(self) -> int:
         return len(list(self.walk(matchmod.always())))
 
-    def __nonzero__(self):
+    def __nonzero__(self) -> bool:
         try:
             next(iter(self))
             return True
@@ -89,30 +101,30 @@
 
     __bool__ = __nonzero__
 
-    def __contains__(self, path):
+    def __contains__(self, path: bytes) -> bool:
         try:
             self._resolve_entry(path)
             return True
         except KeyError:
             return False
 
-    def iterkeys(self):
+    def iterkeys(self) -> Iterator[bytes]:
         return self.walk(matchmod.always())
 
-    def keys(self):
+    def keys(self) -> list[bytes]:
         return list(self.iterkeys())
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[bytes]:
         return self.iterkeys()
 
-    def __setitem__(self, path, node):
+    def __setitem__(self, path: bytes, node: bytes) -> None:
         self._pending_changes[path] = node, self.flags(path)
 
-    def __delitem__(self, path):
+    def __delitem__(self, path: bytes) -> None:
         # TODO: should probably KeyError for already-deleted  files?
         self._pending_changes[path] = None
 
-    def filesnotin(self, other, match=None):
+    def filesnotin(self, other, match=None) -> Set[bytes]:
         if match is not None:
             match = matchmod.badmatch(match, lambda path, msg: None)
             sm2 = set(other.walk(match))
@@ -123,10 +135,18 @@
     def _dirs(self):
         return pathutil.dirs(self)
 
-    def hasdir(self, dir):
+    def hasdir(self, dir: bytes) -> bool:
         return dir in self._dirs
 
-    def diff(self, other, match=lambda x: True, clean=False):
+    def diff(
+        self,
+        other: Any,  # TODO: 'manifestdict' or (better) equivalent interface
+        match: Any = lambda x: True,  # TODO: Optional[matchmod.basematcher] = None,
+        clean: bool = False,
+    ) -> dict[
+        bytes,
+        tuple[tuple[bytes | None, bytes], tuple[bytes | None, bytes]] | None,
+    ]:
         """Finds changes between the current manifest and m2.
 
         The result is returned as a dict with filename as key and
@@ -200,42 +220,43 @@
 
         return result
 
-    def setflag(self, path, flag):
+    def setflag(self, path: bytes, flag: bytes) -> None:
         node, unused_flag = self._resolve_entry(path)
         self._pending_changes[path] = node, flag
 
-    def get(self, path, default=None):
+    def get(self, path: bytes, default=None) -> bytes | None:
         try:
             return self._resolve_entry(path)[0]
         except KeyError:
             return default
 
-    def flags(self, path):
+    def flags(self, path: bytes) -> bytes:
         try:
             return self._resolve_entry(path)[1]
         except KeyError:
             return b''
 
-    def copy(self):
+    def copy(self) -> 'gittreemanifest':
         return gittreemanifest(
             self._git_repo, self._tree, dict(self._pending_changes)
         )
 
-    def items(self):
+    def items(self) -> Iterator[tuple[bytes, bytes]]:
         for f in self:
             # TODO: build a proper iterator version of this
             yield f, self[f]
 
-    def iteritems(self):
+    def iteritems(self) -> Iterator[tuple[bytes, bytes]]:
         return self.items()
 
-    def iterentries(self):
+    def iterentries(self) -> Iterator[tuple[bytes, bytes, bytes]]:
         for f in self:
             # TODO: build a proper iterator version of this
             yield f, *self._resolve_entry(f)
 
-    def text(self):
-        assert False  # TODO can this method move out of the manifest iface?
+    def text(self) -> ByteString:
+        # TODO can this method move out of the manifest iface?
+        raise NotImplementedError
 
     def _walkonetree(self, tree, match, subdir):
         for te in tree:
@@ -249,7 +270,7 @@
             elif match(realname):
                 yield pycompat.fsencode(realname)
 
-    def walk(self, match):
+    def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
         # TODO: this is a very lazy way to merge in the pending
         # changes. There is absolutely room for optimization here by
         # being clever about walking over the sets...