pathspec: allow querying for attributes
The pathspec mechanism is extended via the new ":(attr:eol=input)pattern/to/match" syntax to filter paths so that it requires paths to not just match the given pattern but also have the specified attrs attached for them to be chosen. Based on a patch by Stefan Beller <sbeller@google.com> Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									625568cd88
								
							
						
					
					
						commit
						b0db704652
					
				|  | @ -384,6 +384,27 @@ full pathname may have special meaning: | ||||||
| + | + | ||||||
| Glob magic is incompatible with literal magic. | Glob magic is incompatible with literal magic. | ||||||
|  |  | ||||||
|  | attr;; | ||||||
|  | After `attr:` comes a space separated list of "attribute | ||||||
|  | requirements", all of which must be met in order for the | ||||||
|  | path to be considered a match; this is in addition to the | ||||||
|  | usual non-magic pathspec pattern matching. | ||||||
|  | See linkgit:gitattributes[5]. | ||||||
|  | + | ||||||
|  | Each of the attribute requirements for the path takes one of | ||||||
|  | these forms: | ||||||
|  |  | ||||||
|  | - "`ATTR`" requires that the attribute `ATTR` be set. | ||||||
|  |  | ||||||
|  | - "`-ATTR`" requires that the attribute `ATTR` be unset. | ||||||
|  |  | ||||||
|  | - "`ATTR=VALUE`" requires that the attribute `ATTR` be | ||||||
|  |   set to the string `VALUE`. | ||||||
|  |  | ||||||
|  | - "`!ATTR`" requires that the attribute `ATTR` be | ||||||
|  |   unspecified. | ||||||
|  | + | ||||||
|  |  | ||||||
| exclude;; | exclude;; | ||||||
| 	After a path matches any non-exclude pathspec, it will be run | 	After a path matches any non-exclude pathspec, it will be run | ||||||
| 	through all exclude pathspec (magic signature: `!` or its | 	through all exclude pathspec (magic signature: `!` or its | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								attr.c
								
								
								
								
							
							
						
						
									
										17
									
								
								attr.c
								
								
								
								
							|  | @ -603,6 +603,23 @@ struct attr_check *attr_check_initl(const char *one, ...) | ||||||
| 	return check; | 	return check; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | struct attr_check *attr_check_dup(const struct attr_check *check) | ||||||
|  | { | ||||||
|  | 	struct attr_check *ret; | ||||||
|  |  | ||||||
|  | 	if (!check) | ||||||
|  | 		return NULL; | ||||||
|  |  | ||||||
|  | 	ret = attr_check_alloc(); | ||||||
|  |  | ||||||
|  | 	ret->nr = check->nr; | ||||||
|  | 	ret->alloc = check->alloc; | ||||||
|  | 	ALLOC_ARRAY(ret->items, ret->nr); | ||||||
|  | 	COPY_ARRAY(ret->items, check->items, ret->nr); | ||||||
|  |  | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
| struct attr_check_item *attr_check_append(struct attr_check *check, | struct attr_check_item *attr_check_append(struct attr_check *check, | ||||||
| 					  const struct git_attr *attr) | 					  const struct git_attr *attr) | ||||||
| { | { | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								attr.h
								
								
								
								
							
							
						
						
									
										1
									
								
								attr.h
								
								
								
								
							|  | @ -44,6 +44,7 @@ struct attr_check { | ||||||
|  |  | ||||||
| extern struct attr_check *attr_check_alloc(void); | extern struct attr_check *attr_check_alloc(void); | ||||||
| extern struct attr_check *attr_check_initl(const char *, ...); | extern struct attr_check *attr_check_initl(const char *, ...); | ||||||
|  | extern struct attr_check *attr_check_dup(const struct attr_check *check); | ||||||
|  |  | ||||||
| extern struct attr_check_item *attr_check_append(struct attr_check *check, | extern struct attr_check_item *attr_check_append(struct attr_check *check, | ||||||
| 						 const struct git_attr *attr); | 						 const struct git_attr *attr); | ||||||
|  |  | ||||||
							
								
								
									
										43
									
								
								dir.c
								
								
								
								
							
							
						
						
									
										43
									
								
								dir.c
								
								
								
								
							|  | @ -9,6 +9,7 @@ | ||||||
|  */ |  */ | ||||||
| #include "cache.h" | #include "cache.h" | ||||||
| #include "dir.h" | #include "dir.h" | ||||||
|  | #include "attr.h" | ||||||
| #include "refs.h" | #include "refs.h" | ||||||
| #include "wildmatch.h" | #include "wildmatch.h" | ||||||
| #include "pathspec.h" | #include "pathspec.h" | ||||||
|  | @ -134,7 +135,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec) | ||||||
| 		       PATHSPEC_LITERAL | | 		       PATHSPEC_LITERAL | | ||||||
| 		       PATHSPEC_GLOB | | 		       PATHSPEC_GLOB | | ||||||
| 		       PATHSPEC_ICASE | | 		       PATHSPEC_ICASE | | ||||||
| 		       PATHSPEC_EXCLUDE); | 		       PATHSPEC_EXCLUDE | | ||||||
|  | 		       PATHSPEC_ATTR); | ||||||
|  |  | ||||||
| 	for (n = 0; n < pathspec->nr; n++) { | 	for (n = 0; n < pathspec->nr; n++) { | ||||||
| 		size_t i = 0, len = 0, item_len; | 		size_t i = 0, len = 0, item_len; | ||||||
|  | @ -209,6 +211,36 @@ int within_depth(const char *name, int namelen, | ||||||
| #define DO_MATCH_DIRECTORY (1<<1) | #define DO_MATCH_DIRECTORY (1<<1) | ||||||
| #define DO_MATCH_SUBMODULE (1<<2) | #define DO_MATCH_SUBMODULE (1<<2) | ||||||
|  |  | ||||||
|  | static int match_attrs(const char *name, int namelen, | ||||||
|  | 		       const struct pathspec_item *item) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  |  | ||||||
|  | 	git_check_attr(name, item->attr_check); | ||||||
|  | 	for (i = 0; i < item->attr_match_nr; i++) { | ||||||
|  | 		const char *value; | ||||||
|  | 		int matched; | ||||||
|  | 		enum attr_match_mode match_mode; | ||||||
|  |  | ||||||
|  | 		value = item->attr_check->items[i].value; | ||||||
|  | 		match_mode = item->attr_match[i].match_mode; | ||||||
|  |  | ||||||
|  | 		if (ATTR_TRUE(value)) | ||||||
|  | 			matched = (match_mode == MATCH_SET); | ||||||
|  | 		else if (ATTR_FALSE(value)) | ||||||
|  | 			matched = (match_mode == MATCH_UNSET); | ||||||
|  | 		else if (ATTR_UNSET(value)) | ||||||
|  | 			matched = (match_mode == MATCH_UNSPECIFIED); | ||||||
|  | 		else | ||||||
|  | 			matched = (match_mode == MATCH_VALUE && | ||||||
|  | 				   !strcmp(item->attr_match[i].value, value)); | ||||||
|  | 		if (!matched) | ||||||
|  | 			return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 1; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Does 'match' match the given name? |  * Does 'match' match the given name? | ||||||
|  * A match is found if |  * A match is found if | ||||||
|  | @ -261,6 +293,9 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, | ||||||
| 	    strncmp(item->match, name - prefix, item->prefix)) | 	    strncmp(item->match, name - prefix, item->prefix)) | ||||||
| 		return 0; | 		return 0; | ||||||
|  |  | ||||||
|  | 	if (item->attr_match_nr && !match_attrs(name, namelen, item)) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
| 	/* If the match was just the prefix, we matched */ | 	/* If the match was just the prefix, we matched */ | ||||||
| 	if (!*match) | 	if (!*match) | ||||||
| 		return MATCHED_RECURSIVELY; | 		return MATCHED_RECURSIVELY; | ||||||
|  | @ -339,7 +374,8 @@ static int do_match_pathspec(const struct pathspec *ps, | ||||||
| 		       PATHSPEC_LITERAL | | 		       PATHSPEC_LITERAL | | ||||||
| 		       PATHSPEC_GLOB | | 		       PATHSPEC_GLOB | | ||||||
| 		       PATHSPEC_ICASE | | 		       PATHSPEC_ICASE | | ||||||
| 		       PATHSPEC_EXCLUDE); | 		       PATHSPEC_EXCLUDE | | ||||||
|  | 		       PATHSPEC_ATTR); | ||||||
|  |  | ||||||
| 	if (!ps->nr) { | 	if (!ps->nr) { | ||||||
| 		if (!ps->recursive || | 		if (!ps->recursive || | ||||||
|  | @ -1361,7 +1397,8 @@ static int simplify_away(const char *path, int pathlen, | ||||||
| 		       PATHSPEC_LITERAL | | 		       PATHSPEC_LITERAL | | ||||||
| 		       PATHSPEC_GLOB | | 		       PATHSPEC_GLOB | | ||||||
| 		       PATHSPEC_ICASE | | 		       PATHSPEC_ICASE | | ||||||
| 		       PATHSPEC_EXCLUDE); | 		       PATHSPEC_EXCLUDE | | ||||||
|  | 		       PATHSPEC_ATTR); | ||||||
|  |  | ||||||
| 	for (i = 0; i < pathspec->nr; i++) { | 	for (i = 0; i < pathspec->nr; i++) { | ||||||
| 		const struct pathspec_item *item = &pathspec->items[i]; | 		const struct pathspec_item *item = &pathspec->items[i]; | ||||||
|  |  | ||||||
							
								
								
									
										113
									
								
								pathspec.c
								
								
								
								
							
							
						
						
									
										113
									
								
								pathspec.c
								
								
								
								
							|  | @ -1,6 +1,7 @@ | ||||||
| #include "cache.h" | #include "cache.h" | ||||||
| #include "dir.h" | #include "dir.h" | ||||||
| #include "pathspec.h" | #include "pathspec.h" | ||||||
|  | #include "attr.h" | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Finds which of the given pathspecs match items in the index. |  * Finds which of the given pathspecs match items in the index. | ||||||
|  | @ -72,6 +73,7 @@ static struct pathspec_magic { | ||||||
| 	{ PATHSPEC_GLOB,    '\0', "glob" }, | 	{ PATHSPEC_GLOB,    '\0', "glob" }, | ||||||
| 	{ PATHSPEC_ICASE,   '\0', "icase" }, | 	{ PATHSPEC_ICASE,   '\0', "icase" }, | ||||||
| 	{ PATHSPEC_EXCLUDE,  '!', "exclude" }, | 	{ PATHSPEC_EXCLUDE,  '!', "exclude" }, | ||||||
|  | 	{ PATHSPEC_ATTR,    '\0', "attr" }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) | static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) | ||||||
|  | @ -87,6 +89,72 @@ static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) | ||||||
| 	strbuf_addf(sb, ",prefix:%d)", prefixlen); | 	strbuf_addf(sb, ",prefix:%d)", prefixlen); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value) | ||||||
|  | { | ||||||
|  | 	struct string_list_item *si; | ||||||
|  | 	struct string_list list = STRING_LIST_INIT_DUP; | ||||||
|  |  | ||||||
|  | 	if (item->attr_check || item->attr_match) | ||||||
|  | 		die(_("Only one 'attr:' specification is allowed.")); | ||||||
|  |  | ||||||
|  | 	if (!value || !*value) | ||||||
|  | 		die(_("attr spec must not be empty")); | ||||||
|  |  | ||||||
|  | 	string_list_split(&list, value, ' ', -1); | ||||||
|  | 	string_list_remove_empty_items(&list, 0); | ||||||
|  |  | ||||||
|  | 	item->attr_check = attr_check_alloc(); | ||||||
|  | 	item->attr_match = xcalloc(list.nr, sizeof(struct attr_match)); | ||||||
|  |  | ||||||
|  | 	for_each_string_list_item(si, &list) { | ||||||
|  | 		size_t attr_len; | ||||||
|  | 		char *attr_name; | ||||||
|  | 		const struct git_attr *a; | ||||||
|  |  | ||||||
|  | 		int j = item->attr_match_nr++; | ||||||
|  | 		const char *attr = si->string; | ||||||
|  | 		struct attr_match *am = &item->attr_match[j]; | ||||||
|  |  | ||||||
|  | 		switch (*attr) { | ||||||
|  | 		case '!': | ||||||
|  | 			am->match_mode = MATCH_UNSPECIFIED; | ||||||
|  | 			attr++; | ||||||
|  | 			attr_len = strlen(attr); | ||||||
|  | 			break; | ||||||
|  | 		case '-': | ||||||
|  | 			am->match_mode = MATCH_UNSET; | ||||||
|  | 			attr++; | ||||||
|  | 			attr_len = strlen(attr); | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			attr_len = strcspn(attr, "="); | ||||||
|  | 			if (attr[attr_len] != '=') | ||||||
|  | 				am->match_mode = MATCH_SET; | ||||||
|  | 			else { | ||||||
|  | 				am->match_mode = MATCH_VALUE; | ||||||
|  | 				am->value = xstrdup(&attr[attr_len + 1]); | ||||||
|  | 				if (strchr(am->value, '\\')) | ||||||
|  | 					die(_("attr spec values must not contain backslashes")); | ||||||
|  | 			} | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		attr_name = xmemdupz(attr, attr_len); | ||||||
|  | 		a = git_attr(attr_name); | ||||||
|  | 		if (!a) | ||||||
|  | 			die(_("invalid attribute name %s"), attr_name); | ||||||
|  |  | ||||||
|  | 		attr_check_append(item->attr_check, a); | ||||||
|  |  | ||||||
|  | 		free(attr_name); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (item->attr_check->nr != item->attr_match_nr) | ||||||
|  | 		die("BUG: should have same number of entries"); | ||||||
|  |  | ||||||
|  | 	string_list_clear(&list, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
| static inline int get_literal_global(void) | static inline int get_literal_global(void) | ||||||
| { | { | ||||||
| 	static int literal = -1; | 	static int literal = -1; | ||||||
|  | @ -164,6 +232,7 @@ static int get_global_magic(int element_magic) | ||||||
|  * returns the position in 'elem' after all magic has been parsed |  * returns the position in 'elem' after all magic has been parsed | ||||||
|  */ |  */ | ||||||
| static const char *parse_long_magic(unsigned *magic, int *prefix_len, | static const char *parse_long_magic(unsigned *magic, int *prefix_len, | ||||||
|  | 				    struct pathspec_item *item, | ||||||
| 				    const char *elem) | 				    const char *elem) | ||||||
| { | { | ||||||
| 	const char *pos; | 	const char *pos; | ||||||
|  | @ -189,6 +258,14 @@ static const char *parse_long_magic(unsigned *magic, int *prefix_len, | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if (starts_with(pos, "attr:")) { | ||||||
|  | 			char *attr_body = xmemdupz(pos + 5, len - 5); | ||||||
|  | 			parse_pathspec_attr_match(item, attr_body); | ||||||
|  | 			*magic |= PATHSPEC_ATTR; | ||||||
|  | 			free(attr_body); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { | 		for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { | ||||||
| 			if (strlen(pathspec_magic[i].name) == len && | 			if (strlen(pathspec_magic[i].name) == len && | ||||||
| 			    !strncmp(pathspec_magic[i].name, pos, len)) { | 			    !strncmp(pathspec_magic[i].name, pos, len)) { | ||||||
|  | @ -252,13 +329,14 @@ static const char *parse_short_magic(unsigned *magic, const char *elem) | ||||||
| } | } | ||||||
|  |  | ||||||
| static const char *parse_element_magic(unsigned *magic, int *prefix_len, | static const char *parse_element_magic(unsigned *magic, int *prefix_len, | ||||||
|  | 				       struct pathspec_item *item, | ||||||
| 				       const char *elem) | 				       const char *elem) | ||||||
| { | { | ||||||
| 	if (elem[0] != ':' || get_literal_global()) | 	if (elem[0] != ':' || get_literal_global()) | ||||||
| 		return elem; /* nothing to do */ | 		return elem; /* nothing to do */ | ||||||
| 	else if (elem[1] == '(') | 	else if (elem[1] == '(') | ||||||
| 		/* longhand */ | 		/* longhand */ | ||||||
| 		return parse_long_magic(magic, prefix_len, elem); | 		return parse_long_magic(magic, prefix_len, item, elem); | ||||||
| 	else | 	else | ||||||
| 		/* shorthand */ | 		/* shorthand */ | ||||||
| 		return parse_short_magic(magic, elem); | 		return parse_short_magic(magic, elem); | ||||||
|  | @ -335,12 +413,17 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, | ||||||
| 	char *match; | 	char *match; | ||||||
| 	int pathspec_prefix = -1; | 	int pathspec_prefix = -1; | ||||||
|  |  | ||||||
|  | 	item->attr_check = NULL; | ||||||
|  | 	item->attr_match = NULL; | ||||||
|  | 	item->attr_match_nr = 0; | ||||||
|  |  | ||||||
| 	/* PATHSPEC_LITERAL_PATH ignores magic */ | 	/* PATHSPEC_LITERAL_PATH ignores magic */ | ||||||
| 	if (flags & PATHSPEC_LITERAL_PATH) { | 	if (flags & PATHSPEC_LITERAL_PATH) { | ||||||
| 		magic = PATHSPEC_LITERAL; | 		magic = PATHSPEC_LITERAL; | ||||||
| 	} else { | 	} else { | ||||||
| 		copyfrom = parse_element_magic(&element_magic, | 		copyfrom = parse_element_magic(&element_magic, | ||||||
| 					       &pathspec_prefix, | 					       &pathspec_prefix, | ||||||
|  | 					       item, | ||||||
| 					       elt); | 					       elt); | ||||||
| 		magic |= element_magic; | 		magic |= element_magic; | ||||||
| 		magic |= get_global_magic(element_magic); | 		magic |= get_global_magic(element_magic); | ||||||
|  | @ -565,26 +648,46 @@ void parse_pathspec(struct pathspec *pathspec, | ||||||
|  |  | ||||||
| void copy_pathspec(struct pathspec *dst, const struct pathspec *src) | void copy_pathspec(struct pathspec *dst, const struct pathspec *src) | ||||||
| { | { | ||||||
| 	int i; | 	int i, j; | ||||||
|  |  | ||||||
| 	*dst = *src; | 	*dst = *src; | ||||||
| 	ALLOC_ARRAY(dst->items, dst->nr); | 	ALLOC_ARRAY(dst->items, dst->nr); | ||||||
| 	COPY_ARRAY(dst->items, src->items, dst->nr); | 	COPY_ARRAY(dst->items, src->items, dst->nr); | ||||||
|  |  | ||||||
| 	for (i = 0; i < dst->nr; i++) { | 	for (i = 0; i < dst->nr; i++) { | ||||||
| 		dst->items[i].match = xstrdup(src->items[i].match); | 		struct pathspec_item *d = &dst->items[i]; | ||||||
| 		dst->items[i].original = xstrdup(src->items[i].original); | 		struct pathspec_item *s = &src->items[i]; | ||||||
|  |  | ||||||
|  | 		d->match = xstrdup(s->match); | ||||||
|  | 		d->original = xstrdup(s->original); | ||||||
|  |  | ||||||
|  | 		ALLOC_ARRAY(d->attr_match, d->attr_match_nr); | ||||||
|  | 		COPY_ARRAY(d->attr_match, s->attr_match, d->attr_match_nr); | ||||||
|  | 		for (j = 0; j < d->attr_match_nr; j++) { | ||||||
|  | 			const char *value = s->attr_match[j].value; | ||||||
|  | 			d->attr_match[j].value = xstrdup_or_null(value); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		d->attr_check = attr_check_dup(s->attr_check); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void clear_pathspec(struct pathspec *pathspec) | void clear_pathspec(struct pathspec *pathspec) | ||||||
| { | { | ||||||
| 	int i; | 	int i, j; | ||||||
|  |  | ||||||
| 	for (i = 0; i < pathspec->nr; i++) { | 	for (i = 0; i < pathspec->nr; i++) { | ||||||
| 		free(pathspec->items[i].match); | 		free(pathspec->items[i].match); | ||||||
| 		free(pathspec->items[i].original); | 		free(pathspec->items[i].original); | ||||||
|  |  | ||||||
|  | 		for (j = 0; j < pathspec->items[j].attr_match_nr; j++) | ||||||
|  | 			free(pathspec->items[i].attr_match[j].value); | ||||||
|  | 		free(pathspec->items[i].attr_match); | ||||||
|  |  | ||||||
|  | 		if (pathspec->items[i].attr_check) | ||||||
|  | 			attr_check_free(pathspec->items[i].attr_check); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	free(pathspec->items); | 	free(pathspec->items); | ||||||
| 	pathspec->items = NULL; | 	pathspec->items = NULL; | ||||||
| 	pathspec->nr = 0; | 	pathspec->nr = 0; | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								pathspec.h
								
								
								
								
							
							
						
						
									
										15
									
								
								pathspec.h
								
								
								
								
							|  | @ -8,13 +8,15 @@ | ||||||
| #define PATHSPEC_GLOB		(1<<3) | #define PATHSPEC_GLOB		(1<<3) | ||||||
| #define PATHSPEC_ICASE		(1<<4) | #define PATHSPEC_ICASE		(1<<4) | ||||||
| #define PATHSPEC_EXCLUDE	(1<<5) | #define PATHSPEC_EXCLUDE	(1<<5) | ||||||
|  | #define PATHSPEC_ATTR		(1<<6) | ||||||
| #define PATHSPEC_ALL_MAGIC	  \ | #define PATHSPEC_ALL_MAGIC	  \ | ||||||
| 	(PATHSPEC_FROMTOP	| \ | 	(PATHSPEC_FROMTOP	| \ | ||||||
| 	 PATHSPEC_MAXDEPTH	| \ | 	 PATHSPEC_MAXDEPTH	| \ | ||||||
| 	 PATHSPEC_LITERAL	| \ | 	 PATHSPEC_LITERAL	| \ | ||||||
| 	 PATHSPEC_GLOB		| \ | 	 PATHSPEC_GLOB		| \ | ||||||
| 	 PATHSPEC_ICASE		| \ | 	 PATHSPEC_ICASE		| \ | ||||||
| 	 PATHSPEC_EXCLUDE) | 	 PATHSPEC_EXCLUDE	| \ | ||||||
|  | 	 PATHSPEC_ATTR) | ||||||
|  |  | ||||||
| #define PATHSPEC_ONESTAR 1	/* the pathspec pattern satisfies GFNM_ONESTAR */ | #define PATHSPEC_ONESTAR 1	/* the pathspec pattern satisfies GFNM_ONESTAR */ | ||||||
|  |  | ||||||
|  | @ -31,6 +33,17 @@ struct pathspec { | ||||||
| 		int len, prefix; | 		int len, prefix; | ||||||
| 		int nowildcard_len; | 		int nowildcard_len; | ||||||
| 		int flags; | 		int flags; | ||||||
|  | 		int attr_match_nr; | ||||||
|  | 		struct attr_match { | ||||||
|  | 			char *value; | ||||||
|  | 			enum attr_match_mode { | ||||||
|  | 				MATCH_SET, | ||||||
|  | 				MATCH_UNSET, | ||||||
|  | 				MATCH_VALUE, | ||||||
|  | 				MATCH_UNSPECIFIED | ||||||
|  | 			} match_mode; | ||||||
|  | 		} *attr_match; | ||||||
|  | 		struct attr_check *attr_check; | ||||||
| 	} *items; | 	} *items; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,181 @@ | ||||||
|  | #!/bin/sh | ||||||
|  |  | ||||||
|  | test_description='test labels in pathspecs' | ||||||
|  | . ./test-lib.sh | ||||||
|  |  | ||||||
|  | test_expect_success 'setup a tree' ' | ||||||
|  | 	cat <<-\EOF >expect && | ||||||
|  | 	fileA | ||||||
|  | 	fileAB | ||||||
|  | 	fileAC | ||||||
|  | 	fileB | ||||||
|  | 	fileBC | ||||||
|  | 	fileC | ||||||
|  | 	fileNoLabel | ||||||
|  | 	fileSetLabel | ||||||
|  | 	fileUnsetLabel | ||||||
|  | 	fileValue | ||||||
|  | 	fileWrongLabel | ||||||
|  | 	sub/fileA | ||||||
|  | 	sub/fileAB | ||||||
|  | 	sub/fileAC | ||||||
|  | 	sub/fileB | ||||||
|  | 	sub/fileBC | ||||||
|  | 	sub/fileC | ||||||
|  | 	sub/fileNoLabel | ||||||
|  | 	sub/fileSetLabel | ||||||
|  | 	sub/fileUnsetLabel | ||||||
|  | 	sub/fileValue | ||||||
|  | 	sub/fileWrongLabel | ||||||
|  | 	EOF | ||||||
|  | 	mkdir sub && | ||||||
|  | 	while read path | ||||||
|  | 	do | ||||||
|  | 		: >$path && | ||||||
|  | 		git add $path || return 1 | ||||||
|  | 	done <expect && | ||||||
|  | 	git commit -m "initial commit" && | ||||||
|  | 	git ls-files >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'pathspec with no attr' ' | ||||||
|  | 	test_must_fail git ls-files ":(attr:)" | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'pathspec with labels and non existent .gitattributes' ' | ||||||
|  | 	git ls-files ":(attr:label)" >actual && | ||||||
|  | 	test_must_be_empty actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'setup .gitattributes' ' | ||||||
|  | 	cat <<-\EOF >.gitattributes && | ||||||
|  | 	fileA labelA | ||||||
|  | 	fileB labelB | ||||||
|  | 	fileC labelC | ||||||
|  | 	fileAB labelA labelB | ||||||
|  | 	fileAC labelA labelC | ||||||
|  | 	fileBC labelB labelC | ||||||
|  | 	fileUnsetLabel -label | ||||||
|  | 	fileSetLabel label | ||||||
|  | 	fileValue label=foo | ||||||
|  | 	fileWrongLabel label☺ | ||||||
|  | 	EOF | ||||||
|  | 	git add .gitattributes && | ||||||
|  | 	git commit -m "add attributes" | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'check specific set attr' ' | ||||||
|  | 	cat <<-\EOF >expect && | ||||||
|  | 	fileSetLabel | ||||||
|  | 	sub/fileSetLabel | ||||||
|  | 	EOF | ||||||
|  | 	git ls-files ":(attr:label)" >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'check specific unset attr' ' | ||||||
|  | 	cat <<-\EOF >expect && | ||||||
|  | 	fileUnsetLabel | ||||||
|  | 	sub/fileUnsetLabel | ||||||
|  | 	EOF | ||||||
|  | 	git ls-files ":(attr:-label)" >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'check specific value attr' ' | ||||||
|  | 	cat <<-\EOF >expect && | ||||||
|  | 	fileValue | ||||||
|  | 	sub/fileValue | ||||||
|  | 	EOF | ||||||
|  | 	git ls-files ":(attr:label=foo)" >actual && | ||||||
|  | 	test_cmp expect actual && | ||||||
|  | 	git ls-files ":(attr:label=bar)" >actual && | ||||||
|  | 	test_must_be_empty actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'check unspecified attr' ' | ||||||
|  | 	cat <<-\EOF >expect && | ||||||
|  | 	.gitattributes | ||||||
|  | 	fileA | ||||||
|  | 	fileAB | ||||||
|  | 	fileAC | ||||||
|  | 	fileB | ||||||
|  | 	fileBC | ||||||
|  | 	fileC | ||||||
|  | 	fileNoLabel | ||||||
|  | 	fileWrongLabel | ||||||
|  | 	sub/fileA | ||||||
|  | 	sub/fileAB | ||||||
|  | 	sub/fileAC | ||||||
|  | 	sub/fileB | ||||||
|  | 	sub/fileBC | ||||||
|  | 	sub/fileC | ||||||
|  | 	sub/fileNoLabel | ||||||
|  | 	sub/fileWrongLabel | ||||||
|  | 	EOF | ||||||
|  | 	git ls-files ":(attr:!label)" >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'check multiple unspecified attr' ' | ||||||
|  | 	cat <<-\EOF >expect && | ||||||
|  | 	.gitattributes | ||||||
|  | 	fileC | ||||||
|  | 	fileNoLabel | ||||||
|  | 	fileWrongLabel | ||||||
|  | 	sub/fileC | ||||||
|  | 	sub/fileNoLabel | ||||||
|  | 	sub/fileWrongLabel | ||||||
|  | 	EOF | ||||||
|  | 	git ls-files ":(attr:!labelB !labelA !label)" >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'check label with more labels but excluded path' ' | ||||||
|  | 	cat <<-\EOF >expect && | ||||||
|  | 	fileAB | ||||||
|  | 	fileB | ||||||
|  | 	fileBC | ||||||
|  | 	EOF | ||||||
|  | 	git ls-files ":(attr:labelB)" ":(exclude)sub/" >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'check label excluding other labels' ' | ||||||
|  | 	cat <<-\EOF >expect && | ||||||
|  | 	fileAB | ||||||
|  | 	fileB | ||||||
|  | 	fileBC | ||||||
|  | 	sub/fileAB | ||||||
|  | 	sub/fileB | ||||||
|  | 	EOF | ||||||
|  | 	git ls-files ":(attr:labelB)" ":(exclude,attr:labelC)sub/" >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'fail on multiple attr specifiers in one pathspec item' ' | ||||||
|  | 	test_must_fail git ls-files . ":(attr:labelB,attr:labelC)" 2>actual && | ||||||
|  | 	test_i18ngrep "Only one" actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'fail if attr magic is used places not implemented' ' | ||||||
|  | 	# The main purpose of this test is to check that we actually fail | ||||||
|  | 	# when you attempt to use attr magic in commands that do not implement | ||||||
|  | 	# attr magic. This test does not advocate git-add to stay that way, | ||||||
|  | 	# though, but git-add is convenient as it has its own internal pathspec | ||||||
|  | 	# parsing. | ||||||
|  | 	test_must_fail git add ":(attr:labelB)" 2>actual && | ||||||
|  | 	test_i18ngrep "unsupported magic" actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'abort on giving invalid label on the command line' ' | ||||||
|  | 	test_must_fail git ls-files . ":(attr:☺)" | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'abort on asking for wrong magic' ' | ||||||
|  | 	test_must_fail git ls-files . ":(attr:-label=foo)" && | ||||||
|  | 	test_must_fail git ls-files . ":(attr:!label=foo)" | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_done | ||||||
		Loading…
	
		Reference in New Issue
	
	 Brandon Williams
						Brandon Williams