summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/object.h10
-rw-r--r--include/git2/odb.h13
-rw-r--r--include/git2/sys/odb_backend.h10
-rw-r--r--src/object.c43
-rw-r--r--src/odb.c56
-rw-r--r--src/odb_loose.c33
-rw-r--r--src/odb_pack.c18
-rw-r--r--tests/object/shortid.c44
-rw-r--r--tests/odb/loose.c4
-rw-r--r--tests/odb/mixed.c19
10 files changed, 234 insertions, 16 deletions
diff --git a/include/git2/object.h b/include/git2/object.h
index c40631fa6..416feffbc 100644
--- a/include/git2/object.h
+++ b/include/git2/object.h
@@ -10,6 +10,7 @@
#include "common.h"
#include "types.h"
#include "oid.h"
+#include "buffer.h"
/**
* @file git2/object.h
@@ -104,6 +105,15 @@ GIT_EXTERN(int) git_object_lookup_bypath(
GIT_EXTERN(const git_oid *) git_object_id(const git_object *obj);
/**
+ * Get a short abbreviated OID string for the object
+ *
+ * @param out Buffer to write string into
+ * @param obj The object to get an ID for
+ * @return 0 on success, <0 for error
+ */
+GIT_EXTERN(int) git_object_short_id(git_buf *out, const git_object *obj);
+
+/**
* Get the object type of an object
*
* @param obj the repository object
diff --git a/include/git2/odb.h b/include/git2/odb.h
index 82df4d300..c71e30648 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 8039a5b82..4917ba0f0 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/object.c b/src/object.c
index 40cae184f..3847a0739 100644
--- a/src/object.c
+++ b/src/object.c
@@ -397,3 +397,46 @@ cleanup:
git_tree_free(tree);
return error;
}
+
+int git_object_short_id(git_buf *out, const git_object *obj)
+{
+ git_repository *repo;
+ int len = GIT_ABBREV_DEFAULT, error;
+ git_oid id = {{0}};
+ git_odb *odb;
+
+ assert(out && obj);
+
+ git_buf_sanitize(out);
+ repo = git_object_owner(obj);
+
+ if ((error = git_repository__cvar(&len, repo, GIT_CVAR_ABBREV)) < 0)
+ return error;
+
+ if ((error = git_repository_odb(&odb, repo)) < 0)
+ return error;
+
+ while (len < GIT_OID_HEXSZ) {
+ /* set up short oid */
+ memcpy(&id.id, &obj->cached.oid.id, (len + 1) / 2);
+ if (len & 1)
+ id.id[len / 2] &= 0xf0;
+
+ error = git_odb_exists_prefix(NULL, odb, &id, len);
+ if (error != GIT_EAMBIGUOUS)
+ break;
+
+ giterr_clear();
+ len++;
+ }
+
+ if (!error && !(error = git_buf_grow(out, len + 1))) {
+ git_oid_tostr(out->ptr, len + 1, &id);
+ out->size = len;
+ }
+
+ git_odb_free(odb);
+
+ return error;
+}
+
diff --git a/src/odb.c b/src/odb.c
index 139949e31..30bba9603 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;
@@ -745,7 +800,6 @@ int git_odb_read_prefix(
if (len < GIT_OID_MINPREFIXLEN)
return git_odb__error_ambiguous("prefix length too short");
-
if (len > GIT_OID_HEXSZ)
len = GIT_OID_HEXSZ;
diff --git a/src/odb_loose.c b/src/odb_loose.c
index fd4ffff1e..7b46a6652 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)
@@ -646,12 +647,9 @@ static int loose_backend__read_prefix(
{
int error = 0;
- assert(len <= GIT_OID_HEXSZ);
+ assert(len >= GIT_OID_MINPREFIXLEN && len <= GIT_OID_HEXSZ);
- if (len < GIT_OID_MINPREFIXLEN)
- error = git_odb__error_ambiguous("prefix length too short");
-
- else if (len == GIT_OID_HEXSZ) {
+ if (len == GIT_OID_HEXSZ) {
/* We can fall back to regular read method */
error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid);
if (!error)
@@ -691,6 +689,22 @@ 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 && len >= GIT_OID_MINPREFIXLEN);
+
+ 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 +953,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 903b00d26..9ab683882 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/object/shortid.c b/tests/object/shortid.c
new file mode 100644
index 000000000..fa1dac09a
--- /dev/null
+++ b/tests/object/shortid.c
@@ -0,0 +1,44 @@
+#include "clar_libgit2.h"
+
+git_repository *_repo;
+
+void test_object_shortid__initialize(void)
+{
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("duplicate.git")));
+}
+
+void test_object_shortid__cleanup(void)
+{
+ git_repository_free(_repo);
+ _repo = NULL;
+}
+
+void test_object_shortid__select(void)
+{
+ git_oid full;
+ git_object *obj;
+ git_buf shorty = {0};
+
+ git_oid_fromstr(&full, "ce013625030ba8dba906f756967f9e9ca394464a");
+ cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJ_ANY));
+ cl_git_pass(git_object_short_id(&shorty, obj));
+ cl_assert_equal_i(7, shorty.size);
+ cl_assert_equal_s("ce01362", shorty.ptr);
+ git_object_free(obj);
+
+ git_oid_fromstr(&full, "dea509d097ce692e167dfc6a48a7a280cc5e877e");
+ cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJ_ANY));
+ cl_git_pass(git_object_short_id(&shorty, obj));
+ cl_assert_equal_i(9, shorty.size);
+ cl_assert_equal_s("dea509d09", shorty.ptr);
+ git_object_free(obj);
+
+ git_oid_fromstr(&full, "dea509d0b3cb8ee0650f6ca210bc83f4678851ba");
+ cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJ_ANY));
+ cl_git_pass(git_object_short_id(&shorty, obj));
+ cl_assert_equal_i(9, shorty.size);
+ cl_assert_equal_s("dea509d0b", shorty.ptr);
+ git_object_free(obj);
+
+ git_buf_free(&shorty);
+}
diff --git a/tests/odb/loose.c b/tests/odb/loose.c
index a85f1430d..ef7136e36 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 51970ceec..ceba4ec81 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));