Commit 6c04269c8f558c109b0cd4524feb9d95bbbb3f6b

Edward Thomson 2016-03-04T00:50:35

git_odb_exists_many_prefixes: query odb for multiple short ids Query the object database for multiple objects at a time, given their object ID (which may be abbreviated) and optional type.

diff --git a/include/git2/odb.h b/include/git2/odb.h
index 4f1e18b..2b542b5 100644
--- a/include/git2/odb.h
+++ b/include/git2/odb.h
@@ -10,6 +10,7 @@
 #include "common.h"
 #include "types.h"
 #include "oid.h"
+#include "oidarray.h"
 
 /**
  * @file git2/odb.h
@@ -159,7 +160,8 @@ GIT_EXTERN(int) git_odb_read_header(size_t *len_out, git_otype *type_out, git_od
 GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id);
 
 /**
- * Determine if objects can be found in the object database from a short OID.
+ * Determine if an object can be found in the object database by an
+ * abbreviated object ID.
  *
  * @param out The full OID of the found object if just one is found.
  * @param db The database to be searched for the given object.
@@ -172,6 +174,34 @@ GIT_EXTERN(int) git_odb_exists_prefix(
 	git_oid *out, git_odb *db, const git_oid *short_id, size_t len);
 
 /**
+ * Determine if one or more objects can be found in the object database
+ * by their abbreviated object IDs.  Callers may further restrict the
+ * lookup based on type.  This function will write the complete object
+ * ID to the `id`s array, and the updated length to the `id_lengths`
+ * array.  (If an object is found, it will have its length updated to
+ * `GIT_OID_HEXSZ`; if an object is not found, will be be `0`.)
+ *
+ * Note that since this function operates on multiple objects, the
+ * underlying database will not be asked to be reloaded if an object is
+ * not found (which is unlike other object database operations.)
+ *
+ * @param db The database to be searched for the given objects.
+ * @param ids An array of object IDs to search for
+ * @param id_lengths The corresponding length of each entry in the `ids`
+ *                   array
+ * @param types The corresponding type of each entry in the `ids` array
+ *              (or null to lookup an object of any type)
+ * @param cnt The length of the `ids`, `id_lengths` and `types` arrays
+ * @return 0 on success or an error code on failure
+ */
+GIT_EXTERN(int) git_odb_exists_many_prefixes(
+	git_odb *db,
+	git_oid *ids,
+	size_t *id_lengths,
+	git_otype *types,
+	size_t cnt);
+
+/**
  * Refresh the object database to load newly added files.
  *
  * If the object databases have changed on disk while the library
diff --git a/src/odb.c b/src/odb.c
index cb0f706..e619585 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -18,6 +18,7 @@
 
 #include "git2/odb_backend.h"
 #include "git2/oid.h"
+#include "git2/oidarray.h"
 
 #define GIT_ALTERNATES_FILE "info/alternates"
 
@@ -651,7 +652,7 @@ int git_odb_exists(git_odb *db, const git_oid *id)
 
 	if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
 		git_odb_object_free(object);
-		return (int)true;
+		return 1;
 	}
 
 	if (odb_exists_1(db, id, false))
@@ -716,10 +717,8 @@ int git_odb_exists_prefix(
 
 	if (len < GIT_OID_MINPREFIXLEN)
 		return git_odb__error_ambiguous("prefix length too short");
-	if (len > GIT_OID_HEXSZ)
-		len = GIT_OID_HEXSZ;
 
-	if (len == GIT_OID_HEXSZ) {
+	if (len >= GIT_OID_HEXSZ) {
 		if (git_odb_exists(db, short_id)) {
 			if (out)
 				git_oid_cpy(out, short_id);
@@ -730,10 +729,7 @@ int git_odb_exists_prefix(
 		}
 	}
 
-	/* just copy valid part of short_id */
-	memcpy(&key.id, short_id->id, (len + 1) / 2);
-	if (len & 1)
-		key.id[len / 2] &= 0xF0;
+	git_oid__cpy_prefix(&key, short_id, len);
 
 	error = odb_exists_prefix_1(out, db, &key, len, false);
 
@@ -746,6 +742,69 @@ int git_odb_exists_prefix(
 	return error;
 }
 
+int git_odb_exists_many_prefixes(
+	git_odb *db,
+	git_oid *ids,
+	size_t *id_lengths,
+	git_otype *types,
+	size_t cnt)
+{
+	size_t len, i;
+	int error;
+
+	assert(db && ids && id_lengths);
+
+	for (i = 0; i < cnt; i++) {
+		git_oid *actual_id = NULL, tmp;
+		git_otype actual_type = 0;
+
+		/* if we were given a full object ID, simply look it up */
+		if (id_lengths[i] >= GIT_OID_HEXSZ) {
+			error = git_odb_read_header(&len, &actual_type, db, &ids[i]);
+		}
+
+		/* otherwise, resolve the short id to full, then (optionally)
+		 * read the header.
+		 */
+		else if (id_lengths[i] >= GIT_OID_MINPREFIXLEN) {
+	    	error = odb_exists_prefix_1(&tmp,
+				db, &ids[i], id_lengths[i], false);
+
+			if (!error) {
+				actual_id = &tmp;
+
+				if (types && types[i] != GIT_OBJ_ANY)
+					error = git_odb_read_header(&len, &actual_type, db, &tmp);
+				else
+					actual_type = GIT_OBJ_ANY;
+			}
+		}
+
+		if (error < 0 && error != GIT_ENOTFOUND && error != GIT_EAMBIGUOUS)
+			break;
+
+		error = 0;
+
+		if (types && types[i] != GIT_OBJ_ANY && types[i] != actual_type)
+			actual_type = 0;
+
+		if (!actual_type) {
+			id_lengths[i] = 0;
+			memset(&ids[i], 0, sizeof(git_oid));
+		} else {
+			id_lengths[i] = GIT_OID_HEXSZ;
+
+			if (actual_id)
+				git_oid_cpy(&ids[i], actual_id);
+		}
+	}
+
+	if (!error)
+		giterr_clear();
+
+	return error;
+}
+
 int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id)
 {
 	int error;
@@ -957,10 +1016,7 @@ int git_odb_read_prefix(
 			return 0;
 	}
 
-	/* just copy valid part of short_id */
-	memcpy(&key.id, short_id->id, (len + 1) / 2);
-	if (len & 1)
-		key.id[len / 2] &= 0xF0;
+	git_oid__cpy_prefix(&key, short_id, len);
 
 	error = read_prefix_1(out, db, &key, len, false);
 
diff --git a/src/oid.h b/src/oid.h
index aa1f0bf..922a2a3 100644
--- a/src/oid.h
+++ b/src/oid.h
@@ -44,4 +44,13 @@ GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b)
 	return git_oid__hashcmp(a->id, b->id);
 }
 
+GIT_INLINE(void) git_oid__cpy_prefix(
+	git_oid *out, const git_oid *id, size_t len)
+{
+	memcpy(&out->id, id->id, (len + 1) / 2);
+
+	if (len & 1)
+		out->id[len / 2] &= 0xF0;
+}
+
 #endif
diff --git a/tests/odb/mixed.c b/tests/odb/mixed.c
index 2dad4b6..fe22f85 100644
--- a/tests/odb/mixed.c
+++ b/tests/odb/mixed.c
@@ -108,3 +108,144 @@ void test_odb_mixed__dup_oid_prefix_0(void) {
 	cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
 	git_odb_object_free(obj);
 }
+
+struct odb_test_data {
+	char *lookup_id;
+	char *expected_id;
+	git_otype expected_type;
+};
+
+struct odb_test_data prefix_data[] = {
+	/* some prefixes and their expected values */
+	{ "dea509d0",  NULL, GIT_OBJ_ANY },
+	{ "00000000",  NULL, GIT_OBJ_ANY },
+	{ "dea509d0",  NULL, GIT_OBJ_ANY },
+	{ "dea509d09", "dea509d097ce692e167dfc6a48a7a280cc5e877e", GIT_OBJ_BLOB },
+	{ "dea509d0b", "dea509d0b3cb8ee0650f6ca210bc83f4678851ba", GIT_OBJ_BLOB },
+	{ "ce0136250", "ce013625030ba8dba906f756967f9e9ca394464a", GIT_OBJ_BLOB },
+	{ "0ddeaded",  NULL, GIT_OBJ_ANY },
+	{ "4d5979b",   "4d5979b468252190cb572ae758aca36928e8a91e", GIT_OBJ_TREE },
+	{ "0ddeaded",  NULL, GIT_OBJ_ANY },
+	{ "0ddeadede", "0ddeadede9e6d6ccddce0ee1e5749eed0485e5ea", GIT_OBJ_BLOB },
+	{ "0ddeaded9", "0ddeaded9502971eefe1e41e34d0e536853ae20f", GIT_OBJ_BLOB },
+	{ "f00b4e",    NULL, GIT_OBJ_ANY },
+
+	/* some full-length object ids */
+	{ "0000000000000000000000000000000000000000", NULL, GIT_OBJ_ANY },
+	{
+	  "dea509d097ce692e167dfc6a48a7a280cc5e877e",
+	  "dea509d097ce692e167dfc6a48a7a280cc5e877e",
+	  GIT_OBJ_BLOB
+	},
+	{ "f00f00f00f00f00f00f00f00f00f00f00f00f00f", NULL, GIT_OBJ_ANY },
+	{
+	  "4d5979b468252190cb572ae758aca36928e8a91e",
+	  "4d5979b468252190cb572ae758aca36928e8a91e",
+	  GIT_OBJ_TREE
+	},
+};
+
+static void setup_prefix_query(
+	git_oid **out_ids,
+	size_t **out_lengths,
+	git_otype **out_types,
+	size_t *out_num)
+{
+	git_oid *ids;
+	git_otype *types;
+	size_t num, *lengths, i;
+
+	num = ARRAY_SIZE(prefix_data);
+
+	cl_assert((ids = git__calloc(num, sizeof(git_oid))));
+	cl_assert((lengths = git__calloc(num, sizeof(size_t))));
+	cl_assert((types = git__calloc(num, sizeof(git_otype))));
+
+	for (i = 0; i < num; i++) {
+		lengths[i] = strlen(prefix_data[i].lookup_id);
+		git_oid_fromstrn(&ids[i], prefix_data[i].lookup_id, lengths[i]);
+		types[i] = prefix_data[i].expected_type;
+	}
+
+	*out_ids = ids;
+	*out_lengths = lengths;
+	*out_types = types;
+	*out_num = num;
+}
+
+static void assert_found_objects(git_oid *ids, size_t *lengths)
+{
+	size_t num, i;
+
+	num = ARRAY_SIZE(prefix_data);
+
+	for (i = 0; i < num; i++) {
+		git_oid expected_id = {{0}};
+		size_t expected_len = 0;
+
+		if (prefix_data[i].expected_id) {
+			git_oid_fromstr(&expected_id, prefix_data[i].expected_id);
+			expected_len = GIT_OID_HEXSZ;
+		}
+
+		cl_assert_equal_i(expected_len, lengths[i]);
+		cl_assert_equal_oid(&expected_id, &ids[i]);
+	}
+}
+
+static void assert_notfound_objects(git_oid *ids, size_t *lengths)
+{
+	git_oid expected_id = {{0}};
+	size_t num, i;
+
+	num = ARRAY_SIZE(prefix_data);
+
+	for (i = 0; i < num; i++) {
+		cl_assert_equal_i(0, lengths[i]);
+		cl_assert_equal_oid(&expected_id, &ids[i]);
+	}
+}
+
+void test_odb_mixed__prefix_many(void)
+{
+	git_oid *ids;
+	size_t i, num, *lengths;
+	git_otype *types;
+
+	/* test looking for the actual (correct) types */
+
+	setup_prefix_query(&ids, &lengths, &types, &num);
+	cl_git_pass(git_odb_exists_many_prefixes(_odb, ids, lengths, types, num));
+	assert_found_objects(ids, lengths);
+	git__free(ids); git__free(lengths); git__free(types);
+
+	/* test looking for no specified types (types array == NULL) */
+
+	setup_prefix_query(&ids, &lengths, &types, &num);
+	cl_git_pass(git_odb_exists_many_prefixes(_odb, ids, lengths, NULL, num));
+	assert_found_objects(ids, lengths);
+	git__free(ids); git__free(lengths); git__free(types);
+
+	/* test looking for an explicit GIT_OBJ_ANY */
+
+	setup_prefix_query(&ids, &lengths, &types, &num);
+
+	for (i = 0; i < num; i++)
+		types[i] = GIT_OBJ_ANY;
+
+	cl_git_pass(git_odb_exists_many_prefixes(_odb, ids, lengths, types, num));
+	assert_found_objects(ids, lengths);
+	git__free(ids); git__free(lengths); git__free(types);
+
+	/* test looking for the completely wrong type */
+
+	setup_prefix_query(&ids, &lengths, &types, &num);
+
+	for (i = 0; i < num; i++)
+		types[i] = (types[i] == GIT_OBJ_BLOB) ? GIT_OBJ_TREE : GIT_OBJ_BLOB;
+
+	cl_git_pass(git_odb_exists_many_prefixes(_odb, ids, lengths, types, num));
+	assert_notfound_objects(ids, lengths);
+	git__free(ids); git__free(lengths); git__free(types);
+}
+