Commit 45301cca by Edward Thomson

Merge pull request #2608 from libgit2/cmn/remote-push

Provide a convenience function `git_remote_push()`
parents 8b5b814e 64e3e6d4
......@@ -14,6 +14,7 @@
#include "indexer.h"
#include "strarray.h"
#include "transport.h"
#include "push.h"
/**
* @file git2/remote.h
......@@ -390,6 +391,23 @@ GIT_EXTERN(int) git_remote_fetch(
const char *reflog_message);
/**
* Perform a push
*
* Peform all the steps from a push.
*
* @param remote the remote to push to
* @param refspecs the refspecs to use for pushing. If none are
* passed, the configured refspecs will be used
* @param opts the options
* @param signature signature to use for the reflog of updated references
* @param reflog_message message to use for the reflog of upated references
*/
GIT_EXTERN(int) git_remote_push(git_remote *remote,
git_strarray *refspecs,
const git_push_options *opts,
const git_signature *signature, const char *reflog_message);
/**
* Get a list of the configured remotes for a repo
*
* The string array must be freed by the user.
......@@ -462,6 +480,28 @@ struct git_remote_callbacks {
int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data);
/**
* Function to call with progress information during pack
* building. Be aware that this is called inline with pack
* building operations, so performance may be affected.
*/
git_packbuilder_progress pack_progress;
/**
* Function to call with progress information during the
* upload portion of a push. Be aware that this is called
* inline with pack building operations, so performance may be
* affected.
*/
git_push_transfer_progress push_transfer_progress;
/**
* Called for each updated reference on push. If `status` is
* not `NULL`, the update was rejected by the remote server
* and `status` contains the reason given.
*/
int (*push_update_reference)(const char *refname, const char *status, void *data);
/**
* This will be passed to each of the callbacks in this struct
* as the last parameter.
*/
......
......@@ -2115,3 +2115,63 @@ int git_remote_default_branch(git_buf *out, git_remote *remote)
return git_buf_puts(out, guess->name);
}
int git_remote_push(git_remote *remote, git_strarray *refspecs, const git_push_options *opts,
const git_signature *signature, const char *reflog_message)
{
int error;
size_t i;
git_push *push = NULL;
git_remote_callbacks *cbs;
git_refspec *spec;
assert(remote && refspecs);
if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH)) < 0)
return error;
if ((error = git_push_new(&push, remote)) < 0)
goto cleanup;
if (opts && (error = git_push_set_options(push, opts)) < 0)
goto cleanup;
if (refspecs && refspecs->count > 0) {
for (i = 0; i < refspecs->count; i++) {
if ((error = git_push_add_refspec(push, refspecs->strings[i])) < 0)
goto cleanup;
}
} else {
git_vector_foreach(&remote->refspecs, i, spec) {
if (!spec->push)
continue;
if ((error = git_push_add_refspec(push, spec->string)) < 0)
goto cleanup;
}
}
cbs = &remote->callbacks;
if ((error = git_push_set_callbacks(push,
cbs->pack_progress, cbs->payload,
cbs->push_transfer_progress, cbs->payload)) < 0)
goto cleanup;
if ((error = git_push_finish(push)) < 0)
goto cleanup;
if (!git_push_unpack_ok(push)) {
error = -1;
giterr_set(GITERR_NET, "error in the remote while trying to unpack");
goto cleanup;
}
if ((error = git_push_status_foreach(push, cbs->push_update_reference, cbs->payload)) < 0)
goto cleanup;
error = git_push_update_tips(push, signature, reflog_message);
cleanup:
git_remote_disconnect(remote);
git_push_free(push);
return error;
}
......@@ -89,46 +89,38 @@ static int cred_acquire_cb(
return -1;
}
/* the results of a push status. when used for expected values, msg may be NULL
* to indicate that it should not be matched. */
typedef struct {
const char *ref;
int success;
const char *msg;
} push_status;
/**
* git_push_status_foreach callback that records status entries.
* @param data (git_vector *) of push_status instances
*/
static int record_push_status_cb(const char *ref, const char *msg, void *data)
static int record_push_status_cb(const char *ref, const char *msg, void *payload)
{
git_vector *statuses = (git_vector *)data;
record_callbacks_data *data = (record_callbacks_data *) payload;
push_status *s;
cl_assert(s = git__malloc(sizeof(*s)));
s->ref = ref;
cl_assert(s = git__calloc(1, sizeof(*s)));
if (ref)
cl_assert(s->ref = git__strdup(ref));
s->success = (msg == NULL);
s->msg = msg;
if (msg)
cl_assert(s->msg = git__strdup(msg));
git_vector_insert(statuses, s);
git_vector_insert(&data->statuses, s);
return 0;
}
static void do_verify_push_status(git_push *push, const push_status expected[], const size_t expected_len)
static void do_verify_push_status(record_callbacks_data *data, const push_status expected[], const size_t expected_len)
{
git_vector actual = GIT_VECTOR_INIT;
git_vector *actual = &data->statuses;
push_status *iter;
bool failed = false;
size_t i;
git_push_status_foreach(push, record_push_status_cb, &actual);
if (expected_len != actual.length)
if (expected_len != actual->length)
failed = true;
else
git_vector_foreach(&actual, i, iter)
git_vector_foreach(actual, i, iter)
if (strcmp(expected[i].ref, iter->ref) ||
(expected[i].success != iter->success) ||
(expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) {
......@@ -149,7 +141,7 @@ static void do_verify_push_status(git_push *push, const push_status expected[],
git_buf_puts(&msg, "\nACTUAL:\n");
git_vector_foreach(&actual, i, iter) {
git_vector_foreach(actual, i, iter) {
if (iter->success)
git_buf_printf(&msg, "%s: success\n", iter->ref);
else
......@@ -161,10 +153,10 @@ static void do_verify_push_status(git_push *push, const push_status expected[],
git_buf_free(&msg);
}
git_vector_foreach(&actual, i, iter)
git_vector_foreach(actual, i, iter)
git__free(iter);
git_vector_free(&actual);
git_vector_free(actual);
}
/**
......@@ -431,22 +423,24 @@ void test_online_push__cleanup(void)
static int push_pack_progress_cb(
int stage, unsigned int current, unsigned int total, void* payload)
{
int *calls = (int *)payload;
record_callbacks_data *data = (record_callbacks_data *) payload;
GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total);
if (*calls < 0)
return *calls;
(*calls)++;
if (data->pack_progress_calls < 0)
return data->pack_progress_calls;
data->pack_progress_calls++;
return 0;
}
static int push_transfer_progress_cb(
unsigned int current, unsigned int total, size_t bytes, void* payload)
{
int *calls = (int *)payload;
record_callbacks_data *data = (record_callbacks_data *) payload;
GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes);
if (*calls < 0)
return *calls;
(*calls)++;
if (data->transfer_progress_calls < 0)
return data->transfer_progress_calls;
data->transfer_progress_calls++;
return 0;
}
......@@ -466,62 +460,63 @@ static void do_push(
expected_ref expected_refs[], size_t expected_refs_len,
int expected_ret, int check_progress_cb, int check_update_tips_cb)
{
git_push *push;
git_push_options opts = GIT_PUSH_OPTIONS_INIT;
size_t i;
int pack_progress_calls = 0, transfer_progress_calls = 0;
int error;
git_strarray specs = {0};
git_signature *pusher;
git_remote_callbacks callbacks;
record_callbacks_data *data;
if (_remote) {
/* Auto-detect the number of threads to use */
opts.pb_parallelism = 0;
cl_git_pass(git_signature_now(&pusher, "Foo Bar", "foo@example.com"));
cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
cl_git_pass(git_push_new(&push, _remote));
cl_git_pass(git_push_set_options(push, &opts));
memcpy(&callbacks, git_remote_get_callbacks(_remote), sizeof(callbacks));
data = callbacks.payload;
if (check_progress_cb) {
/* if EUSER, then abort in transfer */
if (expected_ret == GIT_EUSER)
transfer_progress_calls = GIT_EUSER;
callbacks.pack_progress = push_pack_progress_cb;
callbacks.push_transfer_progress = push_transfer_progress_cb;
callbacks.push_update_reference = record_push_status_cb;
cl_git_pass(git_remote_set_callbacks(_remote, &callbacks));
cl_git_pass(
git_push_set_callbacks(
push, push_pack_progress_cb, &pack_progress_calls,
push_transfer_progress_cb, &transfer_progress_calls));
if (refspecs_len) {
specs.count = refspecs_len;
specs.strings = git__calloc(refspecs_len, sizeof(char *));
cl_assert(specs.strings);
}
for (i = 0; i < refspecs_len; i++)
cl_git_pass(git_push_add_refspec(push, refspecs[i]));
specs.strings[i] = (char *) refspecs[i];
/* if EUSER, then abort in transfer */
if (check_progress_cb && expected_ret == GIT_EUSER)
data->transfer_progress_calls = GIT_EUSER;
error = git_remote_push(_remote, &specs, &opts, pusher, "test push");
git__free(specs.strings);
if (expected_ret < 0) {
cl_git_fail_with(git_push_finish(push), expected_ret);
cl_assert_equal_i(0, git_push_unpack_ok(push));
cl_git_fail_with(expected_ret, error);
} else {
cl_git_pass(git_push_finish(push));
cl_assert_equal_i(1, git_push_unpack_ok(push));
cl_git_pass(error);
}
if (check_progress_cb && !expected_ret) {
cl_assert(pack_progress_calls > 0);
cl_assert(transfer_progress_calls > 0);
if (check_progress_cb && expected_ret == 0) {
cl_assert(data->pack_progress_calls > 0);
cl_assert(data->transfer_progress_calls > 0);
}
do_verify_push_status(push, expected_statuses, expected_statuses_len);
do_verify_push_status(data, expected_statuses, expected_statuses_len);
verify_refs(_remote, expected_refs, expected_refs_len);
cl_git_pass(git_push_update_tips(push, pusher, "test push"));
verify_tracking_branches(_remote, expected_refs, expected_refs_len);
if (check_update_tips_cb)
verify_update_tips_callback(_remote, expected_refs, expected_refs_len);
git_push_free(push);
git_remote_disconnect(_remote);
git_signature_free(pusher);
}
......@@ -882,3 +877,35 @@ void test_online_push__notes(void)
git_signature_free(signature);
}
void test_online_push__configured(void)
{
git_oid note_oid, *target_oid, expected_oid;
git_signature *signature;
const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
push_status exp_stats[] = { { "refs/notes/commits", 1 } };
expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
const char *specs_del[] = { ":refs/notes/commits" };
git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb");
target_oid = &_oid_b6;
cl_git_pass(git_remote_add_push(_remote, specs[0]));
/* Create note to push */
cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
cl_git_pass(git_note_create(&note_oid, _repo, signature, signature, NULL, target_oid, "hello world\n", 0));
do_push(NULL, 0,
exp_stats, ARRAY_SIZE(exp_stats),
exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
/* And make sure to delete the note */
do_push(specs_del, ARRAY_SIZE(specs_del),
exp_stats, 1,
NULL, 0, 0, 0, 0);
git_signature_free(signature);
}
......@@ -14,15 +14,31 @@ void updated_tip_free(updated_tip *t)
git__free(t);
}
void push_status_free(push_status *s)
{
git__free(s->ref);
git__free(s->msg);
git__free(s);
}
void record_callbacks_data_clear(record_callbacks_data *data)
{
size_t i;
updated_tip *tip;
push_status *status;
git_vector_foreach(&data->updated_tips, i, tip)
updated_tip_free(tip);
git_vector_free(&data->updated_tips);
git_vector_foreach(&data->statuses, i, status)
push_status_free(status);
git_vector_free(&data->statuses);
data->pack_progress_calls = 0;
data->transfer_progress_calls = 0;
}
int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
......
......@@ -12,7 +12,7 @@ extern const git_oid OID_ZERO;
* @param data pointer to a record_callbacks_data instance
*/
#define RECORD_CALLBACKS_INIT(data) \
{ GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, data }
{ GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, data }
typedef struct {
char *name;
......@@ -22,6 +22,9 @@ typedef struct {
typedef struct {
git_vector updated_tips;
git_vector statuses;
int pack_progress_calls;
int transfer_progress_calls;
} record_callbacks_data;
typedef struct {
......@@ -29,6 +32,15 @@ typedef struct {
const git_oid *oid;
} expected_ref;
/* the results of a push status. when used for expected values, msg may be NULL
* to indicate that it should not be matched. */
typedef struct {
char *ref;
int success;
char *msg;
} push_status;
void updated_tip_free(updated_tip *t);
void record_callbacks_data_clear(record_callbacks_data *data);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment