diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cf81c382..5c4f145f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,3 +24,6 @@ jobs: - name: Check code style with ruff run: ruff format --diff + + - name: Check typing with mypy + run: LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 /bin/sh build.sh mypy diff --git a/build.sh b/build.sh index 1a415a23..4327c1ae 100644 --- a/build.sh +++ b/build.sh @@ -262,6 +262,16 @@ if [ "$1" = "test" ]; then $PREFIX/bin/pytest --cov=pygit2 fi +# Type checking +if [ "$1" = "mypy" ]; then + shift + if [ -n "$WHEELDIR" ]; then + $PREFIX/bin/pip install $WHEELDIR/pygit2*-$PYTHON_TAG-*.whl + fi + $PREFIX/bin/pip install -r requirements-test.txt + $PREFIX/bin/mypy pygit2 +fi + # Test .pyi stub file if [ "$1" = "stubtest" ]; then shift diff --git a/docs/objects.rst b/docs/objects.rst index 7d323a3a..9aed0282 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -33,7 +33,7 @@ implements a subset of the mapping interface. >>> repo = Repository('path/to/pygit2') >>> obj = repo.get("101715bf37440d32291bde4f58c3142bcf7d8adb") >>> obj - <_pygit2.Commit object at 0x7ff27a6b60f0> + .. method:: Repository.__getitem__(id) diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index 2cf8e39e..fc8470c4 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -1,9 +1,12 @@ from typing import Iterator, Literal, Optional, overload, Type, TypedDict -from io import IOBase +from io import IOBase, DEFAULT_BUFFER_SIZE +from queue import Queue +from threading import Event from . import Index from .enums import ( ApplyLocation, BranchType, + BlobFilter, DeltaStatus, DiffFind, DiffFlag, @@ -28,14 +31,251 @@ GIT_OBJ_BLOB = Literal[3] GIT_OBJ_COMMIT = Literal[1] GIT_OBJ_TAG = Literal[4] GIT_OBJ_TREE = Literal[2] -GIT_OID_HEXSZ: int -GIT_OID_HEX_ZERO: str -GIT_OID_MINPREFIXLEN: int -GIT_OID_RAWSZ: int -LIBGIT2_VERSION: str + LIBGIT2_VER_MAJOR: int LIBGIT2_VER_MINOR: int LIBGIT2_VER_REVISION: int +LIBGIT2_VERSION: str +GIT_OPT_GET_MWINDOW_SIZE: int +GIT_OPT_SET_MWINDOW_SIZE: int +GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: int +GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: int +GIT_OPT_GET_SEARCH_PATH: int +GIT_OPT_SET_SEARCH_PATH: int +GIT_OPT_SET_CACHE_OBJECT_LIMIT: int +GIT_OPT_SET_CACHE_MAX_SIZE: int +GIT_OPT_ENABLE_CACHING: int +GIT_OPT_GET_CACHED_MEMORY: int +GIT_OPT_GET_TEMPLATE_PATH: int +GIT_OPT_SET_TEMPLATE_PATH: int +GIT_OPT_SET_SSL_CERT_LOCATIONS: int +GIT_OPT_SET_USER_AGENT: int +GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: int +GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: int +GIT_OPT_SET_SSL_CIPHERS: int +GIT_OPT_GET_USER_AGENT: int +GIT_OPT_ENABLE_OFS_DELTA: int +GIT_OPT_ENABLE_FSYNC_GITDIR: int +GIT_OPT_GET_WINDOWS_SHAREMODE: int +GIT_OPT_SET_WINDOWS_SHAREMODE: int +GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: int +GIT_OPT_SET_ALLOCATOR: int +GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: int +GIT_OPT_GET_PACK_MAX_OBJECTS: int +GIT_OPT_SET_PACK_MAX_OBJECTS: int +GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: int +GIT_OPT_GET_OWNER_VALIDATION: int +GIT_OPT_SET_OWNER_VALIDATION: int +GIT_OPT_GET_MWINDOW_FILE_LIMIT: int +GIT_OPT_SET_MWINDOW_FILE_LIMIT: int +GIT_OID_RAWSZ: int +GIT_OID_HEXSZ: int +GIT_OID_HEX_ZERO: str +GIT_OID_MINPREFIXLEN: int +GIT_OBJECT_ANY: int +GIT_OBJECT_INVALID: int +GIT_OBJECT_COMMIT: int +GIT_OBJECT_TREE: int +GIT_OBJECT_BLOB: int +GIT_OBJECT_TAG: int +GIT_OBJECT_OFS_DELTA: int +GIT_OBJECT_REF_DELTA: int +GIT_FILEMODE_UNREADABLE: int +GIT_FILEMODE_TREE: int +GIT_FILEMODE_BLOB: int +GIT_FILEMODE_BLOB_EXECUTABLE: int +GIT_FILEMODE_LINK: int +GIT_FILEMODE_COMMIT: int +GIT_SORT_NONE: int +GIT_SORT_TOPOLOGICAL: int +GIT_SORT_TIME: int +GIT_SORT_REVERSE: int +GIT_RESET_SOFT: int +GIT_RESET_MIXED: int +GIT_RESET_HARD: int +GIT_REFERENCES_ALL: int +GIT_REFERENCES_BRANCHES: int +GIT_REFERENCES_TAGS: int +GIT_REVSPEC_SINGLE: int +GIT_REVSPEC_RANGE: int +GIT_REVSPEC_MERGE_BASE: int +GIT_BRANCH_LOCAL: int +GIT_BRANCH_REMOTE: int +GIT_BRANCH_ALL: int +GIT_STATUS_CURRENT: int +GIT_STATUS_INDEX_NEW: int +GIT_STATUS_INDEX_MODIFIED: int +GIT_STATUS_INDEX_DELETED: int +GIT_STATUS_INDEX_RENAMED: int +GIT_STATUS_INDEX_TYPECHANGE: int +GIT_STATUS_WT_NEW: int +GIT_STATUS_WT_MODIFIED: int +GIT_STATUS_WT_DELETED: int +GIT_STATUS_WT_TYPECHANGE: int +GIT_STATUS_WT_RENAMED: int +GIT_STATUS_WT_UNREADABLE: int +GIT_STATUS_IGNORED: int +GIT_STATUS_CONFLICTED: int +GIT_CHECKOUT_NONE: int +GIT_CHECKOUT_SAFE: int +GIT_CHECKOUT_FORCE: int +GIT_CHECKOUT_RECREATE_MISSING: int +GIT_CHECKOUT_ALLOW_CONFLICTS: int +GIT_CHECKOUT_REMOVE_UNTRACKED: int +GIT_CHECKOUT_REMOVE_IGNORED: int +GIT_CHECKOUT_UPDATE_ONLY: int +GIT_CHECKOUT_DONT_UPDATE_INDEX: int +GIT_CHECKOUT_NO_REFRESH: int +GIT_CHECKOUT_SKIP_UNMERGED: int +GIT_CHECKOUT_USE_OURS: int +GIT_CHECKOUT_USE_THEIRS: int +GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH: int +GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES: int +GIT_CHECKOUT_DONT_OVERWRITE_IGNORED: int +GIT_CHECKOUT_CONFLICT_STYLE_MERGE: int +GIT_CHECKOUT_CONFLICT_STYLE_DIFF3: int +GIT_CHECKOUT_DONT_REMOVE_EXISTING: int +GIT_CHECKOUT_DONT_WRITE_INDEX: int +GIT_CHECKOUT_DRY_RUN: int +GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3: int +GIT_DIFF_NORMAL: int +GIT_DIFF_REVERSE: int +GIT_DIFF_INCLUDE_IGNORED: int +GIT_DIFF_RECURSE_IGNORED_DIRS: int +GIT_DIFF_INCLUDE_UNTRACKED: int +GIT_DIFF_RECURSE_UNTRACKED_DIRS: int +GIT_DIFF_INCLUDE_UNMODIFIED: int +GIT_DIFF_INCLUDE_TYPECHANGE: int +GIT_DIFF_INCLUDE_TYPECHANGE_TREES: int +GIT_DIFF_IGNORE_FILEMODE: int +GIT_DIFF_IGNORE_SUBMODULES: int +GIT_DIFF_IGNORE_CASE: int +GIT_DIFF_INCLUDE_CASECHANGE: int +GIT_DIFF_DISABLE_PATHSPEC_MATCH: int +GIT_DIFF_SKIP_BINARY_CHECK: int +GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS: int +GIT_DIFF_UPDATE_INDEX: int +GIT_DIFF_INCLUDE_UNREADABLE: int +GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED: int +GIT_DIFF_INDENT_HEURISTIC: int +GIT_DIFF_IGNORE_BLANK_LINES: int +GIT_DIFF_FORCE_TEXT: int +GIT_DIFF_FORCE_BINARY: int +GIT_DIFF_IGNORE_WHITESPACE: int +GIT_DIFF_IGNORE_WHITESPACE_CHANGE: int +GIT_DIFF_IGNORE_WHITESPACE_EOL: int +GIT_DIFF_SHOW_UNTRACKED_CONTENT: int +GIT_DIFF_SHOW_UNMODIFIED: int +GIT_DIFF_PATIENCE: int +GIT_DIFF_MINIMAL: int +GIT_DIFF_SHOW_BINARY: int +GIT_DIFF_STATS_NONE: int +GIT_DIFF_STATS_FULL: int +GIT_DIFF_STATS_SHORT: int +GIT_DIFF_STATS_NUMBER: int +GIT_DIFF_STATS_INCLUDE_SUMMARY: int +GIT_DIFF_FIND_BY_CONFIG: int +GIT_DIFF_FIND_RENAMES: int +GIT_DIFF_FIND_RENAMES_FROM_REWRITES: int +GIT_DIFF_FIND_COPIES: int +GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED: int +GIT_DIFF_FIND_REWRITES: int +GIT_DIFF_BREAK_REWRITES: int +GIT_DIFF_FIND_AND_BREAK_REWRITES: int +GIT_DIFF_FIND_FOR_UNTRACKED: int +GIT_DIFF_FIND_ALL: int +GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE: int +GIT_DIFF_FIND_IGNORE_WHITESPACE: int +GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE: int +GIT_DIFF_FIND_EXACT_MATCH_ONLY: int +GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY: int +GIT_DIFF_FIND_REMOVE_UNMODIFIED: int +GIT_DIFF_FLAG_BINARY: int +GIT_DIFF_FLAG_NOT_BINARY: int +GIT_DIFF_FLAG_VALID_ID: int +GIT_DIFF_FLAG_EXISTS: int +GIT_DIFF_FLAG_VALID_SIZE: int +GIT_DELTA_UNMODIFIED: int +GIT_DELTA_ADDED: int +GIT_DELTA_DELETED: int +GIT_DELTA_MODIFIED: int +GIT_DELTA_RENAMED: int +GIT_DELTA_COPIED: int +GIT_DELTA_IGNORED: int +GIT_DELTA_UNTRACKED: int +GIT_DELTA_TYPECHANGE: int +GIT_DELTA_UNREADABLE: int +GIT_DELTA_CONFLICTED: int +GIT_CONFIG_LEVEL_PROGRAMDATA: int +GIT_CONFIG_LEVEL_SYSTEM: int +GIT_CONFIG_LEVEL_XDG: int +GIT_CONFIG_LEVEL_GLOBAL: int +GIT_CONFIG_LEVEL_LOCAL: int +GIT_CONFIG_LEVEL_WORKTREE: int +GIT_CONFIG_LEVEL_APP: int +GIT_CONFIG_HIGHEST_LEVEL: int +GIT_BLAME_NORMAL: int +GIT_BLAME_TRACK_COPIES_SAME_FILE: int +GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES: int +GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES: int +GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES: int +GIT_BLAME_FIRST_PARENT: int +GIT_BLAME_USE_MAILMAP: int +GIT_BLAME_IGNORE_WHITESPACE: int +GIT_MERGE_ANALYSIS_NONE: int +GIT_MERGE_ANALYSIS_NORMAL: int +GIT_MERGE_ANALYSIS_UP_TO_DATE: int +GIT_MERGE_ANALYSIS_FASTFORWARD: int +GIT_MERGE_ANALYSIS_UNBORN: int +GIT_MERGE_PREFERENCE_NONE: int +GIT_MERGE_PREFERENCE_NO_FASTFORWARD: int +GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY: int +GIT_DESCRIBE_DEFAULT: int +GIT_DESCRIBE_TAGS: int +GIT_DESCRIBE_ALL: int +GIT_STASH_DEFAULT: int +GIT_STASH_KEEP_INDEX: int +GIT_STASH_INCLUDE_UNTRACKED: int +GIT_STASH_INCLUDE_IGNORED: int +GIT_STASH_KEEP_ALL: int +GIT_STASH_APPLY_DEFAULT: int +GIT_STASH_APPLY_REINSTATE_INDEX: int +GIT_APPLY_LOCATION_WORKDIR: int +GIT_APPLY_LOCATION_INDEX: int +GIT_APPLY_LOCATION_BOTH: int +GIT_SUBMODULE_IGNORE_UNSPECIFIED: int +GIT_SUBMODULE_IGNORE_NONE: int +GIT_SUBMODULE_IGNORE_UNTRACKED: int +GIT_SUBMODULE_IGNORE_DIRTY: int +GIT_SUBMODULE_IGNORE_ALL: int +GIT_SUBMODULE_STATUS_IN_HEAD: int +GIT_SUBMODULE_STATUS_IN_INDEX: int +GIT_SUBMODULE_STATUS_IN_CONFIG: int +GIT_SUBMODULE_STATUS_IN_WD: int +GIT_SUBMODULE_STATUS_INDEX_ADDED: int +GIT_SUBMODULE_STATUS_INDEX_DELETED: int +GIT_SUBMODULE_STATUS_INDEX_MODIFIED: int +GIT_SUBMODULE_STATUS_WD_UNINITIALIZED: int +GIT_SUBMODULE_STATUS_WD_ADDED: int +GIT_SUBMODULE_STATUS_WD_DELETED: int +GIT_SUBMODULE_STATUS_WD_MODIFIED: int +GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED: int +GIT_SUBMODULE_STATUS_WD_WD_MODIFIED: int +GIT_SUBMODULE_STATUS_WD_UNTRACKED: int +GIT_BLOB_FILTER_CHECK_FOR_BINARY: int +GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES: int +GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD: int +GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT: int +GIT_FILTER_DRIVER_PRIORITY: int +GIT_FILTER_TO_WORKTREE: int +GIT_FILTER_SMUDGE: int +GIT_FILTER_TO_ODB: int +GIT_FILTER_CLEAN: int +GIT_FILTER_DEFAULT: int +GIT_FILTER_ALLOW_UNSAFE: int +GIT_FILTER_NO_SYSTEM_ATTRIBUTES: int +GIT_FILTER_ATTRIBUTES_FROM_HEAD: int +GIT_FILTER_ATTRIBUTES_FROM_COMMIT: int class Object: _pointer: bytes @@ -47,15 +287,17 @@ class Object: type: 'Literal[GIT_OBJ_COMMIT] | Literal[GIT_OBJ_TREE] | Literal[GIT_OBJ_TAG] | Literal[GIT_OBJ_BLOB]' type_str: "Literal['commit'] | Literal['tree'] | Literal['tag'] | Literal['blob']" @overload - def peel(self, target_type: 'Literal[GIT_OBJ_COMMIT]') -> 'Commit': ... + def peel( + self, target_type: 'Literal[GIT_OBJ_COMMIT] | Type[Commit]' + ) -> 'Commit': ... @overload - def peel(self, target_type: 'Literal[GIT_OBJ_TREE]') -> 'Tree': ... + def peel(self, target_type: 'Literal[GIT_OBJ_TREE] | Type[Tree]') -> 'Tree': ... @overload - def peel(self, target_type: 'Literal[GIT_OBJ_TAG]') -> 'Tag': ... + def peel(self, target_type: 'Literal[GIT_OBJ_TAG] | Type[Tag]') -> 'Tag': ... @overload - def peel(self, target_type: 'Literal[GIT_OBJ_BLOB]') -> 'Blob': ... + def peel(self, target_type: 'Literal[GIT_OBJ_BLOB] | Type[Blob]') -> 'Blob': ... @overload - def peel(self, target_type: 'None') -> 'Commit|Tree|Blob': ... + def peel(self, target_type: 'None') -> 'Commit|Tree|Tag|Blob': ... def read_raw(self) -> bytes: ... def __eq__(self, other) -> bool: ... def __ge__(self, other) -> bool: ... @@ -116,6 +358,15 @@ class Blob(Object): old_as_path: str = ..., buffer_as_path: str = ..., ) -> Patch: ... + def _write_to_queue( + self, + queue: Queue, + closed: Event, + chunk_size: int = DEFAULT_BUFFER_SIZE, + as_path: Optional[str] = None, + flags: BlobFilter = BlobFilter.CHECK_FOR_BINARY, + commit_id: Optional[Oid] = None, + ) -> None: ... class Branch(Reference): branch_name: str @@ -128,6 +379,24 @@ class Branch(Reference): def is_head(self) -> bool: ... def rename(self, name: str, force: bool = False) -> 'Branch': ... # type: ignore[override] +class FetchOptions: + # incomplete + depth: int + proxy_opts: ProxyOpts + +class CloneOptions: + # incomplete + version: int + checkout_opts: object + fetch_opts: FetchOptions + bare: int + local: object + checkout_branch: object + repository_cb: object + repository_cb_payload: object + remote_cb: object + remote_cb_payload: object + class Commit(Object): author: Signature commit_time: int @@ -209,6 +478,10 @@ class DiffStats: insertions: int def format(self, format: DiffStatsFormat, width: int) -> str: ... +class FilterSource: + # probably incomplete + pass + class GitError(Exception): ... class InvalidSpecError(ValueError): ... @@ -427,7 +700,7 @@ class Repository: def TreeBuilder(self, src: Tree | _OidArg = ...) -> TreeBuilder: ... def _disown(self, *args, **kwargs) -> None: ... def _from_c(self, *args, **kwargs) -> None: ... - def __getitem__(self, key: str | bytes | Oid | Reference) -> Commit: ... + def __getitem__(self, key: str | Oid) -> Object: ... def add_worktree(self, name: str, path: str, ref: Reference = ...) -> Worktree: ... def applies( self, @@ -649,5 +922,6 @@ def init_file_backend(path: str, flags: int = 0) -> object: ... def option(opt: Option, *args) -> None: ... def reference_is_valid_name(refname: str) -> bool: ... def tree_entry_cmp(a: Object, b: Object) -> int: ... +def _cache_enums() -> None: ... _OidArg = str | Oid diff --git a/pygit2/_run.py b/pygit2/_run.py index 815910ec..ea921d91 100644 --- a/pygit2/_run.py +++ b/pygit2/_run.py @@ -33,11 +33,11 @@ import sys # Import from cffi -from cffi import FFI +from cffi import FFI # type: ignore # Import from pygit2 try: - from _build import get_libgit2_paths + from _build import get_libgit2_paths # type: ignore except ImportError: from ._build import get_libgit2_paths @@ -85,7 +85,7 @@ ] h_source = [] for h_file in h_files: - h_file = dir_path / 'decl' / h_file + h_file = dir_path / 'decl' / h_file # type: ignore with codecs.open(h_file, 'r', 'utf-8') as f: h_source.append(f.read()) diff --git a/pygit2/blob.py b/pygit2/blob.py index d9f4de89..23e8a9da 100644 --- a/pygit2/blob.py +++ b/pygit2/blob.py @@ -26,7 +26,7 @@ def __init__( ): super().__init__() self._blob = blob - self._queue = Queue(maxsize=1) + self._queue: Optional[Queue] = Queue(maxsize=1) self._ready = threading.Event() self._writer_closed = threading.Event() self._chunk: Optional[bytes] = None @@ -45,7 +45,7 @@ def __init__( def __exit__(self, exc_type, exc_value, traceback): self.close() - def isatty(): + def isatty(self): return False def readable(self): diff --git a/pygit2/callbacks.py b/pygit2/callbacks.py index c0c3249f..60f14f1c 100644 --- a/pygit2/callbacks.py +++ b/pygit2/callbacks.py @@ -79,7 +79,7 @@ if TYPE_CHECKING: from .remotes import TransferProgress - from ._pygit2 import ProxyOpts, PushOptions + from ._pygit2 import ProxyOpts, PushOptions, CloneOptions # # The payload is the way to pass information from the pygit2 API, through # libgit2, to the Python callbacks. And back. @@ -87,6 +87,10 @@ class Payload: + repository: Callable | None + remote: Callable | None + clone_options: 'CloneOptions' + def __init__(self, **kw: object) -> None: for key, value in kw.items(): setattr(self, key, value) diff --git a/pygit2/config.py b/pygit2/config.py index 3b739840..cba72911 100644 --- a/pygit2/config.py +++ b/pygit2/config.py @@ -26,7 +26,7 @@ try: from functools import cached_property except ImportError: - from cached_property import cached_property + from cached_property import cached_property # type: ignore # Import from pygit2 from .errors import check_error diff --git a/pygit2/ffi.py b/pygit2/ffi.py index 04ffefa0..a9a0c615 100644 --- a/pygit2/ffi.py +++ b/pygit2/ffi.py @@ -24,4 +24,4 @@ # Boston, MA 02110-1301, USA. # Import from pygit2 -from ._libgit2 import ffi, lib as C # noqa: F401 +from ._libgit2 import ffi, lib as C # type: ignore # noqa: F401 diff --git a/pygit2/repository.py b/pygit2/repository.py index 37a6d2c5..4c2cf26f 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -29,10 +29,11 @@ from time import time import tarfile import typing +from typing import Optional # Import from pygit2 from ._pygit2 import Repository as _Repository, init_file_backend -from ._pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN +from ._pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN, Object from ._pygit2 import Reference, Tree, Commit, Blob, Signature from ._pygit2 import InvalidSpecError @@ -187,20 +188,20 @@ def __iter__(self): # # Mapping interface # - def get(self, key, default=None): + def get(self, key: str, default: Optional[Commit] = None) -> Object: value = self.git_object_lookup_prefix(key) return value if (value is not None) else default - def __getitem__(self, key): + def __getitem__(self, key: str | Oid) -> Object: value = self.git_object_lookup_prefix(key) if value is None: raise KeyError(key) return value - def __contains__(self, key): + def __contains__(self, key: str | Oid) -> bool: return self.git_object_lookup_prefix(key) is not None - def __repr__(self): + def __repr__(self) -> str: return f'pygit2.Repository({repr(self.path)})' # @@ -529,22 +530,22 @@ def diff( # Case 1: Diff tree to tree if isinstance(a, Tree) and isinstance(b, Tree): - return a.diff_to_tree(b, **options) + return a.diff_to_tree(b, **options) # type: ignore[arg-type] # Case 2: Index to workdir elif a is None and b is None: - return self.index.diff_to_workdir(**options) + return self.index.diff_to_workdir(**options) # type: ignore[arg-type] # Case 3: Diff tree to index or workdir elif isinstance(a, Tree) and b is None: if cached: - return a.diff_to_index(self.index, **options) + return a.diff_to_index(self.index, **options) # type: ignore[arg-type] else: - return a.diff_to_workdir(**options) + return a.diff_to_workdir(**options) # type: ignore[arg-type] # Case 4: Diff blob to blob if isinstance(a, Blob) and isinstance(b, Blob): - return a.diff(b, **options) + return a.diff(b, **options) # type: ignore[arg-type] raise ValueError('Only blobs and treeish can be diffed') @@ -559,7 +560,7 @@ def state(self) -> RepositoryState: return RepositoryState(cstate) except ValueError: # Some value not in the IntEnum - newer libgit2 version? - return cstate + return cstate # type: ignore[return-value] def state_cleanup(self): """Remove all the metadata associated with an ongoing command like @@ -770,9 +771,15 @@ def merge_commits( cindex = ffi.new('git_index **') if isinstance(ours, (str, Oid)): - ours = self[ours] + ours_object = self[ours] + if not isinstance(ours_object, Commit): + raise TypeError(f'expected Commit, got {type(ours_object)}') + ours = ours_object if isinstance(theirs, (str, Oid)): - theirs = self[theirs] + theirs_object = self[theirs] + if not isinstance(theirs_object, Commit): + raise TypeError(f'expected Commit, got {type(theirs_object)}') + theirs = theirs_object ours = ours.peel(Commit) theirs = theirs.peel(Commit) @@ -827,16 +834,9 @@ def merge_trees( theirs_ptr = ffi.new('git_tree **') cindex = ffi.new('git_index **') - if isinstance(ancestor, (str, Oid)): - ancestor = self[ancestor] - if isinstance(ours, (str, Oid)): - ours = self[ours] - if isinstance(theirs, (str, Oid)): - theirs = self[theirs] - - ancestor = ancestor.peel(Tree) - ours = ours.peel(Tree) - theirs = theirs.peel(Tree) + ancestor = self.__ensure_tree(ancestor) + ours = self.__ensure_tree(ours) + theirs = self.__ensure_tree(theirs) opts = self._merge_options(favor, flags, file_flags) @@ -890,7 +890,7 @@ def merge( if isinstance(source, Reference): # Annotated commit from ref cptr = ffi.new('struct git_reference **') - ffi.buffer(cptr)[:] = source._pointer[:] + ffi.buffer(cptr)[:] = source._pointer[:] # type: ignore[attr-defined] commit_ptr = ffi.new('git_annotated_commit **') err = C.git_annotated_commit_from_ref(commit_ptr, self._repo, cptr[0]) check_error(err) @@ -1615,6 +1615,11 @@ def amend_commit( return Oid(raw=bytes(ffi.buffer(coid)[:])) + def __ensure_tree(self, maybe_tree: str | Oid | Tree) -> Tree: + if isinstance(maybe_tree, Tree): + return maybe_tree + return self[maybe_tree].peel(Tree) + class Repository(BaseRepository): def __init__( diff --git a/pygit2/submodules.py b/pygit2/submodules.py index d8506d20..ce83777b 100644 --- a/pygit2/submodules.py +++ b/pygit2/submodules.py @@ -39,6 +39,9 @@ class Submodule: + _repo: BaseRepository + _subm: object + @classmethod def _from_c(cls, repo: BaseRepository, cptr): subm = cls.__new__(cls) @@ -74,7 +77,10 @@ def init(self, overwrite: bool = False): check_error(err) def update( - self, init: bool = False, callbacks: RemoteCallbacks = None, depth: int = 0 + self, + init: bool = False, + callbacks: Optional[RemoteCallbacks] = None, + depth: int = 0, ): """ Update a submodule. This will clone a missing submodule and checkout diff --git a/requirements-test.txt b/requirements-test.txt index 9955decc..a1071106 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,2 +1,3 @@ pytest pytest-cov +mypy