Commit 9191a6d2 by Vicent Martí

Merge remote-tracking branch 'arrbee/git-attributes' into development

Conflicts:
	tests-clay/clay_main.c
parents 9dd4c3e8 bd370b14
/*
* 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);
/**
* 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
#endif
#include "repository.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 "gitattributes"
static int collect_attr_files(
git_repository *repo, const char *path, git_vector *files);
static int attr_cache_init(git_repository *repo);
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;
}
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);
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 ((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(repo, path.ptr, &file);
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 = attr_cache_init(repo)) < GIT_SUCCESS)
goto cleanup;
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 = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
if (error == GIT_SUCCESS)
error = push_attrs(repo, files, NULL, dir.ptr);
else if (error == GIT_ENOTFOUND)
error = GIT_SUCCESS;
}
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;
}
static int attr_cache_init(git_repository *repo)
{
int error = GIT_SUCCESS;
git_attr_cache *cache = &repo->attrcache;
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;
}
This diff is collapsed. Click to expand it.
/*
* 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"
#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 {
char *pattern;
size_t length;
unsigned int flags;
} git_attr_fnmatch;
typedef struct {
git_refcount unused;
const char *name;
unsigned long name_hash;
} git_attr_name;
typedef struct {
git_refcount rc; /* for macros */
char *name;
unsigned long name_hash;
const char *value;
int is_allocated;
} git_attr_assignment;
typedef struct {
git_attr_fnmatch match;
git_vector assigns; /* vector of <git_attr_assignment*> */
} git_attr_rule;
typedef struct {
char *path; /* cache the path this was loaded from */
git_vector rules; /* vector of <git_attr_rule*> */
} git_attr_file;
typedef struct {
const char *path;
const char *basename;
int is_dir;
} 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
*/
extern int git_attr_file__from_buffer(
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 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 void git_attr_rule__free(git_attr_rule *rule);
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);
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
......@@ -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);
}
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)
{
git_buf path = GIT_BUF_INIT;
......@@ -354,79 +359,9 @@ int git_config_find_global(char *global_config_path)
return error;
}
int git_config_find_global_r(git_buf *path)
int git_config_find_system_r(git_buf *path)
{
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 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
return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM);
}
int git_config_find_system(char *system_config_path)
......
......@@ -14,6 +14,7 @@
#define GIT_CONFIG_FILENAME ".gitconfig"
#define GIT_CONFIG_FILENAME_INREPO "config"
#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"
#define GIT_CONFIG_FILE_MODE 0666
struct git_config {
......
......@@ -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);
}
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(
extern int git_futils_cmp_path(const char *name1, int len1, int isdir1,
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__ */
......@@ -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,
......@@ -445,9 +434,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_attr_cache_flush(repo);
git__free(repo->path_repository);
git__free(repo->workdir);
......
......@@ -19,6 +19,7 @@
#include "refs.h"
#include "buffer.h"
#include "odb.h"
#include "attr_file.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;
......
......@@ -348,22 +348,30 @@ uint32_t git__hash(const void *key, int len, uint32_t seed)
* Copyright (c) 1990 Regents of the University of California.
* All rights reserved.
*/
void **git__bsearch(const void *key, void **base, size_t nmemb, int (*compar)(const void *, const void *))
int git__bsearch(
void **array,
size_t array_len,
const void *key,
int (*compare)(const void *, const void *),
size_t *position)
{
int lim, cmp;
void **p;
for (lim = nmemb; lim != 0; lim >>= 1) {
p = base + (lim >> 1);
cmp = (*compar)(key, *p);
if (cmp > 0) { /* key > p: move right */
base = p + 1;
lim--;
} else if (cmp == 0) {
return (void **)p;
} /* else move left */
}
return NULL;
int lim, cmp;
void **part, **base = array;
for (lim = array_len; lim != 0; lim >>= 1) {
part = base + (lim >> 1);
cmp = (*compare)(key, *part);
if (cmp == 0) {
*position = (part - array);
return GIT_SUCCESS;
} else if (cmp > 0) { /* key > p; take right partition */
base = part + 1;
lim--;
} /* else take left partition */
}
*position = (base - array);
return GIT_ENOTFOUND;
}
/**
......
......@@ -105,8 +105,13 @@ extern void git__strtolower(char *str);
extern int git__fnmatch(const char *pattern, const char *name, int flags);
extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *));
extern void **git__bsearch(const void *key, void **base, size_t nmemb,
int (*compar)(const void *, const void *));
extern int git__bsearch(
void **array,
size_t array_len,
const void *key,
int (*compare)(const void *, const void *),
size_t *position);
extern int git__strcmp_cb(const void *a, const void *b);
......
......@@ -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)
......@@ -69,6 +74,45 @@ int git_vector_insert(git_vector *v, void *element)
return GIT_SUCCESS;
}
int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new))
{
int error = GIT_SUCCESS;
size_t pos;
assert(v && v->_cmp);
if (!v->sorted)
git_vector_sort(v);
if (v->length >= v->_alloc_size) {
if (resize_vector(v) < 0)
return GIT_ENOMEM;
}
error = git__bsearch(v->contents, v->length, element, v->_cmp, &pos);
/* If we found the element and have a duplicate handler callback,
* invoke it. If it returns an error, then cancel insert, otherwise
* proceed with normal insert.
*/
if (error == GIT_SUCCESS && on_dup != NULL) {
error = on_dup(&v->contents[pos], element);
if (error != GIT_SUCCESS)
return error;
}
/* shift elements to the right */
if (pos < v->length) {
memmove(v->contents + pos + 1, v->contents + pos,
(v->length - pos) * sizeof(void *));
}
v->contents[pos] = element;
v->length++;
return GIT_SUCCESS;
}
void git_vector_sort(git_vector *v)
{
assert(v);
......@@ -82,7 +126,7 @@ void git_vector_sort(git_vector *v)
int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *key)
{
void **find;
size_t pos;
assert(v && key && key_lookup);
......@@ -92,9 +136,9 @@ int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *ke
git_vector_sort(v);
find = git__bsearch(key, v->contents, v->length, key_lookup);
if (find != NULL)
return (int)(find - v->contents);
if (git__bsearch(v->contents, v->length, key, key_lookup,
&pos) == GIT_SUCCESS)
return (int)pos;
return git__throw(GIT_ENOTFOUND, "Can't find element");
}
......
......@@ -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,7 +41,13 @@ 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_insert_sorted(git_vector *v, void *element,
int (*on_dup)(void **old, void *new));
int git_vector_remove(git_vector *v, unsigned int idx);
void git_vector_uniq(git_vector *v);
#endif
......@@ -57,6 +57,11 @@ wchar_t* gitwin_to_utf16(const char* str)
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* ret;
......
......@@ -11,6 +11,7 @@
#define INCLUDE_git_utfconv_h__
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);
#endif
......
#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(NULL, cl_fixture("attr/attr0"), &file));
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.flags == 0);
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->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(NULL, cl_fixture("attr/attr1"), &file));
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.flags == 0);
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->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.flags == GIT_ATTR_FNMATCH_NEGATIVE);
rule = get_rule(2);
cl_assert_strequal("pat2", rule->match.pattern);
cl_assert(rule->match.length == strlen("pat2"));
cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_DIRECTORY);
rule = get_rule(3);
cl_assert_strequal("pat3dir/pat3file", rule->match.pattern);
cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_FULLPATH);
rule = get_rule(4);
cl_assert_strequal("pat4.*", rule->match.pattern);
cl_assert(rule->match.flags == 0);
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.flags == 0);
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->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(NULL, cl_fixture("attr/attr2"), &file));
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(NULL, cl_fixture("attr/attr3"), &file));
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_file.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(NULL, cl_fixture("attr/attr0"), &file));
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++) {
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(NULL, cl_fixture("attr/attr1"), &file));
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(NULL, cl_fixture("attr/attr2"), &file));
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(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);
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.
* Also rename gitattributes to .gitattributes, because it contains
* macro definitions which are only allowed in the root.
*/
cl_fixture_sandbox("attr");
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"));
}
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_test2", "multiattr", GIT_ATTR_FALSE },
{ "root_test3", "repoattr", GIT_ATTR_TRUE },
{ "root_test3", "rootattr", NULL },
{ "root_test3", "multiattr", "3" },
{ "root_test3", "multi2", 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 */
}
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 *names3[3] = { "macro2", "multi2", "multi3" };
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] == GIT_ATTR_TRUE);
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] == GIT_ATTR_TRUE);
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]);
cl_git_pass(git_attr_get_many(g_repo, "macro_test", 3, names3, values));
cl_assert(values[0] == GIT_ATTR_TRUE);
cl_assert(values[1] == GIT_ATTR_FALSE);
cl_assert_strequal("answer", values[2]);
}
void test_attr_repo__bad_macros(void)
{
const char *names[6] = { "rootattr", "positive", "negative",
"firstmacro", "secondmacro", "thirdmacro" };
const char *values[6];
cl_git_pass(git_attr_get_many(g_repo, "macro_bad", 6, names, values));
/* these three just confirm that the "mymacro" rule ran */
cl_assert(values[0] == NULL);
cl_assert(values[1] == GIT_ATTR_TRUE);
cl_assert(values[2] == GIT_ATTR_FALSE);
/* file contains:
* # let's try some malicious macro defs
* [attr]firstmacro -thirdmacro -secondmacro
* [attr]secondmacro firstmacro -firstmacro
* [attr]thirdmacro secondmacro=hahaha -firstmacro
* macro_bad firstmacro secondmacro thirdmacro
*
* firstmacro assignment list ends up with:
* -thirdmacro -secondmacro
* secondmacro assignment list expands "firstmacro" and ends up with:
* -thirdmacro -secondmacro -firstmacro
* thirdmacro assignment don't expand so list ends up with:
* secondmacro="hahaha"
*
* macro_bad assignment list ends up with:
* -thirdmacro -secondmacro firstmacro &&
* -thirdmacro -secondmacro -firstmacro secondmacro &&
* secondmacro="hahaha" thirdmacro
*
* so summary results should be:
* -firstmacro secondmacro="hahaha" thirdmacro
*/
cl_assert(values[3] == GIT_ATTR_FALSE);
cl_assert_strequal("hahaha", values[4]);
cl_assert(values[5] == GIT_ATTR_TRUE);
}
......@@ -59,6 +59,23 @@ 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__from_buffer(void);
extern void test_attr_lookup__match_variants(void);
extern void test_attr_lookup__simple(void);
extern void test_attr_repo__bad_macros(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_attr_repo__macros(void);
extern void test_attr_repo__manpage_example(void);
extern void test_buf_basic__printf(void);
extern void test_buf_basic__resize(void);
extern void test_config_add__cleanup(void);
......@@ -125,6 +142,9 @@ extern void test_core_strtol__int64(void);
extern void test_core_vector__0(void);
extern void test_core_vector__1(void);
extern void test_core_vector__2(void);
extern void test_core_vector__3(void);
extern void test_core_vector__4(void);
extern void test_core_vector__5(void);
extern void test_index_rename__single_file(void);
extern void test_network_remotelocal__cleanup(void);
extern void test_network_remotelocal__initialize(void);
......
......@@ -108,6 +108,27 @@ 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},
{"from_buffer", &test_attr_lookup__from_buffer},
{"match_variants", &test_attr_lookup__match_variants},
{"simple", &test_attr_lookup__simple}
};
static const struct clay_func _clay_cb_attr_repo[] = {
{"bad_macros", &test_attr_repo__bad_macros},
{"foreach", &test_attr_repo__foreach},
{"get_many", &test_attr_repo__get_many},
{"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[] = {
{"printf", &test_buf_basic__printf},
{"resize", &test_buf_basic__resize}
......@@ -194,7 +215,10 @@ static const struct clay_func _clay_cb_core_strtol[] = {
static const struct clay_func _clay_cb_core_vector[] = {
{"0", &test_core_vector__0},
{"1", &test_core_vector__1},
{"2", &test_core_vector__2}
{"2", &test_core_vector__2},
{"3", &test_core_vector__3},
{"4", &test_core_vector__4},
{"5", &test_core_vector__5}
};
static const struct clay_func _clay_cb_index_rename[] = {
{"single_file", &test_index_rename__single_file}
......@@ -309,6 +333,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, 5
},
{
"attr::repo",
{"initialize", &test_attr_repo__initialize},
{"cleanup", &test_attr_repo__cleanup},
_clay_cb_attr_repo, 6
},
{
"buf::basic",
{NULL, NULL},
{NULL, NULL},
......@@ -396,7 +438,7 @@ static const struct clay_suite _clay_suites[] = {
"core::vector",
{NULL, NULL},
{NULL, NULL},
_clay_cb_core_vector, 3
_clay_cb_core_vector, 6
},
{
"index::rename",
......@@ -538,8 +580,8 @@ static const struct clay_suite _clay_suites[] = {
}
};
static size_t _clay_suite_count = 38;
static size_t _clay_callback_count = 122;
static size_t _clay_suite_count = 41;
static size_t _clay_callback_count = 140;
/* Core test functions */
static void
......
......@@ -64,3 +64,128 @@ void test_core_vector__2(void)
}
static int compare_them(const void *a, const void *b)
{
return (int)((long)a - (long)b);
}
/* insert_sorted */
void test_core_vector__3(void)
{
git_vector x;
long i;
git_vector_init(&x, 1, &compare_them);
for (i = 0; i < 10; i += 2) {
git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
}
for (i = 9; i > 0; i -= 2) {
git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
}
cl_assert(x.length == 10);
for (i = 0; i < 10; ++i) {
cl_assert(git_vector_get(&x, i) == (void*)(i + 1));
}
git_vector_free(&x);
}
/* insert_sorted with duplicates */
void test_core_vector__4(void)
{
git_vector x;
long i;
git_vector_init(&x, 1, &compare_them);
for (i = 0; i < 10; i += 2) {
git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
}
for (i = 9; i > 0; i -= 2) {
git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
}
for (i = 0; i < 10; i += 2) {
git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
}
for (i = 9; i > 0; i -= 2) {
git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
}
cl_assert(x.length == 20);
for (i = 0; i < 20; ++i) {
cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1));
}
git_vector_free(&x);
}
typedef struct {
int content;
int count;
} my_struct;
static int _struct_count = 0;
static int compare_structs(const void *a, const void *b)
{
return ((const my_struct *)a)->content -
((const my_struct *)b)->content;
}
static int merge_structs(void **old_raw, void *new)
{
my_struct *old = *(my_struct **)old_raw;
cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content);
((my_struct *)old)->count += 1;
git__free(new);
_struct_count--;
return GIT_EEXISTS;
}
static my_struct *alloc_struct(int value)
{
my_struct *st = git__malloc(sizeof(my_struct));
st->content = value;
st->count = 0;
_struct_count++;
return st;
}
/* insert_sorted with duplicates and special handling */
void test_core_vector__5(void)
{
git_vector x;
int i;
git_vector_init(&x, 1, &compare_structs);
for (i = 0; i < 10; i += 2)
git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
for (i = 9; i > 0; i -= 2)
git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
cl_assert(x.length == 10);
cl_assert(_struct_count == 10);
for (i = 0; i < 10; i += 2)
git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
for (i = 9; i > 0; i -= 2)
git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
cl_assert(x.length == 10);
cl_assert(_struct_count == 10);
for (i = 0; i < 10; ++i) {
cl_assert(((my_struct *)git_vector_get(&x, i))->content == i);
git__free(git_vector_get(&x, i));
_struct_count--;
}
git_vector_free(&x);
}
[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
6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates
605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer <arrbee@arrbee.com> 1325281762 -0800 commit: more macro tests
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
605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer <arrbee@arrbee.com> 1325281762 -0800 commit: more macro tests
a5d76cad53f66f1312bd995909a5bab3c0820770
# 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
123
\ No newline at end of file
* rootattr
root_test2 -rootattr
root_test3 !rootattr
binfile binary
abc foo bar baz
root_test2 multiattr
root_test3 multi2=foo
root_test3 multiattr=1 multiattr=2 multiattr=3 multi2=abc !multi2
root_test2 multiattr=string -multiattr
[attr]mymacro positive -negative !rootattr
macro* mymacro another=77
[attr]macro2 multi2 -multi2 multi3 !multi3 multi3=answer
macro* macro2 macro2 macro2
# let's try some malicious macro defs
[attr]firstmacro -thirdmacro -secondmacro
[attr]secondmacro firstmacro -firstmacro
[attr]thirdmacro secondmacro=hahaha
macro_bad firstmacro secondmacro thirdmacro
* subattr=yes -negattr
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