Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactors credentials callback on remote to make pushes authenticate #438

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 35 additions & 34 deletions pygit2/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,15 @@ def update_tips(self, refname, old, new):
:param Oid old: the reference's old value
:param Oid new: the reference's new value
"""
pass

def __init__(self, repo, ptr):
"""The constructor is for internal use only"""

self._repo = repo
self._remote = ptr
self._stored_exception = None
self._set_libgit2_callbacks()

def __del__(self):
C.git_remote_free(self._remote)
Expand Down Expand Up @@ -200,47 +202,17 @@ def fetch(self, signature=None, message=None):
Perform a fetch against this remote.
"""

# Get the default callbacks first
defaultcallbacks = ffi.new('git_remote_callbacks *')
err = C.git_remote_init_callbacks(defaultcallbacks, 1)
check_error(err)

# Build custom callback structure
callbacks = ffi.new('git_remote_callbacks *')
callbacks.version = 1
callbacks.sideband_progress = self._sideband_progress_cb
callbacks.transfer_progress = self._transfer_progress_cb
callbacks.update_tips = self._update_tips_cb
callbacks.credentials = self._credentials_cb
# We need to make sure that this handle stays alive
self._self_handle = ffi.new_handle(self)
callbacks.payload = self._self_handle

err = C.git_remote_set_callbacks(self._remote, callbacks)
try:
check_error(err)
except:
self._self_handle = None
raise

if signature:
ptr = signature._pointer[:]
else:
ptr = ffi.NULL

self._stored_exception = None
err = C.git_remote_fetch(self._remote, ptr, to_bytes(message))
if self._stored_exception:
raise self._stored_exception

try:
err = C.git_remote_fetch(self._remote, ptr, to_bytes(message))
if self._stored_exception:
raise self._stored_exception

check_error(err)
finally:
# Even on error, clear stored callbacks and reset to default
self._self_handle = None
err = C.git_remote_set_callbacks(self._remote, defaultcallbacks)
check_error(err)
check_error(err)

return TransferProgress(C.git_remote_stats(self._remote))

Expand Down Expand Up @@ -333,7 +305,13 @@ def push(self, spec, signature=None, message=None):
err = C.git_push_add_refspec(push, to_bytes(spec))
check_error(err)

self._stored_exception = None
err = C.git_push_finish(push)
if self._stored_exception:
# If user-defined callback throws, exception will get lost in
# libgit2 C stack trace. So we swallow the exception on the
# python side, return control to libgit2, then re-throw it here
raise self._stored_exception
check_error(err)

if not C.git_push_unpack_ok(push):
Expand All @@ -357,6 +335,29 @@ def push(self, spec, signature=None, message=None):
finally:
C.git_push_free(push)

def _set_libgit2_callbacks(self):
"""Cause control to transfer back into this code when certain hooked
events occur in libgit2. If user has set callbacks (e.g. credentials),
we transfer control to them."""

# Build libgit2-compatible callback structure
callbacks = ffi.new('git_remote_callbacks *')
err = C.git_remote_init_callbacks(callbacks, 1) # version 1
check_error(err)

callbacks.transfer_progress = self._transfer_progress_cb
callbacks.sideband_progress = self._sideband_progress_cb
callbacks.update_tips = self._update_tips_cb
callbacks.credentials = self._credentials_cb

# libgit2 callbacks back into python pass a void* as their last
# argument. We use this void* to retain a self pointer, allowing
# access back into this object
self._self_handle = ffi.new_handle(self)
callbacks.payload = self._self_handle

err = C.git_remote_set_callbacks(self._remote, callbacks)

# These functions exist to be called by the git_remote as
# callbacks. They proxy the call to whatever the user set

Expand Down