From df743c7d3a04553ffc04ae7cbc64fb300e7f61d2 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 9 Jan 2012 15:37:19 -0800 Subject: [PATCH 1/7] Initial implementation of gitignore support Adds support for .gitignore files to git_status_foreach() and git_status_file(). This includes refactoring the gitattributes code to share logic where possible. The GIT_STATUS_IGNORED flag will now be passed in for files that are ignored (provided they are not already in the index or the head of repo). --- include/git2/status.h | 10 +- src/attr.c | 52 +++---- src/attr.h | 30 ++++ src/attr_file.c | 106 +++++++------- src/attr_file.h | 39 +++--- src/buffer.c | 8 +- src/buffer.h | 9 +- src/fileops.c | 12 ++ src/fileops.h | 8 ++ src/ignore.c | 148 ++++++++++++++++++++ src/ignore.h | 17 +++ src/path.h | 23 +++ src/repository.h | 2 +- src/status.c | 99 ++++++++++--- src/util.h | 7 + tests-clay/core/path.c | 36 +++++ tests-clay/status/status_data.h | 4 +- tests-clay/status/worktree.c | 13 ++ tests/resources/status/.gitted/info/exclude | Bin 240 -> 250 bytes tests/resources/status/ignored_file | Bin 0 -> 13 bytes 20 files changed, 497 insertions(+), 126 deletions(-) create mode 100644 src/attr.h create mode 100644 src/ignore.c create mode 100644 src/ignore.h create mode 100644 tests/resources/status/ignored_file diff --git a/include/git2/status.h b/include/git2/status.h index 42b2dd197..0f2b63de4 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. diff --git a/src/attr.c b/src/attr.c index 679380bba..0c08fc0cf 100644 --- a/src/attr.c +++ b/src/attr.c @@ -6,12 +6,11 @@ #define GIT_ATTR_FILE_INREPO "info/attributes" #define GIT_ATTR_FILE ".gitattributes" #define GIT_ATTR_FILE_SYSTEM "gitattributes" +#define GIT_ATTR_CONFIG "core.attributesfile" 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 +185,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 +214,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 +227,20 @@ 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; + if (base != NULL && + (error = git_buf_joinpath(&path, base, filename)) < 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); + if (file == NULL && git_futils_exists(path.ptr) == GIT_SUCCESS) { + error = (*loader)(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); + 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 +253,8 @@ cleanup: return error; } +#define push_attrs(R,S,B,F) \ + git_attr_cache__push_file((R),(S),(B),(F),git_attr_file__from_file) static int collect_attr_files( git_repository *repo, const char *path, git_vector *files) @@ -265,22 +264,15 @@ static int collect_attr_files( git_config *cfg; const char *workdir = git_repository_workdir(repo); - 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 @@ -311,7 +303,7 @@ static int collect_attr_files( if (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 +329,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 +359,6 @@ static int attr_cache_init(git_repository *repo) return error; } - void git_attr_cache_flush( git_repository *repo) { @@ -398,3 +389,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..86836b56f 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -15,6 +15,7 @@ #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 +23,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 +35,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 +43,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 +53,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 +62,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 +74,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 +83,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 +108,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..b6854258b 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'; } @@ -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..f481bb01d 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -534,3 +534,15 @@ 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) +{ + if (git_path_prettify(dir, path, base) == GIT_SUCCESS) { + /* call dirname if this is not a directory */ + if (git_futils_isdir(dir->ptr) != GIT_SUCCESS) + git_path_dirname_r(dir, dir->ptr); + + git_path_to_dir(dir); + } + + return git_buf_lasterror(dir); +} diff --git a/src/fileops.h b/src/fileops.h index 31f3e6a91..f3f09ec9f 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -101,6 +101,14 @@ 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. + */ +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..8bf22e34a --- /dev/null +++ b/src/ignore.c @@ -0,0 +1,148 @@ +#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); + + 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); + + if (error != GIT_SUCCESS) { + git__rethrow(error, "Could not open ignore file '%s'", path); + git__free(match); + 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) + +int git_ignore__for_path(git_repository *repo, const char *path, git_vector *stack) +{ + int error = GIT_SUCCESS; + git_buf dir = GIT_BUF_INIT, scan; + git_config *cfg; + const char *workdir = git_repository_workdir(repo); + + 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 */ + git_path_walk_up(&dir, &scan, workdir, { + error = push_ignore(repo, stack, scan.ptr, GIT_IGNORE_FILE); + if (error < GIT_SUCCESS) break; + }); + if (error < 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 (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.h b/src/path.h index c308c5bd4..ceb3bb533 100644 --- a/src/path.h +++ b/src/path.h @@ -77,4 +77,27 @@ 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); +/* + * Use as: + * + * git_path_walk_up( + * git_buf *path, git_buf *iterator, const char *root_path, + * ... CALLBACK CODE ...) + * + * to invoke callback directory by directory up the path until the root_path + * is reached (inclusive of a final call at the root_path). If root path is + * NULL or the path is not contained in the root_path, then the callback + * code will be invoked just once on input path. + */ +#define git_path_walk_up(B,IB,ROOT,CODE) do { \ + ssize_t _stop = ((ROOT) && git__prefixcmp((B)->ptr, (ROOT))) ? (ssize_t)strlen(ROOT) : (B)->size; \ + ssize_t _scan = (B)->size; char _oldc = '\0'; \ + (IB)->ptr = (B)->ptr; (IB)->size = (B)->size; \ + while (_scan >= _stop) { \ + CODE; \ + (IB)->ptr[_scan] = _oldc; \ + _scan = git_buf_rfind_next((IB), '/'); \ + if (_scan >= 0) { _scan++; _oldc = (IB)->ptr[_scan]; (IB)->size = _scan; (IB)->ptr[_scan] = '\0'; } \ + } (IB)->ptr[_scan] = _oldc; } while (0) + #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..a92693851 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: diff --git a/src/util.h b/src/util.h index 2654e2de2..bd76a263e 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++; + 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/core/path.c b/tests-clay/core/path.c index bdebfb9c5..712ceb4e0 100644 --- a/tests-clay/core/path.c +++ b/tests-clay/core/path.c @@ -336,3 +336,39 @@ 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); } + +void test_core_path__11_walkup(void) +{ + git_buf p = GIT_BUF_INIT, iter; + 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; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + int cb_count = 0; + + git_buf_sets(&p, expect[i]); + + git_path_walk_up(&p, &iter, root[j], { + cl_assert(expect[i + cb_count] != NULL); + cl_assert_strequal(expect[i + cb_count], iter.ptr); + cb_count++; }); + + 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..15cbb2828 100644 --- a/tests-clay/status/worktree.c +++ b/tests-clay/status/worktree.c @@ -122,3 +122,16 @@ 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); + } +} diff --git a/tests/resources/status/.gitted/info/exclude b/tests/resources/status/.gitted/info/exclude index a5196d1be8fb59edf8062bef36d3a602e0812139..0c4042a6a91fcea1c692c7d573638549005486c4 100644 GIT binary patch delta 17 Ycmeys_=|DE2d>QYy!@in6fG_;07k|Jod5s; delta 6 Ncmeyx_ Date: Wed, 11 Jan 2012 15:25:13 -0800 Subject: [PATCH 2/7] Fix up status tests --- tests/t18-status.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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; From 6a67a812c224878483659c6f25ca21573d1c309b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 11 Jan 2012 16:01:48 -0800 Subject: [PATCH 3/7] Allow ignores (and attribs) for nonexistent files This fixes issue 532 that attributes (and gitignores) could not be checked for files that don't exist. It should be possible to query such things regardless of the existence of the file. --- src/fileops.c | 24 ++++++++++++++++++------ src/fileops.h | 6 ++++-- src/ignore.c | 14 ++++++++++++++ src/ignore.h | 2 ++ tests-clay/attr/repo.c | 1 + tests-clay/status/worktree.c | 17 +++++++++++++++++ tests/resources/attr/gitattributes | Bin 602 -> 625 bytes 7 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index f481bb01d..1d991b36d 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -536,13 +536,25 @@ int git_futils_find_system_file(git_buf *path, const char *filename) int git_futils_dir_for_path(git_buf *dir, const char *path, const char *base) { - if (git_path_prettify(dir, path, base) == GIT_SUCCESS) { - /* call dirname if this is not a directory */ - if (git_futils_isdir(dir->ptr) != GIT_SUCCESS) - git_path_dirname_r(dir, dir->ptr); + int error = GIT_SUCCESS; - git_path_to_dir(dir); + 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); } - return git_buf_lasterror(dir); + /* call dirname if this is not a directory */ + if (error == GIT_SUCCESS && git_futils_isdir(dir->ptr) != GIT_SUCCESS) + error = git_path_dirname_r(dir, dir->ptr); + + if (error == GIT_SUCCESS) + error = git_path_to_dir(dir); + + return error; } diff --git a/src/fileops.h b/src/fileops.h index f3f09ec9f..91903a731 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -104,8 +104,10 @@ 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 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); diff --git a/src/ignore.c b/src/ignore.c index 8bf22e34a..7639b7ba9 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -146,3 +146,17 @@ found: return error; } + +int git_ignore_is_ignored(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/ignore.h b/src/ignore.h index 2954445b5..a6e6a1a34 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -14,4 +14,6 @@ extern int git_ignore__for_path(git_repository *repo, const char *path, git_vect extern void git_ignore__free(git_vector *stack); extern int git_ignore__lookup(git_vector *stack, const char *path, int *ignored); +extern int git_ignore_is_ignored(git_repository *repo, const char *path, int *ignored); + #endif 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/status/worktree.c b/tests-clay/status/worktree.c index 15cbb2828..af6f005a7 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" @@ -135,3 +136,19 @@ void test_status_worktree__single_file(void) 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_ignore_is_ignored(_repository, entry_paths0[i], &ignored)); + cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED)); + } + + cl_git_pass(git_ignore_is_ignored(_repository, "nonexistent_file", &ignored)); + cl_assert(!ignored); + + cl_git_pass(git_ignore_is_ignored(_repository, "ignored_nonexistent_file", &ignored)); + cl_assert(ignored); +} diff --git a/tests/resources/attr/gitattributes b/tests/resources/attr/gitattributes index 2b40c5aca159b04ea8d20ffe36cdf8b09369b14a..c0c2a56d0bfc4c576b8b6c1a4b7e905bb812c3c9 100644 GIT binary patch delta 33 ocmcb`@{wgitXN8ZYO!u!eu-{sMP_k{LRx;lZDnfl#+ZMM0N Date: Wed, 11 Jan 2012 17:28:25 -0800 Subject: [PATCH 4/7] Fix bug in dir_for_path The last checkin accidentally broke dir_for_path by propogating the dirname return code even when there was no error. --- src/fileops.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fileops.c b/src/fileops.c index 1d991b36d..3412a47e2 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -551,7 +551,8 @@ int git_futils_dir_for_path(git_buf *dir, const char *path, const char *base) /* call dirname if this is not a directory */ if (error == GIT_SUCCESS && git_futils_isdir(dir->ptr) != GIT_SUCCESS) - error = git_path_dirname_r(dir, dir->ptr); + 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); From 0cfcff5daac50d5a4ba41d5125b108cdfceed832 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 11 Jan 2012 20:41:55 -0800 Subject: [PATCH 5/7] Convert git_path_walk_up to regular function This gets rid of the crazy macro version of git_path_walk_up and makes it into a normal function that takes a callback parameter. This turned out not to be too messy. --- src/attr.c | 29 +++++++++++++++-------------- src/ignore.c | 22 ++++++++++++++++------ src/path.c | 42 ++++++++++++++++++++++++++++++++++++++++++ src/path.h | 31 +++++++++++-------------------- tests-clay/core/path.c | 28 ++++++++++++++++++++++------ 5 files changed, 106 insertions(+), 46 deletions(-) diff --git a/src/attr.c b/src/attr.c index 0c08fc0cf..06a6601b4 100644 --- a/src/attr.c +++ b/src/attr.c @@ -256,6 +256,17 @@ cleanup: #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) { @@ -263,6 +274,7 @@ 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 = git_attr_cache__init(repo)) < GIT_SUCCESS) goto cleanup; @@ -284,20 +296,9 @@ 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; diff --git a/src/ignore.c b/src/ignore.c index 7639b7ba9..fa71d4941 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -64,12 +64,24 @@ static int load_ignore_file( #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, scan; + 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; @@ -82,11 +94,9 @@ int git_ignore__for_path(git_repository *repo, const char *path, git_vector *sta goto cleanup; /* load .gitignore up the path */ - git_path_walk_up(&dir, &scan, workdir, { - error = push_ignore(repo, stack, scan.ptr, GIT_IGNORE_FILE); - if (error < GIT_SUCCESS) break; - }); - if (error < GIT_SUCCESS) + 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 */ diff --git a/src/path.c b/src/path.c index f9663b7e5..4888123bf 100644 --- a/src/path.c +++ b/src/path.c @@ -305,3 +305,45 @@ 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; + + 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'; + } + } + + iter.ptr[scan] = oldc; + + return error; +} diff --git a/src/path.h b/src/path.h index ceb3bb533..e59c19ad9 100644 --- a/src/path.h +++ b/src/path.h @@ -77,27 +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); -/* - * Use as: +/** + * Invoke callback directory by directory up the path until the ceiling + * is reached (inclusive of a final call at the root_path). * - * git_path_walk_up( - * git_buf *path, git_buf *iterator, const char *root_path, - * ... CALLBACK CODE ...) - * - * to invoke callback directory by directory up the path until the root_path - * is reached (inclusive of a final call at the root_path). If root path is - * NULL or the path is not contained in the root_path, then the callback - * code will be invoked just once on input 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. */ -#define git_path_walk_up(B,IB,ROOT,CODE) do { \ - ssize_t _stop = ((ROOT) && git__prefixcmp((B)->ptr, (ROOT))) ? (ssize_t)strlen(ROOT) : (B)->size; \ - ssize_t _scan = (B)->size; char _oldc = '\0'; \ - (IB)->ptr = (B)->ptr; (IB)->size = (B)->size; \ - while (_scan >= _stop) { \ - CODE; \ - (IB)->ptr[_scan] = _oldc; \ - _scan = git_buf_rfind_next((IB), '/'); \ - if (_scan >= 0) { _scan++; _oldc = (IB)->ptr[_scan]; (IB)->size = _scan; (IB)->ptr[_scan] = '\0'; } \ - } (IB)->ptr[_scan] = _oldc; } while (0) +extern int git_path_walk_up( + git_buf *path, const char *ceiling, + int (*cb)(void *data, git_buf *), void *data); #endif diff --git a/tests-clay/core/path.c b/tests-clay/core/path.c index 712ceb4e0..1a77a1f15 100644 --- a/tests-clay/core/path.c +++ b/tests-clay/core/path.c @@ -337,9 +337,23 @@ void test_core_path__10_fromurl(void) 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, iter; + 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, @@ -353,16 +367,18 @@ void test_core_path__11_walkup(void) }; 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++) { - int cb_count = 0; git_buf_sets(&p, expect[i]); - git_path_walk_up(&p, &iter, root[j], { - cl_assert(expect[i + cb_count] != NULL); - cl_assert_strequal(expect[i + cb_count], iter.ptr); - cb_count++; }); + 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]); From 1dbcc9fc4e6f5264d5bb46f6d7f744eb4a4063e4 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 11 Jan 2012 21:07:16 -0800 Subject: [PATCH 6/7] Fix several memory issues This contains fixes for several issues discovered by MSVC and by valgrind, including some bad data access, some memory leakage (in where certain files were not being successfully added to the cache), and some code simplification. --- src/attr.c | 14 ++++++++------ src/buffer.c | 2 +- src/ignore.c | 4 +++- src/path.c | 4 +++- src/status.c | 49 +++++++++++++++---------------------------------- 5 files changed, 30 insertions(+), 43 deletions(-) diff --git a/src/attr.c b/src/attr.c index 06a6601b4..cbc2a5bf5 100644 --- a/src/attr.c +++ b/src/attr.c @@ -227,14 +227,16 @@ int git_attr_cache__push_file( git_attr_file *file; int add_to_cache = 0; - if (base != NULL && - (error = git_buf_joinpath(&path, base, filename)) < 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 && git_futils_exists(path.ptr) == GIT_SUCCESS) { - error = (*loader)(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); } diff --git a/src/buffer.c b/src/buffer.c index b6854258b..c57e4aa1b 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -181,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'; diff --git a/src/ignore.c b/src/ignore.c index fa71d4941..cdc3edab6 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -23,6 +23,8 @@ static int load_ignore_file( 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) { @@ -49,10 +51,10 @@ static int load_ignore_file( } git_futils_freebuffer(&fbuf); + git__free(match); if (error != GIT_SUCCESS) { git__rethrow(error, "Could not open ignore file '%s'", path); - git__free(match); git_attr_file__free(ignores); } else { *out = ignores; diff --git a/src/path.c b/src/path.c index 4888123bf..03ebfe090 100644 --- a/src/path.c +++ b/src/path.c @@ -329,6 +329,7 @@ int git_path_walk_up( iter.ptr = path->ptr; iter.size = path->size; + iter.asize = path->asize; while (scan >= stop) { if ((error = cb(data, &iter)) < GIT_SUCCESS) @@ -343,7 +344,8 @@ int git_path_walk_up( } } - iter.ptr[scan] = oldc; + if (scan >= 0) + iter.ptr[scan] = oldc; return error; } diff --git a/src/status.c b/src/status.c index a92693851..72ee7b049 100644 --- a/src/status.c +++ b/src/status.c @@ -678,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 @@ -707,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; @@ -745,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); @@ -759,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); } From cfbc880d8a407bcd2074dda4221d337daf72195c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 16 Jan 2012 15:16:44 -0800 Subject: [PATCH 7/7] Patch cleanup for merge After reviewing the gitignore support with Vicent, we came up with a list of minor cleanups to prepare for merge, including: * checking git_repository_config error returns * renaming git_ignore_is_ignored and moving to status.h * fixing next_line skipping to include \r skips * commenting on where ignores are and are not included --- include/git2/index.h | 4 ++++ include/git2/status.h | 16 ++++++++++++++++ src/attr.c | 7 +------ src/attr_file.h | 5 +++++ src/ignore.c | 17 +---------------- src/ignore.h | 2 -- src/status.c | 15 +++++++++++++++ src/util.h | 2 +- tests-clay/status/worktree.c | 6 +++--- 9 files changed, 46 insertions(+), 28 deletions(-) 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 0f2b63de4..c0f38c508 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -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 cbc2a5bf5..dc42379ff 100644 --- a/src/attr.c +++ b/src/attr.c @@ -3,11 +3,6 @@ #include "config.h" #include -#define GIT_ATTR_FILE_INREPO "info/attributes" -#define GIT_ATTR_FILE ".gitattributes" -#define GIT_ATTR_FILE_SYSTEM "gitattributes" -#define GIT_ATTR_CONFIG "core.attributesfile" - static int collect_attr_files( git_repository *repo, const char *path, git_vector *files); @@ -304,7 +299,7 @@ static int collect_attr_files( 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, GIT_ATTR_CONFIG, &core_attribs); git_clearerror(); /* don't care if attributesfile is not set */ diff --git a/src/attr_file.h b/src/attr_file.h index 86836b56f..7190c4c7b 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -11,6 +11,11 @@ #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) diff --git a/src/ignore.c b/src/ignore.c index cdc3edab6..1040574d7 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -106,7 +106,7 @@ int git_ignore__for_path(git_repository *repo, const char *path, git_vector *sta goto cleanup; /* load core.excludesfile */ - if (git_repository_config(&cfg, repo) == GIT_SUCCESS) { + 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) @@ -157,18 +157,3 @@ found: return error; } - - -int git_ignore_is_ignored(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/ignore.h b/src/ignore.h index a6e6a1a34..2954445b5 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -14,6 +14,4 @@ extern int git_ignore__for_path(git_repository *repo, const char *path, git_vect extern void git_ignore__free(git_vector *stack); extern int git_ignore__lookup(git_vector *stack, const char *path, int *ignored); -extern int git_ignore_is_ignored(git_repository *repo, const char *path, int *ignored); - #endif diff --git a/src/status.c b/src/status.c index 72ee7b049..3ead15a87 100644 --- a/src/status.c +++ b/src/status.c @@ -761,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 bd76a263e..6c929cf0a 100644 --- a/src/util.h +++ b/src/util.h @@ -105,7 +105,7 @@ 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++; + while (*s == '\n' || *s == '\r') s++; return s; } diff --git a/tests-clay/status/worktree.c b/tests-clay/status/worktree.c index af6f005a7..2183649f2 100644 --- a/tests-clay/status/worktree.c +++ b/tests-clay/status/worktree.c @@ -142,13 +142,13 @@ void test_status_worktree__ignores(void) int i, ignored; for (i = 0; i < (int)entry_count0; i++) { - cl_git_pass(git_ignore_is_ignored(_repository, entry_paths0[i], &ignored)); + 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_ignore_is_ignored(_repository, "nonexistent_file", &ignored)); + cl_git_pass(git_status_should_ignore(_repository, "nonexistent_file", &ignored)); cl_assert(!ignored); - cl_git_pass(git_ignore_is_ignored(_repository, "ignored_nonexistent_file", &ignored)); + cl_git_pass(git_status_should_ignore(_repository, "ignored_nonexistent_file", &ignored)); cl_assert(ignored); }