in src/pack.c [616:763]
int git_packfile_unpack(
git_rawobj *obj,
struct git_pack_file *p,
off64_t *obj_offset)
{
git_mwindow *w_curs = NULL;
off64_t curpos = *obj_offset;
int error, free_base = 0;
git_dependency_chain chain = GIT_ARRAY_INIT;
struct pack_chain_elem *elem = NULL, *stack;
git_pack_cache_entry *cached = NULL;
struct pack_chain_elem small_stack[SMALL_STACK_SIZE];
size_t stack_size = 0, elem_pos, alloclen;
git_object_t base_type;
/*
* TODO: optionally check the CRC on the packfile
*/
error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset);
if (error < 0)
return error;
obj->data = NULL;
obj->len = 0;
obj->type = GIT_OBJECT_INVALID;
/* let's point to the right stack */
stack = chain.ptr ? chain.ptr : small_stack;
elem_pos = stack_size;
if (cached) {
memcpy(obj, &cached->raw, sizeof(git_rawobj));
base_type = obj->type;
elem_pos--; /* stack_size includes the base, which isn't actually there */
} else {
elem = &stack[--elem_pos];
base_type = elem->type;
}
switch (base_type) {
case GIT_OBJECT_COMMIT:
case GIT_OBJECT_TREE:
case GIT_OBJECT_BLOB:
case GIT_OBJECT_TAG:
if (!cached) {
curpos = elem->offset;
error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type);
git_mwindow_close(&w_curs);
base_type = elem->type;
}
if (error < 0)
goto cleanup;
break;
case GIT_OBJECT_OFS_DELTA:
case GIT_OBJECT_REF_DELTA:
error = packfile_error("dependency chain ends in a delta");
goto cleanup;
default:
error = packfile_error("invalid packfile type in header");
goto cleanup;
}
/*
* Finding the object we want a cached base element is
* problematic, as we need to make sure we don't accidentally
* give the caller the cached object, which it would then feel
* free to free, so we need to copy the data.
*/
if (cached && stack_size == 1) {
void *data = obj->data;
GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, obj->len, 1);
obj->data = git__malloc(alloclen);
GIT_ERROR_CHECK_ALLOC(obj->data);
memcpy(obj->data, data, obj->len + 1);
git_atomic_dec(&cached->refcount);
goto cleanup;
}
/* we now apply each consecutive delta until we run out */
while (elem_pos > 0 && !error) {
git_rawobj base, delta;
/*
* We can now try to add the base to the cache, as
* long as it's not already the cached one.
*/
if (!cached)
free_base = !!cache_add(&cached, &p->bases, obj, elem->base_key);
elem = &stack[elem_pos - 1];
curpos = elem->offset;
error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type);
git_mwindow_close(&w_curs);
if (error < 0) {
/* We have transferred ownership of the data to the cache. */
obj->data = NULL;
break;
}
/* the current object becomes the new base, on which we apply the delta */
base = *obj;
obj->data = NULL;
obj->len = 0;
obj->type = GIT_OBJECT_INVALID;
error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len);
obj->type = base_type;
/*
* We usually don't want to free the base at this
* point, as we put it into the cache in the previous
* iteration. free_base lets us know that we got the
* base object directly from the packfile, so we can free it.
*/
git__free(delta.data);
if (free_base) {
free_base = 0;
git__free(base.data);
}
if (cached) {
git_atomic_dec(&cached->refcount);
cached = NULL;
}
if (error < 0)
break;
elem_pos--;
}
cleanup:
if (error < 0) {
git__free(obj->data);
if (cached)
git_atomic_dec(&cached->refcount);
}
if (elem)
*obj_offset = curpos;
git_array_clear(chain);
return error;
}