ref-filter: support sorting case-insensitively
Similar to version:refname sorting refs by versions, icase:refname will sort by refnames are usually, but strcasecmp will be used instead of strcmp. This may be helpful sometimes when people name their branches <group>-<details> but somebody names it <group>-, some goes with <Group>-, or even <GROUP>-
Syntax is a big problem. This patch does not support icase:version:refname or version:icase:refname, for example. If version sorting learns about this thing, I think I prefer iversion:refname...
Or perhaps we can. I'm losing touch with for-each-ref "pretty" formats, I'm not quite sure what's the guideline here.
Another option is just use a symbol, like '-' or '*' to mark case-insensitivity. But that does not look very descriptive. I don't see any symbol suggesting this case stuff.
What do you think?
--- Documentation/git-for-each-ref.txt | 3 ++- ref-filter.c | 8 ++++++-- ref-filter.h | 1 + 3 files changed, 9 insertions(+), 3 deletions(-)
Documentation/git-for-each-ref.txt +2 -1
@@ -171,7 +171,8 @@ For sorting purposes, fields with numeric values sort in numeric order All other fields are used to sort in their byte-value order. There is also an option to sort by versions, this can be done by using -the fieldname `version:refname` or its alias `v:refname`. +the fieldname `version:refname` or its alias `v:refname`. Prefixing +"icase:" (e.g. `icase:refname`) makes sorting case-insensitive. In any case, a field name that refers to a field inapplicable to the object referred by the ref does not cause an error. It
ref-filter.c +6 -2
@@ -1542,12 +1542,12 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struif (s->version) cmp =versioncmp (va->s, vb->s);else if (cmp_type ==FIELD_STR ) - cmp =strcmp (va->s, vb->s); + cmp = s->strcmp (va->s, vb->s);else {if (va->ul < vb->ul) cmp =-1 ;else if (va->ul == vb->ul) - cmp =strcmp (a->refname, b->refname); + cmp = s->strcmp (a->refname, b->refname);else cmp =1 ; }
@@ -1646,6 +1646,7 @@ struct ref_sorting *ref_default_sorting(void) sorting->next =NULL ; sorting->atom =parse_ref_filter_atom (cstr_name, cstr_name +strlen (cstr_name)); + sorting->strcmp = strcmp;return sorting; }
@@ -1660,6 +1661,7 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) s =xcalloc (1 ,sizeof (*s)); s->next = *sorting_tail; + s->strcmp = strcmp; *sorting_tail = s;if (*arg =='-' ) {
@@ -1669,6 +1671,8 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)if (skip_prefix (arg,"version:" , &arg) ||skip_prefix (arg,"v:" , &arg)) s->version =1 ; +else if (skip_prefix (arg,"icase:" , &arg)) + s->strcmp = strcasecmp; len =strlen (arg); s->atom =parse_ref_filter_atom (arg, arg+len);return 0 ;
ref-filter.h +1 -0
@@ -30,6 +30,7 @@ struct ref_sorting {int atom;/* index into used_atom array (internal) */ unsigned reverse : 1, version : 1; +int (*strcmp)(const char *,const char *); };struct ref_array_item {
Signature · 1 line
2.11.0.rc0.161.g80d5b92
PATCH v2 1/1 tag, branch, for-each-ref: add --ignore-case for sorting and filtering
This option makes sorting ignore case, which is great when you have branches named bug-12-do-something, Bug-12-do-some-more and BUG-12-do-what and want to group them together. Sorting externally may not be an option because we lose coloring and column layout from git-branch and git-tag.
The same could be said for filtering, but it's probably less important because you can always go with the ugly pattern [bB][uU][gG]-* if you're desperate.
You can't have case-sensitive filtering and case-insensitive sorting (or the other way around) with this though. But who would want that?
---
v2 has a different approach, and I think it's a better one even with that unanswered question above.
Documentation/git-branch.txt | 4 ++++ Documentation/git-for-each-ref.txt | 3 +++ Documentation/git-tag.txt | 4 ++++ builtin/branch.c | 23 ++++++++++++++--------- builtin/for-each-ref.c | 5 ++++- builtin/tag.c | 4 ++++ ref-filter.c | 28 +++++++++++++++++++++------- ref-filter.h | 2 ++ t/t3203-branch-output.sh | 22 ++++++++++++++++++++++ t/t7004-tag.sh | 20 ++++++++++++++++++++ 10 files changed, 98 insertions(+), 17 deletions(-)
Documentation/git-branch.txt +4 -0
@@ -118,6 +118,10 @@ OPTIONS default to color output. Same as `--color=never`. +-i:: +--ignore-case:: + Sorting and filtering branches are case insensitive. + --column[=<options>]:: --no-column:: Display branch listing in columns. See configuration variable
Documentation/git-for-each-ref.txt +3 -0
@@ -79,6 +79,9 @@ OPTIONS Only list refs which contain the specified commit (HEAD if not specified). +--ignore-case:: + Sorting and filtering refs are case insensitive. + FIELD NAMES -----------
Documentation/git-tag.txt +4 -0
@@ -108,6 +108,10 @@ OPTIONS variable if it exists, or lexicographic order otherwise. See linkgit:git-config[1]. +-i:: +--ignore-case:: + Sorting and filtering tags are case insensitive. + --column[=<options>]:: --no-column:: Display tag listing in columns. See configuration variable
builtin/branch.c +14 -9
@@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortinif (filter->verbose) maxwidth =calc_maxwidth (&array,strlen (remote_prefix)); -/* -* If no sorting parameter is given then we default to sorting -* by 'refname'. This would give us an alphabetically sorted -* array with the 'HEAD' ref at the beginning followed by -* local branches 'refs/heads/...' and finally remote-tacking -* branches 'refs/remotes/...'. -*/ -if (!sorting) - sorting =ref_default_sorting ();ref_array_sort (sorting, &array);for (i =0 ; i < array.nr; i++)
@@ -645,6 +636,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)const char *new_upstream =NULL ;enum branch_track track;struct ref_filter filter; +int icase =0 ;static struct ref_sorting *sorting =NULL , **sorting_tail = &sorting;struct option options[] = {
@@ -686,6 +678,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only branches of the object"), 0, parse_opt_object_name }, + OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END(), };
@@ -723,6 +716,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)if (filter.abbrev ==-1 ) filter.abbrev =DEFAULT_ABBREV ; + filter.ignore_case = icase; +finalize_colopts (&colopts,-1 );if (filter.verbose) {if (explicitly_enable_column (colopts))
@@ -744,6 +739,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)if ((filter.kind &FILTER_REFS_BRANCHES ) && filter.detached) filter.kind |=FILTER_REFS_DETACHED_HEAD ; filter.name_patterns = argv; +/* +* If no sorting parameter is given then we default to sorting +* by 'refname'. This would give us an alphabetically sorted +* array with the 'HEAD' ref at the beginning followed by +* local branches 'refs/heads/...' and finally remote-tacking +* branches 'refs/remotes/...'. +*/ +if (!sorting) + sorting =ref_default_sorting (); + sorting->ignore_case = icase;print_ref_list (&filter, sorting);print_columns (&output, colopts,NULL );string_list_clear (&output,0 );
builtin/for-each-ref.c +4 -1
@@ -18,7 +18,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)int i;const char *format ="%(objectname) %(objecttype)\t%(refname)" ;struct ref_sorting *sorting =NULL , **sorting_tail = &sorting; -int maxcount =0 , quote_style =0 ; +int maxcount =0 , quote_style =0 , icase =0 ;struct ref_array array;struct ref_filter filter;
@@ -43,6 +43,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)OPT_MERGED (&filter,N_ ("print only refs that are merged" )),OPT_NO_MERGED (&filter,N_ ("print only refs that are not merged" )),OPT_CONTAINS (&filter.with_commit,N_ ("print only refs which contain the commit" )), +OPT_BOOL (0 ,"ignore-case" , &icase,N_ ("sorting and filtering are case insensitive" )), OPT_END(), };
@@ -63,6 +64,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!sorting) sorting =ref_default_sorting (); + sorting->ignore_case = icase; + filter.ignore_case = icase;/* for warn_ambiguous_refs */ git_config (git_default_config,NULL );
builtin/tag.c +4 -0
@@ -335,6 +335,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)struct ref_filter filter;static struct ref_sorting *sorting =NULL , **sorting_tail = &sorting;const char *format =NULL ; +int icase =0 ;struct option options[] = {OPT_CMDMODE ('l' ,"list" , &cmdmode,N_ ("list tag names" ),'l' ), {OPTION_INTEGER ,'n' ,NULL , &filter.lines,N_ ("n" ),
@@ -370,6 +371,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) N_("print only tags of the object"), 0, parse_opt_object_name }, OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),OPT_END () };
@@ -401,6 +403,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) }if (!sorting) sorting =ref_default_sorting (); + sorting->ignore_case = icase; + filter.ignore_case = icase;if (cmdmode =='l' ) {int ret;if (column_active (colopts)) {
ref-filter.c +21 -7
@@ -1231,8 +1231,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)* matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref * matches "refs/heads/mas*", too). */ -static int match_pattern (const char **patterns,const char *refname) +static int match_pattern (const struct ref_filter *filter,const char *refname) { +const char **patterns = filter->name_patterns; +unsigned flags =0 ; + +if (filter->ignore_case) + flags |=WM_CASEFOLD ; +/* * When no '--format' option is given we need to skip the prefix * for matching refs of tags and branches.
@@ -1243,7 +1249,7 @@ static int match_pattern(const char **patterns, const char *refname) skip_prefix(refname, "refs/", &refname));for (; *patterns; patterns++) { -if (!wildmatch (*patterns, refname,0 ,NULL )) +if (!wildmatch (*patterns, refname, flags,NULL ))return 1 ; }return 0 ;
@@ -1255,9 +1261,15 @@ static int match_pattern(const char **patterns, const char *refname)* matches a pattern "refs/heads/" but not "refs/heads/m") or a * wildcard (e.g. the same ref matches "refs/heads/m*", too). */ -static int match_name_as_path (const char **pattern,const char *refname) +static int match_name_as_path (const struct ref_filter *filter,const char *refname) { +const char **pattern = filter->name_patterns;int namelen =strlen (refname); +unsigned flags =WM_PATHNAME ; + +if (filter->ignore_case) + flags |=WM_CASEFOLD ; +for (; *pattern; pattern++) {const char *p = *pattern;int plen =strlen (p);
@@ -1280,8 +1292,8 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)if (!*filter->name_patterns)return 1 ;/* No pattern always matches */ if (filter->match_as_path) -return match_name_as_path (filter->name_patterns, refname); -return match_pattern (filter->name_patterns, refname); +return match_name_as_path (filter, refname); +return match_pattern (filter, refname); }/*
@@ -1536,18 +1548,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, strustruct atom_value *va, *vb;int cmp;cmp_type cmp_type = used_atom[s->atom].type; +int (*cmp_fn)(const char *,const char *);get_ref_atom_value (a, s->atom, &va);get_ref_atom_value (b, s->atom, &vb); + cmp_fn = s->ignore_case ? strcasecmp : strcmp;if (s->version) cmp =versioncmp (va->s, vb->s);else if (cmp_type ==FIELD_STR ) - cmp =strcmp (va->s, vb->s); + cmp =cmp_fn (va->s, vb->s);else {if (va->ul < vb->ul) cmp =-1 ;else if (va->ul == vb->ul) - cmp =strcmp (a->refname, b->refname); + cmp =cmp_fn (a->refname, b->refname);else cmp =1 ; }
ref-filter.h +2 -0
@@ -29,6 +29,7 @@ struct ref_sorting {struct ref_sorting *next;int atom;/* index into used_atom array (internal) */ unsigned reverse : 1, + ignore_case : 1, version : 1; };
@@ -62,6 +63,7 @@ struct ref_filter { unsigned int with_commit_tag_algo : 1, match_as_path : 1, + ignore_case : 1, detached : 1;unsigned int kind, lines;
t/t3203-branch-output.sh +22 -0
@@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' ' awk "{print \$NF}" <tmp >actual && test_cmp expect actual ' +test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' ' + git branch --list --ignore-case -v BRANCH* >tmp && + awk "{print \$NF}" <tmp >actual && + test_cmp expect actual +' test_expect_success 'git branch -v pattern does not show branch summaries' ' test_must_fail git branch -v branch*
@@ -196,4 +201,21 @@ test_expect_success 'local-branch symrefs shortened properly' ' test_cmp expect actual ' +test_expect_success 'sort branches, ignore case' ' + ( + git init sort-icase && + cd sort-icase && + test_commit initial && + git branch branch-one && + git branch BRANCH-two && + git branch --list -i | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + branch-one + BRANCH-two + master + EOF + test_cmp expected actual + ) +' + test_done
t/t7004-tag.sh +20 -0
@@ -27,6 +27,23 @@ test_expect_success 'listing all tags in an empty tree should output nothing' ' test $(git tag | wc -l) -eq 0 ' +test_expect_success 'sort tags, ignore case' ' + ( + git init sort && + cd sort && + test_commit initial && + git tag tag-one && + git tag TAG-two && + git tag -l -i >actual && + cat >expected <<-\EOF && + initial + tag-one + TAG-two + EOF + test_cmp expected actual + ) +' + test_expect_success 'looking for a tag in an empty tree should fail' \ '! (tag_exists mytag)'
@@ -81,6 +98,9 @@ test_expect_success 'listing all tags if one exists should output that tag' ' test_expect_success 'listing a tag using a matching pattern should succeed' \ 'git tag -l mytag' +test_expect_success 'listing a tag using a matching pattern should succeed' \ + 'git tag -l --ignore-case MYTAG' + test_expect_success \ 'listing a tag using a matching pattern should output that tag' \ 'test $(git tag -l mytag) = mytag'
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v2] tag, branch, for-each-ref: add --ignore-case for sorting and filtering
Quoted 9 lines · Nguyễn Thái Ngọc Duy
This option makes sorting ignore case, which is great when you have branches named bug-12-do-something, Bug-12-do-some-more and BUG-12-do-what and want to group them together. Sorting externally may not be an option because we lose coloring and column layout from git-branch and git-tag.
The same could be said for filtering, but it's probably less important because you can always go with the ugly pattern [bB][uU][gG]-* if you're desperate.
But of course --ignore-case is of course much easier.
Quoted 2 lines
You can't have case-sensitive filtering and case-insensitive sorting (or the other way around) with this though. But who would want that?
I do not feel uncomfortable declaring that the answer to that question is "nobody" ;-)
Quoted 4 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
v2 has a different approach, and I think it's a better one even with that unanswered question above.
Yeah, I think this would be easier to use.
builtin/branch.c static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin · line 523
Quoted 13 lines
builtin/branch.c +0
-9@@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortinif (filter->verbose) maxwidth =calc_maxwidth (&array,strlen (remote_prefix)); -/* -* If no sorting parameter is given then we default to sorting -* by 'refname'. This would give us an alphabetically sorted -* array with the 'HEAD' ref at the beginning followed by -* local branches 'refs/heads/...' and finally remote-tacking -* branches 'refs/remotes/...'. -*/ -if (!sorting) - sorting =ref_default_sorting ();
So it is now a BUG() to give sorting==NULL to this function, which is OK and I do not think we even need an assert() for it (i.e. the code with the patch looks good).
Quoted 7 lines
builtin/branch.c +3
-0@@ -744,6 +739,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)if ((filter.kind &FILTER_REFS_BRANCHES ) && filter.detached) filter.kind |=FILTER_REFS_DETACHED_HEAD ; filter.name_patterns = argv; +/* +* If no sorting parameter is given then we default to sorting +* by 'refname'. This would give us an alphabetically sorted
builtin/branch.c int cmd_branch(int argc, const char **argv, const char *prefix) · line 754
Quoted 10 lines
builtin/branch.c +7
-0+* array with the 'HEAD' ref at the beginning followed by +* local branches 'refs/heads/...' and finally remote-tacking +* branches 'refs/remotes/...'. +*/ +if (!sorting) + sorting =ref_default_sorting (); + sorting->ignore_case = icase;print_ref_list (&filter, sorting);print_columns (&output, colopts,NULL );string_list_clear (&output,0 );
... and the fallback is moved to the caller, which makes sense.
Quoted 7 lines
ref-filter.c +0
-0@@ -1231,8 +1231,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)* matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref * matches "refs/heads/mas*", too).
ref-filter.c static int commit_contains(struct ref_filter *filter, struct commit *commit) · line 1241
Quoted 10 lines
ref-filter.c +7
-1*/ -static int match_pattern (const char **patterns,const char *refname) +static int match_pattern (const struct ref_filter *filter,const char *refname) { +const char **patterns = filter->name_patterns; +unsigned flags =0 ; + +if (filter->ignore_case) + flags |=WM_CASEFOLD ; +
Ahh, OK. My reading stuttered when seeing "sorting and filtering" in the option description for "git tag", but this makes perfect sense. Good job.
Quoted 4 lines
ref-filter.c +0
-0@@ -1255,9 +1261,15 @@ static int match_pattern(const char **patterns, const char *refname)* matches a pattern "refs/heads/" but not "refs/heads/m") or a * wildcard (e.g. the same ref matches "refs/heads/m*", too). */
ref-filter.c static int match_pattern(const char **patterns, const char *refname) · line 1272
Quoted 10 lines
ref-filter.c +7
-1-static int match_name_as_path (const char **pattern,const char *refname) +static int match_name_as_path (const struct ref_filter *filter,const char *refname) { +const char **pattern = filter->name_patterns;int namelen =strlen (refname); +unsigned flags =WM_PATHNAME ; + +if (filter->ignore_case) + flags |=WM_CASEFOLD ; +
Likewise. Very simple and nicely done.
Quoted 13 lines
ref-filter.c +2
-1@@ -1536,18 +1548,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, strustruct atom_value *va, *vb;int cmp;cmp_type cmp_type = used_atom[s->atom].type; +int (*cmp_fn)(const char *,const char *);get_ref_atom_value (a, s->atom, &va);get_ref_atom_value (b, s->atom, &vb); + cmp_fn = s->ignore_case ? strcasecmp : strcmp;if (s->version) cmp =versioncmp (va->s, vb->s);else if (cmp_type ==FIELD_STR ) - cmp =strcmp (va->s, vb->s);
ref-filter.c static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru · line 1567
Quoted 10 lines
ref-filter.c +2
-1+ cmp =cmp_fn (va->s, vb->s); else {if (va->ul < vb->ul) cmp =-1 ;else if (va->ul == vb->ul) - cmp =strcmp (a->refname, b->refname); + cmp =cmp_fn (a->refname, b->refname);else cmp =1 ; }
OK.
t/t3203-branch-output.sh test_expect_success 'git branch --list -v pattern shows branch summaries' ' · line 96
Quoted 13 lines
t/t3203-branch-output.sh +5
-0@@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' ' awk "{print \$NF}" <tmp >actual && test_cmp expect actual ' +test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' ' + git branch --list --ignore-case -v BRANCH* >tmp && + awk "{print \$NF}" <tmp >actual && + test_cmp expect actual +'
The way the test ensures it found only branch-one and branch-two looks very sloppy, but that was inherited from the existing one before this new one, so I'll let it pass.
Quoted 10 lines
t/t3203-branch-output.sh +6
-0@@ -196,4 +201,21 @@ test_expect_success 'local-branch symrefs shortened properly' ' test_cmp expect actual ' +test_expect_success 'sort branches, ignore case' ' + ( + git init sort-icase && + cd sort-icase && + test_commit initial && + git branch branch-one &&
t/t3203-branch-output.sh test_expect_success 'local-branch symrefs shortened properly' ' · line 219
Quoted 10 lines
t/t3203-branch-output.sh +10
-0+ git branch BRANCH-two && + git branch --list -i | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + branch-one + BRANCH-two + master + EOF + test_cmp expected actual + ) +'
Is there an existing test that uses refs with mixed cases, i.e. the result of listing sorts differently with and without the -i option? If not, this one should test output from both cases to ensure that the command run without -i stays case sensitive.
Quoted 15 lines
t/t3203-branch-output.sh +0
-0test_donet/t7004-tag.sh +6
-0@@ -27,6 +27,23 @@ test_expect_success 'listing all tags in an empty tree should output nothing' ' test $(git tag | wc -l) -eq 0 ' +test_expect_success 'sort tags, ignore case' ' + ( + git init sort && + cd sort && + test_commit initial && + git tag tag-one &&
t/t7004-tag.sh test_expect_success 'listing all tags in an empty tree should output nothing' ' · line 45
Quoted 10 lines
t/t7004-tag.sh +10
-0+ git tag TAG-two && + git tag -l -i >actual && + cat >expected <<-\EOF && + initial + tag-one + TAG-two + EOF + test_cmp expected actual + ) +'
Ditto.
t/t7004-tag.sh test_expect_success 'listing all tags if one exists should output that tag' ' · line 102
Quoted 9 lines
t/t7004-tag.sh +2
-0test_expect_success 'looking for a tag in an empty tree should fail' \ '! (tag_exists mytag)'@@ -81,6 +98,9 @@ test_expect_success 'listing all tags if one exists should output that tag' ' test_expect_success 'listing a tag using a matching pattern should succeed' \ 'git tag -l mytag' +test_expect_success 'listing a tag using a matching pattern should succeed' \ + 'git tag -l --ignore-case MYTAG'
The existing one before this one merely says that "git tag -l" must exit with 0 status code, no?
IOW, even "git tag -l no-such-tag-anywhere && echo OK" yields OK.
So there is not much point replicating it with "-i", unless you want to say that "git tag -i -l" also must exit with 0 status code.
t/t7004-tag.sh test_expect_success 'listing all tags if one exists should output that tag' ' · line 106
Quoted 3 lines
t/t7004-tag.sh +0
-0test_expect_success \ 'listing a tag using a matching pattern should output that tag' \ 'test $(git tag -l mytag) = mytag'
I think the new one would want to mimic this one instead. Look for MYTAG with the -i option and see it output mytag (in lowercase).
PATCH v2 1/1 tag, branch, for-each-ref: add --ignore-case for sorting and filtering
In reply to Nguyễn Thái Ngọc DuyThis options makes sorting ignore case, which is great when you have branches named bug-12-do-something, Bug-12-do-some-more and BUG-12-do-what and want to group them together. Sorting externally may not be an option because we lose coloring and column layout from git-branch and git-tag.
The same could be said for filtering, but it's probably less important because you can always go with the ugly pattern [bB][uU][gG]-* if you're desperate.
You can't have case-sensitive filtering and case-insensitive sorting (or the other way around) with this though. For branch and tag, that should be no problem. for-each-ref, as a plumbing, might want finer control. But we can always add --{filter,sort}-ignore-case when there is a need for it.
---
Changes are in tests only:
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
index fad79e8..52283df 100755
--- a/t/t3203-branch-output.sh
+++ b/t/t3203-branch-output.sh
@@ -208,6 +208,13 @@ test_expect_success 'sort branches, ignore case' '
test_commit initial &&
git branch branch-one &&
git branch BRANCH-two &&
+ git branch --list | awk "{print \$NF}" >actual &&
+ cat >expected <<-\EOF &&
+ BRANCH-two
+ branch-one
+ master
+ EOF
+ test_cmp expected actual &&
git branch --list -i | awk "{print \$NF}" >actual &&
cat >expected <<-\EOF &&
branch-one
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 2d9cae3..07869b0 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -34,6 +34,13 @@ test_expect_success 'sort tags, ignore case' '
test_commit initial &&
git tag tag-one &&
git tag TAG-two &&
+ git tag -l >actual &&
+ cat >expected <<-\EOF &&
+ TAG-two
+ initial
+ tag-one
+ EOF
+ test_cmp expected actual &&
git tag -l -i >actual &&
cat >expected <<-\EOF &&
initial
@@ -98,8 +105,8 @@ test_expect_success 'listing all tags if one exists should output that tag' '
test_expect_success 'listing a tag using a matching pattern should succeed' \
'git tag -l mytag'
-test_expect_success 'listing a tag using a matching pattern should succeed' \
- 'git tag -l --ignore-case MYTAG'
+test_expect_success 'listing a tag with --ignore-case' \
+ 'test $(git tag -l --ignore-case MYTAG) = mytag'
test_expect_success \
'listing a tag using a matching pattern should output that tag' \Documentation/git-branch.txt | 4 ++++ Documentation/git-for-each-ref.txt | 3 +++ Documentation/git-tag.txt | 4 ++++ builtin/branch.c | 23 ++++++++++++++--------- builtin/for-each-ref.c | 5 ++++- builtin/tag.c | 4 ++++ ref-filter.c | 28 +++++++++++++++++++++------- ref-filter.h | 2 ++ t/t3203-branch-output.sh | 29 +++++++++++++++++++++++++++++ t/t7004-tag.sh | 27 +++++++++++++++++++++++++++ 10 files changed, 112 insertions(+), 17 deletions(-)
Documentation/git-branch.txt +4 -0
@@ -118,6 +118,10 @@ OPTIONS default to color output. Same as `--color=never`. +-i:: +--ignore-case:: + Sorting and filtering branches are case insensitive. + --column[=<options>]:: --no-column:: Display branch listing in columns. See configuration variable
Documentation/git-for-each-ref.txt +3 -0
@@ -79,6 +79,9 @@ OPTIONS Only list refs which contain the specified commit (HEAD if not specified). +--ignore-case:: + Sorting and filtering refs are case insensitive. + FIELD NAMES -----------
Documentation/git-tag.txt +4 -0
@@ -108,6 +108,10 @@ OPTIONS variable if it exists, or lexicographic order otherwise. See linkgit:git-config[1]. +-i:: +--ignore-case:: + Sorting and filtering tags are case insensitive. + --column[=<options>]:: --no-column:: Display tag listing in columns. See configuration variable
builtin/branch.c +14 -9
@@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortinif (filter->verbose) maxwidth =calc_maxwidth (&array,strlen (remote_prefix)); -/* -* If no sorting parameter is given then we default to sorting -* by 'refname'. This would give us an alphabetically sorted -* array with the 'HEAD' ref at the beginning followed by -* local branches 'refs/heads/...' and finally remote-tacking -* branches 'refs/remotes/...'. -*/ -if (!sorting) - sorting =ref_default_sorting ();ref_array_sort (sorting, &array);for (i =0 ; i < array.nr; i++)
@@ -645,6 +636,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)const char *new_upstream =NULL ;enum branch_track track;struct ref_filter filter; +int icase =0 ;static struct ref_sorting *sorting =NULL , **sorting_tail = &sorting;struct option options[] = {
@@ -686,6 +678,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only branches of the object"), 0, parse_opt_object_name }, + OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END(), };
@@ -723,6 +716,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)if (filter.abbrev ==-1 ) filter.abbrev =DEFAULT_ABBREV ; + filter.ignore_case = icase; +finalize_colopts (&colopts,-1 );if (filter.verbose) {if (explicitly_enable_column (colopts))
@@ -744,6 +739,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)if ((filter.kind &FILTER_REFS_BRANCHES ) && filter.detached) filter.kind |=FILTER_REFS_DETACHED_HEAD ; filter.name_patterns = argv; +/* +* If no sorting parameter is given then we default to sorting +* by 'refname'. This would give us an alphabetically sorted +* array with the 'HEAD' ref at the beginning followed by +* local branches 'refs/heads/...' and finally remote-tacking +* branches 'refs/remotes/...'. +*/ +if (!sorting) + sorting =ref_default_sorting (); + sorting->ignore_case = icase;print_ref_list (&filter, sorting);print_columns (&output, colopts,NULL );string_list_clear (&output,0 );
builtin/for-each-ref.c +4 -1
@@ -18,7 +18,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)int i;const char *format ="%(objectname) %(objecttype)\t%(refname)" ;struct ref_sorting *sorting =NULL , **sorting_tail = &sorting; -int maxcount =0 , quote_style =0 ; +int maxcount =0 , quote_style =0 , icase =0 ;struct ref_array array;struct ref_filter filter;
@@ -43,6 +43,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)OPT_MERGED (&filter,N_ ("print only refs that are merged" )),OPT_NO_MERGED (&filter,N_ ("print only refs that are not merged" )),OPT_CONTAINS (&filter.with_commit,N_ ("print only refs which contain the commit" )), +OPT_BOOL (0 ,"ignore-case" , &icase,N_ ("sorting and filtering are case insensitive" )), OPT_END(), };
@@ -63,6 +64,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!sorting) sorting =ref_default_sorting (); + sorting->ignore_case = icase; + filter.ignore_case = icase;/* for warn_ambiguous_refs */ git_config (git_default_config,NULL );
builtin/tag.c +4 -0
@@ -335,6 +335,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)struct ref_filter filter;static struct ref_sorting *sorting =NULL , **sorting_tail = &sorting;const char *format =NULL ; +int icase =0 ;struct option options[] = {OPT_CMDMODE ('l' ,"list" , &cmdmode,N_ ("list tag names" ),'l' ), {OPTION_INTEGER ,'n' ,NULL , &filter.lines,N_ ("n" ),
@@ -370,6 +371,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) N_("print only tags of the object"), 0, parse_opt_object_name }, OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),OPT_END () };
@@ -401,6 +403,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) }if (!sorting) sorting =ref_default_sorting (); + sorting->ignore_case = icase; + filter.ignore_case = icase;if (cmdmode =='l' ) {int ret;if (column_active (colopts)) {
ref-filter.c +21 -7
@@ -1231,8 +1231,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)* matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref * matches "refs/heads/mas*", too). */ -static int match_pattern (const char **patterns,const char *refname) +static int match_pattern (const struct ref_filter *filter,const char *refname) { +const char **patterns = filter->name_patterns; +unsigned flags =0 ; + +if (filter->ignore_case) + flags |=WM_CASEFOLD ; +/* * When no '--format' option is given we need to skip the prefix * for matching refs of tags and branches.
@@ -1243,7 +1249,7 @@ static int match_pattern(const char **patterns, const char *refname) skip_prefix(refname, "refs/", &refname));for (; *patterns; patterns++) { -if (!wildmatch (*patterns, refname,0 ,NULL )) +if (!wildmatch (*patterns, refname, flags,NULL ))return 1 ; }return 0 ;
@@ -1255,9 +1261,15 @@ static int match_pattern(const char **patterns, const char *refname)* matches a pattern "refs/heads/" but not "refs/heads/m") or a * wildcard (e.g. the same ref matches "refs/heads/m*", too). */ -static int match_name_as_path (const char **pattern,const char *refname) +static int match_name_as_path (const struct ref_filter *filter,const char *refname) { +const char **pattern = filter->name_patterns;int namelen =strlen (refname); +unsigned flags =WM_PATHNAME ; + +if (filter->ignore_case) + flags |=WM_CASEFOLD ; +for (; *pattern; pattern++) {const char *p = *pattern;int plen =strlen (p);
@@ -1280,8 +1292,8 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)if (!*filter->name_patterns)return 1 ;/* No pattern always matches */ if (filter->match_as_path) -return match_name_as_path (filter->name_patterns, refname); -return match_pattern (filter->name_patterns, refname); +return match_name_as_path (filter, refname); +return match_pattern (filter, refname); }/*
@@ -1536,18 +1548,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, strustruct atom_value *va, *vb;int cmp;cmp_type cmp_type = used_atom[s->atom].type; +int (*cmp_fn)(const char *,const char *);get_ref_atom_value (a, s->atom, &va);get_ref_atom_value (b, s->atom, &vb); + cmp_fn = s->ignore_case ? strcasecmp : strcmp;if (s->version) cmp =versioncmp (va->s, vb->s);else if (cmp_type ==FIELD_STR ) - cmp =strcmp (va->s, vb->s); + cmp =cmp_fn (va->s, vb->s);else {if (va->ul < vb->ul) cmp =-1 ;else if (va->ul == vb->ul) - cmp =strcmp (a->refname, b->refname); + cmp =cmp_fn (a->refname, b->refname);else cmp =1 ; }
ref-filter.h +2 -0
@@ -29,6 +29,7 @@ struct ref_sorting {struct ref_sorting *next;int atom;/* index into used_atom array (internal) */ unsigned reverse : 1, + ignore_case : 1, version : 1; };
@@ -62,6 +63,7 @@ struct ref_filter { unsigned int with_commit_tag_algo : 1, match_as_path : 1, + ignore_case : 1, detached : 1;unsigned int kind, lines;
t/t3203-branch-output.sh +29 -0
@@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' ' awk "{print \$NF}" <tmp >actual && test_cmp expect actual ' +test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' ' + git branch --list --ignore-case -v BRANCH* >tmp && + awk "{print \$NF}" <tmp >actual && + test_cmp expect actual +' test_expect_success 'git branch -v pattern does not show branch summaries' ' test_must_fail git branch -v branch*
@@ -196,4 +201,28 @@ test_expect_success 'local-branch symrefs shortened properly' ' test_cmp expect actual ' +test_expect_success 'sort branches, ignore case' ' + ( + git init sort-icase && + cd sort-icase && + test_commit initial && + git branch branch-one && + git branch BRANCH-two && + git branch --list | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + BRANCH-two + branch-one + master + EOF + test_cmp expected actual && + git branch --list -i | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + branch-one + BRANCH-two + master + EOF + test_cmp expected actual + ) +' + test_done
t/t7004-tag.sh +27 -0
@@ -27,6 +27,30 @@ test_expect_success 'listing all tags in an empty tree should output nothing' ' test $(git tag | wc -l) -eq 0 ' +test_expect_success 'sort tags, ignore case' ' + ( + git init sort && + cd sort && + test_commit initial && + git tag tag-one && + git tag TAG-two && + git tag -l >actual && + cat >expected <<-\EOF && + TAG-two + initial + tag-one + EOF + test_cmp expected actual && + git tag -l -i >actual && + cat >expected <<-\EOF && + initial + tag-one + TAG-two + EOF + test_cmp expected actual + ) +' + test_expect_success 'looking for a tag in an empty tree should fail' \ '! (tag_exists mytag)'
@@ -81,6 +105,9 @@ test_expect_success 'listing all tags if one exists should output that tag' ' test_expect_success 'listing a tag using a matching pattern should succeed' \ 'git tag -l mytag' +test_expect_success 'listing a tag with --ignore-case' \ + 'test $(git tag -l --ignore-case MYTAG) = mytag' + test_expect_success \ 'listing a tag using a matching pattern should output that tag' \ 'test $(git tag -l mytag) = mytag'
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v2] tag, branch, for-each-ref: add --ignore-case for sorting and filtering
Quoted 10 lines · Nguyễn Thái Ngọc Duy
This options makes sorting ignore case, which is great when you have branches named bug-12-do-something, Bug-12-do-some-more and BUG-12-do-what and want to group them together. Sorting externally may not be an option because we lose coloring and column layout from git-branch and git-tag.
The same could be said for filtering, but it's probably less important because you can always go with the ugly pattern [bB][uU][gG]-* if you're desperate.
Quoted 8 lines
You can't have case-sensitive filtering and case-insensitive sorting (or the other way around) with this though. For branch and tag, that should be no problem. for-each-ref, as a plumbing, might want finer control. But we can always add --{filter,sort}-ignore-case when there is a need for it.
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
It took me a while to figure out the interactions with topics in flight, but I think I resolved it correctly now. There was a topic that added "--format" to branch and tag.
Will be pushed out as part of today's integration cycle.
Thanks.