# On Windows use specific platform sources
IF (WIN32 AND NOT CYGWIN)
ADD_DEFINITIONS(-DWIN32 -D_DEBUG)
- FILE(GLOB SRC src/*.c src/win32/*.c)
+ FILE(GLOB SRC src/*.c src/transports/*.c src/win32/*.c)
ELSE()
- FILE(GLOB SRC src/*.c src/unix/*.c)
+ FILE(GLOB SRC src/*.c src/transports/*.c src/unix/*.c)
ENDIF ()
# Compile and link libgit2
+++ /dev/null
-/*
- * Copyright (C) 2009-2011 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 <stdlib.h>
-#include "git2.h"
-#include "http_parser.h"
-
-#include "transport.h"
-#include "common.h"
-#include "netops.h"
-#include "buffer.h"
-#include "pkt.h"
-#include "refs.h"
-#include "fetch.h"
-#include "filebuf.h"
-#include "repository.h"
-
-enum last_cb {
- NONE,
- FIELD,
- VALUE
-};
-
-typedef struct {
- git_transport parent;
- git_vector refs;
- git_vector common;
- int socket;
- git_buf buf;
- git_remote_head **heads;
- int error;
- int transfer_finished :1,
- ct_found :1,
- ct_finished :1,
- pack_ready :1;
- enum last_cb last_cb;
- http_parser parser;
- char *content_type;
- char *host;
- char *port;
- char *service;
- git_transport_caps caps;
-#ifdef GIT_WIN32
- WSADATA wsd;
-#endif
-} transport_http;
-
-static int gen_request(git_buf *buf, const char *url, const char *host, const char *op,
- const char *service, ssize_t content_length, int ls)
-{
- const char *path = url;
-
- path = strchr(path, '/');
- if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
- path = "/";
-
- if (ls) {
- git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service);
- } else {
- git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service);
- }
- git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
- git_buf_printf(buf, "Host: %s\r\n", host);
- if (content_length > 0) {
- git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service);
- git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service);
- git_buf_printf(buf, "Content-Length: %zd\r\n", content_length);
- } else {
- git_buf_puts(buf, "Accept: */*\r\n");
- }
- git_buf_puts(buf, "\r\n");
-
- if (git_buf_oom(buf))
- return GIT_ENOMEM;
-
- return GIT_SUCCESS;
-}
-
-static int do_connect(transport_http *t, const char *host, const char *port)
-{
- GIT_SOCKET s = -1;
-
- if (t->parent.connected && http_should_keep_alive(&t->parser))
- return GIT_SUCCESS;
-
- s = gitno_connect(host, port);
- if (s < GIT_SUCCESS) {
- return git__rethrow(s, "Failed to connect to host");
- }
- t->socket = s;
- t->parent.connected = 1;
-
- return GIT_SUCCESS;
-}
-
-/*
- * The HTTP parser is streaming, so we need to wait until we're in the
- * field handler before we can be sure that we can store the previous
- * value. Right now, we only care about the
- * Content-Type. on_header_{field,value} should be kept generic enough
- * to work for any request.
- */
-
-static const char *typestr = "Content-Type";
-
-static int on_header_field(http_parser *parser, const char *str, size_t len)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
-
- if (t->last_cb == VALUE && t->ct_found) {
- t->ct_finished = 1;
- t->ct_found = 0;
- t->content_type = git__strdup(git_buf_cstr(buf));
- if (t->content_type == NULL)
- return t->error = GIT_ENOMEM;
- git_buf_clear(buf);
- }
-
- if (t->ct_found) {
- t->last_cb = FIELD;
- return 0;
- }
-
- if (t->last_cb != FIELD)
- git_buf_clear(buf);
-
- git_buf_put(buf, str, len);
- t->last_cb = FIELD;
-
- return git_buf_oom(buf);
-}
-
-static int on_header_value(http_parser *parser, const char *str, size_t len)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
-
- if (t->ct_finished) {
- t->last_cb = VALUE;
- return 0;
- }
-
- if (t->last_cb == VALUE)
- git_buf_put(buf, str, len);
-
- if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
- t->ct_found = 1;
- git_buf_clear(buf);
- git_buf_put(buf, str, len);
- }
-
- t->last_cb = VALUE;
-
- return git_buf_oom(buf);
-}
-
-static int on_headers_complete(http_parser *parser)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
-
- if (t->content_type == NULL) {
- t->content_type = git__strdup(git_buf_cstr(buf));
- if (t->content_type == NULL)
- return t->error = GIT_ENOMEM;
- }
-
- git_buf_clear(buf);
- git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
- if (git_buf_oom(buf))
- return GIT_ENOMEM;
-
- if (strcmp(t->content_type, git_buf_cstr(buf)))
- return t->error = git__throw(GIT_EOBJCORRUPTED, "Content-Type '%s' is wrong", t->content_type);
-
- git_buf_clear(buf);
- return 0;
-}
-
-static int on_body_store_refs(http_parser *parser, const char *str, size_t len)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
- git_vector *refs = &t->refs;
- int error;
- const char *line_end, *ptr;
- static int first_pkt = 1;
-
- if (len == 0) { /* EOF */
- if (buf->size != 0)
- return t->error = git__throw(GIT_ERROR, "EOF and unprocessed data");
- else
- return 0;
- }
-
- git_buf_put(buf, str, len);
- ptr = buf->ptr;
- while (1) {
- git_pkt *pkt;
-
- if (buf->size == 0)
- return 0;
-
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->size);
- if (error == GIT_ESHORTBUFFER)
- return 0; /* Ask for more */
- if (error < GIT_SUCCESS)
- return t->error = git__rethrow(error, "Failed to parse pkt-line");
-
- git_buf_consume(buf, line_end);
-
- if (first_pkt) {
- first_pkt = 0;
- if (pkt->type != GIT_PKT_COMMENT)
- return t->error = git__throw(GIT_EOBJCORRUPTED, "Not a valid smart HTTP response");
- }
-
- error = git_vector_insert(refs, pkt);
- if (error < GIT_SUCCESS)
- return t->error = git__rethrow(error, "Failed to add pkt to list");
- }
-
- return error;
-}
-
-static int on_message_complete(http_parser *parser)
-{
- transport_http *t = (transport_http *) parser->data;
-
- t->transfer_finished = 1;
- return 0;
-}
-
-static int store_refs(transport_http *t)
-{
- int error = GIT_SUCCESS;
- http_parser_settings settings;
- char buffer[1024];
- gitno_buffer buf;
-
- http_parser_init(&t->parser, HTTP_RESPONSE);
- t->parser.data = t;
- memset(&settings, 0x0, sizeof(http_parser_settings));
- settings.on_header_field = on_header_field;
- settings.on_header_value = on_header_value;
- settings.on_headers_complete = on_headers_complete;
- settings.on_body = on_body_store_refs;
- settings.on_message_complete = on_message_complete;
-
- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
-
- while(1) {
- size_t parsed;
-
- error = gitno_recv(&buf);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Error receiving data from network");
-
- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
- /* Both should happen at the same time */
- if (parsed != buf.offset || t->error < GIT_SUCCESS)
- return git__rethrow(t->error, "Error parsing HTTP data");
-
- gitno_consume_n(&buf, parsed);
-
- if (error == 0 || t->transfer_finished)
- return GIT_SUCCESS;
- }
-
- return error;
-}
-
-static int http_connect(git_transport *transport, int direction)
-{
- transport_http *t = (transport_http *) transport;
- int error;
- git_buf request = GIT_BUF_INIT;
- const char *service = "upload-pack";
- const char *url = t->parent.url, *prefix = "http://";
-
- if (direction == GIT_DIR_PUSH)
- return git__throw(GIT_EINVALIDARGS, "Pushing over HTTP is not supported");
-
- t->parent.direction = direction;
- error = git_vector_init(&t->refs, 16, NULL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to init refs vector");
-
- if (!git__prefixcmp(url, prefix))
- url += strlen(prefix);
-
- error = gitno_extract_host_and_port(&t->host, &t->port, url, "80");
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- t->service = git__strdup(service);
- if (t->service == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
- }
-
- error = do_connect(t, t->host, t->port);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to connect to host");
- goto cleanup;
- }
-
- /* Generate and send the HTTP request */
- error = gen_request(&request, url, t->host, "GET", service, 0, 1);
- if (error < GIT_SUCCESS) {
- error = git__throw(error, "Failed to generate request");
- goto cleanup;
- }
-
- error = gitno_send(t->socket, request.ptr, request.size, 0);
- if (error < GIT_SUCCESS)
- error = git__rethrow(error, "Failed to send the HTTP request");
-
- error = store_refs(t);
-
-cleanup:
- git_buf_free(&request);
- git_buf_clear(&t->buf);
-
- return error;
-}
-
-static int http_ls(git_transport *transport, git_headarray *array)
-{
- transport_http *t = (transport_http *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
- int len = 0;
- git_pkt_ref *p;
-
- array->heads = git__calloc(refs->length, sizeof(git_remote_head*));
- if (array->heads == NULL)
- return GIT_ENOMEM;
-
- git_vector_foreach(refs, i, p) {
- if (p->type != GIT_PKT_REF)
- continue;
-
- array->heads[len] = &p->head;
- len++;
- }
-
- array->len = len;
- t->heads = array->heads;
-
- return GIT_SUCCESS;
-}
-
-static int on_body_parse_response(http_parser *parser, const char *str, size_t len)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
- git_vector *common = &t->common;
- int error;
- const char *line_end, *ptr;
-
- if (len == 0) { /* EOF */
- if (buf->size != 0)
- return t->error = git__throw(GIT_ERROR, "EOF and unprocessed data");
- else
- return 0;
- }
-
- git_buf_put(buf, str, len);
- ptr = buf->ptr;
- while (1) {
- git_pkt *pkt;
-
- if (buf->size == 0)
- return 0;
-
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->size);
- if (error == GIT_ESHORTBUFFER) {
- return 0; /* Ask for more */
- }
- if (error < GIT_SUCCESS)
- return t->error = git__rethrow(error, "Failed to parse pkt-line");
-
- git_buf_consume(buf, line_end);
-
- if (pkt->type == GIT_PKT_PACK) {
- free(pkt);
- t->pack_ready = 1;
- return 0;
- }
-
- if (pkt->type == GIT_PKT_NAK) {
- free(pkt);
- return 0;
- }
-
- if (pkt->type != GIT_PKT_ACK) {
- free(pkt);
- continue;
- }
-
- error = git_vector_insert(common, pkt);
- if (error < GIT_SUCCESS)
- return t->error = git__rethrow(error, "Failed to add pkt to list");
- }
-
- return error;
-
-}
-
-static int parse_response(transport_http *t)
-{
- int error = GIT_SUCCESS;
- http_parser_settings settings;
- char buffer[1024];
- gitno_buffer buf;
-
- http_parser_init(&t->parser, HTTP_RESPONSE);
- t->parser.data = t;
- t->transfer_finished = 0;
- memset(&settings, 0x0, sizeof(http_parser_settings));
- settings.on_header_field = on_header_field;
- settings.on_header_value = on_header_value;
- settings.on_headers_complete = on_headers_complete;
- settings.on_body = on_body_parse_response;
- settings.on_message_complete = on_message_complete;
-
- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
-
- while(1) {
- size_t parsed;
-
- error = gitno_recv(&buf);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Error receiving data from network");
-
- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
- /* Both should happen at the same time */
- if (parsed != buf.offset || t->error < GIT_SUCCESS)
- return git__rethrow(t->error, "Error parsing HTTP data");
-
- gitno_consume_n(&buf, parsed);
-
- if (error == 0 || t->transfer_finished || t->pack_ready) {
- return GIT_SUCCESS;
- }
- }
-
- return error;
-}
-
-static int setup_walk(git_revwalk **out, git_repository *repo)
-{
- git_revwalk *walk;
- git_strarray refs;
- unsigned int i;
- git_reference *ref;
- int error;
-
- error = git_reference_listall(&refs, repo, GIT_REF_LISTALL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to list references");
-
- error = git_revwalk_new(&walk, repo);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to setup walk");
-
- git_revwalk_sorting(walk, GIT_SORT_TIME);
-
- for (i = 0; i < refs.count; ++i) {
- /* No tags */
- if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
- continue;
-
- error = git_reference_lookup(&ref, repo, refs.strings[i]);
- if (error < GIT_ERROR) {
- error = git__rethrow(error, "Failed to lookup %s", refs.strings[i]);
- goto cleanup;
- }
-
- if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
- continue;
- error = git_revwalk_push(walk, git_reference_oid(ref));
- if (error < GIT_ERROR) {
- error = git__rethrow(error, "Failed to push %s", refs.strings[i]);
- goto cleanup;
- }
- }
-
- *out = walk;
-cleanup:
- git_strarray_free(&refs);
-
- return error;
-}
-
-static int http_negotiate_fetch(git_transport *transport, git_repository *repo, git_headarray *wants)
-{
- transport_http *t = (transport_http *) transport;
- GIT_UNUSED_ARG(list);
- int error;
- unsigned int i;
- char buff[128];
- gitno_buffer buf;
- git_revwalk *walk = NULL;
- git_oid oid;
- git_pkt_ack *pkt;
- git_vector *common = &t->common;
- const char *prefix = "http://", *url = t->parent.url;
- git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT;
- gitno_buffer_setup(&buf, buff, sizeof(buff), t->socket);
-
- /* TODO: Store url in the transport */
- if (!git__prefixcmp(url, prefix))
- url += strlen(prefix);
-
- error = git_vector_init(common, 16, NULL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to init common vector");
-
- error = setup_walk(&walk, repo);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to setup walk");
- goto cleanup;
- }
-
- do {
- error = do_connect(t, t->host, t->port);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to connect to host");
- goto cleanup;
- }
-
- error = git_pkt_buffer_wants(wants, &t->caps, &data);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to send wants");
- goto cleanup;
- }
-
- /* We need to send these on each connection */
- git_vector_foreach (common, i, pkt) {
- error = git_pkt_buffer_have(&pkt->oid, &data);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to buffer common have");
- goto cleanup;
- }
- }
-
- i = 0;
- while ((i < 20) && ((error = git_revwalk_next(&oid, walk)) == GIT_SUCCESS)) {
- error = git_pkt_buffer_have(&oid, &data);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to buffer have");
- goto cleanup;
- }
- i++;
- }
-
- git_pkt_buffer_done(&data);
-
- error = gen_request(&request, url, t->host, "POST", "upload-pack", data.size, 0);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to generate request");
- goto cleanup;
- }
-
- error = gitno_send(t->socket, request.ptr, request.size, 0);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to send request");
- goto cleanup;
- }
-
- error = gitno_send(t->socket, data.ptr, data.size, 0);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to send data");
- goto cleanup;
- }
-
- git_buf_clear(&request);
- git_buf_clear(&data);
-
- if (error < GIT_SUCCESS || i >= 256)
- break;
-
- error = parse_response(t);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Error parsing the response");
- goto cleanup;
- }
-
- if (t->pack_ready) {
- error = GIT_SUCCESS;
- goto cleanup;
- }
-
- } while(1);
-
-cleanup:
- git_buf_free(&request);
- git_buf_free(&data);
- git_revwalk_free(walk);
- return error;
-}
-
-typedef struct {
- git_filebuf *file;
- transport_http *transport;
-} download_pack_cbdata;
-
-static int on_message_complete_download_pack(http_parser *parser)
-{
- download_pack_cbdata *data = (download_pack_cbdata *) parser->data;
-
- data->transport->transfer_finished = 1;
-
- return 0;
-}
-static int on_body_download_pack(http_parser *parser, const char *str, size_t len)
-{
- download_pack_cbdata *data = (download_pack_cbdata *) parser->data;
- transport_http *t = data->transport;
- git_filebuf *file = data->file;
-
-
- return t->error = git_filebuf_write(file, str, len);
-}
-
-/*
- * As the server is probably using Transfer-Encoding: chunked, we have
- * to use the HTTP parser to download the pack instead of giving it to
- * the simple downloader. Furthermore, we're using keep-alive
- * connections, so the simple downloader would just hang.
- */
-static int http_download_pack(char **out, git_transport *transport, git_repository *repo)
-{
- transport_http *t = (transport_http *) transport;
- git_buf *oldbuf = &t->buf;
- int error = GIT_SUCCESS;
- http_parser_settings settings;
- char buffer[1024];
- gitno_buffer buf;
- download_pack_cbdata data;
- git_filebuf file;
- char path[GIT_PATH_MAX], suff[] = "/objects/pack/pack-received\0";
-
- /*
- * This is part of the previous response, so we don't want to
- * re-init the parser, just set these two callbacks.
- */
- data.file = &file;
- data.transport = t;
- t->parser.data = &data;
- t->transfer_finished = 0;
- memset(&settings, 0x0, sizeof(settings));
- settings.on_message_complete = on_message_complete_download_pack;
- settings.on_body = on_body_download_pack;
-
- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
-
- git_path_join(path, repo->path_repository, suff);
-
- if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) {
- return git__throw(GIT_ERROR, "The pack doesn't start with the signature");
- }
-
- error = git_filebuf_open(&file, path, GIT_FILEBUF_TEMPORARY);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- /* Part of the packfile has been received, don't loose it */
- error = git_filebuf_write(&file, oldbuf->ptr, oldbuf->size);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- while(1) {
- size_t parsed;
-
- error = gitno_recv(&buf);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Error receiving data from network");
-
- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
- /* Both should happen at the same time */
- if (parsed != buf.offset || t->error < GIT_SUCCESS)
- return git__rethrow(t->error, "Error parsing HTTP data");
-
- gitno_consume_n(&buf, parsed);
-
- if (error == 0 || t->transfer_finished) {
- break;
- }
- }
-
- *out = git__strdup(file.path_lock);
- if (*out == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
- }
-
- /* A bit dodgy, but we need to keep the pack at the temporary path */
- error = git_filebuf_commit_at(&file, file.path_lock);
-
-cleanup:
- if (error < GIT_SUCCESS)
- git_filebuf_cleanup(&file);
-
- return error;
-}
-
-static int http_close(git_transport *transport)
-{
- transport_http *t = (transport_http *) transport;
- int error;
-
- error = gitno_close(t->socket);
- if (error < 0)
- return git__throw(GIT_EOSERR, "Failed to close the socket: %s", strerror(errno));
-
- return GIT_SUCCESS;
-}
-
-
-static void http_free(git_transport *transport)
-{
- transport_http *t = (transport_http *) transport;
- git_vector *refs = &t->refs;
- git_vector *common = &t->common;
- unsigned int i;
- git_pkt *p;
-
-#ifdef GIT_WIN32
- /* cleanup the WSA context. note that this context
- * can be initialized more than once with WSAStartup(),
- * and needs to be cleaned one time for each init call
- */
- WSACleanup();
-#endif
-
- git_vector_foreach(refs, i, p) {
- git_pkt_free(p);
- }
- git_vector_free(refs);
- git_vector_foreach(common, i, p) {
- git_pkt_free(p);
- }
- git_vector_free(common);
- git_buf_free(&t->buf);
- free(t->heads);
- free(t->content_type);
- free(t->host);
- free(t->port);
- free(t->service);
- free(t->parent.url);
- free(t);
-}
-
-int git_transport_http(git_transport **out)
-{
- transport_http *t;
-
- t = git__malloc(sizeof(transport_http));
- if (t == NULL)
- return GIT_ENOMEM;
-
- memset(t, 0x0, sizeof(transport_http));
-
- t->parent.connect = http_connect;
- t->parent.ls = http_ls;
- t->parent.negotiate_fetch = http_negotiate_fetch;
- t->parent.download_pack = http_download_pack;
- t->parent.close = http_close;
- t->parent.free = http_free;
-
-#ifdef GIT_WIN32
- /* on win32, the WSA context needs to be initialized
- * before any socket calls can be performed */
- if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {
- http_free((git_transport *) t);
- return git__throw(GIT_EOSERR, "Winsock init failed");
- }
-#endif
-
- *out = (git_transport *) t;
- return GIT_SUCCESS;
-}
+++ /dev/null
-/*
- * Copyright (C) 2009-2011 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 "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 "netops.h"
-#include "filebuf.h"
-#include "repository.h"
-#include "fetch.h"
-
-typedef struct {
- git_transport parent;
- 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;
-
-/*
- * Create a git procol 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=";
- int len;
-
- delim = strchr(url, '/');
- if (delim == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to create proto-request: malformed URL");
-
- repo = delim;
-
- delim = strchr(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);
- git_buf_printf(request, "%04x%s %s%c%s", len, cmd, repo, 0, host);
- git_buf_put(request, url, delim - url);
- git_buf_putc(request, '\0');
-
- return git_buf_oom(request);
-}
-
-static int send_request(GIT_SOCKET s, const char *cmd, const char *url)
-{
- int error;
- git_buf request = GIT_BUF_INIT;
-
- error = gen_proto(&request, cmd, url);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- error = gitno_send(s, request.ptr, request.size, 0);
-
-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)
-{
- GIT_SOCKET s;
- char *host, *port;
- const char prefix[] = "git://";
- int error, connected = 0;
-
- if (!git__prefixcmp(url, prefix))
- url += strlen(prefix);
-
- error = gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT);
- if (error < GIT_SUCCESS)
- return error;
-
- s = gitno_connect(host, port);
- connected = 1;
- error = send_request(s, NULL, url);
- t->socket = s;
-
- free(host);
- free(port);
-
- if (error < GIT_SUCCESS && s > 0)
- close(s);
- if (!connected)
- error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses");
-
- return error;
-}
-
-/*
- * Read from the socket and store the references in the vector
- */
-static int store_refs(transport_git *t)
-{
- gitno_buffer *buf = &t->buf;
- git_vector *refs = &t->refs;
- int error = GIT_SUCCESS;
- const char *line_end, *ptr;
- git_pkt *pkt;
-
-
- while (1) {
- error = gitno_recv(buf);
- if (error < GIT_SUCCESS)
- return git__rethrow(GIT_EOSERR, "Failed to receive data");
- if (error == GIT_SUCCESS) /* Orderly shutdown, so exit */
- return GIT_SUCCESS;
-
- ptr = buf->data;
- while (1) {
- if (buf->offset == 0)
- break;
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
- /*
- * If the error is GIT_ESHORTBUFFER, it means the buffer
- * isn't long enough to satisfy the request. Break out and
- * wait for more input.
- * On any other error, fail.
- */
- if (error == GIT_ESHORTBUFFER) {
- break;
- }
- if (error < GIT_SUCCESS) {
- return error;
- }
-
- /* Get rid of the part we've used already */
- gitno_consume(buf, line_end);
-
- error = git_vector_insert(refs, pkt);
- if (error < GIT_SUCCESS)
- return error;
-
- if (pkt->type == GIT_PKT_FLUSH)
- return GIT_SUCCESS;
-
- }
- }
-
- return error;
-}
-
-static int detect_caps(transport_git *t)
-{
- 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 GIT_SUCCESS;
-
- ptr = pkt->capabilities;
- while (ptr != NULL && *ptr != '\0') {
- if (*ptr == ' ')
- ptr++;
-
- if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
- caps->common = caps->ofs_delta = 1;
- ptr += strlen(GIT_CAP_OFS_DELTA);
- continue;
- }
-
- /* We don't know this capability, so skip it */
- ptr = strchr(ptr, ' ');
- }
-
- return GIT_SUCCESS;
-}
-
-/*
- * 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)
-{
- transport_git *t = (transport_git *) transport;
- int error = GIT_SUCCESS;
-
- if (direction == GIT_DIR_PUSH)
- return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol");
-
- t->parent.direction = direction;
- error = git_vector_init(&t->refs, 16, NULL);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- /* Connect and ask for the refs */
- error = do_connect(t, transport->url);
- if (error < GIT_SUCCESS)
- return error;
-
- gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket);
-
- t->parent.connected = 1;
- error = store_refs(t);
- if (error < GIT_SUCCESS)
- return error;
-
- error = detect_caps(t);
-
-cleanup:
- if (error < GIT_SUCCESS) {
- git_vector_free(&t->refs);
- }
-
- return error;
-}
-
-static int git_ls(git_transport *transport, git_headarray *array)
-{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &t->refs;
- int len = 0;
- unsigned int i;
-
- array->heads = git__calloc(refs->length, sizeof(git_remote_head *));
- if (array->heads == NULL)
- return GIT_ENOMEM;
-
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- if (p->type != GIT_PKT_REF)
- continue;
-
- ++len;
- array->heads[i] = &(((git_pkt_ref *) p)->head);
- }
- array->len = len;
- t->heads = array->heads;
-
- return GIT_SUCCESS;
-}
-
-static int git_negotiate_fetch(git_transport *transport, git_repository *repo, git_headarray *wants)
-{
- transport_git *t = (transport_git *) transport;
- git_revwalk *walk;
- git_reference *ref;
- git_strarray refs;
- git_oid oid;
- int error;
- unsigned int i;
- gitno_buffer *buf = &t->buf;
-
- error = git_pkt_send_wants(wants, &t->caps, t->socket);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to send wants list");
-
- error = git_reference_listall(&refs, repo, GIT_REF_LISTALL);
- if (error < GIT_ERROR)
- return git__rethrow(error, "Failed to list all references");
-
- error = git_revwalk_new(&walk, repo);
- if (error < GIT_ERROR) {
- error = git__rethrow(error, "Failed to list all references");
- goto cleanup;
- }
- git_revwalk_sorting(walk, GIT_SORT_TIME);
-
- for (i = 0; i < refs.count; ++i) {
- /* No tags */
- if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
- continue;
-
- error = git_reference_lookup(&ref, repo, refs.strings[i]);
- if (error < GIT_ERROR) {
- error = git__rethrow(error, "Failed to lookup %s", refs.strings[i]);
- goto cleanup;
- }
-
- if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
- continue;
- error = git_revwalk_push(walk, git_reference_oid(ref));
- if (error < GIT_ERROR) {
- error = git__rethrow(error, "Failed to push %s", refs.strings[i]);
- goto cleanup;
- }
- }
- git_strarray_free(&refs);
-
- /*
- * 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)) == GIT_SUCCESS) {
- error = git_pkt_send_have(&oid, t->socket);
- i++;
- if (i % 20 == 0) {
- const char *ptr = buf->data, *line_end;
- git_pkt *pkt;
- git_pkt_send_flush(t->socket);
- while (1) {
- /* Wait for max. 1 second */
- error = gitno_select_in(buf, 1, 0);
- if (error < GIT_SUCCESS) {
- error = git__throw(GIT_EOSERR, "Error in select");
- } else if (error == 0) {
- /*
- * Some servers don't respond immediately, so if this
- * happens, we keep sending information until it
- * answers.
- */
- break;
- }
-
- error = gitno_recv(buf);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Error receiving data");
- goto cleanup;
- }
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
- if (error == GIT_ESHORTBUFFER)
- continue;
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to get answer");
- goto cleanup;
- }
-
- gitno_consume(buf, line_end);
-
- if (pkt->type == GIT_PKT_ACK) {
- free(pkt);
- error = GIT_SUCCESS;
- goto done;
- } else if (pkt->type == GIT_PKT_NAK) {
- free(pkt);
- break;
- } else {
- error = git__throw(GIT_ERROR, "Got unexpected pkt type");
- goto cleanup;
- }
- }
- }
- }
- if (error == GIT_EREVWALKOVER)
- error = GIT_SUCCESS;
-
-done:
- git_pkt_send_flush(t->socket);
- git_pkt_send_done(t->socket);
-
-cleanup:
- git_revwalk_free(walk);
-
- return error;
-}
-
-static int git_send_flush(git_transport *transport)
-{
- transport_git *t = (transport_git *) transport;
-
- return git_pkt_send_flush(t->socket);
-}
-
-static int git_send_done(git_transport *transport)
-{
- transport_git *t = (transport_git *) transport;
-
- return git_pkt_send_done(t->socket);
-}
-
-static int git_download_pack(char **out, git_transport *transport, git_repository *repo)
-{
- transport_git *t = (transport_git *) transport;
- int error = GIT_SUCCESS;
- gitno_buffer *buf = &t->buf;
- git_pkt *pkt;
- const char *line_end, *ptr;
-
- /*
- * For now, we ignore everything and wait for the pack
- */
- while (1) {
- 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_ESHORTBUFFER)
- break;
-
- if (error < GIT_SUCCESS)
- return error;
-
- if (pkt->type == GIT_PKT_PACK) {
- free(pkt);
- return git_fetch__download_pack(out, buf->data, buf->offset, t->socket, repo);
- }
-
- /* For now we don't care about anything */
- free(pkt);
- gitno_consume(buf, line_end);
- }
-
- error = gitno_recv(buf);
- if (error < GIT_SUCCESS)
- return git__rethrow(GIT_EOSERR, "Failed to receive data");
- if (error == 0) { /* Orderly shutdown */
- return GIT_SUCCESS;
- }
-
- }
-}
-
-
-static int git_close(git_transport *transport)
-{
- transport_git *t = (transport_git*) transport;
- int error;
-
- /* Can't do anything if there's an error, so don't bother checking */
- git_pkt_send_flush(t->socket);
- error = gitno_close(t->socket);
-
- if (error < 0)
- error = git__throw(GIT_EOSERR, "Failed to close socket");
-
-#ifdef GIT_WIN32
- WSACleanup();
-#endif
-
- return error;
-}
-
-static void git_free(git_transport *transport)
-{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
-
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- git_pkt_free(p);
- }
-
- git_vector_free(refs);
- free(t->heads);
- free(t->parent.url);
- free(t);
-}
-
-int git_transport_git(git_transport **out)
-{
- transport_git *t;
-#ifdef GIT_WIN32
- int ret;
-#endif
-
- t = git__malloc(sizeof(transport_git));
- if (t == NULL)
- return GIT_ENOMEM;
-
- memset(t, 0x0, sizeof(transport_git));
-
- t->parent.connect = git_connect;
- t->parent.ls = git_ls;
- t->parent.negotiate_fetch = git_negotiate_fetch;
- t->parent.send_flush = git_send_flush;
- t->parent.send_done = git_send_done;
- t->parent.download_pack = git_download_pack;
- t->parent.close = git_close;
- t->parent.free = git_free;
-
- *out = (git_transport *) t;
-
-#ifdef GIT_WIN32
- ret = WSAStartup(MAKEWORD(2,2), &t->wsd);
- if (ret != 0) {
- git_free(*out);
- return git__throw(GIT_EOSERR, "Winsock init failed");
- }
-#endif
-
- return GIT_SUCCESS;
-}
+++ /dev/null
-/*
- * Copyright (C) 2009-2011 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 "git2/types.h"
-#include "git2/transport.h"
-#include "git2/net.h"
-#include "git2/repository.h"
-#include "git2/object.h"
-#include "git2/tag.h"
-#include "refs.h"
-#include "transport.h"
-#include "posix.h"
-
-typedef struct {
- git_transport parent;
- git_repository *repo;
- git_vector *refs;
-} transport_local;
-
-/*
- * Try to open the url as a git directory. The direction doesn't
- * matter in this case because we're calulating the heads ourselves.
- */
-static int local_connect(git_transport *transport, int GIT_UNUSED(direction))
-{
- git_repository *repo;
- int error;
- transport_local *t = (transport_local *) transport;
- const char *path;
- const char file_prefix[] = "file://";
- GIT_UNUSED_ARG(direction);
-
- /* The repo layer doesn't want the prefix */
- if (!git__prefixcmp(transport->url, file_prefix))
- path = transport->url + strlen(file_prefix);
- else
- path = transport->url;
-
- error = git_repository_open(&repo, path);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to open remote");
-
- t->repo = repo;
- t->parent.connected = 1;
-
- return GIT_SUCCESS;
-}
-
-static int add_ref(const char *name, git_repository *repo, git_vector *vec)
-{
- const char peeled[] = "^{}";
- git_remote_head *head;
- git_reference *ref;
- git_object *obj = NULL;
- int error = GIT_SUCCESS, peel_len, ret;
-
- head = git__malloc(sizeof(git_remote_head));
- if (head == NULL)
- return GIT_ENOMEM;
-
- head->name = git__strdup(name);
- if (head->name == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
-
- error = git_reference_lookup(&ref, repo, name);
- if (error < GIT_SUCCESS)
- goto out;
-
- error = git_reference_resolve(&ref, ref);
- if (error < GIT_SUCCESS)
- goto out;
-
- git_oid_cpy(&head->oid, git_reference_oid(ref));
-
- error = git_vector_insert(vec, head);
- if (error < GIT_SUCCESS)
- goto out;
-
- /* If it's not a tag, we don't need to try to peel it */
- if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
- goto out;
-
- error = git_object_lookup(&obj, repo, &head->oid, GIT_OBJ_ANY);
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to lookup object");
- }
-
- /* If it's not an annotated tag, just get out */
- if (git_object_type(obj) != GIT_OBJ_TAG)
- goto out;
-
- /* And if it's a tag, peel it, and add it to the list */
- head = git__malloc(sizeof(git_remote_head));
- peel_len = strlen(name) + strlen(peeled);
- head->name = git__malloc(peel_len + 1);
- ret = p_snprintf(head->name, peel_len + 1, "%s%s", name, peeled);
- if (ret >= peel_len + 1) {
- error = git__throw(GIT_ERROR, "The string is magically to long");
- }
-
- git_oid_cpy(&head->oid, git_tag_target_oid((git_tag *) obj));
-
- error = git_vector_insert(vec, head);
- if (error < GIT_SUCCESS)
- goto out;
-
- out:
- git_object_close(obj);
- if (error < GIT_SUCCESS) {
- free(head->name);
- free(head);
- }
- return error;
-}
-
-static int local_ls(git_transport *transport, git_headarray *array)
-{
- int error;
- unsigned int i;
- git_repository *repo;
- git_vector *vec;
- git_strarray refs;
- transport_local *t = (transport_local *) transport;
-
- assert(transport && transport->connected);
-
- repo = t->repo;
-
- error = git_reference_listall(&refs, repo, GIT_REF_LISTALL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to list remote heads");
-
- vec = git__malloc(sizeof(git_vector));
- if (vec == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
-
- error = git_vector_init(vec, refs.count, NULL);
- if (error < GIT_SUCCESS)
- return error;
-
- /* Sort the references first */
- git__tsort((void **)refs.strings, refs.count, &git__strcmp_cb);
-
- /* Add HEAD */
- error = add_ref(GIT_HEAD_FILE, repo, vec);
- if (error < GIT_SUCCESS)
- goto out;
-
- for (i = 0; i < refs.count; ++i) {
- error = add_ref(refs.strings[i], repo, vec);
- if (error < GIT_SUCCESS)
- goto out;
- }
-
- array->len = vec->length;
- array->heads = (git_remote_head **)vec->contents;
-
- t->refs = vec;
-
- out:
-
- git_strarray_free(&refs);
-
- return error;
-}
-
-static int local_close(git_transport *GIT_UNUSED(transport))
-{
- /* Nothing to do */
- GIT_UNUSED_ARG(transport);
- return GIT_SUCCESS;
-}
-
-static void local_free(git_transport *transport)
-{
- unsigned int i;
- transport_local *t = (transport_local *) transport;
- git_vector *vec = t->refs;
- git_remote_head *h;
-
- assert(transport);
-
- if (t->refs != NULL) {
- git_vector_foreach (vec, i, h) {
- free(h->name);
- free(h);
- }
- git_vector_free(vec);
- free(vec);
- }
-
- git_repository_free(t->repo);
- free(t->parent.url);
- free(t);
-}
-
-/**************
- * Public API *
- **************/
-
-int git_transport_local(git_transport **out)
-{
- transport_local *t;
-
- t = git__malloc(sizeof(transport_local));
- if (t == NULL)
- return GIT_ENOMEM;
-
- memset(t, 0x0, sizeof(transport_local));
-
- t->parent.connect = local_connect;
- t->parent.ls = local_ls;
- t->parent.close = local_close;
- t->parent.free = local_free;
-
- *out = (git_transport *) t;
-
- return GIT_SUCCESS;
-}
--- /dev/null
+/*
+ * Copyright (C) 2009-2011 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 "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 "netops.h"
+#include "filebuf.h"
+#include "repository.h"
+#include "fetch.h"
+
+typedef struct {
+ git_transport parent;
+ 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;
+
+/*
+ * Create a git procol 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=";
+ int len;
+
+ delim = strchr(url, '/');
+ if (delim == NULL)
+ return git__throw(GIT_EOBJCORRUPTED, "Failed to create proto-request: malformed URL");
+
+ repo = delim;
+
+ delim = strchr(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);
+ git_buf_printf(request, "%04x%s %s%c%s", len, cmd, repo, 0, host);
+ git_buf_put(request, url, delim - url);
+ git_buf_putc(request, '\0');
+
+ return git_buf_oom(request);
+}
+
+static int send_request(GIT_SOCKET s, const char *cmd, const char *url)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+
+ error = gen_proto(&request, cmd, url);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ error = gitno_send(s, request.ptr, request.size, 0);
+
+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)
+{
+ GIT_SOCKET s;
+ char *host, *port;
+ const char prefix[] = "git://";
+ int error, connected = 0;
+
+ if (!git__prefixcmp(url, prefix))
+ url += strlen(prefix);
+
+ error = gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT);
+ if (error < GIT_SUCCESS)
+ return error;
+
+ s = gitno_connect(host, port);
+ connected = 1;
+ error = send_request(s, NULL, url);
+ t->socket = s;
+
+ free(host);
+ free(port);
+
+ if (error < GIT_SUCCESS && s > 0)
+ close(s);
+ if (!connected)
+ error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses");
+
+ return error;
+}
+
+/*
+ * Read from the socket and store the references in the vector
+ */
+static int store_refs(transport_git *t)
+{
+ gitno_buffer *buf = &t->buf;
+ git_vector *refs = &t->refs;
+ int error = GIT_SUCCESS;
+ const char *line_end, *ptr;
+ git_pkt *pkt;
+
+
+ while (1) {
+ error = gitno_recv(buf);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(GIT_EOSERR, "Failed to receive data");
+ if (error == GIT_SUCCESS) /* Orderly shutdown, so exit */
+ return GIT_SUCCESS;
+
+ ptr = buf->data;
+ while (1) {
+ if (buf->offset == 0)
+ break;
+ error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
+ /*
+ * If the error is GIT_ESHORTBUFFER, it means the buffer
+ * isn't long enough to satisfy the request. Break out and
+ * wait for more input.
+ * On any other error, fail.
+ */
+ if (error == GIT_ESHORTBUFFER) {
+ break;
+ }
+ if (error < GIT_SUCCESS) {
+ return error;
+ }
+
+ /* Get rid of the part we've used already */
+ gitno_consume(buf, line_end);
+
+ error = git_vector_insert(refs, pkt);
+ if (error < GIT_SUCCESS)
+ return error;
+
+ if (pkt->type == GIT_PKT_FLUSH)
+ return GIT_SUCCESS;
+
+ }
+ }
+
+ return error;
+}
+
+static int detect_caps(transport_git *t)
+{
+ 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 GIT_SUCCESS;
+
+ ptr = pkt->capabilities;
+ while (ptr != NULL && *ptr != '\0') {
+ if (*ptr == ' ')
+ ptr++;
+
+ if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
+ caps->common = caps->ofs_delta = 1;
+ ptr += strlen(GIT_CAP_OFS_DELTA);
+ continue;
+ }
+
+ /* We don't know this capability, so skip it */
+ ptr = strchr(ptr, ' ');
+ }
+
+ return GIT_SUCCESS;
+}
+
+/*
+ * 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)
+{
+ transport_git *t = (transport_git *) transport;
+ int error = GIT_SUCCESS;
+
+ if (direction == GIT_DIR_PUSH)
+ return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol");
+
+ t->parent.direction = direction;
+ error = git_vector_init(&t->refs, 16, NULL);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ /* Connect and ask for the refs */
+ error = do_connect(t, transport->url);
+ if (error < GIT_SUCCESS)
+ return error;
+
+ gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket);
+
+ t->parent.connected = 1;
+ error = store_refs(t);
+ if (error < GIT_SUCCESS)
+ return error;
+
+ error = detect_caps(t);
+
+cleanup:
+ if (error < GIT_SUCCESS) {
+ git_vector_free(&t->refs);
+ }
+
+ return error;
+}
+
+static int git_ls(git_transport *transport, git_headarray *array)
+{
+ transport_git *t = (transport_git *) transport;
+ git_vector *refs = &t->refs;
+ int len = 0;
+ unsigned int i;
+
+ array->heads = git__calloc(refs->length, sizeof(git_remote_head *));
+ if (array->heads == NULL)
+ return GIT_ENOMEM;
+
+ for (i = 0; i < refs->length; ++i) {
+ git_pkt *p = git_vector_get(refs, i);
+ if (p->type != GIT_PKT_REF)
+ continue;
+
+ ++len;
+ array->heads[i] = &(((git_pkt_ref *) p)->head);
+ }
+ array->len = len;
+ t->heads = array->heads;
+
+ return GIT_SUCCESS;
+}
+
+static int git_negotiate_fetch(git_transport *transport, git_repository *repo, git_headarray *wants)
+{
+ transport_git *t = (transport_git *) transport;
+ git_revwalk *walk;
+ git_reference *ref;
+ git_strarray refs;
+ git_oid oid;
+ int error;
+ unsigned int i;
+ gitno_buffer *buf = &t->buf;
+
+ error = git_pkt_send_wants(wants, &t->caps, t->socket);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to send wants list");
+
+ error = git_reference_listall(&refs, repo, GIT_REF_LISTALL);
+ if (error < GIT_ERROR)
+ return git__rethrow(error, "Failed to list all references");
+
+ error = git_revwalk_new(&walk, repo);
+ if (error < GIT_ERROR) {
+ error = git__rethrow(error, "Failed to list all references");
+ goto cleanup;
+ }
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ for (i = 0; i < refs.count; ++i) {
+ /* No tags */
+ if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
+ continue;
+
+ error = git_reference_lookup(&ref, repo, refs.strings[i]);
+ if (error < GIT_ERROR) {
+ error = git__rethrow(error, "Failed to lookup %s", refs.strings[i]);
+ goto cleanup;
+ }
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
+ continue;
+ error = git_revwalk_push(walk, git_reference_oid(ref));
+ if (error < GIT_ERROR) {
+ error = git__rethrow(error, "Failed to push %s", refs.strings[i]);
+ goto cleanup;
+ }
+ }
+ git_strarray_free(&refs);
+
+ /*
+ * 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)) == GIT_SUCCESS) {
+ error = git_pkt_send_have(&oid, t->socket);
+ i++;
+ if (i % 20 == 0) {
+ const char *ptr = buf->data, *line_end;
+ git_pkt *pkt;
+ git_pkt_send_flush(t->socket);
+ while (1) {
+ /* Wait for max. 1 second */
+ error = gitno_select_in(buf, 1, 0);
+ if (error < GIT_SUCCESS) {
+ error = git__throw(GIT_EOSERR, "Error in select");
+ } else if (error == 0) {
+ /*
+ * Some servers don't respond immediately, so if this
+ * happens, we keep sending information until it
+ * answers.
+ */
+ break;
+ }
+
+ error = gitno_recv(buf);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Error receiving data");
+ goto cleanup;
+ }
+ error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
+ if (error == GIT_ESHORTBUFFER)
+ continue;
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to get answer");
+ goto cleanup;
+ }
+
+ gitno_consume(buf, line_end);
+
+ if (pkt->type == GIT_PKT_ACK) {
+ free(pkt);
+ error = GIT_SUCCESS;
+ goto done;
+ } else if (pkt->type == GIT_PKT_NAK) {
+ free(pkt);
+ break;
+ } else {
+ error = git__throw(GIT_ERROR, "Got unexpected pkt type");
+ goto cleanup;
+ }
+ }
+ }
+ }
+ if (error == GIT_EREVWALKOVER)
+ error = GIT_SUCCESS;
+
+done:
+ git_pkt_send_flush(t->socket);
+ git_pkt_send_done(t->socket);
+
+cleanup:
+ git_revwalk_free(walk);
+
+ return error;
+}
+
+static int git_send_flush(git_transport *transport)
+{
+ transport_git *t = (transport_git *) transport;
+
+ return git_pkt_send_flush(t->socket);
+}
+
+static int git_send_done(git_transport *transport)
+{
+ transport_git *t = (transport_git *) transport;
+
+ return git_pkt_send_done(t->socket);
+}
+
+static int git_download_pack(char **out, git_transport *transport, git_repository *repo)
+{
+ transport_git *t = (transport_git *) transport;
+ int error = GIT_SUCCESS;
+ gitno_buffer *buf = &t->buf;
+ git_pkt *pkt;
+ const char *line_end, *ptr;
+
+ /*
+ * For now, we ignore everything and wait for the pack
+ */
+ while (1) {
+ 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_ESHORTBUFFER)
+ break;
+
+ if (error < GIT_SUCCESS)
+ return error;
+
+ if (pkt->type == GIT_PKT_PACK) {
+ free(pkt);
+ return git_fetch__download_pack(out, buf->data, buf->offset, t->socket, repo);
+ }
+
+ /* For now we don't care about anything */
+ free(pkt);
+ gitno_consume(buf, line_end);
+ }
+
+ error = gitno_recv(buf);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(GIT_EOSERR, "Failed to receive data");
+ if (error == 0) { /* Orderly shutdown */
+ return GIT_SUCCESS;
+ }
+
+ }
+}
+
+
+static int git_close(git_transport *transport)
+{
+ transport_git *t = (transport_git*) transport;
+ int error;
+
+ /* Can't do anything if there's an error, so don't bother checking */
+ git_pkt_send_flush(t->socket);
+ error = gitno_close(t->socket);
+
+ if (error < 0)
+ error = git__throw(GIT_EOSERR, "Failed to close socket");
+
+#ifdef GIT_WIN32
+ WSACleanup();
+#endif
+
+ return error;
+}
+
+static void git_free(git_transport *transport)
+{
+ transport_git *t = (transport_git *) transport;
+ git_vector *refs = &t->refs;
+ unsigned int i;
+
+ for (i = 0; i < refs->length; ++i) {
+ git_pkt *p = git_vector_get(refs, i);
+ git_pkt_free(p);
+ }
+
+ git_vector_free(refs);
+ free(t->heads);
+ free(t->parent.url);
+ free(t);
+}
+
+int git_transport_git(git_transport **out)
+{
+ transport_git *t;
+#ifdef GIT_WIN32
+ int ret;
+#endif
+
+ t = git__malloc(sizeof(transport_git));
+ if (t == NULL)
+ return GIT_ENOMEM;
+
+ memset(t, 0x0, sizeof(transport_git));
+
+ t->parent.connect = git_connect;
+ t->parent.ls = git_ls;
+ t->parent.negotiate_fetch = git_negotiate_fetch;
+ t->parent.send_flush = git_send_flush;
+ t->parent.send_done = git_send_done;
+ t->parent.download_pack = git_download_pack;
+ t->parent.close = git_close;
+ t->parent.free = git_free;
+
+ *out = (git_transport *) t;
+
+#ifdef GIT_WIN32
+ ret = WSAStartup(MAKEWORD(2,2), &t->wsd);
+ if (ret != 0) {
+ git_free(*out);
+ return git__throw(GIT_EOSERR, "Winsock init failed");
+ }
+#endif
+
+ return GIT_SUCCESS;
+}
--- /dev/null
+/*
+ * Copyright (C) 2009-2011 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 <stdlib.h>
+#include "git2.h"
+#include "http_parser.h"
+
+#include "transport.h"
+#include "common.h"
+#include "netops.h"
+#include "buffer.h"
+#include "pkt.h"
+#include "refs.h"
+#include "fetch.h"
+#include "filebuf.h"
+#include "repository.h"
+
+enum last_cb {
+ NONE,
+ FIELD,
+ VALUE
+};
+
+typedef struct {
+ git_transport parent;
+ git_vector refs;
+ git_vector common;
+ int socket;
+ git_buf buf;
+ git_remote_head **heads;
+ int error;
+ int transfer_finished :1,
+ ct_found :1,
+ ct_finished :1,
+ pack_ready :1;
+ enum last_cb last_cb;
+ http_parser parser;
+ char *content_type;
+ char *host;
+ char *port;
+ char *service;
+ git_transport_caps caps;
+#ifdef GIT_WIN32
+ WSADATA wsd;
+#endif
+} transport_http;
+
+static int gen_request(git_buf *buf, const char *url, const char *host, const char *op,
+ const char *service, ssize_t content_length, int ls)
+{
+ const char *path = url;
+
+ path = strchr(path, '/');
+ if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
+ path = "/";
+
+ if (ls) {
+ git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service);
+ } else {
+ git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service);
+ }
+ git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
+ git_buf_printf(buf, "Host: %s\r\n", host);
+ if (content_length > 0) {
+ git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service);
+ git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service);
+ git_buf_printf(buf, "Content-Length: %zd\r\n", content_length);
+ } else {
+ git_buf_puts(buf, "Accept: */*\r\n");
+ }
+ git_buf_puts(buf, "\r\n");
+
+ if (git_buf_oom(buf))
+ return GIT_ENOMEM;
+
+ return GIT_SUCCESS;
+}
+
+static int do_connect(transport_http *t, const char *host, const char *port)
+{
+ GIT_SOCKET s = -1;
+
+ if (t->parent.connected && http_should_keep_alive(&t->parser))
+ return GIT_SUCCESS;
+
+ s = gitno_connect(host, port);
+ if (s < GIT_SUCCESS) {
+ return git__rethrow(s, "Failed to connect to host");
+ }
+ t->socket = s;
+ t->parent.connected = 1;
+
+ return GIT_SUCCESS;
+}
+
+/*
+ * The HTTP parser is streaming, so we need to wait until we're in the
+ * field handler before we can be sure that we can store the previous
+ * value. Right now, we only care about the
+ * Content-Type. on_header_{field,value} should be kept generic enough
+ * to work for any request.
+ */
+
+static const char *typestr = "Content-Type";
+
+static int on_header_field(http_parser *parser, const char *str, size_t len)
+{
+ transport_http *t = (transport_http *) parser->data;
+ git_buf *buf = &t->buf;
+
+ if (t->last_cb == VALUE && t->ct_found) {
+ t->ct_finished = 1;
+ t->ct_found = 0;
+ t->content_type = git__strdup(git_buf_cstr(buf));
+ if (t->content_type == NULL)
+ return t->error = GIT_ENOMEM;
+ git_buf_clear(buf);
+ }
+
+ if (t->ct_found) {
+ t->last_cb = FIELD;
+ return 0;
+ }
+
+ if (t->last_cb != FIELD)
+ git_buf_clear(buf);
+
+ git_buf_put(buf, str, len);
+ t->last_cb = FIELD;
+
+ return git_buf_oom(buf);
+}
+
+static int on_header_value(http_parser *parser, const char *str, size_t len)
+{
+ transport_http *t = (transport_http *) parser->data;
+ git_buf *buf = &t->buf;
+
+ if (t->ct_finished) {
+ t->last_cb = VALUE;
+ return 0;
+ }
+
+ if (t->last_cb == VALUE)
+ git_buf_put(buf, str, len);
+
+ if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
+ t->ct_found = 1;
+ git_buf_clear(buf);
+ git_buf_put(buf, str, len);
+ }
+
+ t->last_cb = VALUE;
+
+ return git_buf_oom(buf);
+}
+
+static int on_headers_complete(http_parser *parser)
+{
+ transport_http *t = (transport_http *) parser->data;
+ git_buf *buf = &t->buf;
+
+ if (t->content_type == NULL) {
+ t->content_type = git__strdup(git_buf_cstr(buf));
+ if (t->content_type == NULL)
+ return t->error = GIT_ENOMEM;
+ }
+
+ git_buf_clear(buf);
+ git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
+ if (git_buf_oom(buf))
+ return GIT_ENOMEM;
+
+ if (strcmp(t->content_type, git_buf_cstr(buf)))
+ return t->error = git__throw(GIT_EOBJCORRUPTED, "Content-Type '%s' is wrong", t->content_type);
+
+ git_buf_clear(buf);
+ return 0;
+}
+
+static int on_body_store_refs(http_parser *parser, const char *str, size_t len)
+{
+ transport_http *t = (transport_http *) parser->data;
+ git_buf *buf = &t->buf;
+ git_vector *refs = &t->refs;
+ int error;
+ const char *line_end, *ptr;
+ static int first_pkt = 1;
+
+ if (len == 0) { /* EOF */
+ if (buf->size != 0)
+ return t->error = git__throw(GIT_ERROR, "EOF and unprocessed data");
+ else
+ return 0;
+ }
+
+ git_buf_put(buf, str, len);
+ ptr = buf->ptr;
+ while (1) {
+ git_pkt *pkt;
+
+ if (buf->size == 0)
+ return 0;
+
+ error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->size);
+ if (error == GIT_ESHORTBUFFER)
+ return 0; /* Ask for more */
+ if (error < GIT_SUCCESS)
+ return t->error = git__rethrow(error, "Failed to parse pkt-line");
+
+ git_buf_consume(buf, line_end);
+
+ if (first_pkt) {
+ first_pkt = 0;
+ if (pkt->type != GIT_PKT_COMMENT)
+ return t->error = git__throw(GIT_EOBJCORRUPTED, "Not a valid smart HTTP response");
+ }
+
+ error = git_vector_insert(refs, pkt);
+ if (error < GIT_SUCCESS)
+ return t->error = git__rethrow(error, "Failed to add pkt to list");
+ }
+
+ return error;
+}
+
+static int on_message_complete(http_parser *parser)
+{
+ transport_http *t = (transport_http *) parser->data;
+
+ t->transfer_finished = 1;
+ return 0;
+}
+
+static int store_refs(transport_http *t)
+{
+ int error = GIT_SUCCESS;
+ http_parser_settings settings;
+ char buffer[1024];
+ gitno_buffer buf;
+
+ http_parser_init(&t->parser, HTTP_RESPONSE);
+ t->parser.data = t;
+ memset(&settings, 0x0, sizeof(http_parser_settings));
+ settings.on_header_field = on_header_field;
+ settings.on_header_value = on_header_value;
+ settings.on_headers_complete = on_headers_complete;
+ settings.on_body = on_body_store_refs;
+ settings.on_message_complete = on_message_complete;
+
+ gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
+
+ while(1) {
+ size_t parsed;
+
+ error = gitno_recv(&buf);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Error receiving data from network");
+
+ parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
+ /* Both should happen at the same time */
+ if (parsed != buf.offset || t->error < GIT_SUCCESS)
+ return git__rethrow(t->error, "Error parsing HTTP data");
+
+ gitno_consume_n(&buf, parsed);
+
+ if (error == 0 || t->transfer_finished)
+ return GIT_SUCCESS;
+ }
+
+ return error;
+}
+
+static int http_connect(git_transport *transport, int direction)
+{
+ transport_http *t = (transport_http *) transport;
+ int error;
+ git_buf request = GIT_BUF_INIT;
+ const char *service = "upload-pack";
+ const char *url = t->parent.url, *prefix = "http://";
+
+ if (direction == GIT_DIR_PUSH)
+ return git__throw(GIT_EINVALIDARGS, "Pushing over HTTP is not supported");
+
+ t->parent.direction = direction;
+ error = git_vector_init(&t->refs, 16, NULL);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to init refs vector");
+
+ if (!git__prefixcmp(url, prefix))
+ url += strlen(prefix);
+
+ error = gitno_extract_host_and_port(&t->host, &t->port, url, "80");
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ t->service = git__strdup(service);
+ if (t->service == NULL) {
+ error = GIT_ENOMEM;
+ goto cleanup;
+ }
+
+ error = do_connect(t, t->host, t->port);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to connect to host");
+ goto cleanup;
+ }
+
+ /* Generate and send the HTTP request */
+ error = gen_request(&request, url, t->host, "GET", service, 0, 1);
+ if (error < GIT_SUCCESS) {
+ error = git__throw(error, "Failed to generate request");
+ goto cleanup;
+ }
+
+ error = gitno_send(t->socket, request.ptr, request.size, 0);
+ if (error < GIT_SUCCESS)
+ error = git__rethrow(error, "Failed to send the HTTP request");
+
+ error = store_refs(t);
+
+cleanup:
+ git_buf_free(&request);
+ git_buf_clear(&t->buf);
+
+ return error;
+}
+
+static int http_ls(git_transport *transport, git_headarray *array)
+{
+ transport_http *t = (transport_http *) transport;
+ git_vector *refs = &t->refs;
+ unsigned int i;
+ int len = 0;
+ git_pkt_ref *p;
+
+ array->heads = git__calloc(refs->length, sizeof(git_remote_head*));
+ if (array->heads == NULL)
+ return GIT_ENOMEM;
+
+ git_vector_foreach(refs, i, p) {
+ if (p->type != GIT_PKT_REF)
+ continue;
+
+ array->heads[len] = &p->head;
+ len++;
+ }
+
+ array->len = len;
+ t->heads = array->heads;
+
+ return GIT_SUCCESS;
+}
+
+static int on_body_parse_response(http_parser *parser, const char *str, size_t len)
+{
+ transport_http *t = (transport_http *) parser->data;
+ git_buf *buf = &t->buf;
+ git_vector *common = &t->common;
+ int error;
+ const char *line_end, *ptr;
+
+ if (len == 0) { /* EOF */
+ if (buf->size != 0)
+ return t->error = git__throw(GIT_ERROR, "EOF and unprocessed data");
+ else
+ return 0;
+ }
+
+ git_buf_put(buf, str, len);
+ ptr = buf->ptr;
+ while (1) {
+ git_pkt *pkt;
+
+ if (buf->size == 0)
+ return 0;
+
+ error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->size);
+ if (error == GIT_ESHORTBUFFER) {
+ return 0; /* Ask for more */
+ }
+ if (error < GIT_SUCCESS)
+ return t->error = git__rethrow(error, "Failed to parse pkt-line");
+
+ git_buf_consume(buf, line_end);
+
+ if (pkt->type == GIT_PKT_PACK) {
+ free(pkt);
+ t->pack_ready = 1;
+ return 0;
+ }
+
+ if (pkt->type == GIT_PKT_NAK) {
+ free(pkt);
+ return 0;
+ }
+
+ if (pkt->type != GIT_PKT_ACK) {
+ free(pkt);
+ continue;
+ }
+
+ error = git_vector_insert(common, pkt);
+ if (error < GIT_SUCCESS)
+ return t->error = git__rethrow(error, "Failed to add pkt to list");
+ }
+
+ return error;
+
+}
+
+static int parse_response(transport_http *t)
+{
+ int error = GIT_SUCCESS;
+ http_parser_settings settings;
+ char buffer[1024];
+ gitno_buffer buf;
+
+ http_parser_init(&t->parser, HTTP_RESPONSE);
+ t->parser.data = t;
+ t->transfer_finished = 0;
+ memset(&settings, 0x0, sizeof(http_parser_settings));
+ settings.on_header_field = on_header_field;
+ settings.on_header_value = on_header_value;
+ settings.on_headers_complete = on_headers_complete;
+ settings.on_body = on_body_parse_response;
+ settings.on_message_complete = on_message_complete;
+
+ gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
+
+ while(1) {
+ size_t parsed;
+
+ error = gitno_recv(&buf);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Error receiving data from network");
+
+ parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
+ /* Both should happen at the same time */
+ if (parsed != buf.offset || t->error < GIT_SUCCESS)
+ return git__rethrow(t->error, "Error parsing HTTP data");
+
+ gitno_consume_n(&buf, parsed);
+
+ if (error == 0 || t->transfer_finished || t->pack_ready) {
+ return GIT_SUCCESS;
+ }
+ }
+
+ return error;
+}
+
+static int setup_walk(git_revwalk **out, git_repository *repo)
+{
+ git_revwalk *walk;
+ git_strarray refs;
+ unsigned int i;
+ git_reference *ref;
+ int error;
+
+ error = git_reference_listall(&refs, repo, GIT_REF_LISTALL);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to list references");
+
+ error = git_revwalk_new(&walk, repo);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to setup walk");
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ for (i = 0; i < refs.count; ++i) {
+ /* No tags */
+ if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
+ continue;
+
+ error = git_reference_lookup(&ref, repo, refs.strings[i]);
+ if (error < GIT_ERROR) {
+ error = git__rethrow(error, "Failed to lookup %s", refs.strings[i]);
+ goto cleanup;
+ }
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
+ continue;
+ error = git_revwalk_push(walk, git_reference_oid(ref));
+ if (error < GIT_ERROR) {
+ error = git__rethrow(error, "Failed to push %s", refs.strings[i]);
+ goto cleanup;
+ }
+ }
+
+ *out = walk;
+cleanup:
+ git_strarray_free(&refs);
+
+ return error;
+}
+
+static int http_negotiate_fetch(git_transport *transport, git_repository *repo, git_headarray *wants)
+{
+ transport_http *t = (transport_http *) transport;
+ GIT_UNUSED_ARG(list);
+ int error;
+ unsigned int i;
+ char buff[128];
+ gitno_buffer buf;
+ git_revwalk *walk = NULL;
+ git_oid oid;
+ git_pkt_ack *pkt;
+ git_vector *common = &t->common;
+ const char *prefix = "http://", *url = t->parent.url;
+ git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT;
+ gitno_buffer_setup(&buf, buff, sizeof(buff), t->socket);
+
+ /* TODO: Store url in the transport */
+ if (!git__prefixcmp(url, prefix))
+ url += strlen(prefix);
+
+ error = git_vector_init(common, 16, NULL);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to init common vector");
+
+ error = setup_walk(&walk, repo);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to setup walk");
+ goto cleanup;
+ }
+
+ do {
+ error = do_connect(t, t->host, t->port);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to connect to host");
+ goto cleanup;
+ }
+
+ error = git_pkt_buffer_wants(wants, &t->caps, &data);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to send wants");
+ goto cleanup;
+ }
+
+ /* We need to send these on each connection */
+ git_vector_foreach (common, i, pkt) {
+ error = git_pkt_buffer_have(&pkt->oid, &data);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to buffer common have");
+ goto cleanup;
+ }
+ }
+
+ i = 0;
+ while ((i < 20) && ((error = git_revwalk_next(&oid, walk)) == GIT_SUCCESS)) {
+ error = git_pkt_buffer_have(&oid, &data);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to buffer have");
+ goto cleanup;
+ }
+ i++;
+ }
+
+ git_pkt_buffer_done(&data);
+
+ error = gen_request(&request, url, t->host, "POST", "upload-pack", data.size, 0);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to generate request");
+ goto cleanup;
+ }
+
+ error = gitno_send(t->socket, request.ptr, request.size, 0);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to send request");
+ goto cleanup;
+ }
+
+ error = gitno_send(t->socket, data.ptr, data.size, 0);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Failed to send data");
+ goto cleanup;
+ }
+
+ git_buf_clear(&request);
+ git_buf_clear(&data);
+
+ if (error < GIT_SUCCESS || i >= 256)
+ break;
+
+ error = parse_response(t);
+ if (error < GIT_SUCCESS) {
+ error = git__rethrow(error, "Error parsing the response");
+ goto cleanup;
+ }
+
+ if (t->pack_ready) {
+ error = GIT_SUCCESS;
+ goto cleanup;
+ }
+
+ } while(1);
+
+cleanup:
+ git_buf_free(&request);
+ git_buf_free(&data);
+ git_revwalk_free(walk);
+ return error;
+}
+
+typedef struct {
+ git_filebuf *file;
+ transport_http *transport;
+} download_pack_cbdata;
+
+static int on_message_complete_download_pack(http_parser *parser)
+{
+ download_pack_cbdata *data = (download_pack_cbdata *) parser->data;
+
+ data->transport->transfer_finished = 1;
+
+ return 0;
+}
+static int on_body_download_pack(http_parser *parser, const char *str, size_t len)
+{
+ download_pack_cbdata *data = (download_pack_cbdata *) parser->data;
+ transport_http *t = data->transport;
+ git_filebuf *file = data->file;
+
+
+ return t->error = git_filebuf_write(file, str, len);
+}
+
+/*
+ * As the server is probably using Transfer-Encoding: chunked, we have
+ * to use the HTTP parser to download the pack instead of giving it to
+ * the simple downloader. Furthermore, we're using keep-alive
+ * connections, so the simple downloader would just hang.
+ */
+static int http_download_pack(char **out, git_transport *transport, git_repository *repo)
+{
+ transport_http *t = (transport_http *) transport;
+ git_buf *oldbuf = &t->buf;
+ int error = GIT_SUCCESS;
+ http_parser_settings settings;
+ char buffer[1024];
+ gitno_buffer buf;
+ download_pack_cbdata data;
+ git_filebuf file;
+ char path[GIT_PATH_MAX], suff[] = "/objects/pack/pack-received\0";
+
+ /*
+ * This is part of the previous response, so we don't want to
+ * re-init the parser, just set these two callbacks.
+ */
+ data.file = &file;
+ data.transport = t;
+ t->parser.data = &data;
+ t->transfer_finished = 0;
+ memset(&settings, 0x0, sizeof(settings));
+ settings.on_message_complete = on_message_complete_download_pack;
+ settings.on_body = on_body_download_pack;
+
+ gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
+
+ git_path_join(path, repo->path_repository, suff);
+
+ if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) {
+ return git__throw(GIT_ERROR, "The pack doesn't start with the signature");
+ }
+
+ error = git_filebuf_open(&file, path, GIT_FILEBUF_TEMPORARY);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ /* Part of the packfile has been received, don't loose it */
+ error = git_filebuf_write(&file, oldbuf->ptr, oldbuf->size);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ while(1) {
+ size_t parsed;
+
+ error = gitno_recv(&buf);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Error receiving data from network");
+
+ parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
+ /* Both should happen at the same time */
+ if (parsed != buf.offset || t->error < GIT_SUCCESS)
+ return git__rethrow(t->error, "Error parsing HTTP data");
+
+ gitno_consume_n(&buf, parsed);
+
+ if (error == 0 || t->transfer_finished) {
+ break;
+ }
+ }
+
+ *out = git__strdup(file.path_lock);
+ if (*out == NULL) {
+ error = GIT_ENOMEM;
+ goto cleanup;
+ }
+
+ /* A bit dodgy, but we need to keep the pack at the temporary path */
+ error = git_filebuf_commit_at(&file, file.path_lock);
+
+cleanup:
+ if (error < GIT_SUCCESS)
+ git_filebuf_cleanup(&file);
+
+ return error;
+}
+
+static int http_close(git_transport *transport)
+{
+ transport_http *t = (transport_http *) transport;
+ int error;
+
+ error = gitno_close(t->socket);
+ if (error < 0)
+ return git__throw(GIT_EOSERR, "Failed to close the socket: %s", strerror(errno));
+
+ return GIT_SUCCESS;
+}
+
+
+static void http_free(git_transport *transport)
+{
+ transport_http *t = (transport_http *) transport;
+ git_vector *refs = &t->refs;
+ git_vector *common = &t->common;
+ unsigned int i;
+ git_pkt *p;
+
+#ifdef GIT_WIN32
+ /* cleanup the WSA context. note that this context
+ * can be initialized more than once with WSAStartup(),
+ * and needs to be cleaned one time for each init call
+ */
+ WSACleanup();
+#endif
+
+ git_vector_foreach(refs, i, p) {
+ git_pkt_free(p);
+ }
+ git_vector_free(refs);
+ git_vector_foreach(common, i, p) {
+ git_pkt_free(p);
+ }
+ git_vector_free(common);
+ git_buf_free(&t->buf);
+ free(t->heads);
+ free(t->content_type);
+ free(t->host);
+ free(t->port);
+ free(t->service);
+ free(t->parent.url);
+ free(t);
+}
+
+int git_transport_http(git_transport **out)
+{
+ transport_http *t;
+
+ t = git__malloc(sizeof(transport_http));
+ if (t == NULL)
+ return GIT_ENOMEM;
+
+ memset(t, 0x0, sizeof(transport_http));
+
+ t->parent.connect = http_connect;
+ t->parent.ls = http_ls;
+ t->parent.negotiate_fetch = http_negotiate_fetch;
+ t->parent.download_pack = http_download_pack;
+ t->parent.close = http_close;
+ t->parent.free = http_free;
+
+#ifdef GIT_WIN32
+ /* on win32, the WSA context needs to be initialized
+ * before any socket calls can be performed */
+ if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {
+ http_free((git_transport *) t);
+ return git__throw(GIT_EOSERR, "Winsock init failed");
+ }
+#endif
+
+ *out = (git_transport *) t;
+ return GIT_SUCCESS;
+}
--- /dev/null
+/*
+ * Copyright (C) 2009-2011 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 "git2/types.h"
+#include "git2/transport.h"
+#include "git2/net.h"
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/tag.h"
+#include "refs.h"
+#include "transport.h"
+#include "posix.h"
+
+typedef struct {
+ git_transport parent;
+ git_repository *repo;
+ git_vector *refs;
+} transport_local;
+
+/*
+ * Try to open the url as a git directory. The direction doesn't
+ * matter in this case because we're calulating the heads ourselves.
+ */
+static int local_connect(git_transport *transport, int GIT_UNUSED(direction))
+{
+ git_repository *repo;
+ int error;
+ transport_local *t = (transport_local *) transport;
+ const char *path;
+ const char file_prefix[] = "file://";
+ GIT_UNUSED_ARG(direction);
+
+ /* The repo layer doesn't want the prefix */
+ if (!git__prefixcmp(transport->url, file_prefix))
+ path = transport->url + strlen(file_prefix);
+ else
+ path = transport->url;
+
+ error = git_repository_open(&repo, path);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to open remote");
+
+ t->repo = repo;
+ t->parent.connected = 1;
+
+ return GIT_SUCCESS;
+}
+
+static int add_ref(const char *name, git_repository *repo, git_vector *vec)
+{
+ const char peeled[] = "^{}";
+ git_remote_head *head;
+ git_reference *ref;
+ git_object *obj = NULL;
+ int error = GIT_SUCCESS, peel_len, ret;
+
+ head = git__malloc(sizeof(git_remote_head));
+ if (head == NULL)
+ return GIT_ENOMEM;
+
+ head->name = git__strdup(name);
+ if (head->name == NULL) {
+ error = GIT_ENOMEM;
+ goto out;
+ }
+
+ error = git_reference_lookup(&ref, repo, name);
+ if (error < GIT_SUCCESS)
+ goto out;
+
+ error = git_reference_resolve(&ref, ref);
+ if (error < GIT_SUCCESS)
+ goto out;
+
+ git_oid_cpy(&head->oid, git_reference_oid(ref));
+
+ error = git_vector_insert(vec, head);
+ if (error < GIT_SUCCESS)
+ goto out;
+
+ /* If it's not a tag, we don't need to try to peel it */
+ if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
+ goto out;
+
+ error = git_object_lookup(&obj, repo, &head->oid, GIT_OBJ_ANY);
+ if (error < GIT_SUCCESS) {
+ git__rethrow(error, "Failed to lookup object");
+ }
+
+ /* If it's not an annotated tag, just get out */
+ if (git_object_type(obj) != GIT_OBJ_TAG)
+ goto out;
+
+ /* And if it's a tag, peel it, and add it to the list */
+ head = git__malloc(sizeof(git_remote_head));
+ peel_len = strlen(name) + strlen(peeled);
+ head->name = git__malloc(peel_len + 1);
+ ret = p_snprintf(head->name, peel_len + 1, "%s%s", name, peeled);
+ if (ret >= peel_len + 1) {
+ error = git__throw(GIT_ERROR, "The string is magically to long");
+ }
+
+ git_oid_cpy(&head->oid, git_tag_target_oid((git_tag *) obj));
+
+ error = git_vector_insert(vec, head);
+ if (error < GIT_SUCCESS)
+ goto out;
+
+ out:
+ git_object_close(obj);
+ if (error < GIT_SUCCESS) {
+ free(head->name);
+ free(head);
+ }
+ return error;
+}
+
+static int local_ls(git_transport *transport, git_headarray *array)
+{
+ int error;
+ unsigned int i;
+ git_repository *repo;
+ git_vector *vec;
+ git_strarray refs;
+ transport_local *t = (transport_local *) transport;
+
+ assert(transport && transport->connected);
+
+ repo = t->repo;
+
+ error = git_reference_listall(&refs, repo, GIT_REF_LISTALL);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to list remote heads");
+
+ vec = git__malloc(sizeof(git_vector));
+ if (vec == NULL) {
+ error = GIT_ENOMEM;
+ goto out;
+ }
+
+ error = git_vector_init(vec, refs.count, NULL);
+ if (error < GIT_SUCCESS)
+ return error;
+
+ /* Sort the references first */
+ git__tsort((void **)refs.strings, refs.count, &git__strcmp_cb);
+
+ /* Add HEAD */
+ error = add_ref(GIT_HEAD_FILE, repo, vec);
+ if (error < GIT_SUCCESS)
+ goto out;
+
+ for (i = 0; i < refs.count; ++i) {
+ error = add_ref(refs.strings[i], repo, vec);
+ if (error < GIT_SUCCESS)
+ goto out;
+ }
+
+ array->len = vec->length;
+ array->heads = (git_remote_head **)vec->contents;
+
+ t->refs = vec;
+
+ out:
+
+ git_strarray_free(&refs);
+
+ return error;
+}
+
+static int local_close(git_transport *GIT_UNUSED(transport))
+{
+ /* Nothing to do */
+ GIT_UNUSED_ARG(transport);
+ return GIT_SUCCESS;
+}
+
+static void local_free(git_transport *transport)
+{
+ unsigned int i;
+ transport_local *t = (transport_local *) transport;
+ git_vector *vec = t->refs;
+ git_remote_head *h;
+
+ assert(transport);
+
+ if (t->refs != NULL) {
+ git_vector_foreach (vec, i, h) {
+ free(h->name);
+ free(h);
+ }
+ git_vector_free(vec);
+ free(vec);
+ }
+
+ git_repository_free(t->repo);
+ free(t->parent.url);
+ free(t);
+}
+
+/**************
+ * Public API *
+ **************/
+
+int git_transport_local(git_transport **out)
+{
+ transport_local *t;
+
+ t = git__malloc(sizeof(transport_local));
+ if (t == NULL)
+ return GIT_ENOMEM;
+
+ memset(t, 0x0, sizeof(transport_local));
+
+ t->parent.connect = local_connect;
+ t->parent.ls = local_ls;
+ t->parent.close = local_close;
+ t->parent.free = local_free;
+
+ *out = (git_transport *) t;
+
+ return GIT_SUCCESS;
+}