From fc936574fd5a1b752f74cd4505a34c55ad863b44 Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Fri, 1 Aug 2025 08:22:05 +0200 Subject: [PATCH 1/6] improve types according to test/test_repository_bare.py --- pygit2/_pygit2.pyi | 6 +++- test/test_repository_bare.py | 68 ++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index d7ca90b3..c8e7ee36 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -766,6 +766,7 @@ class Repository: def _from_c(cls, ptr: 'GitRepositoryC', owned: bool) -> 'Repository': ... def __iter__(self) -> Iterator[Oid]: ... def __getitem__(self, key: str | Oid) -> Object: ... + def __contains__(self, name: _OidArg) -> bool: ... def add_worktree( self, name: str, path: str | Path, ref: Reference = ... ) -> Worktree: ... @@ -976,6 +977,7 @@ class Repository: def raw_listall_references(self) -> list[bytes]: ... @property def raw_message(self) -> bytes: ... + def read(self, oid: _OidArg) -> tuple[int, bytes]: ... def remove_message(self) -> None: ... def references_iterator_init(self) -> Iterator[Reference]: ... def references_iterator_next( @@ -992,6 +994,7 @@ class Repository: def revert_commit( self, revert_commit: Commit, our_commit: Commit, mainline: int = 0 ) -> Index: ... + def set_head(self, target: _OidArg) -> None: ... def set_ident(self, name: str, email: str) -> None: ... def set_odb(self, odb: Odb) -> None: ... def set_refdb(self, refdb: Refdb) -> None: ... @@ -1033,6 +1036,7 @@ class Repository: def walk( self, oid: _OidArg | None, sort_mode: SortMode = SortMode.NONE ) -> Walker: ... + def write(self, type: int, data: bytes) -> Oid: ... def write_archive( self, treeish: str | Tree | Object | Oid, @@ -1145,7 +1149,7 @@ class Worktree: def discover_repository( path: str, across_fs: bool = False, ceiling_dirs: str = ... ) -> str | None: ... -def hash(data: bytes) -> Oid: ... +def hash(data: bytes | str) -> Oid: ... def hashfile(path: str) -> Oid: ... def init_file_backend(path: str, flags: int = 0) -> object: ... @overload diff --git a/test/test_repository_bare.py b/test/test_repository_bare.py index 83392155..4021fc8b 100644 --- a/test/test_repository_bare.py +++ b/test/test_repository_bare.py @@ -28,10 +28,12 @@ import pathlib import sys import tempfile +from pathlib import Path import pytest import pygit2 +from pygit2 import Branch, Commit, Oid, Repository from pygit2.enums import FileMode, ObjectType from . import utils @@ -43,15 +45,15 @@ BLOB_OID = pygit2.Oid(raw=BLOB_RAW) -def test_is_empty(barerepo): +def test_is_empty(barerepo: Repository) -> None: assert not barerepo.is_empty -def test_is_bare(barerepo): +def test_is_bare(barerepo: Repository) -> None: assert barerepo.is_bare -def test_head(barerepo): +def test_head(barerepo: Repository) -> None: head = barerepo.head assert HEAD_SHA == head.target assert type(head) is pygit2.Reference @@ -59,7 +61,7 @@ def test_head(barerepo): assert not barerepo.head_is_detached -def test_set_head(barerepo): +def test_set_head(barerepo: Repository) -> None: # Test setting a detached HEAD. barerepo.set_head(pygit2.Oid(hex=PARENT_SHA)) assert barerepo.head.target == PARENT_SHA @@ -69,9 +71,9 @@ def test_set_head(barerepo): assert barerepo.head.target == HEAD_SHA -def test_read(barerepo): +def test_read(barerepo: Repository) -> None: with pytest.raises(TypeError): - barerepo.read(123) + barerepo.read(123) # type: ignore utils.assertRaisesWithArg(KeyError, '1' * 40, barerepo.read, '1' * 40) ab = barerepo.read(BLOB_OID) @@ -87,7 +89,7 @@ def test_read(barerepo): assert (ObjectType.BLOB, b'a contents\n') == a3 -def test_write(barerepo): +def test_write(barerepo: Repository) -> None: data = b'hello world' # invalid object type with pytest.raises(ValueError): @@ -97,9 +99,9 @@ def test_write(barerepo): assert type(oid) is pygit2.Oid -def test_contains(barerepo): +def test_contains(barerepo: Repository) -> None: with pytest.raises(TypeError): - 123 in barerepo + 123 in barerepo # type: ignore assert BLOB_OID in barerepo assert BLOB_HEX in barerepo assert BLOB_HEX[:10] in barerepo @@ -107,45 +109,47 @@ def test_contains(barerepo): assert ('a' * 20) not in barerepo -def test_iterable(barerepo): +def test_iterable(barerepo: Repository) -> None: oid = pygit2.Oid(hex=BLOB_HEX) assert oid in [obj for obj in barerepo] -def test_lookup_blob(barerepo): +def test_lookup_blob(barerepo: Repository) -> None: with pytest.raises(TypeError): - barerepo[123] + barerepo[123] # type: ignore 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 int(ObjectType.BLOB) == a.type -def test_lookup_blob_prefix(barerepo): +def test_lookup_blob_prefix(barerepo: Repository) -> None: a = barerepo[BLOB_HEX[:5]] assert b'a contents\n' == a.read_raw() assert BLOB_HEX == a.id - assert ObjectType.BLOB == a.type + assert int(ObjectType.BLOB) == a.type -def test_lookup_commit(barerepo): +def test_lookup_commit(barerepo: Repository) -> None: commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' commit = barerepo[commit_sha] assert commit_sha == commit.id - assert ObjectType.COMMIT == commit.type + assert int(ObjectType.COMMIT) == commit.type + assert isinstance(commit, Commit) assert commit.message == ( 'Second test data commit.\n\nThis commit has some additional text.\n' ) -def test_lookup_commit_prefix(barerepo): +def test_lookup_commit_prefix(barerepo: Repository) -> None: commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' commit_sha_prefix = commit_sha[:7] too_short_prefix = commit_sha[:3] commit = barerepo[commit_sha_prefix] assert commit_sha == commit.id - assert ObjectType.COMMIT == commit.type + assert int(ObjectType.COMMIT) == commit.type + assert isinstance(commit, Commit) assert ( 'Second test data commit.\n\n' 'This commit has some additional text.\n' == commit.message @@ -154,14 +158,14 @@ def test_lookup_commit_prefix(barerepo): barerepo.__getitem__(too_short_prefix) -def test_expand_id(barerepo): +def test_expand_id(barerepo: Repository) -> None: commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' expanded = barerepo.expand_id(commit_sha[:7]) assert commit_sha == expanded @utils.requires_refcount -def test_lookup_commit_refcount(barerepo): +def test_lookup_commit_refcount(barerepo: Repository) -> None: start = sys.getrefcount(barerepo) commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' commit = barerepo[commit_sha] @@ -170,30 +174,30 @@ def test_lookup_commit_refcount(barerepo): assert start == end -def test_get_path(barerepo_path): +def test_get_path(barerepo_path: tuple[Repository, Path]) -> None: barerepo, path = barerepo_path directory = pathlib.Path(barerepo.path).resolve() assert directory == path.resolve() -def test_get_workdir(barerepo): +def test_get_workdir(barerepo: Repository) -> None: assert barerepo.workdir is None -def test_revparse_single(barerepo): +def test_revparse_single(barerepo: Repository) -> None: parent = barerepo.revparse_single('HEAD^') assert parent.id == PARENT_SHA -def test_hash(barerepo): +def test_hash(barerepo: Repository) -> None: data = 'foobarbaz' hashed_sha1 = pygit2.hash(data) written_sha1 = barerepo.create_blob(data) assert hashed_sha1 == written_sha1 -def test_hashfile(barerepo): +def test_hashfile(barerepo: Repository) -> None: data = 'bazbarfoo' handle, tempfile_path = tempfile.mkstemp() with os.fdopen(handle, 'w') as fh: @@ -204,8 +208,8 @@ def test_hashfile(barerepo): assert hashed_sha1 == written_sha1 -def test_conflicts_in_bare_repository(barerepo): - def create_conflict_file(repo, branch, content): +def test_conflicts_in_bare_repository(barerepo: Repository) -> None: + def create_conflict_file(repo: Repository, branch: Branch, content: str) -> Oid: oid = repo.create_blob(content.encode('utf-8')) tb = repo.TreeBuilder() tb.insert('conflict', oid, FileMode.BLOB) @@ -218,9 +222,13 @@ def create_conflict_file(repo, branch, content): assert commit is not None return commit - b1 = barerepo.create_branch('b1', barerepo.head.peel()) + head_peeled = barerepo.head.peel() + assert isinstance(head_peeled, Commit) + b1 = barerepo.create_branch('b1', head_peeled) c1 = create_conflict_file(barerepo, b1, 'ASCII - abc') - b2 = barerepo.create_branch('b2', barerepo.head.peel()) + head_peeled = barerepo.head.peel() + assert isinstance(head_peeled, Commit) + b2 = barerepo.create_branch('b2', head_peeled) c2 = create_conflict_file(barerepo, b2, 'Unicode - äüö') index = barerepo.merge_commits(c1, c2) From 49d32f3542d2914f7bdb72872824c5393dee5dde Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Fri, 1 Aug 2025 09:06:42 +0200 Subject: [PATCH 2/6] improve types according to test/test_signature.py --- pygit2/_pygit2.pyi | 2 +- test/test_signature.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index c8e7ee36..573a3639 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -1061,7 +1061,7 @@ class Signature: time: int def __init__( self, - name: str, + name: str | bytes, email: str, time: int = -1, offset: int = 0, diff --git a/test/test_signature.py b/test/test_signature.py index a28a1e07..e90f5539 100644 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -29,9 +29,10 @@ import pytest import pygit2 +from pygit2 import Repository, Signature -def __assert(signature, encoding): +def __assert(signature: Signature, encoding: None | str) -> None: encoding = encoding or 'utf-8' assert signature._encoding == encoding assert signature.name == signature.raw_name.decode(encoding) @@ -41,25 +42,25 @@ def __assert(signature, encoding): @pytest.mark.parametrize('encoding', [None, 'utf-8', 'iso-8859-1']) -def test_encoding(encoding): +def test_encoding(encoding: None | str) -> None: signature = pygit2.Signature('Foo Ibáñez', 'foo@example.com', encoding=encoding) __assert(signature, encoding) assert abs(signature.time - time.time()) < 5 assert str(signature) == 'Foo Ibáñez ' -def test_default_encoding(): +def test_default_encoding() -> None: signature = pygit2.Signature('Foo Ibáñez', 'foo@example.com', 1322174594, 60) __assert(signature, 'utf-8') -def test_ascii(): +def test_ascii() -> None: with pytest.raises(UnicodeEncodeError): pygit2.Signature('Foo Ibáñez', 'foo@example.com', encoding='ascii') @pytest.mark.parametrize('encoding', [None, 'utf-8', 'iso-8859-1']) -def test_repr(encoding): +def test_repr(encoding: str | None) -> None: signature = pygit2.Signature( 'Foo Ibáñez', 'foo@bar.com', 1322174594, 60, encoding=encoding ) @@ -68,7 +69,7 @@ def test_repr(encoding): assert signature == eval(expected) -def test_repr_from_commit(barerepo): +def test_repr_from_commit(barerepo: Repository) -> None: repo = barerepo signature = pygit2.Signature('Foo Ibáñez', 'foo@example.com', encoding=None) tree = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' @@ -80,7 +81,7 @@ def test_repr_from_commit(barerepo): assert repr(signature) == repr(commit.committer) -def test_incorrect_encoding(): +def test_incorrect_encoding() -> None: gbk_bytes = 'Café'.encode('GBK') # deliberately specifying a mismatching encoding (mojibake) From 79a7de57f1fcc1ea2438663d9eae5d32d3036014 Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Fri, 1 Aug 2025 08:23:41 +0200 Subject: [PATCH 3/6] add types to several test files --- test/test_repository_custom.py | 8 +++++--- test/test_repository_empty.py | 8 +++++--- test/test_revparse.py | 16 ++++++++-------- test/test_revwalk.py | 21 +++++++++++---------- test/test_status.py | 13 +++++++++---- test/test_tag.py | 20 ++++++++++++-------- test/test_treebuilder.py | 13 +++++++++---- 7 files changed, 59 insertions(+), 40 deletions(-) diff --git a/test/test_repository_custom.py b/test/test_repository_custom.py index bc4f8b9d..336c20a8 100644 --- a/test/test_repository_custom.py +++ b/test/test_repository_custom.py @@ -24,15 +24,17 @@ # Boston, MA 02110-1301, USA. from pathlib import Path +from typing import Generator import pytest import pygit2 +from pygit2 import Repository from pygit2.enums import ObjectType @pytest.fixture -def repo(testrepopacked): +def repo(testrepopacked: Repository) -> Generator[Repository, None, None]: testrepo = testrepopacked odb = pygit2.Odb() @@ -49,7 +51,7 @@ def repo(testrepopacked): yield repo -def test_references(repo): +def test_references(repo: Repository) -> None: refs = [(ref.name, ref.target) for ref in repo.references.objects] assert sorted(refs) == [ ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), @@ -57,6 +59,6 @@ def test_references(repo): ] -def test_objects(repo): +def test_objects(repo: Repository) -> None: a = repo.read('323fae03f4606ea9991df8befbb2fca795e648fa') assert (ObjectType.BLOB, b'foobar\n') == a diff --git a/test/test_repository_empty.py b/test/test_repository_empty.py index ac44ad83..be8a8d34 100644 --- a/test/test_repository_empty.py +++ b/test/test_repository_empty.py @@ -23,15 +23,17 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from pygit2 import Repository -def test_is_empty(emptyrepo): + +def test_is_empty(emptyrepo: Repository) -> None: assert emptyrepo.is_empty -def test_is_base(emptyrepo): +def test_is_base(emptyrepo: Repository) -> None: assert not emptyrepo.is_bare -def test_head(emptyrepo): +def test_head(emptyrepo: Repository) -> None: assert emptyrepo.head_is_unborn assert not emptyrepo.head_is_detached diff --git a/test/test_revparse.py b/test/test_revparse.py index a83f77db..d61df77d 100644 --- a/test/test_revparse.py +++ b/test/test_revparse.py @@ -27,21 +27,21 @@ from pytest import raises -from pygit2 import InvalidSpecError +from pygit2 import InvalidSpecError, Repository from pygit2.enums import RevSpecFlag HEAD_SHA = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' PARENT_SHA = '5ebeeebb320790caf276b9fc8b24546d63316533' # HEAD^ -def test_revparse_single(testrepo): +def test_revparse_single(testrepo: Repository) -> None: assert testrepo.revparse_single('HEAD').id == HEAD_SHA assert testrepo.revparse_single('HEAD^').id == PARENT_SHA o = testrepo.revparse_single('@{-1}') assert o.id == '5470a671a80ac3789f1a6a8cefbcf43ce7af0563' -def test_revparse_ext(testrepo): +def test_revparse_ext(testrepo: Repository) -> None: o, r = testrepo.revparse_ext('master') assert o.id == HEAD_SHA assert r == testrepo.references['refs/heads/master'] @@ -55,21 +55,21 @@ def test_revparse_ext(testrepo): assert r == testrepo.references['refs/heads/i18n'] -def test_revparse_1(testrepo): +def test_revparse_1(testrepo: Repository) -> None: s = testrepo.revparse('master') assert s.from_object.id == HEAD_SHA assert s.to_object is None assert s.flags == RevSpecFlag.SINGLE -def test_revparse_range_1(testrepo): +def test_revparse_range_1(testrepo: Repository) -> None: s = testrepo.revparse('HEAD^1..acecd5e') assert s.from_object.id == PARENT_SHA assert str(s.to_object.id).startswith('acecd5e') assert s.flags == RevSpecFlag.RANGE -def test_revparse_range_2(testrepo): +def test_revparse_range_2(testrepo: Repository) -> None: s = testrepo.revparse('HEAD...i18n') assert str(s.from_object.id).startswith('2be5719') assert str(s.to_object.id).startswith('5470a67') @@ -77,7 +77,7 @@ def test_revparse_range_2(testrepo): assert testrepo.merge_base(s.from_object.id, s.to_object.id) is not None -def test_revparse_range_errors(testrepo): +def test_revparse_range_errors(testrepo: Repository) -> None: with raises(KeyError): testrepo.revparse('nope..2be571915') @@ -85,7 +85,7 @@ def test_revparse_range_errors(testrepo): testrepo.revparse('master............2be571915') -def test_revparse_repr(testrepo): +def test_revparse_repr(testrepo: Repository) -> None: s = testrepo.revparse('HEAD...i18n') assert ( repr(s) diff --git a/test/test_revwalk.py b/test/test_revwalk.py index fa97456d..28cfc406 100644 --- a/test/test_revwalk.py +++ b/test/test_revwalk.py @@ -25,6 +25,7 @@ """Tests for revision walk.""" +from pygit2 import Repository from pygit2.enums import SortMode # In the order given by git log @@ -50,42 +51,42 @@ ] -def test_log(testrepo): +def test_log(testrepo: Repository) -> None: ref = testrepo.lookup_reference('HEAD') for i, entry in enumerate(ref.log()): assert entry.committer.name == REVLOGS[i][0] assert entry.message == REVLOGS[i][1] -def test_walk(testrepo): +def test_walk(testrepo: Repository) -> None: walker = testrepo.walk(log[0], SortMode.TIME) assert [x.id for x in walker] == log -def test_reverse(testrepo): +def test_reverse(testrepo: Repository) -> None: walker = testrepo.walk(log[0], SortMode.TIME | SortMode.REVERSE) assert [x.id for x in walker] == list(reversed(log)) -def test_hide(testrepo): +def test_hide(testrepo: Repository) -> None: walker = testrepo.walk(log[0], SortMode.TIME) walker.hide('4ec4389a8068641da2d6578db0419484972284c8') assert len(list(walker)) == 2 -def test_hide_prefix(testrepo): +def test_hide_prefix(testrepo: Repository) -> None: walker = testrepo.walk(log[0], SortMode.TIME) walker.hide('4ec4389a') assert len(list(walker)) == 2 -def test_reset(testrepo): +def test_reset(testrepo: Repository) -> None: walker = testrepo.walk(log[0], SortMode.TIME) walker.reset() assert list(walker) == [] -def test_push(testrepo): +def test_push(testrepo: Repository) -> None: walker = testrepo.walk(log[-1], SortMode.TIME) assert [x.id for x in walker] == log[-1:] walker.reset() @@ -93,19 +94,19 @@ def test_push(testrepo): assert [x.id for x in walker] == log -def test_sort(testrepo): +def test_sort(testrepo: Repository) -> None: walker = testrepo.walk(log[0], SortMode.TIME) walker.sort(SortMode.TIME | SortMode.REVERSE) assert [x.id for x in walker] == list(reversed(log)) -def test_simplify_first_parent(testrepo): +def test_simplify_first_parent(testrepo: Repository) -> None: walker = testrepo.walk(log[0], SortMode.TIME) walker.simplify_first_parent() assert len(list(walker)) == 3 -def test_default_sorting(testrepo): +def test_default_sorting(testrepo: Repository) -> None: walker = testrepo.walk(log[0], SortMode.NONE) list1 = list([x.id for x in walker]) walker = testrepo.walk(log[0]) diff --git a/test/test_status.py b/test/test_status.py index a875fd9d..653ed93d 100644 --- a/test/test_status.py +++ b/test/test_status.py @@ -25,10 +25,11 @@ import pytest +from pygit2 import Repository from pygit2.enums import FileStatus -def test_status(dirtyrepo): +def test_status(dirtyrepo: Repository) -> None: """ For every file in the status, check that the flags are correct. """ @@ -38,7 +39,7 @@ def test_status(dirtyrepo): assert status == git_status[filepath] -def test_status_untracked_no(dirtyrepo): +def test_status_untracked_no(dirtyrepo: Repository) -> None: git_status = dirtyrepo.status(untracked_files='no') assert not any(status & FileStatus.WT_NEW for status in git_status.values()) @@ -67,7 +68,9 @@ def test_status_untracked_no(dirtyrepo): ), ], ) -def test_status_untracked_normal(dirtyrepo, untracked_files, expected): +def test_status_untracked_normal( + dirtyrepo: Repository, untracked_files: str, expected: set[str] +) -> None: git_status = dirtyrepo.status(untracked_files=untracked_files) assert { file for file, status in git_status.items() if status & FileStatus.WT_NEW @@ -75,7 +78,9 @@ def test_status_untracked_normal(dirtyrepo, untracked_files, expected): @pytest.mark.parametrize('ignored,expected', [(True, {'ignored'}), (False, set())]) -def test_status_ignored(dirtyrepo, ignored, expected): +def test_status_ignored( + dirtyrepo: Repository, ignored: bool, expected: set[str] +) -> None: git_status = dirtyrepo.status(ignored=ignored) assert { file for file, status in git_status.items() if status & FileStatus.IGNORED diff --git a/test/test_tag.py b/test/test_tag.py index e641dded..e4f017b1 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -28,18 +28,20 @@ import pytest import pygit2 +from pygit2 import Repository from pygit2.enums import ObjectType TAG_SHA = '3d2962987c695a29f1f80b6c3aa4ec046ef44369' -def test_read_tag(barerepo): +def test_read_tag(barerepo: Repository) -> None: repo = barerepo tag = repo[TAG_SHA] - target = repo[tag.target] assert isinstance(tag, pygit2.Tag) - assert ObjectType.TAG == tag.type - assert ObjectType.COMMIT == target.type + target = repo[tag.target] + assert isinstance(target, pygit2.Commit) + assert int(ObjectType.TAG) == tag.type + assert int(ObjectType.COMMIT) == target.type assert 'root' == tag.name assert 'Tagged root commit.\n' == tag.message assert 'Initial test data commit.\n' == target.message @@ -48,7 +50,7 @@ def test_read_tag(barerepo): ) -def test_new_tag(barerepo): +def test_new_tag(barerepo: Repository) -> None: name = 'thetag' target = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' message = 'Tag a blob.\n' @@ -57,10 +59,11 @@ def test_new_tag(barerepo): target_prefix = target[:5] too_short_prefix = target[:3] with pytest.raises(ValueError): - barerepo.create_tag(name, too_short_prefix, ObjectType.BLOB, tagger, message) + barerepo.create_tag(name, too_short_prefix, ObjectType.BLOB, tagger, message) # type: ignore sha = barerepo.create_tag(name, target_prefix, ObjectType.BLOB, tagger, message) tag = barerepo[sha] + assert isinstance(tag, pygit2.Tag) assert '3ee44658fd11660e828dfc96b9b5c5f38d5b49bb' == tag.id assert name == tag.name @@ -70,7 +73,7 @@ def test_new_tag(barerepo): assert name == barerepo[tag.id].name -def test_modify_tag(barerepo): +def test_modify_tag(barerepo: Repository) -> None: name = 'thetag' target = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' message = 'Tag a blob.\n' @@ -87,7 +90,8 @@ def test_modify_tag(barerepo): setattr(tag, 'message', message) -def test_get_object(barerepo): +def test_get_object(barerepo: Repository) -> None: repo = barerepo tag = repo[TAG_SHA] + assert isinstance(tag, pygit2.Tag) assert repo[tag.target].id == tag.get_object().id diff --git a/test/test_treebuilder.py b/test/test_treebuilder.py index fc7bc436..99e4d6b3 100644 --- a/test/test_treebuilder.py +++ b/test/test_treebuilder.py @@ -23,16 +23,18 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from pygit2 import Repository, Tree TREE_SHA = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' -def test_new_empty_treebuilder(barerepo): +def test_new_empty_treebuilder(barerepo: Repository) -> None: barerepo.TreeBuilder() -def test_noop_treebuilder(barerepo): +def test_noop_treebuilder(barerepo: Repository) -> None: tree = barerepo[TREE_SHA] + assert isinstance(tree, Tree) bld = barerepo.TreeBuilder(TREE_SHA) result = bld.write() @@ -40,8 +42,9 @@ def test_noop_treebuilder(barerepo): assert tree.id == result -def test_noop_treebuilder_from_tree(barerepo): +def test_noop_treebuilder_from_tree(barerepo: Repository) -> None: tree = barerepo[TREE_SHA] + assert isinstance(tree, Tree) bld = barerepo.TreeBuilder(tree) result = bld.write() @@ -49,11 +52,13 @@ def test_noop_treebuilder_from_tree(barerepo): assert tree.id == result -def test_rebuild_treebuilder(barerepo): +def test_rebuild_treebuilder(barerepo: Repository) -> None: tree = barerepo[TREE_SHA] + assert isinstance(tree, Tree) bld = barerepo.TreeBuilder() for entry in tree: name = entry.name + assert name is not None assert bld.get(name) is None bld.insert(name, entry.id, entry.filemode) assert bld.get(name).id == entry.id From 51814a91bfcd33ebe4c0c32748b0445742013737 Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Fri, 1 Aug 2025 10:05:08 +0200 Subject: [PATCH 4/6] improve types according to test/test_tree.py --- pygit2/_pygit2.pyi | 6 ++--- test/test_tree.py | 62 +++++++++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index 573a3639..69970ecd 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -1117,11 +1117,11 @@ class Tree(Object): interhunk_lines: int = 0, ) -> Diff: ... def __contains__(self, other: str) -> bool: ... # Tree_contains - def __getitem__(self, index: str | int) -> Object: ... # Tree_subscript + def __getitem__(self, index: str | int) -> Tree | Blob: ... # Tree_subscript def __iter__(self) -> Iterator[Object]: ... def __len__(self) -> int: ... # Tree_len - def __rtruediv__(self, other: str) -> Object: ... - def __truediv__(self, other: str) -> Object: ... # Tree_divide + def __rtruediv__(self, other: str) -> Tree | Blob: ... + def __truediv__(self, other: str) -> Tree | Blob: ... # Tree_divide class TreeBuilder: def clear(self) -> None: ... diff --git a/test/test_tree.py b/test/test_tree.py index 85a22591..c5954004 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -28,6 +28,7 @@ import pytest import pygit2 +from pygit2 import Object, Repository, Tree from pygit2.enums import FileMode, ObjectType from . import utils @@ -36,19 +37,20 @@ SUBTREE_SHA = '614fd9a3094bf618ea938fffc00e7d1a54f89ad0' -def assertTreeEntryEqual(entry, sha, name, filemode): +def assertTreeEntryEqual(entry: Object, sha: str, name: str, filemode: int) -> None: assert entry.id == sha assert entry.name == name assert entry.filemode == filemode assert entry.raw_name == name.encode('utf-8') -def test_read_tree(barerepo): +def test_read_tree(barerepo: Repository) -> None: tree = barerepo[TREE_SHA] + assert isinstance(tree, Tree) with pytest.raises(TypeError): - tree[()] + tree[()] # type: ignore with pytest.raises(TypeError): - tree / 123 + tree / 123 # type: ignore utils.assertRaisesWithArg(KeyError, 'abcd', lambda: tree['abcd']) utils.assertRaisesWithArg(IndexError, -4, lambda: tree[-4]) utils.assertRaisesWithArg(IndexError, 3, lambda: tree[3]) @@ -72,45 +74,50 @@ def test_read_tree(barerepo): sha = '297efb891a47de80be0cfe9c639e4b8c9b450989' assertTreeEntryEqual(tree['c/d'], sha, 'd', 0o0100644) assertTreeEntryEqual(tree / 'c/d', sha, 'd', 0o0100644) - assertTreeEntryEqual(tree / 'c' / 'd', sha, 'd', 0o0100644) - assertTreeEntryEqual(tree['c']['d'], sha, 'd', 0o0100644) - assertTreeEntryEqual((tree / 'c')['d'], sha, 'd', 0o0100644) + assertTreeEntryEqual(tree / 'c' / 'd', sha, 'd', 0o0100644) # type: ignore[operator] + assertTreeEntryEqual(tree['c']['d'], sha, 'd', 0o0100644) # type: ignore[index] + assertTreeEntryEqual((tree / 'c')['d'], sha, 'd', 0o0100644) # type: ignore[index] utils.assertRaisesWithArg(KeyError, 'ab/cd', lambda: tree['ab/cd']) utils.assertRaisesWithArg(KeyError, 'ab/cd', lambda: tree / 'ab/cd') - utils.assertRaisesWithArg(KeyError, 'ab', lambda: tree / 'c' / 'ab') + utils.assertRaisesWithArg(KeyError, 'ab', lambda: tree / 'c' / 'ab') # type: ignore[operator] with pytest.raises(TypeError): - tree / 'a' / 'cd' + tree / 'a' / 'cd' # type: ignore -def test_equality(barerepo): +def test_equality(barerepo: Repository) -> None: tree_a = barerepo['18e2d2e9db075f9eb43bcb2daa65a2867d29a15e'] tree_b = barerepo['2ad1d3456c5c4a1c9e40aeeddb9cd20b409623c8'] + assert isinstance(tree_a, Tree) + assert isinstance(tree_b, Tree) assert tree_a['a'] != tree_b['a'] assert tree_a['a'] != tree_b['b'] assert tree_a['b'] == tree_b['b'] -def test_sorting(barerepo): +def test_sorting(barerepo: Repository) -> None: tree_a = barerepo['18e2d2e9db075f9eb43bcb2daa65a2867d29a15e'] + assert isinstance(tree_a, Tree) assert list(tree_a) == sorted(reversed(list(tree_a)), key=pygit2.tree_entry_key) - assert list(tree_a) != reversed(list(tree_a)) + assert list(tree_a) != reversed(list(tree_a)) # type: ignore[comparison-overlap] -def test_read_subtree(barerepo): +def test_read_subtree(barerepo: Repository) -> None: tree = barerepo[TREE_SHA] + assert isinstance(tree, Tree) subtree_entry = tree['c'] assertTreeEntryEqual(subtree_entry, SUBTREE_SHA, 'c', 0o0040000) - assert subtree_entry.type == ObjectType.TREE + assert subtree_entry.type == int(ObjectType.TREE) assert subtree_entry.type_str == 'tree' subtree_entry = tree / 'c' assertTreeEntryEqual(subtree_entry, SUBTREE_SHA, 'c', 0o0040000) - assert subtree_entry.type == ObjectType.TREE + assert subtree_entry.type == int(ObjectType.TREE) assert subtree_entry.type_str == 'tree' subtree = barerepo[subtree_entry.id] + assert isinstance(subtree, Tree) assert 1 == len(subtree) sha = '297efb891a47de80be0cfe9c639e4b8c9b450989' assertTreeEntryEqual(subtree[0], sha, 'd', 0o0100644) @@ -119,7 +126,7 @@ def test_read_subtree(barerepo): assert subtree_entry == barerepo[subtree_entry.id] -def test_new_tree(barerepo): +def test_new_tree(barerepo: Repository) -> None: repo = barerepo b0 = repo.create_blob('1') b1 = repo.create_blob('2') @@ -138,8 +145,8 @@ def test_new_tree(barerepo): ('y', b1, pygit2.Blob, FileMode.BLOB_EXECUTABLE, ObjectType.BLOB, 'blob'), ('z', subtree.id, pygit2.Tree, FileMode.TREE, ObjectType.TREE, 'tree'), ]: - assert name in tree - obj = tree[name] + assert name in tree # type: ignore[operator] + obj = tree[name] # type: ignore[index] assert isinstance(obj, cls) assert obj.name == name assert obj.filemode == filemode @@ -148,7 +155,7 @@ def test_new_tree(barerepo): assert repo[obj.id].id == oid assert obj == repo[obj.id] - obj = tree / name + obj = tree / name # type: ignore[operator] assert isinstance(obj, cls) assert obj.name == name assert obj.filemode == filemode @@ -158,44 +165,49 @@ def test_new_tree(barerepo): assert obj == repo[obj.id] -def test_modify_tree(barerepo): +def test_modify_tree(barerepo: Repository) -> None: tree = barerepo[TREE_SHA] with pytest.raises(TypeError): - operator.setitem('c', tree['a']) + operator.setitem('c', tree['a']) # type: ignore with pytest.raises(TypeError): - operator.delitem('c') + operator.delitem('c') # type: ignore -def test_iterate_tree(barerepo): +def test_iterate_tree(barerepo: Repository) -> None: """ Testing that we're able to iterate of a Tree object and that the resulting sha strings are consistent with the sha strings we could get with other Tree access methods. """ tree = barerepo[TREE_SHA] + assert isinstance(tree, Tree) for tree_entry in tree: + assert tree_entry.name is not None assert tree_entry == tree[tree_entry.name] -def test_iterate_tree_nested(barerepo): +def test_iterate_tree_nested(barerepo: Repository) -> None: """ Testing that we're able to iterate of a Tree object and then iterate trees we receive as a result. """ tree = barerepo[TREE_SHA] + assert isinstance(tree, Tree) for tree_entry in tree: if isinstance(tree_entry, pygit2.Tree): for tree_entry2 in tree_entry: pass -def test_deep_contains(barerepo): +def test_deep_contains(barerepo: Repository) -> None: tree = barerepo[TREE_SHA] + assert isinstance(tree, Tree) assert 'a' in tree assert 'c' in tree assert 'c/d' in tree assert 'c/e' not in tree assert 'd' not in tree + assert isinstance(tree['c'], Tree) assert 'd' in tree['c'] assert 'e' not in tree['c'] From 5d1f6c5995ba44460d7333e528e79ac746b804ca Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Fri, 1 Aug 2025 10:19:32 +0200 Subject: [PATCH 5/6] add types to test/utils.py --- test/utils.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/utils.py b/test/utils.py index f4490ab4..fe42a760 100644 --- a/test/utils.py +++ b/test/utils.py @@ -32,7 +32,7 @@ import zipfile from pathlib import Path from types import TracebackType -from typing import Callable, Optional, ParamSpec, TypeVar +from typing import Any, Callable, Optional, ParamSpec, TypeVar # Requirements import pytest @@ -75,7 +75,7 @@ ) -def gen_blob_sha1(data): +def gen_blob_sha1(data: bytes) -> str: # http://stackoverflow.com/questions/552659/assigning-git-sha1s-without-git m = hashlib.sha1() m.update(f'blob {len(data)}\0'.encode()) @@ -83,8 +83,13 @@ def gen_blob_sha1(data): return m.hexdigest() -def force_rm_handle(remove_path, path, excinfo): - path = Path(path) +def force_rm_handle( + # Callable[..., Any], str, , object + remove_path: Callable[..., Any], + path_str: str, + excinfo: tuple[type[BaseException], BaseException, TracebackType], +) -> None: + path = Path(path_str) path.chmod(path.stat().st_mode | stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) remove_path(path) From 69dd9f446c38093fd143560711543d7b5d93b3f1 Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Fri, 1 Aug 2025 11:03:45 +0200 Subject: [PATCH 6/6] mypy: enable disallow_untyped_defs for test folder and fix remaining errors --- mypy.ini | 4 +++ pygit2/_pygit2.pyi | 12 +++---- test/conftest.py | 30 ++++++++++------- test/test_blame.py | 7 ++-- test/test_blob.py | 2 +- test/test_config.py | 22 ++++++------ test/test_credentials.py | 32 ++++++++++++------ test/test_diff.py | 2 +- test/test_index.py | 4 +-- test/test_patch.py | 2 +- test/test_refs.py | 16 ++++----- test/test_repository.py | 73 ++++++++++++++++++++++++---------------- 12 files changed, 120 insertions(+), 86 deletions(-) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..e06fd6ba --- /dev/null +++ b/mypy.ini @@ -0,0 +1,4 @@ +[mypy] + +[mypy-test.*] +disallow_untyped_defs = True diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index 69970ecd..1db0b450 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -867,8 +867,8 @@ class Repository: ) -> Oid: ... def diff( self, - a: None | str | bytes | Oid | Reference = None, - b: None | str | bytes | Oid | Reference = None, + a: None | str | bytes | Commit | Oid | Reference = None, + b: None | str | bytes | Commit | Oid | Reference = None, cached: bool = False, flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, @@ -1010,7 +1010,7 @@ class Repository: include_ignored: bool = False, keep_all: bool = False, paths: list[str] | None = None, - ) -> None: ... + ) -> Oid: ... def stash_apply( self, index: int = 0, @@ -1036,7 +1036,7 @@ class Repository: def walk( self, oid: _OidArg | None, sort_mode: SortMode = SortMode.NONE ) -> Walker: ... - def write(self, type: int, data: bytes) -> Oid: ... + def write(self, type: int, data: bytes | str) -> Oid: ... def write_archive( self, treeish: str | Tree | Object | Oid, @@ -1147,7 +1147,7 @@ class Worktree: def prune(self, force=False) -> None: ... def discover_repository( - path: str, across_fs: bool = False, ceiling_dirs: str = ... + path: str | Path, across_fs: bool = False, ceiling_dirs: str = ... ) -> str | None: ... def hash(data: bytes | str) -> Oid: ... def hashfile(path: str) -> Oid: ... @@ -1211,7 +1211,7 @@ def option( Option.DISABLE_PACK_KEEP_FILE_CHECKS, Option.SET_OWNER_VALIDATION, ], - value: bool, + value: bool | Literal[0, 1], ) -> None: ... @overload def option(opt: Literal[Option.GET_OWNER_VALIDATION]) -> int: ... diff --git a/test/conftest.py b/test/conftest.py index 6feb9d23..4ea9b688 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,15 +1,17 @@ import platform from pathlib import Path +from typing import Generator import pytest import pygit2 +from pygit2 import Repository from . import utils @pytest.fixture(scope='session', autouse=True) -def global_git_config(): +def global_git_config() -> None: # Do not use global config for better test reproducibility. # https://github.com/libgit2/pygit2/issues/989 levels = [ @@ -26,37 +28,39 @@ def global_git_config(): @pytest.fixture -def pygit2_empty_key(): +def pygit2_empty_key() -> tuple[Path, str, str]: path = Path(__file__).parent / 'keys' / 'pygit2_empty' return path, f'{path}.pub', 'empty' @pytest.fixture -def barerepo(tmp_path): +def barerepo(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('barerepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture -def barerepo_path(tmp_path): +def barerepo_path(tmp_path: Path) -> Generator[tuple[Repository, Path], None, None]: with utils.TemporaryRepository('barerepo.zip', tmp_path) as path: yield pygit2.Repository(path), path @pytest.fixture -def blameflagsrepo(tmp_path): +def blameflagsrepo(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('blameflagsrepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture -def dirtyrepo(tmp_path): +def dirtyrepo(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('dirtyrepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture -def emptyrepo(barerepo, tmp_path): +def emptyrepo( + barerepo: Repository, tmp_path: Path +) -> Generator[Repository, None, None]: with utils.TemporaryRepository('emptyrepo.zip', tmp_path) as path: repo = pygit2.Repository(path) repo.remotes.create('origin', barerepo.path) @@ -64,36 +68,36 @@ def emptyrepo(barerepo, tmp_path): @pytest.fixture -def encodingrepo(tmp_path): +def encodingrepo(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('encoding.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture -def mergerepo(tmp_path): +def mergerepo(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('testrepoformerging.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture -def testrepo(tmp_path): +def testrepo(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('testrepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture -def testrepo_path(tmp_path): +def testrepo_path(tmp_path: Path) -> Generator[tuple[Repository, Path], None, None]: with utils.TemporaryRepository('testrepo.zip', tmp_path) as path: yield pygit2.Repository(path), path @pytest.fixture -def testrepopacked(tmp_path): +def testrepopacked(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('testrepopacked.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture -def gpgsigned(tmp_path): +def gpgsigned(tmp_path: Path) -> Generator[Repository, None, None]: with utils.TemporaryRepository('gpgsigned.zip', tmp_path) as path: yield pygit2.Repository(path) diff --git a/test/test_blame.py b/test/test_blame.py index 4f1d8e2b..cb122395 100644 --- a/test/test_blame.py +++ b/test/test_blame.py @@ -97,12 +97,11 @@ def test_blame_flags(blameflagsrepo: Repository) -> None: def test_blame_with_invalid_index(testrepo: Repository) -> None: blame = testrepo.blame(PATH) - def test(): + with pytest.raises(IndexError): blame[100000] - blame[-1] - with pytest.raises(IndexError): - test() + with pytest.raises(OverflowError): + blame[-1] def test_blame_for_line(testrepo: Repository) -> None: diff --git a/test/test_blob.py b/test/test_blob.py index 47ced260..dcce71f4 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -110,7 +110,7 @@ def test_create_blob(testrepo: Repository) -> None: assert len(BLOB_NEW_CONTENT) == len(blob_buffer) assert BLOB_NEW_CONTENT == blob_buffer - def set_content(): + def set_content() -> None: blob_buffer[:2] = b'hi' with pytest.raises(TypeError): diff --git a/test/test_config.py b/test/test_config.py index 5d4e5a09..d3416a9a 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -44,11 +44,11 @@ def config(testrepo: Repository) -> Generator[object, None, None]: pass -def test_config(config): +def test_config(config: Config) -> None: assert config is not None -def test_global_config(): +def test_global_config() -> None: try: assert Config.get_global_config() is not None except IOError: @@ -56,7 +56,7 @@ def test_global_config(): pass -def test_system_config(): +def test_system_config() -> None: try: assert Config.get_system_config() is not None except IOError: @@ -64,7 +64,7 @@ def test_system_config(): pass -def test_new(): +def test_new() -> None: # Touch file open(CONFIG_FILENAME, 'w').close() @@ -81,7 +81,7 @@ def test_new(): assert config_read['core.editor'] == 'ed' -def test_add(): +def test_add() -> None: with open(CONFIG_FILENAME, 'w') as new_file: new_file.write('[this]\n\tthat = true\n') new_file.write('[something "other"]\n\there = false') @@ -94,7 +94,7 @@ def test_add(): assert not config.get_bool('something.other.here') -def test_add_aspath(): +def test_add_aspath() -> None: with open(CONFIG_FILENAME, 'w') as new_file: new_file.write('[this]\n\tthat = true\n') @@ -103,7 +103,7 @@ def test_add_aspath(): assert 'this.that' in config -def test_read(config): +def test_read(config: Config) -> None: with pytest.raises(TypeError): config[()] with pytest.raises(TypeError): @@ -121,7 +121,7 @@ def test_read(config): assert config.get_int('core.repositoryformatversion') == 0 -def test_write(config): +def test_write(config: Config) -> None: with pytest.raises(TypeError): config.__setitem__((), 'This should not work') @@ -148,7 +148,7 @@ def test_write(config): assert 'core.dummy3' not in config -def test_multivar(): +def test_multivar() -> None: with open(CONFIG_FILENAME, 'w') as new_file: new_file.write('[this]\n\tthat = foobar\n\tthat = foobeer\n') @@ -175,7 +175,7 @@ def test_multivar(): assert [] == list(config.get_multivar('this.that', '')) -def test_iterator(config): +def test_iterator(config: Config) -> None: lst = {} for entry in config: assert entry.level > -1 @@ -185,7 +185,7 @@ def test_iterator(config): assert lst['core.bare'] -def test_parsing(): +def test_parsing() -> None: assert Config.parse_bool('on') assert Config.parse_bool('1') diff --git a/test/test_credentials.py b/test/test_credentials.py index 1a575257..dbc98823 100644 --- a/test/test_credentials.py +++ b/test/test_credentials.py @@ -137,10 +137,15 @@ def test_keypair_from_memory( pygit2.clone_repository(url, tmp_path, callbacks=callbacks) -def test_callback(testrepo: Repository): +def test_callback(testrepo: Repository) -> None: class MyCallbacks(pygit2.RemoteCallbacks): - def credentials(testrepo, url, username, allowed): - assert allowed & CredentialType.USERPASS_PLAINTEXT + def credentials( + self, + url: str, + username_from_url: str | None, + allowed_types: CredentialType, + ) -> Username | UserPass | Keypair: + assert allowed_types & CredentialType.USERPASS_PLAINTEXT raise Exception("I don't know the password") url = 'https://github.com/github/github' @@ -150,10 +155,15 @@ def credentials(testrepo, url, username, allowed): @utils.requires_network -def test_bad_cred_type(testrepo: Repository): +def test_bad_cred_type(testrepo: Repository) -> None: class MyCallbacks(pygit2.RemoteCallbacks): - def credentials(testrepo, url, username, allowed): - assert allowed & CredentialType.USERPASS_PLAINTEXT + def credentials( + self, + url: str, + username_from_url: str | None, + allowed_types: CredentialType, + ) -> Username | UserPass | Keypair: + assert allowed_types & CredentialType.USERPASS_PLAINTEXT return Keypair('git', 'foo.pub', 'foo', 'sekkrit') url = 'https://github.com/github/github' @@ -163,9 +173,11 @@ def credentials(testrepo, url, username, allowed): @utils.requires_network -def test_fetch_certificate_check(testrepo: Repository): +def test_fetch_certificate_check(testrepo: Repository) -> None: class MyCallbacks(pygit2.RemoteCallbacks): - def certificate_check(testrepo, certificate, valid, host): + def certificate_check( + self, certificate: None, valid: bool, host: bytes + ) -> bool: assert certificate is None assert valid is True assert host == b'github.com' @@ -188,7 +200,7 @@ def certificate_check(testrepo, certificate, valid, host): @utils.requires_network -def test_user_pass(testrepo: Repository): +def test_user_pass(testrepo: Repository) -> None: credentials = UserPass('libgit2', 'libgit2') callbacks = pygit2.RemoteCallbacks(credentials=credentials) @@ -200,7 +212,7 @@ def test_user_pass(testrepo: Repository): @utils.requires_proxy @utils.requires_network @utils.requires_future_libgit2 -def test_proxy(testrepo: Repository): +def test_proxy(testrepo: Repository) -> None: credentials = UserPass('libgit2', 'libgit2') callbacks = pygit2.RemoteCallbacks(credentials=credentials) diff --git a/test/test_diff.py b/test/test_diff.py index dea3a92e..7c75dc4d 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -433,7 +433,7 @@ def test_parse_diff_null() -> None: pygit2.Diff.parse_diff(None) # type: ignore -def test_parse_diff_bad(): +def test_parse_diff_bad() -> None: diff = textwrap.dedent( """ diff --git a/file1 b/file1 diff --git a/test/test_index.py b/test/test_index.py index 6f3f3824..0fa2e540 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -298,11 +298,11 @@ def test_entry_repr(testrepo: Repository) -> None: ) -def test_create_empty(): +def test_create_empty() -> None: Index() -def test_create_empty_read_tree_as_string(): +def test_create_empty_read_tree_as_string() -> None: index = Index() # no repo associated, so we don't know where to read from with pytest.raises(TypeError): diff --git a/test/test_patch.py b/test/test_patch.py index 1c27986e..4b74dd57 100644 --- a/test/test_patch.py +++ b/test/test_patch.py @@ -81,7 +81,7 @@ """ -def test_patch_create_from_buffers(): +def test_patch_create_from_buffers() -> None: patch = pygit2.Patch.create_from( BLOB_OLD_CONTENT, BLOB_NEW_CONTENT, diff --git a/test/test_refs.py b/test/test_refs.py index e1eced7a..5d441fcc 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -734,7 +734,7 @@ def test_peel(testrepo: Repository) -> None: assert commit.tree.id == ref.peel(Tree).id -def test_valid_reference_names_ascii(): +def test_valid_reference_names_ascii() -> None: assert reference_is_valid_name('HEAD') assert reference_is_valid_name('refs/heads/master') assert reference_is_valid_name('refs/heads/perfectly/valid') @@ -742,12 +742,12 @@ def test_valid_reference_names_ascii(): assert reference_is_valid_name('refs/special/ref') -def test_valid_reference_names_unicode(): +def test_valid_reference_names_unicode() -> None: assert reference_is_valid_name('refs/heads/ünicöde') assert reference_is_valid_name('refs/tags/😀') -def test_invalid_reference_names(): +def test_invalid_reference_names() -> None: assert not reference_is_valid_name('') assert not reference_is_valid_name(' refs/heads/master') assert not reference_is_valid_name('refs/heads/in..valid') @@ -762,12 +762,12 @@ def test_invalid_reference_names(): assert not reference_is_valid_name('refs/heads/foo//bar') -def test_invalid_arguments(): +def test_invalid_arguments() -> None: with pytest.raises(TypeError): - reference_is_valid_name() + reference_is_valid_name() # type: ignore with pytest.raises(TypeError): - reference_is_valid_name(None) + reference_is_valid_name(None) # type: ignore with pytest.raises(TypeError): - reference_is_valid_name(1) + reference_is_valid_name(1) # type: ignore with pytest.raises(TypeError): - reference_is_valid_name('too', 'many') + reference_is_valid_name('too', 'many') # type: ignore diff --git a/test/test_repository.py b/test/test_repository.py index 5802ae4b..b98b1784 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -38,14 +38,18 @@ DiffFile, IndexEntry, Oid, + Remote, Repository, + Worktree, clone_repository, discover_repository, init_repository, ) +from pygit2.credentials import Keypair, Username, UserPass from pygit2.enums import ( CheckoutNotify, CheckoutStrategy, + CredentialType, FileMode, FileStatus, ObjectType, @@ -446,7 +450,7 @@ def test_stash_partial(testrepo: Repository) -> None: assert testrepo.status()['bye.txt'] == FileStatus.WT_NEW assert testrepo.status()['untracked2.txt'] == FileStatus.WT_NEW - def stash_pathspecs(paths): + def stash_pathspecs(paths: list[str]) -> bool: stash_id = testrepo.stash( sig, message=stash_message, keep_all=True, paths=paths ) @@ -480,7 +484,7 @@ def test_stash_progress_callback(testrepo: Repository) -> None: progress_sequence = [] class MyStashApplyCallbacks(pygit2.StashApplyCallbacks): - def stash_apply_progress(self, progress: StashApplyProgress): + def stash_apply_progress(self, progress: StashApplyProgress) -> None: progress_sequence.append(progress) # apply the stash @@ -515,7 +519,7 @@ def test_stash_aborted_from_callbacks(testrepo: Repository) -> None: # define callbacks that will abort the unstash process # just as libgit2 is ready to write the files to disk class MyStashApplyCallbacks(pygit2.StashApplyCallbacks): - def stash_apply_progress(self, progress: StashApplyProgress): + def stash_apply_progress(self, progress: StashApplyProgress) -> None: if progress == StashApplyProgress.CHECKOUT_UNTRACKED: raise InterruptedError('Stop applying the stash!') @@ -635,7 +639,7 @@ def test_default_signature(testrepo: Repository) -> None: assert 'rjh@example.com' == sig.email -def test_new_repo(tmp_path): +def test_new_repo(tmp_path: Path) -> None: repo = init_repository(tmp_path, False) oid = repo.write(ObjectType.BLOB, 'Test') @@ -644,55 +648,57 @@ def test_new_repo(tmp_path): assert (tmp_path / '.git').exists() -def test_no_arg(tmp_path): +def test_no_arg(tmp_path: Path) -> None: repo = init_repository(tmp_path) assert not repo.is_bare -def test_no_arg_aspath(tmp_path): +def test_no_arg_aspath(tmp_path: Path) -> None: repo = init_repository(Path(tmp_path)) assert not repo.is_bare -def test_pos_arg_false(tmp_path): +def test_pos_arg_false(tmp_path: Path) -> None: repo = init_repository(tmp_path, False) assert not repo.is_bare -def test_pos_arg_true(tmp_path): +def test_pos_arg_true(tmp_path: Path) -> None: repo = init_repository(tmp_path, True) assert repo.is_bare -def test_keyword_arg_false(tmp_path): +def test_keyword_arg_false(tmp_path: Path) -> None: repo = init_repository(tmp_path, bare=False) assert not repo.is_bare -def test_keyword_arg_true(tmp_path): +def test_keyword_arg_true(tmp_path: Path) -> None: repo = init_repository(tmp_path, bare=True) assert repo.is_bare -def test_discover_repo(tmp_path): +def test_discover_repo(tmp_path: Path) -> None: repo = init_repository(tmp_path, False) subdir = tmp_path / 'test1' / 'test2' subdir.mkdir(parents=True) assert repo.path == discover_repository(str(subdir)) -def test_discover_repo_aspath(tmp_path): +def test_discover_repo_aspath(tmp_path: Path) -> None: repo = init_repository(Path(tmp_path), False) subdir = Path(tmp_path) / 'test1' / 'test2' subdir.mkdir(parents=True) assert repo.path == discover_repository(subdir) -def test_discover_repo_not_found(): - assert discover_repository(tempfile.tempdir) is None +def test_discover_repo_not_found() -> None: + tempdir = tempfile.tempdir + assert tempdir is not None + assert discover_repository(tempdir) is None -def test_repository_init(barerepo_path): +def test_repository_init(barerepo_path: tuple[Repository, Path]) -> None: barerepo, path = barerepo_path assert isinstance(path, Path) pygit2.Repository(path) @@ -700,7 +706,7 @@ def test_repository_init(barerepo_path): pygit2.Repository(bytes(path)) -def test_clone_repository(barerepo, tmp_path): +def test_clone_repository(barerepo: Repository, tmp_path: Path) -> None: assert barerepo.is_bare repo = clone_repository(Path(barerepo.path), tmp_path / 'clonepath') assert not repo.is_empty @@ -710,14 +716,14 @@ def test_clone_repository(barerepo, tmp_path): assert not repo.is_bare -def test_clone_bare_repository(barerepo, tmp_path): +def test_clone_bare_repository(barerepo: Repository, tmp_path: Path) -> None: repo = clone_repository(barerepo.path, tmp_path / 'clone', bare=True) assert not repo.is_empty assert repo.is_bare @utils.requires_network -def test_clone_shallow_repository(tmp_path): +def test_clone_shallow_repository(tmp_path: Path) -> None: # shallow cloning currently only works with remote repositories url = 'https://github.com/libgit2/TestGitRepository' repo = clone_repository(url, tmp_path / 'clone-shallow', depth=1) @@ -725,15 +731,17 @@ def test_clone_shallow_repository(tmp_path): assert repo.is_shallow -def test_clone_repository_and_remote_callbacks(barerepo, tmp_path): +def test_clone_repository_and_remote_callbacks( + barerepo: Repository, tmp_path: Path +) -> None: url = Path(barerepo.path).resolve().as_uri() repo_path = tmp_path / 'clone-into' - def create_repository(path, bare): + def create_repository(path: Path, bare: bool) -> Repository: return init_repository(path, bare) # here we override the name - def create_remote(repo, name, url): + def create_remote(repo: Repository, name: str, url: str) -> Remote: return repo.remotes.create('custom_remote', url) repo = clone_repository( @@ -746,7 +754,7 @@ def create_remote(repo, name, url): @utils.requires_network -def test_clone_with_credentials(tmp_path): +def test_clone_with_credentials(tmp_path: Path) -> None: url = 'https://github.com/libgit2/TestGitRepository' credentials = pygit2.UserPass('libgit2', 'libgit2') callbacks = pygit2.RemoteCallbacks(credentials=credentials) @@ -756,9 +764,14 @@ def test_clone_with_credentials(tmp_path): @utils.requires_network -def test_clone_bad_credentials(tmp_path): +def test_clone_bad_credentials(tmp_path: Path) -> None: class MyCallbacks(pygit2.RemoteCallbacks): - def credentials(self, url, username, allowed): + def credentials( + self, + url: str, + username_from_url: str | None, + allowed_types: CredentialType, + ) -> Username | UserPass | Keypair: raise RuntimeError('Unexpected error') url = 'https://github.com/github/github' @@ -767,12 +780,14 @@ def credentials(self, url, username, allowed): assert str(exc.value) == 'Unexpected error' -def test_clone_with_checkout_branch(barerepo, tmp_path): +def test_clone_with_checkout_branch(barerepo: Repository, tmp_path: Path) -> None: # create a test case which isolates the remote test_repo = clone_repository( barerepo.path, tmp_path / 'testrepo-orig.git', bare=True ) - test_repo.create_branch('test', test_repo[test_repo.head.target]) + commit = test_repo[test_repo.head.target] + assert isinstance(commit, Commit) + test_repo.create_branch('test', commit) repo = clone_repository( test_repo.path, tmp_path / 'testrepo.git', checkout_branch='test', bare=True ) @@ -781,7 +796,7 @@ def test_clone_with_checkout_branch(barerepo, tmp_path): @utils.requires_proxy @utils.requires_network -def test_clone_with_proxy(tmp_path): +def test_clone_with_proxy(tmp_path: Path) -> None: url = 'https://github.com/libgit2/TestGitRepository' repo = clone_repository( url, @@ -839,7 +854,7 @@ def test_worktree(testrepo: Repository) -> None: # worktree later worktree_dir.rmdir() - def _check_worktree(worktree): + def _check_worktree(worktree: Worktree) -> None: # Confirm the name attribute matches the specified name assert worktree.name == worktree_name # Confirm the path attribute points to the correct path @@ -918,7 +933,7 @@ def test_worktree_custom_ref(testrepo: Repository) -> None: assert branch_name in testrepo.branches -def test_open_extended(tmp_path): +def test_open_extended(tmp_path: Path) -> None: with utils.TemporaryRepository('dirtyrepo.zip', tmp_path) as path: orig_repo = pygit2.Repository(path) assert not orig_repo.is_bare