/*
* The MIT License
*
* Copyright (c) 2014 GitHub, Inc
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "rugged.h"
extern VALUE rb_mRugged;
extern VALUE rb_cRuggedRepo;
extern VALUE rb_eRuggedError;
VALUE rb_cRuggedRemote;
#define RUGGED_REMOTE_CALLBACKS_INIT {1, progress_cb, NULL, credentials_cb, NULL, transfer_progress_cb, update_tips_cb, NULL}
static int progress_cb(const char *str, int len, void *data)
{
struct rugged_remote_cb_payload *payload = data;
VALUE args = rb_ary_new2(2);
if (NIL_P(payload->progress))
return 0;
rb_ary_push(args, payload->progress);
rb_ary_push(args, rb_str_new(str, len));
rb_protect(rugged__block_yield_splat, args, &payload->exception);
return payload->exception ? GIT_ERROR : GIT_OK;
}
static int transfer_progress_cb(const git_transfer_progress *stats, void *data)
{
struct rugged_remote_cb_payload *payload = data;
VALUE args = rb_ary_new2(5);
if (NIL_P(payload->transfer_progress))
return 0;
rb_ary_push(args, payload->transfer_progress);
rb_ary_push(args, UINT2NUM(stats->total_objects));
rb_ary_push(args, UINT2NUM(stats->indexed_objects));
rb_ary_push(args, UINT2NUM(stats->received_objects));
rb_ary_push(args, UINT2NUM(stats->local_objects));
rb_ary_push(args, UINT2NUM(stats->total_deltas));
rb_ary_push(args, UINT2NUM(stats->indexed_deltas));
rb_ary_push(args, INT2FIX(stats->received_bytes));
rb_protect(rugged__block_yield_splat, args, &payload->exception);
return payload->exception ? GIT_ERROR : GIT_OK;
}
static int update_tips_cb(const char *refname, const git_oid *src, const git_oid *dest, void *data)
{
struct rugged_remote_cb_payload *payload = data;
VALUE args = rb_ary_new2(4);
if (NIL_P(payload->update_tips))
return 0;
rb_ary_push(args, payload->update_tips);
rb_ary_push(args, rb_str_new_utf8(refname));
rb_ary_push(args, git_oid_iszero(src) ? Qnil : rugged_create_oid(src));
rb_ary_push(args, git_oid_iszero(dest) ? Qnil : rugged_create_oid(dest));
rb_protect(rugged__block_yield_splat, args, &payload->exception);
return payload->exception ? GIT_ERROR : GIT_OK;
}
struct extract_cred_args
{
VALUE rb_callback;
git_cred **cred;
const char *url;
const char *username_from_url;
unsigned int allowed_types;
};
static VALUE allowed_types_to_rb_ary(int allowed_types) {
VALUE rb_allowed_types = rb_ary_new();
if (allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)
rb_ary_push(rb_allowed_types, CSTR2SYM("plaintext"));
if (allowed_types & GIT_CREDTYPE_SSH_KEY)
rb_ary_push(rb_allowed_types, CSTR2SYM("ssh_key"));
if (allowed_types & GIT_CREDTYPE_DEFAULT)
rb_ary_push(rb_allowed_types, CSTR2SYM("default"));
return rb_allowed_types;
}
static VALUE extract_cred(VALUE data) {
struct extract_cred_args *args = (struct extract_cred_args*)data;
VALUE rb_url, rb_username_from_url, rb_cred;
rb_url = args->url ? rb_str_new2(args->url) : Qnil;
rb_username_from_url = args->username_from_url ? rb_str_new2(args->username_from_url) : Qnil;
rb_cred = rb_funcall(args->rb_callback, rb_intern("call"), 3,
rb_url, rb_username_from_url, allowed_types_to_rb_ary(args->allowed_types));
rugged_cred_extract(args->cred, args->allowed_types, rb_cred);
return Qnil;
}
static int credentials_cb(
git_cred **cred,
const char *url,
const char *username_from_url,
unsigned int allowed_types,
void *data)
{
struct rugged_remote_cb_payload *payload = data;
struct extract_cred_args args = {
payload->credentials, cred, url, username_from_url, allowed_types
};
if (NIL_P(payload->credentials))
return GIT_PASSTHROUGH;
rb_protect(extract_cred, (VALUE)&args, &payload->exception);
return payload->exception ? GIT_ERROR : GIT_OK;
}
#define CALLABLE_OR_RAISE(ret, rb_options, name) \
do { \
ret = rb_hash_aref(rb_options, CSTR2SYM(name)); \
\
if (!NIL_P(ret) && !rb_respond_to(ret, rb_intern("call"))) \
rb_raise(rb_eArgError, "Expected a Proc or an object that responds to #call (:" name " )."); \
} while (0);
void rugged_remote_init_callbacks_and_payload_from_options(
VALUE rb_options,
git_remote_callbacks *callbacks,
struct rugged_remote_cb_payload *payload)
{
git_remote_callbacks prefilled = RUGGED_REMOTE_CALLBACKS_INIT;
prefilled.payload = payload;
memcpy(callbacks, &prefilled, sizeof(git_remote_callbacks));
CALLABLE_OR_RAISE(payload->update_tips, rb_options, "update_tips");
CALLABLE_OR_RAISE(payload->progress, rb_options, "progress");
CALLABLE_OR_RAISE(payload->transfer_progress, rb_options, "transfer_progress");
CALLABLE_OR_RAISE(payload->credentials, rb_options, "credentials");
}
static void rb_git_remote__free(git_remote *remote)
{
git_remote_free(remote);
}
VALUE rugged_remote_new(VALUE owner, git_remote *remote)
{
VALUE rb_remote;
rb_remote = Data_Wrap_Struct(rb_cRuggedRemote, NULL, &rb_git_remote__free, remote);
rugged_set_owner(rb_remote, owner);
return rb_remote;
}
static VALUE rugged_rhead_new(const git_remote_head *head)
{
VALUE rb_head = rb_hash_new();
rb_hash_aset(rb_head, CSTR2SYM("local?"), head->local ? Qtrue : Qfalse);
rb_hash_aset(rb_head, CSTR2SYM("oid"), rugged_create_oid(&head->oid));
rb_hash_aset(rb_head, CSTR2SYM("loid"),
git_oid_iszero(&head->loid) ? Qnil : rugged_create_oid(&head->loid));
rb_hash_aset(rb_head, CSTR2SYM("name"), rb_str_new_utf8(head->name));
return rb_head;
}
/*
* call-seq:
* remote.ls(options = {}) -> an_enumerator
* remote.ls(options = {}) { |remote_head_hash| block }
*
* Connects +remote+ to list all references available along with their
* associated commit ids.
*
* The given block is called once for each remote head with a Hash containing the
* following keys:
*
* :local? ::
* +true+ if the remote head is available locally, +false+ otherwise.
*
* :oid ::
* The id of the object the remote head is currently pointing to.
*
* :loid ::
* The id of the object the local copy of the remote head is currently
* pointing to. Set to +nil+ if there is no local copy of the remote head.
*
* :name ::
* The fully qualified reference name of the remote head.
*
* If no block is given, an enumerator will be returned.
*
* The following options can be passed in the +options+ Hash:
*
* :credentials ::
* The credentials to use for the ls operation. Can be either an instance of one
* of the Rugged::Credentials types, or a proc returning one of the former.
* The proc will be called with the +url+, the +username+ from the url (if applicable) and
* a list of applicable credential types.
*/
static VALUE rb_git_remote_ls(int argc, VALUE *argv, VALUE self)
{
git_remote *remote;
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
const git_remote_head **heads;
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
VALUE rb_options;
int error;
size_t heads_len, i;
Data_Get_Struct(self, git_remote, remote);
rb_scan_args(argc, argv, ":", &rb_options);
if (!rb_block_given_p())
return rb_funcall(self, rb_intern("to_enum"), 2, CSTR2SYM("ls"), rb_options);
if (!NIL_P(rb_options))
rugged_remote_init_callbacks_and_payload_from_options(rb_options, &callbacks, &payload);
if ((error = git_remote_set_callbacks(remote, &callbacks)) ||
(error = git_remote_connect(remote, GIT_DIRECTION_FETCH)) ||
(error = git_remote_ls(&heads, &heads_len, remote)))
goto cleanup;
for (i = 0; i < heads_len && !payload.exception; i++)
rb_protect(rb_yield, rugged_rhead_new(heads[i]), &payload.exception);
cleanup:
git_remote_disconnect(remote);
if (payload.exception)
rb_jump_tag(payload.exception);
rugged_exception_check(error);
return Qnil;
}
/*
* call-seq:
* remote.name() -> string
*
* Returns the remote's name.
*
* remote.name #=> "origin"
*/
static VALUE rb_git_remote_name(VALUE self)
{
git_remote *remote;
const char * name;
Data_Get_Struct(self, git_remote, remote);
name = git_remote_name(remote);
return name ? rb_str_new_utf8(name) : Qnil;
}
/*
* call-seq:
* remote.url() -> string
*
* Returns the remote's url
*
* remote.url #=> "git://github.com/libgit2/rugged.git"
*/
static VALUE rb_git_remote_url(VALUE self)
{
git_remote *remote;
Data_Get_Struct(self, git_remote, remote);
return rb_str_new_utf8(git_remote_url(remote));
}
/*
* call-seq:
* remote.url = url -> url
*
* Sets the remote's url without persisting it in the config.
* Existing connections will not be updated.
*
* remote.url = 'git://github.com/libgit2/rugged.git' #=> "git://github.com/libgit2/rugged.git"
*/
static VALUE rb_git_remote_set_url(VALUE self, VALUE rb_url)
{
git_remote *remote;
Check_Type(rb_url, T_STRING);
Data_Get_Struct(self, git_remote, remote);
rugged_exception_check(
git_remote_set_url(remote, StringValueCStr(rb_url))
);
return rb_url;
}
/*
* call-seq:
* remote.push_url() -> string or nil
*
* Returns the remote's url for pushing or nil if no special url for
* pushing is set.
*
* remote.push_url #=> "git://github.com/libgit2/rugged.git"
*/
static VALUE rb_git_remote_push_url(VALUE self)
{
git_remote *remote;
const char * push_url;
Data_Get_Struct(self, git_remote, remote);
push_url = git_remote_pushurl(remote);
return push_url ? rb_str_new_utf8(push_url) : Qnil;
}
/*
* call-seq:
* remote.push_url = url -> url
*
* Sets the remote's url for pushing without persisting it in the config.
* Existing connections will not be updated.
*
* remote.push_url = 'git@github.com/libgit2/rugged.git' #=> "git@github.com/libgit2/rugged.git"
*/
static VALUE rb_git_remote_set_push_url(VALUE self, VALUE rb_url)
{
git_remote *remote;
Check_Type(rb_url, T_STRING);
Data_Get_Struct(self, git_remote, remote);
rugged_exception_check(
git_remote_set_pushurl(remote, StringValueCStr(rb_url))
);
return rb_url;
}
static VALUE rb_git_remote_refspecs(VALUE self, git_direction direction)
{
git_remote *remote;
int error = 0;
git_strarray refspecs;
VALUE rb_refspec_array;
Data_Get_Struct(self, git_remote, remote);
if (direction == GIT_DIRECTION_FETCH)
error = git_remote_get_fetch_refspecs(&refspecs, remote);
else
error = git_remote_get_push_refspecs(&refspecs, remote);
rugged_exception_check(error);
rb_refspec_array = rugged_strarray_to_rb_ary(&refspecs);
git_strarray_free(&refspecs);
return rb_refspec_array;
}
/*
* call-seq:
* remote.fetch_refspecs -> array
*
* Get the remote's list of fetch refspecs as +array+.
*/
static VALUE rb_git_remote_fetch_refspecs(VALUE self)
{
return rb_git_remote_refspecs(self, GIT_DIRECTION_FETCH);
}
/*
* call-seq:
* remote.push_refspecs -> array
*
* Get the remote's list of push refspecs as +array+.
*/
static VALUE rb_git_remote_push_refspecs(VALUE self)
{
return rb_git_remote_refspecs(self, GIT_DIRECTION_PUSH);
}
static VALUE rb_git_remote_add_refspec(VALUE self, VALUE rb_refspec, git_direction direction)
{
git_remote *remote;
int error = 0;
Data_Get_Struct(self, git_remote, remote);
Check_Type(rb_refspec, T_STRING);
if (direction == GIT_DIRECTION_FETCH)
error = git_remote_add_fetch(remote, StringValueCStr(rb_refspec));
else
error = git_remote_add_push(remote, StringValueCStr(rb_refspec));
rugged_exception_check(error);
return Qnil;
}
/*
* call-seq:
* remote.add_fetch(refspec) -> nil
*
* Add a fetch refspec to the remote.
*/
static VALUE rb_git_remote_add_fetch(VALUE self, VALUE rb_refspec)
{
return rb_git_remote_add_refspec(self, rb_refspec, GIT_DIRECTION_FETCH);
}
/*
* call-seq:
* remote.add_push(refspec) -> nil
*
* Add a push refspec to the remote.
*/
static VALUE rb_git_remote_add_push(VALUE self, VALUE rb_refspec)
{
return rb_git_remote_add_refspec(self, rb_refspec, GIT_DIRECTION_PUSH);
}
/*
* call-seq:
* remote.clear_refspecs -> nil
*
* Remove all configured fetch and push refspecs from the remote.
*/
static VALUE rb_git_remote_clear_refspecs(VALUE self)
{
git_remote *remote;
Data_Get_Struct(self, git_remote, remote);
git_remote_clear_refspecs(remote);
return Qnil;
}
/*
* call-seq:
* remote.save -> true
*
* Saves the remote data (url, fetchspecs, ...) to the config.
*
* Anonymous, in-memory remotes created through
* +ReferenceCollection#create_anonymous+ can not be saved.
* Doing so will result in an exception being raised.
*/
static VALUE rb_git_remote_save(VALUE self)
{
git_remote *remote;
Data_Get_Struct(self, git_remote, remote);
rugged_exception_check(
git_remote_save(remote)
);
return Qtrue;
}
/*
* call-seq:
* remote.check_connection(direction, options = {}) -> boolean
*
* Try to connect to the +remote+. Useful to simulate
* git fetch --dry-run and git push --dry-run.
*
* Returns +true+ if connection is successful, +false+ otherwise.
*
* +direction+ must be either +:fetch+ or +:push+.
*
* The following options can be passed in the +options+ Hash:
*
* +credentials+ ::
* The credentials to use for the connection. Can be either an instance of
* one of the Rugged::Credentials types, or a proc returning one of the
* former.
* The proc will be called with the +url+, the +username+ from the url (if
* applicable) and a list of applicable credential types.
*
* Example:
*
* remote = repo.remotes["origin"]
* success = remote.check_connection(:fetch)
* raise Error("Unable to pull without credentials") unless success
*/
static VALUE rb_git_remote_check_connection(int argc, VALUE *argv, VALUE self)
{
git_remote *remote;
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
VALUE rb_direction, rb_options;
ID id_direction;
int error, direction;
Data_Get_Struct(self, git_remote, remote);
rb_scan_args(argc, argv, "01:", &rb_direction, &rb_options);
Check_Type(rb_direction, T_SYMBOL);
id_direction = SYM2ID(rb_direction);
if (id_direction == rb_intern("fetch"))
direction = GIT_DIRECTION_FETCH;
else if (id_direction == rb_intern("push"))
direction = GIT_DIRECTION_PUSH;
else
rb_raise(rb_eTypeError, "Invalid direction. Expected :fetch or :push");
if (!NIL_P(rb_options))
rugged_remote_init_callbacks_and_payload_from_options(rb_options, &callbacks, &payload);
if ((error = git_remote_set_callbacks(remote, &callbacks)) < 0)
goto cleanup;
if (git_remote_connect(remote, direction))
return Qfalse;
else {
git_remote_disconnect(remote);
return Qtrue;
}
cleanup:
if (payload.exception)
rb_jump_tag(payload.exception);
rugged_exception_check(error);
return Qfalse;
}
/*
* call-seq:
* remote.fetch(refspecs = nil, options = {}) -> hash
*
* Downloads new data from the remote for the given +refspecs+ and updates tips.
*
* You can optionally pass in a single or multiple alternative +refspecs+ to use instead of the fetch
* refspecs already configured for +remote+.
*
* Returns a hash containing statistics for the fetch operation.
*
* The following options can be passed in the +options+ Hash:
*
* :credentials ::
* The credentials to use for the fetch operation. Can be either an instance of one
* of the Rugged::Credentials types, or a proc returning one of the former.
* The proc will be called with the +url+, the +username+ from the url (if applicable) and
* a list of applicable credential types.
*
* :progress ::
* A callback that will be executed with the textual progress received from the remote.
* This is the text send over the progress side-band (ie. the "counting objects" output).
*
* :transfer_progress ::
* A callback that will be executed to report clone progress information. It will be passed
* the amount of +total_objects+, +indexed_objects+, +received_objects+, +local_objects+,
* +total_deltas+, +indexed_deltas+ and +received_bytes+.
*
* :update_tips ::
* A callback that will be executed each time a reference is updated locally. It will be
* passed the +refname+, +old_oid+ and +new_oid+.
*
* :message ::
* The message to insert into the reflogs. Defaults to "fetch".
*
* :signature ::
* The signature to be used for updating the reflogs.
*
* Example:
*
* remote = Rugged::Remote.lookup(@repo, 'origin')
* remote.fetch({
* transfer_progress: lambda { |total_objects, indexed_objects, received_objects, local_objects, total_deltas, indexed_deltas, received_bytes|
* # ...
* }
* })
*/
static VALUE rb_git_remote_fetch(int argc, VALUE *argv, VALUE self)
{
git_remote *remote;
git_repository *repo;
git_signature *signature = NULL;
git_strarray refspecs;
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
char *log_message = NULL;
int error;
VALUE rb_options, rb_refspecs, rb_result = Qnil, rb_repo = rugged_owner(self);
rb_scan_args(argc, argv, "01:", &rb_refspecs, &rb_options);
rugged_rb_ary_to_strarray(rb_refspecs, &refspecs);
Data_Get_Struct(self, git_remote, remote);
rugged_check_repo(rb_repo);
Data_Get_Struct(rb_repo, git_repository, repo);
if (!NIL_P(rb_options)) {
VALUE rb_val = rb_hash_aref(rb_options, CSTR2SYM("signature"));
if (!NIL_P(rb_val))
signature = rugged_signature_get(rb_val, repo);
rb_val = rb_hash_aref(rb_options, CSTR2SYM("message"));
if (!NIL_P(rb_val))
log_message = StringValueCStr(rb_val);
rugged_remote_init_callbacks_and_payload_from_options(rb_options, &callbacks, &payload);
}
if ((error = git_remote_set_callbacks(remote, &callbacks)))
goto cleanup;
if ((error = git_remote_fetch(remote, &refspecs, signature, log_message)) == GIT_OK) {
const git_transfer_progress *stats = git_remote_stats(remote);
rb_result = rb_hash_new();
rb_hash_aset(rb_result, CSTR2SYM("total_objects"), UINT2NUM(stats->total_objects));
rb_hash_aset(rb_result, CSTR2SYM("indexed_objects"), UINT2NUM(stats->indexed_objects));
rb_hash_aset(rb_result, CSTR2SYM("received_objects"), UINT2NUM(stats->received_objects));
rb_hash_aset(rb_result, CSTR2SYM("local_objects"), UINT2NUM(stats->local_objects));
rb_hash_aset(rb_result, CSTR2SYM("total_deltas"), UINT2NUM(stats->total_deltas));
rb_hash_aset(rb_result, CSTR2SYM("indexed_deltas"), UINT2NUM(stats->indexed_deltas));
rb_hash_aset(rb_result, CSTR2SYM("received_bytes"), INT2FIX(stats->received_bytes));
}
cleanup:
xfree(refspecs.strings);
git_signature_free(signature);
if (payload.exception)
rb_jump_tag(payload.exception);
rugged_exception_check(error);
return rb_result;
}
static int push_status_cb(const char *ref, const char *msg, void *payload)
{
VALUE rb_result_hash = (VALUE)payload;
if (msg != NULL)
rb_hash_aset(rb_result_hash, rb_str_new_utf8(ref), rb_str_new_utf8(msg));
return GIT_OK;
}
/*
* call-seq:
* remote.push(refspecs = nil, options = {}) -> hash
*
* Pushes the given +refspecs+ to the given +remote+. Returns a hash that contains
* key-value pairs that reflect pushed refs and error messages, if applicable.
*
* You can optionally pass in an alternative list of +refspecs+ to use instead of the push
* refspecs already configured for +remote+.
*
* The following options can be passed in the +options+ Hash:
*
* :credentials ::
* The credentials to use for the push operation. Can be either an instance of one
* of the Rugged::Credentials types, or a proc returning one of the former.
* The proc will be called with the +url+, the +username+ from the url (if applicable) and
* a list of applicable credential types.
*
* :update_tips ::
* A callback that will be executed each time a reference is updated remotely. It will be
* passed the +refname+, +old_oid+ and +new_oid+.
*
* :message ::
* A single line log message to be appended to the reflog of each local remote-tracking
* branch that gets updated. Defaults to: "fetch".
*
* :signature ::
* The signature to be used for populating the reflog entries.
*
* Example:
*
* remote = Rugged::Remote.lookup(@repo, 'origin')
* remote.push(["refs/heads/master", ":refs/heads/to_be_deleted"])
*/
static VALUE rb_git_remote_push(int argc, VALUE *argv, VALUE self)
{
VALUE rb_refspecs, rb_options, rb_val;
VALUE rb_repo = rugged_owner(self);
VALUE rb_exception = Qnil, rb_result = rb_hash_new();
git_repository *repo;
git_remote *remote, *tmp_remote = NULL;
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
git_push *push = NULL;
git_signature *signature = NULL;
int error = 0, i = 0;
char *log_message = NULL;
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, 0 };
rb_scan_args(argc, argv, "01:", &rb_refspecs, &rb_options);
if (!NIL_P(rb_refspecs)) {
Check_Type(rb_refspecs, T_ARRAY);
for (i = 0; i < RARRAY_LEN(rb_refspecs); ++i) {
VALUE rb_refspec = rb_ary_entry(rb_refspecs, i);
Check_Type(rb_refspec, T_STRING);
}
}
rugged_check_repo(rb_repo);
Data_Get_Struct(rb_repo, git_repository, repo);
Data_Get_Struct(self, git_remote, remote);
if (!NIL_P(rb_options)) {
rugged_remote_init_callbacks_and_payload_from_options(rb_options, &callbacks, &payload);
rb_val = rb_hash_aref(rb_options, CSTR2SYM("message"));
if (!NIL_P(rb_val))
log_message = StringValueCStr(rb_val);
rb_val = rb_hash_aref(rb_options, CSTR2SYM("signature"));
if (!NIL_P(rb_val))
signature = rugged_signature_get(rb_val, repo);
}
// Create a temporary remote that we use for pushing
if ((error = git_remote_dup(&tmp_remote, remote)) ||
(error = git_remote_set_callbacks(tmp_remote, &callbacks)))
goto cleanup;
if (!NIL_P(rb_refspecs)) {
git_remote_clear_refspecs(tmp_remote);
for (i = 0; !error && i < RARRAY_LEN(rb_refspecs); ++i) {
VALUE rb_refspec = rb_ary_entry(rb_refspecs, i);
if ((error = git_remote_add_push(tmp_remote, StringValueCStr(rb_refspec))))
goto cleanup;
}
}
if ((error = git_push_new(&push, tmp_remote)))
goto cleanup;
// TODO: Get rid of this once git_remote_push lands in libgit2.
{
git_strarray push_refspecs;
size_t i;
if ((error = git_remote_get_push_refspecs(&push_refspecs, tmp_remote)))
goto cleanup;
if (push_refspecs.count == 0) {
rb_exception = rb_exc_new2(rb_eRuggedError, "no pushspecs are configured for the given remote");
goto cleanup;
}
for (i = 0; !error && i < push_refspecs.count; ++i) {
error = git_push_add_refspec(push, push_refspecs.strings[i]);
}
git_strarray_free(&push_refspecs);
if (error) goto cleanup;
}
if ((error = git_push_finish(push)))
goto cleanup;
if ((error = git_push_status_foreach(push, &push_status_cb, (void *)rb_result)) ||
(error = git_push_update_tips(push, signature, log_message)))
goto cleanup;
cleanup:
git_push_free(push);
git_remote_free(tmp_remote);
git_signature_free(signature);
if (!NIL_P(rb_exception))
rb_exc_raise(rb_exception);
rugged_exception_check(error);
return rb_result;
}
void Init_rugged_remote(void)
{
rb_cRuggedRemote = rb_define_class_under(rb_mRugged, "Remote", rb_cObject);
rb_define_method(rb_cRuggedRemote, "name", rb_git_remote_name, 0);
rb_define_method(rb_cRuggedRemote, "url", rb_git_remote_url, 0);
rb_define_method(rb_cRuggedRemote, "url=", rb_git_remote_set_url, 1);
rb_define_method(rb_cRuggedRemote, "push_url", rb_git_remote_push_url, 0);
rb_define_method(rb_cRuggedRemote, "push_url=", rb_git_remote_set_push_url, 1);
rb_define_method(rb_cRuggedRemote, "fetch_refspecs", rb_git_remote_fetch_refspecs, 0);
rb_define_method(rb_cRuggedRemote, "push_refspecs", rb_git_remote_push_refspecs, 0);
rb_define_method(rb_cRuggedRemote, "add_fetch", rb_git_remote_add_fetch, 1);
rb_define_method(rb_cRuggedRemote, "add_push", rb_git_remote_add_push, 1);
rb_define_method(rb_cRuggedRemote, "ls", rb_git_remote_ls, -1);
rb_define_method(rb_cRuggedRemote, "check_connection", rb_git_remote_check_connection, -1);
rb_define_method(rb_cRuggedRemote, "fetch", rb_git_remote_fetch, -1);
rb_define_method(rb_cRuggedRemote, "push", rb_git_remote_push, -1);
rb_define_method(rb_cRuggedRemote, "clear_refspecs", rb_git_remote_clear_refspecs, 0);
rb_define_method(rb_cRuggedRemote, "save", rb_git_remote_save, 0);
}