diff options
| author | Russell Belfer <arrbee@arrbee.com> | 2012-01-16 15:34:35 -0800 | 
|---|---|---|
| committer | Russell Belfer <arrbee@arrbee.com> | 2012-01-16 15:34:35 -0800 | 
| commit | 6e03b12f5715cb3f5cb5c8be6512e041cdf44a05 (patch) | |
| tree | 8da5a75da0e2013f34bac773fe201b2a989605f5 /src | |
| parent | d9e5430e5a7bd5d2de7c4fee2f1afbd52ec5aa2f (diff) | |
| parent | cfbc880d8a407bcd2074dda4221d337daf72195c (diff) | |
| download | libgit2-6e03b12f5715cb3f5cb5c8be6512e041cdf44a05.tar.gz | |
Merge pull request #531 from arrbee/gitignore
Initial implementation of gitignore support
git_status_foreach() and git_status_file() will now be
gitignore aware.
Diffstat (limited to 'src')
| -rw-r--r-- | src/attr.c | 90 | ||||
| -rw-r--r-- | src/attr.h | 30 | ||||
| -rw-r--r-- | src/attr_file.c | 106 | ||||
| -rw-r--r-- | src/attr_file.h | 44 | ||||
| -rw-r--r-- | src/buffer.c | 10 | ||||
| -rw-r--r-- | src/buffer.h | 9 | ||||
| -rw-r--r-- | src/fileops.c | 25 | ||||
| -rw-r--r-- | src/fileops.h | 10 | ||||
| -rw-r--r-- | src/ignore.c | 159 | ||||
| -rw-r--r-- | src/ignore.h | 17 | ||||
| -rw-r--r-- | src/path.c | 44 | ||||
| -rw-r--r-- | src/path.h | 14 | ||||
| -rw-r--r-- | src/repository.h | 2 | ||||
| -rw-r--r-- | src/status.c | 163 | ||||
| -rw-r--r-- | src/util.h | 7 | 
15 files changed, 555 insertions, 175 deletions
diff --git a/src/attr.c b/src/attr.c index 679380bba..dc42379ff 100644 --- a/src/attr.c +++ b/src/attr.c @@ -3,15 +3,9 @@  #include "config.h"  #include <ctype.h> -#define GIT_ATTR_FILE_INREPO	"info/attributes" -#define GIT_ATTR_FILE			".gitattributes" -#define GIT_ATTR_FILE_SYSTEM	"gitattributes" -  static int collect_attr_files(  	git_repository *repo, const char *path, git_vector *files); -static int attr_cache_init(git_repository *repo); -  int git_attr_get(      git_repository *repo, const char *pathname, @@ -186,7 +180,7 @@ int git_attr_add_macro(  	int error;  	git_attr_rule *macro = NULL; -	if ((error = attr_cache_init(repo)) < GIT_SUCCESS) +	if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS)  		return error;  	macro = git__calloc(1, sizeof(git_attr_rule)); @@ -215,11 +209,12 @@ int git_attr_add_macro(  /* add git_attr_file to vector of files, loading if needed */ -static int push_attrs( +int git_attr_cache__push_file(  	git_repository *repo, -	git_vector     *files, +	git_vector     *stack,  	const char     *base, -	const char     *filename) +	const char     *filename, +	int (*loader)(git_repository *, const char *, git_attr_file **))  {  	int error = GIT_SUCCESS;  	git_attr_cache *cache = &repo->attrcache; @@ -227,23 +222,22 @@ static int push_attrs(  	git_attr_file *file;  	int add_to_cache = 0; -	if ((error = git_path_prettify(&path, filename, base)) < GIT_SUCCESS) { -		if (error == GIT_EOSERR) -			/* file was not found -- ignore error */ -			error = GIT_SUCCESS; -		goto cleanup; +	if (base != NULL) { +		if ((error = git_buf_joinpath(&path, base, filename)) < GIT_SUCCESS) +			goto cleanup; +		filename = path.ptr;  	}  	/* either get attr_file from cache or read from disk */ -	file = git_hashtable_lookup(cache->files, path.ptr); -	if (file == NULL) { -		error = git_attr_file__from_file(repo, path.ptr, &file); +	file = git_hashtable_lookup(cache->files, filename); +	if (file == NULL && git_futils_exists(filename) == GIT_SUCCESS) { +		error = (*loader)(repo, filename, &file);  		add_to_cache = (error == GIT_SUCCESS);  	}  	if (file != NULL) {  		/* add file to vector, if we found it */ -		error = git_vector_insert(files, file); +		error = git_vector_insert(stack, file);  		/* add file to cache, if it is new */  		/* do this after above step b/c it is not critical */ @@ -256,6 +250,19 @@ cleanup:  	return error;  } +#define push_attrs(R,S,B,F) \ +	git_attr_cache__push_file((R),(S),(B),(F),git_attr_file__from_file) + +typedef struct { +	git_repository *repo; +	git_vector *files; +} attr_walk_up_info; + +static int push_one_attr(void *ref, git_buf *path) +{ +	attr_walk_up_info *info = (attr_walk_up_info *)ref; +	return push_attrs(info->repo, info->files, path->ptr, GIT_ATTR_FILE); +}  static int collect_attr_files(  	git_repository *repo, const char *path, git_vector *files) @@ -264,23 +271,17 @@ static int collect_attr_files(  	git_buf dir = GIT_BUF_INIT;  	git_config *cfg;  	const char *workdir = git_repository_workdir(repo); +	attr_walk_up_info info; -	if ((error = attr_cache_init(repo)) < GIT_SUCCESS) +	if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS)  		goto cleanup;  	if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS)  		goto cleanup; -	if ((error = git_path_prettify(&dir, path, workdir)) < GIT_SUCCESS) +	if ((error = git_futils_dir_for_path(&dir, path, workdir)) < GIT_SUCCESS)  		goto cleanup; -	if (git_futils_isdir(dir.ptr) != GIT_SUCCESS) { -		git_path_dirname_r(&dir, dir.ptr); -		git_path_to_dir(&dir); -		if ((error = git_buf_lasterror(&dir)) < GIT_SUCCESS) -			goto cleanup; -	} -  	/* in precendence order highest to lowest:  	 * - $GIT_DIR/info/attributes  	 * - path components with .gitattributes @@ -292,26 +293,15 @@ static int collect_attr_files(  	if (error < GIT_SUCCESS)  		goto cleanup; -	if (workdir && git__prefixcmp(dir.ptr, workdir) == 0) { -		ssize_t rootlen = (ssize_t)strlen(workdir); - -		do { -			error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE); -			if (error == GIT_SUCCESS) { -				git_path_dirname_r(&dir, dir.ptr); -				git_path_to_dir(&dir); -				error = git_buf_lasterror(&dir); -			} -		} while (!error && dir.size >= rootlen); -	} else { -		error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE); -	} +	info.repo = repo; +	info.files = files; +	error = git_path_walk_up(&dir, workdir, push_one_attr, &info);  	if (error < GIT_SUCCESS)  		goto cleanup; -	if (git_repository_config(&cfg, repo) == GIT_SUCCESS) { +	if ((error = git_repository_config(&cfg, repo)) == GIT_SUCCESS) {  		const char *core_attribs = NULL; -		git_config_get_string(cfg, "core.attributesfile", &core_attribs); +		git_config_get_string(cfg, GIT_ATTR_CONFIG, &core_attribs);  		git_clearerror(); /* don't care if attributesfile is not set */  		if (core_attribs)  			error = push_attrs(repo, files, NULL, core_attribs); @@ -337,7 +327,7 @@ static int collect_attr_files(  } -static int attr_cache_init(git_repository *repo) +int git_attr_cache__init(git_repository *repo)  {  	int error = GIT_SUCCESS;  	git_attr_cache *cache = &repo->attrcache; @@ -367,7 +357,6 @@ static int attr_cache_init(git_repository *repo)  	return error;  } -  void git_attr_cache_flush(  	git_repository *repo)  { @@ -398,3 +387,12 @@ void git_attr_cache_flush(  	repo->attrcache.initialized = 0;  } + +int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) +{ +	if (macro->assigns.length == 0) +		return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values"); + +	return git_hashtable_insert( +		repo->attrcache.macros, macro->match.pattern, macro); +} diff --git a/src/attr.h b/src/attr.h new file mode 100644 index 000000000..5edff30d1 --- /dev/null +++ b/src/attr.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009-2011 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attr_h__ +#define INCLUDE_attr_h__ + +#include "attr_file.h" + +typedef struct { +	int initialized; +	git_hashtable *files;	/* hash path to git_attr_file of rules */ +	git_hashtable *macros;	/* hash name to vector<git_attr_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..7190c4c7b 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -11,10 +11,16 @@  #include "vector.h"  #include "hashtable.h" +#define GIT_ATTR_FILE			".gitattributes" +#define GIT_ATTR_FILE_INREPO	"info/attributes" +#define GIT_ATTR_FILE_SYSTEM	"gitattributes" +#define GIT_ATTR_CONFIG			"core.attributesfile" +  #define GIT_ATTR_FNMATCH_NEGATIVE	(1U << 0)  #define GIT_ATTR_FNMATCH_DIRECTORY	(1U << 1)  #define GIT_ATTR_FNMATCH_FULLPATH	(1U << 2)  #define GIT_ATTR_FNMATCH_MACRO		(1U << 3) +#define GIT_ATTR_FNMATCH_IGNORE		(1U << 4)  typedef struct {  	char *pattern; @@ -23,13 +29,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 +48,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 +58,6 @@ typedef struct {  	int is_dir;  } git_attr_path; -typedef struct { -	int initialized; -	git_hashtable *files;	  /* hash path to git_attr_file */ -	git_hashtable *macros;	  /* hash name to vector<git_attr_assignment> */ -} git_attr_cache; -  /*   * git_attr_file API   */ @@ -67,6 +67,7 @@ extern int git_attr_file__from_buffer(  extern int git_attr_file__from_file(  	git_repository *repo, const char *path, git_attr_file **out); +extern int git_attr_file__new(git_attr_file **attrs_ptr);  extern void git_attr_file__free(git_attr_file *file);  extern int git_attr_file__lookup_one( @@ -78,7 +79,7 @@ extern int git_attr_file__lookup_one(  /* loop over rules in file from bottom to top */  #define git_attr_file__foreach_matching_rule(file, path, iter, rule)	\  	git_vector_rforeach(&(file)->rules, (iter), (rule)) \ -		if (git_attr_rule__match_path((rule), (path)) == GIT_SUCCESS) +		if (git_attr_rule__match((rule), (path)) == GIT_SUCCESS)  extern unsigned long git_attr_file__name_hash(const char *name); @@ -87,9 +88,17 @@ extern unsigned long git_attr_file__name_hash(const char *name);   * other utilities   */ +extern int git_attr_fnmatch__parse( +	git_attr_fnmatch *spec, +	const char **base); + +extern int git_attr_fnmatch__match( +	git_attr_fnmatch *rule, +	const git_attr_path *path); +  extern void git_attr_rule__free(git_attr_rule *rule); -extern int git_attr_rule__match_path( +extern int git_attr_rule__match(  	git_attr_rule *rule,  	const git_attr_path *path); @@ -104,7 +113,4 @@ extern int git_attr_assignment__parse(  	git_vector *assigns,  	const char **scan); -extern int git_attr_cache__insert_macro( -	git_repository *repo, git_attr_rule *macro); -  #endif diff --git a/src/buffer.c b/src/buffer.c index def3496ce..c57e4aa1b 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -111,8 +111,10 @@ int git_buf_set(git_buf *buf, const char *data, size_t len)  	if (len == 0 || data == NULL) {  		git_buf_clear(buf);  	} else { -		ENSURE_SIZE(buf, len + 1); -		memmove(buf->ptr, data, len); +		if (data != buf->ptr) { +			ENSURE_SIZE(buf, len + 1); +			memmove(buf->ptr, data, len); +		}  		buf->size = len;  		buf->ptr[buf->size] = '\0';  	} @@ -179,7 +181,7 @@ void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)  {  	size_t copylen; -	assert(data && datasize); +	assert(data && datasize && buf);  	data[0] = '\0'; @@ -205,7 +207,7 @@ void git_buf_consume(git_buf *buf, const char *end)  void git_buf_truncate(git_buf *buf, ssize_t len)  { -	if (len < buf->size) { +	if (len >= 0 && len < buf->size) {  		buf->size = len;  		buf->ptr[buf->size] = '\0';  	} diff --git a/src/buffer.h b/src/buffer.h index 52fd9a678..d06358527 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -102,9 +102,16 @@ GIT_INLINE(const char *) git_buf_cstr(git_buf *buf)  	return buf->ptr;  } -  void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf);  #define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1) +GIT_INLINE(int) git_buf_rfind_next(git_buf *buf, char ch) +{ +	int idx = buf->size - 1; +	while (idx >= 0 && buf->ptr[idx] == ch) idx--; +	while (idx >= 0 && buf->ptr[idx] != ch) idx--; +	return idx; +} +  #endif diff --git a/src/fileops.c b/src/fileops.c index 48bd3514d..3412a47e2 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -534,3 +534,28 @@ int git_futils_find_system_file(git_buf *path, const char *filename)  #endif  } +int git_futils_dir_for_path(git_buf *dir, const char *path, const char *base) +{ +	int error = GIT_SUCCESS; + +	if (base != NULL && git_path_root(path) < 0) +		error = git_buf_joinpath(dir, base, path); +	else +		error = git_buf_sets(dir, path); + +	if (error == GIT_SUCCESS) { +		char buf[GIT_PATH_MAX]; +		if (p_realpath(dir->ptr, buf) != NULL) +			error = git_buf_sets(dir, buf); +	} + +	/* call dirname if this is not a directory */ +	if (error == GIT_SUCCESS && git_futils_isdir(dir->ptr) != GIT_SUCCESS) +		if (git_path_dirname_r(dir, dir->ptr) < GIT_SUCCESS) +			error = git_buf_lasterror(dir); + +	if (error == GIT_SUCCESS) +		error = git_path_to_dir(dir); + +	return error; +} diff --git a/src/fileops.h b/src/fileops.h index 31f3e6a91..91903a731 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -102,6 +102,16 @@ extern int git_futils_mkpath2file(const char *path, const mode_t mode);  extern int git_futils_rmdir_r(const char *path, int force);  /** + * Get the directory for a path. + * + * If the path is a directory, this does nothing (save append a '/' as + * needed).  If path is a normal file, this gets the directory containing + * it.  If the path does not exist, then this treats it a filename and + * returns the dirname of it. + */ +extern int git_futils_dir_for_path(git_buf *dir, const char *path, const char *base); + +/**   * Create and open a temporary file with a `_git2_` suffix.   * Writes the filename into path_out.   * @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..1040574d7 --- /dev/null +++ b/src/ignore.c @@ -0,0 +1,159 @@ +#include "ignore.h" +#include "path.h" +#include "git2/config.h" + +#define GIT_IGNORE_INTERNAL		"[internal]exclude" +#define GIT_IGNORE_FILE_INREPO	"info/exclude" +#define GIT_IGNORE_FILE			".gitignore" +#define GIT_IGNORE_CONFIG		"core.excludesfile" + +static int load_ignore_file( +	git_repository *GIT_UNUSED(repo), const char *path, git_attr_file **out) +{ +	int error = GIT_SUCCESS; +	git_fbuffer fbuf = GIT_FBUFFER_INIT; +	git_attr_file *ignores = NULL; +	git_attr_fnmatch *match = NULL; +	const char *scan = NULL; + +	GIT_UNUSED_ARG(repo); + +	*out = NULL; + +	if ((error = git_futils_readbuffer(&fbuf, path)) == GIT_SUCCESS) +		error = git_attr_file__new(&ignores); + +	ignores->path = git__strdup(path); + +	scan = fbuf.data; + +	while (error == GIT_SUCCESS && *scan) { +		if (!match && !(match = git__calloc(1, sizeof(git_attr_fnmatch)))) { +			error = GIT_ENOMEM; +			break; +		} + +		if (!(error = git_attr_fnmatch__parse(match, &scan))) { +			match->flags = match->flags | GIT_ATTR_FNMATCH_IGNORE; +			scan = git__next_line(scan); +			error = git_vector_insert(&ignores->rules, match); +		} + +		if (error != GIT_SUCCESS) { +			git__free(match->pattern); +			match->pattern = NULL; + +			if (error == GIT_ENOTFOUND) +				error = GIT_SUCCESS; +		} else { +			match = NULL; /* vector now "owns" the match */ +		} +	} + +	git_futils_freebuffer(&fbuf); +	git__free(match); + +	if (error != GIT_SUCCESS) { +		git__rethrow(error, "Could not open ignore file '%s'", path); +		git_attr_file__free(ignores); +	} else { +		*out = ignores; +	} + +	return error; +} + +#define push_ignore(R,S,B,F) \ +	git_attr_cache__push_file((R),(S),(B),(F),load_ignore_file) + +typedef struct { +	git_repository *repo; +	git_vector *stack; +} ignore_walk_up_info; + +static int push_one_ignore(void *ref, git_buf *path) +{ +	ignore_walk_up_info *info = (ignore_walk_up_info *)ref; +	return push_ignore(info->repo, info->stack, path->ptr, GIT_IGNORE_FILE); +} + +int git_ignore__for_path(git_repository *repo, const char *path, git_vector *stack) +{ +	int error = GIT_SUCCESS; +	git_buf dir = GIT_BUF_INIT; +	git_config *cfg; +	const char *workdir = git_repository_workdir(repo); +	ignore_walk_up_info info; + +	if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS) +		goto cleanup; + +	if ((error = git_futils_dir_for_path(&dir, path, workdir)) < GIT_SUCCESS) +		goto cleanup; + +	/* insert internals */ +	if ((error = push_ignore(repo, stack, NULL, GIT_IGNORE_INTERNAL)) < GIT_SUCCESS) +		goto cleanup; + +	/* load .gitignore up the path */ +	info.repo = repo; +	info.stack = stack; +	if ((error = git_path_walk_up(&dir, workdir, push_one_ignore, &info)) < GIT_SUCCESS) +		goto cleanup; + +	/* load .git/info/exclude */ +	if ((error = push_ignore(repo, stack, repo->path_repository, GIT_IGNORE_FILE_INREPO)) < GIT_SUCCESS) +		goto cleanup; + +	/* load core.excludesfile */ +	if ((error = git_repository_config(&cfg, repo)) == GIT_SUCCESS) { +		const char *core_ignore; +		error = git_config_get_string(cfg, GIT_IGNORE_CONFIG, &core_ignore); +		if (error == GIT_SUCCESS && core_ignore != NULL) +			error = push_ignore(repo, stack, NULL, core_ignore); +		else { +			error = GIT_SUCCESS; +			git_clearerror(); /* don't care if attributesfile is not set */ +		} +		git_config_free(cfg); +	} + +cleanup: +	if (error < GIT_SUCCESS) +		git__rethrow(error, "Could not get ignore files for '%s'", path); + +	git_buf_free(&dir); + +	return error; +} + +void git_ignore__free(git_vector *stack) +{ +	git_vector_free(stack); +} + +int git_ignore__lookup(git_vector *stack, const char *pathname, int *ignored) +{ +	int error; +	unsigned int i, j; +	git_attr_file *file; +	git_attr_path path; +	git_attr_fnmatch *match; + +	if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS) +		return git__rethrow(error, "Could not get attribute for '%s'", pathname); + +	*ignored = 0; + +	git_vector_foreach(stack, i, file) { +		git_vector_rforeach(&file->rules, j, match) { +			if (git_attr_fnmatch__match(match, &path) == GIT_SUCCESS) { +				*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); +				goto found; +			} +		} +	} +found: + +	return error; +} diff --git a/src/ignore.h b/src/ignore.h new file mode 100644 index 000000000..2954445b5 --- /dev/null +++ b/src/ignore.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2009-2011 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_ignore_h__ +#define INCLUDE_ignore_h__ + +#include "repository.h" +#include "vector.h" + +extern int git_ignore__for_path(git_repository *repo, const char *path, git_vector *stack); +extern void git_ignore__free(git_vector *stack); +extern int git_ignore__lookup(git_vector *stack, const char *path, int *ignored); + +#endif diff --git a/src/path.c b/src/path.c index f9663b7e5..03ebfe090 100644 --- a/src/path.c +++ b/src/path.c @@ -305,3 +305,47 @@ int git_path_fromurl(git_buf *local_path_out, const char *file_url)  	return error;  } + +int git_path_walk_up( +	git_buf *path, +	const char *ceiling, +	int (*cb)(void *data, git_buf *), +	void *data) +{ +	int error = GIT_SUCCESS; +	git_buf iter; +	ssize_t stop = 0, scan; +	char oldc = '\0'; + +	assert(path && cb); + +	if (ceiling != NULL) { +		if (git__prefixcmp(path->ptr, ceiling) == GIT_SUCCESS) +			stop = (ssize_t)strlen(ceiling); +		else +			stop = path->size; +	} +	scan = path->size; + +	iter.ptr = path->ptr; +	iter.size = path->size; +	iter.asize = path->asize; + +	while (scan >= stop) { +		if ((error = cb(data, &iter)) < GIT_SUCCESS) +			break; +		iter.ptr[scan] = oldc; +		scan = git_buf_rfind_next(&iter, '/'); +		if (scan >= 0) { +			scan++; +			oldc = iter.ptr[scan]; +			iter.size = scan; +			iter.ptr[scan] = '\0'; +		} +	} + +	if (scan >= 0) +		iter.ptr[scan] = oldc; + +	return error; +} diff --git a/src/path.h b/src/path.h index c308c5bd4..e59c19ad9 100644 --- a/src/path.h +++ b/src/path.h @@ -77,4 +77,18 @@ GIT_INLINE(void) git_path_mkposix(char *path)  extern int git__percent_decode(git_buf *decoded_out, const char *input);  extern int git_path_fromurl(git_buf *local_path_out, const char *file_url); +/** + * Invoke callback directory by directory up the path until the ceiling + * is reached (inclusive of a final call at the root_path). + * + * If the ceiling is NULL, this will walk all the way up to the root. + * If the ceiling is not a prefix of the path, the callback will be + * invoked a single time on the verbatim input path.  Returning anything + * other than GIT_SUCCESS from the callback function will stop the + * iteration and propogate the error to the caller. + */ +extern int git_path_walk_up( +	git_buf *path, const char *ceiling, +	int (*cb)(void *data, git_buf *), void *data); +  #endif diff --git a/src/repository.h b/src/repository.h index 82052158a..5274fc1d0 100644 --- a/src/repository.h +++ b/src/repository.h @@ -19,7 +19,7 @@  #include "refs.h"  #include "buffer.h"  #include "odb.h" -#include "attr_file.h" +#include "attr.h"  #define DOT_GIT ".git"  #define GIT_DIR DOT_GIT "/" diff --git a/src/status.c b/src/status.c index 64be6ce29..3ead15a87 100644 --- a/src/status.c +++ b/src/status.c @@ -13,6 +13,7 @@  #include "tree.h"  #include "git2/status.h"  #include "repository.h" +#include "ignore.h"  struct status_entry {  	git_index_time mtime; @@ -21,7 +22,7 @@ struct status_entry {  	git_oid index_oid;  	git_oid wt_oid; -	unsigned int status_flags:6; +	unsigned int status_flags;  	char path[GIT_FLEX_ARRAY]; /* more */  }; @@ -117,10 +118,30 @@ static int status_entry_update_flags(struct status_entry *e)  	return GIT_SUCCESS;  } +static int status_entry_is_ignorable(struct status_entry *e) +{ +	/* don't ignore files that exist in head or index already */ +	return (e->status_flags == GIT_STATUS_WT_NEW); +} + +static int status_entry_update_ignore(struct status_entry *e, git_vector *ignores, const char *path) +{ +	int error, ignored; + +	if ((error = git_ignore__lookup(ignores, path, &ignored)) == GIT_SUCCESS && +		ignored) +		e->status_flags = +			(e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_IGNORED; + +	return error; +} +  struct status_st { +	git_repository *repo;  	git_vector *vector;  	git_index *index;  	git_tree *tree; +	git_vector *ignores;  	int workdir_path_len;  	git_buf head_tree_relative_path; @@ -210,9 +231,22 @@ static int process_folder(  		}  	} -	if (full_path != NULL && path_type == GIT_STATUS_PATH_FOLDER) -		error = alphasorted_futils_direach(full_path, dirent_cb, st); -	else + +	if (full_path != NULL && path_type == GIT_STATUS_PATH_FOLDER) { +		git_vector ignores = GIT_VECTOR_INIT, *old_ignores; + +		if ((error = git_ignore__for_path(st->repo, +			full_path->ptr + st->workdir_path_len, &ignores)) == GIT_SUCCESS) +		{ +			old_ignores = st->ignores; +			st->ignores = &ignores; + +			error = alphasorted_futils_direach(full_path, dirent_cb, st); + +			git_ignore__free(st->ignores); +			st->ignores = old_ignores; +		} +	} else  		error = dirent_cb(st, NULL);  	if (tree_entry_type == GIT_OBJ_TREE) { @@ -232,6 +266,10 @@ static int store_if_changed(struct status_st *st, struct status_entry *e)  	if ((error = status_entry_update_flags(e)) < GIT_SUCCESS)  		return git__throw(error, "Failed to process the file '%s'. It doesn't exist in the workdir, in the HEAD nor in the index", e->path); +	if (status_entry_is_ignorable(e) && +		(error = status_entry_update_ignore(e, st->ignores, e->path)) < GIT_SUCCESS) +		return error; +  	if (e->status_flags == GIT_STATUS_CURRENT) {  		git__free(e);  		return GIT_SUCCESS; @@ -240,7 +278,8 @@ static int store_if_changed(struct status_st *st, struct status_entry *e)  	return git_vector_insert(st->vector, e);  } -static int determine_status(struct status_st *st, +static int determine_status( +	struct status_st *st,  	int in_head, int in_index, int in_workdir,  	const git_tree_entry *tree_entry,  	const git_index_entry *index_entry, @@ -274,8 +313,8 @@ static int determine_status(struct status_st *st,  		}  		if (in_workdir) -			if ((error = status_entry_update_from_workdir(e, full_path->ptr -)) < GIT_SUCCESS) +			if ((error = status_entry_update_from_workdir( +					 e, full_path->ptr)) < GIT_SUCCESS)  				return error;	/* The callee has already set the error message */  		return store_if_changed(st, e); @@ -340,7 +379,6 @@ static int dirent_cb(void *state, git_buf *a)  	int cmpma, cmpmi, cmpai, error;  	const char *pm, *pa, *pi;  	const char *m_name, *i_name, *a_name; -  	struct status_st *st = (struct status_st *)state;  	path_type = path_type_from(a, st->is_dir); @@ -372,7 +410,8 @@ static int dirent_cb(void *state, git_buf *a)  			error = git_buf_lasterror(&st->head_tree_relative_path);  			if (error < GIT_SUCCESS) -				return git__rethrow(error, "An error occured while determining the status of '%s'", a->ptr); +				return git__rethrow(error, "An error occured while " +					"determining the status of '%s'", a->ptr);  			m_name = st->head_tree_relative_path.ptr;  		} else @@ -388,7 +427,8 @@ static int dirent_cb(void *state, git_buf *a)  		pa = ((cmpma >= 0) && (cmpai <= 0)) ? a_name : NULL;  		pi = ((cmpmi >= 0) && (cmpai >= 0)) ? i_name : NULL; -		if((error = determine_status(st, pm != NULL, pi != NULL, pa != NULL, m, entry, a, status_path(pm, pi, pa), path_type)) < GIT_SUCCESS) +		if ((error = determine_status(st, pm != NULL, pi != NULL, pa != NULL, +				m, entry, a, status_path(pm, pi, pa), path_type)) < GIT_SUCCESS)  			return git__rethrow(error, "An error occured while determining the status of '%s'", a->ptr);  		if ((pa != NULL) || (path_type == GIT_STATUS_PATH_FOLDER)) @@ -406,9 +446,12 @@ static int status_cmp(const void *a, const void *b)  #define DEFAULT_SIZE 16 -int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsigned int, void *), void *payload) +int git_status_foreach( +	git_repository *repo, +	int (*callback)(const char *, unsigned int, void *), +	void *payload)  { -	git_vector entries; +	git_vector entries, ignores = GIT_VECTOR_INIT;  	git_index *index = NULL;  	git_buf temp_path = GIT_BUF_INIT;  	struct status_st dirent_st = {0}; @@ -434,14 +477,16 @@ int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsig  	git_vector_init(&entries, DEFAULT_SIZE, status_cmp); -	dirent_st.workdir_path_len = strlen(workdir); -	dirent_st.tree_position = 0; -	dirent_st.index_position = 0; -	dirent_st.tree = tree; -	dirent_st.index = index; +	dirent_st.repo = repo;  	dirent_st.vector = &entries; +	dirent_st.index = index; +	dirent_st.tree = tree; +	dirent_st.ignores = &ignores; +	dirent_st.workdir_path_len = strlen(workdir);  	git_buf_init(&dirent_st.head_tree_relative_path, 0);  	dirent_st.head_tree_relative_path_len = 0; +	dirent_st.tree_position = 0; +	dirent_st.index_position = 0;  	dirent_st.is_dir = 1;  	if (git_futils_isdir(workdir)) { @@ -453,6 +498,10 @@ int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsig  	git_buf_sets(&temp_path, workdir); +	error = git_ignore__for_path(repo, "", dirent_st.ignores); +	if (error < GIT_SUCCESS) +		goto exit; +  	error = alphasorted_futils_direach(  		&temp_path, dirent_cb, &dirent_st); @@ -461,7 +510,8 @@ int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsig  			"Failed to determine statuses. "  			"An error occured while processing the working directory"); -	if ((error == GIT_SUCCESS) && ((error = dirent_cb(&dirent_st, NULL)) < GIT_SUCCESS)) +	if ((error == GIT_SUCCESS) && +		((error = dirent_cb(&dirent_st, NULL)) < GIT_SUCCESS))  		error = git__rethrow(error,  			"Failed to determine statuses. "  			"An error occured while post-processing the HEAD tree and the index"); @@ -483,6 +533,7 @@ exit:  	git_buf_free(&dirent_st.head_tree_relative_path);  	git_buf_free(&temp_path);  	git_vector_free(&entries); +	git_vector_free(&ignores);  	git_tree_free(tree);  	return error;  } @@ -599,6 +650,18 @@ int git_status_file(unsigned int *status_flags, git_repository *repo, const char  		goto cleanup;  	} +	if (status_entry_is_ignorable(e)) { +		git_vector ignores = GIT_VECTOR_INIT; + +		if ((error = git_ignore__for_path(repo, path, &ignores)) == GIT_SUCCESS) +			error = status_entry_update_ignore(e, &ignores, path); + +		git_ignore__free(&ignores); + +		if (error < GIT_SUCCESS) +			goto cleanup; +	} +  	*status_flags = e->status_flags;  cleanup: @@ -615,28 +678,15 @@ cleanup:   *   */ -struct alphasorted_dirent_info { -	int is_dir; -	char path[GIT_FLEX_ARRAY]; /* more */ -}; - -static struct alphasorted_dirent_info *alphasorted_dirent_info_new(const git_buf *path) +static char *alphasorted_dirent_info_new(const git_buf *path)  { -	int is_dir, size; -	struct alphasorted_dirent_info *di; - -	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); +	char *di = git__malloc(path->size + 2); +	if (!di) +		return di; -	if (is_dir) { -		di->is_dir = 1; +	git_buf_copy_cstr(di, path->size + 1, path); +	if (git_futils_isdir(path->ptr) == GIT_SUCCESS) {  		/*  		 * Append a forward slash to the name to force folders  		 * to be ordered in a similar way than in a tree @@ -644,23 +694,16 @@ static struct alphasorted_dirent_info *alphasorted_dirent_info_new(const git_buf  		 * The file "subdir" should appear before the file "subdir.txt"  		 * The folder "subdir" should appear after the file "subdir.txt"  		 */ -		di->path[path->size] = '/'; +		di[path->size] = '/'; +		di[path->size + 1] = '\0';  	}  	return di;  } -static int alphasorted_dirent_info_cmp(const void *a, const void *b) -{ -	struct alphasorted_dirent_info *stra = (struct alphasorted_dirent_info *)a; -	struct alphasorted_dirent_info *strb = (struct alphasorted_dirent_info *)b; - -	return strcmp(stra->path, strb->path); -} -  static int alphasorted_dirent_cb(void *state, git_buf *full_path)  { -	struct alphasorted_dirent_info *entry; +	char *entry;  	git_vector *entry_names;  	entry_names = (git_vector *)state; @@ -682,13 +725,13 @@ static int alphasorted_futils_direach(  	int (*fn)(void *, git_buf *),  	void *arg)  { -	struct alphasorted_dirent_info *entry; +	char *entry;  	git_vector entry_names;  	unsigned int idx;  	int error = GIT_SUCCESS;  	git_buf entry_path = GIT_BUF_INIT; -	if (git_vector_init(&entry_names, 16, alphasorted_dirent_info_cmp) < GIT_SUCCESS) +	if (git_vector_init(&entry_names, 16, git__strcmp_cb) < GIT_SUCCESS)  		return GIT_ENOMEM;  	error = git_futils_direach(path, alphasorted_dirent_cb, &entry_names); @@ -696,17 +739,18 @@ static int alphasorted_futils_direach(  	git_vector_sort(&entry_names);  	for (idx = 0; idx < entry_names.length; ++idx) { -		entry = (struct alphasorted_dirent_info *)git_vector_get(&entry_names, idx); +		entry = (char *)git_vector_get(&entry_names, idx);  		/* We have to walk the entire vector even if there was an error,  		 * in order to free up memory, but we stop making callbacks after  		 * an error.  		 */  		if (error == GIT_SUCCESS) -			error = git_buf_sets(&entry_path, entry->path); +			error = git_buf_sets(&entry_path, entry);  		if (error == GIT_SUCCESS) { -			((struct status_st *)arg)->is_dir = entry->is_dir; +			((struct status_st *)arg)->is_dir = +				(entry[entry_path.size - 1] == '/');  			error = fn(arg, &entry_path);  		} @@ -717,3 +761,18 @@ static int alphasorted_futils_direach(  	git_vector_free(&entry_names);  	return error;  } + + +int git_status_should_ignore(git_repository *repo, const char *path, int *ignored) +{ +	int error; +	git_vector ignores = GIT_VECTOR_INIT; + +	if ((error = git_ignore__for_path(repo, path, &ignores)) == GIT_SUCCESS) +		error = git_ignore__lookup(&ignores, path, ignored); + +	git_ignore__free(&ignores); + +	return error; +} + diff --git a/src/util.h b/src/util.h index 2654e2de2..6c929cf0a 100644 --- a/src/util.h +++ b/src/util.h @@ -102,6 +102,13 @@ extern char *git__strtok(char **end, const char *sep);  extern void git__strntolower(char *str, size_t len);  extern void git__strtolower(char *str); +GIT_INLINE(const char *) git__next_line(const char *s) +{ +	while (*s && *s != '\n') s++; +	while (*s == '\n' || *s == '\r') s++; +	return s; +} +  extern int git__fnmatch(const char *pattern, const char *name, int flags);  extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *));  | 
