diff --git a/include/git2/index.h b/include/git2/index.h index 627d6c4fd..5018c896b 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -169,6 +169,10 @@ GIT_EXTERN(void) git_index_uniq(git_index *index); * * This method will fail in bare index instances. * + * This forces the file to be added to the index, not looking + * at gitignore rules. Those rules can be evaluated through + * the git_status APIs (in status.h) before calling this. + * * @param index an existing index object * @param path filename to add * @param stage stage for the entry diff --git a/include/git2/status.h b/include/git2/status.h index 42b2dd197..c0f38c508 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -20,18 +20,18 @@ GIT_BEGIN_DECL #define GIT_STATUS_CURRENT 0 + /** Flags for index status */ #define GIT_STATUS_INDEX_NEW (1 << 0) -#define GIT_STATUS_INDEX_MODIFIED (1 << 1) -#define GIT_STATUS_INDEX_DELETED (1 << 2) +#define GIT_STATUS_INDEX_MODIFIED (1 << 1) +#define GIT_STATUS_INDEX_DELETED (1 << 2) /** Flags for worktree status */ #define GIT_STATUS_WT_NEW (1 << 3) -#define GIT_STATUS_WT_MODIFIED (1 << 4) +#define GIT_STATUS_WT_MODIFIED (1 << 4) #define GIT_STATUS_WT_DELETED (1 << 5) -// TODO Ignored files not handled yet -#define GIT_STATUS_IGNORED (1 << 6) +#define GIT_STATUS_IGNORED (1 << 6) /** * Gather file statuses and run a callback for each one. @@ -58,6 +58,22 @@ GIT_EXTERN(int) git_status_foreach(git_repository *repo, int (*callback)(const c */ GIT_EXTERN(int) git_status_file(unsigned int *status_flags, git_repository *repo, const char *path); +/** + * Test if the ignore rules apply to a given file. + * + * This function simply checks the ignore rules to see if they would apply + * to the given file. Unlike git_status_file(), this indicates if the file + * would be ignored regardless of whether the file is already in the index + * or in the repository. + * + * @param repo a repository object + * @param path the file to check ignores for, rooted at the repo's workdir + * @param ignored boolean returning 0 if the file is not ignored, 1 if it is + * @return GIT_SUCCESS if the ignore rules could be processed for the file + * (regardless of whether it exists or not), or an error < 0 if they could not. + */ +GIT_EXTERN(int) git_status_should_ignore(git_repository *repo, const char *path, int *ignored); + /** @} */ GIT_END_DECL #endif diff --git a/src/attr.c b/src/attr.c index 679380bba..dc42379ff 100644 --- a/src/attr.c +++ b/src/attr.c @@ -3,15 +3,9 @@ #include "config.h" #include -#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, @@ -186,7 +180,7 @@ int git_attr_add_macro( int error; git_attr_rule *macro = NULL; - if ((error = attr_cache_init(repo)) < GIT_SUCCESS) + if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS) return error; macro = git__calloc(1, sizeof(git_attr_rule)); @@ -215,11 +209,12 @@ int git_attr_add_macro( /* add git_attr_file to vector of files, loading if needed */ -static int push_attrs( +int git_attr_cache__push_file( git_repository *repo, - git_vector *files, + git_vector *stack, const char *base, - const char *filename) + const char *filename, + int (*loader)(git_repository *, const char *, git_attr_file **)) { int error = GIT_SUCCESS; git_attr_cache *cache = &repo->attrcache; @@ -227,23 +222,22 @@ static int push_attrs( 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; + if (base != NULL) { + if ((error = git_buf_joinpath(&path, base, filename)) < GIT_SUCCESS) + goto cleanup; + filename = path.ptr; } /* 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); + file = git_hashtable_lookup(cache->files, filename); + if (file == NULL && git_futils_exists(filename) == GIT_SUCCESS) { + error = (*loader)(repo, filename, &file); add_to_cache = (error == GIT_SUCCESS); } if (file != NULL) { /* add file to vector, if we found it */ - error = git_vector_insert(files, file); + error = git_vector_insert(stack, file); /* add file to cache, if it is new */ /* do this after above step b/c it is not critical */ @@ -256,6 +250,19 @@ cleanup: return error; } +#define push_attrs(R,S,B,F) \ + git_attr_cache__push_file((R),(S),(B),(F),git_attr_file__from_file) + +typedef struct { + git_repository *repo; + git_vector *files; +} attr_walk_up_info; + +static int push_one_attr(void *ref, git_buf *path) +{ + attr_walk_up_info *info = (attr_walk_up_info *)ref; + return push_attrs(info->repo, info->files, path->ptr, GIT_ATTR_FILE); +} static int collect_attr_files( git_repository *repo, const char *path, git_vector *files) @@ -264,23 +271,17 @@ static int collect_attr_files( git_buf dir = GIT_BUF_INIT; git_config *cfg; const char *workdir = git_repository_workdir(repo); + attr_walk_up_info info; - if ((error = attr_cache_init(repo)) < GIT_SUCCESS) + if ((error = git_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) + if ((error = git_futils_dir_for_path(&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 @@ -292,26 +293,15 @@ static int collect_attr_files( 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); - } + info.repo = repo; + info.files = files; + error = git_path_walk_up(&dir, workdir, push_one_attr, &info); if (error < GIT_SUCCESS) goto cleanup; - if (git_repository_config(&cfg, repo) == GIT_SUCCESS) { + if ((error = git_repository_config(&cfg, repo)) == GIT_SUCCESS) { const char *core_attribs = NULL; - git_config_get_string(cfg, "core.attributesfile", &core_attribs); + git_config_get_string(cfg, GIT_ATTR_CONFIG, &core_attribs); git_clearerror(); /* don't care if attributesfile is not set */ if (core_attribs) error = push_attrs(repo, files, NULL, core_attribs); @@ -337,7 +327,7 @@ static int collect_attr_files( } -static int attr_cache_init(git_repository *repo) +int git_attr_cache__init(git_repository *repo) { int error = GIT_SUCCESS; git_attr_cache *cache = &repo->attrcache; @@ -367,7 +357,6 @@ static int attr_cache_init(git_repository *repo) return error; } - void git_attr_cache_flush( git_repository *repo) { @@ -398,3 +387,12 @@ void git_attr_cache_flush( repo->attrcache.initialized = 0; } + +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); +} diff --git a/src/attr.h b/src/attr.h new file mode 100644 index 000000000..5edff30d1 --- /dev/null +++ b/src/attr.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009-2011 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attr_h__ +#define INCLUDE_attr_h__ + +#include "attr_file.h" + +typedef struct { + int initialized; + git_hashtable *files; /* hash path to git_attr_file of rules */ + git_hashtable *macros; /* hash name to vector */ +} git_attr_cache; + +extern int git_attr_cache__init(git_repository *repo); + +extern int git_attr_cache__insert_macro( + git_repository *repo, git_attr_rule *macro); + +extern int git_attr_cache__push_file( + git_repository *repo, + git_vector *stack, + const char *base, + const char *filename, + int (*loader)(git_repository *, const char *, git_attr_file **)); + +#endif diff --git a/src/attr_file.c b/src/attr_file.c index fe8844e2d..5ea07c984 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -6,17 +6,29 @@ 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) +int git_attr_file__new(git_attr_file **attrs_ptr) { - if (macro->assigns.length == 0) - return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values"); + int error; + git_attr_file *attrs = NULL; - return git_hashtable_insert( - repo->attrcache.macros, macro->match.pattern, macro); + attrs = git__calloc(1, sizeof(git_attr_file)); + if (attrs == NULL) + error = GIT_ENOMEM; + else + error = git_vector_init(&attrs->rules, 4, NULL); + + if (error != GIT_SUCCESS) { + git__rethrow(error, "Could not allocate attribute storage"); + git__free(attrs); + attrs = NULL; + } + + *attrs_ptr = attrs; + + return error; } int git_attr_file__from_buffer( @@ -29,17 +41,8 @@ int git_attr_file__from_buffer( *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"); + if ((error = git_attr_file__new(&attrs)) < GIT_SUCCESS) goto cleanup; - } scan = buffer; @@ -166,19 +169,28 @@ int git_attr_file__lookup_one( } -int git_attr_rule__match_path( - git_attr_rule *rule, +int git_attr_fnmatch__match( + git_attr_fnmatch *match, const git_attr_path *path) { int matched = FNM_NOMATCH; - if (rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir) + if (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); + if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) + matched = p_fnmatch(match->pattern, path->path, FNM_PATHNAME); else - matched = p_fnmatch(rule->match.pattern, path->basename, 0); + matched = p_fnmatch(match->pattern, path->basename, 0); + + return matched; +} + +int git_attr_rule__match( + git_attr_rule *rule, + const git_attr_path *path) +{ + int matched = git_attr_fnmatch__match(&rule->match, path); if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS; @@ -186,6 +198,7 @@ int git_attr_rule__match_path( return matched; } + git_attr_assignment *git_attr_rule__lookup_assignment( git_attr_rule *rule, const char *name) { @@ -203,6 +216,7 @@ git_attr_assignment *git_attr_rule__lookup_assignment( int git_attr_path__init( git_attr_path *info, const char *path) { + assert(info && path); info->path = path; info->basename = strrchr(path, '/'); if (info->basename) @@ -251,23 +265,21 @@ int git_attr_path__init( * GIT_ENOTFOUND if the fnmatch does not require matching, or * another error code there was an actual problem. */ -static int git_attr_fnmatch__parse( +int git_attr_fnmatch__parse( git_attr_fnmatch *spec, const char **base) { - const char *pattern; - const char *scan; + const char *pattern, *scan; int slash_count; - int error = GIT_SUCCESS; - assert(base && *base); + assert(spec && base && *base); pattern = *base; while (isspace(*pattern)) pattern++; if (!*pattern || *pattern == '#') { - error = GIT_ENOTFOUND; - goto skip_to_eol; + *base = git__next_line(pattern); + return GIT_ENOTFOUND; } spec->flags = 0; @@ -276,11 +288,8 @@ static int git_attr_fnmatch__parse( 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; } + /* else a character range like [a-e]* which is accepted */ } if (*pattern == '!') { @@ -290,6 +299,7 @@ static int git_attr_fnmatch__parse( slash_count = 0; for (scan = pattern; *scan != '\0'; ++scan) { + /* scan until (non-escaped) white space */ if (isspace(*scan) && *(scan - 1) != '\\') break; @@ -300,13 +310,15 @@ static int git_attr_fnmatch__parse( } *base = scan; + spec->length = scan - pattern; spec->pattern = git__strndup(pattern, spec->length); if (!spec->pattern) { - error = GIT_ENOMEM; - goto skip_to_eol; + *base = git__next_line(pattern); + return GIT_ENOMEM; } else { + /* remove '\' that might have be used for internal whitespace */ char *from = spec->pattern, *to = spec->pattern; while (*from) { if (*from == '\\') { @@ -327,14 +339,6 @@ static int git_attr_fnmatch__parse( } 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) @@ -494,10 +498,7 @@ int git_attr_assignment__parse( if (assign != NULL) git_attr_assignment__free(assign); - while (*scan && *scan != '\n') scan++; - if (*scan == '\n') scan++; - - *base = scan; + *base = git__next_line(scan); return error; } @@ -510,14 +511,15 @@ static void git_attr_rule__clear(git_attr_rule *rule) if (!rule) return; + if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { + git_vector_foreach(&rule->assigns, i, assign) + GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); + git_vector_free(&rule->assigns); + } + 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) diff --git a/src/attr_file.h b/src/attr_file.h index bed440d61..7190c4c7b 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -11,10 +11,16 @@ #include "vector.h" #include "hashtable.h" +#define GIT_ATTR_FILE ".gitattributes" +#define GIT_ATTR_FILE_INREPO "info/attributes" +#define GIT_ATTR_FILE_SYSTEM "gitattributes" +#define GIT_ATTR_CONFIG "core.attributesfile" + #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) +#define GIT_ATTR_FNMATCH_IGNORE (1U << 4) typedef struct { char *pattern; @@ -22,6 +28,11 @@ typedef struct { unsigned int flags; } git_attr_fnmatch; +typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* vector of */ +} git_attr_rule; + typedef struct { git_refcount unused; const char *name; @@ -29,7 +40,7 @@ typedef struct { } git_attr_name; typedef struct { - git_refcount rc; /* for macros */ + git_refcount rc; /* for macros */ char *name; unsigned long name_hash; const char *value; @@ -37,13 +48,8 @@ typedef struct { } git_attr_assignment; typedef struct { - git_attr_fnmatch match; - git_vector assigns; /* vector of */ -} git_attr_rule; - -typedef struct { - char *path; /* cache the path this was loaded from */ - git_vector rules; /* vector of */ + char *path; /* cache the path this was loaded from */ + git_vector rules; /* vector of or */ } git_attr_file; typedef struct { @@ -52,12 +58,6 @@ typedef struct { 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_cache; - /* * git_attr_file API */ @@ -67,6 +67,7 @@ extern int git_attr_file__from_buffer( extern int git_attr_file__from_file( git_repository *repo, const char *path, git_attr_file **out); +extern int git_attr_file__new(git_attr_file **attrs_ptr); extern void git_attr_file__free(git_attr_file *file); extern int git_attr_file__lookup_one( @@ -78,7 +79,7 @@ extern int git_attr_file__lookup_one( /* 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) + if (git_attr_rule__match((rule), (path)) == GIT_SUCCESS) extern unsigned long git_attr_file__name_hash(const char *name); @@ -87,9 +88,17 @@ extern unsigned long git_attr_file__name_hash(const char *name); * other utilities */ +extern int git_attr_fnmatch__parse( + git_attr_fnmatch *spec, + const char **base); + +extern int git_attr_fnmatch__match( + git_attr_fnmatch *rule, + const git_attr_path *path); + extern void git_attr_rule__free(git_attr_rule *rule); -extern int git_attr_rule__match_path( +extern int git_attr_rule__match( git_attr_rule *rule, const git_attr_path *path); @@ -104,7 +113,4 @@ extern int git_attr_assignment__parse( 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/buffer.c b/src/buffer.c index def3496ce..c57e4aa1b 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -111,8 +111,10 @@ int git_buf_set(git_buf *buf, const char *data, size_t len) if (len == 0 || data == NULL) { git_buf_clear(buf); } else { - ENSURE_SIZE(buf, len + 1); - memmove(buf->ptr, data, len); + if (data != buf->ptr) { + ENSURE_SIZE(buf, len + 1); + memmove(buf->ptr, data, len); + } buf->size = len; buf->ptr[buf->size] = '\0'; } @@ -179,7 +181,7 @@ void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf) { size_t copylen; - assert(data && datasize); + assert(data && datasize && buf); data[0] = '\0'; @@ -205,7 +207,7 @@ void git_buf_consume(git_buf *buf, const char *end) void git_buf_truncate(git_buf *buf, ssize_t len) { - if (len < buf->size) { + if (len >= 0 && len < buf->size) { buf->size = len; buf->ptr[buf->size] = '\0'; } diff --git a/src/buffer.h b/src/buffer.h index 52fd9a678..d06358527 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -102,9 +102,16 @@ GIT_INLINE(const char *) git_buf_cstr(git_buf *buf) return buf->ptr; } - void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf); #define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1) +GIT_INLINE(int) git_buf_rfind_next(git_buf *buf, char ch) +{ + int idx = buf->size - 1; + while (idx >= 0 && buf->ptr[idx] == ch) idx--; + while (idx >= 0 && buf->ptr[idx] != ch) idx--; + return idx; +} + #endif diff --git a/src/fileops.c b/src/fileops.c index 48bd3514d..3412a47e2 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -534,3 +534,28 @@ int git_futils_find_system_file(git_buf *path, const char *filename) #endif } +int git_futils_dir_for_path(git_buf *dir, const char *path, const char *base) +{ + int error = GIT_SUCCESS; + + if (base != NULL && git_path_root(path) < 0) + error = git_buf_joinpath(dir, base, path); + else + error = git_buf_sets(dir, path); + + if (error == GIT_SUCCESS) { + char buf[GIT_PATH_MAX]; + if (p_realpath(dir->ptr, buf) != NULL) + error = git_buf_sets(dir, buf); + } + + /* call dirname if this is not a directory */ + if (error == GIT_SUCCESS && git_futils_isdir(dir->ptr) != GIT_SUCCESS) + if (git_path_dirname_r(dir, dir->ptr) < GIT_SUCCESS) + error = git_buf_lasterror(dir); + + if (error == GIT_SUCCESS) + error = git_path_to_dir(dir); + + return error; +} diff --git a/src/fileops.h b/src/fileops.h index 31f3e6a91..91903a731 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -101,6 +101,16 @@ extern int git_futils_mkpath2file(const char *path, const mode_t mode); extern int git_futils_rmdir_r(const char *path, int force); +/** + * Get the directory for a path. + * + * If the path is a directory, this does nothing (save append a '/' as + * needed). If path is a normal file, this gets the directory containing + * it. If the path does not exist, then this treats it a filename and + * returns the dirname of it. + */ +extern int git_futils_dir_for_path(git_buf *dir, const char *path, const char *base); + /** * Create and open a temporary file with a `_git2_` suffix. * Writes the filename into path_out. diff --git a/src/ignore.c b/src/ignore.c new file mode 100644 index 000000000..1040574d7 --- /dev/null +++ b/src/ignore.c @@ -0,0 +1,159 @@ +#include "ignore.h" +#include "path.h" +#include "git2/config.h" + +#define GIT_IGNORE_INTERNAL "[internal]exclude" +#define GIT_IGNORE_FILE_INREPO "info/exclude" +#define GIT_IGNORE_FILE ".gitignore" +#define GIT_IGNORE_CONFIG "core.excludesfile" + +static int load_ignore_file( + git_repository *GIT_UNUSED(repo), const char *path, git_attr_file **out) +{ + int error = GIT_SUCCESS; + git_fbuffer fbuf = GIT_FBUFFER_INIT; + git_attr_file *ignores = NULL; + git_attr_fnmatch *match = NULL; + const char *scan = NULL; + + GIT_UNUSED_ARG(repo); + + *out = NULL; + + if ((error = git_futils_readbuffer(&fbuf, path)) == GIT_SUCCESS) + error = git_attr_file__new(&ignores); + + ignores->path = git__strdup(path); + + scan = fbuf.data; + + while (error == GIT_SUCCESS && *scan) { + if (!match && !(match = git__calloc(1, sizeof(git_attr_fnmatch)))) { + error = GIT_ENOMEM; + break; + } + + if (!(error = git_attr_fnmatch__parse(match, &scan))) { + match->flags = match->flags | GIT_ATTR_FNMATCH_IGNORE; + scan = git__next_line(scan); + error = git_vector_insert(&ignores->rules, match); + } + + if (error != GIT_SUCCESS) { + git__free(match->pattern); + match->pattern = NULL; + + if (error == GIT_ENOTFOUND) + error = GIT_SUCCESS; + } else { + match = NULL; /* vector now "owns" the match */ + } + } + + git_futils_freebuffer(&fbuf); + git__free(match); + + if (error != GIT_SUCCESS) { + git__rethrow(error, "Could not open ignore file '%s'", path); + git_attr_file__free(ignores); + } else { + *out = ignores; + } + + return error; +} + +#define push_ignore(R,S,B,F) \ + git_attr_cache__push_file((R),(S),(B),(F),load_ignore_file) + +typedef struct { + git_repository *repo; + git_vector *stack; +} ignore_walk_up_info; + +static int push_one_ignore(void *ref, git_buf *path) +{ + ignore_walk_up_info *info = (ignore_walk_up_info *)ref; + return push_ignore(info->repo, info->stack, path->ptr, GIT_IGNORE_FILE); +} + +int git_ignore__for_path(git_repository *repo, const char *path, git_vector *stack) +{ + int error = GIT_SUCCESS; + git_buf dir = GIT_BUF_INIT; + git_config *cfg; + const char *workdir = git_repository_workdir(repo); + ignore_walk_up_info info; + + if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS) + goto cleanup; + + if ((error = git_futils_dir_for_path(&dir, path, workdir)) < GIT_SUCCESS) + goto cleanup; + + /* insert internals */ + if ((error = push_ignore(repo, stack, NULL, GIT_IGNORE_INTERNAL)) < GIT_SUCCESS) + goto cleanup; + + /* load .gitignore up the path */ + info.repo = repo; + info.stack = stack; + if ((error = git_path_walk_up(&dir, workdir, push_one_ignore, &info)) < GIT_SUCCESS) + goto cleanup; + + /* load .git/info/exclude */ + if ((error = push_ignore(repo, stack, repo->path_repository, GIT_IGNORE_FILE_INREPO)) < GIT_SUCCESS) + goto cleanup; + + /* load core.excludesfile */ + if ((error = git_repository_config(&cfg, repo)) == GIT_SUCCESS) { + const char *core_ignore; + error = git_config_get_string(cfg, GIT_IGNORE_CONFIG, &core_ignore); + if (error == GIT_SUCCESS && core_ignore != NULL) + error = push_ignore(repo, stack, NULL, core_ignore); + else { + error = GIT_SUCCESS; + git_clearerror(); /* don't care if attributesfile is not set */ + } + git_config_free(cfg); + } + +cleanup: + if (error < GIT_SUCCESS) + git__rethrow(error, "Could not get ignore files for '%s'", path); + + git_buf_free(&dir); + + return error; +} + +void git_ignore__free(git_vector *stack) +{ + git_vector_free(stack); +} + +int git_ignore__lookup(git_vector *stack, const char *pathname, int *ignored) +{ + int error; + unsigned int i, j; + git_attr_file *file; + git_attr_path path; + git_attr_fnmatch *match; + + if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS) + return git__rethrow(error, "Could not get attribute for '%s'", pathname); + + *ignored = 0; + + git_vector_foreach(stack, i, file) { + git_vector_rforeach(&file->rules, j, match) { + if (git_attr_fnmatch__match(match, &path) == GIT_SUCCESS) { + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); + goto found; + } + } + } +found: + + return error; +} diff --git a/src/ignore.h b/src/ignore.h new file mode 100644 index 000000000..2954445b5 --- /dev/null +++ b/src/ignore.h @@ -0,0 +1,17 @@ +/* + * 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_ignore_h__ +#define INCLUDE_ignore_h__ + +#include "repository.h" +#include "vector.h" + +extern int git_ignore__for_path(git_repository *repo, const char *path, git_vector *stack); +extern void git_ignore__free(git_vector *stack); +extern int git_ignore__lookup(git_vector *stack, const char *path, int *ignored); + +#endif diff --git a/src/path.c b/src/path.c index f9663b7e5..03ebfe090 100644 --- a/src/path.c +++ b/src/path.c @@ -305,3 +305,47 @@ int git_path_fromurl(git_buf *local_path_out, const char *file_url) return error; } + +int git_path_walk_up( + git_buf *path, + const char *ceiling, + int (*cb)(void *data, git_buf *), + void *data) +{ + int error = GIT_SUCCESS; + git_buf iter; + ssize_t stop = 0, scan; + char oldc = '\0'; + + assert(path && cb); + + if (ceiling != NULL) { + if (git__prefixcmp(path->ptr, ceiling) == GIT_SUCCESS) + stop = (ssize_t)strlen(ceiling); + else + stop = path->size; + } + scan = path->size; + + iter.ptr = path->ptr; + iter.size = path->size; + iter.asize = path->asize; + + while (scan >= stop) { + if ((error = cb(data, &iter)) < GIT_SUCCESS) + break; + iter.ptr[scan] = oldc; + scan = git_buf_rfind_next(&iter, '/'); + if (scan >= 0) { + scan++; + oldc = iter.ptr[scan]; + iter.size = scan; + iter.ptr[scan] = '\0'; + } + } + + if (scan >= 0) + iter.ptr[scan] = oldc; + + return error; +} diff --git a/src/path.h b/src/path.h index c308c5bd4..e59c19ad9 100644 --- a/src/path.h +++ b/src/path.h @@ -77,4 +77,18 @@ GIT_INLINE(void) git_path_mkposix(char *path) extern int git__percent_decode(git_buf *decoded_out, const char *input); extern int git_path_fromurl(git_buf *local_path_out, const char *file_url); +/** + * Invoke callback directory by directory up the path until the ceiling + * is reached (inclusive of a final call at the root_path). + * + * If the ceiling is NULL, this will walk all the way up to the root. + * If the ceiling is not a prefix of the path, the callback will be + * invoked a single time on the verbatim input path. Returning anything + * other than GIT_SUCCESS from the callback function will stop the + * iteration and propogate the error to the caller. + */ +extern int git_path_walk_up( + git_buf *path, const char *ceiling, + int (*cb)(void *data, git_buf *), void *data); + #endif diff --git a/src/repository.h b/src/repository.h index 82052158a..5274fc1d0 100644 --- a/src/repository.h +++ b/src/repository.h @@ -19,7 +19,7 @@ #include "refs.h" #include "buffer.h" #include "odb.h" -#include "attr_file.h" +#include "attr.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/" diff --git a/src/status.c b/src/status.c index 64be6ce29..3ead15a87 100644 --- a/src/status.c +++ b/src/status.c @@ -13,6 +13,7 @@ #include "tree.h" #include "git2/status.h" #include "repository.h" +#include "ignore.h" struct status_entry { git_index_time mtime; @@ -21,7 +22,7 @@ struct status_entry { git_oid index_oid; git_oid wt_oid; - unsigned int status_flags:6; + unsigned int status_flags; char path[GIT_FLEX_ARRAY]; /* more */ }; @@ -117,10 +118,30 @@ static int status_entry_update_flags(struct status_entry *e) return GIT_SUCCESS; } +static int status_entry_is_ignorable(struct status_entry *e) +{ + /* don't ignore files that exist in head or index already */ + return (e->status_flags == GIT_STATUS_WT_NEW); +} + +static int status_entry_update_ignore(struct status_entry *e, git_vector *ignores, const char *path) +{ + int error, ignored; + + if ((error = git_ignore__lookup(ignores, path, &ignored)) == GIT_SUCCESS && + ignored) + e->status_flags = + (e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_IGNORED; + + return error; +} + struct status_st { + git_repository *repo; git_vector *vector; git_index *index; git_tree *tree; + git_vector *ignores; int workdir_path_len; git_buf head_tree_relative_path; @@ -210,9 +231,22 @@ static int process_folder( } } - if (full_path != NULL && path_type == GIT_STATUS_PATH_FOLDER) - error = alphasorted_futils_direach(full_path, dirent_cb, st); - else + + if (full_path != NULL && path_type == GIT_STATUS_PATH_FOLDER) { + git_vector ignores = GIT_VECTOR_INIT, *old_ignores; + + if ((error = git_ignore__for_path(st->repo, + full_path->ptr + st->workdir_path_len, &ignores)) == GIT_SUCCESS) + { + old_ignores = st->ignores; + st->ignores = &ignores; + + error = alphasorted_futils_direach(full_path, dirent_cb, st); + + git_ignore__free(st->ignores); + st->ignores = old_ignores; + } + } else error = dirent_cb(st, NULL); if (tree_entry_type == GIT_OBJ_TREE) { @@ -232,6 +266,10 @@ static int store_if_changed(struct status_st *st, struct status_entry *e) if ((error = status_entry_update_flags(e)) < GIT_SUCCESS) return git__throw(error, "Failed to process the file '%s'. It doesn't exist in the workdir, in the HEAD nor in the index", e->path); + if (status_entry_is_ignorable(e) && + (error = status_entry_update_ignore(e, st->ignores, e->path)) < GIT_SUCCESS) + return error; + if (e->status_flags == GIT_STATUS_CURRENT) { git__free(e); return GIT_SUCCESS; @@ -240,7 +278,8 @@ static int store_if_changed(struct status_st *st, struct status_entry *e) return git_vector_insert(st->vector, e); } -static int determine_status(struct status_st *st, +static int determine_status( + struct status_st *st, int in_head, int in_index, int in_workdir, const git_tree_entry *tree_entry, const git_index_entry *index_entry, @@ -274,8 +313,8 @@ static int determine_status(struct status_st *st, } if (in_workdir) - if ((error = status_entry_update_from_workdir(e, full_path->ptr -)) < GIT_SUCCESS) + if ((error = status_entry_update_from_workdir( + e, full_path->ptr)) < GIT_SUCCESS) return error; /* The callee has already set the error message */ return store_if_changed(st, e); @@ -340,7 +379,6 @@ static int dirent_cb(void *state, git_buf *a) int cmpma, cmpmi, cmpai, error; const char *pm, *pa, *pi; const char *m_name, *i_name, *a_name; - struct status_st *st = (struct status_st *)state; path_type = path_type_from(a, st->is_dir); @@ -372,7 +410,8 @@ static int dirent_cb(void *state, git_buf *a) error = git_buf_lasterror(&st->head_tree_relative_path); if (error < GIT_SUCCESS) - return git__rethrow(error, "An error occured while determining the status of '%s'", a->ptr); + return git__rethrow(error, "An error occured while " + "determining the status of '%s'", a->ptr); m_name = st->head_tree_relative_path.ptr; } else @@ -388,7 +427,8 @@ static int dirent_cb(void *state, git_buf *a) pa = ((cmpma >= 0) && (cmpai <= 0)) ? a_name : NULL; pi = ((cmpmi >= 0) && (cmpai >= 0)) ? i_name : NULL; - if((error = determine_status(st, pm != NULL, pi != NULL, pa != NULL, m, entry, a, status_path(pm, pi, pa), path_type)) < GIT_SUCCESS) + if ((error = determine_status(st, pm != NULL, pi != NULL, pa != NULL, + m, entry, a, status_path(pm, pi, pa), path_type)) < GIT_SUCCESS) return git__rethrow(error, "An error occured while determining the status of '%s'", a->ptr); if ((pa != NULL) || (path_type == GIT_STATUS_PATH_FOLDER)) @@ -406,9 +446,12 @@ static int status_cmp(const void *a, const void *b) #define DEFAULT_SIZE 16 -int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsigned int, void *), void *payload) +int git_status_foreach( + git_repository *repo, + int (*callback)(const char *, unsigned int, void *), + void *payload) { - git_vector entries; + git_vector entries, ignores = GIT_VECTOR_INIT; git_index *index = NULL; git_buf temp_path = GIT_BUF_INIT; struct status_st dirent_st = {0}; @@ -434,14 +477,16 @@ int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsig git_vector_init(&entries, DEFAULT_SIZE, status_cmp); - dirent_st.workdir_path_len = strlen(workdir); - dirent_st.tree_position = 0; - dirent_st.index_position = 0; - dirent_st.tree = tree; - dirent_st.index = index; + dirent_st.repo = repo; dirent_st.vector = &entries; + dirent_st.index = index; + dirent_st.tree = tree; + dirent_st.ignores = &ignores; + dirent_st.workdir_path_len = strlen(workdir); git_buf_init(&dirent_st.head_tree_relative_path, 0); dirent_st.head_tree_relative_path_len = 0; + dirent_st.tree_position = 0; + dirent_st.index_position = 0; dirent_st.is_dir = 1; if (git_futils_isdir(workdir)) { @@ -453,6 +498,10 @@ int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsig git_buf_sets(&temp_path, workdir); + error = git_ignore__for_path(repo, "", dirent_st.ignores); + if (error < GIT_SUCCESS) + goto exit; + error = alphasorted_futils_direach( &temp_path, dirent_cb, &dirent_st); @@ -461,7 +510,8 @@ int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsig "Failed to determine statuses. " "An error occured while processing the working directory"); - if ((error == GIT_SUCCESS) && ((error = dirent_cb(&dirent_st, NULL)) < GIT_SUCCESS)) + if ((error == GIT_SUCCESS) && + ((error = dirent_cb(&dirent_st, NULL)) < GIT_SUCCESS)) error = git__rethrow(error, "Failed to determine statuses. " "An error occured while post-processing the HEAD tree and the index"); @@ -483,6 +533,7 @@ exit: git_buf_free(&dirent_st.head_tree_relative_path); git_buf_free(&temp_path); git_vector_free(&entries); + git_vector_free(&ignores); git_tree_free(tree); return error; } @@ -599,6 +650,18 @@ int git_status_file(unsigned int *status_flags, git_repository *repo, const char goto cleanup; } + if (status_entry_is_ignorable(e)) { + git_vector ignores = GIT_VECTOR_INIT; + + if ((error = git_ignore__for_path(repo, path, &ignores)) == GIT_SUCCESS) + error = status_entry_update_ignore(e, &ignores, path); + + git_ignore__free(&ignores); + + if (error < GIT_SUCCESS) + goto cleanup; + } + *status_flags = e->status_flags; cleanup: @@ -615,28 +678,15 @@ cleanup: * */ -struct alphasorted_dirent_info { - int is_dir; - char path[GIT_FLEX_ARRAY]; /* more */ -}; - -static struct alphasorted_dirent_info *alphasorted_dirent_info_new(const git_buf *path) +static char *alphasorted_dirent_info_new(const git_buf *path) { - int is_dir, size; - struct alphasorted_dirent_info *di; + char *di = git__malloc(path->size + 2); + if (!di) + return di; - is_dir = git_futils_isdir(path->ptr) == GIT_SUCCESS ? 1 : 0; - size = sizeof(*di) + path->size + is_dir + 1; - - di = git__calloc(size, 1); - if (di == NULL) - return NULL; - - git_buf_copy_cstr(di->path, path->size + 1, path); - - if (is_dir) { - di->is_dir = 1; + git_buf_copy_cstr(di, path->size + 1, path); + if (git_futils_isdir(path->ptr) == GIT_SUCCESS) { /* * Append a forward slash to the name to force folders * to be ordered in a similar way than in a tree @@ -644,23 +694,16 @@ static struct alphasorted_dirent_info *alphasorted_dirent_info_new(const git_buf * The file "subdir" should appear before the file "subdir.txt" * The folder "subdir" should appear after the file "subdir.txt" */ - di->path[path->size] = '/'; + di[path->size] = '/'; + di[path->size + 1] = '\0'; } return di; } -static int alphasorted_dirent_info_cmp(const void *a, const void *b) -{ - struct alphasorted_dirent_info *stra = (struct alphasorted_dirent_info *)a; - struct alphasorted_dirent_info *strb = (struct alphasorted_dirent_info *)b; - - return strcmp(stra->path, strb->path); -} - static int alphasorted_dirent_cb(void *state, git_buf *full_path) { - struct alphasorted_dirent_info *entry; + char *entry; git_vector *entry_names; entry_names = (git_vector *)state; @@ -682,13 +725,13 @@ static int alphasorted_futils_direach( int (*fn)(void *, git_buf *), void *arg) { - struct alphasorted_dirent_info *entry; + char *entry; git_vector entry_names; unsigned int idx; int error = GIT_SUCCESS; git_buf entry_path = GIT_BUF_INIT; - if (git_vector_init(&entry_names, 16, alphasorted_dirent_info_cmp) < GIT_SUCCESS) + if (git_vector_init(&entry_names, 16, git__strcmp_cb) < GIT_SUCCESS) return GIT_ENOMEM; error = git_futils_direach(path, alphasorted_dirent_cb, &entry_names); @@ -696,17 +739,18 @@ static int alphasorted_futils_direach( git_vector_sort(&entry_names); for (idx = 0; idx < entry_names.length; ++idx) { - entry = (struct alphasorted_dirent_info *)git_vector_get(&entry_names, idx); + entry = (char *)git_vector_get(&entry_names, idx); /* We have to walk the entire vector even if there was an error, * in order to free up memory, but we stop making callbacks after * an error. */ if (error == GIT_SUCCESS) - error = git_buf_sets(&entry_path, entry->path); + error = git_buf_sets(&entry_path, entry); if (error == GIT_SUCCESS) { - ((struct status_st *)arg)->is_dir = entry->is_dir; + ((struct status_st *)arg)->is_dir = + (entry[entry_path.size - 1] == '/'); error = fn(arg, &entry_path); } @@ -717,3 +761,18 @@ static int alphasorted_futils_direach( git_vector_free(&entry_names); return error; } + + +int git_status_should_ignore(git_repository *repo, const char *path, int *ignored) +{ + int error; + git_vector ignores = GIT_VECTOR_INIT; + + if ((error = git_ignore__for_path(repo, path, &ignores)) == GIT_SUCCESS) + error = git_ignore__lookup(&ignores, path, ignored); + + git_ignore__free(&ignores); + + return error; +} + diff --git a/src/util.h b/src/util.h index 2654e2de2..6c929cf0a 100644 --- a/src/util.h +++ b/src/util.h @@ -102,6 +102,13 @@ extern char *git__strtok(char **end, const char *sep); extern void git__strntolower(char *str, size_t len); extern void git__strtolower(char *str); +GIT_INLINE(const char *) git__next_line(const char *s) +{ + while (*s && *s != '\n') s++; + while (*s == '\n' || *s == '\r') s++; + return s; +} + 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 *)); diff --git a/tests-clay/attr/repo.c b/tests-clay/attr/repo.c index f87e7bf55..3e9b9de1b 100644 --- a/tests-clay/attr/repo.c +++ b/tests-clay/attr/repo.c @@ -57,6 +57,7 @@ void test_attr_repo__get_one(void) { "subdir/subdir_test2.txt", "subattr", "yes" }, { "subdir/subdir_test2.txt", "negattr", GIT_ATTR_FALSE }, { "subdir/subdir_test2.txt", "another", "one" }, + { "does-not-exist", "foo", "yes" }, { NULL, NULL, NULL } }, *scan; diff --git a/tests-clay/core/path.c b/tests-clay/core/path.c index bdebfb9c5..1a77a1f15 100644 --- a/tests-clay/core/path.c +++ b/tests-clay/core/path.c @@ -336,3 +336,55 @@ void test_core_path__10_fromurl(void) check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); } + +typedef struct { + int expect_idx; + char **expect; +} check_walkup_info; + +static int check_one_walkup_step(void *ref, git_buf *path) +{ + check_walkup_info *info = (check_walkup_info *)ref; + cl_assert(info->expect[info->expect_idx] != NULL); + cl_assert_strequal(info->expect[info->expect_idx], path->ptr); + info->expect_idx++; + return GIT_SUCCESS; +} + +void test_core_path__11_walkup(void) +{ + git_buf p = GIT_BUF_INIT; + char *expect[] = { + "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + "this is a path", NULL, + "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, + NULL + }; + char *root[] = { NULL, NULL, "/", "", "/a/b", "/a/b/", NULL, NULL, NULL }; + int i, j; + check_walkup_info info; + + info.expect = expect; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_buf_sets(&p, expect[i]); + + info.expect_idx = i; + cl_git_pass( + git_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + cl_assert_strequal(p.ptr, expect[i]); + + /* skip to next run of expectations */ + while (expect[i] != NULL) i++; + } + + git_buf_free(&p); +} diff --git a/tests-clay/status/status_data.h b/tests-clay/status/status_data.h index ea903c602..1a68648f4 100644 --- a/tests-clay/status/status_data.h +++ b/tests-clay/status/status_data.h @@ -10,6 +10,7 @@ struct status_entry_counts { static const char *entry_paths0[] = { "file_deleted", + "ignored_file", "modified_file", "new_file", "staged_changes", @@ -28,6 +29,7 @@ static const char *entry_paths0[] = { static const unsigned int entry_statuses0[] = { GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_MODIFIED, @@ -44,5 +46,5 @@ static const unsigned int entry_statuses0[] = { GIT_STATUS_WT_NEW, }; -static const size_t entry_count0 = 14; +static const size_t entry_count0 = 15; diff --git a/tests-clay/status/worktree.c b/tests-clay/status/worktree.c index 1e8a5ddbc..2183649f2 100644 --- a/tests-clay/status/worktree.c +++ b/tests-clay/status/worktree.c @@ -1,5 +1,6 @@ #include "clay_libgit2.h" #include "fileops.h" +#include "ignore.h" #include "status_data.h" @@ -122,3 +123,32 @@ void test_status_worktree__empty_repository(void) git_status_foreach(_repository, cb_status__count, &count); cl_assert(count == 0); } + +void test_status_worktree__single_file(void) +{ + int i; + unsigned int status_flags; + + for (i = 0; i < (int)entry_count0; i++) { + cl_git_pass( + git_status_file(&status_flags, _repository, entry_paths0[i]) + ); + cl_assert(entry_statuses0[i] == status_flags); + } +} + +void test_status_worktree__ignores(void) +{ + int i, ignored; + + for (i = 0; i < (int)entry_count0; i++) { + cl_git_pass(git_status_should_ignore(_repository, entry_paths0[i], &ignored)); + cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED)); + } + + cl_git_pass(git_status_should_ignore(_repository, "nonexistent_file", &ignored)); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(_repository, "ignored_nonexistent_file", &ignored)); + cl_assert(ignored); +} diff --git a/tests/resources/attr/gitattributes b/tests/resources/attr/gitattributes index 2b40c5aca..c0c2a56d0 100644 Binary files a/tests/resources/attr/gitattributes and b/tests/resources/attr/gitattributes differ diff --git a/tests/resources/status/.gitted/info/exclude b/tests/resources/status/.gitted/info/exclude index a5196d1be..0c4042a6a 100644 Binary files a/tests/resources/status/.gitted/info/exclude and b/tests/resources/status/.gitted/info/exclude differ diff --git a/tests/resources/status/ignored_file b/tests/resources/status/ignored_file new file mode 100644 index 000000000..6a79f808a Binary files /dev/null and b/tests/resources/status/ignored_file differ diff --git a/tests/t18-status.c b/tests/t18-status.c index 3799dc2ab..270aa7b46 100644 --- a/tests/t18-status.c +++ b/tests/t18-status.c @@ -64,6 +64,7 @@ END_TEST static const char *entry_paths0[] = { "file_deleted", + "ignored_file", "modified_file", "new_file", "staged_changes", @@ -82,6 +83,7 @@ static const char *entry_paths0[] = { static const unsigned int entry_statuses0[] = { GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_MODIFIED, @@ -98,7 +100,7 @@ static const unsigned int entry_statuses0[] = { GIT_STATUS_WT_NEW, }; -#define ENTRY_COUNT0 14 +#define ENTRY_COUNT0 15 struct status_entry_counts { int wrong_status_flags_count; @@ -185,6 +187,7 @@ END_TEST static const char *entry_paths2[] = { "current_file", "file_deleted", + "ignored_file", "modified_file", "staged_changes", "staged_changes_file_deleted", @@ -202,6 +205,7 @@ static const char *entry_paths2[] = { static const unsigned int entry_statuses2[] = { GIT_STATUS_WT_DELETED, GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, @@ -216,7 +220,7 @@ static const unsigned int entry_statuses2[] = { GIT_STATUS_WT_DELETED, }; -#define ENTRY_COUNT2 14 +#define ENTRY_COUNT2 15 BEGIN_TEST(statuscb2, "test retrieving status for a purged worktree of an valid repository") git_repository *repo; @@ -261,6 +265,7 @@ static const char *entry_paths3[] = { "current_file/modified_file", "current_file/new_file", "file_deleted", + "ignored_file", "modified_file", "new_file", "staged_changes", @@ -286,6 +291,7 @@ static const unsigned int entry_statuses3[] = { GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_MODIFIED, @@ -302,7 +308,7 @@ static const unsigned int entry_statuses3[] = { GIT_STATUS_WT_DELETED, }; -#define ENTRY_COUNT3 22 +#define ENTRY_COUNT3 23 BEGIN_TEST(statuscb3, "test retrieving status for a worktree where a file and a subdir have been renamed and some files have been added") git_repository *repo;