diff --git a/include/git2.h b/include/git2.h index 3bb2fce11..501128c43 100644 --- a/include/git2.h +++ b/include/git2.h @@ -36,6 +36,7 @@ #include "git2/index.h" #include "git2/config.h" +#include "git2/transport.h" #include "git2/remote.h" #include "git2/clone.h" #include "git2/checkout.h" diff --git a/include/git2/remote.h b/include/git2/remote.h index ad5c38902..1827e12b0 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -13,6 +13,7 @@ #include "net.h" #include "indexer.h" #include "strarray.h" +#include "transport.h" /** * @file git2/remote.h @@ -283,9 +284,21 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha * @param remote the remote to configure * @param check whether to check the server's certificate (defaults to yes) */ - GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); +/** + * Sets a custom transport for the remote. The caller can use this function + * to bypass the automatic discovery of a transport by URL scheme (i.e. + * http://, https://, git://) and supply their own transport to be used + * instead. After providing the transport to a remote using this function, + * the transport object belongs exclusively to that remote, and the remote will + * free it when it is freed with git_remote_free. + * + * @param remote the remote to configure + * @param transport the transport object for the remote to use + */ +GIT_EXTERN(int) git_remote_set_transport(git_remote *remote, git_transport *transport); + /** * Argument to the completion callback which tells it which operation * finished. diff --git a/include/git2/transport.h b/include/git2/transport.h new file mode 100644 index 000000000..fd0d56fbe --- /dev/null +++ b/include/git2/transport.h @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_transport_h__ +#define INCLUDE_git_transport_h__ + +#include "indexer.h" +#include "net.h" + +/** + * @file git2/transport.h + * @brief Git transport interfaces and functions + * @defgroup git_transport interfaces and functions + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/* + *** Begin base transport interface *** + */ + +typedef enum { + GIT_TRANSPORTFLAGS_NONE = 0, + /* If the connection is secured with SSL/TLS, the authenticity + * of the server certificate should not be verified. */ + GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1 +} git_transport_flags_t; + +typedef void (*git_transport_message_cb)(const char *str, int len, void *data); + +typedef struct git_transport { + /* Set progress and error callbacks */ + int (*set_callbacks)(struct git_transport *transport, + git_transport_message_cb progress_cb, + git_transport_message_cb error_cb, + void *payload); + + /* Connect the transport to the remote repository, using the given + * direction. */ + int (*connect)(struct git_transport *transport, + const char *url, + int direction, + int flags); + + /* This function may be called after a successful call to connect(). The + * provided callback is invoked for each ref discovered on the remote + * end. */ + int (*ls)(struct git_transport *transport, + git_headlist_cb list_cb, + void *payload); + + /* Reserved until push is implemented. */ + int (*push)(struct git_transport *transport); + + /* This function may be called after a successful call to connect(), when + * the direction is FETCH. The function performs a negotiation to calculate + * the wants list for the fetch. */ + int (*negotiate_fetch)(struct git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count); + + /* This function may be called after a successful call to negotiate_fetch(), + * when the direction is FETCH. This function retrieves the pack file for + * the fetch from the remote end. */ + int (*download_pack)(struct git_transport *transport, + git_repository *repo, + git_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload); + + /* Checks to see if the transport is connected */ + int (*is_connected)(struct git_transport *transport, int *connected); + + /* Reads the flags value previously passed into connect() */ + int (*read_flags)(struct git_transport *transport, int *flags); + + /* Cancels any outstanding transport operation */ + void (*cancel)(struct git_transport *transport); + + /* This function is the reverse of connect() -- it terminates the + * connection to the remote end. */ + int (*close)(struct git_transport *transport); + + /* Frees/destructs the git_transport object. */ + void (*free)(struct git_transport *transport); +} git_transport; + +/** + * Function to use to create a transport from a URL. The transport database + * is scanned to find a transport that implements the scheme of the URI (i.e. + * git:// or http://) and a transport object is returned to the caller. + * + * @param transport The newly created transport (out) + * @param url The URL to connect to + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_new(git_transport **transport, const char *url); + +/** + * Function which checks to see if a transport could be created for the + * given URL (i.e. checks to see if libgit2 has a transport that supports + * the given URL's scheme) + * + * @param url The URL to check + * @return Zero if the URL is not valid; nonzero otherwise + */ +GIT_EXTERN(int) git_transport_valid_url(const char *url); + +/* Signature of a function which creates a transport */ +typedef int (*git_transport_cb)(git_transport **transport, void *param); + +/* Transports which come with libgit2 (match git_transport_cb). The expected + * value for "param" is listed in-line below. */ + +/** + * Create an instance of the dummy transport. + * + * @param transport The newly created transport (out) + * @param param You must pass NULL for this parameter. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_dummy( + git_transport **transport, + /* NULL */ void *param); + +/** + * Create an instance of the local transport. + * + * @param transport The newly created transport (out) + * @param param You must pass NULL for this parameter. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_local( + git_transport **transport, + /* NULL */ void *param); + +/** + * Create an instance of the smart transport. + * + * @param transport The newly created transport (out) + * @param param A pointer to a git_smart_subtransport_definition + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_smart( + git_transport **transport, + /* (git_smart_subtransport_definition *) */ void *param); + +/* + *** End of base transport interface *** + *** Begin interface for subtransports for the smart transport *** + */ + +/* The smart transport knows how to speak the git protocol, but it has no + * knowledge of how to establish a connection between it and another endpoint, + * or how to move data back and forth. For this, a subtransport interface is + * declared, and the smart transport delegates this work to the subtransports. + * Three subtransports are implemented: git, http, and winhttp. (The http and + * winhttp transports each implement both http and https.) */ + +/* Subtransports can either be RPC = 0 (persistent connection) or RPC = 1 + * (request/response). The smart transport handles the differences in its own + * logic. The git subtransport is RPC = 0, while http and winhttp are both + * RPC = 1. */ + +/* Actions that the smart transport can ask + * a subtransport to perform */ +typedef enum { + GIT_SERVICE_UPLOADPACK_LS = 1, + GIT_SERVICE_UPLOADPACK = 2, +} git_smart_service_t; + +struct git_smart_subtransport; + +/* A stream used by the smart transport to read and write data + * from a subtransport */ +typedef struct git_smart_subtransport_stream { + /* The owning subtransport */ + struct git_smart_subtransport *subtransport; + + int (*read)( + struct git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read); + + int (*write)( + struct git_smart_subtransport_stream *stream, + const char *buffer, + size_t len); + + void (*free)( + struct git_smart_subtransport_stream *stream); +} git_smart_subtransport_stream; + +/* An implementation of a subtransport which carries data for the + * smart transport */ +typedef struct git_smart_subtransport { + int (* action)( + git_smart_subtransport_stream **out, + struct git_smart_subtransport *transport, + const char *url, + git_smart_service_t action); + + void (* free)(struct git_smart_subtransport *transport); +} git_smart_subtransport; + +/* A function which creates a new subtransport for the smart transport */ +typedef int (*git_smart_subtransport_cb)( + git_smart_subtransport **out, + git_transport* owner); + +typedef struct git_smart_subtransport_definition { + /* The function to use to create the git_smart_subtransport */ + git_smart_subtransport_cb callback; + /* True if the protocol is stateless; false otherwise. For example, + * http:// is stateless, but git:// is not. */ + unsigned rpc : 1; +} git_smart_subtransport_definition; + +/* Smart transport subtransports that come with libgit2 */ + +/** + * Create an instance of the http subtransport. This subtransport + * also supports https. On Win32, this subtransport may be implemented + * using the WinHTTP library. + * + * @param out The newly created subtransport + * @param owner The smart transport to own this subtransport + * @return 0 or an error code + */ +GIT_EXTERN(int) git_smart_subtransport_http( + git_smart_subtransport **out, + git_transport* owner); + +/** + * Create an instance of the git subtransport. + * + * @param out The newly created subtransport + * @param owner The smart transport to own this subtransport + * @return 0 or an error code + */ +GIT_EXTERN(int) git_smart_subtransport_git( + git_smart_subtransport **out, + git_transport* owner); + +/* + *** End interface for subtransports for the smart transport *** + */ + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/clone.c b/src/clone.c index ab8b9bcbb..7352f5fb2 100644 --- a/src/clone.c +++ b/src/clone.c @@ -18,7 +18,6 @@ #include "common.h" #include "remote.h" -#include "pkt.h" #include "fileops.h" #include "refs.h" #include "path.h" @@ -171,11 +170,19 @@ static int update_head_to_new_branch( return error; } +static int get_head_callback(git_remote_head *head, void *payload) +{ + git_remote_head **destination = (git_remote_head **)payload; + + /* Save the first entry, and terminate the enumeration */ + *destination = head; + return 1; +} + static int update_head_to_remote(git_repository *repo, git_remote *remote) { int retcode = -1; git_remote_head *remote_head; - git_pkt_ref *pkt; struct head_info head_info; git_buf remote_master_name = GIT_BUF_INIT; @@ -189,8 +196,13 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) } /* Get the remote's HEAD. This is always the first ref in remote->refs. */ - pkt = remote->transport->refs.contents[0]; - remote_head = &pkt->head; + remote_head = NULL; + + if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head)) + return -1; + + assert(remote_head); + git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); git_buf_init(&head_info.branchname, 16); head_info.repo = repo; diff --git a/src/common.h b/src/common.h index 747bbf7ce..a35239e3d 100644 --- a/src/common.h +++ b/src/common.h @@ -69,7 +69,4 @@ void giterr_set_regex(const regex_t *regex, int error_code); #include "util.h" -typedef struct git_transport git_transport; -typedef struct gitno_buffer gitno_buffer; - #endif /* INCLUDE_common_h__ */ diff --git a/src/fetch.c b/src/fetch.c index 0aabe744f..4f9f0c6f9 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -9,17 +9,14 @@ #include "git2/refs.h" #include "git2/revwalk.h" #include "git2/indexer.h" +#include "git2/transport.h" #include "common.h" -#include "transport.h" #include "remote.h" #include "refspec.h" #include "pack.h" #include "fetch.h" #include "netops.h" -#include "pkt.h" - -#define NETWORK_XFER_THRESHOLD (100*1024) struct filter_payload { git_remote *remote; @@ -88,61 +85,6 @@ cleanup: return error; } -/* Wait until we get an ack from the */ -static int recv_pkt(git_pkt **out, gitno_buffer *buf) -{ - const char *ptr = buf->data, *line_end = ptr; - git_pkt *pkt; - int pkt_type, error = 0, ret; - - do { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); - else - error = GIT_EBUFS; - - if (error == 0) - break; /* return the pkt */ - - if (error < 0 && error != GIT_EBUFS) - return -1; - - if ((ret = gitno_recv(buf)) < 0) - return -1; - } while (error); - - gitno_consume(buf, line_end); - pkt_type = pkt->type; - if (out != NULL) - *out = pkt; - else - git__free(pkt); - - return pkt_type; -} - -static int store_common(git_transport *t) -{ - git_pkt *pkt = NULL; - gitno_buffer *buf = &t->buffer; - - do { - if (recv_pkt(&pkt, buf) < 0) - return -1; - - if (pkt->type == GIT_PKT_ACK) { - if (git_vector_insert(&t->common, pkt) < 0) - return -1; - } else { - git__free(pkt); - return 0; - } - - } while (1); - - return 0; -} - /* * In this first version, we push all our refs in and start sending * them out. When we get an ACK we hide that commit and continue @@ -151,13 +93,7 @@ static int store_common(git_transport *t) int git_fetch_negotiate(git_remote *remote) { git_transport *t = remote->transport; - gitno_buffer *buf = &t->buffer; - git_buf data = GIT_BUF_INIT; - git_revwalk *walk = NULL; - int error = -1, pkt_type; - unsigned int i; - git_oid oid; - + if (filter_wants(remote) < 0) { giterr_set(GITERR_NET, "Failed to filter the reference list for wants"); return -1; @@ -169,330 +105,23 @@ int git_fetch_negotiate(git_remote *remote) /* * Now we have everything set up so we can start tell the - * server what we want and what we have. Call the function if - * the transport has its own logic. This is transitional and - * will be removed once this function can support git and http. + * server what we want and what we have. */ - if (t->own_logic) - return t->negotiate_fetch(t, remote->repo, &remote->refs); - - /* No own logic, do our thing */ - if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0) - return -1; - - if (git_fetch_setup_walk(&walk, remote->repo) < 0) - goto on_error; - /* - * We don't support any kind of ACK extensions, so the negotiation - * boils down to sending what we have and listening for an ACK - * every once in a while. - */ - i = 0; - while ((error = git_revwalk_next(&oid, walk)) == 0) { - git_pkt_buffer_have(&oid, &data); - i++; - if (i % 20 == 0) { - if (t->cancel.val) { - giterr_set(GITERR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - - git_pkt_buffer_flush(&data); - if (git_buf_oom(&data)) - goto on_error; - - if (t->negotiation_step(t, data.ptr, data.size) < 0) - goto on_error; - - git_buf_clear(&data); - if (t->caps.multi_ack) { - if (store_common(t) < 0) - goto on_error; - } else { - pkt_type = recv_pkt(NULL, buf); - - if (pkt_type == GIT_PKT_ACK) { - break; - } else if (pkt_type == GIT_PKT_NAK) { - continue; - } else { - giterr_set(GITERR_NET, "Unexpected pkt type"); - goto on_error; - } - } - } - - if (t->common.length > 0) - break; - - if (i % 20 == 0 && t->rpc) { - git_pkt_ack *pkt; - unsigned int i; - - if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0) - goto on_error; - - git_vector_foreach(&t->common, i, pkt) { - git_pkt_buffer_have(&pkt->oid, &data); - } - - if (git_buf_oom(&data)) - goto on_error; - } - } - - if (error < 0 && error != GIT_ITEROVER) - goto on_error; - - /* Tell the other end that we're done negotiating */ - if (t->rpc && t->common.length > 0) { - git_pkt_ack *pkt; - unsigned int i; - - if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0) - goto on_error; - - git_vector_foreach(&t->common, i, pkt) { - git_pkt_buffer_have(&pkt->oid, &data); - } - - if (git_buf_oom(&data)) - goto on_error; - } - - git_pkt_buffer_done(&data); - if (t->cancel.val) { - giterr_set(GITERR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - if (t->negotiation_step(t, data.ptr, data.size) < 0) - goto on_error; - - git_buf_free(&data); - git_revwalk_free(walk); - - /* Now let's eat up whatever the server gives us */ - if (!t->caps.multi_ack) { - pkt_type = recv_pkt(NULL, buf); - if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { - giterr_set(GITERR_NET, "Unexpected pkt type"); - return -1; - } - } else { - git_pkt_ack *pkt; - do { - if (recv_pkt((git_pkt **)&pkt, buf) < 0) - return -1; - - if (pkt->type == GIT_PKT_NAK || - (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) { - git__free(pkt); - break; - } - - git__free(pkt); - } while (1); - } - - return 0; - -on_error: - git_revwalk_free(walk); - git_buf_free(&data); - return error; + return t->negotiate_fetch(t, + remote->repo, + (const git_remote_head * const *)remote->refs.contents, + remote->refs.length); } int git_fetch_download_pack( - git_remote *remote, - git_transfer_progress_callback progress_cb, - void *progress_payload) + git_remote *remote, + git_transfer_progress_callback progress_cb, + void *progress_payload) { git_transport *t = remote->transport; if(!remote->need_pack) return 0; - if (t->own_logic) - return t->download_pack(t, remote->repo, &remote->stats); - - return git_fetch__download_pack(t, remote->repo, &remote->stats, - progress_cb, progress_payload); - -} - -static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer *buf, git_transfer_progress *stats) -{ - int recvd; - - do { - if (t->cancel.val) { - giterr_set(GITERR_NET, "The fetch was cancelled by the user"); - return GIT_EUSER; - } - - if (git_indexer_stream_add(idx, buf->data, buf->offset, stats) < 0) - return -1; - - gitno_consume_n(buf, buf->offset); - - if ((recvd = gitno_recv(buf)) < 0) - return -1; - } while(recvd > 0); - - if (git_indexer_stream_finalize(idx, stats)) - return -1; - - return 0; -} - -struct network_packetsize_payload -{ - git_transfer_progress_callback callback; - void *payload; - git_transfer_progress *stats; - git_off_t last_fired_bytes; -}; - -static void network_packetsize(int received, void *payload) -{ - struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; - - /* Accumulate bytes */ - npp->stats->received_bytes += received; - - /* Fire notification if the threshold is reached */ - if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { - npp->last_fired_bytes = npp->stats->received_bytes; - npp->callback(npp->stats, npp->payload); - } -} - -/* Receiving data from a socket and storing it is pretty much the same for git and HTTP */ -int git_fetch__download_pack( - git_transport *t, - git_repository *repo, - git_transfer_progress *stats, - git_transfer_progress_callback progress_cb, - void *progress_payload) -{ - git_buf path = GIT_BUF_INIT; - gitno_buffer *buf = &t->buffer; - git_indexer_stream *idx = NULL; - int error = -1; - struct network_packetsize_payload npp = {0}; - - if (progress_cb) { - npp.callback = progress_cb; - npp.payload = progress_payload; - npp.stats = stats; - buf->packetsize_cb = &network_packetsize; - buf->packetsize_payload = &npp; - } - - if (git_buf_joinpath(&path, git_repository_path(repo), "objects/pack") < 0) - return -1; - - if (git_indexer_stream_new(&idx, git_buf_cstr(&path), progress_cb, progress_payload) < 0) - goto on_error; - - git_buf_free(&path); - memset(stats, 0, sizeof(git_transfer_progress)); - - /* - * If the remote doesn't support the side-band, we can feed - * the data directly to the indexer. Otherwise, we need to - * check which one belongs there. - */ - if (!t->caps.side_band && !t->caps.side_band_64k) { - if (no_sideband(t, idx, buf, stats) < 0) - goto on_error; - - git_indexer_stream_free(idx); - return 0; - } - - do { - git_pkt *pkt; - - if (t->cancel.val) { - giterr_set(GITERR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - - if (recv_pkt(&pkt, buf) < 0) - goto on_error; - - if (pkt->type == GIT_PKT_PROGRESS) { - if (t->progress_cb) { - git_pkt_progress *p = (git_pkt_progress *) pkt; - t->progress_cb(p->data, p->len, t->cb_data); - } - git__free(pkt); - } else if (pkt->type == GIT_PKT_DATA) { - git_pkt_data *p = (git_pkt_data *) pkt; - if (git_indexer_stream_add(idx, p->data, p->len, stats) < 0) - goto on_error; - - git__free(pkt); - } else if (pkt->type == GIT_PKT_FLUSH) { - /* A flush indicates the end of the packfile */ - git__free(pkt); - break; - } - } while (1); - - if (git_indexer_stream_finalize(idx, stats) < 0) - goto on_error; - - git_indexer_stream_free(idx); - return 0; - -on_error: - git_buf_free(&path); - git_indexer_stream_free(idx); - return error; -} - -int git_fetch_setup_walk(git_revwalk **out, git_repository *repo) -{ - git_revwalk *walk; - git_strarray refs; - unsigned int i; - git_reference *ref; - - if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0) - return -1; - - if (git_revwalk_new(&walk, repo) < 0) - return -1; - - 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; - - if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0) - goto on_error; - - if (git_reference_type(ref) == GIT_REF_SYMBOLIC) - continue; - if (git_revwalk_push(walk, git_reference_oid(ref)) < 0) - goto on_error; - - git_reference_free(ref); - } - - git_strarray_free(&refs); - *out = walk; - return 0; - -on_error: - git_reference_free(ref); - git_strarray_free(&refs); - return -1; + return t->download_pack(t, remote->repo, &remote->stats, progress_cb, progress_payload); } diff --git a/src/netops.c b/src/netops.c index d9663e63c..3e2743486 100644 --- a/src/netops.c +++ b/src/netops.c @@ -14,12 +14,13 @@ #else # include # ifdef _MSC_VER -# pragma comment(lib, "ws2_32.lib") +# pragma comment(lib, "ws2_32") # endif #endif #ifdef GIT_SSL # include +# include # include #endif @@ -30,7 +31,6 @@ #include "netops.h" #include "posix.h" #include "buffer.h" -#include "transport.h" #ifdef GIT_WIN32 static void net_set_error(const char *str) @@ -41,6 +41,8 @@ static void net_set_error(const char *str) size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, error, 0, (LPSTR)&err_str, 0, 0); + GIT_UNUSED(size); + giterr_set(GITERR_NET, "%s: %s", str, err_str); LocalFree(err_str); } @@ -108,8 +110,8 @@ static int gitno__recv_ssl(gitno_buffer *buf) int ret; do { - ret = SSL_read(buf->ssl->ssl, buf->data + buf->offset, buf->len - buf->offset); - } while (SSL_get_error(buf->ssl->ssl, ret) == SSL_ERROR_WANT_READ); + ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset); + } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ); if (ret < 0) { net_set_error("Error receiving socket data"); @@ -117,28 +119,26 @@ static int gitno__recv_ssl(gitno_buffer *buf) } buf->offset += ret; - if (buf->packetsize_cb) buf->packetsize_cb(ret, buf->packetsize_payload); return ret; } #endif -int gitno__recv(gitno_buffer *buf) +static int gitno__recv(gitno_buffer *buf) { int ret; - ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0); if (ret < 0) { net_set_error("Error receiving socket data"); return -1; } buf->offset += ret; - if (buf->packetsize_cb) buf->packetsize_cb(ret, buf->packetsize_payload); return ret; } void gitno_buffer_setup_callback( - git_transport *t, + gitno_socket *socket, gitno_buffer *buf, char *data, size_t len, @@ -148,20 +148,21 @@ void gitno_buffer_setup_callback( buf->data = data; buf->len = len; buf->offset = 0; - buf->fd = t->socket; + buf->socket = socket; buf->recv = recv; buf->cb_data = cb_data; } -void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len) +void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len) { #ifdef GIT_SSL - if (t->use_ssl) { - gitno_buffer_setup_callback(t, buf, data, len, gitno__recv_ssl, NULL); - buf->ssl = &t->ssl; - } else + if (socket->ssl.ctx) { + gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL); + return; + } #endif - gitno_buffer_setup_callback(t, buf, data, len, gitno__recv, NULL); + + gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL); } /* Consume up to ptr and move the rest of the buffer to the beginning */ @@ -187,31 +188,23 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) buf->offset -= cons; } -int gitno_ssl_teardown(git_transport *t) +#ifdef GIT_SSL + +static int gitno_ssl_teardown(gitno_ssl *ssl) { -#ifdef GIT_SSL int ret; -#endif - - if (!t->use_ssl) - return 0; - -#ifdef GIT_SSL - + do { - ret = SSL_shutdown(t->ssl.ssl); + ret = SSL_shutdown(ssl->ssl); } while (ret == 0); if (ret < 0) - return ssl_set_error(&t->ssl, ret); + return ssl_set_error(ssl, ret); - SSL_free(t->ssl.ssl); - SSL_CTX_free(t->ssl.ctx); -#endif + SSL_free(ssl->ssl); + SSL_CTX_free(ssl->ctx); return 0; } - -#ifdef GIT_SSL /* Match host names according to RFC 2818 rules */ static int match_host(const char *pattern, const char *host) { @@ -262,7 +255,7 @@ static int check_host_name(const char *name, const char *host) return 0; } -static int verify_server_cert(git_transport *t, const char *host) +static int verify_server_cert(gitno_ssl *ssl, const char *host) { X509 *cert; X509_NAME *peer_name; @@ -275,7 +268,7 @@ static int verify_server_cert(git_transport *t, const char *host) void *addr; int i = -1,j; - if (SSL_get_verify_result(t->ssl.ssl) != X509_V_OK) { + if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) { giterr_set(GITERR_SSL, "The SSL certificate is invalid"); return -1; } @@ -292,7 +285,7 @@ static int verify_server_cert(git_transport *t, const char *host) } - cert = SSL_get_peer_certificate(t->ssl.ssl); + cert = SSL_get_peer_certificate(ssl->ssl); /* Check the alternative names */ alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); @@ -376,7 +369,7 @@ static int verify_server_cert(git_transport *t, const char *host) on_error: OPENSSL_free(peer_cn); - return ssl_set_error(&t->ssl, 0); + return ssl_set_error(ssl, 0); cert_fail: OPENSSL_free(peer_cn); @@ -384,51 +377,81 @@ cert_fail: return -1; } -static int ssl_setup(git_transport *t, const char *host) +static int ssl_setup(gitno_socket *socket, const char *host, int flags) { int ret; SSL_library_init(); SSL_load_error_strings(); - t->ssl.ctx = SSL_CTX_new(SSLv23_method()); - if (t->ssl.ctx == NULL) - return ssl_set_error(&t->ssl, 0); + socket->ssl.ctx = SSL_CTX_new(SSLv23_method()); + if (socket->ssl.ctx == NULL) + return ssl_set_error(&socket->ssl, 0); - SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_NONE, NULL); - if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx)) - return ssl_set_error(&t->ssl, 0); + SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL); + if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx)) + return ssl_set_error(&socket->ssl, 0); - t->ssl.ssl = SSL_new(t->ssl.ctx); - if (t->ssl.ssl == NULL) - return ssl_set_error(&t->ssl, 0); + socket->ssl.ssl = SSL_new(socket->ssl.ctx); + if (socket->ssl.ssl == NULL) + return ssl_set_error(&socket->ssl, 0); - if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0) - return ssl_set_error(&t->ssl, ret); + if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0) + return ssl_set_error(&socket->ssl, ret); - if ((ret = SSL_connect(t->ssl.ssl)) <= 0) - return ssl_set_error(&t->ssl, ret); + if ((ret = SSL_connect(socket->ssl.ssl)) <= 0) + return ssl_set_error(&socket->ssl, ret); - if (t->check_cert && verify_server_cert(t, host) < 0) + if ((GITNO_CONNECT_SSL_NO_CHECK_CERT & flags) || verify_server_cert(&socket->ssl, host) < 0) return -1; return 0; } -#else -static int ssl_setup(git_transport *t, const char *host) -{ - GIT_UNUSED(t); - GIT_UNUSED(host); - return 0; -} #endif -int gitno_connect(git_transport *t, const char *host, const char *port) +static int gitno__close(GIT_SOCKET s) +{ +#ifdef GIT_WIN32 + if (SOCKET_ERROR == closesocket(s)) + return -1; + + if (0 != WSACleanup()) { + giterr_set(GITERR_OS, "Winsock cleanup failed"); + return -1; + } + + return 0; +#else + return close(s); +#endif +} + +int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags) { struct addrinfo *info = NULL, *p; struct addrinfo hints; - int ret; GIT_SOCKET s = INVALID_SOCKET; + int ret; + +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } + + if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { + WSACleanup(); + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } +#endif + + /* Zero the socket structure provided */ + memset(s_out, 0x0, sizeof(gitno_socket)); memset(&hints, 0x0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; @@ -452,7 +475,7 @@ int gitno_connect(git_transport *t, const char *host, const char *port) break; /* If we can't connect, try the next one */ - gitno_close(s); + gitno__close(s); s = INVALID_SOCKET; } @@ -462,46 +485,56 @@ int gitno_connect(git_transport *t, const char *host, const char *port) return -1; } - t->socket = s; + s_out->socket = s; p_freeaddrinfo(info); - if (t->use_ssl && ssl_setup(t, host) < 0) +#ifdef GIT_SSL + if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0) return -1; +#else + /* SSL is not supported */ + if (flags & GITNO_CONNECT_SSL) { + giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2."); + return -1; + } +#endif return 0; } #ifdef GIT_SSL -static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) +static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags) { int ret; size_t off = 0; + GIT_UNUSED(flags); + while (off < len) { ret = SSL_write(ssl->ssl, msg + off, len - off); if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE) return ssl_set_error(ssl, ret); off += ret; - } + } return off; } #endif -int gitno_send(git_transport *t, const char *msg, size_t len, int flags) +int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags) { int ret; size_t off = 0; #ifdef GIT_SSL - if (t->use_ssl) - return send_ssl(&t->ssl, msg, len); + if (socket->ssl.ctx) + return gitno_send_ssl(&socket->ssl, msg, len, flags); #endif while (off < len) { errno = 0; - ret = p_send(t->socket, msg + off, len - off, flags); + ret = p_send(socket->socket, msg + off, len - off, flags); if (ret < 0) { net_set_error("Error sending data"); return -1; @@ -513,19 +546,17 @@ int gitno_send(git_transport *t, const char *msg, size_t len, int flags) return (int)off; } - -#ifdef GIT_WIN32 -int gitno_close(GIT_SOCKET s) +int gitno_close(gitno_socket *s) { - return closesocket(s) == SOCKET_ERROR ? -1 : 0; -} -#else -int gitno_close(GIT_SOCKET s) -{ - return close(s); -} +#ifdef GIT_SSL + if (s->ssl.ctx && + gitno_ssl_teardown(&s->ssl) < 0) + return -1; #endif + return gitno__close(s->socket); +} + int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) { fd_set fds; @@ -535,10 +566,10 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) tv.tv_usec = usec; FD_ZERO(&fds); - FD_SET(buf->fd, &fds); + FD_SET(buf->socket->socket, &fds); /* The select(2) interface is silly */ - return select((int)buf->fd + 1, &fds, NULL, NULL, &tv); + return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv); } int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port) diff --git a/src/netops.h b/src/netops.h index 64da7fba9..efbbc65a4 100644 --- a/src/netops.h +++ b/src/netops.h @@ -10,33 +10,60 @@ #include "posix.h" #include "common.h" +#ifdef GIT_SSL +# include +#endif + +struct gitno_ssl { +#ifdef GIT_SSL + SSL_CTX *ctx; + SSL *ssl; +#else + size_t dummy; +#endif +}; + +typedef struct gitno_ssl gitno_ssl; + +/* Represents a socket that may or may not be using SSL */ +struct gitno_socket { + GIT_SOCKET socket; + gitno_ssl ssl; +}; + +typedef struct gitno_socket gitno_socket; + struct gitno_buffer { char *data; size_t len; size_t offset; - GIT_SOCKET fd; -#ifdef GIT_SSL - struct gitno_ssl *ssl; -#endif - int (*recv)(gitno_buffer *buffer); + gitno_socket *socket; + int (*recv)(struct gitno_buffer *buffer); void *cb_data; - void (*packetsize_cb)(int received, void *payload); - void *packetsize_payload; }; -void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len); -void gitno_buffer_setup_callback(git_transport *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); +typedef struct gitno_buffer gitno_buffer; + +/* Flags to gitno_connect */ +enum { + /* Attempt to create an SSL connection. */ + GITNO_CONNECT_SSL = 1, + + /* Valid only when GITNO_CONNECT_SSL is also specified. + * Indicates that the server certificate should not be validated. */ + GITNO_CONNECT_SSL_NO_CHECK_CERT = 2, +}; + +void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len); +void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); int gitno_recv(gitno_buffer *buf); -int gitno__recv(gitno_buffer *buf); void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -int gitno_connect(git_transport *t, const char *host, const char *port); -int gitno_send(git_transport *t, const char *msg, size_t len, int flags); -int gitno_close(GIT_SOCKET s); -int gitno_ssl_teardown(git_transport *t); -int gitno_send_chunk_size(int s, size_t len); +int gitno_connect(gitno_socket *socket, const char *host, const char *port, int flags); +int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags); +int gitno_close(gitno_socket *s); int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port); diff --git a/src/pack-objects.c b/src/pack-objects.c index eb76e05a2..b39684865 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -604,8 +604,8 @@ on_error: static int send_pack_file(void *buf, size_t size, void *data) { - git_transport *t = (git_transport *)data; - return gitno_send(t, buf, size, 0); + gitno_socket *s = (gitno_socket *)data; + return gitno_send(s, buf, size, 0); } static int write_pack_buf(void *buf, size_t size, void *data) @@ -1231,10 +1231,10 @@ static int prepare_pack(git_packbuilder *pb) #define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; } -int git_packbuilder_send(git_packbuilder *pb, git_transport *t) +int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s) { PREPARE_PACK; - return write_pack(pb, &send_pack_file, t); + return write_pack(pb, &send_pack_file, s); } int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb) diff --git a/src/pack-objects.h b/src/pack-objects.h index 0d4854d0d..0a88f7dd7 100644 --- a/src/pack-objects.h +++ b/src/pack-objects.h @@ -13,6 +13,7 @@ #include "buffer.h" #include "hash.h" #include "oidmap.h" +#include "netops.h" #include "git2/oid.h" @@ -81,7 +82,7 @@ struct git_packbuilder { bool done; }; -int git_packbuilder_send(git_packbuilder *pb, git_transport *t); +int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s); int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb); #endif /* INCLUDE_pack_objects_h__ */ diff --git a/src/pkt.h b/src/pkt.h deleted file mode 100644 index 0fdb5c7cd..000000000 --- a/src/pkt.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_pkt_h__ -#define INCLUDE_pkt_h__ - -#include "common.h" -#include "transport.h" -#include "buffer.h" -#include "posix.h" -#include "git2/net.h" - -enum git_pkt_type { - GIT_PKT_CMD, - GIT_PKT_FLUSH, - GIT_PKT_REF, - GIT_PKT_HAVE, - GIT_PKT_ACK, - GIT_PKT_NAK, - GIT_PKT_PACK, - GIT_PKT_COMMENT, - GIT_PKT_ERR, - GIT_PKT_DATA, - GIT_PKT_PROGRESS, -}; - -/* Used for multi-ack */ -enum git_ack_status { - GIT_ACK_NONE, - GIT_ACK_CONTINUE, - GIT_ACK_COMMON, - GIT_ACK_READY -}; - -/* This would be a flush pkt */ -typedef struct { - enum git_pkt_type type; -} git_pkt; - -struct git_pkt_cmd { - enum git_pkt_type type; - char *cmd; - char *path; - char *host; -}; - -/* This is a pkt-line with some info in it */ -typedef struct { - enum git_pkt_type type; - git_remote_head head; - char *capabilities; -} git_pkt_ref; - -/* Useful later */ -typedef struct { - enum git_pkt_type type; - git_oid oid; - enum git_ack_status status; -} git_pkt_ack; - -typedef struct { - enum git_pkt_type type; - char comment[GIT_FLEX_ARRAY]; -} git_pkt_comment; - -typedef struct { - enum git_pkt_type type; - int len; - char data[GIT_FLEX_ARRAY]; -} git_pkt_data; - -typedef git_pkt_data git_pkt_progress; - -typedef struct { - enum git_pkt_type type; - char error[GIT_FLEX_ARRAY]; -} git_pkt_err; - -int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); -int git_pkt_buffer_flush(git_buf *buf); -int git_pkt_send_flush(GIT_SOCKET s); -int git_pkt_buffer_done(git_buf *buf); -int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf); -int git_pkt_buffer_have(git_oid *oid, git_buf *buf); -void git_pkt_free(git_pkt *pkt); - -#endif diff --git a/src/protocol.c b/src/protocol.c deleted file mode 100644 index affad5173..000000000 --- a/src/protocol.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#include "common.h" -#include "protocol.h" -#include "pkt.h" -#include "buffer.h" - -int git_protocol_store_refs(git_transport *t, int flushes) -{ - gitno_buffer *buf = &t->buffer; - git_vector *refs = &t->refs; - int error, flush = 0, recvd; - const char *line_end; - git_pkt *pkt; - - do { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset); - else - error = GIT_EBUFS; - - if (error < 0 && error != GIT_EBUFS) - return -1; - - if (error == GIT_EBUFS) { - if ((recvd = gitno_recv(buf)) < 0) - return -1; - - if (recvd == 0 && !flush) { - giterr_set(GITERR_NET, "Early EOF"); - return -1; - } - - continue; - } - - gitno_consume(buf, line_end); - if (pkt->type == GIT_PKT_ERR) { - giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error); - git__free(pkt); - return -1; - } - - if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) - return -1; - - if (pkt->type == GIT_PKT_FLUSH) { - flush++; - git_pkt_free(pkt); - } - } while (flush < flushes); - - return flush; -} - -int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps) -{ - const char *ptr; - - /* No refs or capabilites, odd but not a problem */ - if (pkt == NULL || pkt->capabilities == NULL) - return 0; - - 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; - } - - if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { - caps->common = caps->multi_ack = 1; - ptr += strlen(GIT_CAP_MULTI_ACK); - continue; - } - - if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { - caps->common = caps->include_tag = 1; - ptr += strlen(GIT_CAP_INCLUDE_TAG); - continue; - } - - /* Keep side-band check after side-band-64k */ - if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { - caps->common = caps->side_band_64k = 1; - ptr += strlen(GIT_CAP_SIDE_BAND_64K); - continue; - } - - if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { - caps->common = caps->side_band = 1; - ptr += strlen(GIT_CAP_SIDE_BAND); - continue; - } - - - /* We don't know this capability, so skip it */ - ptr = strchr(ptr, ' '); - } - - return 0; -} diff --git a/src/protocol.h b/src/protocol.h deleted file mode 100644 index a990938e5..000000000 --- a/src/protocol.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_protocol_h__ -#define INCLUDE_protocol_h__ - -#include "transport.h" -#include "buffer.h" -#include "pkt.h" - -int git_protocol_store_refs(git_transport *t, int flushes); -int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps); - -#define GIT_SIDE_BAND_DATA 1 -#define GIT_SIDE_BAND_PROGRESS 2 -#define GIT_SIDE_BAND_ERROR 3 - -#endif diff --git a/src/remote.c b/src/remote.c index 26e93c044..47bcaf95f 100644 --- a/src/remote.c +++ b/src/remote.c @@ -14,7 +14,6 @@ #include "remote.h" #include "fetch.h" #include "refs.h" -#include "pkt.h" #include @@ -465,23 +464,30 @@ int git_remote_connect(git_remote *remote, int direction) { git_transport *t; const char *url; + int flags = GIT_TRANSPORTFLAGS_NONE; assert(remote); + t = remote->transport; + url = git_remote__urlfordirection(remote, direction); if (url == NULL ) return -1; - if (git_transport_new(&t, url) < 0) + /* A transport could have been supplied in advance with + * git_remote_set_transport */ + if (!t && git_transport_new(&t, url) < 0) return -1; - t->progress_cb = remote->callbacks.progress; - t->cb_data = remote->callbacks.data; - - t->check_cert = remote->check_cert; - if (t->connect(t, direction) < 0) { + if (t->set_callbacks && + t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.data) < 0) + goto on_error; + + if (!remote->check_cert) + flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT; + + if (t->connect(t, url, direction, flags) < 0) goto on_error; - } remote->transport = t; @@ -494,30 +500,14 @@ on_error: int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) { - git_vector *refs = &remote->transport->refs; - unsigned int i; - git_pkt *p = NULL; - assert(remote); - if (!remote->transport || !remote->transport->connected) { + if (!remote->transport) { giterr_set(GITERR_NET, "The remote is not connected"); return -1; } - git_vector_foreach(refs, i, p) { - git_pkt_ref *pkt = NULL; - - if (p->type != GIT_PKT_REF) - continue; - - pkt = (git_pkt_ref *)p; - - if (list_cb(&pkt->head, payload)) - return GIT_EUSER; - } - - return 0; + return remote->transport->ls(remote->transport, list_cb, payload); } int git_remote_download( @@ -535,54 +525,61 @@ int git_remote_download( return git_fetch_download_pack(remote, progress_cb, progress_payload); } +static int update_tips_callback(git_remote_head *head, void *payload) +{ + git_vector *refs = (git_vector *)payload; + git_vector_insert(refs, head); + + return 0; +} + int git_remote_update_tips(git_remote *remote) { int error = 0, autotag; unsigned int i = 0; git_buf refname = GIT_BUF_INIT; git_oid old; - git_pkt *pkt; git_odb *odb; - git_vector *refs; git_remote_head *head; git_reference *ref; struct git_refspec *spec; git_refspec tagspec; + git_vector refs; assert(remote); - refs = &remote->transport->refs; spec = &remote->fetch; - - if (refs->length == 0) - return 0; - + if (git_repository_odb__weakptr(&odb, remote->repo) < 0) return -1; if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) return -1; - /* HEAD is only allowed to be the first in the list */ - pkt = refs->contents[0]; - head = &((git_pkt_ref *)pkt)->head; - if (!strcmp(head->name, GIT_HEAD_FILE)) { - if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0) - return -1; + /* Make a copy of the transport's refs */ + if (git_vector_init(&refs, 16, NULL) < 0) + return -1; - i = 1; - git_reference_free(ref); + if (remote->transport->ls(remote->transport, update_tips_callback, &refs) < 0) + goto on_error; + + /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */ + if (refs.length > 0) { + head = (git_remote_head *)refs.contents[0]; + + if (!strcmp(head->name, GIT_HEAD_FILE)) { + if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0) + goto on_error; + + i = 1; + git_reference_free(ref); + } } - for (; i < refs->length; ++i) { - git_pkt *pkt = refs->contents[i]; + for (; i < refs.length; ++i) { + head = (git_remote_head *)refs.contents[i]; autotag = 0; - if (pkt->type == GIT_PKT_REF) - head = &((git_pkt_ref *)pkt)->head; - else - continue; - /* Ignore malformed ref names (which also saves us from tag^{} */ if (!git_reference_is_valid_name(head->name)) continue; @@ -631,11 +628,13 @@ int git_remote_update_tips(git_remote *remote) } } + git_vector_free(&refs); git_refspec__free(&tagspec); git_buf_free(&refname); return 0; on_error: + git_vector_free(&refs); git_refspec__free(&tagspec); git_buf_free(&refname); return -1; @@ -644,21 +643,31 @@ on_error: int git_remote_connected(git_remote *remote) { + int connected; + assert(remote); - return remote->transport == NULL ? 0 : remote->transport->connected; + + if (!remote->transport || !remote->transport->is_connected) + return 0; + + /* Ask the transport if it's connected. */ + remote->transport->is_connected(remote->transport, &connected); + + return connected; } void git_remote_stop(git_remote *remote) { - git_atomic_set(&remote->transport->cancel, 1); + if (remote->transport->cancel) + remote->transport->cancel(remote->transport); } void git_remote_disconnect(git_remote *remote) { assert(remote); - if (remote->transport != NULL && remote->transport->connected) - remote->transport->close(remote->transport); + if (git_remote_connected(remote)) + remote->transport->close(remote->transport); } void git_remote_free(git_remote *remote) @@ -787,10 +796,24 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks)); + if (remote->transport && remote->transport->set_callbacks) + remote->transport->set_callbacks(remote->transport, + remote->callbacks.progress, + NULL, + remote->callbacks.data); +} + +int git_remote_set_transport(git_remote *remote, git_transport *transport) +{ + assert(remote && transport); + if (remote->transport) { - remote->transport->progress_cb = remote->callbacks.progress; - remote->transport->cb_data = remote->callbacks.data; + giterr_set(GITERR_NET, "A transport is already bound to this remote"); + return -1; } + + remote->transport = transport; + return 0; } const git_transfer_progress* git_remote_stats(git_remote *remote) diff --git a/src/remote.h b/src/remote.h index 1b382e1bb..b0df2d649 100644 --- a/src/remote.h +++ b/src/remote.h @@ -8,9 +8,9 @@ #define INCLUDE_remote_h__ #include "git2/remote.h" +#include "git2/transport.h" #include "refspec.h" -#include "transport.h" #include "repository.h" #define GIT_REMOTE_ORIGIN "origin" diff --git a/src/transport.c b/src/transport.c index fb2b94946..8c242af6d 100644 --- a/src/transport.c +++ b/src/transport.c @@ -8,52 +8,78 @@ #include "git2/types.h" #include "git2/remote.h" #include "git2/net.h" -#include "transport.h" +#include "git2/transport.h" #include "path.h" -static struct { +typedef struct transport_definition { char *prefix; + unsigned priority; git_transport_cb fn; -} transports[] = { - {"git://", git_transport_git}, - {"http://", git_transport_http}, - {"https://", git_transport_https}, - {"file://", git_transport_local}, - {"git+ssh://", git_transport_dummy}, - {"ssh+git://", git_transport_dummy}, - {NULL, 0} + void *param; +} transport_definition; + +static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL }; +static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL }; + +static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 }; +static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 }; + +static transport_definition transports[] = { + {"git://", 1, git_transport_smart, &git_subtransport_definition}, + {"http://", 1, git_transport_smart, &http_subtransport_definition}, + {"https://", 1, git_transport_smart, &http_subtransport_definition}, + {"file://", 1, git_transport_local, NULL}, + {"git+ssh://", 1, git_transport_dummy, NULL}, + {"ssh+git://", 1, git_transport_dummy, NULL}, + {NULL, 0, 0} }; #define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 -static git_transport_cb transport_find_fn(const char *url) +static int transport_find_fn(const char *url, git_transport_cb *callback, void **param) { size_t i = 0; + unsigned priority = 0; + transport_definition *definition = NULL, *definition_iter; // First, check to see if it's an obvious URL, which a URL scheme for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { - if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix))) - return transports[i].fn; + definition_iter = &transports[i]; + + if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix))) + continue; + + if (definition_iter->priority > priority) + definition = definition_iter; } - /* still here? Check to see if the path points to a file on the local file system */ - if ((git_path_exists(url) == 0) && git_path_isdir(url)) - return &git_transport_local; + if (!definition) { + /* still here? Check to see if the path points to a file on the local file system */ + if ((git_path_exists(url) == 0) && git_path_isdir(url)) + definition = &local_transport_definition; - /* It could be a SSH remote path. Check to see if there's a : */ - if (strrchr(url, ':')) - return &git_transport_dummy; /* SSH is an unsupported transport mechanism in this version of libgit2 */ + /* It could be a SSH remote path. Check to see if there's a : */ + if (strrchr(url, ':')) + definition = &dummy_transport_definition; /* SSH is an unsupported transport mechanism in this version of libgit2 */ + } - return NULL; + if (!definition) + return -1; + + *callback = definition->fn; + *param = definition->param; + + return 0; } /************** * Public API * **************/ -int git_transport_dummy(git_transport **transport) +int git_transport_dummy(git_transport **transport, void *param) { GIT_UNUSED(transport); + GIT_UNUSED(param); giterr_set(GITERR_NET, "This transport isn't implemented. Sorry"); return -1; } @@ -62,22 +88,18 @@ int git_transport_new(git_transport **out, const char *url) { git_transport_cb fn; git_transport *transport; + void *param; int error; - fn = transport_find_fn(url); - - if (fn == NULL) { + if (transport_find_fn(url, &fn, ¶m) < 0) { giterr_set(GITERR_NET, "Unsupported URL protocol"); return -1; } - error = fn(&transport); + error = fn(&transport, param); if (error < 0) return error; - transport->url = git__strdup(url); - GITERR_CHECK_ALLOC(transport->url); - *out = transport; return 0; @@ -86,12 +108,19 @@ int git_transport_new(git_transport **out, const char *url) /* from remote.h */ int git_remote_valid_url(const char *url) { - return transport_find_fn(url) != NULL; + git_transport_cb fn; + void *param; + + return !transport_find_fn(url, &fn, ¶m); } int git_remote_supported_url(const char* url) { - git_transport_cb transport_fn = transport_find_fn(url); + git_transport_cb fn; + void *param; - return ((transport_fn != NULL) && (transport_fn != &git_transport_dummy)); + if (transport_find_fn(url, &fn, ¶m) < 0) + return 0; + + return fn != &git_transport_dummy; } diff --git a/src/transport.h b/src/transport.h deleted file mode 100644 index 1a3eee57d..000000000 --- a/src/transport.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_transport_h__ -#define INCLUDE_transport_h__ - -#include "git2/net.h" -#include "git2/indexer.h" -#include "vector.h" -#include "posix.h" -#include "common.h" -#include "netops.h" -#ifdef GIT_SSL -# include -# include -#endif - - -#define GIT_CAP_OFS_DELTA "ofs-delta" -#define GIT_CAP_MULTI_ACK "multi_ack" -#define GIT_CAP_SIDE_BAND "side-band" -#define GIT_CAP_SIDE_BAND_64K "side-band-64k" -#define GIT_CAP_INCLUDE_TAG "include-tag" - -typedef struct git_transport_caps { - int common:1, - ofs_delta:1, - multi_ack: 1, - side_band:1, - side_band_64k:1, - include_tag:1; -} git_transport_caps; - -#ifdef GIT_SSL -typedef struct gitno_ssl { - SSL_CTX *ctx; - SSL *ssl; -} gitno_ssl; -#endif - - -/* - * A day in the life of a network operation - * ======================================== - * - * The library gets told to ls-remote/push/fetch on/to/from some - * remote. We look at the URL of the remote and fill the function - * table with whatever is appropriate (the remote may be git over git, - * ssh or http(s). It may even be an hg or svn repository, the library - * at this level doesn't care, it just calls the helpers. - * - * The first call is to ->connect() which connects to the remote, - * making use of the direction if necessary. This function must also - * store the remote heads and any other information it needs. - * - * The next useful step is to call ->ls() to get the list of - * references available to the remote. These references may have been - * collected on connect, or we may build them now. For ls-remote, - * nothing else is needed other than closing the connection. - * Otherwise, the higher leves decide which objects we want to - * have. ->send_have() is used to tell the other end what we have. If - * we do need to download a pack, ->download_pack() is called. - * - * When we're done, we call ->close() to close the - * connection. ->free() takes care of freeing all the resources. - */ - -struct git_transport { - /** - * Where the repo lives - */ - char *url; - /** - * Whether we want to push or fetch - */ - int direction : 1, /* 0 fetch, 1 push */ - connected : 1, - check_cert: 1, - use_ssl : 1, - own_logic: 1, /* transitional */ - rpc: 1; /* git-speak for the HTTP transport */ -#ifdef GIT_SSL - struct gitno_ssl ssl; -#endif - git_vector refs; - git_vector common; - gitno_buffer buffer; - GIT_SOCKET socket; - git_transport_caps caps; - void *cb_data; - git_atomic cancel; - - /** - * Connect and store the remote heads - */ - int (*connect)(struct git_transport *transport, int dir); - /** - * Send our side of a negotiation - */ - int (*negotiation_step)(struct git_transport *transport, void *data, size_t len); - /** - * Push the changes over - */ - int (*push)(struct git_transport *transport); - /** - * Negotiate the minimal amount of objects that need to be - * retrieved - */ - int (*negotiate_fetch)(struct git_transport *transport, git_repository *repo, const git_vector *wants); - /** - * Download the packfile - */ - int (*download_pack)(struct git_transport *transport, git_repository *repo, git_transfer_progress *stats); - /** - * Close the connection - */ - int (*close)(struct git_transport *transport); - /** - * Free the associated resources - */ - void (*free)(struct git_transport *transport); - /** - * Callbacks for the progress and error output - */ - void (*progress_cb)(const char *str, int len, void *data); - void (*error_cb)(const char *str, int len, void *data); -}; - - -int git_transport_new(struct git_transport **transport, const char *url); -int git_transport_local(struct git_transport **transport); -int git_transport_git(struct git_transport **transport); -int git_transport_http(struct git_transport **transport); -int git_transport_https(struct git_transport **transport); -int git_transport_dummy(struct git_transport **transport); - -/** - Returns true if the passed URL is valid (a URL with a Git supported scheme, - or pointing to an existing path) -*/ -int git_transport_valid_url(const char *url); - -typedef int (*git_transport_cb)(git_transport **transport); - -#endif diff --git a/src/transports/git.c b/src/transports/git.c index b757495c5..a895c1389 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -5,40 +5,37 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "git2/net.h" -#include "git2/common.h" -#include "git2/types.h" -#include "git2/errors.h" -#include "git2/net.h" -#include "git2/revwalk.h" - -#include "vector.h" -#include "transport.h" -#include "pkt.h" -#include "common.h" +#include "git2.h" +#include "buffer.h" #include "netops.h" -#include "filebuf.h" -#include "repository.h" -#include "fetch.h" -#include "protocol.h" + +#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) + +static const char prefix_git[] = "git://"; +static const char cmd_uploadpack[] = "git-upload-pack"; typedef struct { - git_transport parent; - char buff[65536]; -#ifdef GIT_WIN32 - WSADATA wsd; -#endif -} transport_git; + git_smart_subtransport_stream parent; + gitno_socket socket; + const char *cmd; + char *url; + unsigned sent_command : 1; +} git_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + git_stream *current_stream; +} git_subtransport; /* - * Create a git procol request. + * Create a git protocol request. * * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 */ static int gen_proto(git_buf *request, const char *cmd, const char *url) { char *delim, *repo; - char default_command[] = "git-upload-pack"; char host[] = "host="; size_t len; @@ -54,9 +51,6 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) if (delim == NULL) delim = strchr(url, '/'); - if (cmd == NULL) - cmd = default_command; - len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; git_buf_grow(request, len); @@ -71,175 +65,211 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) return 0; } -static int send_request(git_transport *t, const char *cmd, const char *url) +static int send_command(git_stream *s) { int error; git_buf request = GIT_BUF_INIT; - error = gen_proto(&request, cmd, url); + error = gen_proto(&request, s->cmd, s->url); if (error < 0) goto cleanup; - error = gitno_send(t, request.ptr, request.size, 0); + /* It looks like negative values are errors here, and positive values + * are the number of bytes sent. */ + error = gitno_send(&s->socket, request.ptr, request.size, 0); + + if (error >= 0) + s->sent_command = 1; cleanup: git_buf_free(&request); return error; } -/* - * Parse the URL and connect to a server, storing the socket in - * out. For convenience this also takes care of asking for the remote - * refs - */ -static int do_connect(transport_git *t, const char *url) +static int git_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + git_stream *s = (git_stream *)stream; + gitno_buffer buf; + + *bytes_read = 0; + + if (!s->sent_command && send_command(s) < 0) + return -1; + + gitno_buffer_setup(&s->socket, &buf, buffer, buf_size); + + if (gitno_recv(&buf) < 0) + return -1; + + *bytes_read = buf.offset; + + return 0; +} + +static int git_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + git_stream *s = (git_stream *)stream; + + if (!s->sent_command && send_command(s) < 0) + return -1; + + return gitno_send(&s->socket, buffer, len, 0); +} + +static void git_stream_free(git_smart_subtransport_stream *stream) +{ + git_stream *s = (git_stream *)stream; + git_subtransport *t = OWNING_SUBTRANSPORT(s); + int ret; + + GIT_UNUSED(ret); + + t->current_stream = NULL; + + if (s->socket.socket) { + ret = gitno_close(&s->socket); + assert(!ret); + } + + git__free(s->url); + git__free(s); +} + +static int git_stream_alloc( + git_subtransport *t, + const char *url, + const char *cmd, + git_smart_subtransport_stream **stream) +{ + git_stream *s; + + if (!stream) + return -1; + + s = (git_stream *)git__calloc(sizeof(git_stream), 1); + GITERR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = git_stream_read; + s->parent.write = git_stream_write; + s->parent.free = git_stream_free; + + s->cmd = cmd; + s->url = git__strdup(url); + + if (!s->url) { + git__free(s); + return -1; + } + + *stream = &s->parent; + return 0; +} + +static int git_git_uploadpack_ls( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) { char *host, *port; - const char prefix[] = "git://"; + git_stream *s; - if (!git__prefixcmp(url, prefix)) - url += strlen(prefix); + *stream = NULL; + + if (!git__prefixcmp(url, prefix_git)) + url += strlen(prefix_git); + + if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0) + return -1; + + s = (git_stream *)*stream; if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) - return -1; - - if (gitno_connect((git_transport *)t, host, port) < 0) goto on_error; - if (send_request((git_transport *)t, NULL, url) < 0) + if (gitno_connect(&s->socket, host, port, 0) < 0) goto on_error; + t->current_stream = s; git__free(host); git__free(port); - return 0; on_error: + if (*stream) + git_stream_free(*stream); + git__free(host); git__free(port); - gitno_close(t->parent.socket); return -1; } -/* - * Since this is a network connection, we need to parse and store the - * pkt-lines at this stage and keep them there. - */ -static int git_connect(git_transport *transport, int direction) +static int git_git_uploadpack( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) { - transport_git *t = (transport_git *) transport; + GIT_UNUSED(url); - if (direction == GIT_DIR_PUSH) { - giterr_set(GITERR_NET, "Pushing over git:// is not supported"); - return -1; + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; } - t->parent.direction = direction; - - /* Connect and ask for the refs */ - if (do_connect(t, transport->url) < 0) - return -1; - - gitno_buffer_setup(transport, &transport->buffer, t->buff, sizeof(t->buff)); - - t->parent.connected = 1; - if (git_protocol_store_refs(transport, 1) < 0) - return -1; - - if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0) - return -1; - - return 0; + giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK"); + return -1; } -static int git_negotiation_step(struct git_transport *transport, void *data, size_t len) +static int _git_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *smart_transport, + const char *url, + git_smart_service_t action) { - return gitno_send(transport, data, len, 0); + git_subtransport *t = (git_subtransport *) smart_transport; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return git_git_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return git_git_uploadpack(t, url, stream); + } + + *stream = NULL; + return -1; } -static int git_close(git_transport *t) +static void _git_free(git_smart_subtransport *smart_transport) { - git_buf buf = GIT_BUF_INIT; + git_subtransport *t = (git_subtransport *) smart_transport; - if (git_pkt_buffer_flush(&buf) < 0) - return -1; - /* Can't do anything if there's an error, so don't bother checking */ - gitno_send(t, buf.ptr, buf.size, 0); - git_buf_free(&buf); + assert(!t->current_stream); - if (gitno_close(t->socket) < 0) { - giterr_set(GITERR_NET, "Failed to close socket"); - return -1; - } - - t->connected = 0; - -#ifdef GIT_WIN32 - WSACleanup(); -#endif - - return 0; -} - -static void git_free(git_transport *transport) -{ - transport_git *t = (transport_git *) transport; - git_vector *refs = &transport->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); - - refs = &transport->common; - for (i = 0; i < refs->length; ++i) { - git_pkt *p = git_vector_get(refs, i); - git_pkt_free(p); - } - git_vector_free(refs); - - git__free(t->parent.url); git__free(t); } -int git_transport_git(git_transport **out) +int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner) { - transport_git *t; -#ifdef GIT_WIN32 - int ret; -#endif + git_subtransport *t; - t = git__malloc(sizeof(transport_git)); + if (!out) + return -1; + + t = (git_subtransport *)git__calloc(sizeof(git_subtransport), 1); GITERR_CHECK_ALLOC(t); - memset(t, 0x0, sizeof(transport_git)); - if (git_vector_init(&t->parent.common, 8, NULL)) - goto on_error; - - if (git_vector_init(&t->parent.refs, 16, NULL) < 0) - goto on_error; - - t->parent.connect = git_connect; - t->parent.negotiation_step = git_negotiation_step; - 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); - giterr_set(GITERR_NET, "Winsock init failed"); - return -1; - } -#endif + t->owner = owner; + t->parent.action = _git_action; + t->parent.free = _git_free; + *out = (git_smart_subtransport *) t; return 0; - -on_error: - git__free(t); - return -1; } diff --git a/src/transports/http.c b/src/transports/http.c index 8042aeba1..f33cad7ea 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -4,28 +4,22 @@ * 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 +#ifndef GIT_WINHTTP + #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 "pack.h" -#include "fetch.h" -#include "filebuf.h" -#include "repository.h" -#include "protocol.h" -#if GIT_WINHTTP -# include -# pragma comment(lib, "winhttp.lib") -#endif +#include "netops.h" -#define WIDEN2(s) L ## s -#define WIDEN(s) WIDEN2(s) +static const char *prefix_http = "http://"; +static const char *prefix_https = "https://"; +static const char *upload_pack_service = "upload-pack"; +static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; +static const char *upload_pack_service_url = "/git-upload-pack"; +static const char *get_verb = "GET"; +static const char *post_verb = "POST"; + +#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) enum last_cb { NONE, @@ -34,43 +28,55 @@ enum last_cb { }; typedef struct { - git_transport parent; - http_parser_settings settings; - git_buf buf; - 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 *path; + git_smart_subtransport_stream parent; + const char *service; + const char *service_url; + const char *verb; + unsigned sent_request : 1; +} http_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + gitno_socket socket; + const char *path; char *host; - char *port; - char *service; - char buffer[65536]; -#ifdef GIT_WIN32 - WSADATA wsd; -#endif -#ifdef GIT_WINHTTP - HINTERNET session; - HINTERNET connection; - HINTERNET request; -#endif -} transport_http; + char *port; + unsigned connected : 1, + use_ssl : 1, + no_check_cert : 1; + + /* Parser structures */ + http_parser parser; + http_parser_settings settings; + gitno_buffer parse_buffer; + git_buf parse_temp; + char parse_buffer_data[2048]; + char *content_type; + enum last_cb last_cb; + int parse_error; + unsigned parse_finished : 1, + ct_found : 1, + ct_finished : 1; +} http_subtransport; + +typedef struct { + http_stream *s; + http_subtransport *t; + + /* Target buffer details from read() */ + char *buffer; + size_t buf_size; + size_t *bytes_read; +} parser_context; static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, - const char *service, ssize_t content_length, int ls) + const char *service, const char *service_url, ssize_t content_length) { 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_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url); 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) { @@ -88,172 +94,11 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c return 0; } -static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls) -{ -#ifndef GIT_WINHTTP - git_buf request = GIT_BUF_INIT; - const char *verb; - int error = -1; - - verb = ls ? "GET" : "POST"; - /* Generate and send the HTTP request */ - if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); - return -1; - } - - - if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0) - goto cleanup; - - if (content_length) { - if (gitno_send((git_transport *) t, data, content_length, 0) < 0) - goto cleanup; - } - - error = 0; - -cleanup: - git_buf_free(&request); - return error; - -#else - wchar_t *verb; - wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH]; - git_buf buf = GIT_BUF_INIT; - BOOL ret; - DWORD flags; - void *buffer; - wchar_t *types[] = { - L"*/*", - NULL, - }; - - verb = ls ? L"GET" : L"POST"; - buffer = data ? data : WINHTTP_NO_REQUEST_DATA; - flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0; - - if (ls) - git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service); - else - git_buf_printf(&buf, "%s/git-%s", t->path, service); - - if (git_buf_oom(&buf)) - return -1; - - git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf)); - - t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags); - if (t->request == NULL) { - git_buf_free(&buf); - giterr_set(GITERR_OS, "Failed to open request"); - return -1; - } - - git_buf_clear(&buf); - if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0) - goto on_error; - - git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf)); - - if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) { - giterr_set(GITERR_OS, "Failed to add a header to the request"); - goto on_error; - } - - if (!t->parent.check_cert) { - int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; - if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) { - giterr_set(GITERR_OS, "Failed to set options to ignore cert errors"); - goto on_error; - } - } - - if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, - data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) { - giterr_set(GITERR_OS, "Failed to send request"); - goto on_error; - } - - ret = WinHttpReceiveResponse(t->request, NULL); - if (ret == FALSE) { - giterr_set(GITERR_OS, "Failed to receive response"); - goto on_error; - } - - return 0; - -on_error: - git_buf_free(&buf); - if (t->request) - WinHttpCloseHandle(t->request); - t->request = NULL; - return -1; -#endif -} - -static int do_connect(transport_http *t) -{ -#ifndef GIT_WINHTTP - if (t->parent.connected && http_should_keep_alive(&t->parser)) - return 0; - - if (gitno_connect((git_transport *) t, t->host, t->port) < 0) - return -1; - - t->parent.connected = 1; - - return 0; -#else - wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - wchar_t host[GIT_WIN_PATH]; - int32_t port; - - t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); - - if (t->session == NULL) { - giterr_set(GITERR_OS, "Failed to init WinHTTP"); - goto on_error; - } - - git__utf8_to_16(host, GIT_WIN_PATH, t->host); - - if (git__strtol32(&port, t->port, NULL, 10) < 0) - goto on_error; - - t->connection = WinHttpConnect(t->session, host, port, 0); - if (t->connection == NULL) { - giterr_set(GITERR_OS, "Failed to connect to host"); - goto on_error; - } - - t->parent.connected = 1; - return 0; - -on_error: - if (t->session) { - WinHttpCloseHandle(t->session); - t->session = NULL; - } - return -1; -#endif -} - -/* - * 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; + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; + git_buf *buf = &t->parse_temp; if (t->last_cb == VALUE && t->ct_found) { t->ct_finished = 1; @@ -279,8 +124,9 @@ static int on_header_field(http_parser *parser, const char *str, size_t len) 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; + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; + git_buf *buf = &t->parse_temp; if (t->ct_finished) { t->last_cb = VALUE; @@ -290,7 +136,7 @@ static int on_header_value(http_parser *parser, const char *str, size_t len) if (t->last_cb == VALUE) git_buf_put(buf, str, len); - if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) { + if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), "Content-Type")) { t->ct_found = 1; git_buf_clear(buf); git_buf_put(buf, str, len); @@ -303,8 +149,9 @@ static int on_header_value(http_parser *parser, const char *str, size_t len) static int on_headers_complete(http_parser *parser) { - transport_http *t = (transport_http *) parser->data; - git_buf *buf = &t->buf; + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; + git_buf *buf = &t->parse_temp; /* The content-type is text/plain for 404, so don't validate */ if (parser->status_code == 404) { @@ -315,16 +162,18 @@ static int on_headers_complete(http_parser *parser) if (t->content_type == NULL) { t->content_type = git__strdup(git_buf_cstr(buf)); if (t->content_type == NULL) - return t->error = -1; + return t->parse_error = -1; } git_buf_clear(buf); - git_buf_printf(buf, "application/x-git-%s-advertisement", t->service); + git_buf_printf(buf, "application/x-git-%s-advertisement", ctx->s->service); if (git_buf_oom(buf)) - return t->error = -1; + return t->parse_error = -1; - if (strcmp(t->content_type, git_buf_cstr(buf))) - return t->error = -1; + if (strcmp(t->content_type, git_buf_cstr(buf))) { + giterr_set(GITERR_NET, "Invalid content-type: %s", t->content_type); + return t->parse_error = -1; + } git_buf_clear(buf); return 0; @@ -332,13 +181,14 @@ static int on_headers_complete(http_parser *parser) static int on_message_complete(http_parser *parser) { - transport_http *t = (transport_http *) parser->data; + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; - t->transfer_finished = 1; + t->parse_finished = 1; if (parser->status_code == 404) { - giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf)); - t->error = -1; + giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->parse_temp)); + t->parse_error = -1; } return 0; @@ -346,283 +196,289 @@ static int on_message_complete(http_parser *parser) static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len) { - git_transport *transport = (git_transport *) parser->data; - transport_http *t = (transport_http *) parser->data; - gitno_buffer *buf = &transport->buffer; + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; - if (buf->len - buf->offset < len) { + if (ctx->buf_size < len) { giterr_set(GITERR_NET, "Can't fit data in the buffer"); - return t->error = -1; + return t->parse_error = -1; } - memcpy(buf->data + buf->offset, str, len); - buf->offset += len; + memcpy(ctx->buffer, str, len); + *(ctx->bytes_read) += len; + ctx->buffer += len; + ctx->buf_size -= len; return 0; } -static int http_recv_cb(gitno_buffer *buf) +static int http_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) { - git_transport *transport = (git_transport *) buf->cb_data; - transport_http *t = (transport_http *) transport; - size_t old_len; - char buffer[2048]; -#ifdef GIT_WINHTTP - DWORD recvd; -#else - gitno_buffer inner; - int error; -#endif + http_stream *s = (http_stream *)stream; + http_subtransport *t = OWNING_SUBTRANSPORT(s); + git_buf request = GIT_BUF_INIT; + parser_context ctx; - if (t->transfer_finished) + *bytes_read = 0; + + assert(t->connected); + + if (!s->sent_request) { + if (gen_request(&request, t->path, t->host, s->verb, s->service, s->service_url, 0) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } + + if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) { + git_buf_free(&request); + return -1; + } + + git_buf_free(&request); + s->sent_request = 1; + } + + t->parse_buffer.offset = 0; + + if (t->parse_finished) return 0; -#ifndef GIT_WINHTTP - gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer)); - inner.packetsize_cb = buf->packetsize_cb; - inner.packetsize_payload = buf->packetsize_payload; - - if ((error = gitno_recv(&inner)) < 0) + if (gitno_recv(&t->parse_buffer) < 0) return -1; - old_len = buf->offset; - http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset); - if (t->error < 0) - return t->error; -#else - old_len = buf->offset; - if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) { - giterr_set(GITERR_OS, "Failed to read data from the network"); - return t->error = -1; - } + /* This call to http_parser_execute will result in invocations of the on_* family of + * callbacks. The most interesting of these is on_body_fill_buffer, which is called + * when data is ready to be copied into the target buffer. We need to marshal the + * buffer, buf_size, and bytes_read parameters to this callback. */ + ctx.t = t; + ctx.s = s; + ctx.buffer = buffer; + ctx.buf_size = buf_size; + ctx.bytes_read = bytes_read; - if (buf->len - buf->offset < recvd) { - giterr_set(GITERR_NET, "Can't fit data in the buffer"); - return t->error = -1; - } + /* Set the context, call the parser, then unset the context. */ + t->parser.data = &ctx; + http_parser_execute(&t->parser, &t->settings, t->parse_buffer.data, t->parse_buffer.offset); + t->parser.data = NULL; - memcpy(buf->data + buf->offset, buffer, recvd); - buf->offset += recvd; - if (buf->packetsize_cb) buf->packetsize_cb(recvd, buf->packetsize_payload); -#endif + if (t->parse_error < 0) + return t->parse_error; - return (int)(buf->offset - old_len); + return 0; } -/* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */ -static void setup_gitno_buffer(git_transport *transport) +static int http_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) { - transport_http *t = (transport_http *) transport; + http_stream *s = (http_stream *)stream; + http_subtransport *t = OWNING_SUBTRANSPORT(s); + git_buf request = GIT_BUF_INIT; + + assert(t->connected); + + /* Since we have to write the Content-Length header up front, we're + * basically limited to a single call to write() per request. */ + assert(!s->sent_request); + + if (!s->sent_request) { + if (gen_request(&request, t->path, t->host, s->verb, s->service, s->service_url, len) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } + + if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) + goto on_error; + + if (len && gitno_send(&t->socket, buffer, len, 0) < 0) + goto on_error; + + git_buf_free(&request); + s->sent_request = 1; + } + + return 0; + +on_error: + git_buf_free(&request); + return -1; +} + +static void http_stream_free(git_smart_subtransport_stream *stream) +{ + http_stream *s = (http_stream *)stream; + + git__free(s); +} + +static int http_stream_alloc(http_subtransport *t, git_smart_subtransport_stream **stream) +{ + http_stream *s; + + if (!stream) + return -1; + + s = (http_stream *)git__calloc(sizeof(http_stream), 1); + GITERR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = http_stream_read; + s->parent.write = http_stream_write; + s->parent.free = http_stream_free; + + *stream = (git_smart_subtransport_stream *)s; + return 0; +} + +static int http_uploadpack_ls( + http_subtransport *t, + git_smart_subtransport_stream **stream) +{ + http_stream *s; + + if (http_stream_alloc(t, stream) < 0) + return -1; + + s = (http_stream *)*stream; + + s->service = upload_pack_service; + s->service_url = upload_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int http_uploadpack( + http_subtransport *t, + git_smart_subtransport_stream **stream) +{ + http_stream *s; + + if (http_stream_alloc(t, stream) < 0) + return -1; + + s = (http_stream *)*stream; + + s->service = upload_pack_service; + s->service_url = upload_pack_service_url; + s->verb = post_verb; + + return 0; +} + +static int http_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *smart_transport, + const char *url, + git_smart_service_t action) +{ + http_subtransport *t = (http_subtransport *)smart_transport; + const char *default_port; + int flags = 0, ret; + + if (!stream) + return -1; + + if (!t->host || !t->port || !t->path) { + if (!git__prefixcmp(url, prefix_http)) { + url = url + strlen(prefix_http); + default_port = "80"; + } + + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + t->use_ssl = 1; + } + + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) + return ret; + + t->path = strchr(url, '/'); + } + + if (!t->connected || !http_should_keep_alive(&t->parser)) { + if (t->use_ssl) { + flags |= GITNO_CONNECT_SSL; + + if (t->no_check_cert) + flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT; + } + + if (gitno_connect(&t->socket, t->host, t->port, flags) < 0) + return -1; + + t->parser.data = t; + + http_parser_init(&t->parser, HTTP_RESPONSE); + gitno_buffer_setup(&t->socket, &t->parse_buffer, t->parse_buffer_data, sizeof(t->parse_buffer_data)); + + t->connected = 1; + } + + t->parse_finished = 0; + t->parse_error = 0; + + switch (action) + { + case GIT_SERVICE_UPLOADPACK_LS: + return http_uploadpack_ls(t, stream); + + case GIT_SERVICE_UPLOADPACK: + return http_uploadpack(t, stream); + } + + *stream = NULL; + return -1; +} + +static void http_free(git_smart_subtransport *smart_transport) +{ + http_subtransport *t = (http_subtransport *) smart_transport; + + git_buf_free(&t->parse_temp); + git__free(t->content_type); + git__free(t->host); + git__free(t->port); + git__free(t); +} + +int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner) +{ + http_subtransport *t; + int flags; + + if (!out) + return -1; + + t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1); + GITERR_CHECK_ALLOC(t); + + t->owner = owner; + t->parent.action = http_action; + t->parent.free = http_free; + + /* Read the flags from the owning transport */ + if (owner->read_flags && owner->read_flags(owner, &flags) < 0) { + git__free(t); + return -1; + } + + t->no_check_cert = flags & GIT_TRANSPORTFLAGS_NO_CHECK_CERT; - /* WinHTTP takes care of this for us */ -#ifndef GIT_WINHTTP - http_parser_init(&t->parser, HTTP_RESPONSE); - t->parser.data = t; - t->transfer_finished = 0; - memset(&t->settings, 0x0, sizeof(http_parser_settings)); t->settings.on_header_field = on_header_field; t->settings.on_header_value = on_header_value; t->settings.on_headers_complete = on_headers_complete; t->settings.on_body = on_body_fill_buffer; t->settings.on_message_complete = on_message_complete; -#endif - - gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t); -} - -static int http_connect(git_transport *transport, int direction) -{ - transport_http *t = (transport_http *) transport; - int ret; - git_buf request = GIT_BUF_INIT; - const char *service = "upload-pack"; - const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://"; - const char *default_port; - git_pkt *pkt; - - if (direction == GIT_DIR_PUSH) { - giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); - return -1; - } - - t->parent.direction = direction; - - if (!git__prefixcmp(url, prefix_http)) { - url = t->parent.url + strlen(prefix_http); - default_port = "80"; - } - - if (!git__prefixcmp(url, prefix_https)) { - url += strlen(prefix_https); - default_port = "443"; - } - - t->path = strchr(url, '/'); - - if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) - goto cleanup; - - t->service = git__strdup(service); - GITERR_CHECK_ALLOC(t->service); - - if ((ret = do_connect(t)) < 0) - goto cleanup; - - if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0) - goto cleanup; - - setup_gitno_buffer(transport); - if ((ret = git_protocol_store_refs(transport, 2)) < 0) - goto cleanup; - - pkt = git_vector_get(&transport->refs, 0); - if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) { - giterr_set(GITERR_NET, "Invalid HTTP response"); - return t->error = -1; - } else { - /* Remove the comment pkt from the list */ - git_vector_remove(&transport->refs, 0); - git__free(pkt); - } - - if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0) - return t->error = -1; - -cleanup: - git_buf_free(&request); - git_buf_clear(&t->buf); - - return ret; -} - -static int http_negotiation_step(struct git_transport *transport, void *data, size_t len) -{ - transport_http *t = (transport_http *) transport; - int ret; - - /* First, send the data as a HTTP POST request */ - if ((ret = do_connect(t)) < 0) - return -1; - - if (send_request(t, "upload-pack", data, len, 0) < 0) - return -1; - - /* Then we need to set up the buffer to grab data from the HTTP response */ - setup_gitno_buffer(transport); + *out = (git_smart_subtransport *) t; return 0; } -static int http_close(git_transport *transport) -{ -#ifndef GIT_WINHTTP - if (gitno_ssl_teardown(transport) < 0) - return -1; - - if (gitno_close(transport->socket) < 0) { - giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); - return -1; - } -#else - transport_http *t = (transport_http *) transport; - - if (t->request) - WinHttpCloseHandle(t->request); - if (t->connection) - WinHttpCloseHandle(t->connection); - if (t->session) - WinHttpCloseHandle(t->session); -#endif - - transport->connected = 0; - - return 0; -} - - -static void http_free(git_transport *transport) -{ - transport_http *t = (transport_http *) transport; - git_vector *refs = &transport->refs; - git_vector *common = &transport->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); - git__free(t->content_type); - git__free(t->host); - git__free(t->port); - git__free(t->service); - git__free(t->parent.url); - git__free(t); -} - -int git_transport_http(git_transport **out) -{ - transport_http *t; - - t = git__malloc(sizeof(transport_http)); - GITERR_CHECK_ALLOC(t); - - memset(t, 0x0, sizeof(transport_http)); - - t->parent.connect = http_connect; - t->parent.negotiation_step = http_negotiation_step; - t->parent.close = http_close; - t->parent.free = http_free; - t->parent.rpc = 1; - - if (git_vector_init(&t->parent.refs, 16, NULL) < 0) { - git__free(t); - return -1; - } - -#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); - giterr_set(GITERR_OS, "Winsock init failed"); - return -1; - } -#endif - - *out = (git_transport *) t; - return 0; -} - -int git_transport_https(git_transport **out) -{ -#if defined(GIT_SSL) || defined(GIT_WINHTTP) - transport_http *t; - if (git_transport_http((git_transport **)&t) < 0) - return -1; - - t->parent.use_ssl = 1; - t->parent.check_cert = 1; - *out = (git_transport *) t; - - return 0; -#else - GIT_UNUSED(out); - - giterr_set(GITERR_NET, "HTTPS support not available"); - return -1; -#endif -} +#endif /* !GIT_WINHTTP */ diff --git a/src/transports/local.c b/src/transports/local.c index 561c84fa0..5a279ef00 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -11,15 +11,20 @@ #include "git2/object.h" #include "git2/tag.h" #include "refs.h" -#include "transport.h" +#include "git2/transport.h" #include "posix.h" #include "path.h" #include "buffer.h" -#include "pkt.h" typedef struct { git_transport parent; + char *url; + int direction; + int flags; + git_atomic cancelled; git_repository *repo; + git_vector refs; + unsigned connected : 1; } transport_local; static int add_ref(transport_local *t, const char *name) @@ -27,32 +32,24 @@ static int add_ref(transport_local *t, const char *name) const char peeled[] = "^{}"; git_remote_head *head; git_object *obj = NULL, *target = NULL; - git_transport *transport = (git_transport *) t; git_buf buf = GIT_BUF_INIT; - git_pkt_ref *pkt; - head = git__malloc(sizeof(git_remote_head)); + head = (git_remote_head *)git__malloc(sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); - pkt = git__malloc(sizeof(git_pkt_ref)); - GITERR_CHECK_ALLOC(pkt); head->name = git__strdup(name); GITERR_CHECK_ALLOC(head->name); if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0) { + git__free(head->name); git__free(head); - git__free(pkt->head.name); - git__free(pkt); + return -1; } - pkt->type = GIT_PKT_REF; - memcpy(&pkt->head, head, sizeof(git_remote_head)); - git__free(head); - - if (git_vector_insert(&transport->refs, pkt) < 0) + if (git_vector_insert(&t->refs, head) < 0) { - git__free(pkt->head.name); - git__free(pkt); + git__free(head->name); + git__free(head); return -1; } @@ -60,7 +57,7 @@ static int add_ref(transport_local *t, const char *name) if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) return 0; - if (git_object_lookup(&obj, t->repo, &pkt->head.oid, GIT_OBJ_ANY) < 0) + if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0) return -1; head = NULL; @@ -72,27 +69,21 @@ static int add_ref(transport_local *t, const char *name) } /* And if it's a tag, peel it, and add it to the list */ - head = git__malloc(sizeof(git_remote_head)); + head = (git_remote_head *)git__malloc(sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); if (git_buf_join(&buf, 0, name, peeled) < 0) return -1; head->name = git_buf_detach(&buf); - pkt = git__malloc(sizeof(git_pkt_ref)); - GITERR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_REF; - if (git_tag_peel(&target, (git_tag *) obj) < 0) goto on_error; git_oid_cpy(&head->oid, git_object_id(target)); git_object_free(obj); git_object_free(target); - memcpy(&pkt->head, head, sizeof(git_remote_head)); - git__free(head); - if (git_vector_insert(&transport->refs, pkt) < 0) + if (git_vector_insert(&t->refs, head) < 0) return -1; return 0; @@ -107,12 +98,11 @@ static int store_refs(transport_local *t) { unsigned int i; git_strarray ref_names = {0}; - git_transport *transport = (git_transport *) t; assert(t); if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 || - git_vector_init(&transport->refs, (unsigned int)ref_names.count, NULL) < 0) + git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0) goto on_error; /* Sort the references first */ @@ -131,7 +121,7 @@ static int store_refs(transport_local *t) return 0; on_error: - git_vector_free(&transport->refs); + git_vector_free(&t->refs); git_strarray_free(&ref_names); return -1; } @@ -140,7 +130,7 @@ on_error: * 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 direction) +static int local_connect(git_transport *transport, const char *url, int direction, int flags) { git_repository *repo; int error; @@ -148,18 +138,21 @@ static int local_connect(git_transport *transport, int direction) const char *path; git_buf buf = GIT_BUF_INIT; - GIT_UNUSED(direction); + t->url = git__strdup(url); + GITERR_CHECK_ALLOC(t->url); + t->direction = direction; + t->flags = flags; /* The repo layer doesn't want the prefix */ - if (!git__prefixcmp(transport->url, "file://")) { - if (git_path_fromurl(&buf, transport->url) < 0) { + if (!git__prefixcmp(t->url, "file://")) { + if (git_path_fromurl(&buf, t->url) < 0) { git_buf_free(&buf); return -1; } path = git_buf_cstr(&buf); } else { /* We assume transport->url is already a path */ - path = transport->url; + path = t->url; } error = git_repository_open(&repo, path); @@ -174,26 +167,74 @@ static int local_connect(git_transport *transport, int direction) if (store_refs(t) < 0) return -1; - t->parent.connected = 1; + t->connected = 1; return 0; } -static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) +static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload) +{ + transport_local *t = (transport_local *)transport; + unsigned int i; + git_remote_head *head = NULL; + + if (!t->connected) { + giterr_set(GITERR_NET, "The transport is not connected"); + return -1; + } + + git_vector_foreach(&t->refs, i, head) { + if (list_cb(head, payload)) + return GIT_EUSER; + } + + return 0; +} + +static int local_negotiate_fetch( + git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, size_t count) { GIT_UNUSED(transport); GIT_UNUSED(repo); - GIT_UNUSED(wants); + GIT_UNUSED(refs); + GIT_UNUSED(count); giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry"); return -1; } +static int local_is_connected(git_transport *transport, int *connected) +{ + transport_local *t = (transport_local *)transport; + + *connected = t->connected; + + return 0; +} + +static int local_read_flags(git_transport *transport, int *flags) +{ + transport_local *t = (transport_local *)transport; + + *flags = t->flags; + + return 0; +} + +static void local_cancel(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + git_atomic_set(&t->cancelled, 1); +} + static int local_close(git_transport *transport) { transport_local *t = (transport_local *)transport; - t->parent.connected = 0; + t->connected = 0; git_repository_free(t->repo); t->repo = NULL; @@ -204,18 +245,18 @@ static void local_free(git_transport *transport) { unsigned int i; transport_local *t = (transport_local *) transport; - git_vector *vec = &transport->refs; - git_pkt_ref *pkt; + git_vector *vec = &t->refs; + git_remote_head *head; assert(transport); - git_vector_foreach (vec, i, pkt) { - git__free(pkt->head.name); - git__free(pkt); + git_vector_foreach (vec, i, head) { + git__free(head->name); + git__free(head); } git_vector_free(vec); - git__free(t->parent.url); + git__free(t->url); git__free(t); } @@ -223,20 +264,25 @@ static void local_free(git_transport *transport) * Public API * **************/ -int git_transport_local(git_transport **out) +int git_transport_local(git_transport **out, void *param) { transport_local *t; + GIT_UNUSED(param); + t = git__malloc(sizeof(transport_local)); GITERR_CHECK_ALLOC(t); memset(t, 0x0, sizeof(transport_local)); - - t->parent.own_logic = 1; + t->parent.connect = local_connect; t->parent.negotiate_fetch = local_negotiate_fetch; t->parent.close = local_close; t->parent.free = local_free; + t->parent.ls = local_ls; + t->parent.is_connected = local_is_connected; + t->parent.read_flags = local_read_flags; + t->parent.cancel = local_cancel; *out = (git_transport *) t; diff --git a/src/transports/smart.c b/src/transports/smart.c new file mode 100644 index 000000000..b9c90dfde --- /dev/null +++ b/src/transports/smart.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "git2.h" +#include "smart.h" +#include "refs.h" + +static int git_smart__recv_cb(gitno_buffer *buf) +{ + transport_smart *t = (transport_smart *) buf->cb_data; + size_t old_len, bytes_read; + int error; + + assert(t->current_stream); + + old_len = buf->offset; + + if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0) + return error; + + buf->offset += bytes_read; + + if (t->packetsize_cb) + t->packetsize_cb(bytes_read, t->packetsize_payload); + + return (int)(buf->offset - old_len); +} + +GIT_INLINE(void) git_smart__reset_stream(transport_smart *t) +{ + if (t->current_stream) { + t->current_stream->free(t->current_stream); + t->current_stream = NULL; + } +} + +static int git_smart__set_callbacks( + git_transport *transport, + git_transport_message_cb progress_cb, + git_transport_message_cb error_cb, + void *message_cb_payload) +{ + transport_smart *t = (transport_smart *)transport; + + t->progress_cb = progress_cb; + t->error_cb = error_cb; + t->message_cb_payload = message_cb_payload; + + return 0; +} + +static int git_smart__connect(git_transport *transport, const char *url, int direction, int flags) +{ + transport_smart *t = (transport_smart *)transport; + git_smart_subtransport_stream *stream; + int error; + git_pkt *pkt; + + git_smart__reset_stream(t); + + t->url = git__strdup(url); + GITERR_CHECK_ALLOC(t->url); + + t->direction = direction; + t->flags = flags; + + if (GIT_DIR_FETCH == direction) + { + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 0) + return error; + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + /* 2 flushes for RPC; 1 for stateful */ + if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) + return error; + + /* Strip the comment packet for RPC */ + if (t->rpc) { + pkt = (git_pkt *)git_vector_get(&t->refs, 0); + + if (!pkt || GIT_PKT_COMMENT != pkt->type) { + giterr_set(GITERR_NET, "Invalid response"); + return -1; + } else { + /* Remove the comment pkt from the list */ + git_vector_remove(&t->refs, 0); + git__free(pkt); + } + } + + /* We now have loaded the refs. */ + t->have_refs = 1; + + if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0) + return -1; + + if (t->rpc) + git_smart__reset_stream(t); + + /* We're now logically connected. */ + t->connected = 1; + + return 0; + } + else + { + giterr_set(GITERR_NET, "Push not implemented"); + return -1; + } + + return -1; +} + +static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload) +{ + transport_smart *t = (transport_smart *)transport; + unsigned int i; + git_pkt *p = NULL; + + if (!t->have_refs) { + giterr_set(GITERR_NET, "The transport has not yet loaded the refs"); + return -1; + } + + git_vector_foreach(&t->refs, i, p) { + git_pkt_ref *pkt = NULL; + + if (p->type != GIT_PKT_REF) + continue; + + pkt = (git_pkt_ref *)p; + + if (list_cb(&pkt->head, payload)) + return GIT_EUSER; + } + + return 0; +} + +int git_smart__negotiation_step(git_transport *transport, void *data, size_t len) +{ + transport_smart *t = (transport_smart *)transport; + git_smart_subtransport_stream *stream; + int error; + + if (t->rpc) + git_smart__reset_stream(t); + + if (GIT_DIR_FETCH == t->direction) { + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) + return error; + + /* If this is a stateful implementation, the stream we get back should be the same */ + assert(t->rpc || t->current_stream == stream); + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + if ((error = stream->write(stream, (const char *)data, len)) < 0) + return error; + + gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + return 0; + } + + giterr_set(GITERR_NET, "Push not implemented"); + return -1; +} + +static void git_smart__cancel(git_transport *transport) +{ + transport_smart *t = (transport_smart *)transport; + + git_atomic_set(&t->cancelled, 1); +} + +static int git_smart__is_connected(git_transport *transport, int *connected) +{ + transport_smart *t = (transport_smart *)transport; + + *connected = t->connected; + + return 0; +} + +static int git_smart__read_flags(git_transport *transport, int *flags) +{ + transport_smart *t = (transport_smart *)transport; + + *flags = t->flags; + + return 0; +} + +static int git_smart__close(git_transport *transport) +{ + transport_smart *t = (transport_smart *)transport; + + git_smart__reset_stream(t); + + t->connected = 0; + + return 0; +} + +static void git_smart__free(git_transport *transport) +{ + transport_smart *t = (transport_smart *)transport; + git_vector *refs = &t->refs; + git_vector *common = &t->common; + unsigned int i; + git_pkt *p; + + /* Make sure that the current stream is closed, if we have one. */ + git_smart__close(transport); + + /* Free the subtransport */ + t->wrapped->free(t->wrapped); + + 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__free(t->url); + git__free(t); +} + +int git_transport_smart(git_transport **out, void *param) +{ + transport_smart *t; + git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param; + + if (!param) + return -1; + + t = (transport_smart *)git__calloc(sizeof(transport_smart), 1); + GITERR_CHECK_ALLOC(t); + + t->parent.set_callbacks = git_smart__set_callbacks; + t->parent.connect = git_smart__connect; + t->parent.close = git_smart__close; + t->parent.free = git_smart__free; + t->parent.negotiate_fetch = git_smart__negotiate_fetch; + t->parent.download_pack = git_smart__download_pack; + t->parent.ls = git_smart__ls; + t->parent.is_connected = git_smart__is_connected; + t->parent.read_flags = git_smart__read_flags; + t->parent.cancel = git_smart__cancel; + + t->rpc = definition->rpc; + + if (git_vector_init(&t->refs, 16, NULL) < 0) { + git__free(t); + return -1; + } + + if (definition->callback(&t->wrapped, &t->parent) < 0) { + git__free(t); + return -1; + } + + *out = (git_transport *) t; + return 0; +} diff --git a/src/transports/smart.h b/src/transports/smart.h new file mode 100644 index 000000000..5784713eb --- /dev/null +++ b/src/transports/smart.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "git2.h" +#include "vector.h" +#include "netops.h" +#include "buffer.h" + +#define GIT_SIDE_BAND_DATA 1 +#define GIT_SIDE_BAND_PROGRESS 2 +#define GIT_SIDE_BAND_ERROR 3 + +#define GIT_CAP_OFS_DELTA "ofs-delta" +#define GIT_CAP_MULTI_ACK "multi_ack" +#define GIT_CAP_SIDE_BAND "side-band" +#define GIT_CAP_SIDE_BAND_64K "side-band-64k" +#define GIT_CAP_INCLUDE_TAG "include-tag" + +enum git_pkt_type { + GIT_PKT_CMD, + GIT_PKT_FLUSH, + GIT_PKT_REF, + GIT_PKT_HAVE, + GIT_PKT_ACK, + GIT_PKT_NAK, + GIT_PKT_PACK, + GIT_PKT_COMMENT, + GIT_PKT_ERR, + GIT_PKT_DATA, + GIT_PKT_PROGRESS, +}; + +/* Used for multi-ack */ +enum git_ack_status { + GIT_ACK_NONE, + GIT_ACK_CONTINUE, + GIT_ACK_COMMON, + GIT_ACK_READY +}; + +/* This would be a flush pkt */ +typedef struct { + enum git_pkt_type type; +} git_pkt; + +struct git_pkt_cmd { + enum git_pkt_type type; + char *cmd; + char *path; + char *host; +}; + +/* This is a pkt-line with some info in it */ +typedef struct { + enum git_pkt_type type; + git_remote_head head; + char *capabilities; +} git_pkt_ref; + +/* Useful later */ +typedef struct { + enum git_pkt_type type; + git_oid oid; + enum git_ack_status status; +} git_pkt_ack; + +typedef struct { + enum git_pkt_type type; + char comment[GIT_FLEX_ARRAY]; +} git_pkt_comment; + +typedef struct { + enum git_pkt_type type; + int len; + char data[GIT_FLEX_ARRAY]; +} git_pkt_data; + +typedef git_pkt_data git_pkt_progress; + +typedef struct { + enum git_pkt_type type; + char error[GIT_FLEX_ARRAY]; +} git_pkt_err; + +typedef struct transport_smart_caps { + int common:1, + ofs_delta:1, + multi_ack: 1, + side_band:1, + side_band_64k:1, + include_tag:1; +} transport_smart_caps; + +typedef void (*packetsize_cb)(int received, void *payload); + +typedef struct { + git_transport parent; + char *url; + int direction; + int flags; + git_transport_message_cb progress_cb; + git_transport_message_cb error_cb; + void *message_cb_payload; + git_smart_subtransport *wrapped; + git_smart_subtransport_stream *current_stream; + transport_smart_caps caps; + git_vector refs; + git_vector common; + git_atomic cancelled; + packetsize_cb packetsize_cb; + void *packetsize_payload; + unsigned rpc : 1, + have_refs : 1, + connected : 1; + gitno_buffer buffer; + char buffer_data[65536]; +} transport_smart; + +/* smart_protocol.c */ +int git_smart__store_refs(transport_smart *t, int flushes); +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps); + +int git_smart__negotiate_fetch( + git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count); + +int git_smart__download_pack( + git_transport *transport, + git_repository *repo, + git_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload); + +/* smart.c */ +int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); + +/* smart_pkt.c */ +int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); +int git_pkt_buffer_flush(git_buf *buf); +int git_pkt_send_flush(GIT_SOCKET s); +int git_pkt_buffer_done(git_buf *buf); +int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_buf *buf); +int git_pkt_buffer_have(git_oid *oid, git_buf *buf); +void git_pkt_free(git_pkt *pkt); diff --git a/src/pkt.c b/src/transports/smart_pkt.c similarity index 94% rename from src/pkt.c rename to src/transports/smart_pkt.c index 91f9b65c2..26fc0e4aa 100644 --- a/src/pkt.c +++ b/src/transports/smart_pkt.c @@ -12,12 +12,11 @@ #include "git2/refs.h" #include "git2/revwalk.h" -#include "pkt.h" +#include "smart.h" #include "util.h" #include "netops.h" #include "posix.h" #include "buffer.h" -#include "protocol.h" #include @@ -335,7 +334,7 @@ int git_pkt_buffer_flush(git_buf *buf) return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str)); } -static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf) +static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf) { git_buf str = GIT_BUF_INIT; char oid[GIT_OID_HEXSZ +1] = {0}; @@ -376,28 +375,32 @@ static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps * is overwrite the OID each time. */ -int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf) +int git_pkt_buffer_wants( + const git_remote_head * const *refs, + size_t count, + transport_smart_caps *caps, + git_buf *buf) { - unsigned int i = 0; - git_remote_head *head; + size_t i = 0; + const git_remote_head *head; if (caps->common) { - for (; i < refs->length; ++i) { - head = refs->contents[i]; + for (; i < count; ++i) { + head = refs[i]; if (!head->local) break; } - if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0) + if (buffer_want_with_caps(refs[i], caps, buf) < 0) return -1; i++; } - for (; i < refs->length; ++i) { + for (; i < count; ++i) { char oid[GIT_OID_HEXSZ]; - head = refs->contents[i]; + head = refs[i]; if (head->local) continue; diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c new file mode 100644 index 000000000..a2e9c886b --- /dev/null +++ b/src/transports/smart_protocol.c @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "smart.h" +#include "refs.h" + +#define NETWORK_XFER_THRESHOLD (100*1024) + +int git_smart__store_refs(transport_smart *t, int flushes) +{ + gitno_buffer *buf = &t->buffer; + git_vector *refs = &t->refs; + int error, flush = 0, recvd; + const char *line_end; + git_pkt *pkt; + + do { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset); + else + error = GIT_EBUFS; + + if (error < 0 && error != GIT_EBUFS) + return -1; + + if (error == GIT_EBUFS) { + if ((recvd = gitno_recv(buf)) < 0) + return -1; + + if (recvd == 0 && !flush) { + giterr_set(GITERR_NET, "Early EOF"); + return -1; + } + + continue; + } + + gitno_consume(buf, line_end); + if (pkt->type == GIT_PKT_ERR) { + giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error); + git__free(pkt); + return -1; + } + + if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) + return -1; + + if (pkt->type == GIT_PKT_FLUSH) { + flush++; + git_pkt_free(pkt); + } + } while (flush < flushes); + + return flush; +} + +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) +{ + const char *ptr; + + /* No refs or capabilites, odd but not a problem */ + if (pkt == NULL || pkt->capabilities == NULL) + return 0; + + 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; + } + + if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { + caps->common = caps->multi_ack = 1; + ptr += strlen(GIT_CAP_MULTI_ACK); + continue; + } + + if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { + caps->common = caps->include_tag = 1; + ptr += strlen(GIT_CAP_INCLUDE_TAG); + continue; + } + + /* Keep side-band check after side-band-64k */ + if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { + caps->common = caps->side_band_64k = 1; + ptr += strlen(GIT_CAP_SIDE_BAND_64K); + continue; + } + + if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { + caps->common = caps->side_band = 1; + ptr += strlen(GIT_CAP_SIDE_BAND); + continue; + } + + /* We don't know this capability, so skip it */ + ptr = strchr(ptr, ' '); + } + + return 0; +} + +static int recv_pkt(git_pkt **out, gitno_buffer *buf) +{ + const char *ptr = buf->data, *line_end = ptr; + git_pkt *pkt; + int pkt_type, error = 0, ret; + + do { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); + else + error = GIT_EBUFS; + + if (error == 0) + break; /* return the pkt */ + + if (error < 0 && error != GIT_EBUFS) + return -1; + + if ((ret = gitno_recv(buf)) < 0) + return -1; + } while (error); + + gitno_consume(buf, line_end); + pkt_type = pkt->type; + if (out != NULL) + *out = pkt; + else + git__free(pkt); + + return pkt_type; +} + +static int store_common(transport_smart *t) +{ + git_pkt *pkt = NULL; + gitno_buffer *buf = &t->buffer; + + do { + if (recv_pkt(&pkt, buf) < 0) + return -1; + + if (pkt->type == GIT_PKT_ACK) { + if (git_vector_insert(&t->common, pkt) < 0) + return -1; + } else { + git__free(pkt); + return 0; + } + + } while (1); + + return 0; +} + +static int fetch_setup_walk(git_revwalk **out, git_repository *repo) +{ + git_revwalk *walk; + git_strarray refs; + unsigned int i; + git_reference *ref; + + if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + 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; + + if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0) + goto on_error; + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) + continue; + if (git_revwalk_push(walk, git_reference_oid(ref)) < 0) + goto on_error; + + git_reference_free(ref); + } + + git_strarray_free(&refs); + *out = walk; + return 0; + +on_error: + git_reference_free(ref); + git_strarray_free(&refs); + return -1; +} + +int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count) +{ + transport_smart *t = (transport_smart *)transport; + gitno_buffer *buf = &t->buffer; + git_buf data = GIT_BUF_INIT; + git_revwalk *walk = NULL; + int error = -1, pkt_type; + unsigned int i; + git_oid oid; + + /* No own logic, do our thing */ + if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0) + return -1; + + if (fetch_setup_walk(&walk, repo) < 0) + goto on_error; + /* + * We don't support any kind of ACK extensions, so the negotiation + * boils down to sending what we have and listening for an ACK + * every once in a while. + */ + i = 0; + while ((error = git_revwalk_next(&oid, walk)) == 0) { + git_pkt_buffer_have(&oid, &data); + i++; + if (i % 20 == 0) { + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + + git_pkt_buffer_flush(&data); + if (git_buf_oom(&data)) + goto on_error; + + if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0) + goto on_error; + + git_buf_clear(&data); + if (t->caps.multi_ack) { + if (store_common(t) < 0) + goto on_error; + } else { + pkt_type = recv_pkt(NULL, buf); + + if (pkt_type == GIT_PKT_ACK) { + break; + } else if (pkt_type == GIT_PKT_NAK) { + continue; + } else { + giterr_set(GITERR_NET, "Unexpected pkt type"); + goto on_error; + } + } + } + + if (t->common.length > 0) + break; + + if (i % 20 == 0 && t->rpc) { + git_pkt_ack *pkt; + unsigned int i; + + if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0) + goto on_error; + + git_vector_foreach(&t->common, i, pkt) { + git_pkt_buffer_have(&pkt->oid, &data); + } + + if (git_buf_oom(&data)) + goto on_error; + } + } + + if (error < 0 && error != GIT_ITEROVER) + goto on_error; + + /* Tell the other end that we're done negotiating */ + if (t->rpc && t->common.length > 0) { + git_pkt_ack *pkt; + unsigned int i; + + if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0) + goto on_error; + + git_vector_foreach(&t->common, i, pkt) { + git_pkt_buffer_have(&pkt->oid, &data); + } + + if (git_buf_oom(&data)) + goto on_error; + } + + git_pkt_buffer_done(&data); + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0) + goto on_error; + + git_buf_free(&data); + git_revwalk_free(walk); + + /* Now let's eat up whatever the server gives us */ + if (!t->caps.multi_ack) { + pkt_type = recv_pkt(NULL, buf); + if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { + giterr_set(GITERR_NET, "Unexpected pkt type"); + return -1; + } + } else { + git_pkt_ack *pkt; + do { + if (recv_pkt((git_pkt **)&pkt, buf) < 0) + return -1; + + if (pkt->type == GIT_PKT_NAK || + (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) { + git__free(pkt); + break; + } + + git__free(pkt); + } while (1); + } + + return 0; + +on_error: + git_revwalk_free(walk); + git_buf_free(&data); + return error; +} + +static int no_sideband(transport_smart *t, git_indexer_stream *idx, gitno_buffer *buf, git_transfer_progress *stats) +{ + int recvd; + + do { + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + return GIT_EUSER; + } + + if (git_indexer_stream_add(idx, buf->data, buf->offset, stats) < 0) + return -1; + + gitno_consume_n(buf, buf->offset); + + if ((recvd = gitno_recv(buf)) < 0) + return -1; + } while(recvd > 0); + + if (git_indexer_stream_finalize(idx, stats)) + return -1; + + return 0; +} + +struct network_packetsize_payload +{ + git_transfer_progress_callback callback; + void *payload; + git_transfer_progress *stats; + git_off_t last_fired_bytes; +}; + +static void network_packetsize(int received, void *payload) +{ + struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; + + /* Accumulate bytes */ + npp->stats->received_bytes += received; + + /* Fire notification if the threshold is reached */ + if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { + npp->last_fired_bytes = npp->stats->received_bytes; + npp->callback(npp->stats, npp->payload); + } +} + +int git_smart__download_pack( + git_transport *transport, + git_repository *repo, + git_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload) +{ + transport_smart *t = (transport_smart *)transport; + git_buf path = GIT_BUF_INIT; + gitno_buffer *buf = &t->buffer; + git_indexer_stream *idx = NULL; + int error = -1; + struct network_packetsize_payload npp = {0}; + + if (progress_cb) { + npp.callback = progress_cb; + npp.payload = progress_payload; + npp.stats = stats; + t->packetsize_cb = &network_packetsize; + t->packetsize_payload = &npp; + } + + if (git_buf_joinpath(&path, git_repository_path(repo), "objects/pack") < 0) + return -1; + + if (git_indexer_stream_new(&idx, git_buf_cstr(&path), progress_cb, progress_payload) < 0) + goto on_error; + + git_buf_free(&path); + memset(stats, 0, sizeof(git_transfer_progress)); + + /* + * If the remote doesn't support the side-band, we can feed + * the data directly to the indexer. Otherwise, we need to + * check which one belongs there. + */ + if (!t->caps.side_band && !t->caps.side_band_64k) { + if (no_sideband(t, idx, buf, stats) < 0) + goto on_error; + + git_indexer_stream_free(idx); + return 0; + } + + do { + git_pkt *pkt; + + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + + if (recv_pkt(&pkt, buf) < 0) + goto on_error; + + if (pkt->type == GIT_PKT_PROGRESS) { + if (t->progress_cb) { + git_pkt_progress *p = (git_pkt_progress *) pkt; + t->progress_cb(p->data, p->len, t->message_cb_payload); + } + git__free(pkt); + } else if (pkt->type == GIT_PKT_DATA) { + git_pkt_data *p = (git_pkt_data *) pkt; + if (git_indexer_stream_add(idx, p->data, p->len, stats) < 0) + goto on_error; + + git__free(pkt); + } else if (pkt->type == GIT_PKT_FLUSH) { + /* A flush indicates the end of the packfile */ + git__free(pkt); + break; + } + } while (1); + + if (git_indexer_stream_finalize(idx, stats) < 0) + goto on_error; + + git_indexer_stream_free(idx); + return 0; + +on_error: + git_buf_free(&path); + git_indexer_stream_free(idx); + return error; +} diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c new file mode 100644 index 000000000..0411a70d4 --- /dev/null +++ b/src/transports/winhttp.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifdef GIT_WINHTTP + +#include "git2.h" +#include "git2/transport.h" +#include "buffer.h" +#include "posix.h" +#include "netops.h" +#include "smart.h" + +#include +#pragma comment(lib, "winhttp") + +#define WIDEN2(s) L ## s +#define WIDEN(s) WIDEN2(s) + +#define MAX_CONTENT_TYPE_LEN 100 +#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109 + +static const char *prefix_http = "http://"; +static const char *prefix_https = "https://"; +static const char *upload_pack_service = "upload-pack"; +static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; +static const char *upload_pack_service_url = "/git-upload-pack"; +static const wchar_t *get_verb = L"GET"; +static const wchar_t *post_verb = L"POST"; +static const wchar_t *pragma_nocache = L"Pragma: no-cache"; +static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | + SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | + SECURITY_FLAG_IGNORE_UNKNOWN_CA; + +#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport) + +typedef struct { + git_smart_subtransport_stream parent; + const char *service; + const char *service_url; + const wchar_t *verb; + HINTERNET request; + unsigned sent_request : 1, + received_response : 1; +} winhttp_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + const char *path; + char *host; + char *port; + HINTERNET session; + HINTERNET connection; + unsigned use_ssl : 1, + no_check_cert : 1; +} winhttp_subtransport; + +static int winhttp_stream_connect(winhttp_stream *s) +{ + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + git_buf buf = GIT_BUF_INIT; + wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN]; + wchar_t *types[] = { L"*/*", NULL }; + BOOL peerdist = FALSE; + + /* Prepare URL */ + git_buf_printf(&buf, "%s%s", t->path, s->service_url); + + if (git_buf_oom(&buf)) + return -1; + + git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf)); + + /* Establish request */ + s->request = WinHttpOpenRequest( + t->connection, + s->verb, + url, + NULL, + WINHTTP_NO_REFERER, + types, + t->use_ssl ? WINHTTP_FLAG_SECURE : 0); + + if (!s->request) { + giterr_set(GITERR_OS, "Failed to open request"); + goto on_error; + } + + /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP + * adds itself. This option may not be supported by the underlying + * platform, so we do not error-check it */ + WinHttpSetOption(s->request, + WINHTTP_OPTION_PEERDIST_EXTENSION_STATE, + &peerdist, + sizeof(peerdist)); + + /* Send Pragma: no-cache header */ + if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + + /* Send Content-Type header -- only necessary on a POST */ + if (post_verb == s->verb) { + git_buf_clear(&buf); + if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0) + goto on_error; + + git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + } + + /* If requested, disable certificate validation */ + if (t->use_ssl && t->no_check_cert) { + if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, + (LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) { + giterr_set(GITERR_OS, "Failed to set options to ignore cert errors"); + goto on_error; + } + } + + /* We've done everything up to calling WinHttpSendRequest. */ + + return 0; + +on_error: + git_buf_free(&buf); + return -1; +} + +static int winhttp_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + winhttp_stream *s = (winhttp_stream *)stream; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + + /* Connect if necessary */ + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + if (!s->sent_request && + !WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + 0, 0)) { + giterr_set(GITERR_OS, "Failed to send request"); + return -1; + } + + s->sent_request = 1; + + if (!s->received_response) { + DWORD status_code, status_code_length, content_type_length; + char expected_content_type_8[MAX_CONTENT_TYPE_LEN]; + wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; + + if (!WinHttpReceiveResponse(s->request, 0)) { + giterr_set(GITERR_OS, "Failed to receive response"); + return -1; + } + + /* Verify that we got a 200 back */ + status_code_length = sizeof(status_code); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status_code, &status_code_length, + WINHTTP_NO_HEADER_INDEX)) { + giterr_set(GITERR_OS, "Failed to retreive status code"); + return -1; + } + + if (HTTP_STATUS_OK != status_code) { + giterr_set(GITERR_NET, "Request failed with status code: %d", status_code); + return -1; + } + + /* Verify that we got the correct content-type back */ + if (post_verb == s->verb) + snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service); + else + snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); + + git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8); + content_type_length = sizeof(content_type); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_CONTENT_TYPE, + WINHTTP_HEADER_NAME_BY_INDEX, + &content_type, &content_type_length, + WINHTTP_NO_HEADER_INDEX)) { + giterr_set(GITERR_OS, "Failed to retrieve response content-type"); + return -1; + } + + if (wcscmp(expected_content_type, content_type)) { + giterr_set(GITERR_NET, "Received unexpected content-type"); + return -1; + } + + s->received_response = 1; + } + + if (!WinHttpReadData(s->request, + (LPVOID)buffer, + buf_size, + (LPDWORD)bytes_read)) + { + giterr_set(GITERR_OS, "Failed to read data"); + return -1; + } + + return 0; +} + +static int winhttp_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + DWORD bytes_written; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + /* Since we have to write the Content-Length header up front, we're + * basically limited to a single call to write() per request. */ + if (!s->sent_request && + !WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + (DWORD)len, 0)) { + giterr_set(GITERR_OS, "Failed to send request"); + return -1; + } + + s->sent_request = 1; + + if (!WinHttpWriteData(s->request, + (LPCVOID)buffer, + (DWORD)len, + &bytes_written)) { + giterr_set(GITERR_OS, "Failed to send request"); + return -1; + } + + assert((DWORD)len == bytes_written); + + return 0; +} + +static void winhttp_stream_free(git_smart_subtransport_stream *stream) +{ + winhttp_stream *s = (winhttp_stream *)stream; + + if (s->request) { + WinHttpCloseHandle(s->request); + s->request = NULL; + } + + git__free(s); +} + +static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_stream **stream) +{ + winhttp_stream *s; + + if (!stream) + return -1; + + s = (winhttp_stream *)git__calloc(sizeof(winhttp_stream), 1); + GITERR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = winhttp_stream_read; + s->parent.write = winhttp_stream_write; + s->parent.free = winhttp_stream_free; + + *stream = (git_smart_subtransport_stream *)s; + return 0; +} + +static int winhttp_connect( + winhttp_subtransport *t, + const char *url) +{ + wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; + wchar_t host[GIT_WIN_PATH]; + int32_t port; + const char *default_port; + int ret; + + if (!git__prefixcmp(url, prefix_http)) { + url = url + strlen(prefix_http); + default_port = "80"; + } + + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + t->use_ssl = 1; + } + + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) + return ret; + + t->path = strchr(url, '/'); + + /* Prepare port */ + if (git__strtol32(&port, t->port, NULL, 10) < 0) + return -1; + + /* Prepare host */ + git__utf8_to_16(host, GIT_WIN_PATH, t->host); + + /* Establish session */ + t->session = WinHttpOpen( + ua, + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (!t->session) { + giterr_set(GITERR_OS, "Failed to init WinHTTP"); + return -1; + } + + /* Establish connection */ + t->connection = WinHttpConnect( + t->session, + host, + port, + 0); + + if (!t->connection) { + giterr_set(GITERR_OS, "Failed to connect to host"); + return -1; + } + + return 0; +} + +static int winhttp_uploadpack_ls( + winhttp_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + winhttp_stream *s; + + if (!t->connection && + winhttp_connect(t, url) < 0) + return -1; + + if (winhttp_stream_alloc(t, stream) < 0) + return -1; + + s = (winhttp_stream *)*stream; + + s->service = upload_pack_service; + s->service_url = upload_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int winhttp_uploadpack( + winhttp_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + winhttp_stream *s; + + if (!t->connection && + winhttp_connect(t, url) < 0) + return -1; + + if (winhttp_stream_alloc(t, stream) < 0) + return -1; + + s = (winhttp_stream *)*stream; + + s->service = upload_pack_service; + s->service_url = upload_pack_service_url; + s->verb = post_verb; + + return 0; +} + +static int winhttp_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *smart_transport, + const char *url, + git_smart_service_t action) +{ + winhttp_subtransport *t = (winhttp_subtransport *)smart_transport; + + if (!stream) + return -1; + + switch (action) + { + case GIT_SERVICE_UPLOADPACK_LS: + return winhttp_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return winhttp_uploadpack(t, url, stream); + } + + *stream = NULL; + return -1; +} + +static void winhttp_free(git_smart_subtransport *smart_transport) +{ + winhttp_subtransport *t = (winhttp_subtransport *) smart_transport; + + git__free(t->host); + git__free(t->port); + + if (t->connection) { + WinHttpCloseHandle(t->connection); + t->connection = NULL; + } + + if (t->session) { + WinHttpCloseHandle(t->session); + t->session = NULL; + } + + git__free(t); +} + +int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner) +{ + winhttp_subtransport *t; + int flags; + + if (!out) + return -1; + + t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1); + GITERR_CHECK_ALLOC(t); + + t->owner = owner; + t->parent.action = winhttp_action; + t->parent.free = winhttp_free; + + /* Read the flags from the owning transport */ + if (owner->read_flags && owner->read_flags(owner, &flags) < 0) { + git__free(t); + return -1; + } + + t->no_check_cert = flags & GIT_TRANSPORTFLAGS_NO_CHECK_CERT; + + *out = (git_smart_subtransport *) t; + return 0; +} + +#endif /* GIT_WINHTTP */ diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_helpers.c index 647ea5201..250c9223b 100644 --- a/tests-clar/clar_helpers.c +++ b/tests-clar/clar_helpers.c @@ -89,7 +89,11 @@ int cl_setenv(const char *name, const char *value) if (value != NULL) git__utf8_to_16(value_utf16, GIT_WIN_PATH, value); - cl_assert(SetEnvironmentVariableW(name_utf16, value ? value_utf16 : NULL)); + /* Windows XP returns 0 (failed) when passing NULL for lpValue when lpName + * does not exist in the environment block. This behavior seems to have changed + * in later versions. Don't fail when SetEnvironmentVariable fails, if we passed + * NULL for lpValue. */ + cl_assert(SetEnvironmentVariableW(name_utf16, value ? value_utf16 : NULL) || !value); return 0; } diff --git a/tests-clar/network/remotelocal.c b/tests-clar/network/remotelocal.c index 3ff619748..d4bb1dfef 100644 --- a/tests-clar/network/remotelocal.c +++ b/tests-clar/network/remotelocal.c @@ -1,5 +1,4 @@ #include "clar_libgit2.h" -#include "transport.h" #include "buffer.h" #include "path.h" #include "posix.h" diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index 9fe67d856..21a3aaa41 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -1,7 +1,6 @@ #include "clar_libgit2.h" #include "buffer.h" #include "refspec.h" -#include "transport.h" #include "remote.h" static git_remote *_remote;