Commit 3bb16a8b authored by Duy Nguyen's avatar Duy Nguyen Committed by Junio C Hamano

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: Duy Nguyen's avatarNguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: default avatarJunio C Hamano <gitster@pobox.com>
parent 454cb6bd
......@@ -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
......
......@@ -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
-----------
......
......@@ -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
......
......@@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
if (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);
......
......@@ -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);
......
......@@ -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)) {
......
......@@ -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, stru
struct 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;
}
......
......@@ -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;
......
......@@ -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
......@@ -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'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment