diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/attr.c | 52 | ||||
-rw-r--r-- | src/attr.h | 30 | ||||
-rw-r--r-- | src/attr_file.c | 106 | ||||
-rw-r--r-- | src/attr_file.h | 39 | ||||
-rw-r--r-- | src/buffer.c | 8 | ||||
-rw-r--r-- | src/buffer.h | 9 | ||||
-rw-r--r-- | src/fileops.c | 12 | ||||
-rw-r--r-- | src/fileops.h | 8 | ||||
-rw-r--r-- | src/ignore.c | 148 | ||||
-rw-r--r-- | src/ignore.h | 17 | ||||
-rw-r--r-- | src/path.h | 23 | ||||
-rw-r--r-- | src/repository.h | 2 | ||||
-rw-r--r-- | src/status.c | 99 | ||||
-rw-r--r-- | src/util.h | 7 |
14 files changed, 440 insertions, 120 deletions
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_assignment> */ +} 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; + + 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 git_hashtable_insert( - repo->attrcache.macros, macro->match.pattern, macro); + 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; @@ -23,13 +24,18 @@ typedef struct { } git_attr_fnmatch; typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* vector of <git_attr_assignment*> */ +} git_attr_rule; + +typedef struct { git_refcount unused; const char *name; unsigned long name_hash; } 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_assignment*> */ -} git_attr_rule; - -typedef struct { - char *path; /* cache the path this was loaded from */ - git_vector rules; /* vector of <git_attr_rule*> */ + char *path; /* cache the path this was loaded from */ + git_vector rules; /* vector of <rule*> or <fnmatch*> */ } 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_assignment> */ -} 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 @@ -102,6 +102,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. * @return On success, an open file descriptor, else an error code < 0. 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 *)); |