From f5753999e4cac020c2dd3a4669fe9ba14df93cb5 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 4 Mar 2014 15:34:23 -0800 Subject: [PATCH 1/3] Add exists_prefix to ODB backend and ODB API --- include/git2/odb.h | 13 ++++++++ include/git2/sys/odb_backend.h | 10 +++---- src/odb.c | 55 ++++++++++++++++++++++++++++++++++ src/odb_loose.c | 29 +++++++++++++++--- src/odb_pack.c | 18 +++++++++++ tests/odb/loose.c | 4 +++ tests/odb/mixed.c | 19 +++++++++++- 7 files changed, 138 insertions(+), 10 deletions(-) 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 @@ -158,6 +158,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. * 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/odb.c b/src/odb.c index b413f83b4..d49ee30fd 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 fd4ffff1e..e0b6ed1f3 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 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/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)); From 13f7ecd7b9c5244441eeaae798c8657d1818ea7f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 4 Mar 2014 16:23:28 -0800 Subject: [PATCH 2/3] Add git_object_short_id API to get short id string This finds a short id string that will unambiguously select the given object, starting with the core.abbrev length (usually 7) and growing until it is no longer ambiguous. --- include/git2/object.h | 10 ++++++++++ src/object.c | 43 +++++++++++++++++++++++++++++++++++++++++ tests/object/shortid.c | 44 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 tests/object/shortid.c 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 @@ -103,6 +104,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 * 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/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); +} From 26875825df19d484c24921e355963e75dc0a4476 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 5 Mar 2014 13:06:22 -0800 Subject: [PATCH 3/3] Check short OID len in odb, not in backends --- src/odb.c | 1 - src/odb_loose.c | 12 +++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/odb.c b/src/odb.c index d49ee30fd..6f550ddce 100644 --- a/src/odb.c +++ b/src/odb.c @@ -800,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 e0b6ed1f3..7b46a6652 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -647,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) @@ -698,10 +695,7 @@ static int loose_backend__exists_prefix( 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"); + assert(backend && out && short_id && len >= GIT_OID_MINPREFIXLEN); error = locate_object_short_oid( &object_path, out, (loose_backend *)backend, short_id, len);