summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Schubert <schu@schu.io>2012-08-18 12:47:49 +0200
committerMichael Schubert <schu@schu.io>2012-10-11 20:48:00 +0200
commitf6635b17215164eb1908afed70f53bd479233502 (patch)
tree038f2015cda4fab8cef16a694c3226fb35d54f3d
parentf869d2c09221f9f1bb9c70e666eaeceb19da43f2 (diff)
downloadlibgit2-f6635b17215164eb1908afed70f53bd479233502.tar.gz
gsoc-push WIP
-rw-r--r--include/git2.h1
-rw-r--r--include/git2/push.h59
-rw-r--r--include/git2/remote.h6
-rw-r--r--include/git2/types.h1
-rw-r--r--src/protocol.c5
-rw-r--r--src/push.c449
-rw-r--r--src/transport.h7
-rw-r--r--src/transports/git.c8
-rw-r--r--src/transports/http.c41
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;