libgit2/src/tree-cache.c
Carlos Martín Nieto bb0757d56c tree-cache: correct the entry_count calculation
The entry_count field is the amount of index entries covered by a
particular cache entry, that is how many files are there (recursively)
under a particular directory.

The current code that attemps to do this is severely defincient and is
trying to count the amount of children, which always comes up to zero.

We don't even need to recount, since we have the information during the
cache creation. We can take that number and keep it, as we only ever
invalidate or replace.
2014-10-22 21:25:08 +02:00

270 lines
6.1 KiB
C

/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "tree-cache.h"
#include "pool.h"
#include "tree.h"
static git_tree_cache *find_child(
const git_tree_cache *tree, const char *path, const char *end)
{
size_t i, dirlen = end ? (size_t)(end - path) : strlen(path);
for (i = 0; i < tree->children_count; ++i) {
git_tree_cache *child = tree->children[i];
if (child->namelen == dirlen && !memcmp(path, child->name, dirlen))
return child;
}
return NULL;
}
void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path)
{
const char *ptr = path, *end;
if (tree == NULL)
return;
tree->entry_count = -1;
while (ptr != NULL) {
end = strchr(ptr, '/');
if (end == NULL) /* End of path */
break;
tree = find_child(tree, ptr, end);
if (tree == NULL) /* We don't have that tree */
return;
tree->entry_count = -1;
ptr = end + 1;
}
}
const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path)
{
const char *ptr = path, *end;
if (tree == NULL) {
return NULL;
}
while (1) {
end = strchr(ptr, '/');
tree = find_child(tree, ptr, end);
if (tree == NULL) /* Can't find it */
return NULL;
if (end == NULL || *end + 1 == '\0')
return tree;
ptr = end + 1;
}
}
static int read_tree_internal(git_tree_cache **out,
const char **buffer_in, const char *buffer_end,
git_pool *pool)
{
git_tree_cache *tree = NULL;
const char *name_start, *buffer;
int count;
buffer = name_start = *buffer_in;
if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL)
goto corrupted;
if (++buffer >= buffer_end)
goto corrupted;
if (git_tree_cache_new(&tree, name_start, pool) < 0)
return -1;
/* Blank-terminated ASCII decimal number of entries in this tree */
if (git__strtol32(&count, buffer, &buffer, 10) < 0)
goto corrupted;
tree->entry_count = count;
if (*buffer != ' ' || ++buffer >= buffer_end)
goto corrupted;
/* Number of children of the tree, newline-terminated */
if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < 0)
goto corrupted;
tree->children_count = count;
if (*buffer != '\n' || ++buffer > buffer_end)
goto corrupted;
/* The SHA1 is only there if it's not invalidated */
if (tree->entry_count >= 0) {
/* 160-bit SHA-1 for this tree and it's children */
if (buffer + GIT_OID_RAWSZ > buffer_end)
goto corrupted;
git_oid_fromraw(&tree->oid, (const unsigned char *)buffer);
buffer += GIT_OID_RAWSZ;
}
/* Parse children: */
if (tree->children_count > 0) {
unsigned int i;
tree->children = git_pool_malloc(pool, tree->children_count * sizeof(git_tree_cache *));
GITERR_CHECK_ALLOC(tree->children);
memset(tree->children, 0x0, tree->children_count * sizeof(git_tree_cache *));
for (i = 0; i < tree->children_count; ++i) {
if (read_tree_internal(&tree->children[i], &buffer, buffer_end, pool) < 0)
goto corrupted;
}
}
*buffer_in = buffer;
*out = tree;
return 0;
corrupted:
giterr_set(GITERR_INDEX, "Corrupted TREE extension in index");
return -1;
}
int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool)
{
const char *buffer_end = buffer + buffer_size;
if (read_tree_internal(tree, &buffer, buffer_end, pool) < 0)
return -1;
if (buffer < buffer_end) {
giterr_set(GITERR_INDEX, "Corrupted TREE extension in index (unexpected trailing data)");
return -1;
}
return 0;
}
static int read_tree_recursive(git_tree_cache *cache, const git_tree *tree, git_pool *pool)
{
git_repository *repo;
size_t i, j, nentries, ntrees;
int error;
repo = git_tree_owner(tree);
git_oid_cpy(&cache->oid, git_tree_id(tree));
nentries = git_tree_entrycount(tree);
/*
* We make sure we know how many trees we need to allocate for
* so we don't have to realloc and change the pointers for the
* parents.
*/
ntrees = 0;
for (i = 0; i < nentries; i++) {
const git_tree_entry *entry;
entry = git_tree_entry_byindex(tree, i);
if (git_tree_entry_filemode(entry) == GIT_FILEMODE_TREE)
ntrees++;
}
cache->children_count = ntrees;
cache->children = git_pool_mallocz(pool, ntrees * sizeof(git_tree_cache *));
GITERR_CHECK_ALLOC(cache->children);
j = 0;
for (i = 0; i < nentries; i++) {
const git_tree_entry *entry;
git_tree *subtree;
entry = git_tree_entry_byindex(tree, i);
if (git_tree_entry_filemode(entry) != GIT_FILEMODE_TREE) {
cache->entry_count++;
continue;
}
if ((error = git_tree_cache_new(&cache->children[j], git_tree_entry_name(entry), pool)) < 0)
return error;
if ((error = git_tree_lookup(&subtree, repo, git_tree_entry_id(entry))) < 0)
return error;
error = read_tree_recursive(cache->children[j], subtree, pool);
git_tree_free(subtree);
cache->entry_count += cache->children[j]->entry_count;
j++;
if (error < 0)
return error;
}
return 0;
}
int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool)
{
int error;
git_tree_cache *cache;
if ((error = git_tree_cache_new(&cache, "", pool)) < 0)
return error;
if ((error = read_tree_recursive(cache, tree, pool)) < 0)
return error;
*out = cache;
return 0;
}
int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool)
{
size_t name_len;
git_tree_cache *tree;
name_len = strlen(name);
tree = git_pool_malloc(pool, sizeof(git_tree_cache) + name_len + 1);
GITERR_CHECK_ALLOC(tree);
memset(tree, 0x0, sizeof(git_tree_cache));
/* NUL-terminated tree name */
tree->namelen = name_len;
memcpy(tree->name, name, name_len);
tree->name[name_len] = '\0';
*out = tree;
return 0;
}
static void write_tree(git_buf *out, git_tree_cache *tree)
{
size_t i;
git_buf_printf(out, "%s%c%"PRIdZ" %"PRIuZ"\n", tree->name, 0, tree->entry_count, tree->children_count);
if (tree->entry_count != -1)
git_buf_put(out, (const char *) &tree->oid, GIT_OID_RAWSZ);
for (i = 0; i < tree->children_count; i++)
write_tree(out, tree->children[i]);
}
int git_tree_cache_write(git_buf *out, git_tree_cache *tree)
{
write_tree(out, tree);
return git_buf_oom(out) ? -1 : 0;
}