int git_attr_fnmatch__parse()

in src/attr_file.c [648:765]


int git_attr_fnmatch__parse(
	git_attr_fnmatch *spec,
	git_pool *pool,
	const char *context,
	const char **base)
{
	const char *pattern, *scan;
	int slash_count, allow_space;
	bool escaped;

	assert(spec && base && *base);

	if (parse_optimized_patterns(spec, pool, *base))
		return 0;

	spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
	allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);

	pattern = *base;

	while (!allow_space && git__isspace(*pattern))
		pattern++;

	if (!*pattern || *pattern == '#' || *pattern == '\n' ||
	    (*pattern == '\r' && *(pattern + 1) == '\n')) {
		*base = git__next_line(pattern);
		return GIT_ENOTFOUND;
	}

	if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
		if (strncmp(pattern, "[attr]", 6) == 0) {
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
			pattern += 6;
		}
		/* else a character range like [a-e]* which is accepted */
	}

	if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
		spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
		pattern++;
	}

	slash_count = 0;
	escaped = false;
	/* Scan until a non-escaped whitespace. */
	for (scan = pattern; *scan != '\0'; ++scan) {
		char c = *scan;

		if (c == '\\' && !escaped) {
			escaped = true;
			continue;
		} else if (git__isspace(c) && !escaped) {
			if (!allow_space || (c != ' ' && c != '\t' && c != '\r'))
				break;
		} else if (c == '/') {
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
			slash_count++;

			if (slash_count == 1 && pattern == scan)
				pattern++;
		} else if (git__iswildcard(c) && !escaped) {
			/* remember if we see an unescaped wildcard in pattern */
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
		}

		escaped = false;
	}

	*base = scan;

	if ((spec->length = scan - pattern) == 0)
		return GIT_ENOTFOUND;

	/*
	 * Remove one trailing \r in case this is a CRLF delimited
	 * file, in the case of Icon\r\r\n, we still leave the first
	 * \r there to match against.
	 */
	if (pattern[spec->length - 1] == '\r')
		if (--spec->length == 0)
			return GIT_ENOTFOUND;

	/* Remove trailing spaces. */
	spec->length -= trailing_space_length(pattern, spec->length);

	if (spec->length == 0)
		return GIT_ENOTFOUND;

	if (pattern[spec->length - 1] == '/') {
		spec->length--;
		spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
		if (--slash_count <= 0)
			spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
	}

	if (context) {
		char *slash = strrchr(context, '/');
		size_t len;
		if (slash) {
			/* include the slash for easier matching */
			len = slash - context + 1;
			spec->containing_dir = git_pool_strndup(pool, context, len);
			spec->containing_dir_length = len;
		}
	}

	spec->pattern = git_pool_strndup(pool, pattern, spec->length);

	if (!spec->pattern) {
		*base = git__next_line(pattern);
		return -1;
	} else {
		/* strip '\' that might have been used for internal whitespace */
		spec->length = unescape_spaces(spec->pattern);
	}

	return 0;
}