diff --git a/src/push.c b/src/push.c index 628df7ac4..37f641812 100644 --- a/src/push.c +++ b/src/push.c @@ -83,18 +83,6 @@ static void free_refspec(push_spec *spec) git__free(spec); } -static void free_status(push_status *status) -{ - if (status == NULL) - return; - - if (status->msg) - git__free(status->msg); - - git__free(status->ref); - git__free(status); -} - static int check_rref(char *ref) { if (git__prefixcmp(ref, "refs/")) { @@ -225,8 +213,11 @@ int git_push_update_tips(git_push *push) error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name)); if (!error) { - if ((error = git_reference_delete(remote_ref)) < 0) + if ((error = git_reference_delete(remote_ref)) < 0) { + git_reference_free(remote_ref); goto on_error; + } + git_reference_free(remote_ref); } else if (error == GIT_ENOTFOUND) giterr_clear(); else @@ -526,6 +517,18 @@ int git_push_status_foreach(git_push *push, return 0; } +void git_push_status_free(push_status *status) +{ + if (status == NULL) + return; + + if (status->msg) + git__free(status->msg); + + git__free(status->ref); + git__free(status); +} + void git_push_free(git_push *push) { push_spec *spec; @@ -541,7 +544,7 @@ void git_push_free(git_push *push) git_vector_free(&push->specs); git_vector_foreach(&push->status, i, status) { - free_status(status); + git_push_status_free(status); } git_vector_free(&push->status); diff --git a/src/push.h b/src/push.h index 629583189..e982b8385 100644 --- a/src/push.h +++ b/src/push.h @@ -41,4 +41,11 @@ struct git_push { unsigned pb_parallelism; }; +/** + * Free the given push status object + * + * @param status The push status object + */ +void git_push_status_free(push_status *status); + #endif diff --git a/src/transports/local.c b/src/transports/local.c index 44431d587..5ed7b4519 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -16,6 +16,7 @@ #include "git2/pack.h" #include "git2/commit.h" #include "git2/revparse.h" +#include "git2/push.h" #include "pack-objects.h" #include "refs.h" #include "posix.h" @@ -23,6 +24,8 @@ #include "buffer.h" #include "repository.h" #include "odb.h" +#include "push.h" +#include "remote.h" typedef struct { git_transport parent; @@ -79,8 +82,10 @@ static int add_ref(transport_local *t, const char *name) head = NULL; - /* If it's not an annotated tag, just get out */ - if (git_object_type(obj) != GIT_OBJ_TAG) { + /* If it's not an annotated tag, or if we're mocking + * git-receive-pack, just get out */ + if (git_object_type(obj) != GIT_OBJ_TAG || + t->direction != GIT_DIRECTION_FETCH) { git_object_free(obj); return 0; } @@ -125,8 +130,8 @@ static int store_refs(transport_local *t) /* Sort the references first */ git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); - /* Add HEAD */ - if (add_ref(t, GIT_HEAD_FILE) < 0) + /* Add HEAD iff direction is fetch */ + if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0) goto on_error; for (i = 0; i < ref_names.count; ++i) { @@ -245,6 +250,191 @@ static int local_negotiate_fetch( return 0; } +static int local_push_copy_object( + git_odb *local_odb, + git_odb *remote_odb, + git_pobject *obj) +{ + int error = 0; + git_odb_object *odb_obj = NULL; + git_odb_stream *odb_stream; + size_t odb_obj_size; + git_otype odb_obj_type; + git_oid remote_odb_obj_oid; + + /* Object already exists in the remote ODB; do nothing and return 0*/ + if (git_odb_exists(remote_odb, &obj->id)) + return 0; + + if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0) + return error; + + odb_obj_size = git_odb_object_size(odb_obj); + odb_obj_type = git_odb_object_type(odb_obj); + + if ((error = git_odb_open_wstream(&odb_stream, remote_odb, + odb_obj_size, odb_obj_type)) < 0) + goto on_error; + + if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj), + odb_obj_size) < 0 || + odb_stream->finalize_write(&remote_odb_obj_oid, odb_stream) < 0) { + error = -1; + } else if (git_oid_cmp(&obj->id, &remote_odb_obj_oid) != 0) { + giterr_set(GITERR_ODB, "Error when writing object to remote odb " + "during local push operation. Remote odb object oid does not " + "match local oid."); + error = -1; + } + + odb_stream->free(odb_stream); + +on_error: + git_odb_object_free(odb_obj); + return error; +} + +static int local_push_update_remote_ref( + git_repository *remote_repo, + const char *lref, + const char *rref, + git_oid *loid, + git_oid *roid) +{ + int error; + git_reference *remote_ref = NULL; + + /* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */ + rref = rref ? rref : lref; + + if (lref) { + /* Create or update a ref */ + if ((error = git_reference_create(NULL, remote_repo, rref, loid, + !git_oid_iszero(roid))) < 0) + return error; + } else { + /* Delete a ref */ + if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + return error; + } + + if ((error = git_reference_delete(remote_ref)) < 0) + return error; + + git_reference_free(remote_ref); + } + + return 0; +} + +static int local_push( + git_transport *transport, + git_push *push) +{ + transport_local *t = (transport_local *)transport; + git_odb *remote_odb = NULL; + git_odb *local_odb = NULL; + git_repository *remote_repo = NULL; + push_spec *spec; + char *url = NULL; + int error; + unsigned int i; + size_t j; + + if ((error = git_repository_open(&remote_repo, push->remote->url)) < 0) + return error; + + /* We don't currently support pushing locally to non-bare repos. Proper + non-bare repo push support would require checking configs to see if + we should override the default 'don't let this happen' behavior */ + if (!remote_repo->is_bare) { + error = -1; + goto on_error; + } + + if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 || + (error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0) + goto on_error; + + for (i = 0; i < push->pb->nr_objects; i++) { + if ((error = local_push_copy_object(local_odb, remote_odb, + &push->pb->object_list[i])) < 0) + goto on_error; + } + + push->unpack_ok = 1; + + git_vector_foreach(&push->specs, j, spec) { + push_status *status; + const git_error *last; + char *ref = spec->rref ? spec->rref : spec->lref; + + status = git__calloc(sizeof(push_status), 1); + if (!status) + goto on_error; + + status->ref = git__strdup(ref); + if (!status->ref) { + git_push_status_free(status); + goto on_error; + } + + error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref, + &spec->loid, &spec->roid); + + switch (error) { + case GIT_OK: + break; + case GIT_EINVALIDSPEC: + status->msg = git__strdup("funny refname"); + break; + case GIT_ENOTFOUND: + status->msg = git__strdup("Remote branch not found to delete"); + break; + default: + last = giterr_last(); + + if (last && last->message) + status->msg = git__strdup(last->message); + else + status->msg = git__strdup("Unspecified error encountered"); + break; + } + + /* failed to allocate memory for a status message */ + if (error < 0 && !status->msg) { + git_push_status_free(status); + goto on_error; + } + + /* failed to insert the ref update status */ + if ((error = git_vector_insert(&push->status, status)) < 0) { + git_push_status_free(status); + goto on_error; + } + } + + if (push->specs.length) { + int flags = t->flags; + url = git__strdup(t->url); + + if (!url || t->parent.close(&t->parent) < 0 || + t->parent.connect(&t->parent, url, + push->remote->cred_acquire_cb, NULL, GIT_DIRECTION_PUSH, flags)) + goto on_error; + } + + error = 0; + +on_error: + git_repository_free(remote_repo); + git__free(url); + + return error; +} + typedef struct foreach_data { git_transfer_progress *stats; git_transfer_progress_callback progress_cb; @@ -379,30 +569,39 @@ static void local_cancel(git_transport *transport) static int local_close(git_transport *transport) { transport_local *t = (transport_local *)transport; + size_t i; + git_remote_head *head; t->connected = 0; - git_repository_free(t->repo); - t->repo = NULL; + + if (t->repo) { + git_repository_free(t->repo); + t->repo = NULL; + } + + git_vector_foreach(&t->refs, i, head) { + git__free(head->name); + git__free(head); + } + + git_vector_free(&t->refs); + + if (t->url) { + git__free(t->url); + t->url = NULL; + } return 0; } static void local_free(git_transport *transport) { - unsigned int i; - transport_local *t = (transport_local *) transport; - git_vector *vec = &t->refs; - git_remote_head *head; + transport_local *t = (transport_local *)transport; - assert(transport); + /* Close the transport, if it's still open. */ + local_close(transport); - git_vector_foreach (vec, i, head) { - git__free(head->name); - git__free(head); - } - git_vector_free(vec); - - git__free(t->url); + /* Free the transport */ git__free(t); } @@ -423,6 +622,7 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param) t->parent.connect = local_connect; t->parent.negotiate_fetch = local_negotiate_fetch; t->parent.download_pack = local_download_pack; + t->parent.push = local_push; t->parent.close = local_close; t->parent.free = local_free; t->parent.ls = local_ls;