Commit 005718280712634486a097427212e652b0e29f36

Vicent Marti 2011-03-12T16:04:46

Add new method `git_reference_listall` Lists all the references in a repository. Listing may be filtered by reference type. This should applease Lord Clem.

diff --git a/include/git2/common.h b/include/git2/common.h
index 34efe80..11a08f8 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -27,6 +27,7 @@
 
 #include "thread-utils.h"
 #include <time.h>
+#include <stdlib.h>
 
 #ifdef __cplusplus
 # define GIT_BEGIN_DECL  extern "C" {
@@ -158,6 +159,21 @@
 #define GIT_EINVALIDREFSTATE (GIT_ERROR - 21)
 
 GIT_BEGIN_DECL
+
+typedef struct {
+	char **strings;
+	size_t count;
+} git_strarray;
+
+GIT_INLINE(void) git_strarray_free(git_strarray *array)
+{
+	size_t i;
+	for (i = 0; i < array->count; ++i)
+		free(array->strings[i]);
+
+	free(array->strings);
+}
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/include/git2/refs.h b/include/git2/refs.h
index 1702d7e..4ffc5ce 100644
--- a/include/git2/refs.h
+++ b/include/git2/refs.h
@@ -218,6 +218,29 @@ GIT_EXTERN(int) git_reference_delete(git_reference *ref);
  */
 GIT_EXTERN(int) git_reference_packall(git_repository *repo);
 
+/**
+ * Fill a list with all the references that can be found
+ * in a repository.
+ *
+ * The listed references may be filtered by type, or using
+ * a bitwise OR of several types. Use the magic value
+ * `GIT_REF_LISTALL` to obtain all references, including
+ * packed ones.
+ *
+ * The string array will be filled with the names of all
+ * references; these values are owned by the user and
+ * should be free'd manually when no longer needed, using
+ * `git_strarray_free`.
+ *
+ * @param array Pointer to a git_strarray structure where
+ *		the reference names will be stored
+ * @param repo Repository where to find the refs
+ * @param list_flags Filtering flags for the reference
+ *		listing.
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/include/git2/types.h b/include/git2/types.h
index 62467ec..b5a8d7b 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -145,6 +145,7 @@ typedef enum {
 	GIT_REF_SYMBOLIC = 2, /** A reference which points at another reference */
 	GIT_REF_PACKED = 4,
 	GIT_REF_HAS_PEEL = 8,
+	GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC|GIT_REF_PACKED,
 } git_rtype;
 
 /** @} */
diff --git a/src/refs.c b/src/refs.c
index 2fc383e..93897a7 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -235,6 +235,24 @@ static int loose_read(gitfo_buf *file_content, const char *name, const char *rep
 	return error;
 }
 
+static git_rtype loose_guess_rtype(const char *full_path)
+{
+	gitfo_buf ref_file = GITFO_BUF_INIT;
+	git_rtype type;
+
+	type = GIT_REF_INVALID;
+
+	if (gitfo_read_file(&ref_file, full_path) == GIT_SUCCESS) {
+		if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0)
+			type = GIT_REF_SYMBOLIC;
+		else
+			type = GIT_REF_OID;
+	}
+
+	gitfo_free_buf(&ref_file);
+	return type;
+}
+
 static int loose_lookup(
 		git_reference **ref_out, 
 		git_repository *repo, 
@@ -531,6 +549,31 @@ cleanup:
 	return error;
 }
 
+
+
+
+struct dirent_list_data {
+	git_vector ref_list;
+	size_t repo_path_len;
+	unsigned int list_flags;
+};
+
+static int _dirent_loose_listall(void *_data, char *full_path)
+{
+	struct dirent_list_data *data = (struct dirent_list_data *)_data;
+	char *file_path;
+
+	if (gitfo_isdir(full_path) == GIT_SUCCESS)
+		return gitfo_dirent(full_path, GIT_PATH_MAX, _dirent_loose_listall, _data);
+
+	if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
+		return GIT_SUCCESS; /* we are filtering out this reference */
+
+	file_path = full_path + data->repo_path_len;
+
+	return git_vector_insert(&data->ref_list, git__strdup(file_path));
+}
+
 static int _dirent_loose_load(void *data, char *full_path)
 {
 	git_repository *repository = (git_repository *)data;
@@ -1292,6 +1335,45 @@ int git_reference_packall(git_repository *repo)
 	return packed_write(repo);
 }
 
+int git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags)
+{
+	int error;
+	struct dirent_list_data data;
+	char refs_path[GIT_PATH_MAX];
+
+	array->strings = NULL;
+	array->count = 0;
+
+	git_vector_init(&data.ref_list, 8, NULL);
+	data.repo_path_len = strlen(repo->path_repository);
+	data.list_flags = list_flags;
+
+	git__joinpath(refs_path, repo->path_repository, GIT_REFS_DIR);
+	error = gitfo_dirent(refs_path, GIT_PATH_MAX, _dirent_loose_listall, &data);
+
+	if (error < GIT_SUCCESS) {
+		git_vector_free(&data.ref_list);
+		return error;
+	}
+
+	if (list_flags & GIT_REF_PACKED) {
+		const char *ref_name;
+		void *_unused;
+
+		if ((error = packed_load(repo)) < GIT_SUCCESS) {
+			git_vector_free(&data.ref_list);
+			return error;
+		}
+
+		GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused,
+			git_vector_insert(&data.ref_list, git__strdup(ref_name));
+		);
+	}
+
+	array->strings = (char **)data.ref_list.contents;
+	array->count = data.ref_list.length;
+	return GIT_SUCCESS;
+}
 
 
 
diff --git a/tests/t10-refs.c b/tests/t10-refs.c
index abe3641..c70fb69 100644
--- a/tests/t10-refs.c
+++ b/tests/t10-refs.c
@@ -710,6 +710,18 @@ BEGIN_TEST(normalize2, "tests borrowed from JGit")
 	must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master@{1.hour.ago}", NULL));
 END_TEST
 
+BEGIN_TEST(list0, "try to list all the references in our test repo")
+	git_repository *repo;
+	git_strarray ref_list;
+
+	must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
+	must_pass(git_reference_listall(&ref_list, repo, GIT_REF_LISTALL));
+	must_be_true(ref_list.count == 8); /* 8 refs in total if we include the packed ones */
+
+	git_strarray_free(&ref_list);
+	git_repository_free(repo);
+END_TEST
+
 
 BEGIN_SUITE(refs)
 	ADD_TEST(readtag0);
@@ -741,4 +753,5 @@ BEGIN_SUITE(refs)
 	ADD_TEST(rename4);
 
 	ADD_TEST(delete0);
+	ADD_TEST(list0);
 END_SUITE