Commit dd453c4d by Marc Pegon Committed by Vicent Marti

Added git.git sha1 lookup method to replace simple binary search in pack backend.

Implemented find_unique_short_oid for pack backend, based on git sha1 lookup method;
finding an object given its full oid is just a particular case of searching
the unique object matching an oid prefix (short oid).

Added git_odb_read_unique_short_oid, which iterates over all the backends to
find and read the unique object matching the given oid prefix.

Added a git_object_lookup_short_oid method to find the unique object in
the repository matching a given oid prefix : it generalizes git_object_lookup
which now does nothing but calls git_object_lookup_short_oid.
parent 53c0bd81
...@@ -59,6 +59,36 @@ GIT_BEGIN_DECL ...@@ -59,6 +59,36 @@ GIT_BEGIN_DECL
GIT_EXTERN(int) git_object_lookup(git_object **object, git_repository *repo, const git_oid *id, git_otype type); GIT_EXTERN(int) git_object_lookup(git_object **object, git_repository *repo, const git_oid *id, git_otype type);
/** /**
* Lookup a reference to one of the objects in a repostory,
* given a prefix of its identifier (short id).
*
* The object obtained will be so that its identifier
* matches the first 'len' hexadecimal characters
* (packets of 4 bits) of the given 'id'.
* 'len' must be long enough to identify a unique
* object matching the prefix; otherwise the method will
* fail.
*
* The generated reference is owned by the repository and
* should be closed with the `git_object_close` method
* instead of free'd manually.
*
* The 'type' parameter must match the type of the object
* in the odb; the method will fail otherwise.
* The special value 'GIT_OBJ_ANY' may be passed to let
* the method guess the object's type.
*
* @param object pointer to the looked-up object
* @param repo the repository to look up the object
* @param id a short identifier for the object
* @param len the length of the short identifier
* @param type the type of the object
* @return a reference to the object
*/
GIT_EXTERN(int) git_object_lookup_short_oid(git_object **object_out, git_repository *repo,
const git_oid *id, unsigned int len, git_otype type);
/**
* Get the id (SHA1) of a repository object * Get the id (SHA1) of a repository object
* *
* @param obj the repository object * @param obj the repository object
......
...@@ -109,7 +109,7 @@ GIT_EXTERN(void) git_odb_close(git_odb *db); ...@@ -109,7 +109,7 @@ GIT_EXTERN(void) git_odb_close(git_odb *db);
/** /**
* Read an object from the database. * Read an object from the database.
* *
* This method queries all avaiable ODB backends * This method queries all available ODB backends
* trying to read the given OID. * trying to read the given OID.
* *
* The returned object is reference counted and * The returned object is reference counted and
...@@ -126,6 +126,36 @@ GIT_EXTERN(void) git_odb_close(git_odb *db); ...@@ -126,6 +126,36 @@ GIT_EXTERN(void) git_odb_close(git_odb *db);
GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id); GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id);
/** /**
* Read an object from the database, given a prefix
* of its identifier.
*
* This method queries all available ODB backends
* trying to match the 'len' first hexadecimal
* characters of the 'short_id'.
* The remaining bits (GIT_OID_HEXSZ-len)*4 bits of
* 'short_id' must be 0s.
* The prefix must be long enough to identify
* a unique object in all the backends; the
* method will fail otherwise.
*
* The returned object is reference counted and
* internally cached, so it should be closed
* by the user once it's no longer in use.
*
* @param out_oid the oid of the unique object matching
* the short id
* @param out pointer where to store the read object
* @param db database to search for the object in.
* @param short_id a prefix of the id of the object to read.
* @param len the length of the prefix
* @return
* - GIT_SUCCESS if the object was read;
* - GIT_ENOTFOUND if the object is not in the database.
* - GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix)
*/
GIT_EXTERN(int) git_odb_read_unique_short_oid(git_oid *out_oid, git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len);
/**
* Read the header of an object from the database, without * Read the header of an object from the database, without
* reading its full contents. * reading its full contents.
* *
......
...@@ -95,24 +95,63 @@ static int create_object(git_object **object_out, git_otype type) ...@@ -95,24 +95,63 @@ static int create_object(git_object **object_out, git_otype type)
return GIT_SUCCESS; return GIT_SUCCESS;
} }
int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) int git_object_lookup_short_oid(git_object **object_out, git_repository *repo, const git_oid *id, unsigned int len, git_otype type)
{ {
git_object *object = NULL; git_object *object = NULL;
git_odb_object *odb_obj; git_odb_object *odb_obj;
int error = GIT_SUCCESS; int error = GIT_SUCCESS;
git_oid out_oid;
assert(repo && object_out && id); assert(repo && object_out && id);
object = git_cache_get(&repo->objects, id); if (len == 0)
if (object != NULL) { return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to lookup object. Prefix length should be not be 0.");
if (type != GIT_OBJ_ANY && type != object->type) if (len > GIT_OID_HEXSZ) {
return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB"); len = GIT_OID_HEXSZ;
}
*object_out = object; if (len == GIT_OID_HEXSZ) {
return GIT_SUCCESS; /* We want to match the full id : we can first look up in the cache,
* since there is no need to check for non ambiguousity
*/
object = git_cache_get(&repo->objects, id);
if (object != NULL) {
if (type != GIT_OBJ_ANY && type != object->type)
return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB");
*object_out = object;
return GIT_SUCCESS;
}
/* Object was not found in the cache, let's explore the backends.
* We could just use git_odb_read_unique_short_oid,
* it is the same cost for packed and loose object backends,
* but it may be much more costly for sqlite and hiredis.
*/
error = git_odb_read(&odb_obj, repo->db, id);
git_oid_cpy(&out_oid, id);
} else {
git_oid short_oid;
/* We copy the first len*4 bits from id and fill the remaining with 0s */
memcpy(short_oid.id, id->id, (len + 1) / 2);
if (len % 2)
short_oid.id[len / 2] &= 0xF0;
memset(short_oid.id + (len + 1) / 2, 0, (GIT_OID_HEXSZ - len) / 2);
/* If len < GIT_OID_HEXSZ (a strict short oid was given), we have
* 2 options :
* - We always search in the cache first. If we find that short oid is
* ambiguous, we can stop. But in all the other cases, we must then
* explore all the backends (to find an object if there was match,
* or to check that oid is not ambiguous if we have found 1 match in
* the cache)
* - We never explore the cache, go right to exploring the backends
* We chose the latter : we explore directly the backends.
*/
error = git_odb_read_unique_short_oid(&out_oid, &odb_obj, repo->db, &short_oid, len);
} }
error = git_odb_read(&odb_obj, repo->db, id);
if (error < GIT_SUCCESS) if (error < GIT_SUCCESS)
return git__rethrow(error, "Failed to lookup object"); return git__rethrow(error, "Failed to lookup object");
...@@ -127,7 +166,7 @@ int git_object_lookup(git_object **object_out, git_repository *repo, const git_o ...@@ -127,7 +166,7 @@ int git_object_lookup(git_object **object_out, git_repository *repo, const git_o
return git__rethrow(error, "Failed to lookup object"); return git__rethrow(error, "Failed to lookup object");
/* Initialize parent object */ /* Initialize parent object */
git_oid_cpy(&object->cached.oid, id); git_oid_cpy(&object->cached.oid, &out_oid);
object->repo = repo; object->repo = repo;
switch (type) { switch (type) {
...@@ -162,6 +201,10 @@ int git_object_lookup(git_object **object_out, git_repository *repo, const git_o ...@@ -162,6 +201,10 @@ int git_object_lookup(git_object **object_out, git_repository *repo, const git_o
return GIT_SUCCESS; return GIT_SUCCESS;
} }
int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) {
return git_object_lookup_short_oid(object_out, repo, id, GIT_OID_HEXSZ, type);
}
void git_object__free(void *_obj) void git_object__free(void *_obj)
{ {
git_object *object = (git_object *)_obj; git_object *object = (git_object *)_obj;
......
...@@ -488,6 +488,48 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) ...@@ -488,6 +488,48 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
return error; return error;
} }
int git_odb_read_unique_short_oid(git_oid *out_oid, git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len)
{
unsigned int i;
int error = GIT_ENOTFOUND;
git_rawobj raw;
int found = 0;
assert(out && db && id && len > 0);
if (len > GIT_OID_HEXSZ)
len = GIT_OID_HEXSZ;
if (len == GIT_OID_HEXSZ) {
*out = git_cache_get(&db->cache, short_id);
if (*out != NULL) {
git_oid_cpy(out_oid, short_id);
return GIT_SUCCESS;
}
}
for (i = 0; i < db->backends.length && found < 2; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
if (b->read != NULL) {
error = b->read_unique_short_oid(out_oid, &raw.data, &raw.len, &raw.type, b, short_id, len);
if (error == GIT_SUCCESS)
found++;
}
}
if (found == 1) {
*out = git_cache_try_store(&db->cache, new_odb_object(out_oid, &raw));
} else if (found > 1) {
return git__rethrow(GIT_EAMBIGUOUSOIDPREFIX, "Ambiguous sha1");
} else {
return git__rethrow(GIT_ENOTFOUND, "Failed to read object");
}
return GIT_SUCCESS;
}
int git_odb_write(git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type) int git_odb_write(git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type)
{ {
unsigned int i; unsigned int i;
......
/*
* This file is basically taken from git code.
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
*
* In addition to the permissions in the GNU General Public License,
* the authors give you unlimited permission to link the compiled
* version of this file into combinations with other programs,
* and to distribute those combinations without any restriction
* coming from the use of this file. (The General Public License
* restrictions do apply in other respects; for example, they cover
* modification of the file, and distribution when not linked into
* a combined executable.)
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include "sha1_lookup.h"
#include "common.h"
/*
* Conventional binary search loop looks like this:
*
* unsigned lo, hi;
* do {
* unsigned mi = (lo + hi) / 2;
* int cmp = "entry pointed at by mi" minus "target";
* if (!cmp)
* return (mi is the wanted one)
* if (cmp > 0)
* hi = mi; "mi is larger than target"
* else
* lo = mi+1; "mi is smaller than target"
* } while (lo < hi);
*
* The invariants are:
*
* - When entering the loop, lo points at a slot that is never
* above the target (it could be at the target), hi points at a
* slot that is guaranteed to be above the target (it can never
* be at the target).
*
* - We find a point 'mi' between lo and hi (mi could be the same
* as lo, but never can be as same as hi), and check if it hits
* the target. There are three cases:
*
* - if it is a hit, we are happy.
*
* - if it is strictly higher than the target, we set it to hi,
* and repeat the search.
*
* - if it is strictly lower than the target, we update lo to
* one slot after it, because we allow lo to be at the target.
*
* If the loop exits, there is no matching entry.
*
* When choosing 'mi', we do not have to take the "middle" but
* anywhere in between lo and hi, as long as lo <= mi < hi is
* satisfied. When we somehow know that the distance between the
* target and lo is much shorter than the target and hi, we could
* pick mi that is much closer to lo than the midway.
*
* Now, we can take advantage of the fact that SHA-1 is a good hash
* function, and as long as there are enough entries in the table, we
* can expect uniform distribution. An entry that begins with for
* example "deadbeef..." is much likely to appear much later than in
* the midway of the table. It can reasonably be expected to be near
* 87% (222/256) from the top of the table.
*
* However, we do not want to pick "mi" too precisely. If the entry at
* the 87% in the above example turns out to be higher than the target
* we are looking for, we would end up narrowing the search space down
* only by 13%, instead of 50% we would get if we did a simple binary
* search. So we would want to hedge our bets by being less aggressive.
*
* The table at "table" holds at least "nr" entries of "elem_size"
* bytes each. Each entry has the SHA-1 key at "key_offset". The
* table is sorted by the SHA-1 key of the entries. The caller wants
* to find the entry with "key", and knows that the entry at "lo" is
* not higher than the entry it is looking for, and that the entry at
* "hi" is higher than the entry it is looking for.
*/
int sha1_entry_pos(const void *table,
size_t elem_size,
size_t key_offset,
unsigned lo, unsigned hi, unsigned nr,
const unsigned char *key)
{
const unsigned char *base = table;
const unsigned char *hi_key, *lo_key;
unsigned ofs_0;
if (!nr || lo >= hi)
return -1;
if (nr == hi)
hi_key = NULL;
else
hi_key = base + elem_size * hi + key_offset;
lo_key = base + elem_size * lo + key_offset;
ofs_0 = 0;
do {
int cmp;
unsigned ofs, mi, range;
unsigned lov, hiv, kyv;
const unsigned char *mi_key;
range = hi - lo;
if (hi_key) {
for (ofs = ofs_0; ofs < 20; ofs++)
if (lo_key[ofs] != hi_key[ofs])
break;
ofs_0 = ofs;
/*
* byte 0 thru (ofs-1) are the same between
* lo and hi; ofs is the first byte that is
* different.
*/
hiv = hi_key[ofs_0];
if (ofs_0 < 19)
hiv = (hiv << 8) | hi_key[ofs_0+1];
} else {
hiv = 256;
if (ofs_0 < 19)
hiv <<= 8;
}
lov = lo_key[ofs_0];
kyv = key[ofs_0];
if (ofs_0 < 19) {
lov = (lov << 8) | lo_key[ofs_0+1];
kyv = (kyv << 8) | key[ofs_0+1];
}
assert(lov < hiv);
if (kyv < lov)
return -1 - lo;
if (hiv < kyv)
return -1 - hi;
/*
* Even if we know the target is much closer to 'hi'
* than 'lo', if we pick too precisely and overshoot
* (e.g. when we know 'mi' is closer to 'hi' than to
* 'lo', pick 'mi' that is higher than the target), we
* end up narrowing the search space by a smaller
* amount (i.e. the distance between 'mi' and 'hi')
* than what we would have (i.e. about half of 'lo'
* and 'hi'). Hedge our bets to pick 'mi' less
* aggressively, i.e. make 'mi' a bit closer to the
* middle than we would otherwise pick.
*/
kyv = (kyv * 6 + lov + hiv) / 8;
if (lov < hiv - 1) {
if (kyv == lov)
kyv++;
else if (kyv == hiv)
kyv--;
}
mi = (range - 1) * (kyv - lov) / (hiv - lov) + lo;
#ifdef INDEX_DEBUG_LOOKUP
printf("lo %u hi %u rg %u mi %u ", lo, hi, range, mi);
printf("ofs %u lov %x, hiv %x, kyv %x\n",
ofs_0, lov, hiv, kyv);
#endif
if (!(lo <= mi && mi < hi)) {
return git__throw(GIT_ERROR, "Assertion failure. Binary search invariant is false");
}
mi_key = base + elem_size * mi + key_offset;
cmp = memcmp(mi_key + ofs_0, key + ofs_0, 20 - ofs_0);
if (!cmp)
return mi;
if (cmp > 0) {
hi = mi;
hi_key = mi_key;
} else {
lo = mi + 1;
lo_key = mi_key + elem_size;
}
} while (lo < hi);
return -lo-1;
}
#ifndef INCLUDE_sha1_lookup_h__
#define INCLUDE_sha1_lookup_h__
#include <stdlib.h>
int sha1_entry_pos(const void *table,
size_t elem_size,
size_t key_offset,
unsigned lo, unsigned hi, unsigned nr,
const unsigned char *key);
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment