diff options
author | Michael Schubert <schu@schu.io> | 2012-08-18 12:47:49 +0200 |
---|---|---|
committer | Michael Schubert <schu@schu.io> | 2012-10-11 20:48:00 +0200 |
commit | f6635b17215164eb1908afed70f53bd479233502 (patch) | |
tree | 038f2015cda4fab8cef16a694c3226fb35d54f3d | |
parent | f869d2c09221f9f1bb9c70e666eaeceb19da43f2 (diff) | |
download | libgit2-f6635b17215164eb1908afed70f53bd479233502.tar.gz |
gsoc-push WIP
-rw-r--r-- | include/git2.h | 1 | ||||
-rw-r--r-- | include/git2/push.h | 59 | ||||
-rw-r--r-- | include/git2/remote.h | 6 | ||||
-rw-r--r-- | include/git2/types.h | 1 | ||||
-rw-r--r-- | src/protocol.c | 5 | ||||
-rw-r--r-- | src/push.c | 449 | ||||
-rw-r--r-- | src/transport.h | 7 | ||||
-rw-r--r-- | src/transports/git.c | 8 | ||||
-rw-r--r-- | src/transports/http.c | 41 |
9 files changed, 564 insertions, 13 deletions
diff --git a/include/git2.h b/include/git2.h index d55543986..95475c591 100644 --- a/include/git2.h +++ b/include/git2.h @@ -39,6 +39,7 @@ #include "git2/remote.h" #include "git2/clone.h" #include "git2/checkout.h" +#include "git2/push.h" #include "git2/attr.h" #include "git2/ignore.h" diff --git a/include/git2/push.h b/include/git2/push.h new file mode 100644 index 000000000..5b3613924 --- /dev/null +++ b/include/git2/push.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_push_h__ +#define INCLUDE_git_push_h__ + +#include "common.h" + +/** + * @file git2/push.h + * @brief Git push management functions + * @defgroup git_push push management functions + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Create a new push object + * + * @param out New push object + * @param remote Remote instance + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_push_new(git_push **out, git_remote *remote); + +/** + * Add a refspec to be pushed + * + * @param push The push object + * @param refspec Refspec string + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_push_add_refspec(git_push *push, const char *refspec); + +/** + * Actually push all given refspecs + * + * @param push The push object + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_push_finish(git_push *push); + +/** + * Free the given push object + * + * @param push The push object + */ +GIT_EXTERN(void) git_push_free(git_push *push); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/remote.h b/include/git2/remote.h index cb80f432e..15886ef23 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -188,6 +188,12 @@ GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); /** + * + * + */ +GIT_EXTERN(int) git_remote_push(git_remote *remote); + +/** * Check whether the remote is connected * * Check whether the remote's underlying transport is connected to the diff --git a/include/git2/types.h b/include/git2/types.h index 01ddbf3d6..7f92506d3 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -191,6 +191,7 @@ typedef enum { typedef struct git_refspec git_refspec; typedef struct git_remote git_remote; +typedef struct git_push git_push; typedef struct git_remote_head git_remote_head; typedef struct git_remote_callbacks git_remote_callbacks; diff --git a/src/protocol.c b/src/protocol.c index affad5173..8f14beaf0 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -101,6 +101,11 @@ int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps) continue; } + if(!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) { + caps->common = caps->delete_refs = 1; + ptr += strlen(GIT_CAP_DELETE_REFS); + continue; + } /* We don't know this capability, so skip it */ ptr = strchr(ptr, ' '); diff --git a/src/push.c b/src/push.c new file mode 100644 index 000000000..21c2cb798 --- /dev/null +++ b/src/push.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "pack.h" +#include "pack-objects.h" +#include "remote.h" +#include "transport.h" +#include "vector.h" + +#include "git2/commit.h" +#include "git2/index.h" +#include "git2/pack.h" +#include "git2/push.h" +#include "git2/remote.h" +#include "git2/revwalk.h" +#include "git2/tree.h" +#include "git2/version.h" + +typedef struct push_spec { + char *lref; + char *rref; + + git_oid loid; + git_oid roid; + + bool force; +} push_spec; + +struct git_push { + git_repository *repo; + git_packbuilder *pb; + git_remote *remote; + git_vector specs; +}; + +int git_push_new(git_push **out, git_remote *remote) +{ + git_push *p; + + *out = NULL; + + p = git__malloc(sizeof(*p)); + GITERR_CHECK_ALLOC(p); + memset(p, 0x0, sizeof(*p)); + + p->repo = remote->repo; + p->remote = remote; + + if (git_vector_init(&p->specs, 0, NULL) < 0) { + git__free(p); + return -1; + } + + *out = p; + return 0; +} + +static void free_refspec(push_spec *spec) +{ + if (spec == NULL) + return; + if (spec->lref) + git__free(spec->lref); + if (spec->rref) + git__free(spec->rref); + git__free(spec); +} + +static int parse_refspec(push_spec **spec, const char *str) +{ + push_spec *s; + char *delim; + + *spec = NULL; + + s = git__malloc(sizeof(*s)); + GITERR_CHECK_ALLOC(s); + memset(s, 0x0, sizeof(*s)); + + if (str[0] == '+') { + s->force = true; + str++; + } + + delim = strchr(str, ':'); + if (delim == NULL) { + if (strlen(str)) { + s->lref = git__strdup(str); + GITERR_CHECK_ALLOC(s->lref); + } else + s->lref = NULL; + s->rref = NULL; + } else { + if (delim - str) { + s->lref = git__strndup(str, delim - str); + GITERR_CHECK_ALLOC(s->lref); + } else + s->lref = NULL; + if (strlen(delim + 1)) { + s->rref = git__strdup(delim + 1); + GITERR_CHECK_ALLOC(s->rref); + } else + s->rref = NULL; + } + *spec = s; + return 0; +} + +int git_push_add_refspec(git_push *push, const char *refspec) +{ + push_spec *spec; + + if (strchr(refspec, '*')) { + giterr_set(GITERR_INVALID, "No wildcard refspec supported"); + return -1; + } + + if (parse_refspec(&spec, refspec) || + git_vector_insert(&push->specs, spec) < 0) { + free_refspec(spec); + return -1; + } + + return 0; +} + +static int gen_pktline(git_buf *buf, git_push *push) +{ + git_remote_head *head; + push_spec *spec; + unsigned int i, j, len; + char hex[41]; hex[40] = '\0'; + + git_vector_foreach(&push->specs, i, spec) { + len = 2*GIT_OID_HEXSZ + 7; + + if (spec->lref) { + if (git_reference_name_to_oid(&spec->loid, push->repo, + spec->lref) < 0) + return -1; + + if (!spec->rref) { + /* + * No remote reference given; if we find a remote + * reference with the same name we will update it, + * otherwise a new reference will be created. + */ + len += strlen(spec->lref); + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->lref, head->name)) { + /* update remote reference */ + git_oid_cpy(&spec->roid, &head->oid); + git_oid_fmt(hex, &spec->roid); + git_buf_printf(buf, "%04x%s ", len, hex); + + git_oid_fmt(hex, &spec->loid); + git_buf_printf(buf, "%s %s\n", hex, + spec->lref); + + break; + } + } + if (git_oid_iszero(&spec->roid)) { + /* create remote reference */ + git_oid_fmt(hex, &spec->loid); + git_buf_printf(buf, "%04x%s %s %s\n", len, + GIT_OID_HEX_ZERO, hex, spec->lref); + } + } else { + /* + * Remote reference given; update the given + * reference or create it. + */ + len += strlen(spec->rref); + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->rref, head->name)) { + /* update remote reference */ + git_oid_cpy(&spec->roid, &head->oid); + git_oid_fmt(hex, &spec->roid); + git_buf_printf(buf, "%04x%s ", len, hex); + + git_oid_fmt(hex, &spec->loid); + git_buf_printf(buf, "%s %s\n", hex, + spec->rref); + + break; + } + } + if (git_oid_iszero(&spec->roid)) { + /* create remote reference */ + git_oid_fmt(hex, &spec->loid); + git_buf_printf(buf, "%04x%s %s %s\n", len, + GIT_OID_HEX_ZERO, hex, spec->rref); + } + } + } else { + /* delete remote reference */ + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->rref, head->name)) { + len += strlen(spec->rref); + + git_oid_fmt(hex, &head->oid); + git_buf_printf(buf, "%04x%s %s %s\n", len, + hex, GIT_OID_HEX_ZERO, head->name); + + break; + } + } + } + } + git_buf_puts(buf, "0000"); + + if (git_buf_oom(buf)) + return -1; + + return 0; +} + +static int revwalk(git_vector *commits, git_push *push) +{ + git_remote_head *head; + push_spec *spec; + git_revwalk *rw; + git_oid oid; + unsigned int i; + int error = -1; + + if (git_revwalk_new(&rw, push->repo) < 0) + return -1; + + git_revwalk_sorting(rw, GIT_SORT_TIME); + + git_vector_foreach(&push->specs, i, spec) { + if (git_oid_iszero(&spec->loid)) + /* + * Delete reference on remote side; + * nothing to do here. + */ + continue; + + if (!git_oid_cmp(&spec->loid, &spec->roid)) + continue; /* up-to-date */ + + if (git_revwalk_push(rw, &spec->loid) < 0) + goto on_error; + + if (!spec->force) { + ; /* TODO: check if common ancestor */ + } + + if (!git_oid_iszero(&spec->roid)) { + if (git_revwalk_hide(rw, &spec->roid) < 0) + goto on_error; + } + } + + git_vector_foreach(&push->remote->refs, i, head) { + if (git_oid_iszero(&head->oid)) + continue; + + /* TODO */ + git_revwalk_hide(rw, &head->oid); + } + + while ((error = git_revwalk_next(&oid, rw)) == 0) { + git_oid *o = git__malloc(GIT_OID_RAWSZ); + GITERR_CHECK_ALLOC(o); + git_oid_cpy(o, &oid); + if (git_vector_insert(commits, o) < 0) { + error = -1; + goto on_error; + } + } + +on_error: + git_revwalk_free(rw); + return error == GIT_ITEROVER ? 0 : error; +} + +static int queue_objects(git_push *push) +{ + git_vector commits; + git_oid *o; + unsigned int i; + int error = -1; + + if (git_vector_init(&commits, 0, NULL) < 0) + return -1; + + if (revwalk(&commits, push) < 0) + goto on_error; + + if (!commits.length) + return 0; /* nothing to do */ + + git_vector_foreach(&commits, i, o) { + if (git_packbuilder_insert(push->pb, o, NULL) < 0) + goto on_error; + } + + git_vector_foreach(&commits, i, o) { + git_object *obj; + + if (git_object_lookup(&obj, push->repo, o, GIT_OBJ_ANY) < 0) + goto on_error; + + switch (git_object_type(obj)) { + case GIT_OBJ_TAG: /* TODO: expect tags */ + case GIT_OBJ_COMMIT: + if (git_packbuilder_insert_tree(push->pb, + git_commit_tree_oid((git_commit *)obj)) < 0) { + git_object_free(obj); + goto on_error; + } + break; + case GIT_OBJ_TREE: + case GIT_OBJ_BLOB: + default: + git_object_free(obj); + giterr_set(GITERR_INVALID, "Given object type invalid"); + goto on_error; + } + git_object_free(obj); + } + error = 0; + +on_error: + git_vector_foreach(&commits, i, o) { + git__free(o); + } + git_vector_free(&commits); + return error; +} + +static int do_push(git_push *push) +{ + git_transport *t = push->remote->transport; + git_buf pktline = GIT_BUF_INIT; + + if (gen_pktline(&pktline, push) < 0) + goto on_error; + +#ifdef PUSH_DEBUG +{ + git_remote_head *head; + push_spec *spec; + unsigned int i; + char hex[41]; hex[40] = '\0'; + + git_vector_foreach(&push->remote->refs, i, head) { + git_oid_fmt(hex, &head->oid); + fprintf(stderr, "%s (%s)\n", hex, head->name); + } + + git_vector_foreach(&push->specs, i, spec) { + git_oid_fmt(hex, &spec->roid); + fprintf(stderr, "%s (%s) -> ", hex, spec->lref); + git_oid_fmt(hex, &spec->loid); + fprintf(stderr, "%s (%s)\n", hex, spec->rref ? + spec->rref : spec->lref); + } +} +#endif + + if (git_packbuilder_new(&push->pb, push->repo) < 0) + goto on_error; + + if (queue_objects(push) < 0) + goto on_error; + + if (t->rpc) { + git_buf pack = GIT_BUF_INIT; + + if (git_packbuilder_write_buf(&pack, push->pb) < 0) + goto on_error; + + if (t->push(t, &pktline, &pack) < 0) { + git_buf_free(&pack); + goto on_error; + } + + git_buf_free(&pack); + } else { + if (gitno_send(push->remote->transport, + pktline.ptr, pktline.size, 0) < 0) + goto on_error; + + if (git_packbuilder_send(push->pb, push->remote->transport) < 0) + goto on_error; + } + + git_packbuilder_free(push->pb); + git_buf_free(&pktline); + return 0; + +on_error: + git_packbuilder_free(push->pb); + git_buf_free(&pktline); + return -1; +} + +static int cb_filter_refs(git_remote_head *ref, void *data) +{ + git_remote *remote = data; + return git_vector_insert(&remote->refs, ref); +} + +static int filter_refs(git_remote *remote) +{ + git_vector_clear(&remote->refs); + return git_remote_ls(remote, cb_filter_refs, remote); +} + +int git_push_finish(git_push *push) +{ + if (!git_remote_connected(push->remote)) { + if (git_remote_connect(push->remote, GIT_DIR_PUSH) < 0) + return -1; + } + + if (filter_refs(push->remote) < 0 || do_push(push) < 0) { + git_remote_disconnect(push->remote); + return -1; + } + + git_remote_disconnect(push->remote); + return 0; +} + +void git_push_free(git_push *push) +{ + push_spec *spec; + unsigned int i; + + if (push == NULL) + return; + + git_vector_foreach(&push->specs, i, spec) { + free_refspec(spec); + } + git_vector_free(&push->specs); + + git__free(push); +} diff --git a/src/transport.h b/src/transport.h index 80ceda75e..b907ceea3 100644 --- a/src/transport.h +++ b/src/transport.h @@ -11,6 +11,7 @@ #include "git2/indexer.h" #include "git2/remote.h" +#include "buffer.h" #include "vector.h" #include "posix.h" #include "common.h" @@ -26,6 +27,7 @@ #define GIT_CAP_SIDE_BAND "side-band" #define GIT_CAP_SIDE_BAND_64K "side-band-64k" #define GIT_CAP_INCLUDE_TAG "include-tag" +#define GIT_CAP_DELETE_REFS "delete-refs" typedef struct git_transport_caps { int common:1, @@ -33,7 +35,8 @@ typedef struct git_transport_caps { multi_ack: 1, side_band:1, side_band_64k:1, - include_tag:1; + include_tag:1, + delete_refs:1; } git_transport_caps; #ifdef GIT_SSL @@ -104,7 +107,7 @@ struct git_transport { /** * Push the changes over */ - int (*push)(struct git_transport *transport); + int (*push)(struct git_transport *transport, git_buf *pktline, git_buf *pack); /** * Negotiate the minimal amount of objects that need to be * retrieved diff --git a/src/transports/git.c b/src/transports/git.c index b757495c5..5d5f0c68a 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -106,7 +106,8 @@ static int do_connect(transport_git *t, const char *url) if (gitno_connect((git_transport *)t, host, port) < 0) goto on_error; - if (send_request((git_transport *)t, NULL, url) < 0) + if (send_request((git_transport *)t, + t->parent.direction ? "git-receive-pack" : NULL, url) < 0) goto on_error; git__free(host); @@ -129,11 +130,6 @@ static int git_connect(git_transport *transport, int direction) { transport_git *t = (transport_git *) transport; - if (direction == GIT_DIR_PUSH) { - giterr_set(GITERR_NET, "Pushing over git:// is not supported"); - return -1; - } - t->parent.direction = direction; /* Connect and ask for the refs */ diff --git a/src/transports/http.c b/src/transports/http.c index a038408ce..1f24a254d 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -540,10 +540,8 @@ static int http_connect(git_transport *transport, int direction) const char *default_port; git_pkt *pkt; - if (direction == GIT_DIR_PUSH) { - giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); - return -1; - } + if (direction == GIT_DIR_PUSH) + service = "receive-pack"; t->parent.direction = direction; @@ -568,7 +566,7 @@ static int http_connect(git_transport *transport, int direction) if ((ret = do_connect(t)) < 0) goto cleanup; - if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0) + if ((ret = send_request(t, t->service, NULL, 0, 1)) < 0) goto cleanup; if ((ret = git_protocol_store_refs(transport, 2)) < 0) @@ -595,6 +593,38 @@ cleanup: return ret; } +static int http_push(struct git_transport *transport, git_buf *pktline, git_buf *pack) +{ + transport_http *t = (transport_http *) transport; + gitno_buffer *buf = &transport->buffer; + git_buf request = GIT_BUF_INIT; + int ret; + + if ((ret = do_connect(t)) < 0) + return -1; + + if ((ret = gen_request(&request, t->path, t->host, "POST", "receive-pack", + pktline->size + pack->size, 0, t->user, t->pass)) < 0) + goto cleanup; + + if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0) + goto cleanup; + + if ((ret = gitno_send(transport, pktline->ptr, pktline->size, 0)) < 0) + goto cleanup; + + if ((ret = gitno_send(transport, pack->ptr, pack->size, 0)) < 0) + goto cleanup; + + setup_gitno_buffer(transport); + + ret = gitno_recv(buf); + +cleanup: + git_buf_free(&request); + return ret; +} + static int http_negotiation_step(struct git_transport *transport, void *data, size_t len) { transport_http *t = (transport_http *) transport; @@ -693,6 +723,7 @@ int git_transport_http(git_transport **out) memset(t, 0x0, sizeof(transport_http)); t->parent.connect = http_connect; + t->parent.push = http_push; t->parent.negotiation_step = http_negotiation_step; t->parent.close = http_close; t->parent.free = http_free; |