Commit d6258debbe05e3892466722f897ae932e8e7d8aa

Carlos Martín Nieto 2011-06-25T15:10:09

Implement ls-remote on local drive Signed-off-by: Carlos Martín Nieto <carlos@cmartin.tk>

diff --git a/include/git2.h b/include/git2.h
index 6ede73c..f94b535 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 869309f..67e8a44 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 dfbc1a8..a12b11b 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 69aa289..8e06591 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 c083459..2ef99dc 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 8585b00..d70caaa 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 f492a87..2cc78ca 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;
 }