Commit e087973ec7c3c504696e2be4fad688aa09715d8d

Vicent Martí 2012-11-16T10:27:46

Merge pull request #1075 from carlosmn/alternates-recurse odb: recursively load alternates

diff --git a/src/odb.c b/src/odb.c
index bc135e3..9c602d1 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -23,6 +23,8 @@
 #define GIT_LOOSE_PRIORITY 2
 #define GIT_PACKED_PRIORITY 1
 
+#define GIT_ALTERNATES_MAX_DEPTH 5
+
 typedef struct
 {
 	git_odb_backend *backend;
@@ -30,6 +32,8 @@ typedef struct
 	int is_alternate;
 } backend_internal;
 
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
+
 static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
 {
 	const char *type_str = git_object_type2string(obj_type);
@@ -395,7 +399,7 @@ int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority)
 	return add_backend_internal(odb, backend, priority, 1);
 }
 
-static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates)
+static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates, int alternate_depth)
 {
 	git_odb_backend *loose, *packed;
 
@@ -409,10 +413,10 @@ static int add_default_backends(git_odb *db, const char *objects_dir, int as_alt
 		add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates) < 0)
 		return -1;
 
-	return 0;
+	return load_alternates(db, objects_dir, alternate_depth);
 }
 
-static int load_alternates(git_odb *odb, const char *objects_dir)
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth)
 {
 	git_buf alternates_path = GIT_BUF_INIT;
 	git_buf alternates_buf = GIT_BUF_INIT;
@@ -420,6 +424,11 @@ static int load_alternates(git_odb *odb, const char *objects_dir)
 	const char *alternate;
 	int result = 0;
 
+	/* Git reports an error, we just ignore anything deeper */
+	if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) {
+		return 0;
+	}
+
 	if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0)
 		return -1;
 
@@ -440,14 +449,18 @@ static int load_alternates(git_odb *odb, const char *objects_dir)
 		if (*alternate == '\0' || *alternate == '#')
 			continue;
 
-		/* relative path: build based on the current `objects` folder */
-		if (*alternate == '.') {
+		/*
+		 * Relative path: build based on the current `objects`
+		 * folder. However, relative paths are only allowed in
+		 * the current repository.
+		 */
+		if (*alternate == '.' && !alternate_depth) {
 			if ((result = git_buf_joinpath(&alternates_path, objects_dir, alternate)) < 0)
 				break;
 			alternate = git_buf_cstr(&alternates_path);
 		}
 
-		if ((result = add_default_backends(odb, alternate, 1)) < 0)
+		if ((result = add_default_backends(odb, alternate, 1, alternate_depth + 1)) < 0)
 			break;
 	}
 
@@ -468,8 +481,7 @@ int git_odb_open(git_odb **out, const char *objects_dir)
 	if (git_odb_new(&db) < 0)
 		return -1;
 
-	if (add_default_backends(db, objects_dir, 0) < 0 ||
-		load_alternates(db, objects_dir) < 0)
+	if (add_default_backends(db, objects_dir, 0, 0) < 0)
 	{
 		git_odb_free(db);
 		return -1;
diff --git a/tests-clar/odb/alternates.c b/tests-clar/odb/alternates.c
new file mode 100644
index 0000000..785d3bc
--- /dev/null
+++ b/tests-clar/odb/alternates.c
@@ -0,0 +1,75 @@
+#include "clar_libgit2.h"
+#include "odb.h"
+#include "repository.h"
+
+static git_buf destpath, filepath;
+static const char *paths[] = {
+	"A.git", "B.git", "C.git", "D.git", "E.git", "F.git", "G.git"
+};
+static 	git_filebuf file;
+static git_repository *repo;
+
+void test_odb_alternates__cleanup(void)
+{
+	git_buf_free(&destpath);
+	git_buf_free(&filepath);
+}
+
+static void init_linked_repo(const char *path, const char *alternate)
+{
+	git_buf_clear(&destpath);
+	git_buf_clear(&filepath);
+
+	cl_git_pass(git_repository_init(&repo, path, 1));
+	cl_git_pass(git_path_prettify(&destpath, alternate, NULL));
+	cl_git_pass(git_buf_joinpath(&destpath, destpath.ptr, "objects"));
+	cl_git_pass(git_buf_joinpath(&filepath, git_repository_path(repo), "objects/info"));
+	cl_git_pass(git_futils_mkdir(filepath.ptr, NULL, 0755, GIT_MKDIR_PATH));
+	cl_git_pass(git_buf_joinpath(&filepath, filepath.ptr , "alternates"));
+
+	cl_git_pass(git_filebuf_open(&file, git_buf_cstr(&filepath), 0));
+	git_filebuf_printf(&file, "%s\n", git_buf_cstr(&destpath));
+	cl_git_pass(git_filebuf_commit(&file, 0644));
+
+	git_repository_free(repo);
+}
+
+void test_odb_alternates__chained(void)
+{
+	git_commit *commit;
+	git_oid oid;
+
+	/* Set the alternate A -> testrepo.git */
+	init_linked_repo(paths[0], cl_fixture("testrepo.git"));
+
+	/* Set the alternate B -> A */
+	init_linked_repo(paths[1], paths[0]);
+
+	/* Now load B and see if we can find an object from testrepo.git */
+	cl_git_pass(git_repository_open(&repo, paths[1]));
+	git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+	cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+	git_commit_free(commit);
+	git_repository_free(repo);
+}
+
+void test_odb_alternates__long_chain(void)
+{
+	git_commit *commit;
+	git_oid oid;
+	size_t i;
+
+	/* Set the alternate A -> testrepo.git */
+	init_linked_repo(paths[0], cl_fixture("testrepo.git"));
+
+	/* Set up the five-element chain */
+	for (i = 1; i < ARRAY_SIZE(paths); i++) {
+		init_linked_repo(paths[i], paths[i-1]);
+	}
+
+	/* Now load the last one and see if we can find an object from testrepo.git */
+	cl_git_pass(git_repository_open(&repo, paths[ARRAY_SIZE(paths)-1]));
+	git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+	cl_git_fail(git_commit_lookup(&commit, repo, &oid));
+	git_repository_free(repo);
+}