diff --git a/include/git2.h b/include/git2.h index 6ede73cb5..f94b535c4 100644 --- a/include/git2.h +++ b/include/git2.h @@ -58,4 +58,7 @@ #include "git2/remote.h" #include "git2/refspec.h" +#include "git2/net.h" +#include "git2/transport.h" + #endif diff --git a/include/git2/net.h b/include/git2/net.h index 869309f9d..67e8a44e5 100644 --- a/include/git2/net.h +++ b/include/git2/net.h @@ -27,7 +27,7 @@ struct git_remote_head { struct git_headarray { unsigned int len; - struct git_remote_head *heads; + struct git_remote_head **heads; }; #endif diff --git a/include/git2/transport.h b/include/git2/transport.h index dfbc1a84c..a12b11b34 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -43,12 +43,14 @@ GIT_BEGIN_DECL * @param tranport the transport for the url * @param url the url of the repo */ -GIT_EXTERN(int) git_transport_get(git_transport *transport, const char *url); +GIT_EXTERN(int) git_transport_new(git_transport **transport, git_repository *repo, const char *url); GIT_EXTERN(int) git_transport_connect(git_transport *transport, git_net_direction direction); -/* -GIT_EXTERN(const git_vector *) git_transport_get_refs(git_transport *transport); -*/ + +GIT_EXTERN(int) git_transport_ls(git_transport *transport, git_headarray *array); +GIT_EXTERN(int) git_transport_close(git_transport *transport); +GIT_EXTERN(void) git_transport_free(git_transport *transport); + GIT_EXTERN(int) git_transport_add(git_transport *transport, const char *prefix); /** @} */ diff --git a/include/git2/types.h b/include/git2/types.h index 69aa28955..8e0659127 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -179,6 +179,7 @@ typedef enum git_net_direction git_net_direction; typedef int (*git_transport_cb)(git_transport *transport); +typedef struct git_remote_head git_remote_head; typedef struct git_headarray git_headarray; /** @} */ diff --git a/src/transport.c b/src/transport.c index c08345968..2ef99dc38 100644 --- a/src/transport.c +++ b/src/transport.c @@ -61,6 +61,8 @@ int git_transport_new(git_transport **out, git_repository *repo, const char *url if (transport == NULL) return GIT_ENOMEM; + memset(transport, 0x0, sizeof(git_transport)); + transport->url = git__strdup(url); if (transport->url == NULL) return GIT_ENOMEM; @@ -75,3 +77,23 @@ int git_transport_new(git_transport **out, git_repository *repo, const char *url return GIT_SUCCESS; } + +int git_transport_connect(git_transport *transport, git_net_direction dir) +{ + return transport->connect(transport, dir); +} + +int git_transport_ls(git_transport *transport, git_headarray *array) +{ + return transport->ls(transport, array); +} + +int git_transport_close(git_transport *transport) +{ + return transport->close(transport); +} + +void git_transport_free(git_transport *transport) +{ + transport->free(transport); +} diff --git a/src/transport.h b/src/transport.h index 8585b00f7..d70caaacf 100644 --- a/src/transport.h +++ b/src/transport.h @@ -43,6 +43,7 @@ struct git_transport { * Whether we want to push or fetch */ git_net_direction direction; + int connected : 1; /** * Connect and store the remote heads */ @@ -71,6 +72,10 @@ struct git_transport { * Close the connection */ int (*close)(struct git_transport *transport); + /** + * Free the associated resources + */ + void (*free)(struct git_transport *transport); }; int git_transport_local(struct git_transport *transport); diff --git a/src/transport_local.c b/src/transport_local.c index f492a875f..2cc78ca31 100644 --- a/src/transport_local.c +++ b/src/transport_local.c @@ -3,8 +3,24 @@ #include "git2/transport.h" #include "git2/net.h" #include "git2/repository.h" +#include "git2/object.h" +#include "git2/tag.h" +#include "refs.h" #include "transport.h" +typedef struct { + git_vector *vec; + git_repository *repo; +} callback_data; + +static int compare_heads(const void *a, const void *b) +{ + const git_remote_head *heada = *(const git_remote_head **)a; + const git_remote_head *headb = *(const git_remote_head **)b; + + return strcmp(heada->name, headb->name); +} + /* * Try to open the url as a git directory. The direction doesn't * matter in this case because we're calulating the heads ourselves. @@ -13,21 +29,142 @@ static int local_connect(git_transport *transport, git_net_direction GIT_UNUSED( { git_repository *repo; int error; + const char *path; + const char file_prefix[] = "file://"; + GIT_UNUSED_ARG(dir); - error = git_repository_open(&repo, transport->url); + /* The repo layer doesn't want the prefix */ + if (!git__prefixcmp(transport->url, file_prefix)) + path = transport->url + STRLEN(file_prefix); + else + path = transport->url; + + error = git_repository_open(&repo, path); if (error < GIT_SUCCESS) - return git__rethrow(error, "Can't open remote"); + return git__rethrow(error, "Failed to open remote"); transport->private = repo; + transport->connected = 1; + return GIT_SUCCESS; } +static int heads_cb(const char *name, void *ptr) +{ + callback_data *data = ptr; + git_vector *vec = data->vec; + git_repository *repo = data->repo; + const char peeled[] = "^{}"; + git_remote_head *head; + git_reference *ref; + git_object *obj = NULL; + int error = GIT_SUCCESS, peel_len, ret; + + head = git__malloc(sizeof(git_remote_head)); + if (head == NULL) + return GIT_ENOMEM; + + head->name = git__strdup(name); + if (head->name == NULL) { + error = GIT_ENOMEM; + goto out; + } + + error = git_reference_lookup(&ref, repo, name); + if (error < GIT_SUCCESS) + goto out; + + error = git_reference_resolve(&ref, ref); + if (error < GIT_SUCCESS) + goto out; + + git_oid_cpy(&head->oid, git_reference_oid(ref)); + + error = git_vector_insert(vec, head); + if (error < GIT_SUCCESS) + goto out; + + /* If it's not a tag, we don't need to try to peel it */ + if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) + goto out; + + error = git_object_lookup(&obj, repo, &head->oid, GIT_OBJ_ANY); + if (error < GIT_SUCCESS) { + git__rethrow(error, "Failed to lookup object"); + } + + /* If it's not an annotated tag, just get out */ + if (git_object_type(obj) != GIT_OBJ_TAG) + goto out; + + /* And if it's a tag, peel it, and add it to the list */ + head = git__malloc(sizeof(git_remote_head)); + peel_len = strlen(name) + STRLEN(peeled); + head->name = git__malloc(peel_len + 1); + ret = snprintf(head->name, peel_len + 1, "%s%s", name, peeled); + if (ret >= peel_len + 1) { + error = git__throw(GIT_ERROR, "The string is magically to long"); + } + + git_oid_cpy(&head->oid, git_tag_target_oid((git_tag *) obj)); + + error = git_vector_insert(vec, head); + if (error < GIT_SUCCESS) + goto out; + + out: + git_object_close(obj); + if (error < GIT_SUCCESS) { + free(head->name); + free(head); + } + return error; +} + static int local_ls(git_transport *transport, git_headarray *array) { + int error; + git_repository *repo; + git_vector vec; + callback_data data; + + assert(transport && transport->connected); + + repo = transport->private; + error = git_vector_init(&vec, 16, compare_heads); + if (error < GIT_SUCCESS) + return error; + + data.vec = &vec; + data.repo = repo; + error = git_reference_foreach(repo, GIT_REF_LISTALL, heads_cb, &data); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to list remote heads"); + + git_vector_sort(&vec); + array->len = vec.length; + array->heads = (git_remote_head **) vec.contents; + + return error; +} + +static int local_close(git_transport *GIT_UNUSED(transport)) +{ + /* Nothing to do */ + GIT_UNUSED_ARG(transport); return GIT_SUCCESS; } +static void local_free(git_transport *transport) +{ + assert(transport); + + git_repository_free(transport->private); + free(transport->url); + free(transport); +} + /************** * Public API * **************/ @@ -36,6 +173,8 @@ int git_transport_local(git_transport *transport) { transport->connect = local_connect; transport->ls = local_ls; + transport->close = local_close; + transport->free = local_free; return GIT_SUCCESS; }