diff --git a/mypy.ini b/mypy.ini index e06fd6ba..e9f046b0 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,3 +2,4 @@ [mypy-test.*] disallow_untyped_defs = True +disallow_untyped_calls = True diff --git a/pygit2/__init__.py b/pygit2/__init__.py index f97daa87..38ae2c38 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -35,8 +35,297 @@ from ._build import __version__ # Low level API -from ._pygit2 import * -from ._pygit2 import _cache_enums +from ._pygit2 import ( + GIT_APPLY_LOCATION_BOTH, + GIT_APPLY_LOCATION_INDEX, + GIT_APPLY_LOCATION_WORKDIR, + GIT_BLAME_FIRST_PARENT, + GIT_BLAME_IGNORE_WHITESPACE, + GIT_BLAME_NORMAL, + GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES, + GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES, + GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES, + GIT_BLAME_TRACK_COPIES_SAME_FILE, + GIT_BLAME_USE_MAILMAP, + GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT, + GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD, + GIT_BLOB_FILTER_CHECK_FOR_BINARY, + GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES, + GIT_BRANCH_ALL, + GIT_BRANCH_LOCAL, + GIT_BRANCH_REMOTE, + GIT_CHECKOUT_ALLOW_CONFLICTS, + GIT_CHECKOUT_CONFLICT_STYLE_DIFF3, + GIT_CHECKOUT_CONFLICT_STYLE_MERGE, + GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3, + GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH, + GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, + GIT_CHECKOUT_DONT_REMOVE_EXISTING, + GIT_CHECKOUT_DONT_UPDATE_INDEX, + GIT_CHECKOUT_DONT_WRITE_INDEX, + GIT_CHECKOUT_DRY_RUN, + GIT_CHECKOUT_FORCE, + GIT_CHECKOUT_NO_REFRESH, + GIT_CHECKOUT_NONE, + GIT_CHECKOUT_RECREATE_MISSING, + GIT_CHECKOUT_REMOVE_IGNORED, + GIT_CHECKOUT_REMOVE_UNTRACKED, + GIT_CHECKOUT_SAFE, + GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES, + GIT_CHECKOUT_SKIP_UNMERGED, + GIT_CHECKOUT_UPDATE_ONLY, + GIT_CHECKOUT_USE_OURS, + GIT_CHECKOUT_USE_THEIRS, + GIT_CONFIG_HIGHEST_LEVEL, + GIT_CONFIG_LEVEL_APP, + GIT_CONFIG_LEVEL_GLOBAL, + GIT_CONFIG_LEVEL_LOCAL, + GIT_CONFIG_LEVEL_PROGRAMDATA, + GIT_CONFIG_LEVEL_SYSTEM, + GIT_CONFIG_LEVEL_WORKTREE, + GIT_CONFIG_LEVEL_XDG, + GIT_DELTA_ADDED, + GIT_DELTA_CONFLICTED, + GIT_DELTA_COPIED, + GIT_DELTA_DELETED, + GIT_DELTA_IGNORED, + GIT_DELTA_MODIFIED, + GIT_DELTA_RENAMED, + GIT_DELTA_TYPECHANGE, + GIT_DELTA_UNMODIFIED, + GIT_DELTA_UNREADABLE, + GIT_DELTA_UNTRACKED, + GIT_DESCRIBE_ALL, + GIT_DESCRIBE_DEFAULT, + GIT_DESCRIBE_TAGS, + GIT_DIFF_BREAK_REWRITES, + GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, + GIT_DIFF_DISABLE_PATHSPEC_MATCH, + GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, + GIT_DIFF_FIND_ALL, + GIT_DIFF_FIND_AND_BREAK_REWRITES, + GIT_DIFF_FIND_BY_CONFIG, + GIT_DIFF_FIND_COPIES, + GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, + GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, + GIT_DIFF_FIND_EXACT_MATCH_ONLY, + GIT_DIFF_FIND_FOR_UNTRACKED, + GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, + GIT_DIFF_FIND_IGNORE_WHITESPACE, + GIT_DIFF_FIND_REMOVE_UNMODIFIED, + GIT_DIFF_FIND_RENAMES, + GIT_DIFF_FIND_RENAMES_FROM_REWRITES, + GIT_DIFF_FIND_REWRITES, + GIT_DIFF_FLAG_BINARY, + GIT_DIFF_FLAG_EXISTS, + GIT_DIFF_FLAG_NOT_BINARY, + GIT_DIFF_FLAG_VALID_ID, + GIT_DIFF_FLAG_VALID_SIZE, + GIT_DIFF_FORCE_BINARY, + GIT_DIFF_FORCE_TEXT, + GIT_DIFF_IGNORE_BLANK_LINES, + GIT_DIFF_IGNORE_CASE, + GIT_DIFF_IGNORE_FILEMODE, + GIT_DIFF_IGNORE_SUBMODULES, + GIT_DIFF_IGNORE_WHITESPACE, + GIT_DIFF_IGNORE_WHITESPACE_CHANGE, + GIT_DIFF_IGNORE_WHITESPACE_EOL, + GIT_DIFF_INCLUDE_CASECHANGE, + GIT_DIFF_INCLUDE_IGNORED, + GIT_DIFF_INCLUDE_TYPECHANGE, + GIT_DIFF_INCLUDE_TYPECHANGE_TREES, + GIT_DIFF_INCLUDE_UNMODIFIED, + GIT_DIFF_INCLUDE_UNREADABLE, + GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, + GIT_DIFF_INCLUDE_UNTRACKED, + GIT_DIFF_INDENT_HEURISTIC, + GIT_DIFF_MINIMAL, + GIT_DIFF_NORMAL, + GIT_DIFF_PATIENCE, + GIT_DIFF_RECURSE_IGNORED_DIRS, + GIT_DIFF_RECURSE_UNTRACKED_DIRS, + GIT_DIFF_REVERSE, + GIT_DIFF_SHOW_BINARY, + GIT_DIFF_SHOW_UNMODIFIED, + GIT_DIFF_SHOW_UNTRACKED_CONTENT, + GIT_DIFF_SKIP_BINARY_CHECK, + GIT_DIFF_STATS_FULL, + GIT_DIFF_STATS_INCLUDE_SUMMARY, + GIT_DIFF_STATS_NONE, + GIT_DIFF_STATS_NUMBER, + GIT_DIFF_STATS_SHORT, + GIT_DIFF_UPDATE_INDEX, + GIT_FILEMODE_BLOB, + GIT_FILEMODE_BLOB_EXECUTABLE, + GIT_FILEMODE_COMMIT, + GIT_FILEMODE_LINK, + GIT_FILEMODE_TREE, + GIT_FILEMODE_UNREADABLE, + GIT_FILTER_ALLOW_UNSAFE, + GIT_FILTER_ATTRIBUTES_FROM_COMMIT, + GIT_FILTER_ATTRIBUTES_FROM_HEAD, + GIT_FILTER_CLEAN, + GIT_FILTER_DEFAULT, + GIT_FILTER_DRIVER_PRIORITY, + GIT_FILTER_NO_SYSTEM_ATTRIBUTES, + GIT_FILTER_SMUDGE, + GIT_FILTER_TO_ODB, + GIT_FILTER_TO_WORKTREE, + GIT_MERGE_ANALYSIS_FASTFORWARD, + GIT_MERGE_ANALYSIS_NONE, + GIT_MERGE_ANALYSIS_NORMAL, + GIT_MERGE_ANALYSIS_UNBORN, + GIT_MERGE_ANALYSIS_UP_TO_DATE, + GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY, + GIT_MERGE_PREFERENCE_NO_FASTFORWARD, + GIT_MERGE_PREFERENCE_NONE, + GIT_OBJECT_ANY, + GIT_OBJECT_BLOB, + GIT_OBJECT_COMMIT, + GIT_OBJECT_INVALID, + GIT_OBJECT_OFS_DELTA, + GIT_OBJECT_REF_DELTA, + GIT_OBJECT_TAG, + GIT_OBJECT_TREE, + GIT_OID_HEX_ZERO, + GIT_OID_HEXSZ, + GIT_OID_MINPREFIXLEN, + GIT_OID_RAWSZ, + GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, + GIT_OPT_ENABLE_CACHING, + GIT_OPT_ENABLE_FSYNC_GITDIR, + GIT_OPT_ENABLE_OFS_DELTA, + GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, + GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, + GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, + GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, + GIT_OPT_GET_CACHED_MEMORY, + GIT_OPT_GET_MWINDOW_FILE_LIMIT, + GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, + GIT_OPT_GET_MWINDOW_SIZE, + GIT_OPT_GET_OWNER_VALIDATION, + GIT_OPT_GET_PACK_MAX_OBJECTS, + GIT_OPT_GET_SEARCH_PATH, + GIT_OPT_GET_TEMPLATE_PATH, + GIT_OPT_GET_USER_AGENT, + GIT_OPT_GET_WINDOWS_SHAREMODE, + GIT_OPT_SET_ALLOCATOR, + GIT_OPT_SET_CACHE_MAX_SIZE, + GIT_OPT_SET_CACHE_OBJECT_LIMIT, + GIT_OPT_SET_MWINDOW_FILE_LIMIT, + GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, + GIT_OPT_SET_MWINDOW_SIZE, + GIT_OPT_SET_OWNER_VALIDATION, + GIT_OPT_SET_PACK_MAX_OBJECTS, + GIT_OPT_SET_SEARCH_PATH, + GIT_OPT_SET_SSL_CERT_LOCATIONS, + GIT_OPT_SET_SSL_CIPHERS, + GIT_OPT_SET_TEMPLATE_PATH, + GIT_OPT_SET_USER_AGENT, + GIT_OPT_SET_WINDOWS_SHAREMODE, + GIT_REFERENCES_ALL, + GIT_REFERENCES_BRANCHES, + GIT_REFERENCES_TAGS, + GIT_RESET_HARD, + GIT_RESET_MIXED, + GIT_RESET_SOFT, + GIT_REVSPEC_MERGE_BASE, + GIT_REVSPEC_RANGE, + GIT_REVSPEC_SINGLE, + GIT_SORT_NONE, + GIT_SORT_REVERSE, + GIT_SORT_TIME, + GIT_SORT_TOPOLOGICAL, + GIT_STASH_APPLY_DEFAULT, + GIT_STASH_APPLY_REINSTATE_INDEX, + GIT_STASH_DEFAULT, + GIT_STASH_INCLUDE_IGNORED, + GIT_STASH_INCLUDE_UNTRACKED, + GIT_STASH_KEEP_ALL, + GIT_STASH_KEEP_INDEX, + GIT_STATUS_CONFLICTED, + GIT_STATUS_CURRENT, + GIT_STATUS_IGNORED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_RENAMED, + GIT_STATUS_INDEX_TYPECHANGE, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_RENAMED, + GIT_STATUS_WT_TYPECHANGE, + GIT_STATUS_WT_UNREADABLE, + GIT_SUBMODULE_IGNORE_ALL, + GIT_SUBMODULE_IGNORE_DIRTY, + GIT_SUBMODULE_IGNORE_NONE, + GIT_SUBMODULE_IGNORE_UNSPECIFIED, + GIT_SUBMODULE_IGNORE_UNTRACKED, + GIT_SUBMODULE_STATUS_IN_CONFIG, + GIT_SUBMODULE_STATUS_IN_HEAD, + GIT_SUBMODULE_STATUS_IN_INDEX, + GIT_SUBMODULE_STATUS_IN_WD, + GIT_SUBMODULE_STATUS_INDEX_ADDED, + GIT_SUBMODULE_STATUS_INDEX_DELETED, + GIT_SUBMODULE_STATUS_INDEX_MODIFIED, + GIT_SUBMODULE_STATUS_WD_ADDED, + GIT_SUBMODULE_STATUS_WD_DELETED, + GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED, + GIT_SUBMODULE_STATUS_WD_MODIFIED, + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED, + GIT_SUBMODULE_STATUS_WD_UNTRACKED, + GIT_SUBMODULE_STATUS_WD_WD_MODIFIED, + LIBGIT2_VER_MAJOR, + LIBGIT2_VER_MINOR, + LIBGIT2_VER_REVISION, + LIBGIT2_VERSION, + AlreadyExistsError, + Blob, + Branch, + Commit, + Diff, + DiffDelta, + DiffFile, + DiffHunk, + DiffLine, + DiffStats, + FilterSource, + GitError, + InvalidSpecError, + Mailmap, + Note, + Object, + Odb, + OdbBackend, + OdbBackendLoose, + OdbBackendPack, + Oid, + Patch, + Refdb, + RefdbBackend, + RefdbFsBackend, + Reference, + RefLogEntry, + RevSpec, + Signature, + Stash, + Tag, + Tree, + TreeBuilder, + Walker, + Worktree, + _cache_enums, + discover_repository, + filter_register, + filter_unregister, + hash, + hashfile, + init_file_backend, + option, + reference_is_valid_name, + tree_entry_cmp, +) from .blame import Blame, BlameHunk from .blob import BlobIO from .callbacks import ( diff --git a/pygit2/_libgit2/ffi.pyi b/pygit2/_libgit2/ffi.pyi index 4cb37caa..0e865e31 100644 --- a/pygit2/_libgit2/ffi.pyi +++ b/pygit2/_libgit2/ffi.pyi @@ -25,8 +25,6 @@ from typing import Any, Generic, Literal, NewType, SupportsIndex, TypeVar, overload -from pygit2._pygit2 import Repository - T = TypeVar('T') NULL_TYPE = NewType('NULL_TYPE', object) @@ -38,6 +36,12 @@ char_pointer = NewType('char_pointer', object) class size_t: def __getitem__(self, item: Literal[0]) -> int: ... +class int_c: + def __getitem__(self, item: Literal[0]) -> int: ... + +class int64_t: + def __getitem__(self, item: Literal[0]) -> int: ... + class _Pointer(Generic[T]): def __setitem__(self, item: Literal[0], a: T) -> None: ... @overload @@ -76,7 +80,8 @@ class GitHunkC: class GitRepositoryC: # incomplete # TODO: this has to be unified with pygit2._pygit2(pyi).Repository - def _from_c(cls, ptr: 'GitRepositoryC', owned: bool) -> 'Repository': ... + # def _from_c(cls, ptr: 'GitRepositoryC', owned: bool) -> 'Repository': ... + pass class GitFetchOptionsC: # TODO: FetchOptions exist in _pygit2.pyi @@ -141,9 +146,14 @@ class GitConfigC: # incomplete pass +class GitConfigIteratorC: + # incomplete + pass + class GitConfigEntryC: # incomplete name: char_pointer + value: char_pointer level: int class GitDescribeFormatOptionsC: @@ -269,6 +279,8 @@ def new(a: Literal['git_config *']) -> GitConfigC: ... @overload def new(a: Literal['git_config **']) -> _Pointer[GitConfigC]: ... @overload +def new(a: Literal['git_config_iterator **']) -> _Pointer[GitConfigIteratorC]: ... +@overload def new(a: Literal['git_config_entry **']) -> _Pointer[GitConfigEntryC]: ... @overload def new(a: Literal['git_describe_format_options *']) -> GitDescribeFormatOptionsC: ... @@ -297,11 +309,15 @@ def new(a: Literal['git_signature *']) -> GitSignatureC: ... @overload def new(a: Literal['git_signature **']) -> _Pointer[GitSignatureC]: ... @overload +def new(a: Literal['int *']) -> int_c: ... +@overload +def new(a: Literal['int64_t *']) -> int64_t: ... +@overload def new( a: Literal['git_remote_head ***'], ) -> _Pointer[_MultiPointer[GitRemoteHeadC]]: ... @overload -def new(a: Literal['size_t *']) -> size_t: ... +def new(a: Literal['size_t *', 'size_t*']) -> size_t: ... @overload def new(a: Literal['git_stash_save_options *']) -> GitStashSaveOptionsC: ... @overload diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index 1db0b450..7ca7d269 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -1,11 +1,8 @@ -import tarfile -from collections.abc import Generator from io import DEFAULT_BUFFER_SIZE, IOBase from pathlib import Path from queue import Queue from threading import Event from typing import ( - Callable, Generic, Iterator, Literal, @@ -17,53 +14,35 @@ from typing import ( overload, ) -from . import Index, IndexEntry +from . import Index from ._libgit2.ffi import ( GitCommitC, - GitMergeOptionsC, GitObjectC, GitProxyOptionsC, - GitRepositoryC, GitSignatureC, _Pointer, ) -from .blame import Blame -from .callbacks import CheckoutCallbacks, StashApplyCallbacks -from .config import Config from .enums import ( ApplyLocation, - AttrCheck, - BlameFlag, BlobFilter, BranchType, - CheckoutStrategy, ConfigLevel, DeltaStatus, - DescribeStrategy, DiffFind, DiffFlag, DiffOption, DiffStatsFormat, FileMode, MergeAnalysis, - MergeFavor, - MergeFileFlag, - MergeFlag, MergePreference, ObjectType, Option, ReferenceFilter, ReferenceType, - RepositoryState, ResetMode, SortMode, ) from .filter import Filter -from .index import MergeFileResult -from .packbuilder import PackBuilder -from .remotes import Remote -from .repository import BaseRepository -from .submodules import SubmoduleCollection GIT_OBJ_BLOB = Literal[3] GIT_OBJ_COMMIT = Literal[1] @@ -575,7 +554,7 @@ class Odb: def add_disk_alternate(self, path: str | Path) -> None: ... def exists(self, oid: _OidArg) -> bool: ... def read(self, oid: _OidArg) -> tuple[int, bytes]: ... - def write(self, type: int, data: bytes) -> Oid: ... + def write(self, type: int, data: bytes | str) -> Oid: ... def __contains__(self, other: _OidArg) -> bool: ... def __iter__(self) -> Iterator[Oid]: ... # Odb_as_iter @@ -668,21 +647,6 @@ class RefdbBackend: class RefdbFsBackend(RefdbBackend): def __init__(self, *args, **kwargs) -> None: ... -class References: - def __init__(self, repository: BaseRepository) -> None: ... - def __getitem__(self, name: str) -> Reference: ... - def get(self, key: str) -> Reference: ... - def __iter__(self) -> Iterator[str]: ... - def iterator( - self, references_return_type: ReferenceFilter = ... - ) -> Iterator[Reference]: ... - def create(self, name: str, target: _OidArg, force: bool = False) -> Reference: ... - def delete(self, name: str) -> None: ... - def __contains__(self, name: str) -> bool: ... - @property - def objects(self) -> list[Reference]: ... - def compress(self) -> None: ... - _Proxy = None | Literal[True] | str class _StrArray: @@ -705,81 +669,12 @@ class _LsRemotesDict(TypedDict): symref_target: str | None oid: Oid -class RemoteCollection: - def __init__(self, repo: BaseRepository) -> None: ... - def __len__(self) -> int: ... - def __iter__(self): ... - def __getitem__(self, name: str | int) -> Remote: ... - def names(self) -> Generator[str, None, None]: ... - def create(self, name: str, url: str, fetch: str | None = None) -> Remote: ... - def create_anonymous(self, url: str) -> Remote: ... - def rename(self, name: str, new_name: str) -> list[str]: ... - def delete(self, name: str) -> None: ... - def set_url(self, name: str, url: str) -> None: ... - def set_push_url(self, name: str, url: str) -> None: ... - def add_fetch(self, name: str, refspec: str) -> None: ... - def add_push(self, name: str, refspec: str) -> None: ... - -class Branches: - local: 'Branches' - remote: 'Branches' - def __init__( - self, - repository: BaseRepository, - flag: BranchType = ..., - commit: Commit | _OidArg | None = None, - ) -> None: ... - def __getitem__(self, name: str) -> Branch: ... - def get(self, key: str) -> Branch: ... - def __iter__(self) -> Iterator[str]: ... - def create( - self, name: str, commit: Object | Commit, force: bool = False - ) -> Branch: ... - def delete(self, name: str) -> None: ... - def with_commit(self, commit: Object | Commit | _OidArg | None) -> 'Branches': ... - def __contains__(self, name: _OidArg) -> bool: ... - class Repository: - _pointer: GitRepositoryC - _repo: GitRepositoryC - backend: RefdbBackend - default_signature: Signature - head: Reference - head_is_detached: bool - head_is_unborn: bool - is_bare: bool - is_empty: bool - is_shallow: bool - odb: Odb - path: str - refdb: Refdb - workdir: str - references: References - remotes: RemoteCollection - branches: Branches - submodules: SubmoduleCollection - index: Index - def __init__(self, *args, **kwargs) -> None: ... def TreeBuilder(self, src: Tree | _OidArg = ...) -> TreeBuilder: ... def _disown(self, *args, **kwargs) -> None: ... - @classmethod - def _from_c(cls, ptr: 'GitRepositoryC', owned: bool) -> 'Repository': ... - def __iter__(self) -> Iterator[Oid]: ... - def __getitem__(self, key: str | Oid) -> Object: ... - def __contains__(self, name: _OidArg) -> bool: ... def add_worktree( self, name: str, path: str | Path, ref: Reference = ... ) -> Worktree: ... - def amend_commit( - self, - commit: Commit | Oid | str, - refname: Reference | str | None, - author: Signature | None = None, - committer: Signature | None = None, - message: str | None = None, - tree: Tree | Oid | str | None = None, - encoding: str = 'UTF-8', - ) -> Oid: ... def applies( self, diff: Diff, @@ -789,30 +684,8 @@ class Repository: def apply( self, diff: Diff, location: ApplyLocation = ApplyLocation.WORKDIR ) -> None: ... - def blame( - self, - path: str, - flags: BlameFlag = BlameFlag.NORMAL, - min_match_characters: int | None = None, - newest_commit: _OidArg | None = None, - oldest_commit: _OidArg | None = None, - min_line: int | None = None, - max_line: int | None = None, - ) -> Blame: ... - def checkout( - self, - refname: _OidArg | None | Reference = None, - *, - strategy: CheckoutStrategy | None = None, - directory: str | Path | None = None, - paths: list[str] | None = None, - callbacks: CheckoutCallbacks | None = None, - ) -> None: ... - def ahead_behind(self, local: _OidArg, upstream: _OidArg) -> tuple[int, int]: ... def cherrypick(self, id: _OidArg) -> None: ... def compress_references(self) -> None: ... - @property - def config(self) -> Config: ... def create_blob(self, data: str | bytes) -> Oid: ... def create_blob_fromdisk(self, path: str) -> Oid: ... def create_blob_fromiobase(self, iobase: IOBase) -> Oid: ... @@ -849,13 +722,6 @@ class Repository: ref: str = 'refs/notes/commits', force: bool = False, ) -> Oid: ... - def create_reference( - self, - name: str, - target: _OidArg, - force: bool = False, - message: str | None = None, - ) -> Reference: ... def create_reference_direct( self, name: str, target: _OidArg, force: bool, message: Optional[str] = None ) -> Reference: ... @@ -865,50 +731,13 @@ class Repository: def create_tag( self, name: str, oid: _OidArg, type: ObjectType, tagger: Signature, message: str ) -> Oid: ... - def diff( - self, - a: None | str | bytes | Commit | Oid | Reference = None, - b: None | str | bytes | Commit | Oid | Reference = None, - cached: bool = False, - flags: DiffOption = DiffOption.NORMAL, - context_lines: int = 3, - interhunk_lines: int = 0, - ) -> Diff: ... def descendant_of(self, oid1: _OidArg, oid2: _OidArg) -> bool: ... - def describe( - self, - committish: str | Reference | Commit | None = None, - max_candidates_tags: int | None = None, - describe_strategy: DescribeStrategy = DescribeStrategy.DEFAULT, - pattern: str | None = None, - only_follow_first_parent: bool | None = None, - show_commit_oid_as_fallback: bool | None = None, - abbreviated_size: object | None = None, - always_use_long_format: bool | None = None, - dirty_suffix: str | None = None, - ) -> str: ... def expand_id(self, hex: str) -> Oid: ... def free(self) -> None: ... - def get(self, key: _OidArg, default: Optional[Commit] = None) -> None | Object: ... - def get_attr( - self, - path: str | bytes | Path, - name: str | bytes, - flags: AttrCheck = AttrCheck.FILE_THEN_INDEX, - commit: _OidArg | None = None, - ) -> bool | None | str: ... def git_object_lookup_prefix(self, oid: _OidArg) -> Object: ... - def hashfile( - self, - path: str, - object_type: ObjectType = ObjectType.BLOB, - as_path: str | None = None, - ) -> Oid: ... def list_worktrees(self) -> list[str]: ... def listall_branches(self, flag: BranchType = BranchType.LOCAL) -> list[str]: ... def listall_mergeheads(self) -> list[Oid]: ... - def listall_references(self) -> list[str]: ... - def listall_reference_objects(self) -> list[Reference]: ... def listall_stashes(self) -> list[Stash]: ... def listall_submodules(self) -> list[str]: ... def lookup_branch( @@ -920,65 +749,18 @@ class Repository: def lookup_reference(self, name: str) -> Reference: ... def lookup_reference_dwim(self, name: str) -> Reference: ... def lookup_worktree(self, name: str) -> Worktree: ... - def merge( - self, - source: Reference | Commit | Oid | str, - favor: MergeFavor = MergeFavor.NORMAL, - flags: MergeFlag = MergeFlag.FIND_RENAMES, - file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, - ) -> None: ... def merge_analysis( self, their_head: _OidArg, our_ref: str = 'HEAD' ) -> tuple[MergeAnalysis, MergePreference]: ... def merge_base(self, oid1: _OidArg, oid2: _OidArg) -> Oid: ... def merge_base_many(self, oids: list[_OidArg]) -> Oid: ... def merge_base_octopus(self, oids: list[_OidArg]) -> Oid: ... - def merge_commits( - self, - ours: str | Oid | Commit, - theirs: str | Oid | Commit, - favor: MergeFavor = MergeFavor.NORMAL, - flags: MergeFlag = MergeFlag.FIND_RENAMES, - file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, - ) -> Index: ... - def merge_file_from_index( - self, - ancestor: IndexEntry | None, - ours: IndexEntry | None, - theirs: IndexEntry | None, - use_deprecated: bool = True, - ) -> str | MergeFileResult | None: ... - @staticmethod - def _merge_options( - favor: int | MergeFavor, flags: int | MergeFlag, file_flags: int | MergeFileFlag - ) -> GitMergeOptionsC: ... - def merge_trees( - self, - ancestor: str | Oid | Tree, - ours: str | Oid | Tree, - theirs: str | Oid | Tree, - favor: MergeFavor = MergeFavor.NORMAL, - flags: MergeFlag = MergeFlag.FIND_RENAMES, - file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, - ) -> Index: ... - @property - def message(self) -> str: ... def notes(self) -> Iterator[Note]: ... - def pack( - self, - path: str | Path | None = None, - pack_delegate: Callable[[PackBuilder], None] | None = None, - n_threads: int | None = None, - ) -> bool: ... def path_is_ignored(self, path: str) -> bool: ... def raw_listall_branches( self, flag: BranchType = BranchType.LOCAL ) -> list[bytes]: ... def raw_listall_references(self) -> list[bytes]: ... - @property - def raw_message(self) -> bytes: ... - def read(self, oid: _OidArg) -> tuple[int, bytes]: ... - def remove_message(self) -> None: ... def references_iterator_init(self) -> Iterator[Reference]: ... def references_iterator_next( self, @@ -986,64 +768,18 @@ class Repository: references_return_type: ReferenceFilter = ReferenceFilter.ALL, ) -> Reference: ... def reset(self, oid: _OidArg, reset_type: ResetMode) -> None: ... - def resolve_refish(self, refresh: str) -> tuple[Commit, Reference]: ... def revparse(self, revspec: str) -> RevSpec: ... def revparse_ext(self, revision: str) -> tuple[Object, Reference]: ... def revparse_single(self, revision: str) -> Object: ... - def revert(self, commit: Commit) -> None: ... - def revert_commit( - self, revert_commit: Commit, our_commit: Commit, mainline: int = 0 - ) -> Index: ... - def set_head(self, target: _OidArg) -> None: ... - def set_ident(self, name: str, email: str) -> None: ... def set_odb(self, odb: Odb) -> None: ... def set_refdb(self, refdb: Refdb) -> None: ... def status( self, untracked_files: str = 'all', ignored: bool = False ) -> dict[str, int]: ... - def stash( - self, - stasher: Signature, - message: str | None = None, - keep_index: bool = False, - include_untracked: bool = False, - include_ignored: bool = False, - keep_all: bool = False, - paths: list[str] | None = None, - ) -> Oid: ... - def stash_apply( - self, - index: int = 0, - reinstate_index: bool | None = None, - include_untracked: bool | None = None, - message: str | None = None, - strategy: CheckoutStrategy | None = None, - callbacks: StashApplyCallbacks | None = None, - ) -> None: ... - def stash_pop( - self, - index: int = 0, - reinstate_index: bool | None = None, - include_untracked: bool | None = None, - message: str | None = None, - strategy: CheckoutStrategy | None = None, - callbacks: StashApplyCallbacks | None = None, - ) -> None: ... - def stash_drop(self, index: int = 0) -> None: ... def status_file(self, path: str) -> int: ... - def state(self) -> RepositoryState: ... - def state_cleanup(self) -> None: ... def walk( self, oid: _OidArg | None, sort_mode: SortMode = SortMode.NONE ) -> Walker: ... - def write(self, type: int, data: bytes | str) -> Oid: ... - def write_archive( - self, - treeish: str | Tree | Object | Oid, - archive: tarfile.TarFile, - timestamp: int | None = None, - prefix: str = '', - ) -> None: ... class RevSpec: flags: int diff --git a/pygit2/branches.py b/pygit2/branches.py index 9f5ac6f1..ca32e151 100644 --- a/pygit2/branches.py +++ b/pygit2/branches.py @@ -25,9 +25,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Iterator -from ._pygit2 import Commit, Oid +from ._pygit2 import Branch, Commit, Oid from .enums import BranchType, ReferenceType # Need BaseRepository for type hints, but don't let it cause a circular dependency @@ -36,9 +36,15 @@ class Branches: + local: 'Branches' + remote: 'Branches' + def __init__( - self, repository: BaseRepository, flag: BranchType = BranchType.ALL, commit=None - ): + self, + repository: BaseRepository, + flag: BranchType = BranchType.ALL, + commit: Commit | Oid | str | None = None, + ) -> None: self._repository = repository self._flag = flag if commit is not None: @@ -52,7 +58,7 @@ def __init__( self.local = Branches(repository, flag=BranchType.LOCAL, commit=commit) self.remote = Branches(repository, flag=BranchType.REMOTE, commit=commit) - def __getitem__(self, name: str): + def __getitem__(self, name: str) -> Branch: branch = None if self._flag & BranchType.LOCAL: branch = self._repository.lookup_branch(name, BranchType.LOCAL) @@ -65,36 +71,38 @@ def __getitem__(self, name: str): return branch - def get(self, key: str): + def get(self, key: str) -> Branch: try: return self[key] except KeyError: - return None + return None # type:ignore # next commit - def __iter__(self): + def __iter__(self) -> Iterator[str]: for branch_name in self._repository.listall_branches(self._flag): if self._commit is None or self.get(branch_name) is not None: yield branch_name - def create(self, name: str, commit, force=False): + def create(self, name: str, commit: Commit, force: bool = False) -> Branch: return self._repository.create_branch(name, commit, force) - def delete(self, name: str): + def delete(self, name: str) -> None: self[name].delete() - def _valid(self, branch): + def _valid(self, branch: Branch) -> bool: if branch.type == ReferenceType.SYMBOLIC: - branch = branch.resolve() + branch_direct = branch.resolve() + else: + branch_direct = branch return ( self._commit is None - or branch.target == self._commit - or self._repository.descendant_of(branch.target, self._commit) + or branch_direct.target == self._commit + or self._repository.descendant_of(branch_direct.target, self._commit) ) - def with_commit(self, commit): + def with_commit(self, commit: Commit | Oid | str | None) -> 'Branches': assert self._commit is None return Branches(self._repository, self._flag, commit) - def __contains__(self, name): + def __contains__(self, name: str) -> bool: return self.get(name) is not None diff --git a/pygit2/callbacks.py b/pygit2/callbacks.py index e2b861d9..87914285 100644 --- a/pygit2/callbacks.py +++ b/pygit2/callbacks.py @@ -718,7 +718,7 @@ def _checkout_notify_cb( pyworkdir = DiffFile.from_c(ptr_to_bytes(workdir)) try: - data.checkout_notify(why, pypath, pybaseline, pytarget, pyworkdir) + data.checkout_notify(why, pypath, pybaseline, pytarget, pyworkdir) # type: ignore[arg-type] except Passthrough: # Unlike most other operations with optional callbacks, checkout # doesn't support the GIT_PASSTHROUGH return code, so we must bypass @@ -733,7 +733,7 @@ def _checkout_notify_cb( @libgit2_callback_void def _checkout_progress_cb(path, completed_steps, total_steps, data: CheckoutCallbacks): - data.checkout_progress(maybe_string(path), completed_steps, total_steps) + data.checkout_progress(maybe_string(path), completed_steps, total_steps) # type: ignore[arg-type] def _git_checkout_options( diff --git a/pygit2/config.py b/pygit2/config.py index 0f18d6cf..ec97f543 100644 --- a/pygit2/config.py +++ b/pygit2/config.py @@ -24,7 +24,8 @@ # Boston, MA 02110-1301, USA. from os import PathLike -from typing import TYPE_CHECKING +from pathlib import Path +from typing import TYPE_CHECKING, Callable, Iterator try: from functools import cached_property @@ -37,7 +38,8 @@ from .utils import to_bytes if TYPE_CHECKING: - from ._libgit2.ffi import GitConfigC, GitConfigEntryC, GitRepositoryC + from ._libgit2.ffi import GitConfigC, GitConfigEntryC + from .repository import BaseRepository def str_to_bytes(value: str | PathLike[str] | bytes, name: str) -> bytes: @@ -48,20 +50,20 @@ def str_to_bytes(value: str | PathLike[str] | bytes, name: str) -> bytes: class ConfigIterator: - def __init__(self, config, ptr): + def __init__(self, config, ptr) -> None: self._iter = ptr self._config = config - def __del__(self): + def __del__(self) -> None: C.git_config_iterator_free(self._iter) - def __iter__(self): + def __iter__(self) -> 'ConfigIterator': return self - def __next__(self): + def __next__(self) -> 'ConfigEntry': return self._next_entry() - def _next_entry(self): + def _next_entry(self) -> 'ConfigEntry': centry = ffi.new('git_config_entry **') err = C.git_config_next(centry, self._iter) check_error(err) @@ -70,7 +72,7 @@ def _next_entry(self): class ConfigMultivarIterator(ConfigIterator): - def __next__(self): + def __next__(self) -> str: # type: ignore[override] entry = self._next_entry() return entry.value @@ -78,7 +80,7 @@ def __next__(self): class Config: """Git configuration management.""" - _repo: 'GitRepositoryC' + _repo: 'BaseRepository' _config: 'GitConfigC' def __init__(self, path: str | None = None) -> None: @@ -94,7 +96,7 @@ def __init__(self, path: str | None = None) -> None: self._config = cconfig[0] @classmethod - def from_c(cls, repo: 'GitRepositoryC', ptr: 'GitConfigC') -> 'Config': + def from_c(cls, repo: 'BaseRepository', ptr: 'GitConfigC') -> 'Config': config = cls.__new__(cls) config._repo = repo config._config = ptr @@ -124,7 +126,7 @@ def _get_entry(self, key: str | bytes) -> 'ConfigEntry': check_error(err) return entry - def __contains__(self, key): + def __contains__(self, key: str | bytes) -> bool: err, cstr = self._get(key) if err == C.GIT_ENOTFOUND: @@ -134,7 +136,7 @@ def __contains__(self, key): return True - def __getitem__(self, key): + def __getitem__(self, key: str | bytes) -> str: """ When using the mapping interface, the value is returned as a string. In order to apply the git-config parsing rules, you can use @@ -144,7 +146,7 @@ def __getitem__(self, key): return entry.value - def __setitem__(self, key, value): + def __setitem__(self, key: str | bytes, value: bool | int | str | bytes) -> None: key = str_to_bytes(key, 'key') err = 0 @@ -157,13 +159,13 @@ def __setitem__(self, key, value): check_error(err) - def __delitem__(self, key): + def __delitem__(self, key: str | bytes) -> None: key = str_to_bytes(key, 'key') err = C.git_config_delete_entry(self._config, key) check_error(err) - def __iter__(self): + def __iter__(self) -> Iterator['ConfigEntry']: """ Iterate over configuration entries, returning a ``ConfigEntry`` objects. These contain the name, level, and value of each configuration @@ -176,22 +178,26 @@ def __iter__(self): return ConfigIterator(self, citer[0]) - def get_multivar(self, name, regex=None): + def get_multivar( + self, name: str | bytes, regex: str | None = None + ) -> ConfigMultivarIterator: """Get each value of a multivar ''name'' as a list of strings. The optional ''regex'' parameter is expected to be a regular expression to filter the variables we're interested in. """ name = str_to_bytes(name, 'name') - regex = to_bytes(regex or None) + regex_bytes = to_bytes(regex or None) citer = ffi.new('git_config_iterator **') - err = C.git_config_multivar_iterator_new(citer, self._config, name, regex) + err = C.git_config_multivar_iterator_new(citer, self._config, name, regex_bytes) check_error(err) return ConfigMultivarIterator(self, citer[0]) - def set_multivar(self, name, regex, value): + def set_multivar( + self, name: str | bytes, regex: str | bytes, value: str | bytes + ) -> None: """Set a multivar ''name'' to ''value''. ''regexp'' is a regular expression to indicate which values to replace. """ @@ -202,7 +208,7 @@ def set_multivar(self, name, regex, value): err = C.git_config_set_multivar(self._config, name, regex, value) check_error(err) - def delete_multivar(self, name, regex): + def delete_multivar(self, name: str | bytes, regex: str | bytes) -> None: """Delete a multivar ''name''. ''regexp'' is a regular expression to indicate which values to delete. """ @@ -212,7 +218,7 @@ def delete_multivar(self, name, regex): err = C.git_config_delete_multivar(self._config, name, regex) check_error(err) - def get_bool(self, key): + def get_bool(self, key: str | bytes) -> bool: """Look up *key* and parse its value as a boolean as per the git-config rules. Return a boolean value (True or False). @@ -227,7 +233,7 @@ def get_bool(self, key): return res[0] != 0 - def get_int(self, key): + def get_int(self, key: bytes | str) -> int: """Look up *key* and parse its value as an integer as per the git-config rules. Return an integer. @@ -242,7 +248,7 @@ def get_int(self, key): return res[0] - def add_file(self, path, level=0, force=0): + def add_file(self, path: str | Path, level: int = 0, force: int = 0) -> None: """Add a config file instance to an existing config.""" err = C.git_config_add_file_ondisk( @@ -250,7 +256,7 @@ def add_file(self, path, level=0, force=0): ) check_error(err) - def snapshot(self): + def snapshot(self) -> 'Config': """Create a snapshot from this Config object. This means that looking up multiple values will use the same version @@ -267,7 +273,7 @@ def snapshot(self): # @staticmethod - def parse_bool(text): + def parse_bool(text: str) -> bool: res = ffi.new('int *') err = C.git_config_parse_bool(res, to_bytes(text)) check_error(err) @@ -275,7 +281,7 @@ def parse_bool(text): return res[0] != 0 @staticmethod - def parse_int(text): + def parse_int(text: str) -> int: res = ffi.new('int64_t *') err = C.git_config_parse_int64(res, to_bytes(text)) check_error(err) @@ -287,7 +293,7 @@ def parse_int(text): # @staticmethod - def _from_found_config(fn): + def _from_found_config(fn: Callable) -> 'Config': buf = ffi.new('git_buf *', (ffi.NULL, 0)) err = fn(buf) check_error(err, io=True) @@ -297,17 +303,17 @@ def _from_found_config(fn): return Config(cpath) @staticmethod - def get_system_config(): + def get_system_config() -> 'Config': """Return a object representing the system configuration file.""" return Config._from_found_config(C.git_config_find_system) @staticmethod - def get_global_config(): + def get_global_config() -> 'Config': """Return a object representing the global configuration file.""" return Config._from_found_config(C.git_config_find_global) @staticmethod - def get_xdg_config(): + def get_xdg_config() -> 'Config': """Return a object representing the global configuration file.""" return Config._from_found_config(C.git_config_find_xdg) @@ -349,7 +355,7 @@ def __del__(self) -> None: C.git_config_entry_free(self._entry) @property - def c_value(self): + def c_value(self) -> 'ffi.char_pointer': """The raw ``cData`` entry value.""" return self._entry.value diff --git a/pygit2/index.py b/pygit2/index.py index 3c9f9876..3fedf7c6 100644 --- a/pygit2/index.py +++ b/pygit2/index.py @@ -29,12 +29,15 @@ from os import PathLike # Import from pygit2 -from ._pygit2 import Diff, Oid, Repository, Tree +from ._pygit2 import Diff, Oid, Tree from .enums import DiffOption, FileMode from .errors import check_error from .ffi import C, ffi from .utils import GenericIterator, StrArray, to_bytes, to_str +if typing.TYPE_CHECKING: + from .repository import Repository + class Index: # XXX Implement the basic features in C (_pygit2.Index) and make @@ -103,7 +106,7 @@ def __getitem__(self, key: str | int | PathLike[str]) -> 'IndexEntry': def __iter__(self): return GenericIterator(self) - def read(self, force=True): + def read(self, force: bool = True) -> None: """ Update the contents of the Index by reading from a file. @@ -152,7 +155,7 @@ def read_tree(self, tree: Oid | Tree | str) -> None: err = C.git_index_read_tree(self._index, tree_cptr[0]) check_error(err) - def write_tree(self, repo: Repository | None = None) -> Oid: + def write_tree(self, repo: 'Repository | None' = None) -> Oid: """Create a tree out of the Index. Return the object of the written tree. diff --git a/pygit2/packbuilder.py b/pygit2/packbuilder.py index b7325c04..baa54630 100644 --- a/pygit2/packbuilder.py +++ b/pygit2/packbuilder.py @@ -24,17 +24,20 @@ # Boston, MA 02110-1301, USA. from os import PathLike +from typing import TYPE_CHECKING # Import from pygit2 -from pygit2 import Oid, Repository - from .errors import check_error from .ffi import C, ffi from .utils import to_bytes +if TYPE_CHECKING: + from pygit2 import Oid, Repository + from pygit2.repository import BaseRepository + class PackBuilder: - def __init__(self, repo: Repository) -> None: + def __init__(self, repo: 'Repository | BaseRepository') -> None: cpackbuilder = ffi.new('git_packbuilder **') err = C.git_packbuilder_new(cpackbuilder, repo._repo) check_error(err) @@ -54,17 +57,17 @@ def __len__(self) -> int: return C.git_packbuilder_object_count(self._packbuilder) @staticmethod - def __convert_object_to_oid(oid: Oid) -> 'ffi.GitOidC': + def __convert_object_to_oid(oid: 'Oid') -> 'ffi.GitOidC': git_oid = ffi.new('git_oid *') ffi.buffer(git_oid)[:] = oid.raw[:] return git_oid - def add(self, oid: Oid) -> None: + def add(self, oid: 'Oid') -> None: git_oid = self.__convert_object_to_oid(oid) err = C.git_packbuilder_insert(self._packbuilder, git_oid, ffi.NULL) check_error(err) - def add_recur(self, oid: Oid) -> None: + def add_recur(self, oid: 'Oid') -> None: git_oid = self.__convert_object_to_oid(oid) err = C.git_packbuilder_insert_recur(self._packbuilder, git_oid, ffi.NULL) check_error(err) diff --git a/pygit2/references.py b/pygit2/references.py index 630c3fe5..5cbee8fd 100644 --- a/pygit2/references.py +++ b/pygit2/references.py @@ -25,29 +25,32 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Iterator + +from pygit2 import Oid from .enums import ReferenceFilter # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: + from ._pygit2 import Reference from .repository import BaseRepository class References: - def __init__(self, repository: BaseRepository): + def __init__(self, repository: BaseRepository) -> None: self._repository = repository - def __getitem__(self, name: str): + def __getitem__(self, name: str) -> 'Reference': return self._repository.lookup_reference(name) - def get(self, key: str): + def get(self, key: str) -> 'Reference' | None: try: return self[key] except KeyError: return None - def __iter__(self): + def __iter__(self) -> Iterator[str]: iter = self._repository.references_iterator_init() while True: ref = self._repository.references_iterator_next(iter) @@ -56,7 +59,9 @@ def __iter__(self): else: return - def iterator(self, references_return_type: ReferenceFilter = ReferenceFilter.ALL): + def iterator( + self, references_return_type: ReferenceFilter = ReferenceFilter.ALL + ) -> Iterator['Reference']: """Creates a new iterator and fetches references for a given repository. Can also filter and pass all refs or only branches or only tags. @@ -88,18 +93,18 @@ def iterator(self, references_return_type: ReferenceFilter = ReferenceFilter.ALL else: return - def create(self, name, target, force=False): + def create(self, name: str, target: Oid | str, force: bool = False) -> 'Reference': return self._repository.create_reference(name, target, force) - def delete(self, name: str): + def delete(self, name: str) -> None: self[name].delete() - def __contains__(self, name: str): + def __contains__(self, name: str) -> bool: return self.get(name) is not None @property - def objects(self): + def objects(self) -> list['Reference']: return self._repository.listall_reference_objects() - def compress(self): + def compress(self) -> None: return self._repository.compress_references() diff --git a/pygit2/refspec.py b/pygit2/refspec.py index 70b0f499..968f7fdd 100644 --- a/pygit2/refspec.py +++ b/pygit2/refspec.py @@ -23,6 +23,8 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from typing import Callable + # Import from pygit2 from .errors import check_error from .ffi import C, ffi @@ -37,22 +39,22 @@ def __init__(self, owner, ptr): self._refspec = ptr @property - def src(self): + def src(self) -> str: """Source or lhs of the refspec""" return ffi.string(C.git_refspec_src(self._refspec)).decode('utf-8') @property - def dst(self): + def dst(self) -> str: """Destination or rhs of the refspec""" return ffi.string(C.git_refspec_dst(self._refspec)).decode('utf-8') @property - def force(self): + def force(self) -> bool: """Whether this refspeca llows non-fast-forward updates""" return bool(C.git_refspec_force(self._refspec)) @property - def string(self): + def string(self) -> str: """String which was used to create this refspec""" return ffi.string(C.git_refspec_string(self._refspec)).decode('utf-8') @@ -61,18 +63,18 @@ def direction(self): """Direction of this refspec (fetch or push)""" return C.git_refspec_direction(self._refspec) - def src_matches(self, ref): + def src_matches(self, ref: str) -> bool: """Return True if the given string matches the source of this refspec, False otherwise. """ return bool(C.git_refspec_src_matches(self._refspec, to_bytes(ref))) - def dst_matches(self, ref): + def dst_matches(self, ref: str) -> bool: """Return True if the given string matches the destination of this refspec, False otherwise.""" return bool(C.git_refspec_dst_matches(self._refspec, to_bytes(ref))) - def _transform(self, ref, fn): + def _transform(self, ref: str, fn: Callable) -> str: buf = ffi.new('git_buf *', (ffi.NULL, 0)) err = fn(buf, self._refspec, to_bytes(ref)) check_error(err) @@ -82,13 +84,13 @@ def _transform(self, ref, fn): finally: C.git_buf_dispose(buf) - def transform(self, ref): + def transform(self, ref: str) -> str: """Transform a reference name according to this refspec from the lhs to the rhs. Return an string. """ return self._transform(ref, C.git_refspec_transform) - def rtransform(self, ref): + def rtransform(self, ref: str) -> str: """Transform a reference name according to this refspec from the lhs to the rhs. Return an string. """ diff --git a/pygit2/remotes.py b/pygit2/remotes.py index 93f18479..0c7d3c98 100644 --- a/pygit2/remotes.py +++ b/pygit2/remotes.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, TypedDict +from typing import TYPE_CHECKING, Any, Generator, Iterator, Literal, TypedDict # Import from pygit2 from pygit2 import RemoteCallbacks @@ -46,6 +46,7 @@ # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: + from ._libgit2.ffi import GitRemoteC, char_pointer from .repository import BaseRepository @@ -92,34 +93,39 @@ def __init__(self, tp: Any) -> None: class Remote: - def __init__(self, repo: BaseRepository, ptr): + def __init__(self, repo: BaseRepository, ptr: 'GitRemoteC') -> None: """The constructor is for internal use only.""" self._repo = repo self._remote = ptr self._stored_exception = None - def __del__(self): + def __del__(self) -> None: C.git_remote_free(self._remote) @property - def name(self): + def name(self) -> str | None: """Name of the remote""" return maybe_string(C.git_remote_name(self._remote)) @property - def url(self): + def url(self) -> str | None: """Url of the remote""" return maybe_string(C.git_remote_url(self._remote)) @property - def push_url(self): + def push_url(self) -> str | None: """Push url of the remote""" return maybe_string(C.git_remote_pushurl(self._remote)) - def connect(self, callbacks=None, direction=C.GIT_DIRECTION_FETCH, proxy=None): + def connect( + self, + callbacks: RemoteCallbacks | None = None, + direction: int = C.GIT_DIRECTION_FETCH, + proxy: None | bool | str = None, + ) -> None: """Connect to the remote. Parameters: @@ -144,13 +150,13 @@ def connect(self, callbacks=None, direction=C.GIT_DIRECTION_FETCH, proxy=None): def fetch( self, - refspecs=None, - message=None, - callbacks=None, + refspecs: list[str] | None = None, + message: str | None = None, + callbacks: RemoteCallbacks | None = None, prune: FetchPrune = FetchPrune.UNSPECIFIED, - proxy=None, - depth=0, - ): + proxy: None | Literal[True] | str = None, + depth: int = 0, + ) -> TransferProgress: """Perform a fetch against this remote. Returns a object. @@ -241,18 +247,18 @@ def prune(self, callbacks: RemoteCallbacks | None = None) -> None: payload.check_error(err) @property - def refspec_count(self): + def refspec_count(self) -> int: """Total number of refspecs in this remote""" return C.git_remote_refspec_count(self._remote) - def get_refspec(self, n): + def get_refspec(self, n: int) -> Refspec: """Return the object at the given position.""" spec = C.git_remote_get_refspec(self._remote, n) return Refspec(self, spec) @property - def fetch_refspecs(self): + def fetch_refspecs(self) -> list[str]: """Refspecs that will be used for fetching""" specs = ffi.new('git_strarray *') @@ -261,7 +267,7 @@ def fetch_refspecs(self): return strarray_to_strings(specs) @property - def push_refspecs(self): + def push_refspecs(self) -> list[str]: """Refspecs that will be used for pushing""" specs = ffi.new('git_strarray *') @@ -334,16 +340,16 @@ class RemoteCollection: >>> repo.remotes["origin"] """ - def __init__(self, repo: BaseRepository): + def __init__(self, repo: BaseRepository) -> None: self._repo = repo - def __len__(self): + def __len__(self) -> int: with utils.new_git_strarray() as names: err = C.git_remote_list(names, self._repo._repo) check_error(err) return names.count - def __iter__(self): + def __iter__(self) -> Iterator[Remote]: cremote = ffi.new('git_remote **') for name in self._ffi_names(): err = C.git_remote_lookup(cremote, self._repo._repo, name) @@ -351,7 +357,7 @@ def __iter__(self): yield Remote(self._repo, cremote[0]) - def __getitem__(self, name): + def __getitem__(self, name: str | int) -> Remote: if isinstance(name, int): return list(self)[name] @@ -361,19 +367,19 @@ def __getitem__(self, name): return Remote(self._repo, cremote[0]) - def _ffi_names(self): + def _ffi_names(self) -> Generator['char_pointer', None, None]: with utils.new_git_strarray() as names: err = C.git_remote_list(names, self._repo._repo) check_error(err) for i in range(names.count): - yield names.strings[i] + yield names.strings[i] # type: ignore[index] - def names(self): + def names(self) -> Generator[str | None, None, None]: """An iterator over the names of the available remotes.""" for name in self._ffi_names(): yield maybe_string(name) - def create(self, name, url, fetch=None) -> Remote: + def create(self, name: str, url: str, fetch: str | None = None) -> Remote: """Create a new remote with the given name and url. Returns a object. @@ -382,31 +388,31 @@ def create(self, name, url, fetch=None) -> Remote: """ cremote = ffi.new('git_remote **') - name = to_bytes(name) - url = to_bytes(url) + name_bytes = to_bytes(name) + url_bytes = to_bytes(url) if fetch: - fetch = to_bytes(fetch) + fetch_bytes = to_bytes(fetch) err = C.git_remote_create_with_fetchspec( - cremote, self._repo._repo, name, url, fetch + cremote, self._repo._repo, name_bytes, url_bytes, fetch_bytes ) else: - err = C.git_remote_create(cremote, self._repo._repo, name, url) + err = C.git_remote_create(cremote, self._repo._repo, name_bytes, url_bytes) check_error(err) return Remote(self._repo, cremote[0]) - def create_anonymous(self, url): + def create_anonymous(self, url: str) -> Remote: """Create a new anonymous (in-memory only) remote with the given URL. Returns a object. """ cremote = ffi.new('git_remote **') - url = to_bytes(url) - err = C.git_remote_create_anonymous(cremote, self._repo._repo, url) + url_bytes = to_bytes(url) + err = C.git_remote_create_anonymous(cremote, self._repo._repo, url_bytes) check_error(err) return Remote(self._repo, cremote[0]) - def rename(self, name, new_name): + def rename(self, name: str, new_name: str) -> list[str]: """Rename a remote in the configuration. The refspecs in standard format will be renamed. @@ -427,7 +433,7 @@ def rename(self, name, new_name): check_error(err) return strarray_to_strings(problems) - def delete(self, name): + def delete(self, name: str) -> None: """Remove a remote from the configuration All remote-tracking branches and configuration settings for the remote will be removed. @@ -435,17 +441,17 @@ def delete(self, name): err = C.git_remote_delete(self._repo._repo, to_bytes(name)) check_error(err) - def set_url(self, name, url): + def set_url(self, name: str, url: str) -> None: """Set the URL for a remote""" err = C.git_remote_set_url(self._repo._repo, to_bytes(name), to_bytes(url)) check_error(err) - def set_push_url(self, name, url): + def set_push_url(self, name: str, url: str) -> None: """Set the push-URL for a remote""" err = C.git_remote_set_pushurl(self._repo._repo, to_bytes(name), to_bytes(url)) check_error(err) - def add_fetch(self, name, refspec): + def add_fetch(self, name: str, refspec: str) -> None: """Add a fetch refspec (str) to the remote""" err = C.git_remote_add_fetch( @@ -453,7 +459,7 @@ def add_fetch(self, name, refspec): ) check_error(err) - def add_push(self, name, refspec): + def add_push(self, name: str, refspec: str) -> None: """Add a push refspec (str) to the remote""" err = C.git_remote_add_push(self._repo._repo, to_bytes(name), to_bytes(refspec)) diff --git a/pygit2/repository.py b/pygit2/repository.py index df54065f..94e8e091 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -22,34 +22,39 @@ # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. + import tarfile -import typing import warnings from io import BytesIO -from os import PathLike +from pathlib import Path from string import hexdigits from time import time -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Iterator, Optional, overload +# Import from pygit2 from ._pygit2 import ( GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN, Blob, Commit, + Diff, InvalidSpecError, Object, Oid, + Patch, Reference, Signature, Tree, init_file_backend, ) - -# Import from pygit2 from ._pygit2 import Repository as _Repository from .blame import Blame from .branches import Branches -from .callbacks import git_checkout_options, git_stash_apply_options +from .callbacks import ( + StashApplyCallbacks, + git_checkout_options, + git_stash_apply_options, +) from .config import Config from .enums import ( AttrCheck, @@ -75,15 +80,41 @@ from .utils import StrArray, to_bytes if TYPE_CHECKING: - from pygit2._libgit2.ffi import GitRepositoryC + from pygit2._libgit2.ffi import ( + ArrayC, + GitMergeOptionsC, + GitRepositoryC, + _Pointer, + char, + ) + from pygit2._pygit2 import Odb, Refdb, RefdbBackend class BaseRepository(_Repository): - def __init__(self, *args, **kwargs): + _pointer: '_Pointer[GitRepositoryC]' + _repo: 'GitRepositoryC' + backend: 'RefdbBackend' + default_signature: Signature + head: Reference + head_is_detached: bool + head_is_unborn: bool + is_bare: bool + is_empty: bool + is_shallow: bool + odb: 'Odb' + path: str + refdb: 'Refdb' + workdir: str + references: References + remotes: RemoteCollection + branches: Branches + submodules: SubmoduleCollection + + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._common_init() - def _common_init(self): + def _common_init(self) -> None: self.branches = Branches(self) self.references = References(self) self.remotes = RemoteCollection(self) @@ -96,22 +127,27 @@ def _common_init(self): self._repo = repo_cptr[0] # Backwards compatible ODB access - def read(self, *args, **kwargs): + def read(self, oid: Oid | str) -> tuple[int, bytes]: """read(oid) -> type, data, size Read raw object data from the repository. """ - return self.odb.read(*args, **kwargs) + return self.odb.read(oid) - def write(self, *args, **kwargs): + def write(self, type: int, data: bytes | str) -> Oid: """write(type, data) -> Oid Write raw object data into the repository. First arg is the object type, the second one a buffer with data. Return the Oid of the created object.""" - return self.odb.write(*args, **kwargs) + return self.odb.write(type, data) - def pack(self, path=None, pack_delegate=None, n_threads=None): + def pack( + self, + path: str | Path | None = None, + pack_delegate: Callable[[PackBuilder], None] | None = None, + n_threads: int | None = None, + ) -> int: """Pack the objects in the odb chosen by the pack_delegate function and write `.pack` and `.idx` files for them. @@ -147,8 +183,8 @@ def hashfile( self, path: str, object_type: ObjectType = ObjectType.BLOB, - as_path: typing.Optional[str] = None, - ): + as_path: str | None = None, + ) -> Oid: """Calculate the hash of a file using repository filtering rules. If you simply want to calculate the hash of a file on disk with no filters, @@ -196,13 +232,13 @@ def hashfile( oid = Oid(raw=bytes(ffi.buffer(c_oid.id)[:])) return oid - def __iter__(self): + def __iter__(self) -> Iterator[Oid]: return iter(self.odb) # # Mapping interface # - def get(self, key: str | Oid, default: Optional[Commit] = None) -> Object: + def get(self, key: Oid | str, default: Optional[Commit] = None) -> None | Object: value = self.git_object_lookup_prefix(key) return value if (value is not None) else default @@ -222,7 +258,7 @@ def __repr__(self) -> str: # Configuration # @property - def config(self): + def config(self) -> Config: """The configuration file for this repository. If a the configuration hasn't been set yet, the default config for @@ -251,7 +287,13 @@ def config_snapshot(self): # # References # - def create_reference(self, name, target, force=False, message=None): + def create_reference( + self, + name: str, + target: Oid | str, + force: bool = False, + message: str | None = None, + ) -> 'Reference': """Create a new reference "name" which points to an object or to another reference. @@ -273,25 +315,26 @@ def create_reference(self, name, target, force=False, message=None): repo.create_reference('refs/tags/foo', 'refs/heads/master') repo.create_reference('refs/tags/foo', 'bbb78a9cec580') """ - direct = type(target) is Oid or ( + direct = isinstance(target, Oid) or ( all(c in hexdigits for c in target) and GIT_OID_MINPREFIXLEN <= len(target) <= GIT_OID_HEXSZ ) - if direct: + # duplicate isinstance call for mypy + if direct or isinstance(target, Oid): return self.create_reference_direct(name, target, force, message=message) return self.create_reference_symbolic(name, target, force, message=message) - def listall_references(self) -> typing.List[str]: + def listall_references(self) -> list[str]: """Return a list with all the references in the repository.""" return list(x.name for x in self.references.iterator()) - def listall_reference_objects(self) -> typing.List[Reference]: + def listall_reference_objects(self) -> list[Reference]: """Return a list with all the reference objects in the repository.""" return list(x for x in self.references.iterator()) - def resolve_refish(self, refish): + def resolve_refish(self, refish: str) -> tuple[Commit, Reference]: """Convert a reference-like short name "ref-ish" to a valid (commit, reference) pair. @@ -311,9 +354,9 @@ def resolve_refish(self, refish): reference = None commit = self.revparse_single(refish) else: - commit = reference.peel(Commit) + commit = reference.peel(Commit) # type: ignore - return (commit, reference) + return (commit, reference) # type: ignore # # Checkout @@ -352,7 +395,11 @@ def checkout_tree(self, treeish, **kwargs): err = C.git_checkout_tree(self._repo, cptr[0], payload.checkout_options) payload.check_error(err) - def checkout(self, refname=None, **kwargs): + def checkout( + self, + refname: str | None | Reference = None, + **kwargs, + ) -> None: """ Checkout the given reference using the given strategy, and update the HEAD. @@ -420,7 +467,7 @@ def checkout(self, refname=None, **kwargs): # # Setting HEAD # - def set_head(self, target): + def set_head(self, target: Oid | str) -> None: """ Set HEAD to point to the given target. @@ -467,15 +514,35 @@ def __whatever_to_tree_or_blob(self, obj): return obj + @overload def diff( self, - a=None, - b=None, - cached=False, + a: None | str | bytes | Commit | Oid | Reference = None, + b: None | str | bytes | Commit | Oid | Reference = None, + cached: bool = False, flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, - ): + ) -> Diff: ... + @overload + def diff( + self, + a: Blob | None = None, + b: Blob | None = None, + cached: bool = False, + flags: DiffOption = DiffOption.NORMAL, + context_lines: int = 3, + interhunk_lines: int = 0, + ) -> Patch: ... + def diff( + self, + a: None | Blob | str | bytes | Commit | Oid | Reference = None, + b: None | Blob | str | bytes | Commit | Oid | Reference = None, + cached: bool = False, + flags: DiffOption = DiffOption.NORMAL, + context_lines: int = 3, + interhunk_lines: int = 0, + ) -> Diff | Patch: """ Show changes between the working tree and the index or a tree, changes between the index and a tree, changes between two trees, or @@ -576,7 +643,7 @@ def state(self) -> RepositoryState: # Some value not in the IntEnum - newer libgit2 version? return cstate # type: ignore[return-value] - def state_cleanup(self): + def state_cleanup(self) -> None: """Remove all the metadata associated with an ongoing command like merge, revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, etc. @@ -588,14 +655,14 @@ def state_cleanup(self): # def blame( self, - path, + path: str, flags: BlameFlag = BlameFlag.NORMAL, - min_match_characters=None, - newest_commit=None, - oldest_commit=None, - min_line=None, - max_line=None, - ): + min_match_characters: int | None = None, + newest_commit: Oid | str | None = None, + oldest_commit: Oid | str | None = None, + min_line: int | None = None, + max_line: int | None = None, + ) -> Blame: """ Return a Blame object for a single file. @@ -672,7 +739,7 @@ def index(self): @staticmethod def _merge_options( favor: int | MergeFavor, flags: int | MergeFlag, file_flags: int | MergeFileFlag - ): + ) -> 'GitMergeOptionsC': """Return a 'git_merge_opts *'""" # Check arguments type @@ -697,11 +764,11 @@ def _merge_options( def merge_file_from_index( self, - ancestor: typing.Union[None, IndexEntry], - ours: typing.Union[None, IndexEntry], - theirs: typing.Union[None, IndexEntry], + ancestor: 'IndexEntry | None', + ours: 'IndexEntry | None', + theirs: 'IndexEntry | None', use_deprecated: bool = True, - ) -> typing.Union[str, typing.Union[MergeFileResult, None]]: + ) -> 'str | MergeFileResult | None': """Merge files from index. Returns: A string with the content of the file containing @@ -751,12 +818,12 @@ def merge_file_from_index( def merge_commits( self, - ours: typing.Union[str, Oid, Commit], - theirs: typing.Union[str, Oid, Commit], - favor=MergeFavor.NORMAL, - flags=MergeFlag.FIND_RENAMES, - file_flags=MergeFileFlag.DEFAULT, - ) -> Index: + ours: str | Oid | Commit, + theirs: str | Oid | Commit, + favor: MergeFavor = MergeFavor.NORMAL, + flags: MergeFlag = MergeFlag.FIND_RENAMES, + file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, + ) -> 'Index': """ Merge two arbitrary commits. @@ -813,13 +880,13 @@ def merge_commits( def merge_trees( self, - ancestor: typing.Union[str, Oid, Tree], - ours: typing.Union[str, Oid, Tree], - theirs: typing.Union[str, Oid, Tree], - favor=MergeFavor.NORMAL, - flags=MergeFlag.FIND_RENAMES, - file_flags=MergeFileFlag.DEFAULT, - ): + ancestor: str | Oid | Tree, + ours: str | Oid | Tree, + theirs: str | Oid | Tree, + favor: MergeFavor = MergeFavor.NORMAL, + flags: MergeFlag = MergeFlag.FIND_RENAMES, + file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, + ) -> 'Index': """ Merge two trees. @@ -870,11 +937,11 @@ def merge_trees( def merge( self, - source: typing.Union[Reference, Commit, Oid, str], - favor=MergeFavor.NORMAL, - flags=MergeFlag.FIND_RENAMES, - file_flags=MergeFileFlag.DEFAULT, - ): + source: Reference | Commit | Oid | str, + favor: MergeFavor = MergeFavor.NORMAL, + flags: MergeFlag = MergeFlag.FIND_RENAMES, + file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, + ) -> None: """ Merges the given Reference or Commit into HEAD. @@ -983,7 +1050,7 @@ def message(self) -> str: """ return self.raw_message.decode('utf-8') - def remove_message(self): + def remove_message(self) -> None: """ Remove git's prepared message. """ @@ -995,16 +1062,16 @@ def remove_message(self): # def describe( self, - committish=None, - max_candidates_tags=None, + committish: str | Reference | Commit | None = None, + max_candidates_tags: int | None = None, describe_strategy: DescribeStrategy = DescribeStrategy.DEFAULT, - pattern=None, - only_follow_first_parent=None, - show_commit_oid_as_fallback=None, - abbreviated_size=None, - always_use_long_format=None, - dirty_suffix=None, - ): + pattern: str | None = None, + only_follow_first_parent: bool | None = None, + show_commit_oid_as_fallback: bool | None = None, + abbreviated_size: int | None = None, + always_use_long_format: bool | None = None, + dirty_suffix: str | None = None, + ) -> str: """ Describe a commit-ish or the current working tree. @@ -1078,10 +1145,13 @@ def describe( result = ffi.new('git_describe_result **') if committish: + committish_rev: Object | Reference | Commit if isinstance(committish, str): - committish = self.revparse_single(committish) + committish_rev = self.revparse_single(committish) + else: + committish_rev = committish - commit = committish.peel(Commit) + commit = committish_rev.peel(Commit) cptr = ffi.new('git_object **') ffi.buffer(cptr)[:] = commit._pointer[:] @@ -1124,13 +1194,13 @@ def describe( def stash( self, stasher: Signature, - message: typing.Optional[str] = None, + message: str | None = None, keep_index: bool = False, include_untracked: bool = False, include_ignored: bool = False, keep_all: bool = False, - paths: typing.Optional[typing.List[str]] = None, - ): + paths: list[str] | None = None, + ) -> Oid: """ Save changes to the working directory to the stash. @@ -1194,7 +1264,13 @@ def stash( return Oid(raw=bytes(ffi.buffer(coid)[:])) - def stash_apply(self, index=0, **kwargs): + def stash_apply( + self, + index: int = 0, + reinstate_index: bool = False, + strategy: CheckoutStrategy | None = None, + callbacks: StashApplyCallbacks | None = None, + ) -> None: """ Apply a stashed state in the stash list to the working directory. @@ -1228,11 +1304,15 @@ def stash_apply(self, index=0, **kwargs): >>> repo.stash(repo.default_signature(), 'WIP: stashing') >>> repo.stash_apply(strategy=CheckoutStrategy.ALLOW_CONFLICTS) """ - with git_stash_apply_options(**kwargs) as payload: + with git_stash_apply_options( + reinstate_index=reinstate_index, + strategy=strategy, + callbacks=callbacks, + ) as payload: err = C.git_stash_apply(self._repo, index, payload.stash_apply_options) payload.check_error(err) - def stash_drop(self, index=0): + def stash_drop(self, index: int = 0) -> None: """ Remove a stashed state from the stash list. @@ -1244,19 +1324,35 @@ def stash_drop(self, index=0): """ check_error(C.git_stash_drop(self._repo, index)) - def stash_pop(self, index=0, **kwargs): + def stash_pop( + self, + index: int = 0, + reinstate_index: bool = False, + strategy: CheckoutStrategy | None = None, + callbacks: StashApplyCallbacks | None = None, + ) -> None: """Apply a stashed state and remove it from the stash list. For arguments, see Repository.stash_apply(). """ - with git_stash_apply_options(**kwargs) as payload: + with git_stash_apply_options( + reinstate_index=reinstate_index, + strategy=strategy, + callbacks=callbacks, + ) as payload: err = C.git_stash_pop(self._repo, index, payload.stash_apply_options) payload.check_error(err) # # Utility for writing a tree into an archive # - def write_archive(self, treeish, archive, timestamp=None, prefix=''): + def write_archive( + self, + treeish: str | Tree | Object | Oid, + archive: tarfile.TarFile, + timestamp: int | None = None, + prefix: str = '', + ) -> None: """ Write treeish into an archive. @@ -1328,7 +1424,7 @@ def write_archive(self, treeish, archive, timestamp=None, prefix=''): # # Ahead-behind, which mostly lives on its own namespace # - def ahead_behind(self, local, upstream): + def ahead_behind(self, local: Oid | str, upstream: Oid | str) -> tuple[int, int]: """ Calculate how many different commits are in the non-common parts of the history between the two given ids. @@ -1368,11 +1464,11 @@ def ahead_behind(self, local, upstream): # def get_attr( self, - path: typing.Union[str, bytes, PathLike], - name: typing.Union[str, bytes], + path: str | bytes | Path, + name: str | bytes, flags: AttrCheck = AttrCheck.FILE_THEN_INDEX, - commit: typing.Union[Oid, str, None] = None, - ) -> typing.Union[bool, None, str]: + commit: Oid | str | None = None, + ) -> bool | None | str: """ Retrieve an attribute for a file by path. @@ -1445,7 +1541,7 @@ def ident(self): return (ffi.string(cname).decode('utf-8'), ffi.string(cemail).decode('utf-8')) - def set_ident(self, name, email): + def set_ident(self, name: str, email: str) -> None: """Set the identity to be used for reference operations. Updates to some references also append data to their @@ -1456,7 +1552,7 @@ def set_ident(self, name, email): err = C.git_repository_set_ident(self._repo, to_bytes(name), to_bytes(email)) check_error(err) - def revert(self, commit: Commit): + def revert(self, commit: Commit) -> None: """ Revert the given commit, producing changes in the index and working directory. @@ -1469,7 +1565,9 @@ def revert(self, commit: Commit): err = C.git_revert(self._repo, commit_ptr[0], ffi.NULL) check_error(err) - def revert_commit(self, revert_commit, our_commit, mainline=0): + def revert_commit( + self, revert_commit: Commit, our_commit: Commit, mainline: int = 0 + ) -> Index: """ Revert the given Commit against the given "our" Commit, producing an Index that reflects the result of the revert. @@ -1510,14 +1608,14 @@ def revert_commit(self, revert_commit, our_commit, mainline=0): # def amend_commit( self, - commit, - refname, - author=None, - committer=None, - message=None, - tree=None, - encoding='UTF-8', - ): + commit: Commit | Oid | str, + refname: Reference | str | None, + author: Signature | None = None, + committer: Signature | None = None, + message: str | None = None, + tree: Tree | Oid | str | None = None, + encoding: str = 'UTF-8', + ) -> Oid: """ Amend an existing commit by replacing only explicitly passed values, return the rewritten commit's oid. @@ -1566,24 +1664,24 @@ def amend_commit( # Note: the pointers are all initialized to NULL by default. coid = ffi.new('git_oid *') commit_cptr = ffi.new('git_commit **') - refname_cstr = ffi.NULL + refname_cstr: 'ArrayC[char]' | 'ffi.NULL_TYPE' = ffi.NULL author_cptr = ffi.new('git_signature **') committer_cptr = ffi.new('git_signature **') - message_cstr = ffi.NULL - encoding_cstr = ffi.NULL + message_cstr: 'ArrayC[char]' | 'ffi.NULL_TYPE' = ffi.NULL + encoding_cstr: 'ArrayC[char]' | 'ffi.NULL_TYPE' = ffi.NULL tree_cptr = ffi.new('git_tree **') # Get commit as pointer to git_commit. if isinstance(commit, (str, Oid)): - commit = self[commit] + commit_object = self[commit] + commit_commit = commit_object.peel(Commit) elif isinstance(commit, Commit): - pass + commit_commit = commit elif commit is None: raise ValueError('the commit to amend cannot be None') else: raise TypeError('the commit to amend must be a Commit, str, or Oid') - commit = commit.peel(Commit) - ffi.buffer(commit_cptr)[:] = commit._pointer[:] + ffi.buffer(commit_cptr)[:] = commit_commit._pointer[:] # Get refname as C string. if isinstance(refname, Reference): @@ -1613,9 +1711,11 @@ def amend_commit( # Get tree as pointer to git_tree. if tree is not None: if isinstance(tree, (str, Oid)): - tree = self[tree] - tree = tree.peel(Tree) - ffi.buffer(tree_cptr)[:] = tree._pointer[:] + tree_object = self[tree] + else: + tree_object = tree + tree_tree = tree_object.peel(Tree) + ffi.buffer(tree_cptr)[:] = tree_tree._pointer[:] # Amend the commit. err = C.git_commit_amend( @@ -1641,7 +1741,7 @@ def __ensure_tree(self, maybe_tree: str | Oid | Tree) -> Tree: class Repository(BaseRepository): def __init__( self, - path: typing.Optional[str] = None, + path: str | bytes | None | Path = None, flags: RepositoryOpenFlag = RepositoryOpenFlag.DEFAULT, ): """ diff --git a/pygit2/submodules.py b/pygit2/submodules.py index 0f98e4f4..46227e77 100644 --- a/pygit2/submodules.py +++ b/pygit2/submodules.py @@ -65,7 +65,7 @@ def open(self) -> Repository: err = C.git_submodule_open(crepo, self._subm) check_error(err) - return self._repo._from_c(crepo[0], True) + return self._repo._from_c(crepo[0], True) # type: ignore[attr-defined] def init(self, overwrite: bool = False) -> None: """ @@ -86,7 +86,7 @@ def update( init: bool = False, callbacks: Optional[RemoteCallbacks] = None, depth: int = 0, - ): + ) -> None: """ Update a submodule. This will clone a missing submodule and checkout the subrepository to the commit specified in the index of the @@ -119,7 +119,7 @@ def update( err = C.git_submodule_update(self._subm, int(init), opts) payload.check_error(err) - def reload(self, force: bool = False): + def reload(self, force: bool = False) -> None: """ Reread submodule info from config, index, and HEAD. @@ -272,7 +272,9 @@ def add( check_error(err) return submodule_instance - def init(self, submodules: Optional[Iterable[str]] = None, overwrite: bool = False): + def init( + self, submodules: Optional[Iterable[str]] = None, overwrite: bool = False + ) -> None: """ Initialize submodules in the repository. Just like "git submodule init", this copies information about the submodules into ".git/config". @@ -300,7 +302,7 @@ def update( init: bool = False, callbacks: Optional[RemoteCallbacks] = None, depth: int = 0, - ): + ) -> None: """ Update submodules. This will clone a missing submodule and checkout the subrepository to the commit specified in the index of the @@ -357,7 +359,7 @@ def status( check_error(err) return SubmoduleStatus(cstatus[0]) - def cache_all(self): + def cache_all(self) -> None: """ Load and cache all submodules in the repository. @@ -370,7 +372,7 @@ def cache_all(self): err = C.git_repository_submodule_cache_all(self._repository._repo) check_error(err) - def cache_clear(self): + def cache_clear(self) -> None: """ Clear the submodule cache populated by `submodule_cache_all`. If there is no cache, do nothing. diff --git a/pygit2/utils.py b/pygit2/utils.py index 8b01d3a1..d00b7626 100644 --- a/pygit2/utils.py +++ b/pygit2/utils.py @@ -28,6 +28,7 @@ from types import TracebackType from typing import ( TYPE_CHECKING, + Generator, Generic, Iterator, Optional, @@ -42,10 +43,10 @@ from .ffi import C, ffi if TYPE_CHECKING: - from ._libgit2.ffi import ArrayC, GitStrrayC, char + from ._libgit2.ffi import ArrayC, GitStrrayC, char, char_pointer -def maybe_string(ptr): +def maybe_string(ptr: 'char_pointer | None') -> str | None: if not ptr: return None @@ -105,7 +106,7 @@ def ptr_to_bytes(ptr_cdata): @contextlib.contextmanager -def new_git_strarray(): +def new_git_strarray() -> Generator['GitStrrayC', None, None]: strarray = ffi.new('git_strarray *') yield strarray C.git_strarray_dispose(strarray) diff --git a/test/test_branch.py b/test/test_branch.py index f5bff9d3..63923cd6 100644 --- a/test/test_branch.py +++ b/test/test_branch.py @@ -58,6 +58,7 @@ def test_branches(testrepo: Repository) -> None: def test_branches_create(testrepo: Repository) -> None: commit = testrepo[LAST_COMMIT] + assert isinstance(commit, Commit) reference = testrepo.branches.create('version1', commit) assert 'version1' in testrepo.branches reference = testrepo.branches['version1'] @@ -142,7 +143,9 @@ def test_branches_with_commit(testrepo: Repository) -> None: branches = testrepo.branches.with_commit(LAST_COMMIT) assert sorted(branches) == ['master'] - branches = testrepo.branches.with_commit(testrepo[LAST_COMMIT]) + commit = testrepo[LAST_COMMIT] + assert isinstance(commit, Commit) + branches = testrepo.branches.with_commit(commit) assert sorted(branches) == ['master'] branches = testrepo.branches.remote.with_commit(LAST_COMMIT) diff --git a/test/test_branch_empty.py b/test/test_branch_empty.py index c3a736df..9bfe0088 100644 --- a/test/test_branch_empty.py +++ b/test/test_branch_empty.py @@ -58,7 +58,9 @@ def test_branches_remote_getitem(repo: Repository) -> None: def test_branches_upstream(repo: Repository) -> None: remote_master = repo.branches.remote['origin/master'] - master = repo.branches.create('master', repo[remote_master.target]) + commit = repo[remote_master.target] + assert isinstance(commit, Commit) + master = repo.branches.create('master', commit) assert master.upstream is None master.upstream = remote_master @@ -76,7 +78,9 @@ def set_bad_upstream(): def test_branches_upstream_name(repo: Repository) -> None: remote_master = repo.branches.remote['origin/master'] - master = repo.branches.create('master', repo[remote_master.target]) + commit = repo[remote_master.target] + assert isinstance(commit, Commit) + master = repo.branches.create('master', commit) master.upstream = remote_master assert master.upstream_name == 'refs/remotes/origin/master' diff --git a/test/test_config.py b/test/test_config.py index d3416a9a..f7e63112 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -105,9 +105,9 @@ def test_add_aspath() -> None: def test_read(config: Config) -> None: with pytest.raises(TypeError): - config[()] + config[()] # type: ignore with pytest.raises(TypeError): - config[-4] + config[-4] # type: ignore utils.assertRaisesWithArg( ValueError, "invalid config item name 'abc'", lambda: config['abc'] ) @@ -123,7 +123,7 @@ def test_read(config: Config) -> None: def test_write(config: Config) -> None: with pytest.raises(TypeError): - config.__setitem__((), 'This should not work') + config.__setitem__((), 'This should not work') # type: ignore assert 'core.dummy1' not in config config['core.dummy1'] = 42 diff --git a/test/test_refs.py b/test/test_refs.py index 5d441fcc..9ad830b7 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -35,6 +35,7 @@ GitError, InvalidSpecError, Oid, + Reference, Repository, Signature, Tree, @@ -84,17 +85,20 @@ def test_refs_getitem(testrepo: Repository) -> None: # Test a lookup reference = testrepo.references.get('refs/heads/master') + assert reference is not None assert reference.name == 'refs/heads/master' def test_refs_get_sha(testrepo: Repository) -> None: reference = testrepo.references['refs/heads/master'] + assert reference is not None assert reference.target == LAST_COMMIT def test_refs_set_sha(testrepo: Repository) -> None: NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' reference = testrepo.references.get('refs/heads/master') + assert reference is not None reference.set_target(NEW_COMMIT) assert reference.target == NEW_COMMIT @@ -102,23 +106,27 @@ def test_refs_set_sha(testrepo: Repository) -> None: def test_refs_set_sha_prefix(testrepo: Repository) -> None: NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' reference = testrepo.references.get('refs/heads/master') + assert reference is not None reference.set_target(NEW_COMMIT[0:6]) assert reference.target == NEW_COMMIT def test_refs_get_type(testrepo: Repository) -> None: reference = testrepo.references.get('refs/heads/master') + assert reference is not None assert reference.type == ReferenceType.DIRECT def test_refs_get_target(testrepo: Repository) -> None: reference = testrepo.references.get('HEAD') + assert reference is not None assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' def test_refs_set_target(testrepo: Repository) -> None: reference = testrepo.references.get('HEAD') + assert reference is not None assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' reference.set_target('refs/heads/i18n') @@ -128,6 +136,7 @@ def test_refs_set_target(testrepo: Repository) -> None: def test_refs_get_shorthand(testrepo: Repository) -> None: reference = testrepo.references.get('refs/heads/master') + assert reference is not None assert reference.shorthand == 'master' reference = testrepo.references.create('refs/remotes/origin/master', LAST_COMMIT) assert reference.shorthand == 'origin/master' @@ -135,6 +144,7 @@ def test_refs_get_shorthand(testrepo: Repository) -> None: def test_refs_set_target_with_message(testrepo: Repository) -> None: reference = testrepo.references.get('HEAD') + assert reference is not None assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' sig = Signature('foo', 'bar') @@ -198,6 +208,7 @@ def test_refs_rename(testrepo: Repository) -> None: def test_refs_resolve(testrepo: Repository) -> None: reference = testrepo.references.get('HEAD') + assert reference is not None assert reference.type == ReferenceType.SYMBOLIC reference = reference.resolve() assert reference.type == ReferenceType.DIRECT @@ -206,16 +217,20 @@ def test_refs_resolve(testrepo: Repository) -> None: def test_refs_resolve_identity(testrepo: Repository) -> None: head = testrepo.references.get('HEAD') + assert head is not None ref = head.resolve() assert ref.resolve() is ref def test_refs_create(testrepo: Repository) -> None: # We add a tag as a new reference that points to "origin/master" - reference = testrepo.references.create('refs/tags/version1', LAST_COMMIT) + reference: Reference | None = testrepo.references.create( + 'refs/tags/version1', LAST_COMMIT + ) refs = testrepo.references assert 'refs/tags/version1' in refs reference = testrepo.references.get('refs/tags/version1') + assert reference is not None assert reference.target == LAST_COMMIT # try to create existing reference @@ -256,6 +271,7 @@ def test_refs_create_symbolic(testrepo: Repository) -> None: def test_refs_peel(testrepo: Repository) -> None: ref = testrepo.references.get('refs/heads/master') + assert ref is not None assert testrepo[ref.target].id == ref.peel().id assert not isinstance(ref.raw_target, bytes) assert testrepo[ref.raw_target].id == ref.peel().id diff --git a/test/test_repository.py b/test/test_repository.py index b98b1784..87bd4fbd 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -906,6 +906,7 @@ def test_worktree_custom_ref(testrepo: Repository) -> None: # New branch based on head tip = testrepo.revparse_single('HEAD') + assert isinstance(tip, Commit) worktree_ref = testrepo.branches.create(branch_name, tip) # Delete temp path so that it's not present when we attempt to add the # worktree later