]> git.proxmox.com Git - libgit2.git/commitdiff
Move the transports to their own directory
authorCarlos Martín Nieto <carlos@cmartin.tk>
Thu, 6 Oct 2011 22:44:41 +0000 (00:44 +0200)
committerVicent Marti <tanoku@gmail.com>
Wed, 12 Oct 2011 19:34:25 +0000 (21:34 +0200)
CMakeLists.txt
src/transport-http.c [deleted file]
src/transport_git.c [deleted file]
src/transport_local.c [deleted file]
src/transports/git.c [new file with mode: 0644]
src/transports/http.c [new file with mode: 0644]
src/transports/local.c [new file with mode: 0644]

index fff125df9f674f6b946345e5f42a6ff325f1c374..ba646b61f749b36f1f8b0a8bbac6b4498d0eb97f 100644 (file)
@@ -91,9 +91,9 @@ FILE(GLOB SRC_H include/git2/*.h)
 # 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
diff --git a/src/transport-http.c b/src/transport-http.c
deleted file mode 100644 (file)
index 3426b34..0000000
+++ /dev/null
@@ -1,790 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/transport_git.c b/src/transport_git.c
deleted file mode 100644 (file)
index 4898078..0000000
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/transport_local.c b/src/transport_local.c
deleted file mode 100644 (file)
index 3f47e9b..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/transports/git.c b/src/transports/git.c
new file mode 100644 (file)
index 0000000..4898078
--- /dev/null
@@ -0,0 +1,516 @@
+/*
+ * 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;
+}
diff --git a/src/transports/http.c b/src/transports/http.c
new file mode 100644 (file)
index 0000000..3426b34
--- /dev/null
@@ -0,0 +1,790 @@
+/*
+ * 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;
+}
diff --git a/src/transports/local.c b/src/transports/local.c
new file mode 100644 (file)
index 0000000..3f47e9b
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * 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;
+}