[PATCHv4 0/5] pathspec labels

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Thanks Junio for the review!
Thanks Duy for suggestions to think about in the not-submodule case :)

* when invalid labels are found
  -> in the .gitattributes "warn and ignore"
  -> in command line args die(..)
* treat labels set to false as unset.
* fixes in documentation/ reworded the commit message

Thanks,
Stefan

diff to current origin/sb/pathspec-label (v2 of the series, so a lot):

diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index af2c682..b846848 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -906,6 +906,18 @@ If this attribute is not set or has an invalid value, the value of the
 (See linkgit:git-config[1]).
 
 
+Attaching labels to path
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+`label`
+^^^^^^^
+By the value of this attribute you can specify a list of arbitrary strings
+to be attached to a path as labels. These labels can be used for
+easier paths matching using pathspecs (linkgit:gitglossary[1]).
+It is required to use only alphanumeric characters, dashes and
+underscores for the labels.
+
+
 USING MACRO ATTRIBUTES
 ----------------------
 
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index a1fc9e0..e264a58 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -362,11 +362,6 @@ glob;;
 	For example, "Documentation/{asterisk}.html" matches
 	"Documentation/git.html" but not "Documentation/ppc/ppc.html"
 	or "tools/perf/Documentation/perf.html".
-
-label:<white space separated list>;;
-	Labels can be assigned to pathspecs in the .gitattributes file.
-	By specifying a list of labels the pattern will match only
-	files which have all of the listed labels.
 +
 Two consecutive asterisks ("`**`") in patterns matched against
 full pathname may have special meaning:
@@ -389,6 +384,10 @@ full pathname may have special meaning:
 +
 Glob magic is incompatible with literal magic.
 
+label=<white space separated list>;;
+	Additionally to matching the pathspec, the path must have a 'label'
+	attribute having set all labels listed here.
+
 exclude;;
 	After a path matches any non-exclude pathspec, it will be run
 	through all exclude pathspec (magic signature: `!`). If it
diff --git a/attr.h b/attr.h
index f6fc7c3..8b08d33 100644
--- a/attr.h
+++ b/attr.h
@@ -18,7 +18,6 @@ extern const char git_attr__false[];
 #define ATTR_TRUE(v) ((v) == git_attr__true)
 #define ATTR_FALSE(v) ((v) == git_attr__false)
 #define ATTR_UNSET(v) ((v) == NULL)
-#define ATTR_CUSTOM(v) (!(ATTR_UNSET(v) || ATTR_FALSE(v) || ATTR_TRUE(v)))
 
 /*
  * Send one or more git_attr_check to git_check_attr(), and
diff --git a/dir.c b/dir.c
index 51d5965..fd39f92 100644
--- a/dir.c
+++ b/dir.c
@@ -209,25 +209,56 @@ int within_depth(const char *name, int namelen,
 	return 1;
 }
 
-void load_labels(const char *name, int namelen, struct string_list *list)
+static int has_all_labels(const char *name, int namelen,
+			   const struct string_list *required_labels)
 {
 	static struct git_attr *attr;
+
 	struct git_attr_check check;
-	char *path = xmemdupz(name, namelen);
+	char *path;
+	int ret;
 
 	if (!attr)
 		attr = git_attr("label");
 	check.attr = attr;
 
+	path = xmemdupz(name, namelen);
 	if (git_check_attr(path, 1, &check))
-		die("git_check_attr died");
-
-	if (ATTR_CUSTOM(check.value)) {
-		string_list_split(list, check.value, ',', -1);
-		string_list_sort(list);
+		die("internal bug: git_check_attr died.");
+
+	if (ATTR_TRUE(check.value))
+		ret = 1; /* has all the labels */
+	else if (ATTR_FALSE(check.value)) {
+		warning(_("Path '%s': Label must not be false. Treat as if no label was set."), path);
+		ret = 0;
+	} else if (ATTR_UNSET(check.value))
+		ret = 0; /* has no labels */
+	else {
+		struct string_list_item *si;
+		struct string_list attr_labels = STRING_LIST_INIT_DUP;
+		string_list_split(&attr_labels, check.value, ',', -1);
+		for_each_string_list_item(si, &attr_labels) {
+			if (validate_label_name(si->string)) {
+				warning(_("Ignoring label '%s'"), si->string);
+				free(si->string);
+				si->string = "";
+			}
+		}
+		string_list_remove_empty_items(&attr_labels, 0);
+		string_list_sort(&attr_labels);
+		ret = 1;
+		for_each_string_list_item(si, required_labels) {
+			if (!string_list_has_string(&attr_labels, si->string)) {
+				ret = 0;
+				break;
+			}
+		}
+		string_list_clear(&attr_labels, 0);
 	}
 
 	free(path);
+
+	return ret;
 }
 
 #define DO_MATCH_EXCLUDE   1
@@ -285,14 +316,8 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
 	    strncmp(item->match, name - prefix, item->prefix))
 		return 0;
 
-	if (item->group) {
-		struct string_list has_labels = STRING_LIST_INIT_DUP;
-		struct string_list_item *si;
-		load_labels(name, namelen, &has_labels);
-		for_each_string_list_item(si, item->group)
-			if (!string_list_has_string(&has_labels, si->string))
-				return 0;
-	}
+	if (item->labels && !has_all_labels(name, namelen, item->labels))
+		return 0;
 
 	/* If the match was just the prefix, we matched */
 	if (!*match)
diff --git a/pathspec.c b/pathspec.c
index c227c25..2f053fb 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -88,13 +88,42 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,
 	strbuf_addf(sb, ",prefix:%d)", prefixlen);
 }
 
+static int check_valid_label_name(const char *label)
+{
+	if (!label || !strlen(label))
+		return -1;
+
+	if (!isalpha(*label))
+		return -1;
+
+	while (*label) {
+		if (!(isalnum(*label) ||
+			*label == '-' ||
+			*label == '_'))
+			return -1;
+		label++;
+	}
+
+	return 0;
+}
+
+int validate_label_name(const char *label)
+{
+	int ret = check_valid_label_name(label);
+	if (ret)
+		warning(_("Label '%s': Label names must start with an "
+			  "alphabetic character and use only alphanumeric "
+			  "characters, dashes and underscores."), label);
+	return ret;
+}
+
 static void eat_long_magic(struct pathspec_item *item, const char *elt,
 		unsigned *magic, int *pathspec_prefix,
 		const char **copyfrom_, const char **long_magic_end)
 {
 	int i;
 	const char *copyfrom = *copyfrom_;
-	const char *out;
+	const char *body;
 	/* longhand */
 	const char *nextat;
 	for (copyfrom = elt + 2;
@@ -109,25 +138,24 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt,
 		if (!len)
 			continue;
 
-		if (starts_with(copyfrom, "prefix:")) {
+		if (skip_prefix(copyfrom, "prefix:", &body)) {
 			char *endptr;
-			*pathspec_prefix = strtol(copyfrom + 7,
-						  &endptr, 10);
+			*pathspec_prefix = strtol(body, &endptr, 10);
 			if (endptr - copyfrom != len)
 				die(_("invalid parameter for pathspec magic 'prefix'"));
 			continue;
 		}
 
-		if (skip_prefix(copyfrom, "label:", &out)) {
+		if (skip_prefix(copyfrom, "label=", &body)) {
 			struct strbuf sb = STRBUF_INIT;
-			size_t l = nextat - out;
-			strbuf_add(&sb, out, l);
-			if (!item->group) {
-				item->group = xmalloc(sizeof(*item->group));
-				string_list_init(item->group, 1);
-			}
-			string_list_split(item->group, sb.buf, ' ', -1);
-			string_list_remove_empty_items(item->group, 0);
+			strbuf_add(&sb, body, nextat - body);
+			if (!item->labels) {
+				item->labels = xmalloc(sizeof(*item->labels));
+				string_list_init(item->labels, 1);
+			} else
+				die(_("multiple labels not supported in pathspec magic"));
+			string_list_split(item->labels, sb.buf, ' ', -1);
+			string_list_remove_empty_items(item->labels, 0);
 			strbuf_release(&sb);
 			continue;
 		}
@@ -440,7 +468,7 @@ void parse_pathspec(struct pathspec *pathspec,
 	for (i = 0; i < n; i++) {
 		unsigned short_magic;
 		entry = argv[i];
-		item[i].group = NULL;
+		item[i].labels = NULL;
 		item[i].magic = prefix_pathspec(item + i, &short_magic,
 						argv + i, flags,
 						prefix, prefixlen, entry);
@@ -462,6 +490,13 @@ void parse_pathspec(struct pathspec *pathspec,
 		if (item[i].nowildcard_len < item[i].len)
 			pathspec->has_wildcard = 1;
 		pathspec->magic |= item[i].magic;
+
+		if (item[i].labels) {
+			struct string_list_item *si;
+			for_each_string_list_item(si, item[i].labels)
+				if (validate_label_name(si->string))
+					die(_("Labels in the wrong syntax are prohibited."));
+		}
 	}
 
 	if (nr_exclude == n)
@@ -519,9 +554,9 @@ void free_pathspec(struct pathspec *pathspec)
 {
 	int i;
 	for (i = 0; i < pathspec->nr; i++) {
-		if (pathspec->items[i].group)
-			string_list_clear(pathspec->items[i].group, 1);
-		free(pathspec->items[i].group);
+		if (pathspec->items[i].labels)
+			string_list_clear(pathspec->items[i].labels, 1);
+		free(pathspec->items[i].labels);
 	}
 
 	free(pathspec->items);
diff --git a/pathspec.h b/pathspec.h
index e3f7ebf..bf02931 100644
--- a/pathspec.h
+++ b/pathspec.h
@@ -32,7 +32,7 @@ struct pathspec {
 		int len, prefix;
 		int nowildcard_len;
 		int flags;
-		struct string_list *group;
+		struct string_list *labels;
 	} *items;
 };
 
@@ -99,5 +99,6 @@ extern char *find_pathspecs_matching_against_index(const struct pathspec *pathsp
 extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen);
 extern const char *check_path_for_gitlink(const char *path);
 extern void die_if_path_beyond_symlink(const char *path, const char *prefix);
+extern int validate_label_name(const char *label);
 
 #endif /* PATHSPEC_H */
diff --git a/t/t6134-pathspec-with-labels.sh b/t/t6134-pathspec-with-labels.sh
index 0c061ce..e2e4753 100755
--- a/t/t6134-pathspec-with-labels.sh
+++ b/t/t6134-pathspec-with-labels.sh
@@ -4,88 +4,155 @@ test_description='test labels in pathspecs'
 . ./test-lib.sh
 
 test_expect_success 'setup a tree' '
-	for p in file sub/file sub/sub/file sub/file2 sub/sub/sub/file sub2/file; do
-		if echo $p | grep /; then
-			mkdir -p $(dirname $p)
-		fi &&
+	mkdir sub &&
+	for p in fileA fileB fileC fileAB fileAC fileBC fileNoLabel fileUnsetLabel fileSetLabel; do
 		: >$p &&
 		git add $p &&
-		git commit -m $p
+		: >sub/$p
+		git add sub/$p
 	done &&
-	git log --oneline --format=%s >actual &&
+	git commit -m $p &&
+	git ls-files >actual &&
 	cat <<EOF >expect &&
-sub2/file
-sub/sub/sub/file
-sub/file2
-sub/sub/file
-sub/file
-file
+fileA
+fileAB
+fileAC
+fileB
+fileBC
+fileC
+fileNoLabel
+fileSetLabel
+fileUnsetLabel
+sub/fileA
+sub/fileAB
+sub/fileAC
+sub/fileB
+sub/fileBC
+sub/fileC
+sub/fileNoLabel
+sub/fileSetLabel
+sub/fileUnsetLabel
 EOF
 	test_cmp expect actual
 '
 
-test_expect_success 'pathspec with labels and no .gitattributes exists' '
-	git ls-files ":(label:a)" >actual &&
+test_expect_success 'pathspec with no label and non existent .gitattributes' '
+	git ls-files ":(label=)" >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success 'pathspec with labels and non existent .gitattributes' '
+	git ls-files ":(label=a)" >actual &&
 	test_must_be_empty actual
 '
 
 test_expect_success 'setup .gitattributes' '
-	cat <<-EOF >.gitattributes &&
-	/file label=b
-	sub/file label=a
-	sub/sub/* label=b,c
-	EOF
+	cat <<EOF >.gitattributes &&
+fileA label=labelA
+fileB label=labelB
+fileC label=labelC
+fileAB label=labelA,labelB
+fileAC label=labelA,labelC
+fileBC label=labelB,labelC
+fileUnsetLabel -label
+fileSetLabel label
+EOF
 	git add .gitattributes &&
 	git commit -m "add attributes"
 '
 
-test_expect_success 'check label' '
-	cat <<-EOF >expect &&
-	sub/file
-	EOF
-	git ls-files ":(label:a)" >actual &&
+sq="'"
+
+test_expect_success 'check for any label' '
+	cat <<EOF >expect &&
+fileA
+fileAB
+fileAC
+fileB
+fileBC
+fileC
+fileSetLabel
+sub/fileA
+sub/fileAB
+sub/fileAC
+sub/fileB
+sub/fileBC
+sub/fileC
+sub/fileSetLabel
+EOF
+	cat <<EOF >expect2 &&
+warning: Path ${sq}fileUnsetLabel${sq}: Label must not be unset
+warning: Path ${sq}sub/fileUnsetLabel${sq}: Label must not be unset
+EOF
+	git ls-files ":(label=)" >actual 2>actual2 &&
+	test_cmp expect actual &&
+	test_cmp expect2 actual2
+
+'
+
+test_expect_success 'check specific label' '
+	cat <<EOF >expect &&
+fileA
+fileAB
+fileAC
+fileSetLabel
+sub/fileA
+sub/fileAB
+sub/fileAC
+sub/fileSetLabel
+EOF
+	git ls-files ":(label=labelA)" >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'check label from label list' '
-	cat <<-EOF >expect &&
-	sub/sub/file
-	EOF
-	git ls-files ":(label:c)" >actual &&
+test_expect_success 'check label with 2 labels' '
+	cat <<EOF >expect &&
+fileAB
+fileSetLabel
+sub/fileAB
+sub/fileSetLabel
+EOF
+	git ls-files ":(label=labelA labelB)" >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'check label with more labels' '
-	cat <<-EOF >expect &&
-	file
-	sub/sub/file
-	EOF
-	git ls-files ":(label:b)" >actual &&
-	test_cmp expect actual
+	test_must_fail git ls-files ":(label=labelA,label=labelB)" 2>actual &&
+	test_i18ngrep "not supported" actual
 '
 
 test_expect_success 'check label with more labels but excluded path' '
-	cat <<-EOF >expect &&
-	sub/sub/file
-	EOF
-	git ls-files ":(label:b)" ":(exclude)./file" >actual &&
+	cat <<EOF >expect &&
+fileAB
+fileB
+fileBC
+fileSetLabel
+EOF
+	git ls-files ":(label=labelB)" ":(exclude)sub/" >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'check label specifying more labels' '
-	cat <<-EOF >expect &&
-	sub/sub/file
-	EOF
-	git ls-files ":(label:b c)" >actual &&
+test_expect_success 'check label excluding other labels' '
+	cat <<EOF >expect &&
+fileAB
+fileB
+fileBC
+fileSetLabel
+sub/fileAB
+sub/fileB
+EOF
+	git ls-files ":(label=labelB)" ":(exclude,label=labelC)sub/" >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'check label specifying more labels' '
-	cat <<-EOF >expect &&
-	sub/file
-	sub/sub/file
-	EOF
-	git ls-files ":(label:b c)" ":(label:a)" >actual &&
+test_expect_success 'check for paths with no label' '
+	cat <<EOF >expect &&
+.gitattributes
+fileNoLabel
+sub/fileNoLabel
+EOF
+	git ls-files . ":(exclude,label=)" >actual &&
 	test_cmp expect actual
 '
+
 test_done



Stefan Beller (5):
  Documentation: fix a typo
  Documentation: correct typo in example for querying attributes
  pathspec: move long magic parsing out of prefix_pathspec
  pathspec: move prefix check out of the inner loop
  pathspec: record labels

 Documentation/gitattributes.txt               |  14 ++-
 Documentation/glossary-content.txt            |   4 +
 Documentation/technical/api-gitattributes.txt |   2 +-
 dir.c                                         |  56 +++++++++
 pathspec.c                                    | 144 ++++++++++++++++-------
 pathspec.h                                    |   2 +
 t/t6134-pathspec-with-labels.sh               | 158 ++++++++++++++++++++++++++
 7 files changed, 340 insertions(+), 40 deletions(-)
 create mode 100755 t/t6134-pathspec-with-labels.sh

-- 
2.8.2.401.gb49ffe6.dirty

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]