]> git.proxmox.com Git - libgit2.git/blobdiff - src/attr.c
Merge pull request #968 from arrbee/diff-support-typechange
[libgit2.git] / src / attr.c
index 5cf96acf7f155f30b4167352745dd0c9a5994951..025ad3c87236712fe67c2caa8b7ce114776c2992 100644 (file)
@@ -1,30 +1,59 @@
 #include "repository.h"
 #include "fileops.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)
+{
+       if (attr == NULL || attr == git_attr__unset)
+               return GIT_ATTR_UNSPECIFIED_T;
+
+       if (attr == git_attr__true)
+               return GIT_ATTR_TRUE_T;
+
+       if (attr == git_attr__false)
+               return GIT_ATTR_FALSE_T;
+
+       return GIT_ATTR_VALUE_T;
+}
+
+
 static int collect_attr_files(
-       git_repository *repo, const char *path, git_vector *files);
+       git_repository *repo,
+       uint32_t flags,
+       const char *path,
+       git_vector *files);
 
 
 int git_attr_get(
-    git_repository *repo, const char *pathname,
-       const char *name, const char **value)
+       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;
+       size_t i, j;
        git_attr_file *file;
        git_attr_name attr;
        git_attr_rule *rule;
 
        *value = NULL;
 
-       if ((error = git_attr_path__init(
-                       &path, pathname, git_repository_workdir(repo))) < 0 ||
-               (error = collect_attr_files(repo, pathname, &files)) < 0)
-               return error;
+       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);
@@ -36,13 +65,14 @@ int git_attr_get(
                        if (pos >= 0) {
                                *value = ((git_attr_assignment *)git_vector_get(
                                                          &rule->assigns, pos))->value;
-                               goto found;
+                               goto cleanup;
                        }
                }
        }
 
-found:
+cleanup:
        git_vector_free(&files);
+       git_attr_path__free(&path);
 
        return error;
 }
@@ -54,13 +84,17 @@ typedef struct {
 } attr_get_many_info;
 
 int git_attr_get_many(
-    git_repository *repo, const char *pathname,
-    size_t num_attr, const char **names, const char **values)
+       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;
+       size_t i, j, k;
        git_attr_file *file;
        git_attr_rule *rule;
        attr_get_many_info *info = NULL;
@@ -68,10 +102,11 @@ int git_attr_get_many(
 
        memset((void *)values, 0, sizeof(const char *) * num_attr);
 
-       if ((error = git_attr_path__init(
-                       &path, pathname, git_repository_workdir(repo))) < 0 ||
-               (error = collect_attr_files(repo, pathname, &files)) < 0)
-               return error;
+       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);
@@ -106,6 +141,7 @@ int git_attr_get_many(
 
 cleanup:
        git_vector_free(&files);
+       git_attr_path__free(&path);
        git__free(info);
 
        return error;
@@ -113,25 +149,28 @@ cleanup:
 
 
 int git_attr_foreach(
-    git_repository *repo, const char *pathname,
+    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;
+       size_t i, j, k;
        git_attr_file *file;
        git_attr_rule *rule;
        git_attr_assignment *assign;
-       git_hashtable *seen = NULL;
+       git_strmap *seen = NULL;
 
-       if ((error = git_attr_path__init(
-                       &path, pathname, git_repository_workdir(repo))) < 0 ||
-               (error = collect_attr_files(repo, pathname, &files)) < 0)
-               return error;
+       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_hashtable_alloc(8, git_hash__strhash_cb, git_hash__strcmp_cb);
+       seen = git_strmap_alloc();
        GITERR_CHECK_ALLOC(seen);
 
        git_vector_foreach(&files, i, file) {
@@ -140,21 +179,27 @@ int git_attr_foreach(
 
                        git_vector_foreach(&rule->assigns, k, assign) {
                                /* skip if higher priority assignment was already seen */
-                               if (git_hashtable_lookup(seen, assign->name))
+                               if (git_strmap_exists(seen, assign->name))
                                        continue;
 
-                               if (!(error = git_hashtable_insert(seen, assign->name, assign)))
-                                       error = callback(assign->name, assign->value, payload);
+                               git_strmap_insert(seen, assign->name, assign, error);
+                               if (error < 0)
+                                       goto cleanup;
 
-                               if (error != 0)
+                               error = callback(assign->name, assign->value, payload);
+                               if (error) {
+                                       giterr_clear();
+                                       error = GIT_EUSER;
                                        goto cleanup;
+                               }
                        }
                }
        }
 
 cleanup:
-       git_hashtable_free(seen);
+       git_strmap_free(seen);
        git_vector_free(&files);
+       git_attr_path__free(&path);
 
        return error;
 }
@@ -167,6 +212,7 @@ int git_attr_add_macro(
 {
        int error;
        git_attr_rule *macro = NULL;
+       git_pool *pool;
 
        if (git_attr_cache__init(repo) < 0)
                return -1;
@@ -174,13 +220,15 @@ int git_attr_add_macro(
        macro = git__calloc(1, sizeof(git_attr_rule));
        GITERR_CHECK_ALLOC(macro);
 
-       macro->match.pattern = git__strdup(name);
+       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, &macro->assigns, &values);
+       error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
 
        if (!error)
                error = git_attr_cache__insert_macro(repo, macro);
@@ -191,106 +239,295 @@ int git_attr_add_macro(
        return error;
 }
 
-bool git_attr_cache__is_cached(git_repository *repo, const char *path)
+bool git_attr_cache__is_cached(
+       git_repository *repo, git_attr_file_source source, const char *path)
 {
-       const char *cache_key = path;
-       if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0)
-               cache_key += strlen(git_repository_workdir(repo));
-       return (git_hashtable_lookup(
-               git_repository_attr_cache(repo)->files, cache_key) != NULL);
+       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;
 }
 
-int git_attr_cache__lookup_or_create_file(
-       git_repository *repo,
-       const char *key,
-       const char *filename,
-       int (*loader)(git_repository *, const char *, git_attr_file *),
-       git_attr_file **file_ptr)
+static int load_attr_file(
+       const char **data,
+       git_attr_file_stat_sig *sig,
+       const char *filename)
 {
        int error;
-       git_attr_cache *cache = git_repository_attr_cache(repo);
-       git_attr_file *file = NULL;
+       git_buf content = GIT_BUF_INIT;
+       struct stat st;
 
-       if ((file = git_hashtable_lookup(cache->files, key)) != NULL) {
-               *file_ptr = file;
-               return 0;
+       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;
        }
 
-       if (loader && git_path_exists(filename) == false) {
-               *file_ptr = NULL;
+       *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_attr_file__new(&file) < 0)
+       if (git_buf_printf(&cache_key, "%d#%s", (int)source, relative_path) < 0)
                return -1;
 
-       if (loader)
-               error = loader(repo, filename, file);
-       else
-               error = git_attr_file__set_path(repo, key, file);
+       cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr);
 
-       if (!error)
-               error = git_hashtable_insert(cache->files, file->path, file);
+       git_buf_free(&cache_key);
 
-       if (error < 0) {
-               git_attr_file__free(file);
-               file = NULL;
+       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;
        }
 
-       *file_ptr = file;
+       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;
 }
 
-/* add git_attr_file to vector of files, loading if needed */
 int git_attr_cache__push_file(
        git_repository *repo,
-       git_vector     *stack,
-       const char     *base,
-       const char     *filename,
-       int (*loader)(git_repository *, const char *, git_attr_file *))
+       const char *base,
+       const char *filename,
+       git_attr_file_source source,
+       git_attr_file_parser parse,
+       void* parsedata,
+       git_vector *stack)
 {
-       int error;
+       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;
-       const char *cache_key;
+       git_blob *blob = NULL;
+       git_attr_file_stat_sig st;
 
-       if (base != NULL) {
+       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;
        }
 
-       /* either get attr_file from cache or read from disk */
-       cache_key = filename;
-       if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0)
-               cache_key += strlen(git_repository_workdir(repo));
+       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;
+       }
 
-       error = git_attr_cache__lookup_or_create_file(
-               repo, cache_key, filename, loader, &file);
+       if (parse && (error = parse(repo, parsedata, content, file)) < 0)
+               goto finish;
 
+       git_strmap_insert(cache->files, file->key, file, error); //-V595
+       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_attrs(R,S,B,F) \
-       git_attr_cache__push_file((R),(S),(B),(F),git_attr_file__from_file)
+#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;
+       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;
-       return push_attrs(info->repo, info->files, path->ptr, GIT_ATTR_FILE);
+       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, NULL, info->files);
+
+       return error;
 }
 
 static int collect_attr_files(
-       git_repository *repo, const char *path, git_vector *files)
+       git_repository *repo,
+       uint32_t flags,
+       const char *path,
+       git_vector *files)
 {
        int error;
        git_buf dir = GIT_BUF_INIT;
@@ -301,7 +538,11 @@ static int collect_attr_files(
                git_vector_init(files, 4, NULL) < 0)
                return -1;
 
-       error = git_path_find_dir(&dir, path, workdir);
+       /* 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;
 
@@ -312,29 +553,36 @@ static int collect_attr_files(
         * - $GIT_PREFIX/etc/gitattributes
         */
 
-       error = push_attrs(
+       error = push_attr_file(
                repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO);
        if (error < 0)
                goto cleanup;
 
-       info.repo = repo;
+       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_attrs(
+               error = push_attr_file(
                        repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file);
                if (error < 0)
                        goto cleanup;
        }
 
-       error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
-       if (!error)
-               error = push_attrs(repo, files, NULL, dir.ptr);
-       else if (error == GIT_ENOTFOUND)
-               error = 0;
+       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)
@@ -344,9 +592,22 @@ static int collect_attr_files(
        return error;
 }
 
+static char *try_global_default(const char *relpath)
+{
+       git_buf dflt = GIT_BUF_INIT;
+       char *rval = NULL;
+
+       if (!git_futils_find_global_file(&dflt, relpath))
+               rval = git_buf_detach(&dflt);
+
+       git_buf_free(&dflt);
+
+       return rval;
+}
 
 int git_attr_cache__init(git_repository *repo)
 {
+       int ret;
        git_attr_cache *cache = git_repository_attr_cache(repo);
        git_config *cfg;
 
@@ -354,34 +615,39 @@ int git_attr_cache__init(git_repository *repo)
                return 0;
 
        /* cache config settings for attributes and ignores */
-       if (git_repository_config(&cfg, repo) < 0)
+       if (git_repository_config__weakptr(&cfg, repo) < 0)
                return -1;
-       if (git_config_get_string(cfg, GIT_ATTR_CONFIG, &cache->cfg_attr_file)) {
-               giterr_clear();
-               cache->cfg_attr_file = NULL;
-       }
-       if (git_config_get_string(cfg, GIT_IGNORE_CONFIG, &cache->cfg_excl_file)) {
-               giterr_clear();
-               cache->cfg_excl_file = NULL;
-       }
-       git_config_free(cfg);
+
+       ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG);
+       if (ret < 0 && ret != GIT_ENOTFOUND)
+               return ret;
+       if (ret == GIT_ENOTFOUND)
+               cache->cfg_attr_file = try_global_default(GIT_ATTR_CONFIG_DEFAULT);
+
+       ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG);
+       if (ret < 0 && ret != GIT_ENOTFOUND)
+               return ret;
+       if (ret == GIT_ENOTFOUND)
+               cache->cfg_excl_file = try_global_default(GIT_IGNORE_CONFIG_DEFAULT);
+
+       giterr_clear();
 
        /* allocate hashtable for attribute and ignore file contents */
        if (cache->files == NULL) {
-               cache->files = git_hashtable_alloc(
-                       8, git_hash__strhash_cb, git_hash__strcmp_cb);
-               if (!cache->files)
-                       return -1;
+               cache->files = git_strmap_alloc();
+               GITERR_CHECK_ALLOC(cache->files);
        }
 
        /* allocate hashtable for attribute macros */
        if (cache->macros == NULL) {
-               cache->macros = git_hashtable_alloc(
-                       8, git_hash__strhash_cb, git_hash__strcmp_cb);
-               if (!cache->macros)
-                       return -1;
+               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 */
@@ -391,38 +657,62 @@ int git_attr_cache__init(git_repository *repo)
 void git_attr_cache_flush(
        git_repository *repo)
 {
-       git_hashtable *table;
+       git_attr_cache *cache;
 
        if (!repo)
                return;
 
-       if ((table = git_repository_attr_cache(repo)->files) != NULL) {
+       cache = git_repository_attr_cache(repo);
+
+       if (cache->files != NULL) {
                git_attr_file *file;
 
-               GIT_HASHTABLE_FOREACH_VALUE(table, file, git_attr_file__free(file));
-               git_hashtable_free(table);
+               git_strmap_foreach_value(cache->files, file, {
+                       git_attr_file__free(file);
+               });
 
-               git_repository_attr_cache(repo)->files = NULL;
+               git_strmap_free(cache->files);
        }
 
-       if ((table = git_repository_attr_cache(repo)->macros) != NULL) {
+       if (cache->macros != NULL) {
                git_attr_rule *rule;
 
-               GIT_HASHTABLE_FOREACH_VALUE(table, rule, git_attr_rule__free(rule));
-               git_hashtable_free(table);
+               git_strmap_foreach_value(cache->macros, rule, {
+                       git_attr_rule__free(rule);
+               });
 
-               git_repository_attr_cache(repo)->macros = NULL;
+               git_strmap_free(cache->macros);
        }
 
-       git_repository_attr_cache(repo)->initialized = 0;
+       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;
 
-       return git_hashtable_insert(
-               git_repository_attr_cache(repo)->macros, macro->match.pattern, macro);
+       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);
 }
+