ignore.c 8.06 KB
Newer Older
1
#include "git2/ignore.h"
2
#include "ignore.h"
3
#include "attr.h"
4
#include "path.h"
5
#include "config.h"
6 7 8

#define GIT_IGNORE_INTERNAL		"[internal]exclude"

9 10
#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"

11
static int parse_ignore_file(
12
	git_repository *repo, void *parsedata, const char *buffer, git_attr_file *ignores)
13
{
14
	int error = 0;
15 16
	git_attr_fnmatch *match = NULL;
	const char *scan = NULL;
17
	char *context = NULL;
18
	int ignore_case = false;
19

20 21 22 23 24 25
	/* Prefer to have the caller pass in a git_ignores as the parsedata
	 * object.  If they did not, then look up the value of ignore_case */
	if (parsedata != NULL)
		ignore_case = ((git_ignores *)parsedata)->ignore_case;
	else if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
		return error;
26

27 28 29
	if (ignores->key && git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0) {
		context = ignores->key + 2;
		context[strlen(context) - strlen(GIT_IGNORE_FILE)] = '\0';
30
	}
31

32
	scan = buffer;
33

34 35 36 37
	while (!error && *scan) {
		if (!match) {
			match = git__calloc(1, sizeof(*match));
			GITERR_CHECK_ALLOC(match);
38 39
		}

40 41
		match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;

42 43 44
		if (!(error = git_attr_fnmatch__parse(
			match, ignores->pool, context, &scan)))
		{
45 46 47 48 49
			match->flags |= GIT_ATTR_FNMATCH_IGNORE;

			if (ignore_case)
				match->flags |= GIT_ATTR_FNMATCH_ICASE;

50 51 52 53
			scan = git__next_line(scan);
			error = git_vector_insert(&ignores->rules, match);
		}

54
		if (error != 0) {
55 56 57 58
			git__free(match->pattern);
			match->pattern = NULL;

			if (error == GIT_ENOTFOUND)
59
				error = 0;
60 61 62 63 64
		} else {
			match = NULL; /* vector now "owns" the match */
		}
	}

65
	git__free(match);
66 67 68
	/* restore file path used for context */
	if (context)
		context[strlen(context)] = '.'; /* first char of GIT_IGNORE_FILE */
69 70 71 72

	return error;
}

73 74
#define push_ignore_file(R,IGN,S,B,F) \
	git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(IGN),(S))
75

76 77
static int push_one_ignore(void *ref, git_buf *path)
{
78
	git_ignores *ign = (git_ignores *)ref;
79
	return push_ignore_file(ign->repo, ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
80 81
}

82 83 84 85 86 87 88 89 90 91 92 93 94
static int get_internal_ignores(git_attr_file **ign, git_repository *repo)
{
	int error;

	if (!(error = git_attr_cache__init(repo)))
		error = git_attr_cache__internal_file(repo, GIT_IGNORE_INTERNAL, ign);

	if (!error && !(*ign)->rules.length)
		error = parse_ignore_file(repo, NULL, GIT_IGNORE_DEFAULT_RULES, *ign);

	return error;
}

95 96 97 98
int git_ignore__for_path(
	git_repository *repo,
	const char *path,
	git_ignores *ignores)
99
{
100
	int error = 0;
101
	const char *workdir = git_repository_workdir(repo);
102 103

	assert(ignores);
104

105 106 107 108
	ignores->repo = repo;
	git_buf_init(&ignores->dir, 0);
	ignores->ign_internal = NULL;

109 110 111
	/* Read the ignore_case flag */
	if ((error = git_repository__cvar(
			&ignores->ignore_case, repo, GIT_CVAR_IGNORECASE)) < 0)
112 113
		goto cleanup;

114 115 116
	if ((error = git_vector_init(&ignores->ign_path, 8, NULL)) < 0 ||
		(error = git_vector_init(&ignores->ign_global, 2, NULL)) < 0 ||
		(error = git_attr_cache__init(repo)) < 0)
117 118
		goto cleanup;

119 120 121 122 123 124
	/* given a unrooted path in a non-bare repo, resolve it */
	if (workdir && git_path_root(path) < 0)
		error = git_path_find_dir(&ignores->dir, path, workdir);
	else
		error = git_buf_sets(&ignores->dir, path);
	if (error < 0)
125 126
		goto cleanup;

127
	/* set up internals */
128
	error = get_internal_ignores(&ignores->ign_internal, repo);
129
	if (error < 0)
130 131 132
		goto cleanup;

	/* load .gitignore up the path */
133 134 135 136 137 138
	if (workdir != NULL) {
		error = git_path_walk_up(
			&ignores->dir, workdir, push_one_ignore, ignores);
		if (error < 0)
			goto cleanup;
	}
139 140

	/* load .git/info/exclude */
141
	error = push_ignore_file(repo, ignores, &ignores->ign_global,
142
		git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
143
	if (error < 0)
144 145 146
		goto cleanup;

	/* load core.excludesfile */
147
	if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
148
		error = push_ignore_file(repo, ignores, &ignores->ign_global, NULL,
149
			git_repository_attr_cache(repo)->cfg_excl_file);
150 151

cleanup:
152
	if (error < 0)
153
		git_ignore__free(ignores);
154

155 156 157 158 159
	return error;
}

int git_ignore__push_dir(git_ignores *ign, const char *dir)
{
160 161 162
	if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0)
		return -1;
	else
163
		return push_ignore_file(
164
			ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
165 166
}

167 168 169 170
int git_ignore__pop_dir(git_ignores *ign)
{
	if (ign->ign_path.length > 0) {
		git_attr_file *file = git_vector_last(&ign->ign_path);
171
		if (git__suffixcmp(ign->dir.ptr, file->key + 2) == 0)
172
			git_vector_pop(&ign->ign_path);
173 174
		git_buf_rtruncate_at_char(&ign->dir, '/');
	}
175
	return 0;
176 177
}

178
void git_ignore__free(git_ignores *ignores)
179
{
180 181 182 183 184 185
	/* don't need to free ignores->ign_internal since it is in cache */
	git_vector_free(&ignores->ign_path);
	git_vector_free(&ignores->ign_global);
	git_buf_free(&ignores->dir);
}

186
static bool ignore_lookup_in_rules(
187 188
	git_vector *rules, git_attr_path *path, int *ignored)
{
189
	size_t j;
190 191 192
	git_attr_fnmatch *match;

	git_vector_rforeach(rules, j, match) {
193
		if (git_attr_fnmatch__match(match, path)) {
194
			*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0);
195
			return true;
196 197 198
		}
	}

199
	return false;
200 201
}

202 203
int git_ignore__lookup(
	git_ignores *ignores, const char *pathname, int *ignored)
204
{
205
	unsigned int i;
206 207 208
	git_attr_file *file;
	git_attr_path path;

209 210 211
	if (git_attr_path__init(
		&path, pathname, git_repository_workdir(ignores->repo)) < 0)
		return -1;
212

213 214 215
	/* first process builtins - success means path was found */
	if (ignore_lookup_in_rules(
			&ignores->ign_internal->rules, &path, ignored))
216
		goto cleanup;
217

218 219
	/* next process files in the path */
	git_vector_foreach(&ignores->ign_path, i, file) {
220
		if (ignore_lookup_in_rules(&file->rules, &path, ignored))
221
			goto cleanup;
222 223
	}

224 225
	/* last process global ignores */
	git_vector_foreach(&ignores->ign_global, i, file) {
226
		if (ignore_lookup_in_rules(&file->rules, &path, ignored))
227
			goto cleanup;
228 229 230
	}

	*ignored = 0;
231 232 233

cleanup:
	git_attr_path__free(&path);
234
	return 0;
235
}
236 237 238 239 240 241 242 243

int git_ignore_add_rule(
	git_repository *repo,
	const char *rules)
{
	int error;
	git_attr_file *ign_internal;

244
	if (!(error = get_internal_ignores(&ign_internal, repo)))
245
		error = parse_ignore_file(repo, NULL, rules, ign_internal);
246 247 248 249 250 251 252 253 254 255

	return error;
}

int git_ignore_clear_internal_rules(
	git_repository *repo)
{
	int error;
	git_attr_file *ign_internal;

256
	if (!(error = get_internal_ignores(&ign_internal, repo))) {
257 258
		git_attr_file__clear_rules(ign_internal);

259 260 261 262
		return parse_ignore_file(
			repo, NULL, GIT_IGNORE_DEFAULT_RULES, ign_internal);
	}

263 264
	return error;
}
265 266 267 268

int git_ignore_path_is_ignored(
	int *ignored,
	git_repository *repo,
269
	const char *pathname)
270 271
{
	int error;
272 273 274 275
	const char *workdir;
	git_attr_path path;
	char *tail, *end;
	bool full_is_dir;
276
	git_ignores ignores;
277 278
	unsigned int i;
	git_attr_file *file;
279

280 281 282 283 284 285 286 287 288 289
	assert(ignored && pathname);

	workdir = repo ? git_repository_workdir(repo) : NULL;

	if ((error = git_attr_path__init(&path, pathname, workdir)) < 0)
		return error;

	tail = path.path;
	end  = &path.full.ptr[path.full.size];
	full_is_dir = path.is_dir;
290

291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
	while (1) {
		/* advance to next component of path */
		path.basename = tail;

		while (tail < end && *tail != '/') tail++;
		*tail = '\0';

		path.full.size = (tail - path.full.ptr);
		path.is_dir = (tail == end) ? full_is_dir : true;

		/* update ignores for new path fragment */
		if (path.basename == path.path)
			error = git_ignore__for_path(repo, path.path, &ignores);
		else
			error = git_ignore__push_dir(&ignores, path.basename);
		if (error < 0)
			break;

		/* first process builtins - success means path was found */
		if (ignore_lookup_in_rules(
				&ignores.ign_internal->rules, &path, ignored))
			goto cleanup;

		/* next process files in the path */
		git_vector_foreach(&ignores.ign_path, i, file) {
			if (ignore_lookup_in_rules(&file->rules, &path, ignored))
				goto cleanup;
		}

		/* last process global ignores */
		git_vector_foreach(&ignores.ign_global, i, file) {
			if (ignore_lookup_in_rules(&file->rules, &path, ignored))
				goto cleanup;
		}

		/* if we found no rules before reaching the end, we're done */
		if (tail == end)
			break;

		/* reinstate divider in path */
		*tail = '/';
		while (*tail == '/') tail++;
	}

	*ignored = 0;

cleanup:
	git_attr_path__free(&path);
339 340 341 342
	git_ignore__free(&ignores);
	return error;
}