diff --git a/pygit2/_libgit2/ffi.pyi b/pygit2/_libgit2/ffi.pyi index 39b36b84..b2f6a8f1 100644 --- a/pygit2/_libgit2/ffi.pyi +++ b/pygit2/_libgit2/ffi.pyi @@ -120,8 +120,14 @@ class GitCommitC: pass class GitConfigC: + # incomplete pass +class GitConfigEntryC: + # incomplete + name: char_pointer + level: int + class GitDescribeFormatOptionsC: version: int abbreviated_size: int @@ -229,6 +235,10 @@ def new(a: Literal['git_commit **']) -> _Pointer[GitCommitC]: ... @overload def new(a: Literal['git_config *']) -> GitConfigC: ... @overload +def new(a: Literal['git_config **']) -> _Pointer[GitConfigC]: ... +@overload +def new(a: Literal['git_config_entry **']) -> _Pointer[GitConfigEntryC]: ... +@overload def new(a: Literal['git_describe_format_options *']) -> GitDescribeFormatOptionsC: ... @overload def new(a: Literal['git_describe_options *']) -> GitDescribeOptionsC: ... diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index 88a10d7d..6fa853f0 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -9,6 +9,7 @@ from typing import ( Iterator, Literal, Optional, + Sequence, Type, TypedDict, TypeVar, @@ -26,6 +27,7 @@ from ._libgit2.ffi import ( ) from .blame import Blame from .callbacks import CheckoutCallbacks +from .config import Config from .enums import ( ApplyLocation, AttrCheck, @@ -45,6 +47,7 @@ from .enums import ( Option, ReferenceFilter, ReferenceType, + RepositoryState, ResetMode, SortMode, ) @@ -386,20 +389,23 @@ class Blob(Object): ) -> Patch: ... def diff_to_buffer( self, - buffer: Optional[bytes] = None, + buffer: Optional[bytes | str] = None, flag: DiffOption = DiffOption.NORMAL, old_as_path: str = ..., buffer_as_path: str = ..., ) -> Patch: ... def _write_to_queue( self, - queue: Queue, - closed: Event, + queue: Queue[bytes], + ready: Event, + done: Event, chunk_size: int = DEFAULT_BUFFER_SIZE, as_path: Optional[str] = None, flags: BlobFilter = BlobFilter.CHECK_FOR_BINARY, commit_id: Optional[Oid] = None, ) -> None: ... + def __buffer__(self, flags: int) -> memoryview: ... + def __release_buffer__(self, buffer: memoryview) -> None: ... class Branch(Reference): branch_name: str @@ -736,6 +742,16 @@ class Repository: def _from_c(cls, ptr: 'GitRepositoryC', owned: bool) -> 'Repository': ... def __getitem__(self, key: str | Oid) -> Object: ... def add_worktree(self, name: str, path: str, ref: Reference = ...) -> Worktree: ... + def amend_commit( + self, + commit: Commit | Oid | str, + refname: Reference | str | None, + author: Signature | None = None, + committer: Signature | None = None, + message: str | None = None, + tree: Tree | Oid | str | None = None, + encoding: str = 'UTF-8', + ) -> Oid: ... def applies( self, diff: Diff, @@ -766,10 +782,12 @@ class Repository: ) -> None: ... def cherrypick(self, id: _OidArg) -> None: ... def compress_references(self) -> None: ... + @property + def config(self) -> Config: ... def create_blob(self, data: bytes) -> Oid: ... 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_blob_fromworkdir(self, path: str | Path) -> Oid: ... def create_branch(self, name: str, commit: Commit, force=False) -> Branch: ... def create_commit( self, @@ -778,7 +796,7 @@ class Repository: committer: Signature, message: str | bytes, tree: _OidArg, - parents: list[_OidArg], + parents: Sequence[_OidArg], encoding: str = ..., ) -> Oid: ... def create_commit_string( @@ -826,6 +844,7 @@ class Repository: def descendant_of(self, oid1: _OidArg, oid2: _OidArg) -> bool: ... def expand_id(self, hex: str) -> Oid: ... def free(self) -> None: ... + def get(self, key: _OidArg, default: Optional[Commit] = None) -> None | Object: ... def get_attr( self, path: str | bytes | Path, @@ -840,7 +859,7 @@ class Repository: def listall_stashes(self) -> list[Stash]: ... def listall_submodules(self) -> list[str]: ... def lookup_branch( - self, branch_name: str, branch_type: BranchType = BranchType.LOCAL + self, branch_name: str | bytes, branch_type: BranchType = BranchType.LOCAL ) -> Branch: ... def lookup_note( self, annotated_id: str, ref: str = 'refs/notes/commits' @@ -854,6 +873,8 @@ class Repository: def merge_base(self, oid1: _OidArg, oid2: _OidArg) -> Oid: ... def merge_base_many(self, oids: list[_OidArg]) -> Oid: ... def merge_base_octopus(self, oids: list[_OidArg]) -> Oid: ... + @property + def message(self) -> str: ... def notes(self) -> Iterator[Note]: ... def path_is_ignored(self, path: str) -> bool: ... def raw_listall_branches( @@ -877,6 +898,8 @@ class Repository: self, untracked_files: str = 'all', ignored: bool = False ) -> dict[str, int]: ... def status_file(self, path: str) -> int: ... + def state(self) -> RepositoryState: ... + def state_cleanup(self) -> None: ... def walk( self, oid: _OidArg | None, sort_mode: SortMode = SortMode.NONE ) -> Walker: ... diff --git a/pygit2/config.py b/pygit2/config.py index e8619eda..0f18d6cf 100644 --- a/pygit2/config.py +++ b/pygit2/config.py @@ -23,6 +23,9 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from os import PathLike +from typing import TYPE_CHECKING + try: from functools import cached_property except ImportError: @@ -33,8 +36,11 @@ from .ffi import C, ffi from .utils import to_bytes +if TYPE_CHECKING: + from ._libgit2.ffi import GitConfigC, GitConfigEntryC, GitRepositoryC + -def str_to_bytes(value, name): +def str_to_bytes(value: str | PathLike[str] | bytes, name: str) -> bytes: if not isinstance(value, str): raise TypeError(f'{name} must be a string') @@ -72,33 +78,36 @@ def __next__(self): class Config: """Git configuration management.""" - def __init__(self, path=None): + _repo: 'GitRepositoryC' + _config: 'GitConfigC' + + def __init__(self, path: str | None = None) -> None: cconfig = ffi.new('git_config **') if not path: err = C.git_config_new(cconfig) else: - path = str_to_bytes(path, 'path') - err = C.git_config_open_ondisk(cconfig, path) + path_bytes = str_to_bytes(path, 'path') + err = C.git_config_open_ondisk(cconfig, path_bytes) check_error(err, io=True) self._config = cconfig[0] @classmethod - def from_c(cls, repo, ptr): + def from_c(cls, repo: 'GitRepositoryC', ptr: 'GitConfigC') -> 'Config': config = cls.__new__(cls) config._repo = repo config._config = ptr return config - def __del__(self): + def __del__(self) -> None: try: C.git_config_free(self._config) except AttributeError: pass - def _get(self, key): + def _get(self, key: str | bytes) -> tuple[object, 'ConfigEntry']: key = str_to_bytes(key, 'key') entry = ffi.new('git_config_entry **') @@ -106,7 +115,7 @@ def _get(self, key): return err, ConfigEntry._from_c(entry[0]) - def _get_entry(self, key): + def _get_entry(self, key: str | bytes) -> 'ConfigEntry': err, entry = self._get(key) if err == C.GIT_ENOTFOUND: @@ -306,8 +315,13 @@ def get_xdg_config(): class ConfigEntry: """An entry in a configuration object.""" + _entry: 'GitConfigEntryC' + iterator: ConfigIterator | None + @classmethod - def _from_c(cls, ptr, iterator=None): + def _from_c( + cls, ptr: 'GitConfigEntryC', iterator: ConfigIterator | None = None + ) -> 'ConfigEntry': """Builds the entry from a ``git_config_entry`` pointer. ``iterator`` must be a ``ConfigIterator`` instance if the entry was @@ -330,7 +344,7 @@ def _from_c(cls, ptr, iterator=None): return entry - def __del__(self): + def __del__(self) -> None: if self.iterator is None: C.git_config_entry_free(self._entry) @@ -340,24 +354,24 @@ def c_value(self): return self._entry.value @cached_property - def raw_name(self): + def raw_name(self) -> bytes: return ffi.string(self._entry.name) @cached_property - def raw_value(self): + def raw_value(self) -> bytes: return ffi.string(self.c_value) @cached_property - def level(self): + def level(self) -> int: """The entry's ``git_config_level_t`` value.""" return self._entry.level @property - def name(self): + def name(self) -> str: """The entry's name.""" return self.raw_name.decode('utf-8') @property - def value(self): + def value(self) -> str: """The entry's value as a string.""" return self.raw_value.decode('utf-8') diff --git a/pygit2/repository.py b/pygit2/repository.py index 0c2db04e..7e0b6362 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -202,7 +202,7 @@ def __iter__(self): # # Mapping interface # - def get(self, key: str, default: Optional[Commit] = None) -> Object: + def get(self, key: str | Oid, default: Optional[Commit] = None) -> Object: value = self.git_object_lookup_prefix(key) return value if (value is not None) else default diff --git a/pygit2/utils.py b/pygit2/utils.py index 42798684..e2d4b4c4 100644 --- a/pygit2/utils.py +++ b/pygit2/utils.py @@ -25,7 +25,7 @@ import contextlib import os -from typing import Generic, Iterator, Protocol, TypeVar, Union +from typing import Generic, Iterator, Protocol, TypeVar, Union, overload # Import from pygit2 from .ffi import C, ffi @@ -38,6 +38,18 @@ def maybe_string(ptr): return ffi.string(ptr).decode('utf8', errors='surrogateescape') +@overload +def to_bytes( + s: Union[str, bytes, os.PathLike[str]], + encoding: str = 'utf-8', + errors: str = 'strict', +) -> bytes: ... +@overload +def to_bytes( + s: Union['ffi.NULL_TYPE', None], + encoding: str = 'utf-8', + errors: str = 'strict', +) -> Union['ffi.NULL_TYPE']: ... def to_bytes( s: Union[str, bytes, 'ffi.NULL_TYPE', os.PathLike[str], None], encoding: str = 'utf-8', diff --git a/src/blob.c b/src/blob.c index dac991bf..93e7dbe4 100644 --- a/src/blob.c +++ b/src/blob.c @@ -235,7 +235,7 @@ static void blob_filter_stream_free(git_writestream *s) PyDoc_STRVAR(Blob__write_to_queue__doc__, - "_write_to_queue(queue: queue.Queue, closed: threading.Event, chunk_size: int = io.DEFAULT_BUFFER_SIZE, [as_path: str = None, flags: enums.BlobFilter = enums.BlobFilter.CHECK_FOR_BINARY, commit_id: oid = None]) -> None\n" + "_write_to_queue(queue: queue.Queue, ready: threading.Event, done: threading.Event, chunk_size: int = io.DEFAULT_BUFFER_SIZE, [as_path: str = None, flags: enums.BlobFilter = enums.BlobFilter.CHECK_FOR_BINARY, commit_id: oid = None]) -> None\n" "\n" "Write the contents of the blob in chunks to `queue`.\n" "If `as_path` is None, the raw contents of blob will be written to the queue,\n" diff --git a/test/test_blob.py b/test/test_blob.py index 63e97c00..47ced260 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -33,6 +33,7 @@ import pytest import pygit2 +from pygit2 import Repository from pygit2.enums import ObjectType from . import utils @@ -80,7 +81,7 @@ """ -def test_read_blob(testrepo): +def test_read_blob(testrepo: Repository) -> None: blob = testrepo[BLOB_SHA] assert blob.id == BLOB_SHA assert blob.id == BLOB_SHA @@ -92,7 +93,7 @@ def test_read_blob(testrepo): assert BLOB_CONTENT == blob.read_raw() -def test_create_blob(testrepo): +def test_create_blob(testrepo: Repository) -> None: blob_oid = testrepo.create_blob(BLOB_NEW_CONTENT) blob = testrepo[blob_oid] @@ -116,7 +117,7 @@ def set_content(): set_content() -def test_create_blob_fromworkdir(testrepo): +def test_create_blob_fromworkdir(testrepo: Repository) -> None: blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] @@ -131,19 +132,19 @@ def test_create_blob_fromworkdir(testrepo): assert BLOB_FILE_CONTENT == blob.read_raw() -def test_create_blob_fromworkdir_aspath(testrepo): +def test_create_blob_fromworkdir_aspath(testrepo: Repository) -> None: blob_oid = testrepo.create_blob_fromworkdir(Path('bye.txt')) blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) -def test_create_blob_outside_workdir(testrepo): +def test_create_blob_outside_workdir(testrepo: Repository) -> None: with pytest.raises(KeyError): testrepo.create_blob_fromworkdir(__file__) -def test_create_blob_fromdisk(testrepo): +def test_create_blob_fromdisk(testrepo: Repository) -> None: blob_oid = testrepo.create_blob_fromdisk(__file__) blob = testrepo[blob_oid] @@ -151,9 +152,9 @@ def test_create_blob_fromdisk(testrepo): assert ObjectType.BLOB == blob.type -def test_create_blob_fromiobase(testrepo): +def test_create_blob_fromiobase(testrepo: Repository) -> None: with pytest.raises(TypeError): - testrepo.create_blob_fromiobase('bad type') + testrepo.create_blob_fromiobase('bad type') # type: ignore f = io.BytesIO(BLOB_CONTENT) blob_oid = testrepo.create_blob_fromiobase(f) @@ -166,54 +167,64 @@ def test_create_blob_fromiobase(testrepo): assert BLOB_SHA == blob_oid -def test_diff_blob(testrepo): +def test_diff_blob(testrepo: Repository) -> None: blob = testrepo[BLOB_SHA] + assert isinstance(blob, pygit2.Blob) old_blob = testrepo['3b18e512dba79e4c8300dd08aeb37f8e728b8dad'] + assert isinstance(old_blob, pygit2.Blob) patch = blob.diff(old_blob, old_as_path='hello.txt') assert len(patch.hunks) == 1 -def test_diff_blob_to_buffer(testrepo): +def test_diff_blob_to_buffer(testrepo: Repository) -> None: blob = testrepo[BLOB_SHA] + assert isinstance(blob, pygit2.Blob) patch = blob.diff_to_buffer('hello world') assert len(patch.hunks) == 1 -def test_diff_blob_to_buffer_patch_patch(testrepo): +def test_diff_blob_to_buffer_patch_patch(testrepo: Repository) -> None: blob = testrepo[BLOB_SHA] + assert isinstance(blob, pygit2.Blob) patch = blob.diff_to_buffer('hello world') assert patch.text == BLOB_PATCH -def test_diff_blob_to_buffer_delete(testrepo): +def test_diff_blob_to_buffer_delete(testrepo: Repository) -> None: blob = testrepo[BLOB_SHA] + assert isinstance(blob, pygit2.Blob) patch = blob.diff_to_buffer(None) assert patch.text == BLOB_PATCH_DELETED -def test_diff_blob_create(testrepo): +def test_diff_blob_create(testrepo: Repository) -> None: old = testrepo[testrepo.create_blob(BLOB_CONTENT)] new = testrepo[testrepo.create_blob(BLOB_NEW_CONTENT)] + assert isinstance(old, pygit2.Blob) + assert isinstance(new, pygit2.Blob) patch = old.diff(new) assert patch.text == BLOB_PATCH_2 -def test_blob_from_repo(testrepo): +def test_blob_from_repo(testrepo: Repository) -> None: blob = testrepo[BLOB_SHA] + assert isinstance(blob, pygit2.Blob) patch_one = blob.diff_to_buffer(None) blob = testrepo[BLOB_SHA] + assert isinstance(blob, pygit2.Blob) patch_two = blob.diff_to_buffer(None) assert patch_one.text == patch_two.text -def test_blob_write_to_queue(testrepo): - queue = Queue() +def test_blob_write_to_queue(testrepo: Repository) -> None: + queue: Queue[bytes] = Queue() ready = Event() done = Event() blob = testrepo[BLOB_SHA] + assert isinstance(blob, pygit2.Blob) blob._write_to_queue(queue, ready, done) assert ready.wait() assert done.wait() @@ -223,12 +234,13 @@ def test_blob_write_to_queue(testrepo): assert BLOB_CONTENT == b''.join(chunks) -def test_blob_write_to_queue_filtered(testrepo): - queue = Queue() +def test_blob_write_to_queue_filtered(testrepo: Repository) -> None: + queue: Queue[bytes] = Queue() ready = Event() done = Event() blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] + assert isinstance(blob, pygit2.Blob) blob._write_to_queue(queue, ready, done, as_path='bye.txt') assert ready.wait() assert done.wait() @@ -238,17 +250,19 @@ def test_blob_write_to_queue_filtered(testrepo): assert b'bye world\n' == b''.join(chunks) -def test_blobio(testrepo): +def test_blobio(testrepo: Repository) -> None: blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] + assert isinstance(blob, pygit2.Blob) with pygit2.BlobIO(blob) as reader: assert b'bye world\n' == reader.read() - assert not reader.raw._thread.is_alive() + assert not reader.raw._thread.is_alive() # type: ignore[attr-defined] -def test_blobio_filtered(testrepo): +def test_blobio_filtered(testrepo: Repository) -> None: blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] + assert isinstance(blob, pygit2.Blob) with pygit2.BlobIO(blob, as_path='bye.txt') as reader: assert b'bye world\n' == reader.read() - assert not reader.raw._thread.is_alive() + assert not reader.raw._thread.is_alive() # type: ignore[attr-defined] diff --git a/test/test_branch.py b/test/test_branch.py index 3cb3316d..f5bff9d3 100644 --- a/test/test_branch.py +++ b/test/test_branch.py @@ -30,7 +30,7 @@ import pytest import pygit2 -from pygit2 import Repository +from pygit2 import Commit, Repository from pygit2.enums import BranchType LAST_COMMIT = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' @@ -154,7 +154,7 @@ def test_branches_with_commit(testrepo: Repository) -> None: # -def test_lookup_branch_local(testrepo): +def test_lookup_branch_local(testrepo: Repository) -> None: assert testrepo.lookup_branch('master').target == LAST_COMMIT assert testrepo.lookup_branch(b'master').target == LAST_COMMIT @@ -167,16 +167,17 @@ def test_lookup_branch_local(testrepo): assert testrepo.lookup_branch(b'\xb1') is None -def test_listall_branches(testrepo): +def test_listall_branches(testrepo: Repository) -> None: branches = sorted(testrepo.listall_branches()) assert branches == ['i18n', 'master'] - branches = sorted(testrepo.raw_listall_branches()) - assert branches == [b'i18n', b'master'] + branches_raw = sorted(testrepo.raw_listall_branches()) + assert branches_raw == [b'i18n', b'master'] -def test_create_branch(testrepo): +def test_create_branch(testrepo: Repository) -> None: commit = testrepo[LAST_COMMIT] + assert isinstance(commit, Commit) testrepo.create_branch('version1', commit) refs = testrepo.listall_branches() assert 'version1' in refs @@ -191,64 +192,72 @@ def test_create_branch(testrepo): assert testrepo.create_branch('version1', commit, True).target == LAST_COMMIT -def test_delete(testrepo): +def test_delete(testrepo: Repository) -> None: branch = testrepo.lookup_branch('i18n') branch.delete() assert testrepo.lookup_branch('i18n') is None -def test_cant_delete_master(testrepo): +def test_cant_delete_master(testrepo: Repository) -> None: branch = testrepo.lookup_branch('master') with pytest.raises(pygit2.GitError): branch.delete() -def test_branch_is_head_returns_true_if_branch_is_head(testrepo): +def test_branch_is_head_returns_true_if_branch_is_head(testrepo: Repository) -> None: branch = testrepo.lookup_branch('master') assert branch.is_head() -def test_branch_is_head_returns_false_if_branch_is_not_head(testrepo): +def test_branch_is_head_returns_false_if_branch_is_not_head( + testrepo: Repository, +) -> None: branch = testrepo.lookup_branch('i18n') assert not branch.is_head() -def test_branch_is_checked_out_returns_true_if_branch_is_checked_out(testrepo): +def test_branch_is_checked_out_returns_true_if_branch_is_checked_out( + testrepo: Repository, +) -> None: branch = testrepo.lookup_branch('master') assert branch.is_checked_out() -def test_branch_is_checked_out_returns_false_if_branch_is_not_checked_out(testrepo): +def test_branch_is_checked_out_returns_false_if_branch_is_not_checked_out( + testrepo: Repository, +) -> None: branch = testrepo.lookup_branch('i18n') assert not branch.is_checked_out() -def test_branch_rename_succeeds(testrepo): +def test_branch_rename_succeeds(testrepo: Repository) -> None: branch = testrepo.lookup_branch('i18n') assert branch.rename('new-branch').target == I18N_LAST_COMMIT assert testrepo.lookup_branch('new-branch').target == I18N_LAST_COMMIT -def test_branch_rename_fails_if_destination_already_exists(testrepo): +def test_branch_rename_fails_if_destination_already_exists( + testrepo: Repository, +) -> None: original_branch = testrepo.lookup_branch('i18n') with pytest.raises(ValueError): original_branch.rename('master') -def test_branch_rename_not_fails_if_force_is_true(testrepo): +def test_branch_rename_not_fails_if_force_is_true(testrepo: Repository) -> None: branch = testrepo.lookup_branch('master') assert branch.rename('i18n', True).target == LAST_COMMIT -def test_branch_rename_fails_with_invalid_names(testrepo): +def test_branch_rename_fails_with_invalid_names(testrepo: Repository) -> None: original_branch = testrepo.lookup_branch('i18n') with pytest.raises(ValueError): original_branch.rename('abc@{123') -def test_branch_name(testrepo): +def test_branch_name(testrepo: Repository) -> None: branch = testrepo.lookup_branch('master') assert branch.branch_name == 'master' assert branch.name == 'refs/heads/master' diff --git a/test/test_branch_empty.py b/test/test_branch_empty.py index 56198535..c3a736df 100644 --- a/test/test_branch_empty.py +++ b/test/test_branch_empty.py @@ -23,37 +23,40 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from typing import Generator + import pytest +from pygit2 import Commit, Repository from pygit2.enums import BranchType ORIGIN_MASTER_COMMIT = '784855caf26449a1914d2cf62d12b9374d76ae78' @pytest.fixture -def repo(emptyrepo): +def repo(emptyrepo: Repository) -> Generator[Repository, None, None]: remote = emptyrepo.remotes[0] remote.fetch() yield emptyrepo -def test_branches_remote_get(repo): +def test_branches_remote_get(repo: Repository) -> None: branch = repo.branches.remote.get('origin/master') assert branch.target == ORIGIN_MASTER_COMMIT assert repo.branches.remote.get('origin/not-exists') is None -def test_branches_remote(repo): +def test_branches_remote(repo: Repository) -> None: branches = sorted(repo.branches.remote) assert branches == ['origin/master'] -def test_branches_remote_getitem(repo): +def test_branches_remote_getitem(repo: Repository) -> None: branch = repo.branches.remote['origin/master'] assert branch.remote_name == 'origin' -def test_branches_upstream(repo): +def test_branches_upstream(repo: Repository) -> None: remote_master = repo.branches.remote['origin/master'] master = repo.branches.create('master', repo[remote_master.target]) @@ -71,7 +74,7 @@ def set_bad_upstream(): assert master.upstream is None -def test_branches_upstream_name(repo): +def test_branches_upstream_name(repo: Repository) -> None: remote_master = repo.branches.remote['origin/master'] master = repo.branches.create('master', repo[remote_master.target]) @@ -84,28 +87,30 @@ def test_branches_upstream_name(repo): # -def test_lookup_branch_remote(repo): +def test_lookup_branch_remote(repo: Repository) -> None: branch = repo.lookup_branch('origin/master', BranchType.REMOTE) assert branch.target == ORIGIN_MASTER_COMMIT assert repo.lookup_branch('origin/not-exists', BranchType.REMOTE) is None -def test_listall_branches(repo): +def test_listall_branches(repo: Repository) -> None: branches = sorted(repo.listall_branches(BranchType.REMOTE)) assert branches == ['origin/master'] - branches = sorted(repo.raw_listall_branches(BranchType.REMOTE)) - assert branches == [b'origin/master'] + branches_raw = sorted(repo.raw_listall_branches(BranchType.REMOTE)) + assert branches_raw == [b'origin/master'] -def test_branch_remote_name(repo): +def test_branch_remote_name(repo: Repository) -> None: branch = repo.lookup_branch('origin/master', BranchType.REMOTE) assert branch.remote_name == 'origin' -def test_branch_upstream(repo): +def test_branch_upstream(repo: Repository) -> None: remote_master = repo.lookup_branch('origin/master', BranchType.REMOTE) - master = repo.create_branch('master', repo[remote_master.target]) + commit = repo[remote_master.target] + assert isinstance(commit, Commit) + master = repo.create_branch('master', commit) assert master.upstream is None master.upstream = remote_master @@ -121,9 +126,11 @@ def set_bad_upstream(): assert master.upstream is None -def test_branch_upstream_name(repo): +def test_branch_upstream_name(repo: Repository) -> None: remote_master = repo.lookup_branch('origin/master', BranchType.REMOTE) - master = repo.create_branch('master', repo[remote_master.target]) + commit = repo[remote_master.target] + assert isinstance(commit, Commit) + master = repo.create_branch('master', commit) master.upstream = remote_master assert master.upstream_name == 'refs/remotes/origin/master' diff --git a/test/test_cherrypick.py b/test/test_cherrypick.py index d1aebbe1..6c003223 100644 --- a/test/test_cherrypick.py +++ b/test/test_cherrypick.py @@ -30,23 +30,26 @@ import pytest import pygit2 +from pygit2 import Repository from pygit2.enums import RepositoryState -def test_cherrypick_none(mergerepo): +def test_cherrypick_none(mergerepo: Repository) -> None: with pytest.raises(TypeError): - mergerepo.cherrypick(None) + mergerepo.cherrypick(None) # type: ignore -def test_cherrypick_invalid_hex(mergerepo): +def test_cherrypick_invalid_hex(mergerepo: Repository) -> None: branch_head_hex = '12345678' with pytest.raises(KeyError): mergerepo.cherrypick(branch_head_hex) -def test_cherrypick_already_something_in_index(mergerepo): +def test_cherrypick_already_something_in_index(mergerepo: Repository) -> None: branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' - branch_oid = mergerepo.get(branch_head_hex).id + branch_object = mergerepo.get(branch_head_hex) + assert branch_object is not None + branch_oid = branch_object.id with (Path(mergerepo.workdir) / 'inindex.txt').open('w') as f: f.write('new content') mergerepo.index.add('inindex.txt') @@ -54,7 +57,7 @@ def test_cherrypick_already_something_in_index(mergerepo): mergerepo.cherrypick(branch_oid) -def test_cherrypick_remove_conflicts(mergerepo): +def test_cherrypick_remove_conflicts(mergerepo: Repository) -> None: assert mergerepo.state() == RepositoryState.NONE assert not mergerepo.message diff --git a/test/test_commit.py b/test/test_commit.py index a38559da..21434026 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -29,7 +29,7 @@ import pytest -from pygit2 import GitError, Oid, Signature +from pygit2 import Commit, GitError, Oid, Repository, Signature, Tree from pygit2.enums import ObjectType from . import utils @@ -41,7 +41,7 @@ @utils.requires_refcount -def test_commit_refcount(barerepo): +def test_commit_refcount(barerepo: Repository) -> None: commit = barerepo[COMMIT_SHA] start = sys.getrefcount(commit) tree = commit.tree @@ -50,8 +50,9 @@ def test_commit_refcount(barerepo): assert start == end -def test_read_commit(barerepo): +def test_read_commit(barerepo: Repository) -> None: commit = barerepo[COMMIT_SHA] + assert isinstance(commit, Commit) assert COMMIT_SHA == commit.id parents = commit.parents assert 1 == len(parents) @@ -71,7 +72,7 @@ def test_read_commit(barerepo): assert '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' == commit.tree.id -def test_new_commit(barerepo): +def test_new_commit(barerepo: Repository) -> None: repo = barerepo message = 'New commit.\n\nMessage with non-ascii chars: ééé.\n' committer = Signature('John Doe', 'jdoe@example.com', 12346, 0) @@ -88,8 +89,9 @@ def test_new_commit(barerepo): sha = repo.create_commit(None, author, committer, message, tree_prefix, parents) commit = repo[sha] + assert isinstance(commit, Commit) - assert ObjectType.COMMIT == commit.type + assert ObjectType.COMMIT.value == commit.type assert '98286caaab3f1fde5bf52c8369b2b0423bad743b' == commit.id assert commit.message_encoding is None assert message == commit.message @@ -103,7 +105,7 @@ def test_new_commit(barerepo): assert Oid(hex=COMMIT_SHA) == commit.parent_ids[0] -def test_new_commit_encoding(barerepo): +def test_new_commit_encoding(barerepo: Repository) -> None: repo = barerepo encoding = 'iso-8859-1' message = 'New commit.\n\nMessage with non-ascii chars: ééé.\n' @@ -117,8 +119,9 @@ def test_new_commit_encoding(barerepo): None, author, committer, message, tree_prefix, parents, encoding ) commit = repo[sha] + assert isinstance(commit, Commit) - assert ObjectType.COMMIT == commit.type + assert ObjectType.COMMIT.value == commit.type assert 'iso-8859-1' == commit.message_encoding assert message.encode(encoding) == commit.raw_message assert 12346 == commit.commit_time @@ -131,7 +134,7 @@ def test_new_commit_encoding(barerepo): assert Oid(hex=COMMIT_SHA) == commit.parent_ids[0] -def test_modify_commit(barerepo): +def test_modify_commit(barerepo: Repository) -> None: message = 'New commit.\n\nMessage.\n' committer = ('John Doe', 'jdoe@example.com', 12346) author = ('Jane Doe', 'jdoe2@example.com', 12345) @@ -150,9 +153,10 @@ def test_modify_commit(barerepo): setattr(commit, 'parents', None) -def test_amend_commit_metadata(barerepo): +def test_amend_commit_metadata(barerepo: Repository) -> None: repo = barerepo commit = repo[COMMIT_SHA_TO_AMEND] + assert isinstance(commit, Commit) assert commit.id == repo.head.target encoding = 'iso-8859-1' @@ -173,9 +177,10 @@ def test_amend_commit_metadata(barerepo): encoding=encoding, ) amended_commit = repo[amended_oid] + assert isinstance(amended_commit, Commit) assert repo.head.target == amended_oid - assert ObjectType.COMMIT == amended_commit.type + assert ObjectType.COMMIT.value == amended_commit.type assert amended_committer == amended_commit.committer assert amended_author == amended_commit.author assert amended_message.encode(encoding) == amended_commit.raw_message @@ -184,9 +189,10 @@ def test_amend_commit_metadata(barerepo): assert commit.tree == amended_commit.tree # we didn't touch the tree -def test_amend_commit_tree(barerepo): +def test_amend_commit_tree(barerepo: Repository) -> None: repo = barerepo commit = repo[COMMIT_SHA_TO_AMEND] + assert isinstance(commit, Commit) assert commit.id == repo.head.target tree = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' @@ -194,9 +200,11 @@ def test_amend_commit_tree(barerepo): amended_oid = repo.amend_commit(commit, 'HEAD', tree=tree_prefix) amended_commit = repo[amended_oid] + assert isinstance(amended_commit, Commit) + assert isinstance(commit, Commit) assert repo.head.target == amended_oid - assert ObjectType.COMMIT == amended_commit.type + assert ObjectType.COMMIT.value == amended_commit.type assert commit.message == amended_commit.message assert commit.author == amended_commit.author assert commit.committer == amended_commit.committer @@ -204,11 +212,12 @@ def test_amend_commit_tree(barerepo): assert Oid(hex=tree) == amended_commit.tree_id -def test_amend_commit_not_tip_of_branch(barerepo): +def test_amend_commit_not_tip_of_branch(barerepo: Repository) -> None: repo = barerepo # This commit isn't at the tip of the branch. commit = repo['5fe808e8953c12735680c257f56600cb0de44b10'] + assert isinstance(commit, Commit) assert commit.id != repo.head.target # Can't update HEAD to the rewritten commit because it's not the tip of the branch. @@ -219,16 +228,17 @@ def test_amend_commit_not_tip_of_branch(barerepo): repo.amend_commit(commit, None, message='this will work') -def test_amend_commit_no_op(barerepo): +def test_amend_commit_no_op(barerepo: Repository) -> None: repo = barerepo commit = repo[COMMIT_SHA_TO_AMEND] + assert isinstance(commit, Commit) assert commit.id == repo.head.target amended_oid = repo.amend_commit(commit, None) assert amended_oid == commit.id -def test_amend_commit_argument_types(barerepo): +def test_amend_commit_argument_types(barerepo: Repository) -> None: repo = barerepo some_tree = repo['967fce8df97cc71722d3c2a5930ef3e6f1d27b12'] @@ -236,33 +246,34 @@ def test_amend_commit_argument_types(barerepo): alt_commit1 = Oid(hex=COMMIT_SHA_TO_AMEND) alt_commit2 = COMMIT_SHA_TO_AMEND alt_tree = some_tree + assert isinstance(alt_tree, Tree) alt_refname = ( repo.head ) # try this one last, because it'll change the commit at the tip # Pass bad values/types for the commit with pytest.raises(ValueError): - repo.amend_commit(None, None) + repo.amend_commit(None, None) # type: ignore with pytest.raises(TypeError): - repo.amend_commit(some_tree, None) + repo.amend_commit(some_tree, None) # type: ignore # Pass bad types for signatures with pytest.raises(TypeError): - repo.amend_commit(commit, None, author='Toto') + repo.amend_commit(commit, None, author='Toto') # type: ignore with pytest.raises(TypeError): - repo.amend_commit(commit, None, committer='Toto') + repo.amend_commit(commit, None, committer='Toto') # type: ignore # Pass bad refnames with pytest.raises(ValueError): - repo.amend_commit(commit, 'this-ref-doesnt-exist') + repo.amend_commit(commit, 'this-ref-doesnt-exist') # type: ignore with pytest.raises(TypeError): - repo.amend_commit(commit, repo) + repo.amend_commit(commit, repo) # type: ignore # Pass bad trees with pytest.raises(ValueError): - repo.amend_commit(commit, None, tree="can't parse this") + repo.amend_commit(commit, None, tree="can't parse this") # type: ignore with pytest.raises(KeyError): - repo.amend_commit(commit, None, tree='baaaaad') + repo.amend_commit(commit, None, tree='baaaaad') # type: ignore # Pass an Oid for the commit amended_oid = repo.amend_commit(alt_commit1, None, message='Hello') @@ -273,7 +284,8 @@ def test_amend_commit_argument_types(barerepo): # 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 isinstance(amended_commit, Commit) + assert ObjectType.COMMIT.value == amended_commit.type 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 diff --git a/test/test_commit_gpg.py b/test/test_commit_gpg.py index 88450b6c..d20f584e 100644 --- a/test/test_commit_gpg.py +++ b/test/test_commit_gpg.py @@ -23,7 +23,7 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -from pygit2 import Oid, Signature +from pygit2 import Commit, Oid, Repository, Signature from pygit2.enums import ObjectType content = """\ @@ -84,7 +84,7 @@ # XXX: seems macos wants the space while linux does not -def test_commit_signing(gpgsigned): +def test_commit_signing(gpgsigned: Repository) -> None: repo = gpgsigned message = 'a simple commit which works' author = Signature( @@ -111,6 +111,7 @@ def test_commit_signing(gpgsigned): # create/retrieve signed commit oid = repo.create_commit_with_signature(content, gpgsig) commit = repo.get(oid) + assert isinstance(commit, Commit) signature, payload = commit.gpg_signature # validate signed commit @@ -133,11 +134,12 @@ def test_commit_signing(gpgsigned): assert Oid(hex=parent) == commit.parent_ids[0] -def test_get_gpg_signature_when_unsigned(gpgsigned): +def test_get_gpg_signature_when_unsigned(gpgsigned: Repository) -> None: unhash = '5b5b025afb0b4c913b4c338a42934a3863bf3644' repo = gpgsigned commit = repo.get(unhash) + assert isinstance(commit, Commit) signature, payload = commit.gpg_signature assert signature is None diff --git a/test/test_commit_trailer.py b/test/test_commit_trailer.py index 05504759..51043ff8 100644 --- a/test/test_commit_trailer.py +++ b/test/test_commit_trailer.py @@ -23,26 +23,31 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from pathlib import Path +from typing import Generator + import pytest import pygit2 +from pygit2 import Commit, Repository from . import utils @pytest.fixture -def repo(tmp_path): +def repo(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('trailerrepo.zip', tmp_path) as path: yield pygit2.Repository(path) -def test_get_trailers_array(repo): +def test_get_trailers_array(repo: Repository) -> None: commit_hash = '010231b2fdaee6b21da4f06058cf6c6a3392dd12' expected_trailers = { 'Bug': '1234', 'Signed-off-by': 'Tyler Cipriani ', } commit = repo.get(commit_hash) + assert isinstance(commit, Commit) trailers = commit.message_trailers assert trailers['Bug'] == expected_trailers['Bug'] diff --git a/test/test_config.py b/test/test_config.py index 71b2b534..5d4e5a09 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -24,10 +24,11 @@ # Boston, MA 02110-1301, USA. from pathlib import Path +from typing import Generator import pytest -from pygit2 import Config +from pygit2 import Config, Repository from . import utils @@ -35,7 +36,7 @@ @pytest.fixture -def config(testrepo): +def config(testrepo: Repository) -> Generator[object, None, None]: yield testrepo.config try: Path(CONFIG_FILENAME).unlink() diff --git a/test/test_credentials.py b/test/test_credentials.py index 2cfe4822..1a575257 100644 --- a/test/test_credentials.py +++ b/test/test_credentials.py @@ -29,7 +29,14 @@ import pytest import pygit2 -from pygit2 import Keypair, KeypairFromAgent, KeypairFromMemory, Username, UserPass +from pygit2 import ( + Keypair, + KeypairFromAgent, + KeypairFromMemory, + Repository, + Username, + UserPass, +) from pygit2.enums import CredentialType from . import utils @@ -44,13 +51,13 @@ ORIGIN_REFSPEC = '+refs/heads/*:refs/remotes/origin/*' -def test_username(): +def test_username() -> None: username = 'git' cred = Username(username) assert (username,) == cred.credential_tuple -def test_userpass(): +def test_userpass() -> None: username = 'git' password = 'sekkrit' @@ -58,7 +65,7 @@ def test_userpass(): assert (username, password) == cred.credential_tuple -def test_ssh_key(): +def test_ssh_key() -> None: username = 'git' pubkey = 'id_rsa.pub' privkey = 'id_rsa' @@ -68,7 +75,7 @@ def test_ssh_key(): assert (username, pubkey, privkey, passphrase) == cred.credential_tuple -def test_ssh_key_aspath(): +def test_ssh_key_aspath() -> None: username = 'git' pubkey = Path('id_rsa.pub') privkey = Path('id_rsa') @@ -78,14 +85,14 @@ def test_ssh_key_aspath(): assert (username, pubkey, privkey, passphrase) == cred.credential_tuple -def test_ssh_agent(): +def test_ssh_agent() -> None: username = 'git' cred = KeypairFromAgent(username) assert (username, None, None, None) == cred.credential_tuple -def test_ssh_from_memory(): +def test_ssh_from_memory() -> None: username = 'git' pubkey = 'public key data' privkey = 'private key data' @@ -97,7 +104,7 @@ def test_ssh_from_memory(): @utils.requires_network @utils.requires_ssh -def test_keypair(tmp_path, pygit2_empty_key): +def test_keypair(tmp_path: Path, pygit2_empty_key: tuple[Path, str, str]) -> None: url = 'ssh://git@github.com/pygit2/empty' with pytest.raises(pygit2.GitError): pygit2.clone_repository(url, tmp_path) @@ -111,7 +118,9 @@ def test_keypair(tmp_path, pygit2_empty_key): @utils.requires_network @utils.requires_ssh -def test_keypair_from_memory(tmp_path, pygit2_empty_key): +def test_keypair_from_memory( + tmp_path: Path, pygit2_empty_key: tuple[Path, str, str] +) -> None: url = 'ssh://git@github.com/pygit2/empty' with pytest.raises(pygit2.GitError): pygit2.clone_repository(url, tmp_path) @@ -128,7 +137,7 @@ def test_keypair_from_memory(tmp_path, pygit2_empty_key): pygit2.clone_repository(url, tmp_path, callbacks=callbacks) -def test_callback(testrepo): +def test_callback(testrepo: Repository): class MyCallbacks(pygit2.RemoteCallbacks): def credentials(testrepo, url, username, allowed): assert allowed & CredentialType.USERPASS_PLAINTEXT @@ -141,7 +150,7 @@ def credentials(testrepo, url, username, allowed): @utils.requires_network -def test_bad_cred_type(testrepo): +def test_bad_cred_type(testrepo: Repository): class MyCallbacks(pygit2.RemoteCallbacks): def credentials(testrepo, url, username, allowed): assert allowed & CredentialType.USERPASS_PLAINTEXT @@ -154,7 +163,7 @@ def credentials(testrepo, url, username, allowed): @utils.requires_network -def test_fetch_certificate_check(testrepo): +def test_fetch_certificate_check(testrepo: Repository): class MyCallbacks(pygit2.RemoteCallbacks): def certificate_check(testrepo, certificate, valid, host): assert certificate is None @@ -179,7 +188,7 @@ def certificate_check(testrepo, certificate, valid, host): @utils.requires_network -def test_user_pass(testrepo): +def test_user_pass(testrepo: Repository): credentials = UserPass('libgit2', 'libgit2') callbacks = pygit2.RemoteCallbacks(credentials=credentials) @@ -191,7 +200,7 @@ def test_user_pass(testrepo): @utils.requires_proxy @utils.requires_network @utils.requires_future_libgit2 -def test_proxy(testrepo): +def test_proxy(testrepo: Repository): credentials = UserPass('libgit2', 'libgit2') callbacks = pygit2.RemoteCallbacks(credentials=credentials)