diff -pruN 1.17.0-2/.github/workflows/codespell.yml 1.18.2-1/.github/workflows/codespell.yml
--- 1.17.0-2/.github/workflows/codespell.yml	1970-01-01 00:00:00.000000000 +0000
+++ 1.18.2-1/.github/workflows/codespell.yml	2025-08-16 11:34:29.000000000 +0000
@@ -0,0 +1,23 @@
+# Codespell configuration is within pyproject.toml
+---
+name: Codespell
+
+on:
+  pull_request:
+  push:
+
+permissions:
+  contents: read
+
+jobs:
+  codespell:
+    name: Check for spelling errors
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Annotate locations with typos
+        uses: codespell-project/codespell-problem-matcher@v1
+      - name: Codespell
+        uses: codespell-project/actions-codespell@v2
diff -pruN 1.17.0-2/.github/workflows/lint.yml 1.18.2-1/.github/workflows/lint.yml
--- 1.17.0-2/.github/workflows/lint.yml	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/.github/workflows/lint.yml	2025-08-16 11:34:29.000000000 +0000
@@ -17,10 +17,16 @@ jobs:
     - name: Set up Python
       uses: actions/setup-python@v5
       with:
-        python-version: '3.12'
+        python-version: '3.13'
 
     - name: Install ruff
       run: pip install ruff
 
-    - name: Check code style with ruff
+    - name: Format code with ruff
       run: ruff format --diff
+
+    - name: Check code style with ruff
+      run: ruff check
+
+    - name: Check typing with mypy
+      run: LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 /bin/sh build.sh mypy
diff -pruN 1.17.0-2/.github/workflows/tests.yml 1.18.2-1/.github/workflows/tests.yml
--- 1.17.0-2/.github/workflows/tests.yml	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/.github/workflows/tests.yml	2025-08-16 11:34:29.000000000 +0000
@@ -7,7 +7,7 @@ on:
     - '**.rst'
 
 jobs:
-  linux-x86_64:
+  linux:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
@@ -18,6 +18,8 @@ jobs:
           python-version: '3.13'
         - os: ubuntu-24.04
           python-version: 'pypy3.10'
+        - os: ubuntu-24.04-arm
+          python-version: '3.13'
 
     steps:
     - name: Checkout pygit2
@@ -31,24 +33,7 @@ jobs:
     - name: Linux
       run: |
         sudo apt install tinyproxy
-        LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 /bin/sh build.sh test
-
-  linux-arm64:
-    runs-on: ubuntu-24.04
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-
-    - name: Build & test
-      uses: uraimo/run-on-arch-action@v2
-      with:
-        arch: aarch64
-        distro: ubuntu22.04
-        install: |
-          apt-get update -q -y
-          apt-get install -q -y cmake libssl-dev python3-dev python3-venv wget
-        run: |
-          LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 /bin/sh build.sh test
+        LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 /bin/sh build.sh test
 
   linux-s390x:
     runs-on: ubuntu-24.04
@@ -58,7 +43,7 @@ jobs:
       uses: actions/checkout@v4
 
     - name: Build & test
-      uses: uraimo/run-on-arch-action@v2
+      uses: uraimo/run-on-arch-action@v3
       with:
         arch: s390x
         distro: ubuntu22.04
@@ -66,7 +51,7 @@ jobs:
           apt-get update -q -y
           apt-get install -q -y cmake libssl-dev python3-dev python3-venv wget
         run: |
-          LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 /bin/sh build.sh test
+          LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 /bin/sh build.sh test
       continue-on-error: true # Tests are expected to fail, see issue #812
 
   macos-arm64:
@@ -83,4 +68,4 @@ jobs:
     - name: macOS
       run: |
         export OPENSSL_PREFIX=`brew --prefix openssl@3`
-        LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 /bin/sh build.sh test
+        LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 /bin/sh build.sh test
diff -pruN 1.17.0-2/.github/workflows/wheels.yml 1.18.2-1/.github/workflows/wheels.yml
--- 1.17.0-2/.github/workflows/wheels.yml	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/.github/workflows/wheels.yml	2025-08-16 11:34:29.000000000 +0000
@@ -4,18 +4,21 @@ on:
   push:
     branches:
     - master
+    - wheels-*
     tags:
     - 'v*'
 
 jobs:
   build_wheels:
-    name: Build wheels on ${{ matrix.os }}
+    name: Wheels for ${{ matrix.name }}
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
         include:
-        - name: linux
+        - name: linux-amd
           os: ubuntu-24.04
+        - name: linux-arm
+          os: ubuntu-24.04-arm
         - name: macos
           os: macos-13
 
@@ -24,27 +27,51 @@ jobs:
 
       - uses: actions/setup-python@v5
         with:
-          python-version: '3.11'
+          python-version: '3.13'
+
+      - name: Install cibuildwheel
+        run: python -m pip install cibuildwheel==3.1.1
+
+      - name: Build wheels
+        run: python -m cibuildwheel --output-dir wheelhouse
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheels-${{ matrix.name }}
+          path: ./wheelhouse/*.whl
+
+  build_wheels_ppc:
+    name: Wheels for linux-ppc
+    runs-on: ubuntu-24.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: '3.13'
 
       - uses: docker/setup-qemu-action@v3
-        if: runner.os == 'Linux'
         with:
-          platforms: all
+          platforms: linux/ppc64le
 
       - name: Install cibuildwheel
-        run: python -m pip install cibuildwheel==2.22.0
+        run: python -m pip install cibuildwheel==3.1.1
 
       - name: Build wheels
         run: python -m cibuildwheel --output-dir wheelhouse
+        env:
+          CIBW_ARCHS: ppc64le
+          CIBW_ENVIRONMENT: LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 LIBGIT2=/project/ci
 
       - uses: actions/upload-artifact@v4
         with:
-          name: wheels-${{ matrix.name }}
+          name: wheels-linux-ppc
           path: ./wheelhouse/*.whl
 
   pypi:
     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
-    needs: [build_wheels]
+    needs: [build_wheels, build_wheels_ppc]
     runs-on: ubuntu-24.04
 
     steps:
diff -pruN 1.17.0-2/.gitignore 1.18.2-1/.gitignore
--- 1.17.0-2/.gitignore	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/.gitignore	2025-08-16 11:34:29.000000000 +0000
@@ -4,6 +4,7 @@
 /.envrc
 /.tox/
 /build/
+/ci/
 /dist/
 /docs/_build/
 /MANIFEST
@@ -13,3 +14,5 @@ __pycache__/
 *.pyc
 *.so
 *.swp
+/pygit2/_libgit2.c
+/pygit2/_libgit2.o
diff -pruN 1.17.0-2/.mailmap 1.18.2-1/.mailmap
--- 1.17.0-2/.mailmap	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/.mailmap	2025-08-16 11:34:29.000000000 +0000
@@ -3,6 +3,7 @@ Alexander Linne <alexander.linne@outlook
 Anatoly Techtonik <techtonik@gmail.com>
 Bob Carroll <bob.carroll@alum.rit.edu> <bobc@qti.qualcomm.com>
 Brandon Milton <bmilton@fb.com> <brandon.milton21@gmail.com>
+CJ Steiner <47841949+clintonsteiner@users.noreply.github.com>
 Carlos Martín Nieto <cmn@dwim.me> <carlos@cmartin.tk>
 Christian Boos <cboos@edgewall.org> <cboos@bct-technology.com>
 Grégory Herrero <gregory.herrero@oracle.com>
@@ -12,6 +13,8 @@ J. David Ibáñez <jdavid.ibp@gmail.com>
 Jeremy Westwood <jeremy.westwood@hexagon.com>
 Jose Plana <jplana@tuenti.com> <jplana@gmail.com>
 Kaarel Kitsemets <kitsemets@gmail.com>
+Karl Malmros <itkalle@outlook.com> <44969574+ktpa@users.noreply.github.com>
+Konstantin Baikov <konstantin.baikov@gmail.com>
 Lukas Fleischer <lfleischer@lfos.de> <info@cryptocrack.de>
 Martin Lenders <mlenders@elegosoft.com> <authmill@datalove.me>
 Matthew Duggan <mduggan@qti.qualcomm.com> <mgithub@guarana.org>
@@ -19,6 +22,7 @@ Matthew Gamble <git@matthewgamble.net>
 Matthias Bartelmeß <mba@fourplusone.de>
 Mikhail Yushkovskiy <mailto@zensecurity.su>
 Nabijacz Leweli <nabijaczleweli@nabijaczleweli.xyz>
+Nicolas Rybowski <nicolas.rybowski@uclouvain.be>
 Óscar San José <osanjose@tuenti.com>
 Petr Hosek <petrhosek@gmail.com> <p.hosek@imperial.ac.uk>
 Phil Schleihauf <uniphil@gmail.com>
@@ -29,9 +33,10 @@ Sriram Raghu <imbuedhope@gmail.com> <imb
 Sukhman Bhuller <sbhuller@atlassian.com>
 Tamir Bahar <tamir@north-bit.com> <tmr232@github>
 Tamir Bahar <tamir@north-bit.com> <tmr232@users.noreply.github.com>
-Victor Garcia <bravejolie@gmail.com> <victor@tuenti.com>
 Victor Florea <victor@engineeredarts.co.uk>
+Victor Garcia <bravejolie@gmail.com> <victor@tuenti.com>
 Vlad Temian <vladtemian@gmail.com>
+William Schueller <william.schueller@gmail.com> <wschuell@users.noreply.github.com>
 Wim Jeantine-Glenn <hey@wimglenn.com>
 Xavier Delannoy <xavier.delannoy@gmail.com>
 Xu Tao <xutao881001@gmail.com>
diff -pruN 1.17.0-2/AUTHORS.md 1.18.2-1/AUTHORS.md
--- 1.17.0-2/AUTHORS.md	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/AUTHORS.md	2025-08-16 11:34:29.000000000 +0000
@@ -4,6 +4,7 @@ Authors:
     Carlos Martín Nieto
     Nico von Geyso
     Iliyas Jorio
+    Benedikt Seidl
     Sviatoslav Sydorenko
     Matthias Bartelmeß
     Robert Coup
@@ -19,6 +20,7 @@ Authors:
     Nick Hynes
     Richard Möhn
     Xu Tao
+    Konstantin Baikov
     Matthew Duggan
     Matthew Gamble
     Jeremy Westwood
@@ -27,6 +29,7 @@ Authors:
     Sriram Raghu
     Victor Garcia
     Yonggang Luo
+    Łukasz Langa
     Patrick Steinhardt
     Petr Hosek
     Tamir Bahar
@@ -34,6 +37,7 @@ Authors:
     Xavier Delannoy
     Michael Jones
     Saugat Pachhai
+    Andrej730
     Bernardo Heynemann
     John Szakmeister
     Nabijacz Leweli
@@ -43,14 +47,16 @@ Authors:
     Chad Dombrova
     Lukas Fleischer
     Mathias Leppich
+    Mathieu Parent
+    Michał Kępień
     Nicolas Dandrimont
     Raphael Medaer (Escaux)
+    Yaroslav Halchenko
     Anatoly Techtonik
     Andrew Olsen
     Dan Sully
     David Versmisse
     Grégory Herrero
-    Michał Kępień
     Mikhail Yushkovskiy
     Robin Stocker
     Rohit Sanjay
@@ -64,6 +70,7 @@ Authors:
     Assaf Nativ
     Bob Carroll
     Christian Häggström
+    Edmundo Carmona Antoranz
     Erik Johnson
     Filip Rindler
     Fraser Tweedale
@@ -89,6 +96,7 @@ Authors:
     Andrey Devyatkin
     Arno van Lumig
     Ben Davis
+    CJ Steiner
     Colin Watson
     Dan Yeaw
     Dustin Raimondi
@@ -114,7 +122,6 @@ Authors:
     Kyle Gottfried
     Marcel Waldvogel
     Masud Rahman
-    Mathieu Parent
     Michael Sondergaard
     Natanael Arndt
     Ondřej Nový
@@ -127,9 +134,11 @@ Authors:
     nikitalita
     Adam Gausmann
     Adam Spiers
+    Adrien Nader
     Albin Söderström
     Alexandru Fikl
     Andrew Chin
+    Andrew McNulty
     Andrey Trubachev
     András Veres-Szentkirályi
     Ash Berlin
@@ -180,6 +189,7 @@ Authors:
     Josh Bleecher Snyder
     Julia Evans
     Justin Clift
+    Karl Malmros
     Kevin Valk
     Konstantinos Smanis
     Kyriakos Oikonomakos
@@ -194,6 +204,7 @@ Authors:
     Maxwell G
     Michał Górny
     Na'aman Hirschfeld
+    Nicolas Rybowski
     Nicolás Sanguinetti
     Nikita Kartashov
     Nikolai Zujev
@@ -215,6 +226,7 @@ Authors:
     Rui Chen
     Sandro Jäckel
     Saul Pwanson
+    Sebastian Hamann
     Shane Turner
     Sheeo
     Simone Mosciatti
@@ -224,6 +236,7 @@ Authors:
     Timo Röhling
     Victor Florea
     Vladimir Rutsky
+    William Schueller
     Wim Jeantine-Glenn
     Yu Jianjian
     buhl
diff -pruN 1.17.0-2/CHANGELOG.md 1.18.2-1/CHANGELOG.md
--- 1.17.0-2/CHANGELOG.md	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/CHANGELOG.md	2025-08-16 11:34:29.000000000 +0000
@@ -1,3 +1,156 @@
+# 1.18.2 (2025-08-16)
+
+- Add support for almost all global options
+  [#1409](https://github.com/libgit2/pygit2/pull/1409)
+
+- Now it's possible to set `Submodule.url = url`
+  [#1395](https://github.com/libgit2/pygit2/pull/1395)
+
+- New `RemoteCallbacks.push_negotiation(...)`
+  [#1396](https://github.com/libgit2/pygit2/pull/1396)
+
+- New optional boolean argument `connect` in `Remote.ls_remotes(...)`
+  [#1396](https://github.com/libgit2/pygit2/pull/1396)
+
+- New `Remote.list_heads(...)` returns a list of `RemoteHead` objects
+  [#1397](https://github.com/libgit2/pygit2/pull/1397)
+  [#1410](https://github.com/libgit2/pygit2/pull/1410)
+
+- Documentation fixes
+  [#1388](https://github.com/libgit2/pygit2/pull/1388)
+
+- Typing improvements
+  [#1387](https://github.com/libgit2/pygit2/pull/1387)
+  [#1389](https://github.com/libgit2/pygit2/pull/1389)
+  [#1390](https://github.com/libgit2/pygit2/pull/1390)
+  [#1391](https://github.com/libgit2/pygit2/pull/1391)
+  [#1392](https://github.com/libgit2/pygit2/pull/1392)
+  [#1393](https://github.com/libgit2/pygit2/pull/1393)
+  [#1394](https://github.com/libgit2/pygit2/pull/1394)
+  [#1398](https://github.com/libgit2/pygit2/pull/1398)
+  [#1399](https://github.com/libgit2/pygit2/pull/1399)
+  [#1400](https://github.com/libgit2/pygit2/pull/1400)
+  [#1402](https://github.com/libgit2/pygit2/pull/1402)
+  [#1403](https://github.com/libgit2/pygit2/pull/1403)
+  [#1406](https://github.com/libgit2/pygit2/pull/1406)
+  [#1407](https://github.com/libgit2/pygit2/pull/1407)
+  [#1408](https://github.com/libgit2/pygit2/pull/1408)
+
+Deprecations:
+
+- `Remote.ls_remotes(...)` is deprecated, use `Remote.list_heads(...)`:
+
+      # Before
+      for head in remote.ls_remotes():
+          head['name']
+          head['oid']
+          head['loid']  # None when local is False
+          head['local']
+          head['symref_target']
+
+      # Now
+      for head in remote.list_heads():
+          head.name
+          head.oid
+          head.loid  # The zero oid when local is False
+          head.local
+          head.symref_target
+
+
+# 1.18.1 (2025-07-26)
+
+- Update wheels to libgit2 1.9.1 and OpenSSL 3.3
+
+- New `Index.remove_directory(...)`
+  [#1377](https://github.com/libgit2/pygit2/pull/1377)
+
+- New `Index.add_conflict(...)`
+  [#1382](https://github.com/libgit2/pygit2/pull/1382)
+
+- Now `Repository.merge_file_from_index(...)` returns a `MergeFileResult` object when
+  called with `use_deprecated=False`
+  [#1376](https://github.com/libgit2/pygit2/pull/1376)
+
+- Typing improvements
+  [#1369](https://github.com/libgit2/pygit2/pull/1369)
+  [#1370](https://github.com/libgit2/pygit2/pull/1370)
+  [#1371](https://github.com/libgit2/pygit2/pull/1371)
+  [#1373](https://github.com/libgit2/pygit2/pull/1373)
+  [#1384](https://github.com/libgit2/pygit2/pull/1384)
+  [#1386](https://github.com/libgit2/pygit2/pull/1386)
+
+Deprecations:
+
+- Update your code:
+
+      # Before
+      contents = Repository.merge_file_from_index(...)
+
+      # Now
+      result = Repository.merge_file_from_index(..., use_deprecated=False)
+      contents = result.contents
+
+  At some point in the future `use_deprecated=False` will be the default.
+
+
+# 1.18.0 (2025-04-24)
+
+- Upgrade Linux Glibc wheels to `manylinux_2_28`
+
+- Add `RemoteCallbacks.push_transfer_progress(...)` callback
+  [#1345](https://github.com/libgit2/pygit2/pull/1345)
+
+- New `bool(oid)`
+  [#1347](https://github.com/libgit2/pygit2/pull/1347)
+
+- Now `Repository.merge(...)` accepts a commit or reference object
+  [#1348](https://github.com/libgit2/pygit2/pull/1348)
+
+- New `threads` optional argument in `Remote.push(...)`
+  [#1352](https://github.com/libgit2/pygit2/pull/1352)
+
+- New `proxy` optional argument in `clone_repository(...)`
+  [#1354](https://github.com/libgit2/pygit2/pull/1354)
+
+- New optional arguments `context_lines` and `interhunk_lines` in `Blob.diff(...)` ; and
+  now `Repository.diff(...)` honors these two arguments when the objects diffed are blobs.
+  [#1360](https://github.com/libgit2/pygit2/pull/1360)
+
+- Now `Tree.diff_to_workdir(...)` accepts keyword arguments, not just positional.
+
+- Fix when a reference name has non UTF-8 chars
+  [#1329](https://github.com/libgit2/pygit2/pull/1329)
+
+- Fix condition check in `Repository.remotes.rename(...)`
+  [#1342](https://github.com/libgit2/pygit2/pull/1342)
+
+- Add codespell workflow, fix a number of typos
+  [#1344](https://github.com/libgit2/pygit2/pull/1344)
+
+- Documentation and typing
+  [#1343](https://github.com/libgit2/pygit2/pull/1343)
+  [#1347](https://github.com/libgit2/pygit2/pull/1347)
+  [#1356](https://github.com/libgit2/pygit2/pull/1356)
+
+- CI: Use ARM runner for tests and wheels
+  [#1346](https://github.com/libgit2/pygit2/pull/1346)
+
+- Build and CI updates
+  [#1363](https://github.com/libgit2/pygit2/pull/1363)
+  [#1365](https://github.com/libgit2/pygit2/pull/1365)
+
+Deprecations:
+
+- Passing str to `Repository.merge(...)` is deprecated,
+  instead pass an oid object (or a commit, or a reference)
+  [#1349](https://github.com/libgit2/pygit2/pull/1349)
+
+Breaking changes:
+
+- Keyword argument `flag` has been renamed to `flags` in `Blob.diff(...)` and
+  `Blob.diff_to_buffer(...)`
+
+
 # 1.17.0 (2025-01-08)
 
 - Upgrade to libgit2 1.9
@@ -259,7 +412,7 @@ Deprecations:
 -   New `keep_all` and `paths` optional arguments for
     `Repository.stash(...)`
     [#1202](https://github.com/libgit2/pygit2/pull/1202)
--   New `Respository.state()`
+-   New `Repository.state()`
     [#1204](https://github.com/libgit2/pygit2/pull/1204)
 -   Improve `Repository.write_archive(...)` performance
     [#1183](https://github.com/libgit2/pygit2/pull/1183)
@@ -439,7 +592,7 @@ Breaking changes:
 
 Breaking changes:
 
--   Remove deprecated `GIT_CREDTYPE_XXX` contants, use
+-   Remove deprecated `GIT_CREDTYPE_XXX` constants, use
     `GIT_CREDENTIAL_XXX` instead.
 -   Remove deprecated `Patch.patch` getter, use `Patch.text` instead.
 
@@ -536,7 +689,7 @@ Deprecations:
 
 -   Deprecate `Repository.create_remote(...)`, use instead
     `Repository.remotes.create(...)`
--   Deprecate `GIT_CREDTYPE_XXX` contants, use `GIT_CREDENTIAL_XXX`
+-   Deprecate `GIT_CREDTYPE_XXX` constants, use `GIT_CREDENTIAL_XXX`
     instead.
 
 # 1.2.0 (2020-04-05)
@@ -657,7 +810,7 @@ Breaking changes:
 
 Breaking changes:
 
--   Now the Repository has a new attribue `odb` for object database:
+-   Now the Repository has a new attribute `odb` for object database:
 
         # Before
         repository.read(...)
@@ -862,7 +1015,7 @@ Other changes:
     [#610](https://github.com/libgit2/pygit2/issues/610)
 -   Fix tests failing in some cases
     [#795](https://github.com/libgit2/pygit2/issues/795)
--   Automatize wheels upload to pypi
+-   Automate wheels upload to pypi
     [#563](https://github.com/libgit2/pygit2/issues/563)
 
 # 0.27.0 (2018-03-30)
diff -pruN 1.17.0-2/Makefile 1.18.2-1/Makefile
--- 1.17.0-2/Makefile	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/Makefile	2025-08-16 11:34:29.000000000 +0000
@@ -1,7 +1,7 @@
 .PHONY: build html
 
 build:
-	OPENSSL_VERSION=3.2.3 LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 sh build.sh
+	OPENSSL_VERSION=3.3.3 LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 sh build.sh
 
 html: build
 	make -C docs html
diff -pruN 1.17.0-2/appveyor.yml 1.18.2-1/appveyor.yml
--- 1.17.0-2/appveyor.yml	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/appveyor.yml	2025-08-16 11:34:29.000000000 +0000
@@ -1,4 +1,4 @@
-version: 1.17.{build}
+version: 1.18.{build}
 image: Visual Studio 2019
 configuration: Release
 environment:
@@ -35,7 +35,7 @@ build_script:
 # Clone, build and install libgit2
 - cmd: |
     set LIBGIT2=%APPVEYOR_BUILD_FOLDER%\venv
-    git clone --depth=1 -b v1.9.0 https://github.com/libgit2/libgit2.git libgit2
+    git clone --depth=1 -b v1.9.1 https://github.com/libgit2/libgit2.git libgit2
     cd libgit2
     cmake . -DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="%LIBGIT2%" -G "%GENERATOR%"
     cmake --build . --target install
diff -pruN 1.17.0-2/build.sh 1.18.2-1/build.sh
--- 1.17.0-2/build.sh	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/build.sh	2025-08-16 11:34:29.000000000 +0000
@@ -69,6 +69,7 @@ if [ "$CIBUILDWHEEL" = "1" ]; then
             yum install openssl-devel -y
         else
             yum install perl-IPC-Cmd -y
+            yum install perl-Pod-Html -y
         fi
     elif [ -f /sbin/apk ]; then
         apk add wget
@@ -177,7 +178,7 @@ if [ -n "$LIBGIT2_VERSION" ]; then
     wget https://github.com/libgit2/libgit2/archive/refs/tags/v$LIBGIT2_VERSION.tar.gz -N -O $FILENAME.tar.gz
     tar xf $FILENAME.tar.gz
     cd $FILENAME
-    mkdir build -p
+    mkdir -p build
     cd build
     if [ "$KERNEL" = "Darwin" ] && [ "$CIBUILDWHEEL" = "1" ]; then
         CMAKE_PREFIX_PATH=$OPENSSL_PREFIX:$PREFIX cmake .. \
@@ -261,6 +262,16 @@ if [ "$1" = "test" ]; then
     $PREFIX/bin/pytest --cov=pygit2
 fi
 
+# Type checking
+if [ "$1" = "mypy" ]; then
+    shift
+    if [ -n "$WHEELDIR" ]; then
+        $PREFIX/bin/pip install $WHEELDIR/pygit2*-$PYTHON_TAG-*.whl
+    fi
+    $PREFIX/bin/pip install -r requirements-test.txt
+    $PREFIX/bin/mypy pygit2 test
+fi
+
 # Test .pyi stub file
 if [ "$1" = "stubtest" ]; then
     shift
diff -pruN 1.17.0-2/build_tag.py 1.18.2-1/build_tag.py
--- 1.17.0-2/build_tag.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/build_tag.py	2025-08-16 11:34:29.000000000 +0000
@@ -1,4 +1,5 @@
-import platform, sys
+import platform
+import sys
 
 py = {'CPython': 'cp', 'PyPy': 'pp'}[platform.python_implementation()]
 print(f'{py}{sys.version_info.major}{sys.version_info.minor}')
diff -pruN 1.17.0-2/debian/changelog 1.18.2-1/debian/changelog
--- 1.17.0-2/debian/changelog	2025-04-03 15:39:34.000000000 +0000
+++ 1.18.2-1/debian/changelog	2025-08-19 07:55:20.000000000 +0000
@@ -1,10 +1,25 @@
+python-pygit2 (1.18.2-1) unstable; urgency=medium
+
+  * New upstream version 1.18.2
+  * Update patches.
+    Obsolete: 0006-Fix-GenericIterator-class-interface.patch
+
+ -- Timo Röhling <roehling@debian.org>  Tue, 19 Aug 2025 09:55:20 +0200
+
+python-pygit2 (1.18.1-1) unstable; urgency=medium
+
+  * New upstream version 1.18.1
+  * Refresh patches (no functional changes)
+
+ -- Timo Röhling <roehling@debian.org>  Wed, 13 Aug 2025 14:32:28 +0200
+
 python-pygit2 (1.17.0-2) unstable; urgency=medium
 
   * Upload to unstable.
   * Bump Standards-Version to 4.7.2
   * Drop old FSF snail mail address from d/copyright
 
- -- Timo Röhling <roehling@debian.org>  Thu, 03 Apr 2025 17:39:34 +0200
+ -- Timo Röhling <roehling@debian.org>  Wed, 13 Aug 2025 14:32:23 +0200
 
 python-pygit2 (1.17.0-1) experimental; urgency=medium
 
diff -pruN 1.17.0-2/debian/patches/0001-Remove-privacy-breach.patch 1.18.2-1/debian/patches/0001-Remove-privacy-breach.patch
--- 1.17.0-2/debian/patches/0001-Remove-privacy-breach.patch	2025-04-03 15:39:34.000000000 +0000
+++ 1.18.2-1/debian/patches/0001-Remove-privacy-breach.patch	2025-08-19 07:55:20.000000000 +0000
@@ -7,7 +7,7 @@ Subject: Remove privacy breach
  1 file changed, 9 deletions(-)
 
 diff --git a/docs/development.rst b/docs/development.rst
-index 771a708..19c3743 100644
+index b9422bf..8585a2b 100644
 --- a/docs/development.rst
 +++ b/docs/development.rst
 @@ -2,15 +2,6 @@
diff -pruN 1.17.0-2/debian/patches/0002-Don-t-access-internet-during-build.patch 1.18.2-1/debian/patches/0002-Don-t-access-internet-during-build.patch
--- 1.17.0-2/debian/patches/0002-Don-t-access-internet-during-build.patch	2025-04-03 15:39:34.000000000 +0000
+++ 1.18.2-1/debian/patches/0002-Don-t-access-internet-during-build.patch	2025-08-19 07:55:20.000000000 +0000
@@ -7,10 +7,10 @@ Subject: Don't access internet during bu
  1 file changed, 1 insertion(+), 5 deletions(-)
 
 diff --git a/test/utils.py b/test/utils.py
-index 3f1fefc..a91d12c 100644
+index a3b14cf..b27afab 100644
 --- a/test/utils.py
 +++ b/test/utils.py
-@@ -44,11 +44,7 @@ requires_future_libgit2 = pytest.mark.skipif(
+@@ -49,11 +49,7 @@ requires_future_libgit2 = pytest.mark.xfail(
      reason='This test may work with a future version of libgit2',
  )
  
diff -pruN 1.17.0-2/debian/patches/0003-Skip-broken-unit-tests-in-buildd.patch 1.18.2-1/debian/patches/0003-Skip-broken-unit-tests-in-buildd.patch
--- 1.17.0-2/debian/patches/0003-Skip-broken-unit-tests-in-buildd.patch	2025-04-03 15:39:34.000000000 +0000
+++ 1.18.2-1/debian/patches/0003-Skip-broken-unit-tests-in-buildd.patch	2025-08-19 07:55:20.000000000 +0000
@@ -3,50 +3,42 @@ Date: Fri, 1 Dec 2017 19:04:56 +0100
 Subject: Skip broken unit tests in buildd
 
 ---
- test/test_patch.py | 5 +++++
- 1 file changed, 5 insertions(+)
+ test/test_patch.py | 4 ++++
+ 1 file changed, 4 insertions(+)
 
 diff --git a/test/test_patch.py b/test/test_patch.py
-index 5620f9b..eb65ffe 100644
+index 4b74dd5..2acd7ab 100644
 --- a/test/test_patch.py
 +++ b/test/test_patch.py
-@@ -23,6 +23,7 @@
- # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- # Boston, MA 02110-1301, USA.
- 
-+import unittest
- import pygit2
- import pytest
- 
-@@ -91,6 +92,7 @@ def test_patch_create_from_buffers():
+@@ -92,6 +92,7 @@ def test_patch_create_from_buffers() -> None:
      assert patch.text == BLOB_PATCH
  
  
-+@unittest.skip("Doesn't work in buildd")
- def test_patch_create_from_blobs(testrepo):
++@pytest.mark.skip(reason="buildd")
+ def test_patch_create_from_blobs(testrepo: Repository) -> None:
      old_blob = testrepo[BLOB_OLD_SHA]
      new_blob = testrepo[BLOB_NEW_SHA]
-@@ -105,6 +107,7 @@ def test_patch_create_from_blobs(testrepo):
+@@ -108,6 +109,7 @@ def test_patch_create_from_blobs(testrepo: Repository) -> None:
      assert patch.text == BLOB_PATCH2
  
  
-+@unittest.skip("Doesn't work in buildd")
- def test_patch_create_from_blob_buffer(testrepo):
++@pytest.mark.skip(reason="buildd")
+ def test_patch_create_from_blob_buffer(testrepo: Repository) -> None:
      old_blob = testrepo[BLOB_OLD_SHA]
-     patch = pygit2.Patch.create_from(
-@@ -128,6 +131,7 @@ def test_patch_create_from_blob_buffer_add(testrepo):
+     assert isinstance(old_blob, Blob)
+@@ -132,6 +134,7 @@ def test_patch_create_from_blob_buffer_add(testrepo: Repository) -> None:
      assert patch.text == BLOB_PATCH_ADDED
  
  
-+@unittest.skip("Doesn't work in buildd")
- def test_patch_create_from_blob_buffer_delete(testrepo):
++@pytest.mark.skip(reason="buildd")
+ def test_patch_create_from_blob_buffer_delete(testrepo: Repository) -> None:
      old_blob = testrepo[BLOB_OLD_SHA]
- 
-@@ -169,6 +173,7 @@ def test_context_lines(testrepo):
+     assert isinstance(old_blob, Blob)
+@@ -177,6 +180,7 @@ def test_context_lines(testrepo: Repository) -> None:
      assert context_count != 0
  
  
-+@unittest.skip("Doesn't work in buildd")
- def test_no_context_lines(testrepo):
++@pytest.mark.skip(reason="buildd")
+ def test_no_context_lines(testrepo: Repository) -> None:
      old_blob = testrepo[BLOB_OLD_SHA]
      new_blob = testrepo[BLOB_NEW_SHA]
diff -pruN 1.17.0-2/debian/patches/0004-use-python3-sphinx-rtd-theme.patch 1.18.2-1/debian/patches/0004-use-python3-sphinx-rtd-theme.patch
--- 1.17.0-2/debian/patches/0004-use-python3-sphinx-rtd-theme.patch	2025-04-03 15:39:34.000000000 +0000
+++ 1.18.2-1/debian/patches/0004-use-python3-sphinx-rtd-theme.patch	2025-08-19 07:55:20.000000000 +0000
@@ -8,10 +8,10 @@ Forwarded: not-needed
  1 file changed, 1 deletion(-)
 
 diff --git a/docs/conf.py b/docs/conf.py
-index b3c6c1c..771ced4 100644
+index cf79808..8e44478 100644
 --- a/docs/conf.py
 +++ b/docs/conf.py
-@@ -49,7 +49,6 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+@@ -50,7 +50,6 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
  # a list of builtin themes.
  #
  html_theme = 'sphinx_rtd_theme'
diff -pruN 1.17.0-2/debian/patches/0005-Do-not-insert-source-dir-in-sphinx.patch 1.18.2-1/debian/patches/0005-Do-not-insert-source-dir-in-sphinx.patch
--- 1.17.0-2/debian/patches/0005-Do-not-insert-source-dir-in-sphinx.patch	2025-04-03 15:39:34.000000000 +0000
+++ 1.18.2-1/debian/patches/0005-Do-not-insert-source-dir-in-sphinx.patch	2025-08-19 07:55:20.000000000 +0000
@@ -9,10 +9,10 @@ must be built in the binary package dire
  1 file changed, 1 insertion(+), 1 deletion(-)
 
 diff --git a/docs/conf.py b/docs/conf.py
-index 771ced4..1d5dce5 100644
+index 8e44478..86f234d 100644
 --- a/docs/conf.py
 +++ b/docs/conf.py
-@@ -12,7 +12,7 @@ import os, sys
+@@ -13,7 +13,7 @@ import sys
  # add these directories to sys.path here. If the directory is relative to the
  # documentation root, use os.path.abspath to make it absolute, like shown here.
  #
diff -pruN 1.17.0-2/debian/patches/0006-Fix-GenericIterator-class-interface.patch 1.18.2-1/debian/patches/0006-Fix-GenericIterator-class-interface.patch
--- 1.17.0-2/debian/patches/0006-Fix-GenericIterator-class-interface.patch	2025-04-03 15:39:34.000000000 +0000
+++ 1.18.2-1/debian/patches/0006-Fix-GenericIterator-class-interface.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,22 +0,0 @@
-From: =?utf-8?q?Timo_R=C3=B6hling?= <roehling@debian.org>
-Date: Tue, 19 Nov 2024 09:33:52 +0100
-Subject: Fix GenericIterator class interface
-
----
- pygit2/utils.py | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/pygit2/utils.py b/pygit2/utils.py
-index 1f112b2..d3bc51f 100644
---- a/pygit2/utils.py
-+++ b/pygit2/utils.py
-@@ -168,6 +168,9 @@ class GenericIterator:
-     def __iter__(self):
-         return self
- 
-+    def __iter__(self):
-+        return self
-+
-     def __next__(self):
-         idx = self.idx
-         if idx >= self.length:
diff -pruN 1.17.0-2/debian/patches/series 1.18.2-1/debian/patches/series
--- 1.17.0-2/debian/patches/series	2025-04-03 15:39:34.000000000 +0000
+++ 1.18.2-1/debian/patches/series	2025-08-19 07:55:20.000000000 +0000
@@ -3,4 +3,3 @@
 0003-Skip-broken-unit-tests-in-buildd.patch
 0004-use-python3-sphinx-rtd-theme.patch
 0005-Do-not-insert-source-dir-in-sphinx.patch
-0006-Fix-GenericIterator-class-interface.patch
diff -pruN 1.17.0-2/docs/branches.rst 1.18.2-1/docs/branches.rst
--- 1.17.0-2/docs/branches.rst	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/branches.rst	2025-08-16 11:34:29.000000000 +0000
@@ -26,14 +26,14 @@ Example::
     >>> remote_branches = list(repo.branches.remote)
 
     >>> # Get a branch
-    >>> branch = repo.branches['master']
+    >>> master_branch = repo.branches['master']
     >>> other_branch = repo.branches['does-not-exist']  # Will raise a KeyError
     >>> other_branch = repo.branches.get('does-not-exist')  # Returns None
 
     >>> remote_branch = repo.branches.remote['upstream/feature']
 
-    >>> # Create a local branch
-    >>> new_branch = repo.branches.local.create('new-branch')
+    >>> # Create a local branch, branching from master
+    >>> new_branch = repo.branches.local.create('new-branch', repo[master_branch.target])
 
     >>> And delete it
     >>> repo.branches.delete('new-branch')
diff -pruN 1.17.0-2/docs/conf.py 1.18.2-1/docs/conf.py
--- 1.17.0-2/docs/conf.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/conf.py	2025-08-16 11:34:29.000000000 +0000
@@ -4,7 +4,8 @@
 # list see the documentation:
 # http://www.sphinx-doc.org/en/master/config
 
-import os, sys
+import os
+import sys
 
 # -- Path setup --------------------------------------------------------------
 
@@ -22,7 +23,7 @@ copyright = '2010-2025 The pygit2 contri
 # author = ''
 
 # The full version, including alpha/beta/rc tags
-release = '1.17.0'
+release = '1.18.2'
 
 
 # -- General configuration ---------------------------------------------------
diff -pruN 1.17.0-2/docs/development.rst 1.18.2-1/docs/development.rst
--- 1.17.0-2/docs/development.rst	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/development.rst	2025-08-16 11:34:29.000000000 +0000
@@ -82,7 +82,7 @@ Step 3. Build pygit2 with debug symbols:
 Step 4. Install requirements::
 
   $ $PYTHONBIN/python3 setup.py install
-  $ pip insall pytest
+  $ pip install pytest
 
 Step 4. Run valgrind::
 
diff -pruN 1.17.0-2/docs/index_file.rst 1.18.2-1/docs/index_file.rst
--- 1.17.0-2/docs/index_file.rst	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/index_file.rst	2025-08-16 11:34:29.000000000 +0000
@@ -18,9 +18,10 @@ Iterate over all entries of the index::
 
 Index write::
 
-    >>> index.add('path/to/file')          # git add
-    >>> index.remove('path/to/file')       # git rm
-    >>> index.write()                      # don't forget to save the changes
+    >>> index.add('path/to/file')                    # git add
+    >>> index.remove('path/to/file')                 # git rm
+    >>> index.remove_directory('path/to/directory/') # git rm -r
+    >>> index.write()                                # don't forget to save the changes
 
 Custom entries::
    >>> entry = pygit2.IndexEntry('README.md', blob_id, blob_filemode)
diff -pruN 1.17.0-2/docs/install.rst 1.18.2-1/docs/install.rst
--- 1.17.0-2/docs/install.rst	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/install.rst	2025-08-16 11:34:29.000000000 +0000
@@ -60,7 +60,7 @@ Python requirements (these are specified
 Libgit2 **v1.9.x**; binary wheels already include libgit2, so you only need to
 worry about this if you install the source package.
 
-Optional libgit2 dependecies to support ssh and https:
+Optional libgit2 dependencies to support ssh and https:
 
 - https: WinHTTP (Windows), SecureTransport (OS X) or OpenSSL.
 - ssh: libssh2 1.9.0 or later, pkg-config
@@ -83,39 +83,39 @@ The version number of pygit2 is composed
 The table below summarizes the latest pygit2 versions with the supported versions
 of Python and the required libgit2 version.
 
-+-----------+----------------+------------+
-| pygit2    | Python         | libgit2    |
-+-----------+----------------+------------+
-| 1.17      | 3.10 - 3.13    | 1.9        |
-+-----------+----------------+------------+
-| 1.16      | 3.10 - 3.13    | 1.8        |
-+-----------+----------------+------------+
-| 1.15      | 3.9 - 3.12     | 1.8        |
-+-----------+----------------+------------+
-| 1.14      | 3.9 - 3.12     | 1.7        |
-+-----------+----------------+------------+
-| 1.13      | 3.8 - 3.12     | 1.7        |
-+-----------+----------------+------------+
-| 1.12      | 3.8 - 3.11     | 1.6        |
-+-----------+----------------+------------+
-| 1.11      | 3.8 - 3.11     | 1.5        |
-+-----------+----------------+------------+
-| 1.10      | 3.7 - 3.10     | 1.5        |
-+-----------+----------------+------------+
-| 1.9       | 3.7 - 3.10     | 1.4        |
-+-----------+----------------+------------+
-| 1.7 - 1.8 | 3.7 - 3.10     | 1.3        |
-+-----------+----------------+------------+
-| 1.4 - 1.6 | 3.6 - 3.9      | 1.1        |
-+-----------+----------------+------------+
-| 1.2 - 1.3 | 3.6 - 3.8      | 1.0        |
-+-----------+----------------+------------+
-| 1.1       | 3.5 - 3.8      | 0.99 - 1.0 |
-+-----------+----------------+------------+
-| 1.0       | 3.5 - 3.8      | 0.28       |
-+-----------+----------------+------------+
-| 0.28.2    | 2.7, 3.4 - 3.7 | 0.28       |
-+-----------+----------------+------------+
++-------------+----------------+------------+
+| pygit2      | Python         | libgit2    |
++-------------+----------------+------------+
+| 1.17 - 1.18 | 3.10 - 3.13    | 1.9        |
++-------------+----------------+------------+
+| 1.16        | 3.10 - 3.13    | 1.8        |
++-------------+----------------+------------+
+| 1.15        | 3.9 - 3.12     | 1.8        |
++-------------+----------------+------------+
+| 1.14        | 3.9 - 3.12     | 1.7        |
++-------------+----------------+------------+
+| 1.13        | 3.8 - 3.12     | 1.7        |
++-------------+----------------+------------+
+| 1.12        | 3.8 - 3.11     | 1.6        |
++-------------+----------------+------------+
+| 1.11        | 3.8 - 3.11     | 1.5        |
++-------------+----------------+------------+
+| 1.10        | 3.7 - 3.10     | 1.5        |
++-------------+----------------+------------+
+| 1.9         | 3.7 - 3.10     | 1.4        |
++-------------+----------------+------------+
+| 1.7 - 1.8   | 3.7 - 3.10     | 1.3        |
++-------------+----------------+------------+
+| 1.4 - 1.6   | 3.6 - 3.9      | 1.1        |
++-------------+----------------+------------+
+| 1.2 - 1.3   | 3.6 - 3.8      | 1.0        |
++-------------+----------------+------------+
+| 1.1         | 3.5 - 3.8      | 0.99 - 1.0 |
++-------------+----------------+------------+
+| 1.0         | 3.5 - 3.8      | 0.28       |
++-------------+----------------+------------+
+| 0.28.2      | 2.7, 3.4 - 3.7 | 0.28       |
++-------------+----------------+------------+
 
 .. warning::
 
@@ -214,7 +214,7 @@ libgit2 within a virtual environment
 
 This is how to install both libgit2 and pygit2 within a virtual environment.
 
-This is useful if you don't have root acces to install libgit2 system wide.
+This is useful if you don't have root access to install libgit2 system wide.
 Or if you wish to have different versions of libgit2/pygit2 installed in
 different virtual environments, isolated from each other.
 
@@ -265,7 +265,7 @@ So you need to either set ``LD_LIBRARY_P
 Or, like we have done in the instructions above, use the `rpath
 <http://en.wikipedia.org/wiki/Rpath>`_, it hard-codes extra search paths within
 the pygit2 extension modules, so you don't need to set ``LD_LIBRARY_PATH``
-everytime. Verify yourself if curious:
+every time. Verify yourself if curious:
 
 .. code-block:: sh
 
@@ -317,7 +317,7 @@ source package.
 
 The easiest way is to first install libgit2 with the `Homebrew <http://brew.sh>`_
 package manager and then use pip3 for pygit2. The following example assumes that
-XCode and Hombrew are already installed.
+XCode and Homebrew are already installed.
 
 .. code-block:: sh
 
diff -pruN 1.17.0-2/docs/merge.rst 1.18.2-1/docs/merge.rst
--- 1.17.0-2/docs/merge.rst	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/merge.rst	2025-08-16 11:34:29.000000000 +0000
@@ -66,7 +66,7 @@ The following methods perform the calcul
 .. automethod:: pygit2.Repository.merge_base_many
 .. automethod:: pygit2.Repository.merge_base_octopus
 
-With this base at hand one can do repeated invokations of
+With this base at hand one can do repeated invocations of
 :py:meth:`.Repository.merge_commits` and :py:meth:`.Repository.merge_trees`
 to perform the actual merge into one tree (and deal with conflicts along the
 way).
\ No newline at end of file
diff -pruN 1.17.0-2/docs/objects.rst 1.18.2-1/docs/objects.rst
--- 1.17.0-2/docs/objects.rst	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/objects.rst	2025-08-16 11:34:29.000000000 +0000
@@ -16,7 +16,7 @@ Object lookup
 
 In the previous chapter we learnt about Object IDs. With an Oid we can ask the
 repository to get the associated object. To do that the ``Repository`` class
-implementes a subset of the mapping interface.
+implements a subset of the mapping interface.
 
 .. autoclass:: pygit2.Repository
    :noindex:
@@ -33,7 +33,7 @@ implementes a subset of the mapping inte
         >>> repo = Repository('path/to/pygit2')
         >>> obj = repo.get("101715bf37440d32291bde4f58c3142bcf7d8adb")
         >>> obj
-        <_pygit2.Commit object at 0x7ff27a6b60f0>
+        <pygit2.Object{commit:101715bf37440d32291bde4f58c3142bcf7d8adb}>
 
    .. method:: Repository.__getitem__(id)
 
@@ -90,7 +90,7 @@ Blobs
 =================
 
 A blob is just a raw byte string. They are the Git equivalent to files in
-a filesytem.
+a filesystem.
 
 This is their API:
 
@@ -221,7 +221,7 @@ Creating trees
 Commits
 =================
 
-A commit is a snapshot of the working dir with meta informations like author,
+A commit is a snapshot of the working dir with meta information like author,
 committer and others.
 
 .. autoclass:: pygit2.Commit
diff -pruN 1.17.0-2/docs/oid.rst 1.18.2-1/docs/oid.rst
--- 1.17.0-2/docs/oid.rst	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/oid.rst	2025-08-16 11:34:29.000000000 +0000
@@ -56,9 +56,9 @@ The Oid type
      >>> raw = unhexlify("cff3ceaefc955f0dbe1957017db181bc49913781")
      >>> oid2 = Oid(raw=raw)
 
-And the other way around, from an Oid object we can get the hexadecimal and raw
-forms. You can use the built-in `str()` (or `unicode()` in python 2) to get the
-hexadecimal representation of the Oid.
+And the other way around, from an Oid object we can get the raw form via
+`oid.raw`. You can use `str(oid)` to get the hexadecimal representation of the
+Oid.
 
 .. method:: Oid.__str__()
 .. autoattribute:: pygit2.Oid.raw
@@ -68,8 +68,11 @@ The Oid type supports:
 - rich comparisons, not just for equality, also: lesser-than, lesser-or-equal,
   etc.
 
-- hashing, so Oid objects can be used as keys in a dictionary.
+- `hash(oid)`, so Oid objects can be used as keys in a dictionary.
 
+- `bool(oid)`, returning False if the Oid is a null SHA-1 (all zeros).
+
+- `str(oid)`, returning the hexadecimal representation of the Oid.
 
 Constants
 =========
diff -pruN 1.17.0-2/docs/repository.rst 1.18.2-1/docs/repository.rst
--- 1.17.0-2/docs/repository.rst	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/docs/repository.rst	2025-08-16 11:34:29.000000000 +0000
@@ -29,6 +29,7 @@ Functions
      >>> repo_path = '/path/to/create/repository'
      >>> repo = clone_repository(repo_url, repo_path) # Clones a non-bare repository
      >>> repo = clone_repository(repo_url, repo_path, bare=True) # Clones a bare repository
+     >>> repo = clone_repository(repo_url, repo_path, proxy=True) # Enable automatic proxy detection
 
 
 .. autofunction:: pygit2.discover_repository
diff -pruN 1.17.0-2/mypy.ini 1.18.2-1/mypy.ini
--- 1.17.0-2/mypy.ini	1970-01-01 00:00:00.000000000 +0000
+++ 1.18.2-1/mypy.ini	2025-08-16 11:34:29.000000000 +0000
@@ -0,0 +1,12 @@
+[mypy]
+
+warn_unused_configs = True
+warn_redundant_casts = True
+warn_unused_ignores = True
+no_implicit_reexport = True
+disallow_subclassing_any = True
+disallow_untyped_decorators = True
+
+[mypy-test.*]
+disallow_untyped_defs = True
+disallow_untyped_calls = True
diff -pruN 1.17.0-2/pygit2/__init__.py 1.18.2-1/pygit2/__init__.py
--- 1.17.0-2/pygit2/__init__.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/__init__.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,29 +23,344 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+# ruff: noqa: F401 F403 F405
+
 # Standard Library
 import functools
-from os import PathLike
+import os
 import typing
 
-# Low level API
-from ._pygit2 import *
-from ._pygit2 import _cache_enums
-
 # High level API
 from . import enums
 from ._build import __version__
+
+# Low level API
+from ._pygit2 import (
+    GIT_APPLY_LOCATION_BOTH,
+    GIT_APPLY_LOCATION_INDEX,
+    GIT_APPLY_LOCATION_WORKDIR,
+    GIT_BLAME_FIRST_PARENT,
+    GIT_BLAME_IGNORE_WHITESPACE,
+    GIT_BLAME_NORMAL,
+    GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES,
+    GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES,
+    GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES,
+    GIT_BLAME_TRACK_COPIES_SAME_FILE,
+    GIT_BLAME_USE_MAILMAP,
+    GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT,
+    GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD,
+    GIT_BLOB_FILTER_CHECK_FOR_BINARY,
+    GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES,
+    GIT_BRANCH_ALL,
+    GIT_BRANCH_LOCAL,
+    GIT_BRANCH_REMOTE,
+    GIT_CHECKOUT_ALLOW_CONFLICTS,
+    GIT_CHECKOUT_CONFLICT_STYLE_DIFF3,
+    GIT_CHECKOUT_CONFLICT_STYLE_MERGE,
+    GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3,
+    GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH,
+    GIT_CHECKOUT_DONT_OVERWRITE_IGNORED,
+    GIT_CHECKOUT_DONT_REMOVE_EXISTING,
+    GIT_CHECKOUT_DONT_UPDATE_INDEX,
+    GIT_CHECKOUT_DONT_WRITE_INDEX,
+    GIT_CHECKOUT_DRY_RUN,
+    GIT_CHECKOUT_FORCE,
+    GIT_CHECKOUT_NO_REFRESH,
+    GIT_CHECKOUT_NONE,
+    GIT_CHECKOUT_RECREATE_MISSING,
+    GIT_CHECKOUT_REMOVE_IGNORED,
+    GIT_CHECKOUT_REMOVE_UNTRACKED,
+    GIT_CHECKOUT_SAFE,
+    GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES,
+    GIT_CHECKOUT_SKIP_UNMERGED,
+    GIT_CHECKOUT_UPDATE_ONLY,
+    GIT_CHECKOUT_USE_OURS,
+    GIT_CHECKOUT_USE_THEIRS,
+    GIT_CONFIG_HIGHEST_LEVEL,
+    GIT_CONFIG_LEVEL_APP,
+    GIT_CONFIG_LEVEL_GLOBAL,
+    GIT_CONFIG_LEVEL_LOCAL,
+    GIT_CONFIG_LEVEL_PROGRAMDATA,
+    GIT_CONFIG_LEVEL_SYSTEM,
+    GIT_CONFIG_LEVEL_WORKTREE,
+    GIT_CONFIG_LEVEL_XDG,
+    GIT_DELTA_ADDED,
+    GIT_DELTA_CONFLICTED,
+    GIT_DELTA_COPIED,
+    GIT_DELTA_DELETED,
+    GIT_DELTA_IGNORED,
+    GIT_DELTA_MODIFIED,
+    GIT_DELTA_RENAMED,
+    GIT_DELTA_TYPECHANGE,
+    GIT_DELTA_UNMODIFIED,
+    GIT_DELTA_UNREADABLE,
+    GIT_DELTA_UNTRACKED,
+    GIT_DESCRIBE_ALL,
+    GIT_DESCRIBE_DEFAULT,
+    GIT_DESCRIBE_TAGS,
+    GIT_DIFF_BREAK_REWRITES,
+    GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY,
+    GIT_DIFF_DISABLE_PATHSPEC_MATCH,
+    GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS,
+    GIT_DIFF_FIND_ALL,
+    GIT_DIFF_FIND_AND_BREAK_REWRITES,
+    GIT_DIFF_FIND_BY_CONFIG,
+    GIT_DIFF_FIND_COPIES,
+    GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED,
+    GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE,
+    GIT_DIFF_FIND_EXACT_MATCH_ONLY,
+    GIT_DIFF_FIND_FOR_UNTRACKED,
+    GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE,
+    GIT_DIFF_FIND_IGNORE_WHITESPACE,
+    GIT_DIFF_FIND_REMOVE_UNMODIFIED,
+    GIT_DIFF_FIND_RENAMES,
+    GIT_DIFF_FIND_RENAMES_FROM_REWRITES,
+    GIT_DIFF_FIND_REWRITES,
+    GIT_DIFF_FLAG_BINARY,
+    GIT_DIFF_FLAG_EXISTS,
+    GIT_DIFF_FLAG_NOT_BINARY,
+    GIT_DIFF_FLAG_VALID_ID,
+    GIT_DIFF_FLAG_VALID_SIZE,
+    GIT_DIFF_FORCE_BINARY,
+    GIT_DIFF_FORCE_TEXT,
+    GIT_DIFF_IGNORE_BLANK_LINES,
+    GIT_DIFF_IGNORE_CASE,
+    GIT_DIFF_IGNORE_FILEMODE,
+    GIT_DIFF_IGNORE_SUBMODULES,
+    GIT_DIFF_IGNORE_WHITESPACE,
+    GIT_DIFF_IGNORE_WHITESPACE_CHANGE,
+    GIT_DIFF_IGNORE_WHITESPACE_EOL,
+    GIT_DIFF_INCLUDE_CASECHANGE,
+    GIT_DIFF_INCLUDE_IGNORED,
+    GIT_DIFF_INCLUDE_TYPECHANGE,
+    GIT_DIFF_INCLUDE_TYPECHANGE_TREES,
+    GIT_DIFF_INCLUDE_UNMODIFIED,
+    GIT_DIFF_INCLUDE_UNREADABLE,
+    GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED,
+    GIT_DIFF_INCLUDE_UNTRACKED,
+    GIT_DIFF_INDENT_HEURISTIC,
+    GIT_DIFF_MINIMAL,
+    GIT_DIFF_NORMAL,
+    GIT_DIFF_PATIENCE,
+    GIT_DIFF_RECURSE_IGNORED_DIRS,
+    GIT_DIFF_RECURSE_UNTRACKED_DIRS,
+    GIT_DIFF_REVERSE,
+    GIT_DIFF_SHOW_BINARY,
+    GIT_DIFF_SHOW_UNMODIFIED,
+    GIT_DIFF_SHOW_UNTRACKED_CONTENT,
+    GIT_DIFF_SKIP_BINARY_CHECK,
+    GIT_DIFF_STATS_FULL,
+    GIT_DIFF_STATS_INCLUDE_SUMMARY,
+    GIT_DIFF_STATS_NONE,
+    GIT_DIFF_STATS_NUMBER,
+    GIT_DIFF_STATS_SHORT,
+    GIT_DIFF_UPDATE_INDEX,
+    GIT_FILEMODE_BLOB,
+    GIT_FILEMODE_BLOB_EXECUTABLE,
+    GIT_FILEMODE_COMMIT,
+    GIT_FILEMODE_LINK,
+    GIT_FILEMODE_TREE,
+    GIT_FILEMODE_UNREADABLE,
+    GIT_FILTER_ALLOW_UNSAFE,
+    GIT_FILTER_ATTRIBUTES_FROM_COMMIT,
+    GIT_FILTER_ATTRIBUTES_FROM_HEAD,
+    GIT_FILTER_CLEAN,
+    GIT_FILTER_DEFAULT,
+    GIT_FILTER_DRIVER_PRIORITY,
+    GIT_FILTER_NO_SYSTEM_ATTRIBUTES,
+    GIT_FILTER_SMUDGE,
+    GIT_FILTER_TO_ODB,
+    GIT_FILTER_TO_WORKTREE,
+    GIT_MERGE_ANALYSIS_FASTFORWARD,
+    GIT_MERGE_ANALYSIS_NONE,
+    GIT_MERGE_ANALYSIS_NORMAL,
+    GIT_MERGE_ANALYSIS_UNBORN,
+    GIT_MERGE_ANALYSIS_UP_TO_DATE,
+    GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY,
+    GIT_MERGE_PREFERENCE_NO_FASTFORWARD,
+    GIT_MERGE_PREFERENCE_NONE,
+    GIT_OBJECT_ANY,
+    GIT_OBJECT_BLOB,
+    GIT_OBJECT_COMMIT,
+    GIT_OBJECT_INVALID,
+    GIT_OBJECT_OFS_DELTA,
+    GIT_OBJECT_REF_DELTA,
+    GIT_OBJECT_TAG,
+    GIT_OBJECT_TREE,
+    GIT_OID_HEX_ZERO,
+    GIT_OID_HEXSZ,
+    GIT_OID_MINPREFIXLEN,
+    GIT_OID_RAWSZ,
+    GIT_REFERENCES_ALL,
+    GIT_REFERENCES_BRANCHES,
+    GIT_REFERENCES_TAGS,
+    GIT_RESET_HARD,
+    GIT_RESET_MIXED,
+    GIT_RESET_SOFT,
+    GIT_REVSPEC_MERGE_BASE,
+    GIT_REVSPEC_RANGE,
+    GIT_REVSPEC_SINGLE,
+    GIT_SORT_NONE,
+    GIT_SORT_REVERSE,
+    GIT_SORT_TIME,
+    GIT_SORT_TOPOLOGICAL,
+    GIT_STASH_APPLY_DEFAULT,
+    GIT_STASH_APPLY_REINSTATE_INDEX,
+    GIT_STASH_DEFAULT,
+    GIT_STASH_INCLUDE_IGNORED,
+    GIT_STASH_INCLUDE_UNTRACKED,
+    GIT_STASH_KEEP_ALL,
+    GIT_STASH_KEEP_INDEX,
+    GIT_STATUS_CONFLICTED,
+    GIT_STATUS_CURRENT,
+    GIT_STATUS_IGNORED,
+    GIT_STATUS_INDEX_DELETED,
+    GIT_STATUS_INDEX_MODIFIED,
+    GIT_STATUS_INDEX_NEW,
+    GIT_STATUS_INDEX_RENAMED,
+    GIT_STATUS_INDEX_TYPECHANGE,
+    GIT_STATUS_WT_DELETED,
+    GIT_STATUS_WT_MODIFIED,
+    GIT_STATUS_WT_NEW,
+    GIT_STATUS_WT_RENAMED,
+    GIT_STATUS_WT_TYPECHANGE,
+    GIT_STATUS_WT_UNREADABLE,
+    GIT_SUBMODULE_IGNORE_ALL,
+    GIT_SUBMODULE_IGNORE_DIRTY,
+    GIT_SUBMODULE_IGNORE_NONE,
+    GIT_SUBMODULE_IGNORE_UNSPECIFIED,
+    GIT_SUBMODULE_IGNORE_UNTRACKED,
+    GIT_SUBMODULE_STATUS_IN_CONFIG,
+    GIT_SUBMODULE_STATUS_IN_HEAD,
+    GIT_SUBMODULE_STATUS_IN_INDEX,
+    GIT_SUBMODULE_STATUS_IN_WD,
+    GIT_SUBMODULE_STATUS_INDEX_ADDED,
+    GIT_SUBMODULE_STATUS_INDEX_DELETED,
+    GIT_SUBMODULE_STATUS_INDEX_MODIFIED,
+    GIT_SUBMODULE_STATUS_WD_ADDED,
+    GIT_SUBMODULE_STATUS_WD_DELETED,
+    GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED,
+    GIT_SUBMODULE_STATUS_WD_MODIFIED,
+    GIT_SUBMODULE_STATUS_WD_UNINITIALIZED,
+    GIT_SUBMODULE_STATUS_WD_UNTRACKED,
+    GIT_SUBMODULE_STATUS_WD_WD_MODIFIED,
+    LIBGIT2_VER_MAJOR,
+    LIBGIT2_VER_MINOR,
+    LIBGIT2_VER_REVISION,
+    LIBGIT2_VERSION,
+    AlreadyExistsError,
+    Blob,
+    Branch,
+    Commit,
+    Diff,
+    DiffDelta,
+    DiffFile,
+    DiffHunk,
+    DiffLine,
+    DiffStats,
+    FilterSource,
+    GitError,
+    InvalidSpecError,
+    Mailmap,
+    Note,
+    Object,
+    Odb,
+    OdbBackend,
+    OdbBackendLoose,
+    OdbBackendPack,
+    Oid,
+    Patch,
+    Refdb,
+    RefdbBackend,
+    RefdbFsBackend,
+    Reference,
+    RefLogEntry,
+    RevSpec,
+    Signature,
+    Stash,
+    Tag,
+    Tree,
+    TreeBuilder,
+    Walker,
+    Worktree,
+    _cache_enums,
+    discover_repository,
+    filter_register,
+    filter_unregister,
+    hash,
+    hashfile,
+    init_file_backend,
+    reference_is_valid_name,
+    tree_entry_cmp,
+)
 from .blame import Blame, BlameHunk
 from .blob import BlobIO
-from .callbacks import Payload, RemoteCallbacks, CheckoutCallbacks, StashApplyCallbacks
-from .callbacks import git_clone_options, git_fetch_options, get_credentials
+from .callbacks import (
+    CheckoutCallbacks,
+    Payload,
+    RemoteCallbacks,
+    StashApplyCallbacks,
+    get_credentials,
+    git_clone_options,
+    git_fetch_options,
+    git_proxy_options,
+)
 from .config import Config
 from .credentials import *
-from .errors import check_error, Passthrough
-from .ffi import ffi, C
+from .errors import Passthrough, check_error
+from .ffi import C, ffi
 from .filter import Filter
 from .index import Index, IndexEntry
 from .legacyenums import *
+from .options import (
+    GIT_OPT_ADD_SSL_X509_CERT,
+    GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS,
+    GIT_OPT_ENABLE_CACHING,
+    GIT_OPT_ENABLE_FSYNC_GITDIR,
+    GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE,
+    GIT_OPT_ENABLE_OFS_DELTA,
+    GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION,
+    GIT_OPT_ENABLE_STRICT_OBJECT_CREATION,
+    GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION,
+    GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY,
+    GIT_OPT_GET_CACHED_MEMORY,
+    GIT_OPT_GET_EXTENSIONS,
+    GIT_OPT_GET_HOMEDIR,
+    GIT_OPT_GET_MWINDOW_FILE_LIMIT,
+    GIT_OPT_GET_MWINDOW_MAPPED_LIMIT,
+    GIT_OPT_GET_MWINDOW_SIZE,
+    GIT_OPT_GET_OWNER_VALIDATION,
+    GIT_OPT_GET_PACK_MAX_OBJECTS,
+    GIT_OPT_GET_SEARCH_PATH,
+    GIT_OPT_GET_SERVER_CONNECT_TIMEOUT,
+    GIT_OPT_GET_SERVER_TIMEOUT,
+    GIT_OPT_GET_TEMPLATE_PATH,
+    GIT_OPT_GET_USER_AGENT,
+    GIT_OPT_GET_USER_AGENT_PRODUCT,
+    GIT_OPT_GET_WINDOWS_SHAREMODE,
+    GIT_OPT_SET_ALLOCATOR,
+    GIT_OPT_SET_CACHE_MAX_SIZE,
+    GIT_OPT_SET_CACHE_OBJECT_LIMIT,
+    GIT_OPT_SET_EXTENSIONS,
+    GIT_OPT_SET_HOMEDIR,
+    GIT_OPT_SET_MWINDOW_FILE_LIMIT,
+    GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,
+    GIT_OPT_SET_MWINDOW_SIZE,
+    GIT_OPT_SET_ODB_LOOSE_PRIORITY,
+    GIT_OPT_SET_ODB_PACKED_PRIORITY,
+    GIT_OPT_SET_OWNER_VALIDATION,
+    GIT_OPT_SET_PACK_MAX_OBJECTS,
+    GIT_OPT_SET_SEARCH_PATH,
+    GIT_OPT_SET_SERVER_CONNECT_TIMEOUT,
+    GIT_OPT_SET_SERVER_TIMEOUT,
+    GIT_OPT_SET_SSL_CERT_LOCATIONS,
+    GIT_OPT_SET_SSL_CIPHERS,
+    GIT_OPT_SET_TEMPLATE_PATH,
+    GIT_OPT_SET_USER_AGENT,
+    GIT_OPT_SET_USER_AGENT_PRODUCT,
+    GIT_OPT_SET_WINDOWS_SHAREMODE,
+    option,
+)
 from .packbuilder import PackBuilder
 from .remotes import Remote
 from .repository import Repository
@@ -53,7 +368,6 @@ from .settings import Settings
 from .submodules import Submodule
 from .utils import to_bytes, to_str
 
-
 # Features
 features = enums.Feature(C.git_libgit2_features())
 
@@ -66,12 +380,10 @@ _cache_enums()
 
 
 def init_repository(
-    path: typing.Union[str, bytes, PathLike, None],
+    path: str | bytes | os.PathLike[str] | os.PathLike[bytes] | None,
     bare: bool = False,
     flags: enums.RepositoryInitFlag = enums.RepositoryInitFlag.MKPATH,
-    mode: typing.Union[
-        int, enums.RepositoryInitMode
-    ] = enums.RepositoryInitMode.SHARED_UMASK,
+    mode: int | enums.RepositoryInitMode = enums.RepositoryInitMode.SHARED_UMASK,
     workdir_path: typing.Optional[str] = None,
     description: typing.Optional[str] = None,
     template_path: typing.Optional[str] = None,
@@ -86,7 +398,7 @@ def init_repository(
 
     The *flags* may be a combination of enums.RepositoryInitFlag constants:
 
-    - BARE (overriden by the *bare* parameter)
+    - BARE (overridden by the *bare* parameter)
     - NO_REINIT
     - NO_DOTGIT_DIR
     - MKDIR
@@ -99,6 +411,10 @@ def init_repository(
     The *workdir_path*, *description*, *template_path*, *initial_head* and
     *origin_url* are all strings.
 
+    If a repository already exists at *path*, it may be opened successfully but
+    you must not rely on that behavior and should use the Repository
+    constructor directly instead.
+
     See libgit2's documentation on git_repository_init_ext for further details.
     """
     # Pre-process input parameters
@@ -144,15 +460,16 @@ def init_repository(
 
 
 def clone_repository(
-    url,
-    path,
-    bare=False,
-    repository=None,
-    remote=None,
-    checkout_branch=None,
-    callbacks=None,
-    depth=0,
-):
+    url: str | bytes | os.PathLike[str] | os.PathLike[bytes],
+    path: str | bytes | os.PathLike[str] | os.PathLike[bytes],
+    bare: bool = False,
+    repository: typing.Callable | None = None,
+    remote: typing.Callable | None = None,
+    checkout_branch: str | bytes | None = None,
+    callbacks: RemoteCallbacks | None = None,
+    depth: int = 0,
+    proxy: None | bool | str = None,
+) -> Repository:
     """
     Clones a new Git repository from *url* in the given *path*.
 
@@ -160,9 +477,9 @@ def clone_repository(
 
     Parameters:
 
-    url : str
+    url : str or bytes or pathlike object
         URL of the repository to clone.
-    path : str
+    path : str or bytes or pathlike object
         Local path to clone into.
     bare : bool
         Whether the local repository should be bare.
@@ -178,7 +495,7 @@ def clone_repository(
         The repository callback has `(path, bare) -> Repository` as a
         signature. The Repository it returns will be used instead of creating a
         new one.
-    checkout_branch : str
+    checkout_branch : str or bytes
         Branch to checkout after the clone. The default is to use the remote's
         default branch.
     callbacks : RemoteCallbacks
@@ -192,6 +509,12 @@ def clone_repository(
         If greater than 0, creates a shallow clone with a history truncated to
         the specified number of commits.
         The default is 0 (full commit history).
+    proxy : None or True or str
+        Proxy configuration. Can be one of:
+
+        * `None` (the default) to disable proxy usage
+        * `True` to enable automatic proxy detection
+        * an url to a proxy (`http://proxy.example.org:3128/`)
     """
 
     if callbacks is None:
@@ -212,9 +535,10 @@ def clone_repository(
             opts.checkout_branch = checkout_branch_ref
 
         with git_fetch_options(payload, opts=opts.fetch_opts):
-            crepo = ffi.new('git_repository **')
-            err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts)
-            payload.check_error(err)
+            with git_proxy_options(payload, opts.fetch_opts.proxy_opts, proxy):
+                crepo = ffi.new('git_repository **')
+                err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts)
+                payload.check_error(err)
 
     # Ok
     return Repository._from_c(crepo[0], owned=True)
@@ -223,3 +547,437 @@ def clone_repository(
 tree_entry_key = functools.cmp_to_key(tree_entry_cmp)
 
 settings = Settings()
+
+__all__ = (
+    # Standard Library
+    'functools',
+    'os',
+    'typing',
+    # Standard Library symbols
+    'TYPE_CHECKING',
+    'annotations',
+    # Low level API
+    'GIT_OID_HEX_ZERO',
+    'GIT_OID_HEXSZ',
+    'GIT_OID_MINPREFIXLEN',
+    'GIT_OID_RAWSZ',
+    'LIBGIT2_VER_MAJOR',
+    'LIBGIT2_VER_MINOR',
+    'LIBGIT2_VER_REVISION',
+    'LIBGIT2_VERSION',
+    'Object',
+    'Reference',
+    'AlreadyExistsError',
+    'Blob',
+    'Branch',
+    'Commit',
+    'Diff',
+    'DiffDelta',
+    'DiffFile',
+    'DiffHunk',
+    'DiffLine',
+    'DiffStats',
+    'GitError',
+    'InvalidSpecError',
+    'Mailmap',
+    'Note',
+    'Odb',
+    'OdbBackend',
+    'OdbBackendLoose',
+    'OdbBackendPack',
+    'Oid',
+    'Patch',
+    'RefLogEntry',
+    'Refdb',
+    'RefdbBackend',
+    'RefdbFsBackend',
+    'RevSpec',
+    'Signature',
+    'Stash',
+    'Tag',
+    'Tree',
+    'TreeBuilder',
+    'Walker',
+    'Worktree',
+    'discover_repository',
+    'hash',
+    'hashfile',
+    'init_file_backend',
+    'option',
+    'reference_is_valid_name',
+    'tree_entry_cmp',
+    # Low Level API (not present in .pyi)
+    'FilterSource',
+    'filter_register',
+    'filter_unregister',
+    'GIT_APPLY_LOCATION_BOTH',
+    'GIT_APPLY_LOCATION_INDEX',
+    'GIT_APPLY_LOCATION_WORKDIR',
+    'GIT_BLAME_FIRST_PARENT',
+    'GIT_BLAME_IGNORE_WHITESPACE',
+    'GIT_BLAME_NORMAL',
+    'GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES',
+    'GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES',
+    'GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES',
+    'GIT_BLAME_TRACK_COPIES_SAME_FILE',
+    'GIT_BLAME_USE_MAILMAP',
+    'GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT',
+    'GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD',
+    'GIT_BLOB_FILTER_CHECK_FOR_BINARY',
+    'GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES',
+    'GIT_BRANCH_ALL',
+    'GIT_BRANCH_LOCAL',
+    'GIT_BRANCH_REMOTE',
+    'GIT_CHECKOUT_ALLOW_CONFLICTS',
+    'GIT_CHECKOUT_CONFLICT_STYLE_DIFF3',
+    'GIT_CHECKOUT_CONFLICT_STYLE_MERGE',
+    'GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3',
+    'GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH',
+    'GIT_CHECKOUT_DONT_OVERWRITE_IGNORED',
+    'GIT_CHECKOUT_DONT_REMOVE_EXISTING',
+    'GIT_CHECKOUT_DONT_UPDATE_INDEX',
+    'GIT_CHECKOUT_DONT_WRITE_INDEX',
+    'GIT_CHECKOUT_DRY_RUN',
+    'GIT_CHECKOUT_FORCE',
+    'GIT_CHECKOUT_NO_REFRESH',
+    'GIT_CHECKOUT_NONE',
+    'GIT_CHECKOUT_RECREATE_MISSING',
+    'GIT_CHECKOUT_REMOVE_IGNORED',
+    'GIT_CHECKOUT_REMOVE_UNTRACKED',
+    'GIT_CHECKOUT_SAFE',
+    'GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES',
+    'GIT_CHECKOUT_SKIP_UNMERGED',
+    'GIT_CHECKOUT_UPDATE_ONLY',
+    'GIT_CHECKOUT_USE_OURS',
+    'GIT_CHECKOUT_USE_THEIRS',
+    'GIT_CONFIG_HIGHEST_LEVEL',
+    'GIT_CONFIG_LEVEL_APP',
+    'GIT_CONFIG_LEVEL_GLOBAL',
+    'GIT_CONFIG_LEVEL_LOCAL',
+    'GIT_CONFIG_LEVEL_PROGRAMDATA',
+    'GIT_CONFIG_LEVEL_SYSTEM',
+    'GIT_CONFIG_LEVEL_WORKTREE',
+    'GIT_CONFIG_LEVEL_XDG',
+    'GIT_DELTA_ADDED',
+    'GIT_DELTA_CONFLICTED',
+    'GIT_DELTA_COPIED',
+    'GIT_DELTA_DELETED',
+    'GIT_DELTA_IGNORED',
+    'GIT_DELTA_MODIFIED',
+    'GIT_DELTA_RENAMED',
+    'GIT_DELTA_TYPECHANGE',
+    'GIT_DELTA_UNMODIFIED',
+    'GIT_DELTA_UNREADABLE',
+    'GIT_DELTA_UNTRACKED',
+    'GIT_DESCRIBE_ALL',
+    'GIT_DESCRIBE_DEFAULT',
+    'GIT_DESCRIBE_TAGS',
+    'GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY',
+    'GIT_DIFF_BREAK_REWRITES',
+    'GIT_DIFF_DISABLE_PATHSPEC_MATCH',
+    'GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS',
+    'GIT_DIFF_FIND_ALL',
+    'GIT_DIFF_FIND_AND_BREAK_REWRITES',
+    'GIT_DIFF_FIND_BY_CONFIG',
+    'GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED',
+    'GIT_DIFF_FIND_COPIES',
+    'GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE',
+    'GIT_DIFF_FIND_EXACT_MATCH_ONLY',
+    'GIT_DIFF_FIND_FOR_UNTRACKED',
+    'GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE',
+    'GIT_DIFF_FIND_IGNORE_WHITESPACE',
+    'GIT_DIFF_FIND_REMOVE_UNMODIFIED',
+    'GIT_DIFF_FIND_RENAMES_FROM_REWRITES',
+    'GIT_DIFF_FIND_RENAMES',
+    'GIT_DIFF_FIND_REWRITES',
+    'GIT_DIFF_FLAG_BINARY',
+    'GIT_DIFF_FLAG_EXISTS',
+    'GIT_DIFF_FLAG_NOT_BINARY',
+    'GIT_DIFF_FLAG_VALID_ID',
+    'GIT_DIFF_FLAG_VALID_SIZE',
+    'GIT_DIFF_FORCE_BINARY',
+    'GIT_DIFF_FORCE_TEXT',
+    'GIT_DIFF_IGNORE_BLANK_LINES',
+    'GIT_DIFF_IGNORE_CASE',
+    'GIT_DIFF_IGNORE_FILEMODE',
+    'GIT_DIFF_IGNORE_SUBMODULES',
+    'GIT_DIFF_IGNORE_WHITESPACE_CHANGE',
+    'GIT_DIFF_IGNORE_WHITESPACE_EOL',
+    'GIT_DIFF_IGNORE_WHITESPACE',
+    'GIT_DIFF_INCLUDE_CASECHANGE',
+    'GIT_DIFF_INCLUDE_IGNORED',
+    'GIT_DIFF_INCLUDE_TYPECHANGE_TREES',
+    'GIT_DIFF_INCLUDE_TYPECHANGE',
+    'GIT_DIFF_INCLUDE_UNMODIFIED',
+    'GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED',
+    'GIT_DIFF_INCLUDE_UNREADABLE',
+    'GIT_DIFF_INCLUDE_UNTRACKED',
+    'GIT_DIFF_INDENT_HEURISTIC',
+    'GIT_DIFF_MINIMAL',
+    'GIT_DIFF_NORMAL',
+    'GIT_DIFF_PATIENCE',
+    'GIT_DIFF_RECURSE_IGNORED_DIRS',
+    'GIT_DIFF_RECURSE_UNTRACKED_DIRS',
+    'GIT_DIFF_REVERSE',
+    'GIT_DIFF_SHOW_BINARY',
+    'GIT_DIFF_SHOW_UNMODIFIED',
+    'GIT_DIFF_SHOW_UNTRACKED_CONTENT',
+    'GIT_DIFF_SKIP_BINARY_CHECK',
+    'GIT_DIFF_STATS_FULL',
+    'GIT_DIFF_STATS_INCLUDE_SUMMARY',
+    'GIT_DIFF_STATS_NONE',
+    'GIT_DIFF_STATS_NUMBER',
+    'GIT_DIFF_STATS_SHORT',
+    'GIT_DIFF_UPDATE_INDEX',
+    'GIT_FILEMODE_BLOB_EXECUTABLE',
+    'GIT_FILEMODE_BLOB',
+    'GIT_FILEMODE_COMMIT',
+    'GIT_FILEMODE_LINK',
+    'GIT_FILEMODE_TREE',
+    'GIT_FILEMODE_UNREADABLE',
+    'GIT_FILTER_ALLOW_UNSAFE',
+    'GIT_FILTER_ATTRIBUTES_FROM_COMMIT',
+    'GIT_FILTER_ATTRIBUTES_FROM_HEAD',
+    'GIT_FILTER_CLEAN',
+    'GIT_FILTER_DEFAULT',
+    'GIT_FILTER_DRIVER_PRIORITY',
+    'GIT_FILTER_NO_SYSTEM_ATTRIBUTES',
+    'GIT_FILTER_SMUDGE',
+    'GIT_FILTER_TO_ODB',
+    'GIT_FILTER_TO_WORKTREE',
+    'GIT_MERGE_ANALYSIS_FASTFORWARD',
+    'GIT_MERGE_ANALYSIS_NONE',
+    'GIT_MERGE_ANALYSIS_NORMAL',
+    'GIT_MERGE_ANALYSIS_UNBORN',
+    'GIT_MERGE_ANALYSIS_UP_TO_DATE',
+    'GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY',
+    'GIT_MERGE_PREFERENCE_NO_FASTFORWARD',
+    'GIT_MERGE_PREFERENCE_NONE',
+    'GIT_OBJECT_ANY',
+    'GIT_OBJECT_BLOB',
+    'GIT_OBJECT_COMMIT',
+    'GIT_OBJECT_INVALID',
+    'GIT_OBJECT_OFS_DELTA',
+    'GIT_OBJECT_REF_DELTA',
+    'GIT_OBJECT_TAG',
+    'GIT_OBJECT_TREE',
+    'GIT_OPT_ADD_SSL_X509_CERT',
+    'GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS',
+    'GIT_OPT_ENABLE_CACHING',
+    'GIT_OPT_ENABLE_FSYNC_GITDIR',
+    'GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE',
+    'GIT_OPT_ENABLE_OFS_DELTA',
+    'GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION',
+    'GIT_OPT_ENABLE_STRICT_OBJECT_CREATION',
+    'GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION',
+    'GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY',
+    'GIT_OPT_GET_CACHED_MEMORY',
+    'GIT_OPT_GET_EXTENSIONS',
+    'GIT_OPT_GET_HOMEDIR',
+    'GIT_OPT_GET_MWINDOW_FILE_LIMIT',
+    'GIT_OPT_GET_MWINDOW_MAPPED_LIMIT',
+    'GIT_OPT_GET_MWINDOW_SIZE',
+    'GIT_OPT_GET_OWNER_VALIDATION',
+    'GIT_OPT_GET_PACK_MAX_OBJECTS',
+    'GIT_OPT_GET_SEARCH_PATH',
+    'GIT_OPT_GET_SERVER_CONNECT_TIMEOUT',
+    'GIT_OPT_GET_SERVER_TIMEOUT',
+    'GIT_OPT_GET_TEMPLATE_PATH',
+    'GIT_OPT_GET_USER_AGENT',
+    'GIT_OPT_GET_USER_AGENT_PRODUCT',
+    'GIT_OPT_GET_WINDOWS_SHAREMODE',
+    'GIT_OPT_SET_ALLOCATOR',
+    'GIT_OPT_SET_CACHE_MAX_SIZE',
+    'GIT_OPT_SET_CACHE_OBJECT_LIMIT',
+    'GIT_OPT_SET_EXTENSIONS',
+    'GIT_OPT_SET_HOMEDIR',
+    'GIT_OPT_SET_MWINDOW_FILE_LIMIT',
+    'GIT_OPT_SET_MWINDOW_MAPPED_LIMIT',
+    'GIT_OPT_SET_MWINDOW_SIZE',
+    'GIT_OPT_SET_ODB_LOOSE_PRIORITY',
+    'GIT_OPT_SET_ODB_PACKED_PRIORITY',
+    'GIT_OPT_SET_OWNER_VALIDATION',
+    'GIT_OPT_SET_PACK_MAX_OBJECTS',
+    'GIT_OPT_SET_SEARCH_PATH',
+    'GIT_OPT_SET_SERVER_CONNECT_TIMEOUT',
+    'GIT_OPT_SET_SERVER_TIMEOUT',
+    'GIT_OPT_SET_SSL_CERT_LOCATIONS',
+    'GIT_OPT_SET_SSL_CIPHERS',
+    'GIT_OPT_SET_TEMPLATE_PATH',
+    'GIT_OPT_SET_USER_AGENT',
+    'GIT_OPT_SET_USER_AGENT_PRODUCT',
+    'GIT_OPT_SET_WINDOWS_SHAREMODE',
+    'GIT_REFERENCES_ALL',
+    'GIT_REFERENCES_BRANCHES',
+    'GIT_REFERENCES_TAGS',
+    'GIT_RESET_HARD',
+    'GIT_RESET_MIXED',
+    'GIT_RESET_SOFT',
+    'GIT_REVSPEC_MERGE_BASE',
+    'GIT_REVSPEC_RANGE',
+    'GIT_REVSPEC_SINGLE',
+    'GIT_SORT_NONE',
+    'GIT_SORT_REVERSE',
+    'GIT_SORT_TIME',
+    'GIT_SORT_TOPOLOGICAL',
+    'GIT_STASH_APPLY_DEFAULT',
+    'GIT_STASH_APPLY_REINSTATE_INDEX',
+    'GIT_STASH_DEFAULT',
+    'GIT_STASH_INCLUDE_IGNORED',
+    'GIT_STASH_INCLUDE_UNTRACKED',
+    'GIT_STASH_KEEP_ALL',
+    'GIT_STASH_KEEP_INDEX',
+    'GIT_STATUS_CONFLICTED',
+    'GIT_STATUS_CURRENT',
+    'GIT_STATUS_IGNORED',
+    'GIT_STATUS_INDEX_DELETED',
+    'GIT_STATUS_INDEX_MODIFIED',
+    'GIT_STATUS_INDEX_NEW',
+    'GIT_STATUS_INDEX_RENAMED',
+    'GIT_STATUS_INDEX_TYPECHANGE',
+    'GIT_STATUS_WT_DELETED',
+    'GIT_STATUS_WT_MODIFIED',
+    'GIT_STATUS_WT_NEW',
+    'GIT_STATUS_WT_RENAMED',
+    'GIT_STATUS_WT_TYPECHANGE',
+    'GIT_STATUS_WT_UNREADABLE',
+    'GIT_SUBMODULE_IGNORE_ALL',
+    'GIT_SUBMODULE_IGNORE_DIRTY',
+    'GIT_SUBMODULE_IGNORE_NONE',
+    'GIT_SUBMODULE_IGNORE_UNSPECIFIED',
+    'GIT_SUBMODULE_IGNORE_UNTRACKED',
+    'GIT_SUBMODULE_STATUS_IN_CONFIG',
+    'GIT_SUBMODULE_STATUS_IN_HEAD',
+    'GIT_SUBMODULE_STATUS_IN_INDEX',
+    'GIT_SUBMODULE_STATUS_IN_WD',
+    'GIT_SUBMODULE_STATUS_INDEX_ADDED',
+    'GIT_SUBMODULE_STATUS_INDEX_DELETED',
+    'GIT_SUBMODULE_STATUS_INDEX_MODIFIED',
+    'GIT_SUBMODULE_STATUS_WD_ADDED',
+    'GIT_SUBMODULE_STATUS_WD_DELETED',
+    'GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED',
+    'GIT_SUBMODULE_STATUS_WD_MODIFIED',
+    'GIT_SUBMODULE_STATUS_WD_UNINITIALIZED',
+    'GIT_SUBMODULE_STATUS_WD_UNTRACKED',
+    'GIT_SUBMODULE_STATUS_WD_WD_MODIFIED',
+    # High level API.
+    'enums',
+    'blame',
+    'Blame',
+    'BlameHunk',
+    'blob',
+    'BlobIO',
+    'callbacks',
+    'Payload',
+    'RemoteCallbacks',
+    'CheckoutCallbacks',
+    'StashApplyCallbacks',
+    'git_clone_options',
+    'git_fetch_options',
+    'git_proxy_options',
+    'get_credentials',
+    'config',
+    'Config',
+    'credentials',
+    'CredentialType',
+    'Username',
+    'UserPass',
+    'Keypair',
+    'KeypairFromAgent',
+    'KeypairFromMemory',
+    'errors',
+    'check_error',
+    'Passthrough',
+    'ffi',
+    'C',
+    'filter',
+    'Filter',
+    'index',
+    'Index',
+    'IndexEntry',
+    'legacyenums',
+    'GIT_FEATURE_THREADS',
+    'GIT_FEATURE_HTTPS',
+    'GIT_FEATURE_SSH',
+    'GIT_FEATURE_NSEC',
+    'GIT_REPOSITORY_INIT_BARE',
+    'GIT_REPOSITORY_INIT_NO_REINIT',
+    'GIT_REPOSITORY_INIT_NO_DOTGIT_DIR',
+    'GIT_REPOSITORY_INIT_MKDIR',
+    'GIT_REPOSITORY_INIT_MKPATH',
+    'GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE',
+    'GIT_REPOSITORY_INIT_RELATIVE_GITLINK',
+    'GIT_REPOSITORY_INIT_SHARED_UMASK',
+    'GIT_REPOSITORY_INIT_SHARED_GROUP',
+    'GIT_REPOSITORY_INIT_SHARED_ALL',
+    'GIT_REPOSITORY_OPEN_NO_SEARCH',
+    'GIT_REPOSITORY_OPEN_CROSS_FS',
+    'GIT_REPOSITORY_OPEN_BARE',
+    'GIT_REPOSITORY_OPEN_NO_DOTGIT',
+    'GIT_REPOSITORY_OPEN_FROM_ENV',
+    'GIT_REPOSITORY_STATE_NONE',
+    'GIT_REPOSITORY_STATE_MERGE',
+    'GIT_REPOSITORY_STATE_REVERT',
+    'GIT_REPOSITORY_STATE_REVERT_SEQUENCE',
+    'GIT_REPOSITORY_STATE_CHERRYPICK',
+    'GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE',
+    'GIT_REPOSITORY_STATE_BISECT',
+    'GIT_REPOSITORY_STATE_REBASE',
+    'GIT_REPOSITORY_STATE_REBASE_INTERACTIVE',
+    'GIT_REPOSITORY_STATE_REBASE_MERGE',
+    'GIT_REPOSITORY_STATE_APPLY_MAILBOX',
+    'GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE',
+    'GIT_ATTR_CHECK_FILE_THEN_INDEX',
+    'GIT_ATTR_CHECK_INDEX_THEN_FILE',
+    'GIT_ATTR_CHECK_INDEX_ONLY',
+    'GIT_ATTR_CHECK_NO_SYSTEM',
+    'GIT_ATTR_CHECK_INCLUDE_HEAD',
+    'GIT_ATTR_CHECK_INCLUDE_COMMIT',
+    'GIT_FETCH_PRUNE_UNSPECIFIED',
+    'GIT_FETCH_PRUNE',
+    'GIT_FETCH_NO_PRUNE',
+    'GIT_CHECKOUT_NOTIFY_NONE',
+    'GIT_CHECKOUT_NOTIFY_CONFLICT',
+    'GIT_CHECKOUT_NOTIFY_DIRTY',
+    'GIT_CHECKOUT_NOTIFY_UPDATED',
+    'GIT_CHECKOUT_NOTIFY_UNTRACKED',
+    'GIT_CHECKOUT_NOTIFY_IGNORED',
+    'GIT_CHECKOUT_NOTIFY_ALL',
+    'GIT_STASH_APPLY_PROGRESS_NONE',
+    'GIT_STASH_APPLY_PROGRESS_LOADING_STASH',
+    'GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX',
+    'GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED',
+    'GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED',
+    'GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED',
+    'GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED',
+    'GIT_STASH_APPLY_PROGRESS_DONE',
+    'GIT_CREDENTIAL_USERPASS_PLAINTEXT',
+    'GIT_CREDENTIAL_SSH_KEY',
+    'GIT_CREDENTIAL_SSH_CUSTOM',
+    'GIT_CREDENTIAL_DEFAULT',
+    'GIT_CREDENTIAL_SSH_INTERACTIVE',
+    'GIT_CREDENTIAL_USERNAME',
+    'GIT_CREDENTIAL_SSH_MEMORY',
+    'packbuilder',
+    'PackBuilder',
+    'refspec',
+    'remotes',
+    'Remote',
+    'repository',
+    'Repository',
+    'branches',
+    'references',
+    'settings',
+    'Settings',
+    'submodules',
+    'Submodule',
+    'utils',
+    'to_bytes',
+    'to_str',
+    # __init__ module defined symbols
+    'features',
+    'LIBGIT2_VER',
+    'init_repository',
+    'clone_repository',
+    'tree_entry_key',
+)
diff -pruN 1.17.0-2/pygit2/_build.py 1.18.2-1/pygit2/_build.py
--- 1.17.0-2/pygit2/_build.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/_build.py	2025-08-16 11:34:29.000000000 +0000
@@ -34,13 +34,13 @@ from pathlib import Path
 #
 # The version number of pygit2
 #
-__version__ = '1.17.0'
+__version__ = '1.18.2'
 
 
 #
-# Utility functions to get the paths required for bulding extensions
+# Utility functions to get the paths required for building extensions
 #
-def _get_libgit2_path():
+def _get_libgit2_path() -> Path:
     # LIBGIT2 environment variable takes precedence
     libgit2_path = os.getenv('LIBGIT2')
     if libgit2_path is not None:
@@ -52,7 +52,7 @@ def _get_libgit2_path():
     return Path('/usr/local')
 
 
-def get_libgit2_paths():
+def get_libgit2_paths() -> tuple[Path, dict[str, list[str]]]:
     # Base path
     path = _get_libgit2_path()
 
@@ -61,7 +61,7 @@ def get_libgit2_paths():
     if libgit2_lib is None:
         library_dirs = [path / 'lib', path / 'lib64']
     else:
-        library_dirs = [libgit2_lib]
+        library_dirs = [Path(libgit2_lib)]
 
     include_dirs = [path / 'include']
     return (
diff -pruN 1.17.0-2/pygit2/_libgit2/ffi.pyi 1.18.2-1/pygit2/_libgit2/ffi.pyi
--- 1.17.0-2/pygit2/_libgit2/ffi.pyi	1970-01-01 00:00:00.000000000 +0000
+++ 1.18.2-1/pygit2/_libgit2/ffi.pyi	2025-08-16 11:34:29.000000000 +0000
@@ -0,0 +1,368 @@
+# Copyright 2010-2025 The pygit2 contributors
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# In addition to the permissions in the GNU General Public License,
+# the authors give you unlimited permission to link the compiled
+# version of this file into combinations with other programs,
+# and to distribute those combinations without any restriction
+# coming from the use of this file.  (The General Public License
+# restrictions do apply in other respects; for example, they cover
+# modification of the file, and distribution when not linked into
+# a combined executable.)
+#
+# This file is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING.  If not, write to
+# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+from typing import Any, Generic, Literal, NewType, SupportsIndex, TypeVar, overload
+
+T = TypeVar('T')
+
+NULL_TYPE = NewType('NULL_TYPE', object)
+NULL: NULL_TYPE = ...
+
+char = NewType('char', object)
+char_pointer = NewType('char_pointer', object)
+
+class size_t:
+    def __getitem__(self, item: Literal[0]) -> int: ...
+
+class int_c:
+    def __getitem__(self, item: Literal[0]) -> int: ...
+
+class int64_t:
+    def __getitem__(self, item: Literal[0]) -> int: ...
+
+class ssize_t:
+    def __getitem__(self, item: Literal[0]) -> int: ...
+
+class _Pointer(Generic[T]):
+    def __setitem__(self, item: Literal[0], a: T) -> None: ...
+    @overload
+    def __getitem__(self, item: Literal[0]) -> T: ...
+    @overload
+    def __getitem__(self, item: slice[None, None, None]) -> bytes: ...
+
+class _MultiPointer(Generic[T]):
+    def __getitem__(self, item: int) -> T: ...
+
+class ArrayC(Generic[T]):
+    # incomplete!
+    # def _len(self, ?) -> ?: ...
+    def __getitem__(self, index: int) -> T: ...
+    def __setitem__(self, index: int, value: T) -> None: ...
+
+class GitTimeC:
+    # incomplete
+    time: int
+    offset: int
+
+class GitSignatureC:
+    name: char_pointer
+    email: char_pointer
+    when: GitTimeC
+
+class GitHunkC:
+    # incomplete
+    boundary: char
+    final_start_line_number: int
+    final_signature: GitSignatureC
+    orig_signature: GitSignatureC
+    orig_start_line_number: int
+    orig_path: char_pointer
+    lines_in_hunk: int
+
+class GitRepositoryC:
+    # incomplete
+    # TODO: this has to be unified with pygit2._pygit2(pyi).Repository
+    # def _from_c(cls, ptr: 'GitRepositoryC', owned: bool) -> 'Repository': ...
+    pass
+
+class GitFetchOptionsC:
+    # TODO: FetchOptions exist in _pygit2.pyi
+    # incomplete
+    depth: int
+
+class GitSubmoduleC:
+    pass
+
+class GitSubmoduleUpdateOptionsC:
+    fetch_opts: GitFetchOptionsC
+
+class GitRemoteHeadC:
+    local: int
+    oid: GitOidC
+    loid: GitOidC
+    name: char_pointer
+    symref_target: char_pointer
+
+class UnsignedIntC:
+    def __getitem__(self, item: Literal[0]) -> int: ...
+
+class GitOidC:
+    id: _Pointer[bytes]
+
+class GitBlameOptionsC:
+    flags: int
+    min_match_characters: int
+    newest_commit: object
+    oldest_commit: object
+    min_line: int
+    max_line: int
+
+class GitBlameC:
+    # incomplete
+    pass
+
+class GitMergeOptionsC:
+    file_favor: int
+    flags: int
+    file_flags: int
+
+class GitAnnotatedCommitC:
+    pass
+
+class GitAttrOptionsC:
+    # incomplete
+    version: int
+    flags: int
+
+class GitBufC:
+    ptr: char_pointer
+
+class GitCheckoutOptionsC:
+    # incomplete
+    checkout_strategy: int
+
+class GitCommitC:
+    pass
+
+class GitConfigC:
+    # incomplete
+    pass
+
+class GitConfigIteratorC:
+    # incomplete
+    pass
+
+class GitConfigEntryC:
+    # incomplete
+    name: char_pointer
+    value: char_pointer
+    level: int
+
+class GitDescribeFormatOptionsC:
+    version: int
+    abbreviated_size: int
+    always_use_long_format: int
+    dirty_suffix: ArrayC[char]
+
+class GitDescribeOptionsC:
+    version: int
+    max_candidates_tags: int
+    describe_strategy: int
+    pattern: ArrayC[char]
+    only_follow_first_parent: int
+    show_commit_oid_as_fallback: int
+
+class GitDescribeResultC:
+    pass
+
+class GitIndexC:
+    pass
+
+class GitIndexEntryC:
+    # incomplete?
+    mode: int
+    path: ArrayC[char]
+
+class GitMergeFileResultC:
+    pass
+
+class GitObjectC:
+    pass
+
+class GitStashSaveOptionsC:
+    version: int
+    flags: int
+    stasher: GitSignatureC
+    message: ArrayC[char]
+    paths: GitStrrayC
+
+class GitStrrayC:
+    # incomplete?
+    strings: NULL_TYPE | ArrayC[char_pointer]
+    count: int
+
+class GitTreeC:
+    pass
+
+class GitRepositoryInitOptionsC:
+    version: int
+    flags: int
+    mode: int
+    workdir_path: ArrayC[char]
+    description: ArrayC[char]
+    template_path: ArrayC[char]
+    initial_head: ArrayC[char]
+    origin_url: ArrayC[char]
+
+class GitCloneOptionsC:
+    pass
+
+class GitPackbuilderC:
+    pass
+
+class GitProxyTC:
+    pass
+
+class GitProxyOptionsC:
+    version: int
+    type: GitProxyTC
+    url: char_pointer
+    # credentials
+    # certificate_check
+    # payload
+
+class GitRemoteC:
+    pass
+
+class GitReferenceC:
+    pass
+
+def string(a: char_pointer) -> bytes: ...
+@overload
+def new(a: Literal['git_repository **']) -> _Pointer[GitRepositoryC]: ...
+@overload
+def new(a: Literal['git_remote **']) -> _Pointer[GitRemoteC]: ...
+@overload
+def new(a: Literal['git_repository_init_options *']) -> GitRepositoryInitOptionsC: ...
+@overload
+def new(a: Literal['git_submodule_update_options *']) -> GitSubmoduleUpdateOptionsC: ...
+@overload
+def new(a: Literal['git_submodule **']) -> _Pointer[GitSubmoduleC]: ...
+@overload
+def new(a: Literal['unsigned int *']) -> UnsignedIntC: ...
+@overload
+def new(a: Literal['git_proxy_options *']) -> GitProxyOptionsC: ...
+@overload
+def new(a: Literal['git_oid *']) -> GitOidC: ...
+@overload
+def new(a: Literal['git_blame **']) -> _Pointer[GitBlameC]: ...
+@overload
+def new(a: Literal['git_clone_options *']) -> GitCloneOptionsC: ...
+@overload
+def new(a: Literal['git_merge_options *']) -> GitMergeOptionsC: ...
+@overload
+def new(a: Literal['git_blame_options *']) -> GitBlameOptionsC: ...
+@overload
+def new(a: Literal['git_annotated_commit **']) -> _Pointer[GitAnnotatedCommitC]: ...
+@overload
+def new(a: Literal['git_attr_options *']) -> GitAttrOptionsC: ...
+@overload
+def new(a: Literal['git_buf *']) -> GitBufC: ...
+@overload
+def new(a: Literal['char *'], b: bytes) -> char_pointer: ...
+@overload
+def new(a: Literal['char *[]'], b: list[char_pointer]) -> ArrayC[char_pointer]: ...
+@overload
+def new(a: Literal['git_checkout_options *']) -> GitCheckoutOptionsC: ...
+@overload
+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_iterator **']) -> _Pointer[GitConfigIteratorC]: ...
+@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: ...
+@overload
+def new(a: Literal['git_describe_result *']) -> GitDescribeResultC: ...
+@overload
+def new(a: Literal['git_describe_result **']) -> _Pointer[GitDescribeResultC]: ...
+@overload
+def new(a: Literal['struct git_reference **']) -> _Pointer[GitReferenceC]: ...
+@overload
+def new(a: Literal['git_index **']) -> _Pointer[GitIndexC]: ...
+@overload
+def new(a: Literal['git_index_entry *']) -> GitIndexEntryC: ...
+@overload
+def new(a: Literal['git_merge_file_result *']) -> GitMergeFileResultC: ...
+@overload
+def new(a: Literal['git_object *']) -> GitObjectC: ...
+@overload
+def new(a: Literal['git_object **']) -> _Pointer[GitObjectC]: ...
+@overload
+def new(a: Literal['git_packbuilder **']) -> _Pointer[GitPackbuilderC]: ...
+@overload
+def new(a: Literal['git_signature *']) -> GitSignatureC: ...
+@overload
+def new(a: Literal['git_signature **']) -> _Pointer[GitSignatureC]: ...
+@overload
+def new(a: Literal['int *']) -> int_c: ...
+@overload
+def new(a: Literal['int64_t *']) -> int64_t: ...
+@overload
+def new(
+    a: Literal['git_remote_head ***'],
+) -> _Pointer[_MultiPointer[GitRemoteHeadC]]: ...
+@overload
+def new(a: Literal['size_t *', 'size_t*']) -> size_t: ...
+@overload
+def new(a: Literal['ssize_t *', 'ssize_t*']) -> ssize_t: ...
+@overload
+def new(a: Literal['git_stash_save_options *']) -> GitStashSaveOptionsC: ...
+@overload
+def new(a: Literal['git_strarray *']) -> GitStrrayC: ...
+@overload
+def new(a: Literal['git_tree **']) -> _Pointer[GitTreeC]: ...
+@overload
+def new(a: Literal['git_buf *'], b: tuple[NULL_TYPE, Literal[0]]) -> GitBufC: ...
+@overload
+def new(a: Literal['char **']) -> _Pointer[char_pointer]: ...
+@overload
+def new(a: Literal['void **'], b: bytes) -> _Pointer[bytes]: ...
+@overload
+def new(a: Literal['char[]', 'char []'], b: bytes | NULL_TYPE) -> ArrayC[char]: ...
+@overload
+def new(
+    a: Literal['char *[]'], b: int
+) -> ArrayC[char_pointer]: ...  # For ext_array in SET_EXTENSIONS
+@overload
+def new(
+    a: Literal['char *[]'], b: list[Any]
+) -> ArrayC[char_pointer]: ...  # For string arrays
+def addressof(a: object, attribute: str) -> _Pointer[object]: ...
+
+class buffer(bytes):
+    def __init__(self, a: object) -> None: ...
+    def __setitem__(self, item: slice[None, None, None], value: bytes) -> None: ...
+    @overload
+    def __getitem__(self, item: SupportsIndex) -> int: ...
+    @overload
+    def __getitem__(self, item: slice[Any, Any, Any]) -> bytes: ...
+
+@overload
+def cast(a: Literal['int'], b: object) -> int: ...
+@overload
+def cast(a: Literal['unsigned int'], b: object) -> int: ...
+@overload
+def cast(a: Literal['size_t'], b: object) -> int: ...
+@overload
+def cast(a: Literal['ssize_t'], b: object) -> int: ...
+@overload
+def cast(a: Literal['char *'], b: object) -> char_pointer: ...
diff -pruN 1.17.0-2/pygit2/_pygit2.pyi 1.18.2-1/pygit2/_pygit2.pyi
--- 1.17.0-2/pygit2/_pygit2.pyi	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/_pygit2.pyi	2025-08-16 11:34:29.000000000 +0000
@@ -1,8 +1,29 @@
-from typing import Iterator, Literal, Optional, overload
-from io import IOBase
+from collections.abc import Iterator, Sequence
+from io import DEFAULT_BUFFER_SIZE, IOBase
+from pathlib import Path
+from queue import Queue
+from threading import Event
+from typing import (  # noqa: UP035
+    Generic,
+    Literal,
+    Optional,
+    Type,
+    TypedDict,
+    TypeVar,
+    overload,
+)
+
 from . import Index
+from ._libgit2.ffi import (
+    GitCommitC,
+    GitObjectC,
+    GitProxyOptionsC,
+    GitSignatureC,
+    _Pointer,
+)
 from .enums import (
     ApplyLocation,
+    BlobFilter,
     BranchType,
     DeltaStatus,
     DiffFind,
@@ -13,47 +34,263 @@ from .enums import (
     MergeAnalysis,
     MergePreference,
     ObjectType,
-    Option,
     ReferenceFilter,
     ReferenceType,
     ResetMode,
     SortMode,
 )
+from .filter import Filter
+
+GIT_OBJ_BLOB = Literal[3]
+GIT_OBJ_COMMIT = Literal[1]
+GIT_OBJ_TAG = Literal[4]
+GIT_OBJ_TREE = Literal[2]
 
-GIT_OBJ_BLOB: Literal[3]
-GIT_OBJ_COMMIT: Literal[1]
-GIT_OBJ_TAG: Literal[4]
-GIT_OBJ_TREE: Literal[2]
-GIT_OID_HEXSZ: int
-GIT_OID_HEX_ZERO: str
-GIT_OID_MINPREFIXLEN: int
-GIT_OID_RAWSZ: int
-LIBGIT2_VERSION: str
 LIBGIT2_VER_MAJOR: int
 LIBGIT2_VER_MINOR: int
 LIBGIT2_VER_REVISION: int
+LIBGIT2_VERSION: str
+GIT_OID_RAWSZ: int
+GIT_OID_HEXSZ: int
+GIT_OID_HEX_ZERO: str
+GIT_OID_MINPREFIXLEN: int
+GIT_OBJECT_ANY: int
+GIT_OBJECT_INVALID: int
+GIT_OBJECT_COMMIT: int
+GIT_OBJECT_TREE: int
+GIT_OBJECT_BLOB: int
+GIT_OBJECT_TAG: int
+GIT_OBJECT_OFS_DELTA: int
+GIT_OBJECT_REF_DELTA: int
+GIT_FILEMODE_UNREADABLE: int
+GIT_FILEMODE_TREE: int
+GIT_FILEMODE_BLOB: int
+GIT_FILEMODE_BLOB_EXECUTABLE: int
+GIT_FILEMODE_LINK: int
+GIT_FILEMODE_COMMIT: int
+GIT_SORT_NONE: int
+GIT_SORT_TOPOLOGICAL: int
+GIT_SORT_TIME: int
+GIT_SORT_REVERSE: int
+GIT_RESET_SOFT: int
+GIT_RESET_MIXED: int
+GIT_RESET_HARD: int
+GIT_REFERENCES_ALL: int
+GIT_REFERENCES_BRANCHES: int
+GIT_REFERENCES_TAGS: int
+GIT_REVSPEC_SINGLE: int
+GIT_REVSPEC_RANGE: int
+GIT_REVSPEC_MERGE_BASE: int
+GIT_BRANCH_LOCAL: int
+GIT_BRANCH_REMOTE: int
+GIT_BRANCH_ALL: int
+GIT_STATUS_CURRENT: int
+GIT_STATUS_INDEX_NEW: int
+GIT_STATUS_INDEX_MODIFIED: int
+GIT_STATUS_INDEX_DELETED: int
+GIT_STATUS_INDEX_RENAMED: int
+GIT_STATUS_INDEX_TYPECHANGE: int
+GIT_STATUS_WT_NEW: int
+GIT_STATUS_WT_MODIFIED: int
+GIT_STATUS_WT_DELETED: int
+GIT_STATUS_WT_TYPECHANGE: int
+GIT_STATUS_WT_RENAMED: int
+GIT_STATUS_WT_UNREADABLE: int
+GIT_STATUS_IGNORED: int
+GIT_STATUS_CONFLICTED: int
+GIT_CHECKOUT_NONE: int
+GIT_CHECKOUT_SAFE: int
+GIT_CHECKOUT_FORCE: int
+GIT_CHECKOUT_RECREATE_MISSING: int
+GIT_CHECKOUT_ALLOW_CONFLICTS: int
+GIT_CHECKOUT_REMOVE_UNTRACKED: int
+GIT_CHECKOUT_REMOVE_IGNORED: int
+GIT_CHECKOUT_UPDATE_ONLY: int
+GIT_CHECKOUT_DONT_UPDATE_INDEX: int
+GIT_CHECKOUT_NO_REFRESH: int
+GIT_CHECKOUT_SKIP_UNMERGED: int
+GIT_CHECKOUT_USE_OURS: int
+GIT_CHECKOUT_USE_THEIRS: int
+GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH: int
+GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES: int
+GIT_CHECKOUT_DONT_OVERWRITE_IGNORED: int
+GIT_CHECKOUT_CONFLICT_STYLE_MERGE: int
+GIT_CHECKOUT_CONFLICT_STYLE_DIFF3: int
+GIT_CHECKOUT_DONT_REMOVE_EXISTING: int
+GIT_CHECKOUT_DONT_WRITE_INDEX: int
+GIT_CHECKOUT_DRY_RUN: int
+GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3: int
+GIT_DIFF_NORMAL: int
+GIT_DIFF_REVERSE: int
+GIT_DIFF_INCLUDE_IGNORED: int
+GIT_DIFF_RECURSE_IGNORED_DIRS: int
+GIT_DIFF_INCLUDE_UNTRACKED: int
+GIT_DIFF_RECURSE_UNTRACKED_DIRS: int
+GIT_DIFF_INCLUDE_UNMODIFIED: int
+GIT_DIFF_INCLUDE_TYPECHANGE: int
+GIT_DIFF_INCLUDE_TYPECHANGE_TREES: int
+GIT_DIFF_IGNORE_FILEMODE: int
+GIT_DIFF_IGNORE_SUBMODULES: int
+GIT_DIFF_IGNORE_CASE: int
+GIT_DIFF_INCLUDE_CASECHANGE: int
+GIT_DIFF_DISABLE_PATHSPEC_MATCH: int
+GIT_DIFF_SKIP_BINARY_CHECK: int
+GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS: int
+GIT_DIFF_UPDATE_INDEX: int
+GIT_DIFF_INCLUDE_UNREADABLE: int
+GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED: int
+GIT_DIFF_INDENT_HEURISTIC: int
+GIT_DIFF_IGNORE_BLANK_LINES: int
+GIT_DIFF_FORCE_TEXT: int
+GIT_DIFF_FORCE_BINARY: int
+GIT_DIFF_IGNORE_WHITESPACE: int
+GIT_DIFF_IGNORE_WHITESPACE_CHANGE: int
+GIT_DIFF_IGNORE_WHITESPACE_EOL: int
+GIT_DIFF_SHOW_UNTRACKED_CONTENT: int
+GIT_DIFF_SHOW_UNMODIFIED: int
+GIT_DIFF_PATIENCE: int
+GIT_DIFF_MINIMAL: int
+GIT_DIFF_SHOW_BINARY: int
+GIT_DIFF_STATS_NONE: int
+GIT_DIFF_STATS_FULL: int
+GIT_DIFF_STATS_SHORT: int
+GIT_DIFF_STATS_NUMBER: int
+GIT_DIFF_STATS_INCLUDE_SUMMARY: int
+GIT_DIFF_FIND_BY_CONFIG: int
+GIT_DIFF_FIND_RENAMES: int
+GIT_DIFF_FIND_RENAMES_FROM_REWRITES: int
+GIT_DIFF_FIND_COPIES: int
+GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED: int
+GIT_DIFF_FIND_REWRITES: int
+GIT_DIFF_BREAK_REWRITES: int
+GIT_DIFF_FIND_AND_BREAK_REWRITES: int
+GIT_DIFF_FIND_FOR_UNTRACKED: int
+GIT_DIFF_FIND_ALL: int
+GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE: int
+GIT_DIFF_FIND_IGNORE_WHITESPACE: int
+GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE: int
+GIT_DIFF_FIND_EXACT_MATCH_ONLY: int
+GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY: int
+GIT_DIFF_FIND_REMOVE_UNMODIFIED: int
+GIT_DIFF_FLAG_BINARY: int
+GIT_DIFF_FLAG_NOT_BINARY: int
+GIT_DIFF_FLAG_VALID_ID: int
+GIT_DIFF_FLAG_EXISTS: int
+GIT_DIFF_FLAG_VALID_SIZE: int
+GIT_DELTA_UNMODIFIED: int
+GIT_DELTA_ADDED: int
+GIT_DELTA_DELETED: int
+GIT_DELTA_MODIFIED: int
+GIT_DELTA_RENAMED: int
+GIT_DELTA_COPIED: int
+GIT_DELTA_IGNORED: int
+GIT_DELTA_UNTRACKED: int
+GIT_DELTA_TYPECHANGE: int
+GIT_DELTA_UNREADABLE: int
+GIT_DELTA_CONFLICTED: int
+GIT_CONFIG_LEVEL_PROGRAMDATA: int
+GIT_CONFIG_LEVEL_SYSTEM: int
+GIT_CONFIG_LEVEL_XDG: int
+GIT_CONFIG_LEVEL_GLOBAL: int
+GIT_CONFIG_LEVEL_LOCAL: int
+GIT_CONFIG_LEVEL_WORKTREE: int
+GIT_CONFIG_LEVEL_APP: int
+GIT_CONFIG_HIGHEST_LEVEL: int
+GIT_BLAME_NORMAL: int
+GIT_BLAME_TRACK_COPIES_SAME_FILE: int
+GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES: int
+GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES: int
+GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES: int
+GIT_BLAME_FIRST_PARENT: int
+GIT_BLAME_USE_MAILMAP: int
+GIT_BLAME_IGNORE_WHITESPACE: int
+GIT_MERGE_ANALYSIS_NONE: int
+GIT_MERGE_ANALYSIS_NORMAL: int
+GIT_MERGE_ANALYSIS_UP_TO_DATE: int
+GIT_MERGE_ANALYSIS_FASTFORWARD: int
+GIT_MERGE_ANALYSIS_UNBORN: int
+GIT_MERGE_PREFERENCE_NONE: int
+GIT_MERGE_PREFERENCE_NO_FASTFORWARD: int
+GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY: int
+GIT_DESCRIBE_DEFAULT: int
+GIT_DESCRIBE_TAGS: int
+GIT_DESCRIBE_ALL: int
+GIT_STASH_DEFAULT: int
+GIT_STASH_KEEP_INDEX: int
+GIT_STASH_INCLUDE_UNTRACKED: int
+GIT_STASH_INCLUDE_IGNORED: int
+GIT_STASH_KEEP_ALL: int
+GIT_STASH_APPLY_DEFAULT: int
+GIT_STASH_APPLY_REINSTATE_INDEX: int
+GIT_APPLY_LOCATION_WORKDIR: int
+GIT_APPLY_LOCATION_INDEX: int
+GIT_APPLY_LOCATION_BOTH: int
+GIT_SUBMODULE_IGNORE_UNSPECIFIED: int
+GIT_SUBMODULE_IGNORE_NONE: int
+GIT_SUBMODULE_IGNORE_UNTRACKED: int
+GIT_SUBMODULE_IGNORE_DIRTY: int
+GIT_SUBMODULE_IGNORE_ALL: int
+GIT_SUBMODULE_STATUS_IN_HEAD: int
+GIT_SUBMODULE_STATUS_IN_INDEX: int
+GIT_SUBMODULE_STATUS_IN_CONFIG: int
+GIT_SUBMODULE_STATUS_IN_WD: int
+GIT_SUBMODULE_STATUS_INDEX_ADDED: int
+GIT_SUBMODULE_STATUS_INDEX_DELETED: int
+GIT_SUBMODULE_STATUS_INDEX_MODIFIED: int
+GIT_SUBMODULE_STATUS_WD_UNINITIALIZED: int
+GIT_SUBMODULE_STATUS_WD_ADDED: int
+GIT_SUBMODULE_STATUS_WD_DELETED: int
+GIT_SUBMODULE_STATUS_WD_MODIFIED: int
+GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED: int
+GIT_SUBMODULE_STATUS_WD_WD_MODIFIED: int
+GIT_SUBMODULE_STATUS_WD_UNTRACKED: int
+GIT_BLOB_FILTER_CHECK_FOR_BINARY: int
+GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES: int
+GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD: int
+GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT: int
+GIT_FILTER_DRIVER_PRIORITY: int
+GIT_FILTER_TO_WORKTREE: int
+GIT_FILTER_SMUDGE: int
+GIT_FILTER_TO_ODB: int
+GIT_FILTER_CLEAN: int
+GIT_FILTER_DEFAULT: int
+GIT_FILTER_ALLOW_UNSAFE: int
+GIT_FILTER_NO_SYSTEM_ATTRIBUTES: int
+GIT_FILTER_ATTRIBUTES_FROM_HEAD: int
+GIT_FILTER_ATTRIBUTES_FROM_COMMIT: int
+
+T = TypeVar('T')
 
-class Object:
-    _pointer: bytes
+class _ObjectBase(Generic[T]):
+    _pointer: _Pointer[T]
     filemode: FileMode
-    hex: str
     id: Oid
     name: str | None
-    oid: Oid
     raw_name: bytes | None
     short_id: str
     type: 'Literal[GIT_OBJ_COMMIT] | Literal[GIT_OBJ_TREE] | Literal[GIT_OBJ_TAG] | Literal[GIT_OBJ_BLOB]'
     type_str: "Literal['commit'] | Literal['tree'] | Literal['tag'] | Literal['blob']"
+    author: Signature
+    committer: Signature
+    tree: Tree
     @overload
-    def peel(self, target_type: 'Literal[GIT_OBJ_COMMIT]') -> 'Commit': ...
+    def peel(
+        self, target_type: 'Literal[GIT_OBJ_COMMIT, ObjectType.COMMIT] | Type[Commit]'
+    ) -> 'Commit': ...
     @overload
-    def peel(self, target_type: 'Literal[GIT_OBJ_TREE]') -> 'Tree': ...
+    def peel(
+        self, target_type: 'Literal[GIT_OBJ_TREE, ObjectType.TREE] | Type[Tree]'
+    ) -> 'Tree': ...
     @overload
-    def peel(self, target_type: 'Literal[GIT_OBJ_TAG]') -> 'Tag': ...
+    def peel(
+        self, target_type: 'Literal[GIT_OBJ_TAG, ObjectType.TAG] | Type[Tag]'
+    ) -> 'Tag': ...
     @overload
-    def peel(self, target_type: 'Literal[GIT_OBJ_BLOB]') -> 'Blob': ...
+    def peel(
+        self, target_type: 'Literal[GIT_OBJ_BLOB, ObjectType.BLOB] | Type[Blob]'
+    ) -> 'Blob': ...
     @overload
-    def peel(self, target_type: 'None') -> 'Commit|Tree|Blob': ...
+    def peel(self, target_type: 'None') -> 'Commit|Tree|Tag|Blob': ...
     def read_raw(self) -> bytes: ...
     def __eq__(self, other) -> bool: ...
     def __ge__(self, other) -> bool: ...
@@ -63,6 +300,9 @@ class Object:
     def __lt__(self, other) -> bool: ...
     def __ne__(self, other) -> bool: ...
 
+class Object(_ObjectBase[GitObjectC]):
+    pass
+
 class Reference:
     name: str
     raw_name: bytes
@@ -75,15 +315,15 @@ class Reference:
     def delete(self) -> None: ...
     def log(self) -> Iterator[RefLogEntry]: ...
     @overload
-    def peel(self, type: 'Literal[GIT_OBJ_COMMIT]') -> 'Commit': ...
+    def peel(self, type: 'Literal[GIT_OBJ_COMMIT] | Type[Commit]') -> 'Commit': ...
     @overload
-    def peel(self, type: 'Literal[GIT_OBJ_TREE]') -> 'Tree': ...
+    def peel(self, type: 'Literal[GIT_OBJ_TREE] | Type[Tree]') -> 'Tree': ...
     @overload
-    def peel(self, type: 'Literal[GIT_OBJ_TAG]') -> 'Tag': ...
+    def peel(self, type: 'Literal[GIT_OBJ_TAG] | Type[Tag]') -> 'Tag': ...
     @overload
-    def peel(self, type: 'Literal[GIT_OBJ_BLOB]') -> 'Blob': ...
+    def peel(self, type: 'Literal[GIT_OBJ_BLOB] | Type[Blob]') -> 'Blob': ...
     @overload
-    def peel(self, type: 'None') -> 'Commit|Tree|Blob': ...
+    def peel(self, type: 'None' = None) -> 'Commit|Tree|Tag|Blob': ...
     def rename(self, new_name: str) -> None: ...
     def resolve(self) -> Reference: ...
     def set_target(self, target: _OidArg, message: str = ...) -> None: ...
@@ -109,11 +349,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[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
@@ -124,9 +376,28 @@ class Branch(Reference):
     def delete(self) -> None: ...
     def is_checked_out(self) -> bool: ...
     def is_head(self) -> bool: ...
-    def rename(self, name: str, force: bool = False) -> None: ...
+    def rename(self, name: str, force: bool = False) -> 'Branch': ...  # type: ignore[override]
 
-class Commit(Object):
+class FetchOptions:
+    # incomplete
+    depth: int
+    proxy_opts: GitProxyOptionsC
+
+class CloneOptions:
+    # incomplete
+    version: int
+    checkout_opts: object
+    fetch_opts: FetchOptions
+    bare: int
+    local: object
+    checkout_branch: object
+    repository_cb: object
+    repository_cb_payload: object
+    remote_cb: object
+    remote_cb_payload: object
+
+class Commit(_ObjectBase[GitCommitC]):
+    _pointer: _Pointer[GitCommitC]
     author: Signature
     commit_time: int
     commit_time_offset: int
@@ -146,6 +417,7 @@ class Diff:
     patch: str | None
     patchid: Oid
     stats: DiffStats
+    text: str
     def find_similar(
         self,
         flags: DiffFind = DiffFind.FIND_BY_CONFIG,
@@ -207,6 +479,11 @@ class DiffStats:
     insertions: int
     def format(self, format: DiffStatsFormat, width: int) -> str: ...
 
+class FilterSource:
+    # probably incomplete
+    repo: object
+    pass
+
 class GitError(Exception): ...
 class InvalidSpecError(ValueError): ...
 
@@ -214,9 +491,9 @@ class Mailmap:
     def __init__(self, *args) -> None: ...
     def add_entry(
         self,
-        real_name: str = ...,
-        real_email: str = ...,
-        replace_name: str = ...,
+        real_name: str | None = ...,
+        real_email: str | None = ...,
+        replace_name: str | None = ...,
         replace_email: str = ...,
     ) -> None: ...
     @staticmethod
@@ -230,6 +507,7 @@ class Note:
     annotated_id: Oid
     id: Oid
     message: str
+    data: bytes
     def remove(
         self, author: Signature, committer: Signature, ref: str = 'refs/notes/commits'
     ) -> None: ...
@@ -238,10 +516,10 @@ class Odb:
     backends: Iterator[OdbBackend]
     def __init__(self, *args, **kwargs) -> None: ...
     def add_backend(self, backend: OdbBackend, priority: int) -> None: ...
-    def add_disk_alternate(self, path: str) -> None: ...
+    def add_disk_alternate(self, path: str | Path) -> None: ...
     def exists(self, oid: _OidArg) -> bool: ...
-    def read(self, oid: _OidArg) -> tuple[int, int, bytes]: ...
-    def write(self, type: int, data: bytes) -> Oid: ...
+    def read(self, oid: _OidArg) -> tuple[int, bytes]: ...
+    def write(self, type: int, data: bytes | str) -> Oid: ...
     def __contains__(self, other: _OidArg) -> bool: ...
     def __iter__(self) -> Iterator[Oid]: ...  # Odb_as_iter
 
@@ -262,7 +540,6 @@ class OdbBackendPack(OdbBackend):
     def __init__(self, *args, **kwargs) -> None: ...
 
 class Oid:
-    hex: str
     raw: bytes
     def __init__(self, raw: bytes = ..., hex: str = ...) -> None: ...
     def __eq__(self, other) -> bool: ...
@@ -272,6 +549,7 @@ class Oid:
     def __le__(self, other) -> bool: ...
     def __lt__(self, other) -> bool: ...
     def __ne__(self, other) -> bool: ...
+    def __bool__(self) -> bool: ...
 
 class Patch:
     data: bytes
@@ -310,7 +588,9 @@ class Refdb:
 class RefdbBackend:
     def __init__(self, *args, **kwargs) -> None: ...
     def compress(self) -> None: ...
-    def delete(self, ref_name: str, old_id: _OidArg, old_target: str) -> None: ...
+    def delete(
+        self, ref_name: str, old_id: _OidArg, old_target: str | None
+    ) -> None: ...
     def ensure_log(self, ref_name: str) -> bool: ...
     def exists(self, refname: str) -> bool: ...
     def has_log(self, ref_name: str) -> bool: ...
@@ -324,31 +604,42 @@ class RefdbBackend:
         force: bool,
         who: Signature,
         message: str,
-        old: _OidArg,
-        old_target: str,
+        old: None | _OidArg,
+        old_target: None | str,
     ) -> None: ...
+    def __iter__(self) -> Iterator[Reference]: ...
 
 class RefdbFsBackend(RefdbBackend):
     def __init__(self, *args, **kwargs) -> None: ...
 
+_Proxy = None | Literal[True] | str
+
+class _StrArray:
+    # incomplete
+    count: int
+
+class PushOptions:
+    version: int
+    pb_parallelism: int
+    callbacks: object  # TODO
+    proxy_opts: GitProxyOptionsC
+    follow_redirects: object  # TODO
+    custom_headers: _StrArray
+    remote_push_options: _StrArray
+
+class _LsRemotesDict(TypedDict):
+    local: bool
+    loid: Oid | None
+    name: str | None
+    symref_target: str | None
+    oid: Oid
+
 class Repository:
-    _pointer: bytes
-    default_signature: Signature
-    head: Reference
-    head_is_detached: bool
-    head_is_unborn: bool
-    is_bare: bool
-    is_empty: bool
-    is_shallow: bool
-    odb: Odb
-    path: str
-    refdb: Refdb
-    workdir: str
-    def __init__(self, *args, **kwargs) -> None: ...
     def TreeBuilder(self, src: Tree | _OidArg = ...) -> TreeBuilder: ...
     def _disown(self, *args, **kwargs) -> None: ...
-    def _from_c(self, *args, **kwargs) -> None: ...
-    def add_worktree(self, name: str, path: str, ref: Reference = ...) -> Worktree: ...
+    def add_worktree(
+        self, name: str, path: str | Path, ref: Reference = ...
+    ) -> Worktree: ...
     def applies(
         self,
         diff: Diff,
@@ -360,10 +651,10 @@ class Repository:
     ) -> None: ...
     def cherrypick(self, id: _OidArg) -> None: ...
     def compress_references(self) -> None: ...
-    def create_blob(self, data: bytes) -> Oid: ...
+    def create_blob(self, data: str | 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,
@@ -372,7 +663,7 @@ class Repository:
         committer: Signature,
         message: str | bytes,
         tree: _OidArg,
-        parents: list[_OidArg],
+        parents: Sequence[_OidArg],
         encoding: str = ...,
     ) -> Oid: ...
     def create_commit_string(
@@ -415,7 +706,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'
@@ -438,7 +729,7 @@ class Repository:
     def references_iterator_init(self) -> Iterator[Reference]: ...
     def references_iterator_next(
         self,
-        iter: Iterator,
+        iter: Iterator[T],
         references_return_type: ReferenceFilter = ReferenceFilter.ALL,
     ) -> Reference: ...
     def reset(self, oid: _OidArg, reset_type: ResetMode) -> None: ...
@@ -462,7 +753,7 @@ class RevSpec:
 
 class Signature:
     _encoding: str | None
-    _pointer: bytes
+    _pointer: _Pointer[GitSignatureC]
     email: str
     name: str
     offset: int
@@ -471,7 +762,7 @@ class Signature:
     time: int
     def __init__(
         self,
-        name: str,
+        name: str | bytes,
         email: str,
         time: int = -1,
         offset: int = 0,
@@ -527,11 +818,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: ...
@@ -557,13 +848,15 @@ 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) -> Oid: ...
+def hash(data: bytes | str) -> Oid: ...
 def hashfile(path: str) -> Oid: ...
 def init_file_backend(path: str, flags: int = 0) -> object: ...
-def option(opt: Option, *args) -> None: ...
 def reference_is_valid_name(refname: str) -> bool: ...
 def tree_entry_cmp(a: Object, b: Object) -> int: ...
+def _cache_enums() -> None: ...
+def filter_register(name: str, filter: type[Filter]) -> None: ...
+def filter_unregister(name: str) -> None: ...
 
 _OidArg = str | Oid
diff -pruN 1.17.0-2/pygit2/_run.py 1.18.2-1/pygit2/_run.py
--- 1.17.0-2/pygit2/_run.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/_run.py	2025-08-16 11:34:29.000000000 +0000
@@ -29,15 +29,15 @@ This is an special module, it provides s
 
 # Import from the Standard Library
 import codecs
-from pathlib import Path
 import sys
+from pathlib import Path
 
 # Import from cffi
 from cffi import FFI
 
 # Import from pygit2
 try:
-    from _build import get_libgit2_paths
+    from _build import get_libgit2_paths  # type: ignore
 except ImportError:
     from ._build import get_libgit2_paths
 
@@ -81,11 +81,12 @@ h_files = [
     'revert.h',
     'stash.h',
     'submodule.h',
+    'options.h',
     'callbacks.h',  # Bridge from libgit2 to Python
 ]
 h_source = []
 for h_file in h_files:
-    h_file = dir_path / 'decl' / h_file
+    h_file = dir_path / 'decl' / h_file  # type: ignore
     with codecs.open(h_file, 'r', 'utf-8') as f:
         h_source.append(f.read())
 
diff -pruN 1.17.0-2/pygit2/blame.py 1.18.2-1/pygit2/blame.py
--- 1.17.0-2/pygit2/blame.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/blame.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,13 +23,20 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from collections.abc import Iterator
+from typing import TYPE_CHECKING
+
+from ._pygit2 import Oid, Repository, Signature
+
 # Import from pygit2
-from .ffi import ffi, C
+from .ffi import C, ffi
 from .utils import GenericIterator
-from ._pygit2 import Signature, Oid
+
+if TYPE_CHECKING:
+    from ._libgit2.ffi import GitBlameC, GitHunkC, GitSignatureC
 
 
-def wrap_signature(csig):
+def wrap_signature(csig: 'GitSignatureC') -> None | Signature:
     if not csig:
         return None
 
@@ -43,58 +50,61 @@ def wrap_signature(csig):
 
 
 class BlameHunk:
+    _blame: 'Blame'
+    _hunk: 'GitHunkC'
+
     @classmethod
-    def _from_c(cls, blame, ptr):
+    def _from_c(cls, blame: 'Blame', ptr: 'GitHunkC') -> 'BlameHunk':
         hunk = cls.__new__(cls)
         hunk._blame = blame
         hunk._hunk = ptr
         return hunk
 
     @property
-    def lines_in_hunk(self):
+    def lines_in_hunk(self) -> int:
         """Number of lines"""
         return self._hunk.lines_in_hunk
 
     @property
-    def boundary(self):
+    def boundary(self) -> bool:
         """Tracked to a boundary commit"""
         # Casting directly to bool via cffi does not seem to work
         return int(ffi.cast('int', self._hunk.boundary)) != 0
 
     @property
-    def final_start_line_number(self):
+    def final_start_line_number(self) -> int:
         """Final start line number"""
         return self._hunk.final_start_line_number
 
     @property
-    def final_committer(self):
+    def final_committer(self) -> None | Signature:
         """Final committer"""
         return wrap_signature(self._hunk.final_signature)
 
     @property
-    def final_commit_id(self):
+    def final_commit_id(self) -> Oid:
         return Oid(
             raw=bytes(ffi.buffer(ffi.addressof(self._hunk, 'final_commit_id'))[:])
         )
 
     @property
-    def orig_start_line_number(self):
+    def orig_start_line_number(self) -> int:
         """Origin start line number"""
         return self._hunk.orig_start_line_number
 
     @property
-    def orig_committer(self):
+    def orig_committer(self) -> None | Signature:
         """Original committer"""
         return wrap_signature(self._hunk.orig_signature)
 
     @property
-    def orig_commit_id(self):
+    def orig_commit_id(self) -> Oid:
         return Oid(
             raw=bytes(ffi.buffer(ffi.addressof(self._hunk, 'orig_commit_id'))[:])
         )
 
     @property
-    def orig_path(self):
+    def orig_path(self) -> None | str:
         """Original path"""
         path = self._hunk.orig_path
         if not path:
@@ -104,27 +114,30 @@ class BlameHunk:
 
 
 class Blame:
+    _repo: Repository
+    _blame: 'GitBlameC'
+
     @classmethod
-    def _from_c(cls, repo, ptr):
+    def _from_c(cls, repo: Repository, ptr: 'GitBlameC') -> 'Blame':
         blame = cls.__new__(cls)
         blame._repo = repo
         blame._blame = ptr
         return blame
 
-    def __del__(self):
+    def __del__(self) -> None:
         C.git_blame_free(self._blame)
 
-    def __len__(self):
+    def __len__(self) -> int:
         return C.git_blame_get_hunk_count(self._blame)
 
-    def __getitem__(self, index):
+    def __getitem__(self, index: int) -> BlameHunk:
         chunk = C.git_blame_get_hunk_byindex(self._blame, index)
         if not chunk:
             raise IndexError
 
         return BlameHunk._from_c(self, chunk)
 
-    def for_line(self, line_no):
+    def for_line(self, line_no: int) -> BlameHunk:
         """
         Returns the <BlameHunk> object for a given line given its number in the
         current Blame.
@@ -143,5 +156,5 @@ class Blame:
 
         return BlameHunk._from_c(self, chunk)
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[BlameHunk]:
         return GenericIterator(self)
diff -pruN 1.17.0-2/pygit2/blob.py 1.18.2-1/pygit2/blob.py
--- 1.17.0-2/pygit2/blob.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/blob.py	2025-08-16 11:34:29.000000000 +0000
@@ -2,8 +2,8 @@ import io
 import threading
 import time
 from contextlib import AbstractContextManager
-from typing import Optional
 from queue import Queue
+from typing import Optional
 
 from ._pygit2 import Blob, Oid
 from .enums import BlobFilter
@@ -26,7 +26,7 @@ class _BlobIO(io.RawIOBase):
     ):
         super().__init__()
         self._blob = blob
-        self._queue = Queue(maxsize=1)
+        self._queue: Optional[Queue] = Queue(maxsize=1)
         self._ready = threading.Event()
         self._writer_closed = threading.Event()
         self._chunk: Optional[bytes] = None
@@ -45,7 +45,7 @@ class _BlobIO(io.RawIOBase):
     def __exit__(self, exc_type, exc_value, traceback):
         self.close()
 
-    def isatty():
+    def isatty(self):
         return False
 
     def readable(self):
@@ -84,7 +84,7 @@ class _BlobIO(io.RawIOBase):
         except KeyboardInterrupt:
             return 0
 
-    def close(self):
+    def close(self) -> None:
         try:
             self._ready.wait()
             self._writer_closed.wait()
diff -pruN 1.17.0-2/pygit2/branches.py 1.18.2-1/pygit2/branches.py
--- 1.17.0-2/pygit2/branches.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/branches.py	2025-08-16 11:34:29.000000000 +0000
@@ -24,10 +24,12 @@
 # Boston, MA 02110-1301, USA.
 
 from __future__ import annotations
+
+from collections.abc import Iterator
 from typing import TYPE_CHECKING
 
+from ._pygit2 import Branch, Commit, Oid
 from .enums import BranchType, ReferenceType
-from ._pygit2 import Commit, Oid
 
 # Need BaseRepository for type hints, but don't let it cause a circular dependency
 if TYPE_CHECKING:
@@ -35,9 +37,15 @@ if TYPE_CHECKING:
 
 
 class Branches:
+    local: 'Branches'
+    remote: 'Branches'
+
     def __init__(
-        self, repository: BaseRepository, flag: BranchType = BranchType.ALL, commit=None
-    ):
+        self,
+        repository: BaseRepository,
+        flag: BranchType = BranchType.ALL,
+        commit: Commit | Oid | str | None = None,
+    ) -> None:
         self._repository = repository
         self._flag = flag
         if commit is not None:
@@ -51,7 +59,7 @@ class Branches:
             self.local = Branches(repository, flag=BranchType.LOCAL, commit=commit)
             self.remote = Branches(repository, flag=BranchType.REMOTE, commit=commit)
 
-    def __getitem__(self, name: str):
+    def __getitem__(self, name: str) -> Branch:
         branch = None
         if self._flag & BranchType.LOCAL:
             branch = self._repository.lookup_branch(name, BranchType.LOCAL)
@@ -64,36 +72,38 @@ class Branches:
 
         return branch
 
-    def get(self, key: str):
+    def get(self, key: str) -> Branch:
         try:
             return self[key]
         except KeyError:
-            return None
+            return None  # type:ignore #  next commit
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[str]:
         for branch_name in self._repository.listall_branches(self._flag):
             if self._commit is None or self.get(branch_name) is not None:
                 yield branch_name
 
-    def create(self, name: str, commit, force=False):
+    def create(self, name: str, commit: Commit, force: bool = False) -> Branch:
         return self._repository.create_branch(name, commit, force)
 
-    def delete(self, name: str):
+    def delete(self, name: str) -> None:
         self[name].delete()
 
-    def _valid(self, branch):
+    def _valid(self, branch: Branch) -> bool:
         if branch.type == ReferenceType.SYMBOLIC:
-            branch = branch.resolve()
+            branch_direct = branch.resolve()
+        else:
+            branch_direct = branch
 
         return (
             self._commit is None
-            or branch.target == self._commit
-            or self._repository.descendant_of(branch.target, self._commit)
+            or branch_direct.target == self._commit
+            or self._repository.descendant_of(branch_direct.target, self._commit)
         )
 
-    def with_commit(self, commit):
+    def with_commit(self, commit: Commit | Oid | str | None) -> 'Branches':
         assert self._commit is None
         return Branches(self._repository, self._flag, commit)
 
-    def __contains__(self, name):
+    def __contains__(self, name: str) -> bool:
         return self.get(name) is not None
diff -pruN 1.17.0-2/pygit2/callbacks.py 1.18.2-1/pygit2/callbacks.py
--- 1.17.0-2/pygit2/callbacks.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/callbacks.py	2025-08-16 11:34:29.000000000 +0000
@@ -63,18 +63,26 @@ API.
 """
 
 # Standard Library
+from collections.abc import Callable, Generator
 from contextlib import contextmanager
 from functools import wraps
-from typing import Optional, Union
+from typing import TYPE_CHECKING, Optional, ParamSpec, TypeVar
 
 # pygit2
-from ._pygit2 import Oid, DiffFile
+from ._pygit2 import DiffFile, Oid
+from .credentials import Keypair, Username, UserPass
 from .enums import CheckoutNotify, CheckoutStrategy, CredentialType, StashApplyProgress
-from .errors import check_error, Passthrough
-from .ffi import ffi, C
-from .utils import maybe_string, to_bytes, ptr_to_bytes, StrArray
+from .errors import Passthrough, check_error
+from .ffi import C, ffi
+from .utils import StrArray, maybe_string, ptr_to_bytes, to_bytes
 
+_Credentials = Username | UserPass | Keypair
 
+if TYPE_CHECKING:
+    from pygit2._libgit2.ffi import GitProxyOptionsC
+
+    from ._pygit2 import CloneOptions, PushOptions
+    from .remotes import PushUpdate, TransferProgress
 #
 # The payload is the way to pass information from the pygit2 API, through
 # libgit2, to the Python callbacks. And back.
@@ -82,12 +90,16 @@ from .utils import maybe_string, to_byte
 
 
 class Payload:
-    def __init__(self, **kw):
+    repository: Callable | None
+    remote: Callable | None
+    clone_options: 'CloneOptions'
+
+    def __init__(self, **kw: object) -> None:
         for key, value in kw.items():
             setattr(self, key, value)
         self._stored_exception = None
 
-    def check_error(self, error_code):
+    def check_error(self, error_code: int) -> None:
         if error_code == C.GIT_EUSER:
             assert self._stored_exception is not None
             raise self._stored_exception
@@ -113,14 +125,20 @@ class RemoteCallbacks(Payload):
     RemoteCallbacks(certificate=certificate).
     """
 
-    def __init__(self, credentials=None, certificate_check=None):
+    push_options: 'PushOptions'
+
+    def __init__(
+        self,
+        credentials: _Credentials | None = None,
+        certificate_check: Callable[[None, bool, bytes], bool] | None = None,
+    ) -> None:
         super().__init__()
         if credentials is not None:
-            self.credentials = credentials
+            self.credentials = credentials  # type: ignore[method-assign, assignment]
         if certificate_check is not None:
-            self.certificate_check = certificate_check
+            self.certificate_check = certificate_check  # type: ignore[method-assign, assignment]
 
-    def sideband_progress(self, string):
+    def sideband_progress(self, string: str) -> None:
         """
         Progress output callback.  Override this function with your own
         progress reporting function
@@ -134,9 +152,9 @@ class RemoteCallbacks(Payload):
     def credentials(
         self,
         url: str,
-        username_from_url: Union[str, None],
+        username_from_url: str | None,
         allowed_types: CredentialType,
-    ):
+    ) -> _Credentials:
         """
         Credentials callback.  If the remote server requires authentication,
         this function will be called and its return value used for
@@ -159,7 +177,7 @@ class RemoteCallbacks(Payload):
         """
         raise Passthrough
 
-    def certificate_check(self, certificate, valid, host):
+    def certificate_check(self, certificate: None, valid: bool, host: bytes) -> bool:
         """
         Certificate callback. Override with your own function to determine
         whether to accept the server's certificate.
@@ -181,10 +199,21 @@ class RemoteCallbacks(Payload):
 
         raise Passthrough
 
-    def transfer_progress(self, stats):
+    def push_negotiation(self, updates: list['PushUpdate']) -> None:
+        """
+        During a push, called once between the negotiation step and the upload.
+        Provides information about what updates will be performed.
+
+        Override with your own function to check the pending updates
+        and possibly reject them (by raising an exception).
+        """
+
+    def transfer_progress(self, stats: 'TransferProgress') -> None:
         """
-        Transfer progress callback. Override with your own function to report
-        transfer progress.
+        During the download of new data, this will be regularly called with
+        the indexer's progress.
+
+        Override with your own function to report transfer progress.
 
         Parameters:
 
@@ -192,7 +221,20 @@ class RemoteCallbacks(Payload):
             The progress up to now.
         """
 
-    def update_tips(self, refname, old, new):
+    def push_transfer_progress(
+        self, objects_pushed: int, total_objects: int, bytes_pushed: int
+    ) -> None:
+        """
+        During the upload portion of a push, this will be regularly called
+        with progress information.
+
+        Be aware that this is called inline with pack building operations,
+        so performance may be affected.
+
+        Override with your own function to report push transfer progress.
+        """
+
+    def update_tips(self, refname: str, old: Oid, new: Oid) -> None:
         """
         Update tips callback. Override with your own function to report
         reference updates.
@@ -209,7 +251,7 @@ class RemoteCallbacks(Payload):
             The reference's new value.
         """
 
-    def push_update_reference(self, refname, message):
+    def push_update_reference(self, refname: str, message: str) -> None:
         """
         Push update reference callback. Override with your own function to
         report the remote's acceptance or rejection of reference updates.
@@ -229,7 +271,7 @@ class CheckoutCallbacks(Payload):
     in your class, which you can then pass to checkout operations.
     """
 
-    def __init__(self):
+    def __init__(self) -> None:
         super().__init__()
 
     def checkout_notify_flags(self) -> CheckoutNotify:
@@ -260,7 +302,7 @@ class CheckoutCallbacks(Payload):
         baseline: Optional[DiffFile],
         target: Optional[DiffFile],
         workdir: Optional[DiffFile],
-    ):
+    ) -> None:
         """
         Checkout will invoke an optional notification callback for
         certain cases - you pick which ones via `checkout_notify_flags`.
@@ -275,7 +317,9 @@ class CheckoutCallbacks(Payload):
         """
         pass
 
-    def checkout_progress(self, path: str, completed_steps: int, total_steps: int):
+    def checkout_progress(
+        self, path: str, completed_steps: int, total_steps: int
+    ) -> None:
         """
         Optional callback to notify the consumer of checkout progress.
         """
@@ -289,7 +333,7 @@ class StashApplyCallbacks(CheckoutCallba
     in your class, which you can then pass to stash apply or pop operations.
     """
 
-    def stash_apply_progress(self, progress: StashApplyProgress):
+    def stash_apply_progress(self, progress: StashApplyProgress) -> None:
         """
         Stash application progress notification function.
 
@@ -356,6 +400,29 @@ def git_fetch_options(payload, opts=None
 
 
 @contextmanager
+def git_proxy_options(
+    payload: object,
+    opts: Optional['GitProxyOptionsC'] = None,
+    proxy: None | bool | str = None,
+) -> Generator['GitProxyOptionsC', None, None]:
+    if opts is None:
+        opts = ffi.new('git_proxy_options *')
+        C.git_proxy_options_init(opts, C.GIT_PROXY_OPTIONS_VERSION)
+    if proxy is None:
+        opts.type = C.GIT_PROXY_NONE
+    elif proxy is True:
+        opts.type = C.GIT_PROXY_AUTO
+    elif type(proxy) is str:
+        opts.type = C.GIT_PROXY_SPECIFIED
+        # Keep url in memory, otherwise memory is freed and bad things happen
+        payload.__proxy_url = ffi.new('char[]', to_bytes(proxy))  # type: ignore[attr-defined]
+        opts.url = payload.__proxy_url  # type: ignore[attr-defined]
+    else:
+        raise TypeError('Proxy must be None, True, or a string')
+    yield opts
+
+
+@contextmanager
 def git_push_options(payload, opts=None):
     if payload is None:
         payload = RemoteCallbacks()
@@ -370,6 +437,14 @@ def git_push_options(payload, opts=None)
     opts.callbacks.credentials = C._credentials_cb
     opts.callbacks.certificate_check = C._certificate_check_cb
     opts.callbacks.push_update_reference = C._push_update_reference_cb
+    opts.callbacks.push_negotiation = C._push_negotiation_cb
+    # Per libgit2 sources, push_transfer_progress may incur a performance hit.
+    # So, set it only if the user has overridden the no-op stub.
+    if (
+        type(payload).push_transfer_progress
+        is not RemoteCallbacks.push_transfer_progress
+    ):
+        opts.callbacks.push_transfer_progress = C._push_transfer_progress_cb
     # Payload
     handle = ffi.new_handle(payload)
     opts.callbacks.payload = handle
@@ -405,18 +480,21 @@ def git_remote_callbacks(payload):
 #
 # C callbacks
 #
-# These functions are called by libgit2. They cannot raise execptions, since
+# These functions are called by libgit2. They cannot raise exceptions, since
 # they return to libgit2, they can only send back error codes.
 #
-# They cannot be overriden, but sometimes the only thing these functions do is
+# They cannot be overridden, but sometimes the only thing these functions do is
 # to proxy the call to a user defined function. If user defined functions
 # raises an exception, the callback must store it somewhere and return
 # GIT_EUSER to libgit2, then the outer Python code will be able to reraise the
 # exception.
 #
 
+P = ParamSpec('P')
+T = TypeVar('T')
+
 
-def libgit2_callback(f):
+def libgit2_callback(f: Callable[P, T]) -> Callable[P, T]:
     @wraps(f)
     def wrapper(*args):
         data = ffi.from_handle(args[-1])
@@ -434,10 +512,10 @@ def libgit2_callback(f):
             data._stored_exception = e
             return C.GIT_EUSER
 
-    return ffi.def_extern()(wrapper)
+    return ffi.def_extern()(wrapper)  # type: ignore[attr-defined]
 
 
-def libgit2_callback_void(f):
+def libgit2_callback_void(f: Callable[P, T]) -> Callable[P, T]:
     @wraps(f)
     def wrapper(*args):
         data = ffi.from_handle(args[-1])
@@ -454,7 +532,7 @@ def libgit2_callback_void(f):
             data._stored_exception = e
             pass  # Function returns void, so we can't do much here.
 
-    return ffi.def_extern()(wrapper)
+    return ffi.def_extern()(wrapper)  # type: ignore[attr-defined]
 
 
 @libgit2_callback
@@ -496,6 +574,19 @@ def _credentials_cb(cred_out, url, usern
 
 
 @libgit2_callback
+def _push_negotiation_cb(updates, num_updates, data):
+    from .remotes import PushUpdate
+
+    push_negotiation = getattr(data, 'push_negotiation', None)
+    if not push_negotiation:
+        return 0
+
+    py_updates = [PushUpdate(updates[i]) for i in range(num_updates)]
+    push_negotiation(py_updates)
+    return 0
+
+
+@libgit2_callback
 def _push_update_reference_cb(ref, msg, data):
     push_update_reference = getattr(data, 'push_update_reference', None)
     if not push_update_reference:
@@ -555,6 +646,16 @@ def _transfer_progress_cb(stats_ptr, dat
 
 
 @libgit2_callback
+def _push_transfer_progress_cb(current, total, bytes_pushed, payload):
+    push_transfer_progress = getattr(payload, 'push_transfer_progress', None)
+    if not push_transfer_progress:
+        return 0
+
+    push_transfer_progress(current, total, bytes_pushed)
+    return 0
+
+
+@libgit2_callback
 def _update_tips_cb(refname, a, b, data):
     update_tips = getattr(data, 'update_tips', None)
     if not update_tips:
@@ -644,7 +745,7 @@ def _checkout_notify_cb(
     pyworkdir = DiffFile.from_c(ptr_to_bytes(workdir))
 
     try:
-        data.checkout_notify(why, pypath, pybaseline, pytarget, pyworkdir)
+        data.checkout_notify(why, pypath, pybaseline, pytarget, pyworkdir)  # type: ignore[arg-type]
     except Passthrough:
         # Unlike most other operations with optional callbacks, checkout
         # doesn't support the GIT_PASSTHROUGH return code, so we must bypass
@@ -659,7 +760,7 @@ def _checkout_notify_cb(
 
 @libgit2_callback_void
 def _checkout_progress_cb(path, completed_steps, total_steps, data: CheckoutCallbacks):
-    data.checkout_progress(maybe_string(path), completed_steps, total_steps)
+    data.checkout_progress(maybe_string(path), completed_steps, total_steps)  # type: ignore[arg-type]
 
 
 def _git_checkout_options(
diff -pruN 1.17.0-2/pygit2/config.py 1.18.2-1/pygit2/config.py
--- 1.17.0-2/pygit2/config.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/config.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,18 +23,27 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from collections.abc import Callable, Iterator
+from os import PathLike
+from pathlib import Path
+from typing import TYPE_CHECKING
+
 try:
     from functools import cached_property
 except ImportError:
-    from cached_property import cached_property
+    from cached_property import cached_property  # type: ignore
 
 # Import from pygit2
 from .errors import check_error
-from .ffi import ffi, C
+from .ffi import C, ffi
 from .utils import to_bytes
 
+if TYPE_CHECKING:
+    from ._libgit2.ffi import GitConfigC, GitConfigEntryC
+    from .repository import BaseRepository
+
 
-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')
 
@@ -42,20 +51,20 @@ def str_to_bytes(value, name):
 
 
 class ConfigIterator:
-    def __init__(self, config, ptr):
+    def __init__(self, config, ptr) -> None:
         self._iter = ptr
         self._config = config
 
-    def __del__(self):
+    def __del__(self) -> None:
         C.git_config_iterator_free(self._iter)
 
-    def __iter__(self):
+    def __iter__(self) -> 'ConfigIterator':
         return self
 
-    def __next__(self):
+    def __next__(self) -> 'ConfigEntry':
         return self._next_entry()
 
-    def _next_entry(self):
+    def _next_entry(self) -> 'ConfigEntry':
         centry = ffi.new('git_config_entry **')
         err = C.git_config_next(centry, self._iter)
         check_error(err)
@@ -64,7 +73,7 @@ class ConfigIterator:
 
 
 class ConfigMultivarIterator(ConfigIterator):
-    def __next__(self):
+    def __next__(self) -> str:  # type: ignore[override]
         entry = self._next_entry()
         return entry.value
 
@@ -72,33 +81,36 @@ class ConfigMultivarIterator(ConfigItera
 class Config:
     """Git configuration management."""
 
-    def __init__(self, path=None):
+    _repo: 'BaseRepository'
+    _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: 'BaseRepository', 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[int, 'ConfigEntry']:
         key = str_to_bytes(key, 'key')
 
         entry = ffi.new('git_config_entry **')
@@ -106,7 +118,7 @@ class Config:
 
         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:
@@ -115,7 +127,7 @@ class Config:
         check_error(err)
         return entry
 
-    def __contains__(self, key):
+    def __contains__(self, key: str | bytes) -> bool:
         err, cstr = self._get(key)
 
         if err == C.GIT_ENOTFOUND:
@@ -125,7 +137,7 @@ class Config:
 
         return True
 
-    def __getitem__(self, key):
+    def __getitem__(self, key: str | bytes) -> str:
         """
         When using the mapping interface, the value is returned as a string. In
         order to apply the git-config parsing rules, you can use
@@ -135,7 +147,7 @@ class Config:
 
         return entry.value
 
-    def __setitem__(self, key, value):
+    def __setitem__(self, key: str | bytes, value: bool | int | str | bytes) -> None:
         key = str_to_bytes(key, 'key')
 
         err = 0
@@ -148,13 +160,13 @@ class Config:
 
         check_error(err)
 
-    def __delitem__(self, key):
+    def __delitem__(self, key: str | bytes) -> None:
         key = str_to_bytes(key, 'key')
 
         err = C.git_config_delete_entry(self._config, key)
         check_error(err)
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator['ConfigEntry']:
         """
         Iterate over configuration entries, returning a ``ConfigEntry``
         objects. These contain the name, level, and value of each configuration
@@ -167,22 +179,26 @@ class Config:
 
         return ConfigIterator(self, citer[0])
 
-    def get_multivar(self, name, regex=None):
+    def get_multivar(
+        self, name: str | bytes, regex: str | None = None
+    ) -> ConfigMultivarIterator:
         """Get each value of a multivar ''name'' as a list of strings.
 
         The optional ''regex'' parameter is expected to be a regular expression
         to filter the variables we're interested in.
         """
         name = str_to_bytes(name, 'name')
-        regex = to_bytes(regex or None)
+        regex_bytes = to_bytes(regex or None)
 
         citer = ffi.new('git_config_iterator **')
-        err = C.git_config_multivar_iterator_new(citer, self._config, name, regex)
+        err = C.git_config_multivar_iterator_new(citer, self._config, name, regex_bytes)
         check_error(err)
 
         return ConfigMultivarIterator(self, citer[0])
 
-    def set_multivar(self, name, regex, value):
+    def set_multivar(
+        self, name: str | bytes, regex: str | bytes, value: str | bytes
+    ) -> None:
         """Set a multivar ''name'' to ''value''. ''regexp'' is a regular
         expression to indicate which values to replace.
         """
@@ -193,7 +209,7 @@ class Config:
         err = C.git_config_set_multivar(self._config, name, regex, value)
         check_error(err)
 
-    def delete_multivar(self, name, regex):
+    def delete_multivar(self, name: str | bytes, regex: str | bytes) -> None:
         """Delete a multivar ''name''. ''regexp'' is a regular expression to
         indicate which values to delete.
         """
@@ -203,7 +219,7 @@ class Config:
         err = C.git_config_delete_multivar(self._config, name, regex)
         check_error(err)
 
-    def get_bool(self, key):
+    def get_bool(self, key: str | bytes) -> bool:
         """Look up *key* and parse its value as a boolean as per the git-config
         rules. Return a boolean value (True or False).
 
@@ -218,7 +234,7 @@ class Config:
 
         return res[0] != 0
 
-    def get_int(self, key):
+    def get_int(self, key: bytes | str) -> int:
         """Look up *key* and parse its value as an integer as per the git-config
         rules. Return an integer.
 
@@ -233,7 +249,7 @@ class Config:
 
         return res[0]
 
-    def add_file(self, path, level=0, force=0):
+    def add_file(self, path: str | Path, level: int = 0, force: int = 0) -> None:
         """Add a config file instance to an existing config."""
 
         err = C.git_config_add_file_ondisk(
@@ -241,7 +257,7 @@ class Config:
         )
         check_error(err)
 
-    def snapshot(self):
+    def snapshot(self) -> 'Config':
         """Create a snapshot from this Config object.
 
         This means that looking up multiple values will use the same version
@@ -258,7 +274,7 @@ class Config:
     #
 
     @staticmethod
-    def parse_bool(text):
+    def parse_bool(text: str) -> bool:
         res = ffi.new('int *')
         err = C.git_config_parse_bool(res, to_bytes(text))
         check_error(err)
@@ -266,7 +282,7 @@ class Config:
         return res[0] != 0
 
     @staticmethod
-    def parse_int(text):
+    def parse_int(text: str) -> int:
         res = ffi.new('int64_t *')
         err = C.git_config_parse_int64(res, to_bytes(text))
         check_error(err)
@@ -278,7 +294,7 @@ class Config:
     #
 
     @staticmethod
-    def _from_found_config(fn):
+    def _from_found_config(fn: Callable) -> 'Config':
         buf = ffi.new('git_buf *', (ffi.NULL, 0))
         err = fn(buf)
         check_error(err, io=True)
@@ -288,26 +304,31 @@ class Config:
         return Config(cpath)
 
     @staticmethod
-    def get_system_config():
+    def get_system_config() -> 'Config':
         """Return a <Config> object representing the system configuration file."""
         return Config._from_found_config(C.git_config_find_system)
 
     @staticmethod
-    def get_global_config():
+    def get_global_config() -> 'Config':
         """Return a <Config> object representing the global configuration file."""
         return Config._from_found_config(C.git_config_find_global)
 
     @staticmethod
-    def get_xdg_config():
+    def get_xdg_config() -> 'Config':
         """Return a <Config> object representing the global configuration file."""
         return Config._from_found_config(C.git_config_find_xdg)
 
 
 class ConfigEntry:
-    """An entry in a configuation object."""
+    """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
@@ -321,7 +342,7 @@ class ConfigEntry:
         # git_config_iterator_free when we've deleted all ConfigEntry objects.
         # But it's not, to reproduce the error comment the lines below and run
         # the script in https://github.com/libgit2/pygit2/issues/970
-        # So instead we load the Python object immmediately. Ideally we should
+        # So instead we load the Python object immediately. Ideally we should
         # investigate libgit2 source code.
         if iterator is not None:
             entry.raw_name = entry.raw_name
@@ -330,34 +351,34 @@ class ConfigEntry:
 
         return entry
 
-    def __del__(self):
+    def __del__(self) -> None:
         if self.iterator is None:
             C.git_config_entry_free(self._entry)
 
     @property
-    def c_value(self):
+    def c_value(self) -> 'ffi.char_pointer':
         """The raw ``cData`` entry value."""
         return self._entry.value
 
     @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 -pruN 1.17.0-2/pygit2/credentials.py 1.18.2-1/pygit2/credentials.py
--- 1.17.0-2/pygit2/credentials.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/credentials.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,10 +23,15 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-from .ffi import C
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
 
 from .enums import CredentialType
 
+if TYPE_CHECKING:
+    from pathlib import Path
+
 
 class Username:
     """Username credentials
@@ -35,7 +40,7 @@ class Username:
     callback and for returning from said callback.
     """
 
-    def __init__(self, username):
+    def __init__(self, username: str):
         self._username = username
 
     @property
@@ -43,10 +48,12 @@ class Username:
         return CredentialType.USERNAME
 
     @property
-    def credential_tuple(self):
+    def credential_tuple(self) -> tuple[str]:
         return (self._username,)
 
-    def __call__(self, _url, _username, _allowed):
+    def __call__(
+        self, _url: str, _username: str | None, _allowed: CredentialType
+    ) -> Username:
         return self
 
 
@@ -57,7 +64,7 @@ class UserPass:
     callback and for returning from said callback.
     """
 
-    def __init__(self, username, password):
+    def __init__(self, username: str, password: str):
         self._username = username
         self._password = password
 
@@ -66,10 +73,12 @@ class UserPass:
         return CredentialType.USERPASS_PLAINTEXT
 
     @property
-    def credential_tuple(self):
+    def credential_tuple(self) -> tuple[str, str]:
         return (self._username, self._password)
 
-    def __call__(self, _url, _username, _allowed):
+    def __call__(
+        self, _url: str, _username: str | None, _allowed: CredentialType
+    ) -> UserPass:
         return self
 
 
@@ -96,7 +105,13 @@ class Keypair:
         no passphrase is required.
     """
 
-    def __init__(self, username, pubkey, privkey, passphrase):
+    def __init__(
+        self,
+        username: str,
+        pubkey: str | Path | None,
+        privkey: str | Path | None,
+        passphrase: str | None,
+    ):
         self._username = username
         self._pubkey = pubkey
         self._privkey = privkey
@@ -107,15 +122,19 @@ class Keypair:
         return CredentialType.SSH_KEY
 
     @property
-    def credential_tuple(self):
+    def credential_tuple(
+        self,
+    ) -> tuple[str, str | Path | None, str | Path | None, str | None]:
         return (self._username, self._pubkey, self._privkey, self._passphrase)
 
-    def __call__(self, _url, _username, _allowed):
+    def __call__(
+        self, _url: str, _username: str | None, _allowed: CredentialType
+    ) -> Keypair:
         return self
 
 
 class KeypairFromAgent(Keypair):
-    def __init__(self, username):
+    def __init__(self, username: str):
         super().__init__(username, None, None, None)
 
 
diff -pruN 1.17.0-2/pygit2/decl/callbacks.h 1.18.2-1/pygit2/decl/callbacks.h
--- 1.17.0-2/pygit2/decl/callbacks.h	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/decl/callbacks.h	2025-08-16 11:34:29.000000000 +0000
@@ -16,6 +16,11 @@ extern "Python" int _push_update_referen
     const char *status,
     void *data);
 
+extern "Python" int _push_negotiation_cb(
+    const git_push_update **updates,
+    size_t len,
+    void *data);
+
 extern "Python" int _remote_create_cb(
 	git_remote **out,
 	git_repository *repo,
@@ -38,6 +43,12 @@ extern "Python" int _transfer_progress_c
     const git_indexer_progress *stats,
     void *payload);
 
+extern "Python" int _push_transfer_progress_cb(
+    unsigned int objects_pushed,
+    unsigned int total_objects,
+    size_t bytes_pushed,
+    void *payload);
+
 extern "Python" int _update_tips_cb(
 	const char *refname,
 	const git_oid *a,
diff -pruN 1.17.0-2/pygit2/decl/commit.h 1.18.2-1/pygit2/decl/commit.h
--- 1.17.0-2/pygit2/decl/commit.h	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/decl/commit.h	2025-08-16 11:34:29.000000000 +0000
@@ -13,4 +13,9 @@ int git_annotated_commit_lookup(
 	git_repository *repo,
 	const git_oid *id);
 
+int git_annotated_commit_from_ref(
+	git_annotated_commit **out,
+	git_repository *repo,
+	const struct git_reference *ref);
+
 void git_annotated_commit_free(git_annotated_commit *commit);
diff -pruN 1.17.0-2/pygit2/decl/index.h 1.18.2-1/pygit2/decl/index.h
--- 1.17.0-2/pygit2/decl/index.h	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/decl/index.h	2025-08-16 11:34:29.000000000 +0000
@@ -34,6 +34,7 @@ int git_index_find(size_t *at_pos, git_i
 int git_index_add_bypath(git_index *index, const char *path);
 int git_index_add(git_index *index, const git_index_entry *source_entry);
 int git_index_remove(git_index *index, const char *path, int stage);
+int git_index_remove_directory(git_index *index, const char *path, int stage);
 int git_index_read_tree(git_index *index, const git_tree *tree);
 int git_index_clear(git_index *index);
 int git_index_write_tree(git_oid *out, git_index *index);
@@ -59,6 +60,11 @@ void git_index_conflict_iterator_free(
 int git_index_conflict_iterator_new(
 	git_index_conflict_iterator **iterator_out,
 	git_index *index);
+int git_index_conflict_add(
+    git_index *index,
+    const git_index_entry *ancestor_entry,
+    const git_index_entry *our_entry,
+    const git_index_entry *their_entry);
 int git_index_conflict_get(
 	const git_index_entry **ancestor_out,
 	const git_index_entry **our_out,
diff -pruN 1.17.0-2/pygit2/decl/options.h 1.18.2-1/pygit2/decl/options.h
--- 1.17.0-2/pygit2/decl/options.h	1970-01-01 00:00:00.000000000 +0000
+++ 1.18.2-1/pygit2/decl/options.h	2025-08-16 11:34:29.000000000 +0000
@@ -0,0 +1,50 @@
+typedef enum {
+	GIT_OPT_GET_MWINDOW_SIZE,
+	GIT_OPT_SET_MWINDOW_SIZE,
+	GIT_OPT_GET_MWINDOW_MAPPED_LIMIT,
+	GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,
+	GIT_OPT_GET_SEARCH_PATH,
+	GIT_OPT_SET_SEARCH_PATH,
+	GIT_OPT_SET_CACHE_OBJECT_LIMIT,
+	GIT_OPT_SET_CACHE_MAX_SIZE,
+	GIT_OPT_ENABLE_CACHING,
+	GIT_OPT_GET_CACHED_MEMORY,
+	GIT_OPT_GET_TEMPLATE_PATH,
+	GIT_OPT_SET_TEMPLATE_PATH,
+	GIT_OPT_SET_SSL_CERT_LOCATIONS,
+	GIT_OPT_SET_USER_AGENT,
+	GIT_OPT_ENABLE_STRICT_OBJECT_CREATION,
+	GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION,
+	GIT_OPT_SET_SSL_CIPHERS,
+	GIT_OPT_GET_USER_AGENT,
+	GIT_OPT_ENABLE_OFS_DELTA,
+	GIT_OPT_ENABLE_FSYNC_GITDIR,
+	GIT_OPT_GET_WINDOWS_SHAREMODE,
+	GIT_OPT_SET_WINDOWS_SHAREMODE,
+	GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION,
+	GIT_OPT_SET_ALLOCATOR,
+	GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY,
+	GIT_OPT_GET_PACK_MAX_OBJECTS,
+	GIT_OPT_SET_PACK_MAX_OBJECTS,
+	GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS,
+	GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE,
+	GIT_OPT_GET_MWINDOW_FILE_LIMIT,
+	GIT_OPT_SET_MWINDOW_FILE_LIMIT,
+	GIT_OPT_SET_ODB_PACKED_PRIORITY,
+	GIT_OPT_SET_ODB_LOOSE_PRIORITY,
+	GIT_OPT_GET_EXTENSIONS,
+	GIT_OPT_SET_EXTENSIONS,
+	GIT_OPT_GET_OWNER_VALIDATION,
+	GIT_OPT_SET_OWNER_VALIDATION,
+	GIT_OPT_GET_HOMEDIR,
+	GIT_OPT_SET_HOMEDIR,
+	GIT_OPT_SET_SERVER_CONNECT_TIMEOUT,
+	GIT_OPT_GET_SERVER_CONNECT_TIMEOUT,
+	GIT_OPT_SET_SERVER_TIMEOUT,
+	GIT_OPT_GET_SERVER_TIMEOUT,
+	GIT_OPT_SET_USER_AGENT_PRODUCT,
+	GIT_OPT_GET_USER_AGENT_PRODUCT,
+	GIT_OPT_ADD_SSL_X509_CERT
+} git_libgit2_opt_t;
+
+int git_libgit2_opts(int option, ...);
\ No newline at end of file
diff -pruN 1.17.0-2/pygit2/decl/repository.h 1.18.2-1/pygit2/decl/repository.h
--- 1.17.0-2/pygit2/decl/repository.h	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/decl/repository.h	2025-08-16 11:34:29.000000000 +0000
@@ -81,7 +81,7 @@ int git_repository_set_head(
 
 int git_repository_set_head_detached(
 	git_repository* repo,
-	const git_oid* commitish);
+	const git_oid* committish);
 
 int git_repository_hashfile(git_oid *out, git_repository *repo, const char *path, git_object_t type, const char *as_path);
 int git_repository_ident(const char **name, const char **email, const git_repository *repo);
diff -pruN 1.17.0-2/pygit2/decl/submodule.h 1.18.2-1/pygit2/decl/submodule.h
--- 1.17.0-2/pygit2/decl/submodule.h	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/decl/submodule.h	2025-08-16 11:34:29.000000000 +0000
@@ -41,3 +41,5 @@ const char * git_submodule_branch(git_su
 const git_oid * git_submodule_head_id(git_submodule *submodule);
 
 int git_submodule_status(unsigned int *status, git_repository *repo, const char *name, git_submodule_ignore_t ignore);
+
+int git_submodule_set_url(git_repository *repo, const char *name, const char *url);
diff -pruN 1.17.0-2/pygit2/enums.py 1.18.2-1/pygit2/enums.py
--- 1.17.0-2/pygit2/enums.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/enums.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,7 +25,7 @@
 
 from enum import IntEnum, IntFlag
 
-from . import _pygit2
+from . import _pygit2, options
 from .ffi import C
 
 
@@ -948,51 +948,54 @@ class Option(IntEnum):
     """Global libgit2 library options"""
 
     # Commented out values --> exists in libgit2 but not supported in pygit2's options.c yet
-    GET_MWINDOW_SIZE = _pygit2.GIT_OPT_GET_MWINDOW_SIZE
-    SET_MWINDOW_SIZE = _pygit2.GIT_OPT_SET_MWINDOW_SIZE
-    GET_MWINDOW_MAPPED_LIMIT = _pygit2.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT
-    SET_MWINDOW_MAPPED_LIMIT = _pygit2.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT
-    GET_SEARCH_PATH = _pygit2.GIT_OPT_GET_SEARCH_PATH
-    SET_SEARCH_PATH = _pygit2.GIT_OPT_SET_SEARCH_PATH
-    SET_CACHE_OBJECT_LIMIT = _pygit2.GIT_OPT_SET_CACHE_OBJECT_LIMIT
-    SET_CACHE_MAX_SIZE = _pygit2.GIT_OPT_SET_CACHE_MAX_SIZE
-    ENABLE_CACHING = _pygit2.GIT_OPT_ENABLE_CACHING
-    GET_CACHED_MEMORY = _pygit2.GIT_OPT_GET_CACHED_MEMORY
-    GET_TEMPLATE_PATH = _pygit2.GIT_OPT_GET_TEMPLATE_PATH
-    SET_TEMPLATE_PATH = _pygit2.GIT_OPT_SET_TEMPLATE_PATH
-    SET_SSL_CERT_LOCATIONS = _pygit2.GIT_OPT_SET_SSL_CERT_LOCATIONS
-    SET_USER_AGENT = _pygit2.GIT_OPT_SET_USER_AGENT
-    ENABLE_STRICT_OBJECT_CREATION = _pygit2.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION
+    GET_MWINDOW_SIZE = options.GIT_OPT_GET_MWINDOW_SIZE
+    SET_MWINDOW_SIZE = options.GIT_OPT_SET_MWINDOW_SIZE
+    GET_MWINDOW_MAPPED_LIMIT = options.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT
+    SET_MWINDOW_MAPPED_LIMIT = options.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT
+    GET_SEARCH_PATH = options.GIT_OPT_GET_SEARCH_PATH
+    SET_SEARCH_PATH = options.GIT_OPT_SET_SEARCH_PATH
+    SET_CACHE_OBJECT_LIMIT = options.GIT_OPT_SET_CACHE_OBJECT_LIMIT
+    SET_CACHE_MAX_SIZE = options.GIT_OPT_SET_CACHE_MAX_SIZE
+    ENABLE_CACHING = options.GIT_OPT_ENABLE_CACHING
+    GET_CACHED_MEMORY = options.GIT_OPT_GET_CACHED_MEMORY
+    GET_TEMPLATE_PATH = options.GIT_OPT_GET_TEMPLATE_PATH
+    SET_TEMPLATE_PATH = options.GIT_OPT_SET_TEMPLATE_PATH
+    SET_SSL_CERT_LOCATIONS = options.GIT_OPT_SET_SSL_CERT_LOCATIONS
+    SET_USER_AGENT = options.GIT_OPT_SET_USER_AGENT
+    ENABLE_STRICT_OBJECT_CREATION = options.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION
     ENABLE_STRICT_SYMBOLIC_REF_CREATION = (
-        _pygit2.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION
+        options.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION
     )
-    SET_SSL_CIPHERS = _pygit2.GIT_OPT_SET_SSL_CIPHERS
-    GET_USER_AGENT = _pygit2.GIT_OPT_GET_USER_AGENT
-    ENABLE_OFS_DELTA = _pygit2.GIT_OPT_ENABLE_OFS_DELTA
-    ENABLE_FSYNC_GITDIR = _pygit2.GIT_OPT_ENABLE_FSYNC_GITDIR
-    GET_WINDOWS_SHAREMODE = _pygit2.GIT_OPT_GET_WINDOWS_SHAREMODE
-    SET_WINDOWS_SHAREMODE = _pygit2.GIT_OPT_SET_WINDOWS_SHAREMODE
-    ENABLE_STRICT_HASH_VERIFICATION = _pygit2.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION
-    SET_ALLOCATOR = _pygit2.GIT_OPT_SET_ALLOCATOR
-    ENABLE_UNSAVED_INDEX_SAFETY = _pygit2.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY
-    GET_PACK_MAX_OBJECTS = _pygit2.GIT_OPT_GET_PACK_MAX_OBJECTS
-    SET_PACK_MAX_OBJECTS = _pygit2.GIT_OPT_SET_PACK_MAX_OBJECTS
-    DISABLE_PACK_KEEP_FILE_CHECKS = _pygit2.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS
-    # ENABLE_HTTP_EXPECT_CONTINUE = _pygit2.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE
-    GET_MWINDOW_FILE_LIMIT = _pygit2.GIT_OPT_GET_MWINDOW_FILE_LIMIT
-    SET_MWINDOW_FILE_LIMIT = _pygit2.GIT_OPT_SET_MWINDOW_FILE_LIMIT
-    # SET_ODB_PACKED_PRIORITY = _pygit2.GIT_OPT_SET_ODB_PACKED_PRIORITY
-    # SET_ODB_LOOSE_PRIORITY = _pygit2.GIT_OPT_SET_ODB_LOOSE_PRIORITY
-    # GET_EXTENSIONS = _pygit2.GIT_OPT_GET_EXTENSIONS
-    # SET_EXTENSIONS = _pygit2.GIT_OPT_SET_EXTENSIONS
-    GET_OWNER_VALIDATION = _pygit2.GIT_OPT_GET_OWNER_VALIDATION
-    SET_OWNER_VALIDATION = _pygit2.GIT_OPT_SET_OWNER_VALIDATION
-    # GET_HOMEDIR = _pygit2.GIT_OPT_GET_HOMEDIR
-    # SET_HOMEDIR = _pygit2.GIT_OPT_SET_HOMEDIR
-    # SET_SERVER_CONNECT_TIMEOUT = _pygit2.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT
-    # GET_SERVER_CONNECT_TIMEOUT = _pygit2.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT
-    # SET_SERVER_TIMEOUT = _pygit2.GIT_OPT_SET_SERVER_TIMEOUT
-    # GET_SERVER_TIMEOUT = _pygit2.GIT_OPT_GET_SERVER_TIMEOUT
+    SET_SSL_CIPHERS = options.GIT_OPT_SET_SSL_CIPHERS
+    GET_USER_AGENT = options.GIT_OPT_GET_USER_AGENT
+    ENABLE_OFS_DELTA = options.GIT_OPT_ENABLE_OFS_DELTA
+    ENABLE_FSYNC_GITDIR = options.GIT_OPT_ENABLE_FSYNC_GITDIR
+    GET_WINDOWS_SHAREMODE = options.GIT_OPT_GET_WINDOWS_SHAREMODE
+    SET_WINDOWS_SHAREMODE = options.GIT_OPT_SET_WINDOWS_SHAREMODE
+    ENABLE_STRICT_HASH_VERIFICATION = options.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION
+    SET_ALLOCATOR = options.GIT_OPT_SET_ALLOCATOR
+    ENABLE_UNSAVED_INDEX_SAFETY = options.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY
+    GET_PACK_MAX_OBJECTS = options.GIT_OPT_GET_PACK_MAX_OBJECTS
+    SET_PACK_MAX_OBJECTS = options.GIT_OPT_SET_PACK_MAX_OBJECTS
+    DISABLE_PACK_KEEP_FILE_CHECKS = options.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS
+    ENABLE_HTTP_EXPECT_CONTINUE = options.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE
+    GET_MWINDOW_FILE_LIMIT = options.GIT_OPT_GET_MWINDOW_FILE_LIMIT
+    SET_MWINDOW_FILE_LIMIT = options.GIT_OPT_SET_MWINDOW_FILE_LIMIT
+    SET_ODB_PACKED_PRIORITY = options.GIT_OPT_SET_ODB_PACKED_PRIORITY
+    SET_ODB_LOOSE_PRIORITY = options.GIT_OPT_SET_ODB_LOOSE_PRIORITY
+    GET_EXTENSIONS = options.GIT_OPT_GET_EXTENSIONS
+    SET_EXTENSIONS = options.GIT_OPT_SET_EXTENSIONS
+    GET_OWNER_VALIDATION = options.GIT_OPT_GET_OWNER_VALIDATION
+    SET_OWNER_VALIDATION = options.GIT_OPT_SET_OWNER_VALIDATION
+    GET_HOMEDIR = options.GIT_OPT_GET_HOMEDIR
+    SET_HOMEDIR = options.GIT_OPT_SET_HOMEDIR
+    SET_SERVER_CONNECT_TIMEOUT = options.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT
+    GET_SERVER_CONNECT_TIMEOUT = options.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT
+    SET_SERVER_TIMEOUT = options.GIT_OPT_SET_SERVER_TIMEOUT
+    GET_SERVER_TIMEOUT = options.GIT_OPT_GET_SERVER_TIMEOUT
+    GET_USER_AGENT_PRODUCT = options.GIT_OPT_GET_USER_AGENT_PRODUCT
+    SET_USER_AGENT_PRODUCT = options.GIT_OPT_SET_USER_AGENT_PRODUCT
+    ADD_SSL_X509_CERT = options.GIT_OPT_ADD_SSL_X509_CERT
 
 
 class ReferenceFilter(IntEnum):
diff -pruN 1.17.0-2/pygit2/errors.py 1.18.2-1/pygit2/errors.py
--- 1.17.0-2/pygit2/errors.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/errors.py	2025-08-16 11:34:29.000000000 +0000
@@ -24,14 +24,15 @@
 # Boston, MA 02110-1301, USA.
 
 # Import from pygit2
-from .ffi import ffi, C
 from ._pygit2 import GitError
+from .ffi import C, ffi
 
+__all__ = ['GitError']
 
 value_errors = set([C.GIT_EEXISTS, C.GIT_EINVALIDSPEC, C.GIT_EAMBIGUOUS])
 
 
-def check_error(err, io=False):
+def check_error(err: int, io: bool = False) -> None:
     if err >= 0:
         return
 
@@ -42,7 +43,7 @@ def check_error(err, io=False):
     # Error message
     giterr = C.git_error_last()
     if giterr != ffi.NULL:
-        message = ffi.string(giterr.message).decode('utf8')
+        message = ffi.string(giterr.message).decode('utf8', errors='surrogateescape')
     else:
         message = f'err {err} (no message provided)'
 
@@ -68,5 +69,5 @@ def check_error(err, io=False):
 
 # Indicate that we want libgit2 to pretend a function was not set
 class Passthrough(Exception):
-    def __init__(self):
+    def __init__(self) -> None:
         super().__init__('The function asked for pass-through')
diff -pruN 1.17.0-2/pygit2/ffi.py 1.18.2-1/pygit2/ffi.py
--- 1.17.0-2/pygit2/ffi.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/ffi.py	2025-08-16 11:34:29.000000000 +0000
@@ -24,4 +24,7 @@
 # Boston, MA 02110-1301, USA.
 
 # Import from pygit2
-from ._libgit2 import ffi, lib as C
+from ._libgit2 import ffi  # noqa: F401
+from ._libgit2 import lib as C  # type: ignore # noqa: F401
+
+__all__ = ['C', 'ffi']
diff -pruN 1.17.0-2/pygit2/filter.py 1.18.2-1/pygit2/filter.py
--- 1.17.0-2/pygit2/filter.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/filter.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,7 +23,7 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-from typing import Callable, List, Optional
+from collections.abc import Callable
 
 from ._pygit2 import FilterSource
 
@@ -58,7 +58,7 @@ class Filter:
     def nattrs(cls) -> int:
         return len(cls.attributes.split())
 
-    def check(self, src: FilterSource, attr_values: List[Optional[str]]):
+    def check(self, src: FilterSource, attr_values: list[str | None]) -> None:
         """
         Check whether this filter should be applied to the given source.
 
@@ -77,7 +77,7 @@ class Filter:
 
     def write(
         self, data: bytes, src: FilterSource, write_next: Callable[[bytes], None]
-    ):
+    ) -> None:
         """
         Write input `data` to this filter.
 
@@ -95,7 +95,7 @@ class Filter:
         """
         write_next(data)
 
-    def close(self, write_next: Callable[[bytes], None]):
+    def close(self, write_next: Callable[[bytes], None]) -> None:
         """
         Close this filter.
 
diff -pruN 1.17.0-2/pygit2/index.py 1.18.2-1/pygit2/index.py
--- 1.17.0-2/pygit2/index.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/index.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,16 +23,20 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+import typing
 import warnings
-import weakref
+from dataclasses import dataclass
+from os import PathLike
 
 # Import from pygit2
-from ._pygit2 import Oid, Tree, Diff
+from ._pygit2 import Diff, Oid, Tree
 from .enums import DiffOption, FileMode
 from .errors import check_error
-from .ffi import ffi, C
-from .utils import to_bytes, to_str
-from .utils import GenericIterator, StrArray
+from .ffi import C, ffi
+from .utils import GenericIterator, StrArray, to_bytes, to_str
+
+if typing.TYPE_CHECKING:
+    from .repository import Repository
 
 
 class Index:
@@ -41,7 +45,7 @@ class Index:
     # a proper implementation in some places: e.g. checking the index type
     # from C code (see Tree_diff_to_index)
 
-    def __init__(self, path=None):
+    def __init__(self, path: str | PathLike[str] | None = None) -> None:
         """Create a new Index
 
         If path is supplied, the read and write methods will use that path
@@ -68,13 +72,13 @@ class Index:
     def _pointer(self):
         return bytes(ffi.buffer(self._cindex)[:])
 
-    def __del__(self):
+    def __del__(self) -> None:
         C.git_index_free(self._index)
 
-    def __len__(self):
+    def __len__(self) -> int:
         return C.git_index_entrycount(self._index)
 
-    def __contains__(self, path):
+    def __contains__(self, path) -> bool:
         err = C.git_index_find(ffi.NULL, self._index, to_bytes(path))
         if err == C.GIT_ENOTFOUND:
             return False
@@ -82,7 +86,7 @@ class Index:
         check_error(err)
         return True
 
-    def __getitem__(self, key):
+    def __getitem__(self, key: str | int | PathLike[str]) -> 'IndexEntry':
         centry = ffi.NULL
         if isinstance(key, str) or hasattr(key, '__fspath__'):
             centry = C.git_index_get_bypath(self._index, to_bytes(key), 0)
@@ -102,7 +106,7 @@ class Index:
     def __iter__(self):
         return GenericIterator(self)
 
-    def read(self, force=True):
+    def read(self, force: bool = True) -> None:
         """
         Update the contents of the Index by reading from a file.
 
@@ -116,16 +120,16 @@ class Index:
         err = C.git_index_read(self._index, force)
         check_error(err, io=True)
 
-    def write(self):
+    def write(self) -> None:
         """Write the contents of the Index to disk."""
         err = C.git_index_write(self._index)
         check_error(err, io=True)
 
-    def clear(self):
+    def clear(self) -> None:
         err = C.git_index_clear(self._index)
         check_error(err)
 
-    def read_tree(self, tree):
+    def read_tree(self, tree: Oid | Tree | str) -> None:
         """Replace the contents of the Index with those of the given tree,
         expressed either as a <Tree> object or as an oid (string or <Oid>).
 
@@ -134,6 +138,8 @@ class Index:
         """
         repo = self._repo
         if isinstance(tree, str):
+            if repo is None:
+                raise TypeError('id given but no associated repository')
             tree = repo[tree]
 
         if isinstance(tree, Oid):
@@ -142,14 +148,14 @@ class Index:
 
             tree = repo[tree]
         elif not isinstance(tree, Tree):
-            raise TypeError('argument must be Oid or Tree')
+            raise TypeError('argument must be Oid, Tree or str')
 
         tree_cptr = ffi.new('git_tree **')
         ffi.buffer(tree_cptr)[:] = tree._pointer[:]
         err = C.git_index_read_tree(self._index, tree_cptr[0])
         check_error(err)
 
-    def write_tree(self, repo=None):
+    def write_tree(self, repo: 'Repository | None' = None) -> Oid:
         """Create a tree out of the Index. Return the <Oid> object of the
         written tree.
 
@@ -172,18 +178,23 @@ class Index:
         check_error(err)
         return Oid(raw=bytes(ffi.buffer(coid)[:]))
 
-    def remove(self, path, level=0):
+    def remove(self, path: PathLike[str] | str, level: int = 0) -> None:
         """Remove an entry from the Index."""
         err = C.git_index_remove(self._index, to_bytes(path), level)
         check_error(err, io=True)
 
-    def remove_all(self, pathspecs):
+    def remove_directory(self, path: PathLike[str] | str, level: int = 0) -> None:
+        """Remove a directory from the Index."""
+        err = C.git_index_remove_directory(self._index, to_bytes(path), level)
+        check_error(err, io=True)
+
+    def remove_all(self, pathspecs: typing.Sequence[str | PathLike[str]]) -> None:
         """Remove all index entries matching pathspecs."""
         with StrArray(pathspecs) as arr:
             err = C.git_index_remove_all(self._index, arr.ptr, ffi.NULL, ffi.NULL)
             check_error(err, io=True)
 
-    def add_all(self, pathspecs=None):
+    def add_all(self, pathspecs: None | list[str | PathLike[str]] = None) -> None:
         """Add or update index entries matching files in the working directory.
 
         If pathspecs are specified, only files matching those pathspecs will
@@ -194,7 +205,7 @@ class Index:
             err = C.git_index_add_all(self._index, arr.ptr, 0, ffi.NULL, ffi.NULL)
             check_error(err, io=True)
 
-    def add(self, path_or_entry):
+    def add(self, path_or_entry: 'IndexEntry | str | PathLike[str]') -> None:
         """Add or update an entry in the Index.
 
         If a path is given, that file will be added. The path must be relative
@@ -212,7 +223,46 @@ class Index:
             path = path_or_entry
             err = C.git_index_add_bypath(self._index, to_bytes(path))
         else:
-            raise TypeError('argument must be string or IndexEntry')
+            raise TypeError('argument must be string, Path or IndexEntry')
+
+        check_error(err, io=True)
+
+    def add_conflict(
+        self, ancestor: 'IndexEntry', ours: 'IndexEntry', theirs: 'IndexEntry | None'
+    ) -> None:
+        """
+        Add or update index entries to represent a conflict. Any staged entries that
+        exist at the given paths will be removed.
+
+        Parameters:
+
+        ancestor
+            ancestor of the conflict
+        ours
+            ours side of the conflict
+        theirs
+            their side of the conflict
+        """
+
+        if ancestor and not isinstance(ancestor, IndexEntry):
+            raise TypeError('ancestor has to be an instance of IndexEntry or None')
+        if ours and not isinstance(ours, IndexEntry):
+            raise TypeError('ours has to be an instance of IndexEntry or None')
+        if theirs and not isinstance(theirs, IndexEntry):
+            raise TypeError('theirs has to be an instance of IndexEntry or None')
+
+        centry_ancestor: ffi.NULL_TYPE | ffi.GitIndexEntryC = ffi.NULL
+        centry_ours: ffi.NULL_TYPE | ffi.GitIndexEntryC = ffi.NULL
+        centry_theirs: ffi.NULL_TYPE | ffi.GitIndexEntryC = ffi.NULL
+        if ancestor is not None:
+            centry_ancestor, _ = ancestor._to_c()
+        if ours is not None:
+            centry_ours, _ = ours._to_c()
+        if theirs is not None:
+            centry_theirs, _ = theirs._to_c()
+        err = C.git_index_conflict_add(
+            self._index, centry_ancestor, centry_ours, centry_theirs
+        )
 
         check_error(err, io=True)
 
@@ -311,7 +361,6 @@ class Index:
     #
     # Conflicts
     #
-    _conflicts = None
 
     @property
     def conflicts(self):
@@ -333,19 +382,53 @@ class Index:
         the particular conflict.
         """
         if not C.git_index_has_conflicts(self._index):
-            self._conflicts = None
             return None
 
-        if self._conflicts is None or self._conflicts() is None:
-            conflicts = ConflictCollection(self)
-            self._conflicts = weakref.ref(conflicts)
-            return conflicts
+        return ConflictCollection(self)
+
+
+@dataclass
+class MergeFileResult:
+    automergeable: bool
+    'True if the output was automerged, false if the output contains conflict markers'
+
+    path: str | None | PathLike[str]
+    'The path that the resultant merge file should use, or None if a filename conflict would occur'
+
+    mode: FileMode
+    'The mode that the resultant merge file should use'
+
+    contents: str
+    'Contents of the file, which might include conflict markers'
+
+    def __repr__(self):
+        t = type(self)
+        contents = (
+            self.contents if len(self.contents) <= 20 else f'{self.contents[:20]}...'
+        )
+        return (
+            f'<{t.__module__}.{t.__qualname__} "'
+            f'automergeable={self.automergeable} "'
+            f'path={self.path} '
+            f'mode={self.mode} '
+            f'contents={contents}>'
+        )
+
+    @classmethod
+    def _from_c(cls, centry):
+        if centry == ffi.NULL:
+            return None
+
+        automergeable = centry.automergeable != 0
+        path = to_str(ffi.string(centry.path)) if centry.path else None
+        mode = FileMode(centry.mode)
+        contents = ffi.string(centry.ptr, centry.len).decode('utf-8')
 
-        return self._conflicts()
+        return MergeFileResult(automergeable, path, mode, contents)
 
 
 class IndexEntry:
-    path: str
+    path: str | PathLike[str]
     'The path of this entry'
 
     id: Oid
@@ -354,7 +437,9 @@ class IndexEntry:
     mode: FileMode
     'The mode of this entry, a FileMode value'
 
-    def __init__(self, path, object_id: Oid, mode: FileMode):
+    def __init__(
+        self, path: str | PathLike[str], object_id: Oid, mode: FileMode
+    ) -> None:
         self.path = path
         self.id = object_id
         self.mode = mode
@@ -386,7 +471,7 @@ class IndexEntry:
             self.path == other.path and self.id == other.id and self.mode == other.mode
         )
 
-    def _to_c(self):
+    def _to_c(self) -> tuple['ffi.GitIndexEntryC', 'ffi.ArrayC[ffi.char]']:
         """Convert this entry into the C structure
 
         The first returned arg is the pointer, the second is the reference to
diff -pruN 1.17.0-2/pygit2/options.py 1.18.2-1/pygit2/options.py
--- 1.17.0-2/pygit2/options.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.18.2-1/pygit2/options.py	2025-08-16 11:34:29.000000000 +0000
@@ -0,0 +1,805 @@
+# Copyright 2010-2025 The pygit2 contributors
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# In addition to the permissions in the GNU General Public License,
+# the authors give you unlimited permission to link the compiled
+# version of this file into combinations with other programs,
+# and to distribute those combinations without any restriction
+# coming from the use of this file.  (The General Public License
+# restrictions do apply in other respects; for example, they cover
+# modification of the file, and distribution when not linked into
+# a combined executable.)
+#
+# This file is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING.  If not, write to
+# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+"""
+Libgit2 global options management using CFFI.
+"""
+
+from __future__ import annotations
+
+# Import only for type checking to avoid circular imports
+from typing import TYPE_CHECKING, Any, Literal, cast, overload
+
+from .errors import check_error
+from .ffi import C, ffi
+from .utils import to_bytes, to_str
+
+if TYPE_CHECKING:
+    from ._libgit2.ffi import NULL_TYPE, ArrayC, char, char_pointer
+    from .enums import ConfigLevel, ObjectType, Option
+
+# Export GIT_OPT constants for backward compatibility
+GIT_OPT_GET_MWINDOW_SIZE: int = C.GIT_OPT_GET_MWINDOW_SIZE
+GIT_OPT_SET_MWINDOW_SIZE: int = C.GIT_OPT_SET_MWINDOW_SIZE
+GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: int = C.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT
+GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: int = C.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT
+GIT_OPT_GET_SEARCH_PATH: int = C.GIT_OPT_GET_SEARCH_PATH
+GIT_OPT_SET_SEARCH_PATH: int = C.GIT_OPT_SET_SEARCH_PATH
+GIT_OPT_SET_CACHE_OBJECT_LIMIT: int = C.GIT_OPT_SET_CACHE_OBJECT_LIMIT
+GIT_OPT_SET_CACHE_MAX_SIZE: int = C.GIT_OPT_SET_CACHE_MAX_SIZE
+GIT_OPT_ENABLE_CACHING: int = C.GIT_OPT_ENABLE_CACHING
+GIT_OPT_GET_CACHED_MEMORY: int = C.GIT_OPT_GET_CACHED_MEMORY
+GIT_OPT_GET_TEMPLATE_PATH: int = C.GIT_OPT_GET_TEMPLATE_PATH
+GIT_OPT_SET_TEMPLATE_PATH: int = C.GIT_OPT_SET_TEMPLATE_PATH
+GIT_OPT_SET_SSL_CERT_LOCATIONS: int = C.GIT_OPT_SET_SSL_CERT_LOCATIONS
+GIT_OPT_SET_USER_AGENT: int = C.GIT_OPT_SET_USER_AGENT
+GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: int = C.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION
+GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: int = (
+    C.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION
+)
+GIT_OPT_SET_SSL_CIPHERS: int = C.GIT_OPT_SET_SSL_CIPHERS
+GIT_OPT_GET_USER_AGENT: int = C.GIT_OPT_GET_USER_AGENT
+GIT_OPT_ENABLE_OFS_DELTA: int = C.GIT_OPT_ENABLE_OFS_DELTA
+GIT_OPT_ENABLE_FSYNC_GITDIR: int = C.GIT_OPT_ENABLE_FSYNC_GITDIR
+GIT_OPT_GET_WINDOWS_SHAREMODE: int = C.GIT_OPT_GET_WINDOWS_SHAREMODE
+GIT_OPT_SET_WINDOWS_SHAREMODE: int = C.GIT_OPT_SET_WINDOWS_SHAREMODE
+GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: int = C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION
+GIT_OPT_SET_ALLOCATOR: int = C.GIT_OPT_SET_ALLOCATOR
+GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: int = C.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY
+GIT_OPT_GET_PACK_MAX_OBJECTS: int = C.GIT_OPT_GET_PACK_MAX_OBJECTS
+GIT_OPT_SET_PACK_MAX_OBJECTS: int = C.GIT_OPT_SET_PACK_MAX_OBJECTS
+GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: int = C.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS
+GIT_OPT_GET_MWINDOW_FILE_LIMIT: int = C.GIT_OPT_GET_MWINDOW_FILE_LIMIT
+GIT_OPT_SET_MWINDOW_FILE_LIMIT: int = C.GIT_OPT_SET_MWINDOW_FILE_LIMIT
+GIT_OPT_GET_OWNER_VALIDATION: int = C.GIT_OPT_GET_OWNER_VALIDATION
+GIT_OPT_SET_OWNER_VALIDATION: int = C.GIT_OPT_SET_OWNER_VALIDATION
+GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: int = C.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE
+GIT_OPT_SET_ODB_PACKED_PRIORITY: int = C.GIT_OPT_SET_ODB_PACKED_PRIORITY
+GIT_OPT_SET_ODB_LOOSE_PRIORITY: int = C.GIT_OPT_SET_ODB_LOOSE_PRIORITY
+GIT_OPT_GET_EXTENSIONS: int = C.GIT_OPT_GET_EXTENSIONS
+GIT_OPT_SET_EXTENSIONS: int = C.GIT_OPT_SET_EXTENSIONS
+GIT_OPT_GET_HOMEDIR: int = C.GIT_OPT_GET_HOMEDIR
+GIT_OPT_SET_HOMEDIR: int = C.GIT_OPT_SET_HOMEDIR
+GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: int = C.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT
+GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: int = C.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT
+GIT_OPT_SET_SERVER_TIMEOUT: int = C.GIT_OPT_SET_SERVER_TIMEOUT
+GIT_OPT_GET_SERVER_TIMEOUT: int = C.GIT_OPT_GET_SERVER_TIMEOUT
+GIT_OPT_GET_USER_AGENT_PRODUCT: int = C.GIT_OPT_GET_USER_AGENT_PRODUCT
+GIT_OPT_SET_USER_AGENT_PRODUCT: int = C.GIT_OPT_SET_USER_AGENT_PRODUCT
+GIT_OPT_ADD_SSL_X509_CERT: int = C.GIT_OPT_ADD_SSL_X509_CERT
+
+
+NOT_PASSED = object()
+
+
+def check_args(option: Option, arg1: Any, arg2: Any, expected: int) -> None:
+    if expected == 0 and (arg1 is not NOT_PASSED or arg2 is not NOT_PASSED):
+        raise TypeError(f'option({option}) takes no additional arguments')
+
+    if expected == 1 and (arg1 is NOT_PASSED or arg2 is not NOT_PASSED):
+        raise TypeError(f'option({option}, x) requires 1 additional argument')
+
+    if expected == 2 and (arg1 is NOT_PASSED or arg2 is NOT_PASSED):
+        raise TypeError(f'option({option}, x, y) requires 2 additional arguments')
+
+
+@overload
+def option(
+    option_type: Literal[
+        Option.GET_MWINDOW_SIZE,
+        Option.GET_MWINDOW_MAPPED_LIMIT,
+        Option.GET_MWINDOW_FILE_LIMIT,
+    ],
+) -> int: ...
+
+
+@overload
+def option(
+    option_type: Literal[
+        Option.SET_MWINDOW_SIZE,
+        Option.SET_MWINDOW_MAPPED_LIMIT,
+        Option.SET_MWINDOW_FILE_LIMIT,
+        Option.SET_CACHE_MAX_SIZE,
+    ],
+    arg1: int,  # value
+) -> None: ...
+
+
+@overload
+def option(
+    option_type: Literal[Option.GET_SEARCH_PATH],
+    arg1: ConfigLevel,  # value
+) -> str: ...
+
+
+@overload
+def option(
+    option_type: Literal[Option.SET_SEARCH_PATH],
+    arg1: ConfigLevel,  # type
+    arg2: str,  # value
+) -> None: ...
+
+
+@overload
+def option(
+    option_type: Literal[Option.SET_CACHE_OBJECT_LIMIT],
+    arg1: ObjectType,  # type
+    arg2: int,  # limit
+) -> None: ...
+
+
+@overload
+def option(option_type: Literal[Option.GET_CACHED_MEMORY]) -> tuple[int, int]: ...
+
+
+@overload
+def option(
+    option_type: Literal[Option.SET_SSL_CERT_LOCATIONS],
+    arg1: str | bytes | None,  # cert_file
+    arg2: str | bytes | None,  # cert_dir
+) -> None: ...
+
+
+@overload
+def option(
+    option_type: Literal[
+        Option.ENABLE_CACHING,
+        Option.ENABLE_STRICT_OBJECT_CREATION,
+        Option.ENABLE_STRICT_SYMBOLIC_REF_CREATION,
+        Option.ENABLE_OFS_DELTA,
+        Option.ENABLE_FSYNC_GITDIR,
+        Option.ENABLE_STRICT_HASH_VERIFICATION,
+        Option.ENABLE_UNSAVED_INDEX_SAFETY,
+        Option.DISABLE_PACK_KEEP_FILE_CHECKS,
+        Option.SET_OWNER_VALIDATION,
+    ],
+    arg1: bool,  # value
+) -> None: ...
+
+
+@overload
+def option(option_type: Literal[Option.GET_OWNER_VALIDATION]) -> bool: ...
+
+
+@overload
+def option(
+    option_type: Literal[
+        Option.GET_TEMPLATE_PATH,
+        Option.GET_USER_AGENT,
+        Option.GET_HOMEDIR,
+        Option.GET_USER_AGENT_PRODUCT,
+    ],
+) -> str | None: ...
+
+
+@overload
+def option(
+    option_type: Literal[
+        Option.SET_TEMPLATE_PATH,
+        Option.SET_USER_AGENT,
+        Option.SET_SSL_CIPHERS,
+        Option.SET_HOMEDIR,
+        Option.SET_USER_AGENT_PRODUCT,
+    ],
+    arg1: str | bytes,  # value
+) -> None: ...
+
+
+@overload
+def option(
+    option_type: Literal[
+        Option.GET_WINDOWS_SHAREMODE,
+        Option.GET_PACK_MAX_OBJECTS,
+        Option.GET_SERVER_CONNECT_TIMEOUT,
+        Option.GET_SERVER_TIMEOUT,
+    ],
+) -> int: ...
+
+
+@overload
+def option(
+    option_type: Literal[
+        Option.SET_WINDOWS_SHAREMODE,
+        Option.SET_PACK_MAX_OBJECTS,
+        Option.ENABLE_HTTP_EXPECT_CONTINUE,
+        Option.SET_ODB_PACKED_PRIORITY,
+        Option.SET_ODB_LOOSE_PRIORITY,
+        Option.SET_SERVER_CONNECT_TIMEOUT,
+        Option.SET_SERVER_TIMEOUT,
+    ],
+    arg1: int,  # value
+) -> None: ...
+
+
+@overload
+def option(option_type: Literal[Option.GET_EXTENSIONS]) -> list[str]: ...
+
+
+@overload
+def option(
+    option_type: Literal[Option.SET_EXTENSIONS],
+    arg1: list[str],  # extensions
+    arg2: int,  # length
+) -> None: ...
+
+
+@overload
+def option(
+    option_type: Literal[Option.ADD_SSL_X509_CERT],
+    arg1: str | bytes,  # certificate
+) -> None: ...
+
+
+# Fallback overload for generic Option values (used in tests)
+@overload
+def option(option_type: Option, arg1: Any = ..., arg2: Any = ...) -> Any: ...
+
+
+def option(option_type: Option, arg1: Any = NOT_PASSED, arg2: Any = NOT_PASSED) -> Any:
+    """
+    Get or set a libgit2 option.
+
+    Parameters:
+
+    GIT_OPT_GET_SEARCH_PATH, level
+        Get the config search path for the given level.
+
+    GIT_OPT_SET_SEARCH_PATH, level, path
+        Set the config search path for the given level.
+
+    GIT_OPT_GET_MWINDOW_SIZE
+        Get the maximum mmap window size.
+
+    GIT_OPT_SET_MWINDOW_SIZE, size
+        Set the maximum mmap window size.
+
+    GIT_OPT_GET_MWINDOW_FILE_LIMIT
+        Get the maximum number of files that will be mapped at any time by the library.
+
+    GIT_OPT_SET_MWINDOW_FILE_LIMIT, size
+        Set the maximum number of files that can be mapped at any time by the library. The default (0) is unlimited.
+
+    GIT_OPT_GET_OWNER_VALIDATION
+        Gets the owner validation setting for repository directories.
+
+    GIT_OPT_SET_OWNER_VALIDATION, enabled
+        Set that repository directories should be owned by the current user.
+        The default is to validate ownership.
+
+    GIT_OPT_GET_TEMPLATE_PATH
+        Get the default template path.
+
+    GIT_OPT_SET_TEMPLATE_PATH, path
+        Set the default template path.
+
+    GIT_OPT_GET_USER_AGENT
+        Get the user agent string.
+
+    GIT_OPT_SET_USER_AGENT, user_agent
+        Set the user agent string.
+
+    GIT_OPT_GET_PACK_MAX_OBJECTS
+        Get the maximum number of objects to include in a pack.
+
+    GIT_OPT_SET_PACK_MAX_OBJECTS, count
+        Set the maximum number of objects to include in a pack.
+    """
+
+    result: str | None | list[str]
+
+    if option_type in (
+        C.GIT_OPT_GET_MWINDOW_SIZE,
+        C.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT,
+        C.GIT_OPT_GET_MWINDOW_FILE_LIMIT,
+    ):
+        check_args(option_type, arg1, arg2, 0)
+
+        size_ptr = ffi.new('size_t *')
+        err = C.git_libgit2_opts(option_type, size_ptr)
+        check_error(err)
+        return size_ptr[0]
+
+    elif option_type in (
+        C.GIT_OPT_SET_MWINDOW_SIZE,
+        C.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,
+        C.GIT_OPT_SET_MWINDOW_FILE_LIMIT,
+    ):
+        check_args(option_type, arg1, arg2, 1)
+
+        if not isinstance(arg1, int):
+            raise TypeError(f'option value must be an integer, not {type(arg1)}')
+        size = arg1
+        if size < 0:
+            raise ValueError('size must be non-negative')
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('size_t', size))
+        check_error(err)
+        return None
+
+    elif option_type == C.GIT_OPT_GET_SEARCH_PATH:
+        check_args(option_type, arg1, arg2, 1)
+
+        level = int(arg1)  # Convert enum to int
+        buf = ffi.new('git_buf *')
+        err = C.git_libgit2_opts(option_type, ffi.cast('int', level), buf)
+        check_error(err)
+
+        try:
+            if buf.ptr != ffi.NULL:
+                result = to_str(ffi.string(buf.ptr))
+            else:
+                result = None
+        finally:
+            C.git_buf_dispose(buf)
+
+        return result
+
+    elif option_type == C.GIT_OPT_SET_SEARCH_PATH:
+        check_args(option_type, arg1, arg2, 2)
+
+        level = int(arg1)  # Convert enum to int
+        path = arg2
+
+        path_cdata: ArrayC[char] | NULL_TYPE
+        if path is None:
+            path_cdata = ffi.NULL
+        else:
+            path_bytes = to_bytes(path)
+            path_cdata = ffi.new('char[]', path_bytes)
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('int', level), path_cdata)
+        check_error(err)
+        return None
+
+    elif option_type == C.GIT_OPT_SET_CACHE_OBJECT_LIMIT:
+        check_args(option_type, arg1, arg2, 2)
+
+        object_type = int(arg1)  # Convert enum to int
+        if not isinstance(arg2, int):
+            raise TypeError(
+                f'option value must be an integer, not {type(arg2).__name__}'
+            )
+        size = arg2
+        if size < 0:
+            raise ValueError('size must be non-negative')
+
+        err = C.git_libgit2_opts(
+            option_type, ffi.cast('int', object_type), ffi.cast('size_t', size)
+        )
+        check_error(err)
+        return None
+
+    elif option_type == C.GIT_OPT_SET_CACHE_MAX_SIZE:
+        check_args(option_type, arg1, arg2, 1)
+
+        size = arg1
+        if not isinstance(size, int):
+            raise TypeError(
+                f'option value must be an integer, not {type(size).__name__}'
+            )
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('ssize_t', size))
+        check_error(err)
+        return None
+
+    elif option_type == C.GIT_OPT_GET_CACHED_MEMORY:
+        check_args(option_type, arg1, arg2, 0)
+
+        current_ptr = ffi.new('ssize_t *')
+        allowed_ptr = ffi.new('ssize_t *')
+        err = C.git_libgit2_opts(option_type, current_ptr, allowed_ptr)
+        check_error(err)
+        return (current_ptr[0], allowed_ptr[0])
+
+    elif option_type == C.GIT_OPT_SET_SSL_CERT_LOCATIONS:
+        check_args(option_type, arg1, arg2, 2)
+
+        cert_file = arg1
+        cert_dir = arg2
+
+        cert_file_cdata: ArrayC[char] | NULL_TYPE
+        if cert_file is None:
+            cert_file_cdata = ffi.NULL
+        else:
+            cert_file_bytes = to_bytes(cert_file)
+            cert_file_cdata = ffi.new('char[]', cert_file_bytes)
+
+        cert_dir_cdata: ArrayC[char] | NULL_TYPE
+        if cert_dir is None:
+            cert_dir_cdata = ffi.NULL
+        else:
+            cert_dir_bytes = to_bytes(cert_dir)
+            cert_dir_cdata = ffi.new('char[]', cert_dir_bytes)
+
+        err = C.git_libgit2_opts(option_type, cert_file_cdata, cert_dir_cdata)
+        check_error(err)
+        return None
+
+    # Handle boolean/int enable/disable options
+    elif option_type in (
+        C.GIT_OPT_ENABLE_CACHING,
+        C.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION,
+        C.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION,
+        C.GIT_OPT_ENABLE_OFS_DELTA,
+        C.GIT_OPT_ENABLE_FSYNC_GITDIR,
+        C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION,
+        C.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY,
+        C.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS,
+        C.GIT_OPT_SET_OWNER_VALIDATION,
+    ):
+        check_args(option_type, arg1, arg2, 1)
+
+        enabled = arg1
+        # Convert to int (0 or 1)
+        value = 1 if enabled else 0
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('int', value))
+        check_error(err)
+        return None
+
+    elif option_type == C.GIT_OPT_GET_OWNER_VALIDATION:
+        check_args(option_type, arg1, arg2, 0)
+
+        enabled_ptr = ffi.new('int *')
+        err = C.git_libgit2_opts(option_type, enabled_ptr)
+        check_error(err)
+        return bool(enabled_ptr[0])
+
+    elif option_type == C.GIT_OPT_GET_TEMPLATE_PATH:
+        check_args(option_type, arg1, arg2, 0)
+
+        buf = ffi.new('git_buf *')
+        err = C.git_libgit2_opts(option_type, buf)
+        check_error(err)
+
+        try:
+            if buf.ptr != ffi.NULL:
+                result = to_str(ffi.string(buf.ptr))
+            else:
+                result = None
+        finally:
+            C.git_buf_dispose(buf)
+
+        return result
+
+    elif option_type == C.GIT_OPT_SET_TEMPLATE_PATH:
+        check_args(option_type, arg1, arg2, 1)
+
+        path = arg1
+        template_path_cdata: ArrayC[char] | NULL_TYPE
+        if path is None:
+            template_path_cdata = ffi.NULL
+        else:
+            path_bytes = to_bytes(path)
+            template_path_cdata = ffi.new('char[]', path_bytes)
+
+        err = C.git_libgit2_opts(option_type, template_path_cdata)
+        check_error(err)
+        return None
+
+    elif option_type == C.GIT_OPT_GET_USER_AGENT:
+        check_args(option_type, arg1, arg2, 0)
+
+        buf = ffi.new('git_buf *')
+        err = C.git_libgit2_opts(option_type, buf)
+        check_error(err)
+
+        try:
+            if buf.ptr != ffi.NULL:
+                result = to_str(ffi.string(buf.ptr))
+            else:
+                result = None
+        finally:
+            C.git_buf_dispose(buf)
+
+        return result
+
+    elif option_type == C.GIT_OPT_SET_USER_AGENT:
+        check_args(option_type, arg1, arg2, 1)
+
+        agent = arg1
+        agent_bytes = to_bytes(agent)
+        agent_cdata = ffi.new('char[]', agent_bytes)
+
+        err = C.git_libgit2_opts(option_type, agent_cdata)
+        check_error(err)
+        return None
+
+    elif option_type == C.GIT_OPT_SET_SSL_CIPHERS:
+        check_args(option_type, arg1, arg2, 1)
+
+        ciphers = arg1
+        ciphers_bytes = to_bytes(ciphers)
+        ciphers_cdata = ffi.new('char[]', ciphers_bytes)
+
+        err = C.git_libgit2_opts(option_type, ciphers_cdata)
+        check_error(err)
+        return None
+
+    # Handle GET_WINDOWS_SHAREMODE
+    elif option_type == C.GIT_OPT_GET_WINDOWS_SHAREMODE:
+        check_args(option_type, arg1, arg2, 0)
+
+        value_ptr = ffi.new('unsigned int *')
+        err = C.git_libgit2_opts(option_type, value_ptr)
+        check_error(err)
+        return value_ptr[0]
+
+    # Handle SET_WINDOWS_SHAREMODE
+    elif option_type == C.GIT_OPT_SET_WINDOWS_SHAREMODE:
+        check_args(option_type, arg1, arg2, 1)
+
+        if not isinstance(arg1, int):
+            raise TypeError(
+                f'option value must be an integer, not {type(arg1).__name__}'
+            )
+        value = arg1
+        if value < 0:
+            raise ValueError('value must be non-negative')
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('unsigned int', value))
+        check_error(err)
+        return None
+
+    # Handle GET_PACK_MAX_OBJECTS
+    elif option_type == C.GIT_OPT_GET_PACK_MAX_OBJECTS:
+        check_args(option_type, arg1, arg2, 0)
+
+        size_ptr = ffi.new('size_t *')
+        err = C.git_libgit2_opts(option_type, size_ptr)
+        check_error(err)
+        return size_ptr[0]
+
+    # Handle SET_PACK_MAX_OBJECTS
+    elif option_type == C.GIT_OPT_SET_PACK_MAX_OBJECTS:
+        check_args(option_type, arg1, arg2, 1)
+
+        if not isinstance(arg1, int):
+            raise TypeError(
+                f'option value must be an integer, not {type(arg1).__name__}'
+            )
+        size = arg1
+        if size < 0:
+            raise ValueError('size must be non-negative')
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('size_t', size))
+        check_error(err)
+        return None
+
+    # Handle ENABLE_HTTP_EXPECT_CONTINUE
+    elif option_type == C.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE:
+        check_args(option_type, arg1, arg2, 1)
+
+        enabled = arg1
+        # Convert to int (0 or 1)
+        value = 1 if enabled else 0
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('int', value))
+        check_error(err)
+        return None
+
+    # Handle SET_ODB_PACKED_PRIORITY
+    elif option_type == C.GIT_OPT_SET_ODB_PACKED_PRIORITY:
+        check_args(option_type, arg1, arg2, 1)
+
+        if not isinstance(arg1, int):
+            raise TypeError(
+                f'option value must be an integer, not {type(arg1).__name__}'
+            )
+        priority = arg1
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('int', priority))
+        check_error(err)
+        return None
+
+    # Handle SET_ODB_LOOSE_PRIORITY
+    elif option_type == C.GIT_OPT_SET_ODB_LOOSE_PRIORITY:
+        check_args(option_type, arg1, arg2, 1)
+
+        if not isinstance(arg1, int):
+            raise TypeError(
+                f'option value must be an integer, not {type(arg1).__name__}'
+            )
+        priority = arg1
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('int', priority))
+        check_error(err)
+        return None
+
+    # Handle GET_EXTENSIONS
+    elif option_type == C.GIT_OPT_GET_EXTENSIONS:
+        check_args(option_type, arg1, arg2, 0)
+
+        # GET_EXTENSIONS expects a git_strarray pointer
+        strarray = ffi.new('git_strarray *')
+        err = C.git_libgit2_opts(option_type, strarray)
+        check_error(err)
+
+        result = []
+        try:
+            if strarray.strings != ffi.NULL:
+                # Cast to the non-NULL type for type checking
+                strings = cast('ArrayC[char_pointer]', strarray.strings)
+                for i in range(strarray.count):
+                    if strings[i] != ffi.NULL:
+                        result.append(to_str(ffi.string(strings[i])))
+        finally:
+            # Must dispose of the strarray to free the memory
+            C.git_strarray_dispose(strarray)
+
+        return result
+
+    # Handle SET_EXTENSIONS
+    elif option_type == C.GIT_OPT_SET_EXTENSIONS:
+        check_args(option_type, arg1, arg2, 2)
+
+        extensions = arg1
+        length = arg2
+
+        if not isinstance(extensions, list):
+            raise TypeError('extensions must be a list of strings')
+        if not isinstance(length, int):
+            raise TypeError('length must be an integer')
+
+        # Create array of char pointers
+        # libgit2 will make its own copies with git__strdup
+        ext_array: ArrayC[char_pointer] = ffi.new('char *[]', len(extensions))
+        ext_strings: list[ArrayC[char]] = []  # Keep references during the call
+
+        for i, ext in enumerate(extensions):
+            ext_bytes = to_bytes(ext)
+            ext_string: ArrayC[char] = ffi.new('char[]', ext_bytes)
+            ext_strings.append(ext_string)
+            ext_array[i] = ffi.cast('char *', ext_string)
+
+        err = C.git_libgit2_opts(option_type, ext_array, ffi.cast('size_t', length))
+        check_error(err)
+        return None
+
+    # Handle GET_HOMEDIR
+    elif option_type == C.GIT_OPT_GET_HOMEDIR:
+        check_args(option_type, arg1, arg2, 0)
+
+        buf = ffi.new('git_buf *')
+        err = C.git_libgit2_opts(option_type, buf)
+        check_error(err)
+
+        try:
+            if buf.ptr != ffi.NULL:
+                result = to_str(ffi.string(buf.ptr))
+            else:
+                result = None
+        finally:
+            C.git_buf_dispose(buf)
+
+        return result
+
+    # Handle SET_HOMEDIR
+    elif option_type == C.GIT_OPT_SET_HOMEDIR:
+        check_args(option_type, arg1, arg2, 1)
+
+        path = arg1
+        homedir_cdata: ArrayC[char] | NULL_TYPE
+        if path is None:
+            homedir_cdata = ffi.NULL
+        else:
+            path_bytes = to_bytes(path)
+            homedir_cdata = ffi.new('char[]', path_bytes)
+
+        err = C.git_libgit2_opts(option_type, homedir_cdata)
+        check_error(err)
+        return None
+
+    # Handle GET_SERVER_CONNECT_TIMEOUT
+    elif option_type == C.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT:
+        check_args(option_type, arg1, arg2, 0)
+
+        timeout_ptr = ffi.new('int *')
+        err = C.git_libgit2_opts(option_type, timeout_ptr)
+        check_error(err)
+        return timeout_ptr[0]
+
+    # Handle SET_SERVER_CONNECT_TIMEOUT
+    elif option_type == C.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT:
+        check_args(option_type, arg1, arg2, 1)
+
+        if not isinstance(arg1, int):
+            raise TypeError(
+                f'option value must be an integer, not {type(arg1).__name__}'
+            )
+        timeout = arg1
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('int', timeout))
+        check_error(err)
+        return None
+
+    # Handle GET_SERVER_TIMEOUT
+    elif option_type == C.GIT_OPT_GET_SERVER_TIMEOUT:
+        check_args(option_type, arg1, arg2, 0)
+
+        timeout_ptr = ffi.new('int *')
+        err = C.git_libgit2_opts(option_type, timeout_ptr)
+        check_error(err)
+        return timeout_ptr[0]
+
+    # Handle SET_SERVER_TIMEOUT
+    elif option_type == C.GIT_OPT_SET_SERVER_TIMEOUT:
+        check_args(option_type, arg1, arg2, 1)
+
+        if not isinstance(arg1, int):
+            raise TypeError(
+                f'option value must be an integer, not {type(arg1).__name__}'
+            )
+        timeout = arg1
+
+        err = C.git_libgit2_opts(option_type, ffi.cast('int', timeout))
+        check_error(err)
+        return None
+
+    # Handle GET_USER_AGENT_PRODUCT
+    elif option_type == C.GIT_OPT_GET_USER_AGENT_PRODUCT:
+        check_args(option_type, arg1, arg2, 0)
+
+        buf = ffi.new('git_buf *')
+        err = C.git_libgit2_opts(option_type, buf)
+        check_error(err)
+
+        try:
+            if buf.ptr != ffi.NULL:
+                result = to_str(ffi.string(buf.ptr))
+            else:
+                result = None
+        finally:
+            C.git_buf_dispose(buf)
+
+        return result
+
+    # Handle SET_USER_AGENT_PRODUCT
+    elif option_type == C.GIT_OPT_SET_USER_AGENT_PRODUCT:
+        check_args(option_type, arg1, arg2, 1)
+
+        product = arg1
+        product_bytes = to_bytes(product)
+        product_cdata = ffi.new('char[]', product_bytes)
+
+        err = C.git_libgit2_opts(option_type, product_cdata)
+        check_error(err)
+        return None
+
+    # Not implemented - ADD_SSL_X509_CERT requires directly binding with OpenSSL
+    # as the API works accepts a X509* struct.  Use GIT_OPT_SET_SSL_CERT_LOCATIONS
+    # instead.
+    elif option_type == C.GIT_OPT_ADD_SSL_X509_CERT:
+        raise NotImplementedError('Use GIT_OPT_SET_SSL_CERT_LOCATIONS instead')
+
+    # Not implemented - SET_ALLOCATOR is not feasible from Python level
+    # because it requires providing C function pointers for memory management
+    # (malloc, free, etc.) that must handle raw memory at the C level,
+    # which cannot be safely implemented in pure Python.
+    elif option_type == C.GIT_OPT_SET_ALLOCATOR:
+        raise NotImplementedError('Setting a custom allocator not possible from Python')
+
+    else:
+        raise ValueError(f'Invalid option {option_type}')
diff -pruN 1.17.0-2/pygit2/packbuilder.py 1.18.2-1/pygit2/packbuilder.py
--- 1.17.0-2/pygit2/packbuilder.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/packbuilder.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,15 +23,21 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from os import PathLike
+from typing import TYPE_CHECKING
 
 # Import from pygit2
 from .errors import check_error
-from .ffi import ffi, C
+from .ffi import C, ffi
 from .utils import to_bytes
 
+if TYPE_CHECKING:
+    from pygit2 import Oid, Repository
+    from pygit2.repository import BaseRepository
+
 
 class PackBuilder:
-    def __init__(self, repo):
+    def __init__(self, repo: 'Repository | BaseRepository') -> None:
         cpackbuilder = ffi.new('git_packbuilder **')
         err = C.git_packbuilder_new(cpackbuilder, repo._repo)
         check_error(err)
@@ -41,39 +47,41 @@ class PackBuilder:
         self._cpackbuilder = cpackbuilder
 
     @property
-    def _pointer(self):
+    def _pointer(self) -> bytes:
         return bytes(ffi.buffer(self._packbuilder)[:])
 
-    def __del__(self):
+    def __del__(self) -> None:
         C.git_packbuilder_free(self._packbuilder)
 
-    def __len__(self):
+    def __len__(self) -> int:
         return C.git_packbuilder_object_count(self._packbuilder)
 
     @staticmethod
-    def __convert_object_to_oid(oid):
+    def __convert_object_to_oid(oid: 'Oid') -> 'ffi.GitOidC':
         git_oid = ffi.new('git_oid *')
         ffi.buffer(git_oid)[:] = oid.raw[:]
         return git_oid
 
-    def add(self, oid):
+    def add(self, oid: 'Oid') -> None:
         git_oid = self.__convert_object_to_oid(oid)
         err = C.git_packbuilder_insert(self._packbuilder, git_oid, ffi.NULL)
         check_error(err)
 
-    def add_recur(self, oid):
+    def add_recur(self, oid: 'Oid') -> None:
         git_oid = self.__convert_object_to_oid(oid)
         err = C.git_packbuilder_insert_recur(self._packbuilder, git_oid, ffi.NULL)
         check_error(err)
 
-    def set_threads(self, n_threads):
+    def set_threads(self, n_threads: int) -> int:
         return C.git_packbuilder_set_threads(self._packbuilder, n_threads)
 
-    def write(self, path=None):
-        path = ffi.NULL if path is None else to_bytes(path)
-        err = C.git_packbuilder_write(self._packbuilder, path, 0, ffi.NULL, ffi.NULL)
+    def write(self, path: str | bytes | PathLike[str] | None = None) -> None:
+        path_bytes = ffi.NULL if path is None else to_bytes(path)
+        err = C.git_packbuilder_write(
+            self._packbuilder, path_bytes, 0, ffi.NULL, ffi.NULL
+        )
         check_error(err)
 
     @property
-    def written_objects_count(self):
+    def written_objects_count(self) -> int:
         return C.git_packbuilder_written(self._packbuilder)
diff -pruN 1.17.0-2/pygit2/references.py 1.18.2-1/pygit2/references.py
--- 1.17.0-2/pygit2/references.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/references.py	2025-08-16 11:34:29.000000000 +0000
@@ -24,29 +24,34 @@
 # Boston, MA 02110-1301, USA.
 
 from __future__ import annotations
+
+from collections.abc import Iterator
 from typing import TYPE_CHECKING
 
+from pygit2 import Oid
+
 from .enums import ReferenceFilter
 
 # Need BaseRepository for type hints, but don't let it cause a circular dependency
 if TYPE_CHECKING:
+    from ._pygit2 import Reference
     from .repository import BaseRepository
 
 
 class References:
-    def __init__(self, repository: BaseRepository):
+    def __init__(self, repository: BaseRepository) -> None:
         self._repository = repository
 
-    def __getitem__(self, name: str):
+    def __getitem__(self, name: str) -> 'Reference':
         return self._repository.lookup_reference(name)
 
-    def get(self, key: str):
+    def get(self, key: str) -> 'Reference' | None:
         try:
             return self[key]
         except KeyError:
             return None
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[str]:
         iter = self._repository.references_iterator_init()
         while True:
             ref = self._repository.references_iterator_next(iter)
@@ -55,7 +60,9 @@ class References:
             else:
                 return
 
-    def iterator(self, references_return_type: ReferenceFilter = ReferenceFilter.ALL):
+    def iterator(
+        self, references_return_type: ReferenceFilter = ReferenceFilter.ALL
+    ) -> Iterator['Reference']:
         """Creates a new iterator and fetches references for a given repository.
 
         Can also filter and pass all refs or only branches or only tags.
@@ -87,18 +94,18 @@ class References:
             else:
                 return
 
-    def create(self, name, target, force=False):
+    def create(self, name: str, target: Oid | str, force: bool = False) -> 'Reference':
         return self._repository.create_reference(name, target, force)
 
-    def delete(self, name: str):
+    def delete(self, name: str) -> None:
         self[name].delete()
 
-    def __contains__(self, name: str):
+    def __contains__(self, name: str) -> bool:
         return self.get(name) is not None
 
     @property
-    def objects(self):
+    def objects(self) -> list['Reference']:
         return self._repository.listall_reference_objects()
 
-    def compress(self):
+    def compress(self) -> None:
         return self._repository.compress_references()
diff -pruN 1.17.0-2/pygit2/refspec.py 1.18.2-1/pygit2/refspec.py
--- 1.17.0-2/pygit2/refspec.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/refspec.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,36 +23,38 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from collections.abc import Callable
+
 # Import from pygit2
 from .errors import check_error
-from .ffi import ffi, C
+from .ffi import C, ffi
 from .utils import to_bytes
 
 
 class Refspec:
     """The constructor is for internal use only."""
 
-    def __init__(self, owner, ptr):
+    def __init__(self, owner, ptr) -> None:
         self._owner = owner
         self._refspec = ptr
 
     @property
-    def src(self):
+    def src(self) -> str:
         """Source or lhs of the refspec"""
         return ffi.string(C.git_refspec_src(self._refspec)).decode('utf-8')
 
     @property
-    def dst(self):
-        """Destinaton or rhs of the refspec"""
+    def dst(self) -> str:
+        """Destination or rhs of the refspec"""
         return ffi.string(C.git_refspec_dst(self._refspec)).decode('utf-8')
 
     @property
-    def force(self):
+    def force(self) -> bool:
         """Whether this refspeca llows non-fast-forward updates"""
         return bool(C.git_refspec_force(self._refspec))
 
     @property
-    def string(self):
+    def string(self) -> str:
         """String which was used to create this refspec"""
         return ffi.string(C.git_refspec_string(self._refspec)).decode('utf-8')
 
@@ -61,18 +63,18 @@ class Refspec:
         """Direction of this refspec (fetch or push)"""
         return C.git_refspec_direction(self._refspec)
 
-    def src_matches(self, ref):
+    def src_matches(self, ref: str) -> bool:
         """Return True if the given string matches the source of this refspec,
         False otherwise.
         """
         return bool(C.git_refspec_src_matches(self._refspec, to_bytes(ref)))
 
-    def dst_matches(self, ref):
+    def dst_matches(self, ref: str) -> bool:
         """Return True if the given string matches the destination of this
         refspec, False otherwise."""
         return bool(C.git_refspec_dst_matches(self._refspec, to_bytes(ref)))
 
-    def _transform(self, ref, fn):
+    def _transform(self, ref: str, fn: Callable) -> str:
         buf = ffi.new('git_buf *', (ffi.NULL, 0))
         err = fn(buf, self._refspec, to_bytes(ref))
         check_error(err)
@@ -82,13 +84,13 @@ class Refspec:
         finally:
             C.git_buf_dispose(buf)
 
-    def transform(self, ref):
+    def transform(self, ref: str) -> str:
         """Transform a reference name according to this refspec from the lhs to
         the rhs. Return an string.
         """
         return self._transform(ref, C.git_refspec_transform)
 
-    def rtransform(self, ref):
+    def rtransform(self, ref: str) -> str:
         """Transform a reference name according to this refspec from the lhs to
         the rhs. Return an string.
         """
diff -pruN 1.17.0-2/pygit2/remotes.py 1.18.2-1/pygit2/remotes.py
--- 1.17.0-2/pygit2/remotes.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/remotes.py	2025-08-16 11:34:29.000000000 +0000
@@ -24,27 +24,103 @@
 # Boston, MA 02110-1301, USA.
 
 from __future__ import annotations
-from typing import TYPE_CHECKING
+
+import warnings
+from collections.abc import Generator, Iterator
+from typing import TYPE_CHECKING, Any, Literal
 
 # Import from pygit2
+from pygit2 import RemoteCallbacks
+
+from . import utils
 from ._pygit2 import Oid
-from .callbacks import git_fetch_options, git_push_options, git_remote_callbacks
+from .callbacks import (
+    git_fetch_options,
+    git_proxy_options,
+    git_push_options,
+    git_remote_callbacks,
+)
 from .enums import FetchPrune
 from .errors import check_error
-from .ffi import ffi, C
+from .ffi import C, ffi
 from .refspec import Refspec
-from . import utils
-from .utils import maybe_string, to_bytes, strarray_to_strings, StrArray
+from .utils import StrArray, maybe_string, strarray_to_strings, to_bytes
 
 # Need BaseRepository for type hints, but don't let it cause a circular dependency
 if TYPE_CHECKING:
+    from ._libgit2.ffi import GitRemoteC, char_pointer
     from .repository import BaseRepository
 
 
+class RemoteHead:
+    """
+    Description of a reference advertised by a remote server,
+    given out on `Remote.list_heads` calls.
+    """
+
+    local: bool
+    """Available locally"""
+
+    oid: Oid
+
+    loid: Oid
+
+    name: str | None
+
+    symref_target: str | None
+    """
+    If the server sent a symref mapping for this ref, this will
+    point to the target. 
+    """
+
+    def __init__(self, c_struct: Any) -> None:
+        self.local = bool(c_struct.local)
+        self.oid = Oid(raw=bytes(ffi.buffer(c_struct.oid.id)[:]))
+        self.loid = Oid(raw=bytes(ffi.buffer(c_struct.loid.id)[:]))
+        self.name = maybe_string(c_struct.name)
+        self.symref_target = maybe_string(c_struct.symref_target)
+
+
+class PushUpdate:
+    """
+    Represents an update which will be performed on the remote during push.
+    """
+
+    src_refname: str
+    """The source name of the reference"""
+
+    dst_refname: str
+    """The name of the reference to update on the server"""
+
+    src: Oid
+    """The current target of the reference"""
+
+    dst: Oid
+    """The new target for the reference"""
+
+    def __init__(self, c_struct: Any) -> None:
+        src_refname = maybe_string(c_struct.src_refname)
+        dst_refname = maybe_string(c_struct.dst_refname)
+        assert src_refname is not None, 'libgit2 returned null src_refname'
+        assert dst_refname is not None, 'libgit2 returned null dst_refname'
+        self.src_refname = src_refname
+        self.dst_refname = dst_refname
+        self.src = Oid(raw=bytes(ffi.buffer(c_struct.src.id)[:]))
+        self.dst = Oid(raw=bytes(ffi.buffer(c_struct.dst.id)[:]))
+
+
 class TransferProgress:
     """Progress downloading and indexing data during a fetch."""
 
-    def __init__(self, tp):
+    total_objects: int
+    indexed_objects: int
+    received_objects: int
+    local_objects: int
+    total_deltas: int
+    indexed_deltas: int
+    received_bytes: int
+
+    def __init__(self, tp: Any) -> None:
         self.total_objects = tp.total_objects
         """Total number of objects to download"""
 
@@ -68,34 +144,39 @@ class TransferProgress:
 
 
 class Remote:
-    def __init__(self, repo: BaseRepository, ptr):
+    def __init__(self, repo: BaseRepository, ptr: 'GitRemoteC') -> None:
         """The constructor is for internal use only."""
         self._repo = repo
         self._remote = ptr
         self._stored_exception = None
 
-    def __del__(self):
+    def __del__(self) -> None:
         C.git_remote_free(self._remote)
 
     @property
-    def name(self):
+    def name(self) -> str | None:
         """Name of the remote"""
 
         return maybe_string(C.git_remote_name(self._remote))
 
     @property
-    def url(self):
+    def url(self) -> str | None:
         """Url of the remote"""
 
         return maybe_string(C.git_remote_url(self._remote))
 
     @property
-    def push_url(self):
+    def push_url(self) -> str | None:
         """Push url of the remote"""
 
         return maybe_string(C.git_remote_pushurl(self._remote))
 
-    def connect(self, callbacks=None, direction=C.GIT_DIRECTION_FETCH, proxy=None):
+    def connect(
+        self,
+        callbacks: RemoteCallbacks | None = None,
+        direction: int = C.GIT_DIRECTION_FETCH,
+        proxy: None | bool | str = None,
+    ) -> None:
         """Connect to the remote.
 
         Parameters:
@@ -107,24 +188,26 @@ class Remote:
             * `True` to enable automatic proxy detection
             * an url to a proxy (`http://proxy.example.org:3128/`)
         """
-        proxy_opts = ffi.new('git_proxy_options *')
-        C.git_proxy_options_init(proxy_opts, C.GIT_PROXY_OPTIONS_VERSION)
-        self.__set_proxy(proxy_opts, proxy)
-        with git_remote_callbacks(callbacks) as payload:
-            err = C.git_remote_connect(
-                self._remote, direction, payload.remote_callbacks, proxy_opts, ffi.NULL
-            )
-            payload.check_error(err)
+        with git_proxy_options(self, proxy=proxy) as proxy_opts:
+            with git_remote_callbacks(callbacks) as payload:
+                err = C.git_remote_connect(
+                    self._remote,
+                    direction,
+                    payload.remote_callbacks,
+                    proxy_opts,
+                    ffi.NULL,
+                )
+                payload.check_error(err)
 
     def fetch(
         self,
-        refspecs=None,
-        message=None,
-        callbacks=None,
+        refspecs: list[str] | None = None,
+        message: str | None = None,
+        callbacks: RemoteCallbacks | None = None,
         prune: FetchPrune = FetchPrune.UNSPECIFIED,
-        proxy=None,
-        depth=0,
-    ):
+        proxy: None | Literal[True] | str = None,
+        depth: int = 0,
+    ) -> TransferProgress:
         """Perform a fetch against this remote. Returns a <TransferProgress>
         object.
 
@@ -154,73 +237,93 @@ class Remote:
             opts = payload.fetch_options
             opts.prune = prune
             opts.depth = depth
-            self.__set_proxy(opts.proxy_opts, proxy)
-            with StrArray(refspecs) as arr:
-                err = C.git_remote_fetch(self._remote, arr.ptr, opts, to_bytes(message))
-                payload.check_error(err)
+            with git_proxy_options(self, payload.fetch_options.proxy_opts, proxy):
+                with StrArray(refspecs) as arr:
+                    err = C.git_remote_fetch(
+                        self._remote, arr.ptr, opts, to_bytes(message)
+                    )
+                    payload.check_error(err)
 
         return TransferProgress(C.git_remote_stats(self._remote))
 
-    def ls_remotes(self, callbacks=None, proxy=None):
+    def list_heads(
+        self,
+        callbacks: RemoteCallbacks | None = None,
+        proxy: str | None | bool = None,
+        connect: bool = True,
+    ) -> list[RemoteHead]:
         """
-        Return a list of dicts that maps to `git_remote_head` from a
-        `ls_remotes` call.
+        Get the list of references with which the server responds to a new
+        connection.
 
         Parameters:
 
         callbacks : Passed to connect()
 
         proxy : Passed to connect()
+
+        connect : Whether to connect to the remote first. You can pass False
+        if the remote has already connected. The list remains available after
+        disconnecting as long as a new connection is not initiated.
         """
 
-        self.connect(callbacks=callbacks, proxy=proxy)
+        if connect:
+            self.connect(callbacks=callbacks, proxy=proxy)
 
-        refs = ffi.new('git_remote_head ***')
-        refs_len = ffi.new('size_t *')
+        refs_ptr = ffi.new('git_remote_head ***')
+        size_ptr = ffi.new('size_t *')
 
-        err = C.git_remote_ls(refs, refs_len, self._remote)
+        err = C.git_remote_ls(refs_ptr, size_ptr, self._remote)
         check_error(err)
 
-        results = []
-        for i in range(int(refs_len[0])):
-            ref = refs[0][i]
-            local = bool(ref.local)
-            if local:
-                loid = Oid(raw=bytes(ffi.buffer(ref.loid.id)[:]))
-            else:
-                loid = None
-
-            remote = {
-                'local': local,
-                'loid': loid,
-                'name': maybe_string(ref.name),
-                'symref_target': maybe_string(ref.symref_target),
-                'oid': Oid(raw=bytes(ffi.buffer(ref.oid.id)[:])),
-            }
-
-            results.append(remote)
+        num_refs = int(size_ptr[0])
+        results = [RemoteHead(refs_ptr[0][i]) for i in range(num_refs)]
 
         return results
 
-    def prune(self, callbacks=None):
+    def ls_remotes(
+        self,
+        callbacks: RemoteCallbacks | None = None,
+        proxy: str | None | bool = None,
+        connect: bool = True,
+    ) -> list[dict[str, Any]]:
+        """
+        Deprecated interface to list_heads
+        """
+        warnings.warn('Use list_heads', DeprecationWarning)
+
+        heads = self.list_heads(callbacks, proxy, connect)
+
+        return [
+            {
+                'local': h.local,
+                'oid': h.oid,
+                'loid': h.loid if h.local else None,
+                'name': h.name,
+                'symref_target': h.symref_target,
+            }
+            for h in heads
+        ]
+
+    def prune(self, callbacks: RemoteCallbacks | None = None) -> None:
         """Perform a prune against this remote."""
         with git_remote_callbacks(callbacks) as payload:
             err = C.git_remote_prune(self._remote, payload.remote_callbacks)
             payload.check_error(err)
 
     @property
-    def refspec_count(self):
+    def refspec_count(self) -> int:
         """Total number of refspecs in this remote"""
 
         return C.git_remote_refspec_count(self._remote)
 
-    def get_refspec(self, n):
+    def get_refspec(self, n: int) -> Refspec:
         """Return the <Refspec> object at the given position."""
         spec = C.git_remote_get_refspec(self._remote, n)
         return Refspec(self, spec)
 
     @property
-    def fetch_refspecs(self):
+    def fetch_refspecs(self) -> list[str]:
         """Refspecs that will be used for fetching"""
 
         specs = ffi.new('git_strarray *')
@@ -229,7 +332,7 @@ class Remote:
         return strarray_to_strings(specs)
 
     @property
-    def push_refspecs(self):
+    def push_refspecs(self) -> list[str]:
         """Refspecs that will be used for pushing"""
 
         specs = ffi.new('git_strarray *')
@@ -237,7 +340,14 @@ class Remote:
         check_error(err)
         return strarray_to_strings(specs)
 
-    def push(self, specs, callbacks=None, proxy=None, push_options=None):
+    def push(
+        self,
+        specs: list[str],
+        callbacks: RemoteCallbacks | None = None,
+        proxy: None | bool | str = None,
+        push_options: None | list[str] = None,
+        threads: int = 1,
+    ) -> None:
         """
         Push the given refspec to the remote. Raises ``GitError`` on protocol
         error or unpack failure.
@@ -246,13 +356,15 @@ class Remote:
         function will return successfully. Thus it is strongly recommended to
         install a callback, that implements
         :py:meth:`RemoteCallbacks.push_update_reference` and check the passed
-        parameters for successfull operations.
+        parameters for successful operations.
 
         Parameters:
 
         specs : [str]
             Push refspecs to use.
 
+        callbacks :
+
         proxy : None or True or str
             Proxy configuration. Can be one of:
 
@@ -263,27 +375,24 @@ class Remote:
         push_options : [str]
             Push options to send to the server, which passes them to the
             pre-receive as well as the post-receive hook.
+
+        threads : int
+            If the transport being used to push to the remote requires the
+            creation of a pack file, this controls the number of worker threads
+            used by the packbuilder when creating that pack file to be sent to
+            the remote.
+
+            If set to 0, the packbuilder will auto-detect the number of threads
+            to create. The default value is 1.
         """
         with git_push_options(callbacks) as payload:
             opts = payload.push_options
-            self.__set_proxy(opts.proxy_opts, proxy)
-            with StrArray(specs) as refspecs, StrArray(push_options) as pushopts:
-                pushopts.assign_to(opts.remote_push_options)
-                err = C.git_remote_push(self._remote, refspecs.ptr, opts)
-                payload.check_error(err)
-
-    def __set_proxy(self, proxy_opts, proxy):
-        if proxy is None:
-            proxy_opts.type = C.GIT_PROXY_NONE
-        elif proxy is True:
-            proxy_opts.type = C.GIT_PROXY_AUTO
-        elif type(proxy) is str:
-            proxy_opts.type = C.GIT_PROXY_SPECIFIED
-            # Keep url in memory, otherwise memory is freed and bad things happen
-            self.__url = ffi.new('char[]', to_bytes(proxy))
-            proxy_opts.url = self.__url
-        else:
-            raise TypeError('Proxy must be None, True, or a string')
+            opts.pb_parallelism = threads
+            with git_proxy_options(self, payload.push_options.proxy_opts, proxy):
+                with StrArray(specs) as refspecs, StrArray(push_options) as pushopts:
+                    pushopts.assign_to(opts.remote_push_options)
+                    err = C.git_remote_push(self._remote, refspecs.ptr, opts)
+                    payload.check_error(err)
 
 
 class RemoteCollection:
@@ -296,16 +405,16 @@ class RemoteCollection:
     >>> repo.remotes["origin"]
     """
 
-    def __init__(self, repo: BaseRepository):
+    def __init__(self, repo: BaseRepository) -> None:
         self._repo = repo
 
-    def __len__(self):
+    def __len__(self) -> int:
         with utils.new_git_strarray() as names:
             err = C.git_remote_list(names, self._repo._repo)
             check_error(err)
             return names.count
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[Remote]:
         cremote = ffi.new('git_remote **')
         for name in self._ffi_names():
             err = C.git_remote_lookup(cremote, self._repo._repo, name)
@@ -313,7 +422,7 @@ class RemoteCollection:
 
             yield Remote(self._repo, cremote[0])
 
-    def __getitem__(self, name):
+    def __getitem__(self, name: str | int) -> Remote:
         if isinstance(name, int):
             return list(self)[name]
 
@@ -323,19 +432,19 @@ class RemoteCollection:
 
         return Remote(self._repo, cremote[0])
 
-    def _ffi_names(self):
+    def _ffi_names(self) -> Generator['char_pointer', None, None]:
         with utils.new_git_strarray() as names:
             err = C.git_remote_list(names, self._repo._repo)
             check_error(err)
             for i in range(names.count):
-                yield names.strings[i]
+                yield names.strings[i]  # type: ignore[index]
 
-    def names(self):
+    def names(self) -> Generator[str | None, None, None]:
         """An iterator over the names of the available remotes."""
         for name in self._ffi_names():
             yield maybe_string(name)
 
-    def create(self, name, url, fetch=None):
+    def create(self, name: str, url: str, fetch: str | None = None) -> Remote:
         """Create a new remote with the given name and url. Returns a <Remote>
         object.
 
@@ -344,31 +453,31 @@ class RemoteCollection:
         """
         cremote = ffi.new('git_remote **')
 
-        name = to_bytes(name)
-        url = to_bytes(url)
+        name_bytes = to_bytes(name)
+        url_bytes = to_bytes(url)
         if fetch:
-            fetch = to_bytes(fetch)
+            fetch_bytes = to_bytes(fetch)
             err = C.git_remote_create_with_fetchspec(
-                cremote, self._repo._repo, name, url, fetch
+                cremote, self._repo._repo, name_bytes, url_bytes, fetch_bytes
             )
         else:
-            err = C.git_remote_create(cremote, self._repo._repo, name, url)
+            err = C.git_remote_create(cremote, self._repo._repo, name_bytes, url_bytes)
 
         check_error(err)
 
         return Remote(self._repo, cremote[0])
 
-    def create_anonymous(self, url):
+    def create_anonymous(self, url: str) -> Remote:
         """Create a new anonymous (in-memory only) remote with the given URL.
         Returns a <Remote> object.
         """
         cremote = ffi.new('git_remote **')
-        url = to_bytes(url)
-        err = C.git_remote_create_anonymous(cremote, self._repo._repo, url)
+        url_bytes = to_bytes(url)
+        err = C.git_remote_create_anonymous(cremote, self._repo._repo, url_bytes)
         check_error(err)
         return Remote(self._repo, cremote[0])
 
-    def rename(self, name, new_name):
+    def rename(self, name: str, new_name: str) -> list[str]:
         """Rename a remote in the configuration. The refspecs in standard
         format will be renamed.
 
@@ -376,7 +485,7 @@ class RemoteCollection:
         the standard format and thus could not be remapped.
         """
 
-        if not new_name:
+        if not name:
             raise ValueError('Current remote name must be a non-empty string')
 
         if not new_name:
@@ -389,7 +498,7 @@ class RemoteCollection:
         check_error(err)
         return strarray_to_strings(problems)
 
-    def delete(self, name):
+    def delete(self, name: str) -> None:
         """Remove a remote from the configuration
 
         All remote-tracking branches and configuration settings for the remote will be removed.
@@ -397,17 +506,17 @@ class RemoteCollection:
         err = C.git_remote_delete(self._repo._repo, to_bytes(name))
         check_error(err)
 
-    def set_url(self, name, url):
+    def set_url(self, name: str, url: str) -> None:
         """Set the URL for a remote"""
         err = C.git_remote_set_url(self._repo._repo, to_bytes(name), to_bytes(url))
         check_error(err)
 
-    def set_push_url(self, name, url):
+    def set_push_url(self, name: str, url: str) -> None:
         """Set the push-URL for a remote"""
         err = C.git_remote_set_pushurl(self._repo._repo, to_bytes(name), to_bytes(url))
         check_error(err)
 
-    def add_fetch(self, name, refspec):
+    def add_fetch(self, name: str, refspec: str) -> None:
         """Add a fetch refspec (str) to the remote"""
 
         err = C.git_remote_add_fetch(
@@ -415,7 +524,7 @@ class RemoteCollection:
         )
         check_error(err)
 
-    def add_push(self, name, refspec):
+    def add_push(self, name: str, refspec: str) -> None:
         """Add a push refspec (str) to the remote"""
 
         err = C.git_remote_add_push(self._repo._repo, to_bytes(name), to_bytes(refspec))
diff -pruN 1.17.0-2/pygit2/repository.py 1.18.2-1/pygit2/repository.py
--- 1.17.0-2/pygit2/repository.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/repository.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,27 +23,43 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+import tarfile
+import warnings
+from collections.abc import Callable, Iterator
 from io import BytesIO
-from os import PathLike
+from pathlib import Path
 from string import hexdigits
 from time import time
-import tarfile
-import typing
+from typing import TYPE_CHECKING, Optional, overload
 
 # Import from pygit2
-from ._pygit2 import Repository as _Repository, init_file_backend
-from ._pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN
-from ._pygit2 import Reference, Tree, Commit, Blob, Signature
-from ._pygit2 import InvalidSpecError
-
+from ._pygit2 import (
+    GIT_OID_HEXSZ,
+    GIT_OID_MINPREFIXLEN,
+    Blob,
+    Commit,
+    Diff,
+    InvalidSpecError,
+    Object,
+    Oid,
+    Patch,
+    Reference,
+    Signature,
+    Tree,
+    init_file_backend,
+)
+from ._pygit2 import Repository as _Repository
 from .blame import Blame
 from .branches import Branches
-from .callbacks import git_checkout_options, git_stash_apply_options
+from .callbacks import (
+    StashApplyCallbacks,
+    git_checkout_options,
+    git_stash_apply_options,
+)
 from .config import Config
 from .enums import (
     AttrCheck,
     BlameFlag,
-    BranchType,
     CheckoutStrategy,
     DescribeStrategy,
     DiffOption,
@@ -56,21 +72,50 @@ from .enums import (
     RepositoryState,
 )
 from .errors import check_error
-from .ffi import ffi, C
-from .index import Index, IndexEntry
+from .ffi import C, ffi
+from .index import Index, IndexEntry, MergeFileResult
 from .packbuilder import PackBuilder
 from .references import References
 from .remotes import RemoteCollection
 from .submodules import SubmoduleCollection
-from .utils import to_bytes, StrArray
+from .utils import StrArray, to_bytes
+
+if TYPE_CHECKING:
+    from pygit2._libgit2.ffi import (
+        ArrayC,
+        GitMergeOptionsC,
+        GitRepositoryC,
+        _Pointer,
+        char,
+    )
+    from pygit2._pygit2 import Odb, Refdb, RefdbBackend
 
 
 class BaseRepository(_Repository):
-    def __init__(self, *args, **kwargs):
+    _pointer: '_Pointer[GitRepositoryC]'
+    _repo: 'GitRepositoryC'
+    backend: 'RefdbBackend'
+    default_signature: Signature
+    head: Reference
+    head_is_detached: bool
+    head_is_unborn: bool
+    is_bare: bool
+    is_empty: bool
+    is_shallow: bool
+    odb: 'Odb'
+    path: str
+    refdb: 'Refdb'
+    workdir: str
+    references: References
+    remotes: RemoteCollection
+    branches: Branches
+    submodules: SubmoduleCollection
+
+    def __init__(self, *args, **kwargs) -> None:
         super().__init__(*args, **kwargs)
         self._common_init()
 
-    def _common_init(self):
+    def _common_init(self) -> None:
         self.branches = Branches(self)
         self.references = References(self)
         self.remotes = RemoteCollection(self)
@@ -83,22 +128,27 @@ class BaseRepository(_Repository):
         self._repo = repo_cptr[0]
 
     # Backwards compatible ODB access
-    def read(self, *args, **kwargs):
+    def read(self, oid: Oid | str) -> tuple[int, bytes]:
         """read(oid) -> type, data, size
 
         Read raw object data from the repository.
         """
-        return self.odb.read(*args, **kwargs)
+        return self.odb.read(oid)
 
-    def write(self, *args, **kwargs):
+    def write(self, type: int, data: bytes | str) -> Oid:
         """write(type, data) -> Oid
 
         Write raw object data into the repository. First arg is the object
         type, the second one a buffer with data. Return the Oid of the created
         object."""
-        return self.odb.write(*args, **kwargs)
+        return self.odb.write(type, data)
 
-    def pack(self, path=None, pack_delegate=None, n_threads=None):
+    def pack(
+        self,
+        path: str | Path | None = None,
+        pack_delegate: Callable[[PackBuilder], None] | None = None,
+        n_threads: int | None = None,
+    ) -> int:
         """Pack the objects in the odb chosen by the pack_delegate function
         and write `.pack` and `.idx` files for them.
 
@@ -134,8 +184,8 @@ class BaseRepository(_Repository):
         self,
         path: str,
         object_type: ObjectType = ObjectType.BLOB,
-        as_path: typing.Optional[str] = None,
-    ):
+        as_path: str | None = None,
+    ) -> Oid:
         """Calculate the hash of a file using repository filtering rules.
 
         If you simply want to calculate the hash of a file on disk with no filters,
@@ -167,6 +217,7 @@ class BaseRepository(_Repository):
         """
         c_path = to_bytes(path)
 
+        c_as_path: ffi.NULL_TYPE | bytes
         if as_path is None:
             c_as_path = ffi.NULL
         else:
@@ -182,33 +233,33 @@ class BaseRepository(_Repository):
         oid = Oid(raw=bytes(ffi.buffer(c_oid.id)[:]))
         return oid
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[Oid]:
         return iter(self.odb)
 
     #
     # Mapping interface
     #
-    def get(self, key, default=None):
+    def get(self, key: Oid | str, default: Optional[Commit] = None) -> None | Object:
         value = self.git_object_lookup_prefix(key)
         return value if (value is not None) else default
 
-    def __getitem__(self, key):
+    def __getitem__(self, key: str | Oid) -> Object:
         value = self.git_object_lookup_prefix(key)
         if value is None:
             raise KeyError(key)
         return value
 
-    def __contains__(self, key):
+    def __contains__(self, key: str | Oid) -> bool:
         return self.git_object_lookup_prefix(key) is not None
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return f'pygit2.Repository({repr(self.path)})'
 
     #
     # Configuration
     #
     @property
-    def config(self):
+    def config(self) -> Config:
         """The configuration file for this repository.
 
         If a the configuration hasn't been set yet, the default config for
@@ -237,7 +288,13 @@ class BaseRepository(_Repository):
     #
     # References
     #
-    def create_reference(self, name, target, force=False, message=None):
+    def create_reference(
+        self,
+        name: str,
+        target: Oid | str,
+        force: bool = False,
+        message: str | None = None,
+    ) -> 'Reference':
         """Create a new reference "name" which points to an object or to
         another reference.
 
@@ -259,25 +316,26 @@ class BaseRepository(_Repository):
             repo.create_reference('refs/tags/foo', 'refs/heads/master')
             repo.create_reference('refs/tags/foo', 'bbb78a9cec580')
         """
-        direct = type(target) is Oid or (
+        direct = isinstance(target, Oid) or (
             all(c in hexdigits for c in target)
             and GIT_OID_MINPREFIXLEN <= len(target) <= GIT_OID_HEXSZ
         )
 
-        if direct:
+        # duplicate isinstance call for mypy
+        if direct or isinstance(target, Oid):
             return self.create_reference_direct(name, target, force, message=message)
 
         return self.create_reference_symbolic(name, target, force, message=message)
 
-    def listall_references(self) -> typing.List[str]:
+    def listall_references(self) -> list[str]:
         """Return a list with all the references in the repository."""
         return list(x.name for x in self.references.iterator())
 
-    def listall_reference_objects(self) -> typing.List[Reference]:
+    def listall_reference_objects(self) -> list[Reference]:
         """Return a list with all the reference objects in the repository."""
         return list(x for x in self.references.iterator())
 
-    def resolve_refish(self, refish):
+    def resolve_refish(self, refish: str) -> tuple[Commit, Reference]:
         """Convert a reference-like short name "ref-ish" to a valid
         (commit, reference) pair.
 
@@ -297,9 +355,9 @@ class BaseRepository(_Repository):
             reference = None
             commit = self.revparse_single(refish)
         else:
-            commit = reference.peel(Commit)
+            commit = reference.peel(Commit)  # type: ignore
 
-        return (commit, reference)
+        return (commit, reference)  # type: ignore
 
     #
     # Checkout
@@ -338,7 +396,11 @@ class BaseRepository(_Repository):
             err = C.git_checkout_tree(self._repo, cptr[0], payload.checkout_options)
             payload.check_error(err)
 
-    def checkout(self, refname=None, **kwargs):
+    def checkout(
+        self,
+        refname: str | None | Reference = None,
+        **kwargs,
+    ) -> None:
         """
         Checkout the given reference using the given strategy, and update the
         HEAD.
@@ -406,7 +468,7 @@ class BaseRepository(_Repository):
     #
     # Setting HEAD
     #
-    def set_head(self, target):
+    def set_head(self, target: Oid | str) -> None:
         """
         Set HEAD to point to the given target.
 
@@ -453,15 +515,35 @@ class BaseRepository(_Repository):
 
         return obj
 
+    @overload
     def diff(
         self,
-        a=None,
-        b=None,
-        cached=False,
+        a: None | str | bytes | Commit | Oid | Reference = None,
+        b: None | str | bytes | Commit | Oid | Reference = None,
+        cached: bool = False,
         flags: DiffOption = DiffOption.NORMAL,
         context_lines: int = 3,
         interhunk_lines: int = 0,
-    ):
+    ) -> Diff: ...
+    @overload
+    def diff(
+        self,
+        a: Blob | None = None,
+        b: Blob | None = None,
+        cached: bool = False,
+        flags: DiffOption = DiffOption.NORMAL,
+        context_lines: int = 3,
+        interhunk_lines: int = 0,
+    ) -> Patch: ...
+    def diff(
+        self,
+        a: None | Blob | str | bytes | Commit | Oid | Reference = None,
+        b: None | Blob | str | bytes | Commit | Oid | Reference = None,
+        cached: bool = False,
+        flags: DiffOption = DiffOption.NORMAL,
+        context_lines: int = 3,
+        interhunk_lines: int = 0,
+    ) -> Diff | Patch:
         """
         Show changes between the working tree and the index or a tree,
         changes between the index and a tree, changes between two trees, or
@@ -522,27 +604,30 @@ class BaseRepository(_Repository):
         a = self.__whatever_to_tree_or_blob(a)
         b = self.__whatever_to_tree_or_blob(b)
 
-        opt_keys = ['flags', 'context_lines', 'interhunk_lines']
-        opt_values = [int(flags), context_lines, interhunk_lines]
+        options = {
+            'flags': int(flags),
+            'context_lines': context_lines,
+            'interhunk_lines': interhunk_lines,
+        }
 
         # Case 1: Diff tree to tree
         if isinstance(a, Tree) and isinstance(b, Tree):
-            return a.diff_to_tree(b, **dict(zip(opt_keys, opt_values)))
+            return a.diff_to_tree(b, **options)  # type: ignore[arg-type]
 
         # Case 2: Index to workdir
         elif a is None and b is None:
-            return self.index.diff_to_workdir(*opt_values)
+            return self.index.diff_to_workdir(**options)
 
         # Case 3: Diff tree to index or workdir
         elif isinstance(a, Tree) and b is None:
             if cached:
-                return a.diff_to_index(self.index, *opt_values)
+                return a.diff_to_index(self.index, **options)  # type: ignore[arg-type]
             else:
-                return a.diff_to_workdir(*opt_values)
+                return a.diff_to_workdir(**options)  # type: ignore[arg-type]
 
         # Case 4: Diff blob to blob
         if isinstance(a, Blob) and isinstance(b, Blob):
-            return a.diff(b)
+            return a.diff(b, **options)  # type: ignore[arg-type]
 
         raise ValueError('Only blobs and treeish can be diffed')
 
@@ -557,9 +642,9 @@ class BaseRepository(_Repository):
             return RepositoryState(cstate)
         except ValueError:
             # Some value not in the IntEnum - newer libgit2 version?
-            return cstate
+            return cstate  # type: ignore[return-value]
 
-    def state_cleanup(self):
+    def state_cleanup(self) -> None:
         """Remove all the metadata associated with an ongoing command like
         merge, revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG,
         etc.
@@ -571,14 +656,14 @@ class BaseRepository(_Repository):
     #
     def blame(
         self,
-        path,
+        path: str,
         flags: BlameFlag = BlameFlag.NORMAL,
-        min_match_characters=None,
-        newest_commit=None,
-        oldest_commit=None,
-        min_line=None,
-        max_line=None,
-    ):
+        min_match_characters: int | None = None,
+        newest_commit: Oid | str | None = None,
+        oldest_commit: Oid | str | None = None,
+        min_line: int | None = None,
+        max_line: int | None = None,
+    ) -> Blame:
         """
         Return a Blame object for a single file.
 
@@ -612,6 +697,7 @@ class BaseRepository(_Repository):
         """
 
         options = ffi.new('git_blame_options *')
+
         C.git_blame_options_init(options, C.GIT_BLAME_OPTIONS_VERSION)
         if flags:
             options.flags = int(flags)
@@ -652,7 +738,9 @@ class BaseRepository(_Repository):
     # Merging
     #
     @staticmethod
-    def _merge_options(favor: MergeFavor, flags: MergeFlag, file_flags: MergeFileFlag):
+    def _merge_options(
+        favor: int | MergeFavor, flags: int | MergeFlag, file_flags: int | MergeFileFlag
+    ) -> 'GitMergeOptionsC':
         """Return a 'git_merge_opts *'"""
 
         # Check arguments type
@@ -677,12 +765,16 @@ class BaseRepository(_Repository):
 
     def merge_file_from_index(
         self,
-        ancestor: typing.Union[None, IndexEntry],
-        ours: typing.Union[None, IndexEntry],
-        theirs: typing.Union[None, IndexEntry],
-    ) -> str:
-        """Merge files from index. Return a string with the merge result
-        containing possible conflicts.
+        ancestor: 'IndexEntry | None',
+        ours: 'IndexEntry | None',
+        theirs: 'IndexEntry | None',
+        use_deprecated: bool = True,
+    ) -> 'str | MergeFileResult | None':
+        """Merge files from index.
+
+        Returns: A string with the content of the file containing
+        possible conflicts if use_deprecated==True.
+        If use_deprecated==False then it returns an instance of MergeFileResult.
 
         ancestor
             The index entry which will be used as a common
@@ -691,6 +783,10 @@ class BaseRepository(_Repository):
             The index entry to take as "ours" or base.
         theirs
             The index entry which will be merged into "ours"
+        use_deprecated
+            This controls what will be returned. If use_deprecated==True (default),
+            a string with the contents of the file will be returned.
+            An instance of MergeFileResult will be returned otherwise.
         """
         cmergeresult = ffi.new('git_merge_file_result *')
 
@@ -707,19 +803,28 @@ class BaseRepository(_Repository):
         )
         check_error(err)
 
-        ret = ffi.string(cmergeresult.ptr, cmergeresult.len).decode('utf-8')
+        mergeFileResult = MergeFileResult._from_c(cmergeresult)
         C.git_merge_file_result_free(cmergeresult)
 
-        return ret
+        if use_deprecated:
+            warnings.warn(
+                'Getting an str from Repository.merge_file_from_index is deprecated. '
+                'The method will later return an instance of MergeFileResult by default, instead. '
+                'Check parameter use_deprecated.',
+                DeprecationWarning,
+            )
+            return mergeFileResult.contents if mergeFileResult else ''
+
+        return mergeFileResult
 
     def merge_commits(
         self,
-        ours: typing.Union[str, Oid, Commit],
-        theirs: typing.Union[str, Oid, Commit],
-        favor=MergeFavor.NORMAL,
-        flags=MergeFlag.FIND_RENAMES,
-        file_flags=MergeFileFlag.DEFAULT,
-    ) -> Index:
+        ours: str | Oid | Commit,
+        theirs: str | Oid | Commit,
+        favor: MergeFavor = MergeFavor.NORMAL,
+        flags: MergeFlag = MergeFlag.FIND_RENAMES,
+        file_flags: MergeFileFlag = MergeFileFlag.DEFAULT,
+    ) -> 'Index':
         """
         Merge two arbitrary commits.
 
@@ -751,9 +856,15 @@ class BaseRepository(_Repository):
         cindex = ffi.new('git_index **')
 
         if isinstance(ours, (str, Oid)):
-            ours = self[ours]
+            ours_object = self[ours]
+            if not isinstance(ours_object, Commit):
+                raise TypeError(f'expected Commit, got {type(ours_object)}')
+            ours = ours_object
         if isinstance(theirs, (str, Oid)):
-            theirs = self[theirs]
+            theirs_object = self[theirs]
+            if not isinstance(theirs_object, Commit):
+                raise TypeError(f'expected Commit, got {type(theirs_object)}')
+            theirs = theirs_object
 
         ours = ours.peel(Commit)
         theirs = theirs.peel(Commit)
@@ -770,13 +881,13 @@ class BaseRepository(_Repository):
 
     def merge_trees(
         self,
-        ancestor: typing.Union[str, Oid, Tree],
-        ours: typing.Union[str, Oid, Tree],
-        theirs: typing.Union[str, Oid, Tree],
-        favor=MergeFavor.NORMAL,
-        flags=MergeFlag.FIND_RENAMES,
-        file_flags=MergeFileFlag.DEFAULT,
-    ):
+        ancestor: str | Oid | Tree,
+        ours: str | Oid | Tree,
+        theirs: str | Oid | Tree,
+        favor: MergeFavor = MergeFavor.NORMAL,
+        flags: MergeFlag = MergeFlag.FIND_RENAMES,
+        file_flags: MergeFileFlag = MergeFileFlag.DEFAULT,
+    ) -> 'Index':
         """
         Merge two trees.
 
@@ -808,16 +919,9 @@ class BaseRepository(_Repository):
         theirs_ptr = ffi.new('git_tree **')
         cindex = ffi.new('git_index **')
 
-        if isinstance(ancestor, (str, Oid)):
-            ancestor = self[ancestor]
-        if isinstance(ours, (str, Oid)):
-            ours = self[ours]
-        if isinstance(theirs, (str, Oid)):
-            theirs = self[theirs]
-
-        ancestor = ancestor.peel(Tree)
-        ours = ours.peel(Tree)
-        theirs = theirs.peel(Tree)
+        ancestor = self.__ensure_tree(ancestor)
+        ours = self.__ensure_tree(ours)
+        theirs = self.__ensure_tree(theirs)
 
         opts = self._merge_options(favor, flags, file_flags)
 
@@ -834,23 +938,28 @@ class BaseRepository(_Repository):
 
     def merge(
         self,
-        id: typing.Union[Oid, str],
-        favor=MergeFavor.NORMAL,
-        flags=MergeFlag.FIND_RENAMES,
-        file_flags=MergeFileFlag.DEFAULT,
-    ):
+        source: Reference | Commit | Oid | str,
+        favor: MergeFavor = MergeFavor.NORMAL,
+        flags: MergeFlag = MergeFlag.FIND_RENAMES,
+        file_flags: MergeFileFlag = MergeFileFlag.DEFAULT,
+    ) -> None:
         """
-        Merges the given id into HEAD.
+        Merges the given Reference or Commit into HEAD.
 
-        Merges the given commit(s) into HEAD, writing the results into the working directory.
+        Merges the given commit into HEAD, writing the results into the working directory.
         Any changes are staged for commit and any conflicts are written to the index.
         Callers should inspect the repository's index after this completes,
         resolve any conflicts and prepare a commit.
 
         Parameters:
 
-        id
-            The id to merge into HEAD
+        source
+            The Reference, Commit, or commit Oid to merge into HEAD.
+            It is preferable to pass in a Reference, because this enriches the
+            merge with additional information (for example, Repository.message will
+            specify the name of the branch being merged).
+            Previous versions of pygit2 allowed passing in a partial commit
+            hash as a string; this is deprecated.
 
         favor
             An enums.MergeFavor constant specifying how to deal with file-level conflicts.
@@ -862,12 +971,35 @@ class BaseRepository(_Repository):
         file_flags
             A combination of enums.MergeFileFlag constants.
         """
-        if not isinstance(id, (str, Oid)):
-            raise TypeError(f'expected oid (string or <Oid>) got {type(id)}')
 
-        id = self[id].id
-        c_id = ffi.new('git_oid *')
-        ffi.buffer(c_id)[:] = id.raw[:]
+        if isinstance(source, Reference):
+            # Annotated commit from ref
+            cptr = ffi.new('struct git_reference **')
+            ffi.buffer(cptr)[:] = source._pointer[:]  # type: ignore[attr-defined]
+            commit_ptr = ffi.new('git_annotated_commit **')
+            err = C.git_annotated_commit_from_ref(commit_ptr, self._repo, cptr[0])
+            check_error(err)
+        else:
+            # Annotated commit from commit id
+            if isinstance(source, str):
+                # For backwards compatibility, parse a string as a partial commit hash
+                warnings.warn(
+                    'Passing str to Repository.merge is deprecated. '
+                    'Pass Commit, Oid, or a Reference (such as a Branch) instead.',
+                    DeprecationWarning,
+                )
+                oid = self[source].peel(Commit).id
+            elif isinstance(source, Commit):
+                oid = source.id
+            elif isinstance(source, Oid):
+                oid = source
+            else:
+                raise TypeError('expected Reference, Commit, or Oid')
+            c_id = ffi.new('git_oid *')
+            ffi.buffer(c_id)[:] = oid.raw[:]
+            commit_ptr = ffi.new('git_annotated_commit **')
+            err = C.git_annotated_commit_lookup(commit_ptr, self._repo, c_id)
+            check_error(err)
 
         merge_opts = self._merge_options(favor, flags, file_flags)
 
@@ -877,10 +1009,6 @@ class BaseRepository(_Repository):
             CheckoutStrategy.SAFE | CheckoutStrategy.RECREATE_MISSING
         )
 
-        commit_ptr = ffi.new('git_annotated_commit **')
-        err = C.git_annotated_commit_lookup(commit_ptr, self._repo, c_id)
-        check_error(err)
-
         err = C.git_merge(self._repo, commit_ptr, 1, merge_opts, checkout_opts)
         C.git_annotated_commit_free(commit_ptr[0])
         check_error(err)
@@ -923,7 +1051,7 @@ class BaseRepository(_Repository):
         """
         return self.raw_message.decode('utf-8')
 
-    def remove_message(self):
+    def remove_message(self) -> None:
         """
         Remove git's prepared message.
         """
@@ -935,16 +1063,16 @@ class BaseRepository(_Repository):
     #
     def describe(
         self,
-        committish=None,
-        max_candidates_tags=None,
+        committish: str | Reference | Commit | None = None,
+        max_candidates_tags: int | None = None,
         describe_strategy: DescribeStrategy = DescribeStrategy.DEFAULT,
-        pattern=None,
-        only_follow_first_parent=None,
-        show_commit_oid_as_fallback=None,
-        abbreviated_size=None,
-        always_use_long_format=None,
-        dirty_suffix=None,
-    ):
+        pattern: str | None = None,
+        only_follow_first_parent: bool | None = None,
+        show_commit_oid_as_fallback: bool | None = None,
+        abbreviated_size: int | None = None,
+        always_use_long_format: bool | None = None,
+        dirty_suffix: str | None = None,
+    ) -> str:
         """
         Describe a commit-ish or the current working tree.
 
@@ -987,7 +1115,7 @@ class BaseRepository(_Repository):
 
         always_use_long_format : bool
             Always output the long format (the nearest tag, the number of
-            commits, and the abbrevated commit name) even when the committish
+            commits, and the abbreviated commit name) even when the committish
             matches a tag.
 
         dirty_suffix : str
@@ -1018,10 +1146,13 @@ class BaseRepository(_Repository):
 
         result = ffi.new('git_describe_result **')
         if committish:
+            committish_rev: Object | Reference | Commit
             if isinstance(committish, str):
-                committish = self.revparse_single(committish)
+                committish_rev = self.revparse_single(committish)
+            else:
+                committish_rev = committish
 
-            commit = committish.peel(Commit)
+            commit = committish_rev.peel(Commit)
 
             cptr = ffi.new('git_object **')
             ffi.buffer(cptr)[:] = commit._pointer[:]
@@ -1064,13 +1195,13 @@ class BaseRepository(_Repository):
     def stash(
         self,
         stasher: Signature,
-        message: typing.Optional[str] = None,
+        message: str | None = None,
         keep_index: bool = False,
         include_untracked: bool = False,
         include_ignored: bool = False,
         keep_all: bool = False,
-        paths: typing.Optional[typing.List[str]] = None,
-    ):
+        paths: list[str] | None = None,
+    ) -> Oid:
         """
         Save changes to the working directory to the stash.
 
@@ -1125,7 +1256,7 @@ class BaseRepository(_Repository):
 
         if paths:
             arr = StrArray(paths)
-            opts.paths = arr.ptr[0]
+            opts.paths = arr.ptr[0]  # type: ignore[index]
 
         coid = ffi.new('git_oid *')
         err = C.git_stash_save_with_opts(coid, self._repo, opts)
@@ -1134,7 +1265,13 @@ class BaseRepository(_Repository):
 
         return Oid(raw=bytes(ffi.buffer(coid)[:]))
 
-    def stash_apply(self, index=0, **kwargs):
+    def stash_apply(
+        self,
+        index: int = 0,
+        reinstate_index: bool = False,
+        strategy: CheckoutStrategy | None = None,
+        callbacks: StashApplyCallbacks | None = None,
+    ) -> None:
         """
         Apply a stashed state in the stash list to the working directory.
 
@@ -1168,11 +1305,15 @@ class BaseRepository(_Repository):
             >>> repo.stash(repo.default_signature(), 'WIP: stashing')
             >>> repo.stash_apply(strategy=CheckoutStrategy.ALLOW_CONFLICTS)
         """
-        with git_stash_apply_options(**kwargs) as payload:
+        with git_stash_apply_options(
+            reinstate_index=reinstate_index,
+            strategy=strategy,
+            callbacks=callbacks,
+        ) as payload:
             err = C.git_stash_apply(self._repo, index, payload.stash_apply_options)
             payload.check_error(err)
 
-    def stash_drop(self, index=0):
+    def stash_drop(self, index: int = 0) -> None:
         """
         Remove a stashed state from the stash list.
 
@@ -1184,19 +1325,35 @@ class BaseRepository(_Repository):
         """
         check_error(C.git_stash_drop(self._repo, index))
 
-    def stash_pop(self, index=0, **kwargs):
+    def stash_pop(
+        self,
+        index: int = 0,
+        reinstate_index: bool = False,
+        strategy: CheckoutStrategy | None = None,
+        callbacks: StashApplyCallbacks | None = None,
+    ) -> None:
         """Apply a stashed state and remove it from the stash list.
 
         For arguments, see Repository.stash_apply().
         """
-        with git_stash_apply_options(**kwargs) as payload:
+        with git_stash_apply_options(
+            reinstate_index=reinstate_index,
+            strategy=strategy,
+            callbacks=callbacks,
+        ) as payload:
             err = C.git_stash_pop(self._repo, index, payload.stash_apply_options)
             payload.check_error(err)
 
     #
     # Utility for writing a tree into an archive
     #
-    def write_archive(self, treeish, archive, timestamp=None, prefix=''):
+    def write_archive(
+        self,
+        treeish: str | Tree | Object | Oid,
+        archive: tarfile.TarFile,
+        timestamp: int | None = None,
+        prefix: str = '',
+    ) -> None:
         """
         Write treeish into an archive.
 
@@ -1268,7 +1425,7 @@ class BaseRepository(_Repository):
     #
     # Ahead-behind, which mostly lives on its own namespace
     #
-    def ahead_behind(self, local, upstream):
+    def ahead_behind(self, local: Oid | str, upstream: Oid | str) -> tuple[int, int]:
         """
         Calculate how many different commits are in the non-common parts of the
         history between the two given ids.
@@ -1308,11 +1465,11 @@ class BaseRepository(_Repository):
     #
     def get_attr(
         self,
-        path: typing.Union[str, bytes, PathLike],
-        name: typing.Union[str, bytes],
+        path: str | bytes | Path,
+        name: str | bytes,
         flags: AttrCheck = AttrCheck.FILE_THEN_INDEX,
-        commit: typing.Union[Oid, str, None] = None,
-    ) -> typing.Union[bool, None, str]:
+        commit: Oid | str | None = None,
+    ) -> bool | None | str:
         """
         Retrieve an attribute for a file by path.
 
@@ -1385,7 +1542,7 @@ class BaseRepository(_Repository):
 
         return (ffi.string(cname).decode('utf-8'), ffi.string(cemail).decode('utf-8'))
 
-    def set_ident(self, name, email):
+    def set_ident(self, name: str, email: str) -> None:
         """Set the identity to be used for reference operations.
 
         Updates to some references also append data to their
@@ -1396,7 +1553,7 @@ class BaseRepository(_Repository):
         err = C.git_repository_set_ident(self._repo, to_bytes(name), to_bytes(email))
         check_error(err)
 
-    def revert(self, commit: Commit):
+    def revert(self, commit: Commit) -> None:
         """
         Revert the given commit, producing changes in the index and working
         directory.
@@ -1409,7 +1566,9 @@ class BaseRepository(_Repository):
         err = C.git_revert(self._repo, commit_ptr[0], ffi.NULL)
         check_error(err)
 
-    def revert_commit(self, revert_commit, our_commit, mainline=0):
+    def revert_commit(
+        self, revert_commit: Commit, our_commit: Commit, mainline: int = 0
+    ) -> Index:
         """
         Revert the given Commit against the given "our" Commit, producing an
         Index that reflects the result of the revert.
@@ -1450,14 +1609,14 @@ class BaseRepository(_Repository):
     #
     def amend_commit(
         self,
-        commit,
-        refname,
-        author=None,
-        committer=None,
-        message=None,
-        tree=None,
-        encoding='UTF-8',
-    ):
+        commit: Commit | Oid | str,
+        refname: Reference | str | None,
+        author: Signature | None = None,
+        committer: Signature | None = None,
+        message: str | None = None,
+        tree: Tree | Oid | str | None = None,
+        encoding: str = 'UTF-8',
+    ) -> Oid:
         """
         Amend an existing commit by replacing only explicitly passed values,
         return the rewritten commit's oid.
@@ -1506,24 +1665,24 @@ class BaseRepository(_Repository):
         # Note: the pointers are all initialized to NULL by default.
         coid = ffi.new('git_oid *')
         commit_cptr = ffi.new('git_commit **')
-        refname_cstr = ffi.NULL
+        refname_cstr: 'ArrayC[char]' | 'ffi.NULL_TYPE' = ffi.NULL
         author_cptr = ffi.new('git_signature **')
         committer_cptr = ffi.new('git_signature **')
-        message_cstr = ffi.NULL
-        encoding_cstr = ffi.NULL
+        message_cstr: 'ArrayC[char]' | 'ffi.NULL_TYPE' = ffi.NULL
+        encoding_cstr: 'ArrayC[char]' | 'ffi.NULL_TYPE' = ffi.NULL
         tree_cptr = ffi.new('git_tree **')
 
         # Get commit as pointer to git_commit.
         if isinstance(commit, (str, Oid)):
-            commit = self[commit]
+            commit_object = self[commit]
+            commit_commit = commit_object.peel(Commit)
         elif isinstance(commit, Commit):
-            pass
+            commit_commit = commit
         elif commit is None:
             raise ValueError('the commit to amend cannot be None')
         else:
             raise TypeError('the commit to amend must be a Commit, str, or Oid')
-        commit = commit.peel(Commit)
-        ffi.buffer(commit_cptr)[:] = commit._pointer[:]
+        ffi.buffer(commit_cptr)[:] = commit_commit._pointer[:]
 
         # Get refname as C string.
         if isinstance(refname, Reference):
@@ -1553,9 +1712,11 @@ class BaseRepository(_Repository):
         # Get tree as pointer to git_tree.
         if tree is not None:
             if isinstance(tree, (str, Oid)):
-                tree = self[tree]
-            tree = tree.peel(Tree)
-            ffi.buffer(tree_cptr)[:] = tree._pointer[:]
+                tree_object = self[tree]
+            else:
+                tree_object = tree
+            tree_tree = tree_object.peel(Tree)
+            ffi.buffer(tree_cptr)[:] = tree_tree._pointer[:]
 
         # Amend the commit.
         err = C.git_commit_amend(
@@ -1572,11 +1733,16 @@ class BaseRepository(_Repository):
 
         return Oid(raw=bytes(ffi.buffer(coid)[:]))
 
+    def __ensure_tree(self, maybe_tree: str | Oid | Tree) -> Tree:
+        if isinstance(maybe_tree, Tree):
+            return maybe_tree
+        return self[maybe_tree].peel(Tree)
+
 
 class Repository(BaseRepository):
     def __init__(
         self,
-        path: typing.Optional[str] = None,
+        path: str | bytes | None | Path = None,
         flags: RepositoryOpenFlag = RepositoryOpenFlag.DEFAULT,
     ):
         """
@@ -1610,10 +1776,10 @@ class Repository(BaseRepository):
             super().__init__()
 
     @classmethod
-    def _from_c(cls, ptr, owned):
+    def _from_c(cls, ptr: 'GitRepositoryC', owned: bool) -> 'Repository':
         cptr = ffi.new('git_repository **')
         cptr[0] = ptr
         repo = cls.__new__(cls)
-        BaseRepository._from_c(repo, bytes(ffi.buffer(cptr)[:]), owned)
+        BaseRepository._from_c(repo, bytes(ffi.buffer(cptr)[:]), owned)  # type: ignore
         repo._common_init()
         return repo
diff -pruN 1.17.0-2/pygit2/settings.py 1.18.2-1/pygit2/settings.py
--- 1.17.0-2/pygit2/settings.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/settings.py	2025-08-16 11:34:29.000000000 +0000
@@ -27,20 +27,21 @@
 Settings mapping.
 """
 
-from ssl import get_default_verify_paths
+from ssl import DefaultVerifyPaths, get_default_verify_paths
+from typing import overload
 
 import pygit2.enums
 
-from ._pygit2 import option
-from .enums import Option
+from .enums import ConfigLevel, Option
 from .errors import GitError
+from .options import option
 
 
 class SearchPathList:
-    def __getitem__(self, key):
+    def __getitem__(self, key: ConfigLevel) -> str:
         return option(Option.GET_SEARCH_PATH, key)
 
-    def __setitem__(self, key, value):
+    def __setitem__(self, key: ConfigLevel, value: str) -> None:
         option(Option.SET_SEARCH_PATH, key, value)
 
 
@@ -50,12 +51,15 @@ class Settings:
     __slots__ = '_default_tls_verify_paths', '_ssl_cert_dir', '_ssl_cert_file'
 
     _search_path = SearchPathList()
+    _default_tls_verify_paths: DefaultVerifyPaths | None
+    _ssl_cert_file: str | bytes | None
+    _ssl_cert_dir: str | bytes | None
 
-    def __init__(self):
+    def __init__(self) -> None:
         """Initialize global pygit2 and libgit2 settings."""
         self._initialize_tls_certificate_locations()
 
-    def _initialize_tls_certificate_locations(self):
+    def _initialize_tls_certificate_locations(self) -> None:
         """Set up initial TLS file and directory lookup locations."""
         self._default_tls_verify_paths = get_default_verify_paths()
         try:
@@ -72,7 +76,7 @@ class Settings:
             self._ssl_cert_dir = None
 
     @property
-    def search_path(self):
+    def search_path(self) -> SearchPathList:
         """Configuration file search path.
 
         This behaves like an array whose indices correspond to ConfigLevel values.
@@ -81,16 +85,16 @@ class Settings:
         return self._search_path
 
     @property
-    def mwindow_size(self):
+    def mwindow_size(self) -> int:
         """Get or set the maximum mmap window size"""
         return option(Option.GET_MWINDOW_SIZE)
 
     @mwindow_size.setter
-    def mwindow_size(self, value):
+    def mwindow_size(self, value: int) -> None:
         option(Option.SET_MWINDOW_SIZE, value)
 
     @property
-    def mwindow_mapped_limit(self):
+    def mwindow_mapped_limit(self) -> int:
         """
         Get or set the maximum memory that will be mapped in total by the
         library
@@ -98,18 +102,27 @@ class Settings:
         return option(Option.GET_MWINDOW_MAPPED_LIMIT)
 
     @mwindow_mapped_limit.setter
-    def mwindow_mapped_limit(self, value):
+    def mwindow_mapped_limit(self, value: int) -> None:
         option(Option.SET_MWINDOW_MAPPED_LIMIT, value)
 
     @property
-    def cached_memory(self):
+    def mwindow_file_limit(self) -> int:
+        """Get or set the maximum number of files to be mapped at any time"""
+        return option(Option.GET_MWINDOW_FILE_LIMIT)
+
+    @mwindow_file_limit.setter
+    def mwindow_file_limit(self, value: int) -> None:
+        option(Option.SET_MWINDOW_FILE_LIMIT, value)
+
+    @property
+    def cached_memory(self) -> tuple[int, int]:
         """
         Get the current bytes in cache and the maximum that would be
         allowed in the cache.
         """
         return option(Option.GET_CACHED_MEMORY)
 
-    def enable_caching(self, value=True):
+    def enable_caching(self, value: bool = True) -> None:
         """
         Enable or disable caching completely.
 
@@ -119,7 +132,7 @@ class Settings:
         """
         return option(Option.ENABLE_CACHING, value)
 
-    def disable_pack_keep_file_checks(self, value=True):
+    def disable_pack_keep_file_checks(self, value: bool = True) -> None:
         """
         This will cause .keep file existence checks to be skipped when
         accessing packfiles, which can help performance with remote
@@ -127,7 +140,7 @@ class Settings:
         """
         return option(Option.DISABLE_PACK_KEEP_FILE_CHECKS, value)
 
-    def cache_max_size(self, value):
+    def cache_max_size(self, value: int) -> None:
         """
         Set the maximum total data size that will be cached in memory
         across all repositories before libgit2 starts evicting objects
@@ -137,7 +150,9 @@ class Settings:
         """
         return option(Option.SET_CACHE_MAX_SIZE, value)
 
-    def cache_object_limit(self, object_type: pygit2.enums.ObjectType, value):
+    def cache_object_limit(
+        self, object_type: pygit2.enums.ObjectType, value: int
+    ) -> None:
         """
         Set the maximum data size for the given type of object to be
         considered eligible for caching in memory.  Setting to value to
@@ -148,36 +163,46 @@ class Settings:
         return option(Option.SET_CACHE_OBJECT_LIMIT, object_type, value)
 
     @property
-    def ssl_cert_file(self):
+    def ssl_cert_file(self) -> str | bytes | None:
         """TLS certificate file path."""
         return self._ssl_cert_file
 
     @ssl_cert_file.setter
-    def ssl_cert_file(self, value):
+    def ssl_cert_file(self, value: str | bytes) -> None:
         """Set the TLS cert file path."""
         self.set_ssl_cert_locations(value, self._ssl_cert_dir)
 
     @ssl_cert_file.deleter
-    def ssl_cert_file(self):
+    def ssl_cert_file(self) -> None:
         """Reset the TLS cert file path."""
-        self.ssl_cert_file = self._default_tls_verify_paths.cafile
+        self.ssl_cert_file = self._default_tls_verify_paths.cafile  # type: ignore[union-attr]
 
     @property
-    def ssl_cert_dir(self):
+    def ssl_cert_dir(self) -> str | bytes | None:
         """TLS certificates lookup directory path."""
         return self._ssl_cert_dir
 
     @ssl_cert_dir.setter
-    def ssl_cert_dir(self, value):
+    def ssl_cert_dir(self, value: str | bytes) -> None:
         """Set the TLS certificate lookup folder."""
         self.set_ssl_cert_locations(self._ssl_cert_file, value)
 
     @ssl_cert_dir.deleter
-    def ssl_cert_dir(self):
+    def ssl_cert_dir(self) -> None:
         """Reset the TLS certificate lookup folder."""
-        self.ssl_cert_dir = self._default_tls_verify_paths.capath
+        self.ssl_cert_dir = self._default_tls_verify_paths.capath  # type: ignore[union-attr]
 
-    def set_ssl_cert_locations(self, cert_file, cert_dir):
+    @overload
+    def set_ssl_cert_locations(
+        self, cert_file: str | bytes | None, cert_dir: str | bytes
+    ) -> None: ...
+    @overload
+    def set_ssl_cert_locations(
+        self, cert_file: str | bytes, cert_dir: str | bytes | None
+    ) -> None: ...
+    def set_ssl_cert_locations(
+        self, cert_file: str | bytes | None, cert_dir: str | bytes | None
+    ) -> None:
         """
         Set the SSL certificate-authority locations.
 
@@ -191,3 +216,133 @@ class Settings:
         option(Option.SET_SSL_CERT_LOCATIONS, cert_file, cert_dir)
         self._ssl_cert_file = cert_file
         self._ssl_cert_dir = cert_dir
+
+    @property
+    def template_path(self) -> str | None:
+        """Get or set the default template path for new repositories"""
+        return option(Option.GET_TEMPLATE_PATH)
+
+    @template_path.setter
+    def template_path(self, value: str | bytes) -> None:
+        option(Option.SET_TEMPLATE_PATH, value)
+
+    @property
+    def user_agent(self) -> str | None:
+        """Get or set the user agent string for network operations"""
+        return option(Option.GET_USER_AGENT)
+
+    @user_agent.setter
+    def user_agent(self, value: str | bytes) -> None:
+        option(Option.SET_USER_AGENT, value)
+
+    @property
+    def user_agent_product(self) -> str | None:
+        """Get or set the user agent product name"""
+        return option(Option.GET_USER_AGENT_PRODUCT)
+
+    @user_agent_product.setter
+    def user_agent_product(self, value: str | bytes) -> None:
+        option(Option.SET_USER_AGENT_PRODUCT, value)
+
+    def set_ssl_ciphers(self, ciphers: str | bytes) -> None:
+        """Set the SSL ciphers to use for HTTPS connections"""
+        option(Option.SET_SSL_CIPHERS, ciphers)
+
+    def enable_strict_object_creation(self, value: bool = True) -> None:
+        """Enable or disable strict object creation validation"""
+        option(Option.ENABLE_STRICT_OBJECT_CREATION, value)
+
+    def enable_strict_symbolic_ref_creation(self, value: bool = True) -> None:
+        """Enable or disable strict symbolic reference creation validation"""
+        option(Option.ENABLE_STRICT_SYMBOLIC_REF_CREATION, value)
+
+    def enable_ofs_delta(self, value: bool = True) -> None:
+        """Enable or disable offset delta encoding"""
+        option(Option.ENABLE_OFS_DELTA, value)
+
+    def enable_fsync_gitdir(self, value: bool = True) -> None:
+        """Enable or disable fsync for git directory operations"""
+        option(Option.ENABLE_FSYNC_GITDIR, value)
+
+    def enable_strict_hash_verification(self, value: bool = True) -> None:
+        """Enable or disable strict hash verification"""
+        option(Option.ENABLE_STRICT_HASH_VERIFICATION, value)
+
+    def enable_unsaved_index_safety(self, value: bool = True) -> None:
+        """Enable or disable unsaved index safety checks"""
+        option(Option.ENABLE_UNSAVED_INDEX_SAFETY, value)
+
+    def enable_http_expect_continue(self, value: bool = True) -> None:
+        """Enable or disable HTTP Expect/Continue for large pushes"""
+        option(Option.ENABLE_HTTP_EXPECT_CONTINUE, value)
+
+    @property
+    def windows_sharemode(self) -> int:
+        """Get or set the Windows share mode for opening files"""
+        return option(Option.GET_WINDOWS_SHAREMODE)
+
+    @windows_sharemode.setter
+    def windows_sharemode(self, value: int) -> None:
+        option(Option.SET_WINDOWS_SHAREMODE, value)
+
+    @property
+    def pack_max_objects(self) -> int:
+        """Get or set the maximum number of objects in a pack"""
+        return option(Option.GET_PACK_MAX_OBJECTS)
+
+    @pack_max_objects.setter
+    def pack_max_objects(self, value: int) -> None:
+        option(Option.SET_PACK_MAX_OBJECTS, value)
+
+    @property
+    def owner_validation(self) -> bool:
+        """Get or set repository directory ownership validation"""
+        return option(Option.GET_OWNER_VALIDATION)
+
+    @owner_validation.setter
+    def owner_validation(self, value: bool) -> None:
+        option(Option.SET_OWNER_VALIDATION, value)
+
+    def set_odb_packed_priority(self, priority: int) -> None:
+        """Set the priority for packed ODB backend (default 1)"""
+        option(Option.SET_ODB_PACKED_PRIORITY, priority)
+
+    def set_odb_loose_priority(self, priority: int) -> None:
+        """Set the priority for loose ODB backend (default 2)"""
+        option(Option.SET_ODB_LOOSE_PRIORITY, priority)
+
+    @property
+    def extensions(self) -> list[str]:
+        """Get the list of enabled extensions"""
+        return option(Option.GET_EXTENSIONS)
+
+    def set_extensions(self, extensions: list[str]) -> None:
+        """Set the list of enabled extensions"""
+        option(Option.SET_EXTENSIONS, extensions, len(extensions))
+
+    @property
+    def homedir(self) -> str | None:
+        """Get or set the home directory"""
+        return option(Option.GET_HOMEDIR)
+
+    @homedir.setter
+    def homedir(self, value: str | bytes) -> None:
+        option(Option.SET_HOMEDIR, value)
+
+    @property
+    def server_connect_timeout(self) -> int:
+        """Get or set the server connection timeout in milliseconds"""
+        return option(Option.GET_SERVER_CONNECT_TIMEOUT)
+
+    @server_connect_timeout.setter
+    def server_connect_timeout(self, value: int) -> None:
+        option(Option.SET_SERVER_CONNECT_TIMEOUT, value)
+
+    @property
+    def server_timeout(self) -> int:
+        """Get or set the server timeout in milliseconds"""
+        return option(Option.GET_SERVER_TIMEOUT)
+
+    @server_timeout.setter
+    def server_timeout(self, value: int) -> None:
+        option(Option.SET_SERVER_TIMEOUT, value)
diff -pruN 1.17.0-2/pygit2/submodules.py 1.18.2-1/pygit2/submodules.py
--- 1.17.0-2/pygit2/submodules.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/submodules.py	2025-08-16 11:34:29.000000000 +0000
@@ -24,23 +24,32 @@
 # Boston, MA 02110-1301, USA.
 
 from __future__ import annotations
-from typing import TYPE_CHECKING, Iterable, Iterator, Optional, Union
+
+from collections.abc import Iterable, Iterator
+from pathlib import Path
+from typing import TYPE_CHECKING, Optional
 
 from ._pygit2 import Oid
-from .callbacks import git_fetch_options, RemoteCallbacks
+from .callbacks import RemoteCallbacks, git_fetch_options
 from .enums import SubmoduleIgnore, SubmoduleStatus
 from .errors import check_error
-from .ffi import ffi, C
-from .utils import to_bytes, maybe_string
+from .ffi import C, ffi
+from .utils import maybe_string, to_bytes
 
 # Need BaseRepository for type hints, but don't let it cause a circular dependency
 if TYPE_CHECKING:
+    from pygit2 import Repository
+    from pygit2._libgit2.ffi import GitSubmoduleC
+
     from .repository import BaseRepository
 
 
 class Submodule:
+    _repo: BaseRepository
+    _subm: 'GitSubmoduleC'
+
     @classmethod
-    def _from_c(cls, repo: BaseRepository, cptr):
+    def _from_c(cls, repo: BaseRepository, cptr: 'GitSubmoduleC') -> 'Submodule':
         subm = cls.__new__(cls)
 
         subm._repo = repo
@@ -48,18 +57,18 @@ class Submodule:
 
         return subm
 
-    def __del__(self):
+    def __del__(self) -> None:
         C.git_submodule_free(self._subm)
 
-    def open(self):
+    def open(self) -> Repository:
         """Open the repository for a submodule."""
         crepo = ffi.new('git_repository **')
         err = C.git_submodule_open(crepo, self._subm)
         check_error(err)
 
-        return self._repo._from_c(crepo[0], True)
+        return self._repo._from_c(crepo[0], True)  # type: ignore[attr-defined]
 
-    def init(self, overwrite: bool = False):
+    def init(self, overwrite: bool = False) -> None:
         """
         Just like "git submodule init", this copies information about the submodule
         into ".git/config".
@@ -74,8 +83,11 @@ class Submodule:
         check_error(err)
 
     def update(
-        self, init: bool = False, callbacks: RemoteCallbacks = None, depth: int = 0
-    ):
+        self,
+        init: bool = False,
+        callbacks: Optional[RemoteCallbacks] = None,
+        depth: int = 0,
+    ) -> None:
         """
         Update a submodule. This will clone a missing submodule and checkout
         the subrepository to the commit specified in the index of the
@@ -108,7 +120,7 @@ class Submodule:
             err = C.git_submodule_update(self._subm, int(init), opts)
             payload.check_error(err)
 
-    def reload(self, force: bool = False):
+    def reload(self, force: bool = False) -> None:
         """
         Reread submodule info from config, index, and HEAD.
 
@@ -136,11 +148,19 @@ class Submodule:
         return ffi.string(path).decode('utf-8')
 
     @property
-    def url(self) -> Union[str, None]:
+    def url(self) -> str | None:
         """URL of the submodule."""
         url = C.git_submodule_url(self._subm)
         return maybe_string(url)
 
+    @url.setter
+    def url(self, url: str) -> None:
+        crepo = self._repo._repo
+        cname = ffi.new('char[]', to_bytes(self.name))
+        curl = ffi.new('char[]', to_bytes(url))
+        err = C.git_submodule_set_url(crepo, cname, curl)
+        check_error(err)
+
     @property
     def branch(self):
         """Branch that is to be tracked by the submodule."""
@@ -148,7 +168,7 @@ class Submodule:
         return ffi.string(branch).decode('utf-8')
 
     @property
-    def head_id(self) -> Union[Oid, None]:
+    def head_id(self) -> Oid | None:
         """
         The submodule's HEAD commit id (as recorded in the superproject's
         current HEAD tree).
@@ -167,7 +187,7 @@ class SubmoduleCollection:
     def __init__(self, repository: BaseRepository):
         self._repository = repository
 
-    def __getitem__(self, name: str) -> Submodule:
+    def __getitem__(self, name: str | Path) -> Submodule:
         """
         Look up submodule information by name or path.
         Raises KeyError if there is no such submodule.
@@ -186,7 +206,7 @@ class SubmoduleCollection:
         for s in self._repository.listall_submodules():
             yield self[s]
 
-    def get(self, name: str) -> Union[Submodule, None]:
+    def get(self, name: str) -> Submodule | None:
         """
         Look up submodule information by name or path.
         Unlike __getitem__, this returns None if the submodule is not found.
@@ -261,7 +281,9 @@ class SubmoduleCollection:
         check_error(err)
         return submodule_instance
 
-    def init(self, submodules: Optional[Iterable[str]] = None, overwrite: bool = False):
+    def init(
+        self, submodules: Optional[Iterable[str]] = None, overwrite: bool = False
+    ) -> None:
         """
         Initialize submodules in the repository. Just like "git submodule init",
         this copies information about the submodules into ".git/config".
@@ -289,7 +311,7 @@ class SubmoduleCollection:
         init: bool = False,
         callbacks: Optional[RemoteCallbacks] = None,
         depth: int = 0,
-    ):
+    ) -> None:
         """
         Update submodules. This will clone a missing submodule and checkout
         the subrepository to the commit specified in the index of the
@@ -346,7 +368,7 @@ class SubmoduleCollection:
         check_error(err)
         return SubmoduleStatus(cstatus[0])
 
-    def cache_all(self):
+    def cache_all(self) -> None:
         """
         Load and cache all submodules in the repository.
 
@@ -359,7 +381,7 @@ class SubmoduleCollection:
         err = C.git_repository_submodule_cache_all(self._repository._repo)
         check_error(err)
 
-    def cache_clear(self):
+    def cache_clear(self) -> None:
         """
         Clear the submodule cache populated by `submodule_cache_all`.
         If there is no cache, do nothing.
diff -pruN 1.17.0-2/pygit2/utils.py 1.18.2-1/pygit2/utils.py
--- 1.17.0-2/pygit2/utils.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pygit2/utils.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,19 +25,49 @@
 
 import contextlib
 import os
+from collections.abc import Generator, Iterator, Sequence
+from types import TracebackType
+from typing import (
+    TYPE_CHECKING,
+    Generic,
+    Optional,
+    Protocol,
+    TypeVar,
+    Union,
+    overload,
+)
 
 # Import from pygit2
-from .ffi import ffi, C
+from .ffi import C, ffi
 
+if TYPE_CHECKING:
+    from ._libgit2.ffi import ArrayC, GitStrrayC, char, char_pointer
 
-def maybe_string(ptr):
+
+def maybe_string(ptr: 'char_pointer | None') -> str | None:
     if not ptr:
         return None
 
-    return ffi.string(ptr).decode('utf8')
+    return ffi.string(ptr).decode('utf8', errors='surrogateescape')
 
 
-def to_bytes(s, encoding='utf-8', errors='strict'):
+@overload
+def to_bytes(
+    s: str | bytes | os.PathLike[str] | os.PathLike[bytes],
+    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], os.PathLike[bytes], None],
+    encoding: str = 'utf-8',
+    errors: str = 'strict',
+) -> Union[bytes, 'ffi.NULL_TYPE']:
     if s == ffi.NULL or s is None:
         return ffi.NULL
 
@@ -47,10 +77,10 @@ def to_bytes(s, encoding='utf-8', errors
     if isinstance(s, bytes):
         return s
 
-    return s.encode(encoding, errors)
+    return s.encode(encoding, errors)  # type: ignore[union-attr]
 
 
-def to_str(s):
+def to_str(s: str | bytes | os.PathLike[str] | os.PathLike[bytes]) -> str:
     if hasattr(s, '__fspath__'):
         s = os.fspath(s)
 
@@ -63,7 +93,7 @@ def to_str(s):
     raise TypeError(f'unexpected type "{repr(s)}"')
 
 
-def ptr_to_bytes(ptr_cdata):
+def ptr_to_bytes(ptr_cdata) -> bytes:
     """
     Convert a pointer coming from C code (<cdata 'some_type *'>)
     to a byte buffer containing the address that the pointer refers to.
@@ -74,13 +104,13 @@ def ptr_to_bytes(ptr_cdata):
 
 
 @contextlib.contextmanager
-def new_git_strarray():
+def new_git_strarray() -> Generator['GitStrrayC', None, None]:
     strarray = ffi.new('git_strarray *')
     yield strarray
     C.git_strarray_dispose(strarray)
 
 
-def strarray_to_strings(arr):
+def strarray_to_strings(arr) -> list[str]:
     """
     Return a list of strings from a git_strarray pointer.
 
@@ -113,18 +143,22 @@ class StrArray:
     contents of 'struct' only remain valid within the StrArray context.
     """
 
-    def __init__(self, l):
+    __array: 'GitStrrayC | ffi.NULL_TYPE'
+    __strings: list['None | ArrayC[char]']
+    __arr: 'ArrayC[char_pointer]'
+
+    def __init__(self, lst: None | Sequence[str | os.PathLike[str]]):
         # Allow passing in None as lg2 typically considers them the same as empty
-        if l is None:
+        if lst is None:
             self.__array = ffi.NULL
             return
 
-        if not isinstance(l, (list, tuple)):
+        if not isinstance(lst, (list, tuple)):
             raise TypeError('Value must be a list')
 
-        strings = [None] * len(l)
-        for i in range(len(l)):
-            li = l[i]
+        strings: list[None | 'ArrayC[char]'] = [None] * len(lst)
+        for i in range(len(lst)):
+            li = lst[i]
             if not isinstance(li, str) and not hasattr(li, '__fspath__'):
                 raise TypeError('Value must be a string or PathLike object')
 
@@ -132,19 +166,24 @@ class StrArray:
 
         self.__arr = ffi.new('char *[]', strings)
         self.__strings = strings
-        self.__array = ffi.new('git_strarray *', [self.__arr, len(strings)])
+        self.__array = ffi.new('git_strarray *', [self.__arr, len(strings)])  # type: ignore[call-overload]
 
-    def __enter__(self):
+    def __enter__(self) -> 'StrArray':
         return self
 
-    def __exit__(self, type, value, traceback):
+    def __exit__(
+        self,
+        exc_type: Optional[type[BaseException]],
+        exc_value: Optional[BaseException],
+        traceback: Optional[TracebackType],
+    ) -> None:
         pass
 
     @property
-    def ptr(self):
+    def ptr(self) -> 'GitStrrayC | ffi.NULL_TYPE':
         return self.__array
 
-    def assign_to(self, git_strarray):
+    def assign_to(self, git_strarray: 'GitStrrayC') -> None:
         if self.__array == ffi.NULL:
             git_strarray.strings = ffi.NULL
             git_strarray.count = 0
@@ -153,22 +192,31 @@ class StrArray:
             git_strarray.count = len(self.__strings)
 
 
-class GenericIterator:
+T = TypeVar('T')
+U = TypeVar('U', covariant=True)
+
+
+class SequenceProtocol(Protocol[U]):
+    def __len__(self) -> int: ...
+    def __getitem__(self, index: int) -> U: ...
+
+
+class GenericIterator(Generic[T]):
     """Helper to easily implement an iterator.
 
     The constructor gets a container which must implement __len__ and
     __getitem__
     """
 
-    def __init__(self, container):
+    def __init__(self, container: SequenceProtocol[T]) -> None:
         self.container = container
         self.length = len(container)
         self.idx = 0
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[T]:
         return self
 
-    def __next__(self):
+    def __next__(self) -> T:
         idx = self.idx
         if idx >= self.length:
             raise StopIteration
diff -pruN 1.17.0-2/pyproject.toml 1.18.2-1/pyproject.toml
--- 1.17.0-2/pyproject.toml	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/pyproject.toml	2025-08-16 11:34:29.000000000 +0000
@@ -3,18 +3,17 @@ requires = ["setuptools", "wheel"]
 
 [tool.cibuildwheel]
 enable = ["pypy"]
-skip = "*musllinux_aarch64 *musllinux_ppc64le"
+skip = "*musllinux_aarch64 *musllinux_ppc64le cp314*"
 
-archs = ["auto"]
+archs = ["native"]
 build-frontend = "default"
 dependency-versions = "pinned"
-environment = {LIBGIT2_VERSION="1.9.0", LIBSSH2_VERSION="1.11.1", OPENSSL_VERSION="3.2.3", LIBGIT2="/project/ci"}
+environment = {LIBGIT2_VERSION="1.9.1", LIBSSH2_VERSION="1.11.1", OPENSSL_VERSION="3.3.3", LIBGIT2="/project/ci"}
 
 before-all = "sh build.sh"
 
 [tool.cibuildwheel.linux]
 repair-wheel-command = "LD_LIBRARY_PATH=/project/ci/lib64 auditwheel repair -w {dest_dir} {wheel}"
-archs = ["x86_64", "aarch64", "ppc64le"]
 
 [[tool.cibuildwheel.overrides]]
 select = "*-musllinux*"
@@ -22,18 +21,22 @@ repair-wheel-command = "LD_LIBRARY_PATH=
 
 [tool.cibuildwheel.macos]
 archs = ["universal2"]
-environment = {LIBGIT2_VERSION="1.9.0", LIBSSH2_VERSION="1.11.1", OPENSSL_VERSION="3.2.3", LIBGIT2="/Users/runner/work/pygit2/pygit2/ci"}
+environment = {LIBGIT2_VERSION="1.9.1", LIBSSH2_VERSION="1.11.1", OPENSSL_VERSION="3.3.3", LIBGIT2="/Users/runner/work/pygit2/pygit2/ci"}
 repair-wheel-command = "DYLD_LIBRARY_PATH=/Users/runner/work/pygit2/pygit2/ci/lib delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}"
 
 [tool.ruff]
+extend-exclude = [ ".cache", ".coverage", "build", "site-packages", "venv*"]
 target-version = "py310"  # oldest supported Python version
-fix = true
-extend-exclude = [
-    ".cache",
-    ".coverage",
-    "build",
-    "venv*",
-]
+
+[tool.ruff.lint]
+select = ["E4", "E7", "E9", "F", "I", "UP035", "UP007"]
 
 [tool.ruff.format]
 quote-style = "single"
+
+[tool.codespell]
+# Ref: https://github.com/codespell-project/codespell#using-a-config-file
+skip = '.git*'
+check-hidden = true
+# ignore-regex = ''
+ignore-words-list = 'devault,claus'
diff -pruN 1.17.0-2/requirements-test.txt 1.18.2-1/requirements-test.txt
--- 1.17.0-2/requirements-test.txt	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/requirements-test.txt	2025-08-16 11:34:29.000000000 +0000
@@ -1,2 +1,4 @@
 pytest
 pytest-cov
+mypy
+types-cffi
diff -pruN 1.17.0-2/setup.py 1.18.2-1/setup.py
--- 1.17.0-2/setup.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/setup.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,16 +23,18 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-# Import setuptools before distutils to avoid user warning
-from setuptools import setup, Extension
+# mypy: disable-error-code="import-not-found, import-untyped"
 
+# Import setuptools before distutils to avoid user warning
+import os
+import sys
+from distutils import log  # type: ignore[attr-defined]
 from distutils.command.build import build
 from distutils.command.sdist import sdist
-from distutils import log
-import os
 from pathlib import Path
-from subprocess import Popen, PIPE
-import sys
+from subprocess import PIPE, Popen
+
+from setuptools import Extension, setup
 
 # Import stuff from pygit2/_utils.py without loading the whole pygit2 package
 sys.path.insert(0, 'pygit2')
@@ -45,7 +47,7 @@ libgit2_bin, libgit2_kw = get_libgit2_pa
 
 
 class sdist_files_from_git(sdist):
-    def get_file_list(self):
+    def get_file_list(self) -> None:
         popen = Popen(
             ['git', 'ls-files'], stdout=PIPE, stderr=PIPE, universal_newlines=True
         )
@@ -54,7 +56,7 @@ class sdist_files_from_git(sdist):
             print(stderrdata)
             sys.exit()
 
-        def exclude(line):
+        def exclude(line: str) -> bool:
             for prefix in ['.', 'appveyor.yml', 'docs/', 'misc/']:
                 if line.startswith(prefix):
                     return True
@@ -95,11 +97,11 @@ cmdclass = {
 
 # On Windows, we install the git2.dll too.
 class BuildWithDLLs(build):
-    def _get_dlls(self):
+    def _get_dlls(self) -> list[tuple[Path, Path]]:
         # return a list of (FQ-in-name, relative-out-name) tuples.
         ret = []
         bld_ext = self.distribution.get_command_obj('build_ext')
-        compiler_type = bld_ext.compiler.compiler_type
+        compiler_type = bld_ext.compiler.compiler_type  # type: ignore[attr-defined]
         libgit2_dlls = []
         if compiler_type == 'msvc':
             libgit2_dlls.append('git2.dll')
@@ -119,7 +121,7 @@ class BuildWithDLLs(build):
                 log.debug(f'(looked in {look_dirs})')
         return ret
 
-    def run(self):
+    def run(self) -> None:
         build.run(self)
         for s, d in self._get_dlls():
             self.copy_file(s, d)
@@ -127,7 +129,7 @@ class BuildWithDLLs(build):
 
 # On Windows we package up the dlls with the plugin.
 if os.name == 'nt':
-    cmdclass['build'] = BuildWithDLLs
+    cmdclass['build'] = BuildWithDLLs  # type: ignore[assignment]
 
 src = __dir__ / 'src'
 pygit2_exts = [str(path) for path in sorted(src.iterdir()) if path.suffix == '.c']
diff -pruN 1.17.0-2/src/blob.c 1.18.2-1/src/blob.c
--- 1.17.0-2/src/blob.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/blob.c	2025-08-16 11:34:29.000000000 +0000
@@ -41,7 +41,7 @@ extern PyObject *GitError;
 extern PyTypeObject BlobType;
 
 PyDoc_STRVAR(Blob_diff__doc__,
-  "diff([blob: Blob, flag: int = GIT_DIFF_NORMAL, old_as_path: str, new_as_path: str]) -> Patch\n"
+  "diff([blob: Blob, flags: int = GIT_DIFF_NORMAL, old_as_path: str, new_as_path: str]) -> Patch\n"
   "\n"
   "Directly generate a :py:class:`pygit2.Patch` from the difference\n"
   "between two blobs.\n"
@@ -53,14 +53,22 @@ PyDoc_STRVAR(Blob_diff__doc__,
   "blob : Blob\n"
   "    The :py:class:`~pygit2.Blob` to diff.\n"
   "\n"
-  "flag\n"
-  "    A GIT_DIFF_* constant.\n"
+  "flags\n"
+  "    A combination of GIT_DIFF_* constant.\n"
   "\n"
   "old_as_path : str\n"
   "    Treat old blob as if it had this filename.\n"
   "\n"
   "new_as_path : str\n"
-  "    Treat new blob as if it had this filename.\n");
+  "    Treat new blob as if it had this filename.\n"
+  "\n"
+  "context_lines: int\n"
+  "    Number of unchanged lines that define the boundary of a hunk\n"
+  "    (and to display before and after).\n"
+  "\n"
+  "interhunk_lines: int\n"
+  "    Maximum number of unchanged lines between hunk boundaries\n"
+  "    before the hunks will be merged into one.\n");
 
 PyObject *
 Blob_diff(Blob *self, PyObject *args, PyObject *kwds)
@@ -70,11 +78,12 @@ Blob_diff(Blob *self, PyObject *args, Py
     char *old_as_path = NULL, *new_as_path = NULL;
     Blob *other = NULL;
     int err;
-    char *keywords[] = {"blob", "flag", "old_as_path", "new_as_path", NULL};
+    char *keywords[] = {"blob", "flags", "old_as_path", "new_as_path", "context_lines", "interhunk_lines", NULL};
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!Iss", keywords,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!IssHH", keywords,
                                      &BlobType, &other, &opts.flags,
-                                     &old_as_path, &new_as_path))
+                                     &old_as_path, &new_as_path,
+                                     &opts.context_lines, &opts.interhunk_lines))
         return NULL;
 
     if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load
@@ -91,7 +100,7 @@ Blob_diff(Blob *self, PyObject *args, Py
 
 
 PyDoc_STRVAR(Blob_diff_to_buffer__doc__,
-  "diff_to_buffer(buffer: bytes = None, flag: int = GIT_DIFF_NORMAL[, old_as_path: str, buffer_as_path: str]) -> Patch\n"
+  "diff_to_buffer(buffer: bytes = None, flags: int = GIT_DIFF_NORMAL[, old_as_path: str, buffer_as_path: str]) -> Patch\n"
   "\n"
   "Directly generate a :py:class:`~pygit2.Patch` from the difference\n"
   "between a blob and a buffer.\n"
@@ -103,8 +112,8 @@ PyDoc_STRVAR(Blob_diff_to_buffer__doc__,
   "buffer : bytes\n"
   "    Raw data for new side of diff.\n"
   "\n"
-  "flag\n"
-  "    A GIT_DIFF_* constant.\n"
+  "flags\n"
+  "    A combination of GIT_DIFF_* constants.\n"
   "\n"
   "old_as_path : str\n"
   "    Treat old blob as if it had this filename.\n"
@@ -121,8 +130,7 @@ Blob_diff_to_buffer(Blob *self, PyObject
     const char *buffer = NULL;
     Py_ssize_t buffer_len;
     int err;
-    char *keywords[] = {"buffer", "flag", "old_as_path", "buffer_as_path",
-                        NULL};
+    char *keywords[] = {"buffer", "flags", "old_as_path", "buffer_as_path", NULL};
 
     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|z#Iss", keywords,
                                      &buffer, &buffer_len, &opts.flags,
@@ -227,7 +235,7 @@ static void blob_filter_stream_free(git_
 
 
 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 -pruN 1.17.0-2/src/odb_backend.c 1.18.2-1/src/odb_backend.c
--- 1.17.0-2/src/odb_backend.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/odb_backend.c	2025-08-16 11:34:29.000000000 +0000
@@ -103,7 +103,7 @@ pgit_odb_backend_read_prefix(git_oid *oi
     if (result == NULL)
         return git_error_for_exc();
 
-    // Parse output from calback
+    // Parse output from callback
     PyObject *py_oid_out;
     Py_ssize_t type_value;
     const char *bytes;
diff -pruN 1.17.0-2/src/oid.c 1.18.2-1/src/oid.c
--- 1.17.0-2/src/oid.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/oid.c	2025-08-16 11:34:29.000000000 +0000
@@ -34,6 +34,8 @@
 
 PyTypeObject OidType;
 
+static const git_oid oid_zero = GIT_OID_SHA1_ZERO;
+
 
 PyObject *
 git_oid_to_python(const git_oid *oid)
@@ -255,6 +257,13 @@ Oid__str__(Oid *self)
     return git_oid_to_py_str(&self->oid);
 }
 
+int
+Oid__bool(PyObject *self)
+{
+    git_oid *oid = &((Oid*)self)->oid;
+    return !git_oid_equal(oid, &oid_zero);
+}
+
 PyDoc_STRVAR(Oid_raw__doc__, "Raw oid, a 20 bytes string.");
 
 PyObject *
@@ -269,6 +278,45 @@ PyGetSetDef Oid_getseters[] = {
     {NULL},
 };
 
+PyNumberMethods Oid_as_number = {
+     0,                          /* nb_add */
+     0,                          /* nb_subtract */
+     0,                          /* nb_multiply */
+     0,                          /* nb_remainder */
+     0,                          /* nb_divmod */
+     0,                          /* nb_power */
+     0,                          /* nb_negative */
+     0,                          /* nb_positive */
+     0,                          /* nb_absolute */
+     Oid__bool,                  /* nb_bool */
+     0,                          /* nb_invert */
+     0,                          /* nb_lshift */
+     0,                          /* nb_rshift */
+     0,                          /* nb_and */
+     0,                          /* nb_xor */
+     0,                          /* nb_or */
+     0,                          /* nb_int */
+     0,                          /* nb_reserved */
+     0,                          /* nb_float */
+     0,                          /* nb_inplace_add */
+     0,                          /* nb_inplace_subtract */
+     0,                          /* nb_inplace_multiply */
+     0,                          /* nb_inplace_remainder */
+     0,                          /* nb_inplace_power */
+     0,                          /* nb_inplace_lshift */
+     0,                          /* nb_inplace_rshift */
+     0,                          /* nb_inplace_and */
+     0,                          /* nb_inplace_xor */
+     0,                          /* nb_inplace_or */
+     0,                          /* nb_floor_divide */
+     0,                          /* nb_true_divide */
+     0,                          /* nb_inplace_floor_divide */
+     0,                          /* nb_inplace_true_divide */
+     0,                          /* nb_index */
+     0,                          /* nb_matrix_multiply */
+     0,                          /* nb_inplace_matrix_multiply */
+};
+
 PyDoc_STRVAR(Oid__doc__, "Object id.");
 
 PyTypeObject OidType = {
@@ -282,7 +330,7 @@ PyTypeObject OidType = {
     0,                                         /* tp_setattr        */
     0,                                         /* tp_compare        */
     (reprfunc)Oid__str__,                      /* tp_repr           */
-    0,                                         /* tp_as_number      */
+    &Oid_as_number,                            /* tp_as_number      */
     0,                                         /* tp_as_sequence    */
     0,                                         /* tp_as_mapping     */
     (hashfunc)Oid_hash,                        /* tp_hash           */
diff -pruN 1.17.0-2/src/options.c 1.18.2-1/src/options.c
--- 1.17.0-2/src/options.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/options.c	1970-01-01 00:00:00.000000000 +0000
@@ -1,309 +0,0 @@
-/*
- * Copyright 2010-2025 The pygit2 contributors
- *
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file.  (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING.  If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#define PY_SSIZE_T_CLEAN
-#include <Python.h>
-#include <structmember.h>
-#include "error.h"
-#include "types.h"
-#include "utils.h"
-
-extern PyObject *GitError;
-
-static PyObject *
-get_search_path(long level)
-{
-    git_buf buf = {NULL};
-    PyObject *py_path;
-    int err;
-
-    err = git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, &buf);
-    if (err < 0)
-        return Error_set(err);
-
-    py_path = to_unicode_n(buf.ptr, buf.size, NULL, NULL);
-    git_buf_dispose(&buf);
-
-    if (!py_path)
-        return NULL;
-
-    return py_path;
-}
-
-PyObject *
-option(PyObject *self, PyObject *args)
-{
-    long option;
-    int error;
-    PyObject *py_option;
-
-    py_option = PyTuple_GetItem(args, 0);
-    if (!py_option)
-        return NULL;
-
-    if (!PyLong_Check(py_option))
-        return Error_type_error(
-            "option should be an integer, got %.200s", py_option);
-
-    option = PyLong_AsLong(py_option);
-
-    switch (option) {
-
-        case GIT_OPT_GET_MWINDOW_FILE_LIMIT:
-        case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT:
-        case GIT_OPT_GET_MWINDOW_SIZE:
-        {
-            size_t value;
-
-            error = git_libgit2_opts(option, &value);
-            if (error < 0)
-                return Error_set(error);
-
-            return PyLong_FromSize_t(value);
-        }
-
-        case GIT_OPT_SET_MWINDOW_FILE_LIMIT:
-        case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT:
-        case GIT_OPT_SET_MWINDOW_SIZE:
-        {
-            PyObject *py_value = PyTuple_GetItem(args, 1);
-            if (!py_value)
-                return NULL;
-
-            if (!PyLong_Check(py_value))
-                return Error_type_error("expected integer, got %.200s", py_value);
-
-            size_t value = PyLong_AsSize_t(py_value);
-            error = git_libgit2_opts(option, value);
-            if (error  < 0)
-                return Error_set(error);
-
-            Py_RETURN_NONE;
-        }
-
-        case GIT_OPT_GET_SEARCH_PATH:
-        {
-            PyObject *py_level = PyTuple_GetItem(args, 1);
-            if (!py_level)
-                return NULL;
-
-            if (!PyLong_Check(py_level))
-                return Error_type_error("level should be an integer, got %.200s", py_level);
-
-            return get_search_path(PyLong_AsLong(py_level));
-        }
-
-        case GIT_OPT_SET_SEARCH_PATH:
-        {
-            PyObject *py_level = PyTuple_GetItem(args, 1);
-            if (!py_level)
-                return NULL;
-
-            PyObject *py_path = PyTuple_GetItem(args, 2);
-            if (!py_path)
-                return NULL;
-
-            if (!PyLong_Check(py_level))
-                return Error_type_error("level should be an integer, got %.200s", py_level);
-
-            const char *path = pgit_borrow(py_path);
-            if (!path)
-                return NULL;
-
-            int err = git_libgit2_opts(option, PyLong_AsLong(py_level), path);
-            if (err < 0)
-                return Error_set(err);
-
-            Py_RETURN_NONE;
-        }
-
-        case GIT_OPT_SET_CACHE_OBJECT_LIMIT:
-        {
-            size_t limit;
-            int object_type;
-            PyObject *py_object_type, *py_limit;
-
-            py_object_type = PyTuple_GetItem(args, 1);
-            if (!py_object_type)
-                return NULL;
-
-            py_limit = PyTuple_GetItem(args, 2);
-            if (!py_limit)
-                return NULL;
-
-            if (!PyLong_Check(py_limit))
-                return Error_type_error(
-                    "limit should be an integer, got %.200s", py_limit);
-
-            object_type = PyLong_AsLong(py_object_type);
-            limit = PyLong_AsSize_t(py_limit);
-            error = git_libgit2_opts(option, object_type, limit);
-
-            if (error < 0)
-                return Error_set(error);
-
-            Py_RETURN_NONE;
-        }
-
-        case GIT_OPT_SET_CACHE_MAX_SIZE:
-        {
-            size_t max_size;
-            PyObject *py_max_size;
-
-            py_max_size = PyTuple_GetItem(args, 1);
-            if (!py_max_size)
-                return NULL;
-
-            if (!PyLong_Check(py_max_size))
-                return Error_type_error(
-                    "max_size should be an integer, got %.200s", py_max_size);
-
-            max_size = PyLong_AsSize_t(py_max_size);
-            error = git_libgit2_opts(option, max_size);
-            if (error < 0)
-                return Error_set(error);
-
-            Py_RETURN_NONE;
-        }
-
-        case GIT_OPT_GET_CACHED_MEMORY:
-        {
-            size_t current;
-            size_t allowed;
-            PyObject* tup = PyTuple_New(2);
-
-            error = git_libgit2_opts(option, &current, &allowed);
-            if (error < 0)
-                return Error_set(error);
-
-            PyTuple_SetItem(tup, 0, PyLong_FromLong(current));
-            PyTuple_SetItem(tup, 1, PyLong_FromLong(allowed));
-
-            return tup;
-        }
-
-        case GIT_OPT_GET_TEMPLATE_PATH:
-        case GIT_OPT_SET_TEMPLATE_PATH:
-        {
-            Py_INCREF(Py_NotImplemented);
-            return Py_NotImplemented;
-        }
-
-        case GIT_OPT_SET_SSL_CERT_LOCATIONS:
-        {
-            PyObject *py_file, *py_dir;
-            char *file_path=NULL, *dir_path=NULL;
-            int err;
-
-            py_file = PyTuple_GetItem(args, 1);
-            if (!py_file)
-                return NULL;
-            py_dir = PyTuple_GetItem(args, 2);
-            if (!py_dir)
-                return NULL;
-
-            /* py_file and py_dir are only valid if they are strings */
-            PyObject *tvalue_file = NULL;
-            if (PyUnicode_Check(py_file) || PyBytes_Check(py_file))
-                file_path = pgit_borrow_fsdefault(py_file, &tvalue_file);
-
-            PyObject *tvalue_dir = NULL;
-            if (PyUnicode_Check(py_dir) || PyBytes_Check(py_dir))
-                dir_path = pgit_borrow_fsdefault(py_dir, &tvalue_dir);
-
-            err = git_libgit2_opts(option, file_path, dir_path);
-            Py_XDECREF(tvalue_file);
-            Py_XDECREF(tvalue_dir);
-
-            if (err)
-                return Error_set(err);
-
-            Py_RETURN_NONE;
-        }
-
-        case GIT_OPT_SET_USER_AGENT:
-        {
-            Py_INCREF(Py_NotImplemented);
-            return Py_NotImplemented;
-        }
-
-        // int enabled
-        case GIT_OPT_ENABLE_CACHING:
-        case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION:
-        case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION:
-        case GIT_OPT_ENABLE_OFS_DELTA:
-        case GIT_OPT_ENABLE_FSYNC_GITDIR:
-        case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION:
-        case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY:
-        case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS:
-        case GIT_OPT_SET_OWNER_VALIDATION:
-        {
-            PyObject *py_value = PyTuple_GetItem(args, 1);
-            if (!py_value)
-                return NULL;
-
-            if (!PyLong_Check(py_value))
-                return Error_type_error("expected integer, got %.200s", py_value);
-
-            int value = PyLong_AsSize_t(py_value);
-            error = git_libgit2_opts(option, value);
-            if (error < 0)
-                return Error_set(error);
-
-            Py_RETURN_NONE;
-        }
-
-        // int enabled getter
-        case GIT_OPT_GET_OWNER_VALIDATION:
-        {
-            int enabled;
-
-            error = git_libgit2_opts(option, &enabled);
-            if (error < 0)
-                return Error_set(error);
-
-            return PyLong_FromLong(enabled);
-        }
-
-        // Not implemented
-        case GIT_OPT_SET_SSL_CIPHERS:
-        case GIT_OPT_GET_USER_AGENT:
-        case GIT_OPT_GET_WINDOWS_SHAREMODE:
-        case GIT_OPT_SET_WINDOWS_SHAREMODE:
-        case GIT_OPT_SET_ALLOCATOR:
-        case GIT_OPT_GET_PACK_MAX_OBJECTS:
-        case GIT_OPT_SET_PACK_MAX_OBJECTS:
-        {
-            Py_INCREF(Py_NotImplemented);
-            return Py_NotImplemented;
-        }
-
-    }
-
-    PyErr_SetString(PyExc_ValueError, "unknown/unsupported option value");
-    return NULL;
-}
diff -pruN 1.17.0-2/src/options.h 1.18.2-1/src/options.h
--- 1.17.0-2/src/options.h	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/options.h	1970-01-01 00:00:00.000000000 +0000
@@ -1,72 +0,0 @@
-/*
- * Copyright 2010-2025 The pygit2 contributors
- *
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file.  (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING.  If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef INCLUDE_pygit2_blame_h
-#define INCLUDE_pygit2_blame_h
-
-#define PY_SSIZE_T_CLEAN
-#include <Python.h>
-#include <git2.h>
-#include "types.h"
-
-PyDoc_STRVAR(option__doc__,
-  "option(option, ...)\n"
-  "\n"
-  "Get or set a libgit2 option.\n"
-  "\n"
-  "Parameters:\n"
-  "\n"
-  "GIT_OPT_GET_SEARCH_PATH, level\n"
-  "    Get the config search path for the given level.\n"
-  "\n"
-  "GIT_OPT_SET_SEARCH_PATH, level, path\n"
-  "    Set the config search path for the given level.\n"
-  "\n"
-  "GIT_OPT_GET_MWINDOW_SIZE\n"
-  "    Get the maximum mmap window size.\n"
-  "\n"
-  "GIT_OPT_SET_MWINDOW_SIZE, size\n"
-  "    Set the maximum mmap window size.\n"
-  "\n"
-  "GIT_OPT_GET_MWINDOW_FILE_LIMIT\n"
-  "    Get the maximum number of files that will be mapped at any time by the library.\n"
-  "\n"
-  "GIT_OPT_SET_MWINDOW_FILE_LIMIT, size\n"
-  "    Set the maximum number of files that can be mapped at any time by the library. The default (0) is unlimited.\n"
-  "\n"
-  "GIT_OPT_GET_OWNER_VALIDATION\n"
-  "    Gets the owner validation setting for repository directories.\n"
-  "\n"
-  "GIT_OPT_SET_OWNER_VALIDATION, enabled\n"
-  "    Set that repository directories should be owned by the current user.\n"
-  "    The default is to validate ownership.\n"
-  );
-
-
-PyObject *option(PyObject *self, PyObject *args);
-
-#endif
diff -pruN 1.17.0-2/src/pygit2.c 1.18.2-1/src/pygit2.c
--- 1.17.0-2/src/pygit2.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/pygit2.c	2025-08-16 11:34:29.000000000 +0000
@@ -35,7 +35,6 @@
 #include "utils.h"
 #include "repository.h"
 #include "oid.h"
-#include "options.h"
 #include "filter.h"
 
 PyObject *GitError;
@@ -290,7 +289,7 @@ PyDoc_STRVAR(filter_register__doc__,
     "\n"
     "`priority` defaults to GIT_FILTER_DRIVER_PRIORITY which imitates a core\n"
     "Git filter driver that will be run last on checkout (smudge) and first \n"
-    "on checkin (clean).\n"
+    "on check-in (clean).\n"
     "\n"
     "Note that the filter registry is not thread safe. Any registering or\n"
     "deregistering of filters should be done outside of any possible usage\n"
@@ -439,7 +438,6 @@ PyMethodDef module_methods[] = {
     {"hash", hash, METH_VARARGS, hash__doc__},
     {"hashfile", hashfile, METH_VARARGS, hashfile__doc__},
     {"init_file_backend", init_file_backend, METH_VARARGS, init_file_backend__doc__},
-    {"option", option, METH_VARARGS, option__doc__},
     {"reference_is_valid_name", reference_is_valid_name, METH_O, reference_is_valid_name__doc__},
     {"tree_entry_cmp", tree_entry_cmp, METH_VARARGS, tree_entry_cmp__doc__},
     {"filter_register", (PyCFunction)filter_register, METH_VARARGS | METH_KEYWORDS, filter_register__doc__},
@@ -473,39 +471,6 @@ PyInit__pygit2(void)
     ADD_CONSTANT_INT(m, LIBGIT2_VER_REVISION)
     ADD_CONSTANT_STR(m, LIBGIT2_VERSION)
 
-    /* libgit2 options */
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_SIZE);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_SIZE);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_MAPPED_LIMIT);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_MAPPED_LIMIT);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_SEARCH_PATH);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_SEARCH_PATH);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_CACHE_OBJECT_LIMIT);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_CACHE_MAX_SIZE);
-    ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_CACHING);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_CACHED_MEMORY);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_TEMPLATE_PATH);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_TEMPLATE_PATH);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_SSL_CERT_LOCATIONS);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_USER_AGENT);
-    ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_OBJECT_CREATION);
-    ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_SSL_CIPHERS);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_USER_AGENT);
-    ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_OFS_DELTA);
-    ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_FSYNC_GITDIR);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_WINDOWS_SHAREMODE);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_WINDOWS_SHAREMODE);
-    ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_ALLOCATOR);
-    ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_PACK_MAX_OBJECTS);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_PACK_MAX_OBJECTS);
-    ADD_CONSTANT_INT(m, GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_OWNER_VALIDATION);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_OWNER_VALIDATION);
-    ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_FILE_LIMIT);
-    ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_FILE_LIMIT);
 
     /* Exceptions */
     ADD_EXC(m, GitError, NULL);
diff -pruN 1.17.0-2/src/reference.c 1.18.2-1/src/reference.c
--- 1.17.0-2/src/reference.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/reference.c	2025-08-16 11:34:29.000000000 +0000
@@ -230,7 +230,7 @@ Reference_rename(Reference *self, PyObje
     if (err)
         return Error_set(err);
 
-    // Upadate reference
+    // Update reference
     git_reference_free(self->reference);
     self->reference = new_reference;
 
@@ -440,6 +440,14 @@ Reference_type__get__(Reference *self)
     return pygit2_enum(ReferenceTypeEnum, c_type);
 }
 
+PyDoc_STRVAR(Reference__pointer__doc__, "Get the reference's pointer. For internal use only.");
+
+PyObject *
+Reference__pointer__get__(Reference *self)
+{
+    /* Bytes means a raw buffer */
+    return PyBytes_FromStringAndSize((char *) &self->reference, sizeof(git_reference *));
+}
 
 PyDoc_STRVAR(Reference_log__doc__,
   "log() -> RefLogIter\n"
@@ -668,6 +676,7 @@ PyGetSetDef Reference_getseters[] = {
     GETTER(Reference, target),
     GETTER(Reference, raw_target),
     GETTER(Reference, type),
+    GETTER(Reference, _pointer),
     {NULL}
 };
 
diff -pruN 1.17.0-2/src/repository.c 1.18.2-1/src/repository.c
--- 1.17.0-2/src/repository.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/repository.c	2025-08-16 11:34:29.000000000 +0000
@@ -69,7 +69,7 @@ extern PyObject *FileStatusEnum;
 extern PyObject *MergeAnalysisEnum;
 extern PyObject *MergePreferenceEnum;
 
-/* forward-declaration for Repsository._from_c() */
+/* forward-declaration for Repository._from_c() */
 PyTypeObject RepositoryType;
 
 PyObject *
@@ -2078,7 +2078,7 @@ Repository_free(Repository *self)
 PyDoc_STRVAR(Repository_expand_id__doc__,
     "expand_id(hex: str) -> Oid\n"
     "\n"
-    "Expand a string into a full Oid according to the objects in this repsitory.\n");
+    "Expand a string into a full Oid according to the objects in this repository.\n");
 
 PyObject *
 Repository_expand_id(Repository *self, PyObject *py_hex)
diff -pruN 1.17.0-2/src/signature.c 1.18.2-1/src/signature.c
--- 1.17.0-2/src/signature.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/signature.c	2025-08-16 11:34:29.000000000 +0000
@@ -81,7 +81,7 @@ Signature_init(Signature *self, PyObject
 void
 Signature_dealloc(Signature *self)
 {
-    /* self->obj is the owner of the git_signature C structure, so we musn't free it */
+    /* self->obj is the owner of the git_signature C structure, so we mustn't free it */
     if (self->obj) {
         Py_CLEAR(self->obj);
     } else {
diff -pruN 1.17.0-2/src/tree.c 1.18.2-1/src/tree.c
--- 1.17.0-2/src/tree.c	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/src/tree.c	2025-08-16 11:34:29.000000000 +0000
@@ -222,14 +222,16 @@ PyDoc_STRVAR(Tree_diff_to_workdir__doc__
   "    the hunks will be merged into a one.\n");
 
 PyObject *
-Tree_diff_to_workdir(Tree *self, PyObject *args)
+Tree_diff_to_workdir(Tree *self, PyObject *args, PyObject *kwds)
 {
     git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
     git_diff *diff;
     int err;
 
-    if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines,
-                                        &opts.interhunk_lines))
+    char *keywords[] = {"flags", "context_lines", "interhunk_lines", NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|IHH", keywords, &opts.flags,
+                                     &opts.context_lines, &opts.interhunk_lines))
         return NULL;
 
     if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load
@@ -354,8 +356,7 @@ Tree_diff_to_tree(Tree *self, PyObject *
     git_diff *diff;
     git_tree *from, *to = NULL, *tmp;
     int err, swap = 0;
-    char *keywords[] = {"obj", "flags", "context_lines", "interhunk_lines",
-                        "swap", NULL};
+    char *keywords[] = {"obj", "flags", "context_lines", "interhunk_lines", "swap", NULL};
 
     Tree *other = NULL;
 
@@ -406,7 +407,7 @@ PyMappingMethods Tree_as_mapping = {
 
 PyMethodDef Tree_methods[] = {
     METHOD(Tree, diff_to_tree, METH_VARARGS | METH_KEYWORDS),
-    METHOD(Tree, diff_to_workdir, METH_VARARGS),
+    METHOD(Tree, diff_to_workdir, METH_VARARGS | METH_KEYWORDS),
     METHOD(Tree, diff_to_index, METH_VARARGS | METH_KEYWORDS),
     {NULL}
 };
diff -pruN 1.17.0-2/test/conftest.py 1.18.2-1/test/conftest.py
--- 1.17.0-2/test/conftest.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/conftest.py	2025-08-16 11:34:29.000000000 +0000
@@ -1,13 +1,17 @@
-from pathlib import Path
 import platform
+from collections.abc import Generator
+from pathlib import Path
 
 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 = [
@@ -20,41 +24,43 @@ def global_git_config():
 
     # Fix tests running in AppVeyor
     if platform.system() == 'Windows':
-        pygit2.option(pygit2.enums.Option.SET_OWNER_VALIDATION, 0)
+        pygit2.option(pygit2.enums.Option.SET_OWNER_VALIDATION, False)
 
 
 @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)
@@ -62,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 -pruN 1.17.0-2/test/test_apply_diff.py 1.18.2-1/test/test_apply_diff.py
--- 1.17.0-2/test/test_apply_diff.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_apply_diff.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,39 +23,42 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-import pygit2
-from pygit2.enums import ApplyLocation, CheckoutStrategy, FileStatus
-import pytest
-
 import os
 from pathlib import Path
 
+import pytest
+
+import pygit2
+from pygit2 import Diff, Repository
+from pygit2.enums import ApplyLocation, CheckoutStrategy, FileStatus
+
 
-def read_content(testrepo):
+def read_content(testrepo: Repository) -> str:
     with (Path(testrepo.workdir) / 'hello.txt').open('rb') as f:
         return f.read().decode('utf-8')
 
 
 @pytest.fixture
-def new_content():
-    content = ['bye world', 'adiós', 'au revoir monde']
-    content = ''.join(x + os.linesep for x in content)
+def new_content() -> str:
+    content_list = ['bye world', 'adiós', 'au revoir monde']
+    content = ''.join(x + os.linesep for x in content_list)
     return content
 
 
 @pytest.fixture
-def old_content(testrepo):
+def old_content(testrepo: Repository) -> str:
     with (Path(testrepo.workdir) / 'hello.txt').open('rb') as f:
         return f.read().decode('utf-8')
 
 
 @pytest.fixture
-def patch_diff(testrepo, new_content):
+def patch_diff(testrepo: Repository, new_content: str) -> Diff:
     # Create the patch
     with (Path(testrepo.workdir) / 'hello.txt').open('wb') as f:
         f.write(new_content.encode('utf-8'))
 
     patch = testrepo.diff().patch
+    assert patch is not None
 
     # Rollback all changes
     testrepo.checkout('HEAD', strategy=CheckoutStrategy.FORCE)
@@ -65,7 +68,7 @@ def patch_diff(testrepo, new_content):
 
 
 @pytest.fixture
-def foreign_patch_diff():
+def foreign_patch_diff() -> Diff:
     patch_contents = """diff --git a/this_file_does_not_exist b/this_file_does_not_exist
 index 7f129fd..af431f2 100644
 --- a/this_file_does_not_exist
@@ -77,13 +80,15 @@ index 7f129fd..af431f2 100644
     return pygit2.Diff.parse_diff(patch_contents)
 
 
-def test_apply_type_error(testrepo):
+def test_apply_type_error(testrepo: Repository) -> None:
     # Check apply type error
     with pytest.raises(TypeError):
-        testrepo.apply('HEAD')
+        testrepo.apply('HEAD')  # type: ignore
 
 
-def test_apply_diff_to_workdir(testrepo, new_content, patch_diff):
+def test_apply_diff_to_workdir(
+    testrepo: Repository, new_content: str, patch_diff: Diff
+) -> None:
     # Apply the patch and compare
     testrepo.apply(patch_diff, ApplyLocation.WORKDIR)
 
@@ -91,7 +96,9 @@ def test_apply_diff_to_workdir(testrepo,
     assert testrepo.status_file('hello.txt') == FileStatus.WT_MODIFIED
 
 
-def test_apply_diff_to_index(testrepo, old_content, patch_diff):
+def test_apply_diff_to_index(
+    testrepo: Repository, old_content: str, patch_diff: Diff
+) -> None:
     # Apply the patch and compare
     testrepo.apply(patch_diff, ApplyLocation.INDEX)
 
@@ -99,7 +106,9 @@ def test_apply_diff_to_index(testrepo, o
     assert testrepo.status_file('hello.txt') & FileStatus.INDEX_MODIFIED
 
 
-def test_apply_diff_to_both(testrepo, new_content, patch_diff):
+def test_apply_diff_to_both(
+    testrepo: Repository, new_content: str, patch_diff: Diff
+) -> None:
     # Apply the patch and compare
     testrepo.apply(patch_diff, ApplyLocation.BOTH)
 
@@ -107,7 +116,9 @@ def test_apply_diff_to_both(testrepo, ne
     assert testrepo.status_file('hello.txt') & FileStatus.INDEX_MODIFIED
 
 
-def test_diff_applies_to_workdir(testrepo, old_content, patch_diff):
+def test_diff_applies_to_workdir(
+    testrepo: Repository, old_content: str, patch_diff: Diff
+) -> None:
     # See if patch applies
     assert testrepo.applies(patch_diff, ApplyLocation.WORKDIR)
 
@@ -122,7 +133,9 @@ def test_diff_applies_to_workdir(testrep
     assert testrepo.applies(patch_diff, ApplyLocation.INDEX)
 
 
-def test_diff_applies_to_index(testrepo, old_content, patch_diff):
+def test_diff_applies_to_index(
+    testrepo: Repository, old_content: str, patch_diff: Diff
+) -> None:
     # See if patch applies
     assert testrepo.applies(patch_diff, ApplyLocation.INDEX)
 
@@ -137,7 +150,9 @@ def test_diff_applies_to_index(testrepo,
     assert testrepo.applies(patch_diff, ApplyLocation.WORKDIR)
 
 
-def test_diff_applies_to_both(testrepo, old_content, patch_diff):
+def test_diff_applies_to_both(
+    testrepo: Repository, old_content: str, patch_diff: Diff
+) -> None:
     # See if patch applies
     assert testrepo.applies(patch_diff, ApplyLocation.BOTH)
 
@@ -151,7 +166,9 @@ def test_diff_applies_to_both(testrepo,
     assert not testrepo.applies(patch_diff, ApplyLocation.INDEX)
 
 
-def test_applies_error(testrepo, old_content, patch_diff, foreign_patch_diff):
+def test_applies_error(
+    testrepo: Repository, old_content: str, patch_diff: Diff, foreign_patch_diff: Diff
+) -> None:
     # Try to apply a "foreign" patch that affects files that aren't in the repo;
     # ensure we get OSError about the missing file (due to raise_error)
     with pytest.raises(OSError):
diff -pruN 1.17.0-2/test/test_archive.py 1.18.2-1/test/test_archive.py
--- 1.17.0-2/test/test_archive.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_archive.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,17 +23,18 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-from pathlib import Path
 import tarfile
+from pathlib import Path
 
-from pygit2 import Index, Oid, Tree, Object
-
+from pygit2 import Index, Object, Oid, Repository, Tree
 
 TREE_HASH = 'fd937514cb799514d4b81bb24c5fcfeb6472b245'
 COMMIT_HASH = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'
 
 
-def check_writing(repo, treeish, timestamp=None):
+def check_writing(
+    repo: Repository, treeish: str | Tree | Oid | Object, timestamp: int | None = None
+) -> None:
     archive = tarfile.open('foo.tar', mode='w')
     repo.write_archive(treeish, archive)
 
@@ -55,13 +56,13 @@ def check_writing(repo, treeish, timesta
     path.unlink()
 
 
-def test_write_tree(testrepo):
+def test_write_tree(testrepo: Repository) -> None:
     check_writing(testrepo, TREE_HASH)
     check_writing(testrepo, Oid(hex=TREE_HASH))
     check_writing(testrepo, testrepo[TREE_HASH])
 
 
-def test_write_commit(testrepo):
+def test_write_commit(testrepo: Repository) -> None:
     commit_timestamp = testrepo[COMMIT_HASH].committer.time
     check_writing(testrepo, COMMIT_HASH, commit_timestamp)
     check_writing(testrepo, Oid(hex=COMMIT_HASH), commit_timestamp)
diff -pruN 1.17.0-2/test/test_attributes.py 1.18.2-1/test/test_attributes.py
--- 1.17.0-2/test/test_attributes.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_attributes.py	2025-08-16 11:34:29.000000000 +0000
@@ -26,8 +26,10 @@
 # Standard Library
 from pathlib import Path
 
+from pygit2 import Repository
 
-def test_no_attr(testrepo):
+
+def test_no_attr(testrepo: Repository) -> None:
     assert testrepo.get_attr('file', 'foo') is None
 
     with (Path(testrepo.workdir) / '.gitattributes').open('w+') as f:
@@ -41,7 +43,7 @@ def test_no_attr(testrepo):
     assert 'lf' == testrepo.get_attr('file.sh', 'eol')
 
 
-def test_no_attr_aspath(testrepo):
+def test_no_attr_aspath(testrepo: Repository) -> None:
     with (Path(testrepo.workdir) / '.gitattributes').open('w+') as f:
         print('*.py  text\n', file=f)
 
diff -pruN 1.17.0-2/test/test_blame.py 1.18.2-1/test/test_blame.py
--- 1.17.0-2/test/test_blame.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_blame.py	2025-08-16 11:34:29.000000000 +0000
@@ -27,10 +27,9 @@
 
 import pytest
 
-from pygit2 import Signature, Oid
+from pygit2 import Oid, Repository, Signature
 from pygit2.enums import BlameFlag
 
-
 PATH = 'hello.txt'
 
 HUNKS = [
@@ -61,7 +60,7 @@ HUNKS = [
 ]
 
 
-def test_blame_index(testrepo):
+def test_blame_index(testrepo: Repository) -> None:
     blame = testrepo.blame(PATH)
 
     assert len(blame) == 3
@@ -78,7 +77,7 @@ def test_blame_index(testrepo):
         assert HUNKS[i][3] == hunk.boundary
 
 
-def test_blame_flags(blameflagsrepo):
+def test_blame_flags(blameflagsrepo: Repository) -> None:
     blame = blameflagsrepo.blame(PATH, flags=BlameFlag.IGNORE_WHITESPACE)
 
     assert len(blame) == 3
@@ -95,18 +94,17 @@ def test_blame_flags(blameflagsrepo):
         assert HUNKS[i][3] == hunk.boundary
 
 
-def test_blame_with_invalid_index(testrepo):
+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):
+def test_blame_for_line(testrepo: Repository) -> None:
     blame = testrepo.blame(PATH)
 
     for i, line in zip(range(0, 2), range(1, 3)):
@@ -123,19 +121,18 @@ def test_blame_for_line(testrepo):
         assert HUNKS[i][3] == hunk.boundary
 
 
-def test_blame_with_invalid_line(testrepo):
+def test_blame_with_invalid_line(testrepo: Repository) -> None:
     blame = testrepo.blame(PATH)
 
-    def test():
+    with pytest.raises(IndexError):
         blame.for_line(0)
+    with pytest.raises(IndexError):
         blame.for_line(100000)
-        blame.for_line(-1)
-
     with pytest.raises(IndexError):
-        test()
+        blame.for_line(-1)
 
 
-def test_blame_newest(testrepo):
+def test_blame_newest(testrepo: Repository) -> None:
     revs = [
         ('master^2', 3),
         ('master^2^', 2),
diff -pruN 1.17.0-2/test/test_blob.py 1.18.2-1/test/test_blob.py
--- 1.17.0-2/test/test_blob.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_blob.py	2025-08-16 11:34:29.000000000 +0000
@@ -27,15 +27,16 @@
 
 import io
 from pathlib import Path
-from threading import Event
 from queue import Queue
+from threading import Event
 
 import pytest
 
 import pygit2
+from pygit2 import Repository
 from pygit2.enums import ObjectType
-from . import utils
 
+from . import utils
 
 BLOB_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b'
 BLOB_CONTENT = """hello world
@@ -80,7 +81,7 @@ index a520c24..0000000
 """
 
 
-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]
 
@@ -109,14 +110,14 @@ def test_create_blob(testrepo):
     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):
         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(testrep
     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(te
     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 -pruN 1.17.0-2/test/test_branch.py 1.18.2-1/test/test_branch.py
--- 1.17.0-2/test/test_branch.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_branch.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,11 +25,13 @@
 
 """Tests for branch methods."""
 
-import pygit2
-import pytest
 import os
-from pygit2.enums import BranchType
 
+import pytest
+
+import pygit2
+from pygit2 import Commit, Repository
+from pygit2.enums import BranchType
 
 LAST_COMMIT = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'
 I18N_LAST_COMMIT = '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'
@@ -38,7 +40,7 @@ EXCLUSIVE_MASTER_COMMIT = '5ebeeebb32079
 SHARED_COMMIT = '4ec4389a8068641da2d6578db0419484972284c8'
 
 
-def test_branches_getitem(testrepo):
+def test_branches_getitem(testrepo: Repository) -> None:
     branch = testrepo.branches['master']
     assert branch.target == LAST_COMMIT
 
@@ -49,13 +51,14 @@ def test_branches_getitem(testrepo):
         testrepo.branches['not-exists']
 
 
-def test_branches(testrepo):
+def test_branches(testrepo: Repository) -> None:
     branches = sorted(testrepo.branches)
     assert branches == ['i18n', 'master']
 
 
-def test_branches_create(testrepo):
+def test_branches_create(testrepo: Repository) -> None:
     commit = testrepo[LAST_COMMIT]
+    assert isinstance(commit, Commit)
     reference = testrepo.branches.create('version1', commit)
     assert 'version1' in testrepo.branches
     reference = testrepo.branches['version1']
@@ -70,27 +73,27 @@ def test_branches_create(testrepo):
     assert reference.target == LAST_COMMIT
 
 
-def test_branches_delete(testrepo):
+def test_branches_delete(testrepo: Repository) -> None:
     testrepo.branches.delete('i18n')
     assert testrepo.branches.get('i18n') is None
 
 
-def test_branches_delete_error(testrepo):
+def test_branches_delete_error(testrepo: Repository) -> None:
     with pytest.raises(pygit2.GitError):
         testrepo.branches.delete('master')
 
 
-def test_branches_is_head(testrepo):
+def test_branches_is_head(testrepo: Repository) -> None:
     branch = testrepo.branches.get('master')
     assert branch.is_head()
 
 
-def test_branches_is_not_head(testrepo):
+def test_branches_is_not_head(testrepo: Repository) -> None:
     branch = testrepo.branches.get('i18n')
     assert not branch.is_head()
 
 
-def test_branches_rename(testrepo):
+def test_branches_rename(testrepo: Repository) -> None:
     new_branch = testrepo.branches['i18n'].rename('new-branch')
     assert new_branch.target == I18N_LAST_COMMIT
 
@@ -98,25 +101,25 @@ def test_branches_rename(testrepo):
     assert new_branch_2.target == I18N_LAST_COMMIT
 
 
-def test_branches_rename_error(testrepo):
+def test_branches_rename_error(testrepo: Repository) -> None:
     original_branch = testrepo.branches.get('i18n')
     with pytest.raises(ValueError):
         original_branch.rename('master')
 
 
-def test_branches_rename_force(testrepo):
+def test_branches_rename_force(testrepo: Repository) -> None:
     original_branch = testrepo.branches.get('master')
     new_branch = original_branch.rename('i18n', True)
     assert new_branch.target == LAST_COMMIT
 
 
-def test_branches_rename_invalid(testrepo):
+def test_branches_rename_invalid(testrepo: Repository) -> None:
     original_branch = testrepo.branches.get('i18n')
     with pytest.raises(ValueError):
         original_branch.rename('abc@{123')
 
 
-def test_branches_name(testrepo):
+def test_branches_name(testrepo: Repository) -> None:
     branch = testrepo.branches.get('master')
     assert branch.branch_name == 'master'
     assert branch.name == 'refs/heads/master'
@@ -128,7 +131,7 @@ def test_branches_name(testrepo):
     assert branch.raw_branch_name == branch.branch_name.encode('utf-8')
 
 
-def test_branches_with_commit(testrepo):
+def test_branches_with_commit(testrepo: Repository) -> None:
     branches = testrepo.branches.with_commit(EXCLUSIVE_MASTER_COMMIT)
     assert sorted(branches) == ['master']
     assert branches.get('i18n') is None
@@ -140,7 +143,9 @@ def test_branches_with_commit(testrepo):
     branches = testrepo.branches.with_commit(LAST_COMMIT)
     assert sorted(branches) == ['master']
 
-    branches = testrepo.branches.with_commit(testrepo[LAST_COMMIT])
+    commit = testrepo[LAST_COMMIT]
+    assert isinstance(commit, Commit)
+    branches = testrepo.branches.with_commit(commit)
     assert sorted(branches) == ['master']
 
     branches = testrepo.branches.remote.with_commit(LAST_COMMIT)
@@ -152,7 +157,7 @@ def test_branches_with_commit(testrepo):
 #
 
 
-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
 
@@ -165,16 +170,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
@@ -189,64 +195,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 -pruN 1.17.0-2/test/test_branch_empty.py 1.18.2-1/test/test_branch_empty.py
--- 1.17.0-2/test/test_branch_empty.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_branch_empty.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,39 +23,44 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from collections.abc import Generator
+
 import pytest
-from pygit2.enums import BranchType
 
+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])
+    commit = repo[remote_master.target]
+    assert isinstance(commit, Commit)
+    master = repo.branches.create('master', commit)
 
     assert master.upstream is None
     master.upstream = remote_master
@@ -71,9 +76,11 @@ def test_branches_upstream(repo):
     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])
+    commit = repo[remote_master.target]
+    assert isinstance(commit, Commit)
+    master = repo.branches.create('master', commit)
 
     master.upstream = remote_master
     assert master.upstream_name == 'refs/remotes/origin/master'
@@ -84,28 +91,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 +130,11 @@ def test_branch_upstream(repo):
     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 -pruN 1.17.0-2/test/test_cherrypick.py 1.18.2-1/test/test_cherrypick.py
--- 1.17.0-2/test/test_cherrypick.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_cherrypick.py	2025-08-16 11:34:29.000000000 +0000
@@ -26,26 +26,30 @@
 """Tests for merging and information about it."""
 
 from pathlib import Path
+
 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')
@@ -53,7 +57,7 @@ def test_cherrypick_already_something_in
         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 -pruN 1.17.0-2/test/test_commit.py 1.18.2-1/test/test_commit.py
--- 1.17.0-2/test/test_commit.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_commit.py	2025-08-16 11:34:29.000000000 +0000
@@ -29,10 +29,10 @@ import sys
 
 import pytest
 
-from pygit2 import Signature, Oid, GitError
+from pygit2 import Commit, GitError, Oid, Repository, Signature, Tree
 from pygit2.enums import ObjectType
-from . import utils
 
+from . import utils
 
 COMMIT_SHA = '5fe808e8953c12735680c257f56600cb0de44b10'
 COMMIT_SHA_TO_AMEND = (
@@ -40,8 +40,8 @@ COMMIT_SHA_TO_AMEND = (
 )
 
 
-@utils.refcount
-def test_commit_refcount(barerepo):
+@utils.requires_refcount
+def test_commit_refcount(barerepo: Repository) -> None:
     commit = barerepo[COMMIT_SHA]
     start = sys.getrefcount(commit)
     tree = commit.tree
@@ -50,15 +50,16 @@ 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)
     assert 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c' == parents[0].id
     assert commit.message_encoding is None
     assert commit.message == (
-        'Second test data commit.\n\n' 'This commit has some additional text.\n'
+        'Second test data commit.\n\nThis commit has some additional text.\n'
     )
     commit_time = 1288481576
     assert commit_time == commit.commit_time
@@ -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(
     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(bar
     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(bar
     # 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 -pruN 1.17.0-2/test/test_commit_gpg.py 1.18.2-1/test/test_commit_gpg.py
--- 1.17.0-2/test/test_commit_gpg.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_commit_gpg.py	2025-08-16 11:34:29.000000000 +0000
@@ -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 @@ a simple commit which works\
 # 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 -pruN 1.17.0-2/test/test_commit_trailer.py 1.18.2-1/test/test_commit_trailer.py
--- 1.17.0-2/test/test_commit_trailer.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_commit_trailer.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,25 +23,31 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-import pygit2
+from collections.abc import Generator
+from pathlib import Path
+
 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 <tyler@tylercipriani.com>',
     }
     commit = repo.get(commit_hash)
+    assert isinstance(commit, Commit)
     trailers = commit.message_trailers
 
     assert trailers['Bug'] == expected_trailers['Bug']
diff -pruN 1.17.0-2/test/test_config.py 1.18.2-1/test/test_config.py
--- 1.17.0-2/test/test_config.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_config.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,19 +23,20 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from collections.abc import Generator
 from pathlib import Path
 
 import pytest
 
-from pygit2 import Config
-from . import utils
+from pygit2 import Config, Repository
 
+from . import utils
 
 CONFIG_FILENAME = 'test_config'
 
 
 @pytest.fixture
-def config(testrepo):
+def config(testrepo: Repository) -> Generator[object, None, None]:
     yield testrepo.config
     try:
         Path(CONFIG_FILENAME).unlink()
@@ -43,11 +44,11 @@ def config(testrepo):
         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:
@@ -55,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:
@@ -63,7 +64,7 @@ def test_system_config():
         pass
 
 
-def test_new():
+def test_new() -> None:
     # Touch file
     open(CONFIG_FILENAME, 'w').close()
 
@@ -80,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')
@@ -93,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')
 
@@ -102,11 +103,11 @@ def test_add_aspath():
     assert 'this.that' in config
 
 
-def test_read(config):
+def test_read(config: Config) -> None:
     with pytest.raises(TypeError):
-        config[()]
+        config[()]  # type: ignore
     with pytest.raises(TypeError):
-        config[-4]
+        config[-4]  # type: ignore
     utils.assertRaisesWithArg(
         ValueError, "invalid config item name 'abc'", lambda: config['abc']
     )
@@ -120,9 +121,9 @@ 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')
+        config.__setitem__((), 'This should not work')  # type: ignore
 
     assert 'core.dummy1' not in config
     config['core.dummy1'] = 42
@@ -147,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')
 
@@ -174,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
@@ -184,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 -pruN 1.17.0-2/test/test_credentials.py 1.18.2-1/test/test_credentials.py
--- 1.17.0-2/test/test_credentials.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_credentials.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,18 +23,23 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-"""Tests for credentials"""
-
-from pathlib import Path
 import platform
+from pathlib import Path
 
 import pytest
 
 import pygit2
-from pygit2 import Username, UserPass, Keypair, KeypairFromAgent, KeypairFromMemory
+from pygit2 import (
+    Keypair,
+    KeypairFromAgent,
+    KeypairFromMemory,
+    Repository,
+    Username,
+    UserPass,
+)
 from pygit2.enums import CredentialType
-from . import utils
 
+from . import utils
 
 REMOTE_NAME = 'origin'
 REMOTE_URL = 'git://github.com/libgit2/pygit2.git'
@@ -46,13 +51,13 @@ REMOTE_REPO_BYTES = 2758
 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'
 
@@ -60,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'
@@ -70,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')
@@ -80,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'
@@ -99,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)
@@ -113,7 +118,9 @@ def test_keypair(tmp_path, pygit2_empty_
 
 @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)
@@ -130,10 +137,15 @@ def test_keypair_from_memory(tmp_path, p
     pygit2.clone_repository(url, tmp_path, callbacks=callbacks)
 
 
-def test_callback(testrepo):
+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'
@@ -143,10 +155,15 @@ def test_callback(testrepo):
 
 
 @utils.requires_network
-def test_bad_cred_type(testrepo):
+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'
@@ -156,9 +173,11 @@ def test_bad_cred_type(testrepo):
 
 
 @utils.requires_network
-def test_fetch_certificate_check(testrepo):
+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'
@@ -181,7 +200,7 @@ def test_fetch_certificate_check(testrep
 
 
 @utils.requires_network
-def test_user_pass(testrepo):
+def test_user_pass(testrepo: Repository) -> None:
     credentials = UserPass('libgit2', 'libgit2')
     callbacks = pygit2.RemoteCallbacks(credentials=credentials)
 
@@ -193,7 +212,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) -> None:
     credentials = UserPass('libgit2', 'libgit2')
     callbacks = pygit2.RemoteCallbacks(credentials=credentials)
 
diff -pruN 1.17.0-2/test/test_describe.py 1.18.2-1/test/test_describe.py
--- 1.17.0-2/test/test_describe.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_describe.py	2025-08-16 11:34:29.000000000 +0000
@@ -27,11 +27,12 @@
 
 import pytest
 
-from pygit2.enums import DescribeStrategy, ObjectType
 import pygit2
+from pygit2 import Oid, Repository
+from pygit2.enums import DescribeStrategy, ObjectType
 
 
-def add_tag(repo, name, target):
+def add_tag(repo: Repository, name: str, target: str) -> Oid:
     message = 'Example tag.\n'
     tagger = pygit2.Signature('John Doe', 'jdoe@example.com', 12347, 0)
 
@@ -39,21 +40,21 @@ def add_tag(repo, name, target):
     return sha
 
 
-def test_describe(testrepo):
+def test_describe(testrepo: Repository) -> None:
     add_tag(testrepo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8')
     assert 'thetag-2-g2be5719' == testrepo.describe()
 
 
-def test_describe_without_ref(testrepo):
+def test_describe_without_ref(testrepo: Repository) -> None:
     with pytest.raises(pygit2.GitError):
         testrepo.describe()
 
 
-def test_describe_default_oid(testrepo):
+def test_describe_default_oid(testrepo: Repository) -> None:
     assert '2be5719' == testrepo.describe(show_commit_oid_as_fallback=True)
 
 
-def test_describe_strategies(testrepo):
+def test_describe_strategies(testrepo: Repository) -> None:
     assert 'heads/master' == testrepo.describe(describe_strategy=DescribeStrategy.ALL)
 
     testrepo.create_reference(
@@ -66,14 +67,14 @@ def test_describe_strategies(testrepo):
     )
 
 
-def test_describe_pattern(testrepo):
+def test_describe_pattern(testrepo: Repository) -> None:
     add_tag(testrepo, 'private/tag1', '5ebeeebb320790caf276b9fc8b24546d63316533')
     add_tag(testrepo, 'public/tag2', '4ec4389a8068641da2d6578db0419484972284c8')
 
     assert 'public/tag2-2-g2be5719' == testrepo.describe(pattern='public/*')
 
 
-def test_describe_committish(testrepo):
+def test_describe_committish(testrepo: Repository) -> None:
     add_tag(testrepo, 'thetag', 'acecd5ea2924a4b900e7e149496e1f4b57976e51')
     assert 'thetag-4-g2be5719' == testrepo.describe(committish='HEAD')
     assert 'thetag-1-g5ebeeeb' == testrepo.describe(committish='HEAD^')
@@ -86,28 +87,28 @@ def test_describe_committish(testrepo):
     assert 'thetag-1-g6aaa262' == testrepo.describe(committish='6aaa262')
 
 
-def test_describe_follows_first_branch_only(testrepo):
+def test_describe_follows_first_branch_only(testrepo: Repository) -> None:
     add_tag(testrepo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8')
     with pytest.raises(KeyError):
         testrepo.describe(only_follow_first_parent=True)
 
 
-def test_describe_abbreviated_size(testrepo):
+def test_describe_abbreviated_size(testrepo: Repository) -> None:
     add_tag(testrepo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8')
     assert 'thetag-2-g2be5719152d4f82c' == testrepo.describe(abbreviated_size=16)
     assert 'thetag' == testrepo.describe(abbreviated_size=0)
 
 
-def test_describe_long_format(testrepo):
+def test_describe_long_format(testrepo: Repository) -> None:
     add_tag(testrepo, 'thetag', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98')
     assert 'thetag-0-g2be5719' == testrepo.describe(always_use_long_format=True)
 
 
-def test_describe_dirty(dirtyrepo):
+def test_describe_dirty(dirtyrepo: Repository) -> None:
     add_tag(dirtyrepo, 'thetag', 'a763aa560953e7cfb87ccbc2f536d665aa4dff22')
     assert 'thetag' == dirtyrepo.describe()
 
 
-def test_describe_dirty_with_suffix(dirtyrepo):
+def test_describe_dirty_with_suffix(dirtyrepo: Repository) -> None:
     add_tag(dirtyrepo, 'thetag', 'a763aa560953e7cfb87ccbc2f536d665aa4dff22')
     assert 'thetag-dirty' == dirtyrepo.describe(dirty_suffix='-dirty')
diff -pruN 1.17.0-2/test/test_diff.py 1.18.2-1/test/test_diff.py
--- 1.17.0-2/test/test_diff.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_diff.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,15 +25,16 @@
 
 """Tests for Diff objects."""
 
-from itertools import chain
 import textwrap
+from collections.abc import Iterator
+from itertools import chain
 
 import pytest
 
 import pygit2
+from pygit2 import Diff, Repository
 from pygit2.enums import DeltaStatus, DiffFlag, DiffOption, DiffStatsFormat, FileMode
 
-
 COMMIT_SHA1_1 = '5fe808e8953c12735680c257f56600cb0de44b10'
 COMMIT_SHA1_2 = 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c'
 COMMIT_SHA1_3 = '2cdae28389c059815e951d0bb9eed6533f61a46b'
@@ -107,8 +108,70 @@ STATS_EXPECTED = """ a   | 2 +-
  delete mode 100644 c/d
 """
 
+TEXT_BLOB1 = """Common header of the file
+Blob 1 line 1
+Common middle line 1
+Common middle line 2
+Common middle line 3
+Blob 1 line 2
+Common footer of the file
+"""
+
+TEXT_BLOB2 = """Common header of the file
+Blob 2 line 1
+Common middle line 1
+Common middle line 2
+Common middle line 3
+Blob 2 line 2
+Common footer of the file
+"""
+
+PATCH_BLOBS_DEFAULT = """diff --git a/file b/file
+index 0b5ac93..ddfdbcc 100644
+--- a/file
++++ b/file
+@@ -1,7 +1,7 @@
+ Common header of the file
+-Blob 1 line 1
++Blob 2 line 1
+ Common middle line 1
+ Common middle line 2
+ Common middle line 3
+-Blob 1 line 2
++Blob 2 line 2
+ Common footer of the file
+"""
 
-def test_diff_empty_index(dirtyrepo):
+PATCH_BLOBS_NO_LEEWAY = """diff --git a/file b/file
+index 0b5ac93..ddfdbcc 100644
+--- a/file
++++ b/file
+@@ -2 +2 @@ Common header of the file
+-Blob 1 line 1
++Blob 2 line 1
+@@ -6 +6 @@ Common middle line 3
+-Blob 1 line 2
++Blob 2 line 2
+"""
+
+PATCH_BLOBS_ONE_CONTEXT_LINE = """diff --git a/file b/file
+index 0b5ac93..ddfdbcc 100644
+--- a/file
++++ b/file
+@@ -1,3 +1,3 @@
+ Common header of the file
+-Blob 1 line 1
++Blob 2 line 1
+ Common middle line 1
+@@ -5,3 +5,3 @@ Common middle line 2
+ Common middle line 3
+-Blob 1 line 2
++Blob 2 line 2
+ Common footer of the file
+"""
+
+
+def test_diff_empty_index(dirtyrepo: Repository) -> None:
     repo = dirtyrepo
     head = repo[repo.lookup_reference('HEAD').resolve().target]
 
@@ -121,7 +184,7 @@ def test_diff_empty_index(dirtyrepo):
     assert DIFF_HEAD_TO_INDEX_EXPECTED == files
 
 
-def test_workdir_to_tree(dirtyrepo):
+def test_workdir_to_tree(dirtyrepo: Repository) -> None:
     repo = dirtyrepo
     head = repo[repo.lookup_reference('HEAD').resolve().target]
 
@@ -134,22 +197,22 @@ def test_workdir_to_tree(dirtyrepo):
     assert DIFF_HEAD_TO_WORKDIR_EXPECTED == files
 
 
-def test_index_to_workdir(dirtyrepo):
+def test_index_to_workdir(dirtyrepo: Repository) -> None:
     diff = dirtyrepo.diff()
     files = [patch.delta.new_file.path for patch in diff]
     assert DIFF_INDEX_TO_WORK_EXPECTED == files
 
 
-def test_diff_invalid(barerepo):
+def test_diff_invalid(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
     with pytest.raises(TypeError):
-        commit_a.tree.diff_to_tree(commit_b)
+        commit_a.tree.diff_to_tree(commit_b)  # type: ignore
     with pytest.raises(TypeError):
-        commit_a.tree.diff_to_index(commit_b)
+        commit_a.tree.diff_to_index(commit_b)  # type: ignore
 
 
-def test_diff_empty_index_bare(barerepo):
+def test_diff_empty_index_bare(barerepo: Repository) -> None:
     repo = barerepo
     head = repo[repo.lookup_reference('HEAD').resolve().target]
 
@@ -166,11 +229,11 @@ def test_diff_empty_index_bare(barerepo)
     assert [x.name for x in head.tree] == files
 
 
-def test_diff_tree(barerepo):
+def test_diff_tree(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
 
-    def _test(diff):
+    def _test(diff: Diff) -> None:
         assert diff is not None
         assert 2 == sum(map(lambda x: len(x.hunks), diff))
 
@@ -199,11 +262,11 @@ def test_diff_tree(barerepo):
     _test(barerepo.diff(COMMIT_SHA1_1, COMMIT_SHA1_2))
 
 
-def test_diff_empty_tree(barerepo):
+def test_diff_empty_tree(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     diff = commit_a.tree.diff_to_tree()
 
-    def get_context_for_lines(diff):
+    def get_context_for_lines(diff: Diff) -> Iterator[str]:
         hunks = chain.from_iterable(map(lambda x: x.hunks, diff))
         lines = chain.from_iterable(map(lambda x: x.lines, hunks))
         return map(lambda x: x.origin, lines)
@@ -218,12 +281,12 @@ def test_diff_empty_tree(barerepo):
     assert all('+' == x for x in get_context_for_lines(diff_swaped))
 
 
-def test_diff_revparse(barerepo):
+def test_diff_revparse(barerepo: Repository) -> None:
     diff = barerepo.diff('HEAD', 'HEAD~6')
-    assert type(diff) == pygit2.Diff
+    assert type(diff) is pygit2.Diff
 
 
-def test_diff_tree_opts(barerepo):
+def test_diff_tree_opts(barerepo: Repository) -> None:
     commit_c = barerepo[COMMIT_SHA1_3]
     commit_d = barerepo[COMMIT_SHA1_4]
 
@@ -237,7 +300,7 @@ def test_diff_tree_opts(barerepo):
     assert 1 == len(diff[0].hunks)
 
 
-def test_diff_merge(barerepo):
+def test_diff_merge(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
     commit_c = barerepo[COMMIT_SHA1_3]
@@ -264,7 +327,7 @@ def test_diff_merge(barerepo):
     assert patch.delta.new_file.path == 'a'
 
 
-def test_diff_patch(barerepo):
+def test_diff_patch(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
 
@@ -273,7 +336,7 @@ def test_diff_patch(barerepo):
     assert len(diff) == len([patch for patch in diff])
 
 
-def test_diff_ids(barerepo):
+def test_diff_ids(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
     patch = commit_a.tree.diff_to_tree(commit_b.tree)[0]
@@ -282,7 +345,7 @@ def test_diff_ids(barerepo):
     assert delta.new_file.id == 'af431f20fc541ed6d5afede3e2dc7160f6f01f16'
 
 
-def test_diff_patchid(barerepo):
+def test_diff_patchid(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
     diff = commit_a.tree.diff_to_tree(commit_b.tree)
@@ -290,7 +353,7 @@ def test_diff_patchid(barerepo):
     assert diff.patchid == PATCHID
 
 
-def test_hunk_content(barerepo):
+def test_hunk_content(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
     patch = commit_a.tree.diff_to_tree(commit_b.tree)[0]
@@ -301,7 +364,7 @@ def test_hunk_content(barerepo):
         assert line.content == line.raw_content.decode()
 
 
-def test_find_similar(barerepo):
+def test_find_similar(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_6]
     commit_b = barerepo[COMMIT_SHA1_7]
 
@@ -315,7 +378,7 @@ def test_find_similar(barerepo):
     assert any(x.delta.status_char() == 'R' for x in diff)
 
 
-def test_diff_stats(barerepo):
+def test_diff_stats(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
 
@@ -330,7 +393,7 @@ def test_diff_stats(barerepo):
     assert STATS_EXPECTED == formatted
 
 
-def test_deltas(barerepo):
+def test_deltas(barerepo: Repository) -> None:
     commit_a = barerepo[COMMIT_SHA1_1]
     commit_b = barerepo[COMMIT_SHA1_2]
     diff = commit_a.tree.diff_to_tree(commit_b.tree)
@@ -353,7 +416,7 @@ def test_deltas(barerepo):
         # assert delta.flags == patch_delta.flags
 
 
-def test_diff_parse(barerepo):
+def test_diff_parse(barerepo: Repository) -> None:
     diff = pygit2.Diff.parse_diff(PATCH)
 
     stats = diff.stats
@@ -365,12 +428,12 @@ def test_diff_parse(barerepo):
     assert 2 == len(deltas)
 
 
-def test_parse_diff_null():
+def test_parse_diff_null() -> None:
     with pytest.raises(TypeError):
-        pygit2.Diff.parse_diff(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
@@ -382,3 +445,17 @@ def test_parse_diff_bad():
     )
     with pytest.raises(pygit2.GitError):
         pygit2.Diff.parse_diff(diff)
+
+
+def test_diff_blobs(emptyrepo: Repository) -> None:
+    repo = emptyrepo
+    blob1 = repo.create_blob(TEXT_BLOB1.encode())
+    blob2 = repo.create_blob(TEXT_BLOB2.encode())
+    diff_default = repo.diff(blob1, blob2)
+    assert diff_default.text == PATCH_BLOBS_DEFAULT
+    diff_no_leeway = repo.diff(blob1, blob2, context_lines=0)
+    assert diff_no_leeway.text == PATCH_BLOBS_NO_LEEWAY
+    diff_one_context_line = repo.diff(blob1, blob2, context_lines=1)
+    assert diff_one_context_line.text == PATCH_BLOBS_ONE_CONTEXT_LINE
+    diff_all_together = repo.diff(blob1, blob2, context_lines=1, interhunk_lines=1)
+    assert diff_all_together.text == PATCH_BLOBS_DEFAULT
diff -pruN 1.17.0-2/test/test_diff_binary.py 1.18.2-1/test/test_diff_binary.py
--- 1.17.0-2/test/test_diff_binary.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_diff_binary.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,16 +23,20 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from collections.abc import Generator
+from pathlib import Path
+
 import pytest
 
 import pygit2
+from pygit2 import Repository
 from pygit2.enums import DiffOption
 
 from . import utils
 
 
 @pytest.fixture
-def repo(tmp_path):
+def repo(tmp_path: Path) -> Generator[Repository, None, None]:
     with utils.TemporaryRepository('binaryfilerepo.zip', tmp_path) as path:
         yield pygit2.Repository(path)
 
@@ -54,7 +58,7 @@ Pc${NM&PdElPvrst3ey5{
 """
 
 
-def test_binary_diff(repo):
+def test_binary_diff(repo: Repository) -> None:
     diff = repo.diff('HEAD', 'HEAD^')
     assert PATCH_BINARY == diff.patch
     diff = repo.diff('HEAD', 'HEAD^', flags=DiffOption.SHOW_BINARY)
diff -pruN 1.17.0-2/test/test_filter.py 1.18.2-1/test/test_filter.py
--- 1.17.0-2/test/test_filter.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_filter.py	2025-08-16 11:34:29.000000000 +0000
@@ -1,39 +1,52 @@
-from io import BytesIO
 import codecs
+from collections.abc import Callable, Generator
+from io import BytesIO
+
 import pytest
 
 import pygit2
+from pygit2 import Blob, Filter, FilterSource, Repository
 from pygit2.enums import BlobFilter
 from pygit2.errors import Passthrough
 
 
-def _rot13(data):
+def _rot13(data: bytes) -> bytes:
     return codecs.encode(data.decode('utf-8'), 'rot_13').encode('utf-8')
 
 
 class _Rot13Filter(pygit2.Filter):
     attributes = 'text'
 
-    def write(self, data, src, write_next):
+    def write(
+        self,
+        data: bytes,
+        src: FilterSource,
+        write_next: Callable[[bytes], None],
+    ) -> None:
         return super().write(_rot13(data), src, write_next)
 
 
 class _BufferedFilter(pygit2.Filter):
     attributes = 'text'
 
-    def __init__(self):
+    def __init__(self) -> None:
         super().__init__()
         self.buf = BytesIO()
 
-    def write(self, data, src, write_next):
+    def write(
+        self,
+        data: bytes,
+        src: FilterSource,
+        write_next: Callable[[bytes], None],
+    ) -> None:
         self.buf.write(data)
 
-    def close(self, write_next):
+    def close(self, write_next: Callable[[bytes], None]) -> None:
         write_next(_rot13(self.buf.getvalue()))
 
 
 class _PassthroughFilter(_Rot13Filter):
-    def check(self, src, attr_values):
+    def check(self, src: FilterSource, attr_values: list[str | None]) -> None:
         assert attr_values == [None]
         assert src.repo
         raise Passthrough
@@ -44,36 +57,37 @@ class _UnmatchedFilter(_Rot13Filter):
 
 
 @pytest.fixture
-def rot13_filter():
+def rot13_filter() -> Generator[None, None, None]:
     pygit2.filter_register('rot13', _Rot13Filter)
     yield
     pygit2.filter_unregister('rot13')
 
 
 @pytest.fixture
-def passthrough_filter():
+def passthrough_filter() -> Generator[None, None, None]:
     pygit2.filter_register('passthrough-rot13', _PassthroughFilter)
     yield
     pygit2.filter_unregister('passthrough-rot13')
 
 
 @pytest.fixture
-def buffered_filter():
+def buffered_filter() -> Generator[None, None, None]:
     pygit2.filter_register('buffered-rot13', _BufferedFilter)
     yield
     pygit2.filter_unregister('buffered-rot13')
 
 
 @pytest.fixture
-def unmatched_filter():
+def unmatched_filter() -> Generator[None, None, None]:
     pygit2.filter_register('unmatched-rot13', _UnmatchedFilter)
     yield
     pygit2.filter_unregister('unmatched-rot13')
 
 
-def test_filter(testrepo, rot13_filter):
+def test_filter(testrepo: Repository, rot13_filter: Filter) -> None:
     blob_oid = testrepo.create_blob_fromworkdir('bye.txt')
     blob = testrepo[blob_oid]
+    assert isinstance(blob, Blob)
     flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD
     assert b'olr jbeyq\n' == blob.data
     with pygit2.BlobIO(blob) as reader:
@@ -82,9 +96,10 @@ def test_filter(testrepo, rot13_filter):
         assert b'bye world\n' == reader.read()
 
 
-def test_filter_buffered(testrepo, buffered_filter):
+def test_filter_buffered(testrepo: Repository, buffered_filter: Filter) -> None:
     blob_oid = testrepo.create_blob_fromworkdir('bye.txt')
     blob = testrepo[blob_oid]
+    assert isinstance(blob, Blob)
     flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD
     assert b'olr jbeyq\n' == blob.data
     with pygit2.BlobIO(blob) as reader:
@@ -93,9 +108,10 @@ def test_filter_buffered(testrepo, buffe
         assert b'bye world\n' == reader.read()
 
 
-def test_filter_passthrough(testrepo, passthrough_filter):
+def test_filter_passthrough(testrepo: Repository, passthrough_filter: Filter) -> None:
     blob_oid = testrepo.create_blob_fromworkdir('bye.txt')
     blob = testrepo[blob_oid]
+    assert isinstance(blob, Blob)
     flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD
     assert b'bye world\n' == blob.data
     with pygit2.BlobIO(blob) as reader:
@@ -104,9 +120,10 @@ def test_filter_passthrough(testrepo, pa
         assert b'bye world\n' == reader.read()
 
 
-def test_filter_unmatched(testrepo, unmatched_filter):
+def test_filter_unmatched(testrepo: Repository, unmatched_filter: Filter) -> None:
     blob_oid = testrepo.create_blob_fromworkdir('bye.txt')
     blob = testrepo[blob_oid]
+    assert isinstance(blob, Blob)
     flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD
     assert b'bye world\n' == blob.data
     with pygit2.BlobIO(blob) as reader:
@@ -115,7 +132,7 @@ def test_filter_unmatched(testrepo, unma
         assert b'bye world\n' == reader.read()
 
 
-def test_filter_cleanup(dirtyrepo, rot13_filter):
+def test_filter_cleanup(dirtyrepo: Repository, rot13_filter: Filter) -> None:
     # Indirectly test that pygit2_filter_cleanup has the GIL
     # before calling pygit2_filter_payload_free.
     dirtyrepo.diff()
diff -pruN 1.17.0-2/test/test_index.py 1.18.2-1/test/test_index.py
--- 1.17.0-2/test/test_index.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_index.py	2025-08-16 11:34:29.000000000 +0000
@@ -30,20 +30,21 @@ from pathlib import Path
 import pytest
 
 import pygit2
-from pygit2 import Repository, Index, Oid
+from pygit2 import Index, IndexEntry, Oid, Repository, Tree
 from pygit2.enums import FileMode
+
 from . import utils
 
 
-def test_bare(barerepo):
+def test_bare(barerepo: Repository) -> None:
     assert len(barerepo.index) == 0
 
 
-def test_index(testrepo):
+def test_index(testrepo: Repository) -> None:
     assert testrepo.index is not None
 
 
-def test_read(testrepo):
+def test_read(testrepo: Repository) -> None:
     index = testrepo.index
     assert len(index) == 2
 
@@ -59,7 +60,7 @@ def test_read(testrepo):
     assert index[1].id == sha
 
 
-def test_add(testrepo):
+def test_add(testrepo: Repository) -> None:
     index = testrepo.index
 
     sha = '0907563af06c7464d62a70cdd135a6ba7d2b41d8'
@@ -70,7 +71,7 @@ def test_add(testrepo):
     assert index['bye.txt'].id == sha
 
 
-def test_add_aspath(testrepo):
+def test_add_aspath(testrepo: Repository) -> None:
     index = testrepo.index
 
     assert 'bye.txt' not in index
@@ -78,7 +79,7 @@ def test_add_aspath(testrepo):
     assert 'bye.txt' in index
 
 
-def test_add_all(testrepo):
+def test_add_all(testrepo: Repository) -> None:
     clear(testrepo)
 
     sha_bye = '0907563af06c7464d62a70cdd135a6ba7d2b41d8'
@@ -112,7 +113,7 @@ def test_add_all(testrepo):
     assert index['hello.txt'].id == sha_hello
 
 
-def test_add_all_aspath(testrepo):
+def test_add_all_aspath(testrepo: Repository) -> None:
     clear(testrepo)
 
     index = testrepo.index
@@ -121,14 +122,14 @@ def test_add_all_aspath(testrepo):
     assert 'hello.txt' in index
 
 
-def clear(repo):
+def clear(repo: Repository) -> None:
     index = repo.index
     assert len(index) == 2
     index.clear()
     assert len(index) == 0
 
 
-def test_write(testrepo):
+def test_write(testrepo: Repository) -> None:
     index = testrepo.index
     index.add('bye.txt')
     index.write()
@@ -139,7 +140,7 @@ def test_write(testrepo):
     assert 'bye.txt' in index
 
 
-def test_read_tree(testrepo):
+def test_read_tree(testrepo: Repository) -> None:
     tree_oid = '68aba62e560c0ebc3396e8ae9335232cd93a3f60'
     # Test reading first tree
     index = testrepo.index
@@ -153,11 +154,11 @@ def test_read_tree(testrepo):
     assert len(index) == 2
 
 
-def test_write_tree(testrepo):
+def test_write_tree(testrepo: Repository) -> None:
     assert testrepo.index.write_tree() == 'fd937514cb799514d4b81bb24c5fcfeb6472b245'
 
 
-def test_iter(testrepo):
+def test_iter(testrepo: Repository) -> None:
     index = testrepo.index
     n = len(index)
     assert len(list(index)) == n
@@ -167,7 +168,7 @@ def test_iter(testrepo):
     assert list(x.id for x in index) == entries
 
 
-def test_mode(testrepo):
+def test_mode(testrepo: Repository) -> None:
     """
     Testing that we can access an index entry mode.
     """
@@ -177,7 +178,7 @@ def test_mode(testrepo):
     assert hello_mode == 33188
 
 
-def test_bare_index(testrepo):
+def test_bare_index(testrepo: Repository) -> None:
     index = pygit2.Index(Path(testrepo.path) / 'index')
     assert [x.id for x in index] == [x.id for x in testrepo.index]
 
@@ -185,14 +186,21 @@ def test_bare_index(testrepo):
         index.add('bye.txt')
 
 
-def test_remove(testrepo):
+def test_remove(testrepo: Repository) -> None:
     index = testrepo.index
     assert 'hello.txt' in index
     index.remove('hello.txt')
     assert 'hello.txt' not in index
 
 
-def test_remove_all(testrepo):
+def test_remove_directory(dirtyrepo: Repository) -> None:
+    index = dirtyrepo.index
+    assert 'subdir/current_file' in index
+    index.remove_directory('subdir')
+    assert 'subdir/current_file' not in index
+
+
+def test_remove_all(testrepo: Repository) -> None:
     index = testrepo.index
     assert 'hello.txt' in index
     index.remove_all(['*.txt'])
@@ -201,21 +209,28 @@ def test_remove_all(testrepo):
     index.remove_all(['not-existing'])  # this doesn't error
 
 
-def test_remove_aspath(testrepo):
+def test_remove_aspath(testrepo: Repository) -> None:
     index = testrepo.index
     assert 'hello.txt' in index
     index.remove(Path('hello.txt'))
     assert 'hello.txt' not in index
 
 
-def test_remove_all_aspath(testrepo):
+def test_remove_directory_aspath(dirtyrepo: Repository) -> None:
+    index = dirtyrepo.index
+    assert 'subdir/current_file' in index
+    index.remove_directory(Path('subdir'))
+    assert 'subdir/current_file' not in index
+
+
+def test_remove_all_aspath(testrepo: Repository) -> None:
     index = testrepo.index
     assert 'hello.txt' in index
     index.remove_all([Path('hello.txt')])
     assert 'hello.txt' not in index
 
 
-def test_change_attributes(testrepo):
+def test_change_attributes(testrepo: Repository) -> None:
     index = testrepo.index
     entry = index['hello.txt']
     ign_entry = index['.gitignore']
@@ -229,7 +244,7 @@ def test_change_attributes(testrepo):
     assert FileMode.BLOB_EXECUTABLE == entry.mode
 
 
-def test_write_tree_to(testrepo, tmp_path):
+def test_write_tree_to(testrepo: Repository, tmp_path: Path) -> None:
     pygit2.option(pygit2.enums.Option.ENABLE_STRICT_OBJECT_CREATION, False)
     with utils.TemporaryRepository('emptyrepo.zip', tmp_path) as path:
         nrepo = Repository(path)
@@ -237,7 +252,7 @@ def test_write_tree_to(testrepo, tmp_pat
         assert nrepo[id] is not None
 
 
-def test_create_entry(testrepo):
+def test_create_entry(testrepo: Repository) -> None:
     index = testrepo.index
     hello_entry = index['hello.txt']
     entry = pygit2.IndexEntry('README.md', hello_entry.id, hello_entry.mode)
@@ -245,7 +260,7 @@ def test_create_entry(testrepo):
     assert '60e769e57ae1d6a2ab75d8d253139e6260e1f912' == index.write_tree()
 
 
-def test_create_entry_aspath(testrepo):
+def test_create_entry_aspath(testrepo: Repository) -> None:
     index = testrepo.index
     hello_entry = index[Path('hello.txt')]
     entry = pygit2.IndexEntry(Path('README.md'), hello_entry.id, hello_entry.mode)
@@ -253,7 +268,7 @@ def test_create_entry_aspath(testrepo):
     index.write_tree()
 
 
-def test_entry_eq(testrepo):
+def test_entry_eq(testrepo: Repository) -> None:
     index = testrepo.index
     hello_entry = index['hello.txt']
     entry = pygit2.IndexEntry(hello_entry.path, hello_entry.id, hello_entry.mode)
@@ -270,7 +285,7 @@ def test_entry_eq(testrepo):
     assert hello_entry != entry
 
 
-def test_entry_repr(testrepo):
+def test_entry_repr(testrepo: Repository) -> None:
     index = testrepo.index
     hello_entry = index['hello.txt']
     assert (
@@ -283,17 +298,42 @@ def test_entry_repr(testrepo):
     )
 
 
-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):
-        index('read_tree', 'fd937514cb799514d4b81bb24c5fcfeb6472b245')
+        index('read_tree', 'fd937514cb799514d4b81bb24c5fcfeb6472b245')  # type: ignore
+
 
+def test_create_empty_read_tree(testrepo: Repository) -> None:
+    index = Index()
+    tree = testrepo['fd937514cb799514d4b81bb24c5fcfeb6472b245']
+    assert isinstance(tree, Tree)
+    index.read_tree(tree)
+
+
+@utils.fails_in_macos
+def test_add_conflict(testrepo: Repository) -> None:
+    ancestor_blob_id = testrepo.create_blob('ancestor')
+    ancestor = IndexEntry('conflict.txt', ancestor_blob_id, FileMode.BLOB_EXECUTABLE)
+
+    ours_blob_id = testrepo.create_blob('ours')
+    ours = IndexEntry('conflict.txt', ours_blob_id, FileMode.BLOB)
 
-def test_create_empty_read_tree(testrepo):
     index = Index()
-    index.read_tree(testrepo['fd937514cb799514d4b81bb24c5fcfeb6472b245'])
+    assert index.conflicts is None
+
+    index.add_conflict(ancestor, ours, None)
+
+    assert index.conflicts is not None
+    assert 'conflict.txt' in index.conflicts
+    conflict_ancestor, conflict_ours, conflict_theirs = index.conflicts['conflict.txt']
+    assert conflict_ancestor.id == ancestor_blob_id
+    assert conflict_ancestor.mode == FileMode.BLOB_EXECUTABLE
+    assert conflict_ours.id == ours_blob_id
+    assert conflict_ours.mode == FileMode.BLOB
+    assert conflict_theirs is None
diff -pruN 1.17.0-2/test/test_mailmap.py 1.18.2-1/test/test_mailmap.py
--- 1.17.0-2/test/test_mailmap.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_mailmap.py	2025-08-16 11:34:29.000000000 +0000
@@ -27,7 +27,6 @@
 
 from pygit2 import Mailmap
 
-
 TEST_MAILMAP = """\
 # Simple Comment line
 <cto@company.xx>                       <cto@coompany.xx>
@@ -63,14 +62,14 @@ TEST_RESOLVE = [
 ]
 
 
-def test_empty():
+def test_empty() -> None:
     mailmap = Mailmap()
 
     for _, _, name, email in TEST_RESOLVE:
         assert mailmap.resolve(name, email) == (name, email)
 
 
-def test_new():
+def test_new() -> None:
     mailmap = Mailmap()
 
     # Add entries to the mailmap
@@ -81,7 +80,7 @@ def test_new():
         assert mailmap.resolve(name, email) == (real_name, real_email)
 
 
-def test_parsed():
+def test_parsed() -> None:
     mailmap = Mailmap.from_buffer(TEST_MAILMAP)
 
     for real_name, real_email, name, email in TEST_RESOLVE:
diff -pruN 1.17.0-2/test/test_merge.py 1.18.2-1/test/test_merge.py
--- 1.17.0-2/test/test_merge.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_merge.py	2025-08-16 11:34:29.000000000 +0000
@@ -30,18 +30,29 @@ from pathlib import Path
 import pytest
 
 import pygit2
-from pygit2.enums import FileStatus, MergeAnalysis, MergeFavor, MergeFlag, MergeFileFlag
+from pygit2 import Repository
+from pygit2.enums import FileStatus, MergeAnalysis, MergeFavor, MergeFileFlag, MergeFlag
 
 
 @pytest.mark.parametrize('id', [None, 42])
-def test_merge_invalid_type(mergerepo, id):
+def test_merge_invalid_type(mergerepo: Repository, id: None | int) -> None:
     with pytest.raises(TypeError):
-        mergerepo.merge(id)
+        mergerepo.merge(id)  # type:ignore
 
 
-def test_merge_analysis_uptodate(mergerepo):
+# TODO: Once Repository.merge drops support for str arguments,
+#       add an extra parameter to test_merge_invalid_type above
+#       to make sure we cover legacy code.
+def test_merge_string_argument_deprecated(mergerepo: Repository) -> None:
     branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533'
-    branch_id = mergerepo.get(branch_head_hex).id
+
+    with pytest.warns(DeprecationWarning, match=r'Pass Commit.+instead'):
+        mergerepo.merge(branch_head_hex)
+
+
+def test_merge_analysis_uptodate(mergerepo: Repository) -> None:
+    branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533'
+    branch_id = mergerepo[branch_head_hex].id
 
     analysis, preference = mergerepo.merge_analysis(branch_id)
     assert analysis & MergeAnalysis.UP_TO_DATE
@@ -54,9 +65,9 @@ def test_merge_analysis_uptodate(mergere
     assert {} == mergerepo.status()
 
 
-def test_merge_analysis_fastforward(mergerepo):
+def test_merge_analysis_fastforward(mergerepo: Repository) -> None:
     branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87'
-    branch_id = mergerepo.get(branch_head_hex).id
+    branch_id = mergerepo[branch_head_hex].id
 
     analysis, preference = mergerepo.merge_analysis(branch_id)
     assert not analysis & MergeAnalysis.UP_TO_DATE
@@ -69,9 +80,9 @@ def test_merge_analysis_fastforward(merg
     assert {} == mergerepo.status()
 
 
-def test_merge_no_fastforward_no_conflicts(mergerepo):
+def test_merge_no_fastforward_no_conflicts(mergerepo: Repository) -> None:
     branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
-    branch_id = mergerepo.get(branch_head_hex).id
+    branch_id = mergerepo[branch_head_hex].id
     analysis, preference = mergerepo.merge_analysis(branch_id)
     assert not analysis & MergeAnalysis.UP_TO_DATE
     assert not analysis & MergeAnalysis.FASTFORWARD
@@ -80,15 +91,18 @@ def test_merge_no_fastforward_no_conflic
     assert {} == mergerepo.status()
 
 
-def test_merge_invalid_hex(mergerepo):
+def test_merge_invalid_hex(mergerepo: Repository) -> None:
     branch_head_hex = '12345678'
-    with pytest.raises(KeyError):
+    with (
+        pytest.raises(KeyError),
+        pytest.warns(DeprecationWarning, match=r'Pass Commit.+instead'),
+    ):
         mergerepo.merge(branch_head_hex)
 
 
-def test_merge_already_something_in_index(mergerepo):
+def test_merge_already_something_in_index(mergerepo: Repository) -> None:
     branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
-    branch_oid = mergerepo.get(branch_head_hex).id
+    branch_oid = mergerepo[branch_head_hex].id
     with (Path(mergerepo.workdir) / 'inindex.txt').open('w') as f:
         f.write('new content')
     mergerepo.index.add('inindex.txt')
@@ -96,9 +110,9 @@ def test_merge_already_something_in_inde
         mergerepo.merge(branch_oid)
 
 
-def test_merge_no_fastforward_conflicts(mergerepo):
+def test_merge_no_fastforward_conflicts(mergerepo: Repository) -> None:
     branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
-    branch_id = mergerepo.get(branch_head_hex).id
+    branch_id = mergerepo[branch_head_hex].id
 
     analysis, preference = mergerepo.merge_analysis(branch_id)
     assert not analysis & MergeAnalysis.UP_TO_DATE
@@ -131,8 +145,8 @@ def test_merge_no_fastforward_conflicts(
     assert {'.gitignore': FileStatus.INDEX_MODIFIED} == mergerepo.status()
 
 
-def test_merge_remove_conflicts(mergerepo):
-    other_branch_tip = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
+def test_merge_remove_conflicts(mergerepo: Repository) -> None:
+    other_branch_tip = pygit2.Oid(hex='1b2bae55ac95a4be3f8983b86cd579226d0eb247')
     mergerepo.merge(other_branch_tip)
     idx = mergerepo.index
     conflicts = idx.conflicts
@@ -141,7 +155,7 @@ def test_merge_remove_conflicts(mergerep
     try:
         conflicts['.gitignore']
     except KeyError:
-        mergerepo.fail("conflicts['.gitignore'] raised KeyError unexpectedly")
+        mergerepo.fail("conflicts['.gitignore'] raised KeyError unexpectedly")  # type: ignore
     del idx.conflicts['.gitignore']
     with pytest.raises(KeyError):
         conflicts.__getitem__('.gitignore')
@@ -157,31 +171,30 @@ def test_merge_remove_conflicts(mergerep
         MergeFavor.UNION,
     ],
 )
-def test_merge_favor(mergerepo, favor):
-    branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
-    mergerepo.merge(branch_head_hex, favor=favor)
+def test_merge_favor(mergerepo: Repository, favor: MergeFavor) -> None:
+    branch_head = pygit2.Oid(hex='1b2bae55ac95a4be3f8983b86cd579226d0eb247')
+    mergerepo.merge(branch_head, favor=favor)
 
     assert mergerepo.index.conflicts is None
 
 
-def test_merge_fail_on_conflict(mergerepo):
-    branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
+def test_merge_fail_on_conflict(mergerepo: Repository) -> None:
+    branch_head = pygit2.Oid(hex='1b2bae55ac95a4be3f8983b86cd579226d0eb247')
 
-    with pytest.raises(pygit2.GitError):
+    with pytest.raises(pygit2.GitError, match=r'merge conflicts exist'):
         mergerepo.merge(
-            branch_head_hex, flags=MergeFlag.FIND_RENAMES | MergeFlag.FAIL_ON_CONFLICT
+            branch_head, flags=MergeFlag.FIND_RENAMES | MergeFlag.FAIL_ON_CONFLICT
         )
 
 
-def test_merge_commits(mergerepo):
-    branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
-    branch_id = mergerepo.get(branch_head_hex).id
+def test_merge_commits(mergerepo: Repository) -> None:
+    branch_head = pygit2.Oid(hex='03490f16b15a09913edb3a067a3dc67fbb8d41f1')
 
-    merge_index = mergerepo.merge_commits(mergerepo.head.target, branch_head_hex)
+    merge_index = mergerepo.merge_commits(mergerepo.head.target, branch_head)
     assert merge_index.conflicts is None
     merge_commits_tree = merge_index.write_tree(mergerepo)
 
-    mergerepo.merge(branch_id)
+    mergerepo.merge(branch_head)
     index = mergerepo.index
     assert index.conflicts is None
     merge_tree = index.write_tree()
@@ -189,27 +202,24 @@ def test_merge_commits(mergerepo):
     assert merge_tree == merge_commits_tree
 
 
-def test_merge_commits_favor(mergerepo):
-    branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
+def test_merge_commits_favor(mergerepo: Repository) -> None:
+    branch_head = pygit2.Oid(hex='1b2bae55ac95a4be3f8983b86cd579226d0eb247')
 
     merge_index = mergerepo.merge_commits(
-        mergerepo.head.target, branch_head_hex, favor=MergeFavor.OURS
+        mergerepo.head.target, branch_head, favor=MergeFavor.OURS
     )
     assert merge_index.conflicts is None
 
     # Incorrect favor value
-    with pytest.raises(TypeError):
-        mergerepo.merge_commits(mergerepo.head.target, branch_head_hex, favor='foo')
+    with pytest.raises(TypeError, match=r'favor argument must be MergeFavor'):
+        mergerepo.merge_commits(mergerepo.head.target, branch_head, favor='foo')  # type: ignore
 
 
-def test_merge_trees(mergerepo):
-    branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
-    branch_id = mergerepo.get(branch_head_hex).id
+def test_merge_trees(mergerepo: Repository) -> None:
+    branch_id = pygit2.Oid(hex='03490f16b15a09913edb3a067a3dc67fbb8d41f1')
     ancestor_id = mergerepo.merge_base(mergerepo.head.target, branch_id)
 
-    merge_index = mergerepo.merge_trees(
-        ancestor_id, mergerepo.head.target, branch_head_hex
-    )
+    merge_index = mergerepo.merge_trees(ancestor_id, mergerepo.head.target, branch_id)
     assert merge_index.conflicts is None
     merge_commits_tree = merge_index.write_tree(mergerepo)
 
@@ -221,7 +231,7 @@ def test_merge_trees(mergerepo):
     assert merge_tree == merge_commits_tree
 
 
-def test_merge_trees_favor(mergerepo):
+def test_merge_trees_favor(mergerepo: Repository) -> None:
     branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
     ancestor_id = mergerepo.merge_base(mergerepo.head.target, branch_head_hex)
     merge_index = mergerepo.merge_trees(
@@ -231,14 +241,19 @@ def test_merge_trees_favor(mergerepo):
 
     with pytest.raises(TypeError):
         mergerepo.merge_trees(
-            ancestor_id, mergerepo.head.target, branch_head_hex, favor='foo'
+            ancestor_id,
+            mergerepo.head.target,
+            branch_head_hex,
+            favor='foo',  # type: ignore
         )
 
 
-def test_merge_options():
+def test_merge_options() -> None:
     favor = MergeFavor.OURS
-    flags = MergeFlag.FIND_RENAMES | MergeFlag.FAIL_ON_CONFLICT
-    file_flags = MergeFileFlag.IGNORE_WHITESPACE | MergeFileFlag.DIFF_PATIENCE
+    flags: int | MergeFlag = MergeFlag.FIND_RENAMES | MergeFlag.FAIL_ON_CONFLICT
+    file_flags: int | MergeFileFlag = (
+        MergeFileFlag.IGNORE_WHITESPACE | MergeFileFlag.DIFF_PATIENCE
+    )
     o1 = pygit2.Repository._merge_options(
         favor=favor, flags=flags, file_flags=file_flags
     )
@@ -271,9 +286,9 @@ def test_merge_options():
     assert file_flags == o1.file_flags
 
 
-def test_merge_many(mergerepo):
+def test_merge_many(mergerepo: Repository) -> None:
     branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
-    branch_id = mergerepo.get(branch_head_hex).id
+    branch_id = mergerepo[branch_head_hex].id
     ancestor_id = mergerepo.merge_base_many([mergerepo.head.target, branch_id])
 
     merge_index = mergerepo.merge_trees(
@@ -290,9 +305,9 @@ def test_merge_many(mergerepo):
     assert merge_tree == merge_commits_tree
 
 
-def test_merge_octopus(mergerepo):
+def test_merge_octopus(mergerepo: Repository) -> None:
     branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
-    branch_id = mergerepo.get(branch_head_hex).id
+    branch_id = mergerepo[branch_head_hex].id
     ancestor_id = mergerepo.merge_base_octopus([mergerepo.head.target, branch_id])
 
     merge_index = mergerepo.merge_trees(
@@ -309,38 +324,56 @@ def test_merge_octopus(mergerepo):
     assert merge_tree == merge_commits_tree
 
 
-def test_merge_mergeheads(mergerepo):
+def test_merge_mergeheads(mergerepo: Repository) -> None:
     assert mergerepo.listall_mergeheads() == []
 
-    branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
-    mergerepo.merge(branch_head_hex)
+    branch_head = pygit2.Oid(hex='1b2bae55ac95a4be3f8983b86cd579226d0eb247')
+    mergerepo.merge(branch_head)
 
-    assert mergerepo.listall_mergeheads() == [pygit2.Oid(hex=branch_head_hex)]
+    assert mergerepo.listall_mergeheads() == [branch_head]
 
     mergerepo.state_cleanup()
-    assert (
-        mergerepo.listall_mergeheads() == []
-    ), 'state_cleanup() should wipe the mergeheads'
+    assert mergerepo.listall_mergeheads() == [], (
+        'state_cleanup() should wipe the mergeheads'
+    )
 
 
-def test_merge_message(mergerepo):
+def test_merge_message(mergerepo: Repository) -> None:
     assert not mergerepo.message
     assert not mergerepo.raw_message
 
-    branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
-    mergerepo.merge(branch_head_hex)
+    branch_head = pygit2.Oid(hex='1b2bae55ac95a4be3f8983b86cd579226d0eb247')
+    mergerepo.merge(branch_head)
 
-    assert mergerepo.message.startswith(f"Merge commit '{branch_head_hex}'")
+    assert mergerepo.message.startswith(f"Merge commit '{branch_head}'")
     assert mergerepo.message.encode('utf-8') == mergerepo.raw_message
 
     mergerepo.state_cleanup()
     assert not mergerepo.message
 
 
-def test_merge_remove_message(mergerepo):
-    branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
-    mergerepo.merge(branch_head_hex)
+def test_merge_remove_message(mergerepo: Repository) -> None:
+    branch_head = pygit2.Oid(hex='1b2bae55ac95a4be3f8983b86cd579226d0eb247')
+    mergerepo.merge(branch_head)
 
-    assert mergerepo.message.startswith(f"Merge commit '{branch_head_hex}'")
+    assert mergerepo.message.startswith(f"Merge commit '{branch_head}'")
     mergerepo.remove_message()
     assert not mergerepo.message
+
+
+def test_merge_commit(mergerepo: Repository) -> None:
+    commit = mergerepo['1b2bae55ac95a4be3f8983b86cd579226d0eb247']
+    assert isinstance(commit, pygit2.Commit)
+    mergerepo.merge(commit)
+
+    assert mergerepo.message.startswith(f"Merge commit '{str(commit.id)}'")
+    assert mergerepo.listall_mergeheads() == [commit.id]
+
+
+def test_merge_reference(mergerepo: Repository) -> None:
+    branch = mergerepo.branches.local['branch-conflicts']
+    branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
+    mergerepo.merge(branch)
+
+    assert mergerepo.message.startswith("Merge branch 'branch-conflicts'")
+    assert mergerepo.listall_mergeheads() == [pygit2.Oid(hex=branch_head_hex)]
diff -pruN 1.17.0-2/test/test_nonunicode.py 1.18.2-1/test/test_nonunicode.py
--- 1.17.0-2/test/test_nonunicode.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.18.2-1/test/test_nonunicode.py	2025-08-16 11:34:29.000000000 +0000
@@ -0,0 +1,59 @@
+# Copyright 2010-2024 The pygit2 contributors
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# In addition to the permissions in the GNU General Public License,
+# the authors give you unlimited permission to link the compiled
+# version of this file into combinations with other programs,
+# and to distribute those combinations without any restriction
+# coming from the use of this file.  (The General Public License
+# restrictions do apply in other respects; for example, they cover
+# modification of the file, and distribution when not linked into
+# a combined executable.)
+#
+# This file is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING.  If not, write to
+# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+"""Tests for non unicode byte strings"""
+
+import os
+import shutil
+import sys
+
+import pytest
+
+import pygit2
+from pygit2 import Repository
+
+from . import utils
+
+# FIXME Detect the filesystem rather than the operating system
+works_in_linux = pytest.mark.xfail(
+    sys.platform != 'linux',
+    reason='fails in macOS/Windows, and also in Linux with the FAT filesystem',
+)
+
+
+@utils.requires_network
+@works_in_linux
+def test_nonunicode_branchname(testrepo: Repository) -> None:
+    folderpath = 'temp_repo_nonutf'
+    if os.path.exists(folderpath):
+        shutil.rmtree(folderpath)
+    newrepo = pygit2.clone_repository(
+        path=folderpath, url='https://github.com/pygit2/test_branch_notutf.git'
+    )
+    bstring = b'\xc3master'
+    assert bstring in [
+        (ref.split('/')[-1]).encode('utf8', 'surrogateescape')
+        for ref in newrepo.listall_references()
+    ]  # Remote branch among references: 'refs/remotes/origin/\udcc3master'
diff -pruN 1.17.0-2/test/test_note.py 1.18.2-1/test/test_note.py
--- 1.17.0-2/test/test_note.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_note.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,9 +25,9 @@
 
 """Tests for note objects."""
 
-from pygit2 import Signature
 import pytest
 
+from pygit2 import Blob, Repository, Signature
 
 NOTE = ('6c8980ba963cad8b25a9bcaf68d4023ee57370d8', 'note message')
 
@@ -45,24 +45,26 @@ NOTES = [
 ]
 
 
-def test_create_note(barerepo):
+def test_create_note(barerepo: Repository) -> None:
     annotated_id = barerepo.revparse_single('HEAD~3').id
     author = committer = Signature('Foo bar', 'foo@bar.com', 12346, 0)
     note_id = barerepo.create_note(NOTE[1], author, committer, str(annotated_id))
     assert NOTE[0] == note_id
 
+    note = barerepo[note_id]
+    assert isinstance(note, Blob)
     # check the note blob
-    assert NOTE[1].encode() == barerepo[note_id].data
+    assert NOTE[1].encode() == note.data
 
 
-def test_lookup_note(barerepo):
+def test_lookup_note(barerepo: Repository) -> None:
     annotated_id = str(barerepo.head.target)
     note = barerepo.lookup_note(annotated_id)
     assert NOTES[0][0] == note.id
     assert NOTES[0][1] == note.message
 
 
-def test_remove_note(barerepo):
+def test_remove_note(barerepo: Repository) -> None:
     head = barerepo.head
     note = barerepo.lookup_note(str(head.target))
     author = committer = Signature('Foo bar', 'foo@bar.com', 12346, 0)
@@ -71,11 +73,14 @@ def test_remove_note(barerepo):
         barerepo.lookup_note(str(head.target))
 
 
-def test_iterate_notes(barerepo):
+def test_iterate_notes(barerepo: Repository) -> None:
     for i, note in enumerate(barerepo.notes()):
-        assert NOTES[i] == (note.id, note.message, note.annotated_id)
+        note_id, message, annotated_id = NOTES[i]
+        assert note_id == note.id
+        assert message == note.message
+        assert annotated_id == note.annotated_id
 
 
-def test_iterate_non_existing_ref(barerepo):
+def test_iterate_non_existing_ref(barerepo: Repository) -> None:
     with pytest.raises(KeyError):
-        barerepo.notes('refs/notes/bad_ref')
+        barerepo.notes('refs/notes/bad_ref')  # type: ignore
diff -pruN 1.17.0-2/test/test_object.py 1.18.2-1/test/test_object.py
--- 1.17.0-2/test/test_object.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_object.py	2025-08-16 11:34:29.000000000 +0000
@@ -27,10 +27,9 @@
 
 import pytest
 
-from pygit2 import Tree, Tag
+from pygit2 import Commit, Object, Oid, Repository, Tag, Tree
 from pygit2.enums import ObjectType
 
-
 BLOB_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b'
 BLOB_CONTENT = """hello world
 hola mundo
@@ -40,7 +39,7 @@ BLOB_NEW_CONTENT = b'foo bar\n'
 BLOB_FILE_CONTENT = b'bye world\n'
 
 
-def test_equality(testrepo):
+def test_equality(testrepo: Repository) -> None:
     # get a commit object twice and see if it equals ittestrepo
     commit_id = testrepo.lookup_reference('refs/heads/master').target
     commit_a = testrepo[commit_id]
@@ -51,7 +50,7 @@ def test_equality(testrepo):
     assert not (commit_a != commit_b)
 
 
-def test_hashing(testrepo):
+def test_hashing(testrepo: Repository) -> None:
     # get a commit object twice and compare hashes
     commit_id = testrepo.lookup_reference('refs/heads/master').target
     commit_a = testrepo[commit_id]
@@ -81,27 +80,27 @@ def test_hashing(testrepo):
     assert commit_b == commit_a
 
 
-def test_peel_commit(testrepo):
+def test_peel_commit(testrepo: Repository) -> None:
     # start by looking up the commit
     commit_id = testrepo.lookup_reference('refs/heads/master').target
     commit = testrepo[commit_id]
     # and peel to the tree
     tree = commit.peel(ObjectType.TREE)
 
-    assert type(tree) == Tree
+    assert type(tree) is Tree
     assert tree.id == 'fd937514cb799514d4b81bb24c5fcfeb6472b245'
 
 
-def test_peel_commit_type(testrepo):
+def test_peel_commit_type(testrepo: Repository) -> None:
     commit_id = testrepo.lookup_reference('refs/heads/master').target
     commit = testrepo[commit_id]
     tree = commit.peel(Tree)
 
-    assert type(tree) == Tree
+    assert type(tree) is Tree
     assert tree.id == 'fd937514cb799514d4b81bb24c5fcfeb6472b245'
 
 
-def test_invalid(testrepo):
+def test_invalid(testrepo: Repository) -> None:
     commit_id = testrepo.lookup_reference('refs/heads/master').target
     commit = testrepo[commit_id]
 
@@ -109,7 +108,7 @@ def test_invalid(testrepo):
         commit.peel(ObjectType.TAG)
 
 
-def test_invalid_type(testrepo):
+def test_invalid_type(testrepo: Repository) -> None:
     commit_id = testrepo.lookup_reference('refs/heads/master').target
     commit = testrepo[commit_id]
 
@@ -117,10 +116,10 @@ def test_invalid_type(testrepo):
         commit.peel(Tag)
 
 
-def test_short_id(testrepo):
-    seen = {}  # from short_id to full hex id
+def test_short_id(testrepo: Repository) -> None:
+    seen: dict[str, Oid] = {}  # from short_id to full hex id
 
-    def test_obj(obj, msg):
+    def test_obj(obj: Object | Commit, msg: str) -> None:
         short_id = obj.short_id
         msg = msg + f' short_id={short_id}'
         already = seen.get(short_id)
@@ -139,7 +138,7 @@ def test_short_id(testrepo):
             test_obj(testrepo[entry.id], f'entry={entry.name}#{entry.id}')
 
 
-def test_repr(testrepo):
+def test_repr(testrepo: Repository) -> None:
     commit_id = testrepo.lookup_reference('refs/heads/master').target
     commit_a = testrepo[commit_id]
     assert repr(commit_a) == '<pygit2.Object{commit:%s}>' % commit_id
diff -pruN 1.17.0-2/test/test_odb.py 1.18.2-1/test/test_odb.py
--- 1.17.0-2/test/test_odb.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_odb.py	2025-08-16 11:34:29.000000000 +0000
@@ -27,22 +27,23 @@
 
 # Standard Library
 import binascii
+from collections.abc import Generator
 from pathlib import Path
 
 import pytest
 
 # pygit2
-from pygit2 import Odb, Oid
+from pygit2 import Odb, Oid, Repository
 from pygit2.enums import ObjectType
-from . import utils
 
+from . import utils
 
 BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16'
 BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii'))
 BLOB_OID = Oid(raw=BLOB_RAW)
 
 
-def test_emptyodb(barerepo):
+def test_emptyodb(barerepo: Repository) -> None:
     odb = Odb()
 
     assert len(list(odb)) == 0
@@ -53,22 +54,22 @@ def test_emptyodb(barerepo):
 
 
 @pytest.fixture
-def odb(barerepo):
+def odb(barerepo: Repository) -> Generator[Odb, None, None]:
     odb = barerepo.odb
     yield odb
 
 
-def test_iterable(odb):
+def test_iterable(odb: Odb) -> None:
     assert BLOB_HEX in odb
 
 
-def test_contains(odb):
+def test_contains(odb: Odb) -> None:
     assert BLOB_HEX in odb
 
 
-def test_read(odb):
+def test_read(odb: Odb) -> None:
     with pytest.raises(TypeError):
-        odb.read(123)
+        odb.read(123)  # type: ignore
     utils.assertRaisesWithArg(KeyError, '1' * 40, odb.read, '1' * 40)
 
     ab = odb.read(BLOB_OID)
@@ -84,11 +85,11 @@ def test_read(odb):
     assert (ObjectType.BLOB, b'a contents\n') == a3
 
 
-def test_write(odb):
+def test_write(odb: Odb) -> None:
     data = b'hello world'
     # invalid object type
     with pytest.raises(ValueError):
         odb.write(ObjectType.ANY, data)
 
     oid = odb.write(ObjectType.BLOB, data)
-    assert type(oid) == Oid
+    assert type(oid) is Oid
diff -pruN 1.17.0-2/test/test_odb_backend.py 1.18.2-1/test/test_odb_backend.py
--- 1.17.0-2/test/test_odb_backend.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_odb_backend.py	2025-08-16 11:34:29.000000000 +0000
@@ -27,15 +27,17 @@
 
 # Standard Library
 import binascii
+from collections.abc import Generator, Iterator
 from pathlib import Path
 
 import pytest
 
 # pygit2
 import pygit2
+from pygit2 import Odb, Oid, Repository
 from pygit2.enums import ObjectType
-from . import utils
 
+from . import utils
 
 BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16'
 BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii'))
@@ -43,12 +45,12 @@ BLOB_OID = pygit2.Oid(raw=BLOB_RAW)
 
 
 @pytest.fixture
-def odb(barerepo):
+def odb_path(barerepo: Repository) -> Generator[tuple[Odb, Path], None, None]:
     yield barerepo.odb, Path(barerepo.path) / 'objects'
 
 
-def test_pack(odb):
-    odb, path = odb
+def test_pack(odb_path: tuple[Odb, Path]) -> None:
+    odb, path = odb_path
 
     pack = pygit2.OdbBackendPack(path)
     assert len(list(pack)) > 0
@@ -56,8 +58,8 @@ def test_pack(odb):
         assert obj in odb
 
 
-def test_loose(odb):
-    odb, path = odb
+def test_loose(odb_path: tuple[Odb, Path]) -> None:
+    odb, path = odb_path
 
     pack = pygit2.OdbBackendLoose(path, 5, False)
     assert len(list(pack)) > 0
@@ -66,30 +68,30 @@ def test_loose(odb):
 
 
 class ProxyBackend(pygit2.OdbBackend):
-    def __init__(self, source):
+    def __init__(self, source: pygit2.OdbBackend | pygit2.OdbBackendPack) -> None:
         super().__init__()
         self.source = source
 
-    def read_cb(self, oid):
+    def read_cb(self, oid: Oid | str) -> tuple[int, bytes]:
         return self.source.read(oid)
 
-    def read_prefix_cb(self, oid):
+    def read_prefix_cb(self, oid: Oid | str) -> tuple[int, bytes, Oid]:
         return self.source.read_prefix(oid)
 
-    def read_header_cb(self, oid):
+    def read_header_cb(self, oid: Oid | str) -> tuple[int, int]:
         typ, data = self.source.read(oid)
         return typ, len(data)
 
-    def exists_cb(self, oid):
+    def exists_cb(self, oid: Oid | str) -> bool:
         return self.source.exists(oid)
 
-    def exists_prefix_cb(self, oid):
+    def exists_prefix_cb(self, oid: Oid | str) -> Oid:
         return self.source.exists_prefix(oid)
 
-    def refresh_cb(self):
+    def refresh_cb(self) -> None:
         self.source.refresh()
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[Oid]:
         return iter(self.source)
 
 
@@ -100,18 +102,18 @@ class ProxyBackend(pygit2.OdbBackend):
 
 
 @pytest.fixture
-def proxy(barerepo):
+def proxy(barerepo: Repository) -> Generator[ProxyBackend, None, None]:
     path = Path(barerepo.path) / 'objects'
     yield ProxyBackend(pygit2.OdbBackendPack(path))
 
 
-def test_iterable(proxy):
+def test_iterable(proxy: ProxyBackend) -> None:
     assert BLOB_HEX in [o for o in proxy]
 
 
-def test_read(proxy):
+def test_read(proxy: ProxyBackend) -> None:
     with pytest.raises(TypeError):
-        proxy.read(123)
+        proxy.read(123)  # type: ignore
     utils.assertRaisesWithArg(KeyError, '1' * 40, proxy.read, '1' * 40)
 
     ab = proxy.read(BLOB_OID)
@@ -120,21 +122,21 @@ def test_read(proxy):
     assert (ObjectType.BLOB, b'a contents\n') == a
 
 
-def test_read_prefix(proxy):
+def test_read_prefix(proxy: ProxyBackend) -> None:
     a_hex_prefix = BLOB_HEX[:4]
     a3 = proxy.read_prefix(a_hex_prefix)
     assert (ObjectType.BLOB, b'a contents\n', BLOB_OID) == a3
 
 
-def test_exists(proxy):
+def test_exists(proxy: ProxyBackend) -> None:
     with pytest.raises(TypeError):
-        proxy.exists(123)
+        proxy.exists(123)  # type: ignore
 
     assert not proxy.exists('1' * 40)
     assert proxy.exists(BLOB_HEX)
 
 
-def test_exists_prefix(proxy):
+def test_exists_prefix(proxy: ProxyBackend) -> None:
     a_hex_prefix = BLOB_HEX[:4]
     assert BLOB_HEX == proxy.exists_prefix(a_hex_prefix)
 
@@ -145,12 +147,12 @@ def test_exists_prefix(proxy):
 
 
 @pytest.fixture
-def repo(barerepo):
+def repo(barerepo: Repository) -> Generator[Repository, None, None]:
     odb = pygit2.Odb()
 
     path = Path(barerepo.path) / 'objects'
-    backend = pygit2.OdbBackendPack(path)
-    backend = ProxyBackend(backend)
+    backend_org = pygit2.OdbBackendPack(path)
+    backend = ProxyBackend(backend_org)
     odb.add_backend(backend, 1)
 
     repo = pygit2.Repository()
@@ -158,9 +160,9 @@ def repo(barerepo):
     yield repo
 
 
-def test_repo_read(repo):
+def test_repo_read(repo: Repository) -> None:
     with pytest.raises(TypeError):
-        repo[123]
+        repo[123]  # type: ignore
 
     utils.assertRaisesWithArg(KeyError, '1' * 40, repo.__getitem__, '1' * 40)
 
diff -pruN 1.17.0-2/test/test_oid.py 1.18.2-1/test/test_oid.py
--- 1.17.0-2/test/test_oid.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_oid.py	2025-08-16 11:34:29.000000000 +0000
@@ -28,50 +28,50 @@
 # Standard Library
 from binascii import unhexlify
 
-from pygit2 import Oid
 import pytest
 
+from pygit2 import Oid
 
 HEX = '15b648aec6ed045b5ca6f57f8b7831a8b4757298'
 RAW = unhexlify(HEX.encode('ascii'))
 
 
-def test_raw():
+def test_raw() -> None:
     oid = Oid(raw=RAW)
     assert oid.raw == RAW
     assert oid == HEX
 
 
-def test_hex():
+def test_hex() -> None:
     oid = Oid(hex=HEX)
     assert oid.raw == RAW
     assert oid == HEX
 
 
-def test_hex_bytes():
+def test_hex_bytes() -> None:
     hex = bytes(HEX, 'ascii')
     with pytest.raises(TypeError):
-        Oid(hex=hex)
+        Oid(hex=hex)  # type: ignore
 
 
-def test_none():
+def test_none() -> None:
     with pytest.raises(ValueError):
         Oid()
 
 
-def test_both():
+def test_both() -> None:
     with pytest.raises(ValueError):
         Oid(raw=RAW, hex=HEX)
 
 
-def test_long():
+def test_long() -> None:
     with pytest.raises(ValueError):
         Oid(raw=RAW + b'a')
     with pytest.raises(ValueError):
         Oid(hex=HEX + 'a')
 
 
-def test_cmp():
+def test_cmp() -> None:
     oid1 = Oid(raw=RAW)
 
     # Equal
@@ -90,7 +90,7 @@ def test_cmp():
     assert not oid1 >= oid2
 
 
-def test_hash():
+def test_hash() -> None:
     s = set()
     s.add(Oid(raw=RAW))
     s.add(Oid(hex=HEX))
@@ -99,3 +99,10 @@ def test_hash():
     s.add(Oid(hex='0000000000000000000000000000000000000000'))
     s.add(Oid(hex='0000000000000000000000000000000000000001'))
     assert len(s) == 3
+
+
+def test_bool() -> None:
+    assert Oid(raw=RAW)
+    assert Oid(hex=HEX)
+    assert not Oid(raw=b'')
+    assert not Oid(hex='0000000000000000000000000000000000000000')
diff -pruN 1.17.0-2/test/test_options.py 1.18.2-1/test/test_options.py
--- 1.17.0-2/test/test_options.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_options.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,12 +23,16 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+import sys
+
+import pytest
+
 import pygit2
 from pygit2 import option
 from pygit2.enums import ConfigLevel, ObjectType, Option
 
 
-def __option(getter, setter, value):
+def __option(getter: Option, setter: Option, value: object) -> None:
     old_value = option(getter)
     option(setter, value)
     assert value == option(getter)
@@ -36,7 +40,7 @@ def __option(getter, setter, value):
     option(setter, old_value)
 
 
-def __proxy(name, value):
+def __proxy(name: str, value: object) -> None:
     old_value = getattr(pygit2.settings, name)
     setattr(pygit2.settings, name, value)
     assert value == getattr(pygit2.settings, name)
@@ -44,44 +48,44 @@ def __proxy(name, value):
     setattr(pygit2.settings, name, old_value)
 
 
-def test_mwindow_size():
+def test_mwindow_size() -> None:
     __option(Option.GET_MWINDOW_SIZE, Option.SET_MWINDOW_SIZE, 200 * 1024)
 
 
-def test_mwindow_size_proxy():
+def test_mwindow_size_proxy() -> None:
     __proxy('mwindow_size', 300 * 1024)
 
 
-def test_mwindow_mapped_limit_200():
+def test_mwindow_mapped_limit_200() -> None:
     __option(
         Option.GET_MWINDOW_MAPPED_LIMIT, Option.SET_MWINDOW_MAPPED_LIMIT, 200 * 1024
     )
 
 
-def test_mwindow_mapped_limit_300():
+def test_mwindow_mapped_limit_300() -> None:
     __proxy('mwindow_mapped_limit', 300 * 1024)
 
 
-def test_cache_object_limit():
+def test_cache_object_limit() -> None:
     new_limit = 2 * 1024
     option(Option.SET_CACHE_OBJECT_LIMIT, ObjectType.BLOB, new_limit)
 
 
-def test_cache_object_limit_proxy():
+def test_cache_object_limit_proxy() -> None:
     new_limit = 4 * 1024
     pygit2.settings.cache_object_limit(ObjectType.BLOB, new_limit)
 
 
-def test_cached_memory():
+def test_cached_memory() -> None:
     value = option(Option.GET_CACHED_MEMORY)
     assert value[1] == 256 * 1024**2
 
 
-def test_cached_memory_proxy():
+def test_cached_memory_proxy() -> None:
     assert pygit2.settings.cached_memory[1] == 256 * 1024**2
 
 
-def test_enable_caching():
+def test_enable_caching() -> None:
     pygit2.settings.enable_caching(False)
     pygit2.settings.enable_caching(True)
     # Lower level API
@@ -89,7 +93,7 @@ def test_enable_caching():
     option(Option.ENABLE_CACHING, True)
 
 
-def test_disable_pack_keep_file_checks():
+def test_disable_pack_keep_file_checks() -> None:
     pygit2.settings.disable_pack_keep_file_checks(False)
     pygit2.settings.disable_pack_keep_file_checks(True)
     # Lower level API
@@ -97,14 +101,14 @@ def test_disable_pack_keep_file_checks()
     option(Option.DISABLE_PACK_KEEP_FILE_CHECKS, True)
 
 
-def test_cache_max_size_proxy():
+def test_cache_max_size_proxy() -> None:
     pygit2.settings.cache_max_size(128 * 1024**2)
     assert pygit2.settings.cached_memory[1] == 128 * 1024**2
     pygit2.settings.cache_max_size(256 * 1024**2)
     assert pygit2.settings.cached_memory[1] == 256 * 1024**2
 
 
-def test_search_path():
+def test_search_path() -> None:
     paths = [
         (ConfigLevel.GLOBAL, '/tmp/global'),
         (ConfigLevel.XDG, '/tmp/xdg'),
@@ -116,7 +120,7 @@ def test_search_path():
         assert path == option(Option.GET_SEARCH_PATH, level)
 
 
-def test_search_path_proxy():
+def test_search_path_proxy() -> None:
     paths = [
         (ConfigLevel.GLOBAL, '/tmp2/global'),
         (ConfigLevel.XDG, '/tmp2/xdg'),
@@ -128,5 +132,131 @@ def test_search_path_proxy():
         assert path == pygit2.settings.search_path[level]
 
 
-def test_owner_validation():
+def test_owner_validation() -> None:
     __option(Option.GET_OWNER_VALIDATION, Option.SET_OWNER_VALIDATION, 0)
+
+
+def test_template_path() -> None:
+    original_path = option(Option.GET_TEMPLATE_PATH)
+
+    test_path = '/tmp/test_templates'
+    option(Option.SET_TEMPLATE_PATH, test_path)
+    assert option(Option.GET_TEMPLATE_PATH) == test_path
+
+    if original_path:
+        option(Option.SET_TEMPLATE_PATH, original_path)
+    else:
+        option(Option.SET_TEMPLATE_PATH, None)
+
+
+def test_user_agent() -> None:
+    original_agent = option(Option.GET_USER_AGENT)
+
+    test_agent = 'test-agent/1.0'
+    option(Option.SET_USER_AGENT, test_agent)
+    assert option(Option.GET_USER_AGENT) == test_agent
+
+    if original_agent:
+        option(Option.SET_USER_AGENT, original_agent)
+
+
+def test_pack_max_objects() -> None:
+    __option(Option.GET_PACK_MAX_OBJECTS, Option.SET_PACK_MAX_OBJECTS, 100000)
+
+
+@pytest.mark.skipif(sys.platform != 'win32', reason='Windows-specific feature')
+def test_windows_sharemode() -> None:
+    __option(Option.GET_WINDOWS_SHAREMODE, Option.SET_WINDOWS_SHAREMODE, 1)
+
+
+def test_ssl_ciphers() -> None:
+    # Setting SSL ciphers (no getter available)
+    try:
+        option(Option.SET_SSL_CIPHERS, 'DEFAULT')
+    except pygit2.GitError as e:
+        if "TLS backend doesn't support custom ciphers" in str(e):
+            pytest.skip(str(e))
+        raise
+
+
+def test_enable_http_expect_continue() -> None:
+    option(Option.ENABLE_HTTP_EXPECT_CONTINUE, True)
+    option(Option.ENABLE_HTTP_EXPECT_CONTINUE, False)
+
+
+def test_odb_priorities() -> None:
+    option(Option.SET_ODB_PACKED_PRIORITY, 1)
+    option(Option.SET_ODB_LOOSE_PRIORITY, 2)
+
+
+def test_extensions() -> None:
+    original_extensions = option(Option.GET_EXTENSIONS)
+    assert isinstance(original_extensions, list)
+
+    test_extensions = ['objectformat', 'worktreeconfig']
+    option(Option.SET_EXTENSIONS, test_extensions, len(test_extensions))
+
+    new_extensions = option(Option.GET_EXTENSIONS)
+    assert isinstance(new_extensions, list)
+
+    # Note: libgit2 may add its own built-in extensions and sort them
+    for ext in test_extensions:
+        assert ext in new_extensions, f"Extension '{ext}' not found in {new_extensions}"
+
+    option(Option.SET_EXTENSIONS, [], 0)
+    empty_extensions = option(Option.GET_EXTENSIONS)
+    assert isinstance(empty_extensions, list)
+
+    custom_extensions = ['myextension', 'objectformat']
+    option(Option.SET_EXTENSIONS, custom_extensions, len(custom_extensions))
+    custom_result = option(Option.GET_EXTENSIONS)
+    assert 'myextension' in custom_result
+    assert 'objectformat' in custom_result
+
+    if original_extensions:
+        option(Option.SET_EXTENSIONS, original_extensions, len(original_extensions))
+    else:
+        option(Option.SET_EXTENSIONS, [], 0)
+
+    final_extensions = option(Option.GET_EXTENSIONS)
+    assert set(final_extensions) == set(original_extensions)
+
+
+def test_homedir() -> None:
+    original_homedir = option(Option.GET_HOMEDIR)
+
+    test_homedir = '/tmp/test_home'
+    option(Option.SET_HOMEDIR, test_homedir)
+    assert option(Option.GET_HOMEDIR) == test_homedir
+
+    if original_homedir:
+        option(Option.SET_HOMEDIR, original_homedir)
+    else:
+        option(Option.SET_HOMEDIR, None)
+
+
+def test_server_timeouts() -> None:
+    original_connect = option(Option.GET_SERVER_CONNECT_TIMEOUT)
+    option(Option.SET_SERVER_CONNECT_TIMEOUT, 5000)
+    assert option(Option.GET_SERVER_CONNECT_TIMEOUT) == 5000
+    option(Option.SET_SERVER_CONNECT_TIMEOUT, original_connect)
+
+    original_timeout = option(Option.GET_SERVER_TIMEOUT)
+    option(Option.SET_SERVER_TIMEOUT, 10000)
+    assert option(Option.GET_SERVER_TIMEOUT) == 10000
+    option(Option.SET_SERVER_TIMEOUT, original_timeout)
+
+
+def test_user_agent_product() -> None:
+    original_product = option(Option.GET_USER_AGENT_PRODUCT)
+
+    test_product = 'test-product'
+    option(Option.SET_USER_AGENT_PRODUCT, test_product)
+    assert option(Option.GET_USER_AGENT_PRODUCT) == test_product
+
+    if original_product:
+        option(Option.SET_USER_AGENT_PRODUCT, original_product)
+
+
+def test_mwindow_file_limit() -> None:
+    __option(Option.GET_MWINDOW_FILE_LIMIT, Option.SET_MWINDOW_FILE_LIMIT, 100)
diff -pruN 1.17.0-2/test/test_packbuilder.py 1.18.2-1/test/test_packbuilder.py
--- 1.17.0-2/test/test_packbuilder.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_packbuilder.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,20 +25,22 @@
 
 """Tests for Index files."""
 
+from collections.abc import Callable
 from pathlib import Path
 
 import pygit2
-from pygit2 import PackBuilder
+from pygit2 import Oid, PackBuilder, Repository
+
 from . import utils
 
 
-def test_create_packbuilder(testrepo):
+def test_create_packbuilder(testrepo: Repository) -> None:
     # simple test of PackBuilder creation
     packbuilder = PackBuilder(testrepo)
     assert len(packbuilder) == 0
 
 
-def test_add(testrepo):
+def test_add(testrepo: Repository) -> None:
     # Add a few objects and confirm that the count is correct
     packbuilder = PackBuilder(testrepo)
     objects_to_add = [obj for obj in testrepo]
@@ -48,9 +50,10 @@ def test_add(testrepo):
     assert len(packbuilder) == 2
 
 
-def test_add_recursively(testrepo):
+def test_add_recursively(testrepo: Repository) -> None:
     # Add the head object and referenced objects recursively and confirm that the count is correct
     packbuilder = PackBuilder(testrepo)
+    assert isinstance(testrepo.head.target, Oid)
     packbuilder.add_recur(testrepo.head.target)
 
     # expect a count of 4 made up of the following referenced objects:
@@ -62,14 +65,14 @@ def test_add_recursively(testrepo):
     assert len(packbuilder) == 4
 
 
-def test_repo_pack(testrepo, tmp_path):
+def test_repo_pack(testrepo: Repository, tmp_path: Path) -> None:
     # pack the repo with the default strategy
     confirm_same_repo_after_packing(testrepo, tmp_path, None)
 
 
-def test_pack_with_delegate(testrepo, tmp_path):
+def test_pack_with_delegate(testrepo: Repository, tmp_path: Path) -> None:
     # loop through all branches and add each commit to the packbuilder
-    def pack_delegate(pb):
+    def pack_delegate(pb: PackBuilder) -> None:
         for branch in pb._repo.branches:
             br = pb._repo.branches.get(branch)
             for commit in br.log():
@@ -78,7 +81,7 @@ def test_pack_with_delegate(testrepo, tm
     confirm_same_repo_after_packing(testrepo, tmp_path, pack_delegate)
 
 
-def setup_second_repo(tmp_path):
+def setup_second_repo(tmp_path: Path) -> Repository:
     # helper method to set up a second repo for comparison
     tmp_path_2 = tmp_path / 'test_repo2'
     with utils.TemporaryRepository('testrepo.zip', tmp_path_2) as path:
@@ -86,7 +89,11 @@ def setup_second_repo(tmp_path):
     return testrepo
 
 
-def confirm_same_repo_after_packing(testrepo, tmp_path, pack_delegate):
+def confirm_same_repo_after_packing(
+    testrepo: Repository,
+    tmp_path: Path,
+    pack_delegate: Callable[[PackBuilder], None] | None,
+) -> None:
     # Helper method to confirm the contents of two repos before and after packing
     pack_repo = setup_second_repo(tmp_path)
     pack_repo_path = Path(pack_repo.path)
diff -pruN 1.17.0-2/test/test_patch.py 1.18.2-1/test/test_patch.py
--- 1.17.0-2/test/test_patch.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_patch.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,9 +23,10 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-import pygit2
 import pytest
 
+import pygit2
+from pygit2 import Blob, Repository
 
 BLOB_OLD_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b'
 BLOB_NEW_SHA = '3b18e512dba79e4c8300dd08aeb37f8e728b8dad'
@@ -80,7 +81,7 @@ index a520c24..0000000
 """
 
 
-def test_patch_create_from_buffers():
+def test_patch_create_from_buffers() -> None:
     patch = pygit2.Patch.create_from(
         BLOB_OLD_CONTENT,
         BLOB_NEW_CONTENT,
@@ -91,9 +92,11 @@ def test_patch_create_from_buffers():
     assert patch.text == BLOB_PATCH
 
 
-def test_patch_create_from_blobs(testrepo):
+def test_patch_create_from_blobs(testrepo: Repository) -> None:
     old_blob = testrepo[BLOB_OLD_SHA]
     new_blob = testrepo[BLOB_NEW_SHA]
+    assert isinstance(old_blob, Blob)
+    assert isinstance(new_blob, Blob)
 
     patch = pygit2.Patch.create_from(
         old_blob,
@@ -105,8 +108,9 @@ def test_patch_create_from_blobs(testrep
     assert patch.text == BLOB_PATCH2
 
 
-def test_patch_create_from_blob_buffer(testrepo):
+def test_patch_create_from_blob_buffer(testrepo: Repository) -> None:
     old_blob = testrepo[BLOB_OLD_SHA]
+    assert isinstance(old_blob, Blob)
     patch = pygit2.Patch.create_from(
         old_blob,
         BLOB_NEW_CONTENT,
@@ -117,7 +121,7 @@ def test_patch_create_from_blob_buffer(t
     assert patch.text == BLOB_PATCH
 
 
-def test_patch_create_from_blob_buffer_add(testrepo):
+def test_patch_create_from_blob_buffer_add(testrepo: Repository) -> None:
     patch = pygit2.Patch.create_from(
         None,
         BLOB_NEW_CONTENT,
@@ -128,8 +132,9 @@ def test_patch_create_from_blob_buffer_a
     assert patch.text == BLOB_PATCH_ADDED
 
 
-def test_patch_create_from_blob_buffer_delete(testrepo):
+def test_patch_create_from_blob_buffer_delete(testrepo: Repository) -> None:
     old_blob = testrepo[BLOB_OLD_SHA]
+    assert isinstance(old_blob, Blob)
 
     patch = pygit2.Patch.create_from(
         old_blob,
@@ -141,19 +146,21 @@ def test_patch_create_from_blob_buffer_d
     assert patch.text == BLOB_PATCH_DELETED
 
 
-def test_patch_create_from_bad_old_type_arg(testrepo):
+def test_patch_create_from_bad_old_type_arg(testrepo: Repository) -> None:
     with pytest.raises(TypeError):
-        pygit2.Patch.create_from(testrepo, BLOB_NEW_CONTENT)
+        pygit2.Patch.create_from(testrepo, BLOB_NEW_CONTENT)  # type: ignore
 
 
-def test_patch_create_from_bad_new_type_arg(testrepo):
+def test_patch_create_from_bad_new_type_arg(testrepo: Repository) -> None:
     with pytest.raises(TypeError):
-        pygit2.Patch.create_from(None, testrepo)
+        pygit2.Patch.create_from(None, testrepo)  # type: ignore
 
 
-def test_context_lines(testrepo):
+def test_context_lines(testrepo: Repository) -> None:
     old_blob = testrepo[BLOB_OLD_SHA]
     new_blob = testrepo[BLOB_NEW_SHA]
+    assert isinstance(old_blob, Blob)
+    assert isinstance(new_blob, Blob)
 
     patch = pygit2.Patch.create_from(
         old_blob,
@@ -162,6 +169,7 @@ def test_context_lines(testrepo):
         new_as_path=BLOB_NEW_PATH,
     )
 
+    assert patch.text is not None
     context_count = len(
         [line for line in patch.text.splitlines() if line.startswith(' ')]
     )
@@ -169,9 +177,11 @@ def test_context_lines(testrepo):
     assert context_count != 0
 
 
-def test_no_context_lines(testrepo):
+def test_no_context_lines(testrepo: Repository) -> None:
     old_blob = testrepo[BLOB_OLD_SHA]
     new_blob = testrepo[BLOB_NEW_SHA]
+    assert isinstance(old_blob, Blob)
+    assert isinstance(new_blob, Blob)
 
     patch = pygit2.Patch.create_from(
         old_blob,
@@ -181,6 +191,7 @@ def test_no_context_lines(testrepo):
         context_lines=0,
     )
 
+    assert patch.text is not None
     context_count = len(
         [line for line in patch.text.splitlines() if line.startswith(' ')]
     )
@@ -188,9 +199,11 @@ def test_no_context_lines(testrepo):
     assert context_count == 0
 
 
-def test_patch_create_blob_blobs(testrepo):
+def test_patch_create_blob_blobs(testrepo: Repository) -> None:
     old_blob = testrepo[testrepo.create_blob(BLOB_OLD_CONTENT)]
     new_blob = testrepo[testrepo.create_blob(BLOB_NEW_CONTENT)]
+    assert isinstance(old_blob, Blob)
+    assert isinstance(new_blob, Blob)
 
     patch = pygit2.Patch.create_from(
         old_blob,
@@ -202,8 +215,9 @@ def test_patch_create_blob_blobs(testrep
     assert patch.text == BLOB_PATCH
 
 
-def test_patch_create_blob_buffer(testrepo):
+def test_patch_create_blob_buffer(testrepo: Repository) -> None:
     blob = testrepo[testrepo.create_blob(BLOB_OLD_CONTENT)]
+    assert isinstance(blob, Blob)
     patch = pygit2.Patch.create_from(
         blob,
         BLOB_NEW_CONTENT,
@@ -214,8 +228,9 @@ def test_patch_create_blob_buffer(testre
     assert patch.text == BLOB_PATCH
 
 
-def test_patch_create_blob_delete(testrepo):
+def test_patch_create_blob_delete(testrepo: Repository) -> None:
     blob = testrepo[testrepo.create_blob(BLOB_OLD_CONTENT)]
+    assert isinstance(blob, Blob)
     patch = pygit2.Patch.create_from(
         blob,
         None,
@@ -226,8 +241,9 @@ def test_patch_create_blob_delete(testre
     assert patch.text == BLOB_PATCH_DELETED
 
 
-def test_patch_create_blob_add(testrepo):
+def test_patch_create_blob_add(testrepo: Repository) -> None:
     blob = testrepo[testrepo.create_blob(BLOB_NEW_CONTENT)]
+    assert isinstance(blob, Blob)
     patch = pygit2.Patch.create_from(
         None,
         blob,
@@ -238,8 +254,9 @@ def test_patch_create_blob_add(testrepo)
     assert patch.text == BLOB_PATCH_ADDED
 
 
-def test_patch_delete_blob(testrepo):
+def test_patch_delete_blob(testrepo: Repository) -> None:
     blob = testrepo[BLOB_OLD_SHA]
+    assert isinstance(blob, Blob)
     patch = pygit2.Patch.create_from(
         blob,
         None,
@@ -253,12 +270,14 @@ def test_patch_delete_blob(testrepo):
     assert patch.text == BLOB_PATCH_DELETED
 
 
-def test_patch_multi_blob(testrepo):
+def test_patch_multi_blob(testrepo: Repository) -> None:
     blob = testrepo[BLOB_OLD_SHA]
+    assert isinstance(blob, Blob)
     patch = pygit2.Patch.create_from(blob, None)
     patch_text = patch.text
 
     blob = testrepo[BLOB_OLD_SHA]
+    assert isinstance(blob, Blob)
     patch2 = pygit2.Patch.create_from(blob, None)
     patch_text2 = patch.text
 
diff -pruN 1.17.0-2/test/test_patch_encoding.py 1.18.2-1/test/test_patch_encoding.py
--- 1.17.0-2/test/test_patch_encoding.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_patch_encoding.py	2025-08-16 11:34:29.000000000 +0000
@@ -24,7 +24,7 @@
 # Boston, MA 02110-1301, USA.
 
 import pygit2
-
+from pygit2 import Blob, Repository
 
 expected_diff = b"""diff --git a/iso-8859-1.txt b/iso-8859-1.txt
 index e84e339..201e0c9 100644
@@ -36,7 +36,7 @@ index e84e339..201e0c9 100644
 """
 
 
-def test_patch_from_non_utf8():
+def test_patch_from_non_utf8() -> None:
     # blobs encoded in ISO-8859-1
     old_content = b'Kristian H\xf8gsberg\n'
     new_content = old_content + b'foo\n'
@@ -55,10 +55,14 @@ def test_patch_from_non_utf8():
     assert patch.text.encode('utf-8') != expected_diff
 
 
-def test_patch_create_from_blobs(encodingrepo):
+def test_patch_create_from_blobs(encodingrepo: Repository) -> None:
+    old_content = encodingrepo['e84e339ac7fcc823106efa65a6972d7a20016c85']
+    new_content = encodingrepo['201e0c908e3d9f526659df3e556c3d06384ef0df']
+    assert isinstance(old_content, Blob)
+    assert isinstance(new_content, Blob)
     patch = pygit2.Patch.create_from(
-        encodingrepo['e84e339ac7fcc823106efa65a6972d7a20016c85'],
-        encodingrepo['201e0c908e3d9f526659df3e556c3d06384ef0df'],
+        old_content,
+        new_content,
         old_as_path='iso-8859-1.txt',
         new_as_path='iso-8859-1.txt',
     )
diff -pruN 1.17.0-2/test/test_refdb_backend.py 1.18.2-1/test/test_refdb_backend.py
--- 1.17.0-2/test/test_refdb_backend.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_refdb_backend.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,11 +25,14 @@
 
 """Tests for Refdb objects."""
 
+from collections.abc import Generator, Iterator
 from pathlib import Path
 
-import pygit2
 import pytest
 
+import pygit2
+from pygit2 import Commit, Oid, Reference, Repository, Signature
+
 
 # Note: the refdb abstraction from libgit2 is meant to provide information
 # which libgit2 transforms into something more useful, and in general YMMV by
@@ -37,77 +40,88 @@ import pytest
 # incomplete, to avoid hitting the semi-valid states that refdbs produce by
 # design.
 class ProxyRefdbBackend(pygit2.RefdbBackend):
-    def __init__(testrepo, source):
-        testrepo.source = source
-
-    def exists(testrepo, ref):
-        return testrepo.source.exists(ref)
-
-    def lookup(testrepo, ref):
-        return testrepo.source.lookup(ref)
-
-    def write(testrepo, ref, force, who, message, old, old_target):
-        return testrepo.source.write(ref, force, who, message, old, old_target)
+    def __init__(self, source: pygit2.RefdbBackend) -> None:
+        self.source = source
 
-    def rename(testrepo, old_name, new_name, force, who, message):
-        return testrepo.source.rename(old_name, new_name, force, who, message)
+    def exists(self, ref: str) -> bool:
+        return self.source.exists(ref)
 
-    def delete(testrepo, ref_name, old_id, old_target):
-        return testrepo.source.delete(ref_name, old_id, old_target)
+    def lookup(self, ref: str) -> Reference:
+        return self.source.lookup(ref)
 
-    def compress(testrepo):
-        return testrepo.source.compress()
+    def write(
+        self,
+        ref: Reference,
+        force: bool,
+        who: Signature,
+        message: str,
+        old: None | str | Oid,
+        old_target: None | str,
+    ) -> None:
+        return self.source.write(ref, force, who, message, old, old_target)
+
+    def rename(
+        self, old_name: str, new_name: str, force: bool, who: Signature, message: str
+    ) -> Reference:
+        return self.source.rename(old_name, new_name, force, who, message)
+
+    def delete(self, ref_name: str, old_id: Oid | str, old_target: str | None) -> None:
+        return self.source.delete(ref_name, old_id, old_target)
+
+    def compress(self) -> None:
+        return self.source.compress()
 
-    def has_log(testrepo, ref_name):
-        return testrepo.source.has_log(ref_name)
+    def has_log(self, ref_name: str) -> bool:
+        return self.source.has_log(ref_name)
 
-    def ensure_log(testrepo, ref_name):
-        return testrepo.source.ensure_log(ref_name)
+    def ensure_log(self, ref_name: str) -> bool:
+        return self.source.ensure_log(ref_name)
 
-    def __iter__(testrepo):
-        return iter(testrepo.source)
+    def __iter__(self) -> Iterator[Reference]:
+        return iter(self.source)
 
 
 @pytest.fixture
-def repo(testrepo):
+def repo(testrepo: Repository) -> Generator[Repository, None, None]:
     testrepo.backend = ProxyRefdbBackend(pygit2.RefdbFsBackend(testrepo))
     yield testrepo
 
 
-def test_exists(repo):
+def test_exists(repo: Repository) -> None:
     assert not repo.backend.exists('refs/heads/does-not-exist')
     assert repo.backend.exists('refs/heads/master')
 
 
-def test_lookup(repo):
+def test_lookup(repo: Repository) -> None:
     assert repo.backend.lookup('refs/heads/does-not-exist') is None
     assert repo.backend.lookup('refs/heads/master').name == 'refs/heads/master'
 
 
-def test_write(repo):
+def test_write(repo: Repository) -> None:
     master = repo.backend.lookup('refs/heads/master')
-    commit = repo.get(master.target)
+    commit = repo[master.target]
     ref = pygit2.Reference('refs/heads/test-write', master.target, None)
     repo.backend.write(ref, False, commit.author, 'Create test-write', None, None)
     assert repo.backend.lookup('refs/heads/test-write').target == master.target
 
 
-def test_rename(repo):
+def test_rename(repo: Repository) -> None:
     old_ref = repo.backend.lookup('refs/heads/i18n')
     target = repo.get(old_ref.target)
+    assert isinstance(target, Commit)
     repo.backend.rename(
         'refs/heads/i18n', 'refs/heads/intl', False, target.committer, target.message
     )
     assert repo.backend.lookup('refs/heads/intl').target == target.id
 
 
-def test_delete(repo):
+def test_delete(repo: Repository) -> None:
     old = repo.backend.lookup('refs/heads/i18n')
     repo.backend.delete('refs/heads/i18n', old.target, None)
     assert not repo.backend.lookup('refs/heads/i18n')
 
 
-def test_compress(repo):
+def test_compress(repo: Repository) -> None:
     repo = repo
     packed_refs_file = Path(repo.path) / 'packed-refs'
     assert not packed_refs_file.exists()
@@ -115,12 +129,12 @@ def test_compress(repo):
     assert packed_refs_file.exists()
 
 
-def test_has_log(repo):
+def test_has_log(repo: Repository) -> None:
     assert repo.backend.has_log('refs/heads/master')
     assert not repo.backend.has_log('refs/heads/does-not-exist')
 
 
-def test_ensure_log(repo):
+def test_ensure_log(repo: Repository) -> None:
     assert not repo.backend.has_log('refs/heads/new-log')
     repo.backend.ensure_log('refs/heads/new-log')
     assert repo.backend.has_log('refs/heads/new-log')
diff -pruN 1.17.0-2/test/test_refs.py 1.18.2-1/test/test_refs.py
--- 1.17.0-2/test/test_refs.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_refs.py	2025-08-16 11:34:29.000000000 +0000
@@ -29,15 +29,24 @@ from pathlib import Path
 
 import pytest
 
-from pygit2 import Commit, Signature, Tree, reference_is_valid_name
-from pygit2 import AlreadyExistsError, GitError, InvalidSpecError
-from pygit2.enums import ReferenceType
-
+from pygit2 import (
+    AlreadyExistsError,
+    Commit,
+    GitError,
+    InvalidSpecError,
+    Oid,
+    Reference,
+    Repository,
+    Signature,
+    Tree,
+    reference_is_valid_name,
+)
+from pygit2.enums import ReferenceFilter, ReferenceType
 
 LAST_COMMIT = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'
 
 
-def test_refs_list_objects(testrepo):
+def test_refs_list_objects(testrepo: Repository) -> None:
     refs = [(ref.name, ref.target) for ref in testrepo.references.objects]
     assert sorted(refs) == [
         ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'),
@@ -45,7 +54,7 @@ def test_refs_list_objects(testrepo):
     ]
 
 
-def test_refs_list(testrepo):
+def test_refs_list(testrepo: Repository) -> None:
     # Without argument
     assert sorted(testrepo.references) == ['refs/heads/i18n', 'refs/heads/master']
 
@@ -58,13 +67,14 @@ def test_refs_list(testrepo):
     ]
 
 
-def test_head(testrepo):
+def test_head(testrepo: Repository) -> None:
     head = testrepo.head
     assert LAST_COMMIT == testrepo[head.target].id
+    assert not isinstance(head.raw_target, bytes)
     assert LAST_COMMIT == testrepo[head.raw_target].id
 
 
-def test_refs_getitem(testrepo):
+def test_refs_getitem(testrepo: Repository) -> None:
     refname = 'refs/foo'
     # Raise KeyError ?
     with pytest.raises(KeyError):
@@ -75,41 +85,48 @@ def test_refs_getitem(testrepo):
 
     # Test a lookup
     reference = testrepo.references.get('refs/heads/master')
+    assert reference is not None
     assert reference.name == 'refs/heads/master'
 
 
-def test_refs_get_sha(testrepo):
+def test_refs_get_sha(testrepo: Repository) -> None:
     reference = testrepo.references['refs/heads/master']
+    assert reference is not None
     assert reference.target == LAST_COMMIT
 
 
-def test_refs_set_sha(testrepo):
+def test_refs_set_sha(testrepo: Repository) -> None:
     NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533'
     reference = testrepo.references.get('refs/heads/master')
+    assert reference is not None
     reference.set_target(NEW_COMMIT)
     assert reference.target == NEW_COMMIT
 
 
-def test_refs_set_sha_prefix(testrepo):
+def test_refs_set_sha_prefix(testrepo: Repository) -> None:
     NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533'
     reference = testrepo.references.get('refs/heads/master')
+    assert reference is not None
     reference.set_target(NEW_COMMIT[0:6])
     assert reference.target == NEW_COMMIT
 
 
-def test_refs_get_type(testrepo):
+def test_refs_get_type(testrepo: Repository) -> None:
     reference = testrepo.references.get('refs/heads/master')
+    assert reference is not None
     assert reference.type == ReferenceType.DIRECT
 
 
-def test_refs_get_target(testrepo):
+def test_refs_get_target(testrepo: Repository) -> None:
     reference = testrepo.references.get('HEAD')
+    assert reference is not None
     assert reference.target == 'refs/heads/master'
     assert reference.raw_target == b'refs/heads/master'
 
 
-def test_refs_set_target(testrepo):
+def test_refs_set_target(testrepo: Repository) -> None:
     reference = testrepo.references.get('HEAD')
+    assert reference is not None
     assert reference.target == 'refs/heads/master'
     assert reference.raw_target == b'refs/heads/master'
     reference.set_target('refs/heads/i18n')
@@ -117,15 +134,17 @@ def test_refs_set_target(testrepo):
     assert reference.raw_target == b'refs/heads/i18n'
 
 
-def test_refs_get_shorthand(testrepo):
+def test_refs_get_shorthand(testrepo: Repository) -> None:
     reference = testrepo.references.get('refs/heads/master')
+    assert reference is not None
     assert reference.shorthand == 'master'
     reference = testrepo.references.create('refs/remotes/origin/master', LAST_COMMIT)
     assert reference.shorthand == 'origin/master'
 
 
-def test_refs_set_target_with_message(testrepo):
+def test_refs_set_target_with_message(testrepo: Repository) -> None:
     reference = testrepo.references.get('HEAD')
+    assert reference is not None
     assert reference.target == 'refs/heads/master'
     assert reference.raw_target == b'refs/heads/master'
     sig = Signature('foo', 'bar')
@@ -139,7 +158,7 @@ def test_refs_set_target_with_message(te
     assert first.committer == sig
 
 
-def test_refs_delete(testrepo):
+def test_refs_delete(testrepo: Repository) -> None:
     # We add a tag as a new reference that points to "origin/master"
     reference = testrepo.references.create('refs/tags/version1', LAST_COMMIT)
     assert 'refs/tags/version1' in testrepo.references
@@ -163,7 +182,7 @@ def test_refs_delete(testrepo):
         reference.rename('refs/tags/version2')
 
 
-def test_refs_rename(testrepo):
+def test_refs_rename(testrepo: Repository) -> None:
     # We add a tag as a new reference that points to "origin/master"
     reference = testrepo.references.create('refs/tags/version1', LAST_COMMIT)
     assert reference.name == 'refs/tags/version1'
@@ -177,7 +196,7 @@ def test_refs_rename(testrepo):
         reference.rename('b1')
 
 
-# def test_reload(testrepo):
+# def test_reload(testrepo: Repository) -> None:
 #    name = 'refs/tags/version1'
 #    ref = testrepo.create_reference(name, "refs/heads/master", symbolic=True)
 #    ref2 = testrepo.lookup_reference(name)
@@ -187,26 +206,31 @@ def test_refs_rename(testrepo):
 #    with pytest.raises(GitError): getattr(ref2, 'name')
 
 
-def test_refs_resolve(testrepo):
+def test_refs_resolve(testrepo: Repository) -> None:
     reference = testrepo.references.get('HEAD')
+    assert reference is not None
     assert reference.type == ReferenceType.SYMBOLIC
     reference = reference.resolve()
     assert reference.type == ReferenceType.DIRECT
     assert reference.target == LAST_COMMIT
 
 
-def test_refs_resolve_identity(testrepo):
+def test_refs_resolve_identity(testrepo: Repository) -> None:
     head = testrepo.references.get('HEAD')
+    assert head is not None
     ref = head.resolve()
     assert ref.resolve() is ref
 
 
-def test_refs_create(testrepo):
+def test_refs_create(testrepo: Repository) -> None:
     # We add a tag as a new reference that points to "origin/master"
-    reference = testrepo.references.create('refs/tags/version1', LAST_COMMIT)
+    reference: Reference | None = testrepo.references.create(
+        'refs/tags/version1', LAST_COMMIT
+    )
     refs = testrepo.references
     assert 'refs/tags/version1' in refs
     reference = testrepo.references.get('refs/tags/version1')
+    assert reference is not None
     assert reference.target == LAST_COMMIT
 
     # try to create existing reference
@@ -220,7 +244,7 @@ def test_refs_create(testrepo):
     assert reference.target == LAST_COMMIT
 
 
-def test_refs_create_symbolic(testrepo):
+def test_refs_create_symbolic(testrepo: Repository) -> None:
     # We add a tag as a new symbolic reference that always points to
     # "refs/heads/master"
     reference = testrepo.references.create('refs/tags/beta', 'refs/heads/master')
@@ -241,20 +265,22 @@ def test_refs_create_symbolic(testrepo):
     assert reference.raw_target == b'refs/heads/master'
 
 
-# def test_packall_references(testrepo):
+# def test_packall_references(testrepo: Repository) -> None:
 #    testrepo.packall_references()
 
 
-def test_refs_peel(testrepo):
+def test_refs_peel(testrepo: Repository) -> None:
     ref = testrepo.references.get('refs/heads/master')
+    assert ref is not None
     assert testrepo[ref.target].id == ref.peel().id
+    assert not isinstance(ref.raw_target, bytes)
     assert testrepo[ref.raw_target].id == ref.peel().id
 
     commit = ref.peel(Commit)
     assert commit.tree.id == ref.peel(Tree).id
 
 
-def test_refs_equality(testrepo):
+def test_refs_equality(testrepo: Repository) -> None:
     ref1 = testrepo.references.get('refs/heads/master')
     ref2 = testrepo.references.get('refs/heads/master')
     ref3 = testrepo.references.get('refs/heads/i18n')
@@ -267,7 +293,7 @@ def test_refs_equality(testrepo):
     assert not ref1 == ref3
 
 
-def test_refs_compress(testrepo):
+def test_refs_compress(testrepo: Repository) -> None:
     packed_refs_file = Path(testrepo.path) / 'packed-refs'
     assert not packed_refs_file.exists()
     old_refs = [(ref.name, ref.target) for ref in testrepo.references.objects]
@@ -283,7 +309,7 @@ def test_refs_compress(testrepo):
 #
 
 
-def test_list_all_reference_objects(testrepo):
+def test_list_all_reference_objects(testrepo: Repository) -> None:
     repo = testrepo
     refs = [(ref.name, ref.target) for ref in repo.listall_reference_objects()]
 
@@ -293,7 +319,7 @@ def test_list_all_reference_objects(test
     ]
 
 
-def test_list_all_references(testrepo):
+def test_list_all_references(testrepo: Repository) -> None:
     repo = testrepo
 
     # Without argument
@@ -317,14 +343,14 @@ def test_list_all_references(testrepo):
     ]
 
 
-def test_references_iterator_init(testrepo):
+def test_references_iterator_init(testrepo: Repository) -> None:
     repo = testrepo
     iter = repo.references_iterator_init()
 
     assert iter.__class__.__name__ == 'RefsIterator'
 
 
-def test_references_iterator_next(testrepo):
+def test_references_iterator_next(testrepo: Repository) -> None:
     repo = testrepo
     repo.create_reference(
         'refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'
@@ -350,7 +376,9 @@ def test_references_iterator_next(testre
     iter_branches = repo.references_iterator_init()
     all_branches = []
     for _ in range(4):
-        curr_ref = repo.references_iterator_next(iter_branches, 1)
+        curr_ref = repo.references_iterator_next(
+            iter_branches, ReferenceFilter.BRANCHES
+        )
         if curr_ref:
             all_branches.append((curr_ref.name, curr_ref.target))
 
@@ -362,7 +390,7 @@ def test_references_iterator_next(testre
     iter_tags = repo.references_iterator_init()
     all_tags = []
     for _ in range(4):
-        curr_ref = repo.references_iterator_next(iter_tags, 2)
+        curr_ref = repo.references_iterator_next(iter_tags, ReferenceFilter.TAGS)
         if curr_ref:
             all_tags.append((curr_ref.name, curr_ref.target))
 
@@ -372,7 +400,7 @@ def test_references_iterator_next(testre
     ]
 
 
-def test_references_iterator_next_python(testrepo):
+def test_references_iterator_next_python(testrepo: Repository) -> None:
     repo = testrepo
     repo.create_reference(
         'refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'
@@ -389,41 +417,43 @@ def test_references_iterator_next_python
         ('refs/tags/version2', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'),
     ]
 
-    branches = [(x.name, x.target) for x in repo.references.iterator(1)]
+    branches = [
+        (x.name, x.target) for x in repo.references.iterator(ReferenceFilter.BRANCHES)
+    ]
     assert sorted(branches) == [
         ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'),
         ('refs/heads/master', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'),
     ]
 
-    tags = [(x.name, x.target) for x in repo.references.iterator(2)]
+    tags = [(x.name, x.target) for x in repo.references.iterator(ReferenceFilter.TAGS)]
     assert sorted(tags) == [
         ('refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'),
         ('refs/tags/version2', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'),
     ]
 
 
-def test_references_iterator_invalid_filter(testrepo):
+def test_references_iterator_invalid_filter(testrepo: Repository) -> None:
     repo = testrepo
     iter_all = repo.references_iterator_init()
 
     all_refs = []
     for _ in range(4):
-        curr_ref = repo.references_iterator_next(iter_all, 5)
+        curr_ref = repo.references_iterator_next(iter_all, 5)  # type: ignore
         if curr_ref:
             all_refs.append((curr_ref.name, curr_ref.target))
 
     assert all_refs == []
 
 
-def test_references_iterator_invalid_filter_python(testrepo):
+def test_references_iterator_invalid_filter_python(testrepo: Repository) -> None:
     repo = testrepo
     refs = []
     with pytest.raises(ValueError):
-        for ref in repo.references.iterator(5):
+        for ref in repo.references.iterator(5):  # type: ignore
             refs.append((ref.name, ref.target))
 
 
-def test_lookup_reference(testrepo):
+def test_lookup_reference(testrepo: Repository) -> None:
     repo = testrepo
 
     # Raise KeyError ?
@@ -435,7 +465,7 @@ def test_lookup_reference(testrepo):
     assert reference.name == 'refs/heads/master'
 
 
-def test_lookup_reference_dwim(testrepo):
+def test_lookup_reference_dwim(testrepo: Repository) -> None:
     repo = testrepo
 
     # remote ref
@@ -465,7 +495,7 @@ def test_lookup_reference_dwim(testrepo)
     assert reference.name == 'refs/tags/version1'
 
 
-def test_resolve_refish(testrepo):
+def test_resolve_refish(testrepo: Repository) -> None:
     repo = testrepo
 
     # remote ref
@@ -507,37 +537,37 @@ def test_resolve_refish(testrepo):
     assert commit.id == '5ebeeebb320790caf276b9fc8b24546d63316533'
 
 
-def test_reference_get_sha(testrepo):
+def test_reference_get_sha(testrepo: Repository) -> None:
     reference = testrepo.lookup_reference('refs/heads/master')
     assert reference.target == LAST_COMMIT
 
 
-def test_reference_set_sha(testrepo):
+def test_reference_set_sha(testrepo: Repository) -> None:
     NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533'
     reference = testrepo.lookup_reference('refs/heads/master')
     reference.set_target(NEW_COMMIT)
     assert reference.target == NEW_COMMIT
 
 
-def test_reference_set_sha_prefix(testrepo):
+def test_reference_set_sha_prefix(testrepo: Repository) -> None:
     NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533'
     reference = testrepo.lookup_reference('refs/heads/master')
     reference.set_target(NEW_COMMIT[0:6])
     assert reference.target == NEW_COMMIT
 
 
-def test_reference_get_type(testrepo):
+def test_reference_get_type(testrepo: Repository) -> None:
     reference = testrepo.lookup_reference('refs/heads/master')
     assert reference.type == ReferenceType.DIRECT
 
 
-def test_get_target(testrepo):
+def test_get_target(testrepo: Repository) -> None:
     reference = testrepo.lookup_reference('HEAD')
     assert reference.target == 'refs/heads/master'
     assert reference.raw_target == b'refs/heads/master'
 
 
-def test_set_target(testrepo):
+def test_set_target(testrepo: Repository) -> None:
     reference = testrepo.lookup_reference('HEAD')
     assert reference.target == 'refs/heads/master'
     assert reference.raw_target == b'refs/heads/master'
@@ -546,14 +576,14 @@ def test_set_target(testrepo):
     assert reference.raw_target == b'refs/heads/i18n'
 
 
-def test_get_shorthand(testrepo):
+def test_get_shorthand(testrepo: Repository) -> None:
     reference = testrepo.lookup_reference('refs/heads/master')
     assert reference.shorthand == 'master'
     reference = testrepo.create_reference('refs/remotes/origin/master', LAST_COMMIT)
     assert reference.shorthand == 'origin/master'
 
 
-def test_set_target_with_message(testrepo):
+def test_set_target_with_message(testrepo: Repository) -> None:
     reference = testrepo.lookup_reference('HEAD')
     assert reference.target == 'refs/heads/master'
     assert reference.raw_target == b'refs/heads/master'
@@ -568,7 +598,7 @@ def test_set_target_with_message(testrep
     assert first.committer == sig
 
 
-def test_delete(testrepo):
+def test_delete(testrepo: Repository) -> None:
     repo = testrepo
 
     # We add a tag as a new reference that points to "origin/master"
@@ -596,7 +626,7 @@ def test_delete(testrepo):
         reference.rename('refs/tags/version2')
 
 
-def test_rename(testrepo):
+def test_rename(testrepo: Repository) -> None:
     # We add a tag as a new reference that points to "origin/master"
     reference = testrepo.create_reference('refs/tags/version1', LAST_COMMIT)
     assert reference.name == 'refs/tags/version1'
@@ -604,7 +634,7 @@ def test_rename(testrepo):
     assert reference.name == 'refs/tags/version2'
 
 
-#   def test_reload(testrepo):
+#   def test_reload(testrepo: Repository) -> None:
 #       name = 'refs/tags/version1'
 
 #       repo = testrepo
@@ -616,7 +646,7 @@ def test_rename(testrepo):
 #       with pytest.raises(GitError): getattr(ref2, 'name')
 
 
-def test_reference_resolve(testrepo):
+def test_reference_resolve(testrepo: Repository) -> None:
     reference = testrepo.lookup_reference('HEAD')
     assert reference.type == ReferenceType.SYMBOLIC
     reference = reference.resolve()
@@ -624,13 +654,13 @@ def test_reference_resolve(testrepo):
     assert reference.target == LAST_COMMIT
 
 
-def test_reference_resolve_identity(testrepo):
+def test_reference_resolve_identity(testrepo: Repository) -> None:
     head = testrepo.lookup_reference('HEAD')
     ref = head.resolve()
     assert ref.resolve() is ref
 
 
-def test_create_reference(testrepo):
+def test_create_reference(testrepo: Repository) -> None:
     # We add a tag as a new reference that points to "origin/master"
     reference = testrepo.create_reference('refs/tags/version1', LAST_COMMIT)
     assert 'refs/tags/version1' in testrepo.listall_references()
@@ -651,7 +681,7 @@ def test_create_reference(testrepo):
     assert reference.target == LAST_COMMIT
 
 
-def test_create_reference_with_message(testrepo):
+def test_create_reference_with_message(testrepo: Repository) -> None:
     sig = Signature('foo', 'bar')
     testrepo.set_ident('foo', 'bar')
     msg = 'Hello log'
@@ -663,7 +693,7 @@ def test_create_reference_with_message(t
     assert first.committer == sig
 
 
-def test_create_symbolic_reference(testrepo):
+def test_create_symbolic_reference(testrepo: Repository) -> None:
     repo = testrepo
     # We add a tag as a new symbolic reference that always points to
     # "refs/heads/master"
@@ -684,7 +714,7 @@ def test_create_symbolic_reference(testr
     assert reference.raw_target == b'refs/heads/master'
 
 
-def test_create_symbolic_reference_with_message(testrepo):
+def test_create_symbolic_reference_with_message(testrepo: Repository) -> None:
     sig = Signature('foo', 'bar')
     testrepo.set_ident('foo', 'bar')
     msg = 'Hello log'
@@ -696,7 +726,7 @@ def test_create_symbolic_reference_with_
     assert first.committer == sig
 
 
-def test_create_invalid_reference(testrepo):
+def test_create_invalid_reference(testrepo: Repository) -> None:
     repo = testrepo
 
     # try to create a reference with an invalid name
@@ -705,21 +735,22 @@ def test_create_invalid_reference(testre
     assert isinstance(error.value, ValueError)
 
 
-#   def test_packall_references(testrepo):
+#   def test_packall_references(testrepo: Repository) -> None:
 #       testrepo.packall_references()
 
 
-def test_peel(testrepo):
+def test_peel(testrepo: Repository) -> None:
     repo = testrepo
     ref = repo.lookup_reference('refs/heads/master')
     assert repo[ref.target].id == ref.peel().id
+    assert isinstance(ref.raw_target, Oid)
     assert repo[ref.raw_target].id == ref.peel().id
 
     commit = ref.peel(Commit)
     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')
@@ -727,12 +758,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')
@@ -747,12 +778,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 -pruN 1.17.0-2/test/test_remote.py 1.18.2-1/test/test_remote.py
--- 1.17.0-2/test/test_remote.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_remote.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,18 +23,17 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-"""Tests for Remote objects."""
-
-from unittest.mock import patch
 import sys
+from collections.abc import Generator
+from pathlib import Path
 
 import pytest
 
 import pygit2
-from pygit2 import Oid
-from pygit2.ffi import ffi
-from . import utils
+from pygit2 import Remote, Repository
+from pygit2.remotes import PushUpdate, TransferProgress
 
+from . import utils
 
 REMOTE_NAME = 'origin'
 REMOTE_URL = 'https://github.com/libgit2/pygit2.git'
@@ -48,13 +47,13 @@ REMOTE_REPO_FETCH_HEAD_COMMIT_OBJECTS =
 ORIGIN_REFSPEC = '+refs/heads/*:refs/remotes/origin/*'
 
 
-def test_remote_create(testrepo):
+def test_remote_create(testrepo: Repository) -> None:
     name = 'upstream'
     url = 'https://github.com/libgit2/pygit2.git'
 
     remote = testrepo.remotes.create(name, url)
 
-    assert type(remote) == pygit2.Remote
+    assert type(remote) is pygit2.Remote
     assert name == remote.name
     assert url == remote.url
     assert remote.push_url is None
@@ -63,21 +62,21 @@ def test_remote_create(testrepo):
         testrepo.remotes.create(*(name, url))
 
 
-def test_remote_create_with_refspec(testrepo):
+def test_remote_create_with_refspec(testrepo: Repository) -> None:
     name = 'upstream'
     url = 'https://github.com/libgit2/pygit2.git'
     fetch = '+refs/*:refs/*'
 
     remote = testrepo.remotes.create(name, url, fetch)
 
-    assert type(remote) == pygit2.Remote
+    assert type(remote) is pygit2.Remote
     assert name == remote.name
     assert url == remote.url
     assert [fetch] == remote.fetch_refspecs
     assert remote.push_url is None
 
 
-def test_remote_create_anonymous(testrepo):
+def test_remote_create_anonymous(testrepo: Repository) -> None:
     url = 'https://github.com/libgit2/pygit2.git'
 
     remote = testrepo.remotes.create_anonymous(url)
@@ -88,7 +87,7 @@ def test_remote_create_anonymous(testrep
     assert [] == remote.push_refspecs
 
 
-def test_remote_delete(testrepo):
+def test_remote_delete(testrepo: Repository) -> None:
     name = 'upstream'
     url = 'https://github.com/libgit2/pygit2.git'
 
@@ -101,7 +100,7 @@ def test_remote_delete(testrepo):
     assert 1 == len(testrepo.remotes)
 
 
-def test_remote_rename(testrepo):
+def test_remote_rename(testrepo: Repository) -> None:
     remote = testrepo.remotes[0]
 
     assert REMOTE_NAME == remote.name
@@ -112,10 +111,10 @@ def test_remote_rename(testrepo):
     with pytest.raises(ValueError):
         testrepo.remotes.rename('', '')
     with pytest.raises(ValueError):
-        testrepo.remotes.rename(None, None)
+        testrepo.remotes.rename(None, None)  # type: ignore
 
 
-def test_remote_set_url(testrepo):
+def test_remote_set_url(testrepo: Repository) -> None:
     remote = testrepo.remotes['origin']
     assert REMOTE_URL == remote.url
 
@@ -134,7 +133,7 @@ def test_remote_set_url(testrepo):
         testrepo.remotes.set_push_url('origin', '')
 
 
-def test_refspec(testrepo):
+def test_refspec(testrepo: Repository) -> None:
     remote = testrepo.remotes['origin']
 
     assert remote.refspec_count == 1
@@ -144,7 +143,7 @@ def test_refspec(testrepo):
     assert refspec.force is True
     assert ORIGIN_REFSPEC == refspec.string
 
-    assert list == type(remote.fetch_refspecs)
+    assert list is type(remote.fetch_refspecs)
     assert 1 == len(remote.fetch_refspecs)
     assert ORIGIN_REFSPEC == remote.fetch_refspecs[0]
 
@@ -153,18 +152,18 @@ def test_refspec(testrepo):
     assert 'refs/remotes/origin/master' == refspec.transform('refs/heads/master')
     assert 'refs/heads/master' == refspec.rtransform('refs/remotes/origin/master')
 
-    assert list == type(remote.push_refspecs)
+    assert list is type(remote.push_refspecs)
     assert 0 == len(remote.push_refspecs)
 
     push_specs = remote.push_refspecs
-    assert list == type(push_specs)
+    assert list is type(push_specs)
     assert 0 == len(push_specs)
 
     testrepo.remotes.add_fetch('origin', '+refs/test/*:refs/test/remotes/*')
     remote = testrepo.remotes['origin']
 
     fetch_specs = remote.fetch_refspecs
-    assert list == type(fetch_specs)
+    assert list is type(fetch_specs)
     assert 2 == len(fetch_specs)
     assert [
         '+refs/heads/*:refs/remotes/origin/*',
@@ -174,13 +173,13 @@ def test_refspec(testrepo):
     testrepo.remotes.add_push('origin', '+refs/test/*:refs/test/remotes/*')
 
     with pytest.raises(TypeError):
-        testrepo.remotes.add_fetch(['+refs/*:refs/*', 5])
+        testrepo.remotes.add_fetch(['+refs/*:refs/*', 5])  # type: ignore
 
     remote = testrepo.remotes['origin']
     assert ['+refs/test/*:refs/test/remotes/*'] == remote.push_refspecs
 
 
-def test_remote_list(testrepo):
+def test_remote_list(testrepo: Repository) -> None:
     assert 1 == len(testrepo.remotes)
     remote = testrepo.remotes[0]
     assert REMOTE_NAME == remote.name
@@ -194,18 +193,59 @@ def test_remote_list(testrepo):
 
 
 @utils.requires_network
-def test_ls_remotes(testrepo):
+def test_list_heads(testrepo: Repository) -> None:
+    assert 1 == len(testrepo.remotes)
+    remote = testrepo.remotes[0]
+
+    refs = remote.list_heads()
+    assert refs
+
+    # Check that a known ref is returned.
+    assert next(iter(r for r in refs if r.name == 'refs/tags/v0.28.2'))
+
+
+@utils.requires_network
+def test_ls_remotes_deprecated(testrepo: Repository) -> None:
+    assert 1 == len(testrepo.remotes)
+    remote = testrepo.remotes[0]
+
+    new_refs = remote.list_heads()
+
+    with pytest.warns(DeprecationWarning, match='Use list_heads'):
+        old_refs = remote.ls_remotes()
+
+    assert new_refs
+    assert old_refs
+
+    for new, old in zip(new_refs, old_refs, strict=True):
+        assert new.name == old['name']
+        assert new.oid == old['oid']
+        assert new.local == old['local']
+        assert new.symref_target == old['symref_target']
+        if new.local:
+            assert new.loid == old['loid']
+        else:
+            assert new.loid == pygit2.Oid(b'')
+            assert old['loid'] is None
+
+
+@utils.requires_network
+def test_list_heads_without_implicit_connect(testrepo: Repository) -> None:
     assert 1 == len(testrepo.remotes)
     remote = testrepo.remotes[0]
 
-    refs = remote.ls_remotes()
+    with pytest.raises(pygit2.GitError, match='this remote has never connected'):
+        remote.list_heads(connect=False)
+
+    remote.connect()
+    refs = remote.list_heads(connect=False)
     assert refs
 
     # Check that a known ref is returned.
-    assert next(iter(r for r in refs if r['name'] == 'refs/tags/v0.28.2'))
+    assert next(iter(r for r in refs if r.name == 'refs/tags/v0.28.2'))
 
 
-def test_remote_collection(testrepo):
+def test_remote_collection(testrepo: Repository) -> None:
     remote = testrepo.remotes['origin']
     assert REMOTE_NAME == remote.name
     assert REMOTE_URL == remote.url
@@ -220,8 +260,8 @@ def test_remote_collection(testrepo):
     assert remote.name in [x.name for x in testrepo.remotes]
 
 
-@utils.refcount
-def test_remote_refcount(testrepo):
+@utils.requires_refcount
+def test_remote_refcount(testrepo: Repository) -> None:
     start = sys.getrefcount(testrepo)
     remote = testrepo.remotes[0]
     del remote
@@ -229,7 +269,7 @@ def test_remote_refcount(testrepo):
     assert start == end
 
 
-def test_fetch(emptyrepo):
+def test_fetch(emptyrepo: Repository) -> None:
     remote = emptyrepo.remotes[0]
     stats = remote.fetch()
     assert stats.received_bytes > 2700
@@ -239,7 +279,7 @@ def test_fetch(emptyrepo):
 
 
 @utils.requires_network
-def test_fetch_depth_zero(testrepo):
+def test_fetch_depth_zero(testrepo: Repository) -> None:
     remote = testrepo.remotes[0]
     stats = remote.fetch(REMOTE_FETCHTEST_FETCHSPECS, depth=0)
     assert stats.indexed_objects == REMOTE_REPO_FETCH_ALL_OBJECTS
@@ -247,17 +287,17 @@ def test_fetch_depth_zero(testrepo):
 
 
 @utils.requires_network
-def test_fetch_depth_one(testrepo):
+def test_fetch_depth_one(testrepo: Repository) -> None:
     remote = testrepo.remotes[0]
     stats = remote.fetch(REMOTE_FETCHTEST_FETCHSPECS, depth=1)
     assert stats.indexed_objects == REMOTE_REPO_FETCH_HEAD_COMMIT_OBJECTS
     assert stats.received_objects == REMOTE_REPO_FETCH_HEAD_COMMIT_OBJECTS
 
 
-def test_transfer_progress(emptyrepo):
+def test_transfer_progress(emptyrepo: Repository) -> None:
     class MyCallbacks(pygit2.RemoteCallbacks):
-        def transfer_progress(emptyrepo, stats):
-            emptyrepo.tp = stats
+        def transfer_progress(self, stats: TransferProgress) -> None:
+            self.tp = stats
 
     callbacks = MyCallbacks()
     remote = emptyrepo.remotes[0]
@@ -267,27 +307,29 @@ def test_transfer_progress(emptyrepo):
     assert stats.received_objects == callbacks.tp.received_objects
 
 
-def test_update_tips(emptyrepo):
+def test_update_tips(emptyrepo: Repository) -> None:
     remote = emptyrepo.remotes[0]
     tips = [
         (
             'refs/remotes/origin/master',
-            Oid(hex='0' * 40),
-            Oid(hex='784855caf26449a1914d2cf62d12b9374d76ae78'),
+            pygit2.Oid(hex='0' * 40),
+            pygit2.Oid(hex='784855caf26449a1914d2cf62d12b9374d76ae78'),
         ),
         (
             'refs/tags/root',
-            Oid(hex='0' * 40),
-            Oid(hex='3d2962987c695a29f1f80b6c3aa4ec046ef44369'),
+            pygit2.Oid(hex='0' * 40),
+            pygit2.Oid(hex='3d2962987c695a29f1f80b6c3aa4ec046ef44369'),
         ),
     ]
 
     class MyCallbacks(pygit2.RemoteCallbacks):
-        def __init__(self, tips):
+        tips: list[tuple[str, pygit2.Oid, pygit2.Oid]]
+
+        def __init__(self, tips: list[tuple[str, pygit2.Oid, pygit2.Oid]]) -> None:
             self.tips = tips
             self.i = 0
 
-        def update_tips(self, name, old, new):
+        def update_tips(self, name: str, old: pygit2.Oid, new: pygit2.Oid) -> None:
             assert self.tips[self.i] == (name, old, new)
             self.i += 1
 
@@ -297,14 +339,16 @@ def test_update_tips(emptyrepo):
 
 
 @utils.requires_network
-def test_ls_remotes_certificate_check():
+def test_list_heads_certificate_check() -> None:
     url = 'https://github.com/pygit2/empty.git'
 
     class MyCallbacks(pygit2.RemoteCallbacks):
-        def __init__(self):
+        def __init__(self) -> None:
             self.i = 0
 
-        def certificate_check(self, certificate, valid, host):
+        def certificate_check(
+            self, certificate: None, valid: bool, host: str | bytes
+        ) -> bool:
             self.i += 1
 
             assert certificate is None
@@ -317,7 +361,7 @@ def test_ls_remotes_certificate_check():
     remote = git.remotes.create_anonymous(url)
 
     callbacks = MyCallbacks()
-    refs = remote.ls_remotes(callbacks=callbacks)
+    refs = remote.list_heads(callbacks=callbacks)
 
     # Sanity check that we indeed got some refs.
     assert len(refs) > 0
@@ -327,13 +371,13 @@ def test_ls_remotes_certificate_check():
 
 
 @pytest.fixture
-def origin(tmp_path):
+def origin(tmp_path: Path) -> Generator[Repository, None, None]:
     with utils.TemporaryRepository('barerepo.zip', tmp_path) as path:
         yield pygit2.Repository(path)
 
 
 @pytest.fixture
-def clone(tmp_path):
+def clone(tmp_path: Path) -> Generator[Repository, None, None]:
     clone = tmp_path / 'clone'
     clone.mkdir()
     with utils.TemporaryRepository('barerepo.zip', clone) as path:
@@ -341,11 +385,13 @@ def clone(tmp_path):
 
 
 @pytest.fixture
-def remote(origin, clone):
+def remote(origin: Repository, clone: Repository) -> Generator[Remote, None, None]:
     yield clone.remotes.create('origin', origin.path)
 
 
-def test_push_fast_forward_commits_to_remote_succeeds(origin, clone, remote):
+def test_push_fast_forward_commits_to_remote_succeeds(
+    origin: Repository, clone: Repository, remote: Remote
+) -> None:
     tip = clone[clone.head.target]
     oid = clone.create_commit(
         'refs/heads/master',
@@ -359,14 +405,87 @@ def test_push_fast_forward_commits_to_re
     assert origin[origin.head.target].id == oid
 
 
-def test_push_when_up_to_date_succeeds(origin, clone, remote):
+def test_push_when_up_to_date_succeeds(
+    origin: Repository, clone: Repository, remote: Remote
+) -> None:
     remote.push(['refs/heads/master'])
     origin_tip = origin[origin.head.target].id
     clone_tip = clone[clone.head.target].id
     assert origin_tip == clone_tip
 
 
-def test_push_non_fast_forward_commits_to_remote_fails(origin, clone, remote):
+def test_push_transfer_progress(
+    origin: Repository, clone: Repository, remote: Remote
+) -> None:
+    tip = clone[clone.head.target]
+    new_tip_id = clone.create_commit(
+        'refs/heads/master',
+        tip.author,
+        tip.author,
+        'empty commit',
+        tip.tree.id,
+        [tip.id],
+    )
+
+    # NOTE: We're currently not testing bytes_pushed due to a bug in libgit2
+    # 1.9.0: it passes a junk value for bytes_pushed when pushing to a remote
+    # on the local filesystem, as is the case in this unit test. (When pushing
+    # to a remote over the network, the value is correct.)
+    class MyCallbacks(pygit2.RemoteCallbacks):
+        def push_transfer_progress(
+            self, objects_pushed: int, total_objects: int, bytes_pushed: int
+        ) -> None:
+            self.objects_pushed = objects_pushed
+            self.total_objects = total_objects
+
+    assert origin.branches['master'].target == tip.id
+
+    callbacks = MyCallbacks()
+    remote.push(['refs/heads/master'], callbacks=callbacks)
+    assert callbacks.objects_pushed == 1
+    assert callbacks.total_objects == 1
+    assert origin.branches['master'].target == new_tip_id
+
+
+@pytest.mark.parametrize('reject_from', ['push_transfer_progress', 'push_negotiation'])
+def test_push_interrupted_from_callbacks(
+    origin: Repository, clone: Repository, remote: Remote, reject_from: str
+) -> None:
+    reject_message = 'retreat! retreat!'
+
+    tip = clone[clone.head.target]
+    clone.create_commit(
+        'refs/heads/master',
+        tip.author,
+        tip.author,
+        'empty commit',
+        tip.tree.id,
+        [tip.id],
+    )
+
+    class MyCallbacks(pygit2.RemoteCallbacks):
+        def push_negotiation(self, updates: list[PushUpdate]) -> None:
+            if reject_from == 'push_negotiation':
+                raise InterruptedError(reject_message)
+
+        def push_transfer_progress(
+            self, objects_pushed: int, total_objects: int, bytes_pushed: int
+        ) -> None:
+            if reject_from == 'push_transfer_progress':
+                raise InterruptedError(reject_message)
+
+    assert origin.branches['master'].target == tip.id
+
+    callbacks = MyCallbacks()
+    with pytest.raises(InterruptedError, match='retreat! retreat!'):
+        remote.push(['refs/heads/master'], callbacks=callbacks)
+
+    assert origin.branches['master'].target == tip.id
+
+
+def test_push_non_fast_forward_commits_to_remote_fails(
+    origin: Repository, clone: Repository, remote: Remote
+) -> None:
     tip = origin[origin.head.target]
     origin.create_commit(
         'refs/heads/master',
@@ -390,22 +509,81 @@ def test_push_non_fast_forward_commits_t
         remote.push(['refs/heads/master'])
 
 
-@patch.object(pygit2.callbacks, 'RemoteCallbacks')
-def test_push_options(mock_callbacks, origin, clone, remote):
-    remote.push(['refs/heads/master'])
-    remote_push_options = mock_callbacks.return_value.push_options.remote_push_options
+def test_push_options(origin: Repository, clone: Repository, remote: Remote) -> None:
+    from pygit2 import RemoteCallbacks
+
+    callbacks = RemoteCallbacks()
+    remote.push(['refs/heads/master'], callbacks)
+    remote_push_options = callbacks.push_options.remote_push_options
     assert remote_push_options.count == 0
 
-    remote.push(['refs/heads/master'], push_options=[])
-    remote_push_options = mock_callbacks.return_value.push_options.remote_push_options
+    callbacks = RemoteCallbacks()
+    remote.push(['refs/heads/master'], callbacks, push_options=[])
+    remote_push_options = callbacks.push_options.remote_push_options
     assert remote_push_options.count == 0
 
-    remote.push(['refs/heads/master'], push_options=['foo'])
-    remote_push_options = mock_callbacks.return_value.push_options.remote_push_options
+    callbacks = RemoteCallbacks()
+    # Local remotes don't support push_options, so pushing will raise an error.
+    # However, push_options should still be set in RemoteCallbacks.
+    with pytest.raises(pygit2.GitError, match='push-options not supported by remote'):
+        remote.push(['refs/heads/master'], callbacks, push_options=['foo'])
+    remote_push_options = callbacks.push_options.remote_push_options
     assert remote_push_options.count == 1
     # strings pointed to by remote_push_options.strings[] are already freed
 
-    remote.push(['refs/heads/master'], push_options=['Option A', 'Option B'])
-    remote_push_options = mock_callbacks.return_value.push_options.remote_push_options
+    callbacks = RemoteCallbacks()
+    with pytest.raises(pygit2.GitError, match='push-options not supported by remote'):
+        remote.push(['refs/heads/master'], callbacks, push_options=['Opt A', 'Opt B'])
+    remote_push_options = callbacks.push_options.remote_push_options
     assert remote_push_options.count == 2
     # strings pointed to by remote_push_options.strings[] are already freed
+
+
+def test_push_threads(origin: Repository, clone: Repository, remote: Remote) -> None:
+    from pygit2 import RemoteCallbacks
+
+    callbacks = RemoteCallbacks()
+    remote.push(['refs/heads/master'], callbacks)
+    assert callbacks.push_options.pb_parallelism == 1
+
+    callbacks = RemoteCallbacks()
+    remote.push(['refs/heads/master'], callbacks, threads=0)
+    assert callbacks.push_options.pb_parallelism == 0
+
+    callbacks = RemoteCallbacks()
+    remote.push(['refs/heads/master'], callbacks, threads=1)
+    assert callbacks.push_options.pb_parallelism == 1
+
+
+def test_push_negotiation(
+    origin: Repository, clone: Repository, remote: Remote
+) -> None:
+    old_tip = clone[clone.head.target]
+    new_tip_id = clone.create_commit(
+        'refs/heads/master',
+        old_tip.author,
+        old_tip.author,
+        'empty commit',
+        old_tip.tree.id,
+        [old_tip.id],
+    )
+
+    the_updates: list[PushUpdate] = []
+
+    class MyCallbacks(pygit2.RemoteCallbacks):
+        def push_negotiation(self, updates: list[PushUpdate]) -> None:
+            the_updates.extend(updates)
+
+    assert origin.branches['master'].target == old_tip.id
+    assert 'new_branch' not in origin.branches
+
+    callbacks = MyCallbacks()
+    remote.push(['refs/heads/master'], callbacks=callbacks)
+
+    assert len(the_updates) == 1
+    assert the_updates[0].src_refname == 'refs/heads/master'
+    assert the_updates[0].dst_refname == 'refs/heads/master'
+    assert the_updates[0].src == old_tip.id
+    assert the_updates[0].dst == new_tip_id
+
+    assert origin.branches['master'].target == new_tip_id
diff -pruN 1.17.0-2/test/test_remote_prune.py 1.18.2-1/test/test_remote_prune.py
--- 1.17.0-2/test/test_remote_prune.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_remote_prune.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,14 +23,20 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from collections.abc import Generator
+from pathlib import Path
+
 import pytest
 
 import pygit2
+from pygit2 import Oid, Repository
 from pygit2.enums import FetchPrune
 
 
 @pytest.fixture
-def clonerepo(testrepo, tmp_path):
+def clonerepo(
+    testrepo: Repository, tmp_path: Path
+) -> Generator[Repository, None, None]:
     cloned_repo_path = tmp_path / 'test_remote_prune'
 
     pygit2.clone_repository(testrepo.workdir, cloned_repo_path)
@@ -39,26 +45,26 @@ def clonerepo(testrepo, tmp_path):
     yield clonerepo
 
 
-def test_fetch_remote_default(clonerepo):
+def test_fetch_remote_default(clonerepo: Repository) -> None:
     clonerepo.remotes[0].fetch()
     assert 'origin/i18n' in clonerepo.branches
 
 
-def test_fetch_remote_prune(clonerepo):
+def test_fetch_remote_prune(clonerepo: Repository) -> None:
     clonerepo.remotes[0].fetch(prune=FetchPrune.PRUNE)
     assert 'origin/i18n' not in clonerepo.branches
 
 
-def test_fetch_no_prune(clonerepo):
+def test_fetch_no_prune(clonerepo: Repository) -> None:
     clonerepo.remotes[0].fetch(prune=FetchPrune.NO_PRUNE)
     assert 'origin/i18n' in clonerepo.branches
 
 
-def test_remote_prune(clonerepo):
+def test_remote_prune(clonerepo: Repository) -> None:
     pruned = []
 
     class MyCallbacks(pygit2.RemoteCallbacks):
-        def update_tips(self, name, old, new):
+        def update_tips(self, name: str, old: Oid, new: Oid) -> None:
             pruned.append(name)
 
     callbacks = MyCallbacks()
diff -pruN 1.17.0-2/test/test_remote_utf8.py 1.18.2-1/test/test_remote_utf8.py
--- 1.17.0-2/test/test_remote_utf8.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_remote_utf8.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,17 +23,22 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-import pygit2
+from collections.abc import Generator
+from pathlib import Path
+
 import pytest
+
+import pygit2
+
 from . import utils
 
 
 @pytest.fixture
-def repo(tmp_path):
+def repo(tmp_path: Path) -> Generator[pygit2.Repository, None, None]:
     with utils.TemporaryRepository('utf8branchrepo.zip', tmp_path) as path:
         yield pygit2.Repository(path)
 
 
-def test_fetch(repo):
+def test_fetch(repo: pygit2.Repository) -> None:
     remote = repo.remotes.create('origin', repo.workdir)
     remote.fetch()
diff -pruN 1.17.0-2/test/test_repository.py 1.18.2-1/test/test_repository.py
--- 1.17.0-2/test/test_repository.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_repository.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,19 +23,34 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-from pathlib import Path
 import shutil
 import tempfile
+from pathlib import Path
+from typing import Optional
 
 import pytest
 
 # pygit2
 import pygit2
-from pygit2 import init_repository, clone_repository, discover_repository
-from pygit2 import Oid
+from pygit2 import (
+    Blob,
+    Commit,
+    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,
     RepositoryOpenFlag,
@@ -43,34 +58,36 @@ from pygit2.enums import (
     ResetMode,
     StashApplyProgress,
 )
+from pygit2.index import MergeFileResult
+
 from . import utils
 
 
-def test_is_empty(testrepo):
+def test_is_empty(testrepo: Repository) -> None:
     assert not testrepo.is_empty
 
 
-def test_is_bare(testrepo):
+def test_is_bare(testrepo: Repository) -> None:
     assert not testrepo.is_bare
 
 
-def test_get_path(testrepo_path):
+def test_get_path(testrepo_path: tuple[Repository, Path]) -> None:
     testrepo, path = testrepo_path
     assert Path(testrepo.path).resolve() == (path / '.git').resolve()
 
 
-def test_get_workdir(testrepo_path):
+def test_get_workdir(testrepo_path: tuple[Repository, Path]) -> None:
     testrepo, path = testrepo_path
     assert Path(testrepo.workdir).resolve() == path.resolve()
 
 
-def test_set_workdir(testrepo):
+def test_set_workdir(testrepo: Repository) -> None:
     directory = tempfile.mkdtemp()
     testrepo.workdir = directory
     assert Path(testrepo.workdir).resolve() == Path(directory).resolve()
 
 
-def test_checkout_ref(testrepo):
+def test_checkout_ref(testrepo: Repository) -> None:
     ref_i18n = testrepo.lookup_reference('refs/heads/i18n')
 
     # checkout i18n with conflicts and default strategy should
@@ -79,77 +96,104 @@ def test_checkout_ref(testrepo):
         testrepo.checkout(ref_i18n)
 
     # checkout i18n with GIT_CHECKOUT_FORCE
-    head = testrepo.head
-    head = testrepo[head.target]
+    head_object = testrepo.head
+    head = testrepo[head_object.target]
     assert 'new' not in head.tree
     testrepo.checkout(ref_i18n, strategy=CheckoutStrategy.FORCE)
 
-    head = testrepo.head
-    head = testrepo[head.target]
+    head_object = testrepo.head
+    head = testrepo[head_object.target]
     assert head.id == ref_i18n.target
     assert 'new' in head.tree
     assert 'bye.txt' not in testrepo.status()
 
 
-def test_checkout_callbacks(testrepo):
+def test_checkout_callbacks(testrepo: Repository) -> None:
     ref_i18n = testrepo.lookup_reference('refs/heads/i18n')
 
     class MyCheckoutCallbacks(pygit2.CheckoutCallbacks):
-        def __init__(self):
+        def __init__(self) -> None:
             super().__init__()
-            self.conflicting_paths = set()
-            self.updated_paths = set()
+            self.conflicting_paths: set[str] = set()
+            self.updated_paths: set[str] = set()
+            self.completed_steps = -1
+            self.total_steps = -1
 
         def checkout_notify_flags(self) -> CheckoutNotify:
             return CheckoutNotify.CONFLICT | CheckoutNotify.UPDATED
 
-        def checkout_notify(self, why, path, baseline, target, workdir):
+        def checkout_notify(
+            self,
+            why: CheckoutNotify,
+            path: str,
+            baseline: Optional[DiffFile],
+            target: Optional[DiffFile],
+            workdir: Optional[DiffFile],
+        ) -> None:
             if why == CheckoutNotify.CONFLICT:
                 self.conflicting_paths.add(path)
             elif why == CheckoutNotify.UPDATED:
                 self.updated_paths.add(path)
 
+        def checkout_progress(
+            self, path: str, completed_steps: int, total_steps: int
+        ) -> None:
+            self.completed_steps = completed_steps
+            self.total_steps = total_steps
+
     # checkout i18n with conflicts and default strategy should not be possible
     callbacks = MyCheckoutCallbacks()
     with pytest.raises(pygit2.GitError):
         testrepo.checkout(ref_i18n, callbacks=callbacks)
     # make sure the callbacks caught that
     assert {'bye.txt'} == callbacks.conflicting_paths
+    assert -1 == callbacks.completed_steps  # shouldn't have done anything
 
     # checkout i18n with GIT_CHECKOUT_FORCE
-    head = testrepo.head
-    head = testrepo[head.target]
+    head_object = testrepo.head
+    head = testrepo[head_object.target]
     assert 'new' not in head.tree
     callbacks = MyCheckoutCallbacks()
     testrepo.checkout(ref_i18n, strategy=CheckoutStrategy.FORCE, callbacks=callbacks)
     # make sure the callbacks caught the files affected by the checkout
     assert set() == callbacks.conflicting_paths
     assert {'bye.txt', 'new'} == callbacks.updated_paths
+    assert callbacks.completed_steps > 0
+    assert callbacks.completed_steps == callbacks.total_steps
 
 
-def test_checkout_aborted_from_callbacks(testrepo):
+def test_checkout_aborted_from_callbacks(testrepo: Repository) -> None:
     ref_i18n = testrepo.lookup_reference('refs/heads/i18n')
 
-    def read_bye_txt():
-        return testrepo[testrepo.create_blob_fromworkdir('bye.txt')].data
+    def read_bye_txt() -> bytes:
+        blob = testrepo[testrepo.create_blob_fromworkdir('bye.txt')]
+        assert isinstance(blob, Blob)
+        return blob.data
 
     s = testrepo.status()
     assert s == {'bye.txt': FileStatus.WT_NEW}
 
     class MyCheckoutCallbacks(pygit2.CheckoutCallbacks):
-        def __init__(self):
+        def __init__(self) -> None:
             super().__init__()
             self.invoked_times = 0
 
-        def checkout_notify(self, why, path, baseline, target, workdir):
+        def checkout_notify(
+            self,
+            why: CheckoutNotify,
+            path: str,
+            baseline: Optional[DiffFile],
+            target: Optional[DiffFile],
+            workdir: Optional[DiffFile],
+        ) -> None:
             self.invoked_times += 1
             # skip one file so we're certain that NO files are affected,
             # even if aborting the checkout from the second file
             if self.invoked_times == 2:
                 raise InterruptedError('Stop the checkout!')
 
-    head = testrepo.head
-    head = testrepo[head.target]
+    head_object = testrepo.head
+    head = testrepo[head_object.target]
     assert 'new' not in head.tree
     assert b'bye world\n' == read_bye_txt()
     callbacks = MyCheckoutCallbacks()
@@ -165,7 +209,7 @@ def test_checkout_aborted_from_callbacks
     assert b'bye world\n' == read_bye_txt()
 
 
-def test_checkout_branch(testrepo):
+def test_checkout_branch(testrepo: Repository) -> None:
     branch_i18n = testrepo.lookup_branch('i18n')
 
     # checkout i18n with conflicts and default strategy should
@@ -174,19 +218,19 @@ def test_checkout_branch(testrepo):
         testrepo.checkout(branch_i18n)
 
     # checkout i18n with GIT_CHECKOUT_FORCE
-    head = testrepo.head
-    head = testrepo[head.target]
+    head_object = testrepo.head
+    head = testrepo[head_object.target]
     assert 'new' not in head.tree
     testrepo.checkout(branch_i18n, strategy=CheckoutStrategy.FORCE)
 
-    head = testrepo.head
-    head = testrepo[head.target]
+    head_object = testrepo.head
+    head = testrepo[head_object.target]
     assert head.id == branch_i18n.target
     assert 'new' in head.tree
     assert 'bye.txt' not in testrepo.status()
 
 
-def test_checkout_index(testrepo):
+def test_checkout_index(testrepo: Repository) -> None:
     # some changes to working dir
     with (Path(testrepo.workdir) / 'hello.txt').open('w') as f:
         f.write('new content')
@@ -197,7 +241,7 @@ def test_checkout_index(testrepo):
     assert 'hello.txt' not in testrepo.status()
 
 
-def test_checkout_head(testrepo):
+def test_checkout_head(testrepo: Repository) -> None:
     # some changes to the index
     with (Path(testrepo.workdir) / 'bye.txt').open('w') as f:
         f.write('new content')
@@ -213,7 +257,7 @@ def test_checkout_head(testrepo):
     assert 'bye.txt' not in testrepo.status()
 
 
-def test_checkout_alternative_dir(testrepo):
+def test_checkout_alternative_dir(testrepo: Repository) -> None:
     ref_i18n = testrepo.lookup_reference('refs/heads/i18n')
     extra_dir = Path(testrepo.workdir) / 'extra-dir'
     extra_dir.mkdir()
@@ -222,7 +266,7 @@ def test_checkout_alternative_dir(testre
     assert not len(list(extra_dir.iterdir())) == 0
 
 
-def test_checkout_paths(testrepo):
+def test_checkout_paths(testrepo: Repository) -> None:
     ref_i18n = testrepo.lookup_reference('refs/heads/i18n')
     ref_master = testrepo.lookup_reference('refs/heads/master')
     testrepo.checkout(ref_master)
@@ -231,7 +275,7 @@ def test_checkout_paths(testrepo):
     assert status['new'] == FileStatus.INDEX_NEW
 
 
-def test_merge_base(testrepo):
+def test_merge_base(testrepo: Repository) -> None:
     commit = testrepo.merge_base(
         '5ebeeebb320790caf276b9fc8b24546d63316533',
         '4ec4389a8068641da2d6578db0419484972284c8',
@@ -247,7 +291,7 @@ def test_merge_base(testrepo):
     assert testrepo.merge_base(indep, commit) is None
 
 
-def test_descendent_of(testrepo):
+def test_descendent_of(testrepo: Repository) -> None:
     assert not testrepo.descendant_of(
         '5ebeeebb320790caf276b9fc8b24546d63316533',
         '4ec4389a8068641da2d6578db0419484972284c8',
@@ -272,7 +316,7 @@ def test_descendent_of(testrepo):
         )
 
 
-def test_ahead_behind(testrepo):
+def test_ahead_behind(testrepo: Repository) -> None:
     ahead, behind = testrepo.ahead_behind(
         '5ebeeebb320790caf276b9fc8b24546d63316533',
         '4ec4389a8068641da2d6578db0419484972284c8',
@@ -288,7 +332,7 @@ def test_ahead_behind(testrepo):
     assert 1 == behind
 
 
-def test_reset_hard(testrepo):
+def test_reset_hard(testrepo: Repository) -> None:
     ref = '5ebeeebb320790caf276b9fc8b24546d63316533'
     with (Path(testrepo.workdir) / 'hello.txt').open() as f:
         lines = f.readlines()
@@ -305,7 +349,7 @@ def test_reset_hard(testrepo):
     assert 'bonjour le monde\n' not in lines
 
 
-def test_reset_soft(testrepo):
+def test_reset_soft(testrepo: Repository) -> None:
     ref = '5ebeeebb320790caf276b9fc8b24546d63316533'
     with (Path(testrepo.workdir) / 'hello.txt').open() as f:
         lines = f.readlines()
@@ -326,7 +370,7 @@ def test_reset_soft(testrepo):
         diff[0]
 
 
-def test_reset_mixed(testrepo):
+def test_reset_mixed(testrepo: Repository) -> None:
     ref = '5ebeeebb320790caf276b9fc8b24546d63316533'
     with (Path(testrepo.workdir) / 'hello.txt').open() as f:
         lines = f.readlines()
@@ -345,11 +389,12 @@ def test_reset_mixed(testrepo):
 
     # mixed reset will set the index to match working copy
     diff = testrepo.diff(cached=True)
+    assert diff.patch is not None
     assert 'hola mundo\n' in diff.patch
     assert 'bonjour le monde\n' in diff.patch
 
 
-def test_stash(testrepo):
+def test_stash(testrepo: Repository) -> None:
     stash_hash = '6aab5192f88018cb98a7ede99c242f43add5a2fd'
     stash_message = 'custom stash message'
     sig = pygit2.Signature(
@@ -386,7 +431,7 @@ def test_stash(testrepo):
         testrepo.stash_pop()
 
 
-def test_stash_partial(testrepo):
+def test_stash_partial(testrepo: Repository) -> None:
     stash_message = 'custom stash message'
     sig = pygit2.Signature(
         name='Stasher', email='stasher@example.com', time=1641000000, offset=0
@@ -405,7 +450,7 @@ def test_stash_partial(testrepo):
     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
         )
@@ -424,7 +469,7 @@ def test_stash_partial(testrepo):
     assert stash_pathspecs(['hello.txt', 'bye.txt'])
 
 
-def test_stash_progress_callback(testrepo):
+def test_stash_progress_callback(testrepo: Repository) -> None:
     sig = pygit2.Signature(
         name='Stasher', email='stasher@example.com', time=1641000000, offset=0
     )
@@ -439,7 +484,7 @@ def test_stash_progress_callback(testrep
     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
@@ -457,7 +502,7 @@ def test_stash_progress_callback(testrep
     ]
 
 
-def test_stash_aborted_from_callbacks(testrepo):
+def test_stash_aborted_from_callbacks(testrepo: Repository) -> None:
     sig = pygit2.Signature(
         name='Stasher', email='stasher@example.com', time=1641000000, offset=0
     )
@@ -474,7 +519,7 @@ def test_stash_aborted_from_callbacks(te
     # 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!')
 
@@ -496,7 +541,7 @@ def test_stash_aborted_from_callbacks(te
     assert repo_stashes[0].message == 'On master: custom stash message'
 
 
-def test_stash_apply_checkout_options(testrepo):
+def test_stash_apply_checkout_options(testrepo: Repository) -> None:
     sig = pygit2.Signature(
         name='Stasher', email='stasher@example.com', time=1641000000, offset=0
     )
@@ -512,7 +557,14 @@ def test_stash_apply_checkout_options(te
 
     # define callbacks that raise an InterruptedError when checkout detects a conflict
     class MyStashApplyCallbacks(pygit2.StashApplyCallbacks):
-        def checkout_notify(self, why, path, baseline, target, workdir):
+        def checkout_notify(
+            self,
+            why: CheckoutNotify,
+            path: str,
+            baseline: Optional[DiffFile],
+            target: Optional[DiffFile],
+            workdir: Optional[DiffFile],
+        ) -> None:
             if why == CheckoutNotify.CONFLICT:
                 raise InterruptedError('Applying the stash would create a conflict')
 
@@ -539,9 +591,12 @@ def test_stash_apply_checkout_options(te
         assert f.read() == 'stashed content'
 
 
-def test_revert_commit(testrepo):
+def test_revert_commit(testrepo: Repository) -> None:
     master = testrepo.head.peel()
+    assert isinstance(master, Commit)
     commit_to_revert = testrepo['4ec4389a8068641da2d6578db0419484972284c8']
+    assert isinstance(commit_to_revert, Commit)
+
     parent = commit_to_revert.parents[0]
     commit_diff_stats = parent.tree.diff_to_tree(commit_to_revert.tree).stats
 
@@ -553,9 +608,10 @@ def test_revert_commit(testrepo):
     assert revert_diff_stats.files_changed == commit_diff_stats.files_changed
 
 
-def test_revert(testrepo):
+def test_revert(testrepo: Repository) -> None:
     hello_txt = Path(testrepo.workdir) / 'hello.txt'
     commit_to_revert = testrepo['4ec4389a8068641da2d6578db0419484972284c8']
+    assert isinstance(commit_to_revert, Commit)
 
     assert testrepo.state() == RepositoryState.NONE
     assert not testrepo.message
@@ -573,7 +629,7 @@ def test_revert(testrepo):
     )
 
 
-def test_default_signature(testrepo):
+def test_default_signature(testrepo: Repository) -> None:
     config = testrepo.config
     config['user.name'] = 'Random J Hacker'
     config['user.email'] = 'rjh@example.com'
@@ -583,65 +639,66 @@ def test_default_signature(testrepo):
     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')
-    assert type(oid) == Oid
+    assert type(oid) is Oid
 
     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))
 
 
-@utils.fspath
-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)
@@ -649,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
@@ -659,14 +716,14 @@ def test_clone_repository(barerepo, tmp_
     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)
@@ -674,15 +731,17 @@ def test_clone_shallow_repository(tmp_pa
     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(
@@ -695,7 +754,7 @@ def test_clone_repository_and_remote_cal
 
 
 @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)
@@ -705,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'
@@ -716,18 +780,32 @@ def test_clone_bad_credentials(tmp_path)
     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
     )
     assert repo.lookup_reference('HEAD').target == 'refs/heads/test'
 
 
+@utils.requires_proxy
+@utils.requires_network
+def test_clone_with_proxy(tmp_path: Path) -> None:
+    url = 'https://github.com/libgit2/TestGitRepository'
+    repo = clone_repository(
+        url,
+        tmp_path / 'testrepo-orig.git',
+        proxy=True,
+    )
+    assert not repo.is_empty
+
+
 # FIXME The tests below are commented because they are broken:
 #
 # - test_clone_push_url: Passes, but does nothing useful.
@@ -769,14 +847,14 @@ def test_clone_with_checkout_branch(bare
 #    # assert repo.remotes[0].fetchspec == "refs/heads/test"
 
 
-def test_worktree(testrepo):
+def test_worktree(testrepo: Repository) -> None:
     worktree_name = 'foo'
     worktree_dir = Path(tempfile.mkdtemp())
     # Delete temp path so that it's not present when we attempt to add the
     # 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
@@ -811,8 +889,7 @@ def test_worktree(testrepo):
     assert testrepo.list_worktrees() == []
 
 
-@utils.fspath
-def test_worktree_aspath(testrepo):
+def test_worktree_aspath(testrepo: Repository) -> None:
     worktree_name = 'foo'
     worktree_dir = Path(tempfile.mkdtemp())
     # Delete temp path so that it's not present when we attempt to add the
@@ -822,13 +899,14 @@ def test_worktree_aspath(testrepo):
     assert testrepo.list_worktrees() == [worktree_name]
 
 
-def test_worktree_custom_ref(testrepo):
+def test_worktree_custom_ref(testrepo: Repository) -> None:
     worktree_name = 'foo'
     worktree_dir = Path(tempfile.mkdtemp())
     branch_name = 'version1'
 
     # New branch based on head
     tip = testrepo.revparse_single('HEAD')
+    assert isinstance(tip, Commit)
     worktree_ref = testrepo.branches.create(branch_name, tip)
     # Delete temp path so that it's not present when we attempt to add the
     # worktree later
@@ -856,7 +934,7 @@ def test_worktree_custom_ref(testrepo):
     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
@@ -890,7 +968,7 @@ def test_open_extended(tmp_path):
         assert not repo.workdir
 
 
-def test_is_shallow(testrepo):
+def test_is_shallow(testrepo: Repository) -> None:
     assert not testrepo.is_shallow
 
     # create a dummy shallow file
@@ -900,7 +978,7 @@ def test_is_shallow(testrepo):
     assert testrepo.is_shallow
 
 
-def test_repository_hashfile(testrepo):
+def test_repository_hashfile(testrepo: Repository) -> None:
     original_hash = testrepo.index['hello.txt'].id
 
     # Test simple use
@@ -910,8 +988,8 @@ def test_repository_hashfile(testrepo):
     # Test absolute path
     # For best results on Windows, pass a pure POSIX path. (See https://github.com/libgit2/libgit2/issues/6825)
     absolute_path = Path(testrepo.workdir, 'hello.txt')
-    absolute_path = absolute_path.as_posix()  # Windows compatibility
-    h = testrepo.hashfile(str(absolute_path))
+    absolute_path_str = absolute_path.as_posix()  # Windows compatibility
+    h = testrepo.hashfile(str(absolute_path_str))
     assert h == original_hash
 
     # Test missing path
@@ -923,7 +1001,7 @@ def test_repository_hashfile(testrepo):
         testrepo.hashfile('hello.txt', ObjectType.OFS_DELTA)
 
 
-def test_repository_hashfile_filter(testrepo):
+def test_repository_hashfile_filter(testrepo: Repository) -> None:
     original_hash = testrepo.index['hello.txt'].id
 
     with open(Path(testrepo.workdir, 'hello.txt'), 'rb') as f:
@@ -950,8 +1028,8 @@ def test_repository_hashfile_filter(test
     # Treat absolute path with filters.
     # For best results on Windows, pass a pure POSIX path. (See https://github.com/libgit2/libgit2/issues/6825)
     absolute_path = Path(testrepo.workdir, 'hellocrlf.txt')
-    absolute_path = absolute_path.as_posix()  # Windows compatibility
-    h = testrepo.hashfile(str(absolute_path))
+    absolute_path_str = absolute_path.as_posix()  # Windows compatibility
+    h = testrepo.hashfile(str(absolute_path_str))
     assert h == original_hash
 
     # Bypass filters
@@ -966,3 +1044,152 @@ def test_repository_hashfile_filter(test
     testrepo.config['core.safecrlf'] = 'fail'
     with pytest.raises(pygit2.GitError):
         h = testrepo.hashfile('hello.txt')
+
+
+def test_merge_file_from_index_deprecated(testrepo: Repository) -> None:
+    hello_txt = testrepo.index['hello.txt']
+    hello_txt_executable = IndexEntry(
+        hello_txt.path, hello_txt.id, FileMode.BLOB_EXECUTABLE
+    )
+    hello_world = IndexEntry('hello_world.txt', hello_txt.id, hello_txt.mode)
+
+    def get_hello_txt_from_repo() -> str:
+        blob = testrepo.get(hello_txt.id)
+        assert isinstance(blob, Blob)
+        return blob.data.decode()
+
+    # no change
+    res = testrepo.merge_file_from_index(hello_txt, hello_txt, hello_txt)
+    assert res == get_hello_txt_from_repo()
+
+    # executable switch on ours
+    res = testrepo.merge_file_from_index(hello_txt, hello_txt_executable, hello_txt)
+    assert res == get_hello_txt_from_repo()
+
+    # executable switch on theirs
+    res = testrepo.merge_file_from_index(hello_txt, hello_txt, hello_txt_executable)
+    assert res == get_hello_txt_from_repo()
+
+    # executable switch on both
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_txt_executable, hello_txt_executable
+    )
+    assert res == get_hello_txt_from_repo()
+
+    # path switch on ours
+    res = testrepo.merge_file_from_index(hello_txt, hello_world, hello_txt)
+    assert res == get_hello_txt_from_repo()
+
+    # path switch on theirs
+    res = testrepo.merge_file_from_index(hello_txt, hello_txt, hello_world)
+    assert res == get_hello_txt_from_repo()
+
+    # path switch on both
+    res = testrepo.merge_file_from_index(hello_txt, hello_world, hello_world)
+    assert res == get_hello_txt_from_repo()
+
+    # path switch on ours, executable flag switch on theirs
+    res = testrepo.merge_file_from_index(hello_txt, hello_world, hello_txt_executable)
+    assert res == get_hello_txt_from_repo()
+
+    # path switch on theirs, executable flag switch on ours
+    res = testrepo.merge_file_from_index(hello_txt, hello_txt_executable, hello_world)
+    assert res == get_hello_txt_from_repo()
+
+
+def test_merge_file_from_index_non_deprecated(testrepo: Repository) -> None:
+    hello_txt = testrepo.index['hello.txt']
+    hello_txt_executable = IndexEntry(
+        hello_txt.path, hello_txt.id, FileMode.BLOB_EXECUTABLE
+    )
+    hello_world = IndexEntry('hello_world.txt', hello_txt.id, hello_txt.mode)
+
+    def get_hello_txt_from_repo() -> str:
+        blob = testrepo.get(hello_txt.id)
+        assert isinstance(blob, Blob)
+        return blob.data.decode()
+
+    # no change
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_txt, hello_txt, use_deprecated=False
+    )
+    assert res == MergeFileResult(
+        True, hello_txt.path, hello_txt.mode, get_hello_txt_from_repo()
+    )
+
+    # executable switch on ours
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_txt_executable, hello_txt, use_deprecated=False
+    )
+    assert res == MergeFileResult(
+        True,
+        hello_txt.path,
+        hello_txt_executable.mode,
+        get_hello_txt_from_repo(),
+    )
+
+    # executable switch on theirs
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_txt, hello_txt_executable, use_deprecated=False
+    )
+    assert res == MergeFileResult(
+        True,
+        hello_txt.path,
+        hello_txt_executable.mode,
+        get_hello_txt_from_repo(),
+    )
+
+    # executable switch on both
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_txt_executable, hello_txt_executable, use_deprecated=False
+    )
+    assert res == MergeFileResult(
+        True,
+        hello_txt.path,
+        hello_txt_executable.mode,
+        get_hello_txt_from_repo(),
+    )
+
+    # path switch on ours
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_world, hello_txt, use_deprecated=False
+    )
+    assert res == MergeFileResult(
+        True, hello_world.path, hello_txt.mode, get_hello_txt_from_repo()
+    )
+
+    # path switch on theirs
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_txt, hello_world, use_deprecated=False
+    )
+    assert res == MergeFileResult(
+        True, hello_world.path, hello_txt.mode, get_hello_txt_from_repo()
+    )
+
+    # path switch on both
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_world, hello_world, use_deprecated=False
+    )
+    assert res == MergeFileResult(True, None, hello_txt.mode, get_hello_txt_from_repo())
+
+    # path switch on ours, executable flag switch on theirs
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_world, hello_txt_executable, use_deprecated=False
+    )
+    assert res == MergeFileResult(
+        True,
+        hello_world.path,
+        hello_txt_executable.mode,
+        get_hello_txt_from_repo(),
+    )
+
+    # path switch on theirs, executable flag switch on ours
+    res = testrepo.merge_file_from_index(
+        hello_txt, hello_txt_executable, hello_world, use_deprecated=False
+    )
+    assert res == MergeFileResult(
+        True,
+        hello_world.path,
+        hello_txt_executable.mode,
+        get_hello_txt_from_repo(),
+    )
diff -pruN 1.17.0-2/test/test_repository_bare.py 1.18.2-1/test/test_repository_bare.py
--- 1.17.0-2/test/test_repository_bare.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_repository_bare.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,18 +23,20 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
-# Standard Library
 import binascii
 import os
-from pathlib import Path
+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
 
+from . import utils
 
 HEAD_SHA = '784855caf26449a1914d2cf62d12b9374d76ae78'
 PARENT_SHA = 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87'  # HEAD^
@@ -43,24 +45,24 @@ BLOB_RAW = binascii.unhexlify(BLOB_HEX.e
 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) == pygit2.Reference
+    assert type(head) is pygit2.Reference
     assert not barerepo.head_is_unborn
     assert not barerepo.head_is_detached
 
 
-def test_set_head(barerepo):
-    # Test setting a detatched HEAD.
+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
     # And test setting a normal HEAD.
@@ -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,19 +89,19 @@ 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):
         barerepo.write(ObjectType.ANY, data)
 
     oid = barerepo.write(ObjectType.BLOB, data)
-    assert type(oid) == pygit2.Oid
+    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\n' 'This commit has some additional text.\n'
+        '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.refcount
-def test_lookup_commit_refcount(barerepo):
+@utils.requires_refcount
+def test_lookup_commit_refcount(barerepo: Repository) -> None:
     start = sys.getrefcount(barerepo)
     commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10'
     commit = barerepo[commit_sha]
@@ -170,42 +174,42 @@ 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 = Path(barerepo.path).resolve()
+    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:
         fh.write(data)
     hashed_sha1 = pygit2.hashfile(tempfile_path)
-    Path(tempfile_path).unlink()
+    pathlib.Path(tempfile_path).unlink()
     written_sha1 = barerepo.create_blob(data)
     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 test_conflicts_in_bare_repository(ba
         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)
diff -pruN 1.17.0-2/test/test_repository_custom.py 1.18.2-1/test/test_repository_custom.py
--- 1.17.0-2/test/test_repository_custom.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_repository_custom.py	2025-08-16 11:34:29.000000000 +0000
@@ -23,15 +23,18 @@
 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 # Boston, MA 02110-1301, USA.
 
+from collections.abc import Generator
 from pathlib import Path
+
 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()
@@ -48,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'),
@@ -56,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 -pruN 1.17.0-2/test/test_repository_empty.py 1.18.2-1/test/test_repository_empty.py
--- 1.17.0-2/test/test_repository_empty.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_repository_empty.py	2025-08-16 11:34:29.000000000 +0000
@@ -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 -pruN 1.17.0-2/test/test_revparse.py 1.18.2-1/test/test_revparse.py
--- 1.17.0-2/test/test_revparse.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_revparse.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,22 +25,23 @@
 
 """Tests for revision parsing."""
 
-from pygit2 import InvalidSpecError
-from pygit2.enums import RevSpecFlag
 from pytest import raises
 
+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']
@@ -54,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')
@@ -76,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')
 
@@ -84,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 -pruN 1.17.0-2/test/test_revwalk.py 1.18.2-1/test/test_revwalk.py
--- 1.17.0-2/test/test_revwalk.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_revwalk.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,9 +25,9 @@
 
 """Tests for revision walk."""
 
+from pygit2 import Repository
 from pygit2.enums import SortMode
 
-
 # In the order given by git log
 log = [
     '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98',
@@ -51,42 +51,42 @@ REVLOGS = [
 ]
 
 
-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()
@@ -94,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 -pruN 1.17.0-2/test/test_settings.py 1.18.2-1/test/test_settings.py
--- 1.17.0-2/test/test_settings.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.18.2-1/test/test_settings.py	2025-08-16 11:34:29.000000000 +0000
@@ -0,0 +1,284 @@
+# Copyright 2010-2025 The pygit2 contributors
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# In addition to the permissions in the GNU General Public License,
+# the authors give you unlimited permission to link the compiled
+# version of this file into combinations with other programs,
+# and to distribute those combinations without any restriction
+# coming from the use of this file.  (The General Public License
+# restrictions do apply in other respects; for example, they cover
+# modification of the file, and distribution when not linked into
+# a combined executable.)
+#
+# This file is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING.  If not, write to
+# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+"""Test the Settings class."""
+
+import sys
+
+import pytest
+
+import pygit2
+from pygit2.enums import ConfigLevel, ObjectType
+
+
+def test_mwindow_size() -> None:
+    original = pygit2.settings.mwindow_size
+    try:
+        test_size = 200 * 1024
+        pygit2.settings.mwindow_size = test_size
+        assert pygit2.settings.mwindow_size == test_size
+    finally:
+        pygit2.settings.mwindow_size = original
+
+
+def test_mwindow_mapped_limit() -> None:
+    original = pygit2.settings.mwindow_mapped_limit
+    try:
+        test_limit = 300 * 1024
+        pygit2.settings.mwindow_mapped_limit = test_limit
+        assert pygit2.settings.mwindow_mapped_limit == test_limit
+    finally:
+        pygit2.settings.mwindow_mapped_limit = original
+
+
+def test_cached_memory() -> None:
+    cached = pygit2.settings.cached_memory
+    assert isinstance(cached, tuple)
+    assert len(cached) == 2
+    assert isinstance(cached[0], int)
+    assert isinstance(cached[1], int)
+
+
+def test_enable_caching() -> None:
+    assert hasattr(pygit2.settings, 'enable_caching')
+    assert callable(pygit2.settings.enable_caching)
+
+    # Should not raise exceptions
+    pygit2.settings.enable_caching(False)
+    pygit2.settings.enable_caching(True)
+
+
+def test_disable_pack_keep_file_checks() -> None:
+    assert hasattr(pygit2.settings, 'disable_pack_keep_file_checks')
+    assert callable(pygit2.settings.disable_pack_keep_file_checks)
+
+    # Should not raise exceptions
+    pygit2.settings.disable_pack_keep_file_checks(False)
+    pygit2.settings.disable_pack_keep_file_checks(True)
+    pygit2.settings.disable_pack_keep_file_checks(False)
+
+
+def test_cache_max_size() -> None:
+    original_max_size = pygit2.settings.cached_memory[1]
+    try:
+        pygit2.settings.cache_max_size(128 * 1024**2)
+        assert pygit2.settings.cached_memory[1] == 128 * 1024**2
+        pygit2.settings.cache_max_size(256 * 1024**2)
+        assert pygit2.settings.cached_memory[1] == 256 * 1024**2
+    finally:
+        pygit2.settings.cache_max_size(original_max_size)
+
+
+@pytest.mark.parametrize(
+    'object_type,test_size,default_size',
+    [
+        (ObjectType.BLOB, 2 * 1024, 0),
+        (ObjectType.COMMIT, 8 * 1024, 4096),
+        (ObjectType.TREE, 8 * 1024, 4096),
+        (ObjectType.TAG, 8 * 1024, 4096),
+        (ObjectType.BLOB, 0, 0),
+    ],
+)
+def test_cache_object_limit(
+    object_type: ObjectType, test_size: int, default_size: int
+) -> None:
+    assert callable(pygit2.settings.cache_object_limit)
+
+    pygit2.settings.cache_object_limit(object_type, test_size)
+    pygit2.settings.cache_object_limit(object_type, default_size)
+
+
+@pytest.mark.parametrize(
+    'level,test_path',
+    [
+        (ConfigLevel.GLOBAL, '/tmp/test_global'),
+        (ConfigLevel.XDG, '/tmp/test_xdg'),
+        (ConfigLevel.SYSTEM, '/tmp/test_system'),
+    ],
+)
+def test_search_path(level: ConfigLevel, test_path: str) -> None:
+    original = pygit2.settings.search_path[level]
+    try:
+        pygit2.settings.search_path[level] = test_path
+        assert pygit2.settings.search_path[level] == test_path
+    finally:
+        pygit2.settings.search_path[level] = original
+
+
+def test_template_path() -> None:
+    original = pygit2.settings.template_path
+    try:
+        pygit2.settings.template_path = '/tmp/test_templates'
+        assert pygit2.settings.template_path == '/tmp/test_templates'
+    finally:
+        if original:
+            pygit2.settings.template_path = original
+
+
+def test_user_agent() -> None:
+    original = pygit2.settings.user_agent
+    try:
+        pygit2.settings.user_agent = 'test-agent/1.0'
+        assert pygit2.settings.user_agent == 'test-agent/1.0'
+    finally:
+        if original:
+            pygit2.settings.user_agent = original
+
+
+def test_user_agent_product() -> None:
+    original = pygit2.settings.user_agent_product
+    try:
+        pygit2.settings.user_agent_product = 'test-product'
+        assert pygit2.settings.user_agent_product == 'test-product'
+    finally:
+        if original:
+            pygit2.settings.user_agent_product = original
+
+
+def test_pack_max_objects() -> None:
+    original = pygit2.settings.pack_max_objects
+    try:
+        pygit2.settings.pack_max_objects = 100000
+        assert pygit2.settings.pack_max_objects == 100000
+    finally:
+        pygit2.settings.pack_max_objects = original
+
+
+def test_owner_validation() -> None:
+    original = pygit2.settings.owner_validation
+    try:
+        pygit2.settings.owner_validation = False
+        assert pygit2.settings.owner_validation == False  # noqa: E712
+        pygit2.settings.owner_validation = True
+        assert pygit2.settings.owner_validation == True  # noqa: E712
+    finally:
+        pygit2.settings.owner_validation = original
+
+
+def test_mwindow_file_limit() -> None:
+    original = pygit2.settings.mwindow_file_limit
+    try:
+        pygit2.settings.mwindow_file_limit = 100
+        assert pygit2.settings.mwindow_file_limit == 100
+    finally:
+        pygit2.settings.mwindow_file_limit = original
+
+
+def test_homedir() -> None:
+    original = pygit2.settings.homedir
+    try:
+        pygit2.settings.homedir = '/tmp/test_home'
+        assert pygit2.settings.homedir == '/tmp/test_home'
+    finally:
+        if original:
+            pygit2.settings.homedir = original
+
+
+def test_server_timeouts() -> None:
+    original_connect = pygit2.settings.server_connect_timeout
+    original_timeout = pygit2.settings.server_timeout
+    try:
+        pygit2.settings.server_connect_timeout = 5000
+        assert pygit2.settings.server_connect_timeout == 5000
+
+        pygit2.settings.server_timeout = 10000
+        assert pygit2.settings.server_timeout == 10000
+    finally:
+        pygit2.settings.server_connect_timeout = original_connect
+        pygit2.settings.server_timeout = original_timeout
+
+
+def test_extensions() -> None:
+    original = pygit2.settings.extensions
+    try:
+        test_extensions = ['objectformat', 'worktreeconfig']
+        pygit2.settings.set_extensions(test_extensions)
+
+        new_extensions = pygit2.settings.extensions
+        for ext in test_extensions:
+            assert ext in new_extensions
+    finally:
+        if original:
+            pygit2.settings.set_extensions(original)
+
+
+@pytest.mark.parametrize(
+    'method_name,default_value',
+    [
+        ('enable_strict_object_creation', True),
+        ('enable_strict_symbolic_ref_creation', True),
+        ('enable_ofs_delta', True),
+        ('enable_fsync_gitdir', False),
+        ('enable_strict_hash_verification', True),
+        ('enable_unsaved_index_safety', False),
+        ('enable_http_expect_continue', False),
+    ],
+)
+def test_enable_methods(method_name: str, default_value: bool) -> None:
+    assert hasattr(pygit2.settings, method_name)
+    method = getattr(pygit2.settings, method_name)
+    assert callable(method)
+
+    method(True)
+    method(False)
+    method(default_value)
+
+
+@pytest.mark.parametrize('priority', [1, 5, 10, 0, -1, -2])
+def test_odb_priorities(priority: int) -> None:
+    """Test setting ODB priorities"""
+    assert hasattr(pygit2.settings, 'set_odb_packed_priority')
+    assert hasattr(pygit2.settings, 'set_odb_loose_priority')
+    assert callable(pygit2.settings.set_odb_packed_priority)
+    assert callable(pygit2.settings.set_odb_loose_priority)
+
+    pygit2.settings.set_odb_packed_priority(priority)
+    pygit2.settings.set_odb_loose_priority(priority)
+
+    pygit2.settings.set_odb_packed_priority(1)
+    pygit2.settings.set_odb_loose_priority(2)
+
+
+def test_ssl_ciphers() -> None:
+    assert callable(pygit2.settings.set_ssl_ciphers)
+
+    try:
+        pygit2.settings.set_ssl_ciphers('DEFAULT')
+    except pygit2.GitError as e:
+        if "TLS backend doesn't support" in str(e):
+            pytest.skip(str(e))
+        raise
+
+
+@pytest.mark.skipif(sys.platform != 'win32', reason='Windows-specific feature')
+def test_windows_sharemode() -> None:
+    original = pygit2.settings.windows_sharemode
+    try:
+        pygit2.settings.windows_sharemode = 1
+        assert pygit2.settings.windows_sharemode == 1
+        pygit2.settings.windows_sharemode = 2
+        assert pygit2.settings.windows_sharemode == 2
+    finally:
+        pygit2.settings.windows_sharemode = original
diff -pruN 1.17.0-2/test/test_signature.py 1.18.2-1/test/test_signature.py
--- 1.17.0-2/test/test_signature.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_signature.py	2025-08-16 11:34:29.000000000 +0000
@@ -29,9 +29,10 @@ import time
 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 <foo@example.com>'
 
 
-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)
diff -pruN 1.17.0-2/test/test_status.py 1.18.2-1/test/test_status.py
--- 1.17.0-2/test/test_status.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_status.py	2025-08-16 11:34:29.000000000 +0000
@@ -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(dirtyre
 
 
 @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 -pruN 1.17.0-2/test/test_submodule.py 1.18.2-1/test/test_submodule.py
--- 1.17.0-2/test/test_submodule.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_submodule.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,14 +25,17 @@
 
 """Tests for Submodule objects."""
 
+from collections.abc import Generator
 from pathlib import Path
 
-import pygit2
 import pytest
 
-from . import utils
-from pygit2.enums import SubmoduleIgnore as SI, SubmoduleStatus as SS
+import pygit2
+from pygit2 import Repository, Submodule
+from pygit2.enums import SubmoduleIgnore as SI
+from pygit2.enums import SubmoduleStatus as SS
 
+from . import utils
 
 SUBM_NAME = 'TestGitRepository'
 SUBM_PATH = 'TestGitRepository'
@@ -42,48 +45,48 @@ SUBM_BOTTOM_SHA = '6c8b137b1c652731597c8
 
 
 @pytest.fixture
-def repo(tmp_path):
+def repo(tmp_path: Path) -> Generator[Repository, None, None]:
     with utils.TemporaryRepository('submodulerepo.zip', tmp_path) as path:
         yield pygit2.Repository(path)
 
 
-def test_lookup_submodule(repo):
-    s = repo.submodules[SUBM_PATH]
+def test_lookup_submodule(repo: Repository) -> None:
+    s: Submodule | None = repo.submodules[SUBM_PATH]
     assert s is not None
     s = repo.submodules.get(SUBM_PATH)
     assert s is not None
 
 
-def test_lookup_submodule_aspath(repo):
+def test_lookup_submodule_aspath(repo: Repository) -> None:
     s = repo.submodules[Path(SUBM_PATH)]
     assert s is not None
 
 
-def test_lookup_missing_submodule(repo):
+def test_lookup_missing_submodule(repo: Repository) -> None:
     with pytest.raises(KeyError):
         repo.submodules['does-not-exist']
     assert repo.submodules.get('does-not-exist') is None
 
 
-def test_listall_submodules(repo):
+def test_listall_submodules(repo: Repository) -> None:
     submodules = repo.listall_submodules()
     assert len(submodules) == 1
     assert submodules[0] == SUBM_PATH
 
 
-def test_contains_submodule(repo):
+def test_contains_submodule(repo: Repository) -> None:
     assert SUBM_PATH in repo.submodules
     assert 'does-not-exist' not in repo.submodules
 
 
-def test_submodule_iterator(repo):
+def test_submodule_iterator(repo: Repository) -> None:
     for s in repo.submodules:
         assert isinstance(s, pygit2.Submodule)
         assert s.path == repo.submodules[s.path].path
 
 
 @utils.requires_network
-def test_submodule_open(repo):
+def test_submodule_open(repo: Repository) -> None:
     s = repo.submodules[SUBM_PATH]
     repo.submodules.init()
     repo.submodules.update()
@@ -93,7 +96,7 @@ def test_submodule_open(repo):
 
 
 @utils.requires_network
-def test_submodule_open_from_repository_subclass(repo):
+def test_submodule_open_from_repository_subclass(repo: Repository) -> None:
     class CustomRepoClass(pygit2.Repository):
         pass
 
@@ -106,22 +109,33 @@ def test_submodule_open_from_repository_
     assert r.head.target == SUBM_HEAD_SHA
 
 
-def test_name(repo):
+def test_name(repo: Repository) -> None:
     s = repo.submodules[SUBM_PATH]
     assert SUBM_NAME == s.name
 
 
-def test_path(repo):
+def test_path(repo: Repository) -> None:
     s = repo.submodules[SUBM_PATH]
     assert SUBM_PATH == s.path
 
 
-def test_url(repo):
+def test_url(repo: Repository) -> None:
     s = repo.submodules[SUBM_PATH]
     assert SUBM_URL == s.url
 
 
-def test_missing_url(repo):
+def test_set_url(repo: Repository) -> None:
+    new_url = 'ssh://git@127.0.0.1:2222/my_repo'
+    s = repo.submodules[SUBM_PATH]
+    s.url = new_url
+    assert new_url == repo.submodules[SUBM_PATH].url
+    # Ensure .gitmodules has been correctly altered
+    with open(Path(repo.workdir, '.gitmodules'), 'r') as fd:
+        modules = fd.read()
+    assert new_url in modules
+
+
+def test_missing_url(repo: Repository) -> None:
     # Remove "url" from .gitmodules
     with open(Path(repo.workdir, '.gitmodules'), 'wt') as f:
         f.write('[submodule "TestGitRepository"]\n')
@@ -131,7 +145,7 @@ def test_missing_url(repo):
 
 
 @utils.requires_network
-def test_init_and_update(repo):
+def test_init_and_update(repo: Repository) -> None:
     subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
     assert not subrepo_file_path.exists()
 
@@ -148,7 +162,7 @@ def test_init_and_update(repo):
 
 
 @utils.requires_network
-def test_specified_update(repo):
+def test_specified_update(repo: Repository) -> None:
     subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
     assert not subrepo_file_path.exists()
     repo.submodules.init(submodules=['TestGitRepository'])
@@ -157,7 +171,7 @@ def test_specified_update(repo):
 
 
 @utils.requires_network
-def test_update_instance(repo):
+def test_update_instance(repo: Repository) -> None:
     subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
     assert not subrepo_file_path.exists()
     sm = repo.submodules['TestGitRepository']
@@ -168,7 +182,7 @@ def test_update_instance(repo):
 
 @utils.requires_network
 @pytest.mark.parametrize('depth', [0, 1])
-def test_oneshot_update(repo, depth):
+def test_oneshot_update(repo: Repository, depth: int) -> None:
     status = repo.submodules.status(SUBM_NAME)
     assert status == (SS.IN_HEAD | SS.IN_INDEX | SS.IN_CONFIG | SS.WD_UNINITIALIZED)
 
@@ -190,7 +204,7 @@ def test_oneshot_update(repo, depth):
 
 @utils.requires_network
 @pytest.mark.parametrize('depth', [0, 1])
-def test_oneshot_update_instance(repo, depth):
+def test_oneshot_update_instance(repo: Repository, depth: int) -> None:
     subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
     assert not subrepo_file_path.exists()
     sm = repo.submodules[SUBM_NAME]
@@ -206,12 +220,12 @@ def test_oneshot_update_instance(repo, d
 
 
 @utils.requires_network
-def test_head_id(repo):
+def test_head_id(repo: Repository) -> None:
     assert repo.submodules[SUBM_PATH].head_id == SUBM_HEAD_SHA
 
 
 @utils.requires_network
-def test_head_id_null(repo):
+def test_head_id_null(repo: Repository) -> None:
     gitmodules_newlines = (
         '\n'
         '[submodule "uncommitted_submodule"]\n'
@@ -230,7 +244,7 @@ def test_head_id_null(repo):
 
 @utils.requires_network
 @pytest.mark.parametrize('depth', [0, 1])
-def test_add_submodule(repo, depth):
+def test_add_submodule(repo: Repository, depth: int) -> None:
     sm_repo_path = 'test/testrepo'
     sm = repo.submodules.add(SUBM_URL, sm_repo_path, depth=depth)
 
@@ -250,7 +264,7 @@ def test_add_submodule(repo, depth):
 
 
 @utils.requires_network
-def test_submodule_status(repo):
+def test_submodule_status(repo: Repository) -> None:
     common_status = SS.IN_HEAD | SS.IN_INDEX | SS.IN_CONFIG
 
     # Submodule needs initializing
@@ -302,7 +316,7 @@ def test_submodule_status(repo):
     )
 
 
-def test_submodule_cache(repo):
+def test_submodule_cache(repo: Repository) -> None:
     # When the cache is turned on, looking up the same submodule twice must return the same git_submodule object
     repo.submodules.cache_all()
     sm1 = repo.submodules[SUBM_NAME]
@@ -317,7 +331,7 @@ def test_submodule_cache(repo):
     assert sm3._subm != sm4._subm
 
 
-def test_submodule_reload(repo):
+def test_submodule_reload(repo: Repository) -> None:
     sm = repo.submodules[SUBM_NAME]
     assert sm.url == 'https://github.com/libgit2/TestGitRepository'
 
diff -pruN 1.17.0-2/test/test_tag.py 1.18.2-1/test/test_tag.py
--- 1.17.0-2/test/test_tag.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_tag.py	2025-08-16 11:34:29.000000000 +0000
@@ -28,19 +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
@@ -49,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'
@@ -62,6 +63,7 @@ def test_new_tag(barerepo):
 
     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
@@ -71,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'
@@ -88,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 -pruN 1.17.0-2/test/test_tree.py 1.18.2-1/test/test_tree.py
--- 1.17.0-2/test/test_tree.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_tree.py	2025-08-16 11:34:29.000000000 +0000
@@ -24,31 +24,33 @@
 # Boston, MA 02110-1301, USA.
 
 import operator
+
 import pytest
 
 import pygit2
+from pygit2 import Object, Repository, Tree
 from pygit2.enums import FileMode, ObjectType
 
 from . import utils
 
-
 TREE_SHA = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12'
 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))
 
 
-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 consitent with the sha strings we could
+    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']
diff -pruN 1.17.0-2/test/test_treebuilder.py 1.18.2-1/test/test_treebuilder.py
--- 1.17.0-2/test/test_treebuilder.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/test_treebuilder.py	2025-08-16 11:34:29.000000000 +0000
@@ -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(bare
     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
diff -pruN 1.17.0-2/test/utils.py 1.18.2-1/test/utils.py
--- 1.17.0-2/test/utils.py	2025-01-08 09:08:49.000000000 +0000
+++ 1.18.2-1/test/utils.py	2025-08-16 11:34:29.000000000 +0000
@@ -25,12 +25,15 @@
 
 # Standard library
 import hashlib
-from pathlib import Path
 import shutil
 import socket
 import stat
 import sys
 import zipfile
+from collections.abc import Callable
+from pathlib import Path
+from types import TracebackType
+from typing import Any, Optional, ParamSpec, TypeVar
 
 # Requirements
 import pytest
@@ -38,8 +41,10 @@ import pytest
 # Pygit2
 import pygit2
 
+T = TypeVar('T')
+P = ParamSpec('P')
 
-requires_future_libgit2 = pytest.mark.skipif(
+requires_future_libgit2 = pytest.mark.xfail(
     pygit2.LIBGIT2_VER < (2, 0, 0),
     reason='This test may work with a future version of libgit2',
 )
@@ -64,15 +69,14 @@ requires_ssh = pytest.mark.skipif(
 
 is_pypy = '__pypy__' in sys.builtin_module_names
 
-fspath = pytest.mark.skipif(
-    is_pypy,
-    reason="PyPy doesn't fully support fspath, see https://foss.heptapod.net/pypy/pypy/-/issues/3168",
-)
+requires_refcount = pytest.mark.skipif(is_pypy, reason='skip refcounts checks in pypy')
 
-refcount = pytest.mark.skipif(is_pypy, reason='skip refcounts checks in pypy')
+fails_in_macos = pytest.mark.xfail(
+    sys.platform == 'darwin', reason='fails in macOS for an unknown reason'
+)
 
 
-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())
@@ -80,13 +84,18 @@ 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)
 
 
-def rmtree(path):
+def rmtree(path: str | Path) -> None:
     """In Windows a read-only file cannot be removed, and shutil.rmtree fails.
     So we implement our own version of rmtree to address this issue.
     """
@@ -95,11 +104,11 @@ def rmtree(path):
 
 
 class TemporaryRepository:
-    def __init__(self, name, tmp_path):
+    def __init__(self, name: str, tmp_path: Path) -> None:
         self.name = name
         self.tmp_path = tmp_path
 
-    def __enter__(self):
+    def __enter__(self) -> Path:
         path = Path(__file__).parent / 'data' / self.name
         temp_repo_path = Path(self.tmp_path) / path.stem
         if path.suffix == '.zip':
@@ -112,11 +121,22 @@ class TemporaryRepository:
 
         return temp_repo_path
 
-    def __exit__(self, exc_type, exc_value, traceback):
+    def __exit__(
+        self,
+        exc_type: Optional[type[BaseException]],
+        exc_value: Optional[BaseException],
+        traceback: Optional[TracebackType],
+    ) -> None:
         pass
 
 
-def assertRaisesWithArg(exc_class, arg, func, *args, **kwargs):
+def assertRaisesWithArg(
+    exc_class: type[Exception],
+    arg: object,
+    func: Callable[P, T],
+    *args: P.args,
+    **kwargs: P.kwargs,
+) -> None:
     with pytest.raises(exc_class) as excinfo:
         func(*args, **kwargs)
     assert excinfo.value.args == (arg,)
