diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 0fc6f488..825c7318 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -25,27 +25,103 @@ # Standard Library import functools -from os import PathLike import typing - -# Low level API -from ._pygit2 import * -from ._pygit2 import _cache_enums +from os import PathLike # High level API from . import enums from ._build import __version__ + +# Low level API +from ._pygit2 import * +from ._pygit2 import _cache_enums from .blame import Blame, BlameHunk from .blob import BlobIO -from .callbacks import Payload, RemoteCallbacks, CheckoutCallbacks, StashApplyCallbacks -from .callbacks import git_clone_options, git_fetch_options, get_credentials +from .callbacks import ( + CheckoutCallbacks, + Payload, + RemoteCallbacks, + StashApplyCallbacks, + get_credentials, + git_clone_options, + git_fetch_options, +) from .config import Config -from .credentials import * -from .errors import check_error, Passthrough -from .ffi import ffi, C +from .credentials import ( + Keypair, + KeypairFromAgent, + KeypairFromMemory, + Username, + UserPass, +) +from .errors import Passthrough, check_error +from .ffi import C, ffi from .filter import Filter from .index import Index, IndexEntry -from .legacyenums import * +from .legacyenums import ( + GIT_ATTR_CHECK_FILE_THEN_INDEX, + GIT_ATTR_CHECK_INCLUDE_COMMIT, + GIT_ATTR_CHECK_INCLUDE_HEAD, + GIT_ATTR_CHECK_INDEX_ONLY, + GIT_ATTR_CHECK_INDEX_THEN_FILE, + GIT_ATTR_CHECK_NO_SYSTEM, + GIT_CHECKOUT_NOTIFY_ALL, + GIT_CHECKOUT_NOTIFY_CONFLICT, + GIT_CHECKOUT_NOTIFY_DIRTY, + GIT_CHECKOUT_NOTIFY_IGNORED, + GIT_CHECKOUT_NOTIFY_NONE, + GIT_CHECKOUT_NOTIFY_UNTRACKED, + GIT_CHECKOUT_NOTIFY_UPDATED, + GIT_CREDENTIAL_DEFAULT, + GIT_CREDENTIAL_SSH_CUSTOM, + GIT_CREDENTIAL_SSH_INTERACTIVE, + GIT_CREDENTIAL_SSH_KEY, + GIT_CREDENTIAL_SSH_MEMORY, + GIT_CREDENTIAL_USERNAME, + GIT_CREDENTIAL_USERPASS_PLAINTEXT, + GIT_FEATURE_HTTPS, + GIT_FEATURE_NSEC, + GIT_FEATURE_SSH, + GIT_FEATURE_THREADS, + GIT_FETCH_NO_PRUNE, + GIT_FETCH_PRUNE, + GIT_FETCH_PRUNE_UNSPECIFIED, + GIT_REPOSITORY_INIT_BARE, + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE, + GIT_REPOSITORY_INIT_MKDIR, + GIT_REPOSITORY_INIT_MKPATH, + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR, + GIT_REPOSITORY_INIT_NO_REINIT, + GIT_REPOSITORY_INIT_RELATIVE_GITLINK, + GIT_REPOSITORY_INIT_SHARED_ALL, + GIT_REPOSITORY_INIT_SHARED_GROUP, + GIT_REPOSITORY_INIT_SHARED_UMASK, + GIT_REPOSITORY_OPEN_BARE, + GIT_REPOSITORY_OPEN_CROSS_FS, + GIT_REPOSITORY_OPEN_FROM_ENV, + GIT_REPOSITORY_OPEN_NO_DOTGIT, + GIT_REPOSITORY_OPEN_NO_SEARCH, + GIT_REPOSITORY_STATE_APPLY_MAILBOX, + GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE, + GIT_REPOSITORY_STATE_BISECT, + GIT_REPOSITORY_STATE_CHERRYPICK, + GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE, + GIT_REPOSITORY_STATE_MERGE, + GIT_REPOSITORY_STATE_NONE, + GIT_REPOSITORY_STATE_REBASE, + GIT_REPOSITORY_STATE_REBASE_INTERACTIVE, + GIT_REPOSITORY_STATE_REBASE_MERGE, + GIT_REPOSITORY_STATE_REVERT, + GIT_REPOSITORY_STATE_REVERT_SEQUENCE, + GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX, + GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED, + GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED, + GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED, + GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED, + GIT_STASH_APPLY_PROGRESS_DONE, + GIT_STASH_APPLY_PROGRESS_LOADING_STASH, + GIT_STASH_APPLY_PROGRESS_NONE, +) from .packbuilder import PackBuilder from .remotes import Remote from .repository import Repository @@ -53,6 +129,398 @@ from .submodules import Submodule from .utils import to_bytes, to_str +__all__ = [ + 'AlreadyExistsError', + 'Blame', + 'BlameHunk', + 'Blob', + 'BlobIO', + 'Branch', + 'C', + 'CheckoutCallbacks', + 'Commit', + 'Config', + # 'CredentialType', + 'Diff', + 'DiffDelta', + 'DiffFile', + 'DiffHunk', + 'DiffLine', + 'DiffStats', + 'Filter', + 'FilterSource', + 'GIT_APPLY_LOCATION_BOTH', + 'GIT_APPLY_LOCATION_INDEX', + 'GIT_APPLY_LOCATION_WORKDIR', + 'GIT_ATTR_CHECK_FILE_THEN_INDEX', + 'GIT_ATTR_CHECK_INCLUDE_COMMIT', + 'GIT_ATTR_CHECK_INCLUDE_HEAD', + 'GIT_ATTR_CHECK_INDEX_ONLY', + 'GIT_ATTR_CHECK_INDEX_THEN_FILE', + 'GIT_ATTR_CHECK_NO_SYSTEM', + '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_NONE', + 'GIT_CHECKOUT_NOTIFY_ALL', + 'GIT_CHECKOUT_NOTIFY_CONFLICT', + 'GIT_CHECKOUT_NOTIFY_DIRTY', + 'GIT_CHECKOUT_NOTIFY_IGNORED', + 'GIT_CHECKOUT_NOTIFY_NONE', + 'GIT_CHECKOUT_NOTIFY_UNTRACKED', + 'GIT_CHECKOUT_NOTIFY_UPDATED', + 'GIT_CHECKOUT_NO_REFRESH', + '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_CREDENTIAL_DEFAULT', + 'GIT_CREDENTIAL_SSH_CUSTOM', + 'GIT_CREDENTIAL_SSH_INTERACTIVE', + 'GIT_CREDENTIAL_SSH_KEY', + 'GIT_CREDENTIAL_SSH_MEMORY', + 'GIT_CREDENTIAL_USERNAME', + 'GIT_CREDENTIAL_USERPASS_PLAINTEXT', + '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_FEATURE_HTTPS', + 'GIT_FEATURE_NSEC', + 'GIT_FEATURE_SSH', + 'GIT_FEATURE_THREADS', + 'GIT_FETCH_NO_PRUNE', + 'GIT_FETCH_PRUNE', + 'GIT_FETCH_PRUNE_UNSPECIFIED', + '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_NONE', + 'GIT_MERGE_PREFERENCE_NO_FASTFORWARD', + '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_HEXSZ', + 'GIT_OID_HEX_ZERO', + '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_REPOSITORY_INIT_BARE', + 'GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE', + 'GIT_REPOSITORY_INIT_MKDIR', + 'GIT_REPOSITORY_INIT_MKPATH', + 'GIT_REPOSITORY_INIT_NO_DOTGIT_DIR', + 'GIT_REPOSITORY_INIT_NO_REINIT', + 'GIT_REPOSITORY_INIT_RELATIVE_GITLINK', + 'GIT_REPOSITORY_INIT_SHARED_ALL', + 'GIT_REPOSITORY_INIT_SHARED_GROUP', + 'GIT_REPOSITORY_INIT_SHARED_UMASK', + 'GIT_REPOSITORY_OPEN_BARE', + 'GIT_REPOSITORY_OPEN_CROSS_FS', + 'GIT_REPOSITORY_OPEN_FROM_ENV', + 'GIT_REPOSITORY_OPEN_NO_DOTGIT', + 'GIT_REPOSITORY_OPEN_NO_SEARCH', + 'GIT_REPOSITORY_STATE_APPLY_MAILBOX', + 'GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE', + 'GIT_REPOSITORY_STATE_BISECT', + 'GIT_REPOSITORY_STATE_CHERRYPICK', + 'GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE', + 'GIT_REPOSITORY_STATE_MERGE', + 'GIT_REPOSITORY_STATE_NONE', + 'GIT_REPOSITORY_STATE_REBASE', + 'GIT_REPOSITORY_STATE_REBASE_INTERACTIVE', + 'GIT_REPOSITORY_STATE_REBASE_MERGE', + 'GIT_REPOSITORY_STATE_REVERT', + 'GIT_REPOSITORY_STATE_REVERT_SEQUENCE', + '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_PROGRESS_ANALYZE_INDEX', + 'GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED', + 'GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED', + 'GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED', + 'GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED', + 'GIT_STASH_APPLY_PROGRESS_DONE', + 'GIT_STASH_APPLY_PROGRESS_LOADING_STASH', + 'GIT_STASH_APPLY_PROGRESS_NONE', + '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_INDEX_ADDED', + 'GIT_SUBMODULE_STATUS_INDEX_DELETED', + 'GIT_SUBMODULE_STATUS_INDEX_MODIFIED', + 'GIT_SUBMODULE_STATUS_IN_CONFIG', + 'GIT_SUBMODULE_STATUS_IN_HEAD', + 'GIT_SUBMODULE_STATUS_IN_INDEX', + 'GIT_SUBMODULE_STATUS_IN_WD', + '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', + 'GitError', + 'Index', + 'IndexEntry', + 'InvalidSpecError', + 'Keypair', + 'KeypairFromAgent', + 'KeypairFromMemory', + 'LIBGIT2_VER', + 'LIBGIT2_VERSION', + 'LIBGIT2_VER_MAJOR', + 'LIBGIT2_VER_MINOR', + 'LIBGIT2_VER_REVISION', + 'Mailmap', + 'Note', + 'Object', + 'Odb', + 'OdbBackend', + 'OdbBackendLoose', + 'OdbBackendPack', + 'Oid', + 'PackBuilder', + 'Passthrough', + 'Patch', + 'PathLike', + 'Payload', + 'RefLogEntry', + 'Refdb', + 'RefdbBackend', + 'RefdbFsBackend', + 'Reference', + 'Remote', + 'RemoteCallbacks', + 'Repository', + 'RevSpec', + 'Settings', + 'Signature', + 'Stash', + 'StashApplyCallbacks', + 'Submodule', + 'Tag', + 'Tree', + 'TreeBuilder', + 'UserPass', + 'Username', + 'Walker', + 'Worktree', + '__version__', + 'check_error', + 'clone_repository', + 'discover_repository', + 'features', + 'ffi', + 'filter', + 'filter_register', + 'filter_unregister', + 'functools', + 'get_credentials', + 'git_clone_options', + 'git_fetch_options', + 'hash', + 'hashfile', + 'init_file_backend', + 'init_repository', + 'option', + 'reference_is_valid_name', + 'settings', + 'to_bytes', + 'to_str', + 'tree_entry_key', +] # Features features = enums.Feature(C.git_libgit2_features()) @@ -66,7 +534,7 @@ def init_repository( - path: typing.Union[str, bytes, PathLike, None], + path: typing.Union[str, bytes, PathLike[str], PathLike[bytes], None], bare: bool = False, flags: enums.RepositoryInitFlag = enums.RepositoryInitFlag.MKPATH, mode: typing.Union[ @@ -144,15 +612,15 @@ def init_repository( def clone_repository( - url, - path, - bare=False, - repository=None, - remote=None, - checkout_branch=None, - callbacks=None, - depth=0, -): + url: str, + path: str, + bare: bool = False, + repository: typing.Callable[[str, bool], Repository] | None = None, + remote: typing.Callable[[Repository, str, str], Remote] | None = None, + checkout_branch: str | None = None, + callbacks: RemoteCallbacks | None = None, + depth: int = 0, +) -> Repository: """ Clones a new Git repository from *url* in the given *path*. diff --git a/pygit2/_build.py b/pygit2/_build.py index 30e0d13f..78a8aad4 100644 --- a/pygit2/_build.py +++ b/pygit2/_build.py @@ -48,7 +48,7 @@ def _get_libgit2_path(): # Default if os.name == 'nt': - return Path(r'%s\libgit2' % os.getenv('ProgramFiles')) + return Path(r'%s\libgit2' % os.getenv('ProgramFiles')) # noqa: SIM112 return Path('/usr/local') diff --git a/pygit2/_ctyping.py b/pygit2/_ctyping.py new file mode 100644 index 00000000..c5ca357e --- /dev/null +++ b/pygit2/_ctyping.py @@ -0,0 +1 @@ +# placeholder for pyright diff --git a/pygit2/_ctyping.pyi b/pygit2/_ctyping.pyi new file mode 100644 index 00000000..2be1411f --- /dev/null +++ b/pygit2/_ctyping.pyi @@ -0,0 +1,35 @@ +from _cffi_backend import _CDataBase + +class CData(_CDataBase): ... # type: ignore + +class _CSignatureTime(CData): + time: int + offset: int + +class _CSignature(CData): + name: _CDataBase + email: _CDataBase + when: _CSignatureTime + +class _COid(CData): + id: _CDataBase + +class _CHunk(CData): + boundary: CData + final_commit_id: _COid + final_signature: _CSignature + final_start_line_number: int + lines_in_hunk: int + orig_commit_id: _COid + orig_path: _CDataBase + orig_signature: _CSignature + orig_start_line_number: int + +class _CConfigEntry(CData): + backend_type: _CDataBase + free: _CDataBase + include_depth: int + level: int + name: _CDataBase + origin_path: _CDataBase + value: _CDataBase diff --git a/pygit2/_libgit2.pyi b/pygit2/_libgit2.pyi new file mode 100644 index 00000000..4e0c929a --- /dev/null +++ b/pygit2/_libgit2.pyi @@ -0,0 +1,6 @@ +from typing import Any + +import _cffi_backend + +ffi: _cffi_backend.FFI +lib: Any diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index 4895bade..03cf45dc 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -1,8 +1,14 @@ -from typing import Iterator, Literal, Optional, overload -from io import IOBase +import queue +import threading +from io import DEFAULT_BUFFER_SIZE, IOBase +from typing import Any, Callable, Iterator, Literal, Optional, TypeAlias, overload + +from pygit2.filter import Filter + from . import Index from .enums import ( ApplyLocation, + BlobFilter, BranchType, DeltaStatus, DiffFind, @@ -10,6 +16,8 @@ from .enums import ( DiffOption, DiffStatsFormat, FileMode, + FilterFlag, + FilterMode, MergeAnalysis, MergePreference, ObjectType, @@ -20,48 +28,288 @@ from .enums import ( SortMode, ) -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 +# version constants LIBGIT2_VERSION: str LIBGIT2_VER_MAJOR: int LIBGIT2_VER_MINOR: int LIBGIT2_VER_REVISION: int +# libgit2 constants +GIT_OBJECT_BLOB: Literal[3] +GIT_OBJECT_COMMIT: Literal[1] +GIT_OBJECT_TAG: Literal[4] +GIT_OBJECT_TREE: Literal[2] +GIT_OID_HEXSZ: Literal[40] +GIT_OID_HEX_ZERO: Literal['0000000000000000000000000000000000000000'] +GIT_OID_MINPREFIXLEN: Literal[4] +GIT_OID_RAWSZ: Literal[20] +GIT_APPLY_LOCATION_BOTH: Literal[2] +GIT_APPLY_LOCATION_INDEX: Literal[1] +GIT_APPLY_LOCATION_WORKDIR: Literal[0] +GIT_BLAME_FIRST_PARENT: Literal[16] +GIT_BLAME_IGNORE_WHITESPACE: Literal[64] +GIT_BLAME_NORMAL: Literal[0] +GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES: Literal[8] +GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES: Literal[4] +GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES: Literal[2] +GIT_BLAME_TRACK_COPIES_SAME_FILE: Literal[1] +GIT_BLAME_USE_MAILMAP: Literal[32] +GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT: Literal[8] +GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD: Literal[4] +GIT_BLOB_FILTER_CHECK_FOR_BINARY: Literal[1] +GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES: Literal[2] +GIT_BRANCH_ALL: Literal[3] +GIT_BRANCH_LOCAL: Literal[1] +GIT_BRANCH_REMOTE: Literal[2] +GIT_CHECKOUT_ALLOW_CONFLICTS: Literal[16] +GIT_CHECKOUT_CONFLICT_STYLE_DIFF3: Literal[2097152] +GIT_CHECKOUT_CONFLICT_STYLE_MERGE: Literal[1048576] +GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3: Literal[33554432] +GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH: Literal[8192] +GIT_CHECKOUT_DONT_OVERWRITE_IGNORED: Literal[524288] +GIT_CHECKOUT_DONT_REMOVE_EXISTING: Literal[4194304] +GIT_CHECKOUT_DONT_UPDATE_INDEX: Literal[256] +GIT_CHECKOUT_DONT_WRITE_INDEX: Literal[8388608] +GIT_CHECKOUT_DRY_RUN: Literal[16777216] +GIT_CHECKOUT_FORCE: Literal[2] +GIT_CHECKOUT_NONE: Literal[0] +GIT_CHECKOUT_NO_REFRESH: Literal[512] +GIT_CHECKOUT_RECREATE_MISSING: Literal[4] +GIT_CHECKOUT_REMOVE_IGNORED: Literal[64] +GIT_CHECKOUT_REMOVE_UNTRACKED: Literal[32] +GIT_CHECKOUT_SAFE: Literal[1] +GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES: Literal[262144] +GIT_CHECKOUT_SKIP_UNMERGED: Literal[1024] +GIT_CHECKOUT_UPDATE_ONLY: Literal[128] +GIT_CHECKOUT_USE_OURS: Literal[2048] +GIT_CHECKOUT_USE_THEIRS: Literal[4096] +GIT_CONFIG_HIGHEST_LEVEL: Literal[-1] +GIT_CONFIG_LEVEL_APP: Literal[7] +GIT_CONFIG_LEVEL_GLOBAL: Literal[4] +GIT_CONFIG_LEVEL_LOCAL: Literal[5] +GIT_CONFIG_LEVEL_PROGRAMDATA: Literal[1] +GIT_CONFIG_LEVEL_SYSTEM: Literal[2] +GIT_CONFIG_LEVEL_WORKTREE: Literal[6] +GIT_CONFIG_LEVEL_XDG: Literal[3] +GIT_DELTA_ADDED: Literal[1] +GIT_DELTA_CONFLICTED: Literal[10] +GIT_DELTA_COPIED: Literal[5] +GIT_DELTA_DELETED: Literal[2] +GIT_DELTA_IGNORED: Literal[6] +GIT_DELTA_MODIFIED: Literal[3] +GIT_DELTA_RENAMED: Literal[4] +GIT_DELTA_TYPECHANGE: Literal[8] +GIT_DELTA_UNMODIFIED: Literal[0] +GIT_DELTA_UNREADABLE: Literal[9] +GIT_DELTA_UNTRACKED: Literal[7] +GIT_DESCRIBE_ALL: Literal[2] +GIT_DESCRIBE_DEFAULT: Literal[0] +GIT_DESCRIBE_TAGS: Literal[1] +GIT_DIFF_BREAK_REWRITES: Literal[32] +GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY: Literal[32768] +GIT_DIFF_DISABLE_PATHSPEC_MATCH: Literal[4096] +GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS: Literal[16384] +GIT_DIFF_FIND_ALL: Literal[255] +GIT_DIFF_FIND_AND_BREAK_REWRITES: Literal[48] +GIT_DIFF_FIND_BY_CONFIG: Literal[0] +GIT_DIFF_FIND_COPIES: Literal[4] +GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED: Literal[8] +GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE: Literal[8192] +GIT_DIFF_FIND_EXACT_MATCH_ONLY: Literal[16384] +GIT_DIFF_FIND_FOR_UNTRACKED: Literal[64] +GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE: Literal[0] +GIT_DIFF_FIND_IGNORE_WHITESPACE: Literal[4096] +GIT_DIFF_FIND_REMOVE_UNMODIFIED: Literal[65536] +GIT_DIFF_FIND_RENAMES: Literal[1] +GIT_DIFF_FIND_RENAMES_FROM_REWRITES: Literal[2] +GIT_DIFF_FIND_REWRITES: Literal[16] +GIT_DIFF_FLAG_BINARY: Literal[1] +GIT_DIFF_FLAG_EXISTS: Literal[8] +GIT_DIFF_FLAG_NOT_BINARY: Literal[2] +GIT_DIFF_FLAG_VALID_ID: Literal[4] +GIT_DIFF_FLAG_VALID_SIZE: Literal[16] +GIT_DIFF_FORCE_BINARY: Literal[2097152] +GIT_DIFF_FORCE_TEXT: Literal[1048576] +GIT_DIFF_IGNORE_BLANK_LINES: Literal[524288] +GIT_DIFF_IGNORE_CASE: Literal[1024] +GIT_DIFF_IGNORE_FILEMODE: Literal[256] +GIT_DIFF_IGNORE_SUBMODULES: Literal[512] +GIT_DIFF_IGNORE_WHITESPACE: Literal[4194304] +GIT_DIFF_IGNORE_WHITESPACE_CHANGE: Literal[8388608] +GIT_DIFF_IGNORE_WHITESPACE_EOL: Literal[16777216] +GIT_DIFF_INCLUDE_CASECHANGE: Literal[2048] +GIT_DIFF_INCLUDE_IGNORED: Literal[2] +GIT_DIFF_INCLUDE_TYPECHANGE: Literal[64] +GIT_DIFF_INCLUDE_TYPECHANGE_TREES: Literal[128] +GIT_DIFF_INCLUDE_UNMODIFIED: Literal[32] +GIT_DIFF_INCLUDE_UNREADABLE: Literal[65536] +GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED: Literal[131072] +GIT_DIFF_INCLUDE_UNTRACKED: Literal[8] +GIT_DIFF_INDENT_HEURISTIC: Literal[262144] +GIT_DIFF_MINIMAL: Literal[536870912] +GIT_DIFF_NORMAL: Literal[0] +GIT_DIFF_PATIENCE: Literal[268435456] +GIT_DIFF_RECURSE_IGNORED_DIRS: Literal[4] +GIT_DIFF_RECURSE_UNTRACKED_DIRS: Literal[16] +GIT_DIFF_REVERSE: Literal[1] +GIT_DIFF_SHOW_BINARY: Literal[1073741824] +GIT_DIFF_SHOW_UNMODIFIED: Literal[67108864] +GIT_DIFF_SHOW_UNTRACKED_CONTENT: Literal[33554432] +GIT_DIFF_SKIP_BINARY_CHECK: Literal[8192] +GIT_DIFF_STATS_FULL: Literal[1] +GIT_DIFF_STATS_INCLUDE_SUMMARY: Literal[8] +GIT_DIFF_STATS_NONE: Literal[0] +GIT_DIFF_STATS_NUMBER: Literal[4] +GIT_DIFF_STATS_SHORT: Literal[2] +GIT_DIFF_UPDATE_INDEX: Literal[32768] +GIT_FILEMODE_BLOB: Literal[33188] +GIT_FILEMODE_BLOB_EXECUTABLE: Literal[33261] +GIT_FILEMODE_COMMIT: Literal[57344] +GIT_FILEMODE_LINK: Literal[40960] +GIT_FILEMODE_TREE: Literal[16384] +GIT_FILEMODE_UNREADABLE: Literal[0] +GIT_FILTER_ALLOW_UNSAFE: Literal[1] +GIT_FILTER_ATTRIBUTES_FROM_COMMIT: Literal[8] +GIT_FILTER_ATTRIBUTES_FROM_HEAD: Literal[4] +GIT_FILTER_CLEAN: Literal[1] +GIT_FILTER_DEFAULT: Literal[0] +GIT_FILTER_DRIVER_PRIORITY: Literal[200] +GIT_FILTER_NO_SYSTEM_ATTRIBUTES: Literal[2] +GIT_FILTER_SMUDGE: Literal[0] +GIT_FILTER_TO_ODB: Literal[1] +GIT_FILTER_TO_WORKTREE: Literal[0] +GIT_MERGE_ANALYSIS_FASTFORWARD: Literal[4] +GIT_MERGE_ANALYSIS_NONE: Literal[0] +GIT_MERGE_ANALYSIS_NORMAL: Literal[1] +GIT_MERGE_ANALYSIS_UNBORN: Literal[8] +GIT_MERGE_ANALYSIS_UP_TO_DATE: Literal[2] +GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY: Literal[2] +GIT_MERGE_PREFERENCE_NONE: Literal[0] +GIT_MERGE_PREFERENCE_NO_FASTFORWARD: Literal[1] +GIT_OBJECT_ANY: Literal[-2] +GIT_OBJECT_INVALID: Literal[-1] +GIT_OBJECT_OFS_DELTA: Literal[6] +GIT_OBJECT_REF_DELTA: Literal[7] +GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: Literal[27] +GIT_OPT_ENABLE_CACHING: Literal[8] +GIT_OPT_ENABLE_FSYNC_GITDIR: Literal[19] +GIT_OPT_ENABLE_OFS_DELTA: Literal[18] +GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: Literal[22] +GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: Literal[14] +GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: Literal[15] +GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: Literal[24] +GIT_OPT_GET_CACHED_MEMORY: Literal[9] +GIT_OPT_GET_MWINDOW_FILE_LIMIT: Literal[29] +GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: Literal[2] +GIT_OPT_GET_MWINDOW_SIZE: Literal[0] +GIT_OPT_GET_OWNER_VALIDATION: Literal[35] +GIT_OPT_GET_PACK_MAX_OBJECTS: Literal[25] +GIT_OPT_GET_SEARCH_PATH: Literal[4] +GIT_OPT_GET_TEMPLATE_PATH: Literal[10] +GIT_OPT_GET_USER_AGENT: Literal[17] +GIT_OPT_GET_WINDOWS_SHAREMODE: Literal[20] +GIT_OPT_SET_ALLOCATOR: Literal[23] +GIT_OPT_SET_CACHE_MAX_SIZE: Literal[7] +GIT_OPT_SET_CACHE_OBJECT_LIMIT: Literal[6] +GIT_OPT_SET_MWINDOW_FILE_LIMIT: Literal[30] +GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: Literal[3] +GIT_OPT_SET_MWINDOW_SIZE: Literal[1] +GIT_OPT_SET_OWNER_VALIDATION: Literal[36] +GIT_OPT_SET_PACK_MAX_OBJECTS: Literal[26] +GIT_OPT_SET_SEARCH_PATH: Literal[5] +GIT_OPT_SET_SSL_CERT_LOCATIONS: Literal[12] +GIT_OPT_SET_SSL_CIPHERS: Literal[16] +GIT_OPT_SET_TEMPLATE_PATH: Literal[11] +GIT_OPT_SET_USER_AGENT: Literal[13] +GIT_OPT_SET_WINDOWS_SHAREMODE: Literal[21] +GIT_REFERENCES_ALL: Literal[0] +GIT_REFERENCES_BRANCHES: Literal[1] +GIT_REFERENCES_TAGS: Literal[2] +GIT_RESET_HARD: Literal[3] +GIT_RESET_MIXED: Literal[2] +GIT_RESET_SOFT: Literal[1] +GIT_REVSPEC_MERGE_BASE: Literal[4] +GIT_REVSPEC_RANGE: Literal[2] +GIT_REVSPEC_SINGLE: Literal[1] +GIT_SORT_NONE: Literal[0] +GIT_SORT_REVERSE: Literal[4] +GIT_SORT_TIME: Literal[2] +GIT_SORT_TOPOLOGICAL: Literal[1] +GIT_STASH_APPLY_DEFAULT: Literal[0] +GIT_STASH_APPLY_REINSTATE_INDEX: Literal[1] +GIT_STASH_DEFAULT: Literal[0] +GIT_STASH_INCLUDE_IGNORED: Literal[4] +GIT_STASH_INCLUDE_UNTRACKED: Literal[2] +GIT_STASH_KEEP_ALL: Literal[8] +GIT_STASH_KEEP_INDEX: Literal[1] +GIT_STATUS_CONFLICTED: Literal[32768] +GIT_STATUS_CURRENT: Literal[0] +GIT_STATUS_IGNORED: Literal[16384] +GIT_STATUS_INDEX_DELETED: Literal[4] +GIT_STATUS_INDEX_MODIFIED: Literal[2] +GIT_STATUS_INDEX_NEW: Literal[1] +GIT_STATUS_INDEX_RENAMED: Literal[8] +GIT_STATUS_INDEX_TYPECHANGE: Literal[16] +GIT_STATUS_WT_DELETED: Literal[512] +GIT_STATUS_WT_MODIFIED: Literal[256] +GIT_STATUS_WT_NEW: Literal[128] +GIT_STATUS_WT_RENAMED: Literal[2048] +GIT_STATUS_WT_TYPECHANGE: Literal[1024] +GIT_STATUS_WT_UNREADABLE: Literal[4096] +GIT_SUBMODULE_IGNORE_ALL: Literal[4] +GIT_SUBMODULE_IGNORE_DIRTY: Literal[3] +GIT_SUBMODULE_IGNORE_NONE: Literal[1] +GIT_SUBMODULE_IGNORE_UNSPECIFIED: Literal[-1] +GIT_SUBMODULE_IGNORE_UNTRACKED: Literal[2] +GIT_SUBMODULE_STATUS_INDEX_ADDED: Literal[16] +GIT_SUBMODULE_STATUS_INDEX_DELETED: Literal[32] +GIT_SUBMODULE_STATUS_INDEX_MODIFIED: Literal[64] +GIT_SUBMODULE_STATUS_IN_CONFIG: Literal[4] +GIT_SUBMODULE_STATUS_IN_HEAD: Literal[1] +GIT_SUBMODULE_STATUS_IN_INDEX: Literal[2] +GIT_SUBMODULE_STATUS_IN_WD: Literal[8] +GIT_SUBMODULE_STATUS_WD_ADDED: Literal[256] +GIT_SUBMODULE_STATUS_WD_DELETED: Literal[512] +GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED: Literal[2048] +GIT_SUBMODULE_STATUS_WD_MODIFIED: Literal[1024] +GIT_SUBMODULE_STATUS_WD_UNINITIALIZED: Literal[128] +GIT_SUBMODULE_STATUS_WD_UNTRACKED: Literal[8192] +GIT_SUBMODULE_STATUS_WD_WD_MODIFIED: Literal[4096] + +_OidArg: TypeAlias = 'str | Oid' + class Object: _pointer: bytes filemode: FileMode - hex: str id: Oid name: str | None - oid: Oid raw_name: bytes | None short_id: str - type: 'Literal[GIT_OBJ_COMMIT] | Literal[GIT_OBJ_TREE] | Literal[GIT_OBJ_TAG] | Literal[GIT_OBJ_BLOB]' + # GIT_OBJECT_COMMIT | GIT_OBJECT_TREE | GIT_OBJECT_TAG | GIT_OBJECT_BLOB + type: Literal[1, 2, 4, 3] 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[1, ObjectType.COMMIT] | type[Commit] + ) -> Commit: ... @overload - def peel(self, target_type: 'Literal[GIT_OBJ_TREE]') -> 'Tree': ... + def peel(self, target_type: Literal[2, ObjectType.TREE] | type[Tree]) -> Tree: ... @overload - def peel(self, target_type: 'Literal[GIT_OBJ_TAG]') -> 'Tag': ... + def peel(self, target_type: Literal[4, ObjectType.TAG] | type[Tag]) -> Tag: ... @overload - def peel(self, target_type: 'Literal[GIT_OBJ_BLOB]') -> 'Blob': ... + def peel(self, target_type: Literal[3, ObjectType.BLOB] | type[Blob]) -> Blob: ... @overload - def peel(self, target_type: 'None') -> 'Commit|Tree|Blob': ... + def peel( + self, target_type: Literal[None, ObjectType.ANY] + ) -> Commit | Tree | Blob | Tag: ... def read_raw(self) -> bytes: ... - def __eq__(self, other) -> bool: ... - def __ge__(self, other) -> bool: ... - def __gt__(self, other) -> bool: ... + def __eq__(self, other: object) -> bool: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... def __hash__(self) -> int: ... - def __le__(self, other) -> bool: ... - def __lt__(self, other) -> bool: ... - def __ne__(self, other) -> bool: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... class Reference: name: str @@ -71,28 +319,32 @@ class Reference: shorthand: str target: Oid | str type: ReferenceType - def __init__(self, *args) -> None: ... + + @overload + def __init__(self, name: str, target: str) -> None: ... + @overload + def __init__(self, name: str, oid: Oid, peel: Oid) -> None: ... def delete(self) -> None: ... def log(self) -> Iterator[RefLogEntry]: ... @overload - def peel(self, type: 'Literal[GIT_OBJ_COMMIT]') -> 'Commit': ... + def peel(self, type: Literal[1] | type[Commit]) -> Commit: ... @overload - def peel(self, type: 'Literal[GIT_OBJ_TREE]') -> 'Tree': ... + def peel(self, type: Literal[2, ObjectType.TREE] | type[Tree]) -> Tree: ... @overload - def peel(self, type: 'Literal[GIT_OBJ_TAG]') -> 'Tag': ... + def peel(self, type: Literal[4, ObjectType.TAG] | type[Tag]) -> Tag: ... @overload - def peel(self, type: 'Literal[GIT_OBJ_BLOB]') -> 'Blob': ... + def peel(self, type: Literal[3, ObjectType.BLOB] | type[Blob]) -> Blob: ... @overload - def peel(self, type: 'None') -> 'Commit|Tree|Blob': ... + def peel(self, type: Literal[None, ObjectType.ANY]) -> Commit | Tree | Blob: ... def rename(self, new_name: str) -> None: ... def resolve(self) -> Reference: ... def set_target(self, target: _OidArg, message: str = ...) -> None: ... - def __eq__(self, other) -> bool: ... - def __ge__(self, other) -> bool: ... - def __gt__(self, other) -> bool: ... - def __le__(self, other) -> bool: ... - def __lt__(self, other) -> bool: ... - def __ne__(self, other) -> bool: ... + def __eq__(self, other: object) -> bool: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... class AlreadyExistsError(ValueError): ... @@ -114,6 +366,16 @@ class Blob(Object): old_as_path: str = ..., buffer_as_path: str = ..., ) -> Patch: ... + def _write_to_queue( + self, + queue: queue.Queue[bytes | None], + closed: threading.Event, + chunk_size: int = DEFAULT_BUFFER_SIZE, + *, + as_path: str | None = None, + flags: BlobFilter = BlobFilter.CHECK_FOR_BINARY, + commit_id: Oid | None = None, + ) -> None: ... class Branch(Reference): branch_name: str @@ -124,7 +386,7 @@ class Branch(Reference): def delete(self) -> None: ... def is_checked_out(self) -> bool: ... def is_head(self) -> bool: ... - def rename(self, name: str, force: bool = False) -> None: ... + def rename(self, name: str, force: bool = False) -> None: ... # type: ignore class Commit(Object): author: Signature @@ -157,7 +419,7 @@ class Diff: ) -> None: ... def merge(self, diff: Diff) -> None: ... @staticmethod - def from_c(diff, repo) -> Diff: ... + def from_c(diff: Any, repo: Any) -> Diff: ... @staticmethod def parse_diff(git_diff: str | bytes) -> Diff: ... def __getitem__(self, index: int) -> Patch: ... # Diff_getitem @@ -182,7 +444,7 @@ class DiffFile: raw_path: bytes size: int @staticmethod - def from_c(bytes) -> DiffFile: ... + def from_c(bytes: bytes) -> DiffFile: ... class DiffHunk: header: str @@ -211,7 +473,7 @@ class GitError(Exception): ... class InvalidSpecError(ValueError): ... class Mailmap: - def __init__(self, *args) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... def add_entry( self, real_name: str = ..., @@ -236,7 +498,7 @@ class Note: class Odb: backends: Iterator[OdbBackend] - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... def add_backend(self, backend: OdbBackend, priority: int) -> None: ... def add_disk_alternate(self, path: str) -> None: ... def exists(self, oid: _OidArg) -> bool: ... @@ -246,7 +508,7 @@ class Odb: def __iter__(self) -> Iterator[Oid]: ... # Odb_as_iter class OdbBackend: - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... def exists(self, oid: _OidArg) -> bool: ... def exists_prefix(self, partial_id: _OidArg) -> Oid: ... def read(self, oid: _OidArg) -> tuple[int, bytes]: ... @@ -256,21 +518,22 @@ class OdbBackend: def __iter__(self) -> Iterator[Oid]: ... # OdbBackend_as_iter class OdbBackendLoose(OdbBackend): - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... class OdbBackendPack(OdbBackend): - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... class Oid: raw: bytes def __init__(self, raw: bytes = ..., hex: str = ...) -> None: ... - def __eq__(self, other) -> bool: ... - def __ge__(self, other) -> bool: ... - def __gt__(self, other) -> bool: ... + def __eq__(self, other: object) -> bool: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... def __hash__(self) -> int: ... - def __le__(self, other) -> bool: ... - def __lt__(self, other) -> bool: ... - def __ne__(self, other) -> bool: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __str__(self) -> str: ... def __bool__(self) -> bool: ... class Patch: @@ -296,10 +559,10 @@ class RefLogEntry: message: str oid_new: Oid oid_old: Oid - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... class Refdb: - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... def compress(self) -> None: ... @staticmethod def new(repo: Repository) -> Refdb: ... @@ -308,7 +571,7 @@ class Refdb: def set_backend(self, backend: RefdbBackend) -> None: ... class RefdbBackend: - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... def compress(self) -> None: ... def delete(self, ref_name: str, old_id: _OidArg, old_target: str) -> None: ... def ensure_log(self, ref_name: str) -> bool: ... @@ -329,7 +592,7 @@ class RefdbBackend: ) -> None: ... class RefdbFsBackend(RefdbBackend): - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... class Repository: _pointer: bytes @@ -344,10 +607,10 @@ class Repository: path: str refdb: Refdb workdir: str - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... def TreeBuilder(self, src: Tree | _OidArg = ...) -> TreeBuilder: ... - def _disown(self, *args, **kwargs) -> None: ... - def _from_c(self, *args, **kwargs) -> None: ... + def _disown(self, *args: Any, **kwargs: Any) -> None: ... + def _from_c(self, *args: Any, **kwargs: Any) -> None: ... def add_worktree(self, name: str, path: str, ref: Reference = ...) -> Worktree: ... def applies( self, @@ -364,7 +627,9 @@ class Repository: def create_blob_fromdisk(self, path: str) -> Oid: ... def create_blob_fromiobase(self, iobase: IOBase) -> Oid: ... def create_blob_fromworkdir(self, path: str) -> Oid: ... - def create_branch(self, name: str, commit: Commit, force=False) -> Branch: ... + def create_branch( + self, name: str, commit: Commit, force: bool = False + ) -> Branch: ... def create_commit( self, reference_name: Optional[str], @@ -438,13 +703,13 @@ class Repository: def references_iterator_init(self) -> Iterator[Reference]: ... def references_iterator_next( self, - iter: Iterator, + iter: Iterator[Any], references_return_type: ReferenceFilter = ReferenceFilter.ALL, ) -> Reference: ... def reset(self, oid: _OidArg, reset_type: ResetMode) -> None: ... - def revparse(self, revspec: str) -> RevSpec: ... - def revparse_ext(self, revision: str) -> tuple[Object, Reference]: ... - def revparse_single(self, revision: str) -> Object: ... + def revparse(self, revspec: str | bytes) -> RevSpec: ... + def revparse_ext(self, revision: str | bytes) -> tuple[Object, Reference]: ... + def revparse_single(self, revision: str | bytes) -> Object: ... def set_odb(self, odb: Odb) -> None: ... def set_refdb(self, refdb: Refdb) -> None: ... def status( @@ -477,29 +742,29 @@ class Signature: offset: int = 0, encoding: Optional[str] = None, ) -> None: ... - def __eq__(self, other) -> bool: ... - def __ge__(self, other) -> bool: ... - def __gt__(self, other) -> bool: ... - def __le__(self, other) -> bool: ... - def __lt__(self, other) -> bool: ... - def __ne__(self, other) -> bool: ... + def __eq__(self, other: object) -> bool: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... class Stash: commit_id: Oid message: str raw_message: bytes - def __eq__(self, other) -> bool: ... - def __ge__(self, other) -> bool: ... - def __gt__(self, other) -> bool: ... - def __le__(self, other) -> bool: ... - def __lt__(self, other) -> bool: ... - def __ne__(self, other) -> bool: ... + def __eq__(self, other: object) -> bool: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... class Tag(Object): message: str - name: str + name: str | None raw_message: bytes - raw_name: bytes + raw_name: bytes | None tagger: Signature target: Oid def get_object(self) -> Object: ... @@ -554,7 +819,15 @@ class Worktree: is_prunable: bool name: str path: str - def prune(self, force=False) -> None: ... + def prune(self, force: bool = False) -> None: ... + +class FilterSource: + filemode: int + flags: FilterFlag + mode: FilterMode + oid: Oid | None + path: str + repo: Repository def discover_repository( path: str, across_fs: bool = False, ceiling_dirs: str = ... @@ -562,8 +835,12 @@ def discover_repository( def hash(data: bytes) -> Oid: ... def hashfile(path: str) -> Oid: ... def init_file_backend(path: str, flags: int = 0) -> object: ... -def option(opt: Option, *args) -> None: ... +def option(opt: Option, *args: Any) -> None: ... def reference_is_valid_name(refname: str) -> bool: ... def tree_entry_cmp(a: Object, b: Object) -> int: ... +def filter_register( + name: str, filter_cls: type[Filter], priority: int = GIT_FILTER_DRIVER_PRIORITY +) -> None: ... +def filter_unregister(name: str) -> None: ... -_OidArg = str | Oid +_cache_enums: Callable[..., None] diff --git a/pygit2/_run.py b/pygit2/_run.py index 815910ec..5f191afa 100644 --- a/pygit2/_run.py +++ b/pygit2/_run.py @@ -29,8 +29,8 @@ # Import from the Standard Library import codecs -from pathlib import Path import sys +from pathlib import Path # Import from cffi from cffi import FFI @@ -45,7 +45,7 @@ # C_HEADER_SRC if getattr(sys, 'frozen', False): if hasattr(sys, '_MEIPASS'): - dir_path = Path(sys._MEIPASS) + dir_path = Path(sys._MEIPASS) # type: ignore else: dir_path = Path(sys.executable).parent else: @@ -83,10 +83,10 @@ 'submodule.h', 'callbacks.h', # Bridge from libgit2 to Python ] -h_source = [] +h_source: list[str] = [] for h_file in h_files: - h_file = dir_path / 'decl' / h_file - with codecs.open(h_file, 'r', 'utf-8') as f: + h_file = dir_path / 'decl' / h_file # noqa: PLW2901 + with codecs.open(str(h_file), 'r', 'utf-8') as f: h_source.append(f.read()) C_HEADER_SRC = '\n'.join(h_source) @@ -99,7 +99,7 @@ # ffi _, libgit2_kw = get_libgit2_paths() ffi = FFI() -ffi.set_source('pygit2._libgit2', C_PREAMBLE, **libgit2_kw) +ffi.set_source('pygit2._libgit2', C_PREAMBLE, **libgit2_kw) # type: ignore ffi.cdef(C_HEADER_SRC) diff --git a/pygit2/blame.py b/pygit2/blame.py index a1b8e42e..16cdefc6 100644 --- a/pygit2/blame.py +++ b/pygit2/blame.py @@ -23,19 +23,29 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + # Import from pygit2 -from .ffi import ffi, C -from .utils import GenericIterator -from ._pygit2 import Signature, Oid +from ._pygit2 import Oid, Signature +from .ffi import C, ffi +from .utils import GenericIterator, buffer_to_bytes, maybe_string + +if TYPE_CHECKING: + from _cffi_backend import _CDataBase as CData + from ._ctyping import _CHunk, _CSignature + from .repository import BaseRepository -def wrap_signature(csig): + +def wrap_signature(csig: _CSignature): if not csig: return None return Signature( - ffi.string(csig.name).decode('utf-8'), - ffi.string(csig.email).decode('utf-8'), + cast(str, maybe_string(csig.name)), + cast(str, maybe_string(csig.email)), csig.when.time, csig.when.offset, 'utf-8', @@ -43,8 +53,12 @@ def wrap_signature(csig): class BlameHunk: + if TYPE_CHECKING: + _blame: Blame + _hunk: _CHunk + @classmethod - def _from_c(cls, blame, ptr): + def _from_c(cls, blame: Blame, ptr: _CHunk): hunk = cls.__new__(cls) hunk._blame = blame hunk._hunk = ptr @@ -73,9 +87,7 @@ def final_committer(self): @property def final_commit_id(self): - return Oid( - raw=bytes(ffi.buffer(ffi.addressof(self._hunk, 'final_commit_id'))[:]) - ) + return Oid(raw=buffer_to_bytes(ffi.addressof(self._hunk, 'final_commit_id'))) @property def orig_start_line_number(self): @@ -89,23 +101,17 @@ def orig_committer(self): @property def orig_commit_id(self): - return Oid( - raw=bytes(ffi.buffer(ffi.addressof(self._hunk, 'orig_commit_id'))[:]) - ) + return Oid(raw=buffer_to_bytes(ffi.addressof(self._hunk, 'orig_commit_id'))) @property def orig_path(self): """Original path""" - path = self._hunk.orig_path - if not path: - return None - - return ffi.string(path).decode('utf-8') + return maybe_string(self._hunk.orig_path) class Blame: @classmethod - def _from_c(cls, repo, ptr): + def _from_c(cls, repo: BaseRepository, ptr: CData): blame = cls.__new__(cls) blame._repo = repo blame._blame = ptr @@ -117,14 +123,14 @@ def __del__(self): def __len__(self): return C.git_blame_get_hunk_count(self._blame) - def __getitem__(self, index): + def __getitem__(self, index: int): chunk = C.git_blame_get_hunk_byindex(self._blame, index) if not chunk: raise IndexError return BlameHunk._from_c(self, chunk) - def for_line(self, line_no): + def for_line(self, line_no: int) -> BlameHunk: """ Returns the object for a given line given its number in the current Blame. diff --git a/pygit2/blob.py b/pygit2/blob.py index d9f4de89..144f2b79 100644 --- a/pygit2/blob.py +++ b/pygit2/blob.py @@ -1,13 +1,19 @@ +from __future__ import annotations + import io import threading import time from contextlib import AbstractContextManager -from typing import Optional from queue import Queue +from types import TracebackType +from typing import TYPE_CHECKING, Any, Optional, overload from ._pygit2 import Blob, Oid from .enums import BlobFilter +if TYPE_CHECKING: + from _typeshed import WriteableBuffer + class _BlobIO(io.RawIOBase): """Low-level wrapper for streaming blob content. @@ -26,7 +32,7 @@ def __init__( ): super().__init__() self._blob = blob - self._queue = Queue(maxsize=1) + self._queue: Queue[bytes | None] | None = Queue(maxsize=1) self._ready = threading.Event() self._writer_closed = threading.Event() self._chunk: Optional[bytes] = None @@ -42,10 +48,15 @@ def __init__( ) self._thread.start() - def __exit__(self, exc_type, exc_value, traceback): + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: self.close() - def isatty(): + def isatty(self): return False def readable(self): @@ -57,7 +68,14 @@ def writable(self): def seekable(self): return False - def readinto(self, b, /): + # workaround for type checking + if TYPE_CHECKING: + @overload + def readinto(self, b: WriteableBuffer, /) -> int: ... # type: ignore + + def readinto(self, b: Any, /) -> int: + if not self._queue: + raise RuntimeError('Blob I/O is closed') try: while self._chunk is None: self._ready.wait() @@ -96,7 +114,7 @@ def close(self): self._queue = None -class BlobIO(io.BufferedReader, AbstractContextManager): +class BlobIO(io.BufferedReader, AbstractContextManager['BlobIO']): """Read-only wrapper for streaming blob content. Supports reading both raw and filtered blob content. @@ -147,7 +165,12 @@ def __init__( raw = _BlobIO(blob, as_path=as_path, flags=flags, commit_id=commit_id) super().__init__(raw) - def __exit__(self, exc_type, exc_value, traceback): + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: self.close() diff --git a/pygit2/branches.py b/pygit2/branches.py index c6323a1f..65b31d21 100644 --- a/pygit2/branches.py +++ b/pygit2/branches.py @@ -24,10 +24,11 @@ # Boston, MA 02110-1301, USA. from __future__ import annotations + from typing import TYPE_CHECKING +from ._pygit2 import Branch, Commit, Oid, Reference from .enums import BranchType, ReferenceType -from ._pygit2 import Commit, Oid # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: @@ -36,7 +37,10 @@ class 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, ): self._repository = repository self._flag = flag @@ -75,13 +79,13 @@ def __iter__(self): 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): return self._repository.create_branch(name, commit, force) def delete(self, name: str): self[name].delete() - def _valid(self, branch): + def _valid(self, branch: Branch | Reference): if branch.type == ReferenceType.SYMBOLIC: branch = branch.resolve() @@ -91,9 +95,9 @@ def _valid(self, branch): or self._repository.descendant_of(branch.target, self._commit) ) - def with_commit(self, commit): + def with_commit(self, commit: Commit | Oid | str | None): assert self._commit is None return Branches(self._repository, self._flag, commit) - def __contains__(self, name): + def __contains__(self, name: str): return self.get(name) is not None diff --git a/pygit2/callbacks.py b/pygit2/callbacks.py index 57e3d773..39f05b76 100644 --- a/pygit2/callbacks.py +++ b/pygit2/callbacks.py @@ -62,18 +62,23 @@ API. """ +from __future__ import annotations + # Standard Library from contextlib import contextmanager from functools import wraps -from typing import Optional, Union +from typing import TYPE_CHECKING, Any, Callable, Optional, Union # pygit2 -from ._pygit2 import Oid, DiffFile +from ._pygit2 import DiffFile, Oid from .enums import CheckoutNotify, CheckoutStrategy, CredentialType, StashApplyProgress -from .errors import check_error, Passthrough -from .ffi import ffi, C -from .utils import maybe_string, to_bytes, ptr_to_bytes, StrArray +from .errors import Passthrough, check_error +from .ffi import C, ffi +from .utils import StrArray, maybe_string, ptr_to_bytes, to_bytes +if TYPE_CHECKING: + from .remotes import Remote, TransferProgress + from .repository import Repository # # The payload is the way to pass information from the pygit2 API, through @@ -85,7 +90,7 @@ class Payload: def __init__(self, **kw: object): for key, value in kw.items(): setattr(self, key, value) - self._stored_exception = None + self._stored_exception: BaseException | None = None def check_error(self, error_code: int) -> None: if error_code == C.GIT_EUSER: @@ -113,6 +118,10 @@ class RemoteCallbacks(Payload): RemoteCallbacks(certificate=certificate). """ + if TYPE_CHECKING: + repository: Callable[[str, bool], Repository] + remote: Callable[[Repository, str, str], Remote] + def __init__(self, credentials=None, certificate_check=None): super().__init__() if credentials is not None: @@ -181,7 +190,7 @@ def certificate_check(self, certificate: None, valid: bool, host: str) -> bool: raise Passthrough - def transfer_progress(self, stats): + def transfer_progress(self, stats: TransferProgress): """ During the download of new data, this will be regularly called with the indexer's progress. @@ -206,8 +215,7 @@ def push_transfer_progress( Override with your own function to report push transfer progress. """ - - def update_tips(self, refname, old, new): + def update_tips(self, refname: str, old: Oid, new: Oid): """ Update tips callback. Override with your own function to report reference updates. @@ -224,7 +232,7 @@ def update_tips(self, refname, old, new): The reference's new value. """ - def push_update_reference(self, refname, message): + def push_update_reference(self, refname: str, message: str | None): """ Push update reference callback. Override with your own function to report the remote's acceptance or rejection of reference updates. @@ -438,9 +446,9 @@ def git_remote_callbacks(payload): # -def libgit2_callback(f): +def libgit2_callback(f: Callable[..., int]): @wraps(f) - def wrapper(*args): + def wrapper(*args: Any) -> int: data = ffi.from_handle(args[-1]) args = args[:-1] + (data,) try: @@ -459,10 +467,10 @@ def wrapper(*args): return ffi.def_extern()(wrapper) -def libgit2_callback_void(f): +def libgit2_callback_void(f: Callable[..., object]): @wraps(f) - def wrapper(*args): - data = ffi.from_handle(args[-1]) + def wrapper(*args: Any): + data: Payload = ffi.from_handle(args[-1]) args = args[:-1] + (data,) try: f(*args) @@ -480,7 +488,7 @@ def wrapper(*args): @libgit2_callback -def _certificate_check_cb(cert_i, valid, host, data): +def _certificate_check_cb(cert_i, valid: int, host, data: RemoteCallbacks): # We want to simulate what should happen if libgit2 supported pass-through # for this callback. For SSH, 'valid' is always False, because it doesn't # look at known_hosts, but we do want to let it through in order to do what @@ -493,9 +501,7 @@ def _certificate_check_cb(cert_i, valid, host, data): if not val: return C.GIT_ECERTIFICATE except Passthrough: - if is_ssh: - return 0 - elif valid: + if is_ssh or valid: return 0 else: return C.GIT_ECERTIFICATE @@ -504,7 +510,7 @@ def _certificate_check_cb(cert_i, valid, host, data): @libgit2_callback -def _credentials_cb(cred_out, url, username, allowed, data): +def _credentials_cb(cred_out, url, username, allowed: int, data: RemoteCallbacks): credentials = getattr(data, 'credentials', None) if not credentials: return 0 @@ -518,7 +524,7 @@ def _credentials_cb(cred_out, url, username, allowed, data): @libgit2_callback -def _push_update_reference_cb(ref, msg, data): +def _push_update_reference_cb(ref, msg, data: RemoteCallbacks): push_update_reference = getattr(data, 'push_update_reference', None) if not push_update_reference: return 0 @@ -544,7 +550,7 @@ def _remote_create_cb(remote_out, repo, name, url, data): @libgit2_callback -def _repository_create_cb(repo_out, path, bare, data): +def _repository_create_cb(repo_out, path, bare: int, data: RemoteCallbacks): repository = data.repository(ffi.string(path), bare != 0) # we no longer own the C object repository._disown() @@ -554,7 +560,7 @@ def _repository_create_cb(repo_out, path, bare, data): @libgit2_callback -def _sideband_progress_cb(string, length, data): +def _sideband_progress_cb(string, length: int, data: RemoteCallbacks): sideband_progress = getattr(data, 'sideband_progress', None) if not sideband_progress: return 0 @@ -565,7 +571,7 @@ def _sideband_progress_cb(string, length, data): @libgit2_callback -def _transfer_progress_cb(stats_ptr, data): +def _transfer_progress_cb(stats_ptr, data: RemoteCallbacks): from .remotes import TransferProgress transfer_progress = getattr(data, 'transfer_progress', None) @@ -576,7 +582,7 @@ def _transfer_progress_cb(stats_ptr, data): return 0 -@libgit2_callback + def _push_transfer_progress_cb(current, total, bytes_pushed, payload): push_transfer_progress = getattr(payload, 'push_transfer_progress', None) if not push_transfer_progress: @@ -587,7 +593,7 @@ def _push_transfer_progress_cb(current, total, bytes_pushed, payload): @libgit2_callback -def _update_tips_cb(refname, a, b, data): +def _update_tips_cb(refname, a, b, data: RemoteCallbacks): update_tips = getattr(data, 'update_tips', None) if not update_tips: return 0 @@ -604,7 +610,7 @@ def _update_tips_cb(refname, a, b, data): # -def get_credentials(fn, url, username, allowed): +def get_credentials(fn, url, username, allowed: CredentialType): """Call fn and return the credentials object.""" url_str = maybe_string(url) username_str = maybe_string(username) @@ -668,7 +674,7 @@ def get_credentials(fn, url, username, allowed): @libgit2_callback def _checkout_notify_cb( - why, path_cstr, baseline, target, workdir, data: CheckoutCallbacks + why: int, path_cstr, baseline, target, workdir, data: CheckoutCallbacks ): pypath = maybe_string(path_cstr) pybaseline = DiffFile.from_c(ptr_to_bytes(baseline)) @@ -695,16 +701,13 @@ def _checkout_progress_cb(path, completed_steps, total_steps, data: CheckoutCall def _git_checkout_options( - callbacks=None, - strategy=None, + callbacks: CheckoutCallbacks | None = None, + strategy: CheckoutStrategy | None = None, directory=None, paths=None, c_checkout_options_ptr=None, ): - if callbacks is None: - payload = CheckoutCallbacks() - else: - payload = callbacks + payload = CheckoutCallbacks() if callbacks is None else callbacks # Get handle to payload handle = ffi.new_handle(payload) @@ -731,7 +734,7 @@ def _git_checkout_options( if paths: strarray = StrArray(paths) - refs.append(strarray) + refs.append(strarray) # type: ignore opts.paths = strarray.ptr[0] # If we want to receive any notifications, set up notify_cb in the options @@ -755,7 +758,12 @@ def _git_checkout_options( @contextmanager -def git_checkout_options(callbacks=None, strategy=None, directory=None, paths=None): +def git_checkout_options( + callbacks: CheckoutCallbacks | None = None, + strategy=None, + directory=None, + paths=None, +): yield _git_checkout_options( callbacks=callbacks, strategy=strategy, directory=directory, paths=paths ) @@ -784,7 +792,11 @@ def _stash_apply_progress_cb(progress: StashApplyProgress, data: StashApplyCallb @contextmanager def git_stash_apply_options( - callbacks=None, reinstate_index=False, strategy=None, directory=None, paths=None + callbacks: StashApplyCallbacks | None = None, + reinstate_index: bool = False, + strategy=None, + directory=None, + paths=None, ): if callbacks is None: callbacks = StashApplyCallbacks() diff --git a/pygit2/config.py b/pygit2/config.py index 3b739840..dd7d4dd4 100644 --- a/pygit2/config.py +++ b/pygit2/config.py @@ -23,18 +23,38 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -try: - from functools import cached_property -except ImportError: - from cached_property import cached_property +from __future__ import annotations + +import contextlib +from functools import cached_property +from typing import TYPE_CHECKING, Callable, cast +from __future__ import annotations + +import contextlib +from functools import cached_property +from typing import TYPE_CHECKING, Callable, cast # Import from pygit2 from .errors import check_error -from .ffi import ffi, C -from .utils import to_bytes +from .ffi import C, ffi +from .utils import StrOrBytesPath, maybe_bytes, to_bytes + +if TYPE_CHECKING: + from _cffi_backend import _CDataBase as CData + + from ._ctyping import _CConfigEntry + from .repository import BaseRepository +from .utils import StrOrBytesPath, maybe_bytes, to_bytes + +if TYPE_CHECKING: + from _cffi_backend import _CDataBase as CData + + from ._ctyping import _CConfigEntry + from .repository import BaseRepository -def str_to_bytes(value, name): +def str_to_bytes(value: StrOrBytesPath, name: str): +def str_to_bytes(value: StrOrBytesPath, name: str): if not isinstance(value, str): raise TypeError(f'{name} must be a string') @@ -42,7 +62,8 @@ def str_to_bytes(value, name): class ConfigIterator: - def __init__(self, config, ptr): + def __init__(self, config: Config, ptr: CData): + def __init__(self, config: Config, ptr: CData): self._iter = ptr self._config = config @@ -60,11 +81,11 @@ def _next_entry(self): err = C.git_config_next(centry, self._iter) check_error(err) - return ConfigEntry._from_c(centry[0], self) + return ConfigEntry._from_c(cast(_CConfigEntry, centry[0]), self) class ConfigMultivarIterator(ConfigIterator): - def __next__(self): + def __next__(self): # type: ignore entry = self._next_entry() return entry.value @@ -72,7 +93,16 @@ def __next__(self): class Config: """Git configuration management.""" - def __init__(self, path=None): + if TYPE_CHECKING: + _repo: BaseRepository + _config: CData + + def __init__(self, path: StrOrBytesPath | None = None): + if TYPE_CHECKING: + _repo: BaseRepository + _config: CData + + def __init__(self, path: StrOrBytesPath | None = None): cconfig = ffi.new('git_config **') if not path: @@ -85,7 +115,8 @@ def __init__(self, path=None): self._config = cconfig[0] @classmethod - def from_c(cls, repo, ptr): + def from_c(cls, repo: BaseRepository, ptr: CData): + def from_c(cls, repo: BaseRepository, ptr: CData): config = cls.__new__(cls) config._repo = repo config._config = ptr @@ -93,20 +124,22 @@ def from_c(cls, repo, ptr): return config def __del__(self): - try: + with contextlib.suppress(AttributeError): C.git_config_free(self._config) - except AttributeError: - pass - def _get(self, key): - key = str_to_bytes(key, 'key') + def _get(self, key: str | bytes): + rkey = str_to_bytes(key, 'key') + def _get(self, key: str | bytes): + rkey = str_to_bytes(key, 'key') entry = ffi.new('git_config_entry **') - err = C.git_config_get_entry(entry, self._config, key) + err = C.git_config_get_entry(entry, self._config, rkey) + err = C.git_config_get_entry(entry, self._config, rkey) - return err, ConfigEntry._from_c(entry[0]) + return err, ConfigEntry._from_c(cast(_CConfigEntry, entry[0])) - def _get_entry(self, key): + def _get_entry(self, key: str | bytes): + def _get_entry(self, key: str | bytes): err, entry = self._get(key) if err == C.GIT_ENOTFOUND: @@ -115,8 +148,10 @@ def _get_entry(self, key): check_error(err) return entry - def __contains__(self, key): - err, cstr = self._get(key) + def __contains__(self, key: str): + err, _ = self._get(key) + def __contains__(self, key: str): + err, _ = self._get(key) if err == C.GIT_ENOTFOUND: return False @@ -125,7 +160,8 @@ def __contains__(self, key): return True - def __getitem__(self, key): + def __getitem__(self, key: str): + def __getitem__(self, key: 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 @@ -135,23 +171,31 @@ def __getitem__(self, key): return entry.value - def __setitem__(self, key, value): - key = str_to_bytes(key, 'key') + def __setitem__(self, key: str, value: int | bool | str): + rkey = str_to_bytes(key, 'key') + def __setitem__(self, key: str, value: int | bool | str): + rkey = str_to_bytes(key, 'key') err = 0 if isinstance(value, bool): - err = C.git_config_set_bool(self._config, key, value) + err = C.git_config_set_bool(self._config, rkey, value) + err = C.git_config_set_bool(self._config, rkey, value) elif isinstance(value, int): - err = C.git_config_set_int64(self._config, key, value) + err = C.git_config_set_int64(self._config, rkey, value) + err = C.git_config_set_int64(self._config, rkey, value) else: - err = C.git_config_set_string(self._config, key, to_bytes(value)) + err = C.git_config_set_string(self._config, rkey, to_bytes(value)) + err = C.git_config_set_string(self._config, rkey, to_bytes(value)) check_error(err) - def __delitem__(self, key): - key = str_to_bytes(key, 'key') + def __delitem__(self, key: str): + rkey = str_to_bytes(key, 'key') + def __delitem__(self, key: str): + rkey = str_to_bytes(key, 'key') - err = C.git_config_delete_entry(self._config, key) + err = C.git_config_delete_entry(self._config, rkey) + err = C.git_config_delete_entry(self._config, rkey) check_error(err) def __iter__(self): @@ -165,24 +209,31 @@ def __iter__(self): err = C.git_config_iterator_new(citer, self._config) check_error(err) - return ConfigIterator(self, citer[0]) + ptr = cast(CData, citer[0]) - def get_multivar(self, name, regex=None): + return ConfigIterator(self, ptr) + + def get_multivar(self, name: str | bytes, regex: CData | str | bytes | None = None): + def get_multivar(self, name: str | bytes, regex: CData | str | bytes | None = None): """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 = to_bytes(regex) + regex = to_bytes(regex) citer = ffi.new('git_config_iterator **') err = C.git_config_multivar_iterator_new(citer, self._config, name, regex) check_error(err) + cit = cast(CData, citer[0]) - return ConfigMultivarIterator(self, citer[0]) + return ConfigMultivarIterator(self, cit) + return ConfigMultivarIterator(self, cit) - def set_multivar(self, name, regex, value): + def set_multivar(self, name: str | bytes, regex: str | bytes, value: str | bytes): + def set_multivar(self, name: str | bytes, regex: str | bytes, value: str | bytes): """Set a multivar ''name'' to ''value''. ''regexp'' is a regular expression to indicate which values to replace. """ @@ -193,7 +244,8 @@ 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): + def delete_multivar(self, name: str | bytes, regex: str | bytes): """Delete a multivar ''name''. ''regexp'' is a regular expression to indicate which values to delete. """ @@ -203,7 +255,8 @@ 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): + def get_bool(self, key: str | bytes): """Look up *key* and parse its value as a boolean as per the git-config rules. Return a boolean value (True or False). @@ -216,9 +269,11 @@ def get_bool(self, key): err = C.git_config_parse_bool(res, entry.c_value) check_error(err) - return res[0] != 0 + return cast(int, res[0]) != 0 + return cast(int, res[0]) != 0 - def get_int(self, key): + def get_int(self, key: str | bytes): + def get_int(self, key: str | bytes): """Look up *key* and parse its value as an integer as per the git-config rules. Return an integer. @@ -231,9 +286,11 @@ def get_int(self, key): err = C.git_config_parse_int64(res, entry.c_value) check_error(err) - return res[0] + return cast(int, res[0]) + return cast(int, res[0]) - def add_file(self, path, level=0, force=0): + def add_file(self, path: StrOrBytesPath, level: int = 0, force: int = 0): + def add_file(self, path: StrOrBytesPath, level: int = 0, force: int = 0): """Add a config file instance to an existing config.""" err = C.git_config_add_file_ondisk( @@ -251,41 +308,48 @@ def snapshot(self): err = C.git_config_snapshot(ccfg, self._config) check_error(err) - return Config.from_c(self._repo, ccfg[0]) + return Config.from_c(self._repo, cast(CData, ccfg[0])) # # Methods to parse a string according to the git-config rules # @staticmethod - def parse_bool(text): + def parse_bool(text: str): + def parse_bool(text: str): res = ffi.new('int *') err = C.git_config_parse_bool(res, to_bytes(text)) check_error(err) - return res[0] != 0 + return cast(int, res[0]) != 0 + return cast(int, res[0]) != 0 @staticmethod - def parse_int(text): + def parse_int(text: str): + def parse_int(text: str): res = ffi.new('int64_t *') err = C.git_config_parse_int64(res, to_bytes(text)) check_error(err) - return res[0] + return cast(int, res[0]) + return cast(int, res[0]) # # Static methods to get specialized version of the config # @staticmethod - def _from_found_config(fn): + def _from_found_config(fn: Callable[[CData], int]): + def _from_found_config(fn: Callable[[CData], int]): buf = ffi.new('git_buf *', (ffi.NULL, 0)) err = fn(buf) check_error(err, io=True) - cpath = ffi.string(buf.ptr).decode('utf-8') + cpath = maybe_bytes(cast(CData, buf.ptr)) + assert cpath C.git_buf_dispose(buf) - return Config(cpath) + return Config(cpath.decode('utf-8')) + return Config(cpath.decode('utf-8')) @staticmethod def get_system_config(): @@ -306,8 +370,12 @@ def get_xdg_config(): class ConfigEntry: """An entry in a configuration object.""" + if TYPE_CHECKING: + _entry: _CConfigEntry + @classmethod - def _from_c(cls, ptr, iterator=None): + def _from_c(cls, ptr: _CConfigEntry, iterator: ConfigIterator | None = None): + def _from_c(cls, ptr: _CConfigEntry, iterator: ConfigIterator | None = None): """Builds the entry from a ``git_config_entry`` pointer. ``iterator`` must be a ``ConfigIterator`` instance if the entry was @@ -341,11 +409,13 @@ def c_value(self): @cached_property def raw_name(self): - return ffi.string(self._entry.name) + return cast(bytes, maybe_bytes(self._entry.name)) + return cast(bytes, maybe_bytes(self._entry.name)) @cached_property def raw_value(self): - return ffi.string(self.c_value) + return cast(bytes, maybe_bytes(self.c_value)) + return cast(bytes, maybe_bytes(self.c_value)) @cached_property def level(self): diff --git a/pygit2/credentials.py b/pygit2/credentials.py index 9a09db76..508740a1 100644 --- a/pygit2/credentials.py +++ b/pygit2/credentials.py @@ -25,14 +25,20 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import Any, Protocol, TYPE_CHECKING from .enums import CredentialType - if TYPE_CHECKING: from pathlib import Path +class BaseCredentials(Protocol): + @property + def credential_type(self) -> CredentialType: ... + @property + def credential_tuple(self) -> tuple[str, ...]: ... + def __call__(self, _url: Any, _username: Any, _allowed: Any) -> BaseCredentials: ... + class Username: """Username credentials diff --git a/pygit2/enums.py b/pygit2/enums.py index fe642168..1485a08a 100644 --- a/pygit2/enums.py +++ b/pygit2/enums.py @@ -24,6 +24,7 @@ # Boston, MA 02110-1301, USA. from enum import IntEnum, IntFlag +from typing import cast from . import _pygit2 from .ffi import C @@ -52,12 +53,12 @@ class ApplyLocation(IntEnum): class AttrCheck(IntFlag): - FILE_THEN_INDEX = C.GIT_ATTR_CHECK_FILE_THEN_INDEX - INDEX_THEN_FILE = C.GIT_ATTR_CHECK_INDEX_THEN_FILE - INDEX_ONLY = C.GIT_ATTR_CHECK_INDEX_ONLY - NO_SYSTEM = C.GIT_ATTR_CHECK_NO_SYSTEM - INCLUDE_HEAD = C.GIT_ATTR_CHECK_INCLUDE_HEAD - INCLUDE_COMMIT = C.GIT_ATTR_CHECK_INCLUDE_COMMIT + FILE_THEN_INDEX = cast(int, C.GIT_ATTR_CHECK_FILE_THEN_INDEX) + INDEX_THEN_FILE = cast(int, C.GIT_ATTR_CHECK_INDEX_THEN_FILE) + INDEX_ONLY = cast(int, C.GIT_ATTR_CHECK_INDEX_ONLY) + NO_SYSTEM = cast(int, C.GIT_ATTR_CHECK_NO_SYSTEM) + INCLUDE_HEAD = cast(int, C.GIT_ATTR_CHECK_INCLUDE_HEAD) + INCLUDE_COMMIT = cast(int, C.GIT_ATTR_CHECK_INCLUDE_COMMIT) class BlameFlag(IntFlag): @@ -119,28 +120,28 @@ class CheckoutNotify(IntFlag): ones via `CheckoutCallbacks.checkout_notify_flags`. """ - NONE = C.GIT_CHECKOUT_NOTIFY_NONE + NONE = cast(int, C.GIT_CHECKOUT_NOTIFY_NONE) - CONFLICT = C.GIT_CHECKOUT_NOTIFY_CONFLICT + CONFLICT = cast(int, C.GIT_CHECKOUT_NOTIFY_CONFLICT) 'Invokes checkout on conflicting paths.' - DIRTY = C.GIT_CHECKOUT_NOTIFY_DIRTY + DIRTY = cast(int, C.GIT_CHECKOUT_NOTIFY_DIRTY) """ Notifies about "dirty" files, i.e. those that do not need an update but no longer match the baseline. Core git displays these files when checkout runs, but won't stop the checkout. """ - UPDATED = C.GIT_CHECKOUT_NOTIFY_UPDATED + UPDATED = cast(int, C.GIT_CHECKOUT_NOTIFY_UPDATED) 'Sends notification for any file changed.' - UNTRACKED = C.GIT_CHECKOUT_NOTIFY_UNTRACKED + UNTRACKED = cast(int, C.GIT_CHECKOUT_NOTIFY_UNTRACKED) 'Notifies about untracked files.' - IGNORED = C.GIT_CHECKOUT_NOTIFY_IGNORED + IGNORED = cast(int, C.GIT_CHECKOUT_NOTIFY_IGNORED) 'Notifies about ignored files.' - ALL = C.GIT_CHECKOUT_NOTIFY_ALL + ALL = cast(int, C.GIT_CHECKOUT_NOTIFY_ALL) class CheckoutStrategy(IntFlag): @@ -268,29 +269,29 @@ class CredentialType(IntFlag): authentication methods supported by the library. """ - USERPASS_PLAINTEXT = C.GIT_CREDENTIAL_USERPASS_PLAINTEXT + USERPASS_PLAINTEXT = cast(int, C.GIT_CREDENTIAL_USERPASS_PLAINTEXT) 'A vanilla user/password request' - SSH_KEY = C.GIT_CREDENTIAL_SSH_KEY + SSH_KEY = cast(int, C.GIT_CREDENTIAL_SSH_KEY) 'An SSH key-based authentication request' - SSH_CUSTOM = C.GIT_CREDENTIAL_SSH_CUSTOM + SSH_CUSTOM = cast(int, C.GIT_CREDENTIAL_SSH_CUSTOM) 'An SSH key-based authentication request, with a custom signature' - DEFAULT = C.GIT_CREDENTIAL_DEFAULT + DEFAULT = cast(int, C.GIT_CREDENTIAL_DEFAULT) 'An NTLM/Negotiate-based authentication request.' - SSH_INTERACTIVE = C.GIT_CREDENTIAL_SSH_INTERACTIVE + SSH_INTERACTIVE = cast(int, C.GIT_CREDENTIAL_SSH_INTERACTIVE) 'An SSH interactive authentication request.' - USERNAME = C.GIT_CREDENTIAL_USERNAME + USERNAME = cast(int, C.GIT_CREDENTIAL_USERNAME) """ Username-only authentication request. Used as a pre-authentication step if the underlying transport (eg. SSH, with no username in its URL) does not know which username to use. """ - SSH_MEMORY = C.GIT_CREDENTIAL_SSH_MEMORY + SSH_MEMORY = cast(int, C.GIT_CREDENTIAL_SSH_MEMORY) """ An SSH key-based authentication request. Allows credentials to be read from memory instead of files. @@ -651,23 +652,23 @@ class Feature(IntFlag): was compiled. """ - THREADS = C.GIT_FEATURE_THREADS - HTTPS = C.GIT_FEATURE_HTTPS - SSH = C.GIT_FEATURE_SSH - NSEC = C.GIT_FEATURE_NSEC + THREADS = cast(int, C.GIT_FEATURE_THREADS) + HTTPS = cast(int, C.GIT_FEATURE_HTTPS) + SSH = cast(int, C.GIT_FEATURE_SSH) + NSEC = cast(int, C.GIT_FEATURE_NSEC) class FetchPrune(IntEnum): """Acceptable prune settings when fetching.""" - UNSPECIFIED = C.GIT_FETCH_PRUNE_UNSPECIFIED + UNSPECIFIED = cast(int, C.GIT_FETCH_PRUNE_UNSPECIFIED) 'Use the setting from the configuration' - PRUNE = C.GIT_FETCH_PRUNE + PRUNE = cast(int, C.GIT_FETCH_PRUNE) """Force pruning on: remove any remote branch in the local repository that does not exist in the remote.""" - NO_PRUNE = C.GIT_FETCH_NO_PRUNE + NO_PRUNE = cast(int, C.GIT_FETCH_NO_PRUNE) """Force pruning off: always keep the remote branches.""" @@ -783,7 +784,7 @@ class MergeFavor(IntEnum): merging functionality how to deal with conflicting regions of the files. """ - NORMAL = C.GIT_MERGE_FILE_FAVOR_NORMAL + NORMAL = cast(int, C.GIT_MERGE_FILE_FAVOR_NORMAL) """ When a region of a file is changed in both branches, a conflict will be recorded in the index so that `checkout` can produce a merge file with @@ -792,7 +793,7 @@ class MergeFavor(IntEnum): This is the default. """ - OURS = C.GIT_MERGE_FILE_FAVOR_OURS + OURS = cast(int, C.GIT_MERGE_FILE_FAVOR_OURS) """ When a region of a file is changed in both branches, the file created in the index will contain the "ours" side of any conflicting region. @@ -800,7 +801,7 @@ class MergeFavor(IntEnum): The index will not record a conflict. """ - THEIRS = C.GIT_MERGE_FILE_FAVOR_THEIRS + THEIRS = cast(int, C.GIT_MERGE_FILE_FAVOR_THEIRS) """ When a region of a file is changed in both branches, the file created in the index will contain the "theirs" side of any conflicting region. @@ -808,7 +809,7 @@ class MergeFavor(IntEnum): The index will not record a conflict. """ - UNION = C.GIT_MERGE_FILE_FAVOR_UNION + UNION = cast(int, C.GIT_MERGE_FILE_FAVOR_UNION) """ When a region of a file is changed in both branches, the file created in the index will contain each unique line from each side, @@ -821,37 +822,37 @@ class MergeFavor(IntEnum): class MergeFileFlag(IntFlag): """File merging flags""" - DEFAULT = C.GIT_MERGE_FILE_DEFAULT + DEFAULT = cast(int, C.GIT_MERGE_FILE_DEFAULT) """ Defaults """ - STYLE_MERGE = C.GIT_MERGE_FILE_STYLE_MERGE + STYLE_MERGE = cast(int, C.GIT_MERGE_FILE_STYLE_MERGE) """ Create standard conflicted merge files """ - STYLE_DIFF3 = C.GIT_MERGE_FILE_STYLE_DIFF3 + STYLE_DIFF3 = cast(int, C.GIT_MERGE_FILE_STYLE_DIFF3) """ Create diff3-style files """ - SIMPLIFY_ALNUM = C.GIT_MERGE_FILE_SIMPLIFY_ALNUM + SIMPLIFY_ALNUM = cast(int, C.GIT_MERGE_FILE_SIMPLIFY_ALNUM) """ Condense non-alphanumeric regions for simplified diff file """ - IGNORE_WHITESPACE = C.GIT_MERGE_FILE_IGNORE_WHITESPACE + IGNORE_WHITESPACE = cast(int, C.GIT_MERGE_FILE_IGNORE_WHITESPACE) """ Ignore all whitespace """ - IGNORE_WHITESPACE_CHANGE = C.GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE + IGNORE_WHITESPACE_CHANGE = cast(int, C.GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE) """ Ignore changes in amount of whitespace """ - IGNORE_WHITESPACE_EOL = C.GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL + IGNORE_WHITESPACE_EOL = cast(int, C.GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL) """ Ignore whitespace at end of line """ - DIFF_PATIENCE = C.GIT_MERGE_FILE_DIFF_PATIENCE + DIFF_PATIENCE = cast(int, C.GIT_MERGE_FILE_DIFF_PATIENCE) """ Use the "patience diff" algorithm """ - DIFF_MINIMAL = C.GIT_MERGE_FILE_DIFF_MINIMAL + DIFF_MINIMAL = cast(int, C.GIT_MERGE_FILE_DIFF_MINIMAL) """ Take extra time to find minimal diff """ - STYLE_ZDIFF3 = C.GIT_MERGE_FILE_STYLE_ZDIFF3 + STYLE_ZDIFF3 = cast(int, C.GIT_MERGE_FILE_STYLE_ZDIFF3) """ Create zdiff3 ("zealous diff3")-style files """ - ACCEPT_CONFLICTS = C.GIT_MERGE_FILE_ACCEPT_CONFLICTS + ACCEPT_CONFLICTS = cast(int, C.GIT_MERGE_FILE_ACCEPT_CONFLICTS) """ Do not produce file conflicts when common regions have changed; keep the conflict markers in the file and accept that as the merge result. @@ -864,26 +865,26 @@ class MergeFlag(IntFlag): A combination of these flags can be passed in via the `flags` value. """ - FIND_RENAMES = C.GIT_MERGE_FIND_RENAMES + FIND_RENAMES = cast(int, C.GIT_MERGE_FIND_RENAMES) """ Detect renames that occur between the common ancestor and the "ours" side or the common ancestor and the "theirs" side. This will enable the ability to merge between a modified and renamed file. """ - FAIL_ON_CONFLICT = C.GIT_MERGE_FAIL_ON_CONFLICT + FAIL_ON_CONFLICT = cast(int, C.GIT_MERGE_FAIL_ON_CONFLICT) """ If a conflict occurs, exit immediately instead of attempting to continue resolving conflicts. The merge operation will raise GitError (GIT_EMERGECONFLICT) and no index will be returned. """ - SKIP_REUC = C.GIT_MERGE_SKIP_REUC + SKIP_REUC = cast(int, C.GIT_MERGE_SKIP_REUC) """ Do not write the REUC extension on the generated index. """ - NO_RECURSIVE = C.GIT_MERGE_NO_RECURSIVE + NO_RECURSIVE = cast(int, C.GIT_MERGE_NO_RECURSIVE) """ If the commits being merged have multiple merge bases, do not build a recursive merge base (by merging the multiple merge bases), @@ -891,7 +892,7 @@ class MergeFlag(IntFlag): merge base to `git-merge-resolve`. """ - VIRTUAL_BASE = C.GIT_MERGE_VIRTUAL_BASE + VIRTUAL_BASE = cast(int, C.GIT_MERGE_VIRTUAL_BASE) """ Treat this merge as if it is to produce the virtual base of a recursive merge. This will ensure that there are no conflicts, any conflicting @@ -1006,16 +1007,16 @@ class ReferenceFilter(IntEnum): class ReferenceType(IntFlag): """Basic type of any Git reference.""" - INVALID = C.GIT_REFERENCE_INVALID + INVALID = cast(int, C.GIT_REFERENCE_INVALID) 'Invalid reference' - DIRECT = C.GIT_REFERENCE_DIRECT + DIRECT = cast(int, C.GIT_REFERENCE_DIRECT) 'A reference that points at an object id' - SYMBOLIC = C.GIT_REFERENCE_SYMBOLIC + SYMBOLIC = cast(int, C.GIT_REFERENCE_SYMBOLIC) 'A reference that points at another reference' - ALL = C.GIT_REFERENCE_ALL + ALL = cast(int, C.GIT_REFERENCE_ALL) 'Bitwise OR of (DIRECT | SYMBOLIC)' @@ -1024,33 +1025,33 @@ class RepositoryInitFlag(IntFlag): Option flags for pygit2.init_repository(). """ - BARE = C.GIT_REPOSITORY_INIT_BARE + BARE = cast(int, C.GIT_REPOSITORY_INIT_BARE) 'Create a bare repository with no working directory.' - NO_REINIT = C.GIT_REPOSITORY_INIT_NO_REINIT + NO_REINIT = cast(int, C.GIT_REPOSITORY_INIT_NO_REINIT) 'Raise GitError if the path appears to already be a git repository.' - NO_DOTGIT_DIR = C.GIT_REPOSITORY_INIT_NO_DOTGIT_DIR + NO_DOTGIT_DIR = cast(int, C.GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) """Normally a "/.git/" will be appended to the repo path for non-bare repos (if it is not already there), but passing this flag prevents that behavior.""" - MKDIR = C.GIT_REPOSITORY_INIT_MKDIR + MKDIR = cast(int, C.GIT_REPOSITORY_INIT_MKDIR) """Make the repo_path (and workdir_path) as needed. Init is always willing to create the ".git" directory even without this flag. This flag tells init to create the trailing component of the repo and workdir paths as needed.""" - MKPATH = C.GIT_REPOSITORY_INIT_MKPATH + MKPATH = cast(int, C.GIT_REPOSITORY_INIT_MKPATH) 'Recursively make all components of the repo and workdir paths as necessary.' - EXTERNAL_TEMPLATE = C.GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE + EXTERNAL_TEMPLATE = cast(int, C.GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) """libgit2 normally uses internal templates to initialize a new repo. This flags enables external templates, looking at the "template_path" from the options if set, or the `init.templatedir` global config if not, or falling back on "/usr/share/git-core/templates" if it exists.""" - RELATIVE_GITLINK = C.GIT_REPOSITORY_INIT_RELATIVE_GITLINK + RELATIVE_GITLINK = cast(int, C.GIT_REPOSITORY_INIT_RELATIVE_GITLINK) """If an alternate workdir is specified, use relative paths for the gitdir and core.worktree.""" @@ -1060,16 +1061,16 @@ class RepositoryInitMode(IntEnum): Mode options for pygit2.init_repository(). """ - SHARED_UMASK = C.GIT_REPOSITORY_INIT_SHARED_UMASK + SHARED_UMASK = cast(int, C.GIT_REPOSITORY_INIT_SHARED_UMASK) 'Use permissions configured by umask - the default.' - SHARED_GROUP = C.GIT_REPOSITORY_INIT_SHARED_GROUP + SHARED_GROUP = cast(int, C.GIT_REPOSITORY_INIT_SHARED_GROUP) """ Use '--shared=group' behavior, chmod'ing the new repo to be group writable and "g+sx" for sticky group assignment. """ - SHARED_ALL = C.GIT_REPOSITORY_INIT_SHARED_ALL + SHARED_ALL = cast(int, C.GIT_REPOSITORY_INIT_SHARED_ALL) "Use '--shared=all' behavior, adding world readability." @@ -1081,14 +1082,14 @@ class RepositoryOpenFlag(IntFlag): DEFAULT = 0 'Default flags.' - NO_SEARCH = C.GIT_REPOSITORY_OPEN_NO_SEARCH + NO_SEARCH = cast(int, C.GIT_REPOSITORY_OPEN_NO_SEARCH) """ Only open the repository if it can be immediately found in the start_path. Do not walk up from the start_path looking at parent directories. """ - CROSS_FS = C.GIT_REPOSITORY_OPEN_CROSS_FS + CROSS_FS = cast(int, C.GIT_REPOSITORY_OPEN_CROSS_FS) """ Unless this flag is set, open will not continue searching across filesystem boundaries (i.e. when `st_dev` changes from the `stat` @@ -1097,21 +1098,21 @@ class RepositoryOpenFlag(IntFlag): "/" is a different filesystem than "/home". """ - BARE = C.GIT_REPOSITORY_OPEN_BARE + BARE = cast(int, C.GIT_REPOSITORY_OPEN_BARE) """ Open repository as a bare repo regardless of core.bare config, and defer loading config file for faster setup. Unlike `git_repository_open_bare`, this can follow gitlinks. """ - NO_DOTGIT = C.GIT_REPOSITORY_OPEN_NO_DOTGIT + NO_DOTGIT = cast(int, C.GIT_REPOSITORY_OPEN_NO_DOTGIT) """ Do not check for a repository by appending /.git to the start_path; only open the repository if start_path itself points to the git directory. """ - FROM_ENV = C.GIT_REPOSITORY_OPEN_FROM_ENV + FROM_ENV = cast(int, C.GIT_REPOSITORY_OPEN_FROM_ENV) """ Find and open a git repository, respecting the environment variables used by the git command-line tools. @@ -1135,18 +1136,18 @@ class RepositoryState(IntEnum): to be in, based on the current operation which is ongoing. """ - NONE = C.GIT_REPOSITORY_STATE_NONE - MERGE = C.GIT_REPOSITORY_STATE_MERGE - REVERT = C.GIT_REPOSITORY_STATE_REVERT - REVERT_SEQUENCE = C.GIT_REPOSITORY_STATE_REVERT_SEQUENCE - CHERRYPICK = C.GIT_REPOSITORY_STATE_CHERRYPICK - CHERRYPICK_SEQUENCE = C.GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE - BISECT = C.GIT_REPOSITORY_STATE_BISECT - REBASE = C.GIT_REPOSITORY_STATE_REBASE - REBASE_INTERACTIVE = C.GIT_REPOSITORY_STATE_REBASE_INTERACTIVE - REBASE_MERGE = C.GIT_REPOSITORY_STATE_REBASE_MERGE - APPLY_MAILBOX = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX - APPLY_MAILBOX_OR_REBASE = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE + NONE = cast(int, C.GIT_REPOSITORY_STATE_NONE) + MERGE = cast(int, C.GIT_REPOSITORY_STATE_MERGE) + REVERT = cast(int, C.GIT_REPOSITORY_STATE_REVERT) + REVERT_SEQUENCE = cast(int, C.GIT_REPOSITORY_STATE_REVERT_SEQUENCE) + CHERRYPICK = cast(int, C.GIT_REPOSITORY_STATE_CHERRYPICK) + CHERRYPICK_SEQUENCE = cast(int, C.GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE) + BISECT = cast(int, C.GIT_REPOSITORY_STATE_BISECT) + REBASE = cast(int, C.GIT_REPOSITORY_STATE_REBASE) + REBASE_INTERACTIVE = cast(int, C.GIT_REPOSITORY_STATE_REBASE_INTERACTIVE) + REBASE_MERGE = cast(int, C.GIT_REPOSITORY_STATE_REBASE_MERGE) + APPLY_MAILBOX = cast(int, C.GIT_REPOSITORY_STATE_APPLY_MAILBOX) + APPLY_MAILBOX_OR_REBASE = cast(int, C.GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE) class ResetMode(IntEnum): @@ -1214,27 +1215,27 @@ class StashApplyProgress(IntEnum): Stash apply progression states """ - NONE = C.GIT_STASH_APPLY_PROGRESS_NONE + NONE = cast(int, C.GIT_STASH_APPLY_PROGRESS_NONE) - LOADING_STASH = C.GIT_STASH_APPLY_PROGRESS_LOADING_STASH + LOADING_STASH = cast(int, C.GIT_STASH_APPLY_PROGRESS_LOADING_STASH) 'Loading the stashed data from the object database.' - ANALYZE_INDEX = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX + ANALYZE_INDEX = cast(int, C.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX) 'The stored index is being analyzed.' - ANALYZE_MODIFIED = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED + ANALYZE_MODIFIED = cast(int, C.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED) 'The modified files are being analyzed.' - ANALYZE_UNTRACKED = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED + ANALYZE_UNTRACKED = cast(int, C.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED) 'The untracked and ignored files are being analyzed.' - CHECKOUT_UNTRACKED = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED + CHECKOUT_UNTRACKED = cast(int, C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED) 'The untracked files are being written to disk.' - CHECKOUT_MODIFIED = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED + CHECKOUT_MODIFIED = cast(int, C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED) 'The modified files are being written to disk.' - DONE = C.GIT_STASH_APPLY_PROGRESS_DONE + DONE = cast(int, C.GIT_STASH_APPLY_PROGRESS_DONE) 'The stash was applied successfully.' diff --git a/pygit2/errors.py b/pygit2/errors.py index 3ecef9df..a84d4ed9 100644 --- a/pygit2/errors.py +++ b/pygit2/errors.py @@ -24,25 +24,26 @@ # Boston, MA 02110-1301, USA. # Import from pygit2 -from .ffi import ffi, C from ._pygit2 import GitError +from .ffi import C, ffi +value_errors = {C.GIT_EEXISTS, C.GIT_EINVALIDSPEC, C.GIT_EAMBIGUOUS} -value_errors = set([C.GIT_EEXISTS, C.GIT_EINVALIDSPEC, C.GIT_EAMBIGUOUS]) - -def check_error(err, io=False): +def check_error(err: int, io: bool = False) -> None: if err >= 0: return # These are special error codes, they should never reach here - test = err != C.GIT_EUSER and err != C.GIT_PASSTHROUGH + test = err not in (C.GIT_EUSER, C.GIT_PASSTHROUGH) assert test, f'Unexpected error code {err}' # Error message giterr = C.git_error_last() if giterr != ffi.NULL: - message = ffi.string(giterr.message).decode('utf8', errors='surrogateescape') + message = ffi.string(giterr.message) + if isinstance(message, bytes): + message = message.decode('utf8', errors='surrogateescape') else: message = f'err {err} (no message provided)' @@ -67,6 +68,6 @@ def check_error(err, io=False): # Indicate that we want libgit2 to pretend a function was not set -class Passthrough(Exception): +class Passthrough(NotImplementedError): def __init__(self): super().__init__('The function asked for pass-through') diff --git a/pygit2/ffi.py b/pygit2/ffi.py index 7b4c09c1..90fb2f58 100644 --- a/pygit2/ffi.py +++ b/pygit2/ffi.py @@ -24,4 +24,7 @@ # Boston, MA 02110-1301, USA. # Import from pygit2 -from ._libgit2 import ffi, lib as C +from ._libgit2 import ffi +from ._libgit2 import lib as C + +__all__ = ['ffi', 'C'] diff --git a/pygit2/index.py b/pygit2/index.py index c073fa6d..e8a2d5a9 100644 --- a/pygit2/index.py +++ b/pygit2/index.py @@ -27,12 +27,11 @@ import weakref # Import from pygit2 -from ._pygit2 import Oid, Tree, Diff +from ._pygit2 import Diff, Oid, Tree from .enums import DiffOption, FileMode from .errors import check_error -from .ffi import ffi, C -from .utils import to_bytes, to_str -from .utils import GenericIterator, StrArray +from .ffi import C, ffi +from .utils import GenericIterator, StrArray, to_bytes, to_str class Index: @@ -367,7 +366,7 @@ def oid(self): @property def hex(self): """The id of the referenced object as a hex string""" - warnings.warn('Use str(entry.id)', DeprecationWarning) + warnings.warn('Use str(entry.id)', DeprecationWarning, stacklevel=2) return str(self.id) def __str__(self): diff --git a/pygit2/packbuilder.py b/pygit2/packbuilder.py index b9844d52..ea538ee7 100644 --- a/pygit2/packbuilder.py +++ b/pygit2/packbuilder.py @@ -23,26 +23,32 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from __future__ import annotations + +from typing import TYPE_CHECKING, Any # Import from pygit2 +from ._pygit2 import Oid from .errors import check_error -from .ffi import ffi, C -from .utils import to_bytes +from .ffi import C, ffi +from .utils import StrOrBytesPath, buffer_to_bytes, to_bytes +if TYPE_CHECKING: + from .repository import BaseRepository class PackBuilder: - def __init__(self, repo): + def __init__(self, repo: BaseRepository): cpackbuilder = ffi.new('git_packbuilder **') err = C.git_packbuilder_new(cpackbuilder, repo._repo) check_error(err) self._repo = repo - self._packbuilder = cpackbuilder[0] + self._packbuilder: Any = cpackbuilder[0] self._cpackbuilder = cpackbuilder @property def _pointer(self): - return bytes(ffi.buffer(self._packbuilder)[:]) + return bytes(buffer_to_bytes(self._packbuilder)) def __del__(self): C.git_packbuilder_free(self._packbuilder) @@ -51,29 +57,31 @@ def __len__(self): return C.git_packbuilder_object_count(self._packbuilder) @staticmethod - def __convert_object_to_oid(oid): + def __convert_object_to_oid(oid: Oid): git_oid = ffi.new('git_oid *') ffi.buffer(git_oid)[:] = oid.raw[:] return git_oid - def add(self, oid): + def add(self, oid: Oid): 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): + def add_recur(self, oid: Oid): git_oid = self.__convert_object_to_oid(oid) err = C.git_packbuilder_insert_recur(self._packbuilder, git_oid, ffi.NULL) check_error(err) - def set_threads(self, n_threads): + def set_threads(self, n_threads: int): return C.git_packbuilder_set_threads(self._packbuilder, n_threads) - def write(self, path=None): - path = ffi.NULL if path is None else to_bytes(path) - err = C.git_packbuilder_write(self._packbuilder, path, 0, ffi.NULL, ffi.NULL) + def write(self, path: StrOrBytesPath | None = None): + resolved_path = ffi.NULL if path is None else to_bytes(path) + err = C.git_packbuilder_write( + self._packbuilder, resolved_path, 0, ffi.NULL, ffi.NULL + ) check_error(err) @property - def written_objects_count(self): + def written_objects_count(self) -> int: return C.git_packbuilder_written(self._packbuilder) diff --git a/pygit2/references.py b/pygit2/references.py index ca1d23dc..630c3fe5 100644 --- a/pygit2/references.py +++ b/pygit2/references.py @@ -24,6 +24,7 @@ # Boston, MA 02110-1301, USA. from __future__ import annotations + from typing import TYPE_CHECKING from .enums import ReferenceFilter diff --git a/pygit2/refspec.py b/pygit2/refspec.py index 447cf7dc..70b0f499 100644 --- a/pygit2/refspec.py +++ b/pygit2/refspec.py @@ -25,7 +25,7 @@ # Import from pygit2 from .errors import check_error -from .ffi import ffi, C +from .ffi import C, ffi from .utils import to_bytes diff --git a/pygit2/remotes.py b/pygit2/remotes.py index 30c2e5bd..ebc2a747 100644 --- a/pygit2/remotes.py +++ b/pygit2/remotes.py @@ -24,17 +24,19 @@ # Boston, MA 02110-1301, USA. from __future__ import annotations + from typing import TYPE_CHECKING +from . import utils + # Import from pygit2 from ._pygit2 import Oid from .callbacks import git_fetch_options, git_push_options, git_remote_callbacks from .enums import FetchPrune from .errors import check_error -from .ffi import ffi, C +from .ffi import C, ffi from .refspec import Refspec -from . import utils -from .utils import maybe_string, to_bytes, strarray_to_strings, StrArray +from .utils import StrArray, maybe_string, strarray_to_strings, to_bytes # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: @@ -185,10 +187,7 @@ def ls_remotes(self, callbacks=None, proxy=None): for i in range(int(refs_len[0])): ref = refs[0][i] local = bool(ref.local) - if local: - loid = Oid(raw=bytes(ffi.buffer(ref.loid.id)[:])) - else: - loid = None + loid = Oid(raw=bytes(ffi.buffer(ref.loid.id)[:])) if local else None remote = { 'local': local, diff --git a/pygit2/repository.py b/pygit2/repository.py index 9a6594d0..b175cf5e 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -22,23 +22,58 @@ # 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. + +from __future__ import annotations + +import tarfile +import typing import warnings from io import BytesIO from os import PathLike from string import hexdigits from time import time -import tarfile -import typing + +from ._pygit2 import ( + GIT_OID_HEXSZ, + GIT_OID_MINPREFIXLEN, + Blob, + Commit, + InvalidSpecError, + Object, + Oid, + Reference, + Signature, + Tree, + init_file_backend, +) # 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 Reference, Tree, Commit, Blob, Signature -from ._pygit2 import InvalidSpecError +from ._pygit2 import Repository as _Repository + +from ._pygit2 import ( + GIT_OID_HEXSZ, + GIT_OID_MINPREFIXLEN, + Blob, + Commit, + InvalidSpecError, + Object, + Oid, + 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 ( + CheckoutCallbacks, + StashApplyCallbacks, + git_checkout_options, + git_stash_apply_options, +) from .config import Config from .enums import ( AttrCheck, @@ -55,17 +90,22 @@ RepositoryState, ) from .errors import check_error -from .ffi import ffi, C +from .ffi import C, ffi from .index import Index, IndexEntry from .packbuilder import PackBuilder from .references import References from .remotes import RemoteCollection from .submodules import SubmoduleCollection -from .utils import to_bytes, StrArray +from .utils import StrArray, to_bytes +from .utils import StrArray, to_bytes + +T = typing.TypeVar('T') + +T = typing.TypeVar('T') class BaseRepository(_Repository): - def __init__(self, *args, **kwargs): + def __init__(self, *args: typing.Any, **kwargs: typing.Any): super().__init__(*args, **kwargs) self._common_init() @@ -82,22 +122,28 @@ def _common_init(self): self._repo = repo_cptr[0] # Backwards compatible ODB access - def read(self, *args, **kwargs): + # def read(self, *args, **kwargs): + def read(self, oid: Oid | str) -> tuple[int, 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) -> 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 | bytes | PathLike[str] | PathLike[bytes] | None = None, + pack_delegate: typing.Callable[[PackBuilder], object] | 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. @@ -115,7 +161,7 @@ def pack(self, path=None, pack_delegate=None, n_threads=None): The number of threads the `PackBuilder` will spawn. If set to 0, libgit2 will autodetect the number of CPUs. """ - def pack_all_objects(pack_builder): + def pack_all_objects(pack_builder: PackBuilder) -> None: for obj in self.odb: pack_builder.add(obj) @@ -131,10 +177,10 @@ def pack_all_objects(pack_builder): def hashfile( self, - path: str, + path: str | bytes | PathLike[str] | PathLike[bytes], object_type: ObjectType = ObjectType.BLOB, - as_path: typing.Optional[str] = None, - ): + as_path: typing.Optional[str | bytes | PathLike[str] | PathLike[bytes]] = 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, @@ -166,10 +212,7 @@ def hashfile( """ c_path = to_bytes(path) - if as_path is None: - c_as_path = ffi.NULL - else: - c_as_path = to_bytes(as_path) + c_as_path = ffi.NULL if as_path is None else to_bytes(as_path) c_oid = ffi.new('git_oid *') @@ -178,8 +221,7 @@ def hashfile( ) check_error(err) - oid = Oid(raw=bytes(ffi.buffer(c_oid.id)[:])) - return oid + return Oid(raw=bytes(ffi.buffer(c_oid.id)[:])) def __iter__(self): return iter(self.odb) @@ -187,27 +229,30 @@ def __iter__(self): # # Mapping interface # - def get(self, key, default=None): + def get(self, key: Oid | str, default: T = None) -> Object | T: value = self.git_object_lookup_prefix(key) return value if (value is not None) else default - def __getitem__(self, key): + def __getitem__(self, key: Object | Oid | str) -> Object: + # Maybe either returns itself or lookup???? + if isinstance(key, Object): + key = key.id value = self.git_object_lookup_prefix(key) if value is None: raise KeyError(key) return value - def __contains__(self, key): + def __contains__(self, key: Oid | str) -> 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)})' # # 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 @@ -221,7 +266,7 @@ def config(self): return Config.from_c(self, cconfig[0]) @property - def config_snapshot(self): + def config_snapshot(self) -> Config: """A snapshot for this repositiory's configuration This allows reads over multiple values to use the same version @@ -236,7 +281,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. @@ -258,7 +309,7 @@ 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 ) @@ -268,15 +319,15 @@ def create_reference(self, name, target, force=False, message=None): 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()) + return [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()) + return list(self.references.iterator()) - def resolve_refish(self, refish): + def resolve_refish(self, refish: str) -> tuple[Commit, Reference | None]: """Convert a reference-like short name "ref-ish" to a valid (commit, reference) pair. @@ -294,7 +345,7 @@ def resolve_refish(self, refish): reference = self.lookup_reference_dwim(refish) except (KeyError, InvalidSpecError): reference = None - commit = self.revparse_single(refish) + commit: Commit = self.revparse_single(refish) else: commit = reference.peel(Commit) @@ -304,21 +355,36 @@ def resolve_refish(self, refish): # Checkout # - def checkout_head(self, **kwargs): + def checkout_head( + self, + strategy: CheckoutStrategy = CheckoutStrategy.SAFE + | CheckoutStrategy.RECREATE_MISSING, + directory: str | None = None, + paths: list[str] | None = None, + callbacks: CheckoutCallbacks | None = None, + ): """Checkout HEAD For arguments, see Repository.checkout(). """ - with git_checkout_options(**kwargs) as payload: + with git_checkout_options(callbacks, strategy, directory, paths) as payload: err = C.git_checkout_head(self._repo, payload.checkout_options) payload.check_error(err) - def checkout_index(self, index=None, **kwargs): + def checkout_index( + self, + index: Index | None = None, + strategy: CheckoutStrategy = CheckoutStrategy.SAFE + | CheckoutStrategy.RECREATE_MISSING, + directory: str | None = None, + paths: list[str] | None = None, + callbacks: CheckoutCallbacks | None = None, + ) -> None: """Checkout the given index or the repository's index For arguments, see Repository.checkout(). """ - with git_checkout_options(**kwargs) as payload: + with git_checkout_options(callbacks, strategy, directory, paths) as payload: err = C.git_checkout_index( self._repo, index._index if index else ffi.NULL, @@ -326,18 +392,34 @@ def checkout_index(self, index=None, **kwargs): ) payload.check_error(err) - def checkout_tree(self, treeish, **kwargs): + def checkout_tree( + self, + treeish: typing.Any, + strategy: CheckoutStrategy = CheckoutStrategy.SAFE + | CheckoutStrategy.RECREATE_MISSING, + directory: str | None = None, + paths: list[str] | None = None, + callbacks: CheckoutCallbacks | None = None, + ) -> None: """Checkout the given treeish For arguments, see Repository.checkout(). """ - with git_checkout_options(**kwargs) as payload: + with git_checkout_options(callbacks, strategy, directory, paths) as payload: cptr = ffi.new('git_object **') ffi.buffer(cptr)[:] = treeish._pointer[:] 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 | Reference | None = None, + strategy: CheckoutStrategy = CheckoutStrategy.SAFE + | CheckoutStrategy.RECREATE_MISSING, + directory: str | None = None, + paths: list[str] | None = None, + callbacks: CheckoutCallbacks | None = None, + ): """ Checkout the given reference using the given strategy, and update the HEAD. @@ -382,11 +464,15 @@ def checkout(self, refname=None, **kwargs): # Case 1: Checkout index if refname is None: - return self.checkout_index(**kwargs) + return self.checkout_index( + strategy=strategy, directory=directory, paths=paths, callbacks=callbacks + ) # Case 2: Checkout head if refname == 'HEAD': - return self.checkout_head(**kwargs) + return self.checkout_head( + strategy=strategy, directory=directory, paths=paths, callbacks=callbacks + ) # Case 3: Reference if isinstance(refname, Reference): @@ -397,15 +483,21 @@ def checkout(self, refname=None, **kwargs): oid = reference.resolve().target treeish = self[oid] - self.checkout_tree(treeish, **kwargs) + self.checkout_tree( + treeish, + strategy=strategy, + directory=directory, + paths=paths, + callbacks=callbacks, + ) - if 'paths' not in kwargs: + if paths is None: self.set_head(refname) # # Setting HEAD # - def set_head(self, target): + def set_head(self, target: str | Oid) -> None: """ Set HEAD to point to the given target. @@ -429,34 +521,34 @@ def set_head(self, target): # # Diff # - def __whatever_to_tree_or_blob(self, obj): + def __whatever_to_tree_or_blob(self, obj: str | bytes | Oid | None): if obj is None: return None # If it's a string, then it has to be valid revspec - if isinstance(obj, str) or isinstance(obj, bytes): - obj = self.revparse_single(obj) - elif isinstance(obj, Oid): - obj = self[obj] + if isinstance(obj, (str, bytes)): # noqa: SIM108 + robj = self.revparse_single(obj) + else: + robj = self[obj] # First we try to get to a blob try: - obj = obj.peel(Blob) + robj = robj.peel(Blob) except Exception: # And if that failed, try to get a tree, raising a type # error if that still doesn't work try: - obj = obj.peel(Tree) + robj = robj.peel(Tree) except Exception: - raise TypeError(f'unexpected "{type(obj)}"') + raise TypeError(f'unexpected "{type(obj)}"') from None - return obj + return robj def diff( self, - a=None, - b=None, - cached=False, + a: str | Reference | None = None, + b: str | Reference | None = None, + cached: bool = False, flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, @@ -518,30 +610,34 @@ def diff( API (Tree.diff_to_tree()) directly. """ - a = self.__whatever_to_tree_or_blob(a) - b = self.__whatever_to_tree_or_blob(b) - - opt_keys = ['flags', 'context_lines', 'interhunk_lines'] - opt_values = [int(flags), context_lines, interhunk_lines] + resolved_a = self.__whatever_to_tree_or_blob(a) + resolved_b = self.__whatever_to_tree_or_blob(b) # Case 1: Diff tree to tree - if isinstance(a, Tree) and isinstance(b, Tree): - return a.diff_to_tree(b, **dict(zip(opt_keys, opt_values))) + if isinstance(resolved_a, Tree) and isinstance(resolved_b, Tree): + return resolved_a.diff_to_tree( + resolved_b, + flags=flags, + context_lines=context_lines, + interhunk_lines=interhunk_lines, + ) # Case 2: Index to workdir - elif a is None and b is None: - return self.index.diff_to_workdir(*opt_values) + elif resolved_a is None and resolved_b is None: + return self.index.diff_to_workdir(flags, context_lines, interhunk_lines) # Case 3: Diff tree to index or workdir - elif isinstance(a, Tree) and b is None: + elif isinstance(resolved_a, Tree) and resolved_b is None: if cached: - return a.diff_to_index(self.index, *opt_values) + return resolved_a.diff_to_index( + self.index, flags, context_lines, interhunk_lines + ) else: - return a.diff_to_workdir(*opt_values) + return resolved_a.diff_to_workdir(flags, context_lines, interhunk_lines) # Case 4: Diff blob to blob - if isinstance(a, Blob) and isinstance(b, Blob): - return a.diff(b) + if isinstance(resolved_a, Blob) and isinstance(resolved_b, Blob): + return resolved_a.diff(resolved_b) raise ValueError('Only blobs and treeish can be diffed') @@ -556,7 +652,7 @@ def state(self) -> RepositoryState: return RepositoryState(cstate) except ValueError: # Some value not in the IntEnum - newer libgit2 version? - return cstate + raise def state_cleanup(self): """Remove all the metadata associated with an ongoing command like @@ -570,13 +666,13 @@ 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, ): """ Return a Blame object for a single file. @@ -693,13 +789,11 @@ def merge_file_from_index( """ cmergeresult = ffi.new('git_merge_file_result *') - cancestor, ancestor_str_ref = ( + cancestor, _ = ( ancestor._to_c() if ancestor is not None else (ffi.NULL, ffi.NULL) ) - cours, ours_str_ref = ours._to_c() if ours is not None else (ffi.NULL, ffi.NULL) - ctheirs, theirs_str_ref = ( - theirs._to_c() if theirs is not None else (ffi.NULL, ffi.NULL) - ) + cours, _ = ours._to_c() if ours is not None else (ffi.NULL, ffi.NULL) + ctheirs, _ = theirs._to_c() if theirs is not None else (ffi.NULL, ffi.NULL) err = C.git_merge_file_from_index( cmergeresult, self._repo, cancestor, cours, ctheirs, ffi.NULL @@ -715,9 +809,9 @@ 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, + favor: MergeFavor = MergeFavor.NORMAL, + flags: MergeFlag = MergeFlag.FIND_RENAMES, + file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, ) -> Index: """ Merge two arbitrary commits. @@ -749,18 +843,20 @@ def merge_commits( theirs_ptr = ffi.new('git_commit **') cindex = ffi.new('git_index **') + object_ours, object_theirs = ours, theirs + if isinstance(ours, (str, Oid)): - ours = self[ours] + object_ours = self[ours] if isinstance(theirs, (str, Oid)): - theirs = self[theirs] + object_theirs = self[theirs] - ours = ours.peel(Commit) - theirs = theirs.peel(Commit) + cours = object_ours.peel(Commit) # type: ignore + ctheirs = object_theirs.peel(Commit) # type: ignore opts = self._merge_options(favor, flags, file_flags) - ffi.buffer(ours_ptr)[:] = ours._pointer[:] - ffi.buffer(theirs_ptr)[:] = theirs._pointer[:] + ffi.buffer(ours_ptr)[:] = cours._pointer[:] + ffi.buffer(theirs_ptr)[:] = ctheirs._pointer[:] err = C.git_merge_commits(cindex, self._repo, ours_ptr[0], theirs_ptr[0], opts) check_error(err) @@ -772,10 +868,10 @@ def merge_trees( 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, - ): + favor: MergeFavor = MergeFavor.NORMAL, + flags: MergeFlag = MergeFlag.FIND_RENAMES, + file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, + ) -> Index: """ Merge two trees. @@ -808,15 +904,15 @@ def merge_trees( cindex = ffi.new('git_index **') if isinstance(ancestor, (str, Oid)): - ancestor = self[ancestor] + object_ancestor = self[ancestor] if isinstance(ours, (str, Oid)): - ours = self[ours] + object_ours = self[ours] if isinstance(theirs, (str, Oid)): - theirs = self[theirs] + object_theirs = self[theirs] - ancestor = ancestor.peel(Tree) - ours = ours.peel(Tree) - theirs = theirs.peel(Tree) + ancestor = object_ancestor.peel(Tree) # type: ignore + ours = object_ours.peel(Tree) # type: ignore + theirs = object_theirs.peel(Tree) # type: ignore opts = self._merge_options(favor, flags, file_flags) @@ -834,10 +930,10 @@ def merge_trees( def merge( self, source: typing.Union[Reference, Commit, Oid, str], - favor=MergeFavor.NORMAL, - flags=MergeFlag.FIND_RENAMES, - file_flags=MergeFileFlag.DEFAULT, - ): + favor: MergeFavor = MergeFavor.NORMAL, + flags: MergeFlag = MergeFlag.FIND_RENAMES, + file_flags: MergeFileFlag = MergeFileFlag.DEFAULT, + ) -> None: """ Merges the given Reference or Commit into HEAD. @@ -923,7 +1019,7 @@ def raw_message(self) -> bytes: if err == C.GIT_ENOTFOUND: return b'' check_error(err) - return ffi.string(buf.ptr) + return ffi.string(buf.ptr) # type: ignore finally: C.git_buf_dispose(buf) @@ -958,16 +1054,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. @@ -1093,7 +1189,7 @@ def stash( include_ignored: bool = False, keep_all: bool = False, paths: typing.Optional[typing.List[str]] = None, - ): + ) -> Oid: """ Save changes to the working directory to the stash. @@ -1157,7 +1253,13 @@ def stash( return Oid(raw=bytes(ffi.buffer(coid)[:])) - def stash_apply(self, index=0, **kwargs): + def stash_apply( + self, + index: int = 0, + strategy: CheckoutStrategy | None = None, + reinstate_index: bool = False, + callbacks: StashApplyCallbacks | None = None, + ) -> None: """ Apply a stashed state in the stash list to the working directory. @@ -1191,11 +1293,11 @@ 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(callbacks, reinstate_index, strategy) 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. @@ -1393,7 +1495,7 @@ def get_attr( elif attr_kind == C.GIT_ATTR_VALUE_STRING: return ffi.string(cvalue[0]).decode('utf-8') - assert False, 'the attribute value from libgit2 is invalid' + raise AssertionError('the attribute value from libgit2 is invalid') # # Identity for reference operations @@ -1633,7 +1735,7 @@ def __init__( super().__init__() @classmethod - def _from_c(cls, ptr, owned): + def _from_c(cls, ptr, owned: bool) -> Repository: cptr = ffi.new('git_repository **') cptr[0] = ptr repo = cls.__new__(cls) diff --git a/pygit2/submodules.py b/pygit2/submodules.py index d8506d20..cebf36a1 100644 --- a/pygit2/submodules.py +++ b/pygit2/submodules.py @@ -24,14 +24,15 @@ # Boston, MA 02110-1301, USA. from __future__ import annotations + from typing import TYPE_CHECKING, Iterable, Iterator, Optional, Union from ._pygit2 import Oid -from .callbacks import git_fetch_options, RemoteCallbacks +from .callbacks import RemoteCallbacks, git_fetch_options from .enums import SubmoduleIgnore, SubmoduleStatus from .errors import check_error -from .ffi import ffi, C -from .utils import to_bytes, maybe_string +from .ffi import C, ffi +from .utils import maybe_string, to_bytes # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: diff --git a/pygit2/utils.py b/pygit2/utils.py index 1139b23c..f2aa32af 100644 --- a/pygit2/utils.py +++ b/pygit2/utils.py @@ -23,54 +23,97 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from __future__ import annotations + import contextlib import os +from types import TracebackType +from typing import TYPE_CHECKING, Any, Generic, TypeAlias, TypeVar, cast, overload # Import from pygit2 -from .ffi import ffi, C +from .ffi import C, ffi + +if TYPE_CHECKING: + from _cffi_backend import _CDataBase as CData + from _typeshed import SupportsLenAndGetItem + +_T = TypeVar('_T') +StrOrBytesPath: TypeAlias = str | bytes | os.PathLike[str] | os.PathLike[bytes] -def maybe_string(ptr): +def maybe_bytes(ptr: CData | Any) -> bytes | None: if not ptr: return None - return ffi.string(ptr).decode('utf8', errors='surrogateescape') + out = ffi.string(ptr) + if isinstance(out, bytes): + out = out.decode('utf8', errors='surrogateescape') + return out def to_bytes(s, encoding='utf-8', errors='strict'): + out = ffi.string(ptr) + return cast(bytes, out) + + +def maybe_string(ptr: CData | Any) -> str | None: + out = maybe_bytes(ptr) + if isinstance(out, bytes): + out = out.decode('utf8') + return out + + +# Cannot describe ffi.NULL, so using CData + + +@overload +def to_bytes(s: CData | None) -> CData: ... +@overload +def to_bytes(s: StrOrBytesPath) -> bytes: ... + + +def to_bytes( + s: StrOrBytesPath | CData | None, + encoding: str = 'utf-8', + errors: str = 'strict', +) -> bytes | CData: if s == ffi.NULL or s is None: return ffi.NULL - if hasattr(s, '__fspath__'): + if isinstance(s, os.PathLike): s = os.fspath(s) if isinstance(s, bytes): return s - return s.encode(encoding, errors) + return cast(str, s).encode(encoding, errors) -def to_str(s): +def to_str(s: StrOrBytesPath) -> str: if hasattr(s, '__fspath__'): s = os.fspath(s) - if type(s) is str: + if isinstance(s, str): return s - if type(s) is bytes: + if isinstance(s, bytes): return s.decode() raise TypeError(f'unexpected type "{repr(s)}"') -def ptr_to_bytes(ptr_cdata): +def buffer_to_bytes(cdata: CData) -> bytes: + buf = ffi.buffer(cdata) + return cast(bytes, buf[:]) + + +def ptr_to_bytes(ptr_cdata: Any) -> bytes: """ Convert a pointer coming from C code () to a byte buffer containing the address that the pointer refers to. """ - pp = ffi.new('void **', ptr_cdata) - return bytes(ffi.buffer(pp)[:]) + return buffer_to_bytes(ffi.new('void **', ptr_cdata)) @contextlib.contextmanager @@ -80,7 +123,7 @@ def new_git_strarray(): C.git_strarray_dispose(strarray) -def strarray_to_strings(arr): +def strarray_to_strings(arr: Any) -> list[str]: """ Return a list of strings from a git_strarray pointer. @@ -88,7 +131,7 @@ def strarray_to_strings(arr): calling this function. """ try: - return [ffi.string(arr.strings[i]).decode('utf-8') for i in range(arr.count)] + return [ffi.string(arr.strings[i]).decode('utf-8') for i in range(arr.count)] # type: ignore finally: C.git_strarray_dispose(arr) @@ -113,18 +156,20 @@ class StrArray: contents of 'struct' only remain valid within the StrArray context. """ - def __init__(self, lst): + def __init__(self, listarg: Any): # Allow passing in None as lg2 typically considers them the same as empty - if lst is None: + if listarg is None: self.__array = ffi.NULL return - if not isinstance(lst, (list, tuple)): + if not isinstance(listarg, (list, tuple)): raise TypeError('Value must be a list') - strings = [None] * len(lst) - for i in range(len(lst)): - li = lst[i] + listarg = cast(list[Any], listarg) + + strings: list[Any] = [None] * len(listarg) + for i in range(len(listarg)): + li = listarg[i] if not isinstance(li, str) and not hasattr(li, '__fspath__'): raise TypeError('Value must be a string or PathLike object') @@ -137,14 +182,16 @@ def __init__(self, lst): def __enter__(self): return self - def __exit__(self, type, value, traceback): + def __exit__( + self, type: type[BaseException], value: BaseException, traceback: TracebackType + ) -> None: pass @property def ptr(self): return self.__array - def assign_to(self, git_strarray): + def assign_to(self, git_strarray: Any): if self.__array == ffi.NULL: git_strarray.strings = ffi.NULL git_strarray.count = 0 @@ -153,22 +200,22 @@ def assign_to(self, git_strarray): git_strarray.count = len(self.__strings) -class GenericIterator: +class GenericIterator(Generic[_T]): """Helper to easily implement an iterator. The constructor gets a container which must implement __len__ and __getitem__ """ - def __init__(self, container): + def __init__(self, container: SupportsLenAndGetItem[_T]): self.container = container self.length = len(container) self.idx = 0 - def __iter__(self): - return self + def next(self) -> _T: + return self.__next__() - def __next__(self): + def __next__(self) -> _T: idx = self.idx if idx >= self.length: raise StopIteration diff --git a/pyproject.toml b/pyproject.toml index 4971280d..3ec6d477 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,10 +27,47 @@ repair-wheel-command = "DYLD_LIBRARY_PATH=/Users/runner/work/pygit2/pygit2/ci/li [tool.ruff] extend-exclude = [ ".cache", ".coverage", "build", "site-packages", "venv*"] target-version = "py310" # oldest supported Python version +fix = true +lint.select = [ + "C", + "E", + "W", + "F", + "I", + "B", + "C4", + "ARG", + "SIM", + "PTH", + "PL", + "TID", +] +lint.ignore = [ + "W291", # Trailing whitespace + "E501", # Line too long + "W293", # Blank line contains whitespace + "PLR0912", # Too many branches + "PLR2004", # Magic values + "PLR0915", # Too many statements + "PLW0603", # Global statement + "PLR0913", # Too many arguments + "B010", # setattr + "F401", # unused imports + "ARG002", # unused arguments +] [tool.ruff.format] quote-style = "single" +[tool.pyright] +typeCheckingMode = "strict" +pythonVersion = "3.10" +reportPrivateUsage = "none" +reportAttributeAccessIssue = "none" +reportUnknownMemberType = "none" +reportUnusedFunction = "none" +reportUnnecessaryIsInstance = "none" + [tool.codespell] # Ref: https://github.com/codespell-project/codespell#using-a-config-file skip = '.git*' diff --git a/setup.py b/setup.py index 88d1536d..1ad61821 100644 --- a/setup.py +++ b/setup.py @@ -24,15 +24,15 @@ # Boston, MA 02110-1301, USA. # Import setuptools before distutils to avoid user warning -from setuptools import setup, Extension - +import os +import sys +from distutils import log from distutils.command.build import build from distutils.command.sdist import sdist -from distutils import log -import os from pathlib import Path -from subprocess import Popen, PIPE -import sys +from subprocess import PIPE, Popen + +from setuptools import Extension, setup # Import stuff from pygit2/_utils.py without loading the whole pygit2 package sys.path.insert(0, 'pygit2') diff --git a/test/conftest.py b/test/conftest.py index 1c6d7b8f..6feb9d23 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,8 +1,10 @@ -from pathlib import Path import platform +from pathlib import Path import pytest + import pygit2 + from . import utils diff --git a/test/test_apply_diff.py b/test/test_apply_diff.py index 87e766dd..b36381e0 100644 --- a/test/test_apply_diff.py +++ b/test/test_apply_diff.py @@ -23,13 +23,14 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -import pygit2 -from pygit2.enums import ApplyLocation, CheckoutStrategy, FileStatus -import pytest - import os from pathlib import Path +import pytest + +import pygit2 +from pygit2.enums import ApplyLocation, CheckoutStrategy, FileStatus + def read_content(testrepo): with (Path(testrepo.workdir) / 'hello.txt').open('rb') as f: diff --git a/test/test_archive.py b/test/test_archive.py index 7e2454f1..7d52b467 100644 --- a/test/test_archive.py +++ b/test/test_archive.py @@ -23,11 +23,10 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -from pathlib import Path import tarfile +from pathlib import Path -from pygit2 import Index, Oid, Tree, Object - +from pygit2 import Index, Object, Oid, Tree TREE_HASH = 'fd937514cb799514d4b81bb24c5fcfeb6472b245' COMMIT_HASH = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' diff --git a/test/test_attributes.py b/test/test_attributes.py index 00ac91ad..66fc58c9 100644 --- a/test/test_attributes.py +++ b/test/test_attributes.py @@ -38,7 +38,7 @@ def test_no_attr(testrepo): assert testrepo.get_attr('file.py', 'foo') is None assert testrepo.get_attr('file.py', 'text') assert not testrepo.get_attr('file.jpg', 'text') - assert 'lf' == testrepo.get_attr('file.sh', 'eol') + assert testrepo.get_attr('file.sh', 'eol') == 'lf' def test_no_attr_aspath(testrepo): diff --git a/test/test_blame.py b/test/test_blame.py index 251f7e6d..0afb8c1a 100644 --- a/test/test_blame.py +++ b/test/test_blame.py @@ -27,10 +27,9 @@ import pytest -from pygit2 import Signature, Oid +from pygit2 import Oid, Signature from pygit2.enums import BlameFlag - PATH = 'hello.txt' HUNKS = [ @@ -109,7 +108,7 @@ def test(): def test_blame_for_line(testrepo): blame = testrepo.blame(PATH) - for i, line in zip(range(0, 2), range(1, 3)): + for i, line in zip(range(0, 2), range(1, 3), strict=False): hunk = blame.for_line(line) assert hunk.lines_in_hunk == 1 diff --git a/test/test_blob.py b/test/test_blob.py index c9025f49..8852a7ab 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -27,15 +27,15 @@ import io from pathlib import Path -from threading import Event from queue import Queue +from threading import Event import pytest import pygit2 from pygit2.enums import ObjectType -from . import utils +from . import utils BLOB_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b' BLOB_CONTENT = """hello world @@ -86,10 +86,10 @@ def test_read_blob(testrepo): assert blob.id == BLOB_SHA assert isinstance(blob, pygit2.Blob) assert not blob.is_binary - assert ObjectType.BLOB == blob.type - assert BLOB_CONTENT == blob.data + assert blob.type == ObjectType.BLOB + assert blob.data == BLOB_CONTENT assert len(BLOB_CONTENT) == blob.size - assert BLOB_CONTENT == blob.read_raw() + assert blob.read_raw() == BLOB_CONTENT def test_create_blob(testrepo): @@ -97,17 +97,17 @@ def test_create_blob(testrepo): blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) - assert ObjectType.BLOB == blob.type + assert blob.type == ObjectType.BLOB assert blob_oid == blob.id assert utils.gen_blob_sha1(BLOB_NEW_CONTENT) == blob_oid - assert BLOB_NEW_CONTENT == blob.data + assert blob.data == BLOB_NEW_CONTENT assert len(BLOB_NEW_CONTENT) == blob.size - assert BLOB_NEW_CONTENT == blob.read_raw() + assert blob.read_raw() == BLOB_NEW_CONTENT blob_buffer = memoryview(blob) assert len(BLOB_NEW_CONTENT) == len(blob_buffer) - assert BLOB_NEW_CONTENT == blob_buffer + assert blob_buffer == BLOB_NEW_CONTENT def set_content(): blob_buffer[:2] = b'hi' @@ -121,14 +121,14 @@ def test_create_blob_fromworkdir(testrepo): blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) - assert ObjectType.BLOB == blob.type + assert blob.type == ObjectType.BLOB assert blob_oid == blob.id assert utils.gen_blob_sha1(BLOB_FILE_CONTENT) == blob_oid - assert BLOB_FILE_CONTENT == blob.data + assert blob.data == BLOB_FILE_CONTENT assert len(BLOB_FILE_CONTENT) == blob.size - assert BLOB_FILE_CONTENT == blob.read_raw() + assert blob.read_raw() == BLOB_FILE_CONTENT def test_create_blob_fromworkdir_aspath(testrepo): @@ -148,7 +148,7 @@ def test_create_blob_fromdisk(testrepo): blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) - assert ObjectType.BLOB == blob.type + assert blob.type == ObjectType.BLOB def test_create_blob_fromiobase(testrepo): @@ -160,10 +160,10 @@ def test_create_blob_fromiobase(testrepo): blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) - assert ObjectType.BLOB == blob.type + assert blob.type == ObjectType.BLOB assert blob_oid == blob.id - assert BLOB_SHA == blob_oid + assert blob_oid == BLOB_SHA def test_diff_blob(testrepo): @@ -220,7 +220,7 @@ def test_blob_write_to_queue(testrepo): chunks = [] while not queue.empty(): chunks.append(queue.get()) - assert BLOB_CONTENT == b''.join(chunks) + assert b''.join(chunks) == BLOB_CONTENT def test_blob_write_to_queue_filtered(testrepo): @@ -235,14 +235,14 @@ def test_blob_write_to_queue_filtered(testrepo): chunks = [] while not queue.empty(): chunks.append(queue.get()) - assert b'bye world\n' == b''.join(chunks) + assert b''.join(chunks) == b'bye world\n' def test_blobio(testrepo): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] with pygit2.BlobIO(blob) as reader: - assert b'bye world\n' == reader.read() + assert reader.read() == b'bye world\n' assert not reader.raw._thread.is_alive() @@ -250,5 +250,5 @@ def test_blobio_filtered(testrepo): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] with pygit2.BlobIO(blob, as_path='bye.txt') as reader: - assert b'bye world\n' == reader.read() + assert reader.read() == b'bye world\n' assert not reader.raw._thread.is_alive() diff --git a/test/test_branch.py b/test/test_branch.py index bfc944c0..f188d7fd 100644 --- a/test/test_branch.py +++ b/test/test_branch.py @@ -25,11 +25,12 @@ """Tests for branch methods.""" -import pygit2 -import pytest import os -from pygit2.enums import BranchType +import pytest + +import pygit2 +from pygit2.enums import BranchType LAST_COMMIT = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' I18N_LAST_COMMIT = '5470a671a80ac3789f1a6a8cefbcf43ce7af0563' diff --git a/test/test_branch_empty.py b/test/test_branch_empty.py index c1d07970..56198535 100644 --- a/test/test_branch_empty.py +++ b/test/test_branch_empty.py @@ -24,8 +24,8 @@ # Boston, MA 02110-1301, USA. import pytest -from pygit2.enums import BranchType +from pygit2.enums import BranchType ORIGIN_MASTER_COMMIT = '784855caf26449a1914d2cf62d12b9374d76ae78' diff --git a/test/test_cherrypick.py b/test/test_cherrypick.py index 136e7d26..d1aebbe1 100644 --- a/test/test_cherrypick.py +++ b/test/test_cherrypick.py @@ -26,6 +26,7 @@ """Tests for merging and information about it.""" from pathlib import Path + import pytest import pygit2 diff --git a/test/test_commit.py b/test/test_commit.py index 8967e5cd..02e81488 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -29,10 +29,10 @@ import pytest -from pygit2 import Signature, Oid, GitError +from pygit2 import GitError, Oid, Signature from pygit2.enums import ObjectType -from . import utils +from . import utils COMMIT_SHA = '5fe808e8953c12735680c257f56600cb0de44b10' COMMIT_SHA_TO_AMEND = ( @@ -52,10 +52,10 @@ def test_commit_refcount(barerepo): def test_read_commit(barerepo): commit = barerepo[COMMIT_SHA] - assert COMMIT_SHA == commit.id + assert commit.id == COMMIT_SHA parents = commit.parents - assert 1 == len(parents) - assert 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c' == parents[0].id + assert len(parents) == 1 + assert parents[0].id == 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c' assert commit.message_encoding is None assert commit.message == ( 'Second test data commit.\n\nThis commit has some additional text.\n' @@ -68,7 +68,7 @@ def test_read_commit(barerepo): assert commit.author == Signature( 'Dave Borowitz', 'dborowitz@google.com', 1288477363, -420 ) - assert '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' == commit.tree.id + assert commit.tree.id == '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' def test_new_commit(barerepo): @@ -89,17 +89,17 @@ def test_new_commit(barerepo): sha = repo.create_commit(None, author, committer, message, tree_prefix, parents) commit = repo[sha] - assert ObjectType.COMMIT == commit.type - assert '98286caaab3f1fde5bf52c8369b2b0423bad743b' == commit.id + assert commit.type == ObjectType.COMMIT + assert commit.id == '98286caaab3f1fde5bf52c8369b2b0423bad743b' assert commit.message_encoding is None assert message == commit.message - assert 12346 == commit.commit_time + assert commit.commit_time == 12346 assert committer == commit.committer assert author == commit.author assert tree == commit.tree.id assert Oid(hex=tree) == commit.tree_id - assert 1 == len(commit.parents) - assert COMMIT_SHA == commit.parents[0].id + assert len(commit.parents) == 1 + assert commit.parents[0].id == COMMIT_SHA assert Oid(hex=COMMIT_SHA) == commit.parent_ids[0] @@ -118,16 +118,16 @@ def test_new_commit_encoding(barerepo): ) commit = repo[sha] - assert ObjectType.COMMIT == commit.type - assert 'iso-8859-1' == commit.message_encoding + assert commit.type == ObjectType.COMMIT + assert commit.message_encoding == 'iso-8859-1' assert message.encode(encoding) == commit.raw_message - assert 12346 == commit.commit_time + assert commit.commit_time == 12346 assert committer == commit.committer assert author == commit.author assert tree == commit.tree.id assert Oid(hex=tree) == commit.tree_id - assert 1 == len(commit.parents) - assert COMMIT_SHA == commit.parents[0].id + assert len(commit.parents) == 1 + assert commit.parents[0].id == COMMIT_SHA assert Oid(hex=COMMIT_SHA) == commit.parent_ids[0] @@ -175,7 +175,7 @@ def test_amend_commit_metadata(barerepo): amended_commit = repo[amended_oid] assert repo.head.target == amended_oid - assert ObjectType.COMMIT == amended_commit.type + assert amended_commit.type == ObjectType.COMMIT assert amended_committer == amended_commit.committer assert amended_author == amended_commit.author assert amended_message.encode(encoding) == amended_commit.raw_message @@ -196,7 +196,7 @@ def test_amend_commit_tree(barerepo): amended_commit = repo[amended_oid] assert repo.head.target == amended_oid - assert ObjectType.COMMIT == amended_commit.type + assert amended_commit.type == ObjectType.COMMIT assert commit.message == amended_commit.message assert commit.author == amended_commit.author assert commit.committer == amended_commit.committer @@ -267,13 +267,13 @@ def test_amend_commit_argument_types(barerepo): # Pass an Oid for the commit amended_oid = repo.amend_commit(alt_commit1, None, message='Hello') amended_commit = repo[amended_oid] - assert ObjectType.COMMIT == amended_commit.type + assert amended_commit.type == ObjectType.COMMIT assert amended_oid != COMMIT_SHA_TO_AMEND # Pass a str for the commit amended_oid = repo.amend_commit(alt_commit2, None, message='Hello', tree=alt_tree) amended_commit = repo[amended_oid] - assert ObjectType.COMMIT == amended_commit.type + assert amended_commit.type == ObjectType.COMMIT assert amended_oid != COMMIT_SHA_TO_AMEND assert repo[COMMIT_SHA_TO_AMEND].tree != amended_commit.tree assert alt_tree.id == amended_commit.tree_id @@ -282,6 +282,6 @@ def test_amend_commit_argument_types(barerepo): # (Warning: the tip of the branch will be altered after this test!) amended_oid = repo.amend_commit(alt_commit2, alt_refname, message='Hello') amended_commit = repo[amended_oid] - assert ObjectType.COMMIT == amended_commit.type + assert amended_commit.type == ObjectType.COMMIT assert amended_oid != COMMIT_SHA_TO_AMEND assert repo.head.target == amended_oid diff --git a/test/test_commit_gpg.py b/test/test_commit_gpg.py index 88450b6c..822cce71 100644 --- a/test/test_commit_gpg.py +++ b/test/test_commit_gpg.py @@ -119,16 +119,16 @@ def test_commit_signing(gpgsigned): assert gpgsig_content == commit.read_raw().decode('utf-8') # perform sanity checks - assert ObjectType.COMMIT == commit.type - assert '6569fdf71dbd99081891154641869c537784a3ba' == commit.id + assert commit.type == ObjectType.COMMIT + assert commit.id == '6569fdf71dbd99081891154641869c537784a3ba' assert commit.message_encoding is None assert message == commit.message - assert 1358451456 == commit.commit_time + assert commit.commit_time == 1358451456 assert committer == commit.committer assert author == commit.author assert tree == commit.tree.id assert Oid(hex=tree) == commit.tree_id - assert 1 == len(commit.parents) + assert len(commit.parents) == 1 assert parent == commit.parents[0].id assert Oid(hex=parent) == commit.parent_ids[0] diff --git a/test/test_commit_trailer.py b/test/test_commit_trailer.py index d7236cd8..05504759 100644 --- a/test/test_commit_trailer.py +++ b/test/test_commit_trailer.py @@ -23,9 +23,10 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -import pygit2 import pytest +import pygit2 + from . import utils diff --git a/test/test_config.py b/test/test_config.py index 0284d76f..5f31007d 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -23,13 +23,14 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +import contextlib from pathlib import Path import pytest from pygit2 import Config -from . import utils +from . import utils CONFIG_FILENAME = 'test_config' @@ -37,10 +38,8 @@ @pytest.fixture def config(testrepo): yield testrepo.config - try: + with contextlib.suppress(OSError): Path(CONFIG_FILENAME).unlink() - except OSError: - pass def test_config(config): @@ -155,23 +154,24 @@ def test_multivar(): config.add_file(CONFIG_FILENAME, 6) assert 'this.that' in config - assert ['foobar', 'foobeer'] == list(config.get_multivar('this.that')) - assert ['foobar'] == list(config.get_multivar('this.that', 'bar')) - assert ['foobar', 'foobeer'] == list(config.get_multivar('this.that', 'foo.*')) + assert list(config.get_multivar('this.that')) == ['foobar', 'foobeer'] + assert list(config.get_multivar('this.that', 'bar')) == ['foobar'] + assert list(config.get_multivar('this.that', 'foo.*')) == ['foobar', 'foobeer'] config.set_multivar('this.that', '^.*beer', 'fool') - assert ['fool'] == list(config.get_multivar('this.that', 'fool')) + assert list(config.get_multivar('this.that', 'fool')) == ['fool'] config.set_multivar('this.that', 'foo.*', 'foo-123456') - assert ['foo-123456', 'foo-123456'] == list( - config.get_multivar('this.that', 'foo.*') - ) + assert list(config.get_multivar('this.that', 'foo.*')) == [ + 'foo-123456', + 'foo-123456', + ] config.delete_multivar('this.that', 'bar') - assert ['foo-123456', 'foo-123456'] == list(config.get_multivar('this.that', '')) + assert list(config.get_multivar('this.that', '')) == ['foo-123456', 'foo-123456'] config.delete_multivar('this.that', 'foo-[0-9]+') - assert [] == list(config.get_multivar('this.that', '')) + assert list(config.get_multivar('this.that', '')) == [] def test_iterator(config): @@ -188,5 +188,5 @@ def test_parsing(): assert Config.parse_bool('on') assert Config.parse_bool('1') - assert 5 == Config.parse_int('5') - assert 1024 == Config.parse_int('1k') + assert Config.parse_int('5') == 5 + assert Config.parse_int('1k') == 1024 diff --git a/test/test_credentials.py b/test/test_credentials.py index e9578b36..182883dd 100644 --- a/test/test_credentials.py +++ b/test/test_credentials.py @@ -23,16 +23,18 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -from pathlib import Path +"""Tests for credentials""" + import platform +from pathlib import Path import pytest import pygit2 -from pygit2 import Username, UserPass, Keypair, KeypairFromAgent, KeypairFromMemory +from pygit2 import Keypair, KeypairFromAgent, KeypairFromMemory, Username, UserPass from pygit2.enums import CredentialType -from . import utils +from . import utils REMOTE_NAME = 'origin' REMOTE_URL = 'git://github.com/libgit2/pygit2.git' diff --git a/test/test_describe.py b/test/test_describe.py index 22650a5d..c7f42390 100644 --- a/test/test_describe.py +++ b/test/test_describe.py @@ -27,8 +27,8 @@ import pytest -from pygit2.enums import DescribeStrategy, ObjectType import pygit2 +from pygit2.enums import DescribeStrategy, ObjectType def add_tag(repo, name, target): @@ -41,7 +41,7 @@ def add_tag(repo, name, target): def test_describe(testrepo): add_tag(testrepo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8') - assert 'thetag-2-g2be5719' == testrepo.describe() + assert testrepo.describe() == 'thetag-2-g2be5719' def test_describe_without_ref(testrepo): @@ -50,19 +50,20 @@ def test_describe_without_ref(testrepo): def test_describe_default_oid(testrepo): - assert '2be5719' == testrepo.describe(show_commit_oid_as_fallback=True) + assert testrepo.describe(show_commit_oid_as_fallback=True) == '2be5719' def test_describe_strategies(testrepo): - assert 'heads/master' == testrepo.describe(describe_strategy=DescribeStrategy.ALL) + assert testrepo.describe(describe_strategy=DescribeStrategy.ALL) == 'heads/master' testrepo.create_reference( 'refs/tags/thetag', '4ec4389a8068641da2d6578db0419484972284c8' ) with pytest.raises(KeyError): testrepo.describe() - assert 'thetag-2-g2be5719' == testrepo.describe( - describe_strategy=DescribeStrategy.TAGS + assert ( + testrepo.describe(describe_strategy=DescribeStrategy.TAGS) + == 'thetag-2-g2be5719' ) @@ -70,20 +71,21 @@ def test_describe_pattern(testrepo): add_tag(testrepo, 'private/tag1', '5ebeeebb320790caf276b9fc8b24546d63316533') add_tag(testrepo, 'public/tag2', '4ec4389a8068641da2d6578db0419484972284c8') - assert 'public/tag2-2-g2be5719' == testrepo.describe(pattern='public/*') + assert testrepo.describe(pattern='public/*') == 'public/tag2-2-g2be5719' def test_describe_committish(testrepo): add_tag(testrepo, 'thetag', 'acecd5ea2924a4b900e7e149496e1f4b57976e51') - assert 'thetag-4-g2be5719' == testrepo.describe(committish='HEAD') - assert 'thetag-1-g5ebeeeb' == testrepo.describe(committish='HEAD^') + assert testrepo.describe(committish='HEAD') == 'thetag-4-g2be5719' + assert testrepo.describe(committish='HEAD^') == 'thetag-1-g5ebeeeb' - assert 'thetag-4-g2be5719' == testrepo.describe(committish=testrepo.head) + assert testrepo.describe(committish=testrepo.head) == 'thetag-4-g2be5719' - assert 'thetag-1-g6aaa262' == testrepo.describe( - committish='6aaa262e655dd54252e5813c8e5acd7780ed097d' + assert ( + testrepo.describe(committish='6aaa262e655dd54252e5813c8e5acd7780ed097d') + == 'thetag-1-g6aaa262' ) - assert 'thetag-1-g6aaa262' == testrepo.describe(committish='6aaa262') + assert testrepo.describe(committish='6aaa262') == 'thetag-1-g6aaa262' def test_describe_follows_first_branch_only(testrepo): @@ -94,20 +96,20 @@ def test_describe_follows_first_branch_only(testrepo): def test_describe_abbreviated_size(testrepo): add_tag(testrepo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8') - assert 'thetag-2-g2be5719152d4f82c' == testrepo.describe(abbreviated_size=16) - assert 'thetag' == testrepo.describe(abbreviated_size=0) + assert testrepo.describe(abbreviated_size=16) == 'thetag-2-g2be5719152d4f82c' + assert testrepo.describe(abbreviated_size=0) == 'thetag' def test_describe_long_format(testrepo): add_tag(testrepo, 'thetag', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98') - assert 'thetag-0-g2be5719' == testrepo.describe(always_use_long_format=True) + assert testrepo.describe(always_use_long_format=True) == 'thetag-0-g2be5719' def test_describe_dirty(dirtyrepo): add_tag(dirtyrepo, 'thetag', 'a763aa560953e7cfb87ccbc2f536d665aa4dff22') - assert 'thetag' == dirtyrepo.describe() + assert dirtyrepo.describe() == 'thetag' def test_describe_dirty_with_suffix(dirtyrepo): add_tag(dirtyrepo, 'thetag', 'a763aa560953e7cfb87ccbc2f536d665aa4dff22') - assert 'thetag-dirty' == dirtyrepo.describe(dirty_suffix='-dirty') + assert dirtyrepo.describe(dirty_suffix='-dirty') == 'thetag-dirty' diff --git a/test/test_diff.py b/test/test_diff.py index 28e1b0c3..3ba2d00a 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -25,15 +25,14 @@ """Tests for Diff objects.""" -from itertools import chain import textwrap +from itertools import chain import pytest import pygit2 from pygit2.enums import DeltaStatus, DiffFlag, DiffOption, DiffStatsFormat, FileMode - COMMIT_SHA1_1 = '5fe808e8953c12735680c257f56600cb0de44b10' COMMIT_SHA1_2 = 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c' COMMIT_SHA1_3 = '2cdae28389c059815e951d0bb9eed6533f61a46b' @@ -114,11 +113,11 @@ def test_diff_empty_index(dirtyrepo): diff = head.tree.diff_to_index(repo.index) files = [patch.delta.new_file.path for patch in diff] - assert DIFF_HEAD_TO_INDEX_EXPECTED == files + assert files == DIFF_HEAD_TO_INDEX_EXPECTED diff = repo.diff('HEAD', cached=True) files = [patch.delta.new_file.path for patch in diff] - assert DIFF_HEAD_TO_INDEX_EXPECTED == files + assert files == DIFF_HEAD_TO_INDEX_EXPECTED def test_workdir_to_tree(dirtyrepo): @@ -127,17 +126,17 @@ def test_workdir_to_tree(dirtyrepo): diff = head.tree.diff_to_workdir() files = [patch.delta.new_file.path for patch in diff] - assert DIFF_HEAD_TO_WORKDIR_EXPECTED == files + assert files == DIFF_HEAD_TO_WORKDIR_EXPECTED diff = repo.diff('HEAD') files = [patch.delta.new_file.path for patch in diff] - assert DIFF_HEAD_TO_WORKDIR_EXPECTED == files + assert files == DIFF_HEAD_TO_WORKDIR_EXPECTED def test_index_to_workdir(dirtyrepo): diff = dirtyrepo.diff() files = [patch.delta.new_file.path for patch in diff] - assert DIFF_INDEX_TO_WORK_EXPECTED == files + assert files == DIFF_INDEX_TO_WORK_EXPECTED def test_diff_invalid(barerepo): @@ -172,7 +171,7 @@ def test_diff_tree(barerepo): def _test(diff): assert diff is not None - assert 2 == sum(map(lambda x: len(x.hunks), diff)) + assert sum((len(x.hunks) for x in diff)) == 2 patch = diff[0] hunk = patch.hunks[0] @@ -204,18 +203,18 @@ def test_diff_empty_tree(barerepo): diff = commit_a.tree.diff_to_tree() def get_context_for_lines(diff): - hunks = chain.from_iterable(map(lambda x: x.hunks, diff)) - lines = chain.from_iterable(map(lambda x: x.lines, hunks)) - return map(lambda x: x.origin, lines) + hunks = chain.from_iterable((x.hunks for x in diff)) + lines = chain.from_iterable((x.lines for x in hunks)) + return (x.origin for x in lines) entries = [p.delta.new_file.path for p in diff] assert all(commit_a.tree[x] for x in entries) - assert all('-' == x for x in get_context_for_lines(diff)) + assert all(x == '-' for x in get_context_for_lines(diff)) diff_swaped = commit_a.tree.diff_to_tree(swap=True) entries = [p.delta.new_file.path for p in diff_swaped] assert all(commit_a.tree[x] for x in entries) - assert all('+' == x for x in get_context_for_lines(diff_swaped)) + assert all(x == '+' for x in get_context_for_lines(diff_swaped)) def test_diff_revparse(barerepo): @@ -230,11 +229,11 @@ def test_diff_tree_opts(barerepo): for flag in [DiffOption.IGNORE_WHITESPACE, DiffOption.IGNORE_WHITESPACE_EOL]: diff = commit_c.tree.diff_to_tree(commit_d.tree, flag) assert diff is not None - assert 0 == len(diff[0].hunks) + assert len(diff[0].hunks) == 0 diff = commit_c.tree.diff_to_tree(commit_d.tree) assert diff is not None - assert 1 == len(diff[0].hunks) + assert len(diff[0].hunks) == 1 def test_diff_merge(barerepo): @@ -270,7 +269,7 @@ def test_diff_patch(barerepo): diff = commit_a.tree.diff_to_tree(commit_b.tree) assert diff.patch == PATCH - assert len(diff) == len([patch for patch in diff]) + assert len(diff) == len(list(diff)) def test_diff_ids(barerepo): @@ -296,7 +295,7 @@ def test_hunk_content(barerepo): patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] hunk = patch.hunks[0] lines = (f'{x.origin} {x.content}' for x in hunk.lines) - assert HUNK_EXPECTED == ''.join(lines) + assert ''.join(lines) == HUNK_EXPECTED for line in hunk.lines: assert line.content == line.raw_content.decode() @@ -321,13 +320,13 @@ def test_diff_stats(barerepo): diff = commit_a.tree.diff_to_tree(commit_b.tree) stats = diff.stats - assert 1 == stats.insertions - assert 2 == stats.deletions - assert 2 == stats.files_changed + assert stats.insertions == 1 + assert stats.deletions == 2 + assert stats.files_changed == 2 formatted = stats.format( format=DiffStatsFormat.FULL | DiffStatsFormat.INCLUDE_SUMMARY, width=80 ) - assert STATS_EXPECTED == formatted + assert formatted == STATS_EXPECTED def test_deltas(barerepo): @@ -357,12 +356,12 @@ def test_diff_parse(barerepo): diff = pygit2.Diff.parse_diff(PATCH) stats = diff.stats - assert 2 == stats.deletions - assert 1 == stats.insertions - assert 2 == stats.files_changed + assert stats.deletions == 2 + assert stats.insertions == 1 + assert stats.files_changed == 2 deltas = list(diff.deltas) - assert 2 == len(deltas) + assert len(deltas) == 2 def test_parse_diff_null(): diff --git a/test/test_diff_binary.py b/test/test_diff_binary.py index e23583ad..52c6d920 100644 --- a/test/test_diff_binary.py +++ b/test/test_diff_binary.py @@ -56,10 +56,10 @@ def repo(tmp_path): def test_binary_diff(repo): diff = repo.diff('HEAD', 'HEAD^') - assert PATCH_BINARY == diff.patch + assert diff.patch == PATCH_BINARY diff = repo.diff('HEAD', 'HEAD^', flags=DiffOption.SHOW_BINARY) - assert PATCH_BINARY_SHOW == diff.patch + assert diff.patch == PATCH_BINARY_SHOW diff = repo.diff(b'HEAD', b'HEAD^') - assert PATCH_BINARY == diff.patch + assert diff.patch == PATCH_BINARY diff = repo.diff(b'HEAD', b'HEAD^', flags=DiffOption.SHOW_BINARY) - assert PATCH_BINARY_SHOW == diff.patch + assert diff.patch == PATCH_BINARY_SHOW diff --git a/test/test_filter.py b/test/test_filter.py index f37f9e1c..43ce09c7 100644 --- a/test/test_filter.py +++ b/test/test_filter.py @@ -1,5 +1,6 @@ -from io import BytesIO import codecs +from io import BytesIO + import pytest import pygit2 @@ -75,44 +76,44 @@ def test_filter(testrepo, rot13_filter): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD - assert b'olr jbeyq\n' == blob.data + assert blob.data == b'olr jbeyq\n' with pygit2.BlobIO(blob) as reader: - assert b'olr jbeyq\n' == reader.read() + assert reader.read() == b'olr jbeyq\n' with pygit2.BlobIO(blob, as_path='bye.txt', flags=flags) as reader: - assert b'bye world\n' == reader.read() + assert reader.read() == b'bye world\n' def test_filter_buffered(testrepo, buffered_filter): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD - assert b'olr jbeyq\n' == blob.data + assert blob.data == b'olr jbeyq\n' with pygit2.BlobIO(blob) as reader: - assert b'olr jbeyq\n' == reader.read() + assert reader.read() == b'olr jbeyq\n' with pygit2.BlobIO(blob, 'bye.txt', flags=flags) as reader: - assert b'bye world\n' == reader.read() + assert reader.read() == b'bye world\n' def test_filter_passthrough(testrepo, passthrough_filter): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD - assert b'bye world\n' == blob.data + assert blob.data == b'bye world\n' with pygit2.BlobIO(blob) as reader: - assert b'bye world\n' == reader.read() + assert reader.read() == b'bye world\n' with pygit2.BlobIO(blob, 'bye.txt', flags=flags) as reader: - assert b'bye world\n' == reader.read() + assert reader.read() == b'bye world\n' def test_filter_unmatched(testrepo, unmatched_filter): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD - assert b'bye world\n' == blob.data + assert blob.data == b'bye world\n' with pygit2.BlobIO(blob) as reader: - assert b'bye world\n' == reader.read() + assert reader.read() == b'bye world\n' with pygit2.BlobIO(blob, as_path='bye.txt', flags=flags) as reader: - assert b'bye world\n' == reader.read() + assert reader.read() == b'bye world\n' def test_filter_cleanup(dirtyrepo, rot13_filter): diff --git a/test/test_index.py b/test/test_index.py index 5aae31ad..ac711a25 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -30,8 +30,9 @@ import pytest import pygit2 -from pygit2 import Repository, Index, Oid +from pygit2 import Index, Oid, Repository from pygit2.enums import FileMode + from . import utils @@ -164,7 +165,7 @@ def test_iter(testrepo): # Compare SHAs, not IndexEntry object identity entries = [index[x].id for x in range(n)] - assert list(x.id for x in index) == entries + assert [x.id for x in index] == entries def test_mode(testrepo): @@ -224,9 +225,9 @@ def test_change_attributes(testrepo): entry.path = 'foo.txt' entry.id = ign_entry.id entry.mode = FileMode.BLOB_EXECUTABLE - assert 'foo.txt' == entry.path + assert entry.path == 'foo.txt' assert ign_entry.id == entry.id - assert FileMode.BLOB_EXECUTABLE == entry.mode + assert entry.mode == FileMode.BLOB_EXECUTABLE def test_write_tree_to(testrepo, tmp_path): @@ -242,7 +243,7 @@ def test_create_entry(testrepo): hello_entry = index['hello.txt'] entry = pygit2.IndexEntry('README.md', hello_entry.id, hello_entry.mode) index.add(entry) - assert '60e769e57ae1d6a2ab75d8d253139e6260e1f912' == index.write_tree() + assert index.write_tree() == '60e769e57ae1d6a2ab75d8d253139e6260e1f912' def test_create_entry_aspath(testrepo): diff --git a/test/test_mailmap.py b/test/test_mailmap.py index 3cdef056..e5a3b90e 100644 --- a/test/test_mailmap.py +++ b/test/test_mailmap.py @@ -27,7 +27,6 @@ from pygit2 import Mailmap - TEST_MAILMAP = """\ # Simple Comment line diff --git a/test/test_merge.py b/test/test_merge.py index 959854b3..5233786a 100644 --- a/test/test_merge.py +++ b/test/test_merge.py @@ -30,7 +30,7 @@ import pytest import pygit2 -from pygit2.enums import FileStatus, MergeAnalysis, MergeFavor, MergeFlag, MergeFileFlag +from pygit2.enums import FileStatus, MergeAnalysis, MergeFavor, MergeFileFlag, MergeFlag @pytest.mark.parametrize('id', [None, 42]) @@ -56,12 +56,12 @@ def test_merge_analysis_uptodate(mergerepo): analysis, preference = mergerepo.merge_analysis(branch_id) assert analysis & MergeAnalysis.UP_TO_DATE assert not analysis & MergeAnalysis.FASTFORWARD - assert {} == mergerepo.status() + assert mergerepo.status() == {} analysis, preference = mergerepo.merge_analysis(branch_id, 'refs/heads/ff-branch') assert analysis & MergeAnalysis.UP_TO_DATE assert not analysis & MergeAnalysis.FASTFORWARD - assert {} == mergerepo.status() + assert mergerepo.status() == {} def test_merge_analysis_fastforward(mergerepo): @@ -71,12 +71,12 @@ def test_merge_analysis_fastforward(mergerepo): analysis, preference = mergerepo.merge_analysis(branch_id) assert not analysis & MergeAnalysis.UP_TO_DATE assert analysis & MergeAnalysis.FASTFORWARD - assert {} == mergerepo.status() + assert mergerepo.status() == {} analysis, preference = mergerepo.merge_analysis(branch_id, 'refs/heads/master') assert not analysis & MergeAnalysis.UP_TO_DATE assert analysis & MergeAnalysis.FASTFORWARD - assert {} == mergerepo.status() + assert mergerepo.status() == {} def test_merge_no_fastforward_no_conflicts(mergerepo): @@ -86,8 +86,8 @@ def test_merge_no_fastforward_no_conflicts(mergerepo): assert not analysis & MergeAnalysis.UP_TO_DATE assert not analysis & MergeAnalysis.FASTFORWARD # Asking twice to assure the reference counting is correct - assert {} == mergerepo.status() - assert {} == mergerepo.status() + assert mergerepo.status() == {} + assert mergerepo.status() == {} def test_merge_invalid_hex(mergerepo): @@ -126,22 +126,22 @@ def test_merge_no_fastforward_conflicts(mergerepo): status = FileStatus.CONFLICTED # Asking twice to assure the reference counting is correct - assert {'.gitignore': status} == mergerepo.status() - assert {'.gitignore': status} == mergerepo.status() + assert mergerepo.status() == {'.gitignore': status} + assert mergerepo.status() == {'.gitignore': status} ancestor, ours, theirs = mergerepo.index.conflicts['.gitignore'] assert ancestor is None assert ours is not None assert theirs is not None - assert '.gitignore' == ours.path - assert '.gitignore' == theirs.path - assert 1 == len(list(mergerepo.index.conflicts)) + assert ours.path == '.gitignore' + assert theirs.path == '.gitignore' + assert len(list(mergerepo.index.conflicts)) == 1 # Checking the index works as expected mergerepo.index.add('.gitignore') mergerepo.index.write() assert mergerepo.index.conflicts is None - assert {'.gitignore': FileStatus.INDEX_MODIFIED} == mergerepo.status() + assert mergerepo.status() == {'.gitignore': FileStatus.INDEX_MODIFIED} def test_merge_remove_conflicts(mergerepo): diff --git a/test/test_note.py b/test/test_note.py index 2a171924..1f95e2a4 100644 --- a/test/test_note.py +++ b/test/test_note.py @@ -25,9 +25,9 @@ """Tests for note objects.""" -from pygit2 import Signature import pytest +from pygit2 import Signature NOTE = ('6c8980ba963cad8b25a9bcaf68d4023ee57370d8', 'note message') diff --git a/test/test_object.py b/test/test_object.py index 668d2d66..bd520d75 100644 --- a/test/test_object.py +++ b/test/test_object.py @@ -27,10 +27,9 @@ import pytest -from pygit2 import Tree, Tag +from pygit2 import Tag, Tree from pygit2.enums import ObjectType - BLOB_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b' BLOB_CONTENT = """hello world hola mundo @@ -48,7 +47,7 @@ def test_equality(testrepo): assert commit_a is not commit_b assert commit_a == commit_b - assert not (commit_a != commit_b) + assert commit_a == commit_b def test_hashing(testrepo): diff --git a/test/test_odb.py b/test/test_odb.py index c7e60f22..d03d9883 100644 --- a/test/test_odb.py +++ b/test/test_odb.py @@ -34,8 +34,8 @@ # pygit2 from pygit2 import Odb, Oid from pygit2.enums import ObjectType -from . import utils +from . import utils BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii')) @@ -74,14 +74,14 @@ def test_read(odb): ab = odb.read(BLOB_OID) a = odb.read(BLOB_HEX) assert ab == a - assert (ObjectType.BLOB, b'a contents\n') == a + assert a == (ObjectType.BLOB, b'a contents\n') a2 = odb.read('7f129fd57e31e935c6d60a0c794efe4e6927664b') - assert (ObjectType.BLOB, b'a contents 2\n') == a2 + assert a2 == (ObjectType.BLOB, b'a contents 2\n') a_hex_prefix = BLOB_HEX[:4] a3 = odb.read(a_hex_prefix) - assert (ObjectType.BLOB, b'a contents\n') == a3 + assert a3 == (ObjectType.BLOB, b'a contents\n') def test_write(odb): diff --git a/test/test_odb_backend.py b/test/test_odb_backend.py index 026834c3..430faefb 100644 --- a/test/test_odb_backend.py +++ b/test/test_odb_backend.py @@ -34,8 +34,8 @@ # pygit2 import pygit2 from pygit2.enums import ObjectType -from . import utils +from . import utils BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii')) @@ -106,7 +106,7 @@ def proxy(barerepo): def test_iterable(proxy): - assert BLOB_HEX in [o for o in proxy] + assert BLOB_HEX in list(proxy) def test_read(proxy): @@ -117,13 +117,13 @@ def test_read(proxy): ab = proxy.read(BLOB_OID) a = proxy.read(BLOB_HEX) assert ab == a - assert (ObjectType.BLOB, b'a contents\n') == a + assert a == (ObjectType.BLOB, b'a contents\n') def test_read_prefix(proxy): a_hex_prefix = BLOB_HEX[:4] a3 = proxy.read_prefix(a_hex_prefix) - assert (ObjectType.BLOB, b'a contents\n', BLOB_OID) == a3 + assert a3 == (ObjectType.BLOB, b'a contents\n', BLOB_OID) def test_exists(proxy): @@ -136,7 +136,7 @@ def test_exists(proxy): def test_exists_prefix(proxy): a_hex_prefix = BLOB_HEX[:4] - assert BLOB_HEX == proxy.exists_prefix(a_hex_prefix) + assert proxy.exists_prefix(a_hex_prefix) == BLOB_HEX # diff --git a/test/test_oid.py b/test/test_oid.py index c6cbf3e8..60c80fbe 100644 --- a/test/test_oid.py +++ b/test/test_oid.py @@ -28,9 +28,9 @@ # Standard Library from binascii import unhexlify -from pygit2 import Oid import pytest +from pygit2 import Oid HEX = '15b648aec6ed045b5ca6f57f8b7831a8b4757298' RAW = unhexlify(HEX.encode('ascii')) @@ -85,7 +85,7 @@ def test_cmp(): # Other assert oid1 < oid2 assert oid1 <= oid2 - assert not oid1 == oid2 + assert oid1 != oid2 assert not oid1 > oid2 assert not oid1 >= oid2 diff --git a/test/test_packbuilder.py b/test/test_packbuilder.py index 6d4ed0d9..464d88ca 100644 --- a/test/test_packbuilder.py +++ b/test/test_packbuilder.py @@ -29,6 +29,7 @@ import pygit2 from pygit2 import PackBuilder + from . import utils @@ -41,7 +42,7 @@ def test_create_packbuilder(testrepo): def test_add(testrepo): # Add a few objects and confirm that the count is correct packbuilder = PackBuilder(testrepo) - objects_to_add = [obj for obj in testrepo] + objects_to_add = list(testrepo) packbuilder.add(objects_to_add[0]) assert len(packbuilder) == 1 packbuilder.add(objects_to_add[1]) @@ -98,14 +99,14 @@ def confirm_same_repo_after_packing(testrepo, tmp_path, pack_delegate): # assert that the number of written objects is the same as the number of objects in the repo written_objects = testrepo.pack(pack_path, pack_delegate=pack_delegate) - assert written_objects == len([obj for obj in testrepo]) + assert written_objects == len(list(testrepo)) # assert that the number of objects in the pack repo is the same as the original repo - orig_objects = [obj for obj in testrepo.odb] - packed_objects = [obj for obj in pack_repo.odb] + orig_objects = list(testrepo.odb) + packed_objects = list(pack_repo.odb) assert len(packed_objects) == len(orig_objects) # assert that the objects in the packed repo are the same objects as the original repo - for i, obj in enumerate(orig_objects): + for _i, obj in enumerate(orig_objects): assert pack_repo[obj].type == testrepo[obj].type assert pack_repo[obj].read_raw() == testrepo[obj].read_raw() diff --git a/test/test_patch.py b/test/test_patch.py index 5620f9b5..b6b7c630 100644 --- a/test/test_patch.py +++ b/test/test_patch.py @@ -23,9 +23,9 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -import pygit2 import pytest +import pygit2 BLOB_OLD_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b' BLOB_NEW_SHA = '3b18e512dba79e4c8300dd08aeb37f8e728b8dad' diff --git a/test/test_patch_encoding.py b/test/test_patch_encoding.py index 23f4aca1..30072cf4 100644 --- a/test/test_patch_encoding.py +++ b/test/test_patch_encoding.py @@ -25,7 +25,6 @@ import pygit2 - expected_diff = b"""diff --git a/iso-8859-1.txt b/iso-8859-1.txt index e84e339..201e0c9 100644 --- a/iso-8859-1.txt diff --git a/test/test_refdb_backend.py b/test/test_refdb_backend.py index a7f10cf5..cec430c2 100644 --- a/test/test_refdb_backend.py +++ b/test/test_refdb_backend.py @@ -27,9 +27,10 @@ from pathlib import Path -import pygit2 import pytest +import pygit2 + # Note: the refdb abstraction from libgit2 is meant to provide information # which libgit2 transforms into something more useful, and in general YMMV by diff --git a/test/test_refs.py b/test/test_refs.py index cddfa038..813d5fe7 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -29,11 +29,17 @@ import pytest -from pygit2 import Commit, Signature, Tree, reference_is_valid_name -from pygit2 import AlreadyExistsError, GitError, InvalidSpecError +from pygit2 import ( + AlreadyExistsError, + Commit, + GitError, + InvalidSpecError, + Signature, + Tree, + reference_is_valid_name, +) from pygit2.enums import ReferenceType - LAST_COMMIT = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' @@ -60,8 +66,8 @@ def test_refs_list(testrepo): def test_head(testrepo): head = testrepo.head - assert LAST_COMMIT == testrepo[head.target].id - assert LAST_COMMIT == testrepo[head.raw_target].id + assert testrepo[head.target].id == LAST_COMMIT + assert testrepo[head.raw_target].id == LAST_COMMIT def test_refs_getitem(testrepo): @@ -150,11 +156,11 @@ def test_refs_delete(testrepo): # Access the deleted reference with pytest.raises(GitError): - getattr(reference, 'name') + reference.name with pytest.raises(GitError): - getattr(reference, 'type') + reference.type with pytest.raises(GitError): - getattr(reference, 'target') + reference.target with pytest.raises(GitError): reference.delete() with pytest.raises(GitError): @@ -261,10 +267,10 @@ def test_refs_equality(testrepo): assert ref1 is not ref2 assert ref1 == ref2 - assert not ref1 != ref2 + assert ref1 == ref2 assert ref1 != ref3 - assert not ref1 == ref3 + assert ref1 != ref3 def test_refs_compress(testrepo): @@ -583,11 +589,11 @@ def test_delete(testrepo): # Access the deleted reference with pytest.raises(GitError): - getattr(reference, 'name') + reference.name with pytest.raises(GitError): - getattr(reference, 'type') + reference.type with pytest.raises(GitError): - getattr(reference, 'target') + reference.target with pytest.raises(GitError): reference.delete() with pytest.raises(GitError): diff --git a/test/test_remote.py b/test/test_remote.py index 1d214733..6816bcee 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -23,13 +23,18 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +"""Tests for Remote objects.""" + import sys +from unittest.mock import patch import pytest import pygit2 -from . import utils +from pygit2 import Oid +from pygit2.ffi import ffi +from . import utils REMOTE_NAME = 'origin' REMOTE_URL = 'https://github.com/libgit2/pygit2.git' @@ -79,8 +84,10 @@ def test_remote_create_anonymous(testrepo): assert remote.name is None assert url == remote.url assert remote.push_url is None - assert [] == remote.fetch_refspecs - assert [] == remote.push_refspecs + assert remote.fetch_refspecs == [] + assert remote.push_refspecs == [] + assert remote.fetch_refspecs == [] + assert remote.push_refspecs == [] def test_remote_delete(testrepo): @@ -88,21 +95,26 @@ def test_remote_delete(testrepo): url = 'https://github.com/libgit2/pygit2.git' testrepo.remotes.create(name, url) - assert 2 == len(testrepo.remotes) + assert len(testrepo.remotes) == 2 + assert len(testrepo.remotes) == 2 remote = testrepo.remotes[1] assert name == remote.name testrepo.remotes.delete(remote.name) - assert 1 == len(testrepo.remotes) + assert len(testrepo.remotes) == 1 + assert len(testrepo.remotes) == 1 def test_remote_rename(testrepo): remote = testrepo.remotes[0] - assert REMOTE_NAME == remote.name + assert remote.name == REMOTE_NAME + assert remote.name == REMOTE_NAME problems = testrepo.remotes.rename(remote.name, 'new') - assert [] == problems - assert 'new' != remote.name + assert problems == [] + assert remote.name != 'new' + assert problems == [] + assert remote.name != 'new' with pytest.raises(ValueError): testrepo.remotes.rename('', '') @@ -112,7 +124,8 @@ def test_remote_rename(testrepo): def test_remote_set_url(testrepo): remote = testrepo.remotes['origin'] - assert REMOTE_URL == remote.url + assert remote.url == REMOTE_URL + assert remote.url == REMOTE_URL new_url = 'https://github.com/cholin/pygit2.git' testrepo.remotes.set_url('origin', new_url) @@ -137,7 +150,8 @@ def test_refspec(testrepo): assert refspec.src == REMOTE_FETCHSPEC_SRC assert refspec.dst == REMOTE_FETCHSPEC_DST assert refspec.force is True - assert ORIGIN_REFSPEC == refspec.string + assert refspec.string == ORIGIN_REFSPEC + assert refspec.string == ORIGIN_REFSPEC assert list is type(remote.fetch_refspecs) assert 1 == len(remote.fetch_refspecs) @@ -145,8 +159,10 @@ def test_refspec(testrepo): assert refspec.src_matches('refs/heads/master') assert refspec.dst_matches('refs/remotes/origin/master') - assert 'refs/remotes/origin/master' == refspec.transform('refs/heads/master') - assert 'refs/heads/master' == refspec.rtransform('refs/remotes/origin/master') + assert refspec.transform('refs/heads/master') == 'refs/remotes/origin/master' + assert refspec.rtransform('refs/remotes/origin/master') == 'refs/heads/master' + assert refspec.transform('refs/heads/master') == 'refs/remotes/origin/master' + assert refspec.rtransform('refs/remotes/origin/master') == 'refs/heads/master' assert list is type(remote.push_refspecs) assert 0 == len(remote.push_refspecs) @@ -164,7 +180,8 @@ def test_refspec(testrepo): assert [ '+refs/heads/*:refs/remotes/origin/*', '+refs/test/*:refs/test/remotes/*', - ] == fetch_specs + ] + ] testrepo.remotes.add_push('origin', '+refs/test/*:refs/test/remotes/*') @@ -172,14 +189,18 @@ def test_refspec(testrepo): testrepo.remotes.add_fetch(['+refs/*:refs/*', 5]) remote = testrepo.remotes['origin'] - assert ['+refs/test/*:refs/test/remotes/*'] == remote.push_refspecs + assert remote.push_refspecs == ['+refs/test/*:refs/test/remotes/*'] + assert remote.push_refspecs == ['+refs/test/*:refs/test/remotes/*'] def test_remote_list(testrepo): - assert 1 == len(testrepo.remotes) + assert len(testrepo.remotes) == 1 + assert len(testrepo.remotes) == 1 remote = testrepo.remotes[0] - assert REMOTE_NAME == remote.name - assert REMOTE_URL == remote.url + assert remote.name == REMOTE_NAME + assert remote.url == REMOTE_URL + assert remote.name == REMOTE_NAME + assert remote.url == REMOTE_URL name = 'upstream' url = 'https://github.com/libgit2/pygit2.git' @@ -190,7 +211,8 @@ def test_remote_list(testrepo): @utils.requires_network def test_ls_remotes(testrepo): - assert 1 == len(testrepo.remotes) + assert len(testrepo.remotes) == 1 + assert len(testrepo.remotes) == 1 remote = testrepo.remotes[0] refs = remote.ls_remotes() @@ -202,8 +224,10 @@ def test_ls_remotes(testrepo): def test_remote_collection(testrepo): remote = testrepo.remotes['origin'] - assert REMOTE_NAME == remote.name - assert REMOTE_URL == remote.url + assert remote.name == REMOTE_NAME + assert remote.url == REMOTE_URL + assert remote.name == REMOTE_NAME + assert remote.url == REMOTE_URL with pytest.raises(KeyError): testrepo.remotes['upstream'] diff --git a/test/test_remote_utf8.py b/test/test_remote_utf8.py index cf58a8d5..1307e897 100644 --- a/test/test_remote_utf8.py +++ b/test/test_remote_utf8.py @@ -23,8 +23,10 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -import pygit2 import pytest + +import pygit2 + from . import utils diff --git a/test/test_repository.py b/test/test_repository.py index adf339e4..17169112 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -23,16 +23,15 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -from pathlib import Path import shutil import tempfile +from pathlib import Path import pytest # pygit2 import pygit2 -from pygit2 import init_repository, clone_repository, discover_repository -from pygit2 import Oid +from pygit2 import Oid, clone_repository, discover_repository, init_repository from pygit2.enums import ( CheckoutNotify, CheckoutStrategy, @@ -43,6 +42,7 @@ ResetMode, StashApplyProgress, ) + from . import utils @@ -160,7 +160,7 @@ def checkout_notify(self, why, path, baseline, target, workdir): head = testrepo.head head = testrepo[head.target] assert 'new' not in head.tree - assert b'bye world\n' == read_bye_txt() + assert read_bye_txt() == b'bye world\n' callbacks = MyCheckoutCallbacks() # checkout i18n with GIT_CHECKOUT_FORCE - callbacks should prevent checkout from completing @@ -171,7 +171,7 @@ def checkout_notify(self, why, path, baseline, target, workdir): assert callbacks.invoked_times == 2 assert 'new' not in head.tree - assert b'bye world\n' == read_bye_txt() + assert read_bye_txt() == b'bye world\n' def test_checkout_branch(testrepo): @@ -228,7 +228,7 @@ def test_checkout_alternative_dir(testrepo): extra_dir.mkdir() assert len(list(extra_dir.iterdir())) == 0 testrepo.checkout(ref_i18n, directory=extra_dir) - assert not len(list(extra_dir.iterdir())) == 0 + assert len(list(extra_dir.iterdir())) != 0 def test_checkout_paths(testrepo): @@ -286,15 +286,15 @@ def test_ahead_behind(testrepo): '5ebeeebb320790caf276b9fc8b24546d63316533', '4ec4389a8068641da2d6578db0419484972284c8', ) - assert 1 == ahead - assert 2 == behind + assert ahead == 1 + assert behind == 2 ahead, behind = testrepo.ahead_behind( '4ec4389a8068641da2d6578db0419484972284c8', '5ebeeebb320790caf276b9fc8b24546d63316533', ) - assert 2 == ahead - assert 1 == behind + assert ahead == 2 + assert behind == 1 def test_reset_hard(testrepo): @@ -369,7 +369,7 @@ def test_stash(testrepo): ) # make sure we're starting with no stashes - assert [] == testrepo.listall_stashes() + assert testrepo.listall_stashes() == [] # some changes to working dir with (Path(testrepo.workdir) / 'hello.txt').open('w') as f: @@ -379,7 +379,7 @@ def test_stash(testrepo): assert 'hello.txt' not in testrepo.status() repo_stashes = testrepo.listall_stashes() - assert 1 == len(repo_stashes) + assert len(repo_stashes) == 1 assert repr(repo_stashes[0]) == f'' assert repo_stashes[0].commit_id == stash_hash assert repo_stashes[0].message == 'On master: ' + stash_message @@ -389,7 +389,7 @@ def test_stash(testrepo): assert repo_stashes == testrepo.listall_stashes() # still the same stashes testrepo.stash_drop() - assert [] == testrepo.listall_stashes() + assert testrepo.listall_stashes() == [] with pytest.raises(KeyError): testrepo.stash_pop() @@ -402,7 +402,7 @@ def test_stash_partial(testrepo): ) # make sure we're starting with no stashes - assert [] == testrepo.listall_stashes() + assert testrepo.listall_stashes() == [] # some changes to working dir with (Path(testrepo.workdir) / 'hello.txt').open('w') as f: @@ -420,7 +420,7 @@ def stash_pathspecs(paths): ) stash_commit = testrepo[stash_id].peel(pygit2.Commit) stash_diff = testrepo.diff(stash_commit.parents[0], stash_commit) - stash_files = set(patch.delta.new_file.path for patch in stash_diff) + stash_files = {patch.delta.new_file.path for patch in stash_diff} return stash_files == set(paths) # Stash a modified file @@ -501,7 +501,7 @@ def stash_apply_progress(self, progress: StashApplyProgress): # and since we didn't let stash_pop run to completion, the stash itself should still be here repo_stashes = testrepo.listall_stashes() - assert 1 == len(repo_stashes) + assert len(repo_stashes) == 1 assert repo_stashes[0].message == 'On master: custom stash message' @@ -588,8 +588,8 @@ def test_default_signature(testrepo): config['user.email'] = 'rjh@example.com' sig = testrepo.default_signature - assert 'Random J Hacker' == sig.name - assert 'rjh@example.com' == sig.email + assert sig.name == 'Random J Hacker' + assert sig.email == 'rjh@example.com' def test_new_repo(tmp_path): diff --git a/test/test_repository_bare.py b/test/test_repository_bare.py index 0274a401..8bf20d78 100644 --- a/test/test_repository_bare.py +++ b/test/test_repository_bare.py @@ -28,13 +28,14 @@ import pathlib import sys import tempfile +from pathlib import Path import pytest import pygit2 from pygit2.enums import FileMode, ObjectType -from . import utils +from . import utils HEAD_SHA = '784855caf26449a1914d2cf62d12b9374d76ae78' PARENT_SHA = 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87' # HEAD^ @@ -77,14 +78,14 @@ def test_read(barerepo): ab = barerepo.read(BLOB_OID) a = barerepo.read(BLOB_HEX) assert ab == a - assert (ObjectType.BLOB, b'a contents\n') == a + assert a == (ObjectType.BLOB, b'a contents\n') a2 = barerepo.read('7f129fd57e31e935c6d60a0c794efe4e6927664b') - assert (ObjectType.BLOB, b'a contents 2\n') == a2 + assert a2 == (ObjectType.BLOB, b'a contents 2\n') a_hex_prefix = BLOB_HEX[:4] a3 = barerepo.read(a_hex_prefix) - assert (ObjectType.BLOB, b'a contents\n') == a3 + assert a3 == (ObjectType.BLOB, b'a contents\n') def test_write(barerepo): @@ -109,7 +110,7 @@ def test_contains(barerepo): def test_iterable(barerepo): oid = pygit2.Oid(hex=BLOB_HEX) - assert oid in [obj for obj in barerepo] + assert oid in list(barerepo) def test_lookup_blob(barerepo): @@ -117,23 +118,23 @@ def test_lookup_blob(barerepo): barerepo[123] assert barerepo[BLOB_OID].id == BLOB_HEX a = barerepo[BLOB_HEX] - assert b'a contents\n' == a.read_raw() - assert BLOB_HEX == a.id - assert ObjectType.BLOB == a.type + assert a.read_raw() == b'a contents\n' + assert a.id == BLOB_HEX + assert a.type == ObjectType.BLOB def test_lookup_blob_prefix(barerepo): a = barerepo[BLOB_HEX[:5]] - assert b'a contents\n' == a.read_raw() - assert BLOB_HEX == a.id - assert ObjectType.BLOB == a.type + assert a.read_raw() == b'a contents\n' + assert a.id == BLOB_HEX + assert a.type == ObjectType.BLOB def test_lookup_commit(barerepo): commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' commit = barerepo[commit_sha] assert commit_sha == commit.id - assert ObjectType.COMMIT == commit.type + assert commit.type == ObjectType.COMMIT assert commit.message == ( 'Second test data commit.\n\nThis commit has some additional text.\n' ) @@ -145,10 +146,10 @@ def test_lookup_commit_prefix(barerepo): too_short_prefix = commit_sha[:3] commit = barerepo[commit_sha_prefix] assert commit_sha == commit.id - assert ObjectType.COMMIT == commit.type + assert commit.type == ObjectType.COMMIT assert ( - 'Second test data commit.\n\n' - 'This commit has some additional text.\n' == commit.message + commit.message == 'Second test data commit.\n\n' + 'This commit has some additional text.\n' ) with pytest.raises(ValueError): barerepo.__getitem__(too_short_prefix) @@ -234,10 +235,9 @@ def create_conflict_file(repo, branch, content): diff = barerepo.merge_file_from_index(a, t, o) assert ( diff - == """<<<<<<< conflict + == """ ASCII - abc ======= Unicode - äüö ->>>>>>> conflict """ ) diff --git a/test/test_repository_custom.py b/test/test_repository_custom.py index 5c365e09..e6c13f56 100644 --- a/test/test_repository_custom.py +++ b/test/test_repository_custom.py @@ -24,6 +24,7 @@ # Boston, MA 02110-1301, USA. from pathlib import Path + import pytest import pygit2 @@ -58,4 +59,4 @@ def test_references(repo): def test_objects(repo): a = repo.read('323fae03f4606ea9991df8befbb2fca795e648fa') - assert (ObjectType.BLOB, b'foobar\n') == a + assert a == (ObjectType.BLOB, b'foobar\n') diff --git a/test/test_revparse.py b/test/test_revparse.py index 10effc49..a83f77db 100644 --- a/test/test_revparse.py +++ b/test/test_revparse.py @@ -25,9 +25,10 @@ """Tests for revision parsing.""" +from pytest import raises + from pygit2 import InvalidSpecError from pygit2.enums import RevSpecFlag -from pytest import raises HEAD_SHA = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' PARENT_SHA = '5ebeeebb320790caf276b9fc8b24546d63316533' # HEAD^ diff --git a/test/test_revwalk.py b/test/test_revwalk.py index 483984c0..b3495340 100644 --- a/test/test_revwalk.py +++ b/test/test_revwalk.py @@ -27,7 +27,6 @@ from pygit2.enums import SortMode - # In the order given by git log log = [ '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98', @@ -108,8 +107,8 @@ def test_simplify_first_parent(testrepo): def test_default_sorting(testrepo): walker = testrepo.walk(log[0], SortMode.NONE) - list1 = list([x.id for x in walker]) + list1 = [x.id for x in walker] walker = testrepo.walk(log[0]) - list2 = list([x.id for x in walker]) + list2 = [x.id for x in walker] assert list1 == list2 diff --git a/test/test_signature.py b/test/test_signature.py index a28a1e07..1159e054 100644 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -99,7 +99,7 @@ def test_incorrect_encoding(): ) # repr() and str() may display junk, but they must not crash - assert "pygit2.Signature('(error)', '(error)', 999, 0, '(error)')" == repr( - signature + assert ( + repr(signature) == "pygit2.Signature('(error)', '(error)', 999, 0, '(error)')" ) - assert '(error) <(error)>' == str(signature) + assert str(signature) == '(error) <(error)>' diff --git a/test/test_submodule.py b/test/test_submodule.py index 235fed66..03483641 100644 --- a/test/test_submodule.py +++ b/test/test_submodule.py @@ -27,12 +27,13 @@ from pathlib import Path -import pygit2 import pytest -from . import utils -from pygit2.enums import SubmoduleIgnore as SI, SubmoduleStatus as SS +import pygit2 +from pygit2.enums import SubmoduleIgnore as SI +from pygit2.enums import SubmoduleStatus as SS +from . import utils SUBM_NAME = 'TestGitRepository' SUBM_PATH = 'TestGitRepository' @@ -108,17 +109,17 @@ class CustomRepoClass(pygit2.Repository): def test_name(repo): s = repo.submodules[SUBM_PATH] - assert SUBM_NAME == s.name + assert s.name == SUBM_NAME def test_path(repo): s = repo.submodules[SUBM_PATH] - assert SUBM_PATH == s.path + assert s.path == SUBM_PATH def test_url(repo): s = repo.submodules[SUBM_PATH] - assert SUBM_URL == s.url + assert s.url == SUBM_URL def test_missing_url(repo): @@ -239,7 +240,7 @@ def test_add_submodule(repo, depth): sm_repo = sm.open() assert sm_repo_path == sm.path - assert SUBM_URL == sm.url + assert sm.url == SUBM_URL assert not sm_repo.is_empty if depth == 0: diff --git a/test/test_tag.py b/test/test_tag.py index e0e73322..6a4f3b0f 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -30,7 +30,6 @@ import pygit2 from pygit2.enums import ObjectType - TAG_SHA = '3d2962987c695a29f1f80b6c3aa4ec046ef44369' @@ -39,11 +38,11 @@ def test_read_tag(barerepo): tag = repo[TAG_SHA] target = repo[tag.target] assert isinstance(tag, pygit2.Tag) - assert ObjectType.TAG == tag.type - assert ObjectType.COMMIT == target.type - assert 'root' == tag.name - assert 'Tagged root commit.\n' == tag.message - assert 'Initial test data commit.\n' == target.message + assert tag.type == ObjectType.TAG + assert target.type == ObjectType.COMMIT + assert tag.name == 'root' + assert tag.message == 'Tagged root commit.\n' + assert target.message == 'Initial test data commit.\n' assert tag.tagger == pygit2.Signature( 'Dave Borowitz', 'dborowitz@google.com', 1288724692, -420 ) @@ -63,7 +62,7 @@ def test_new_tag(barerepo): sha = barerepo.create_tag(name, target_prefix, ObjectType.BLOB, tagger, message) tag = barerepo[sha] - assert '3ee44658fd11660e828dfc96b9b5c5f38d5b49bb' == tag.id + assert tag.id == '3ee44658fd11660e828dfc96b9b5c5f38d5b49bb' assert name == tag.name assert target == tag.target assert tagger == tag.tagger diff --git a/test/test_tree.py b/test/test_tree.py index c5000083..62ff7339 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -24,6 +24,7 @@ # Boston, MA 02110-1301, USA. import operator + import pytest import pygit2 @@ -31,7 +32,6 @@ from . import utils - TREE_SHA = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' SUBTREE_SHA = '614fd9a3094bf618ea938fffc00e7d1a54f89ad0' @@ -54,7 +54,7 @@ def test_read_tree(barerepo): utils.assertRaisesWithArg(IndexError, 3, lambda: tree[3]) utils.assertRaisesWithArg(KeyError, 'abcd', lambda: tree / 'abcd') - assert 3 == len(tree) + assert len(tree) == 3 sha = '7f129fd57e31e935c6d60a0c794efe4e6927664b' assert 'a' in tree assertTreeEntryEqual(tree[0], sha, 'a', 0o0100644) @@ -93,7 +93,7 @@ def test_equality(barerepo): def test_sorting(barerepo): tree_a = barerepo['18e2d2e9db075f9eb43bcb2daa65a2867d29a15e'] - assert list(tree_a) == sorted(reversed(list(tree_a)), key=pygit2.tree_entry_key) + assert list(tree_a) == sorted(tree_a, key=pygit2.tree_entry_key) assert list(tree_a) != reversed(list(tree_a)) @@ -111,7 +111,7 @@ def test_read_subtree(barerepo): assert subtree_entry.type_str == 'tree' subtree = barerepo[subtree_entry.id] - assert 1 == len(subtree) + assert len(subtree) == 1 sha = '297efb891a47de80be0cfe9c639e4b8c9b450989' assertTreeEntryEqual(subtree[0], sha, 'd', 0o0100644) @@ -185,7 +185,7 @@ def test_iterate_tree_nested(barerepo): tree = barerepo[TREE_SHA] for tree_entry in tree: if isinstance(tree_entry, pygit2.Tree): - for tree_entry2 in tree_entry: + for _tree_entry2 in tree_entry: pass diff --git a/test/utils.py b/test/utils.py index 3a5dcf46..f97a1c9a 100644 --- a/test/utils.py +++ b/test/utils.py @@ -25,12 +25,12 @@ # Standard library import hashlib -from pathlib import Path import shutil import socket import stat import sys import zipfile +from pathlib import Path # Requirements import pytest