Labels were originally designed to manage large amount of submodules, the discussion steered this in a more general direction, such that other files can be labeled as well. Labels are meant to describe arbitrary set of files, which is not described via the tree layout. Signed-off-by: Stefan Beller <sbeller@xxxxxxxxxx> --- Documentation/glossary-content.txt | 5 +++ attr.h | 1 + dir.c | 31 +++++++++++++ pathspec.c | 24 +++++++++- pathspec.h | 1 + t/t6134-pathspec-with-labels.sh | 91 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100755 t/t6134-pathspec-with-labels.sh diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 8ad29e6..a1fc9e0 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -362,6 +362,11 @@ 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: diff --git a/attr.h b/attr.h index 8b08d33..f6fc7c3 100644 --- a/attr.h +++ b/attr.h @@ -18,6 +18,7 @@ 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 656f272..51d5965 100644 --- a/dir.c +++ b/dir.c @@ -9,6 +9,7 @@ */ #include "cache.h" #include "dir.h" +#include "attr.h" #include "refs.h" #include "wildmatch.h" #include "pathspec.h" @@ -208,6 +209,27 @@ int within_depth(const char *name, int namelen, return 1; } +void load_labels(const char *name, int namelen, struct string_list *list) +{ + static struct git_attr *attr; + struct git_attr_check check; + char *path = xmemdupz(name, namelen); + + if (!attr) + attr = git_attr("label"); + check.attr = attr; + + 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); + } + + free(path); +} + #define DO_MATCH_EXCLUDE 1 #define DO_MATCH_DIRECTORY 2 @@ -263,6 +285,15 @@ 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 the match was just the prefix, we matched */ if (!*match) return MATCHED_RECURSIVELY; diff --git a/pathspec.c b/pathspec.c index 4dff252..c227c25 100644 --- a/pathspec.c +++ b/pathspec.c @@ -94,6 +94,7 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt, { int i; const char *copyfrom = *copyfrom_; + const char *out; /* longhand */ const char *nextat; for (copyfrom = elt + 2; @@ -117,6 +118,20 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt, continue; } + if (skip_prefix(copyfrom, "label:", &out)) { + 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_release(&sb); + continue; + } + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { if (strlen(pathspec_magic[i].name) == len && !strncmp(pathspec_magic[i].name, copyfrom, len)) { @@ -425,7 +440,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].magic = prefix_pathspec(item + i, &short_magic, argv + i, flags, prefix, prefixlen, entry); @@ -502,6 +517,13 @@ void copy_pathspec(struct pathspec *dst, const struct pathspec *src) 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); + } + free(pathspec->items); pathspec->items = NULL; } diff --git a/pathspec.h b/pathspec.h index 0c11262..e3f7ebf 100644 --- a/pathspec.h +++ b/pathspec.h @@ -32,6 +32,7 @@ struct pathspec { int len, prefix; int nowildcard_len; int flags; + struct string_list *group; } *items; }; diff --git a/t/t6134-pathspec-with-labels.sh b/t/t6134-pathspec-with-labels.sh new file mode 100755 index 0000000..0c061ce --- /dev/null +++ b/t/t6134-pathspec-with-labels.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +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 && + : >$p && + git add $p && + git commit -m $p + done && + git log --oneline --format=%s >actual && + cat <<EOF >expect && +sub2/file +sub/sub/sub/file +sub/file2 +sub/sub/file +sub/file +file +EOF + test_cmp expect actual +' + +test_expect_success 'pathspec with labels and no .gitattributes exists' ' + 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 + 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 && + 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_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_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 && + 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_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_cmp expect actual +' +test_done -- 2.8.2.400.g66c4903.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