diff options
Diffstat (limited to 'src/attr_file.c')
-rw-r--r-- | src/attr_file.c | 1027 |
1 files changed, 0 insertions, 1027 deletions
diff --git a/src/attr_file.c b/src/attr_file.c deleted file mode 100644 index 71bd20a0f..000000000 --- a/src/attr_file.c +++ /dev/null @@ -1,1027 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "attr_file.h" - -#include "repository.h" -#include "filebuf.h" -#include "attrcache.h" -#include "git2/blob.h" -#include "git2/tree.h" -#include "blob.h" -#include "index.h" -#include "wildmatch.h" -#include <ctype.h> - -static void attr_file_free(git_attr_file *file) -{ - bool unlock = !git_mutex_lock(&file->lock); - git_attr_file__clear_rules(file, false); - git_pool_clear(&file->pool); - if (unlock) - git_mutex_unlock(&file->lock); - git_mutex_free(&file->lock); - - git__memzero(file, sizeof(*file)); - git__free(file); -} - -int git_attr_file__new( - git_attr_file **out, - git_attr_file_entry *entry, - git_attr_file_source *source) -{ - git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file)); - GIT_ERROR_CHECK_ALLOC(attrs); - - if (git_mutex_init(&attrs->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to initialize lock"); - goto on_error; - } - - if (git_pool_init(&attrs->pool, 1) < 0) - goto on_error; - - GIT_REFCOUNT_INC(attrs); - attrs->entry = entry; - memcpy(&attrs->source, source, sizeof(git_attr_file_source)); - *out = attrs; - return 0; - -on_error: - git__free(attrs); - return -1; -} - -int git_attr_file__clear_rules(git_attr_file *file, bool need_lock) -{ - unsigned int i; - git_attr_rule *rule; - - if (need_lock && git_mutex_lock(&file->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); - return -1; - } - - git_vector_foreach(&file->rules, i, rule) - git_attr_rule__free(rule); - git_vector_free(&file->rules); - - if (need_lock) - git_mutex_unlock(&file->lock); - - return 0; -} - -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_session *attr_session, - git_attr_file_entry *entry, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros) -{ - int error = 0; - git_commit *commit = NULL; - git_tree *tree = NULL; - git_tree_entry *tree_entry = NULL; - git_blob *blob = NULL; - git_buf content = GIT_BUF_INIT; - const char *content_str; - git_attr_file *file; - struct stat st; - bool nonexistent = false; - int bom_offset; - git_buf_bom_t bom; - git_oid id; - git_object_size_t blobsize; - - *out = NULL; - - switch (source->type) { - case GIT_ATTR_FILE_SOURCE_MEMORY: - /* in-memory attribute file doesn't need data */ - break; - case GIT_ATTR_FILE_SOURCE_INDEX: { - if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 || - (error = git_blob_lookup(&blob, repo, &id)) < 0) - return error; - - /* Do not assume that data straight from the ODB is NULL-terminated; - * copy the contents of a file to a buffer to work on */ - blobsize = git_blob_rawsize(blob); - - GIT_ERROR_CHECK_BLOBSIZE(blobsize); - git_buf_put(&content, git_blob_rawcontent(blob), (size_t)blobsize); - break; - } - case GIT_ATTR_FILE_SOURCE_FILE: { - int fd = -1; - - /* For open or read errors, pretend that we got ENOTFOUND. */ - /* TODO: issue warning when warning API is available */ - - if (p_stat(entry->fullpath, &st) < 0 || - S_ISDIR(st.st_mode) || - (fd = git_futils_open_ro(entry->fullpath)) < 0 || - (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0) - nonexistent = true; - - if (fd >= 0) - p_close(fd); - - break; - } - case GIT_ATTR_FILE_SOURCE_HEAD: - case GIT_ATTR_FILE_SOURCE_COMMIT: { - if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) { - if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0 || - (error = git_commit_tree(&tree, commit)) < 0) - goto cleanup; - } else { - if ((error = git_repository_head_tree(&tree, repo)) < 0) - goto cleanup; - } - - if ((error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0) { - /* - * If the attributes file does not exist, we can - * cache an empty file for this commit to prevent - * needless future lookups. - */ - if (error == GIT_ENOTFOUND) { - error = 0; - break; - } - - goto cleanup; - } - - if ((error = git_blob_lookup(&blob, repo, git_tree_entry_id(tree_entry))) < 0) - goto cleanup; - - /* - * Do not assume that data straight from the ODB is NULL-terminated; - * copy the contents of a file to a buffer to work on. - */ - blobsize = git_blob_rawsize(blob); - - GIT_ERROR_CHECK_BLOBSIZE(blobsize); - if ((error = git_buf_put(&content, - git_blob_rawcontent(blob), (size_t)blobsize)) < 0) - goto cleanup; - - break; - } - default: - git_error_set(GIT_ERROR_INVALID, "unknown file source %d", source->type); - return -1; - } - - if ((error = git_attr_file__new(&file, entry, source)) < 0) - goto cleanup; - - /* advance over a UTF8 BOM */ - content_str = git_buf_cstr(&content); - bom_offset = git_buf_detect_bom(&bom, &content); - - if (bom == GIT_BUF_BOM_UTF8) - content_str += bom_offset; - - /* store the key of the attr_reader; don't bother with cache - * invalidation during the same attr reader session. - */ - if (attr_session) - file->session_key = attr_session->key; - - if (parser && (error = parser(repo, file, content_str, allow_macros)) < 0) { - git_attr_file__free(file); - goto cleanup; - } - - /* write cache breakers */ - if (nonexistent) - file->nonexistent = 1; - else if (source->type == GIT_ATTR_FILE_SOURCE_INDEX) - git_oid_cpy(&file->cache_data.oid, git_blob_id(blob)); - else if (source->type == GIT_ATTR_FILE_SOURCE_HEAD) - git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); - else if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) - git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); - else if (source->type == GIT_ATTR_FILE_SOURCE_FILE) - git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st); - /* else always cacheable */ - - *out = file; - -cleanup: - git_blob_free(blob); - git_tree_entry_free(tree_entry); - git_tree_free(tree); - git_commit_free(commit); - git_buf_dispose(&content); - - return error; -} - -int git_attr_file__out_of_date( - git_repository *repo, - git_attr_session *attr_session, - git_attr_file *file, - git_attr_file_source *source) -{ - if (!file) - return 1; - - /* we are never out of date if we just created this data in the same - * attr_session; otherwise, nonexistent files must be invalidated - */ - if (attr_session && attr_session->key == file->session_key) - return 0; - else if (file->nonexistent) - return 1; - - switch (file->source.type) { - case GIT_ATTR_FILE_SOURCE_MEMORY: - return 0; - - case GIT_ATTR_FILE_SOURCE_FILE: - return git_futils_filestamp_check( - &file->cache_data.stamp, file->entry->fullpath); - - case GIT_ATTR_FILE_SOURCE_INDEX: { - int error; - git_oid id; - - if ((error = attr_file_oid_from_index( - &id, repo, file->entry->path)) < 0) - return error; - - return (git_oid__cmp(&file->cache_data.oid, &id) != 0); - } - - case GIT_ATTR_FILE_SOURCE_HEAD: { - git_tree *tree = NULL; - int error = git_repository_head_tree(&tree, repo); - - if (error < 0) - return error; - - error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); - - git_tree_free(tree); - return error; - } - - case GIT_ATTR_FILE_SOURCE_COMMIT: { - git_commit *commit = NULL; - git_tree *tree = NULL; - int error; - - if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0) - return error; - - error = git_commit_tree(&tree, commit); - git_commit_free(commit); - - if (error < 0) - return error; - - error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); - - git_tree_free(tree); - return error; - } - - default: - git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source.type); - return -1; - } -} - -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( - git_attr_fnmatch *spec, - git_pool *pool, - const char *pattern); - -int git_attr_file__parse_buffer( - git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) -{ - const char *scan = data, *context = NULL; - git_attr_rule *rule = NULL; - int error = 0; - - /* If subdir file path, convert context for file paths */ - if (attrs->entry && git_path_root(attrs->entry->path) < 0 && - !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE)) - context = attrs->entry->path; - - if (git_mutex_lock(&attrs->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); - return -1; - } - - while (!error && *scan) { - /* Allocate rule if needed, otherwise re-use previous rule */ - if (!rule) { - rule = git__calloc(1, sizeof(*rule)); - GIT_ERROR_CHECK_ALLOC(rule); - } else - git_attr_rule__clear(rule); - - rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO; - - /* Parse the next "pattern attr attr attr" line */ - if ((error = git_attr_fnmatch__parse(&rule->match, &attrs->pool, context, &scan)) < 0 || - (error = git_attr_assignment__parse(repo, &attrs->pool, &rule->assigns, &scan)) < 0) - { - if (error != GIT_ENOTFOUND) - goto out; - error = 0; - continue; - } - - if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) { - /* TODO: warning if macro found in file below repo root */ - if (!allow_macros) - continue; - if ((error = git_attr_cache__insert_macro(repo, rule)) < 0) - goto out; - } else if ((error = git_vector_insert(&attrs->rules, rule)) < 0) - goto out; - - rule = NULL; - } - -out: - git_mutex_unlock(&attrs->lock); - git_attr_rule__free(rule); - - return error; -} - -uint32_t git_attr_file__name_hash(const char *name) -{ - uint32_t h = 5381; - int c; - - GIT_ASSERT_ARG(name); - - while ((c = (int)*name++) != 0) - h = ((h << 5) + h) + c; - return h; -} - -int git_attr_file__lookup_one( - git_attr_file *file, - git_attr_path *path, - const char *attr, - const char **value) -{ - size_t i; - git_attr_name name; - git_attr_rule *rule; - - *value = NULL; - - name.name = attr; - name.name_hash = git_attr_file__name_hash(attr); - - git_attr_file__foreach_matching_rule(file, path, i, rule) { - size_t pos; - - if (!git_vector_bsearch(&pos, &rule->assigns, &name)) { - *value = ((git_attr_assignment *) - git_vector_get(&rule->assigns, pos))->value; - break; - } - } - - return 0; -} - -int git_attr_file__load_standalone(git_attr_file **out, const char *path) -{ - git_buf content = GIT_BUF_INIT; - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; - git_attr_file *file = NULL; - int error; - - if ((error = git_futils_readbuffer(&content, path)) < 0) - goto out; - - /* - * 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_attr_file__new(&file, NULL, &source)) < 0 || - (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 || - (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0) - goto out; - - *out = file; -out: - if (error < 0) - git_attr_file__free(file); - git_buf_dispose(&content); - - return error; -} - -bool git_attr_fnmatch__match( - git_attr_fnmatch *match, - git_attr_path *path) -{ - const char *relpath = path->path; - const char *filename; - int flags = 0; - - /* - * If the rule was generated in a subdirectory, we must only - * use it for paths inside that directory. We can thus return - * a non-match if the prefixes don't match. - */ - if (match->containing_dir) { - if (match->flags & GIT_ATTR_FNMATCH_ICASE) { - if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length)) - return 0; - } else { - if (git__prefixcmp(path->path, match->containing_dir)) - return 0; - } - - relpath += match->containing_dir_length; - } - - if (match->flags & GIT_ATTR_FNMATCH_ICASE) - flags |= WM_CASEFOLD; - - if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) { - filename = relpath; - flags |= WM_PATHNAME; - } else { - filename = path->basename; - } - - if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { - bool samename; - - /* - * for attribute checks or checks at the root of this match's - * containing_dir (or root of the repository if no containing_dir), - * do not match. - */ - if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || - path->basename == relpath) - return false; - - /* fail match if this is a file with same name as ignored folder */ - samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? - !strcasecmp(match->pattern, relpath) : - !strcmp(match->pattern, relpath); - - if (samename) - return false; - - return (wildmatch(match->pattern, relpath, flags) == WM_MATCH); - } - - return (wildmatch(match->pattern, filename, flags) == WM_MATCH); -} - -bool git_attr_rule__match( - git_attr_rule *rule, - git_attr_path *path) -{ - bool matched = git_attr_fnmatch__match(&rule->match, path); - - if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) - matched = !matched; - - return matched; -} - -git_attr_assignment *git_attr_rule__lookup_assignment( - git_attr_rule *rule, const char *name) -{ - size_t pos; - git_attr_name key; - key.name = name; - key.name_hash = git_attr_file__name_hash(name); - - if (git_vector_bsearch(&pos, &rule->assigns, &key)) - return NULL; - - return git_vector_get(&rule->assigns, pos); -} - -int git_attr_path__init( - git_attr_path *info, - const char *path, - const char *base, - git_dir_flag dir_flag) -{ - ssize_t root; - - /* build full path as best we can */ - git_buf_init(&info->full, 0); - - if (git_path_join_unrooted(&info->full, path, base, &root) < 0) - return -1; - - info->path = info->full.ptr + root; - - /* remove trailing slashes */ - while (info->full.size > 0) { - if (info->full.ptr[info->full.size - 1] != '/') - break; - info->full.size--; - } - info->full.ptr[info->full.size] = '\0'; - - /* skip leading slashes in path */ - while (*info->path == '/') - info->path++; - - /* find trailing basename component */ - info->basename = strrchr(info->path, '/'); - if (info->basename) - info->basename++; - if (!info->basename || !*info->basename) - info->basename = info->path; - - switch (dir_flag) - { - case GIT_DIR_FLAG_FALSE: - info->is_dir = 0; - break; - - case GIT_DIR_FLAG_TRUE: - info->is_dir = 1; - break; - - case GIT_DIR_FLAG_UNKNOWN: - default: - info->is_dir = (int)git_path_isdir(info->full.ptr); - break; - } - - return 0; -} - -void git_attr_path__free(git_attr_path *info) -{ - git_buf_dispose(&info->full); - info->path = NULL; - info->basename = NULL; -} - -/* - * From gitattributes(5): - * - * Patterns have the following format: - * - * - A blank line matches no files, so it can serve as a separator for - * readability. - * - * - A line starting with # serves as a comment. - * - * - An optional prefix ! which negates the pattern; any matching file - * excluded by a previous pattern will become included again. If a negated - * pattern matches, this will override lower precedence patterns sources. - * - * - If the pattern ends with a slash, it is removed for the purpose of the - * following description, but it would only find a match with a directory. In - * other words, foo/ will match a directory foo and paths underneath it, but - * will not match a regular file or a symbolic link foo (this is consistent - * with the way how pathspec works in general in git). - * - * - If the pattern does not contain a slash /, git treats it as a shell glob - * pattern and checks for a match against the pathname without leading - * directories. - * - * - Otherwise, git treats the pattern as a shell glob suitable for consumption - * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will - * not match a / in the pathname. For example, "Documentation/\*.html" matches - * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading - * slash matches the beginning of the pathname; for example, "/\*.c" matches - * "cat-file.c" but not "mozilla-sha1/sha1.c". - */ - -/* - * Determine the length of trailing spaces. Escaped spaces do not count as - * trailing whitespace. - */ -static size_t trailing_space_length(const char *p, size_t len) -{ - size_t n, i; - for (n = len; n; n--) { - if (p[n-1] != ' ' && p[n-1] != '\t') - break; - - /* - * Count escape-characters before space. In case where it's an - * even number of escape characters, then the escape char itself - * is escaped and the whitespace is an unescaped whitespace. - * Otherwise, the last escape char is not escaped and the - * whitespace in an escaped whitespace. - */ - i = n; - while (i > 1 && p[i-2] == '\\') - i--; - if ((n - i) % 2) - break; - } - return len - n; -} - -static size_t unescape_spaces(char *str) -{ - char *scan, *pos = str; - bool escaped = false; - - if (!str) - return 0; - - for (scan = str; *scan; scan++) { - if (!escaped && *scan == '\\') { - escaped = true; - continue; - } - - /* Only insert the escape character for escaped non-spaces */ - if (escaped && !git__isspace(*scan)) - *pos++ = '\\'; - - *pos++ = *scan; - escaped = false; - } - - if (pos != scan) - *pos = '\0'; - - return (pos - str); -} - -/* - * This will return 0 if the spec was filled out, - * GIT_ENOTFOUND if the fnmatch does not require matching, or - * another error code there was an actual problem. - */ -int git_attr_fnmatch__parse( - git_attr_fnmatch *spec, - git_pool *pool, - const char *context, - const char **base) -{ - const char *pattern, *scan; - int slash_count, allow_space; - bool escaped; - - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(base && *base); - - if (parse_optimized_patterns(spec, pool, *base)) - return 0; - - spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING); - allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0); - - pattern = *base; - - while (!allow_space && git__isspace(*pattern)) - pattern++; - - if (!*pattern || *pattern == '#' || *pattern == '\n' || - (*pattern == '\r' && *(pattern + 1) == '\n')) { - *base = git__next_line(pattern); - return GIT_ENOTFOUND; - } - - if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) { - if (strncmp(pattern, "[attr]", 6) == 0) { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; - pattern += 6; - } - /* else a character range like [a-e]* which is accepted */ - } - - if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; - pattern++; - } - - slash_count = 0; - escaped = false; - /* Scan until a non-escaped whitespace. */ - for (scan = pattern; *scan != '\0'; ++scan) { - char c = *scan; - - if (c == '\\' && !escaped) { - escaped = true; - continue; - } else if (git__isspace(c) && !escaped) { - if (!allow_space || (c != ' ' && c != '\t' && c != '\r')) - break; - } else if (c == '/') { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; - slash_count++; - - if (slash_count == 1 && pattern == scan) - pattern++; - } else if (git__iswildcard(c) && !escaped) { - /* remember if we see an unescaped wildcard in pattern */ - spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD; - } - - escaped = false; - } - - *base = scan; - - if ((spec->length = scan - pattern) == 0) - return GIT_ENOTFOUND; - - /* - * Remove one trailing \r in case this is a CRLF delimited - * file, in the case of Icon\r\r\n, we still leave the first - * \r there to match against. - */ - if (pattern[spec->length - 1] == '\r') - if (--spec->length == 0) - return GIT_ENOTFOUND; - - /* Remove trailing spaces. */ - spec->length -= trailing_space_length(pattern, spec->length); - - if (spec->length == 0) - return GIT_ENOTFOUND; - - if (pattern[spec->length - 1] == '/') { - spec->length--; - spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; - if (--slash_count <= 0) - spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; - } - - if (context) { - char *slash = strrchr(context, '/'); - size_t len; - if (slash) { - /* include the slash for easier matching */ - len = slash - context + 1; - spec->containing_dir = git_pool_strndup(pool, context, len); - spec->containing_dir_length = len; - } - } - - spec->pattern = git_pool_strndup(pool, pattern, spec->length); - - if (!spec->pattern) { - *base = git__next_line(pattern); - return -1; - } else { - /* strip '\' that might have been used for internal whitespace */ - spec->length = unescape_spaces(spec->pattern); - } - - return 0; -} - -static bool parse_optimized_patterns( - git_attr_fnmatch *spec, - git_pool *pool, - const char *pattern) -{ - if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) { - spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL; - spec->pattern = git_pool_strndup(pool, pattern, 1); - spec->length = 1; - - return true; - } - - return false; -} - -static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) -{ - const git_attr_name *a = a_raw; - const git_attr_name *b = b_raw; - - if (b->name_hash < a->name_hash) - return 1; - else if (b->name_hash > a->name_hash) - return -1; - else - return strcmp(b->name, a->name); -} - -static void git_attr_assignment__free(git_attr_assignment *assign) -{ - /* name and value are stored in a git_pool associated with the - * git_attr_file, so they do not need to be freed here - */ - assign->name = NULL; - assign->value = NULL; - git__free(assign); -} - -static int merge_assignments(void **old_raw, void *new_raw) -{ - git_attr_assignment **old = (git_attr_assignment **)old_raw; - git_attr_assignment *new = (git_attr_assignment *)new_raw; - - GIT_REFCOUNT_DEC(*old, git_attr_assignment__free); - *old = new; - return GIT_EEXISTS; -} - -int git_attr_assignment__parse( - git_repository *repo, - git_pool *pool, - git_vector *assigns, - const char **base) -{ - int error; - const char *scan = *base; - git_attr_assignment *assign = NULL; - - GIT_ASSERT_ARG(assigns && !assigns->length); - - git_vector_set_cmp(assigns, sort_by_hash_and_name); - - while (*scan && *scan != '\n') { - const char *name_start, *value_start; - - /* skip leading blanks */ - while (git__isspace(*scan) && *scan != '\n') scan++; - - /* allocate assign if needed */ - if (!assign) { - assign = git__calloc(1, sizeof(git_attr_assignment)); - GIT_ERROR_CHECK_ALLOC(assign); - GIT_REFCOUNT_INC(assign); - } - - assign->name_hash = 5381; - assign->value = git_attr__true; - - /* look for magic name prefixes */ - if (*scan == '-') { - assign->value = git_attr__false; - scan++; - } else if (*scan == '!') { - assign->value = git_attr__unset; /* explicit unspecified state */ - scan++; - } else if (*scan == '#') /* comment rest of line */ - break; - - /* find the name */ - name_start = scan; - while (*scan && !git__isspace(*scan) && *scan != '=') { - assign->name_hash = - ((assign->name_hash << 5) + assign->name_hash) + *scan; - scan++; - } - if (scan == name_start) { - /* must have found lone prefix (" - ") or leading = ("=foo") - * or end of buffer -- advance until whitespace and continue - */ - while (*scan && !git__isspace(*scan)) scan++; - continue; - } - - /* allocate permanent storage for name */ - assign->name = git_pool_strndup(pool, name_start, scan - name_start); - GIT_ERROR_CHECK_ALLOC(assign->name); - - /* if there is an equals sign, find the value */ - if (*scan == '=') { - for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan); - - /* if we found a value, allocate permanent storage for it */ - if (scan > value_start) { - assign->value = git_pool_strndup(pool, value_start, scan - value_start); - GIT_ERROR_CHECK_ALLOC(assign->value); - } - } - - /* expand macros (if given a repo with a macro cache) */ - if (repo != NULL && assign->value == git_attr__true) { - git_attr_rule *macro = - git_attr_cache__lookup_macro(repo, assign->name); - - if (macro != NULL) { - unsigned int i; - git_attr_assignment *massign; - - git_vector_foreach(¯o->assigns, i, massign) { - GIT_REFCOUNT_INC(massign); - - error = git_vector_insert_sorted( - assigns, massign, &merge_assignments); - if (error < 0 && error != GIT_EEXISTS) { - git_attr_assignment__free(assign); - return error; - } - } - } - } - - /* insert allocated assign into vector */ - error = git_vector_insert_sorted(assigns, assign, &merge_assignments); - if (error < 0 && error != GIT_EEXISTS) - return error; - - /* clear assign since it is now "owned" by the vector */ - assign = NULL; - } - - if (assign != NULL) - git_attr_assignment__free(assign); - - *base = git__next_line(scan); - - return (assigns->length == 0) ? GIT_ENOTFOUND : 0; -} - -static void git_attr_rule__clear(git_attr_rule *rule) -{ - unsigned int i; - git_attr_assignment *assign; - - if (!rule) - return; - - if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { - git_vector_foreach(&rule->assigns, i, assign) - GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); - git_vector_free(&rule->assigns); - } - - /* match.pattern is stored in a git_pool, so no need to free */ - rule->match.pattern = NULL; - rule->match.length = 0; -} - -void git_attr_rule__free(git_attr_rule *rule) -{ - git_attr_rule__clear(rule); - git__free(rule); -} - -int git_attr_session__init(git_attr_session *session, git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - - memset(session, 0, sizeof(*session)); - session->key = git_atomic32_inc(&repo->attr_session_key); - - return 0; -} - -void git_attr_session__free(git_attr_session *session) -{ - if (!session) - return; - - git_buf_dispose(&session->sysdir); - git_buf_dispose(&session->tmp); - - memset(session, 0, sizeof(git_attr_session)); -} |