Remote: generalize push()

Move to use git_remote_push() instead of doing the steps ourselves. We
also change to accept a list of refspecs instead of just the one refspec
for the push method.

As part of this, we no longer error out if the server rejected any
updates, as this is a different concern from whether the push itself
failed or not. We do still error out if we attempt to push non-ff
updates.
This commit is contained in:
Carlos Martín Nieto
2014-12-11 15:36:14 +01:00
parent 1dbf94011a
commit f68b266e60
3 changed files with 54 additions and 60 deletions

View File

@@ -1,7 +1,6 @@
typedef ... git_repository;
typedef ... git_remote;
typedef ... git_refspec;
typedef ... git_push;
typedef ... git_cred;
typedef ... git_object;
typedef ... git_tree;
@@ -175,6 +174,11 @@ struct git_remote_callbacks {
typedef struct git_remote_callbacks git_remote_callbacks;
typedef struct {
unsigned int version;
unsigned int pb_parallelism;
} git_push_options;
int git_remote_list(git_strarray *out, git_repository *repo);
int git_remote_lookup(git_remote **out, git_repository *repo, const char *name);
int git_remote_create(
@@ -193,6 +197,7 @@ int git_remote_set_url(git_remote *remote, const char* url);
const char * git_remote_pushurl(const git_remote *remote);
int git_remote_set_pushurl(git_remote *remote, const char* url);
int git_remote_fetch(git_remote *remote, const git_strarray *refspecs, const git_signature *signature, const char *reflog_message);
int git_remote_push(git_remote *remote, git_strarray *refspecs, const git_push_options *opts, const git_signature *signature, const char *reflog_message);
const git_transfer_progress * git_remote_stats(git_remote *remote);
int git_remote_add_push(git_remote *remote, const char *refspec);
int git_remote_add_fetch(git_remote *remote, const char *refspec);
@@ -209,21 +214,6 @@ int git_remote_set_push_refspecs(git_remote *remote, git_strarray *array);
void git_remote_free(git_remote *remote);
int git_push_new(git_push **push, git_remote *remote);
int git_push_add_refspec(git_push *push, const char *refspec);
int git_push_finish(git_push *push);
int git_push_status_foreach(
git_push *push,
int (*cb)(const char *ref, const char *msg, void *data),
void *data);
int git_push_update_tips(
git_push *push,
const git_signature *signature,
const char *reflog_message);
void git_push_free(git_push *push);
const char * git_refspec_src(const git_refspec *refspec);
const char * git_refspec_dst(const git_refspec *refspec);
int git_refspec_force(const git_refspec *refspec);

View File

@@ -121,6 +121,16 @@ class Remote(object):
:param Oid new: the reference's new value
"""
def push_update_reference(self, refname, message):
"""Push update reference callback
Override with your own function to report the remote's
acceptace or rejection of reference updates.
:param str refname: the name of the reference (on the remote)
:param str messsage: rejection message from the remote. If None, the update was accepted.
"""
def __init__(self, repo, ptr):
"""The constructor is for internal use only"""
@@ -279,28 +289,31 @@ class Remote(object):
err = C.git_remote_add_push(self._remote, to_bytes(spec))
check_error(err)
@ffi.callback("int (*cb)(const char *ref, const char *msg, void *data)")
def _push_cb(ref, msg, data):
self = ffi.from_handle(data)
if msg:
self._bad_message = ffi.string(msg).decode()
return 0
def push(self, specs, signature=None, message=None):
"""push(specs, signature, message)
def push(self, spec, signature=None, message=None):
"""push(refspec, signature, message)
Push the given refspec to the remote. Raises ``GitError`` on error.
Push the given refspec to the remote. Raises ``GitError`` on
protocol error or unpack failure.
May require libssh2.
:param str spec: push refspec to use
:param [str] specs: push refspecs to use
:param Signature signature: signature to use when updating the tips
:param str message: message to use when updating the tips
"""
# Get the default callbacks first
defaultcallbacks = ffi.new('git_remote_callbacks *')
err = C.git_remote_init_callbacks(defaultcallbacks, 1)
check_error(err)
refspecs, refspecs_refs = strings_to_strarray(specs)
if signature:
sig_cptr = ffi.new('git_signature **')
ffi.buffer(sig_cptr)[:] = signature._pointer[:]
sig_ptr = sig_cptr[0]
else:
sig_ptr = ffi.NULL
# Build custom callback structure
callbacks = ffi.new('git_remote_callbacks *')
callbacks.version = 1
@@ -308,50 +321,23 @@ class Remote(object):
callbacks.transfer_progress = self._transfer_progress_cb
callbacks.update_tips = self._update_tips_cb
callbacks.credentials = self._credentials_cb
callbacks.push_update_reference = self._push_update_reference_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:
err = C.git_remote_set_callbacks(self._remote, callbacks)
check_error(err)
except:
self._self_handle = None
raise
cpush = ffi.new('git_push **')
err = C.git_push_new(cpush, self._remote)
check_error(err)
push = cpush[0]
try:
err = C.git_push_add_refspec(push, to_bytes(spec))
err = C.git_remote_push(self._remote, refspecs, ffi.NULL, sig_ptr, to_bytes(message))
check_error(err)
err = C.git_push_finish(push)
check_error(err)
err = C.git_push_status_foreach(push, self._push_cb,
ffi.new_handle(self))
check_error(err)
if hasattr(self, '_bad_message'):
raise GitError(self._bad_message)
if signature:
ptr = signature._pointer[:]
else:
ptr = ffi.NULL
err = C.git_push_update_tips(push, ptr, to_bytes(message))
check_error(err)
finally:
self._self_handle = None
C.git_push_free(push)
# These functions exist to be called by the git_remote as
# callbacks. They proxy the call to whatever the user set
@@ -408,6 +394,23 @@ class Remote(object):
return 0
@ffi.callback("int (*push_update_reference)(const char *ref, const char *msg, void *data)")
def _push_update_reference_cb(ref, msg, data):
self = ffi.from_handle(data)
if not hasattr(self, 'push_update_reference') or not self.push_update_reference:
return 0
try:
refname = ffi.string(ref)
message = maybe_string(msg)
self.push_update_reference(refname, message)
except Exception as e:
self._stored_exception = e
return C.GIT_EUSER
return 0
@ffi.callback('int (*credentials)(git_cred **cred, const char *url,'
'const char *username_from_url, unsigned int allowed_types,'
'void *data)')

View File

@@ -274,11 +274,11 @@ class PushTestCase(unittest.TestCase):
'refs/heads/master', tip.author, tip.author, 'empty commit',
tip.tree.id, [tip.id]
)
self.remote.push('refs/heads/master')
self.remote.push(['refs/heads/master'])
self.assertEqual(self.origin[self.origin.head.target].id, oid)
def test_push_when_up_to_date_succeeds(self):
self.remote.push('refs/heads/master')
self.remote.push(['refs/heads/master'])
origin_tip = self.origin[self.origin.head.target].id
clone_tip = self.clone[self.clone.head.target].id
self.assertEqual(origin_tip, clone_tip)
@@ -294,7 +294,8 @@ class PushTestCase(unittest.TestCase):
'refs/heads/master', tip.author, tip.author, 'other commit',
tip.tree.id, [tip.id]
)
self.assertRaises(pygit2.GitError, self.remote.push, 'refs/heads/master')
self.assertRaises(pygit2.GitError, self.remote.push, ['refs/heads/master'])
if __name__ == '__main__':
unittest.main()