Add APIs for git attributes

This adds APIs for querying git attributes.  In addition to
the new API in include/git2/attr.h, most of the action is in
src/attr_file.[hc] which contains utilities for dealing with
a single attributes file, and src/attr.[hc] which contains
the implementation of the APIs that merge all applicable
attributes files.
This commit is contained in:
Russell Belfer 2011-12-16 10:56:43 -08:00
parent be00b00dd1
commit ee1f0b1aed
54 changed files with 1630 additions and 16 deletions

56
include/git2/attr.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2009-2011 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_git_attr_h__
#define INCLUDE_git_attr_h__
#include "common.h"
#include "types.h"
/**
* @file git2/attr.h
* @brief Git attribute management routines
* @defgroup git_attr Git attribute management routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
#define GIT_ATTR_TRUE git_attr__true
#define GIT_ATTR_FALSE git_attr__false
#define GIT_ATTR_UNSPECIFIED NULL
GIT_EXTERN(const char *)git_attr__true;
GIT_EXTERN(const char *)git_attr__false;
/**
* Lookup attribute for path returning string caller must free
*/
GIT_EXTERN(int) git_attr_get(
git_repository *repo, const char *path, const char *name,
const char **value);
/**
* Lookup list of attributes for path, populating array of strings
*/
GIT_EXTERN(int) git_attr_get_many(
git_repository *repo, const char *path,
size_t num_attr, const char **names,
const char **values);
/**
* Perform an operation on each attribute of a path.
*/
GIT_EXTERN(int) git_attr_foreach(
git_repository *repo, const char *path,
int (*callback)(const char *name, const char *value, void *payload),
void *payload);
/** @} */
GIT_END_DECL
#endif

311
src/attr.c Normal file
View File

@ -0,0 +1,311 @@
#include "attr.h"
#include "buffer.h"
#include "fileops.h"
#include "config.h"
#include <ctype.h>
#define GIT_ATTR_FILE_INREPO "info/attributes"
#define GIT_ATTR_FILE ".gitattributes"
#define GIT_ATTR_FILE_SYSTEM "/etc/gitattributes"
#if GIT_WIN32
#define GIT_ATTR_FILE_WIN32 L"%PROGRAMFILES%\\Git\\etc\\gitattributes"
#endif
static int collect_attr_files(
git_repository *repo, const char *path, git_vector *files);
int git_attr_get(
git_repository *repo, const char *pathname,
const char *name, const char **value)
{
int error;
git_attr_path path;
git_vector files = GIT_VECTOR_INIT;
unsigned int i, j;
git_attr_file *file;
git_attr_name attr;
git_attr_rule *rule;
*value = NULL;
if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS ||
(error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
return git__rethrow(error, "Could not get attribute for %s", pathname);
attr.name = name;
attr.name_hash = git_attr_file__name_hash(name);
git_vector_foreach(&files, i, file) {
git_attr_file__foreach_matching_rule(file, &path, j, rule) {
int pos = git_vector_bsearch(&rule->assigns, &attr);
git_clearerror(); /* okay if search failed */
if (pos >= 0) {
*value = ((git_attr_assignment *)git_vector_get(
&rule->assigns, pos))->value;
goto found;
}
}
}
found:
git_vector_free(&files);
return error;
}
typedef struct {
git_attr_name name;
git_attr_assignment *found;
} 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)
{
int error;
git_attr_path path;
git_vector files = GIT_VECTOR_INIT;
unsigned int i, j, k;
git_attr_file *file;
git_attr_rule *rule;
attr_get_many_info *info = NULL;
size_t num_found = 0;
memset(values, 0, sizeof(const char *) * num_attr);
if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS ||
(error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
return git__rethrow(error, "Could not get attributes for %s", pathname);
if ((info = git__calloc(num_attr, sizeof(attr_get_many_info))) == NULL) {
git__rethrow(GIT_ENOMEM, "Could not get attributes for %s", pathname);
goto cleanup;
}
git_vector_foreach(&files, i, file) {
git_attr_file__foreach_matching_rule(file, &path, j, rule) {
for (k = 0; k < num_attr; k++) {
int pos;
if (info[k].found != NULL) /* already found assignment */
continue;
if (!info[k].name.name) {
info[k].name.name = names[k];
info[k].name.name_hash = git_attr_file__name_hash(names[k]);
}
pos = git_vector_bsearch(&rule->assigns, &info[k].name);
git_clearerror(); /* okay if search failed */
if (pos >= 0) {
info[k].found = (git_attr_assignment *)
git_vector_get(&rule->assigns, pos);
values[k] = info[k].found->value;
if (++num_found == num_attr)
goto cleanup;
}
}
}
}
cleanup:
git_vector_free(&files);
git__free(info);
return error;
}
int git_attr_foreach(
git_repository *repo, const char *pathname,
int (*callback)(const char *name, const char *value, void *payload),
void *payload)
{
int error;
git_attr_path path;
git_vector files = GIT_VECTOR_INIT;
unsigned int i, j, k;
git_attr_file *file;
git_attr_rule *rule;
git_attr_assignment *assign;
git_hashtable *seen = NULL;
if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS ||
(error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
return git__rethrow(error, "Could not get attributes for %s", pathname);
seen = git_hashtable_alloc(8, git_hash__strhash_cb, git_hash__strcmp_cb);
if (!seen) {
error = GIT_ENOMEM;
goto cleanup;
}
git_vector_foreach(&files, i, file) {
git_attr_file__foreach_matching_rule(file, &path, j, rule) {
git_vector_foreach(&rule->assigns, k, assign) {
/* skip if higher priority assignment was already seen */
if (git_hashtable_lookup(seen, assign->name))
continue;
error = git_hashtable_insert(seen, assign->name, assign);
if (error != GIT_SUCCESS)
goto cleanup;
error = callback(assign->name, assign->value, payload);
if (error != GIT_SUCCESS)
goto cleanup;
}
}
}
cleanup:
if (seen)
git_hashtable_free(seen);
git_vector_free(&files);
if (error != GIT_SUCCESS)
(void)git__rethrow(error, "Could not get attributes for %s", pathname);
return error;
}
/* add git_attr_file to vector of files, loading if needed */
static int push_attrs(
git_repository *repo,
git_vector *files,
const char *base,
const char *filename)
{
int error = GIT_SUCCESS;
git_attr_cache *cache = &repo->attrcache;
git_buf path = GIT_BUF_INIT;
git_attr_file *file;
int add_to_cache = 0;
if (cache->files == NULL) {
cache->files = git_hashtable_alloc(
8, git_hash__strhash_cb, git_hash__strcmp_cb);
if (!cache->files)
return git__throw(GIT_ENOMEM, "Could not create attribute cache");
}
if ((error = git_path_prettify(&path, filename, base)) < GIT_SUCCESS) {
if (error == GIT_EOSERR)
/* file was not found -- ignore error */
error = GIT_SUCCESS;
goto cleanup;
}
/* either get attr_file from cache or read from disk */
file = git_hashtable_lookup(cache->files, path.ptr);
if (file == NULL) {
error = git_attr_file__from_file(&file, path.ptr);
add_to_cache = (error == GIT_SUCCESS);
}
if (file != NULL) {
/* add file to vector, if we found it */
error = git_vector_insert(files, file);
/* add file to cache, if it is new */
/* do this after above step b/c it is not critical */
if (error == GIT_SUCCESS && add_to_cache && file->path != NULL)
error = git_hashtable_insert(cache->files, file->path, file);
}
cleanup:
git_buf_free(&path);
return error;
}
static int collect_attr_files(
git_repository *repo, const char *path, git_vector *files)
{
int error = GIT_SUCCESS;
git_buf dir = GIT_BUF_INIT;
git_config *cfg;
const char *workdir = git_repository_workdir(repo);
if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS)
goto cleanup;
if ((error = git_path_prettify(&dir, path, workdir)) < GIT_SUCCESS)
goto cleanup;
if (git_futils_isdir(dir.ptr) != GIT_SUCCESS) {
git_path_dirname_r(&dir, dir.ptr);
git_path_to_dir(&dir);
if ((error = git_buf_lasterror(&dir)) < GIT_SUCCESS)
goto cleanup;
}
/* in precendence order highest to lowest:
* - $GIT_DIR/info/attributes
* - path components with .gitattributes
* - config core.attributesfile
* - $GIT_PREFIX/etc/gitattributes
*/
error = push_attrs(repo, files, repo->path_repository, GIT_ATTR_FILE_INREPO);
if (error < GIT_SUCCESS)
goto cleanup;
if (workdir && git__prefixcmp(dir.ptr, workdir) == 0) {
ssize_t rootlen = (ssize_t)strlen(workdir);
do {
error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE);
if (error == GIT_SUCCESS) {
git_path_dirname_r(&dir, dir.ptr);
git_path_to_dir(&dir);
error = git_buf_lasterror(&dir);
}
} while (!error && dir.size >= rootlen);
} else {
error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE);
}
if (error < GIT_SUCCESS)
goto cleanup;
if (git_repository_config(&cfg, repo) == GIT_SUCCESS) {
const char *core_attribs = NULL;
git_config_get_string(cfg, "core.attributesfile", &core_attribs);
git_clearerror(); /* don't care if attributesfile is not set */
if (core_attribs)
error = push_attrs(repo, files, NULL, core_attribs);
git_config_free(cfg);
}
if (error == GIT_SUCCESS)
error = push_attrs(repo, files, NULL, GIT_ATTR_FILE_SYSTEM);
cleanup:
if (error < GIT_SUCCESS) {
git__rethrow(error, "Could not get attributes for '%s'", path);
git_vector_free(files);
}
git_buf_free(&dir);
return error;
}
void git_repository__attr_cache_free(git_attr_cache *attrs)
{
if (attrs && attrs->files) {
git_hashtable_free(attrs->files);
attrs->files = NULL;
}
}

21
src/attr.h Normal file
View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2009-2011 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_attr_h__
#define INCLUDE_attr_h__
#include "hashtable.h"
#include "attr_file.h"
/* EXPORT */
typedef struct {
git_hashtable *files; /* hash path to git_attr_file */
} git_attr_cache;
extern void git_repository__attr_cache_free(git_attr_cache *attrs);
#endif

456
src/attr_file.c Normal file
View File

@ -0,0 +1,456 @@
#include "common.h"
#include "attr_file.h"
#include "filebuf.h"
#include <ctype.h>
const char *git_attr__true = "[internal]__TRUE__";
const char *git_attr__false = "[internal]__FALSE__";
static int parse_fnmatch(git_attr_fnmatch *spec, const char **base);
static int parse_assigns(git_vector *assigns, const char **base);
static int free_rule(git_attr_rule *rule);
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
int git_attr_file__from_buffer(git_attr_file **out, const char *buffer)
{
int error = GIT_SUCCESS;
git_attr_file *attrs = NULL;
const char *scan = NULL;
git_attr_rule *rule = NULL;
*out = NULL;
attrs = git__calloc(1, sizeof(git_attr_file));
if (attrs == NULL)
return git__throw(GIT_ENOMEM, "Could not allocate attribute storage");
attrs->path = NULL;
error = git_vector_init(&attrs->rules, 4, NULL);
if (error != GIT_SUCCESS) {
git__rethrow(error, "Could not initialize attribute storage");
goto cleanup;
}
scan = buffer;
while (error == GIT_SUCCESS && *scan) {
/* allocate rule if needed */
if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) {
error = GIT_ENOMEM;
break;
}
/* parse the next "pattern attr attr attr" line */
if (!(error = parse_fnmatch(&rule->match, &scan)) &&
!(error = parse_assigns(&rule->assigns, &scan)))
error = git_vector_insert(&attrs->rules, rule);
/* if the rule wasn't a pattern, on to the next */
if (error != GIT_SUCCESS) {
free_rule(rule); /* release anything partially allocated */
if (error == GIT_ENOTFOUND)
error = GIT_SUCCESS;
} else {
rule = NULL; /* vector now "owns" the rule */
}
}
cleanup:
if (error != GIT_SUCCESS) {
git_attr_file__free(attrs);
git__free(attrs);
} else {
*out = attrs;
}
return error;
}
int git_attr_file__from_file(git_attr_file **out, const char *path)
{
int error = GIT_SUCCESS;
git_fbuffer fbuf = GIT_FBUFFER_INIT;
*out = NULL;
if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS ||
(error = git_attr_file__from_buffer(out, fbuf.data)) < GIT_SUCCESS)
{
git__rethrow(error, "Could not open attribute file '%s'", path);
} else {
/* save path (okay to fail) */
(*out)->path = git__strdup(path);
}
git_futils_freebuffer(&fbuf);
return error;
}
void git_attr_file__free(git_attr_file *file)
{
unsigned int i;
git_attr_rule *rule;
if (!file)
return;
git_vector_foreach(&file->rules, i, rule) {
free_rule(rule);
}
git_vector_free(&file->rules);
git__free(file->path);
file->path = NULL;
}
unsigned long git_attr_file__name_hash(const char *name)
{
unsigned long h = 5381;
int c;
assert(name);
while ((c = (int)*name++) != 0)
h = ((h << 5) + h) + c;
return h;
}
int git_attr_file__lookup_one(
git_attr_file *file,
const git_attr_path *path,
const char *attr,
const char **value)
{
unsigned int 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) {
int pos = git_vector_bsearch(&rule->assigns, &name);
git_clearerror(); /* okay if search failed */
if (pos >= 0) {
*value = ((git_attr_assignment *)
git_vector_get(&rule->assigns, pos))->value;
break;
}
}
return GIT_SUCCESS;
}
int git_attr_rule__match_path(
git_attr_rule *rule,
const git_attr_path *path)
{
int matched = FNM_NOMATCH;
if (rule->match.directory && !path->is_dir)
return matched;
if (rule->match.fullpath)
matched = p_fnmatch(rule->match.pattern, path->path, FNM_PATHNAME);
else
matched = p_fnmatch(rule->match.pattern, path->basename, 0);
if (rule->match.negative)
matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS;
return matched;
}
git_attr_assignment *git_attr_rule__lookup_assignment(
git_attr_rule *rule, const char *name)
{
int pos;
git_attr_name key;
key.name = name;
key.name_hash = git_attr_file__name_hash(name);
pos = git_vector_bsearch(&rule->assigns, &key);
git_clearerror(); /* okay if search failed */
return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL;
}
int git_attr_path__init(
git_attr_path *info, const char *path)
{
info->path = path;
info->basename = strrchr(path, '/');
if (info->basename)
info->basename++;
if (!info->basename || !*info->basename)
info->basename = path;
info->is_dir = (git_futils_isdir(path) == GIT_SUCCESS);
return GIT_SUCCESS;
}
/*
* 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".
*/
/*
* This will return GIT_SUCCESS if the spec was filled out,
* GIT_ENOTFOUND if the fnmatch does not require matching, or
* another error code there was an actual problem.
*/
static int parse_fnmatch(
git_attr_fnmatch *spec,
const char **base)
{
const char *pattern;
const char *scan;
int slash_count;
int error = GIT_SUCCESS;
assert(base && *base);
pattern = *base;
while (isspace(*pattern)) pattern++;
if (!*pattern || *pattern == '#') {
error = GIT_ENOTFOUND;
goto skip_to_eol;
}
if (*pattern == '!') {
spec->negative = 1;
pattern++;
} else {
spec->negative = 0;
}
spec->fullpath = 0;
slash_count = 0;
for (scan = pattern; *scan != '\0'; ++scan) {
if (isspace(*scan) && *(scan - 1) != '\\')
break;
if (*scan == '/') {
spec->fullpath = 1;
slash_count++;
}
}
*base = scan;
spec->length = scan - pattern;
spec->pattern = git__strndup(pattern, spec->length);
if (!spec->pattern) {
error = GIT_ENOMEM;
goto skip_to_eol;
} else {
char *from = spec->pattern, *to = spec->pattern;
while (*from) {
if (*from == '\\') {
from++;
spec->length--;
}
*to++ = *from++;
}
*to = '\0';
}
if (pattern[spec->length - 1] == '/') {
spec->length--;
spec->pattern[spec->length] = '\0';
spec->directory = 1;
if (--slash_count <= 0)
spec->fullpath = 0;
} else {
spec->directory = 0;
}
return GIT_SUCCESS;
skip_to_eol:
/* skip to end of line */
while (*pattern && *pattern != '\n') pattern++;
if (*pattern == '\n') pattern++;
*base = pattern;
return error;
}
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 int parse_assigns(
git_vector *assigns,
const char **base)
{
int error = GIT_SUCCESS;
const char *scan = *base;
git_attr_assignment *assign = NULL;
assert(assigns && !assigns->length);
while (*scan && *scan != '\n') {
const char *name_start, *value_start;
/* skip leading blanks */
while (isspace(*scan) && *scan != '\n') scan++;
/* allocate assign if needed */
if (!assign) {
assign = git__calloc(1, sizeof(git_attr_assignment));
if (!assign) {
error = GIT_ENOMEM;
break;
}
}
assign->name_hash = 5381;
assign->value = GIT_ATTR_TRUE;
assign->is_allocated = 0;
/* look for magic name prefixes */
if (*scan == '-') {
assign->value = GIT_ATTR_FALSE;
scan++;
} else if (*scan == '!') {
assign->value = NULL; /* explicit unspecified state */
scan++;
} else if (*scan == '#') /* comment rest of line */
break;
/* find the name */
name_start = scan;
while (*scan && !isspace(*scan) && *scan != '=') {
assign->name_hash =
((assign->name_hash << 5) + assign->name_hash) + *scan;
scan++;
}
assign->name_len = scan - name_start;
if (assign->name_len <= 0) {
/* must have found lone prefix (" - ") or leading = ("=foo")
* or end of buffer -- advance until whitespace and continue
*/
while (*scan && !isspace(*scan)) scan++;
continue;
}
/* if there is an equals sign, find the value */
if (*scan == '=') {
for (value_start = ++scan; *scan && !isspace(*scan); ++scan);
/* if we found a value, allocate permanent storage for it */
if (scan > value_start) {
assign->value = git__strndup(value_start, scan - value_start);
if (!assign->value) {
error = GIT_ENOMEM;
break;
} else {
assign->is_allocated = 1;
}
}
}
/* allocate permanent storage for name */
assign->name = git__strndup(name_start, assign->name_len);
if (!assign->name) {
error = GIT_ENOMEM;
break;
}
/* insert allocated assign into vector */
error = git_vector_insert(assigns, assign);
if (error < GIT_SUCCESS)
break;
/* clear assign since it is now "owned" by the vector */
assign = NULL;
}
if (!assigns->length)
error = git__throw(GIT_ENOTFOUND, "No attribute assignments found for rule");
else {
assigns->_cmp = sort_by_hash_and_name;
git_vector_sort(assigns);
}
if (assign != NULL) {
git__free(assign->name);
if (assign->is_allocated)
git__free((void *)assign->value);
git__free(assign);
}
while (*scan && *scan != '\n') scan++;
*base = scan;
return error;
}
static int free_rule(git_attr_rule *rule)
{
unsigned int i;
git_attr_assignment *assign;
if (!rule)
return GIT_SUCCESS;
git__free(rule->match.pattern);
rule->match.pattern = NULL;
rule->match.length = 0;
git_vector_foreach(&rule->assigns, i, assign) {
git__free(assign->name);
assign->name = NULL;
if (assign->is_allocated) {
git__free((void *)assign->value);
assign->value = NULL;
}
}
return GIT_SUCCESS;
}

87
src/attr_file.h Normal file
View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2009-2011 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_attr_file_h__
#define INCLUDE_attr_file_h__
#include "git2/attr.h"
#include "vector.h"
typedef struct {
char *pattern;
size_t length;
int negative;
int directory;
int fullpath;
} git_attr_fnmatch;
typedef struct {
const char *name;
unsigned long name_hash;
} git_attr_name;
typedef struct {
char *name;
unsigned long name_hash;
size_t name_len;
const char *value;
int is_allocated;
} git_attr_assignment;
typedef struct {
git_attr_fnmatch match;
git_vector assigns; /* <git_attr_assignment*> */
} git_attr_rule;
typedef struct {
char *path;
git_vector rules; /* <git_attr_rule*> */
} git_attr_file;
typedef struct {
const char *path;
const char *basename;
int is_dir;
} git_attr_path;
/*
* git_attr_file API
*/
extern int git_attr_file__from_buffer(git_attr_file **out, const char *buf);
extern int git_attr_file__from_file(git_attr_file **out, const char *path);
extern void git_attr_file__free(git_attr_file *file);
extern int git_attr_file__lookup_one(
git_attr_file *file,
const git_attr_path *path,
const char *attr,
const char **value);
/* loop over rules in file from bottom to top */
#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \
git_vector_rforeach(&(file)->rules, (iter), (rule)) \
if (git_attr_rule__match_path((rule), (path)) == GIT_SUCCESS)
extern unsigned long git_attr_file__name_hash(const char *name);
/*
* other utilities
*/
extern int git_attr_rule__match_path(
git_attr_rule *rule,
const git_attr_path *path);
extern git_attr_assignment *git_attr_rule__lookup_assignment(
git_attr_rule *rule, const char *name);
extern int git_attr_path__init(
git_attr_path *info, const char *path);
#endif

View File

@ -241,3 +241,17 @@ int git_hashtable_merge(git_hashtable *self, git_hashtable *other)
return insert_nodes(self, other->nodes, other->key_count);
}
/**
* Standard string
*/
uint32_t git_hash__strhash_cb(const void *key, int hash_id)
{
static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
2147483647,
0x5d20bb23,
0x7daaab3c
};
return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
}

View File

@ -76,5 +76,12 @@ GIT_INLINE(int) git_hashtable_insert(git_hashtable *h, const void *key, void *va
_node->key = NULL; _node->value = NULL; _self->key_count--;\
}
/*
* If you want a hashtable with standard string keys, you can
* just pass git_hash__strcmp_cb and git_hash__strhash_cb to
* git_hashtable_alloc.
*/
#define git_hash__strcmp_cb git__strcmp_cb
extern uint32_t git_hash__strhash_cb(const void *key, int hash_id);
#endif

View File

@ -31,17 +31,6 @@ struct packref {
static const int default_table_size = 32;
static uint32_t reftable_hash(const void *key, int hash_id)
{
static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
2147483647,
0x5d20bb23,
0x7daaab3c
};
return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
}
static int reference_read(
git_fbuffer *file_content,
time_t *mtime,
@ -443,9 +432,7 @@ static int packed_load(git_repository *repo)
/* First we make sure we have allocated the hash table */
if (ref_cache->packfile == NULL) {
ref_cache->packfile = git_hashtable_alloc(
default_table_size,
reftable_hash,
(git_hash_keyeq_ptr)&git__strcmp_cb);
default_table_size, git_hash__strhash_cb, git_hash__strcmp_cb);
if (ref_cache->packfile == NULL) {
error = GIT_ENOMEM;

View File

@ -59,6 +59,7 @@ void git_repository_free(git_repository *repo)
git_cache_free(&repo->objects);
git_repository__refcache_free(&repo->references);
git_repository__attr_cache_free(&repo->attrcache);
git__free(repo->path_repository);
git__free(repo->workdir);

View File

@ -19,6 +19,7 @@
#include "refs.h"
#include "buffer.h"
#include "odb.h"
#include "attr.h"
#define DOT_GIT ".git"
#define GIT_DIR DOT_GIT "/"
@ -38,6 +39,7 @@ struct git_repository {
git_cache objects;
git_refcache references;
git_attr_cache attrcache;
char *path_repository;
char *workdir;

View File

@ -109,6 +109,7 @@ extern void **git__bsearch(const void *key, void **base, size_t nmemb,
int (*compar)(const void *, const void *));
extern int git__strcmp_cb(const void *a, const void *b);
extern uint32_t git__strhash_cb(const void *key, int hash_id);
typedef struct {
short refcount;

View File

@ -29,7 +29,12 @@ static int resize_vector(git_vector *v)
void git_vector_free(git_vector *v)
{
assert(v);
git__free(v->contents);
v->contents = NULL;
v->length = 0;
v->_alloc_size = 0;
}
int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp)

View File

@ -19,6 +19,8 @@ typedef struct git_vector {
int sorted;
} git_vector;
#define GIT_VECTOR_INIT {0}
int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp);
void git_vector_free(git_vector *v);
void git_vector_clear(git_vector *v);
@ -39,6 +41,9 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position)
#define git_vector_foreach(v, iter, elem) \
for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ )
#define git_vector_rforeach(v, iter, elem) \
for ((iter) = (v)->length; (iter) > 0 && ((elem) = (v)->contents[(iter)-1], 1); (iter)-- )
int git_vector_insert(git_vector *v, void *element);
int git_vector_remove(git_vector *v, unsigned int idx);
void git_vector_uniq(git_vector *v);

236
tests-clay/attr/file.c Normal file
View File

@ -0,0 +1,236 @@
#include "clay_libgit2.h"
#include "attr_file.h"
#define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X)))
#define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y)))
void test_attr_file__simple_read(void)
{
git_attr_file *file = NULL;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr0")));
cl_assert_strequal(cl_fixture("attr/attr0"), file->path);
cl_assert(file->rules.length == 1);
git_attr_rule *rule = get_rule(0);
cl_assert(rule != NULL);
cl_assert_strequal("*", rule->match.pattern);
cl_assert(rule->match.length == 1);
cl_assert(!rule->match.negative);
cl_assert(!rule->match.directory);
cl_assert(!rule->match.fullpath);
cl_assert(rule->assigns.length == 1);
git_attr_assignment *assign = get_assign(rule, 0);
cl_assert(assign != NULL);
cl_assert_strequal("binary", assign->name);
cl_assert(assign->name_len == 6);
cl_assert(assign->value == GIT_ATTR_TRUE);
cl_assert(!assign->is_allocated);
git_attr_file__free(file);
}
void test_attr_file__match_variants(void)
{
git_attr_file *file = NULL;
git_attr_rule *rule;
git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr1")));
cl_assert_strequal(cl_fixture("attr/attr1"), file->path);
cl_assert(file->rules.length == 10);
/* let's do a thorough check of this rule, then just verify
* the things that are unique for the later rules
*/
rule = get_rule(0);
cl_assert(rule);
cl_assert_strequal("pat0", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat0"));
cl_assert(!rule->match.negative);
cl_assert(!rule->match.directory);
cl_assert(!rule->match.fullpath);
cl_assert(rule->assigns.length == 1);
assign = get_assign(rule,0);
cl_assert_strequal("attr0", assign->name);
cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name));
cl_assert(assign->name_len == strlen("attr0"));
cl_assert(assign->value == GIT_ATTR_TRUE);
cl_assert(!assign->is_allocated);
rule = get_rule(1);
cl_assert_strequal("pat1", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat1"));
cl_assert(rule->match.negative);
rule = get_rule(2);
cl_assert_strequal("pat2", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat2"));
cl_assert(rule->match.directory);
cl_assert(!rule->match.fullpath);
rule = get_rule(3);
cl_assert_strequal("pat3dir/pat3file", rule->match.pattern);
cl_assert(!rule->match.directory);
cl_assert(rule->match.fullpath);
rule = get_rule(4);
cl_assert_strequal("pat4.*", rule->match.pattern);
cl_assert(!rule->match.negative);
cl_assert(!rule->match.directory);
cl_assert(!rule->match.fullpath);
rule = get_rule(5);
cl_assert_strequal("*.pat5", rule->match.pattern);
rule = get_rule(7);
cl_assert_strequal("pat7[a-e]??[xyz]", rule->match.pattern);
cl_assert(rule->assigns.length == 1);
assign = get_assign(rule,0);
cl_assert_strequal("attr7", assign->name);
cl_assert(assign->value == GIT_ATTR_TRUE);
rule = get_rule(8);
cl_assert_strequal("pat8 with spaces", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat8 with spaces"));
cl_assert(!rule->match.negative);
cl_assert(!rule->match.directory);
cl_assert(!rule->match.fullpath);
rule = get_rule(9);
cl_assert_strequal("pat9", rule->match.pattern);
git_attr_file__free(file);
}
static void check_one_assign(
git_attr_file *file,
int rule_idx,
int assign_idx,
const char *pattern,
const char *name,
const char *value,
int is_allocated)
{
git_attr_rule *rule = get_rule(rule_idx);
git_attr_assignment *assign = get_assign(rule, assign_idx);
cl_assert_strequal(pattern, rule->match.pattern);
cl_assert(rule->assigns.length == 1);
cl_assert_strequal(name, assign->name);
cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name));
cl_assert(assign->name_len == strlen(name));
cl_assert(assign->is_allocated == is_allocated);
if (is_allocated)
cl_assert_strequal(value, assign->value);
else
cl_assert(assign->value == value);
}
void test_attr_file__assign_variants(void)
{
git_attr_file *file = NULL;
git_attr_rule *rule;
git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr2")));
cl_assert_strequal(cl_fixture("attr/attr2"), file->path);
cl_assert(file->rules.length == 11);
check_one_assign(file, 0, 0, "pat0", "simple", GIT_ATTR_TRUE, 0);
check_one_assign(file, 1, 0, "pat1", "neg", GIT_ATTR_FALSE, 0);
check_one_assign(file, 2, 0, "*", "notundef", GIT_ATTR_TRUE, 0);
check_one_assign(file, 3, 0, "pat2", "notundef", NULL, 0);
check_one_assign(file, 4, 0, "pat3", "assigned", "test-value", 1);
check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", "value-with-more-chars", 1);
check_one_assign(file, 6, 0, "pat5", "empty", GIT_ATTR_TRUE, 0);
check_one_assign(file, 7, 0, "pat6", "negempty", GIT_ATTR_FALSE, 0);
rule = get_rule(8);
cl_assert_strequal("pat7", rule->match.pattern);
cl_assert(rule->assigns.length == 5);
/* assignments will be sorted by hash value, so we have to do
* lookups by search instead of by position
*/
assign = git_attr_rule__lookup_assignment(rule, "multiple");
cl_assert(assign);
cl_assert_strequal("multiple", assign->name);
cl_assert(assign->value == GIT_ATTR_TRUE);
assign = git_attr_rule__lookup_assignment(rule, "single");
cl_assert(assign);
cl_assert_strequal("single", assign->name);
cl_assert(assign->value == GIT_ATTR_FALSE);
assign = git_attr_rule__lookup_assignment(rule, "values");
cl_assert(assign);
cl_assert_strequal("values", assign->name);
cl_assert_strequal("1", assign->value);
assign = git_attr_rule__lookup_assignment(rule, "also");
cl_assert(assign);
cl_assert_strequal("also", assign->name);
cl_assert_strequal("a-really-long-value/*", assign->value);
assign = git_attr_rule__lookup_assignment(rule, "happy");
cl_assert(assign);
cl_assert_strequal("happy", assign->name);
cl_assert_strequal("yes!", assign->value);
assign = git_attr_rule__lookup_assignment(rule, "other");
cl_assert(!assign);
rule = get_rule(9);
cl_assert_strequal("pat8", rule->match.pattern);
cl_assert(rule->assigns.length == 2);
assign = git_attr_rule__lookup_assignment(rule, "again");
cl_assert(assign);
cl_assert_strequal("again", assign->name);
cl_assert(assign->value == GIT_ATTR_TRUE);
assign = git_attr_rule__lookup_assignment(rule, "another");
cl_assert(assign);
cl_assert_strequal("another", assign->name);
cl_assert_strequal("12321", assign->value);
check_one_assign(file, 10, 0, "pat9", "at-eof", GIT_ATTR_FALSE, 0);
git_attr_file__free(file);
}
void test_attr_file__check_attr_examples(void)
{
git_attr_file *file = NULL;
git_attr_rule *rule;
git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr3")));
cl_assert_strequal(cl_fixture("attr/attr3"), file->path);
cl_assert(file->rules.length == 3);
rule = get_rule(0);
cl_assert_strequal("*.java", rule->match.pattern);
cl_assert(rule->assigns.length == 3);
assign = git_attr_rule__lookup_assignment(rule, "diff");
cl_assert_strequal("diff", assign->name);
cl_assert_strequal("java", assign->value);
assign = git_attr_rule__lookup_assignment(rule, "crlf");
cl_assert_strequal("crlf", assign->name);
cl_assert(GIT_ATTR_FALSE == assign->value);
assign = git_attr_rule__lookup_assignment(rule, "myAttr");
cl_assert_strequal("myAttr", assign->name);
cl_assert(GIT_ATTR_TRUE == assign->value);
assign = git_attr_rule__lookup_assignment(rule, "missing");
cl_assert(assign == NULL);
rule = get_rule(1);
cl_assert_strequal("NoMyAttr.java", rule->match.pattern);
cl_assert(rule->assigns.length == 1);
assign = get_assign(rule, 0);
cl_assert_strequal("myAttr", assign->name);
cl_assert(assign->value == NULL);
rule = get_rule(2);
cl_assert_strequal("README", rule->match.pattern);
cl_assert(rule->assigns.length == 1);
assign = get_assign(rule, 0);
cl_assert_strequal("caveat", assign->name);
cl_assert_strequal("unspecified", assign->value);
git_attr_file__free(file);
}

237
tests-clay/attr/lookup.c Normal file
View File

@ -0,0 +1,237 @@
#include "clay_libgit2.h"
#include "attr.h"
void test_attr_lookup__simple(void)
{
git_attr_file *file = NULL;
git_attr_path path;
const char *value = NULL;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr0")));
cl_assert_strequal(cl_fixture("attr/attr0"), file->path);
cl_assert(file->rules.length == 1);
cl_git_pass(git_attr_path__init(&path, "test"));
cl_assert_strequal("test", path.path);
cl_assert_strequal("test", path.basename);
cl_assert(!path.is_dir);
cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value));
cl_assert(value == GIT_ATTR_TRUE);
cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value));
cl_assert(!value);
git_attr_file__free(file);
}
typedef struct {
const char *path;
const char *attr;
const char *expected;
int use_strcmp;
int force_dir;
} test_case;
static void run_test_cases(git_attr_file *file, test_case *cases)
{
git_attr_path path;
const char *value = NULL;
test_case *c;
int error;
for (c = cases; c->path != NULL; c++) {
/* Put this in because I was surprised that all the tests passed */
/* fprintf(stderr, "checking '%s' attr %s == %s\n", */
/* c->path, c->attr, c->expected); */
cl_git_pass(git_attr_path__init(&path, c->path));
if (c->force_dir)
path.is_dir = 1;
error = git_attr_file__lookup_one(file,&path,c->attr,&value);
if (error != GIT_SUCCESS)
fprintf(stderr, "failure with %s %s %s\n", c->path, c->attr, c->expected);
cl_git_pass(error);
if (c->use_strcmp)
cl_assert_strequal(c->expected, value);
else
cl_assert(c->expected == value);
}
}
void test_attr_lookup__match_variants(void)
{
git_attr_file *file = NULL;
git_attr_path path;
test_case cases[] = {
/* pat0 -> simple match */
{ "pat0", "attr0", GIT_ATTR_TRUE, 0, 0 },
{ "/testing/for/pat0", "attr0", GIT_ATTR_TRUE, 0, 0 },
{ "relative/to/pat0", "attr0", GIT_ATTR_TRUE, 0, 0 },
{ "this-contains-pat0-inside", "attr0", NULL, 0, 0 },
{ "this-aint-right", "attr0", NULL, 0, 0 },
{ "/this/pat0/dont/match", "attr0", NULL, 0, 0 },
/* negative match */
{ "pat0", "attr1", GIT_ATTR_TRUE, 0, 0 },
{ "pat1", "attr1", NULL, 0, 0 },
{ "/testing/for/pat1", "attr1", NULL, 0, 0 },
{ "/testing/for/pat0", "attr1", GIT_ATTR_TRUE, 0, 0 },
{ "/testing/for/pat1/inside", "attr1", GIT_ATTR_TRUE, 0, 0 },
{ "misc", "attr1", GIT_ATTR_TRUE, 0, 0 },
/* dir match */
{ "pat2", "attr2", NULL, 0, 0 },
{ "pat2", "attr2", GIT_ATTR_TRUE, 0, 1 },
{ "/testing/for/pat2", "attr2", NULL, 0, 0 },
{ "/testing/for/pat2", "attr2", GIT_ATTR_TRUE, 0, 1 },
{ "/not/pat2/yousee", "attr2", NULL, 0, 0 },
{ "/not/pat2/yousee", "attr2", NULL, 0, 1 },
/* path match */
{ "pat3file", "attr3", NULL, 0, 0 },
{ "/pat3dir/pat3file", "attr3", NULL, 0, 0 },
{ "pat3dir/pat3file", "attr3", GIT_ATTR_TRUE, 0, 0 },
/* pattern* match */
{ "pat4.txt", "attr4", GIT_ATTR_TRUE, 0, 0 },
{ "/fun/fun/fun/pat4.c", "attr4", GIT_ATTR_TRUE, 0, 0 },
{ "pat4.", "attr4", GIT_ATTR_TRUE, 0, 0 },
{ "pat4", "attr4", NULL, 0, 0 },
{ "/fun/fun/fun/pat4.dir", "attr4", GIT_ATTR_TRUE, 0, 1 },
/* *pattern match */
{ "foo.pat5", "attr5", GIT_ATTR_TRUE, 0, 0 },
{ "foo.pat5", "attr5", GIT_ATTR_TRUE, 0, 1 },
{ "/this/is/ok.pat5", "attr5", GIT_ATTR_TRUE, 0, 0 },
{ "/this/is/bad.pat5/yousee.txt", "attr5", NULL, 0, 0 },
{ "foo.pat5", "attr100", NULL, 0, 0 },
/* glob match with slashes */
{ "foo.pat6", "attr6", NULL, 0, 0 },
{ "pat6/pat6/foobar.pat6", "attr6", GIT_ATTR_TRUE, 0, 0 },
{ "pat6/pat6/.pat6", "attr6", GIT_ATTR_TRUE, 0, 0 },
{ "pat6/pat6/extra/foobar.pat6", "attr6", NULL, 0, 0 },
{ "/prefix/pat6/pat6/foobar.pat6", "attr6", NULL, 0, 0 },
{ "/pat6/pat6/foobar.pat6", "attr6", NULL, 0, 0 },
/* complex pattern */
{ "pat7a12z", "attr7", GIT_ATTR_TRUE, 0, 0 },
{ "pat7e__x", "attr7", GIT_ATTR_TRUE, 0, 0 },
{ "pat7b/1y", "attr7", NULL, 0, 0 }, /* ? does not match / */
{ "pat7e_x", "attr7", NULL, 0, 0 },
{ "pat7aaaa", "attr7", NULL, 0, 0 },
{ "pat7zzzz", "attr7", NULL, 0, 0 },
{ "/this/can/be/anything/pat7a12z", "attr7", GIT_ATTR_TRUE, 0, 0 },
{ "but/it/still/must/match/pat7aaaa", "attr7", NULL, 0, 0 },
{ "pat7aaay.fail", "attr7", NULL, 0, 0 },
/* pattern with spaces */
{ "pat8 with spaces", "attr8", GIT_ATTR_TRUE, 0, 0 },
{ "/gotta love/pat8 with spaces", "attr8", GIT_ATTR_TRUE, 0, 0 },
{ "failing pat8 with spaces", "attr8", NULL, 0, 0 },
{ "spaces", "attr8", NULL, 0, 0 },
/* pattern at eof */
{ "pat9", "attr9", GIT_ATTR_TRUE, 0, 0 },
{ "/eof/pat9", "attr9", GIT_ATTR_TRUE, 0, 0 },
{ "pat", "attr9", NULL, 0, 0 },
{ "at9", "attr9", NULL, 0, 0 },
{ "pat9.fail", "attr9", NULL, 0, 0 },
/* sentinel at end */
{ NULL, NULL, NULL, 0, 0 }
};
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr1")));
cl_assert_strequal(cl_fixture("attr/attr1"), file->path);
cl_assert(file->rules.length == 10);
cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0"));
cl_assert_strequal("pat0", path.basename);
run_test_cases(file, cases);
git_attr_file__free(file);
}
void test_attr_lookup__assign_variants(void)
{
git_attr_file *file = NULL;
test_case cases[] = {
/* pat0 -> simple assign */
{ "pat0", "simple", GIT_ATTR_TRUE, 0, 0 },
{ "/testing/pat0", "simple", GIT_ATTR_TRUE, 0, 0 },
{ "pat0", "fail", NULL, 0, 0 },
{ "/testing/pat0", "fail", NULL, 0, 0 },
/* negative assign */
{ "pat1", "neg", GIT_ATTR_FALSE, 0, 0 },
{ "/testing/pat1", "neg", GIT_ATTR_FALSE, 0, 0 },
{ "pat1", "fail", NULL, 0, 0 },
{ "/testing/pat1", "fail", NULL, 0, 0 },
/* forced undef */
{ "pat1", "notundef", GIT_ATTR_TRUE, 0, 0 },
{ "pat2", "notundef", NULL, 0, 0 },
{ "/lead/in/pat1", "notundef", GIT_ATTR_TRUE, 0, 0 },
{ "/lead/in/pat2", "notundef", NULL, 0, 0 },
/* assign value */
{ "pat3", "assigned", "test-value", 1, 0 },
{ "pat3", "notassigned", NULL, 0, 0 },
/* assign value */
{ "pat4", "rule-with-more-chars", "value-with-more-chars", 1, 0 },
{ "pat4", "notassigned-rule-with-more-chars", NULL, 0, 0 },
/* empty assignments */
{ "pat5", "empty", GIT_ATTR_TRUE, 0, 0 },
{ "pat6", "negempty", GIT_ATTR_FALSE, 0, 0 },
/* multiple assignment */
{ "pat7", "multiple", GIT_ATTR_TRUE, 0, 0 },
{ "pat7", "single", GIT_ATTR_FALSE, 0, 0 },
{ "pat7", "values", "1", 1, 0 },
{ "pat7", "also", "a-really-long-value/*", 1, 0 },
{ "pat7", "happy", "yes!", 1, 0 },
{ "pat8", "again", GIT_ATTR_TRUE, 0, 0 },
{ "pat8", "another", "12321", 1, 0 },
/* bad assignment */
{ "patbad0", "simple", NULL, 0, 0 },
{ "patbad0", "notundef", GIT_ATTR_TRUE, 0, 0 },
{ "patbad1", "simple", NULL, 0, 0 },
/* eof assignment */
{ "pat9", "at-eof", GIT_ATTR_FALSE, 0, 0 },
/* sentinel at end */
{ NULL, NULL, NULL, 0, 0 }
};
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr2")));
cl_assert(file->rules.length == 11);
run_test_cases(file, cases);
git_attr_file__free(file);
}
void test_attr_lookup__check_attr_examples(void)
{
git_attr_file *file = NULL;
test_case cases[] = {
{ "foo.java", "diff", "java", 1, 0 },
{ "foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
{ "foo.java", "myAttr", GIT_ATTR_TRUE, 0, 0 },
{ "foo.java", "other", NULL, 0, 0 },
{ "/prefix/dir/foo.java", "diff", "java", 1, 0 },
{ "/prefix/dir/foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
{ "/prefix/dir/foo.java", "myAttr", GIT_ATTR_TRUE, 0, 0 },
{ "/prefix/dir/foo.java", "other", NULL, 0, 0 },
{ "NoMyAttr.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
{ "NoMyAttr.java", "myAttr", NULL, 0, 0 },
{ "NoMyAttr.java", "other", NULL, 0, 0 },
{ "/prefix/dir/NoMyAttr.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
{ "/prefix/dir/NoMyAttr.java", "myAttr", NULL, 0, 0 },
{ "/prefix/dir/NoMyAttr.java", "other", NULL, 0, 0 },
{ "README", "caveat", "unspecified", 1, 0 },
{ "/specific/path/README", "caveat", "unspecified", 1, 0 },
{ "README", "missing", NULL, 0, 0 },
{ "/specific/path/README", "missing", NULL, 0, 0 },
/* sentinel at end */
{ NULL, NULL, NULL, 0, 0 }
};
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr3")));
cl_assert(file->rules.length == 3);
run_test_cases(file, cases);
git_attr_file__free(file);
}

140
tests-clay/attr/repo.c Normal file
View File

@ -0,0 +1,140 @@
#include "clay_libgit2.h"
#include "fileops.h"
#include "git2/attr.h"
static git_repository *g_repo = NULL;
void test_attr_repo__initialize(void)
{
/* before each test, instantiate the attr repo from the fixtures and
* rename the .gitted to .git so it is a repo with a working dir.
*/
cl_fixture_sandbox("attr");
cl_git_pass(p_rename("attr/.gitted", "attr/.git"));
cl_git_pass(git_repository_open(&g_repo, "attr/.git"));
}
void test_attr_repo__cleanup(void)
{
git_repository_free(g_repo);
g_repo = NULL;
cl_fixture_cleanup("attr");
}
void test_attr_repo__get_one(void)
{
const char *value;
struct {
const char *file;
const char *attr;
const char *expected;
} test_cases[] = {
{ "root_test1", "repoattr", GIT_ATTR_TRUE },
{ "root_test1", "rootattr", GIT_ATTR_TRUE },
{ "root_test1", "missingattr", NULL },
{ "root_test1", "subattr", NULL },
{ "root_test1", "negattr", NULL },
{ "root_test2", "repoattr", GIT_ATTR_TRUE },
{ "root_test2", "rootattr", GIT_ATTR_FALSE },
{ "root_test2", "missingattr", NULL },
{ "root_test3", "repoattr", GIT_ATTR_TRUE },
{ "root_test3", "rootattr", NULL },
{ "subdir/subdir_test1", "repoattr", GIT_ATTR_TRUE },
{ "subdir/subdir_test1", "rootattr", GIT_ATTR_TRUE },
{ "subdir/subdir_test1", "missingattr", NULL },
{ "subdir/subdir_test1", "subattr", "yes" },
{ "subdir/subdir_test1", "negattr", GIT_ATTR_FALSE },
{ "subdir/subdir_test1", "another", NULL },
{ "subdir/subdir_test2.txt", "repoattr", GIT_ATTR_TRUE },
{ "subdir/subdir_test2.txt", "rootattr", GIT_ATTR_TRUE },
{ "subdir/subdir_test2.txt", "missingattr", NULL },
{ "subdir/subdir_test2.txt", "subattr", "yes" },
{ "subdir/subdir_test2.txt", "negattr", GIT_ATTR_FALSE },
{ "subdir/subdir_test2.txt", "another", "one" },
{ NULL, NULL, NULL }
}, *scan;
for (scan = test_cases; scan->file != NULL; scan++) {
git_buf b = GIT_BUF_INIT;
git_buf_printf(&b, "%s:%s == expect %s",
scan->file, scan->attr, scan->expected);
cl_must_pass_(
git_attr_get(g_repo, scan->file, scan->attr, &value) == GIT_SUCCESS,
b.ptr);
git_buf_printf(&b, ", got %s", value);
if (scan->expected == NULL ||
scan->expected == GIT_ATTR_TRUE ||
scan->expected == GIT_ATTR_FALSE)
{
cl_assert_(scan->expected == value, b.ptr);
} else {
cl_assert_strequal(scan->expected, value);
}
git_buf_free(&b);
}
}
void test_attr_repo__get_many(void)
{
const char *names[4] = { "repoattr", "rootattr", "missingattr", "subattr" };
const char *values[4];
cl_git_pass(git_attr_get_many(g_repo, "root_test1", 4, names, values));
cl_assert(values[0] == GIT_ATTR_TRUE);
cl_assert(values[1] == GIT_ATTR_TRUE);
cl_assert(values[2] == NULL);
cl_assert(values[3] == NULL);
cl_git_pass(git_attr_get_many(g_repo, "root_test2", 4, names, values));
cl_assert(values[0] == GIT_ATTR_TRUE);
cl_assert(values[1] == GIT_ATTR_FALSE);
cl_assert(values[2] == NULL);
cl_assert(values[3] == NULL);
cl_git_pass(git_attr_get_many(g_repo, "subdir/subdir_test1", 4, names, values));
cl_assert(values[0] == GIT_ATTR_TRUE);
cl_assert(values[1] == GIT_ATTR_TRUE);
cl_assert(values[2] == NULL);
cl_assert_strequal("yes", values[3]);
}
static int count_attrs(
const char *GIT_UNUSED(name),
const char *GIT_UNUSED(value),
void *payload)
{
GIT_UNUSED_ARG(name);
GIT_UNUSED_ARG(value);
*((int *)payload) += 1;
return GIT_SUCCESS;
}
void test_attr_repo__foreach(void)
{
int count;
count = 0;
cl_git_pass(git_attr_foreach(g_repo, "root_test1", &count_attrs, &count));
cl_assert(count == 2);
count = 0;
cl_git_pass(git_attr_foreach(g_repo, "subdir/subdir_test1",
&count_attrs, &count));
cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */
count = 0;
cl_git_pass(git_attr_foreach(g_repo, "subdir/subdir_test2.txt",
&count_attrs, &count));
cl_assert(count == 5); /* repoattr, rootattr, subattr, negattr, another */
}

View File

@ -59,6 +59,19 @@ void cl_fixture_cleanup(const char *fixture_name);
*/
extern void clay_on_init(void);
extern void clay_on_shutdown(void);
extern void test_attr_file__assign_variants(void);
extern void test_attr_file__check_attr_examples(void);
extern void test_attr_file__match_variants(void);
extern void test_attr_file__simple_read(void);
extern void test_attr_lookup__assign_variants(void);
extern void test_attr_lookup__check_attr_examples(void);
extern void test_attr_lookup__match_variants(void);
extern void test_attr_lookup__simple(void);
extern void test_attr_repo__cleanup(void);
extern void test_attr_repo__foreach(void);
extern void test_attr_repo__get_many(void);
extern void test_attr_repo__get_one(void);
extern void test_attr_repo__initialize(void);
extern void test_buf_basic__printf(void);
extern void test_buf_basic__resize(void);
extern void test_config_add__cleanup(void);

View File

@ -108,6 +108,23 @@ static int clay_sandbox(void);
#define clay_on_suite() /* nop */
/* Autogenerated test data by clay */
static const struct clay_func _clay_cb_attr_file[] = {
{"assign_variants", &test_attr_file__assign_variants},
{"check_attr_examples", &test_attr_file__check_attr_examples},
{"match_variants", &test_attr_file__match_variants},
{"simple_read", &test_attr_file__simple_read}
};
static const struct clay_func _clay_cb_attr_lookup[] = {
{"assign_variants", &test_attr_lookup__assign_variants},
{"check_attr_examples", &test_attr_lookup__check_attr_examples},
{"match_variants", &test_attr_lookup__match_variants},
{"simple", &test_attr_lookup__simple}
};
static const struct clay_func _clay_cb_attr_repo[] = {
{"foreach", &test_attr_repo__foreach},
{"get_many", &test_attr_repo__get_many},
{"get_one", &test_attr_repo__get_one}
};
static const struct clay_func _clay_cb_buf_basic[] = {
{"printf", &test_buf_basic__printf},
{"resize", &test_buf_basic__resize}
@ -303,6 +320,24 @@ static const struct clay_func _clay_cb_status_worktree[] = {
static const struct clay_suite _clay_suites[] = {
{
"attr::file",
{NULL, NULL},
{NULL, NULL},
_clay_cb_attr_file, 4
},
{
"attr::lookup",
{NULL, NULL},
{NULL, NULL},
_clay_cb_attr_lookup, 4
},
{
"attr::repo",
{"initialize", &test_attr_repo__initialize},
{"cleanup", &test_attr_repo__cleanup},
_clay_cb_attr_repo, 3
},
{
"buf::basic",
{NULL, NULL},
{NULL, NULL},
@ -520,8 +555,8 @@ static const struct clay_suite _clay_suites[] = {
}
};
static size_t _clay_suite_count = 36;
static size_t _clay_callback_count = 120;
static size_t _clay_suite_count = 39;
static size_t _clay_callback_count = 131;
/* Core test functions */
static void

BIN
tests/resources/attr/.gitattributes vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/resources/attr/attr0 Normal file

Binary file not shown.

BIN
tests/resources/attr/attr1 Normal file

Binary file not shown.

BIN
tests/resources/attr/attr2 Normal file

Binary file not shown.

BIN
tests/resources/attr/attr3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.