mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 19:37:29 +00:00

This fixes two bugs: * Issue #728 where git_status_file was not working for files that contain spaces. This was caused by reusing the "fnmatch" parsing code from ignore and attribute files to interpret the "pathspec" that constrained the files to apply the status to. In that code, unescaped whitespace was considered terminal to the pattern, so a file with internal whitespace was excluded from the matched files. The fix was to add a mode to that code that allows spaces and tabs inside patterns. This mode only comes into play when parsing in-memory strings. * The other issue was undetected, but it was in the recently added code to reload gitattributes / gitignores when they were changed on disk. That code was not clearing out the old values from the cached file content before reparsing which meant that newly added patterns would be read in, but deleted patterns would not be removed. The fix was to clear the vector of patterns in a cached file before reparsing the file.
678 lines
15 KiB
C
678 lines
15 KiB
C
#include "repository.h"
|
|
#include "fileops.h"
|
|
#include "config.h"
|
|
#include <ctype.h>
|
|
|
|
GIT__USE_STRMAP;
|
|
|
|
static int collect_attr_files(
|
|
git_repository *repo,
|
|
uint32_t flags,
|
|
const char *path,
|
|
git_vector *files);
|
|
|
|
|
|
int git_attr_get(
|
|
const char **value,
|
|
git_repository *repo,
|
|
uint32_t flags,
|
|
const char *pathname,
|
|
const char *name)
|
|
{
|
|
int error;
|
|
git_attr_path path;
|
|
git_vector files = GIT_VECTOR_INIT;
|
|
unsigned int i, j;
|
|
git_attr_file *file;
|
|
git_attr_name attr;
|
|
git_attr_rule *rule;
|
|
|
|
*value = NULL;
|
|
|
|
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
|
|
return -1;
|
|
|
|
if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
|
|
goto cleanup;
|
|
|
|
attr.name = name;
|
|
attr.name_hash = git_attr_file__name_hash(name);
|
|
|
|
git_vector_foreach(&files, i, file) {
|
|
|
|
git_attr_file__foreach_matching_rule(file, &path, j, rule) {
|
|
int pos = git_vector_bsearch(&rule->assigns, &attr);
|
|
if (pos >= 0) {
|
|
*value = ((git_attr_assignment *)git_vector_get(
|
|
&rule->assigns, pos))->value;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
git_vector_free(&files);
|
|
git_attr_path__free(&path);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
git_attr_name name;
|
|
git_attr_assignment *found;
|
|
} attr_get_many_info;
|
|
|
|
int git_attr_get_many(
|
|
const char **values,
|
|
git_repository *repo,
|
|
uint32_t flags,
|
|
const char *pathname,
|
|
size_t num_attr,
|
|
const char **names)
|
|
{
|
|
int error;
|
|
git_attr_path path;
|
|
git_vector files = GIT_VECTOR_INIT;
|
|
unsigned int i, j, k;
|
|
git_attr_file *file;
|
|
git_attr_rule *rule;
|
|
attr_get_many_info *info = NULL;
|
|
size_t num_found = 0;
|
|
|
|
memset((void *)values, 0, sizeof(const char *) * num_attr);
|
|
|
|
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
|
|
return -1;
|
|
|
|
if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
|
|
goto cleanup;
|
|
|
|
info = git__calloc(num_attr, sizeof(attr_get_many_info));
|
|
GITERR_CHECK_ALLOC(info);
|
|
|
|
git_vector_foreach(&files, i, file) {
|
|
|
|
git_attr_file__foreach_matching_rule(file, &path, j, rule) {
|
|
|
|
for (k = 0; k < num_attr; k++) {
|
|
int pos;
|
|
|
|
if (info[k].found != NULL) /* already found assignment */
|
|
continue;
|
|
|
|
if (!info[k].name.name) {
|
|
info[k].name.name = names[k];
|
|
info[k].name.name_hash = git_attr_file__name_hash(names[k]);
|
|
}
|
|
|
|
pos = git_vector_bsearch(&rule->assigns, &info[k].name);
|
|
if (pos >= 0) {
|
|
info[k].found = (git_attr_assignment *)
|
|
git_vector_get(&rule->assigns, pos);
|
|
values[k] = info[k].found->value;
|
|
|
|
if (++num_found == num_attr)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
git_vector_free(&files);
|
|
git_attr_path__free(&path);
|
|
git__free(info);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
int git_attr_foreach(
|
|
git_repository *repo,
|
|
uint32_t flags,
|
|
const char *pathname,
|
|
int (*callback)(const char *name, const char *value, void *payload),
|
|
void *payload)
|
|
{
|
|
int error;
|
|
git_attr_path path;
|
|
git_vector files = GIT_VECTOR_INIT;
|
|
unsigned int i, j, k;
|
|
git_attr_file *file;
|
|
git_attr_rule *rule;
|
|
git_attr_assignment *assign;
|
|
git_strmap *seen = NULL;
|
|
|
|
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
|
|
return -1;
|
|
|
|
if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
|
|
goto cleanup;
|
|
|
|
seen = git_strmap_alloc();
|
|
GITERR_CHECK_ALLOC(seen);
|
|
|
|
git_vector_foreach(&files, i, file) {
|
|
|
|
git_attr_file__foreach_matching_rule(file, &path, j, rule) {
|
|
|
|
git_vector_foreach(&rule->assigns, k, assign) {
|
|
/* skip if higher priority assignment was already seen */
|
|
if (git_strmap_exists(seen, assign->name))
|
|
continue;
|
|
|
|
git_strmap_insert(seen, assign->name, assign, error);
|
|
if (error >= 0)
|
|
error = callback(assign->name, assign->value, payload);
|
|
|
|
if (error != 0)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
git_strmap_free(seen);
|
|
git_vector_free(&files);
|
|
git_attr_path__free(&path);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
int git_attr_add_macro(
|
|
git_repository *repo,
|
|
const char *name,
|
|
const char *values)
|
|
{
|
|
int error;
|
|
git_attr_rule *macro = NULL;
|
|
git_pool *pool;
|
|
|
|
if (git_attr_cache__init(repo) < 0)
|
|
return -1;
|
|
|
|
macro = git__calloc(1, sizeof(git_attr_rule));
|
|
GITERR_CHECK_ALLOC(macro);
|
|
|
|
pool = &git_repository_attr_cache(repo)->pool;
|
|
|
|
macro->match.pattern = git_pool_strdup(pool, name);
|
|
GITERR_CHECK_ALLOC(macro->match.pattern);
|
|
|
|
macro->match.length = strlen(macro->match.pattern);
|
|
macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
|
|
|
|
error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values);
|
|
|
|
if (!error)
|
|
error = git_attr_cache__insert_macro(repo, macro);
|
|
|
|
if (error < 0)
|
|
git_attr_rule__free(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_attr_file_stat_sig *sig,
|
|
const char *filename)
|
|
{
|
|
int error;
|
|
git_buf content = GIT_BUF_INIT;
|
|
struct stat st;
|
|
|
|
if (p_stat(filename, &st) < 0)
|
|
return GIT_ENOTFOUND;
|
|
|
|
if (sig != NULL &&
|
|
(git_time_t)st.st_mtime == sig->seconds &&
|
|
(git_off_t)st.st_size == sig->size &&
|
|
(unsigned int)st.st_ino == sig->ino)
|
|
return GIT_ENOTFOUND;
|
|
|
|
error = git_futils_readbuffer_updated(&content, filename, NULL, NULL);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
if (sig != NULL) {
|
|
sig->seconds = (git_time_t)st.st_mtime;
|
|
sig->size = (git_off_t)st.st_size;
|
|
sig->ino = (unsigned int)st.st_ino;
|
|
}
|
|
|
|
*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;
|
|
git_index *index;
|
|
git_index_entry *entry;
|
|
|
|
if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
|
|
(error = git_index_find(index, relfile)) < 0)
|
|
return error;
|
|
|
|
entry = git_index_get(index, error);
|
|
|
|
if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0)
|
|
return GIT_ENOTFOUND;
|
|
|
|
if ((error = git_blob_lookup(blob, repo, &entry->oid)) < 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;
|
|
|
|
cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr);
|
|
|
|
git_buf_free(&cache_key);
|
|
|
|
if (git_strmap_valid_index(cache->files, cache_pos))
|
|
*file = git_strmap_value_at(cache->files, cache_pos);
|
|
|
|
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 = 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);
|
|
return 0;
|
|
}
|
|
|
|
if (git_attr_file__new(file, 0, filename, &cache->pool) < 0)
|
|
return -1;
|
|
|
|
git_strmap_insert(cache->files, (*file)->key + 2, *file, error);
|
|
if (error > 0)
|
|
error = 0;
|
|
|
|
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,
|
|
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_attr_file_stat_sig st;
|
|
|
|
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) {
|
|
if (file)
|
|
memcpy(&st, &file->cache_data.st, sizeof(st));
|
|
else
|
|
memset(&st, 0, sizeof(st));
|
|
|
|
error = load_attr_file(&content, &st, 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, content, file)) < 0)
|
|
goto finish;
|
|
|
|
git_strmap_insert(cache->files, file->key, file, error);
|
|
if (error > 0)
|
|
error = 0;
|
|
|
|
/* remember "cache buster" file signature */
|
|
if (blob)
|
|
git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob));
|
|
else
|
|
memcpy(&file->cache_data.st, &st, sizeof(st));
|
|
|
|
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,(S))
|
|
|
|
typedef struct {
|
|
git_repository *repo;
|
|
uint32_t flags;
|
|
const char *workdir;
|
|
git_index *index;
|
|
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)
|
|
{
|
|
int count = 0;
|
|
|
|
switch (flags & 0x03) {
|
|
case GIT_ATTR_CHECK_FILE_THEN_INDEX:
|
|
if (has_wd)
|
|
srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
|
|
if (has_index)
|
|
srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
|
|
break;
|
|
case GIT_ATTR_CHECK_INDEX_THEN_FILE:
|
|
if (has_index)
|
|
srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
|
|
if (has_wd)
|
|
srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
|
|
break;
|
|
case GIT_ATTR_CHECK_INDEX_ONLY:
|
|
if (has_index)
|
|
srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
|
|
break;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
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];
|
|
|
|
n_src = git_attr_cache__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, info->files);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int collect_attr_files(
|
|
git_repository *repo,
|
|
uint32_t flags,
|
|
const char *path,
|
|
git_vector *files)
|
|
{
|
|
int error;
|
|
git_buf dir = GIT_BUF_INIT;
|
|
const char *workdir = git_repository_workdir(repo);
|
|
attr_walk_up_info info;
|
|
|
|
if (git_attr_cache__init(repo) < 0 ||
|
|
git_vector_init(files, 4, NULL) < 0)
|
|
return -1;
|
|
|
|
/* Resolve path in a non-bare repo */
|
|
if (workdir != NULL)
|
|
error = git_path_find_dir(&dir, path, workdir);
|
|
else
|
|
error = git_path_dirname_r(&dir, path);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
/* in precendence order highest to lowest:
|
|
* - $GIT_DIR/info/attributes
|
|
* - path components with .gitattributes
|
|
* - config core.attributesfile
|
|
* - $GIT_PREFIX/etc/gitattributes
|
|
*/
|
|
|
|
error = push_attr_file(
|
|
repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
info.repo = repo;
|
|
info.flags = flags;
|
|
info.workdir = workdir;
|
|
if (git_repository_index__weakptr(&info.index, repo) < 0)
|
|
giterr_clear(); /* no error even if there is no index */
|
|
info.files = files;
|
|
|
|
error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
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);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
|
|
error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
|
|
if (!error)
|
|
error = push_attr_file(repo, files, NULL, dir.ptr);
|
|
else if (error == GIT_ENOTFOUND)
|
|
error = 0;
|
|
}
|
|
|
|
cleanup:
|
|
if (error < 0)
|
|
git_vector_free(files);
|
|
git_buf_free(&dir);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
int git_attr_cache__init(git_repository *repo)
|
|
{
|
|
int ret;
|
|
git_attr_cache *cache = git_repository_attr_cache(repo);
|
|
git_config *cfg;
|
|
|
|
if (cache->initialized)
|
|
return 0;
|
|
|
|
/* cache config settings for attributes and ignores */
|
|
if (git_repository_config__weakptr(&cfg, repo) < 0)
|
|
return -1;
|
|
|
|
ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG);
|
|
if (ret < 0 && ret != GIT_ENOTFOUND)
|
|
return ret;
|
|
|
|
ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG);
|
|
if (ret < 0 && ret != GIT_ENOTFOUND)
|
|
return ret;
|
|
|
|
giterr_clear();
|
|
|
|
/* allocate hashtable for attribute and ignore file contents */
|
|
if (cache->files == NULL) {
|
|
cache->files = git_strmap_alloc();
|
|
GITERR_CHECK_ALLOC(cache->files);
|
|
}
|
|
|
|
/* allocate hashtable for attribute macros */
|
|
if (cache->macros == NULL) {
|
|
cache->macros = git_strmap_alloc();
|
|
GITERR_CHECK_ALLOC(cache->macros);
|
|
}
|
|
|
|
/* allocate string pool */
|
|
if (git_pool_init(&cache->pool, 1, 0) < 0)
|
|
return -1;
|
|
|
|
cache->initialized = 1;
|
|
|
|
/* insert default macros */
|
|
return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
|
|
}
|
|
|
|
void git_attr_cache_flush(
|
|
git_repository *repo)
|
|
{
|
|
git_attr_cache *cache;
|
|
|
|
if (!repo)
|
|
return;
|
|
|
|
cache = git_repository_attr_cache(repo);
|
|
|
|
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);
|
|
|
|
cache->initialized = 0;
|
|
}
|
|
|
|
int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
|
|
{
|
|
git_strmap *macros = git_repository_attr_cache(repo)->macros;
|
|
int error;
|
|
|
|
/* TODO: generate warning log if (macro->assigns.length == 0) */
|
|
if (macro->assigns.length == 0)
|
|
return 0;
|
|
|
|
git_strmap_insert(macros, macro->match.pattern, macro, error);
|
|
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);
|
|
}
|
|
|