mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-11 21:55:18 +00:00
256 lines
6.4 KiB
C
256 lines
6.4 KiB
C
/*
|
|
* 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 "common.h"
|
|
#include "repository.h"
|
|
#include "commit.h"
|
|
|
|
#define MAX_LOOPS 5
|
|
static const double max_load_factor = 0.65;
|
|
|
|
static int resize_to(git_hashtable *self, size_t new_size);
|
|
static int set_size(git_hashtable *self, size_t new_size);
|
|
static git_hashtable_node *node_with_hash(git_hashtable *self, const void *key, int hash_id);
|
|
static void node_swap_with(git_hashtable_node *self, git_hashtable_node *other);
|
|
static int node_insert(git_hashtable *self, git_hashtable_node *new_node);
|
|
static int insert_nodes(git_hashtable *self, git_hashtable_node *old_nodes, size_t old_size);
|
|
|
|
static int resize_to(git_hashtable *self, size_t new_size)
|
|
{
|
|
git_hashtable_node *old_nodes = self->nodes;
|
|
size_t old_size = self->size;
|
|
|
|
self->is_resizing = 1;
|
|
|
|
do {
|
|
self->size = new_size;
|
|
self->size_mask = new_size - 1;
|
|
self->key_count = 0;
|
|
self->nodes = git__calloc(1, sizeof(git_hashtable_node) * self->size);
|
|
|
|
if (self->nodes == NULL)
|
|
return GIT_ENOMEM;
|
|
|
|
if (insert_nodes(self, old_nodes, old_size) == 0)
|
|
self->is_resizing = 0;
|
|
else {
|
|
new_size *= 2;
|
|
free(self->nodes);
|
|
}
|
|
} while(self->is_resizing);
|
|
|
|
free(old_nodes);
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
static int set_size(git_hashtable *self, size_t new_size)
|
|
{
|
|
self->nodes = realloc(self->nodes, new_size * sizeof(git_hashtable_node));
|
|
if (self->nodes == NULL)
|
|
return GIT_ENOMEM;
|
|
|
|
if (new_size > self->size) {
|
|
memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(git_hashtable_node));
|
|
}
|
|
|
|
self->size = new_size;
|
|
self->size_mask = new_size - 1;
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
static git_hashtable_node *node_with_hash(git_hashtable *self, const void *key, int hash_id)
|
|
{
|
|
size_t pos = self->hash(key, hash_id) & self->size_mask;
|
|
return git_hashtable_node_at(self->nodes, pos);
|
|
}
|
|
|
|
static void node_swap_with(git_hashtable_node *self, git_hashtable_node *other)
|
|
{
|
|
git_hashtable_node tmp = *self;
|
|
*self = *other;
|
|
*other = tmp;
|
|
}
|
|
|
|
static int node_insert(git_hashtable *self, git_hashtable_node *new_node)
|
|
{
|
|
int iteration, hash_id;
|
|
|
|
for (iteration = 0; iteration < MAX_LOOPS; iteration++) {
|
|
for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) {
|
|
git_hashtable_node *node;
|
|
node = node_with_hash(self, new_node->key, hash_id);
|
|
node_swap_with(new_node, node);
|
|
if(new_node->key == 0x0){
|
|
self->key_count++;
|
|
return GIT_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->is_resizing)
|
|
return GIT_EBUSY;
|
|
|
|
resize_to(self, self->size * 2);
|
|
git_hashtable_insert(self, new_node->key, new_node->value);
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
static int insert_nodes(git_hashtable *self, git_hashtable_node *old_nodes, size_t old_size)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < old_size; ++i) {
|
|
git_hashtable_node *node = git_hashtable_node_at(old_nodes, i);
|
|
if (node->key && git_hashtable_insert(self, node->key, node->value) < GIT_SUCCESS)
|
|
return GIT_ENOMEM;
|
|
}
|
|
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
git_hashtable *git_hashtable_alloc(size_t min_size,
|
|
git_hash_ptr hash,
|
|
git_hash_keyeq_ptr key_eq)
|
|
{
|
|
git_hashtable *table;
|
|
|
|
assert(hash && key_eq);
|
|
|
|
if ((table = git__malloc(sizeof(git_hashtable))) == NULL)
|
|
return NULL;
|
|
|
|
memset(table, 0x0, sizeof(git_hashtable));
|
|
|
|
if (min_size < 8)
|
|
min_size = 8;
|
|
|
|
/* round up size to closest power of 2 */
|
|
min_size--;
|
|
min_size |= min_size >> 1;
|
|
min_size |= min_size >> 2;
|
|
min_size |= min_size >> 4;
|
|
min_size |= min_size >> 8;
|
|
min_size |= min_size >> 16;
|
|
|
|
table->hash = hash;
|
|
table->key_equal = key_eq;
|
|
|
|
set_size(table, min_size + 1);
|
|
|
|
return table;
|
|
}
|
|
|
|
void git_hashtable_clear(git_hashtable *self)
|
|
{
|
|
assert(self);
|
|
|
|
memset(self->nodes, 0x0, sizeof(git_hashtable_node) * self->size);
|
|
self->key_count = 0;
|
|
}
|
|
|
|
void git_hashtable_free(git_hashtable *self)
|
|
{
|
|
assert(self);
|
|
|
|
free(self->nodes);
|
|
free(self);
|
|
}
|
|
|
|
|
|
int git_hashtable_insert2(git_hashtable *self, const void *key, void *value, void **old_value)
|
|
{
|
|
int hash_id;
|
|
git_hashtable_node *node;
|
|
|
|
*old_value = NULL;
|
|
|
|
for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) {
|
|
node = node_with_hash(self, key, hash_id);
|
|
|
|
if (!node->key) {
|
|
node->key = key;
|
|
node->value = value;
|
|
self->key_count++;
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
if (key == node->key || self->key_equal(key, node->key) == 0) {
|
|
*old_value = node->value;
|
|
node->key = key;
|
|
node->value = value;
|
|
return GIT_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/* no space in table; must do cuckoo dance */
|
|
{
|
|
git_hashtable_node x;
|
|
x.key = key;
|
|
x.value = value;
|
|
return node_insert(self, &x);
|
|
}
|
|
}
|
|
|
|
void *git_hashtable_lookup(git_hashtable *self, const void *key)
|
|
{
|
|
int hash_id;
|
|
git_hashtable_node *node;
|
|
|
|
for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) {
|
|
node = node_with_hash(self, key, hash_id);
|
|
if (node->key && self->key_equal(key, node->key) == 0)
|
|
return node->value;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int git_hashtable_remove(git_hashtable *self, const void *key)
|
|
{
|
|
int hash_id;
|
|
git_hashtable_node *node;
|
|
|
|
for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) {
|
|
node = node_with_hash(self, key, hash_id);
|
|
if (node->key && self->key_equal(key, node->key) == 0) {
|
|
node->key = NULL;
|
|
node->value = NULL;
|
|
self->key_count--;
|
|
return GIT_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return GIT_ENOTFOUND;
|
|
}
|
|
|
|
int git_hashtable_merge(git_hashtable *self, git_hashtable *other)
|
|
{
|
|
if (resize_to(self, (self->size + other->size) * 2) < GIT_SUCCESS)
|
|
return GIT_ENOMEM;
|
|
|
|
return insert_nodes(self, other->nodes, other->key_count);
|
|
}
|
|
|