Commit f5753999e4cac020c2dd3a4669fe9ba14df93cb5

Russell Belfer 2014-03-04T15:34:23

Add exists_prefix to ODB backend and ODB API

diff --git a/include/git2/odb.h b/include/git2/odb.h
index 82df4d3..c71e306 100644
--- a/include/git2/odb.h
+++ b/include/git2/odb.h
@@ -159,6 +159,19 @@ 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.
+ *
+ * @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.
+ * @param short_id A prefix of the id of the object to read.
+ * @param len The length of the prefix.
+ * @return 0 if found, GIT_ENOTFOUND if not found, GIT_EAMBIGUOUS if multiple
+ *         matches were found, other value < 0 if there was a read error.
+ */
+GIT_EXTERN(int) git_odb_exists_prefix(
+	git_oid *out, git_odb *db, const git_oid *short_id, size_t len);
+
+/**
  * Refresh the object database to load newly added files.
  *
  * If the object databases have changed on disk while the library
diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h
index 8039a5b..4917ba0 100644
--- a/include/git2/sys/odb_backend.h
+++ b/include/git2/sys/odb_backend.h
@@ -35,11 +35,8 @@ struct git_odb_backend {
 	int (* read)(
 		void **, size_t *, git_otype *, git_odb_backend *, const git_oid *);
 
-	/* To find a unique object given a prefix
-	 * of its oid.
-	 * The oid given must be so that the
-	 * remaining (GIT_OID_HEXSZ - len)*4 bits
-	 * are 0s.
+	/* To find a unique object given a prefix of its oid.  The oid given
+	 * must be so that the remaining (GIT_OID_HEXSZ - len)*4 bits are 0s.
 	 */
 	int (* read_prefix)(
 		git_oid *, void **, size_t *, git_otype *,
@@ -64,6 +61,9 @@ struct git_odb_backend {
 	int (* exists)(
 		git_odb_backend *, const git_oid *);
 
+	int (* exists_prefix)(
+		git_oid *, git_odb_backend *, const git_oid *, size_t);
+
 	/**
 	 * If the backend implements a refreshing mechanism, it should be exposed
 	 * through this endpoint. Each call to `git_odb_refresh()` will invoke it.
diff --git a/src/odb.c b/src/odb.c
index b413f83..d49ee30 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -635,6 +635,61 @@ int git_odb_exists(git_odb *db, const git_oid *id)
 	return (int)found;
 }
 
+int git_odb_exists_prefix(
+	git_oid *out, git_odb *db, const git_oid *short_id, size_t len)
+{
+	int error = GIT_ENOTFOUND, num_found = 0;
+	size_t i;
+	git_oid last_found = {{0}}, found;
+
+	assert(db && short_id);
+
+	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 (git_odb_exists(db, short_id)) {
+			if (out)
+				git_oid_cpy(out, short_id);
+			return 0;
+		} else {
+			return git_odb__error_notfound("no match for id prefix", short_id);
+		}
+	}
+
+	for (i = 0; i < db->backends.length; ++i) {
+		backend_internal *internal = git_vector_get(&db->backends, i);
+		git_odb_backend *b = internal->backend;
+
+		if (!b->exists_prefix)
+			continue;
+
+		error = b->exists_prefix(&found, b, short_id, len);
+		if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH)
+			continue;
+		if (error)
+			return error;
+
+		/* make sure found item doesn't introduce ambiguity */
+		if (num_found) {
+			if (git_oid__cmp(&last_found, &found))
+				return git_odb__error_ambiguous("multiple matches for prefix");
+		} else {
+			git_oid_cpy(&last_found, &found);
+			num_found++;
+		}
+	}
+
+	if (!num_found)
+		return git_odb__error_notfound("no match for id prefix", short_id);
+	if (out)
+		git_oid_cpy(out, &last_found);
+
+	return error;
+}
+
 int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id)
 {
 	int error;
diff --git a/src/odb_loose.c b/src/odb_loose.c
index fd4ffff..e0b6ed1 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -519,11 +519,11 @@ static int locate_object_short_oid(
 	loose_locate_object_state state;
 	int error;
 
-	/* prealloc memory for OBJ_DIR/xx/ */
-	if (git_buf_grow(object_location, dir_len + 5) < 0)
+	/* prealloc memory for OBJ_DIR/xx/xx..38x..xx */
+	if (git_buf_grow(object_location, dir_len + 3 + GIT_OID_HEXSZ) < 0)
 		return -1;
 
-	git_buf_sets(object_location, objects_dir);
+	git_buf_set(object_location, objects_dir, dir_len);
 	git_path_to_dir(object_location);
 
 	/* save adjusted position at end of dir so it can be restored later */
@@ -533,8 +533,9 @@ static int locate_object_short_oid(
 	git_oid_fmt((char *)state.short_oid, short_oid);
 
 	/* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */
-	if (git_buf_printf(object_location, "%.2s/", state.short_oid) < 0)
+	if (git_buf_put(object_location, (char *)state.short_oid, 3) < 0)
 		return -1;
+	object_location->ptr[object_location->size - 1] = '/';
 
 	/* Check that directory exists */
 	if (git_path_isdir(object_location->ptr) == false)
@@ -691,6 +692,25 @@ static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid)
 	return !error;
 }
 
+static int loose_backend__exists_prefix(
+	git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len)
+{
+	git_buf object_path = GIT_BUF_INIT;
+	int error;
+
+	assert(backend && out && short_id);
+
+	if (len < GIT_OID_MINPREFIXLEN)
+		return git_odb__error_ambiguous("prefix length too short");
+
+	error = locate_object_short_oid(
+		&object_path, out, (loose_backend *)backend, short_id, len);
+
+	git_buf_free(&object_path);
+
+	return error;
+}
+
 struct foreach_state {
 	size_t dir_len;
 	git_odb_foreach_cb cb;
@@ -939,6 +959,7 @@ int git_odb_backend_loose(
 	backend->parent.read_header = &loose_backend__read_header;
 	backend->parent.writestream = &loose_backend__stream;
 	backend->parent.exists = &loose_backend__exists;
+	backend->parent.exists_prefix = &loose_backend__exists_prefix;
 	backend->parent.foreach = &loose_backend__foreach;
 	backend->parent.free = &loose_backend__free;
 
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 903b00d..9ab6838 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -493,6 +493,23 @@ static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid)
 	return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0;
 }
 
+static int pack_backend__exists_prefix(
+	git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len)
+{
+	int error;
+	struct pack_backend *pb = (struct pack_backend *)backend;
+	struct git_pack_entry e = {0};
+
+	error = pack_entry_find_prefix(&e, pb, short_id, len);
+
+	if (error == GIT_ENOTFOUND && !(error = pack_backend__refresh(backend)))
+		error = pack_entry_find_prefix(&e, pb, short_id, len);
+
+	git_oid_cpy(out, &e.sha1);
+
+	return error;
+}
+
 static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
 {
 	int error;
@@ -612,6 +629,7 @@ static int pack_backend__alloc(struct pack_backend **out, size_t initial_size)
 	backend->parent.read_prefix = &pack_backend__read_prefix;
 	backend->parent.read_header = &pack_backend__read_header;
 	backend->parent.exists = &pack_backend__exists;
+	backend->parent.exists_prefix = &pack_backend__exists_prefix;
 	backend->parent.refresh = &pack_backend__refresh;
 	backend->parent.foreach = &pack_backend__foreach;
 	backend->parent.writepack = &pack_backend__writepack;
diff --git a/tests/odb/loose.c b/tests/odb/loose.c
index a85f143..ef7136e 100644
--- a/tests/odb/loose.c
+++ b/tests/odb/loose.c
@@ -76,9 +76,13 @@ void test_odb_loose__exists(void)
 
     cl_assert(git_odb_exists(odb, &id));
 
+	cl_assert(git_odb_exists_prefix(&id2, odb, &id, 8));
+	cl_assert(git_oid_equal(&id, &id2));
+
 	/* Test for a non-existant object */
     cl_git_pass(git_oid_fromstr(&id2, "8b137891791fe96927ad78e64b0aad7bded08baa"));
     cl_assert(!git_odb_exists(odb, &id2));
+	cl_assert_equal_i(GIT_ENOTFOUND, git_odb_exists_prefix(NULL, odb, &id2, 8));
 
 	git_odb_free(odb);
 }
diff --git a/tests/odb/mixed.c b/tests/odb/mixed.c
index 51970ce..ceba4ec 100644
--- a/tests/odb/mixed.c
+++ b/tests/odb/mixed.c
@@ -23,9 +23,14 @@ void test_odb_mixed__dup_oid(void) {
 	cl_git_pass(git_oid_fromstr(&oid, hex));
 	cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ));
 	git_odb_object_free(obj);
+
+	cl_git_pass(git_odb_exists_prefix(NULL, _odb, &oid, GIT_OID_HEXSZ));
+
 	cl_git_pass(git_oid_fromstrn(&oid, short_hex, sizeof(short_hex) - 1));
 	cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, sizeof(short_hex) - 1));
 	git_odb_object_free(obj);
+
+	cl_git_pass(git_odb_exists_prefix(NULL, _odb, &oid, sizeof(short_hex) - 1));
 }
 
 /* some known sha collisions of file content:
@@ -37,7 +42,7 @@ void test_odb_mixed__dup_oid(void) {
 
 void test_odb_mixed__dup_oid_prefix_0(void) {
 	char hex[10];
-	git_oid oid;
+	git_oid oid, found;
 	git_odb_object *obj;
 
 	/* ambiguous in the same pack file */
@@ -46,10 +51,14 @@ void test_odb_mixed__dup_oid_prefix_0(void) {
 	cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
 	cl_assert_equal_i(
 		GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+	cl_assert_equal_i(
+		GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
 
 	strncpy(hex, "dea509d09", sizeof(hex));
 	cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
 	cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+	cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+	cl_assert(git_oid_equal(&found, git_odb_object_id(obj)));
 	git_odb_object_free(obj);
 
 	strncpy(hex, "dea509d0b", sizeof(hex));
@@ -63,10 +72,14 @@ void test_odb_mixed__dup_oid_prefix_0(void) {
 	cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
 	cl_assert_equal_i(
 		GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+	cl_assert_equal_i(
+		GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
 
 	strncpy(hex, "81b5bff5b", sizeof(hex));
 	cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
 	cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+	cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+	cl_assert(git_oid_equal(&found, git_odb_object_id(obj)));
 	git_odb_object_free(obj);
 
 	strncpy(hex, "81b5bff5f", sizeof(hex));
@@ -80,10 +93,14 @@ void test_odb_mixed__dup_oid_prefix_0(void) {
 	cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
 	cl_assert_equal_i(
 		GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+	cl_assert_equal_i(
+		GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
 
 	strncpy(hex, "0ddeaded9", sizeof(hex));
 	cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
 	cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+	cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+	cl_assert(git_oid_equal(&found, git_odb_object_id(obj)));
 	git_odb_object_free(obj);
 
 	strncpy(hex, "0ddeadede", sizeof(hex));