Commit 73b51450 by Russell Belfer

Add support for macros and cache flush API.

Add support for git attribute macro definitions.  Also, add
support for cache flush API to clear the attribute file content
cache when needed.

Additionally, improved the handling of global and system files,
making common utility functions in fileops and converting config
and attr to both use the common functions.

Adds a bunch more tests and fixed some memory leaks.  Note that
adding macros required me to use refcounted attribute assignment
definitions, which complicated, but probably improved memory usage.
parent ee1f0b1a
...@@ -50,6 +50,30 @@ GIT_EXTERN(int) git_attr_foreach( ...@@ -50,6 +50,30 @@ GIT_EXTERN(int) git_attr_foreach(
int (*callback)(const char *name, const char *value, void *payload), int (*callback)(const char *name, const char *value, void *payload),
void *payload); void *payload);
/**
* Flush the gitattributes cache.
*
* Call this if you have reason to believe that the attributes files
* on disk no longer match the cached contents of memory.
*/
GIT_EXTERN(void) git_attr_cache_flush(
git_repository *repo);
/**
* Add a macro definition.
*
* Macros will automatically be loaded from the top level .gitattributes
* file of the repository (plus the build-in "binary" macro). This
* function allows you to add others. For example, to add the default
* macro, you would call:
*
* git_attr_add_macro(repo, "binary", "-diff -crlf");
*/
GIT_EXTERN(int) git_attr_add_macro(
git_repository *repo,
const char *name,
const char *values);
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
#endif #endif
......
#include "attr.h" #include "repository.h"
#include "buffer.h"
#include "fileops.h" #include "fileops.h"
#include "config.h" #include "config.h"
#include <ctype.h> #include <ctype.h>
#define GIT_ATTR_FILE_INREPO "info/attributes" #define GIT_ATTR_FILE_INREPO "info/attributes"
#define GIT_ATTR_FILE ".gitattributes" #define GIT_ATTR_FILE ".gitattributes"
#define GIT_ATTR_FILE_SYSTEM "/etc/gitattributes" #define GIT_ATTR_FILE_SYSTEM "gitattributes"
#if GIT_WIN32
#define GIT_ATTR_FILE_WIN32 L"%PROGRAMFILES%\\Git\\etc\\gitattributes"
#endif
static int collect_attr_files( static int collect_attr_files(
git_repository *repo, const char *path, git_vector *files); git_repository *repo, const char *path, git_vector *files);
static int attr_cache_init(git_repository *repo);
int git_attr_get( int git_attr_get(
git_repository *repo, const char *pathname, git_repository *repo, const char *pathname,
...@@ -180,6 +178,44 @@ cleanup: ...@@ -180,6 +178,44 @@ cleanup:
} }
int git_attr_add_macro(
git_repository *repo,
const char *name,
const char *values)
{
int error;
git_attr_rule *macro = NULL;
if ((error = attr_cache_init(repo)) < GIT_SUCCESS)
return error;
macro = git__calloc(1, sizeof(git_attr_rule));
if (!macro)
return GIT_ENOMEM;
macro->match.pattern = git__strdup(name);
if (!macro->match.pattern) {
git__free(macro);
return GIT_ENOMEM;
}
macro->match.length = strlen(macro->match.pattern);
macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
error = git_attr_assignment__parse(repo, &macro->assigns, &values);
if (error == GIT_SUCCESS)
error = git_attr_cache__insert_macro(repo, macro);
if (error < GIT_SUCCESS) {
git_attr_rule__free(macro);
git__free(macro);
}
return error;
}
/* add git_attr_file to vector of files, loading if needed */ /* add git_attr_file to vector of files, loading if needed */
static int push_attrs( static int push_attrs(
git_repository *repo, git_repository *repo,
...@@ -193,13 +229,6 @@ static int push_attrs( ...@@ -193,13 +229,6 @@ static int push_attrs(
git_attr_file *file; git_attr_file *file;
int add_to_cache = 0; 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_path_prettify(&path, filename, base)) < GIT_SUCCESS) {
if (error == GIT_EOSERR) if (error == GIT_EOSERR)
/* file was not found -- ignore error */ /* file was not found -- ignore error */
...@@ -210,7 +239,7 @@ static int push_attrs( ...@@ -210,7 +239,7 @@ static int push_attrs(
/* either get attr_file from cache or read from disk */ /* either get attr_file from cache or read from disk */
file = git_hashtable_lookup(cache->files, path.ptr); file = git_hashtable_lookup(cache->files, path.ptr);
if (file == NULL) { if (file == NULL) {
error = git_attr_file__from_file(&file, path.ptr); error = git_attr_file__from_file(repo, path.ptr, &file);
add_to_cache = (error == GIT_SUCCESS); add_to_cache = (error == GIT_SUCCESS);
} }
...@@ -238,6 +267,9 @@ static int collect_attr_files( ...@@ -238,6 +267,9 @@ static int collect_attr_files(
git_config *cfg; git_config *cfg;
const char *workdir = git_repository_workdir(repo); const char *workdir = git_repository_workdir(repo);
if ((error = attr_cache_init(repo)) < GIT_SUCCESS)
goto cleanup;
if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS) if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS)
goto cleanup; goto cleanup;
...@@ -288,8 +320,13 @@ static int collect_attr_files( ...@@ -288,8 +320,13 @@ static int collect_attr_files(
git_config_free(cfg); git_config_free(cfg);
} }
if (error == GIT_SUCCESS) {
error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
if (error == GIT_SUCCESS) if (error == GIT_SUCCESS)
error = push_attrs(repo, files, NULL, GIT_ATTR_FILE_SYSTEM); error = push_attrs(repo, files, NULL, dir.ptr);
else if (error == GIT_ENOTFOUND)
error = GIT_SUCCESS;
}
cleanup: cleanup:
if (error < GIT_SUCCESS) { if (error < GIT_SUCCESS) {
...@@ -302,10 +339,64 @@ static int collect_attr_files( ...@@ -302,10 +339,64 @@ static int collect_attr_files(
} }
void git_repository__attr_cache_free(git_attr_cache *attrs) static int attr_cache_init(git_repository *repo)
{ {
if (attrs && attrs->files) { int error = GIT_SUCCESS;
git_hashtable_free(attrs->files); git_attr_cache *cache = &repo->attrcache;
attrs->files = NULL;
if (cache->initialized)
return GIT_SUCCESS;
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 initialize attribute cache");
}
if (cache->macros == NULL) {
cache->macros = git_hashtable_alloc(
8, git_hash__strhash_cb, git_hash__strcmp_cb);
if (!cache->macros)
return git__throw(GIT_ENOMEM, "Could not initialize attribute cache");
} }
cache->initialized = 1;
/* insert default macros */
error = git_attr_add_macro(repo, "binary", "-diff -crlf");
return error;
}
void git_attr_cache_flush(
git_repository *repo)
{
if (!repo)
return;
if (repo->attrcache.files) {
const void *GIT_UNUSED(name);
git_attr_file *file;
GIT_HASHTABLE_FOREACH(repo->attrcache.files, name, file,
git_attr_file__free(file));
git_hashtable_free(repo->attrcache.files);
repo->attrcache.files = NULL;
}
if (repo->attrcache.macros) {
const void *GIT_UNUSED(name);
git_attr_rule *rule;
GIT_HASHTABLE_FOREACH(repo->attrcache.macros, name, rule,
git_attr_rule__free(rule));
git_hashtable_free(repo->attrcache.macros);
repo->attrcache.macros = NULL;
}
repo->attrcache.initialized = 0;
} }
/*
* 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 "common.h"
#include "attr_file.h" #include "repository.h"
#include "filebuf.h" #include "filebuf.h"
#include <ctype.h> #include <ctype.h>
const char *git_attr__true = "[internal]__TRUE__"; const char *git_attr__true = "[internal]__TRUE__";
const char *git_attr__false = "[internal]__FALSE__"; const char *git_attr__false = "[internal]__FALSE__";
static int parse_fnmatch(git_attr_fnmatch *spec, const char **base); static int git_attr_fnmatch__parse(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); 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 git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
{
unsigned int i;
git_attr_assignment *assign;
if (macro->assigns.length == 0)
return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values");
git_vector_foreach(&macro->assigns, i, assign) {
GIT_REFCOUNT_OWN(assign, macro);
GIT_REFCOUNT_INC(assign);
}
return git_hashtable_insert(
repo->attrcache.macros, macro->match.pattern, macro);
}
int git_attr_file__from_buffer(
git_repository *repo, const char *buffer, git_attr_file **out)
{ {
int error = GIT_SUCCESS; int error = GIT_SUCCESS;
git_attr_file *attrs = NULL; git_attr_file *attrs = NULL;
...@@ -42,13 +58,21 @@ int git_attr_file__from_buffer(git_attr_file **out, const char *buffer) ...@@ -42,13 +58,21 @@ int git_attr_file__from_buffer(git_attr_file **out, const char *buffer)
} }
/* parse the next "pattern attr attr attr" line */ /* parse the next "pattern attr attr attr" line */
if (!(error = parse_fnmatch(&rule->match, &scan)) && if (!(error = git_attr_fnmatch__parse(&rule->match, &scan)) &&
!(error = parse_assigns(&rule->assigns, &scan))) !(error = git_attr_assignment__parse(repo, &rule->assigns, &scan)))
{
if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
/* should generate error/warning if this is coming from any
* file other than .gitattributes at repo root.
*/
error = git_attr_cache__insert_macro(repo, rule);
else
error = git_vector_insert(&attrs->rules, rule); error = git_vector_insert(&attrs->rules, rule);
}
/* if the rule wasn't a pattern, on to the next */ /* if the rule wasn't a pattern, on to the next */
if (error != GIT_SUCCESS) { if (error != GIT_SUCCESS) {
free_rule(rule); /* release anything partially allocated */ git_attr_rule__free(rule); /* free anything partially allocated */
if (error == GIT_ENOTFOUND) if (error == GIT_ENOTFOUND)
error = GIT_SUCCESS; error = GIT_SUCCESS;
} else { } else {
...@@ -58,6 +82,7 @@ int git_attr_file__from_buffer(git_attr_file **out, const char *buffer) ...@@ -58,6 +82,7 @@ int git_attr_file__from_buffer(git_attr_file **out, const char *buffer)
cleanup: cleanup:
if (error != GIT_SUCCESS) { if (error != GIT_SUCCESS) {
git__free(rule);
git_attr_file__free(attrs); git_attr_file__free(attrs);
git__free(attrs); git__free(attrs);
} else { } else {
...@@ -67,7 +92,8 @@ cleanup: ...@@ -67,7 +92,8 @@ cleanup:
return error; return error;
} }
int git_attr_file__from_file(git_attr_file **out, const char *path) int git_attr_file__from_file(
git_repository *repo, const char *path, git_attr_file **out)
{ {
int error = GIT_SUCCESS; int error = GIT_SUCCESS;
git_fbuffer fbuf = GIT_FBUFFER_INIT; git_fbuffer fbuf = GIT_FBUFFER_INIT;
...@@ -75,7 +101,7 @@ int git_attr_file__from_file(git_attr_file **out, const char *path) ...@@ -75,7 +101,7 @@ int git_attr_file__from_file(git_attr_file **out, const char *path)
*out = NULL; *out = NULL;
if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS || if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS ||
(error = git_attr_file__from_buffer(out, fbuf.data)) < GIT_SUCCESS) (error = git_attr_file__from_buffer(repo, fbuf.data, out)) < GIT_SUCCESS)
{ {
git__rethrow(error, "Could not open attribute file '%s'", path); git__rethrow(error, "Could not open attribute file '%s'", path);
} else { } else {
...@@ -97,7 +123,7 @@ void git_attr_file__free(git_attr_file *file) ...@@ -97,7 +123,7 @@ void git_attr_file__free(git_attr_file *file)
return; return;
git_vector_foreach(&file->rules, i, rule) { git_vector_foreach(&file->rules, i, rule) {
free_rule(rule); git_attr_rule__free(rule);
} }
git_vector_free(&file->rules); git_vector_free(&file->rules);
...@@ -153,15 +179,15 @@ int git_attr_rule__match_path( ...@@ -153,15 +179,15 @@ int git_attr_rule__match_path(
{ {
int matched = FNM_NOMATCH; int matched = FNM_NOMATCH;
if (rule->match.directory && !path->is_dir) if (rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
return matched; return matched;
if (rule->match.fullpath) if (rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH)
matched = p_fnmatch(rule->match.pattern, path->path, FNM_PATHNAME); matched = p_fnmatch(rule->match.pattern, path->path, FNM_PATHNAME);
else else
matched = p_fnmatch(rule->match.pattern, path->basename, 0); matched = p_fnmatch(rule->match.pattern, path->basename, 0);
if (rule->match.negative) if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS; matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS;
return matched; return matched;
...@@ -232,7 +258,7 @@ int git_attr_path__init( ...@@ -232,7 +258,7 @@ int git_attr_path__init(
* GIT_ENOTFOUND if the fnmatch does not require matching, or * GIT_ENOTFOUND if the fnmatch does not require matching, or
* another error code there was an actual problem. * another error code there was an actual problem.
*/ */
static int parse_fnmatch( static int git_attr_fnmatch__parse(
git_attr_fnmatch *spec, git_attr_fnmatch *spec,
const char **base) const char **base)
{ {
...@@ -251,21 +277,31 @@ static int parse_fnmatch( ...@@ -251,21 +277,31 @@ static int parse_fnmatch(
goto skip_to_eol; goto skip_to_eol;
} }
spec->flags = 0;
if (*pattern == '[') {
if (strncmp(pattern, "[attr]", 6) == 0) {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
pattern += 6;
} else {
/* unrecognized meta instructions - skip the line */
error = GIT_ENOTFOUND;
goto skip_to_eol;
}
}
if (*pattern == '!') { if (*pattern == '!') {
spec->negative = 1; spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
pattern++; pattern++;
} else {
spec->negative = 0;
} }
spec->fullpath = 0;
slash_count = 0; slash_count = 0;
for (scan = pattern; *scan != '\0'; ++scan) { for (scan = pattern; *scan != '\0'; ++scan) {
if (isspace(*scan) && *(scan - 1) != '\\') if (isspace(*scan) && *(scan - 1) != '\\')
break; break;
if (*scan == '/') { if (*scan == '/') {
spec->fullpath = 1; spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
slash_count++; slash_count++;
} }
} }
...@@ -292,11 +328,9 @@ static int parse_fnmatch( ...@@ -292,11 +328,9 @@ static int parse_fnmatch(
if (pattern[spec->length - 1] == '/') { if (pattern[spec->length - 1] == '/') {
spec->length--; spec->length--;
spec->pattern[spec->length] = '\0'; spec->pattern[spec->length] = '\0';
spec->directory = 1; spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
if (--slash_count <= 0) if (--slash_count <= 0)
spec->fullpath = 0; spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
} else {
spec->directory = 0;
} }
return GIT_SUCCESS; return GIT_SUCCESS;
...@@ -323,7 +357,21 @@ static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) ...@@ -323,7 +357,21 @@ static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
return strcmp(b->name, a->name); return strcmp(b->name, a->name);
} }
static int parse_assigns( static void free_assign(git_attr_assignment *assign)
{
git__free(assign->name);
assign->name = NULL;
if (assign->is_allocated) {
git__free((void *)assign->value);
assign->value = NULL;
}
git__free(assign);
}
int git_attr_assignment__parse(
git_repository *repo,
git_vector *assigns, git_vector *assigns,
const char **base) const char **base)
{ {
...@@ -333,7 +381,7 @@ static int parse_assigns( ...@@ -333,7 +381,7 @@ static int parse_assigns(
assert(assigns && !assigns->length); assert(assigns && !assigns->length);
while (*scan && *scan != '\n') { while (*scan && *scan != '\n' && error == GIT_SUCCESS) {
const char *name_start, *value_start; const char *name_start, *value_start;
/* skip leading blanks */ /* skip leading blanks */
...@@ -369,8 +417,7 @@ static int parse_assigns( ...@@ -369,8 +417,7 @@ static int parse_assigns(
((assign->name_hash << 5) + assign->name_hash) + *scan; ((assign->name_hash << 5) + assign->name_hash) + *scan;
scan++; scan++;
} }
assign->name_len = scan - name_start; if (scan == name_start) {
if (assign->name_len <= 0) {
/* must have found lone prefix (" - ") or leading = ("=foo") /* must have found lone prefix (" - ") or leading = ("=foo")
* or end of buffer -- advance until whitespace and continue * or end of buffer -- advance until whitespace and continue
*/ */
...@@ -378,6 +425,13 @@ static int parse_assigns( ...@@ -378,6 +425,13 @@ static int parse_assigns(
continue; continue;
} }
/* allocate permanent storage for name */
assign->name = git__strndup(name_start, scan - name_start);
if (!assign->name) {
error = GIT_ENOMEM;
break;
}
/* if there is an equals sign, find the value */ /* if there is an equals sign, find the value */
if (*scan == '=') { if (*scan == '=') {
for (value_start = ++scan; *scan && !isspace(*scan); ++scan); for (value_start = ++scan; *scan && !isspace(*scan); ++scan);
...@@ -394,11 +448,34 @@ static int parse_assigns( ...@@ -394,11 +448,34 @@ static int parse_assigns(
} }
} }
/* allocate permanent storage for name */ /* expand macros (if given a repo) */
assign->name = git__strndup(name_start, assign->name_len); if (repo != NULL) {
if (!assign->name) { git_attr_rule *macro =
error = GIT_ENOMEM; git_hashtable_lookup(repo->attrcache.macros, assign->name);
if (macro != NULL) {
unsigned int i;
git_attr_assignment *massign;
/* issue warning: if assign->value != GIT_ATTR_TRUE */
git__free(assign->name);
assign->name = NULL;
if (assign->is_allocated) {
git__free((void *)assign->value);
assign->value = NULL;
}
git_vector_foreach(&macro->assigns, i, massign) {
error = git_vector_insert(assigns, massign);
if (error != GIT_SUCCESS)
break; break;
GIT_REFCOUNT_INC(&massign->rc);
}
/* continue to next assignment */
continue;
}
} }
/* insert allocated assign into vector */ /* insert allocated assign into vector */
...@@ -417,40 +494,34 @@ static int parse_assigns( ...@@ -417,40 +494,34 @@ static int parse_assigns(
git_vector_sort(assigns); git_vector_sort(assigns);
} }
if (assign != NULL) { if (assign != NULL)
git__free(assign->name); free_assign(assign);
if (assign->is_allocated)
git__free((void *)assign->value);
git__free(assign);
}
while (*scan && *scan != '\n') scan++; while (*scan && *scan != '\n') scan++;
if (*scan == '\n') scan++;
*base = scan; *base = scan;
return error; return error;
} }
static int free_rule(git_attr_rule *rule) void git_attr_rule__free(git_attr_rule *rule)
{ {
unsigned int i; unsigned int i;
git_attr_assignment *assign; git_attr_assignment *assign;
if (!rule) if (!rule)
return GIT_SUCCESS; return;
git__free(rule->match.pattern); git__free(rule->match.pattern);
rule->match.pattern = NULL; rule->match.pattern = NULL;
rule->match.length = 0; rule->match.length = 0;
git_vector_foreach(&rule->assigns, i, assign) { git_vector_foreach(&rule->assigns, i, assign) {
git__free(assign->name); if (GIT_REFCOUNT_OWNER(assign) == rule)
assign->name = NULL; GIT_REFCOUNT_OWN(assign, NULL);
GIT_REFCOUNT_DEC(assign, free_assign);
if (assign->is_allocated) {
git__free((void *)assign->value);
assign->value = NULL;
}
} }
return GIT_SUCCESS; git_vector_free(&rule->assigns);
} }
...@@ -9,36 +9,41 @@ ...@@ -9,36 +9,41 @@
#include "git2/attr.h" #include "git2/attr.h"
#include "vector.h" #include "vector.h"
#include "hashtable.h"
#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0)
#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1)
#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2)
#define GIT_ATTR_FNMATCH_MACRO (1U << 3)
typedef struct { typedef struct {
char *pattern; char *pattern;
size_t length; size_t length;
int negative; unsigned int flags;
int directory;
int fullpath;
} git_attr_fnmatch; } git_attr_fnmatch;
typedef struct { typedef struct {
git_refcount unused;
const char *name; const char *name;
unsigned long name_hash; unsigned long name_hash;
} git_attr_name; } git_attr_name;
typedef struct { typedef struct {
git_refcount rc; /* for macros */
char *name; char *name;
unsigned long name_hash; unsigned long name_hash;
size_t name_len;
const char *value; const char *value;
int is_allocated; int is_allocated;
} git_attr_assignment; } git_attr_assignment;
typedef struct { typedef struct {
git_attr_fnmatch match; git_attr_fnmatch match;
git_vector assigns; /* <git_attr_assignment*> */ git_vector assigns; /* vector of <git_attr_assignment*> */
} git_attr_rule; } git_attr_rule;
typedef struct { typedef struct {
char *path; char *path; /* cache the path this was loaded from */
git_vector rules; /* <git_attr_rule*> */ git_vector rules; /* vector of <git_attr_rule*> */
} git_attr_file; } git_attr_file;
typedef struct { typedef struct {
...@@ -47,12 +52,20 @@ typedef struct { ...@@ -47,12 +52,20 @@ typedef struct {
int is_dir; int is_dir;
} git_attr_path; } git_attr_path;
typedef struct {
int initialized;
git_hashtable *files; /* hash path to git_attr_file */
git_hashtable *macros; /* hash name to vector<git_attr_assignment> */
} git_attr_cache;
/* /*
* git_attr_file API * git_attr_file API
*/ */
extern int git_attr_file__from_buffer(git_attr_file **out, const char *buf); extern int git_attr_file__from_buffer(
extern int git_attr_file__from_file(git_attr_file **out, const char *path); git_repository *repo, const char *buf, git_attr_file **out);
extern int git_attr_file__from_file(
git_repository *repo, const char *path, git_attr_file **out);
extern void git_attr_file__free(git_attr_file *file); extern void git_attr_file__free(git_attr_file *file);
...@@ -74,6 +87,8 @@ extern unsigned long git_attr_file__name_hash(const char *name); ...@@ -74,6 +87,8 @@ extern unsigned long git_attr_file__name_hash(const char *name);
* other utilities * other utilities
*/ */
extern void git_attr_rule__free(git_attr_rule *rule);
extern int git_attr_rule__match_path( extern int git_attr_rule__match_path(
git_attr_rule *rule, git_attr_rule *rule,
const git_attr_path *path); const git_attr_path *path);
...@@ -84,4 +99,12 @@ extern git_attr_assignment *git_attr_rule__lookup_assignment( ...@@ -84,4 +99,12 @@ extern git_attr_assignment *git_attr_rule__lookup_assignment(
extern int git_attr_path__init( extern int git_attr_path__init(
git_attr_path *info, const char *path); git_attr_path *info, const char *path);
extern int git_attr_assignment__parse(
git_repository *repo, /* needed to expand macros */
git_vector *assigns,
const char **scan);
extern int git_attr_cache__insert_macro(
git_repository *repo, git_attr_rule *macro);
#endif #endif
...@@ -337,6 +337,11 @@ int git_config_get_string(git_config *cfg, const char *name, const char **out) ...@@ -337,6 +337,11 @@ int git_config_get_string(git_config *cfg, const char *name, const char **out)
return git__throw(error, "Config value '%s' not found", name); return git__throw(error, "Config value '%s' not found", name);
} }
int git_config_find_global_r(git_buf *path)
{
return git_futils_find_global_file(path, GIT_CONFIG_FILENAME);
}
int git_config_find_global(char *global_config_path) int git_config_find_global(char *global_config_path)
{ {
git_buf path = GIT_BUF_INIT; git_buf path = GIT_BUF_INIT;
...@@ -354,79 +359,9 @@ int git_config_find_global(char *global_config_path) ...@@ -354,79 +359,9 @@ int git_config_find_global(char *global_config_path)
return error; return error;
} }
int git_config_find_global_r(git_buf *path) int git_config_find_system_r(git_buf *path)
{ {
int error; return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM);
const char *home = getenv("HOME");
#ifdef GIT_WIN32
if (home == NULL)
home = getenv("USERPROFILE");
#endif
if (home == NULL)
return git__throw(GIT_EOSERR, "Failed to open global config file. Cannot locate the user's home directory");
if ((error = git_buf_joinpath(path, home, GIT_CONFIG_FILENAME)) < GIT_SUCCESS)
return error;
if (git_futils_exists(path->ptr) < GIT_SUCCESS) {
git_buf_clear(path);
return git__throw(GIT_EOSERR, "Failed to open global config file. The file does not exist");
}
return GIT_SUCCESS;
}
#if GIT_WIN32
static int win32_find_system(git_buf *system_config_path)
{
const wchar_t *query = L"%PROGRAMFILES%\\Git\\etc\\gitconfig";
wchar_t *apphome_utf16;
char *apphome_utf8;
DWORD size, ret;
size = ExpandEnvironmentStringsW(query, NULL, 0);
/* The function gave us the full size of the buffer in chars, including NUL */
apphome_utf16 = git__malloc(size * sizeof(wchar_t));
if (apphome_utf16 == NULL)
return GIT_ENOMEM;
ret = ExpandEnvironmentStringsW(query, apphome_utf16, size);
if (ret != size)
return git__throw(GIT_ERROR, "Failed to expand environment strings");
if (_waccess(apphome_utf16, F_OK) < 0) {
git__free(apphome_utf16);
return GIT_ENOTFOUND;
}
apphome_utf8 = gitwin_from_utf16(apphome_utf16);
git__free(apphome_utf16);
git_buf_attach(system_config_path, apphome_utf8, 0);
return GIT_SUCCESS;
}
#endif
int git_config_find_system_r(git_buf *system_config_path)
{
if (git_buf_sets(system_config_path, "/etc/gitconfig") < GIT_SUCCESS)
return git_buf_lasterror(system_config_path);
if (git_futils_exists(system_config_path->ptr) == GIT_SUCCESS)
return GIT_SUCCESS;
git_buf_clear(system_config_path);
#if GIT_WIN32
return win32_find_system(system_config_path);
#else
return GIT_ENOTFOUND;
#endif
} }
int git_config_find_system(char *system_config_path) int git_config_find_system(char *system_config_path)
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#define GIT_CONFIG_FILENAME ".gitconfig" #define GIT_CONFIG_FILENAME ".gitconfig"
#define GIT_CONFIG_FILENAME_INREPO "config" #define GIT_CONFIG_FILENAME_INREPO "config"
#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"
#define GIT_CONFIG_FILE_MODE 0666 #define GIT_CONFIG_FILE_MODE 0666
struct git_config { struct git_config {
......
...@@ -403,3 +403,134 @@ int git_futils_contains_file(git_buf *base, const char *file, int append_if_exis ...@@ -403,3 +403,134 @@ int git_futils_contains_file(git_buf *base, const char *file, int append_if_exis
return _check_dir_contents(base, file, append_if_exists, &git_futils_isfile); return _check_dir_contents(base, file, append_if_exists, &git_futils_isfile);
} }
int git_futils_find_global_file(git_buf *path, const char *filename)
{
int error;
const char *home = getenv("HOME");
#ifdef GIT_WIN32
if (home == NULL)
home = getenv("USERPROFILE");
#endif
if (home == NULL)
return git__throw(GIT_EOSERR, "Failed to open global %s file. "
"Cannot locate the user's home directory.", filename);
if ((error = git_buf_joinpath(path, home, filename)) < GIT_SUCCESS)
return error;
if (git_futils_exists(path->ptr) < GIT_SUCCESS) {
git_buf_clear(path);
return GIT_ENOTFOUND;
}
return GIT_SUCCESS;
}
#ifdef GIT_WIN32
typedef struct {
wchar_t *path;
DWORD len;
} win32_path;
static const win32_path *win32_system_root(void)
{
static win32_path s_root = { 0, 0 };
if (s_root.path == NULL) {
const wchar_t *root_tmpl = L"%PROGRAMFILES%\\Git\\etc\\";
s_root.len = ExpandEnvironmentStringsW(root_tmpl, NULL, 0);
if (s_root.len <= 0) {
git__throw(GIT_EOSERR, "Failed to expand environment strings");
return NULL;
}
s_root.path = git__calloc(s_root.len, sizeof(wchar_t));
if (s_root.path == NULL)
return NULL;
if (ExpandEnvironmentStringsW(root_tmpl, s_root.path, s_root.len) != s_root.len) {
git__throw(GIT_EOSERR, "Failed to expand environment strings");
git__free(s_root.path);
s_root.path = NULL;
return NULL;
}
}
return &s_root;
}
static int win32_find_system_file(git_buf *path, const char *filename)
{
int error = GIT_SUCCESS;
const win32_path *root = win32_system_root();
size_t len;
wchar_t *file_utf16 = NULL, *scan;
char *file_utf8 = NULL;
if (!root || !filename || (len = strlen(filename)) == 0)
return GIT_ENOTFOUND;
/* allocate space for wchar_t path to file */
file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t));
if (!file_utf16)
return GIT_ENOMEM;
/* append root + '\\' + filename as wchar_t */
memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));
if (*filename == '/' || *filename == '\\')
filename++;
if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) !=
(int)len) {
error = git__throw(GIT_EOSERR, "Failed to build file path");
goto cleanup;
}
for (scan = file_utf16; *scan; scan++)
if (*scan == L'/')
*scan = L'\\';
/* check access */
if (_waccess(file_utf16, F_OK) < 0) {
error = GIT_ENOTFOUND;
goto cleanup;
}
/* convert to utf8 */
if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL)
error = GIT_ENOMEM;
if (file_utf8) {
git_path_mkposix(file_utf8);
git_buf_attach(path, file_utf8, 0);
}
cleanup:
git__free(file_utf16);
return error;
}
#endif
int git_futils_find_system_file(git_buf *path, const char *filename)
{
if (git_buf_joinpath(path, "/etc", filename) < GIT_SUCCESS)
return git_buf_lasterror(path);
if (git_futils_exists(path->ptr) == GIT_SUCCESS)
return GIT_SUCCESS;
git_buf_clear(path);
#ifdef GIT_WIN32
return win32_find_system_file(path, filename);
#else
return GIT_ENOTFOUND;
#endif
}
...@@ -165,4 +165,28 @@ extern int git_futils_direach( ...@@ -165,4 +165,28 @@ extern int git_futils_direach(
extern int git_futils_cmp_path(const char *name1, int len1, int isdir1, extern int git_futils_cmp_path(const char *name1, int len1, int isdir1,
const char *name2, int len2, int isdir2); const char *name2, int len2, int isdir2);
/**
* Find a "global" file (i.e. one in a user's home directory).
*
* @param pathbuf buffer to write the full path into
* @param filename name of file to find in the home directory
* @return
* - GIT_SUCCESS if found;
* - GIT_ENOTFOUND if not found;
* - GIT_EOSERR on an unspecified OS related error.
*/
extern int git_futils_find_global_file(git_buf *path, const char *filename);
/**
* Find a "system" file (i.e. one shared for all users of the system).
*
* @param pathbuf buffer to write the full path into
* @param filename name of file to find in the home directory
* @return
* - GIT_SUCCESS if found;
* - GIT_ENOTFOUND if not found;
* - GIT_EOSERR on an unspecified OS related error.
*/
extern int git_futils_find_system_file(git_buf *path, const char *filename);
#endif /* INCLUDE_fileops_h__ */ #endif /* INCLUDE_fileops_h__ */
...@@ -59,7 +59,7 @@ void git_repository_free(git_repository *repo) ...@@ -59,7 +59,7 @@ void git_repository_free(git_repository *repo)
git_cache_free(&repo->objects); git_cache_free(&repo->objects);
git_repository__refcache_free(&repo->references); git_repository__refcache_free(&repo->references);
git_repository__attr_cache_free(&repo->attrcache); git_attr_cache_flush(repo);
git__free(repo->path_repository); git__free(repo->path_repository);
git__free(repo->workdir); git__free(repo->workdir);
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
#include "refs.h" #include "refs.h"
#include "buffer.h" #include "buffer.h"
#include "odb.h" #include "odb.h"
#include "attr.h" #include "attr_file.h"
#define DOT_GIT ".git" #define DOT_GIT ".git"
#define GIT_DIR DOT_GIT "/" #define GIT_DIR DOT_GIT "/"
......
...@@ -109,7 +109,6 @@ extern void **git__bsearch(const void *key, void **base, size_t nmemb, ...@@ -109,7 +109,6 @@ extern void **git__bsearch(const void *key, void **base, size_t nmemb,
int (*compar)(const void *, const void *)); int (*compar)(const void *, const void *));
extern int git__strcmp_cb(const void *a, const void *b); 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 { typedef struct {
short refcount; short refcount;
......
...@@ -57,6 +57,11 @@ wchar_t* gitwin_to_utf16(const char* str) ...@@ -57,6 +57,11 @@ wchar_t* gitwin_to_utf16(const char* str)
return ret; return ret;
} }
int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len)
{
return MultiByteToWideChar(_active_codepage, 0, str, -1, buffer, len);
}
char* gitwin_from_utf16(const wchar_t* str) char* gitwin_from_utf16(const wchar_t* str)
{ {
char* ret; char* ret;
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#define INCLUDE_git_utfconv_h__ #define INCLUDE_git_utfconv_h__
wchar_t* gitwin_to_utf16(const char* str); wchar_t* gitwin_to_utf16(const char* str);
int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len)
char* gitwin_from_utf16(const wchar_t* str); char* gitwin_from_utf16(const wchar_t* str);
#endif #endif
......
...@@ -8,7 +8,7 @@ void test_attr_file__simple_read(void) ...@@ -8,7 +8,7 @@ void test_attr_file__simple_read(void)
{ {
git_attr_file *file = NULL; git_attr_file *file = NULL;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr0"))); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), &file));
cl_assert_strequal(cl_fixture("attr/attr0"), file->path); cl_assert_strequal(cl_fixture("attr/attr0"), file->path);
cl_assert(file->rules.length == 1); cl_assert(file->rules.length == 1);
...@@ -16,15 +16,12 @@ void test_attr_file__simple_read(void) ...@@ -16,15 +16,12 @@ void test_attr_file__simple_read(void)
cl_assert(rule != NULL); cl_assert(rule != NULL);
cl_assert_strequal("*", rule->match.pattern); cl_assert_strequal("*", rule->match.pattern);
cl_assert(rule->match.length == 1); cl_assert(rule->match.length == 1);
cl_assert(!rule->match.negative); cl_assert(rule->match.flags == 0);
cl_assert(!rule->match.directory);
cl_assert(!rule->match.fullpath);
cl_assert(rule->assigns.length == 1); cl_assert(rule->assigns.length == 1);
git_attr_assignment *assign = get_assign(rule, 0); git_attr_assignment *assign = get_assign(rule, 0);
cl_assert(assign != NULL); cl_assert(assign != NULL);
cl_assert_strequal("binary", assign->name); cl_assert_strequal("binary", assign->name);
cl_assert(assign->name_len == 6);
cl_assert(assign->value == GIT_ATTR_TRUE); cl_assert(assign->value == GIT_ATTR_TRUE);
cl_assert(!assign->is_allocated); cl_assert(!assign->is_allocated);
...@@ -37,7 +34,7 @@ void test_attr_file__match_variants(void) ...@@ -37,7 +34,7 @@ void test_attr_file__match_variants(void)
git_attr_rule *rule; git_attr_rule *rule;
git_attr_assignment *assign; git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr1"))); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), &file));
cl_assert_strequal(cl_fixture("attr/attr1"), file->path); cl_assert_strequal(cl_fixture("attr/attr1"), file->path);
cl_assert(file->rules.length == 10); cl_assert(file->rules.length == 10);
...@@ -48,38 +45,31 @@ void test_attr_file__match_variants(void) ...@@ -48,38 +45,31 @@ void test_attr_file__match_variants(void)
cl_assert(rule); cl_assert(rule);
cl_assert_strequal("pat0", rule->match.pattern); cl_assert_strequal("pat0", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat0")); cl_assert(rule->match.length == strlen("pat0"));
cl_assert(!rule->match.negative); cl_assert(rule->match.flags == 0);
cl_assert(!rule->match.directory);
cl_assert(!rule->match.fullpath);
cl_assert(rule->assigns.length == 1); cl_assert(rule->assigns.length == 1);
assign = get_assign(rule,0); assign = get_assign(rule,0);
cl_assert_strequal("attr0", assign->name); cl_assert_strequal("attr0", assign->name);
cl_assert(assign->name_hash == git_attr_file__name_hash(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->value == GIT_ATTR_TRUE);
cl_assert(!assign->is_allocated); cl_assert(!assign->is_allocated);
rule = get_rule(1); rule = get_rule(1);
cl_assert_strequal("pat1", rule->match.pattern); cl_assert_strequal("pat1", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat1")); cl_assert(rule->match.length == strlen("pat1"));
cl_assert(rule->match.negative); cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_NEGATIVE);
rule = get_rule(2); rule = get_rule(2);
cl_assert_strequal("pat2", rule->match.pattern); cl_assert_strequal("pat2", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat2")); cl_assert(rule->match.length == strlen("pat2"));
cl_assert(rule->match.directory); cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_DIRECTORY);
cl_assert(!rule->match.fullpath);
rule = get_rule(3); rule = get_rule(3);
cl_assert_strequal("pat3dir/pat3file", rule->match.pattern); cl_assert_strequal("pat3dir/pat3file", rule->match.pattern);
cl_assert(!rule->match.directory); cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_FULLPATH);
cl_assert(rule->match.fullpath);
rule = get_rule(4); rule = get_rule(4);
cl_assert_strequal("pat4.*", rule->match.pattern); cl_assert_strequal("pat4.*", rule->match.pattern);
cl_assert(!rule->match.negative); cl_assert(rule->match.flags == 0);
cl_assert(!rule->match.directory);
cl_assert(!rule->match.fullpath);
rule = get_rule(5); rule = get_rule(5);
cl_assert_strequal("*.pat5", rule->match.pattern); cl_assert_strequal("*.pat5", rule->match.pattern);
...@@ -94,9 +84,7 @@ void test_attr_file__match_variants(void) ...@@ -94,9 +84,7 @@ void test_attr_file__match_variants(void)
rule = get_rule(8); rule = get_rule(8);
cl_assert_strequal("pat8 with spaces", rule->match.pattern); cl_assert_strequal("pat8 with spaces", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat8 with spaces")); cl_assert(rule->match.length == strlen("pat8 with spaces"));
cl_assert(!rule->match.negative); cl_assert(rule->match.flags == 0);
cl_assert(!rule->match.directory);
cl_assert(!rule->match.fullpath);
rule = get_rule(9); rule = get_rule(9);
cl_assert_strequal("pat9", rule->match.pattern); cl_assert_strequal("pat9", rule->match.pattern);
...@@ -120,7 +108,6 @@ static void check_one_assign( ...@@ -120,7 +108,6 @@ static void check_one_assign(
cl_assert(rule->assigns.length == 1); cl_assert(rule->assigns.length == 1);
cl_assert_strequal(name, assign->name); cl_assert_strequal(name, assign->name);
cl_assert(assign->name_hash == git_attr_file__name_hash(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); cl_assert(assign->is_allocated == is_allocated);
if (is_allocated) if (is_allocated)
cl_assert_strequal(value, assign->value); cl_assert_strequal(value, assign->value);
...@@ -134,7 +121,7 @@ void test_attr_file__assign_variants(void) ...@@ -134,7 +121,7 @@ void test_attr_file__assign_variants(void)
git_attr_rule *rule; git_attr_rule *rule;
git_attr_assignment *assign; git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr2"))); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), &file));
cl_assert_strequal(cl_fixture("attr/attr2"), file->path); cl_assert_strequal(cl_fixture("attr/attr2"), file->path);
cl_assert(file->rules.length == 11); cl_assert(file->rules.length == 11);
...@@ -199,7 +186,7 @@ void test_attr_file__check_attr_examples(void) ...@@ -199,7 +186,7 @@ void test_attr_file__check_attr_examples(void)
git_attr_rule *rule; git_attr_rule *rule;
git_attr_assignment *assign; git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr3"))); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), &file));
cl_assert_strequal(cl_fixture("attr/attr3"), file->path); cl_assert_strequal(cl_fixture("attr/attr3"), file->path);
cl_assert(file->rules.length == 3); cl_assert(file->rules.length == 3);
......
#include "clay_libgit2.h" #include "clay_libgit2.h"
#include "attr.h" #include "attr_file.h"
void test_attr_lookup__simple(void) void test_attr_lookup__simple(void)
{ {
...@@ -7,7 +7,7 @@ void test_attr_lookup__simple(void) ...@@ -7,7 +7,7 @@ void test_attr_lookup__simple(void)
git_attr_path path; git_attr_path path;
const char *value = NULL; const char *value = NULL;
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr0"))); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), &file));
cl_assert_strequal(cl_fixture("attr/attr0"), file->path); cl_assert_strequal(cl_fixture("attr/attr0"), file->path);
cl_assert(file->rules.length == 1); cl_assert(file->rules.length == 1);
...@@ -41,10 +41,6 @@ static void run_test_cases(git_attr_file *file, test_case *cases) ...@@ -41,10 +41,6 @@ static void run_test_cases(git_attr_file *file, test_case *cases)
int error; int error;
for (c = cases; c->path != NULL; c++) { 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)); cl_git_pass(git_attr_path__init(&path, c->path));
if (c->force_dir) if (c->force_dir)
...@@ -136,7 +132,7 @@ void test_attr_lookup__match_variants(void) ...@@ -136,7 +132,7 @@ void test_attr_lookup__match_variants(void)
{ NULL, NULL, NULL, 0, 0 } { NULL, NULL, NULL, 0, 0 }
}; };
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr1"))); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), &file));
cl_assert_strequal(cl_fixture("attr/attr1"), file->path); cl_assert_strequal(cl_fixture("attr/attr1"), file->path);
cl_assert(file->rules.length == 10); cl_assert(file->rules.length == 10);
...@@ -194,7 +190,7 @@ void test_attr_lookup__assign_variants(void) ...@@ -194,7 +190,7 @@ void test_attr_lookup__assign_variants(void)
{ NULL, NULL, NULL, 0, 0 } { NULL, NULL, NULL, 0, 0 }
}; };
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr2"))); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), &file));
cl_assert(file->rules.length == 11); cl_assert(file->rules.length == 11);
run_test_cases(file, cases); run_test_cases(file, cases);
...@@ -228,7 +224,31 @@ void test_attr_lookup__check_attr_examples(void) ...@@ -228,7 +224,31 @@ void test_attr_lookup__check_attr_examples(void)
{ NULL, NULL, NULL, 0, 0 } { NULL, NULL, NULL, 0, 0 }
}; };
cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr3"))); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), &file));
cl_assert(file->rules.length == 3);
run_test_cases(file, cases);
git_attr_file__free(file);
}
void test_attr_lookup__from_buffer(void)
{
git_attr_file *file = NULL;
test_case cases[] = {
{ "abc", "foo", GIT_ATTR_TRUE, 0, 0 },
{ "abc", "bar", GIT_ATTR_TRUE, 0, 0 },
{ "abc", "baz", GIT_ATTR_TRUE, 0, 0 },
{ "aaa", "foo", GIT_ATTR_TRUE, 0, 0 },
{ "aaa", "bar", NULL, 0, 0 },
{ "aaa", "baz", GIT_ATTR_TRUE, 0, 0 },
{ "qqq", "foo", NULL, 0, 0 },
{ "qqq", "bar", NULL, 0, 0 },
{ "qqq", "baz", GIT_ATTR_TRUE, 0, 0 },
{ NULL, NULL, NULL, 0, 0 }
};
cl_git_pass(git_attr_file__from_buffer(NULL, "a* foo\nabc bar\n* baz", &file));
cl_assert(file->rules.length == 3); cl_assert(file->rules.length == 3);
run_test_cases(file, cases); run_test_cases(file, cases);
......
...@@ -6,11 +6,14 @@ static git_repository *g_repo = NULL; ...@@ -6,11 +6,14 @@ static git_repository *g_repo = NULL;
void test_attr_repo__initialize(void) void test_attr_repo__initialize(void)
{ {
/* before each test, instantiate the attr repo from the fixtures and /* 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. * rename the .gitted to .git so it is a repo with a working dir.
* Also rename gitattributes to .gitattributes, because it contains
* macro definitions which are only allowed in the root.
*/ */
cl_fixture_sandbox("attr"); cl_fixture_sandbox("attr");
cl_git_pass(p_rename("attr/.gitted", "attr/.git")); cl_git_pass(p_rename("attr/.gitted", "attr/.git"));
cl_git_pass(p_rename("attr/gitattributes", "attr/.gitattributes"));
cl_git_pass(git_repository_open(&g_repo, "attr/.git")); cl_git_pass(git_repository_open(&g_repo, "attr/.git"));
} }
...@@ -138,3 +141,46 @@ void test_attr_repo__foreach(void) ...@@ -138,3 +141,46 @@ void test_attr_repo__foreach(void)
&count_attrs, &count)); &count_attrs, &count));
cl_assert(count == 5); /* repoattr, rootattr, subattr, negattr, another */ cl_assert(count == 5); /* repoattr, rootattr, subattr, negattr, another */
} }
void test_attr_repo__manpage_example(void)
{
const char *value;
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "foo", &value));
cl_assert(value == GIT_ATTR_TRUE);
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "bar", &value));
cl_assert(value == NULL);
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "baz", &value));
cl_assert(value == GIT_ATTR_FALSE);
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "merge", &value));
cl_assert_strequal("filfre", value);
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "frotz", &value));
cl_assert(value == NULL);
}
void test_attr_repo__macros(void)
{
const char *names[5] = { "rootattr", "binary", "diff", "crlf", "frotz" };
const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" };
const char *values[5];
cl_git_pass(git_attr_get_many(g_repo, "binfile", 5, names, values));
cl_assert(values[0] == GIT_ATTR_TRUE);
cl_assert(values[1] == NULL);
cl_assert(values[2] == GIT_ATTR_FALSE);
cl_assert(values[3] == GIT_ATTR_FALSE);
cl_assert(values[4] == NULL);
cl_git_pass(git_attr_get_many(g_repo, "macro_test", 5, names2, values));
cl_assert(values[0] == NULL);
cl_assert(values[1] == GIT_ATTR_TRUE);
cl_assert(values[2] == GIT_ATTR_FALSE);
cl_assert(values[3] == NULL);
cl_assert_strequal("77", values[4]);
}
...@@ -65,6 +65,7 @@ extern void test_attr_file__match_variants(void); ...@@ -65,6 +65,7 @@ extern void test_attr_file__match_variants(void);
extern void test_attr_file__simple_read(void); extern void test_attr_file__simple_read(void);
extern void test_attr_lookup__assign_variants(void); extern void test_attr_lookup__assign_variants(void);
extern void test_attr_lookup__check_attr_examples(void); extern void test_attr_lookup__check_attr_examples(void);
extern void test_attr_lookup__from_buffer(void);
extern void test_attr_lookup__match_variants(void); extern void test_attr_lookup__match_variants(void);
extern void test_attr_lookup__simple(void); extern void test_attr_lookup__simple(void);
extern void test_attr_repo__cleanup(void); extern void test_attr_repo__cleanup(void);
...@@ -72,6 +73,8 @@ extern void test_attr_repo__foreach(void); ...@@ -72,6 +73,8 @@ extern void test_attr_repo__foreach(void);
extern void test_attr_repo__get_many(void); extern void test_attr_repo__get_many(void);
extern void test_attr_repo__get_one(void); extern void test_attr_repo__get_one(void);
extern void test_attr_repo__initialize(void); extern void test_attr_repo__initialize(void);
extern void test_attr_repo__macros(void);
extern void test_attr_repo__manpage_example(void);
extern void test_buf_basic__printf(void); extern void test_buf_basic__printf(void);
extern void test_buf_basic__resize(void); extern void test_buf_basic__resize(void);
extern void test_config_add__cleanup(void); extern void test_config_add__cleanup(void);
......
...@@ -117,13 +117,16 @@ static const struct clay_func _clay_cb_attr_file[] = { ...@@ -117,13 +117,16 @@ static const struct clay_func _clay_cb_attr_file[] = {
static const struct clay_func _clay_cb_attr_lookup[] = { static const struct clay_func _clay_cb_attr_lookup[] = {
{"assign_variants", &test_attr_lookup__assign_variants}, {"assign_variants", &test_attr_lookup__assign_variants},
{"check_attr_examples", &test_attr_lookup__check_attr_examples}, {"check_attr_examples", &test_attr_lookup__check_attr_examples},
{"from_buffer", &test_attr_lookup__from_buffer},
{"match_variants", &test_attr_lookup__match_variants}, {"match_variants", &test_attr_lookup__match_variants},
{"simple", &test_attr_lookup__simple} {"simple", &test_attr_lookup__simple}
}; };
static const struct clay_func _clay_cb_attr_repo[] = { static const struct clay_func _clay_cb_attr_repo[] = {
{"foreach", &test_attr_repo__foreach}, {"foreach", &test_attr_repo__foreach},
{"get_many", &test_attr_repo__get_many}, {"get_many", &test_attr_repo__get_many},
{"get_one", &test_attr_repo__get_one} {"get_one", &test_attr_repo__get_one},
{"macros", &test_attr_repo__macros},
{"manpage_example", &test_attr_repo__manpage_example}
}; };
static const struct clay_func _clay_cb_buf_basic[] = { static const struct clay_func _clay_cb_buf_basic[] = {
{"printf", &test_buf_basic__printf}, {"printf", &test_buf_basic__printf},
...@@ -329,13 +332,13 @@ static const struct clay_suite _clay_suites[] = { ...@@ -329,13 +332,13 @@ static const struct clay_suite _clay_suites[] = {
"attr::lookup", "attr::lookup",
{NULL, NULL}, {NULL, NULL},
{NULL, NULL}, {NULL, NULL},
_clay_cb_attr_lookup, 4 _clay_cb_attr_lookup, 5
}, },
{ {
"attr::repo", "attr::repo",
{"initialize", &test_attr_repo__initialize}, {"initialize", &test_attr_repo__initialize},
{"cleanup", &test_attr_repo__cleanup}, {"cleanup", &test_attr_repo__cleanup},
_clay_cb_attr_repo, 3 _clay_cb_attr_repo, 5
}, },
{ {
"buf::basic", "buf::basic",
...@@ -556,7 +559,7 @@ static const struct clay_suite _clay_suites[] = { ...@@ -556,7 +559,7 @@ static const struct clay_suite _clay_suites[] = {
}; };
static size_t _clay_suite_count = 39; static size_t _clay_suite_count = 39;
static size_t _clay_callback_count = 131; static size_t _clay_callback_count = 134;
/* Core test functions */ /* Core test functions */
static void static void
......
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 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates
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 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates
6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7
123
\ No newline at end of file
* rootattr * rootattr
root_test2 -rootattr root_test2 -rootattr
root_test3 !rootattr root_test3 !rootattr
binfile binary
abc foo bar baz
[attr]mymacro positive -negative !rootattr
macro* mymacro another=77
* subattr=yes -negattr * subattr=yes -negattr
subdir/*.txt another=one subdir/*.txt another=one
ab* merge=filfre
abc -foo -bar
*.c frotz
# Test file from gitattributes(5) example:
If you have these three gitattributes file:
(in $GIT_DIR/info/attributes)
a* foo !bar -baz
(in .gitattributes)
abc foo bar baz
(in t/.gitattributes)
ab* merge=filfre
abc -foo -bar
*.c frotz
the attributes given to path t/abc are computed as follows:
1. By examining t/.gitattributes (which is in the same directory as the path
in question), git finds that the first line matches. merge attribute is
set. It also finds that the second line matches, and attributes foo and
bar are unset.
2. Then it examines .gitattributes (which is in the parent directory), and
finds that the first line matches, but t/.gitattributes file already
decided how merge, foo and bar attributes should be given to this path,
so it leaves foo and bar unset. Attribute baz is set.
3. Finally it examines $GIT_DIR/info/attributes. This file is used to
override the in-tree settings. The first line is a match, and foo is set,
bar is reverted to unspecified state, and baz is unset.
As the result, the attributes assignment to t/abc becomes:
foo set to true
bar unspecified
baz set to false
merge set to string value "filfre"
frotz unspecified
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