]> git.proxmox.com Git - libgit2.git/blobdiff - src/attr.c
New upstream version 1.3.0+dfsg.1
[libgit2.git] / src / attr.c
index 98a328a551e14186001635d6245a4cdae7687954..95b49e3de41132abf778c894724919607c0f400e 100644 (file)
@@ -1,43 +1,51 @@
-#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 "fileops.h"
+#include "sysdir.h"
 #include "config.h"
-#include "attr.h"
+#include "attr_file.h"
 #include "ignore.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)
 {
@@ -48,15 +56,25 @@ int git_attr_get(
        git_attr_file *file;
        git_attr_name attr;
        git_attr_rule *rule;
+       git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
+
+       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));
        attr.name = name;
        attr.name_hash = git_attr_file__name_hash(name);
 
@@ -74,22 +92,37 @@ int git_attr_get(
        }
 
 cleanup:
-       git_vector_free(&files);
+       release_attr_files(&files);
        git_attr_path__free(&path);
 
        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)
@@ -102,15 +135,28 @@ int git_attr_get_many(
        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;
+
+       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_repository_is_bare(repo))
+               dir_flag = GIT_DIR_FLAG_FALSE;
 
-       if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+       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) {
 
@@ -145,13 +191,40 @@ int git_attr_get_many(
        }
 
 cleanup:
-       git_vector_free(&files);
+       release_attr_files(&files);
        git_attr_path__free(&path);
        git__free(info);
 
        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,
@@ -159,6 +232,20 @@ int git_attr_foreach(
        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;
@@ -168,16 +255,22 @@ int git_attr_foreach(
        git_attr_rule *rule;
        git_attr_assignment *assign;
        git_strmap *seen = NULL;
+       git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
+
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(callback);
+       GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options");
+
+       if (git_repository_is_bare(repo))
+               dir_flag = GIT_DIR_FLAG_FALSE;
 
-       if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+       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 ||
+           (error = git_strmap_new(&seen)) < 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) {
@@ -187,14 +280,12 @@ int git_attr_foreach(
                                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_clear();
-                                       error = GIT_EUSER;
+                                       git_error_set_after_callback(error);
                                        goto cleanup;
                                }
                        }
@@ -203,350 +294,355 @@ int git_attr_foreach(
 
 cleanup:
        git_strmap_free(seen);
-       git_vector_free(&files);
+       release_attr_files(&files);
        git_attr_path__free(&path);
 
        return error;
 }
 
-
-int git_attr_add_macro(
+static int preload_attr_source(
        git_repository *repo,
-       const char *name,
-       const char *values)
+       git_attr_session *attr_session,
+       git_attr_file_source *source)
 {
        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);
+       git_attr_file *preload = NULL;
 
-       macro->match.length = strlen(macro->match.pattern);
-       macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
+       if (!source)
+               return 0;
 
-       error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
+       error = git_attr_cache__get(&preload, repo, attr_session, source,
+                                   git_attr_file__parse_buffer, true);
 
        if (!error)
-               error = git_attr_cache__insert_macro(repo, macro);
-
-       if (error < 0)
-               git_attr_rule__free(macro);
+               git_attr_file__free(preload);
 
        return error;
 }
 
-bool git_attr_cache__is_cached(
-       git_repository *repo, git_attr_file_source source, const char *path)
+GIT_INLINE(int) preload_attr_file(
+       git_repository *repo,
+       git_attr_session *attr_session,
+       const char *base,
+       const char *filename)
 {
-       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;
+       git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE };
 
-       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));
+       if (!filename)
+               return 0;
 
-       git_buf_free(&cache_key);
+       source.base = base;
+       source.filename = filename;
 
-       return rval;
+       return preload_attr_source(repo, attr_session, &source);
 }
 
-static int load_attr_file(
-       const char **data,
-       git_futils_filestamp *stamp,
-       const char *filename)
+static int system_attr_file(
+       git_buf *out,
+       git_attr_session *attr_session)
 {
        int error;
-       git_buf content = GIT_BUF_INIT;
 
-       error = git_futils_filestamp_check(stamp, filename);
-       if (error < 0)
+       if (!attr_session) {
+               error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM);
+
+               if (error == GIT_ENOTFOUND)
+                       git_error_clear();
+
                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;
+       if (!attr_session->init_sysdir) {
+               error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM);
 
-       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;
+               if (error == GIT_ENOTFOUND)
+                       git_error_clear();
+               else if (error)
+                       return error;
 
-               /* TODO: once warnings are available, issue a warning callback */
+               attr_session->init_sysdir = 1;
        }
 
-       *data = git_buf_detach(&content);
+       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 load_attr_blob_from_index(
-       const char **content,
-       git_blob **blob,
+static int attr_setup(
        git_repository *repo,
-       const git_oid *old_oid,
-       const char *relfile)
+       git_attr_session *attr_session,
+       git_attr_options *opts)
 {
-       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);
+       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;
+       const char *workdir;
+       int error = 0;
 
-       if (old_oid && git_oid__cmp(old_oid, &entry->oid) == 0)
-               return GIT_ENOTFOUND;
+       if (attr_session && attr_session->init_setup)
+               return 0;
 
-       if ((error = git_blob_lookup(blob, repo, &entry->oid)) < 0)
+       if ((error = git_attr_cache__init(repo)) < 0)
                return error;
 
-       *content = git_blob_rawcontent(*blob);
-       return 0;
-}
+       /*
+        * Preload attribute files that could contain macros so the
+        * definitions will be available for later file parsing.
+        */
 
-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;
+       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;
 
-       *file = NULL;
+               error = 0;
+       }
 
-       if (!cache || !cache->files)
-               return 0;
+       if ((error = preload_attr_file(repo, attr_session, NULL,
+                                      git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
+               goto out;
 
-       if (git_buf_printf(&cache_key, "%d#%s", (int)source, relative_path) < 0)
-               return -1;
+       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;
 
-       cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr);
+               error = 0;
+       }
 
-       git_buf_free(&cache_key);
+       if ((workdir = git_repository_workdir(repo)) != NULL &&
+           (error = preload_attr_file(repo, attr_session, workdir, GIT_ATTR_FILE)) < 0)
+                       goto out;
 
-       if (git_strmap_valid_index(cache->files, cache_pos))
-               *file = git_strmap_value_at(cache->files, cache_pos);
+       if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
+           (error = preload_attr_source(repo, attr_session, &index_source)) < 0)
+                       goto out;
 
-       return 0;
-}
+       if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) &&
+           (error = preload_attr_source(repo, attr_session, &head_source)) < 0)
+               goto out;
 
-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 ((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 (git_strmap_valid_index(cache->files, cache_pos)) {
-               *file = git_strmap_value_at(cache->files, cache_pos);
-               return 0;
+               if ((error = preload_attr_source(repo, attr_session, &commit_source)) < 0)
+                       goto out;
        }
 
-       if (git_attr_file__new(file, 0, filename, &cache->pool) < 0)
-               return -1;
+       if (attr_session)
+               attr_session->init_setup = 1;
 
-       git_strmap_insert(cache->files, (*file)->key + 2, *file, error);
-       if (error > 0)
-               error = 0;
+out:
+       git_buf_dispose(&system);
+       git_buf_dispose(&info);
 
        return error;
 }
 
-int git_attr_cache__push_file(
+int git_attr_add_macro(
        git_repository *repo,
-       const char *base,
-       const char *filename,
-       git_attr_file_source source,
-       git_attr_file_parser parse,
-       void* parsedata,
-       git_vector *stack)
+       const char *name,
+       const char *values)
 {
-       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);
-       }
+       int error;
+       git_attr_rule *macro = NULL;
+       git_pool *pool;
 
-       if (error) {
-               /* not finding a file is not an error for this function */
-               if (error == GIT_ENOTFOUND) {
-                       giterr_clear();
-                       error = 0;
-               }
-               goto finish;
-       }
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
 
-       /* 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 ((error = git_attr_cache__init(repo)) < 0)
+               return error;
 
-       if (parse && (error = parse(repo, parsedata, content, file)) < 0)
-               goto finish;
+       macro = git__calloc(1, sizeof(git_attr_rule));
+       GIT_ERROR_CHECK_ALLOC(macro);
 
-       git_strmap_insert(cache->files, file->key, file, error); //-V595
-       if (error > 0)
-               error = 0;
+       pool = &git_repository_attr_cache(repo)->pool;
 
-       /* 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);
+       macro->match.pattern = git_pool_strdup(pool, name);
+       GIT_ERROR_CHECK_ALLOC(macro->match.pattern);
 
-finish:
-       /* push file onto vector if we found one*/
-       if (!error && file != NULL)
-               error = git_vector_insert(stack, file);
+       macro->match.length = strlen(macro->match.pattern);
+       macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
 
-       if (error != 0)
-               git_attr_file__free(file);
+       error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
 
-       if (blob)
-               git_blob_free(blob);
-       else
-               git__free((void *)content);
+       if (!error)
+               error = git_attr_cache__insert_macro(repo, macro);
 
-       git_buf_free(&path);
+       if (error < 0)
+               git_attr_rule__free(macro);
 
        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;
+       git_attr_session *attr_session;
+       git_attr_options *opts;
        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)
+static int attr_decide_sources(
+       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_one_attr(void *ref, git_buf *path)
+static int push_attr_source(
+       git_repository *repo,
+       git_attr_session *attr_session,
+       git_vector *list,
+       git_attr_file_source *source,
+       bool allow_macros)
+{
+       int error = 0;
+       git_attr_file *file = NULL;
+
+       error = git_attr_cache__get(&file, repo, attr_session,
+                                   source,
+                                   git_attr_file__parse_buffer,
+                                   allow_macros);
+
+       if (error < 0)
+               return error;
+
+       if (file != NULL) {
+               if ((error = git_vector_insert(list, file)) < 0)
+                       git_attr_file__free(file);
+       }
+
+       return error;
+}
+
+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 = git_attr_cache__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 = git_attr_cache__push_file(
-                       info->repo, path->ptr, GIT_ATTR_FILE, src[i],
-                       git_attr_file__parse_buffer, NULL, info->files);
+       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 void release_attr_files(git_vector *files)
+{
+       size_t i;
+       git_attr_file *file;
+
+       git_vector_foreach(files, i, file) {
+               git_attr_file__free(file);
+               files->contents[i] = NULL;
+       }
+       git_vector_free(files);
+}
+
 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;
-       git_buf dir = GIT_BUF_INIT;
+       int error = 0;
+       git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT;
        const char *workdir = git_repository_workdir(repo);
-       attr_walk_up_info info;
+       attr_walk_up_info info = { NULL };
 
-       if (git_attr_cache__init(repo) < 0 ||
-               git_vector_init(files, 4, NULL) < 0)
-               return -1;
+       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);
-       else
+       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;
 
@@ -557,188 +653,48 @@ static int collect_attr_files(
         * - $GIT_PREFIX/etc/gitattributes
         */
 
-       error = push_attr_file(
-               repo, files, 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, 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_futils_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, 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)
-               git_vector_free(files);
-       git_buf_free(&dir);
-
-       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 char *cfgval = NULL;
-
-       *out = NULL;
-
-       if (!(error = git_config_get_string(&cfgval, cfg, key))) {
-
-               /* expand leading ~/ as needed */
-               if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
-                       !git_futils_find_global_file(&buf, &cfgval[2]))
-                       *out = git_buf_detach(&buf);
-               else if (cfgval)
-                       *out = git__strdup(cfgval);
-
-       } else if (error == GIT_ENOTFOUND) {
-               giterr_clear();
-               error = 0;
-
-               if (!git_futils_find_xdg_file(&buf, fallback))
-                       *out = git_buf_detach(&buf);
-       }
-
-       git_buf_free(&buf);
+               release_attr_files(files);
+       git_buf_dispose(&attrfile);
+       git_buf_dispose(&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 = attr_cache__lookup_path(
-               &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
-       if (ret < 0)
-               return ret;
-
-       ret = attr_cache__lookup_path(
-               &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
-       if (ret < 0)
-               return ret;
-
-       /* 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);
-
-       git__free(cache->cfg_attr_file);
-       cache->cfg_attr_file = NULL;
-
-       git__free(cache->cfg_excl_file);
-       cache->cfg_excl_file = NULL;
-
-       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);
-}
-