-#include "common.h"
+/*
+ * 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.h"
+
#include "repository.h"
#include "sysdir.h"
#include "config.h"
#include "git2/oid.h"
#include <ctype.h>
-GIT__USE_STRMAP;
-
const char *git_attr__true = "[internal]__TRUE__";
const char *git_attr__false = "[internal]__FALSE__";
const char *git_attr__unset = "[internal]__UNSET__";
-git_attr_t git_attr_value(const char *attr)
+git_attr_value_t git_attr_value(const char *attr)
{
if (attr == NULL || attr == git_attr__unset)
- return GIT_ATTR_UNSPECIFIED_T;
+ return GIT_ATTR_VALUE_UNSPECIFIED;
if (attr == git_attr__true)
- return GIT_ATTR_TRUE_T;
+ return GIT_ATTR_VALUE_TRUE;
if (attr == git_attr__false)
- return GIT_ATTR_FALSE_T;
+ return GIT_ATTR_VALUE_FALSE;
- return GIT_ATTR_VALUE_T;
+ return GIT_ATTR_VALUE_STRING;
}
static int collect_attr_files(
git_repository *repo,
- uint32_t flags,
+ git_attr_session *attr_session,
+ git_attr_options *opts,
const char *path,
git_vector *files);
static void release_attr_files(git_vector *files);
-int git_attr_get(
+int git_attr_get_ext(
const char **value,
git_repository *repo,
- uint32_t flags,
+ git_attr_options *opts,
const char *pathname,
const char *name)
{
git_attr_file *file;
git_attr_name attr;
git_attr_rule *rule;
+ git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
- assert(value && repo && name);
+ GIT_ASSERT_ARG(value);
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(name);
+ GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options");
*value = NULL;
- if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ if (git_repository_is_bare(repo))
+ dir_flag = GIT_DIR_FLAG_FALSE;
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
- if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
+ if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0)
goto cleanup;
memset(&attr, 0, sizeof(attr));
return error;
}
+int git_attr_get(
+ const char **value,
+ git_repository *repo,
+ uint32_t flags,
+ const char *pathname,
+ const char *name)
+{
+ git_attr_options opts = GIT_ATTR_OPTIONS_INIT;
+
+ opts.flags = flags;
+
+ return git_attr_get_ext(value, repo, &opts, pathname, name);
+}
+
typedef struct {
git_attr_name name;
git_attr_assignment *found;
} attr_get_many_info;
-int git_attr_get_many(
+int git_attr_get_many_with_session(
const char **values,
git_repository *repo,
- uint32_t flags,
+ git_attr_session *attr_session,
+ git_attr_options *opts,
const char *pathname,
size_t num_attr,
const char **names)
git_attr_rule *rule;
attr_get_many_info *info = NULL;
size_t num_found = 0;
+ git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
if (!num_attr)
return 0;
- assert(values && repo && names);
+ GIT_ASSERT_ARG(values);
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(pathname);
+ GIT_ASSERT_ARG(names);
+ GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options");
- if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ if (git_repository_is_bare(repo))
+ dir_flag = GIT_DIR_FLAG_FALSE;
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
- if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
+ if ((error = collect_attr_files(repo, attr_session, opts, pathname, &files)) < 0)
goto cleanup;
info = git__calloc(num_attr, sizeof(attr_get_many_info));
- GITERR_CHECK_ALLOC(info);
+ GIT_ERROR_CHECK_ALLOC(info);
git_vector_foreach(&files, i, file) {
return error;
}
+int git_attr_get_many(
+ const char **values,
+ git_repository *repo,
+ uint32_t flags,
+ const char *pathname,
+ size_t num_attr,
+ const char **names)
+{
+ git_attr_options opts = GIT_ATTR_OPTIONS_INIT;
+
+ opts.flags = flags;
+
+ return git_attr_get_many_with_session(
+ values, repo, NULL, &opts, pathname, num_attr, names);
+}
+
+int git_attr_get_many_ext(
+ const char **values,
+ git_repository *repo,
+ git_attr_options *opts,
+ const char *pathname,
+ size_t num_attr,
+ const char **names)
+{
+ return git_attr_get_many_with_session(
+ values, repo, NULL, opts, pathname, num_attr, names);
+}
int git_attr_foreach(
git_repository *repo,
const char *pathname,
int (*callback)(const char *name, const char *value, void *payload),
void *payload)
+{
+ git_attr_options opts = GIT_ATTR_OPTIONS_INIT;
+
+ opts.flags = flags;
+
+ return git_attr_foreach_ext(repo, &opts, pathname, callback, payload);
+}
+
+int git_attr_foreach_ext(
+ git_repository *repo,
+ git_attr_options *opts,
+ const char *pathname,
+ int (*callback)(const char *name, const char *value, void *payload),
+ void *payload)
{
int error;
git_attr_path path;
git_attr_rule *rule;
git_attr_assignment *assign;
git_strmap *seen = NULL;
+ git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
- assert(repo && callback);
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(callback);
+ GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options");
- if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ if (git_repository_is_bare(repo))
+ dir_flag = GIT_DIR_FLAG_FALSE;
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
- if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0 ||
- (error = git_strmap_alloc(&seen)) < 0)
+ if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0 ||
+ (error = git_strmap_new(&seen)) < 0)
goto cleanup;
git_vector_foreach(&files, i, file) {
if (git_strmap_exists(seen, assign->name))
continue;
- git_strmap_insert(seen, assign->name, assign, error);
- if (error < 0)
+ if ((error = git_strmap_set(seen, assign->name, assign)) < 0)
goto cleanup;
error = callback(assign->name, assign->value, payload);
if (error) {
- giterr_set_after_callback(error);
+ git_error_set_after_callback(error);
goto cleanup;
}
}
return error;
}
-static int preload_attr_file(
+static int preload_attr_source(
git_repository *repo,
- git_attr_file_source source,
- const char *base,
- const char *file)
+ git_attr_session *attr_session,
+ git_attr_file_source *source)
{
int error;
git_attr_file *preload = NULL;
- if (!file)
+ if (!source)
return 0;
- if (!(error = git_attr_cache__get(
- &preload, repo, source, base, file, git_attr_file__parse_buffer)))
+
+ error = git_attr_cache__get(&preload, repo, attr_session, source,
+ git_attr_file__parse_buffer, true);
+
+ if (!error)
git_attr_file__free(preload);
return error;
}
-static int attr_setup(git_repository *repo)
+GIT_INLINE(int) preload_attr_file(
+ git_repository *repo,
+ git_attr_session *attr_session,
+ const char *base,
+ const char *filename)
{
- int error = 0;
- const char *workdir = git_repository_workdir(repo);
+ git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE };
+
+ if (!filename)
+ return 0;
+
+ source.base = base;
+ source.filename = filename;
+
+ return preload_attr_source(repo, attr_session, &source);
+}
+
+static int system_attr_file(
+ git_buf *out,
+ git_attr_session *attr_session)
+{
+ int error;
+
+ if (!attr_session) {
+ error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM);
+
+ if (error == GIT_ENOTFOUND)
+ git_error_clear();
+
+ return error;
+ }
+
+ if (!attr_session->init_sysdir) {
+ error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM);
+
+ if (error == GIT_ENOTFOUND)
+ git_error_clear();
+ else if (error)
+ return error;
+
+ attr_session->init_sysdir = 1;
+ }
+
+ if (attr_session->sysdir.size == 0)
+ return GIT_ENOTFOUND;
+
+ /* We can safely provide a git_buf with no allocation (asize == 0) to
+ * a consumer. This allows them to treat this as a regular `git_buf`,
+ * but their call to `git_buf_dispose` will not attempt to free it.
+ */
+ git_buf_attach_notowned(
+ out, attr_session->sysdir.ptr, attr_session->sysdir.size);
+ return 0;
+}
+
+static int attr_setup(
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_attr_options *opts)
+{
+ git_buf system = GIT_BUF_INIT, info = GIT_BUF_INIT;
+ git_attr_file_source index_source = { GIT_ATTR_FILE_SOURCE_INDEX, NULL, GIT_ATTR_FILE, NULL };
+ git_attr_file_source head_source = { GIT_ATTR_FILE_SOURCE_HEAD, NULL, GIT_ATTR_FILE, NULL };
+ git_attr_file_source commit_source = { GIT_ATTR_FILE_SOURCE_COMMIT, NULL, GIT_ATTR_FILE, NULL };
git_index *idx = NULL;
- git_buf sys = GIT_BUF_INIT;
+ const char *workdir;
+ int error = 0;
+
+ if (attr_session && attr_session->init_setup)
+ return 0;
if ((error = git_attr_cache__init(repo)) < 0)
return error;
- /* preload attribute files that could contain macros so the
- * definitions will be available for later file parsing
+ /*
+ * Preload attribute files that could contain macros so the
+ * definitions will be available for later file parsing.
*/
- if (!(error = git_sysdir_find_system_file(&sys, GIT_ATTR_FILE_SYSTEM))) {
- error = preload_attr_file(
- repo, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
- git_buf_free(&sys);
- }
- if (error < 0) {
- if (error == GIT_ENOTFOUND) {
- giterr_clear();
- error = 0;
- } else
- return error;
+ if ((error = system_attr_file(&system, attr_session)) < 0 ||
+ (error = preload_attr_file(repo, attr_session, NULL, system.ptr)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto out;
+
+ error = 0;
}
- if ((error = preload_attr_file(
- repo, GIT_ATTR_FILE__FROM_FILE,
- NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
- return error;
+ if ((error = preload_attr_file(repo, attr_session, NULL,
+ git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
+ goto out;
- if ((error = preload_attr_file(
- repo, GIT_ATTR_FILE__FROM_FILE,
- git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0)
- return error;
+ if ((error = git_repository_item_path(&info, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
+ (error = preload_attr_file(repo, attr_session, info.ptr, GIT_ATTR_FILE_INREPO)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto out;
- if (workdir != NULL &&
- (error = preload_attr_file(
- repo, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
- return error;
+ error = 0;
+ }
+
+ if ((workdir = git_repository_workdir(repo)) != NULL &&
+ (error = preload_attr_file(repo, attr_session, workdir, GIT_ATTR_FILE)) < 0)
+ goto out;
if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
- (error = preload_attr_file(
- repo, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
- return error;
+ (error = preload_attr_source(repo, attr_session, &index_source)) < 0)
+ goto out;
+
+ if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) &&
+ (error = preload_attr_source(repo, attr_session, &head_source)) < 0)
+ goto out;
+
+ if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0)) {
+#ifndef GIT_DEPRECATE_HARD
+ if (opts->commit_id)
+ commit_source.commit_id = opts->commit_id;
+ else
+#endif
+ commit_source.commit_id = &opts->attr_commit_id;
+
+ if ((error = preload_attr_source(repo, attr_session, &commit_source)) < 0)
+ goto out;
+ }
+
+ if (attr_session)
+ attr_session->init_setup = 1;
+
+out:
+ git_buf_dispose(&system);
+ git_buf_dispose(&info);
return error;
}
git_attr_rule *macro = NULL;
git_pool *pool;
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(name);
+
if ((error = git_attr_cache__init(repo)) < 0)
return error;
macro = git__calloc(1, sizeof(git_attr_rule));
- GITERR_CHECK_ALLOC(macro);
+ GIT_ERROR_CHECK_ALLOC(macro);
pool = &git_repository_attr_cache(repo)->pool;
macro->match.pattern = git_pool_strdup(pool, name);
- GITERR_CHECK_ALLOC(macro->match.pattern);
+ GIT_ERROR_CHECK_ALLOC(macro->match.pattern);
macro->match.length = strlen(macro->match.pattern);
macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
typedef struct {
git_repository *repo;
- uint32_t flags;
+ git_attr_session *attr_session;
+ git_attr_options *opts;
const char *workdir;
git_index *index;
git_vector *files;
} attr_walk_up_info;
static int attr_decide_sources(
- uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
+ uint32_t flags,
+ bool has_wd,
+ bool has_index,
+ git_attr_file_source_t *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_FILE_SOURCE_FILE;
if (has_index)
- srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX;
break;
case GIT_ATTR_CHECK_INDEX_THEN_FILE:
if (has_index)
- srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX;
if (has_wd)
- srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
+ srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE;
break;
case GIT_ATTR_CHECK_INDEX_ONLY:
if (has_index)
- srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX;
break;
}
+ if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0)
+ srcs[count++] = GIT_ATTR_FILE_SOURCE_HEAD;
+
+ if ((flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0)
+ srcs[count++] = GIT_ATTR_FILE_SOURCE_COMMIT;
+
return count;
}
-static int push_attr_file(
+static int push_attr_source(
git_repository *repo,
+ git_attr_session *attr_session,
git_vector *list,
- git_attr_file_source source,
- const char *base,
- const char *filename)
+ git_attr_file_source *source,
+ bool allow_macros)
{
int error = 0;
git_attr_file *file = NULL;
- error = git_attr_cache__get(
- &file, repo, source, base, filename, git_attr_file__parse_buffer);
+ error = git_attr_cache__get(&file, repo, attr_session,
+ source,
+ git_attr_file__parse_buffer,
+ allow_macros);
+
if (error < 0)
return error;
return error;
}
-static int push_one_attr(void *ref, git_buf *path)
+GIT_INLINE(int) push_attr_file(
+ git_repository *repo,
+ git_attr_session *attr_session,
+ git_vector *list,
+ const char *base,
+ const char *filename)
+{
+ git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename };
+ return push_attr_source(repo, attr_session, list, &source, true);
+}
+
+static int push_one_attr(void *ref, const char *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_file_source_t src[GIT_ATTR_FILE_NUM_SOURCES];
+ int error = 0, n_src, i;
+ bool allow_macros;
+
+ n_src = attr_decide_sources(info->opts ? info->opts->flags : 0,
+ info->workdir != NULL,
+ info->index != NULL,
+ src);
- n_src = attr_decide_sources(
- info->flags, info->workdir != NULL, info->index != NULL, src);
+ allow_macros = info->workdir ? !strcmp(info->workdir, path) : false;
- for (i = 0; !error && i < n_src; ++i)
- error = push_attr_file(
- info->repo, info->files, src[i], path->ptr, GIT_ATTR_FILE);
+ for (i = 0; !error && i < n_src; ++i) {
+ git_attr_file_source source = { src[i], path, GIT_ATTR_FILE };
+
+ if (src[i] == GIT_ATTR_FILE_SOURCE_COMMIT && info->opts) {
+#ifndef GIT_DEPRECATE_HARD
+ if (info->opts->commit_id)
+ source.commit_id = info->opts->commit_id;
+ else
+#endif
+ source.commit_id = &info->opts->attr_commit_id;
+ }
+
+ error = push_attr_source(info->repo, info->attr_session, info->files,
+ &source, allow_macros);
+ }
return error;
}
static int collect_attr_files(
git_repository *repo,
- uint32_t flags,
+ git_attr_session *attr_session,
+ git_attr_options *opts,
const char *path,
git_vector *files)
{
int error = 0;
- git_buf dir = GIT_BUF_INIT;
+ git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT;
const char *workdir = git_repository_workdir(repo);
attr_walk_up_info info = { NULL };
- if ((error = attr_setup(repo)) < 0)
+ GIT_ASSERT(!git_path_is_absolute(path));
+
+ if ((error = attr_setup(repo, attr_session, opts)) < 0)
return error;
/* Resolve path in a non-bare repo */
- if (workdir != NULL)
- error = git_path_find_dir(&dir, path, workdir);
- /* when in a bare repo, find the containing folder if the given
- * path is a subfolder (if not, the containing folder is the root) */
- else if (strchr(path, '/') != NULL)
- error = git_path_dirname_r(&dir, path);
+ if (workdir != NULL) {
+ if (!(error = git_repository_workdir_path(&dir, repo, path)))
+ error = git_path_find_dir(&dir);
+ }
+ else {
+ error = git_path_dirname_r(&dir, path);
+ }
+
if (error < 0)
goto cleanup;
* - $GIT_PREFIX/etc/gitattributes
*/
- error = push_attr_file(
- repo, files, GIT_ATTR_FILE__FROM_FILE,
- git_repository_path(repo), GIT_ATTR_FILE_INREPO);
- if (error < 0)
- goto cleanup;
+ if ((error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
+ (error = push_attr_file(repo, attr_session, files, attrfile.ptr, GIT_ATTR_FILE_INREPO)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto cleanup;
+ }
- info.repo = repo;
- info.flags = flags;
+ info.repo = repo;
+ info.attr_session = attr_session;
+ info.opts = opts;
info.workdir = workdir;
if (git_repository_index__weakptr(&info.index, repo) < 0)
- giterr_clear(); /* no error even if there is no index */
+ git_error_clear(); /* no error even if there is no index */
info.files = files;
- error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
+ if (!strcmp(dir.ptr, "."))
+ error = push_one_attr(&info, "");
+ else
+ 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, GIT_ATTR_FILE__FROM_FILE,
- NULL, git_repository_attr_cache(repo)->cfg_attr_file);
+ error = push_attr_file(repo, attr_session, 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_sysdir_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
+ if (!opts || (opts->flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
+ error = system_attr_file(&dir, attr_session);
+
if (!error)
- error = push_attr_file(
- repo, files, GIT_ATTR_FILE__FROM_FILE, NULL, dir.ptr);
- else if (error == GIT_ENOTFOUND) {
- giterr_clear();
+ error = push_attr_file(repo, attr_session, files, NULL, dir.ptr);
+ else if (error == GIT_ENOTFOUND)
error = 0;
- }
}
cleanup:
if (error < 0)
release_attr_files(files);
- git_buf_free(&dir);
+ git_buf_dispose(&attrfile);
+ git_buf_dispose(&dir);
return error;
}