Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pygit2 for openSUSE:Factory 
checked in at 2022-12-05 18:00:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pygit2 (Old)
 and      /work/SRC/openSUSE:Factory/.python-pygit2.new.1835 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pygit2"

Mon Dec  5 18:00:44 2022 rev:30 rq:1040007 version:1.11.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pygit2/python-pygit2.changes      
2022-09-20 19:24:10.482575754 +0200
+++ /work/SRC/openSUSE:Factory/.python-pygit2.new.1835/python-pygit2.changes    
2022-12-05 18:00:45.832438707 +0100
@@ -1,0 +2,17 @@
+Sat Dec  3 20:06:29 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com>
+
+- Update to version 1.11.1 (2022-11-09)
+  * Fix Linux wheels, downgrade to manylinux 2_24 #1176
+  * Windows wheels for Python 3.11 #1177
+  * CI: Use 3.11 final release for testing #1178
+  * Drop support for Python 3.7
+  * Update Linux wheels to manylinux 2_28 #1136
+  * Fix crash in signature representation #1162
+  * Fix memory leak in Signature #1173
+  * New optional argument raise_error in Repository.applies(...) #1166
+  * New notify/progress callbacks for checkout and stash #1167 #1169
+  * New Repository.remotes.names() #1159
+  * Now refname argument in RemoteCallbacks.push_update_reference(...) is a 
string, not bytes #1168
+  * Add missing newline at end of pygit2/decl/pack.h #1163 
+
+-------------------------------------------------------------------

Old:
----
  pygit2-1.10.1.tar.gz

New:
----
  pygit2-1.11.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pygit2.spec ++++++
--- /var/tmp/diff_new_pack.0uSYyC/_old  2022-12-05 18:00:46.408441844 +0100
+++ /var/tmp/diff_new_pack.0uSYyC/_new  2022-12-05 18:00:46.412441866 +0100
@@ -20,7 +20,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-pygit2
-Version:        1.10.1
+Version:        1.11.1
 Release:        0
 Summary:        Python bindings for libgit2
 License:        GPL-2.0-only

++++++ pygit2-1.10.1.tar.gz -> pygit2-1.11.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/AUTHORS.rst 
new/pygit2-1.11.1/AUTHORS.rst
--- old/pygit2-1.10.1/AUTHORS.rst       2022-08-28 11:14:16.406786400 +0200
+++ new/pygit2-1.11.1/AUTHORS.rst       2022-11-09 08:08:45.083533500 +0100
@@ -9,9 +9,9 @@
   W. Trevor King
   Drew DeVault
   Dave Borowitz
+  Iliyas Jorio
   Brandon Milton
   Daniel Rodríguez Troitiño
-  Iliyas Jorio
   Richo Healey
   Christian Boos
   Julien Miotte
@@ -32,6 +32,7 @@
   Tamir Bahar
   Valentin Haenel
   Michael Jones
+  Saugat Pachhai
   Bernardo Heynemann
   Brodie Rao
   John Szakmeister
@@ -42,7 +43,6 @@
   Lukas Fleischer
   Nicolas Dandrimont
   Raphael Medaer (Escaux)
-  Saugat Pachhai
   Anatoly Techtonik
   Andrew Olsen
   Dan Sully
@@ -114,6 +114,7 @@
   Vicent Marti
   Zbigniew Jędrzejewski-Szmek
   Zoran Zaric
+  Adam Gausmann
   Adam Spiers
   Alexandru Fikl
   Andrew Chin
@@ -168,6 +169,7 @@
   Lance Eftink
   Legorooj
   Lukas Berk
+  Martin von Zweigbergk
   Mathieu Bridon
   Mathieu Parent
   Mathieu Pillard
@@ -192,6 +194,7 @@
   Saul Pwanson
   Shane Turner
   Sheeo
+  Simone Mosciatti
   Soasme
   Steven Winfield
   Tad Hardesty
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/CHANGELOG.rst 
new/pygit2-1.11.1/CHANGELOG.rst
--- old/pygit2-1.10.1/CHANGELOG.rst     2022-08-28 11:13:13.705961500 +0200
+++ new/pygit2-1.11.1/CHANGELOG.rst     2022-11-09 08:06:54.571543000 +0100
@@ -1,3 +1,48 @@
+1.11.1 (2022-11-09)
+-------------------------
+
+- Fix Linux wheels, downgrade to manylinux 2_24
+  `#1176 <https://github.com/libgit2/pygit2/issues/1176>`_
+
+- Windows wheels for Python 3.11
+  `#1177 <https://github.com/libgit2/pygit2/pull/1177>`_
+
+- CI: Use 3.11 final release for testing
+  `#1178 <https://github.com/libgit2/pygit2/pull/1178>`_
+
+
+1.11.0 (2022-11-06)
+-------------------------
+
+- Drop support for Python 3.7
+
+- Update Linux wheels to manylinux 2_28
+  `#1136 <https://github.com/libgit2/pygit2/issues/1136>`_
+
+- Fix crash in signature representation
+  `#1162 <https://github.com/libgit2/pygit2/pull/1162>`_
+
+- Fix memory leak in ``Signature``
+  `#1173 <https://github.com/libgit2/pygit2/pull/1173>`_
+
+- New optional argument ``raise_error`` in ``Repository.applies(...)``
+  `#1166 <https://github.com/libgit2/pygit2/pull/1166>`_
+
+- New notify/progress callbacks for checkout and stash
+  `#1167 <https://github.com/libgit2/pygit2/pull/1167>`_
+  `#1169 <https://github.com/libgit2/pygit2/pull/1169>`_
+
+- New ``Repository.remotes.names()``
+  `#1159 <https://github.com/libgit2/pygit2/pull/1159>`_
+
+- Now ``refname`` argument in ``RemoteCallbacks.push_update_reference(...)`` is
+  a string, not bytes
+  `#1168 <https://github.com/libgit2/pygit2/pull/1168>`_
+
+- Add missing newline at end of ``pygit2/decl/pack.h``
+  `#1163 <https://github.com/libgit2/pygit2/pull/1163>`_
+
+
 1.10.1 (2022-08-28)
 -------------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/PKG-INFO new/pygit2-1.11.1/PKG-INFO
--- old/pygit2-1.10.1/PKG-INFO  2022-08-28 13:41:46.114741000 +0200
+++ new/pygit2-1.11.1/PKG-INFO  2022-11-09 10:53:56.286999700 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pygit2
-Version: 1.10.1
+Version: 1.11.1
 Summary: Python bindings for libgit2.
 Home-page: https://github.com/libgit2/pygit2
 Maintainer: J. David Ibáñez
@@ -14,14 +14,14 @@
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Topic :: Software Development :: Version Control
-Requires-Python: >=3.7
+Requires-Python: >=3.8
 License-File: COPYING
 License-File: AUTHORS.rst
 
@@ -30,7 +30,7 @@
 ######################################################################
 
 Bindings to the libgit2 shared library, implements Git plumbing.
-Supports Python 3.7+ and PyPy3 7.3+
+Supports Python 3.8+ and PyPy3 7.3+
 
 .. image:: 
https://github.com/libgit2/pygit2/actions/workflows/tests.yml/badge.svg
    :target: https://github.com/libgit2/pygit2/actions/workflows/tests.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/README.rst new/pygit2-1.11.1/README.rst
--- old/pygit2-1.10.1/README.rst        2021-12-06 09:47:08.838489800 +0100
+++ new/pygit2-1.11.1/README.rst        2022-10-23 11:28:21.044473400 +0200
@@ -3,7 +3,7 @@
 ######################################################################
 
 Bindings to the libgit2 shared library, implements Git plumbing.
-Supports Python 3.7+ and PyPy3 7.3+
+Supports Python 3.8+ and PyPy3 7.3+
 
 .. image:: 
https://github.com/libgit2/pygit2/actions/workflows/tests.yml/badge.svg
    :target: https://github.com/libgit2/pygit2/actions/workflows/tests.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/build.sh new/pygit2-1.11.1/build.sh
--- old/pygit2-1.10.1/build.sh  2022-08-28 11:11:54.262747800 +0200
+++ new/pygit2-1.11.1/build.sh  2022-11-08 13:48:28.755278000 +0100
@@ -68,8 +68,11 @@
     rm -rf ci
     mkdir ci || true
     cd ci
-    if [ "$KERNEL" = "Linux" ]; then
-        yum install wget openssl-devel libssh2-devel zlib-devel -y
+    if [ -f /usr/bin/apt-get ]; then
+        apt-get update
+        apt-get install libssl-dev wget -y
+    elif [ -f /usr/bin/yum ]; then
+        yum install wget openssl-devel zlib-devel -y
     fi
 else
     # Create a virtual environment
@@ -188,15 +191,13 @@
 fi
 
 if [ "$CIBUILDWHEEL" = "1" ]; then
-    # This is gross. auditwheel/delocate-wheel are not so good
-    # at finding libraries in random places, so we have to
-    # put them in the loader path.
     if [ "$KERNEL" = "Darwin" ]; then
+        # Copy libraries where delocate-wheel can find them.
+        # In Linux we use LD_LIBRARY_PATH to avoid this, maybe
+        # DYLD_LIBRARY_PATH would work for macOS.
         cp -r $OPENSSL_PREFIX/*.dylib /usr/local/lib
         cp -r $LIBSSH2_PREFIX/lib/*.dylib /usr/local/lib
         cp -r $FILENAME/*.dylib /usr/local/lib
-    else
-        cp -r $PREFIX/lib64/*.so* /usr/local/lib
     fi
     # we're done building dependencies, cibuildwheel action will take over
     exit 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/__init__.py 
new/pygit2-1.11.1/pygit2/__init__.py
--- old/pygit2-1.10.1/pygit2/__init__.py        2021-09-17 13:25:19.987411300 
+0200
+++ new/pygit2-1.11.1/pygit2/__init__.py        2022-10-19 09:26:54.059615100 
+0200
@@ -32,7 +32,7 @@
 # High level API
 from .blame import Blame, BlameHunk
 from .callbacks import git_clone_options, git_fetch_options, get_credentials
-from .callbacks import Payload, RemoteCallbacks
+from .callbacks import Payload, RemoteCallbacks, CheckoutCallbacks, 
StashApplyCallbacks
 from .config import Config
 from .credentials import *
 from .errors import check_error, Passthrough
@@ -84,6 +84,25 @@
 GIT_FETCH_PRUNE                = C.GIT_FETCH_PRUNE
 GIT_FETCH_NO_PRUNE             = C.GIT_FETCH_NO_PRUNE
 
+# GIT_CHECKOUT_NOTIFY_*
+GIT_CHECKOUT_NOTIFY_NONE       : int = C.GIT_CHECKOUT_NOTIFY_NONE
+GIT_CHECKOUT_NOTIFY_CONFLICT   : int = C.GIT_CHECKOUT_NOTIFY_CONFLICT
+GIT_CHECKOUT_NOTIFY_DIRTY      : int = C.GIT_CHECKOUT_NOTIFY_DIRTY
+GIT_CHECKOUT_NOTIFY_UPDATED    : int = C.GIT_CHECKOUT_NOTIFY_UPDATED
+GIT_CHECKOUT_NOTIFY_UNTRACKED  : int = C.GIT_CHECKOUT_NOTIFY_UNTRACKED
+GIT_CHECKOUT_NOTIFY_IGNORED    : int = C.GIT_CHECKOUT_NOTIFY_IGNORED
+GIT_CHECKOUT_NOTIFY_ALL        : int = C.GIT_CHECKOUT_NOTIFY_ALL
+
+# GIT_STASH_APPLY_PROGRESS_*
+GIT_STASH_APPLY_PROGRESS_NONE               : int = 
C.GIT_STASH_APPLY_PROGRESS_NONE
+GIT_STASH_APPLY_PROGRESS_LOADING_STASH      : int = 
C.GIT_STASH_APPLY_PROGRESS_LOADING_STASH
+GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX      : int = 
C.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX
+GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED   : int = 
C.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED
+GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED  : int = 
C.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED
+GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED : int = 
C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED
+GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED  : int = 
C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED
+GIT_STASH_APPLY_PROGRESS_DONE               : int = 
C.GIT_STASH_APPLY_PROGRESS_DONE
+
 # libgit version tuple
 LIBGIT2_VER = (LIBGIT2_VER_MAJOR, LIBGIT2_VER_MINOR, LIBGIT2_VER_REVISION)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/_build.py 
new/pygit2-1.11.1/pygit2/_build.py
--- old/pygit2-1.10.1/pygit2/_build.py  2022-08-28 11:14:43.573393300 +0200
+++ new/pygit2-1.11.1/pygit2/_build.py  2022-11-09 08:09:05.476718700 +0100
@@ -35,7 +35,7 @@
 #
 # The version number of pygit2
 #
-__version__ = '1.10.1'
+__version__ = '1.11.1'
 
 
 #
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/_pygit2.pyi 
new/pygit2-1.11.1/pygit2/_pygit2.pyi
--- old/pygit2-1.10.1/pygit2/_pygit2.pyi        2022-08-24 10:35:14.088508000 
+0200
+++ new/pygit2-1.11.1/pygit2/_pygit2.pyi        2022-10-19 09:26:54.059615100 
+0200
@@ -300,6 +300,8 @@
     path: str
     raw_path: bytes
     size: int
+    @staticmethod
+    def from_c(bytes) -> DiffFile: ...
 
 class DiffHunk:
     header: str
@@ -449,7 +451,7 @@
     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 applies(self, diff: Diff, location: int = GIT_APPLY_LOCATION_INDEX) -> 
bool: ...
+    def applies(self, diff: Diff, location: int = GIT_APPLY_LOCATION_INDEX, 
raise_error: bool = False) -> bool: ...
     def apply(self, diff: Diff, location: int = GIT_APPLY_LOCATION_WORKDIR) -> 
None: ...
     def cherrypick(self, id: _OidArg) -> None: ...
     def compress_references(self) -> None: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/callbacks.py 
new/pygit2-1.11.1/pygit2/callbacks.py
--- old/pygit2-1.10.1/pygit2/callbacks.py       2022-01-29 08:18:52.169225500 
+0100
+++ new/pygit2-1.11.1/pygit2/callbacks.py       2022-11-06 12:28:05.708092000 
+0100
@@ -65,12 +65,13 @@
 # Standard Library
 from contextlib import contextmanager
 from functools import wraps
+from typing import Optional
 
 # pygit2
-from ._pygit2 import Oid
+from ._pygit2 import Oid, DiffFile, GIT_CHECKOUT_SAFE, 
GIT_CHECKOUT_RECREATE_MISSING
 from .errors import check_error, Passthrough
 from .ffi import ffi, C
-from .utils import maybe_string, to_bytes
+from .utils import maybe_string, to_bytes, ptr_to_bytes, StrArray
 
 
 #
@@ -89,6 +90,10 @@
         if error_code == C.GIT_EUSER:
             assert self._stored_exception is not None
             raise self._stored_exception
+        elif self._stored_exception is not None:
+            # A callback mapped to a C function returning void
+            # might still have raised an exception.
+            raise self._stored_exception
 
         check_error(error_code)
 
@@ -210,6 +215,84 @@
         """
 
 
+class CheckoutCallbacks(Payload):
+    """Base class for pygit2 checkout callbacks.
+
+    Inherit from this class and override the callbacks that you want to use
+    in your class, which you can then pass to checkout operations.
+    """
+
+    def __init__(self):
+        super().__init__()
+
+    def checkout_notify_flags(self) -> int:
+        """
+        Returns a bit mask of the notifications to receive from a checkout
+        (GIT_CHECKOUT_NOTIFY values combined with bitwise OR).
+
+        By default, if you override `checkout_notify`, all notifications will
+        be enabled. You can fine tune the notification types to enable by
+        overriding `checkout_notify_flags`.
+
+        Please note that the flags are only sampled once when checkout begins.
+        You cannot change the flags while a checkout is in progress.
+        """
+        if type(self).checkout_notify == CheckoutCallbacks.checkout_notify:
+            # If the user hasn't overridden the notify function,
+            # filter out all notifications.
+            return C.GIT_CHECKOUT_NOTIFY_NONE
+        else:
+            # If the user provides their own notify function,
+            # enable all notifications by default.
+            return C.GIT_CHECKOUT_NOTIFY_ALL
+
+    def checkout_notify(self,
+        why: int,
+        path: str,
+        baseline: Optional[DiffFile],
+        target: Optional[DiffFile],
+        workdir: Optional[DiffFile]
+    ):
+        """
+        Checkout will invoke an optional notification callback for
+        certain cases - you pick which ones via `checkout_notify_flags`.
+
+        Raising an exception from this callback will cancel the checkout.
+        The exception will be propagated back and raised by the
+        Repository.checkout_... call.
+
+        Notification callbacks are made prior to modifying any files on disk,
+        so canceling on any notification will still happen prior to any files
+        being modified.
+        """
+        pass
+
+    def checkout_progress(self, path: str, completed_steps: int, total_steps: 
int):
+        """
+        Optional callback to notify the consumer of checkout progress.
+        """
+        pass
+
+
+class StashApplyCallbacks(CheckoutCallbacks):
+    """Base class for pygit2 stash apply callbacks.
+
+    Inherit from this class and override the callbacks that you want to use
+    in your class, which you can then pass to stash apply or pop operations.
+    """
+
+    def stash_apply_progress(self, progress: int):
+        """
+        Stash application progress notification function.
+
+        `progress` is a GIT_STASH_APPLY_PROGRESS constant.
+
+        Raising an exception from this callback will abort the stash
+        application.
+        """
+        pass
+
+
 #
 # The context managers below wrap the calls to libgit2 functions, which them in
 # turn call to callbacks defined later in this module. These context managers
@@ -334,7 +417,7 @@
             # then libgit2 will behave as if there was no callback set in the
             # first place.
             return C.GIT_PASSTHROUGH
-        except Exception as e:
+        except BaseException as e:
             # Keep the exception to be re-raised later, and inform libgit2 that
             # the user defined callback has failed.
             data._stored_exception = e
@@ -343,6 +426,26 @@
     return ffi.def_extern()(wrapper)
 
 
+def libgit2_callback_void(f):
+    @wraps(f)
+    def wrapper(*args):
+        data = ffi.from_handle(args[-1])
+        args = args[:-1] + (data,)
+        try:
+            f(*args)
+        except Passthrough:
+            # A user defined callback can raise Passthrough to decline to act;
+            # then libgit2 will behave as if there was no callback set in the
+            # first place.
+            pass  # Function returns void
+        except BaseException as e:
+            # Keep the exception to be re-raised later
+            data._stored_exception = e
+            pass  # Function returns void, so we can't do much here.
+
+    return ffi.def_extern()(wrapper)
+
+
 @libgit2_callback
 def _certificate_cb(cert_i, valid, host, data):
     # We want to simulate what should happen if libgit2 supported pass-through
@@ -384,7 +487,7 @@
     if not push_update_reference:
         return 0
 
-    refname = ffi.string(ref)
+    refname = maybe_string(ref)
     message = maybe_string(msg)
     push_update_reference(refname, message)
     return 0
@@ -503,3 +606,148 @@
     check_error(err)
 
     return ccred
+
+
+#
+# Checkout callbacks
+#
+
+@libgit2_callback
+def _checkout_notify_cb(why, path_cstr, baseline, target, workdir, data: 
CheckoutCallbacks):
+    pypath = maybe_string(path_cstr)
+    pybaseline = DiffFile.from_c(ptr_to_bytes(baseline))
+    pytarget = DiffFile.from_c(ptr_to_bytes(target))
+    pyworkdir = DiffFile.from_c(ptr_to_bytes(workdir))
+
+    try:
+        data.checkout_notify(why, pypath, pybaseline, pytarget, pyworkdir)
+    except Passthrough:
+        # Unlike most other operations with optional callbacks, checkout
+        # doesn't support the GIT_PASSTHROUGH return code, so we must bypass
+        # libgit2_callback's error handling and return 0 explicitly here.
+        pass
+
+    # If the user's callback has raised any other exception type,
+    # it's caught by the libgit2_callback decorator by now.
+    # So, return success code to libgit2.
+    return 0
+
+
+@libgit2_callback_void
+def _checkout_progress_cb(path, completed_steps, total_steps, data: 
CheckoutCallbacks):
+    data.checkout_progress(maybe_string(path), completed_steps, total_steps)
+
+
+def _git_checkout_options(callbacks=None, strategy=None, directory=None, 
paths=None, c_checkout_options_ptr=None):
+    if callbacks is None:
+        payload = CheckoutCallbacks()
+    else:
+        payload = callbacks
+
+    # Get handle to payload
+    handle = ffi.new_handle(payload)
+
+    # Create the options struct to pass
+    if not c_checkout_options_ptr:
+        opts = ffi.new('git_checkout_options *')
+    else:
+        opts = c_checkout_options_ptr
+    check_error(C.git_checkout_init_options(opts, 1))
+
+    # References we need to keep to strings and so forth
+    refs = [handle]
+
+    # pygit2's default is SAFE | RECREATE_MISSING
+    opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING
+    # and go through the arguments to see what the user wanted
+    if strategy:
+        opts.checkout_strategy = strategy
+
+    if directory:
+        target_dir = ffi.new('char[]', to_bytes(directory))
+        refs.append(target_dir)
+        opts.target_directory = target_dir
+
+    if paths:
+        strarray = StrArray(paths)
+        refs.append(strarray)
+        opts.paths = strarray.array[0]
+
+    # If we want to receive any notifications, set up notify_cb in the options
+    notify_flags = payload.checkout_notify_flags()
+    if notify_flags != C.GIT_CHECKOUT_NOTIFY_NONE:
+        opts.notify_cb = C._checkout_notify_cb
+        opts.notify_flags = notify_flags
+        opts.notify_payload = handle
+
+    # Set up progress callback if the user has provided their own
+    if type(payload).checkout_progress != CheckoutCallbacks.checkout_progress:
+        opts.progress_cb = C._checkout_progress_cb
+        opts.progress_payload = handle
+
+    # Give back control
+    payload.checkout_options = opts
+    payload._ffi_handle = handle
+    payload._refs = refs
+    payload._stored_exception = None
+    return payload
+
+
+@contextmanager
+def git_checkout_options(callbacks=None, strategy=None, directory=None, 
paths=None):
+    yield _git_checkout_options(callbacks=callbacks, strategy=strategy, 
directory=directory, paths=paths)
+
+
+#
+# Stash callbacks
+#
+
+@libgit2_callback
+def _stash_apply_progress_cb(progress: int, data: StashApplyCallbacks):
+    try:
+        data.stash_apply_progress(progress)
+    except Passthrough:
+        # Unlike most other operations with optional callbacks, stash apply
+        # doesn't support the GIT_PASSTHROUGH return code, so we must bypass
+        # libgit2_callback's error handling and return 0 explicitly here.
+        pass
+
+    # If the user's callback has raised any other exception type,
+    # it's caught by the libgit2_callback decorator by now.
+    # So, return success code to libgit2.
+    return 0
+
+
+@contextmanager
+def git_stash_apply_options(callbacks=None, reinstate_index=False, 
strategy=None, directory=None, paths=None):
+    if callbacks is None:
+        callbacks = StashApplyCallbacks()
+
+    # Set up stash options
+    # TODO: git_stash_apply_init_options is deprecated (along with a bunch of 
other git_XXX_init_options functions)
+    stash_apply_options = ffi.new('git_stash_apply_options *')
+    check_error(C.git_stash_apply_init_options(stash_apply_options, 1))
+
+    flags = reinstate_index * C.GIT_STASH_APPLY_REINSTATE_INDEX
+    stash_apply_options.flags = flags
+
+    # Now set up checkout options
+    c_checkout_options_ptr = 
ffi.addressof(stash_apply_options.checkout_options)
+    payload = _git_checkout_options(
+        callbacks=callbacks,
+        strategy=strategy,
+        directory=directory,
+        paths=paths,
+        c_checkout_options_ptr=c_checkout_options_ptr,
+    )
+    assert payload == callbacks
+    assert payload.checkout_options == c_checkout_options_ptr
+
+    # Set up stash progress callback if the user has provided their own
+    if type(callbacks).stash_apply_progress != 
StashApplyCallbacks.stash_apply_progress:
+        stash_apply_options.progress_cb = C._stash_apply_progress_cb
+        stash_apply_options.progress_payload = payload._ffi_handle
+
+    # Give back control
+    payload.stash_apply_options = stash_apply_options
+    yield payload
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/decl/callbacks.h 
new/pygit2-1.11.1/pygit2/decl/callbacks.h
--- old/pygit2-1.10.1/pygit2/decl/callbacks.h   2021-06-19 11:40:24.646733300 
+0200
+++ new/pygit2-1.11.1/pygit2/decl/callbacks.h   2022-10-19 09:26:54.059615100 
+0200
@@ -43,3 +43,25 @@
        const git_oid *a,
        const git_oid *b,
        void *data);
+
+/* Checkout */
+
+extern "Python" int _checkout_notify_cb(
+    git_checkout_notify_t why,
+    const char *path,
+    const git_diff_file *baseline,
+    const git_diff_file *target,
+    const git_diff_file *workdir,
+    void *payload);
+
+extern "Python" void _checkout_progress_cb(
+    const char *path,
+    size_t completed_steps,
+    size_t total_steps,
+    void *payload);
+
+/* Stash */
+
+extern "Python" int _stash_apply_progress_cb(
+    git_stash_apply_progress_t progress,
+    void *payload);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/decl/pack.h 
new/pygit2-1.11.1/pygit2/decl/pack.h
--- old/pygit2-1.10.1/pygit2/decl/pack.h        2021-06-19 11:40:24.646733300 
+0200
+++ new/pygit2-1.11.1/pygit2/decl/pack.h        2022-10-06 18:14:22.057606500 
+0200
@@ -15,4 +15,4 @@
 int git_packbuilder_write(git_packbuilder *pb, const char *path, unsigned int 
mode, git_indexer_progress_cb progress_cb, void *progress_cb_payload);
 uint32_t git_packbuilder_written(git_packbuilder *pb);
 
-unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n);
\ No newline at end of file
+unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/remote.py 
new/pygit2-1.11.1/pygit2/remote.py
--- old/pygit2-1.10.1/pygit2/remote.py  2022-04-26 20:55:32.101869000 +0200
+++ new/pygit2-1.11.1/pygit2/remote.py  2022-11-06 12:28:47.396510000 +0100
@@ -295,20 +295,12 @@
             C.git_strarray_free(names)
 
     def __iter__(self):
-        names = ffi.new('git_strarray *')
-
-        try:
-            err = C.git_remote_list(names, self._repo._repo)
+        cremote = ffi.new('git_remote **')
+        for name in self._ffi_names():
+            err = C.git_remote_lookup(cremote, self._repo._repo, name)
             check_error(err)
 
-            cremote = ffi.new('git_remote **')
-            for i in range(names.count):
-                err = C.git_remote_lookup(cremote, self._repo._repo, 
names.strings[i])
-                check_error(err)
-
-                yield Remote(self._repo, cremote[0])
-        finally:
-            C.git_strarray_free(names)
+            yield Remote(self._repo, cremote[0])
 
     def __getitem__(self, name):
         if isinstance(name, int):
@@ -320,6 +312,23 @@
 
         return Remote(self._repo, cremote[0])
 
+    def _ffi_names(self):
+        names = ffi.new('git_strarray *')
+
+        try:
+            err = C.git_remote_list(names, self._repo._repo)
+            check_error(err)
+
+            for i in range(names.count):
+                yield names.strings[i]
+        finally:
+            C.git_strarray_free(names)
+
+    def names(self):
+        """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):
         """Create a new remote with the given name and url. Returns a <Remote>
         object.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/repository.py 
new/pygit2-1.11.1/pygit2/repository.py
--- old/pygit2-1.10.1/pygit2/repository.py      2022-03-14 09:15:20.348721500 
+0100
+++ new/pygit2-1.11.1/pygit2/repository.py      2022-10-23 20:23:35.574572000 
+0200
@@ -40,7 +40,7 @@
 from ._pygit2 import Reference, Tree, Commit, Blob, Signature
 from ._pygit2 import InvalidSpecError
 
-from .callbacks import git_fetch_options
+from .callbacks import git_checkout_options, git_fetch_options, 
git_stash_apply_options
 from .config import Config
 from .errors import check_error
 from .ffi import ffi, C
@@ -318,59 +318,35 @@
     #
     # Checkout
     #
-    @staticmethod
-    def _checkout_args_to_options(strategy=None, directory=None, paths=None):
-        # Create the options struct to pass
-        copts = ffi.new('git_checkout_options *')
-        check_error(C.git_checkout_init_options(copts, 1))
-
-        # References we need to keep to strings and so forth
-        refs = []
-
-        # pygit2's default is SAFE | RECREATE_MISSING
-        copts.checkout_strategy = GIT_CHECKOUT_SAFE | 
GIT_CHECKOUT_RECREATE_MISSING
-        # and go through the arguments to see what the user wanted
-        if strategy:
-            copts.checkout_strategy = strategy
-
-        if directory:
-            target_dir = ffi.new('char[]', to_bytes(directory))
-            refs.append(target_dir)
-            copts.target_directory = target_dir
-
-        if paths:
-            strarray = StrArray(paths)
-            refs.append(strarray)
-            copts.paths = strarray.array[0]
-
-        return copts, refs
 
     def checkout_head(self, **kwargs):
         """Checkout HEAD
 
         For arguments, see Repository.checkout().
         """
-        copts, refs = Repository._checkout_args_to_options(**kwargs)
-        check_error(C.git_checkout_head(self._repo, copts))
+        with git_checkout_options(**kwargs) as payload:
+            err = C.git_checkout_head(self._repo, payload.checkout_options)
+            payload.check_error(err)
 
     def checkout_index(self, index=None, **kwargs):
         """Checkout the given index or the repository's index
 
         For arguments, see Repository.checkout().
         """
-        copts, refs = Repository._checkout_args_to_options(**kwargs)
-        check_error(C.git_checkout_index(self._repo, index._index if index 
else ffi.NULL, copts))
+        with git_checkout_options(**kwargs) as payload:
+            err = C.git_checkout_index(self._repo, index._index if index else 
ffi.NULL, payload.checkout_options)
+            payload.check_error(err)
 
     def checkout_tree(self, treeish, **kwargs):
         """Checkout the given treeish
 
         For arguments, see Repository.checkout().
         """
-        copts, refs = Repository._checkout_args_to_options(**kwargs)
-        cptr = ffi.new('git_object **')
-        ffi.buffer(cptr)[:] = treeish._pointer[:]
-
-        check_error(C.git_checkout_tree(self._repo, cptr[0], copts))
+        with git_checkout_options(**kwargs) as payload:
+            cptr = ffi.new('git_object **')
+            ffi.buffer(cptr)[:] = treeish._pointer[:]
+            err = C.git_checkout_tree(self._repo, cptr[0], 
payload.checkout_options)
+            payload.check_error(err)
 
     def checkout(self, refname=None, **kwargs):
         """
@@ -397,6 +373,15 @@
             A list of files to checkout from the given reference.
             If paths is provided, HEAD will not be set to the reference.
 
+        callbacks : CheckoutCallbacks
+            Optional. Supply a `callbacks` object to get information about
+            conflicted files, updated files, etc. as the checkout is being
+            performed. The callbacks can also abort the checkout prematurely.
+
+            The callbacks should be an object which inherits from
+            `pyclass:CheckoutCallbacks`. It should implement the callbacks
+            as overridden methods.
+
         Examples:
 
         * To checkout from the HEAD, just pass 'HEAD'::
@@ -1092,19 +1077,6 @@
 
         return Oid(raw=bytes(ffi.buffer(coid)[:]))
 
-    @staticmethod
-    def _stash_args_to_options(reinstate_index=False, **kwargs):
-        stash_opts = ffi.new('git_stash_apply_options *')
-        check_error(C.git_stash_apply_init_options(stash_opts, 1))
-
-        flags = reinstate_index * C.GIT_STASH_APPLY_REINSTATE_INDEX
-        stash_opts.flags = flags
-
-        copts, refs = Repository._checkout_args_to_options(**kwargs)
-        stash_opts.checkout_options = copts[0]
-
-        return stash_opts
-
     def stash_apply(self, index=0, **kwargs):
         """
         Apply a stashed state in the stash list to the working directory.
@@ -1118,6 +1090,18 @@
         reinstate_index : bool
             Try to reinstate stashed changes to the index.
 
+        callbacks : StashApplyCallbacks
+            Optional. Supply a `callbacks` object to get information about
+            the progress of the stash application as it is being performed.
+
+            The callbacks should be an object which inherits from
+            `pyclass:StashApplyCallbacks`. It should implement the callbacks
+            as overridden methods.
+
+            Note that this class inherits from CheckoutCallbacks, so you can
+            also get information from the checkout part of the unstashing
+            process via the callbacks.
+
         The checkout options may be customized using the same arguments taken 
by
         Repository.checkout().
 
@@ -1127,8 +1111,9 @@
             >>> repo.stash(repo.default_signature(), 'WIP: stashing')
             >>> repo.stash_apply(strategy=GIT_CHECKOUT_ALLOW_CONFLICTS)
         """
-        stash_opts = Repository._stash_args_to_options(**kwargs)
-        check_error(C.git_stash_apply(self._repo, index, stash_opts))
+        with git_stash_apply_options(**kwargs) as payload:
+            err = C.git_stash_apply(self._repo, index, 
payload.stash_apply_options)
+            payload.check_error(err)
 
     def stash_drop(self, index=0):
         """
@@ -1147,8 +1132,9 @@
 
         For arguments, see Repository.stash_apply().
         """
-        stash_opts = Repository._stash_args_to_options(**kwargs)
-        check_error(C.git_stash_pop(self._repo, index, stash_opts))
+        with git_stash_apply_options(**kwargs) 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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/pygit2/utils.py 
new/pygit2-1.11.1/pygit2/utils.py
--- old/pygit2-1.10.1/pygit2/utils.py   2021-09-17 13:25:19.995411400 +0200
+++ new/pygit2-1.11.1/pygit2/utils.py   2022-10-19 09:26:54.059615100 +0200
@@ -62,6 +62,16 @@
     raise TypeError('unexpected type "%s"' % repr(s))
 
 
+def ptr_to_bytes(ptr_cdata):
+    """
+    Convert a pointer coming from C code (<cdata 'some_type *'>)
+    to a byte buffer containing the address that the pointer refers to.
+    """
+
+    pp = ffi.new('void **', ptr_cdata)
+    return bytes(ffi.buffer(pp)[:])
+
+
 def strarray_to_strings(arr):
     l = [None] * arr.count
     for i in range(arr.count):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/requirements.txt 
new/pygit2-1.11.1/requirements.txt
--- old/pygit2-1.10.1/requirements.txt  2022-02-23 13:39:10.615412500 +0100
+++ new/pygit2-1.11.1/requirements.txt  2022-10-23 11:28:21.044473400 +0200
@@ -1,2 +1 @@
-cached-property; python_version < "3.8"
 cffi>=1.9.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/setup.py new/pygit2-1.11.1/setup.py
--- old/pygit2-1.10.1/setup.py  2022-03-20 21:36:06.725472500 +0100
+++ new/pygit2-1.11.1/setup.py  2022-10-23 11:28:21.044473400 +0200
@@ -75,10 +75,10 @@
     "Intended Audience :: Developers",
     "Programming Language :: Python",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: Implementation :: PyPy",
     "Programming Language :: Python :: Implementation :: CPython",
     "Topic :: Software Development :: Version Control"]
@@ -152,7 +152,7 @@
     cffi_modules=['pygit2/_run.py:ffi'],
     ext_modules=ext_modules,
     # Requirements
-    python_requires='>=3.7',
+    python_requires='>=3.8',
     setup_requires=['cffi>=1.9.1'],
     install_requires=install_requires,
     # URLs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/src/diff.c new/pygit2-1.11.1/src/diff.c
--- old/pygit2-1.10.1/src/diff.c        2022-03-14 09:11:42.426751900 +0100
+++ new/pygit2-1.11.1/src/diff.c        2022-10-19 09:26:54.059615100 +0200
@@ -179,6 +179,31 @@
     PyObject_Del(self);
 }
 
+PyDoc_STRVAR(DiffFile_from_c__doc__, "Method exposed for _checkout_notify_cb 
to hook into");
+
+/* Expose wrap_diff_file to python so we can call it in callbacks.py. */
+PyObject *
+DiffFile_from_c(DiffFile *dummy, PyObject *py_diff_file_ptr)
+{
+    const git_diff_file *diff_file;
+    char *buffer;
+    Py_ssize_t length;
+
+    /* Here we need to do the opposite conversion from the _pointer getters */
+    if (PyBytes_AsStringAndSize(py_diff_file_ptr, &buffer, &length))
+        return NULL;
+
+    if (length != sizeof(git_diff_file *)) {
+        PyErr_SetString(PyExc_TypeError, "passed value is not a pointer");
+        return NULL;
+    }
+
+    /* the "buffer" contains the pointer */
+    diff_file = *((const git_diff_file **) buffer);
+
+    return wrap_diff_file(diff_file);
+}
+
 PyMemberDef DiffFile_members[] = {
     MEMBER(DiffFile, id, T_OBJECT, "Oid of the item."),
     MEMBER(DiffFile, path, T_STRING, "Path to the entry."),
@@ -189,6 +214,10 @@
     {NULL}
 };
 
+PyMethodDef DiffFile_methods[] = {
+    METHOD(DiffFile, from_c, METH_STATIC | METH_O),
+    {NULL},
+};
 
 PyDoc_STRVAR(DiffFile__doc__, "DiffFile object.");
 
@@ -220,7 +249,7 @@
     0,                                         /* tp_weaklistoffset */
     0,                                         /* tp_iter           */
     0,                                         /* tp_iternext       */
-    0,                                         /* tp_methods        */
+    DiffFile_methods,                          /* tp_methods        */
     DiffFile_members,                          /* tp_members        */
     0,                                         /* tp_getset         */
     0,                                         /* tp_base           */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/src/repository.c 
new/pygit2-1.11.1/src/repository.c
--- old/pygit2-1.10.1/src/repository.c  2022-07-25 09:42:29.865465900 +0200
+++ new/pygit2-1.11.1/src/repository.c  2022-10-10 08:59:10.300657700 +0200
@@ -2301,7 +2301,7 @@
 }
 
 PyDoc_STRVAR(Repository_applies__doc__,
-  "applies(diff: Diff, location: int = GIT_APPLY_LOCATION_INDEX) -> bool\n"
+  "applies(diff: Diff, location: int = GIT_APPLY_LOCATION_INDEX, raise_error: 
bool = False) -> bool\n"
   "\n"
   "Tests if the given patch will apply to HEAD, without writing it.\n"
   "\n"
@@ -2313,6 +2313,10 @@
   "location\n"
   "    The location to apply: GIT_APPLY_LOCATION_WORKDIR,\n"
   "    GIT_APPLY_LOCATION_INDEX (default), or GIT_APPLY_LOCATION_BOTH.\n"
+  "\n"
+  "raise_error\n"
+  "    If the patch doesn't apply, raise an exception containing more 
details\n"
+  "    about the failure instead of returning False.\n"
   );
 
 PyObject *
@@ -2320,19 +2324,24 @@
 {
     Diff *py_diff;
     int location = GIT_APPLY_LOCATION_INDEX;
+    int raise_error = 0;
     git_apply_options options = GIT_APPLY_OPTIONS_INIT;
     options.flags |= GIT_APPLY_CHECK;
 
-    char* keywords[] = {"diff", "location", NULL};
+    char* keywords[] = {"diff", "location", "raise_error", NULL};
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|i", keywords,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|ii", keywords,
                                      &DiffType, &py_diff,
-                                     &location))
+                                     &location, &raise_error))
         return NULL;
 
     int err = git_apply(self->repo, ((Diff*)py_diff)->diff, location, 
&options);
-    if (err != 0)
-        Py_RETURN_FALSE;
+    if (err != 0) {
+        if (raise_error)
+            return Error_set(err);
+        else
+            Py_RETURN_FALSE;
+    }
 
     Py_RETURN_TRUE;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/src/signature.c 
new/pygit2-1.11.1/src/signature.c
--- old/pygit2-1.10.1/src/signature.c   2022-08-23 11:36:41.305126400 +0200
+++ new/pygit2-1.11.1/src/signature.c   2022-10-30 15:53:06.262505000 +0100
@@ -81,14 +81,16 @@
 void
 Signature_dealloc(Signature *self)
 {
-    /* self->obj is the owner of the git_signature, so we musn't free it */
+    /* self->obj is the owner of the git_signature C structure, so we musn't 
free it */
     if (self->obj) {
         Py_CLEAR(self->obj);
     } else {
         git_signature_free((git_signature *) self->signature);
-        free(self->encoding);
     }
 
+    /* we own self->encoding */
+    free(self->encoding);
+
     PyObject_Del(self);
 }
 
@@ -246,6 +248,7 @@
         encoding = to_unicode(self->encoding, self->encoding, NULL);
     } else {
         encoding = Py_None;
+        Py_INCREF(Py_None);
     }
 
     str = PyUnicode_FromFormat(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/test/test_apply_diff.py 
new/pygit2-1.11.1/test/test_apply_diff.py
--- old/pygit2-1.10.1/test/test_apply_diff.py   2022-03-20 09:43:45.006981400 
+0100
+++ new/pygit2-1.11.1/test/test_apply_diff.py   2022-10-10 08:59:10.300657700 
+0200
@@ -59,6 +59,18 @@
     # Return the diff
     return pygit2.Diff.parse_diff(patch)
 
+@pytest.fixture
+def foreign_patch_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
++++ b/this_file_does_not_exist
+@@ -1 +1 @@
+-a contents 2
++a contents
+"""
+    return pygit2.Diff.parse_diff(patch_contents)
+
 
 def test_apply_type_error(testrepo):
     # Check apply type error
@@ -127,3 +139,15 @@
     assert not testrepo.applies(patch_diff, pygit2.GIT_APPLY_LOCATION_WORKDIR)
     assert not testrepo.applies(patch_diff, pygit2.GIT_APPLY_LOCATION_INDEX)
 
+def test_applies_error(testrepo, old_content, patch_diff, foreign_patch_diff):
+    # 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):
+        testrepo.applies(foreign_patch_diff, pygit2.GIT_APPLY_LOCATION_BOTH, 
raise_error=True)
+
+    # Apply a valid patch
+    testrepo.apply(patch_diff, pygit2.GIT_APPLY_LOCATION_BOTH)
+
+    # Ensure it can't be applied again and we get an exception about it (due 
to raise_error)
+    with pytest.raises(pygit2.GitError):
+        testrepo.applies(patch_diff, pygit2.GIT_APPLY_LOCATION_BOTH, 
raise_error=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/test/test_remote.py 
new/pygit2-1.11.1/test/test_remote.py
--- old/pygit2-1.10.1/test/test_remote.py       2022-05-05 16:36:05.882153000 
+0200
+++ new/pygit2-1.11.1/test/test_remote.py       2022-10-30 16:57:53.820178300 
+0100
@@ -162,6 +162,7 @@
     name = 'upstream'
     url = 'https://github.com/libgit2/pygit2.git'
     remote = testrepo.remotes.create(name, url)
+    assert remote.name in testrepo.remotes.names()
     assert remote.name in [x.name for x in testrepo.remotes]
 
 @utils.requires_network
@@ -170,7 +171,6 @@
     remote = testrepo.remotes[0]
 
     refs = remote.ls_remotes()
-
     assert refs
 
     # Check that a known ref is returned.
@@ -187,6 +187,7 @@
     name = 'upstream'
     url = 'https://github.com/libgit2/pygit2.git'
     remote = testrepo.remotes.create(name, url)
+    assert remote.name in testrepo.remotes.names()
     assert remote.name in [x.name for x in testrepo.remotes]
 
 @utils.refcount
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/test/test_repository.py 
new/pygit2-1.11.1/test/test_repository.py
--- old/pygit2-1.10.1/test/test_repository.py   2022-05-05 16:36:05.882153000 
+0200
+++ new/pygit2-1.11.1/test/test_repository.py   2022-11-06 12:29:06.013696400 
+0100
@@ -77,6 +77,75 @@
     assert 'new' in head.tree
     assert 'bye.txt' not in testrepo.status()
 
+def test_checkout_callbacks(testrepo):
+    ref_i18n = testrepo.lookup_reference('refs/heads/i18n')
+
+    class MyCheckoutCallbacks(pygit2.CheckoutCallbacks):
+        def __init__(self):
+            super().__init__()
+            self.conflicting_paths = set()
+            self.updated_paths = set()
+
+        def checkout_notify_flags(self) -> int:
+            return pygit2.GIT_CHECKOUT_NOTIFY_CONFLICT | 
pygit2.GIT_CHECKOUT_NOTIFY_UPDATED
+
+        def checkout_notify(self, why, path, baseline, target, workdir):
+            if why == pygit2.GIT_CHECKOUT_NOTIFY_CONFLICT:
+                self.conflicting_paths.add(path)
+            elif why == pygit2.GIT_CHECKOUT_NOTIFY_UPDATED:
+                self.updated_paths.add(path)
+
+    # 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
+
+    # checkout i18n with GIT_CHECKOUT_FORCE
+    head = testrepo.head
+    head = testrepo[head.target]
+    assert 'new' not in head.tree
+    callbacks = MyCheckoutCallbacks()
+    testrepo.checkout(ref_i18n, strategy=pygit2.GIT_CHECKOUT_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
+
+def test_checkout_aborted_from_callbacks(testrepo):
+    ref_i18n = testrepo.lookup_reference('refs/heads/i18n')
+
+    def read_bye_txt():
+        return testrepo[testrepo.create_blob_fromworkdir("bye.txt")].data
+
+    s = testrepo.status()
+    assert s == {'bye.txt': pygit2.GIT_STATUS_WT_NEW}
+
+    class MyCheckoutCallbacks(pygit2.CheckoutCallbacks):
+        def __init__(self):
+            super().__init__()
+            self.invoked_times = 0
+
+        def checkout_notify(self, why, path, baseline, target, workdir):
+            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]
+    assert 'new' not in head.tree
+    assert b'bye world\n' == read_bye_txt()
+    callbacks = MyCheckoutCallbacks()
+
+    # checkout i18n with GIT_CHECKOUT_FORCE - callbacks should prevent 
checkout from completing
+    with pytest.raises(InterruptedError):
+        testrepo.checkout(ref_i18n, strategy=pygit2.GIT_CHECKOUT_FORCE, 
callbacks=callbacks)
+
+    assert callbacks.invoked_times == 2
+    assert 'new' not in head.tree
+    assert b'bye world\n' == read_bye_txt()
+
 def test_checkout_branch(testrepo):
     branch_i18n = testrepo.lookup_branch('i18n')
 
@@ -279,6 +348,107 @@
 
     with pytest.raises(KeyError): testrepo.stash_pop()
 
+def test_stash_progress_callback(testrepo):
+    sig = pygit2.Signature(name='Stasher', email='stas...@example.com', 
time=1641000000, offset=0)
+
+    # some changes to working dir
+    with (Path(testrepo.workdir) / 'hello.txt').open('w') as f:
+        f.write('new content')
+
+    # create the stash
+    testrepo.stash(sig, include_untracked=True, message="custom stash message")
+
+    progress_sequence = []
+
+    class MyStashApplyCallbacks(pygit2.StashApplyCallbacks):
+        def stash_apply_progress(self, progress: int):
+            progress_sequence.append(progress)
+
+    # apply the stash
+    testrepo.stash_apply(callbacks=MyStashApplyCallbacks())
+
+    # make sure the callbacks were notified of all the steps
+    assert progress_sequence == [
+        pygit2.GIT_STASH_APPLY_PROGRESS_LOADING_STASH,
+        pygit2.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX,
+        pygit2.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED,
+        pygit2.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED,
+        pygit2.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED,
+        pygit2.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED,
+        pygit2.GIT_STASH_APPLY_PROGRESS_DONE,
+    ]
+
+def test_stash_aborted_from_callbacks(testrepo):
+    sig = pygit2.Signature(name='Stasher', email='stas...@example.com', 
time=1641000000, offset=0)
+
+    # some changes to working dir
+    with (Path(testrepo.workdir) / 'hello.txt').open('w') as f:
+        f.write('new content')
+    with (Path(testrepo.workdir) / 'untracked.txt').open('w') as f:
+        f.write('yo')
+
+    # create the stash
+    testrepo.stash(sig, include_untracked=True, message="custom stash message")
+
+    # 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: int):
+            if progress == pygit2.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED:
+                raise InterruptedError("Stop applying the stash!")
+
+    # attempt to apply and delete the stash; the callbacks will interrupt that
+    with pytest.raises(InterruptedError):
+        testrepo.stash_pop(callbacks=MyStashApplyCallbacks())
+
+    # we interrupted right before the checkout part of the unstashing process,
+    # so the untracked file shouldn't be here
+    assert not (Path(testrepo.workdir) / 'untracked.txt').exists()
+
+    # and hello.txt should be as it is on master
+    with (Path(testrepo.workdir) / 'hello.txt').open('r') as f:
+        assert f.read() == 'hello world\nhola mundo\nbonjour le monde\n'
+
+    # and since we didn't let stash_pop run to completion, the stash itself 
should still be here
+    repo_stashes = testrepo.listall_stashes()
+    assert 1 == len(repo_stashes)
+    assert repo_stashes[0].message == "On master: custom stash message"
+
+def test_stash_apply_checkout_options(testrepo):
+    sig = pygit2.Signature(name='Stasher', email='stas...@example.com', 
time=1641000000, offset=0)
+
+    hello_txt = Path(testrepo.workdir) / 'hello.txt'
+
+    # some changes to working dir
+    with hello_txt.open('w') as f:
+        f.write('stashed content')
+
+    # create the stash
+    testrepo.stash(sig, include_untracked=True, message="custom stash message")
+
+    # define callbacks that raise an InterruptedError when checkout detects a 
conflict
+    class MyStashApplyCallbacks(pygit2.StashApplyCallbacks):
+        def checkout_notify(self, why, path, baseline, target, workdir):
+            if why == pygit2.GIT_CHECKOUT_NOTIFY_CONFLICT:
+                raise InterruptedError("Applying the stash would create a 
conflict")
+
+    # overwrite hello.txt so that applying the stash would create a conflict
+    with hello_txt.open('w') as f:
+        f.write('conflicting content')
+
+    # apply the stash with the default (safe) strategy;
+    # the callbacks should detect a conflict on checkout
+    with pytest.raises(InterruptedError):
+        testrepo.stash_apply(strategy=pygit2.GIT_CHECKOUT_SAFE, 
callbacks=MyStashApplyCallbacks())
+
+    # hello.txt should be intact
+    with hello_txt.open('r') as f: assert f.read() == 'conflicting content'
+
+    # force apply the stash; this should work
+    testrepo.stash_apply(strategy=pygit2.GIT_CHECKOUT_FORCE, 
callbacks=MyStashApplyCallbacks())
+    with hello_txt.open('r') as f: assert f.read() == 'stashed content'
+
+
 def test_revert(testrepo):
     master = testrepo.head.peel()
     commit_to_revert = testrepo['4ec4389a8068641da2d6578db0419484972284c8']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pygit2-1.10.1/test/utils.py 
new/pygit2-1.11.1/test/utils.py
--- old/pygit2-1.10.1/test/utils.py     2022-04-23 18:14:30.410124800 +0200
+++ new/pygit2-1.11.1/test/utils.py     2022-11-04 09:27:27.316116800 +0100
@@ -30,7 +30,6 @@
 import socket
 import stat
 import sys
-import tarfile
 import zipfile
 
 # Requirements
@@ -112,9 +111,6 @@
         if path.suffix == '.zip':
             with zipfile.ZipFile(path) as zipf:
                 zipf.extractall(self.tmp_path)
-        elif path.suffix == '.tar':
-            with tarfile.open(path) as tar:
-                tar.extractall(self.tmp_path)
         elif path.suffix == '.git':
             shutil.copytree(path, temp_repo_path)
         else:

Reply via email to