Commit ee1f0b1a by Russell Belfer

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.
parent be00b00d
/*
* 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
#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;
}
}
/*
* 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
#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;
}
/*
* 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
......@@ -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]);
}
......@@ -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
......@@ -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;
......
......@@ -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);
......
......@@ -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;
......
......@@ -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;
......
......@@ -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)
......
......@@ -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);
......
#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);
}
#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);
}
#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 */
}
......@@ -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);
......
......@@ -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
......
* rootattr
root_test2 -rootattr
root_test3 !rootattr
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
Unnamed repository; edit this file 'description' to name the repository.
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer <arrbee@arrbee.com> 1324416995 -0800 commit (initial): initial test data
0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer <arrbee@arrbee.com> 1324416995 -0800 commit (initial): initial test data
6bab5c79cd5140d0f800917f550eb2a3dc32b0da
# a comment followed by some blank lines
# another comment that is indented
# variations on fnmatch
pat0 attr0
!pat1 attr1
pat2/ attr2
pat3dir/pat3file attr3
pat4.* attr4
*.pat5 attr5
pat6/pat6/*.pat6 attr6
pat7[a-e]??[xyz] attr7 # with a comment on the line
pat8\ with\ spaces attr8
invalid # attr with no assignments doesn't count
also/invalid
invalid.again/
# next attr is at eof
pat9 attr9
\ No newline at end of file
# variations on assignments
pat0 simple
pat1 -neg
* notundef
pat2 !notundef
pat3 assigned=test-value
pat4 rule-with-more-chars=value-with-more-chars
pat5 empty=
pat6 -negempty=
pat7 multiple -single values=1 also=a-really-long-value/* happy=yes!
# the next line has trailing spaces
pat8 again= another=12321
patbad0 # empty assignment does not count
# next line will be another simple empty assign that should not count
patbad1
# BTW I think there are 11 valid rules and two "invalid" empty ones
pat9 -at-eof
\ No newline at end of file
# These are examples from the git-check-attr.1 man page
*.java diff=java -crlf myAttr
NoMyAttr.java !myAttr
README caveat=unspecified
* subattr=yes -negattr
subdir/*.txt another=one
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment