mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-04 20:45:07 +00:00
Attribute file cache refactor
This is a big refactoring of the attribute file cache to be a bit simpler which in turn makes it easier to enforce a lock around any updates to the cache so that it can be used in a threaded env. Tons of changes to the attributes and ignores code.
This commit is contained in:
parent
1fa17b5c92
commit
7d4908724f
478
src/attr.c
478
src/attr.c
@ -2,7 +2,7 @@
|
||||
#include "repository.h"
|
||||
#include "sysdir.h"
|
||||
#include "config.h"
|
||||
#include "attr.h"
|
||||
#include "attr_file.h"
|
||||
#include "ignore.h"
|
||||
#include "git2/oid.h"
|
||||
#include <ctype.h>
|
||||
@ -216,7 +216,6 @@ cleanup:
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
int git_attr_add_macro(
|
||||
git_repository *repo,
|
||||
const char *name,
|
||||
@ -251,261 +250,6 @@ int git_attr_add_macro(
|
||||
return error;
|
||||
}
|
||||
|
||||
bool git_attr_cache__is_cached(
|
||||
git_repository *repo, git_attr_file_source source, const char *path)
|
||||
{
|
||||
git_buf cache_key = GIT_BUF_INIT;
|
||||
git_strmap *files = git_repository_attr_cache(repo)->files;
|
||||
const char *workdir = git_repository_workdir(repo);
|
||||
bool rval;
|
||||
|
||||
if (workdir && git__prefixcmp(path, workdir) == 0)
|
||||
path += strlen(workdir);
|
||||
if (git_buf_printf(&cache_key, "%d#%s", (int)source, path) < 0)
|
||||
return false;
|
||||
|
||||
rval = git_strmap_exists(files, git_buf_cstr(&cache_key));
|
||||
|
||||
git_buf_free(&cache_key);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
static int load_attr_file(
|
||||
const char **data,
|
||||
git_futils_filestamp *stamp,
|
||||
const char *filename)
|
||||
{
|
||||
int error;
|
||||
git_buf content = GIT_BUF_INIT;
|
||||
|
||||
error = git_futils_filestamp_check(stamp, filename);
|
||||
if (error < 0)
|
||||
return error;
|
||||
|
||||
/* if error == 0, then file is up to date. By returning GIT_ENOTFOUND,
|
||||
* we tell the caller not to reparse this file...
|
||||
*/
|
||||
if (!error)
|
||||
return GIT_ENOTFOUND;
|
||||
|
||||
error = git_futils_readbuffer(&content, filename);
|
||||
if (error < 0) {
|
||||
/* convert error into ENOTFOUND so failed permissions / invalid
|
||||
* file type don't actually stop the operation in progress.
|
||||
*/
|
||||
return GIT_ENOTFOUND;
|
||||
|
||||
/* TODO: once warnings are available, issue a warning callback */
|
||||
}
|
||||
|
||||
*data = git_buf_detach(&content);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_attr_blob_from_index(
|
||||
const char **content,
|
||||
git_blob **blob,
|
||||
git_repository *repo,
|
||||
const git_oid *old_oid,
|
||||
const char *relfile)
|
||||
{
|
||||
int error;
|
||||
size_t pos;
|
||||
git_index *index;
|
||||
const git_index_entry *entry;
|
||||
|
||||
if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
|
||||
(error = git_index_find(&pos, index, relfile)) < 0)
|
||||
return error;
|
||||
|
||||
entry = git_index_get_byindex(index, pos);
|
||||
|
||||
if (old_oid && git_oid__cmp(old_oid, &entry->id) == 0)
|
||||
return GIT_ENOTFOUND;
|
||||
|
||||
if ((error = git_blob_lookup(blob, repo, &entry->id)) < 0)
|
||||
return error;
|
||||
|
||||
*content = git_blob_rawcontent(*blob);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_attr_from_cache(
|
||||
git_attr_file **file,
|
||||
git_attr_cache *cache,
|
||||
git_attr_file_source source,
|
||||
const char *relative_path)
|
||||
{
|
||||
git_buf cache_key = GIT_BUF_INIT;
|
||||
khiter_t cache_pos;
|
||||
|
||||
*file = NULL;
|
||||
|
||||
if (!cache || !cache->files)
|
||||
return 0;
|
||||
|
||||
if (git_buf_printf(&cache_key, "%d#%s", (int)source, relative_path) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_mutex_lock(&cache->lock) < 0) {
|
||||
giterr_set(GITERR_OS, "Could not get cache attr lock");
|
||||
git_buf_free(&cache_key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr);
|
||||
|
||||
if (git_strmap_valid_index(cache->files, cache_pos)) {
|
||||
*file = git_strmap_value_at(cache->files, cache_pos);
|
||||
GIT_REFCOUNT_INC(*file);
|
||||
}
|
||||
|
||||
git_mutex_unlock(&cache->lock);
|
||||
git_buf_free(&cache_key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_attr_cache__internal_file(
|
||||
git_repository *repo,
|
||||
const char *filename,
|
||||
git_attr_file **file)
|
||||
{
|
||||
int error = 0;
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
khiter_t cache_pos;
|
||||
|
||||
if (git_mutex_lock(&cache->lock) < 0) {
|
||||
giterr_set(GITERR_OS, "Unable to get attr cache lock");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cache_pos = git_strmap_lookup_index(cache->files, filename);
|
||||
|
||||
if (git_strmap_valid_index(cache->files, cache_pos)) {
|
||||
*file = git_strmap_value_at(cache->files, cache_pos);
|
||||
}
|
||||
else if (!(error = git_attr_file__new(file, 0, filename, &cache->pool))) {
|
||||
|
||||
git_strmap_insert(cache->files, (*file)->key + 2, *file, error);
|
||||
if (error > 0)
|
||||
error = 0;
|
||||
}
|
||||
|
||||
git_mutex_unlock(&cache->lock);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_attr_cache__push_file(
|
||||
git_repository *repo,
|
||||
const char *base,
|
||||
const char *filename,
|
||||
git_attr_file_source source,
|
||||
git_attr_file_parser parse,
|
||||
void* parsedata,
|
||||
git_vector *stack)
|
||||
{
|
||||
int error = 0;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
const char *workdir = git_repository_workdir(repo);
|
||||
const char *relfile, *content = NULL;
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
git_attr_file *file = NULL;
|
||||
git_blob *blob = NULL;
|
||||
git_futils_filestamp stamp;
|
||||
|
||||
assert(filename && stack);
|
||||
|
||||
/* join base and path as needed */
|
||||
if (base != NULL && git_path_root(filename) < 0) {
|
||||
if (git_buf_joinpath(&path, base, filename) < 0)
|
||||
return -1;
|
||||
filename = path.ptr;
|
||||
}
|
||||
|
||||
relfile = filename;
|
||||
if (workdir && git__prefixcmp(relfile, workdir) == 0)
|
||||
relfile += strlen(workdir);
|
||||
|
||||
/* check cache */
|
||||
if (load_attr_from_cache(&file, cache, source, relfile) < 0)
|
||||
return -1;
|
||||
|
||||
/* if not in cache, load data, parse, and cache */
|
||||
|
||||
if (source == GIT_ATTR_FILE_FROM_FILE) {
|
||||
git_futils_filestamp_set(
|
||||
&stamp, file ? &file->cache_data.stamp : NULL);
|
||||
|
||||
error = load_attr_file(&content, &stamp, filename);
|
||||
} else {
|
||||
error = load_attr_blob_from_index(&content, &blob,
|
||||
repo, file ? &file->cache_data.oid : NULL, relfile);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
/* not finding a file is not an error for this function */
|
||||
if (error == GIT_ENOTFOUND) {
|
||||
giterr_clear();
|
||||
error = 0;
|
||||
}
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* if we got here, we have to parse and/or reparse the file */
|
||||
if (file)
|
||||
git_attr_file__clear_rules(file);
|
||||
else {
|
||||
error = git_attr_file__new(&file, source, relfile, &cache->pool);
|
||||
if (error < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (parse && (error = parse(repo, parsedata, content, file)) < 0)
|
||||
goto finish;
|
||||
|
||||
if (git_mutex_lock(&cache->lock) < 0) {
|
||||
giterr_set(GITERR_OS, "Unable to get attr cache lock");
|
||||
error = -1;
|
||||
} else {
|
||||
git_strmap_insert(cache->files, file->key, file, error); /* -V595 */
|
||||
if (error > 0) { /* > 0 means inserting for the first time */
|
||||
error = 0;
|
||||
GIT_REFCOUNT_INC(file);
|
||||
}
|
||||
git_mutex_unlock(&cache->lock);
|
||||
}
|
||||
|
||||
/* remember "cache buster" file signature */
|
||||
if (blob)
|
||||
git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob));
|
||||
else
|
||||
git_futils_filestamp_set(&file->cache_data.stamp, &stamp);
|
||||
|
||||
finish:
|
||||
/* push file onto vector if we found one*/
|
||||
if (!error && file != NULL)
|
||||
error = git_vector_insert(stack, file);
|
||||
|
||||
if (error != 0)
|
||||
git_attr_file__free(file);
|
||||
|
||||
if (blob)
|
||||
git_blob_free(blob);
|
||||
else
|
||||
git__free((void *)content);
|
||||
|
||||
git_buf_free(&path);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
#define push_attr_file(R,S,B,F) \
|
||||
git_attr_cache__push_file \
|
||||
((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,NULL,(S))
|
||||
|
||||
typedef struct {
|
||||
git_repository *repo;
|
||||
uint32_t flags;
|
||||
@ -514,46 +258,64 @@ typedef struct {
|
||||
git_vector *files;
|
||||
} attr_walk_up_info;
|
||||
|
||||
int git_attr_cache__decide_sources(
|
||||
uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
|
||||
static int attr_decide_sources(
|
||||
uint32_t flags, bool has_wd, bool has_index, git_attr_cache_source *srcs)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
switch (flags & 0x03) {
|
||||
case GIT_ATTR_CHECK_FILE_THEN_INDEX:
|
||||
if (has_wd)
|
||||
srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
|
||||
srcs[count++] = GIT_ATTR_CACHE__FROM_FILE;
|
||||
if (has_index)
|
||||
srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
|
||||
srcs[count++] = GIT_ATTR_CACHE__FROM_INDEX;
|
||||
break;
|
||||
case GIT_ATTR_CHECK_INDEX_THEN_FILE:
|
||||
if (has_index)
|
||||
srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
|
||||
srcs[count++] = GIT_ATTR_CACHE__FROM_INDEX;
|
||||
if (has_wd)
|
||||
srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
|
||||
srcs[count++] = GIT_ATTR_CACHE__FROM_FILE;
|
||||
break;
|
||||
case GIT_ATTR_CHECK_INDEX_ONLY:
|
||||
if (has_index)
|
||||
srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
|
||||
srcs[count++] = GIT_ATTR_CACHE__FROM_INDEX;
|
||||
break;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int push_attr_file(
|
||||
git_repository *repo,
|
||||
git_vector *list,
|
||||
git_attr_cache_source source,
|
||||
const char *base,
|
||||
const char *filename)
|
||||
{
|
||||
int error = 0;
|
||||
git_attr_file *file = NULL;
|
||||
|
||||
if ((error = git_attr_cache__get(
|
||||
&file, repo, source, base, filename,
|
||||
git_attr_file__parse_buffer, NULL)) < 0 ||
|
||||
(error = git_vector_insert(list, file)) < 0)
|
||||
git_attr_file__free(file);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int push_one_attr(void *ref, git_buf *path)
|
||||
{
|
||||
int error = 0, n_src, i;
|
||||
attr_walk_up_info *info = (attr_walk_up_info *)ref;
|
||||
git_attr_file_source src[2];
|
||||
git_attr_cache_source src[2];
|
||||
|
||||
n_src = git_attr_cache__decide_sources(
|
||||
n_src = attr_decide_sources(
|
||||
info->flags, info->workdir != NULL, info->index != NULL, src);
|
||||
|
||||
for (i = 0; !error && i < n_src; ++i)
|
||||
error = git_attr_cache__push_file(
|
||||
info->repo, path->ptr, GIT_ATTR_FILE, src[i],
|
||||
git_attr_file__parse_buffer, NULL, info->files);
|
||||
error = push_attr_file(
|
||||
info->repo, info->files, src[i], path->ptr, GIT_ATTR_FILE);
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -601,7 +363,8 @@ static int collect_attr_files(
|
||||
*/
|
||||
|
||||
error = push_attr_file(
|
||||
repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO);
|
||||
repo, files, GIT_ATTR_CACHE__FROM_FILE,
|
||||
git_repository_path(repo), GIT_ATTR_FILE_INREPO);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
@ -618,7 +381,8 @@ static int collect_attr_files(
|
||||
|
||||
if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
|
||||
error = push_attr_file(
|
||||
repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file);
|
||||
repo, files, GIT_ATTR_CACHE__FROM_FILE,
|
||||
NULL, git_repository_attr_cache(repo)->cfg_attr_file);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
@ -626,7 +390,8 @@ static int collect_attr_files(
|
||||
if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
|
||||
error = git_sysdir_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
|
||||
if (!error)
|
||||
error = push_attr_file(repo, files, NULL, dir.ptr);
|
||||
error = push_attr_file(
|
||||
repo, files, GIT_ATTR_CACHE__FROM_FILE, NULL, dir.ptr);
|
||||
else if (error == GIT_ENOTFOUND) {
|
||||
giterr_clear();
|
||||
error = 0;
|
||||
@ -640,172 +405,3 @@ static int collect_attr_files(
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int attr_cache__lookup_path(
|
||||
char **out, git_config *cfg, const char *key, const char *fallback)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
int error;
|
||||
const git_config_entry *entry = NULL;
|
||||
|
||||
*out = NULL;
|
||||
|
||||
if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
|
||||
return error;
|
||||
|
||||
if (entry) {
|
||||
const char *cfgval = entry->value;
|
||||
|
||||
/* expand leading ~/ as needed */
|
||||
if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
|
||||
!git_sysdir_find_global_file(&buf, &cfgval[2]))
|
||||
*out = git_buf_detach(&buf);
|
||||
else if (cfgval)
|
||||
*out = git__strdup(cfgval);
|
||||
|
||||
}
|
||||
else if (!git_sysdir_find_xdg_file(&buf, fallback))
|
||||
*out = git_buf_detach(&buf);
|
||||
|
||||
git_buf_free(&buf);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void attr_cache__free(git_attr_cache *cache)
|
||||
{
|
||||
if (!cache)
|
||||
return;
|
||||
|
||||
if (cache->files != NULL) {
|
||||
git_attr_file *file;
|
||||
|
||||
git_strmap_foreach_value(cache->files, file, {
|
||||
git_attr_file__free(file);
|
||||
});
|
||||
|
||||
git_strmap_free(cache->files);
|
||||
}
|
||||
|
||||
if (cache->macros != NULL) {
|
||||
git_attr_rule *rule;
|
||||
|
||||
git_strmap_foreach_value(cache->macros, rule, {
|
||||
git_attr_rule__free(rule);
|
||||
});
|
||||
|
||||
git_strmap_free(cache->macros);
|
||||
}
|
||||
|
||||
git_pool_clear(&cache->pool);
|
||||
|
||||
git__free(cache->cfg_attr_file);
|
||||
cache->cfg_attr_file = NULL;
|
||||
|
||||
git__free(cache->cfg_excl_file);
|
||||
cache->cfg_excl_file = NULL;
|
||||
|
||||
git_mutex_free(&cache->lock);
|
||||
|
||||
git__free(cache);
|
||||
}
|
||||
|
||||
int git_attr_cache__init(git_repository *repo)
|
||||
{
|
||||
int ret = 0;
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
git_config *cfg;
|
||||
|
||||
if (cache)
|
||||
return 0;
|
||||
|
||||
if ((ret = git_repository_config__weakptr(&cfg, repo)) < 0)
|
||||
return ret;
|
||||
|
||||
cache = git__calloc(1, sizeof(git_attr_cache));
|
||||
GITERR_CHECK_ALLOC(cache);
|
||||
|
||||
/* set up lock */
|
||||
if (git_mutex_init(&cache->lock) < 0) {
|
||||
giterr_set(GITERR_OS, "Unable to initialize lock for attr cache");
|
||||
git__free(cache);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* cache config settings for attributes and ignores */
|
||||
ret = attr_cache__lookup_path(
|
||||
&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
|
||||
if (ret < 0)
|
||||
goto cancel;
|
||||
|
||||
ret = attr_cache__lookup_path(
|
||||
&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
|
||||
if (ret < 0)
|
||||
goto cancel;
|
||||
|
||||
/* allocate hashtable for attribute and ignore file contents,
|
||||
* hashtable for attribute macros, and string pool
|
||||
*/
|
||||
if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
|
||||
(ret = git_strmap_alloc(&cache->macros)) < 0 ||
|
||||
(ret = git_pool_init(&cache->pool, 1, 0)) < 0)
|
||||
goto cancel;
|
||||
|
||||
cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
|
||||
if (cache)
|
||||
goto cancel; /* raced with another thread, free this but no error */
|
||||
|
||||
/* insert default macros */
|
||||
return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
|
||||
|
||||
cancel:
|
||||
attr_cache__free(cache);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void git_attr_cache_flush(git_repository *repo)
|
||||
{
|
||||
git_attr_cache *cache;
|
||||
|
||||
/* this could be done less expensively, but for now, we'll just free
|
||||
* the entire attrcache and let the next use reinitialize it...
|
||||
*/
|
||||
if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
|
||||
attr_cache__free(cache);
|
||||
}
|
||||
|
||||
int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
|
||||
{
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
git_strmap *macros = cache->macros;
|
||||
int error;
|
||||
|
||||
/* TODO: generate warning log if (macro->assigns.length == 0) */
|
||||
if (macro->assigns.length == 0)
|
||||
return 0;
|
||||
|
||||
if (git_mutex_lock(&cache->lock) < 0) {
|
||||
giterr_set(GITERR_OS, "Unable to get attr cache lock");
|
||||
error = -1;
|
||||
} else {
|
||||
git_strmap_insert(macros, macro->match.pattern, macro, error);
|
||||
git_mutex_unlock(&cache->lock);
|
||||
}
|
||||
|
||||
return (error < 0) ? -1 : 0;
|
||||
}
|
||||
|
||||
git_attr_rule *git_attr_cache__lookup_macro(
|
||||
git_repository *repo, const char *name)
|
||||
{
|
||||
git_strmap *macros = git_repository_attr_cache(repo)->macros;
|
||||
khiter_t pos;
|
||||
|
||||
pos = git_strmap_lookup_index(macros, name);
|
||||
|
||||
if (!git_strmap_valid_index(macros, pos))
|
||||
return NULL;
|
||||
|
||||
return (git_attr_rule *)git_strmap_value_at(macros, pos);
|
||||
}
|
||||
|
||||
|
34
src/attr.h
34
src/attr.h
@ -8,38 +8,6 @@
|
||||
#define INCLUDE_attr_h__
|
||||
|
||||
#include "attr_file.h"
|
||||
|
||||
#define GIT_ATTR_CONFIG "core.attributesfile"
|
||||
#define GIT_IGNORE_CONFIG "core.excludesfile"
|
||||
|
||||
typedef int (*git_attr_file_parser)(
|
||||
git_repository *, void *, const char *, git_attr_file *);
|
||||
|
||||
extern int git_attr_cache__insert_macro(
|
||||
git_repository *repo, git_attr_rule *macro);
|
||||
|
||||
extern git_attr_rule *git_attr_cache__lookup_macro(
|
||||
git_repository *repo, const char *name);
|
||||
|
||||
extern int git_attr_cache__push_file(
|
||||
git_repository *repo,
|
||||
const char *base,
|
||||
const char *filename,
|
||||
git_attr_file_source source,
|
||||
git_attr_file_parser parse,
|
||||
void *parsedata, /* passed through to parse function */
|
||||
git_vector *stack);
|
||||
|
||||
extern int git_attr_cache__internal_file(
|
||||
git_repository *repo,
|
||||
const char *key,
|
||||
git_attr_file **file_ptr);
|
||||
|
||||
/* returns true if path is in cache */
|
||||
extern bool git_attr_cache__is_cached(
|
||||
git_repository *repo, git_attr_file_source source, const char *path);
|
||||
|
||||
extern int git_attr_cache__decide_sources(
|
||||
uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs);
|
||||
#include "attrcache.h"
|
||||
|
||||
#endif
|
||||
|
342
src/attr_file.c
342
src/attr_file.c
@ -1,11 +1,144 @@
|
||||
#include "common.h"
|
||||
#include "repository.h"
|
||||
#include "filebuf.h"
|
||||
#include "attr.h"
|
||||
#include "attr_file.h"
|
||||
#include "git2/blob.h"
|
||||
#include "git2/tree.h"
|
||||
#include "index.h"
|
||||
#include <ctype.h>
|
||||
|
||||
static void attr_file_free(git_attr_file *file)
|
||||
{
|
||||
git_attr_file__clear_rules(file);
|
||||
git_pool_clear(&file->pool);
|
||||
git__memzero(file, sizeof(*file));
|
||||
git__free(file);
|
||||
}
|
||||
|
||||
int git_attr_file__new(
|
||||
git_attr_file **out,
|
||||
git_attr_cache_entry *ce,
|
||||
git_attr_cache_source source)
|
||||
{
|
||||
git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
|
||||
GITERR_CHECK_ALLOC(attrs);
|
||||
|
||||
if (git_pool_init(&attrs->pool, 1, 0) < 0 ||
|
||||
git_vector_init(&attrs->rules, 0, NULL) < 0)
|
||||
{
|
||||
attr_file_free(attrs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
GIT_REFCOUNT_INC(attrs);
|
||||
attrs->ce = ce;
|
||||
attrs->source = source;
|
||||
*out = attrs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void git_attr_file__clear_rules(git_attr_file *file)
|
||||
{
|
||||
unsigned int i;
|
||||
git_attr_rule *rule;
|
||||
|
||||
git_vector_foreach(&file->rules, i, rule)
|
||||
git_attr_rule__free(rule);
|
||||
git_vector_free(&file->rules);
|
||||
}
|
||||
|
||||
void git_attr_file__free(git_attr_file *file)
|
||||
{
|
||||
if (!file)
|
||||
return;
|
||||
GIT_REFCOUNT_DEC(file, attr_file_free);
|
||||
}
|
||||
|
||||
static int attr_file_oid_from_index(
|
||||
git_oid *oid, git_repository *repo, const char *path)
|
||||
{
|
||||
int error;
|
||||
git_index *idx;
|
||||
size_t pos;
|
||||
const git_index_entry *entry;
|
||||
|
||||
if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
|
||||
(error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
|
||||
return error;
|
||||
|
||||
if (!(entry = git_index_get_byindex(idx, pos)))
|
||||
return GIT_ENOTFOUND;
|
||||
|
||||
*oid = entry->id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_attr_file__load(
|
||||
git_attr_file **out,
|
||||
git_repository *repo,
|
||||
git_attr_cache_entry *ce,
|
||||
git_attr_cache_source source,
|
||||
git_attr_cache_parser parser,
|
||||
void *payload)
|
||||
{
|
||||
int error = 0;
|
||||
git_blob *blob = NULL;
|
||||
git_buf content = GIT_BUF_INIT;
|
||||
const char *data = NULL;
|
||||
git_attr_file *file;
|
||||
|
||||
*out = NULL;
|
||||
|
||||
if (source == GIT_ATTR_CACHE__FROM_INDEX) {
|
||||
git_oid id;
|
||||
|
||||
if ((error = attr_file_oid_from_index(&id, repo, ce->path)) < 0 ||
|
||||
(error = git_blob_lookup(&blob, repo, &id)) < 0)
|
||||
return error;
|
||||
|
||||
data = git_blob_rawcontent(blob);
|
||||
} else {
|
||||
if ((error = git_futils_readbuffer(&content, ce->fullpath)) < 0)
|
||||
/* always return ENOTFOUND so item will just be skipped */
|
||||
/* TODO: issue a warning once warnings API is available */
|
||||
return GIT_ENOTFOUND;
|
||||
data = content.ptr;
|
||||
}
|
||||
|
||||
if ((error = git_attr_file__new(&file, ce, source)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (parser && (error = parser(repo, file, data, payload)) < 0)
|
||||
git_attr_file__free(file);
|
||||
else
|
||||
*out = file;
|
||||
|
||||
cleanup:
|
||||
git_blob_free(blob);
|
||||
git_buf_free(&content);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_attr_file__out_of_date(git_repository *repo, git_attr_file *file)
|
||||
{
|
||||
if (!file)
|
||||
return 1;
|
||||
|
||||
if (file->source == GIT_ATTR_CACHE__FROM_INDEX) {
|
||||
int error;
|
||||
git_oid id;
|
||||
|
||||
if ((error = attr_file_oid_from_index(&id, repo, file->ce->path)) < 0)
|
||||
return error;
|
||||
|
||||
return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
|
||||
}
|
||||
|
||||
return git_futils_filestamp_check(
|
||||
&file->cache_data.stamp, file->ce->fullpath);
|
||||
}
|
||||
|
||||
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
|
||||
static void git_attr_rule__clear(git_attr_rule *rule);
|
||||
static bool parse_optimized_patterns(
|
||||
@ -13,74 +146,28 @@ static bool parse_optimized_patterns(
|
||||
git_pool *pool,
|
||||
const char *pattern);
|
||||
|
||||
int git_attr_file__new(
|
||||
git_attr_file **attrs_ptr,
|
||||
git_attr_file_source from,
|
||||
const char *path,
|
||||
git_pool *pool)
|
||||
{
|
||||
git_attr_file *attrs = NULL;
|
||||
|
||||
attrs = git__calloc(1, sizeof(git_attr_file));
|
||||
GITERR_CHECK_ALLOC(attrs);
|
||||
GIT_REFCOUNT_INC(attrs);
|
||||
|
||||
if (pool)
|
||||
attrs->pool = pool;
|
||||
else {
|
||||
attrs->pool = git__calloc(1, sizeof(git_pool));
|
||||
if (!attrs->pool || git_pool_init(attrs->pool, 1, 0) < 0)
|
||||
goto fail;
|
||||
attrs->pool_is_allocated = true;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
size_t len = strlen(path);
|
||||
|
||||
attrs->key = git_pool_malloc(attrs->pool, (uint32_t)len + 3);
|
||||
GITERR_CHECK_ALLOC(attrs->key);
|
||||
|
||||
attrs->key[0] = '0' + (char)from;
|
||||
attrs->key[1] = '#';
|
||||
memcpy(&attrs->key[2], path, len);
|
||||
attrs->key[len + 2] = '\0';
|
||||
}
|
||||
|
||||
if (git_vector_init(&attrs->rules, 4, NULL) < 0)
|
||||
goto fail;
|
||||
|
||||
*attrs_ptr = attrs;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
git_attr_file__free(attrs);
|
||||
attrs_ptr = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int git_attr_file__parse_buffer(
|
||||
git_repository *repo, void *parsedata, const char *buffer, git_attr_file *attrs)
|
||||
git_repository *repo,
|
||||
git_attr_file *attrs,
|
||||
const char *data,
|
||||
void *payload)
|
||||
{
|
||||
int error = 0;
|
||||
const char *scan = NULL, *context = NULL;
|
||||
const char *scan = data, *context = NULL;
|
||||
git_attr_rule *rule = NULL;
|
||||
|
||||
GIT_UNUSED(parsedata);
|
||||
|
||||
assert(buffer && attrs);
|
||||
|
||||
scan = buffer;
|
||||
GIT_UNUSED(payload);
|
||||
|
||||
/* if subdir file path, convert context for file paths */
|
||||
if (attrs->key &&
|
||||
git_path_root(attrs->key + 2) < 0 &&
|
||||
git__suffixcmp(attrs->key, "/" GIT_ATTR_FILE) == 0)
|
||||
context = attrs->key + 2;
|
||||
if (attrs->ce &&
|
||||
git_path_root(attrs->ce->path) < 0 &&
|
||||
!git__suffixcmp(attrs->ce->path, "/" GIT_ATTR_FILE))
|
||||
context = attrs->ce->path;
|
||||
|
||||
while (!error && *scan) {
|
||||
/* allocate rule if needed */
|
||||
if (!rule) {
|
||||
if (!(rule = git__calloc(1, sizeof(git_attr_rule)))) {
|
||||
if (!(rule = git__calloc(1, sizeof(*rule)))) {
|
||||
error = -1;
|
||||
break;
|
||||
}
|
||||
@ -90,9 +177,9 @@ int git_attr_file__parse_buffer(
|
||||
|
||||
/* parse the next "pattern attr attr attr" line */
|
||||
if (!(error = git_attr_fnmatch__parse(
|
||||
&rule->match, attrs->pool, context, &scan)) &&
|
||||
&rule->match, &attrs->pool, context, &scan)) &&
|
||||
!(error = git_attr_assignment__parse(
|
||||
repo, attrs->pool, &rule->assigns, &scan)))
|
||||
repo, &attrs->pool, &rule->assigns, &scan)))
|
||||
{
|
||||
if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
|
||||
/* should generate error/warning if this is coming from any
|
||||
@ -118,61 +205,6 @@ int git_attr_file__parse_buffer(
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_attr_file__new_and_load(
|
||||
git_attr_file **attrs_ptr,
|
||||
const char *path)
|
||||
{
|
||||
int error;
|
||||
git_buf content = GIT_BUF_INIT;
|
||||
|
||||
if ((error = git_attr_file__new(attrs_ptr, 0, path, NULL)) < 0)
|
||||
return error;
|
||||
|
||||
if (!(error = git_futils_readbuffer(&content, path)))
|
||||
error = git_attr_file__parse_buffer(
|
||||
NULL, NULL, git_buf_cstr(&content), *attrs_ptr);
|
||||
|
||||
git_buf_free(&content);
|
||||
|
||||
if (error) {
|
||||
git_attr_file__free(*attrs_ptr);
|
||||
*attrs_ptr = NULL;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void git_attr_file__clear_rules(git_attr_file *file)
|
||||
{
|
||||
unsigned int i;
|
||||
git_attr_rule *rule;
|
||||
|
||||
git_vector_foreach(&file->rules, i, rule)
|
||||
git_attr_rule__free(rule);
|
||||
|
||||
git_vector_free(&file->rules);
|
||||
}
|
||||
|
||||
static void attr_file_free(git_attr_file *file)
|
||||
{
|
||||
git_attr_file__clear_rules(file);
|
||||
|
||||
if (file->pool_is_allocated) {
|
||||
git_pool_clear(file->pool);
|
||||
git__free(file->pool);
|
||||
}
|
||||
file->pool = NULL;
|
||||
|
||||
git__free(file);
|
||||
}
|
||||
|
||||
void git_attr_file__free(git_attr_file *file)
|
||||
{
|
||||
if (!file)
|
||||
return;
|
||||
GIT_REFCOUNT_DEC(file, attr_file_free);
|
||||
}
|
||||
|
||||
uint32_t git_attr_file__name_hash(const char *name)
|
||||
{
|
||||
uint32_t h = 5381;
|
||||
@ -183,7 +215,6 @@ uint32_t git_attr_file__name_hash(const char *name)
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
int git_attr_file__lookup_one(
|
||||
git_attr_file *file,
|
||||
const git_attr_path *path,
|
||||
@ -212,25 +243,64 @@ int git_attr_file__lookup_one(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_attr_file__load_standalone(
|
||||
git_attr_file **out,
|
||||
const char *path)
|
||||
{
|
||||
int error;
|
||||
git_attr_file *file;
|
||||
git_buf content = GIT_BUF_INIT;
|
||||
|
||||
error = git_attr_file__new(&file, NULL, GIT_ATTR_CACHE__FROM_FILE);
|
||||
if (error < 0)
|
||||
return error;
|
||||
|
||||
error = git_attr_cache_entry__new(&file->ce, NULL, path, &file->pool);
|
||||
if (error < 0) {
|
||||
git_attr_file__free(file);
|
||||
return error;
|
||||
}
|
||||
/* because the cache entry is allocated from the file's own pool, we
|
||||
* don't have to free it - freeing file+pool will free cache entry, too.
|
||||
*/
|
||||
|
||||
if (!(error = git_futils_readbuffer(&content, path))) {
|
||||
error = git_attr_file__parse_buffer(NULL, file, content.ptr, NULL);
|
||||
git_buf_free(&content);
|
||||
}
|
||||
|
||||
if (error < 0)
|
||||
git_attr_file__free(file);
|
||||
else
|
||||
*out = file;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
bool git_attr_fnmatch__match(
|
||||
git_attr_fnmatch *match,
|
||||
const git_attr_path *path)
|
||||
{
|
||||
int fnm;
|
||||
int icase_flags = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? FNM_CASEFOLD : 0;
|
||||
const char *filename;
|
||||
int flags = 0;
|
||||
|
||||
if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
|
||||
if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir)
|
||||
return false;
|
||||
|
||||
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH)
|
||||
fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME | icase_flags);
|
||||
else if (path->is_dir)
|
||||
fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR | icase_flags);
|
||||
else
|
||||
fnm = p_fnmatch(match->pattern, path->basename, icase_flags);
|
||||
if (match->flags & GIT_ATTR_FNMATCH_ICASE)
|
||||
flags |= FNM_CASEFOLD;
|
||||
|
||||
return (fnm == FNM_NOMATCH) ? false : true;
|
||||
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
|
||||
filename = path->path;
|
||||
flags |= FNM_PATHNAME;
|
||||
} else {
|
||||
filename = path->basename;
|
||||
|
||||
if (path->is_dir)
|
||||
flags |= FNM_LEADING_DIR;
|
||||
}
|
||||
|
||||
return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
|
||||
}
|
||||
|
||||
bool git_attr_rule__match(
|
||||
@ -245,7 +315,6 @@ bool git_attr_rule__match(
|
||||
return matched;
|
||||
}
|
||||
|
||||
|
||||
git_attr_assignment *git_attr_rule__lookup_assignment(
|
||||
git_attr_rule *rule, const char *name)
|
||||
{
|
||||
@ -344,7 +413,7 @@ void git_attr_path__free(git_attr_path *info)
|
||||
int git_attr_fnmatch__parse(
|
||||
git_attr_fnmatch *spec,
|
||||
git_pool *pool,
|
||||
const char *source,
|
||||
const char *context,
|
||||
const char **base)
|
||||
{
|
||||
const char *pattern, *scan;
|
||||
@ -412,21 +481,21 @@ int git_attr_fnmatch__parse(
|
||||
}
|
||||
|
||||
if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
|
||||
source != NULL && git_path_root(pattern) < 0)
|
||||
context != NULL && git_path_root(pattern) < 0)
|
||||
{
|
||||
/* use context path minus the trailing filename */
|
||||
char *slash = strrchr(source, '/');
|
||||
size_t sourcelen = slash ? slash - source + 1 : 0;
|
||||
/* use context path minus the trailing filename */
|
||||
char *slash = strrchr(context, '/');
|
||||
size_t contextlen = slash ? slash - context + 1 : 0;
|
||||
|
||||
/* given an unrooted fullpath match from a file inside a repo,
|
||||
* prefix the pattern with the relative directory of the source file
|
||||
*/
|
||||
spec->pattern = git_pool_malloc(
|
||||
pool, (uint32_t)(sourcelen + spec->length + 1));
|
||||
pool, (uint32_t)(contextlen + spec->length + 1));
|
||||
if (spec->pattern) {
|
||||
memcpy(spec->pattern, source, sourcelen);
|
||||
memcpy(spec->pattern + sourcelen, pattern, spec->length);
|
||||
spec->length += sourcelen;
|
||||
memcpy(spec->pattern, context, contextlen);
|
||||
memcpy(spec->pattern + contextlen, pattern, spec->length);
|
||||
spec->length += contextlen;
|
||||
spec->pattern[spec->length] = '\0';
|
||||
}
|
||||
} else {
|
||||
@ -439,6 +508,7 @@ int git_attr_fnmatch__parse(
|
||||
} else {
|
||||
/* strip '\' that might have be used for internal whitespace */
|
||||
spec->length = git__unescape(spec->pattern);
|
||||
/* TODO: convert remaining '\' into '/' for POSIX ??? */
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "pool.h"
|
||||
#include "buffer.h"
|
||||
#include "fileops.h"
|
||||
#include "attrcache.h"
|
||||
|
||||
#define GIT_ATTR_FILE ".gitattributes"
|
||||
#define GIT_ATTR_FILE_INREPO "info/attributes"
|
||||
@ -45,10 +46,10 @@ typedef struct {
|
||||
unsigned int flags;
|
||||
} git_attr_fnmatch;
|
||||
|
||||
typedef struct {
|
||||
struct git_attr_rule {
|
||||
git_attr_fnmatch match;
|
||||
git_vector assigns; /* vector of <git_attr_assignment*> */
|
||||
} git_attr_rule;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
git_refcount unused;
|
||||
@ -63,17 +64,17 @@ typedef struct {
|
||||
const char *value;
|
||||
} git_attr_assignment;
|
||||
|
||||
typedef struct {
|
||||
struct git_attr_file {
|
||||
git_refcount rc;
|
||||
char *key; /* cache "source#path" this was loaded from */
|
||||
git_vector rules; /* vector of <rule*> or <fnmatch*> */
|
||||
git_pool *pool;
|
||||
bool pool_is_allocated;
|
||||
git_attr_cache_entry *ce;
|
||||
git_attr_cache_source source;
|
||||
git_vector rules; /* vector of <rule*> or <fnmatch*> */
|
||||
git_pool pool;
|
||||
union {
|
||||
git_oid oid;
|
||||
git_futils_filestamp stamp;
|
||||
} cache_data;
|
||||
} git_attr_file;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
git_buf full;
|
||||
@ -82,29 +83,41 @@ typedef struct {
|
||||
int is_dir;
|
||||
} git_attr_path;
|
||||
|
||||
typedef enum {
|
||||
GIT_ATTR_FILE_FROM_FILE = 0,
|
||||
GIT_ATTR_FILE_FROM_INDEX = 1
|
||||
} git_attr_file_source;
|
||||
|
||||
/*
|
||||
* git_attr_file API
|
||||
*/
|
||||
|
||||
extern int git_attr_file__new(
|
||||
git_attr_file **attrs_ptr, git_attr_file_source src, const char *path, git_pool *pool);
|
||||
int git_attr_file__new(
|
||||
git_attr_file **out,
|
||||
git_attr_cache_entry *ce,
|
||||
git_attr_cache_source source);
|
||||
|
||||
extern int git_attr_file__new_and_load(
|
||||
git_attr_file **attrs_ptr, const char *path);
|
||||
void git_attr_file__free(git_attr_file *file);
|
||||
|
||||
extern void git_attr_file__free(git_attr_file *file);
|
||||
int git_attr_file__load(
|
||||
git_attr_file **out,
|
||||
git_repository *repo,
|
||||
git_attr_cache_entry *ce,
|
||||
git_attr_cache_source source,
|
||||
git_attr_cache_parser parser,
|
||||
void *payload);
|
||||
|
||||
extern void git_attr_file__clear_rules(git_attr_file *file);
|
||||
int git_attr_file__load_standalone(
|
||||
git_attr_file **out,
|
||||
const char *path);
|
||||
|
||||
extern int git_attr_file__parse_buffer(
|
||||
git_repository *repo, void *parsedata, const char *buf, git_attr_file *file);
|
||||
int git_attr_file__out_of_date(
|
||||
git_repository *repo, git_attr_file *file);
|
||||
|
||||
extern int git_attr_file__lookup_one(
|
||||
int git_attr_file__parse_buffer(
|
||||
git_repository *repo,
|
||||
git_attr_file *attrs,
|
||||
const char *data,
|
||||
void *payload);
|
||||
|
||||
void git_attr_file__clear_rules(git_attr_file *file);
|
||||
|
||||
int git_attr_file__lookup_one(
|
||||
git_attr_file *file,
|
||||
const git_attr_path *path,
|
||||
const char *attr,
|
||||
@ -115,7 +128,7 @@ extern int git_attr_file__lookup_one(
|
||||
git_vector_rforeach(&(file)->rules, (iter), (rule)) \
|
||||
if (git_attr_rule__match((rule), (path)))
|
||||
|
||||
extern uint32_t git_attr_file__name_hash(const char *name);
|
||||
uint32_t git_attr_file__name_hash(const char *name);
|
||||
|
||||
|
||||
/*
|
||||
|
397
src/attrcache.c
Normal file
397
src/attrcache.c
Normal file
@ -0,0 +1,397 @@
|
||||
#include "common.h"
|
||||
#include "repository.h"
|
||||
#include "attr_file.h"
|
||||
#include "config.h"
|
||||
#include "sysdir.h"
|
||||
#include "ignore.h"
|
||||
|
||||
GIT__USE_STRMAP;
|
||||
|
||||
GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
|
||||
{
|
||||
GIT_UNUSED(cache); /* avoid warning if threading is off */
|
||||
|
||||
if (git_mutex_lock(&cache->lock) < 0) {
|
||||
giterr_set(GITERR_OS, "Unable to get attr cache lock");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache)
|
||||
{
|
||||
GIT_UNUSED(cache); /* avoid warning if threading is off */
|
||||
git_mutex_unlock(&cache->lock);
|
||||
}
|
||||
|
||||
GIT_INLINE(git_attr_cache_entry *) attr_cache_lookup_entry(
|
||||
git_attr_cache *cache, const char *path)
|
||||
{
|
||||
khiter_t pos = git_strmap_lookup_index(cache->files, path);
|
||||
|
||||
if (git_strmap_valid_index(cache->files, pos))
|
||||
return git_strmap_value_at(cache->files, pos);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int git_attr_cache_entry__new(
|
||||
git_attr_cache_entry **out,
|
||||
const char *base,
|
||||
const char *path,
|
||||
git_pool *pool)
|
||||
{
|
||||
size_t baselen = base ? strlen(base) : 0, pathlen = strlen(path);
|
||||
size_t cachesize = sizeof(git_attr_cache_entry) + baselen + pathlen + 1;
|
||||
git_attr_cache_entry *ce;
|
||||
|
||||
ce = git_pool_mallocz(pool, cachesize);
|
||||
GITERR_CHECK_ALLOC(ce);
|
||||
|
||||
if (baselen)
|
||||
memcpy(ce->fullpath, base, baselen);
|
||||
memcpy(&ce->fullpath[baselen], path, pathlen);
|
||||
ce->path = &ce->fullpath[baselen];
|
||||
*out = ce;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* call with attrcache locked */
|
||||
static int attr_cache_make_entry(
|
||||
git_attr_cache_entry **out, git_repository *repo, const char *path)
|
||||
{
|
||||
int error = 0;
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
git_attr_cache_entry *ce = NULL;
|
||||
|
||||
error = git_attr_cache_entry__new(
|
||||
&ce, git_repository_workdir(repo), path, &cache->pool);
|
||||
|
||||
if (!error) {
|
||||
git_strmap_insert(cache->files, ce->path, ce, error);
|
||||
if (error > 0)
|
||||
error = 0;
|
||||
}
|
||||
|
||||
*out = ce;
|
||||
return error;
|
||||
}
|
||||
|
||||
/* insert entry or replace existing if we raced with another thread */
|
||||
static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file)
|
||||
{
|
||||
git_attr_cache_entry *ce;
|
||||
git_attr_file *old;
|
||||
|
||||
if (attr_cache_lock(cache) < 0)
|
||||
return -1;
|
||||
|
||||
ce = attr_cache_lookup_entry(cache, file->ce->path);
|
||||
|
||||
old = ce->file[file->source];
|
||||
|
||||
GIT_REFCOUNT_OWN(file, ce);
|
||||
GIT_REFCOUNT_INC(file);
|
||||
ce->file[file->source] = file;
|
||||
|
||||
if (old) {
|
||||
GIT_REFCOUNT_OWN(old, NULL);
|
||||
git_attr_file__free(old);
|
||||
}
|
||||
|
||||
attr_cache_unlock(cache);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
|
||||
{
|
||||
int error = 0;
|
||||
git_attr_cache_entry *ce;
|
||||
bool found = false;
|
||||
|
||||
if (!file)
|
||||
return 0;
|
||||
if ((error = attr_cache_lock(cache)) < 0)
|
||||
return error;
|
||||
|
||||
if ((ce = attr_cache_lookup_entry(cache, file->ce->path)) != NULL &&
|
||||
ce->file[file->source] == file)
|
||||
{
|
||||
ce->file[file->source] = NULL;
|
||||
found = true;
|
||||
}
|
||||
|
||||
attr_cache_unlock(cache);
|
||||
|
||||
if (found)
|
||||
git_attr_file__free(file);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_attr_cache__get(
|
||||
git_attr_file **out,
|
||||
git_repository *repo,
|
||||
git_attr_cache_source source,
|
||||
const char *base,
|
||||
const char *filename,
|
||||
git_attr_cache_parser parser,
|
||||
void *payload)
|
||||
{
|
||||
int error = 0;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
const char *wd = git_repository_workdir(repo), *relfile;
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
git_attr_cache_entry *ce = NULL;
|
||||
git_attr_file *file = NULL;
|
||||
|
||||
/* join base and path as needed */
|
||||
if (base != NULL && git_path_root(filename) < 0) {
|
||||
if (git_buf_joinpath(&path, base, filename) < 0)
|
||||
return -1;
|
||||
filename = path.ptr;
|
||||
}
|
||||
|
||||
relfile = filename;
|
||||
if (wd && !git__prefixcmp(relfile, wd))
|
||||
relfile += strlen(wd);
|
||||
|
||||
/* check cache for existing entry */
|
||||
if ((error = attr_cache_lock(cache)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
ce = attr_cache_lookup_entry(cache, relfile);
|
||||
if (!ce) {
|
||||
if ((error = attr_cache_make_entry(&ce, repo, relfile)) < 0)
|
||||
goto cleanup;
|
||||
} else if (ce->file[source] != NULL) {
|
||||
file = ce->file[source];
|
||||
GIT_REFCOUNT_INC(file);
|
||||
}
|
||||
|
||||
attr_cache_unlock(cache);
|
||||
|
||||
/* if this is not a file backed entry, just create a new empty one */
|
||||
if (!parser) {
|
||||
error = git_attr_file__new(&file, ce, source);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* otherwise load and/or reload as needed */
|
||||
switch (git_attr_file__out_of_date(repo, file)) {
|
||||
case 1:
|
||||
if (!(error = git_attr_file__load(
|
||||
&file, repo, ce, source, parser, payload)))
|
||||
error = attr_cache_upsert(cache, file);
|
||||
break;
|
||||
case 0:
|
||||
/* just use the file */
|
||||
break;
|
||||
case GIT_ENOTFOUND:
|
||||
/* did exist and now does not - remove from cache */
|
||||
error = attr_cache_remove(cache, file);
|
||||
file = NULL;
|
||||
break;
|
||||
default:
|
||||
/* other error (e.g. out of memory, can't read index) */
|
||||
giterr_clear();
|
||||
break;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
*out = error ? NULL : file;
|
||||
git_buf_free(&path);
|
||||
return error;
|
||||
}
|
||||
|
||||
bool git_attr_cache__is_cached(
|
||||
git_repository *repo,
|
||||
git_attr_cache_source source,
|
||||
const char *filename)
|
||||
{
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
git_strmap *files;
|
||||
khiter_t pos;
|
||||
git_attr_cache_entry *ce;
|
||||
|
||||
if (!(cache = git_repository_attr_cache(repo)) ||
|
||||
!(files = cache->files))
|
||||
return false;
|
||||
|
||||
pos = git_strmap_lookup_index(files, filename);
|
||||
if (!git_strmap_valid_index(files, pos))
|
||||
return false;
|
||||
|
||||
ce = git_strmap_value_at(files, pos);
|
||||
|
||||
return ce && (ce->file[source] != NULL);
|
||||
}
|
||||
|
||||
|
||||
static int attr_cache__lookup_path(
|
||||
char **out, git_config *cfg, const char *key, const char *fallback)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
int error;
|
||||
const git_config_entry *entry = NULL;
|
||||
|
||||
*out = NULL;
|
||||
|
||||
if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
|
||||
return error;
|
||||
|
||||
if (entry) {
|
||||
const char *cfgval = entry->value;
|
||||
|
||||
/* expand leading ~/ as needed */
|
||||
if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
|
||||
!git_sysdir_find_global_file(&buf, &cfgval[2]))
|
||||
*out = git_buf_detach(&buf);
|
||||
else if (cfgval)
|
||||
*out = git__strdup(cfgval);
|
||||
|
||||
}
|
||||
else if (!git_sysdir_find_xdg_file(&buf, fallback))
|
||||
*out = git_buf_detach(&buf);
|
||||
|
||||
git_buf_free(&buf);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void attr_cache__free(git_attr_cache *cache)
|
||||
{
|
||||
if (!cache)
|
||||
return;
|
||||
|
||||
if (cache->files != NULL) {
|
||||
git_attr_file *file;
|
||||
|
||||
git_strmap_foreach_value(cache->files, file, {
|
||||
git_attr_file__free(file);
|
||||
});
|
||||
git_strmap_free(cache->files);
|
||||
}
|
||||
|
||||
if (cache->macros != NULL) {
|
||||
git_attr_rule *rule;
|
||||
|
||||
git_strmap_foreach_value(cache->macros, rule, {
|
||||
git_attr_rule__free(rule);
|
||||
});
|
||||
git_strmap_free(cache->macros);
|
||||
}
|
||||
|
||||
git_pool_clear(&cache->pool);
|
||||
|
||||
git__free(cache->cfg_attr_file);
|
||||
cache->cfg_attr_file = NULL;
|
||||
|
||||
git__free(cache->cfg_excl_file);
|
||||
cache->cfg_excl_file = NULL;
|
||||
|
||||
git_mutex_free(&cache->lock);
|
||||
|
||||
git__free(cache);
|
||||
}
|
||||
|
||||
int git_attr_cache__init(git_repository *repo)
|
||||
{
|
||||
int ret = 0;
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
git_config *cfg;
|
||||
|
||||
if (cache)
|
||||
return 0;
|
||||
|
||||
if ((ret = git_repository_config__weakptr(&cfg, repo)) < 0)
|
||||
return ret;
|
||||
|
||||
cache = git__calloc(1, sizeof(git_attr_cache));
|
||||
GITERR_CHECK_ALLOC(cache);
|
||||
|
||||
/* set up lock */
|
||||
if (git_mutex_init(&cache->lock) < 0) {
|
||||
giterr_set(GITERR_OS, "Unable to initialize lock for attr cache");
|
||||
git__free(cache);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* cache config settings for attributes and ignores */
|
||||
ret = attr_cache__lookup_path(
|
||||
&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
|
||||
if (ret < 0)
|
||||
goto cancel;
|
||||
|
||||
ret = attr_cache__lookup_path(
|
||||
&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
|
||||
if (ret < 0)
|
||||
goto cancel;
|
||||
|
||||
/* allocate hashtable for attribute and ignore file contents,
|
||||
* hashtable for attribute macros, and string pool
|
||||
*/
|
||||
if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
|
||||
(ret = git_strmap_alloc(&cache->macros)) < 0 ||
|
||||
(ret = git_pool_init(&cache->pool, 1, 0)) < 0)
|
||||
goto cancel;
|
||||
|
||||
cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
|
||||
if (cache)
|
||||
goto cancel; /* raced with another thread, free this but no error */
|
||||
|
||||
/* insert default macros */
|
||||
return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
|
||||
|
||||
cancel:
|
||||
attr_cache__free(cache);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void git_attr_cache_flush(git_repository *repo)
|
||||
{
|
||||
git_attr_cache *cache;
|
||||
|
||||
/* this could be done less expensively, but for now, we'll just free
|
||||
* the entire attrcache and let the next use reinitialize it...
|
||||
*/
|
||||
if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
|
||||
attr_cache__free(cache);
|
||||
}
|
||||
|
||||
int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
|
||||
{
|
||||
git_attr_cache *cache = git_repository_attr_cache(repo);
|
||||
git_strmap *macros = cache->macros;
|
||||
int error;
|
||||
|
||||
/* TODO: generate warning log if (macro->assigns.length == 0) */
|
||||
if (macro->assigns.length == 0)
|
||||
return 0;
|
||||
|
||||
if (git_mutex_lock(&cache->lock) < 0) {
|
||||
giterr_set(GITERR_OS, "Unable to get attr cache lock");
|
||||
error = -1;
|
||||
} else {
|
||||
git_strmap_insert(macros, macro->match.pattern, macro, error);
|
||||
git_mutex_unlock(&cache->lock);
|
||||
}
|
||||
|
||||
return (error < 0) ? -1 : 0;
|
||||
}
|
||||
|
||||
git_attr_rule *git_attr_cache__lookup_macro(
|
||||
git_repository *repo, const char *name)
|
||||
{
|
||||
git_strmap *macros = git_repository_attr_cache(repo)->macros;
|
||||
khiter_t pos;
|
||||
|
||||
pos = git_strmap_lookup_index(macros, name);
|
||||
|
||||
if (!git_strmap_valid_index(macros, pos))
|
||||
return NULL;
|
||||
|
||||
return (git_attr_rule *)git_strmap_value_at(macros, pos);
|
||||
}
|
||||
|
@ -9,11 +9,15 @@
|
||||
|
||||
#include "pool.h"
|
||||
#include "strmap.h"
|
||||
#include "buffer.h"
|
||||
|
||||
#define GIT_ATTR_CONFIG "core.attributesfile"
|
||||
#define GIT_IGNORE_CONFIG "core.excludesfile"
|
||||
|
||||
typedef struct {
|
||||
char *cfg_attr_file; /* cached value of core.attributesfile */
|
||||
char *cfg_excl_file; /* cached value of core.excludesfile */
|
||||
git_strmap *files; /* hash path to git_attr_file of rules */
|
||||
git_strmap *files; /* hash path to git_attr_cache_entry records */
|
||||
git_strmap *macros; /* hash name to vector<git_attr_assignment> */
|
||||
git_mutex lock;
|
||||
git_pool pool;
|
||||
@ -21,4 +25,53 @@ typedef struct {
|
||||
|
||||
extern int git_attr_cache__init(git_repository *repo);
|
||||
|
||||
typedef enum {
|
||||
GIT_ATTR_CACHE__FROM_FILE = 0,
|
||||
GIT_ATTR_CACHE__FROM_INDEX = 1,
|
||||
|
||||
GIT_ATTR_CACHE_NUM_SOURCES = 2
|
||||
} git_attr_cache_source;
|
||||
|
||||
typedef struct git_attr_file git_attr_file;
|
||||
typedef struct git_attr_rule git_attr_rule;
|
||||
|
||||
typedef struct {
|
||||
git_attr_file *file[GIT_ATTR_CACHE_NUM_SOURCES];
|
||||
const char *path; /* points into fullpath */
|
||||
char fullpath[GIT_FLEX_ARRAY];
|
||||
} git_attr_cache_entry;
|
||||
|
||||
typedef int (*git_attr_cache_parser)(
|
||||
git_repository *repo,
|
||||
git_attr_file *file,
|
||||
const char *data,
|
||||
void *payload);
|
||||
|
||||
/* get file - loading and reload as needed */
|
||||
extern int git_attr_cache__get(
|
||||
git_attr_file **file,
|
||||
git_repository *repo,
|
||||
git_attr_cache_source source,
|
||||
const char *base,
|
||||
const char *filename,
|
||||
git_attr_cache_parser parser,
|
||||
void *payload);
|
||||
|
||||
extern bool git_attr_cache__is_cached(
|
||||
git_repository *repo,
|
||||
git_attr_cache_source source,
|
||||
const char *path);
|
||||
|
||||
extern int git_attr_cache__insert_macro(
|
||||
git_repository *repo, git_attr_rule *macro);
|
||||
|
||||
extern git_attr_rule *git_attr_cache__lookup_macro(
|
||||
git_repository *repo, const char *name);
|
||||
|
||||
extern int git_attr_cache_entry__new(
|
||||
git_attr_cache_entry **out,
|
||||
const char *base,
|
||||
const char *path,
|
||||
git_pool *pool);
|
||||
|
||||
#endif
|
||||
|
@ -804,10 +804,8 @@ int git_futils_filestamp_check(
|
||||
if (stamp == NULL)
|
||||
return 1;
|
||||
|
||||
if (p_stat(path, &st) < 0) {
|
||||
giterr_set(GITERR_OS, "Could not stat '%s'", path);
|
||||
if (p_stat(path, &st) < 0)
|
||||
return GIT_ENOTFOUND;
|
||||
}
|
||||
|
||||
if (stamp->mtime == (git_time_t)st.st_mtime &&
|
||||
stamp->size == (git_off_t)st.st_size &&
|
||||
|
@ -292,13 +292,14 @@ typedef struct {
|
||||
* Compare stat information for file with reference info.
|
||||
*
|
||||
* This function updates the file stamp to current data for the given path
|
||||
* and returns 0 if the file is up-to-date relative to the prior setting or
|
||||
* 1 if the file has been changed. (This also may return GIT_ENOTFOUND if
|
||||
* the file doesn't exist.)
|
||||
* and returns 0 if the file is up-to-date relative to the prior setting,
|
||||
* 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't
|
||||
* exist. This will not call giterr_set, so you must set the error if you
|
||||
* plan to return an error.
|
||||
*
|
||||
* @param stamp File stamp to be checked
|
||||
* @param path Path to stat and check if changed
|
||||
* @return 0 if up-to-date, 1 if out-of-date, <0 on error
|
||||
* @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat
|
||||
*/
|
||||
extern int git_futils_filestamp_check(
|
||||
git_futils_filestamp *stamp, const char *path);
|
||||
|
128
src/ignore.c
128
src/ignore.c
@ -1,7 +1,7 @@
|
||||
#include "git2/ignore.h"
|
||||
#include "common.h"
|
||||
#include "ignore.h"
|
||||
#include "attr.h"
|
||||
#include "attr_file.h"
|
||||
#include "path.h"
|
||||
#include "config.h"
|
||||
|
||||
@ -10,26 +10,27 @@
|
||||
#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
|
||||
|
||||
static int parse_ignore_file(
|
||||
git_repository *repo, void *parsedata, const char *buffer, git_attr_file *ignores)
|
||||
git_repository *repo,
|
||||
git_attr_file *attrs,
|
||||
const char *data,
|
||||
void *payload)
|
||||
{
|
||||
int error = 0;
|
||||
git_attr_fnmatch *match = NULL;
|
||||
const char *scan = NULL, *context = NULL;
|
||||
int ignore_case = false;
|
||||
const char *scan = data, *context = NULL;
|
||||
git_attr_fnmatch *match = NULL;
|
||||
|
||||
/* Prefer to have the caller pass in a git_ignores as the parsedata
|
||||
* object. If they did not, then look up the value of ignore_case */
|
||||
if (parsedata != NULL)
|
||||
ignore_case = ((git_ignores *)parsedata)->ignore_case;
|
||||
/* either read ignore_case from ignores structure or use repo config */
|
||||
if (payload != NULL)
|
||||
ignore_case = ((git_ignores *)payload)->ignore_case;
|
||||
else if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
|
||||
return error;
|
||||
giterr_clear();
|
||||
|
||||
if (ignores->key &&
|
||||
git_path_root(ignores->key + 2) < 0 &&
|
||||
git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0)
|
||||
context = ignores->key + 2;
|
||||
|
||||
scan = buffer;
|
||||
/* if subdir file path, convert context for file paths */
|
||||
if (attrs->ce &&
|
||||
git_path_root(attrs->ce->path) < 0 &&
|
||||
!git__suffixcmp(attrs->ce->path, "/" GIT_IGNORE_FILE))
|
||||
context = attrs->ce->path;
|
||||
|
||||
while (!error && *scan) {
|
||||
if (!match) {
|
||||
@ -40,7 +41,7 @@ static int parse_ignore_file(
|
||||
match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
|
||||
|
||||
if (!(error = git_attr_fnmatch__parse(
|
||||
match, ignores->pool, context, &scan)))
|
||||
match, &attrs->pool, context, &scan)))
|
||||
{
|
||||
match->flags |= GIT_ATTR_FNMATCH_IGNORE;
|
||||
|
||||
@ -48,7 +49,7 @@ static int parse_ignore_file(
|
||||
match->flags |= GIT_ATTR_FNMATCH_ICASE;
|
||||
|
||||
scan = git__next_line(scan);
|
||||
error = git_vector_insert(&ignores->rules, match);
|
||||
error = git_vector_insert(&attrs->rules, match);
|
||||
}
|
||||
|
||||
if (error != 0) {
|
||||
@ -67,28 +68,46 @@ static int parse_ignore_file(
|
||||
return error;
|
||||
}
|
||||
|
||||
#define push_ignore_file(R,IGN,S,B,F) \
|
||||
git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(IGN),(S))
|
||||
static int push_ignore_file(
|
||||
git_ignores *ignores,
|
||||
git_vector *which_list,
|
||||
const char *base,
|
||||
const char *filename)
|
||||
{
|
||||
int error = 0;
|
||||
git_attr_file *file = NULL;
|
||||
|
||||
if ((error = git_attr_cache__get(
|
||||
&file, ignores->repo, GIT_ATTR_CACHE__FROM_FILE,
|
||||
base, filename, parse_ignore_file, ignores)) < 0 ||
|
||||
(error = git_vector_insert(which_list, file)) < 0)
|
||||
git_attr_file__free(file);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int push_one_ignore(void *payload, git_buf *path)
|
||||
{
|
||||
git_ignores *ign = payload;
|
||||
|
||||
ign->depth++;
|
||||
|
||||
return push_ignore_file(
|
||||
ign->repo, ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
|
||||
return push_ignore_file(ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
|
||||
}
|
||||
|
||||
static int get_internal_ignores(git_attr_file **ign, git_repository *repo)
|
||||
static int get_internal_ignores(git_attr_file **out, git_repository *repo)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!(error = git_attr_cache__init(repo)))
|
||||
error = git_attr_cache__internal_file(repo, GIT_IGNORE_INTERNAL, ign);
|
||||
if ((error = git_attr_cache__init(repo)) < 0)
|
||||
return error;
|
||||
|
||||
if (!error && !(*ign)->rules.length)
|
||||
error = parse_ignore_file(repo, NULL, GIT_IGNORE_DEFAULT_RULES, *ign);
|
||||
/* get with NULL parser, gives existing or empty git_attr_file */
|
||||
error = git_attr_cache__get(
|
||||
out, repo, GIT_ATTR_CACHE__FROM_FILE,
|
||||
NULL, GIT_IGNORE_INTERNAL, NULL, NULL);
|
||||
|
||||
/* if internal rules list is empty, insert default rules */
|
||||
if (!error && !(*out)->rules.length)
|
||||
error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, NULL);
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -127,8 +146,7 @@ int git_ignore__for_path(
|
||||
goto cleanup;
|
||||
|
||||
/* set up internals */
|
||||
error = get_internal_ignores(&ignores->ign_internal, repo);
|
||||
if (error < 0)
|
||||
if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* load .gitignore up the path */
|
||||
@ -140,14 +158,16 @@ int git_ignore__for_path(
|
||||
}
|
||||
|
||||
/* load .git/info/exclude */
|
||||
error = push_ignore_file(repo, ignores, &ignores->ign_global,
|
||||
error = push_ignore_file(
|
||||
ignores, &ignores->ign_global,
|
||||
git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* load core.excludesfile */
|
||||
if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
|
||||
error = push_ignore_file(repo, ignores, &ignores->ign_global, NULL,
|
||||
error = push_ignore_file(
|
||||
ignores, &ignores->ign_global, NULL,
|
||||
git_repository_attr_cache(repo)->cfg_excl_file);
|
||||
|
||||
cleanup:
|
||||
@ -165,35 +185,33 @@ int git_ignore__push_dir(git_ignores *ign, const char *dir)
|
||||
ign->depth++;
|
||||
|
||||
return push_ignore_file(
|
||||
ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
|
||||
ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
|
||||
}
|
||||
|
||||
int git_ignore__pop_dir(git_ignores *ign)
|
||||
{
|
||||
if (ign->ign_path.length > 0) {
|
||||
git_attr_file *file = git_vector_last(&ign->ign_path);
|
||||
const char *start, *end, *scan;
|
||||
size_t keylen;
|
||||
const char *start = file->ce->path, *end;
|
||||
|
||||
/* - ign->dir looks something like "a/b/" (or "a/b/c/d/")
|
||||
* - file->key looks something like "0#a/b/.gitignore
|
||||
/* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/")
|
||||
* - file->path looks something like "a/b/.gitignore
|
||||
*
|
||||
* We are popping the last directory off ign->dir. We also want to
|
||||
* remove the file from the vector if the directory part of the key
|
||||
* matches the ign->dir path. We need to test if the "a/b" part of
|
||||
* We are popping the last directory off ign->dir. We also want
|
||||
* to remove the file from the vector if the popped directory
|
||||
* matches the ignore path. We need to test if the "a/b" part of
|
||||
* the file key matches the path we are about to pop.
|
||||
*/
|
||||
|
||||
for (start = end = scan = &file->key[2]; *scan; ++scan)
|
||||
if (*scan == '/')
|
||||
end = scan; /* point 'end' to last '/' in key */
|
||||
keylen = (end - start) + 1;
|
||||
if ((end = strrchr(start, '/')) != NULL) {
|
||||
size_t dirlen = (end - start) + 1;
|
||||
|
||||
if (ign->dir.size >= keylen &&
|
||||
!memcmp(ign->dir.ptr + ign->dir.size - keylen, start, keylen))
|
||||
{
|
||||
git_attr_file__free(git_vector_last(&ign->ign_path));
|
||||
git_vector_pop(&ign->ign_path);
|
||||
if (ign->dir.size >= dirlen &&
|
||||
!memcmp(ign->dir.ptr + ign->dir.size - dirlen, start, dirlen))
|
||||
{
|
||||
git_vector_pop(&ign->ign_path);
|
||||
git_attr_file__free(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +228,7 @@ void git_ignore__free(git_ignores *ignores)
|
||||
unsigned int i;
|
||||
git_attr_file *file;
|
||||
|
||||
/* don't need to free ignores->ign_internal it is cached exactly once */
|
||||
git_attr_file__free(ignores->ign_internal);
|
||||
|
||||
git_vector_foreach(&ignores->ign_path, i, file) {
|
||||
git_attr_file__free(file);
|
||||
@ -283,10 +301,12 @@ int git_ignore_add_rule(
|
||||
const char *rules)
|
||||
{
|
||||
int error;
|
||||
git_attr_file *ign_internal;
|
||||
git_attr_file *ign_internal = NULL;
|
||||
|
||||
if (!(error = get_internal_ignores(&ign_internal, repo)))
|
||||
if (!(error = get_internal_ignores(&ign_internal, repo))) {
|
||||
error = parse_ignore_file(repo, NULL, rules, ign_internal);
|
||||
git_attr_file__free(ign_internal);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -300,8 +320,10 @@ int git_ignore_clear_internal_rules(
|
||||
if (!(error = get_internal_ignores(&ign_internal, repo))) {
|
||||
git_attr_file__clear_rules(ign_internal);
|
||||
|
||||
return parse_ignore_file(
|
||||
error = parse_ignore_file(
|
||||
repo, NULL, GIT_IGNORE_DEFAULT_RULES, ign_internal);
|
||||
|
||||
git_attr_file__free(ign_internal);
|
||||
}
|
||||
|
||||
return error;
|
||||
|
17
src/index.c
17
src/index.c
@ -607,8 +607,15 @@ int git_index_read(git_index *index, int force)
|
||||
}
|
||||
|
||||
updated = git_futils_filestamp_check(&stamp, index->index_file_path);
|
||||
if (updated < 0 || (!updated && !force))
|
||||
if (updated < 0) {
|
||||
giterr_set(
|
||||
GITERR_INDEX,
|
||||
"Failed to read index: '%s' no longer exists",
|
||||
index->index_file_path);
|
||||
return updated;
|
||||
}
|
||||
if (!updated && !force)
|
||||
return 0;
|
||||
|
||||
error = git_futils_readbuffer(&buffer, index->index_file_path);
|
||||
if (error < 0)
|
||||
@ -667,11 +674,11 @@ int git_index_write(git_index *index)
|
||||
if ((error = git_filebuf_commit(&file)) < 0)
|
||||
return error;
|
||||
|
||||
error = git_futils_filestamp_check(&index->stamp, index->index_file_path);
|
||||
if (error < 0)
|
||||
return error;
|
||||
if (git_futils_filestamp_check(&index->stamp, index->index_file_path) < 0)
|
||||
/* index could not be read from disk! */;
|
||||
else
|
||||
index->on_disk = 1;
|
||||
|
||||
index->on_disk = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -232,9 +232,8 @@ unlock:
|
||||
|
||||
void git_sortedcache_updated(git_sortedcache *sc)
|
||||
{
|
||||
/* update filestamp to latest value */
|
||||
if (git_futils_filestamp_check(&sc->stamp, sc->path) < 0)
|
||||
giterr_clear();
|
||||
/* update filestamp to latest value */
|
||||
git_futils_filestamp_check(&sc->stamp, sc->path);
|
||||
}
|
||||
|
||||
/* release all items in sorted cache */
|
||||
|
@ -1693,8 +1693,6 @@ static int submodule_cache_refresh(git_submodule_cache *cache, int refresh)
|
||||
update_gitmod = (wd != NULL) ?
|
||||
git_futils_filestamp_check(&cache->gitmodules_stamp, path.ptr) :
|
||||
(cache->gitmodules_stamp.mtime != 0);
|
||||
if (update_gitmod < 0)
|
||||
giterr_clear();
|
||||
}
|
||||
|
||||
/* clear submodule flags that are to be refreshed */
|
||||
|
@ -11,9 +11,9 @@ void test_attr_file__simple_read(void)
|
||||
git_attr_assignment *assign;
|
||||
git_attr_rule *rule;
|
||||
|
||||
cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr0")));
|
||||
cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0")));
|
||||
|
||||
cl_assert_equal_s(cl_fixture("attr/attr0"), file->key + 2);
|
||||
cl_assert_equal_s(cl_fixture("attr/attr0"), file->ce->path);
|
||||
cl_assert(file->rules.length == 1);
|
||||
|
||||
rule = get_rule(0);
|
||||
@ -37,9 +37,9 @@ void test_attr_file__match_variants(void)
|
||||
git_attr_rule *rule;
|
||||
git_attr_assignment *assign;
|
||||
|
||||
cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr1")));
|
||||
cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1")));
|
||||
|
||||
cl_assert_equal_s(cl_fixture("attr/attr1"), file->key + 2);
|
||||
cl_assert_equal_s(cl_fixture("attr/attr1"), file->ce->path);
|
||||
cl_assert(file->rules.length == 10);
|
||||
|
||||
/* let's do a thorough check of this rule, then just verify
|
||||
@ -121,9 +121,9 @@ void test_attr_file__assign_variants(void)
|
||||
git_attr_rule *rule;
|
||||
git_attr_assignment *assign;
|
||||
|
||||
cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr2")));
|
||||
cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2")));
|
||||
|
||||
cl_assert_equal_s(cl_fixture("attr/attr2"), file->key + 2);
|
||||
cl_assert_equal_s(cl_fixture("attr/attr2"), file->ce->path);
|
||||
cl_assert(file->rules.length == 11);
|
||||
|
||||
check_one_assign(file, 0, 0, "pat0", "simple", EXPECT_TRUE, NULL);
|
||||
@ -187,8 +187,8 @@ void test_attr_file__check_attr_examples(void)
|
||||
git_attr_rule *rule;
|
||||
git_attr_assignment *assign;
|
||||
|
||||
cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr3")));
|
||||
cl_assert_equal_s(cl_fixture("attr/attr3"), file->key + 2);
|
||||
cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3")));
|
||||
cl_assert_equal_s(cl_fixture("attr/attr3"), file->ce->path);
|
||||
cl_assert(file->rules.length == 3);
|
||||
|
||||
rule = get_rule(0);
|
||||
|
@ -9,8 +9,8 @@ void test_attr_lookup__simple(void)
|
||||
git_attr_path path;
|
||||
const char *value = NULL;
|
||||
|
||||
cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr0")));
|
||||
cl_assert_equal_s(cl_fixture("attr/attr0"), file->key + 2);
|
||||
cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0")));
|
||||
cl_assert_equal_s(cl_fixture("attr/attr0"), file->ce->path);
|
||||
cl_assert(file->rules.length == 1);
|
||||
|
||||
cl_git_pass(git_attr_path__init(&path, "test", NULL));
|
||||
@ -129,8 +129,8 @@ void test_attr_lookup__match_variants(void)
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr1")));
|
||||
cl_assert_equal_s(cl_fixture("attr/attr1"), file->key + 2);
|
||||
cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1")));
|
||||
cl_assert_equal_s(cl_fixture("attr/attr1"), file->ce->path);
|
||||
cl_assert(file->rules.length == 10);
|
||||
|
||||
cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL));
|
||||
@ -190,7 +190,7 @@ void test_attr_lookup__assign_variants(void)
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr2")));
|
||||
cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2")));
|
||||
cl_assert(file->rules.length == 11);
|
||||
|
||||
run_test_cases(file, cases, 0);
|
||||
@ -225,7 +225,7 @@ void test_attr_lookup__check_attr_examples(void)
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr3")));
|
||||
cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3")));
|
||||
cl_assert(file->rules.length == 3);
|
||||
|
||||
run_test_cases(file, cases, 0);
|
||||
@ -250,9 +250,9 @@ void test_attr_lookup__from_buffer(void)
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
cl_git_pass(git_attr_file__new(&file, 0, NULL, NULL));
|
||||
cl_git_pass(git_attr_file__new(&file, NULL, 0));
|
||||
|
||||
cl_git_pass(git_attr_file__parse_buffer(NULL, NULL, "a* foo\nabc bar\n* baz", file));
|
||||
cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz", NULL));
|
||||
|
||||
cl_assert(file->rules.length == 3);
|
||||
|
||||
|
@ -1,59 +1,22 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "thread-utils.h"
|
||||
#include "thread_helpers.h"
|
||||
|
||||
static git_repository *_repo;
|
||||
static git_tree *_a, *_b;
|
||||
static git_atomic _counts[4];
|
||||
static int _check_counts;
|
||||
|
||||
#define THREADS 20
|
||||
|
||||
void test_threads_diff__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
static void run_in_parallel(
|
||||
int repeats, int threads, void *(*func)(void *),
|
||||
void (*before_test)(void), void (*after_test)(void))
|
||||
{
|
||||
int r, t, *id = git__calloc(threads, sizeof(int));
|
||||
#ifdef GIT_THREADS
|
||||
git_thread *th = git__calloc(threads, sizeof(git_thread));
|
||||
cl_assert(th != NULL);
|
||||
#else
|
||||
void *th = NULL;
|
||||
#endif
|
||||
|
||||
cl_assert(id != NULL);
|
||||
|
||||
for (r = 0; r < repeats; ++r) {
|
||||
_repo = cl_git_sandbox_reopen(); /* reopen sandbox to flush caches */
|
||||
|
||||
if (before_test) before_test();
|
||||
|
||||
for (t = 0; t < threads; ++t) {
|
||||
id[t] = t;
|
||||
#ifdef GIT_THREADS
|
||||
cl_git_pass(git_thread_create(&th[t], NULL, func, &id[t]));
|
||||
#else
|
||||
cl_assert(func(&id[t]) == &id[t]);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef GIT_THREADS
|
||||
for (t = 0; t < threads; ++t)
|
||||
cl_git_pass(git_thread_join(th[t], NULL));
|
||||
memset(th, 0, threads * sizeof(git_thread));
|
||||
#endif
|
||||
|
||||
if (after_test) after_test();
|
||||
}
|
||||
|
||||
git__free(id);
|
||||
git__free(th);
|
||||
}
|
||||
|
||||
static void setup_trees(void)
|
||||
{
|
||||
_repo = cl_git_sandbox_reopen(); /* reopen sandbox to flush caches */
|
||||
|
||||
cl_git_pass(git_revparse_single(
|
||||
(git_object **)&_a, _repo, "0017bd4ab1^{tree}"));
|
||||
cl_git_pass(git_revparse_single(
|
||||
@ -62,8 +25,6 @@ static void setup_trees(void)
|
||||
memset(_counts, 0, sizeof(_counts));
|
||||
}
|
||||
|
||||
#define THREADS 20
|
||||
|
||||
static void free_trees(void)
|
||||
{
|
||||
git_tree_free(_a); _a = NULL;
|
||||
|
49
tests/threads/iterator.c
Normal file
49
tests/threads/iterator.c
Normal file
@ -0,0 +1,49 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "thread_helpers.h"
|
||||
#include "iterator.h"
|
||||
|
||||
static git_repository *_repo;
|
||||
|
||||
void test_threads_iterator__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
static void *run_workdir_iterator(void *arg)
|
||||
{
|
||||
int error = 0, thread = *(int *)arg;
|
||||
git_iterator *iter;
|
||||
const git_index_entry *entry = NULL;
|
||||
|
||||
cl_git_pass(git_iterator_for_workdir(
|
||||
&iter, _repo, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
|
||||
|
||||
while (!error) {
|
||||
if (entry && entry->mode == GIT_FILEMODE_TREE) {
|
||||
error = git_iterator_advance_into(&entry, iter);
|
||||
|
||||
if (error == GIT_ENOTFOUND)
|
||||
error = git_iterator_advance(&entry, iter);
|
||||
} else {
|
||||
error = git_iterator_advance(&entry, iter);
|
||||
}
|
||||
|
||||
if (!error)
|
||||
(void)git_iterator_current_is_ignored(iter);
|
||||
}
|
||||
|
||||
cl_assert_equal_i(GIT_ITEROVER, error);
|
||||
|
||||
git_iterator_free(iter);
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
void test_threads_iterator__workdir(void)
|
||||
{
|
||||
_repo = cl_git_sandbox_init("status");
|
||||
|
||||
run_in_parallel(
|
||||
1, 20, run_workdir_iterator, NULL, NULL);
|
||||
}
|
44
tests/threads/thread_helpers.c
Normal file
44
tests/threads/thread_helpers.c
Normal file
@ -0,0 +1,44 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "thread_helpers.h"
|
||||
|
||||
void run_in_parallel(
|
||||
int repeats,
|
||||
int threads,
|
||||
void *(*func)(void *),
|
||||
void (*before_test)(void),
|
||||
void (*after_test)(void))
|
||||
{
|
||||
int r, t, *id = git__calloc(threads, sizeof(int));
|
||||
#ifdef GIT_THREADS
|
||||
git_thread *th = git__calloc(threads, sizeof(git_thread));
|
||||
cl_assert(th != NULL);
|
||||
#else
|
||||
void *th = NULL;
|
||||
#endif
|
||||
|
||||
cl_assert(id != NULL);
|
||||
|
||||
for (r = 0; r < repeats; ++r) {
|
||||
if (before_test) before_test();
|
||||
|
||||
for (t = 0; t < threads; ++t) {
|
||||
id[t] = t;
|
||||
#ifdef GIT_THREADS
|
||||
cl_git_pass(git_thread_create(&th[t], NULL, func, &id[t]));
|
||||
#else
|
||||
cl_assert(func(&id[t]) == &id[t]);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef GIT_THREADS
|
||||
for (t = 0; t < threads; ++t)
|
||||
cl_git_pass(git_thread_join(th[t], NULL));
|
||||
memset(th, 0, threads * sizeof(git_thread));
|
||||
#endif
|
||||
|
||||
if (after_test) after_test();
|
||||
}
|
||||
|
||||
git__free(id);
|
||||
git__free(th);
|
||||
}
|
8
tests/threads/thread_helpers.h
Normal file
8
tests/threads/thread_helpers.h
Normal file
@ -0,0 +1,8 @@
|
||||
#include "thread-utils.h"
|
||||
|
||||
void run_in_parallel(
|
||||
int repeats,
|
||||
int threads,
|
||||
void *(*func)(void *),
|
||||
void (*before_test)(void),
|
||||
void (*after_test)(void));
|
Loading…
Reference in New Issue
Block a user