tag, branch, for-each-ref: add --ignore-case for sorting and filtering
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. 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 <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
454cb6bd52
commit
3bb16a8bf2
@ -118,6 +118,10 @@ OPTIONS
|
|||||||
default to color output.
|
default to color output.
|
||||||
Same as `--color=never`.
|
Same as `--color=never`.
|
||||||
|
|
||||||
|
-i::
|
||||||
|
--ignore-case::
|
||||||
|
Sorting and filtering branches are case insensitive.
|
||||||
|
|
||||||
--column[=<options>]::
|
--column[=<options>]::
|
||||||
--no-column::
|
--no-column::
|
||||||
Display branch listing in columns. See configuration variable
|
Display branch listing in columns. See configuration variable
|
||||||
|
@ -79,6 +79,9 @@ OPTIONS
|
|||||||
Only list refs which contain the specified commit (HEAD if not
|
Only list refs which contain the specified commit (HEAD if not
|
||||||
specified).
|
specified).
|
||||||
|
|
||||||
|
--ignore-case::
|
||||||
|
Sorting and filtering refs are case insensitive.
|
||||||
|
|
||||||
FIELD NAMES
|
FIELD NAMES
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@ OPTIONS
|
|||||||
variable if it exists, or lexicographic order otherwise. See
|
variable if it exists, or lexicographic order otherwise. See
|
||||||
linkgit:git-config[1].
|
linkgit:git-config[1].
|
||||||
|
|
||||||
|
-i::
|
||||||
|
--ignore-case::
|
||||||
|
Sorting and filtering tags are case insensitive.
|
||||||
|
|
||||||
--column[=<options>]::
|
--column[=<options>]::
|
||||||
--no-column::
|
--no-column::
|
||||||
Display tag listing in columns. See configuration variable
|
Display tag listing in columns. See configuration variable
|
||||||
|
@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
|
|||||||
if (filter->verbose)
|
if (filter->verbose)
|
||||||
maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
|
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);
|
ref_array_sort(sorting, &array);
|
||||||
|
|
||||||
for (i = 0; i < array.nr; i++)
|
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;
|
const char *new_upstream = NULL;
|
||||||
enum branch_track track;
|
enum branch_track track;
|
||||||
struct ref_filter filter;
|
struct ref_filter filter;
|
||||||
|
int icase = 0;
|
||||||
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
|
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
|
||||||
|
|
||||||
struct option options[] = {
|
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"),
|
OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
|
||||||
N_("print only branches of the object"), 0, parse_opt_object_name
|
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(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -723,6 +716,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
|
|||||||
|
|
||||||
if (filter.abbrev == -1)
|
if (filter.abbrev == -1)
|
||||||
filter.abbrev = DEFAULT_ABBREV;
|
filter.abbrev = DEFAULT_ABBREV;
|
||||||
|
filter.ignore_case = icase;
|
||||||
|
|
||||||
finalize_colopts(&colopts, -1);
|
finalize_colopts(&colopts, -1);
|
||||||
if (filter.verbose) {
|
if (filter.verbose) {
|
||||||
if (explicitly_enable_column(colopts))
|
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)
|
if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
|
||||||
filter.kind |= FILTER_REFS_DETACHED_HEAD;
|
filter.kind |= FILTER_REFS_DETACHED_HEAD;
|
||||||
filter.name_patterns = argv;
|
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_ref_list(&filter, sorting);
|
||||||
print_columns(&output, colopts, NULL);
|
print_columns(&output, colopts, NULL);
|
||||||
string_list_clear(&output, 0);
|
string_list_clear(&output, 0);
|
||||||
|
@ -18,7 +18,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
|||||||
int i;
|
int i;
|
||||||
const char *format = "%(objectname) %(objecttype)\t%(refname)";
|
const char *format = "%(objectname) %(objecttype)\t%(refname)";
|
||||||
struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
|
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_array array;
|
||||||
struct ref_filter filter;
|
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_MERGED(&filter, N_("print only refs that are merged")),
|
||||||
OPT_NO_MERGED(&filter, N_("print only refs that are not 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_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(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,6 +64,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
|||||||
|
|
||||||
if (!sorting)
|
if (!sorting)
|
||||||
sorting = ref_default_sorting();
|
sorting = ref_default_sorting();
|
||||||
|
sorting->ignore_case = icase;
|
||||||
|
filter.ignore_case = icase;
|
||||||
|
|
||||||
/* for warn_ambiguous_refs */
|
/* for warn_ambiguous_refs */
|
||||||
git_config(git_default_config, NULL);
|
git_config(git_default_config, NULL);
|
||||||
|
@ -335,6 +335,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
|
|||||||
struct ref_filter filter;
|
struct ref_filter filter;
|
||||||
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
|
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
|
||||||
const char *format = NULL;
|
const char *format = NULL;
|
||||||
|
int icase = 0;
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
|
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
|
||||||
{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
|
{ 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
|
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_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()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -401,6 +403,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
|
|||||||
}
|
}
|
||||||
if (!sorting)
|
if (!sorting)
|
||||||
sorting = ref_default_sorting();
|
sorting = ref_default_sorting();
|
||||||
|
sorting->ignore_case = icase;
|
||||||
|
filter.ignore_case = icase;
|
||||||
if (cmdmode == 'l') {
|
if (cmdmode == 'l') {
|
||||||
int ret;
|
int ret;
|
||||||
if (column_active(colopts)) {
|
if (column_active(colopts)) {
|
||||||
|
28
ref-filter.c
28
ref-filter.c
@ -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 a pattern "refs/heads/mas") or a wildcard (e.g. the same ref
|
||||||
* matches "refs/heads/mas*", too).
|
* 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
|
* When no '--format' option is given we need to skip the prefix
|
||||||
* for matching refs of tags and branches.
|
* 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));
|
skip_prefix(refname, "refs/", &refname));
|
||||||
|
|
||||||
for (; *patterns; patterns++) {
|
for (; *patterns; patterns++) {
|
||||||
if (!wildmatch(*patterns, refname, 0, NULL))
|
if (!wildmatch(*patterns, refname, flags, NULL))
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
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
|
* matches a pattern "refs/heads/" but not "refs/heads/m") or a
|
||||||
* wildcard (e.g. the same ref matches "refs/heads/m*", too).
|
* 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);
|
int namelen = strlen(refname);
|
||||||
|
unsigned flags = WM_PATHNAME;
|
||||||
|
|
||||||
|
if (filter->ignore_case)
|
||||||
|
flags |= WM_CASEFOLD;
|
||||||
|
|
||||||
for (; *pattern; pattern++) {
|
for (; *pattern; pattern++) {
|
||||||
const char *p = *pattern;
|
const char *p = *pattern;
|
||||||
int plen = strlen(p);
|
int plen = strlen(p);
|
||||||
@ -1280,8 +1292,8 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)
|
|||||||
if (!*filter->name_patterns)
|
if (!*filter->name_patterns)
|
||||||
return 1; /* No pattern always matches */
|
return 1; /* No pattern always matches */
|
||||||
if (filter->match_as_path)
|
if (filter->match_as_path)
|
||||||
return match_name_as_path(filter->name_patterns, refname);
|
return match_name_as_path(filter, refname);
|
||||||
return match_pattern(filter->name_patterns, refname);
|
return match_pattern(filter, refname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1536,18 +1548,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
|
|||||||
struct atom_value *va, *vb;
|
struct atom_value *va, *vb;
|
||||||
int cmp;
|
int cmp;
|
||||||
cmp_type cmp_type = used_atom[s->atom].type;
|
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(a, s->atom, &va);
|
||||||
get_ref_atom_value(b, s->atom, &vb);
|
get_ref_atom_value(b, s->atom, &vb);
|
||||||
|
cmp_fn = s->ignore_case ? strcasecmp : strcmp;
|
||||||
if (s->version)
|
if (s->version)
|
||||||
cmp = versioncmp(va->s, vb->s);
|
cmp = versioncmp(va->s, vb->s);
|
||||||
else if (cmp_type == FIELD_STR)
|
else if (cmp_type == FIELD_STR)
|
||||||
cmp = strcmp(va->s, vb->s);
|
cmp = cmp_fn(va->s, vb->s);
|
||||||
else {
|
else {
|
||||||
if (va->ul < vb->ul)
|
if (va->ul < vb->ul)
|
||||||
cmp = -1;
|
cmp = -1;
|
||||||
else if (va->ul == vb->ul)
|
else if (va->ul == vb->ul)
|
||||||
cmp = strcmp(a->refname, b->refname);
|
cmp = cmp_fn(a->refname, b->refname);
|
||||||
else
|
else
|
||||||
cmp = 1;
|
cmp = 1;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ struct ref_sorting {
|
|||||||
struct ref_sorting *next;
|
struct ref_sorting *next;
|
||||||
int atom; /* index into used_atom array (internal) */
|
int atom; /* index into used_atom array (internal) */
|
||||||
unsigned reverse : 1,
|
unsigned reverse : 1,
|
||||||
|
ignore_case : 1,
|
||||||
version : 1;
|
version : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,6 +63,7 @@ struct ref_filter {
|
|||||||
|
|
||||||
unsigned int with_commit_tag_algo : 1,
|
unsigned int with_commit_tag_algo : 1,
|
||||||
match_as_path : 1,
|
match_as_path : 1,
|
||||||
|
ignore_case : 1,
|
||||||
detached : 1;
|
detached : 1;
|
||||||
unsigned int kind,
|
unsigned int kind,
|
||||||
lines;
|
lines;
|
||||||
|
@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' '
|
|||||||
awk "{print \$NF}" <tmp >actual &&
|
awk "{print \$NF}" <tmp >actual &&
|
||||||
test_cmp expect 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_expect_success 'git branch -v pattern does not show branch summaries' '
|
||||||
test_must_fail git branch -v branch*
|
test_must_fail git branch -v branch*
|
||||||
@ -196,4 +201,28 @@ test_expect_success 'local-branch symrefs shortened properly' '
|
|||||||
test_cmp expect actual
|
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
|
test_done
|
||||||
|
@ -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 $(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' \
|
test_expect_success 'looking for a tag in an empty tree should fail' \
|
||||||
'! (tag_exists mytag)'
|
'! (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' \
|
test_expect_success 'listing a tag using a matching pattern should succeed' \
|
||||||
'git tag -l mytag'
|
'git tag -l mytag'
|
||||||
|
|
||||||
|
test_expect_success 'listing a tag with --ignore-case' \
|
||||||
|
'test $(git tag -l --ignore-case MYTAG) = mytag'
|
||||||
|
|
||||||
test_expect_success \
|
test_expect_success \
|
||||||
'listing a tag using a matching pattern should output that tag' \
|
'listing a tag using a matching pattern should output that tag' \
|
||||||
'test $(git tag -l mytag) = mytag'
|
'test $(git tag -l mytag) = mytag'
|
||||||
|
Loading…
Reference in New Issue
Block a user