{"thread":{"id":"44912","subject":"[PATCH v4 5/5] describe: teach describe negative pattern matches","startedAt":"2017-01-18T23:07:05Z","lastAt":"2017-01-19T23:46:06Z","messageCount":11,"participants":["Jacob Keller","Junio C Hamano"],"isPatch":true,"patchVersion":4,"patchTotal":5},"messages":[{"id":"309691","messageId":"20170118230608.28030-6-jacob.e.keller@intel.com","threadId":"44912","inReplyTo":"20170118230608.28030-1-jacob.e.keller@intel.com","subject":"[PATCH v4 5/5] describe: teach describe negative pattern matches","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T23:06:08Z","receivedAt":"2017-01-18T23:07:05Z","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 release tag that introduces the commit\nabcdef:\n\n  git describe --contains --match=\"v*\" --exclude=\"*rc*\" abcdef\n\nAdd documentation, tests, and completion for this change.\n\nSigned-off-by: Jacob Keller <jacob.keller@gmail.com>\n---\n Documentation/git-describe.txt         | 10 ++++++++++\n builtin/describe.c                     | 21 +++++++++++++++++++++\n contrib/completion/git-completion.bash |  1 +\n t/t6120-describe.sh                    |  8 ++++++++\n 4 files changed, 40 insertions(+)\n\ndiff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt\nindex 7ad41e2f6ade..8755f3af7bcd 100644\n--- a/Documentation/git-describe.txt\n+++ b/Documentation/git-describe.txt\n@@ -88,6 +88,16 @@ 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. When combined with --match a tag will\n+\tbe considered when it matches at least one --match pattern and does not\n+\tmatch any of the --exclude patterns. 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@@ -129,6 +130,22 @@ 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/*\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@@ -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/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 6721ff80fb13..835d7fcfd4f2 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1198,6 +1198,7 @@ _git_describe ()\n \t\t__gitcomp \"\n \t\t\t--all --tags --contains --abbrev= --candidates=\n \t\t\t--exact-match --debug --long --match --always\n+\t\t\t--exclude\n \t\t\t\"\n \t\treturn\n \tesac\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.488.g1cece4bcb7a5\n\n"},{"id":"309692","messageId":"20170118230608.28030-5-jacob.e.keller@intel.com","threadId":"44912","inReplyTo":"20170118230608.28030-1-jacob.e.keller@intel.com","subject":"[PATCH v4 4/5] describe: teach --match to accept multiple patterns","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T23:06:07Z","receivedAt":"2017-01-18T23:07:09Z","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.488.g1cece4bcb7a5\n\n"},{"id":"309693","messageId":"20170118230608.28030-1-jacob.e.keller@intel.com","threadId":"44912","inReplyTo":null,"subject":"[PATCH v4 0/5] extend git-describe pattern matching","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T23:06:03Z","receivedAt":"2017-01-18T23:07:24Z","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\n- v3\n* fix broken test (sorry for the thrash!)\n\n- v4\n* update documentation and commit messages at Junio's suggestion\n* add completion\n\nAfter even more thought, I do not like the way that git-log works with\n--exclude, and do not believe it gives enough extra expressive power to\nbother with the complexity. The current implementation of --match and\n--exclude is relatively easy to explain and makes some sense. Since we\ndid not have a historical reasoning in the same way that git-log does, I\ndo not think using something like an exclude_list or similar is worth\nthe added complexity.\n\n-- interdiff since v3 --\ndiff --git c/Documentation/git-describe.txt w/Documentation/git-describe.txt\nindex 21a43b78924a..8755f3af7bcd 100644\n--- c/Documentation/git-describe.txt\n+++ w/Documentation/git-describe.txt\n@@ -93,7 +93,9 @@ OPTIONS\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+\tof the patterns will be excluded. When combined with --match a tag will\n+\tbe considered when it matches at least one --match pattern and does not\n+\tmatch any of the --exclude patterns. 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 301b4a8d55e6..da83f280ed88 100644\n--- c/Documentation/git-name-rev.txt\n+++ w/Documentation/git-name-rev.txt\n@@ -33,9 +33,11 @@ OPTIONS\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+\tname. If given multiple times, a ref will be excluded when it matches\n+\tany of the given patterns. When used together with --refs, a ref will\n+\tbe used as a match only when it matches at least one --ref pattern and\n+\tdoes not match any --exclude patterns. Use `--no-exclude` to clear the\n+\tlist of exclude patterns.\n \n --all::\n \tList all commits reachable from all refs\ndiff --git c/Documentation/technical/api-parse-options.txt w/Documentation/technical/api-parse-options.txt\nindex 6914f54f5f44..36768b479e16 100644\n--- c/Documentation/technical/api-parse-options.txt\n+++ w/Documentation/technical/api-parse-options.txt\n@@ -168,10 +168,10 @@ 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+`OPT_STRING_LIST(short, long, &struct string_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+\tThe string argument is stored as an element in `string_list`.\n+\tUse of `--no-option` will clear the list of preceding values.\n \n `OPT_INTEGER(short, long, &int_var, description)`::\n \tIntroduce an option with integer argument.\ndiff --git c/builtin/name-rev.c w/builtin/name-rev.c\nindex da4a0d7c0fdf..ea8ef48102f8 100644\n--- c/builtin/name-rev.c\n+++ w/builtin/name-rev.c\n@@ -166,10 +166,11 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo\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/* Check every pattern even after we found a match so\n+\t\t\t * that we can determine when we should abbreviate the\n+\t\t\t * output. We will abbreviate the output when any of\n+\t\t\t * the patterns match a subpath, even if one of the\n+\t\t\t * patterns matches fully.\n \t\t\t */\n \t\t\tswitch (subpath_matches(path, item->string)) {\n \t\t\tcase -1: /* did not match */\ndiff --git c/contrib/completion/git-completion.bash w/contrib/completion/git-completion.bash\nindex 6721ff80fb13..835d7fcfd4f2 100644\n--- c/contrib/completion/git-completion.bash\n+++ w/contrib/completion/git-completion.bash\n@@ -1198,6 +1198,7 @@ _git_describe ()\n \t\t__gitcomp \"\n \t\t\t--all --tags --contains --abbrev= --candidates=\n \t\t\t--exact-match --debug --long --match --always\n+\t\t\t--exclude\n \t\t\t\"\n \t\treturn\n \tesac\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                | 15 +++++++-\n Documentation/git-name-rev.txt                | 13 ++++++-\n Documentation/technical/api-parse-options.txt |  5 +++\n builtin/describe.c                            | 51 +++++++++++++++++++++----\n builtin/name-rev.c                            | 54 +++++++++++++++++++++------\n contrib/completion/git-completion.bash        |  1 +\n t/t6007-rev-list-cherry-pick-file.sh          | 38 +++++++++++++++++++\n t/t6120-describe.sh                           | 27 ++++++++++++++\n 8 files changed, 183 insertions(+), 21 deletions(-)\n\n-- \n2.11.0.488.g1cece4bcb7a5\n\n"},{"id":"309694","messageId":"20170118230608.28030-4-jacob.e.keller@intel.com","threadId":"44912","inReplyTo":"20170118230608.28030-1-jacob.e.keller@intel.com","subject":"[PATCH v4 3/5] name-rev: add support to exclude refs by pattern match","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T23:06:06Z","receivedAt":"2017-01-18T23:07: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\nExtend git-name-rev to support excluding refs which match shell patterns\nusing --exclude. These patterns can be used to limit the scope of refs\nby excluding any ref that matches one of the --exclude patterns. A ref\nwill only be used for naming when it matches at least one --ref pattern\nbut does not match any of the --exclude patterns. Thus, --exlude\npatterns are given precedence over --ref patterns.\n\nFor example, suppose you wish to name a series of commits based on an\nofficial release tag of the form \"v*\" but excluding any pre-release tags\nwhich match \"*rc*\". You can use the following to do so:\n\n  git name-rev --refs=\"v*\" --exclude=\"*rc*\" --all\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       |  9 +++++++++\n builtin/name-rev.c                   | 14 +++++++++++++-\n t/t6007-rev-list-cherry-pick-file.sh | 12 ++++++++++++\n 3 files changed, 34 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt\nindex 7433627db12d..da83f280ed88 100644\n--- a/Documentation/git-name-rev.txt\n+++ b/Documentation/git-name-rev.txt\n@@ -30,6 +30,15 @@ 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, a ref will be excluded when it matches\n+\tany of the given patterns. When used together with --refs, a ref will\n+\tbe used as a match only when it matches at least one --ref pattern and\n+\tdoes not match any --exclude patterns. Use `--no-exclude` to clear the\n+\tlist of exclude patterns.\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 85897ea1f714..ea8ef48102f8 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@@ -324,12 +334,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 d9827a6389a3..29597451967d 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.488.g1cece4bcb7a5\n\n"},{"id":"309695","messageId":"20170118230608.28030-3-jacob.e.keller@intel.com","threadId":"44912","inReplyTo":"20170118230608.28030-1-jacob.e.keller@intel.com","subject":"[PATCH v4 2/5] name-rev: extend --refs to accept multiple patterns","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T23:06:05Z","receivedAt":"2017-01-18T23:07:36Z","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 multiple --refs stored as a string list of\npatterns. The list of patterns will be matched inclusively, and each ref\nonly needs to match one pattern to be included. A ref will only be\nexcluded if it does not match any of the given patterns. Additionally,\nif any of the patterns would allow abbreviation, then we will abbreviate\nthe ref, even if another pattern is more strict and would not have\nallowed abbreviation on its own.\n\nAdd tests and documentation for this change. The tests expected output\nis dynamically generated, but this is in order to avoid hard-coding\na commit object id in the test results (as the expected output is to\nsimply leave the commit object unnamed).\n\nSigned-off-by: Jacob Keller <jacob.keller@gmail.com>\n---\n Documentation/git-name-rev.txt       |  4 +++-\n builtin/name-rev.c                   | 42 +++++++++++++++++++++++++-----------\n t/t6007-rev-list-cherry-pick-file.sh | 26 ++++++++++++++++++++++\n 3 files changed, 59 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..85897ea1f714 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,34 @@ 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/* Check every pattern even after we found a match so\n+\t\t\t * that we can determine when we should abbreviate the\n+\t\t\t * output. We will abbreviate the output when any of\n+\t\t\t * the patterns match a subpath, even if one of the\n+\t\t\t * patterns matches fully.\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 +324,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..d9827a6389a3 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<expect >actual &&\n+\ttest_cmp actual expect\n+'\n+\n cat >expect <<EOF\n +tags/F\n =tags/D\n-- \n2.11.0.488.g1cece4bcb7a5\n\n"},{"id":"309696","messageId":"20170118230608.28030-2-jacob.e.keller@intel.com","threadId":"44912","inReplyTo":"20170118230608.28030-1-jacob.e.keller@intel.com","subject":"[PATCH v4 1/5] doc: add documentation for OPT_STRING_LIST","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2017-01-18T23:06:04Z","receivedAt":"2017-01-18T23:07:40Z","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..36768b479e16 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, &struct string_list, arg_str, description)`::\n+\tIntroduce an option with string argument.\n+\tThe string argument is stored as an element in `string_list`.\n+\tUse of `--no-option` will clear the list of preceding values.\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.488.g1cece4bcb7a5\n\n"},{"id":"309779","messageId":"xmqqk29qzrru.fsf@gitster.mtv.corp.google.com","threadId":"44912","inReplyTo":"20170118230608.28030-3-jacob.e.keller@intel.com","subject":"Re: [PATCH v4 2/5] name-rev: extend --refs to accept multiple patterns","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-19T21:03:33Z","receivedAt":"2017-01-19T21:07:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.e.keller@intel.com> writes:\n\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/* Check every pattern even after we found a match so\n> +\t\t\t * that we can determine when we should abbreviate the\n> +\t\t\t * output. We will abbreviate the output when any of\n> +\t\t\t * the patterns match a subpath, even if one of the\n> +\t\t\t * patterns matches fully.\n> +\t\t\t */\n\nThis describe \"what\" we do, which we can read from the code.  What I\nasked you to mention was \"why\", which cannot be read from the code.\n\n\t/*\n\t * Check all patterns even after finding a match, \n\t * so that we can see if a match with a subpath exists.\n\t * When a user asked for 'refs/tags/v*' and 'v1.*', both\n\t * of which match, the user is showing her willingness\n\t * to accept a shortened output by having the 'v1.*' in\n\t * the acceptable refnames, so we shouldn't stop when seeing\n\t * 'refs/tags/v1.4' matches 'refs/tags/v*'.  We should show\n\t * it as 'v1.4'.\n\t */\n\nor something like that, perhaps?\n"},{"id":"309780","messageId":"xmqqfukezrmh.fsf@gitster.mtv.corp.google.com","threadId":"44912","inReplyTo":"20170118230608.28030-3-jacob.e.keller@intel.com","subject":"Re: [PATCH v4 2/5] name-rev: extend --refs to accept multiple patterns","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-19T21:06:46Z","receivedAt":"2017-01-19T21:07:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.e.keller@intel.com> writes:\n\n> From: Jacob Keller <jacob.keller@gmail.com>\n>\n> Teach git name-rev to take multiple --refs stored as a string list of\n> patterns. The list of patterns will be matched inclusively, and each ref\n> only needs to match one pattern to be included. A ref will only be\n> excluded if it does not match any of the given patterns. Additionally,\n> if any of the patterns would allow abbreviation, then we will abbreviate\n> the ref, even if another pattern is more strict and would not have\n> allowed abbreviation on its own.\n>\n> Add tests and documentation for this change. The tests expected output\n> is dynamically generated, but this is in order to avoid hard-coding\n> a commit object id in the test results (as the expected output is to\n> simply leave the commit object unnamed).\n\nMakes sense.  \n\nI do not see anything that requires \"... generated, but\" there,\nthough, as if it is a bad thing to do to prepare expected output\ndynamically.  I'd just reword \"generated.  This is...\" to make it\nneutral.\n"},{"id":"309781","messageId":"xmqqbmv2zrju.fsf@gitster.mtv.corp.google.com","threadId":"44912","inReplyTo":"20170118230608.28030-4-jacob.e.keller@intel.com","subject":"Re: [PATCH v4 3/5] name-rev: add support to exclude refs by pattern match","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-19T21:08:21Z","receivedAt":"2017-01-19T21:09:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.e.keller@intel.com> writes:\n\n> From: Jacob Keller <jacob.keller@gmail.com>\n>\n> Extend git-name-rev to support excluding refs which match shell patterns\n> using --exclude. These patterns can be used to limit the scope of refs\n> by excluding any ref that matches one of the --exclude patterns. A ref\n> will only be used for naming when it matches at least one --ref pattern\n\ns/--ref pattern/--refs pattern/\n\n> but does not match any of the --exclude patterns. Thus, --exlude\n\ns/--exlude/--exclude/\n\n> patterns are given precedence over --ref patterns.\n\ns/--ref pattern/--refs pattern/\n\nNo need to resend.  Thanks.\n"},{"id":"309803","messageId":"CA+P7+xokEvYo4as5w0O5SbS6KE86fO=a7VnR_JroTOHebWSayw@mail.gmail.com","threadId":"44912","inReplyTo":"xmqqfukezrmh.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v4 2/5] name-rev: extend --refs to accept multiple patterns","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2017-01-19T23:36:41Z","receivedAt":"2017-01-19T23:38:06Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 19, 2017 at 1:06 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Jacob Keller <jacob.e.keller@intel.com> writes:\n>\n>> From: Jacob Keller <jacob.keller@gmail.com>\n>>\n>> Teach git name-rev to take multiple --refs stored as a string list of\n>> patterns. The list of patterns will be matched inclusively, and each ref\n>> only needs to match one pattern to be included. A ref will only be\n>> excluded if it does not match any of the given patterns. Additionally,\n>> if any of the patterns would allow abbreviation, then we will abbreviate\n>> the ref, even if another pattern is more strict and would not have\n>> allowed abbreviation on its own.\n>>\n>> Add tests and documentation for this change. The tests expected output\n>> is dynamically generated, but this is in order to avoid hard-coding\n>> a commit object id in the test results (as the expected output is to\n>> simply leave the commit object unnamed).\n>\n> Makes sense.\n>\n> I do not see anything that requires \"... generated, but\" there,\n> though, as if it is a bad thing to do to prepare expected output\n> dynamically.  I'd just reword \"generated.  This is...\" to make it\n> neutral.\n\nMakes sense. I was commenting this way since I was requested that\nsomeone on the list preferred non-dynamic test expectations.\n\nThanks,\nJake\n"},{"id":"309806","messageId":"CA+P7+xp20UPrwZBU9hC45yKLOjp0Jm0r4exAvzL=tuXCV8KwUA@mail.gmail.com","threadId":"44912","inReplyTo":"xmqqfukezrmh.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v4 2/5] name-rev: extend --refs to accept multiple patterns","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2017-01-19T23:45:39Z","receivedAt":"2017-01-19T23:46:06Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 19, 2017 at 1:06 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Jacob Keller <jacob.e.keller@intel.com> writes:\n>\n>> From: Jacob Keller <jacob.keller@gmail.com>\n>>\n>> Teach git name-rev to take multiple --refs stored as a string list of\n>> patterns. The list of patterns will be matched inclusively, and each ref\n>> only needs to match one pattern to be included. A ref will only be\n>> excluded if it does not match any of the given patterns. Additionally,\n>> if any of the patterns would allow abbreviation, then we will abbreviate\n>> the ref, even if another pattern is more strict and would not have\n>> allowed abbreviation on its own.\n>>\n>> Add tests and documentation for this change. The tests expected output\n>> is dynamically generated, but this is in order to avoid hard-coding\n>> a commit object id in the test results (as the expected output is to\n>> simply leave the commit object unnamed).\n>\n> Makes sense.\n>\n> I do not see anything that requires \"... generated, but\" there,\n> though, as if it is a bad thing to do to prepare expected output\n> dynamically.  I'd just reword \"generated.  This is...\" to make it\n> neutral.\n\nTo clarify my earlier comment, I think the SQUASH?? you queued looks great.\n\nThanks,\nJake\n"}]}