+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "ignore.h"
+
#include "git2/ignore.h"
#include "common.h"
-#include "ignore.h"
#include "attrcache.h"
#include "path.h"
#include "config.h"
-#include "fnmatch.h"
+#include "wildmatch.h"
#define GIT_IGNORE_INTERNAL "[internal]exclude"
*/
static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
{
+ int (*cmp)(const char *, const char *, size_t);
git_attr_fnmatch *longer, *shorter;
char *p;
- if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0
- && (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0) {
-
- /* If lengths match we need to have an exact match */
- if (rule->length == neg->length) {
- return strcmp(rule->pattern, neg->pattern) == 0;
- } else if (rule->length < neg->length) {
- shorter = rule;
- longer = neg;
- } else {
- shorter = neg;
- longer = rule;
- }
-
- /* Otherwise, we need to check if the shorter
- * rule is a basename only (that is, it contains
- * no path separator) and, if so, if it
- * matches the tail of the longer rule */
- p = longer->pattern + longer->length - shorter->length;
+ if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0
+ || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0)
+ return false;
+
+ if (neg->flags & GIT_ATTR_FNMATCH_ICASE)
+ cmp = git__strncasecmp;
+ else
+ cmp = git__strncmp;
+
+ /* If lengths match we need to have an exact match */
+ if (rule->length == neg->length) {
+ return cmp(rule->pattern, neg->pattern, rule->length) == 0;
+ } else if (rule->length < neg->length) {
+ shorter = rule;
+ longer = neg;
+ } else {
+ shorter = neg;
+ longer = rule;
+ }
- if (p[-1] != '/')
- return false;
- if (memchr(shorter->pattern, '/', shorter->length) != NULL)
- return false;
+ /* Otherwise, we need to check if the shorter
+ * rule is a basename only (that is, it contains
+ * no path separator) and, if so, if it
+ * matches the tail of the longer rule */
+ p = longer->pattern + longer->length - shorter->length;
- return memcmp(p, shorter->pattern, shorter->length) == 0;
- }
+ if (p[-1] != '/')
+ return false;
+ if (memchr(shorter->pattern, '/', shorter->length) != NULL)
+ return false;
- return false;
+ return cmp(p, shorter->pattern, shorter->length) == 0;
}
/**
*/
static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match)
{
- int error = 0;
+ int error = 0, wildmatch_flags;
size_t i;
git_attr_fnmatch *rule;
char *path;
*out = 0;
+ wildmatch_flags = WM_PATHNAME;
+ if (match->flags & GIT_ATTR_FNMATCH_ICASE)
+ wildmatch_flags |= WM_CASEFOLD;
+
/* path of the file relative to the workdir, so we match the rules in subdirs */
if (match->containing_dir) {
git_buf_puts(&buf, match->containing_dir);
continue;
}
- /*
- * When dealing with a directory, we add '/<star>' so
- * p_fnmatch() honours FNM_PATHNAME. Checking for LEADINGDIR
- * alone isn't enough as that's also set for nagations, so we
- * need to check that NEGATIVE is off.
- */
git_buf_clear(&buf);
- if (rule->containing_dir) {
+ if (rule->containing_dir)
git_buf_puts(&buf, rule->containing_dir);
- }
+ git_buf_puts(&buf, rule->pattern);
- error = git_buf_puts(&buf, rule->pattern);
-
- if ((rule->flags & (GIT_ATTR_FNMATCH_LEADINGDIR | GIT_ATTR_FNMATCH_NEGATIVE)) == GIT_ATTR_FNMATCH_LEADINGDIR)
- error = git_buf_PUTS(&buf, "/*");
-
- if (error < 0)
+ if (git_buf_oom(&buf))
goto out;
- if ((error = p_fnmatch(git_buf_cstr(&buf), path, FNM_PATHNAME)) < 0) {
- giterr_set(GITERR_INVALID, "error matching pattern");
- goto out;
- }
-
/* if we found a match, we want to keep this rule */
- if (error != FNM_NOMATCH) {
+ if ((wildmatch(git_buf_cstr(&buf), path, wildmatch_flags)) == WM_MATCH) {
*out = 1;
error = 0;
goto out;
out:
git__free(path);
- git_buf_free(&buf);
+ git_buf_dispose(&buf);
return error;
}
static int parse_ignore_file(
- git_repository *repo, git_attr_file *attrs, const char *data)
+ git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros)
{
int error = 0;
int ignore_case = false;
const char *scan = data, *context = NULL;
git_attr_fnmatch *match = NULL;
- if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
- giterr_clear();
+ GIT_UNUSED(allow_macros);
+
+ if (git_repository__configmap_lookup(&ignore_case, repo, GIT_CONFIGMAP_IGNORECASE) < 0)
+ git_error_clear();
/* if subdir file path, convert context for file paths */
if (attrs->entry &&
context = attrs->entry->path;
if (git_mutex_lock(&attrs->lock) < 0) {
- giterr_set(GITERR_OS, "Failed to lock ignore file");
+ git_error_set(GIT_ERROR_OS, "failed to lock ignore file");
return -1;
}
break;
}
- match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
+ match->flags =
+ GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
if (!(error = git_attr_fnmatch__parse(
match, &attrs->pool, context, &scan)))
scan = git__next_line(scan);
- /* if a negative match doesn't actually do anything, throw it away */
- if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE)
+ /*
+ * If a negative match doesn't actually do anything,
+ * throw it away. As we cannot always verify whether a
+ * rule containing wildcards negates another rule, we
+ * do not optimize away these rules, though.
+ * */
+ if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE
+ && !(match->flags & GIT_ATTR_FNMATCH_HASWILD))
error = does_negate_rule(&valid_rule, &attrs->rules, match);
if (!error && valid_rule)
int error = 0;
git_attr_file *file = NULL;
- error = git_attr_cache__get(
- &file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE,
- base, filename, parse_ignore_file);
+ error = git_attr_cache__get(&file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE,
+ base, filename, parse_ignore_file, false);
if (error < 0)
return error;
if ((error = git_attr_cache__init(repo)) < 0)
return error;
- error = git_attr_cache__get(
- out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL);
+ error = git_attr_cache__get(out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL,
+ GIT_IGNORE_INTERNAL, NULL, false);
/* if internal rules list is empty, insert default rules */
if (!error && !(*out)->rules.length)
- error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES);
+ error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, false);
return error;
}
{
int error = 0;
const char *workdir = git_repository_workdir(repo);
+ git_buf infopath = GIT_BUF_INIT;
- assert(ignores && path);
+ assert(repo && ignores && path);
memset(ignores, 0, sizeof(*ignores));
ignores->repo = repo;
/* Read the ignore_case flag */
- if ((error = git_repository__cvar(
- &ignores->ignore_case, repo, GIT_CVAR_IGNORECASE)) < 0)
+ if ((error = git_repository__configmap_lookup(
+ &ignores->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE)) < 0)
goto cleanup;
if ((error = git_attr_cache__init(repo)) < 0)
(error = git_path_to_dir(&local)) < 0 ||
(error = git_buf_joinpath(&ignores->dir, workdir, local.ptr)) < 0)
{;} /* Nothing, we just want to stop on the first error */
- git_buf_free(&local);
+ git_buf_dispose(&local);
} else {
error = git_buf_joinpath(&ignores->dir, path, "");
}
goto cleanup;
}
- /* load .git/info/exclude */
- error = push_ignore_file(
- ignores, &ignores->ign_global,
- git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
- if (error < 0)
- goto cleanup;
+ /* load .git/info/exclude if possible */
+ if ((error = git_repository_item_path(&infopath, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
+ (error = push_ignore_file(ignores, &ignores->ign_global, infopath.ptr, GIT_IGNORE_FILE_INREPO)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto cleanup;
+ error = 0;
+ }
/* load core.excludesfile */
if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
git_repository_attr_cache(repo)->cfg_excl_file);
cleanup:
+ git_buf_dispose(&infopath);
if (error < 0)
git_ignore__free(ignores);
}
git_vector_free(&ignores->ign_global);
- git_buf_free(&ignores->dir);
+ git_buf_dispose(&ignores->dir);
}
static bool ignore_lookup_in_rules(
git_attr_fnmatch *match;
git_vector_rforeach(&file->rules, j, match) {
+ if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY &&
+ path->is_dir == GIT_DIR_FLAG_FALSE)
+ continue;
if (git_attr_fnmatch__match(match, path)) {
*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ?
GIT_IGNORE_TRUE : GIT_IGNORE_FALSE;
int git_ignore__lookup(
int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag)
{
- unsigned int i;
+ size_t i;
git_attr_file *file;
git_attr_path path;
if (ignore_lookup_in_rules(out, ignores->ign_internal, &path))
goto cleanup;
- /* next process files in the path */
- git_vector_foreach(&ignores->ign_path, i, file) {
+ /* next process files in the path.
+ * this process has to process ignores in reverse order
+ * to ensure correct prioritization of rules
+ */
+ git_vector_rforeach(&ignores->ign_path, i, file) {
if (ignore_lookup_in_rules(out, file, &path))
goto cleanup;
}
if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
return error;
- error = parse_ignore_file(repo, ign_internal, rules);
+ error = parse_ignore_file(repo, ign_internal, rules, false);
git_attr_file__free(ign_internal);
return error;
if (!(error = git_attr_file__clear_rules(ign_internal, true)))
error = parse_ignore_file(
- repo, ign_internal, GIT_IGNORE_DEFAULT_RULES);
+ repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false);
git_attr_file__free(ign_internal);
return error;
git_ignores ignores;
unsigned int i;
git_attr_file *file;
+ git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
- assert(ignored && pathname);
+ assert(repo && ignored && pathname);
- workdir = repo ? git_repository_workdir(repo) : NULL;
+ workdir = git_repository_workdir(repo);
memset(&path, 0, sizeof(path));
memset(&ignores, 0, sizeof(ignores));
- if ((error = git_attr_path__init(&path, pathname, workdir, GIT_DIR_FLAG_UNKNOWN)) < 0 ||
+ if (!git__suffixcmp(pathname, "/"))
+ dir_flag = GIT_DIR_FLAG_TRUE;
+ else if (git_repository_is_bare(repo))
+ dir_flag = GIT_DIR_FLAG_FALSE;
+
+ if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 ||
(error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
goto cleanup;
break;
if (ignored) {
- giterr_set(GITERR_INVALID, "pathspec contains ignored file '%s'",
+ git_error_set(GIT_ERROR_INVALID, "pathspec contains ignored file '%s'",
filename);
error = GIT_EINVALIDSPEC;
break;
}
git_index_free(idx);
- git_buf_free(&path);
+ git_buf_dispose(&path);
return error;
}