summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVicent Martí <tanoku@gmail.com>2012-01-02 09:56:48 +0100
committerVicent Martí <tanoku@gmail.com>2012-01-02 09:56:48 +0100
commit9191a6d2466d0f3c187a66f65757c95c1c3f772d (patch)
tree1fca9c4f9498eb01b3ff91b2ca4425d83c62b393 /src
parent9dd4c3e80690ec08eba604e5218d0f4abb8f1a02 (diff)
parentbd370b14fefdba3844a9bf0bbf87171ca48f49be (diff)
downloadlibgit2-9191a6d2466d0f3c187a66f65757c95c1c3f772d.tar.gz
Merge remote-tracking branch 'arrbee/git-attributes' into development
Conflicts: tests-clay/clay_main.c
Diffstat (limited to 'src')
-rw-r--r--src/attr.c400
-rw-r--r--src/attr_file.c528
-rw-r--r--src/attr_file.h110
-rw-r--r--src/config.c79
-rw-r--r--src/config.h1
-rw-r--r--src/fileops.c131
-rw-r--r--src/fileops.h24
-rw-r--r--src/hashtable.c14
-rw-r--r--src/hashtable.h7
-rw-r--r--src/refs.c15
-rw-r--r--src/repository.c1
-rw-r--r--src/repository.h2
-rw-r--r--src/util.c38
-rw-r--r--src/util.h9
-rw-r--r--src/vector.c52
-rw-r--r--src/vector.h8
-rw-r--r--src/win32/utf-conv.c5
-rw-r--r--src/win32/utf-conv.h1
18 files changed, 1318 insertions, 107 deletions
diff --git a/src/attr.c b/src/attr.c
new file mode 100644
index 000000000..f984458d4
--- /dev/null
+++ b/src/attr.c
@@ -0,0 +1,400 @@
+#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;
+}
diff --git a/src/attr_file.c b/src/attr_file.c
new file mode 100644
index 000000000..fe8844e2d
--- /dev/null
+++ b/src/attr_file.c
@@ -0,0 +1,528 @@
+#include "common.h"
+#include "repository.h"
+#include "filebuf.h"
+#include <ctype.h>
+
+const char *git_attr__true = "[internal]__TRUE__";
+const char *git_attr__false = "[internal]__FALSE__";
+
+static int git_attr_fnmatch__parse(git_attr_fnmatch *spec, const char **base);
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
+static void git_attr_rule__clear(git_attr_rule *rule);
+
+int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
+{
+ if (macro->assigns.length == 0)
+ return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values");
+
+ 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;
+ git_attr_file *attrs = NULL;
+ const char *scan = NULL;
+ git_attr_rule *rule = NULL;
+
+ *out = NULL;
+
+ attrs = git__calloc(1, sizeof(git_attr_file));
+ if (attrs == NULL)
+ return git__throw(GIT_ENOMEM, "Could not allocate attribute storage");
+
+ attrs->path = NULL;
+
+ error = git_vector_init(&attrs->rules, 4, NULL);
+ if (error != GIT_SUCCESS) {
+ git__rethrow(error, "Could not initialize attribute storage");
+ goto cleanup;
+ }
+
+ scan = buffer;
+
+ while (error == GIT_SUCCESS && *scan) {
+ /* allocate rule if needed */
+ if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) {
+ error = GIT_ENOMEM;
+ break;
+ }
+
+ /* parse the next "pattern attr attr attr" line */
+ if (!(error = git_attr_fnmatch__parse(&rule->match, &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);
+ }
+
+ /* if the rule wasn't a pattern, on to the next */
+ if (error != GIT_SUCCESS) {
+ git_attr_rule__clear(rule); /* reset rule contents */
+ if (error == GIT_ENOTFOUND)
+ error = GIT_SUCCESS;
+ } else {
+ rule = NULL; /* vector now "owns" the rule */
+ }
+ }
+
+cleanup:
+ if (error != GIT_SUCCESS) {
+ git_attr_rule__free(rule);
+ git_attr_file__free(attrs);
+ } else {
+ *out = attrs;
+ }
+
+ return error;
+}
+
+int git_attr_file__from_file(
+ git_repository *repo, const char *path, git_attr_file **out)
+{
+ int error = GIT_SUCCESS;
+ git_fbuffer fbuf = GIT_FBUFFER_INIT;
+
+ *out = NULL;
+
+ if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS ||
+ (error = git_attr_file__from_buffer(repo, fbuf.data, out)) < GIT_SUCCESS)
+ {
+ git__rethrow(error, "Could not open attribute file '%s'", path);
+ } else {
+ /* save path (okay to fail) */
+ (*out)->path = git__strdup(path);
+ }
+
+ git_futils_freebuffer(&fbuf);
+
+ return error;
+}
+
+void git_attr_file__free(git_attr_file *file)
+{
+ unsigned int i;
+ git_attr_rule *rule;
+
+ if (!file)
+ return;
+
+ git_vector_foreach(&file->rules, i, rule)
+ git_attr_rule__free(rule);
+
+ git_vector_free(&file->rules);
+
+ git__free(file->path);
+ file->path = NULL;
+
+ git__free(file);
+}
+
+unsigned long git_attr_file__name_hash(const char *name)
+{
+ unsigned long h = 5381;
+ int c;
+ assert(name);
+ while ((c = (int)*name++) != 0)
+ h = ((h << 5) + h) + c;
+ return h;
+}
+
+
+int git_attr_file__lookup_one(
+ git_attr_file *file,
+ const git_attr_path *path,
+ const char *attr,
+ const char **value)
+{
+ unsigned int i;
+ git_attr_name name;
+ git_attr_rule *rule;
+
+ *value = NULL;
+
+ name.name = attr;
+ name.name_hash = git_attr_file__name_hash(attr);
+
+ git_attr_file__foreach_matching_rule(file, path, i, rule) {
+ int pos = git_vector_bsearch(&rule->assigns, &name);
+ git_clearerror(); /* okay if search failed */
+
+ if (pos >= 0) {
+ *value = ((git_attr_assignment *)
+ git_vector_get(&rule->assigns, pos))->value;
+ break;
+ }
+ }
+
+ return GIT_SUCCESS;
+}
+
+
+int git_attr_rule__match_path(
+ git_attr_rule *rule,
+ const git_attr_path *path)
+{
+ int matched = FNM_NOMATCH;
+
+ if (rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
+ return matched;
+
+ if (rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH)
+ matched = p_fnmatch(rule->match.pattern, path->path, FNM_PATHNAME);
+ else
+ matched = p_fnmatch(rule->match.pattern, path->basename, 0);
+
+ if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
+ matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS;
+
+ return matched;
+}
+
+git_attr_assignment *git_attr_rule__lookup_assignment(
+ git_attr_rule *rule, const char *name)
+{
+ int pos;
+ git_attr_name key;
+ key.name = name;
+ key.name_hash = git_attr_file__name_hash(name);
+
+ pos = git_vector_bsearch(&rule->assigns, &key);
+ git_clearerror(); /* okay if search failed */
+
+ return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL;
+}
+
+int git_attr_path__init(
+ git_attr_path *info, const char *path)
+{
+ info->path = path;
+ info->basename = strrchr(path, '/');
+ if (info->basename)
+ info->basename++;
+ if (!info->basename || !*info->basename)
+ info->basename = path;
+ info->is_dir = (git_futils_isdir(path) == GIT_SUCCESS);
+ return GIT_SUCCESS;
+}
+
+
+/*
+ * From gitattributes(5):
+ *
+ * Patterns have the following format:
+ *
+ * - A blank line matches no files, so it can serve as a separator for
+ * readability.
+ *
+ * - A line starting with # serves as a comment.
+ *
+ * - An optional prefix ! which negates the pattern; any matching file
+ * excluded by a previous pattern will become included again. If a negated
+ * pattern matches, this will override lower precedence patterns sources.
+ *
+ * - If the pattern ends with a slash, it is removed for the purpose of the
+ * following description, but it would only find a match with a directory. In
+ * other words, foo/ will match a directory foo and paths underneath it, but
+ * will not match a regular file or a symbolic link foo (this is consistent
+ * with the way how pathspec works in general in git).
+ *
+ * - If the pattern does not contain a slash /, git treats it as a shell glob
+ * pattern and checks for a match against the pathname without leading
+ * directories.
+ *
+ * - Otherwise, git treats the pattern as a shell glob suitable for consumption
+ * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
+ * not match a / in the pathname. For example, "Documentation/\*.html" matches
+ * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
+ * slash matches the beginning of the pathname; for example, "/\*.c" matches
+ * "cat-file.c" but not "mozilla-sha1/sha1.c".
+ */
+
+/*
+ * This will return GIT_SUCCESS if the spec was filled out,
+ * GIT_ENOTFOUND if the fnmatch does not require matching, or
+ * another error code there was an actual problem.
+ */
+static int git_attr_fnmatch__parse(
+ git_attr_fnmatch *spec,
+ const char **base)
+{
+ const char *pattern;
+ const char *scan;
+ int slash_count;
+ int error = GIT_SUCCESS;
+
+ assert(base && *base);
+
+ pattern = *base;
+
+ while (isspace(*pattern)) pattern++;
+ if (!*pattern || *pattern == '#') {
+ error = GIT_ENOTFOUND;
+ goto skip_to_eol;
+ }
+
+ 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 == '!') {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
+ pattern++;
+ }
+
+ slash_count = 0;
+ for (scan = pattern; *scan != '\0'; ++scan) {
+ if (isspace(*scan) && *(scan - 1) != '\\')
+ break;
+
+ if (*scan == '/') {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
+ slash_count++;
+ }
+ }
+
+ *base = scan;
+ spec->length = scan - pattern;
+ spec->pattern = git__strndup(pattern, spec->length);
+
+ if (!spec->pattern) {
+ error = GIT_ENOMEM;
+ goto skip_to_eol;
+ } else {
+ char *from = spec->pattern, *to = spec->pattern;
+ while (*from) {
+ if (*from == '\\') {
+ from++;
+ spec->length--;
+ }
+ *to++ = *from++;
+ }
+ *to = '\0';
+ }
+
+ if (pattern[spec->length - 1] == '/') {
+ spec->length--;
+ spec->pattern[spec->length] = '\0';
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
+ if (--slash_count <= 0)
+ spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
+ }
+
+ return GIT_SUCCESS;
+
+skip_to_eol:
+ /* skip to end of line */
+ while (*pattern && *pattern != '\n') pattern++;
+ if (*pattern == '\n') pattern++;
+ *base = pattern;
+
+ return error;
+}
+
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
+{
+ const git_attr_name *a = a_raw;
+ const git_attr_name *b = b_raw;
+
+ if (b->name_hash < a->name_hash)
+ return 1;
+ else if (b->name_hash > a->name_hash)
+ return -1;
+ else
+ return strcmp(b->name, a->name);
+}
+
+static void git_attr_assignment__free(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);
+}
+
+static int merge_assignments(void **old_raw, void *new_raw)
+{
+ git_attr_assignment **old = (git_attr_assignment **)old_raw;
+ git_attr_assignment *new = (git_attr_assignment *)new_raw;
+
+ GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
+ *old = new;
+ return GIT_EEXISTS;
+}
+
+int git_attr_assignment__parse(
+ git_repository *repo,
+ git_vector *assigns,
+ const char **base)
+{
+ int error = GIT_SUCCESS;
+ const char *scan = *base;
+ git_attr_assignment *assign = NULL;
+
+ assert(assigns && !assigns->length);
+
+ assigns->_cmp = sort_by_hash_and_name;
+
+ while (*scan && *scan != '\n' && error == GIT_SUCCESS) {
+ const char *name_start, *value_start;
+
+ /* skip leading blanks */
+ while (isspace(*scan) && *scan != '\n') scan++;
+
+ /* allocate assign if needed */
+ if (!assign) {
+ assign = git__calloc(1, sizeof(git_attr_assignment));
+ if (!assign) {
+ error = GIT_ENOMEM;
+ break;
+ }
+ GIT_REFCOUNT_INC(assign);
+ }
+
+ assign->name_hash = 5381;
+ assign->value = GIT_ATTR_TRUE;
+ assign->is_allocated = 0;
+
+ /* look for magic name prefixes */
+ if (*scan == '-') {
+ assign->value = GIT_ATTR_FALSE;
+ scan++;
+ } else if (*scan == '!') {
+ assign->value = NULL; /* explicit unspecified state */
+ scan++;
+ } else if (*scan == '#') /* comment rest of line */
+ break;
+
+ /* find the name */
+ name_start = scan;
+ while (*scan && !isspace(*scan) && *scan != '=') {
+ assign->name_hash =
+ ((assign->name_hash << 5) + assign->name_hash) + *scan;
+ scan++;
+ }
+ if (scan == name_start) {
+ /* must have found lone prefix (" - ") or leading = ("=foo")
+ * or end of buffer -- advance until whitespace and continue
+ */
+ while (*scan && !isspace(*scan)) scan++;
+ 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 (*scan == '=') {
+ for (value_start = ++scan; *scan && !isspace(*scan); ++scan);
+
+ /* if we found a value, allocate permanent storage for it */
+ if (scan > value_start) {
+ assign->value = git__strndup(value_start, scan - value_start);
+ if (!assign->value) {
+ error = GIT_ENOMEM;
+ break;
+ } else {
+ assign->is_allocated = 1;
+ }
+ }
+ }
+
+ /* expand macros (if given a repo with a macro cache) */
+ if (repo != NULL && assign->value == GIT_ATTR_TRUE) {
+ git_attr_rule *macro =
+ git_hashtable_lookup(repo->attrcache.macros, assign->name);
+
+ if (macro != NULL) {
+ unsigned int i;
+ git_attr_assignment *massign;
+
+ git_vector_foreach(&macro->assigns, i, massign) {
+ GIT_REFCOUNT_INC(massign);
+
+ error = git_vector_insert_sorted(
+ assigns, massign, &merge_assignments);
+
+ if (error == GIT_EEXISTS)
+ error = GIT_SUCCESS;
+ else if (error != GIT_SUCCESS)
+ break;
+ }
+ }
+ }
+
+ /* insert allocated assign into vector */
+ error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
+ if (error == GIT_EEXISTS)
+ error = GIT_SUCCESS;
+ else if (error < GIT_SUCCESS)
+ break;
+
+ /* clear assign since it is now "owned" by the vector */
+ assign = NULL;
+ }
+
+ if (!assigns->length)
+ error = git__throw(GIT_ENOTFOUND, "No attribute assignments found for rule");
+
+ if (assign != NULL)
+ git_attr_assignment__free(assign);
+
+ while (*scan && *scan != '\n') scan++;
+ if (*scan == '\n') scan++;
+
+ *base = scan;
+
+ return error;
+}
+
+static void git_attr_rule__clear(git_attr_rule *rule)
+{
+ unsigned int i;
+ git_attr_assignment *assign;
+
+ if (!rule)
+ return;
+
+ git__free(rule->match.pattern);
+ rule->match.pattern = NULL;
+ rule->match.length = 0;
+
+ git_vector_foreach(&rule->assigns, i, assign)
+ GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
+
+ git_vector_free(&rule->assigns);
+}
+
+void git_attr_rule__free(git_attr_rule *rule)
+{
+ git_attr_rule__clear(rule);
+ git__free(rule);
+}
+
diff --git a/src/attr_file.h b/src/attr_file.h
new file mode 100644
index 000000000..bed440d61
--- /dev/null
+++ b/src/attr_file.h
@@ -0,0 +1,110 @@
+/*
+ * 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
diff --git a/src/config.c b/src/config.c
index f8ff05056..1338ef3b1 100644
--- a/src/config.c
+++ b/src/config.c
@@ -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)
diff --git a/src/config.h b/src/config.h
index fc639c6d4..6345b0a5d 100644
--- a/src/config.h
+++ b/src/config.h
@@ -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 {
diff --git a/src/fileops.c b/src/fileops.c
index fb2f954d7..5eb7bf6ec 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -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
+}
+
diff --git a/src/fileops.h b/src/fileops.h
index df135d0db..31f3e6a91 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -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__ */
diff --git a/src/hashtable.c b/src/hashtable.c
index 15d173992..f836f166d 100644
--- a/src/hashtable.c
+++ b/src/hashtable.c
@@ -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]);
+}
diff --git a/src/hashtable.h b/src/hashtable.h
index f0ca3ebd2..485b17aa6 100644
--- a/src/hashtable.h
+++ b/src/hashtable.h
@@ -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
diff --git a/src/refs.c b/src/refs.c
index 4950fd595..2842adab1 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -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;
diff --git a/src/repository.c b/src/repository.c
index 67afa2ee2..a94ecce55 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -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);
diff --git a/src/repository.h b/src/repository.h
index c3a9a5c60..82052158a 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -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;
diff --git a/src/util.c b/src/util.c
index b3af7ffd8..1ca9d850c 100644
--- a/src/util.c
+++ b/src/util.c
@@ -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;
}
/**
diff --git a/src/util.h b/src/util.h
index 4b1104b7b..2367bb5f3 100644
--- a/src/util.h
+++ b/src/util.h
@@ -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);
diff --git a/src/vector.c b/src/vector.c
index 123aae8e6..593d037d4 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -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");
}
diff --git a/src/vector.h b/src/vector.h
index 08f5a501c..9ee3c9ed5 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -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
diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
index b41c78f92..b1b838eb7 100644
--- a/src/win32/utf-conv.c
+++ b/src/win32/utf-conv.c
@@ -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;
diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h
index da03e3385..bbb5c4f69 100644
--- a/src/win32/utf-conv.h
+++ b/src/win32/utf-conv.h
@@ -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