Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/transports/git.c')
-rw-r--r--src/transports/git.c544
1 files changed, 209 insertions, 335 deletions
diff --git a/src/transports/git.c b/src/transports/git.c
index 5baa810f0..3a0b86345 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -1,50 +1,42 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
* 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 "git2/net.h"
-#include "git2/common.h"
-#include "git2/types.h"
-#include "git2/errors.h"
-#include "git2/net.h"
-#include "git2/revwalk.h"
-
-#include "vector.h"
-#include "transport.h"
-#include "pkt.h"
-#include "common.h"
+#include "git2.h"
+#include "buffer.h"
#include "netops.h"
-#include "filebuf.h"
-#include "repository.h"
-#include "fetch.h"
-#include "protocol.h"
+
+#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_git[] = "git://";
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
typedef struct {
- git_transport parent;
- git_protocol proto;
- GIT_SOCKET socket;
- git_vector refs;
- git_remote_head **heads;
- git_transport_caps caps;
- char buff[1024];
- gitno_buffer buf;
-#ifdef GIT_WIN32
- WSADATA wsd;
-#endif
-} transport_git;
+ git_smart_subtransport_stream parent;
+ gitno_socket socket;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} git_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ git_transport *owner;
+ git_stream *current_stream;
+} git_subtransport;
/*
- * Create a git procol request.
+ * Create a git protocol request.
*
* For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
*/
static int gen_proto(git_buf *request, const char *cmd, const char *url)
{
char *delim, *repo;
- char default_command[] = "git-upload-pack";
char host[] = "host=";
size_t len;
@@ -60,9 +52,6 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
if (delim == NULL)
delim = strchr(url, '/');
- if (cmd == NULL)
- cmd = default_command;
-
len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
git_buf_grow(request, len);
@@ -77,402 +66,287 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
return 0;
}
-static int send_request(GIT_SOCKET s, const char *cmd, const char *url)
+static int send_command(git_stream *s)
{
int error;
git_buf request = GIT_BUF_INIT;
- error = gen_proto(&request, cmd, url);
+ error = gen_proto(&request, s->cmd, s->url);
if (error < 0)
goto cleanup;
- error = gitno_send(s, request.ptr, request.size, 0);
+ /* It looks like negative values are errors here, and positive values
+ * are the number of bytes sent. */
+ error = gitno_send(&s->socket, request.ptr, request.size, 0);
+
+ if (error >= 0)
+ s->sent_command = 1;
cleanup:
git_buf_free(&request);
return error;
}
-/*
- * Parse the URL and connect to a server, storing the socket in
- * out. For convenience this also takes care of asking for the remote
- * refs
- */
-static int do_connect(transport_git *t, const char *url)
+static int git_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
{
- char *host, *port;
- const char prefix[] = "git://";
- int error;
-
- t->socket = INVALID_SOCKET;
+ git_stream *s = (git_stream *)stream;
+ gitno_buffer buf;
- if (!git__prefixcmp(url, prefix))
- url += strlen(prefix);
+ *bytes_read = 0;
- if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
+ if (!s->sent_command && send_command(s) < 0)
return -1;
- if ((error = gitno_connect(&t->socket, host, port)) == 0) {
- error = send_request(t->socket, NULL, url);
- }
-
- git__free(host);
- git__free(port);
-
- if (error < 0 && t->socket != INVALID_SOCKET) {
- gitno_close(t->socket);
- t->socket = INVALID_SOCKET;
- }
+ gitno_buffer_setup(&s->socket, &buf, buffer, buf_size);
- if (t->socket == INVALID_SOCKET) {
- giterr_set(GITERR_NET, "Failed to connect to the host");
+ if (gitno_recv(&buf) < 0)
return -1;
- }
+
+ *bytes_read = buf.offset;
return 0;
}
-/*
- * Read from the socket and store the references in the vector
- */
-static int store_refs(transport_git *t)
+static int git_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
{
- gitno_buffer *buf = &t->buf;
- int ret = 0;
-
- while (1) {
- if ((ret = gitno_recv(buf)) < 0)
- return -1;
- if (ret == 0) /* Orderly shutdown, so exit */
- return 0;
-
- ret = git_protocol_store_refs(&t->proto, buf->data, buf->offset);
- if (ret == GIT_EBUFS) {
- gitno_consume_n(buf, buf->len);
- continue;
- }
-
- if (ret < 0)
- return ret;
-
- gitno_consume_n(buf, buf->offset);
-
- if (t->proto.flush) { /* No more refs */
- t->proto.flush = 0;
- return 0;
- }
- }
+ git_stream *s = (git_stream *)stream;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ return gitno_send(&s->socket, buffer, len, 0);
}
-static int detect_caps(transport_git *t)
+static void git_stream_free(git_smart_subtransport_stream *stream)
{
- git_vector *refs = &t->refs;
- git_pkt_ref *pkt;
- git_transport_caps *caps = &t->caps;
- const char *ptr;
-
- pkt = git_vector_get(refs, 0);
- /* No refs or capabilites, odd but not a problem */
- if (pkt == NULL || pkt->capabilities == NULL)
- return 0;
+ git_stream *s = (git_stream *)stream;
+ git_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int ret;
- ptr = pkt->capabilities;
- while (ptr != NULL && *ptr != '\0') {
- if (*ptr == ' ')
- ptr++;
+ GIT_UNUSED(ret);
- if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
- caps->common = caps->ofs_delta = 1;
- ptr += strlen(GIT_CAP_OFS_DELTA);
- continue;
- }
+ t->current_stream = NULL;
- /* We don't know this capability, so skip it */
- ptr = strchr(ptr, ' ');
+ if (s->socket.socket) {
+ ret = gitno_close(&s->socket);
+ assert(!ret);
}
- return 0;
+ git__free(s->url);
+ git__free(s);
}
-/*
- * Since this is a network connection, we need to parse and store the
- * pkt-lines at this stage and keep them there.
- */
-static int git_connect(git_transport *transport, int direction)
+static int git_stream_alloc(
+ git_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
-
- if (direction == GIT_DIR_PUSH) {
- giterr_set(GITERR_NET, "Pushing over git:// is not supported");
- return -1;
- }
+ git_stream *s;
- t->parent.direction = direction;
- if (git_vector_init(&t->refs, 16, NULL) < 0)
+ if (!stream)
return -1;
- /* Connect and ask for the refs */
- if (do_connect(t, transport->url) < 0)
- goto cleanup;
+ s = git__calloc(sizeof(git_stream), 1);
+ GITERR_CHECK_ALLOC(s);
- gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket);
+ s->parent.subtransport = &t->parent;
+ s->parent.read = git_stream_read;
+ s->parent.write = git_stream_write;
+ s->parent.free = git_stream_free;
- t->parent.connected = 1;
- if (store_refs(t) < 0)
- goto cleanup;
+ s->cmd = cmd;
+ s->url = git__strdup(url);
- if (detect_caps(t) < 0)
- goto cleanup;
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+ *stream = &s->parent;
return 0;
-cleanup:
- git_vector_free(&t->refs);
- return -1;
}
-static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque)
+static int _git_uploadpack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
- git_pkt *p = NULL;
+ char *host, *port, *user=NULL, *pass=NULL;
+ git_stream *s;
+
+ *stream = NULL;
- git_vector_foreach(refs, i, p) {
- git_pkt_ref *pkt = NULL;
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
- if (p->type != GIT_PKT_REF)
- continue;
+ if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0)
+ return -1;
- pkt = (git_pkt_ref *)p;
+ s = (git_stream *)*stream;
- if (list_cb(&pkt->head, opaque) < 0) {
- giterr_set(GITERR_NET, "User callback returned error");
- return -1;
- }
- }
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
+ goto on_error;
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
return 0;
+
+on_error:
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ return -1;
}
-/* Wait until we get an ack from the */
-static int recv_pkt(gitno_buffer *buf)
+static int _git_uploadpack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- const char *ptr = buf->data, *line_end;
- git_pkt *pkt;
- int pkt_type, error;
-
- do {
- /* Wait for max. 1 second */
- if ((error = gitno_select_in(buf, 1, 0)) < 0) {
- return -1;
- } else if (error == 0) {
- /*
- * Some servers don't respond immediately, so if this
- * happens, we keep sending information until it
- * answers. Pretend we received a NAK to convince higher
- * layers to do so.
- */
- return GIT_PKT_NAK;
- }
-
- if ((error = gitno_recv(buf)) < 0)
- return -1;
-
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
- if (error == GIT_EBUFS)
- continue;
- if (error < 0)
- return -1;
- } while (error);
-
- gitno_consume(buf, line_end);
- pkt_type = pkt->type;
- git__free(pkt);
-
- return pkt_type;
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
}
-static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
+static int _git_receivepack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
- git_revwalk *walk;
- git_oid oid;
- int error;
- unsigned int i;
- git_buf data = GIT_BUF_INIT;
- gitno_buffer *buf = &t->buf;
+ char *host, *port, *user=NULL, *pass=NULL;
+ git_stream *s;
- if (git_pkt_buffer_wants(wants, &t->caps, &data) < 0)
- return -1;
+ *stream = NULL;
- if (git_fetch_setup_walk(&walk, repo) < 0)
- goto on_error;
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
- if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)
- goto on_error;
+ if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0)
+ return -1;
- git_buf_clear(&data);
- /*
- * We don't support any kind of ACK extensions, so the negotiation
- * boils down to sending what we have and listening for an ACK
- * every once in a while.
- */
- i = 0;
- while ((error = git_revwalk_next(&oid, walk)) == 0) {
- git_pkt_buffer_have(&oid, &data);
- i++;
- if (i % 20 == 0) {
- int pkt_type;
-
- git_pkt_buffer_flush(&data);
- if (git_buf_oom(&data))
- goto on_error;
-
- if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)
- goto on_error;
-
- pkt_type = recv_pkt(buf);
-
- if (pkt_type == GIT_PKT_ACK) {
- break;
- } else if (pkt_type == GIT_PKT_NAK) {
- continue;
- } else {
- giterr_set(GITERR_NET, "Unexpected pkt type");
- goto on_error;
- }
-
- }
- }
- if (error < 0 && error != GIT_REVWALKOVER)
+ s = (git_stream *)*stream;
+
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
goto on_error;
- /* Tell the other end that we're done negotiating */
- git_buf_clear(&data);
- git_pkt_buffer_flush(&data);
- git_pkt_buffer_done(&data);
- if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
goto on_error;
- git_buf_free(&data);
- git_revwalk_free(walk);
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
return 0;
on_error:
- git_buf_free(&data);
- git_revwalk_free(walk);
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
return -1;
}
-static int git_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats)
+static int _git_receivepack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
- int error = 0, read_bytes;
- gitno_buffer *buf = &t->buf;
- git_pkt *pkt;
- const char *line_end, *ptr;
-
- /*
- * For now, we ignore everything and wait for the pack
- */
- do {
- ptr = buf->data;
- /* Whilst we're searching for the pack */
- while (1) {
- if (buf->offset == 0) {
- break;
- }
-
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
- if (error == GIT_EBUFS)
- break;
-
- if (error < 0)
- return error;
-
- if (pkt->type == GIT_PKT_PACK) {
- git__free(pkt);
- return git_fetch__download_pack(buf->data, buf->offset, t->socket, repo, bytes, stats);
- }
-
- /* For now we don't care about anything */
- git__free(pkt);
- gitno_consume(buf, line_end);
- }
-
- read_bytes = gitno_recv(buf);
- } while (read_bytes);
-
- return read_bytes;
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
}
-static int git_close(git_transport *transport)
+static int _git_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
{
- transport_git *t = (transport_git*) transport;
+ git_subtransport *t = (git_subtransport *) subtransport;
- /* Can't do anything if there's an error, so don't bother checking */
- git_pkt_send_flush(t->socket);
- if (gitno_close(t->socket) < 0) {
- giterr_set(GITERR_NET, "Failed to close socket");
- return -1;
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return _git_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return _git_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return _git_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return _git_receivepack(t, url, stream);
}
-#ifdef GIT_WIN32
- WSACleanup();
-#endif
+ *stream = NULL;
+ return -1;
+}
+
+static int _git_close(git_smart_subtransport *subtransport)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
return 0;
}
-static void git_free(git_transport *transport)
+static void _git_free(git_smart_subtransport *subtransport)
{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
+ git_subtransport *t = (git_subtransport *) subtransport;
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- git_pkt_free(p);
- }
+ assert(!t->current_stream);
- git_vector_free(refs);
- git__free(t->heads);
- git_buf_free(&t->proto.buf);
- git__free(t->parent.url);
git__free(t);
}
-int git_transport_git(git_transport **out)
+int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner)
{
- transport_git *t;
-#ifdef GIT_WIN32
- int ret;
-#endif
-
- t = git__malloc(sizeof(transport_git));
- GITERR_CHECK_ALLOC(t);
-
- memset(t, 0x0, sizeof(transport_git));
+ git_subtransport *t;
- t->parent.connect = git_connect;
- t->parent.ls = git_ls;
- t->parent.negotiate_fetch = git_negotiate_fetch;
- t->parent.download_pack = git_download_pack;
- t->parent.close = git_close;
- t->parent.free = git_free;
- t->proto.refs = &t->refs;
- t->proto.transport = (git_transport *) t;
+ if (!out)
+ return -1;
- *out = (git_transport *) t;
+ t = git__calloc(sizeof(git_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
-#ifdef GIT_WIN32
- ret = WSAStartup(MAKEWORD(2,2), &t->wsd);
- if (ret != 0) {
- git_free(*out);
- giterr_set(GITERR_NET, "Winsock init failed");
- return -1;
- }
-#endif
+ t->owner = owner;
+ t->parent.action = _git_action;
+ t->parent.close = _git_close;
+ t->parent.free = _git_free;
+ *out = (git_smart_subtransport *) t;
return 0;
}