{"thread":{"id":"44505","subject":"[PATCH/RFC] ref-filter: support sorting case-insensitively","startedAt":"2016-11-17T10:21:32Z","lastAt":"2016-12-06T21:11:28Z","messageCount":5,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"306102","messageId":"20161117102114.16649-1-pclouds@gmail.com","threadId":"44505","inReplyTo":null,"subject":"[PATCH/RFC] ref-filter: support sorting case-insensitively","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-17T10:21:14Z","receivedAt":"2016-11-17T10:21:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Similar to version:refname sorting refs by versions, icase:refname\nwill sort by refnames are usually, but strcasecmp will be used instead\nof strcmp. This may be helpful sometimes when people name their\nbranches <group>-<details> but somebody names it <group>-, some goes\nwith <Group>-, or even <GROUP>-\n\nSyntax is a big problem. This patch does not support\nicase:version:refname or version:icase:refname, for example. If\nversion sorting learns about this thing, I think I prefer\niversion:refname...\n\nOr perhaps we can. I'm losing touch with for-each-ref \"pretty\"\nformats, I'm not quite sure what's the guideline here.\n\nAnother option is just use a symbol, like '-' or '*' to mark\ncase-insensitivity. But that does not look very descriptive. I don't\nsee any symbol suggesting this case stuff.\n\nWhat do you think?\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-for-each-ref.txt | 3 ++-\n ref-filter.c                       | 8 ++++++--\n ref-filter.h                       | 1 +\n 3 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex f57e69bc83..e41005cf0e 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -171,7 +171,8 @@ For sorting purposes, fields with numeric values sort in numeric order\n All other fields are used to sort in their byte-value order.\n \n There is also an option to sort by versions, this can be done by using\n-the fieldname `version:refname` or its alias `v:refname`.\n+the fieldname `version:refname` or its alias `v:refname`. Prefixing\n+\"icase:\" (e.g. `icase:refname`) makes sorting case-insensitive.\n \n In any case, a field name that refers to a field inapplicable to\n the object referred by the ref does not cause an error.  It\ndiff --git a/ref-filter.c b/ref-filter.c\nindex d4c2931f3a..fd63b9c710 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1542,12 +1542,12 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n \tif (s->version)\n \t\tcmp = versioncmp(va->s, vb->s);\n \telse if (cmp_type == FIELD_STR)\n-\t\tcmp = strcmp(va->s, vb->s);\n+\t\tcmp = s->strcmp(va->s, vb->s);\n \telse {\n \t\tif (va->ul < vb->ul)\n \t\t\tcmp = -1;\n \t\telse if (va->ul == vb->ul)\n-\t\t\tcmp = strcmp(a->refname, b->refname);\n+\t\t\tcmp = s->strcmp(a->refname, b->refname);\n \t\telse\n \t\t\tcmp = 1;\n \t}\n@@ -1646,6 +1646,7 @@ struct ref_sorting *ref_default_sorting(void)\n \n \tsorting->next = NULL;\n \tsorting->atom = parse_ref_filter_atom(cstr_name, cstr_name + strlen(cstr_name));\n+\tsorting->strcmp = strcmp;\n \treturn sorting;\n }\n \n@@ -1660,6 +1661,7 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)\n \n \ts = xcalloc(1, sizeof(*s));\n \ts->next = *sorting_tail;\n+\ts->strcmp = strcmp;\n \t*sorting_tail = s;\n \n \tif (*arg == '-') {\n@@ -1669,6 +1671,8 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)\n \tif (skip_prefix(arg, \"version:\", &arg) ||\n \t    skip_prefix(arg, \"v:\", &arg))\n \t\ts->version = 1;\n+\telse if (skip_prefix(arg, \"icase:\", &arg))\n+\t\ts->strcmp = strcasecmp;\n \tlen = strlen(arg);\n \ts->atom = parse_ref_filter_atom(arg, arg+len);\n \treturn 0;\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 14d435e2cc..ea2db565f1 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -30,6 +30,7 @@ struct ref_sorting {\n \tint atom; /* index into used_atom array (internal) */\n \tunsigned reverse : 1,\n \t\tversion : 1;\n+\tint (*strcmp)(const char *, const char *);\n };\n \n struct ref_array_item {\n-- \n2.11.0.rc0.161.g80d5b92\n\n"},{"id":"306589","messageId":"20161130123502.12973-1-pclouds@gmail.com","threadId":"44505","inReplyTo":"20161117102114.16649-1-pclouds@gmail.com","subject":"[PATCH v2] tag, branch, for-each-ref: add --ignore-case for sorting and filtering","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-30T12:35:02Z","receivedAt":"2016-11-30T12:35:25Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This option makes sorting ignore case, which is great when you have\nbranches named bug-12-do-something, Bug-12-do-some-more and\nBUG-12-do-what and want to group them together. Sorting externally may\nnot be an option because we lose coloring and column layout from\ngit-branch and git-tag.\n\nThe same could be said for filtering, but it's probably less important\nbecause you can always go with the ugly pattern [bB][uU][gG]-* if you're\ndesperate.\n\nYou can't have case-sensitive filtering and case-insensitive sorting (or\nthe other way around) with this though. But who would want that?\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n v2 has a different approach, and I think it's a better one even with\n that unanswered question above.\n\n Documentation/git-branch.txt       |  4 ++++\n Documentation/git-for-each-ref.txt |  3 +++\n Documentation/git-tag.txt          |  4 ++++\n builtin/branch.c                   | 23 ++++++++++++++---------\n builtin/for-each-ref.c             |  5 ++++-\n builtin/tag.c                      |  4 ++++\n ref-filter.c                       | 28 +++++++++++++++++++++-------\n ref-filter.h                       |  2 ++\n t/t3203-branch-output.sh           | 22 ++++++++++++++++++++++\n t/t7004-tag.sh                     | 20 ++++++++++++++++++++\n 10 files changed, 98 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 1fe7344..5516a47 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -118,6 +118,10 @@ OPTIONS\n \tdefault to color output.\n \tSame as `--color=never`.\n \n+-i::\n+--ignore-case::\n+\tSorting and filtering branches are case insensitive.\n+\n --column[=<options>]::\n --no-column::\n \tDisplay branch listing in columns. See configuration variable\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex f57e69b..6d22974 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -79,6 +79,9 @@ OPTIONS\n \tOnly list refs which contain the specified commit (HEAD if not\n \tspecified).\n \n+--ignore-case::\n+\tSorting and filtering refs are case insensitive.\n+\n FIELD NAMES\n -----------\n \ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 80019c5..76cfe40 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -108,6 +108,10 @@ OPTIONS\n \tvariable if it exists, or lexicographic order otherwise. See\n \tlinkgit:git-config[1].\n \n+-i::\n+--ignore-case::\n+\tSorting and filtering tags are case insensitive.\n+\n --column[=<options>]::\n --no-column::\n \tDisplay tag listing in columns. See configuration variable\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 60cc5c8..36e0a21 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin\n \tif (filter->verbose)\n \t\tmaxwidth = calc_maxwidth(&array, strlen(remote_prefix));\n \n-\t/*\n-\t * If no sorting parameter is given then we default to sorting\n-\t * by 'refname'. This would give us an alphabetically sorted\n-\t * array with the 'HEAD' ref at the beginning followed by\n-\t * local branches 'refs/heads/...' and finally remote-tacking\n-\t * branches 'refs/remotes/...'.\n-\t */\n-\tif (!sorting)\n-\t\tsorting = ref_default_sorting();\n \tref_array_sort(sorting, &array);\n \n \tfor (i = 0; i < array.nr; i++)\n@@ -645,6 +636,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tconst char *new_upstream = NULL;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n+\tint icase = 0;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \n \tstruct option options[] = {\n@@ -686,6 +678,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\t\tOPTION_CALLBACK, 0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), 0, parse_opt_object_name\n \t\t},\n+\t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_END(),\n \t};\n \n@@ -723,6 +716,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n+\tfilter.ignore_case = icase;\n+\n \tfinalize_colopts(&colopts, -1);\n \tif (filter.verbose) {\n \t\tif (explicitly_enable_column(colopts))\n@@ -744,6 +739,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)\n \t\t\tfilter.kind |= FILTER_REFS_DETACHED_HEAD;\n \t\tfilter.name_patterns = argv;\n+\t\t/*\n+\t\t * If no sorting parameter is given then we default to sorting\n+\t\t * by 'refname'. This would give us an alphabetically sorted\n+\t\t * array with the 'HEAD' ref at the beginning followed by\n+\t\t * local branches 'refs/heads/...' and finally remote-tacking\n+\t\t * branches 'refs/remotes/...'.\n+\t\t */\n+\t\tif (!sorting)\n+\t\t\tsorting = ref_default_sorting();\n+\t\tsorting->ignore_case = icase;\n \t\tprint_ref_list(&filter, sorting);\n \t\tprint_columns(&output, colopts, NULL);\n \t\tstring_list_clear(&output, 0);\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 4e9f6c2..df41fa0 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -18,7 +18,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \tint i;\n \tconst char *format = \"%(objectname) %(objecttype)\\t%(refname)\";\n \tstruct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n-\tint maxcount = 0, quote_style = 0;\n+\tint maxcount = 0, quote_style = 0, icase = 0;\n \tstruct ref_array array;\n \tstruct ref_filter filter;\n \n@@ -43,6 +43,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t\tOPT_MERGED(&filter, N_(\"print only refs that are merged\")),\n \t\tOPT_NO_MERGED(&filter, N_(\"print only refs that are not merged\")),\n \t\tOPT_CONTAINS(&filter.with_commit, N_(\"print only refs which contain the commit\")),\n+\t\tOPT_BOOL(0, \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_END(),\n \t};\n \n@@ -63,6 +64,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \n \tif (!sorting)\n \t\tsorting = ref_default_sorting();\n+\tsorting->ignore_case = icase;\n+\tfilter.ignore_case = icase;\n \n \t/* for warn_ambiguous_refs */\n \tgit_config(git_default_config, NULL);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 50e4ae5..73df728 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -335,6 +335,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \tconst char *format = NULL;\n+\tint icase = 0;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n \t\t{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_(\"n\"),\n@@ -370,6 +371,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"print only tags of the object\"), 0, parse_opt_object_name\n \t\t},\n \t\tOPT_STRING(  0 , \"format\", &format, N_(\"format\"), N_(\"format to use for the output\")),\n+\t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_END()\n \t};\n \n@@ -401,6 +403,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t}\n \tif (!sorting)\n \t\tsorting = ref_default_sorting();\n+\tsorting->ignore_case = icase;\n+\tfilter.ignore_case = icase;\n \tif (cmdmode == 'l') {\n \t\tint ret;\n \t\tif (column_active(colopts)) {\ndiff --git a/ref-filter.c b/ref-filter.c\nindex f5f7a70..bd98010 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1231,8 +1231,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)\n  * matches a pattern \"refs/heads/mas\") or a wildcard (e.g. the same ref\n  * matches \"refs/heads/mas*\", too).\n  */\n-static int match_pattern(const char **patterns, const char *refname)\n+static int match_pattern(const struct ref_filter *filter, const char *refname)\n {\n+\tconst char **patterns = filter->name_patterns;\n+\tunsigned flags = 0;\n+\n+\tif (filter->ignore_case)\n+\t\tflags |= WM_CASEFOLD;\n+\n \t/*\n \t * When no '--format' option is given we need to skip the prefix\n \t * for matching refs of tags and branches.\n@@ -1243,7 +1249,7 @@ static int match_pattern(const char **patterns, const char *refname)\n \t       skip_prefix(refname, \"refs/\", &refname));\n \n \tfor (; *patterns; patterns++) {\n-\t\tif (!wildmatch(*patterns, refname, 0, NULL))\n+\t\tif (!wildmatch(*patterns, refname, flags, NULL))\n \t\t\treturn 1;\n \t}\n \treturn 0;\n@@ -1255,9 +1261,15 @@ static int match_pattern(const char **patterns, const char *refname)\n  * matches a pattern \"refs/heads/\" but not \"refs/heads/m\") or a\n  * wildcard (e.g. the same ref matches \"refs/heads/m*\", too).\n  */\n-static int match_name_as_path(const char **pattern, const char *refname)\n+static int match_name_as_path(const struct ref_filter *filter, const char *refname)\n {\n+\tconst char **pattern = filter->name_patterns;\n \tint namelen = strlen(refname);\n+\tunsigned flags = WM_PATHNAME;\n+\n+\tif (filter->ignore_case)\n+\t\tflags |= WM_CASEFOLD;\n+\n \tfor (; *pattern; pattern++) {\n \t\tconst char *p = *pattern;\n \t\tint plen = strlen(p);\n@@ -1280,8 +1292,8 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)\n \tif (!*filter->name_patterns)\n \t\treturn 1; /* No pattern always matches */\n \tif (filter->match_as_path)\n-\t\treturn match_name_as_path(filter->name_patterns, refname);\n-\treturn match_pattern(filter->name_patterns, refname);\n+\t\treturn match_name_as_path(filter, refname);\n+\treturn match_pattern(filter, refname);\n }\n \n /*\n@@ -1536,18 +1548,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n \tstruct atom_value *va, *vb;\n \tint cmp;\n \tcmp_type cmp_type = used_atom[s->atom].type;\n+\tint (*cmp_fn)(const char *, const char *);\n \n \tget_ref_atom_value(a, s->atom, &va);\n \tget_ref_atom_value(b, s->atom, &vb);\n+\tcmp_fn = s->ignore_case ? strcasecmp : strcmp;\n \tif (s->version)\n \t\tcmp = versioncmp(va->s, vb->s);\n \telse if (cmp_type == FIELD_STR)\n-\t\tcmp = strcmp(va->s, vb->s);\n+\t\tcmp = cmp_fn(va->s, vb->s);\n \telse {\n \t\tif (va->ul < vb->ul)\n \t\t\tcmp = -1;\n \t\telse if (va->ul == vb->ul)\n-\t\t\tcmp = strcmp(a->refname, b->refname);\n+\t\t\tcmp = cmp_fn(a->refname, b->refname);\n \t\telse\n \t\t\tcmp = 1;\n \t}\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 14d435e..fc55fa3 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -29,6 +29,7 @@ struct ref_sorting {\n \tstruct ref_sorting *next;\n \tint atom; /* index into used_atom array (internal) */\n \tunsigned reverse : 1,\n+\t\tignore_case : 1,\n \t\tversion : 1;\n };\n \n@@ -62,6 +63,7 @@ struct ref_filter {\n \n \tunsigned int with_commit_tag_algo : 1,\n \t\tmatch_as_path : 1,\n+\t\tignore_case : 1,\n \t\tdetached : 1;\n \tunsigned int kind,\n \t\tlines;\ndiff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh\nindex c6a3ccb..fad79e8 100755\n--- a/t/t3203-branch-output.sh\n+++ b/t/t3203-branch-output.sh\n@@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' '\n \tawk \"{print \\$NF}\" <tmp >actual &&\n \ttest_cmp expect actual\n '\n+test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' '\n+\tgit branch --list --ignore-case -v BRANCH* >tmp &&\n+\tawk \"{print \\$NF}\" <tmp >actual &&\n+\ttest_cmp expect actual\n+'\n \n test_expect_success 'git branch -v pattern does not show branch summaries' '\n \ttest_must_fail git branch -v branch*\n@@ -196,4 +201,21 @@ test_expect_success 'local-branch symrefs shortened properly' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'sort branches, ignore case' '\n+\t(\n+\t\tgit init sort-icase &&\n+\t\tcd sort-icase &&\n+\t\ttest_commit initial &&\n+\t\tgit branch branch-one &&\n+\t\tgit branch BRANCH-two &&\n+\t\tgit branch --list -i | awk \"{print \\$NF}\" >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\tbranch-one\n+\t\tBRANCH-two\n+\t\tmaster\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 8b0f71a..2d9cae3 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -27,6 +27,23 @@ test_expect_success 'listing all tags in an empty tree should output nothing' '\n \ttest $(git tag | wc -l) -eq 0\n '\n \n+test_expect_success 'sort tags, ignore case' '\n+\t(\n+\t\tgit init sort &&\n+\t\tcd sort &&\n+\t\ttest_commit initial &&\n+\t\tgit tag tag-one &&\n+\t\tgit tag TAG-two &&\n+\t\tgit tag -l -i >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\tinitial\n+\t\ttag-one\n+\t\tTAG-two\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n test_expect_success 'looking for a tag in an empty tree should fail' \\\n \t'! (tag_exists mytag)'\n \n@@ -81,6 +98,9 @@ test_expect_success 'listing all tags if one exists should output that tag' '\n test_expect_success 'listing a tag using a matching pattern should succeed' \\\n \t'git tag -l mytag'\n \n+test_expect_success 'listing a tag using a matching pattern should succeed' \\\n+\t'git tag -l --ignore-case MYTAG'\n+\n test_expect_success \\\n \t'listing a tag using a matching pattern should output that tag' \\\n \t'test $(git tag -l mytag) = mytag'\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"306614","messageId":"xmqqk2bkd5q2.fsf@gitster.mtv.corp.google.com","threadId":"44505","inReplyTo":"20161130123502.12973-1-pclouds@gmail.com","subject":"Re: [PATCH v2] tag, branch, for-each-ref: add --ignore-case for sorting and filtering","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-30T21:21:09Z","receivedAt":"2016-11-30T21:26:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> This option makes sorting ignore case, which is great when you have\n> branches named bug-12-do-something, Bug-12-do-some-more and\n> BUG-12-do-what and want to group them together. Sorting externally may\n> not be an option because we lose coloring and column layout from\n> git-branch and git-tag.\n>\n> The same could be said for filtering, but it's probably less important\n> because you can always go with the ugly pattern [bB][uU][gG]-* if you're\n> desperate.\n\nBut of course --ignore-case is of course much easier.\n\n> You can't have case-sensitive filtering and case-insensitive sorting (or\n> the other way around) with this though. But who would want that?\n\nI do not feel uncomfortable declaring that the answer to that\nquestion is \"nobody\" ;-)\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  v2 has a different approach, and I think it's a better one even with\n>  that unanswered question above.\n\nYeah, I think this would be easier to use.\n\n> @@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin\n>  \tif (filter->verbose)\n>  \t\tmaxwidth = calc_maxwidth(&array, strlen(remote_prefix));\n>  \n> -\t/*\n> -\t * If no sorting parameter is given then we default to sorting\n> -\t * by 'refname'. This would give us an alphabetically sorted\n> -\t * array with the 'HEAD' ref at the beginning followed by\n> -\t * local branches 'refs/heads/...' and finally remote-tacking\n> -\t * branches 'refs/remotes/...'.\n> -\t */\n> -\tif (!sorting)\n> -\t\tsorting = ref_default_sorting();\n\nSo it is now a BUG() to give sorting==NULL to this function, which\nis OK and I do not think we even need an assert() for it (i.e. the\ncode with the patch looks good).\n\n> @@ -744,6 +739,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\tif ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)\n>  \t\t\tfilter.kind |= FILTER_REFS_DETACHED_HEAD;\n>  \t\tfilter.name_patterns = argv;\n> +\t\t/*\n> +\t\t * If no sorting parameter is given then we default to sorting\n> +\t\t * by 'refname'. This would give us an alphabetically sorted\n> +\t\t * array with the 'HEAD' ref at the beginning followed by\n> +\t\t * local branches 'refs/heads/...' and finally remote-tacking\n> +\t\t * branches 'refs/remotes/...'.\n> +\t\t */\n> +\t\tif (!sorting)\n> +\t\t\tsorting = ref_default_sorting();\n> +\t\tsorting->ignore_case = icase;\n>  \t\tprint_ref_list(&filter, sorting);\n>  \t\tprint_columns(&output, colopts, NULL);\n>  \t\tstring_list_clear(&output, 0);\n\n... and the fallback is moved to the caller, which makes sense.\n\n> diff --git a/ref-filter.c b/ref-filter.c\n> index f5f7a70..bd98010 100644\n> --- a/ref-filter.c\n> +++ b/ref-filter.c\n> @@ -1231,8 +1231,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)\n>   * matches a pattern \"refs/heads/mas\") or a wildcard (e.g. the same ref\n>   * matches \"refs/heads/mas*\", too).\n>   */\n> -static int match_pattern(const char **patterns, const char *refname)\n> +static int match_pattern(const struct ref_filter *filter, const char *refname)\n>  {\n> +\tconst char **patterns = filter->name_patterns;\n> +\tunsigned flags = 0;\n> +\n> +\tif (filter->ignore_case)\n> +\t\tflags |= WM_CASEFOLD;\n> +\n\nAhh, OK.  My reading stuttered when seeing \"sorting and filtering\"\nin the option description for \"git tag\", but this makes perfect\nsense.  Good job.\n\n> @@ -1255,9 +1261,15 @@ static int match_pattern(const char **patterns, const char *refname)\n>   * matches a pattern \"refs/heads/\" but not \"refs/heads/m\") or a\n>   * wildcard (e.g. the same ref matches \"refs/heads/m*\", too).\n>   */\n> -static int match_name_as_path(const char **pattern, const char *refname)\n> +static int match_name_as_path(const struct ref_filter *filter, const char *refname)\n>  {\n> +\tconst char **pattern = filter->name_patterns;\n>  \tint namelen = strlen(refname);\n> +\tunsigned flags = WM_PATHNAME;\n> +\n> +\tif (filter->ignore_case)\n> +\t\tflags |= WM_CASEFOLD;\n> +\n\nLikewise.  Very simple and nicely done.\n\n> @@ -1536,18 +1548,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n>  \tstruct atom_value *va, *vb;\n>  \tint cmp;\n>  \tcmp_type cmp_type = used_atom[s->atom].type;\n> +\tint (*cmp_fn)(const char *, const char *);\n>  \n>  \tget_ref_atom_value(a, s->atom, &va);\n>  \tget_ref_atom_value(b, s->atom, &vb);\n> +\tcmp_fn = s->ignore_case ? strcasecmp : strcmp;\n>  \tif (s->version)\n>  \t\tcmp = versioncmp(va->s, vb->s);\n>  \telse if (cmp_type == FIELD_STR)\n> -\t\tcmp = strcmp(va->s, vb->s);\n> +\t\tcmp = cmp_fn(va->s, vb->s);\n>  \telse {\n>  \t\tif (va->ul < vb->ul)\n>  \t\t\tcmp = -1;\n>  \t\telse if (va->ul == vb->ul)\n> -\t\t\tcmp = strcmp(a->refname, b->refname);\n> +\t\t\tcmp = cmp_fn(a->refname, b->refname);\n>  \t\telse\n>  \t\t\tcmp = 1;\n>  \t}\n\nOK.\n\n> diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh\n> index c6a3ccb..fad79e8 100755\n> --- a/t/t3203-branch-output.sh\n> +++ b/t/t3203-branch-output.sh\n> @@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' '\n>  \tawk \"{print \\$NF}\" <tmp >actual &&\n>  \ttest_cmp expect actual\n>  '\n> +test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' '\n> +\tgit branch --list --ignore-case -v BRANCH* >tmp &&\n> +\tawk \"{print \\$NF}\" <tmp >actual &&\n> +\ttest_cmp expect actual\n> +'\n\nThe way the test ensures it found only branch-one and branch-two\nlooks very sloppy, but that was inherited from the existing one\nbefore this new one, so I'll let it pass.\n\n> @@ -196,4 +201,21 @@ test_expect_success 'local-branch symrefs shortened properly' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'sort branches, ignore case' '\n> +\t(\n> +\t\tgit init sort-icase &&\n> +\t\tcd sort-icase &&\n> +\t\ttest_commit initial &&\n> +\t\tgit branch branch-one &&\n> +\t\tgit branch BRANCH-two &&\n> +\t\tgit branch --list -i | awk \"{print \\$NF}\" >actual &&\n> +\t\tcat >expected <<-\\EOF &&\n> +\t\tbranch-one\n> +\t\tBRANCH-two\n> +\t\tmaster\n> +\t\tEOF\n> +\t\ttest_cmp expected actual\n> +\t)\n> +'\n\nIs there an existing test that uses refs with mixed cases, i.e. the\nresult of listing sorts differently with and without the -i option?\nIf not, this one should test output from both cases to ensure that\nthe command run without -i stays case sensitive.\n\n>  test_done\n> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> index 8b0f71a..2d9cae3 100755\n> --- a/t/t7004-tag.sh\n> +++ b/t/t7004-tag.sh\n> @@ -27,6 +27,23 @@ test_expect_success 'listing all tags in an empty tree should output nothing' '\n>  \ttest $(git tag | wc -l) -eq 0\n>  '\n>  \n> +test_expect_success 'sort tags, ignore case' '\n> +\t(\n> +\t\tgit init sort &&\n> +\t\tcd sort &&\n> +\t\ttest_commit initial &&\n> +\t\tgit tag tag-one &&\n> +\t\tgit tag TAG-two &&\n> +\t\tgit tag -l -i >actual &&\n> +\t\tcat >expected <<-\\EOF &&\n> +\t\tinitial\n> +\t\ttag-one\n> +\t\tTAG-two\n> +\t\tEOF\n> +\t\ttest_cmp expected actual\n> +\t)\n> +'\n\nDitto.\n\n\n>  test_expect_success 'looking for a tag in an empty tree should fail' \\\n>  \t'! (tag_exists mytag)'\n>  \n> @@ -81,6 +98,9 @@ test_expect_success 'listing all tags if one exists should output that tag' '\n>  test_expect_success 'listing a tag using a matching pattern should succeed' \\\n>  \t'git tag -l mytag'\n>  \n> +test_expect_success 'listing a tag using a matching pattern should succeed' \\\n> +\t'git tag -l --ignore-case MYTAG'\n\nThe existing one before this one merely says that \"git tag -l\" must\nexit with 0 status code, no?\n\nIOW, even \"git tag -l no-such-tag-anywhere && echo OK\" yields OK.\n\nSo there is not much point replicating it with \"-i\", unless you want\nto say that \"git tag -i -l\" also must exit with 0 status code.\n\n>  test_expect_success \\\n>  \t'listing a tag using a matching pattern should output that tag' \\\n>  \t'test $(git tag -l mytag) = mytag'\n\nI think the new one would want to mimic this one instead.  Look for\nMYTAG with the -i option and see it output mytag (in lowercase).\n\n"},{"id":"306884","messageId":"20161204025225.11158-1-pclouds@gmail.com","threadId":"44505","inReplyTo":"20161130123502.12973-1-pclouds@gmail.com","subject":"[PATCH v2] tag, branch, for-each-ref: add --ignore-case for sorting and filtering","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-12-04T02:52:25Z","receivedAt":"2016-12-04T02:53:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This options makes sorting ignore case, which is great when you have\nbranches named bug-12-do-something, Bug-12-do-some-more and\nBUG-12-do-what and want to group them together. Sorting externally may\nnot be an option because we lose coloring and column layout from\ngit-branch and git-tag.\n\nThe same could be said for filtering, but it's probably less important\nbecause you can always go with the ugly pattern [bB][uU][gG]-* if you're\ndesperate.\n\nYou can't have case-sensitive filtering and case-insensitive sorting (or\nthe other way around) with this though. For branch and tag, that should\nbe no problem. for-each-ref, as a plumbing, might want finer control.\nBut we can always add --{filter,sort}-ignore-case when there is a need\nfor it.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Changes are in tests only:\n\n    diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh\n    index fad79e8..52283df 100755\n    --- a/t/t3203-branch-output.sh\n    +++ b/t/t3203-branch-output.sh\n    @@ -208,6 +208,13 @@ test_expect_success 'sort branches, ignore case' '\n     \t\ttest_commit initial &&\n     \t\tgit branch branch-one &&\n     \t\tgit branch BRANCH-two &&\n    +\t\tgit branch --list | awk \"{print \\$NF}\" >actual &&\n    +\t\tcat >expected <<-\\EOF &&\n    +\t\tBRANCH-two\n    +\t\tbranch-one\n    +\t\tmaster\n    +\t\tEOF\n    +\t\ttest_cmp expected actual &&\n     \t\tgit branch --list -i | awk \"{print \\$NF}\" >actual &&\n     \t\tcat >expected <<-\\EOF &&\n     \t\tbranch-one\n    diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n    index 2d9cae3..07869b0 100755\n    --- a/t/t7004-tag.sh\n    +++ b/t/t7004-tag.sh\n    @@ -34,6 +34,13 @@ test_expect_success 'sort tags, ignore case' '\n     \t\ttest_commit initial &&\n     \t\tgit tag tag-one &&\n     \t\tgit tag TAG-two &&\n    +\t\tgit tag -l >actual &&\n    +\t\tcat >expected <<-\\EOF &&\n    +\t\tTAG-two\n    +\t\tinitial\n    +\t\ttag-one\n    +\t\tEOF\n    +\t\ttest_cmp expected actual &&\n     \t\tgit tag -l -i >actual &&\n     \t\tcat >expected <<-\\EOF &&\n     \t\tinitial\n    @@ -98,8 +105,8 @@ test_expect_success 'listing all tags if one exists should output that tag' '\n     test_expect_success 'listing a tag using a matching pattern should succeed' \\\n     \t'git tag -l mytag'\n     \n    -test_expect_success 'listing a tag using a matching pattern should succeed' \\\n    -\t'git tag -l --ignore-case MYTAG'\n    +test_expect_success 'listing a tag with --ignore-case' \\\n    +\t'test $(git tag -l --ignore-case MYTAG) = mytag'\n     \n     test_expect_success \\\n     \t'listing a tag using a matching pattern should output that tag' \\\n\n Documentation/git-branch.txt       |  4 ++++\n Documentation/git-for-each-ref.txt |  3 +++\n Documentation/git-tag.txt          |  4 ++++\n builtin/branch.c                   | 23 ++++++++++++++---------\n builtin/for-each-ref.c             |  5 ++++-\n builtin/tag.c                      |  4 ++++\n ref-filter.c                       | 28 +++++++++++++++++++++-------\n ref-filter.h                       |  2 ++\n t/t3203-branch-output.sh           | 29 +++++++++++++++++++++++++++++\n t/t7004-tag.sh                     | 27 +++++++++++++++++++++++++++\n 10 files changed, 112 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 1fe7344..5516a47 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -118,6 +118,10 @@ OPTIONS\n \tdefault to color output.\n \tSame as `--color=never`.\n \n+-i::\n+--ignore-case::\n+\tSorting and filtering branches are case insensitive.\n+\n --column[=<options>]::\n --no-column::\n \tDisplay branch listing in columns. See configuration variable\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex f57e69b..6d22974 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -79,6 +79,9 @@ OPTIONS\n \tOnly list refs which contain the specified commit (HEAD if not\n \tspecified).\n \n+--ignore-case::\n+\tSorting and filtering refs are case insensitive.\n+\n FIELD NAMES\n -----------\n \ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 80019c5..76cfe40 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -108,6 +108,10 @@ OPTIONS\n \tvariable if it exists, or lexicographic order otherwise. See\n \tlinkgit:git-config[1].\n \n+-i::\n+--ignore-case::\n+\tSorting and filtering tags are case insensitive.\n+\n --column[=<options>]::\n --no-column::\n \tDisplay tag listing in columns. See configuration variable\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 60cc5c8..36e0a21 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin\n \tif (filter->verbose)\n \t\tmaxwidth = calc_maxwidth(&array, strlen(remote_prefix));\n \n-\t/*\n-\t * If no sorting parameter is given then we default to sorting\n-\t * by 'refname'. This would give us an alphabetically sorted\n-\t * array with the 'HEAD' ref at the beginning followed by\n-\t * local branches 'refs/heads/...' and finally remote-tacking\n-\t * branches 'refs/remotes/...'.\n-\t */\n-\tif (!sorting)\n-\t\tsorting = ref_default_sorting();\n \tref_array_sort(sorting, &array);\n \n \tfor (i = 0; i < array.nr; i++)\n@@ -645,6 +636,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tconst char *new_upstream = NULL;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n+\tint icase = 0;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \n \tstruct option options[] = {\n@@ -686,6 +678,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\t\tOPTION_CALLBACK, 0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), 0, parse_opt_object_name\n \t\t},\n+\t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_END(),\n \t};\n \n@@ -723,6 +716,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n+\tfilter.ignore_case = icase;\n+\n \tfinalize_colopts(&colopts, -1);\n \tif (filter.verbose) {\n \t\tif (explicitly_enable_column(colopts))\n@@ -744,6 +739,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)\n \t\t\tfilter.kind |= FILTER_REFS_DETACHED_HEAD;\n \t\tfilter.name_patterns = argv;\n+\t\t/*\n+\t\t * If no sorting parameter is given then we default to sorting\n+\t\t * by 'refname'. This would give us an alphabetically sorted\n+\t\t * array with the 'HEAD' ref at the beginning followed by\n+\t\t * local branches 'refs/heads/...' and finally remote-tacking\n+\t\t * branches 'refs/remotes/...'.\n+\t\t */\n+\t\tif (!sorting)\n+\t\t\tsorting = ref_default_sorting();\n+\t\tsorting->ignore_case = icase;\n \t\tprint_ref_list(&filter, sorting);\n \t\tprint_columns(&output, colopts, NULL);\n \t\tstring_list_clear(&output, 0);\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 4e9f6c2..df41fa0 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -18,7 +18,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \tint i;\n \tconst char *format = \"%(objectname) %(objecttype)\\t%(refname)\";\n \tstruct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n-\tint maxcount = 0, quote_style = 0;\n+\tint maxcount = 0, quote_style = 0, icase = 0;\n \tstruct ref_array array;\n \tstruct ref_filter filter;\n \n@@ -43,6 +43,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t\tOPT_MERGED(&filter, N_(\"print only refs that are merged\")),\n \t\tOPT_NO_MERGED(&filter, N_(\"print only refs that are not merged\")),\n \t\tOPT_CONTAINS(&filter.with_commit, N_(\"print only refs which contain the commit\")),\n+\t\tOPT_BOOL(0, \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_END(),\n \t};\n \n@@ -63,6 +64,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \n \tif (!sorting)\n \t\tsorting = ref_default_sorting();\n+\tsorting->ignore_case = icase;\n+\tfilter.ignore_case = icase;\n \n \t/* for warn_ambiguous_refs */\n \tgit_config(git_default_config, NULL);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 50e4ae5..73df728 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -335,6 +335,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \tconst char *format = NULL;\n+\tint icase = 0;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n \t\t{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_(\"n\"),\n@@ -370,6 +371,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"print only tags of the object\"), 0, parse_opt_object_name\n \t\t},\n \t\tOPT_STRING(  0 , \"format\", &format, N_(\"format\"), N_(\"format to use for the output\")),\n+\t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_END()\n \t};\n \n@@ -401,6 +403,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t}\n \tif (!sorting)\n \t\tsorting = ref_default_sorting();\n+\tsorting->ignore_case = icase;\n+\tfilter.ignore_case = icase;\n \tif (cmdmode == 'l') {\n \t\tint ret;\n \t\tif (column_active(colopts)) {\ndiff --git a/ref-filter.c b/ref-filter.c\nindex f5f7a70..bd98010 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1231,8 +1231,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)\n  * matches a pattern \"refs/heads/mas\") or a wildcard (e.g. the same ref\n  * matches \"refs/heads/mas*\", too).\n  */\n-static int match_pattern(const char **patterns, const char *refname)\n+static int match_pattern(const struct ref_filter *filter, const char *refname)\n {\n+\tconst char **patterns = filter->name_patterns;\n+\tunsigned flags = 0;\n+\n+\tif (filter->ignore_case)\n+\t\tflags |= WM_CASEFOLD;\n+\n \t/*\n \t * When no '--format' option is given we need to skip the prefix\n \t * for matching refs of tags and branches.\n@@ -1243,7 +1249,7 @@ static int match_pattern(const char **patterns, const char *refname)\n \t       skip_prefix(refname, \"refs/\", &refname));\n \n \tfor (; *patterns; patterns++) {\n-\t\tif (!wildmatch(*patterns, refname, 0, NULL))\n+\t\tif (!wildmatch(*patterns, refname, flags, NULL))\n \t\t\treturn 1;\n \t}\n \treturn 0;\n@@ -1255,9 +1261,15 @@ static int match_pattern(const char **patterns, const char *refname)\n  * matches a pattern \"refs/heads/\" but not \"refs/heads/m\") or a\n  * wildcard (e.g. the same ref matches \"refs/heads/m*\", too).\n  */\n-static int match_name_as_path(const char **pattern, const char *refname)\n+static int match_name_as_path(const struct ref_filter *filter, const char *refname)\n {\n+\tconst char **pattern = filter->name_patterns;\n \tint namelen = strlen(refname);\n+\tunsigned flags = WM_PATHNAME;\n+\n+\tif (filter->ignore_case)\n+\t\tflags |= WM_CASEFOLD;\n+\n \tfor (; *pattern; pattern++) {\n \t\tconst char *p = *pattern;\n \t\tint plen = strlen(p);\n@@ -1280,8 +1292,8 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)\n \tif (!*filter->name_patterns)\n \t\treturn 1; /* No pattern always matches */\n \tif (filter->match_as_path)\n-\t\treturn match_name_as_path(filter->name_patterns, refname);\n-\treturn match_pattern(filter->name_patterns, refname);\n+\t\treturn match_name_as_path(filter, refname);\n+\treturn match_pattern(filter, refname);\n }\n \n /*\n@@ -1536,18 +1548,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n \tstruct atom_value *va, *vb;\n \tint cmp;\n \tcmp_type cmp_type = used_atom[s->atom].type;\n+\tint (*cmp_fn)(const char *, const char *);\n \n \tget_ref_atom_value(a, s->atom, &va);\n \tget_ref_atom_value(b, s->atom, &vb);\n+\tcmp_fn = s->ignore_case ? strcasecmp : strcmp;\n \tif (s->version)\n \t\tcmp = versioncmp(va->s, vb->s);\n \telse if (cmp_type == FIELD_STR)\n-\t\tcmp = strcmp(va->s, vb->s);\n+\t\tcmp = cmp_fn(va->s, vb->s);\n \telse {\n \t\tif (va->ul < vb->ul)\n \t\t\tcmp = -1;\n \t\telse if (va->ul == vb->ul)\n-\t\t\tcmp = strcmp(a->refname, b->refname);\n+\t\t\tcmp = cmp_fn(a->refname, b->refname);\n \t\telse\n \t\t\tcmp = 1;\n \t}\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 14d435e..fc55fa3 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -29,6 +29,7 @@ struct ref_sorting {\n \tstruct ref_sorting *next;\n \tint atom; /* index into used_atom array (internal) */\n \tunsigned reverse : 1,\n+\t\tignore_case : 1,\n \t\tversion : 1;\n };\n \n@@ -62,6 +63,7 @@ struct ref_filter {\n \n \tunsigned int with_commit_tag_algo : 1,\n \t\tmatch_as_path : 1,\n+\t\tignore_case : 1,\n \t\tdetached : 1;\n \tunsigned int kind,\n \t\tlines;\ndiff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh\nindex c6a3ccb..52283df 100755\n--- a/t/t3203-branch-output.sh\n+++ b/t/t3203-branch-output.sh\n@@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' '\n \tawk \"{print \\$NF}\" <tmp >actual &&\n \ttest_cmp expect actual\n '\n+test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' '\n+\tgit branch --list --ignore-case -v BRANCH* >tmp &&\n+\tawk \"{print \\$NF}\" <tmp >actual &&\n+\ttest_cmp expect actual\n+'\n \n test_expect_success 'git branch -v pattern does not show branch summaries' '\n \ttest_must_fail git branch -v branch*\n@@ -196,4 +201,28 @@ test_expect_success 'local-branch symrefs shortened properly' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'sort branches, ignore case' '\n+\t(\n+\t\tgit init sort-icase &&\n+\t\tcd sort-icase &&\n+\t\ttest_commit initial &&\n+\t\tgit branch branch-one &&\n+\t\tgit branch BRANCH-two &&\n+\t\tgit branch --list | awk \"{print \\$NF}\" >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\tBRANCH-two\n+\t\tbranch-one\n+\t\tmaster\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\t\tgit branch --list -i | awk \"{print \\$NF}\" >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\tbranch-one\n+\t\tBRANCH-two\n+\t\tmaster\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 8b0f71a..07869b0 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -27,6 +27,30 @@ test_expect_success 'listing all tags in an empty tree should output nothing' '\n \ttest $(git tag | wc -l) -eq 0\n '\n \n+test_expect_success 'sort tags, ignore case' '\n+\t(\n+\t\tgit init sort &&\n+\t\tcd sort &&\n+\t\ttest_commit initial &&\n+\t\tgit tag tag-one &&\n+\t\tgit tag TAG-two &&\n+\t\tgit tag -l >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\tTAG-two\n+\t\tinitial\n+\t\ttag-one\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\t\tgit tag -l -i >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\tinitial\n+\t\ttag-one\n+\t\tTAG-two\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n test_expect_success 'looking for a tag in an empty tree should fail' \\\n \t'! (tag_exists mytag)'\n \n@@ -81,6 +105,9 @@ test_expect_success 'listing all tags if one exists should output that tag' '\n test_expect_success 'listing a tag using a matching pattern should succeed' \\\n \t'git tag -l mytag'\n \n+test_expect_success 'listing a tag with --ignore-case' \\\n+\t'test $(git tag -l --ignore-case MYTAG) = mytag'\n+\n test_expect_success \\\n \t'listing a tag using a matching pattern should output that tag' \\\n \t'test $(git tag -l mytag) = mytag'\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"307049","messageId":"xmqqpol4zrt3.fsf@gitster.mtv.corp.google.com","threadId":"44505","inReplyTo":"20161204025225.11158-1-pclouds@gmail.com","subject":"Re: [PATCH v2] tag, branch, for-each-ref: add --ignore-case for sorting and filtering","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-12-06T21:11:20Z","receivedAt":"2016-12-06T21:11:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> This options makes sorting ignore case, which is great when you have\n> branches named bug-12-do-something, Bug-12-do-some-more and\n> BUG-12-do-what and want to group them together. Sorting externally may\n> not be an option because we lose coloring and column layout from\n> git-branch and git-tag.\n>\n> The same could be said for filtering, but it's probably less important\n> because you can always go with the ugly pattern [bB][uU][gG]-* if you're\n> desperate.\n>\n> You can't have case-sensitive filtering and case-insensitive sorting (or\n> the other way around) with this though. For branch and tag, that should\n> be no problem. for-each-ref, as a plumbing, might want finer control.\n> But we can always add --{filter,sort}-ignore-case when there is a need\n> for it.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n\nIt took me a while to figure out the interactions with topics in\nflight, but I think I resolved it correctly now.  There was a topic\nthat added \"--format\" to branch and tag.\n\nWill be pushed out as part of today's integration cycle.\n\nThanks.\n"}]}