{"thread":{"id":"44904","subject":"[PATCH v2 1/5] doc: add documentation for OPT_STRING_LIST","startedAt":"2017-01-18T00:04:44Z","lastAt":"2017-01-18T00:10:28Z","messageCount":6,"participants":["Jacob Keller"],"isPatch":true,"patchVersion":2,"patchTotal":5},"messages":[{"id":"309589","messageId":"20170118000345.31196-2-jacob.e.keller@intel.com","threadId":"44904","inReplyTo":"20170118000345.31196-1-jacob.e.keller@intel.com","subject":"[PATCH v2 1/5] doc: add documentation for OPT_STRING_LIST","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T00:03:41Z","receivedAt":"2017-01-18T00:04:44Z","isPatch":true,"sender":{"key":"jacob.e.keller@intel.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"From: Jacob Keller <jacob.keller@gmail.com>\n\nCommit c8ba16391655 (\"parse-options: add OPT_STRING_LIST helper\",\n2011-06-09) added the OPT_STRING_LIST as a way to accumulate a repeated\nlist of strings. However, this was not documented in the\napi-parse-options documentation. Add documentation now so that future\ndevelopers may learn of its existence.\n\nSigned-off-by: Jacob Keller <jacob.keller@gmail.com>\n---\n Documentation/technical/api-parse-options.txt | 5 +++++\n 1 file changed, 5 insertions(+)\n\ndiff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt\nindex 27bd701c0d68..6914f54f5f44 100644\n--- a/Documentation/technical/api-parse-options.txt\n+++ b/Documentation/technical/api-parse-options.txt\n@@ -168,6 +168,11 @@ There are some macros to easily define options:\n \tIntroduce an option with string argument.\n \tThe string argument is put into `str_var`.\n \n+`OPT_STRING_LIST(short, long, &list, arg_str, description)`::\n+\tIntroduce an option with string argument.\n+\tThe string argument is stored as an element in `&list` which must be a\n+\tstruct string_list. Reset the list using `--no-option`.\n+\n `OPT_INTEGER(short, long, &int_var, description)`::\n \tIntroduce an option with integer argument.\n \tThe integer is put into `int_var`.\n-- \n2.11.0.403.g196674b8396b\n\n"},{"id":"309590","messageId":"20170118000345.31196-6-jacob.e.keller@intel.com","threadId":"44904","inReplyTo":"20170118000345.31196-1-jacob.e.keller@intel.com","subject":"[PATCH v2 5/5] describe: teach describe negative pattern matches","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T00:03:45Z","receivedAt":"2017-01-18T00:04:49Z","isPatch":true,"sender":{"key":"jacob.e.keller@intel.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"From: Jacob Keller <jacob.keller@gmail.com>\n\nTeach git-describe the `--exclude` option which will allow specifying\na glob pattern of tags to ignore. This can be combined with the\n`--match` patterns to enable more flexibility in determining which tags\nto consider.\n\nFor example, suppose you wish to find the first official release tag\nthat contains a certain commit. If we assume that official release tags\nare of the form \"v*\" and pre-release candidates include \"*rc*\" in their\nname, we can now find the first tag that introduces commit abcdef via:\n\n  git describe --contains --match=\"v*\" --exclude=\"*rc*\"\n\nAdd documentation and tests for this change.\n\nSigned-off-by: Jacob Keller <jacob.keller@gmail.com>\n---\n Documentation/git-describe.txt |  8 ++++++++\n builtin/describe.c             | 21 +++++++++++++++++++++\n t/t6120-describe.sh            |  8 ++++++++\n 3 files changed, 37 insertions(+)\n\ndiff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt\nindex 7ad41e2f6ade..21a43b78924a 100644\n--- a/Documentation/git-describe.txt\n+++ b/Documentation/git-describe.txt\n@@ -88,6 +88,14 @@ OPTIONS\n \tpatterns will be considered. Use `--no-match` to clear and reset the\n \tlist of patterns.\n \n+--exclude <pattern>::\n+\tDo not consider tags matching the given `glob(7)` pattern, excluding\n+\tthe \"refs/tags/\" prefix. This can be used to narrow the tag space and\n+\tfind only tags matching some meaningful criteria. If given multiple\n+\ttimes, a list of patterns will be accumulated and tags matching any\n+\tof the patterns will be excluded. Use `--no-exclude` to clear and\n+\treset the list of patterns.\n+\n --always::\n \tShow uniquely abbreviated commit object as fallback.\n \ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex 5cc9e9abe798..6769446e1f57 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -29,6 +29,7 @@ static int max_candidates = 10;\n static struct hashmap names;\n static int have_util;\n static struct string_list patterns = STRING_LIST_INIT_NODUP;\n+static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP;\n static int always;\n static const char *dirty;\n \n@@ -130,6 +131,22 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi\n \t\treturn 0;\n \n \t/*\n+\t * If we're given exclude patterns, first exclude any tag which match\n+\t * any of the exclude pattern.\n+\t */\n+\tif (exclude_patterns.nr) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tif (!is_tag)\n+\t\t\treturn 0;\n+\n+\t\tfor_each_string_list_item(item, &exclude_patterns) {\n+\t\t\tif (!wildmatch(item->string, path + 10, 0, NULL))\n+\t\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\t/*\n \t * If we're given patterns, accept only tags which match at least one\n \t * pattern.\n \t */\n@@ -421,6 +438,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"consider <n> most recent tags (default: 10)\")),\n \t\tOPT_STRING_LIST(0, \"match\", &patterns, N_(\"pattern\"),\n \t\t\t   N_(\"only consider tags matching <pattern>\")),\n+\t\tOPT_STRING_LIST(0, \"exclude\", &exclude_patterns, N_(\"pattern\"),\n+\t\t\t   N_(\"do not consider tags matching <pattern>\")),\n \t\tOPT_BOOL(0, \"always\",        &always,\n \t\t\tN_(\"show abbreviated commit object as fallback\")),\n \t\t{OPTION_STRING, 0, \"dirty\",  &dirty, N_(\"mark\"),\n@@ -458,6 +477,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)\n \t\t\targv_array_push(&args, \"--tags\");\n \t\t\tfor_each_string_list_item(item, &patterns)\n \t\t\t\targv_array_pushf(&args, \"--refs=refs/tags/%s\", item->string);\n+\t\t\tfor_each_string_list_item(item, &exclude_patterns)\n+\t\t\t\targv_array_pushf(&args, \"--exclude=refs/tags/%s\", item->string);\n \t\t}\n \t\tif (argc)\n \t\t\targv_array_pushv(&args, argv);\ndiff --git a/t/t6120-describe.sh b/t/t6120-describe.sh\nindex 9e5db9b87a1f..167491fd5b0d 100755\n--- a/t/t6120-describe.sh\n+++ b/t/t6120-describe.sh\n@@ -218,6 +218,14 @@ test_expect_success 'describe --contains and --match' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'describe --exclude' '\n+\techo \"c~1\" >expect &&\n+\ttagged_commit=$(git rev-parse \"refs/tags/A^0\") &&\n+\ttest_must_fail git describe --contains --match=\"B\" $tagged_commit &&\n+\tgit describe --contains --match=\"?\" --exclude=\"A\" $tagged_commit >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'describe --contains and --no-match' '\n \techo \"A^0\" >expect &&\n \ttagged_commit=$(git rev-parse \"refs/tags/A^0\") &&\n-- \n2.11.0.403.g196674b8396b\n\n"},{"id":"309592","messageId":"20170118000345.31196-5-jacob.e.keller@intel.com","threadId":"44904","inReplyTo":"20170118000345.31196-1-jacob.e.keller@intel.com","subject":"[PATCH v2 4/5] describe: teach --match to accept multiple patterns","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T00:03:44Z","receivedAt":"2017-01-18T00:07:16Z","isPatch":true,"sender":{"key":"jacob.e.keller@intel.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"From: Jacob Keller <jacob.keller@gmail.com>\n\nTeach `--match` to be accepted multiple times, accumulating a list of\npatterns to match into a string list. Each pattern is inclusive, such\nthat a tag need only match one of the provided patterns to be\nconsidered for matching.\n\nThis extension is useful as it enables more flexibility in what tags\nmatch, and may avoid the need to run the describe command multiple\ntimes to get the same result.\n\nAdd tests and update the documentation for this change.\n\nSigned-off-by: Jacob Keller <jacob.keller@gmail.com>\n---\n Documentation/git-describe.txt |  5 ++++-\n builtin/describe.c             | 30 +++++++++++++++++++++++-------\n t/t6120-describe.sh            | 19 +++++++++++++++++++\n 3 files changed, 46 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt\nindex e4ac448ff565..7ad41e2f6ade 100644\n--- a/Documentation/git-describe.txt\n+++ b/Documentation/git-describe.txt\n@@ -83,7 +83,10 @@ OPTIONS\n --match <pattern>::\n \tOnly consider tags matching the given `glob(7)` pattern,\n \texcluding the \"refs/tags/\" prefix.  This can be used to avoid\n-\tleaking private tags from the repository.\n+\tleaking private tags from the repository. If given multiple times, a\n+\tlist of patterns will be accumulated, and tags matching any of the\n+\tpatterns will be considered. Use `--no-match` to clear and reset the\n+\tlist of patterns.\n \n --always::\n \tShow uniquely abbreviated commit object as fallback.\ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex 01490a157efc..5cc9e9abe798 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -28,7 +28,7 @@ static int abbrev = -1; /* unspecified */\n static int max_candidates = 10;\n static struct hashmap names;\n static int have_util;\n-static const char *pattern;\n+static struct string_list patterns = STRING_LIST_INIT_NODUP;\n static int always;\n static const char *dirty;\n \n@@ -129,9 +129,24 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi\n \tif (!all && !is_tag)\n \t\treturn 0;\n \n-\t/* Accept only tags that match the pattern, if given */\n-\tif (pattern && (!is_tag || wildmatch(pattern, path + 10, 0, NULL)))\n-\t\treturn 0;\n+\t/*\n+\t * If we're given patterns, accept only tags which match at least one\n+\t * pattern.\n+\t */\n+\tif (patterns.nr) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tif (!is_tag)\n+\t\t\treturn 0;\n+\n+\t\tfor_each_string_list_item(item, &patterns) {\n+\t\t\tif (!wildmatch(item->string, path + 10, 0, NULL))\n+\t\t\t\tbreak;\n+\n+\t\t\t/* If we get here, no pattern matched. */\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n \n \t/* Is it annotated? */\n \tif (!peel_ref(path, peeled.hash)) {\n@@ -404,7 +419,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"only output exact matches\"), 0),\n \t\tOPT_INTEGER(0, \"candidates\", &max_candidates,\n \t\t\t    N_(\"consider <n> most recent tags (default: 10)\")),\n-\t\tOPT_STRING(0, \"match\",       &pattern, N_(\"pattern\"),\n+\t\tOPT_STRING_LIST(0, \"match\", &patterns, N_(\"pattern\"),\n \t\t\t   N_(\"only consider tags matching <pattern>\")),\n \t\tOPT_BOOL(0, \"always\",        &always,\n \t\t\tN_(\"show abbreviated commit object as fallback\")),\n@@ -430,6 +445,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"--long is incompatible with --abbrev=0\"));\n \n \tif (contains) {\n+\t\tstruct string_list_item *item;\n \t\tstruct argv_array args;\n \n \t\targv_array_init(&args);\n@@ -440,8 +456,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)\n \t\t\targv_array_push(&args, \"--always\");\n \t\tif (!all) {\n \t\t\targv_array_push(&args, \"--tags\");\n-\t\t\tif (pattern)\n-\t\t\t\targv_array_pushf(&args, \"--refs=refs/tags/%s\", pattern);\n+\t\t\tfor_each_string_list_item(item, &patterns)\n+\t\t\t\targv_array_pushf(&args, \"--refs=refs/tags/%s\", item->string);\n \t\t}\n \t\tif (argc)\n \t\t\targv_array_pushv(&args, argv);\ndiff --git a/t/t6120-describe.sh b/t/t6120-describe.sh\nindex 85f269411cb3..9e5db9b87a1f 100755\n--- a/t/t6120-describe.sh\n+++ b/t/t6120-describe.sh\n@@ -182,6 +182,10 @@ check_describe \"test2-lightweight-*\" --tags --match=\"test2-*\"\n \n check_describe \"test2-lightweight-*\" --long --tags --match=\"test2-*\" HEAD^\n \n+check_describe \"test1-lightweight-*\" --long --tags --match=\"test1-*\" --match=\"test2-*\" HEAD^\n+\n+check_describe \"test2-lightweight-*\" --long --tags --match=\"test1-*\" --no-match --match=\"test2-*\" HEAD^\n+\n test_expect_success 'name-rev with exact tags' '\n \techo A >expect &&\n \ttag_object=$(git rev-parse refs/tags/A) &&\n@@ -206,4 +210,19 @@ test_expect_success 'describe --contains with the exact tags' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'describe --contains and --match' '\n+\techo \"A^0\" >expect &&\n+\ttagged_commit=$(git rev-parse \"refs/tags/A^0\") &&\n+\ttest_must_fail git describe --contains --match=\"B\" $tagged_commit &&\n+\tgit describe --contains --match=\"B\" --match=\"A\" $tagged_commit >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'describe --contains and --no-match' '\n+\techo \"A^0\" >expect &&\n+\ttagged_commit=$(git rev-parse \"refs/tags/A^0\") &&\n+\tgit describe --contains --match=\"B\" --no-match $tagged_commit >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.11.0.403.g196674b8396b\n\n"},{"id":"309594","messageId":"20170118000345.31196-4-jacob.e.keller@intel.com","threadId":"44904","inReplyTo":"20170118000345.31196-1-jacob.e.keller@intel.com","subject":"[PATCH v2 3/5] name-rev: add support to exclude refs by pattern match","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T00:03:43Z","receivedAt":"2017-01-18T00:07:56Z","isPatch":true,"sender":{"key":"jacob.e.keller@intel.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"From: Jacob Keller <jacob.keller@gmail.com>\n\nExtend name-rev further to support matching refs by adding `--exclude`\npatterns. These patterns will limit the scope of refs by excluding any\nref that matches at least one exclude pattern. Checking the exclude refs\nshall happen first, before checking the include --refs patterns. This\nwill allow more flexibility to matching certain kinds of references.\n\nAdd tests and update Documentation for this change.\n\nSigned-off-by: Jacob Keller <jacob.keller@gmail.com>\n---\n Documentation/git-name-rev.txt       |  7 +++++++\n builtin/name-rev.c                   | 14 +++++++++++++-\n t/t6007-rev-list-cherry-pick-file.sh | 12 ++++++++++++\n 3 files changed, 32 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt\nindex 7433627db12d..301b4a8d55e6 100644\n--- a/Documentation/git-name-rev.txt\n+++ b/Documentation/git-name-rev.txt\n@@ -30,6 +30,13 @@ OPTIONS\n \tgiven multiple times, use refs whose names match any of the given shell\n \tpatterns. Use `--no-refs` to clear any previous ref patterns given.\n \n+--exclude=<pattern>::\n+\tDo not use any ref whose name matches a given shell pattern. The\n+\tpattern can be one of branch name, tag name or fully qualified ref\n+\tname. If given multiple times, exclude refs that match any of the given\n+\tshell patterns. Use `--no-exclude` to clear the list of exclude\n+\tpatterns.\n+\n --all::\n \tList all commits reachable from all refs\n \ndiff --git a/builtin/name-rev.c b/builtin/name-rev.c\nindex 000a2a700ed3..da4a0d7c0fdf 100644\n--- a/builtin/name-rev.c\n+++ b/builtin/name-rev.c\n@@ -109,6 +109,7 @@ struct name_ref_data {\n \tint tags_only;\n \tint name_only;\n \tstruct string_list ref_filters;\n+\tstruct string_list exclude_filters;\n };\n \n static struct tip_table {\n@@ -150,6 +151,15 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo\n \tif (data->tags_only && !starts_with(path, \"refs/tags/\"))\n \t\treturn 0;\n \n+\tif (data->exclude_filters.nr) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, &data->exclude_filters) {\n+\t\t\tif (subpath_matches(path, item->string) >= 0)\n+\t\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n \tif (data->ref_filters.nr) {\n \t\tstruct string_list_item *item;\n \t\tint matched = 0;\n@@ -323,12 +333,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)\n {\n \tstruct object_array revs = OBJECT_ARRAY_INIT;\n \tint all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;\n-\tstruct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP };\n+\tstruct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };\n \tstruct option opts[] = {\n \t\tOPT_BOOL(0, \"name-only\", &data.name_only, N_(\"print only names (no SHA-1)\")),\n \t\tOPT_BOOL(0, \"tags\", &data.tags_only, N_(\"only use tags to name the commits\")),\n \t\tOPT_STRING_LIST(0, \"refs\", &data.ref_filters, N_(\"pattern\"),\n \t\t\t\t   N_(\"only use refs matching <pattern>\")),\n+\t\tOPT_STRING_LIST(0, \"exclude\", &data.exclude_filters, N_(\"pattern\"),\n+\t\t\t\t   N_(\"ignore refs matching <pattern>\")),\n \t\tOPT_GROUP(\"\"),\n \t\tOPT_BOOL(0, \"all\", &all, N_(\"list all commits reachable from all refs\")),\n \t\tOPT_BOOL(0, \"stdin\", &transform_stdin, N_(\"read from stdin\")),\ndiff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh\nindex f724ff24044b..83838d0da208 100755\n--- a/t/t6007-rev-list-cherry-pick-file.sh\n+++ b/t/t6007-rev-list-cherry-pick-file.sh\n@@ -118,6 +118,18 @@ test_expect_success 'name-rev --refs excludes non-matched patterns' '\n \ttest_cmp actual.named expect\n '\n \n+cat >expect <<EOF\n+<tags/F\n+EOF\n+\n+test_expect_success 'name-rev --exclude excludes matched patterns' '\n+\tgit rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&\n+\tgit rev-list --left-right --cherry-pick F...E -- bar >actual &&\n+\tgit name-rev --stdin --name-only --refs=\"*tags/*\" --exclude=\"*E\" \\\n+\t\t<actual >actual.named &&\n+\ttest_cmp actual.named expect\n+'\n+\n test_expect_success 'name-rev --no-refs clears the refs list' '\n \tgit rev-list --left-right --cherry-pick F...E -- bar >expect &&\n \tgit name-rev --stdin --name-only --refs=\"*tags/F\" --refs=\"*tags/E\" --no-refs --refs=\"*tags/G\" \\\n-- \n2.11.0.403.g196674b8396b\n\n"},{"id":"309595","messageId":"20170118000345.31196-3-jacob.e.keller@intel.com","threadId":"44904","inReplyTo":"20170118000345.31196-1-jacob.e.keller@intel.com","subject":"[PATCH v2 2/5] name-rev: extend --refs to accept multiple patterns","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T00:03:42Z","receivedAt":"2017-01-18T00:08:01Z","isPatch":true,"sender":{"key":"jacob.e.keller@intel.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"From: Jacob Keller <jacob.keller@gmail.com>\n\nTeach git name-rev to take a string list of patterns from --refs instead\nof only a single pattern. The list of patterns will be matched\ninclusively, such that a ref only needs to match one pattern to be\nincluded. If a ref will only be excluded if it does not match any of the\npatterns.\n\nAdd tests and documentation for this change. The tests do use\ndynamically generated output as part of the expected output because the\nexpected output is the raw commit-id and it seemed like a bad idea to\nhardcode that into a tests expected output.\n\nSigned-off-by: Jacob Keller <jacob.keller@gmail.com>\n---\n Documentation/git-name-rev.txt       |  4 +++-\n builtin/name-rev.c                   | 41 +++++++++++++++++++++++++-----------\n t/t6007-rev-list-cherry-pick-file.sh | 26 +++++++++++++++++++++++\n 3 files changed, 58 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt\nindex ca28fb8e2a07..7433627db12d 100644\n--- a/Documentation/git-name-rev.txt\n+++ b/Documentation/git-name-rev.txt\n@@ -26,7 +26,9 @@ OPTIONS\n \n --refs=<pattern>::\n \tOnly use refs whose names match a given shell pattern.  The pattern\n-\tcan be one of branch name, tag name or fully qualified ref name.\n+\tcan be one of branch name, tag name or fully qualified ref name. If\n+\tgiven multiple times, use refs whose names match any of the given shell\n+\tpatterns. Use `--no-refs` to clear any previous ref patterns given.\n \n --all::\n \tList all commits reachable from all refs\ndiff --git a/builtin/name-rev.c b/builtin/name-rev.c\nindex cd89d48b65e8..000a2a700ed3 100644\n--- a/builtin/name-rev.c\n+++ b/builtin/name-rev.c\n@@ -108,7 +108,7 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)\n struct name_ref_data {\n \tint tags_only;\n \tint name_only;\n-\tconst char *ref_filter;\n+\tstruct string_list ref_filters;\n };\n \n static struct tip_table {\n@@ -150,16 +150,33 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo\n \tif (data->tags_only && !starts_with(path, \"refs/tags/\"))\n \t\treturn 0;\n \n-\tif (data->ref_filter) {\n-\t\tswitch (subpath_matches(path, data->ref_filter)) {\n-\t\tcase -1: /* did not match */\n-\t\t\treturn 0;\n-\t\tcase 0:  /* matched fully */\n-\t\t\tbreak;\n-\t\tdefault: /* matched subpath */\n-\t\t\tcan_abbreviate_output = 1;\n-\t\t\tbreak;\n+\tif (data->ref_filters.nr) {\n+\t\tstruct string_list_item *item;\n+\t\tint matched = 0;\n+\n+\t\t/* See if any of the patterns match. */\n+\t\tfor_each_string_list_item(item, &data->ref_filters) {\n+\t\t\t/*\n+\t\t\t * We want to check every pattern even if we already\n+\t\t\t * found a match, just in case one of the later\n+\t\t\t * patterns could abbreviate the output.\n+\t\t\t */\n+\t\t\tswitch (subpath_matches(path, item->string)) {\n+\t\t\tcase -1: /* did not match */\n+\t\t\t\tbreak;\n+\t\t\tcase 0: /* matched fully */\n+\t\t\t\tmatched = 1;\n+\t\t\t\tbreak;\n+\t\t\tdefault: /* matched subpath */\n+\t\t\t\tmatched = 1;\n+\t\t\t\tcan_abbreviate_output = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n \t\t}\n+\n+\t\t/* If none of the patterns matched, stop now */\n+\t\tif (!matched)\n+\t\t\treturn 0;\n \t}\n \n \tadd_to_tip_table(oid->hash, path, can_abbreviate_output);\n@@ -306,11 +323,11 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)\n {\n \tstruct object_array revs = OBJECT_ARRAY_INIT;\n \tint all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;\n-\tstruct name_ref_data data = { 0, 0, NULL };\n+\tstruct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP };\n \tstruct option opts[] = {\n \t\tOPT_BOOL(0, \"name-only\", &data.name_only, N_(\"print only names (no SHA-1)\")),\n \t\tOPT_BOOL(0, \"tags\", &data.tags_only, N_(\"only use tags to name the commits\")),\n-\t\tOPT_STRING(0, \"refs\", &data.ref_filter, N_(\"pattern\"),\n+\t\tOPT_STRING_LIST(0, \"refs\", &data.ref_filters, N_(\"pattern\"),\n \t\t\t\t   N_(\"only use refs matching <pattern>\")),\n \t\tOPT_GROUP(\"\"),\n \t\tOPT_BOOL(0, \"all\", &all, N_(\"list all commits reachable from all refs\")),\ndiff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh\nindex 1408b608eb03..f724ff24044b 100755\n--- a/t/t6007-rev-list-cherry-pick-file.sh\n+++ b/t/t6007-rev-list-cherry-pick-file.sh\n@@ -99,6 +99,32 @@ test_expect_success '--cherry-pick bar does not come up empty (II)' '\n \ttest_cmp actual.named expect\n '\n \n+test_expect_success 'name-rev multiple --refs combine inclusive' '\n+\tgit rev-list --left-right --cherry-pick F...E -- bar >actual &&\n+\tgit name-rev --stdin --name-only --refs=\"*tags/F\" --refs=\"*tags/E\" \\\n+\t\t<actual >actual.named &&\n+\ttest_cmp actual.named expect\n+'\n+\n+cat >expect <<EOF\n+<tags/F\n+EOF\n+\n+test_expect_success 'name-rev --refs excludes non-matched patterns' '\n+\tgit rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&\n+\tgit rev-list --left-right --cherry-pick F...E -- bar >actual &&\n+\tgit name-rev --stdin --name-only --refs=\"*tags/F\" \\\n+\t\t<actual >actual.named &&\n+\ttest_cmp actual.named expect\n+'\n+\n+test_expect_success 'name-rev --no-refs clears the refs list' '\n+\tgit rev-list --left-right --cherry-pick F...E -- bar >expect &&\n+\tgit name-rev --stdin --name-only --refs=\"*tags/F\" --refs=\"*tags/E\" --no-refs --refs=\"*tags/G\" \\\n+\t\t<actual >actual &&\n+\ttest_cmp actual expect\n+'\n+\n cat >expect <<EOF\n +tags/F\n =tags/D\n-- \n2.11.0.403.g196674b8396b\n\n"},{"id":"309603","messageId":"20170118000345.31196-1-jacob.e.keller@intel.com","threadId":"44904","inReplyTo":null,"subject":"[PATCH v2 0/5] extend git-describe pattern matching","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T00:03:40Z","receivedAt":"2017-01-18T00:10:28Z","isPatch":true,"sender":{"key":"jacob.e.keller@intel.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"From: Jacob Keller <jacob.keller@gmail.com>\n\nTeach git describe and git name-rev the ability to match multiple\npatterns inclusively. Additionally, teach these commands to also accept\nnegative patterns to exclude any refs which match.\n\nThe pattern lists for positive and negative patterns are inclusive. This\nmeans that for the positive patterns, a reference will be considered as\nlong as it matches at least one of the match patterns. It need not match\nall given patterns. Additionally for negative patterns, we will not\nconsider any ref which matches any negative pattern, even if it matches\none of the positive patterns.\n\nTogether this allows the ability to express far more sets of tags than a\nsingle match pattern alone. It does not provide quite the same depth as\nwould teaching full regexp but it is simpler and easy enough to\nunderstand.\n\n- v2\n* use --exclude instead of --discard\n* use modern style in tests\n\nI chose *not* to implement the suggestion of ordered values for exclude\nand match, since this is not how the current implementation of\ngit-describe worked, and it didn't really make sense to me what was\nbeing requested. I looked at the interface for git-log, and it appears\nthat the command accepts multiple invocations of --branches, --remotes,\nand similar. I do not believe these need to be identical interfaces. I\nwelcome feedback on this decision, but I am not convinced yet that the\nordered arguments are worth the trouble.\n\ndiff --git c/Documentation/git-describe.txt w/Documentation/git-describe.txt\nindex a89bbde207b2..21a43b78924a 100644\n--- c/Documentation/git-describe.txt\n+++ w/Documentation/git-describe.txt\n@@ -88,12 +88,12 @@ OPTIONS\n\tpatterns will be considered. Use `--no-match` to clear and reset the\n\tlist of patterns.\n \n---discard <pattern>::\n+--exclude <pattern>::\n\tDo not consider tags matching the given `glob(7)` pattern, excluding\n\tthe \"refs/tags/\" prefix. This can be used to narrow the tag space and\n\tfind only tags matching some meaningful criteria. If given multiple\n\ttimes, a list of patterns will be accumulated and tags matching any\n-\tof the patterns will be discarded. Use `--no-discard` to clear and\n+\tof the patterns will be excluded. Use `--no-exclude` to clear and\n\treset the list of patterns.\n \n --always::\ndiff --git c/Documentation/git-name-rev.txt w/Documentation/git-name-rev.txt\nindex 9b46e5ea9aae..301b4a8d55e6 100644\n--- c/Documentation/git-name-rev.txt\n+++ w/Documentation/git-name-rev.txt\n@@ -30,11 +30,11 @@ OPTIONS\n\tgiven multiple times, use refs whose names match any of the given shell\n\tpatterns. Use `--no-refs` to clear any previous ref patterns given.\n \n---discard=<pattern>::\n+--exclude=<pattern>::\n\tDo not use any ref whose name matches a given shell pattern. The\n\tpattern can be one of branch name, tag name or fully qualified ref\n-\tname. If given multiple times, discard refs that match any of the given\n-\tshell patterns. Use `--no-discards` to clear the list of discard\n+\tname. If given multiple times, exclude refs that match any of the given\n+\tshell patterns. Use `--no-exclude` to clear the list of exclude\n\tpatterns.\n \n --all::\ndiff --git c/Documentation/technical/api-parse-options.txt w/Documentation/technical/api-parse-options.txt\nindex 15e876e4c804..6914f54f5f44 100644\n--- c/Documentation/technical/api-parse-options.txt\n+++ w/Documentation/technical/api-parse-options.txt\n@@ -169,9 +169,9 @@ There are some macros to easily define options:\n\tThe string argument is put into `str_var`.\n \n `OPT_STRING_LIST(short, long, &list, arg_str, description)`::\n-\tIntroduce an option with a string argument. Repeated invocations\n-\taccumulate into a list of strings. Reset and clear the list with\n-\t`--no-option`.\n+\tIntroduce an option with string argument.\n+\tThe string argument is stored as an element in `&list` which must be a\n+\tstruct string_list. Reset the list using `--no-option`.\n \n `OPT_INTEGER(short, long, &int_var, description)`::\n\tIntroduce an option with integer argument.\ndiff --git c/builtin/describe.c w/builtin/describe.c\nindex c09288ee6321..6769446e1f57 100644\n--- c/builtin/describe.c\n+++ w/builtin/describe.c\n@@ -29,7 +29,7 @@ static int max_candidates = 10;\n static struct hashmap names;\n static int have_util;\n static struct string_list patterns = STRING_LIST_INIT_NODUP;\n-static struct string_list discard_patterns = STRING_LIST_INIT_NODUP;\n+static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP;\n static int always;\n static const char *dirty;\n \n@@ -131,16 +131,16 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi\n\t\treturn 0;\n \n\t/*\n-\t * If we're given discard patterns, first discard any tag which match\n-\t * any of the discard pattern.\n+\t * If we're given exclude patterns, first exclude any tag which match\n+\t * any of the exclude pattern.\n\t */\n-\tif (discard_patterns.nr) {\n+\tif (exclude_patterns.nr) {\n\t\tstruct string_list_item *item;\n \n\t\tif (!is_tag)\n\t\t\treturn 0;\n \n-\t\tfor_each_string_list_item(item, &discard_patterns) {\n+\t\tfor_each_string_list_item(item, &exclude_patterns) {\n\t\t\tif (!wildmatch(item->string, path + 10, 0, NULL))\n\t\t\t\treturn 0;\n\t\t}\n@@ -438,7 +438,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)\n\t\t\t    N_(\"consider <n> most recent tags (default: 10)\")),\n\t\tOPT_STRING_LIST(0, \"match\", &patterns, N_(\"pattern\"),\n\t\t\t   N_(\"only consider tags matching <pattern>\")),\n-\t\tOPT_STRING_LIST(0, \"discard\", &discard_patterns, N_(\"pattern\"),\n+\t\tOPT_STRING_LIST(0, \"exclude\", &exclude_patterns, N_(\"pattern\"),\n\t\t\t   N_(\"do not consider tags matching <pattern>\")),\n\t\tOPT_BOOL(0, \"always\",        &always,\n\t\t\tN_(\"show abbreviated commit object as fallback\")),\n@@ -477,8 +477,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)\n\t\t\targv_array_push(&args, \"--tags\");\n\t\t\tfor_each_string_list_item(item, &patterns)\n\t\t\t\targv_array_pushf(&args, \"--refs=refs/tags/%s\", item->string);\n-\t\t\tfor_each_string_list_item(item, &discard_patterns)\n-\t\t\t\targv_array_pushf(&args, \"--discard=refs/tags/%s\", item->string);\n+\t\t\tfor_each_string_list_item(item, &exclude_patterns)\n+\t\t\t\targv_array_pushf(&args, \"--exclude=refs/tags/%s\", item->string);\n\t\t}\n\t\tif (argc)\n\t\t\targv_array_pushv(&args, argv);\ndiff --git c/builtin/name-rev.c w/builtin/name-rev.c\nindex 86479c17a7c9..da4a0d7c0fdf 100644\n--- c/builtin/name-rev.c\n+++ w/builtin/name-rev.c\n@@ -109,7 +109,7 @@ struct name_ref_data {\n\tint tags_only;\n\tint name_only;\n\tstruct string_list ref_filters;\n-\tstruct string_list discard_filters;\n+\tstruct string_list exclude_filters;\n };\n \n static struct tip_table {\n@@ -151,10 +151,10 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo\n\tif (data->tags_only && !starts_with(path, \"refs/tags/\"))\n\t\treturn 0;\n \n-\tif (data->discard_filters.nr) {\n+\tif (data->exclude_filters.nr) {\n\t\tstruct string_list_item *item;\n \n-\t\tfor_each_string_list_item(item, &data->discard_filters) {\n+\t\tfor_each_string_list_item(item, &data->exclude_filters) {\n\t\t\tif (subpath_matches(path, item->string) >= 0)\n\t\t\t\treturn 0;\n\t\t}\n@@ -339,7 +339,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)\n\t\tOPT_BOOL(0, \"tags\", &data.tags_only, N_(\"only use tags to name the commits\")),\n\t\tOPT_STRING_LIST(0, \"refs\", &data.ref_filters, N_(\"pattern\"),\n\t\t\t\t   N_(\"only use refs matching <pattern>\")),\n-\t\tOPT_STRING_LIST(0, \"discard\", &data.discard_filters, N_(\"pattern\"),\n+\t\tOPT_STRING_LIST(0, \"exclude\", &data.exclude_filters, N_(\"pattern\"),\n\t\t\t\t   N_(\"ignore refs matching <pattern>\")),\n\t\tOPT_GROUP(\"\"),\n\t\tOPT_BOOL(0, \"all\", &all, N_(\"list all commits reachable from all refs\")),\ndiff --git c/t/t6007-rev-list-cherry-pick-file.sh w/t/t6007-rev-list-cherry-pick-file.sh\nindex 8a4c35f6ffee..83838d0da208 100755\n--- c/t/t6007-rev-list-cherry-pick-file.sh\n+++ w/t/t6007-rev-list-cherry-pick-file.sh\n@@ -100,42 +100,43 @@ test_expect_success '--cherry-pick bar does not come up empty (II)' '\n '\n \n test_expect_success 'name-rev multiple --refs combine inclusive' '\n-\tgit rev-list --left-right --cherry-pick F...E -- bar > actual &&\n+\tgit rev-list --left-right --cherry-pick F...E -- bar >actual &&\n\tgit name-rev --stdin --name-only --refs=\"*tags/F\" --refs=\"*tags/E\" \\\n-\t\t< actual > actual.named &&\n+\t\t<actual >actual.named &&\n\ttest_cmp actual.named expect\n '\n \n cat >expect <<EOF\n <tags/F\n-$(git rev-list --left-right --right-only --cherry-pick F...E -- bar)\n EOF\n \n test_expect_success 'name-rev --refs excludes non-matched patterns' '\n-\tgit rev-list --left-right --cherry-pick F...E -- bar > actual &&\n+\tgit rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&\n+\tgit rev-list --left-right --cherry-pick F...E -- bar >actual &&\n\tgit name-rev --stdin --name-only --refs=\"*tags/F\" \\\n-\t\t< actual > actual.named &&\n-\ttest_cmp actual.named expect\n-'\n-\n-test_expect_success 'name-rev --discard excludes matched patterns' '\n-\tgit rev-list --left-right --cherry-pick F...E -- bar > actual &&\n-\tgit name-rev --stdin --name-only --refs=\"*tags/*\" --discard=\"*E\" \\\n-\t\t< actual > actual.named &&\n+\t\t<actual >actual.named &&\n\ttest_cmp actual.named expect\n '\n \n cat >expect <<EOF\n-$(git rev-list --left-right --cherry-pick F...E -- bar)\n+<tags/F\n EOF\n \n-test_expect_success 'name-rev --no-refs clears the refs list' '\n-\tgit rev-list --left-right --cherry-pick F...E -- bar > actual &&\n-\tgit name-rev --stdin --name-only --refs=\"*tags/F\" --refs=\"*tags/E\" --no-refs --refs=\"*tags/G\" \\\n-\t\t< actual > actual.named &&\n+test_expect_success 'name-rev --exclude excludes matched patterns' '\n+\tgit rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&\n+\tgit rev-list --left-right --cherry-pick F...E -- bar >actual &&\n+\tgit name-rev --stdin --name-only --refs=\"*tags/*\" --exclude=\"*E\" \\\n+\t\t<actual >actual.named &&\n\ttest_cmp actual.named expect\n '\n \n+test_expect_success 'name-rev --no-refs clears the refs list' '\n+\tgit rev-list --left-right --cherry-pick F...E -- bar >expect &&\n+\tgit name-rev --stdin --name-only --refs=\"*tags/F\" --refs=\"*tags/E\" --no-refs --refs=\"*tags/G\" \\\n+\t\t<actual >actual &&\n+\ttest_cmp actual expect\n+'\n+\n cat >expect <<EOF\n +tags/F\n =tags/D\ndiff --git c/t/t6120-describe.sh w/t/t6120-describe.sh\nindex 4e4a9f2e5305..167491fd5b0d 100755\n--- c/t/t6120-describe.sh\n+++ w/t/t6120-describe.sh\n@@ -218,11 +218,11 @@ test_expect_success 'describe --contains and --match' '\n\ttest_cmp expect actual\n '\n \n-test_expect_success 'describe --discard' '\n+test_expect_success 'describe --exclude' '\n\techo \"c~1\" >expect &&\n\ttagged_commit=$(git rev-parse \"refs/tags/A^0\") &&\n\ttest_must_fail git describe --contains --match=\"B\" $tagged_commit &&\n-\tgit describe --contains --match=\"?\" --discard=\"A\" $tagged_commit >actual &&\n+\tgit describe --contains --match=\"?\" --exclude=\"A\" $tagged_commit >actual &&\n\ttest_cmp expect actual\n '\n \n\nJacob Keller (5):\n  doc: add documentation for OPT_STRING_LIST\n  name-rev: extend --refs to accept multiple patterns\n  name-rev: add support to exclude refs by pattern match\n  describe: teach --match to accept multiple patterns\n  describe: teach describe negative pattern matches\n\n Documentation/git-describe.txt                | 13 ++++++-\n Documentation/git-name-rev.txt                | 11 +++++-\n Documentation/technical/api-parse-options.txt |  5 +++\n builtin/describe.c                            | 51 ++++++++++++++++++++++----\n builtin/name-rev.c                            | 53 +++++++++++++++++++++------\n t/t6007-rev-list-cherry-pick-file.sh          | 38 +++++++++++++++++++\n t/t6120-describe.sh                           | 27 ++++++++++++++\n 7 files changed, 177 insertions(+), 21 deletions(-)\n\n-- \n2.11.0.403.g196674b8396b\n\n"}]}