parse_pathspec: accept :(icase)path syntax
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
parent
bd30c2e484
commit
93d9353716
|
|
@ -466,6 +466,10 @@ help ...`.
|
||||||
globbing on individual pathspecs can be done using pathspec
|
globbing on individual pathspecs can be done using pathspec
|
||||||
magic ":(glob)"
|
magic ":(glob)"
|
||||||
|
|
||||||
|
--icase-pathspecs:
|
||||||
|
Add "icase" magic to all pathspec. This is equivalent to setting
|
||||||
|
the `GIT_ICASE_PATHSPECS` environment variable to `1`.
|
||||||
|
|
||||||
GIT COMMANDS
|
GIT COMMANDS
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
@ -879,6 +883,10 @@ GIT_NOGLOB_PATHSPECS::
|
||||||
Setting this variable to `1` will cause Git to treat all
|
Setting this variable to `1` will cause Git to treat all
|
||||||
pathspecs as literal (aka "literal" magic).
|
pathspecs as literal (aka "literal" magic).
|
||||||
|
|
||||||
|
GIT_ICASE_PATHSPECS::
|
||||||
|
Setting this variable to `1` will cause Git to treat all
|
||||||
|
pathspecs as case-insensitive.
|
||||||
|
|
||||||
|
|
||||||
Discussion[[Discussion]]
|
Discussion[[Discussion]]
|
||||||
------------------------
|
------------------------
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,9 @@ literal;;
|
||||||
Wildcards in the pattern such as `*` or `?` are treated
|
Wildcards in the pattern such as `*` or `?` are treated
|
||||||
as literal characters.
|
as literal characters.
|
||||||
|
|
||||||
|
icase;;
|
||||||
|
Case insensitive match.
|
||||||
|
|
||||||
glob;;
|
glob;;
|
||||||
Git treats the pattern as a shell glob suitable for
|
Git treats the pattern as a shell glob suitable for
|
||||||
consumption by fnmatch(3) with the FNM_PATHNAME flag:
|
consumption by fnmatch(3) with the FNM_PATHNAME flag:
|
||||||
|
|
|
||||||
|
|
@ -544,12 +544,14 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||||
GUARD_PATHSPEC(&pathspec,
|
GUARD_PATHSPEC(&pathspec,
|
||||||
PATHSPEC_FROMTOP |
|
PATHSPEC_FROMTOP |
|
||||||
PATHSPEC_LITERAL |
|
PATHSPEC_LITERAL |
|
||||||
PATHSPEC_GLOB);
|
PATHSPEC_GLOB |
|
||||||
|
PATHSPEC_ICASE);
|
||||||
|
|
||||||
for (i = 0; i < pathspec.nr; i++) {
|
for (i = 0; i < pathspec.nr; i++) {
|
||||||
const char *path = pathspec.items[i].match;
|
const char *path = pathspec.items[i].match;
|
||||||
if (!seen[i] &&
|
if (!seen[i] &&
|
||||||
((pathspec.items[i].magic & PATHSPEC_GLOB) ||
|
((pathspec.items[i].magic &
|
||||||
|
(PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
|
||||||
!file_exists(path))) {
|
!file_exists(path))) {
|
||||||
if (ignore_missing) {
|
if (ignore_missing) {
|
||||||
int dtype = DT_UNKNOWN;
|
int dtype = DT_UNKNOWN;
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
|
||||||
* cannot be lifted until it is converted to use
|
* cannot be lifted until it is converted to use
|
||||||
* match_pathspec_depth() or tree_entry_interesting()
|
* match_pathspec_depth() or tree_entry_interesting()
|
||||||
*/
|
*/
|
||||||
parse_pathspec(&pathspec, PATHSPEC_GLOB,
|
parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE,
|
||||||
PATHSPEC_PREFER_CWD,
|
PATHSPEC_PREFER_CWD,
|
||||||
prefix, argv + 1);
|
prefix, argv + 1);
|
||||||
for (i = 0; i < pathspec.nr; i++)
|
for (i = 0; i < pathspec.nr; i++)
|
||||||
|
|
|
||||||
1
cache.h
1
cache.h
|
|
@ -369,6 +369,7 @@ static inline enum object_type object_type(unsigned int mode)
|
||||||
#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
|
#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
|
||||||
#define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
|
#define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
|
||||||
#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
|
#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
|
||||||
|
#define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This environment variable is expected to contain a boolean indicating
|
* This environment variable is expected to contain a boolean indicating
|
||||||
|
|
|
||||||
74
dir.c
74
dir.c
|
|
@ -57,7 +57,7 @@ inline int git_fnmatch(const struct pathspec_item *item,
|
||||||
int prefix)
|
int prefix)
|
||||||
{
|
{
|
||||||
if (prefix > 0) {
|
if (prefix > 0) {
|
||||||
if (strncmp(pattern, string, prefix))
|
if (ps_strncmp(item, pattern, string, prefix))
|
||||||
return FNM_NOMATCH;
|
return FNM_NOMATCH;
|
||||||
pattern += prefix;
|
pattern += prefix;
|
||||||
string += prefix;
|
string += prefix;
|
||||||
|
|
@ -66,14 +66,18 @@ inline int git_fnmatch(const struct pathspec_item *item,
|
||||||
int pattern_len = strlen(++pattern);
|
int pattern_len = strlen(++pattern);
|
||||||
int string_len = strlen(string);
|
int string_len = strlen(string);
|
||||||
return string_len < pattern_len ||
|
return string_len < pattern_len ||
|
||||||
strcmp(pattern,
|
ps_strcmp(item, pattern,
|
||||||
string + string_len - pattern_len);
|
string + string_len - pattern_len);
|
||||||
}
|
}
|
||||||
if (item->magic & PATHSPEC_GLOB)
|
if (item->magic & PATHSPEC_GLOB)
|
||||||
return wildmatch(pattern, string, WM_PATHNAME, NULL);
|
return wildmatch(pattern, string,
|
||||||
|
WM_PATHNAME |
|
||||||
|
(item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0),
|
||||||
|
NULL);
|
||||||
else
|
else
|
||||||
/* wildmatch has not learned no FNM_PATHNAME mode yet */
|
/* wildmatch has not learned no FNM_PATHNAME mode yet */
|
||||||
return fnmatch(pattern, string, 0);
|
return fnmatch(pattern, string,
|
||||||
|
item->magic & PATHSPEC_ICASE ? FNM_CASEFOLD : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fnmatch_icase_mem(const char *pattern, int patternlen,
|
static int fnmatch_icase_mem(const char *pattern, int patternlen,
|
||||||
|
|
@ -110,16 +114,27 @@ static size_t common_prefix_len(const struct pathspec *pathspec)
|
||||||
int n;
|
int n;
|
||||||
size_t max = 0;
|
size_t max = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ":(icase)path" is treated as a pathspec full of
|
||||||
|
* wildcard. In other words, only prefix is considered common
|
||||||
|
* prefix. If the pathspec is abc/foo abc/bar, running in
|
||||||
|
* subdir xyz, the common prefix is still xyz, not xuz/abc as
|
||||||
|
* in non-:(icase).
|
||||||
|
*/
|
||||||
GUARD_PATHSPEC(pathspec,
|
GUARD_PATHSPEC(pathspec,
|
||||||
PATHSPEC_FROMTOP |
|
PATHSPEC_FROMTOP |
|
||||||
PATHSPEC_MAXDEPTH |
|
PATHSPEC_MAXDEPTH |
|
||||||
PATHSPEC_LITERAL |
|
PATHSPEC_LITERAL |
|
||||||
PATHSPEC_GLOB);
|
PATHSPEC_GLOB |
|
||||||
|
PATHSPEC_ICASE);
|
||||||
|
|
||||||
for (n = 0; n < pathspec->nr; n++) {
|
for (n = 0; n < pathspec->nr; n++) {
|
||||||
size_t i = 0, len = 0;
|
size_t i = 0, len = 0, item_len;
|
||||||
while (i < pathspec->items[n].nowildcard_len &&
|
if (pathspec->items[n].magic & PATHSPEC_ICASE)
|
||||||
(n == 0 || i < max)) {
|
item_len = pathspec->items[n].prefix;
|
||||||
|
else
|
||||||
|
item_len = pathspec->items[n].nowildcard_len;
|
||||||
|
while (i < item_len && (n == 0 || i < max)) {
|
||||||
char c = pathspec->items[n].match[i];
|
char c = pathspec->items[n].match[i];
|
||||||
if (c != pathspec->items[0].match[i])
|
if (c != pathspec->items[0].match[i])
|
||||||
break;
|
break;
|
||||||
|
|
@ -196,11 +211,44 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
|
||||||
const char *match = item->match + prefix;
|
const char *match = item->match + prefix;
|
||||||
int matchlen = item->len - prefix;
|
int matchlen = item->len - prefix;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The normal call pattern is:
|
||||||
|
* 1. prefix = common_prefix_len(ps);
|
||||||
|
* 2. prune something, or fill_directory
|
||||||
|
* 3. match_pathspec_depth()
|
||||||
|
*
|
||||||
|
* 'prefix' at #1 may be shorter than the command's prefix and
|
||||||
|
* it's ok for #2 to match extra files. Those extras will be
|
||||||
|
* trimmed at #3.
|
||||||
|
*
|
||||||
|
* Suppose the pathspec is 'foo' and '../bar' running from
|
||||||
|
* subdir 'xyz'. The common prefix at #1 will be empty, thanks
|
||||||
|
* to "../". We may have xyz/foo _and_ XYZ/foo after #2. The
|
||||||
|
* user does not want XYZ/foo, only the "foo" part should be
|
||||||
|
* case-insensitive. We need to filter out XYZ/foo here. In
|
||||||
|
* other words, we do not trust the caller on comparing the
|
||||||
|
* prefix part when :(icase) is involved. We do exact
|
||||||
|
* comparison ourselves.
|
||||||
|
*
|
||||||
|
* Normally the caller (common_prefix_len() in fact) does
|
||||||
|
* _exact_ matching on name[-prefix+1..-1] and we do not need
|
||||||
|
* to check that part. Be defensive and check it anyway, in
|
||||||
|
* case common_prefix_len is changed, or a new caller is
|
||||||
|
* introduced that does not use common_prefix_len.
|
||||||
|
*
|
||||||
|
* If the penalty turns out too high when prefix is really
|
||||||
|
* long, maybe change it to
|
||||||
|
* strncmp(match, name, item->prefix - prefix)
|
||||||
|
*/
|
||||||
|
if (item->prefix && (item->magic & PATHSPEC_ICASE) &&
|
||||||
|
strncmp(item->match, name - prefix, item->prefix))
|
||||||
|
return 0;
|
||||||
|
|
||||||
/* If the match was just the prefix, we matched */
|
/* If the match was just the prefix, we matched */
|
||||||
if (!*match)
|
if (!*match)
|
||||||
return MATCHED_RECURSIVELY;
|
return MATCHED_RECURSIVELY;
|
||||||
|
|
||||||
if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
|
if (matchlen <= namelen && !ps_strncmp(item, match, name, matchlen)) {
|
||||||
if (matchlen == namelen)
|
if (matchlen == namelen)
|
||||||
return MATCHED_EXACTLY;
|
return MATCHED_EXACTLY;
|
||||||
|
|
||||||
|
|
@ -241,7 +289,8 @@ int match_pathspec_depth(const struct pathspec *ps,
|
||||||
PATHSPEC_FROMTOP |
|
PATHSPEC_FROMTOP |
|
||||||
PATHSPEC_MAXDEPTH |
|
PATHSPEC_MAXDEPTH |
|
||||||
PATHSPEC_LITERAL |
|
PATHSPEC_LITERAL |
|
||||||
PATHSPEC_GLOB);
|
PATHSPEC_GLOB |
|
||||||
|
PATHSPEC_ICASE);
|
||||||
|
|
||||||
if (!ps->nr) {
|
if (!ps->nr) {
|
||||||
if (!ps->recursive ||
|
if (!ps->recursive ||
|
||||||
|
|
@ -1301,7 +1350,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru
|
||||||
PATHSPEC_FROMTOP |
|
PATHSPEC_FROMTOP |
|
||||||
PATHSPEC_MAXDEPTH |
|
PATHSPEC_MAXDEPTH |
|
||||||
PATHSPEC_LITERAL |
|
PATHSPEC_LITERAL |
|
||||||
PATHSPEC_GLOB);
|
PATHSPEC_GLOB |
|
||||||
|
PATHSPEC_ICASE);
|
||||||
|
|
||||||
if (has_symlink_leading_path(path, len))
|
if (has_symlink_leading_path(path, len))
|
||||||
return dir->nr;
|
return dir->nr;
|
||||||
|
|
|
||||||
4
git.c
4
git.c
|
|
@ -155,6 +155,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
|
||||||
setenv(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, "1", 1);
|
setenv(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, "1", 1);
|
||||||
if (envchanged)
|
if (envchanged)
|
||||||
*envchanged = 1;
|
*envchanged = 1;
|
||||||
|
} else if (!strcmp(cmd, "--icase-pathspecs")) {
|
||||||
|
setenv(GIT_ICASE_PATHSPECS_ENVIRONMENT, "1", 1);
|
||||||
|
if (envchanged)
|
||||||
|
*envchanged = 1;
|
||||||
} else if (!strcmp(cmd, "--shallow-file")) {
|
} else if (!strcmp(cmd, "--shallow-file")) {
|
||||||
(*argv)++;
|
(*argv)++;
|
||||||
(*argc)--;
|
(*argc)--;
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ char *find_pathspecs_matching_against_index(const struct pathspec *pathspec)
|
||||||
*
|
*
|
||||||
* Possible future magic semantics include stuff like:
|
* Possible future magic semantics include stuff like:
|
||||||
*
|
*
|
||||||
* { PATHSPEC_ICASE, '\0', "icase" },
|
|
||||||
* { PATHSPEC_RECURSIVE, '*', "recursive" },
|
* { PATHSPEC_RECURSIVE, '*', "recursive" },
|
||||||
* { PATHSPEC_REGEXP, '\0', "regexp" },
|
* { PATHSPEC_REGEXP, '\0', "regexp" },
|
||||||
*
|
*
|
||||||
|
|
@ -71,6 +70,7 @@ static struct pathspec_magic {
|
||||||
{ PATHSPEC_FROMTOP, '/', "top" },
|
{ PATHSPEC_FROMTOP, '/', "top" },
|
||||||
{ PATHSPEC_LITERAL, 0, "literal" },
|
{ PATHSPEC_LITERAL, 0, "literal" },
|
||||||
{ PATHSPEC_GLOB, '\0', "glob" },
|
{ PATHSPEC_GLOB, '\0', "glob" },
|
||||||
|
{ PATHSPEC_ICASE, '\0', "icase" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -95,6 +95,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
|
||||||
static int literal_global = -1;
|
static int literal_global = -1;
|
||||||
static int glob_global = -1;
|
static int glob_global = -1;
|
||||||
static int noglob_global = -1;
|
static int noglob_global = -1;
|
||||||
|
static int icase_global = -1;
|
||||||
unsigned magic = 0, short_magic = 0, global_magic = 0;
|
unsigned magic = 0, short_magic = 0, global_magic = 0;
|
||||||
const char *copyfrom = elt, *long_magic_end = NULL;
|
const char *copyfrom = elt, *long_magic_end = NULL;
|
||||||
char *match;
|
char *match;
|
||||||
|
|
@ -116,6 +117,12 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
|
||||||
if (glob_global && noglob_global)
|
if (glob_global && noglob_global)
|
||||||
die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));
|
die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));
|
||||||
|
|
||||||
|
|
||||||
|
if (icase_global < 0)
|
||||||
|
icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0);
|
||||||
|
if (icase_global)
|
||||||
|
global_magic |= PATHSPEC_ICASE;
|
||||||
|
|
||||||
if ((global_magic & PATHSPEC_LITERAL) &&
|
if ((global_magic & PATHSPEC_LITERAL) &&
|
||||||
(global_magic & ~PATHSPEC_LITERAL))
|
(global_magic & ~PATHSPEC_LITERAL))
|
||||||
die(_("global 'literal' pathspec setting is incompatible "
|
die(_("global 'literal' pathspec setting is incompatible "
|
||||||
|
|
|
||||||
22
pathspec.h
22
pathspec.h
|
|
@ -6,11 +6,13 @@
|
||||||
#define PATHSPEC_MAXDEPTH (1<<1)
|
#define PATHSPEC_MAXDEPTH (1<<1)
|
||||||
#define PATHSPEC_LITERAL (1<<2)
|
#define PATHSPEC_LITERAL (1<<2)
|
||||||
#define PATHSPEC_GLOB (1<<3)
|
#define PATHSPEC_GLOB (1<<3)
|
||||||
|
#define PATHSPEC_ICASE (1<<4)
|
||||||
#define PATHSPEC_ALL_MAGIC \
|
#define PATHSPEC_ALL_MAGIC \
|
||||||
(PATHSPEC_FROMTOP | \
|
(PATHSPEC_FROMTOP | \
|
||||||
PATHSPEC_MAXDEPTH | \
|
PATHSPEC_MAXDEPTH | \
|
||||||
PATHSPEC_LITERAL | \
|
PATHSPEC_LITERAL | \
|
||||||
PATHSPEC_GLOB)
|
PATHSPEC_GLOB | \
|
||||||
|
PATHSPEC_ICASE)
|
||||||
|
|
||||||
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */
|
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */
|
||||||
|
|
||||||
|
|
@ -65,6 +67,24 @@ extern void parse_pathspec(struct pathspec *pathspec,
|
||||||
extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
|
extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
|
||||||
extern void free_pathspec(struct pathspec *);
|
extern void free_pathspec(struct pathspec *);
|
||||||
|
|
||||||
|
static inline int ps_strncmp(const struct pathspec_item *item,
|
||||||
|
const char *s1, const char *s2, size_t n)
|
||||||
|
{
|
||||||
|
if (item->magic & PATHSPEC_ICASE)
|
||||||
|
return strncasecmp(s1, s2, n);
|
||||||
|
else
|
||||||
|
return strncmp(s1, s2, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int ps_strcmp(const struct pathspec_item *item,
|
||||||
|
const char *s1, const char *s2)
|
||||||
|
{
|
||||||
|
if (item->magic & PATHSPEC_ICASE)
|
||||||
|
return strcasecmp(s1, s2);
|
||||||
|
else
|
||||||
|
return strcmp(s1, s2);
|
||||||
|
}
|
||||||
|
|
||||||
extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec);
|
extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec);
|
||||||
extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen);
|
extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen);
|
||||||
extern const char *check_path_for_gitlink(const char *path);
|
extern const char *check_path_for_gitlink(const char *path);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='test case insensitive pathspec limiting'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success 'create commits with glob characters' '
|
||||||
|
test_commit bar bar &&
|
||||||
|
test_commit bAr bAr &&
|
||||||
|
test_commit BAR BAR &&
|
||||||
|
mkdir foo &&
|
||||||
|
test_commit foo/bar foo/bar &&
|
||||||
|
test_commit foo/bAr foo/bAr &&
|
||||||
|
test_commit foo/BAR foo/BAR &&
|
||||||
|
mkdir fOo &&
|
||||||
|
test_commit fOo/bar fOo/bar &&
|
||||||
|
test_commit fOo/bAr fOo/bAr &&
|
||||||
|
test_commit fOo/BAR fOo/BAR &&
|
||||||
|
mkdir FOO &&
|
||||||
|
test_commit FOO/bar FOO/bar &&
|
||||||
|
test_commit FOO/bAr FOO/bAr &&
|
||||||
|
test_commit FOO/BAR FOO/BAR
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'tree_entry_interesting matches bar' '
|
||||||
|
echo bar >expect &&
|
||||||
|
git log --format=%s -- "bar" >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'tree_entry_interesting matches :(icase)bar' '
|
||||||
|
cat <<-EOF >expect &&
|
||||||
|
BAR
|
||||||
|
bAr
|
||||||
|
bar
|
||||||
|
EOF
|
||||||
|
git log --format=%s -- ":(icase)bar" >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'tree_entry_interesting matches :(icase)bar with prefix' '
|
||||||
|
cat <<-EOF >expect &&
|
||||||
|
fOo/BAR
|
||||||
|
fOo/bAr
|
||||||
|
fOo/bar
|
||||||
|
EOF
|
||||||
|
( cd fOo && git log --format=%s -- ":(icase)bar" ) >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'tree_entry_interesting matches :(icase)bar with empty prefix' '
|
||||||
|
cat <<-EOF >expect &&
|
||||||
|
FOO/BAR
|
||||||
|
FOO/bAr
|
||||||
|
FOO/bar
|
||||||
|
fOo/BAR
|
||||||
|
fOo/bAr
|
||||||
|
fOo/bar
|
||||||
|
foo/BAR
|
||||||
|
foo/bAr
|
||||||
|
foo/bar
|
||||||
|
EOF
|
||||||
|
( cd fOo && git log --format=%s -- ":(icase)../foo/bar" ) >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'match_pathspec_depth matches :(icase)bar' '
|
||||||
|
cat <<-EOF >expect &&
|
||||||
|
BAR
|
||||||
|
bAr
|
||||||
|
bar
|
||||||
|
EOF
|
||||||
|
git ls-files ":(icase)bar" >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'match_pathspec_depth matches :(icase)bar with prefix' '
|
||||||
|
cat <<-EOF >expect &&
|
||||||
|
fOo/BAR
|
||||||
|
fOo/bAr
|
||||||
|
fOo/bar
|
||||||
|
EOF
|
||||||
|
( cd fOo && git ls-files --full-name ":(icase)bar" ) >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'match_pathspec_depth matches :(icase)bar with empty prefix' '
|
||||||
|
cat <<-EOF >expect &&
|
||||||
|
bar
|
||||||
|
fOo/BAR
|
||||||
|
fOo/bAr
|
||||||
|
fOo/bar
|
||||||
|
EOF
|
||||||
|
( cd fOo && git ls-files --full-name ":(icase)bar" ../bar ) >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
||||||
59
tree-walk.c
59
tree-walk.c
|
|
@ -489,13 +489,25 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int match_entry(const struct name_entry *entry, int pathlen,
|
static int match_entry(const struct pathspec_item *item,
|
||||||
|
const struct name_entry *entry, int pathlen,
|
||||||
const char *match, int matchlen,
|
const char *match, int matchlen,
|
||||||
enum interesting *never_interesting)
|
enum interesting *never_interesting)
|
||||||
{
|
{
|
||||||
int m = -1; /* signals that we haven't called strncmp() */
|
int m = -1; /* signals that we haven't called strncmp() */
|
||||||
|
|
||||||
if (*never_interesting != entry_not_interesting) {
|
if (item->magic & PATHSPEC_ICASE)
|
||||||
|
/*
|
||||||
|
* "Never interesting" trick requires exact
|
||||||
|
* matching. We could do something clever with inexact
|
||||||
|
* matching, but it's trickier (and not to forget that
|
||||||
|
* strcasecmp is locale-dependent, at least in
|
||||||
|
* glibc). Just disable it for now. It can't be worse
|
||||||
|
* than the wildcard's codepath of '[Tt][Hi][Is][Ss]'
|
||||||
|
* pattern.
|
||||||
|
*/
|
||||||
|
*never_interesting = entry_not_interesting;
|
||||||
|
else if (*never_interesting != entry_not_interesting) {
|
||||||
/*
|
/*
|
||||||
* We have not seen any match that sorts later
|
* We have not seen any match that sorts later
|
||||||
* than the current path.
|
* than the current path.
|
||||||
|
|
@ -541,7 +553,7 @@ static int match_entry(const struct name_entry *entry, int pathlen,
|
||||||
* we cheated and did not do strncmp(), so we do
|
* we cheated and did not do strncmp(), so we do
|
||||||
* that here.
|
* that here.
|
||||||
*/
|
*/
|
||||||
m = strncmp(match, entry->path, pathlen);
|
m = ps_strncmp(item, match, entry->path, pathlen);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If common part matched earlier then it is a hit,
|
* If common part matched earlier then it is a hit,
|
||||||
|
|
@ -549,15 +561,39 @@ static int match_entry(const struct name_entry *entry, int pathlen,
|
||||||
* leading directory and is shorter than match.
|
* leading directory and is shorter than match.
|
||||||
*/
|
*/
|
||||||
if (!m)
|
if (!m)
|
||||||
|
/*
|
||||||
|
* match_entry does not check if the prefix part is
|
||||||
|
* matched case-sensitively. If the entry is a
|
||||||
|
* directory and part of prefix, it'll be rematched
|
||||||
|
* eventually by basecmp with special treatment for
|
||||||
|
* the prefix.
|
||||||
|
*/
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int match_dir_prefix(const char *base,
|
/* :(icase)-aware string compare */
|
||||||
|
static int basecmp(const struct pathspec_item *item,
|
||||||
|
const char *base, const char *match, int len)
|
||||||
|
{
|
||||||
|
if (item->magic & PATHSPEC_ICASE) {
|
||||||
|
int ret, n = len > item->prefix ? item->prefix : len;
|
||||||
|
ret = strncmp(base, match, n);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
base += n;
|
||||||
|
match += n;
|
||||||
|
len -= n;
|
||||||
|
}
|
||||||
|
return ps_strncmp(item, base, match, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int match_dir_prefix(const struct pathspec_item *item,
|
||||||
|
const char *base,
|
||||||
const char *match, int matchlen)
|
const char *match, int matchlen)
|
||||||
{
|
{
|
||||||
if (strncmp(base, match, matchlen))
|
if (basecmp(item, base, match, matchlen))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -594,7 +630,7 @@ static int match_wildcard_base(const struct pathspec_item *item,
|
||||||
*/
|
*/
|
||||||
if (baselen >= matchlen) {
|
if (baselen >= matchlen) {
|
||||||
*matched = matchlen;
|
*matched = matchlen;
|
||||||
return !strncmp(base, match, matchlen);
|
return !basecmp(item, base, match, matchlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
dirlen = matchlen;
|
dirlen = matchlen;
|
||||||
|
|
@ -607,7 +643,7 @@ static int match_wildcard_base(const struct pathspec_item *item,
|
||||||
* base ends with '/' so we are sure it really matches
|
* base ends with '/' so we are sure it really matches
|
||||||
* directory
|
* directory
|
||||||
*/
|
*/
|
||||||
if (strncmp(base, match, baselen))
|
if (basecmp(item, base, match, baselen))
|
||||||
return 0;
|
return 0;
|
||||||
*matched = baselen;
|
*matched = baselen;
|
||||||
} else
|
} else
|
||||||
|
|
@ -640,7 +676,8 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
|
||||||
PATHSPEC_FROMTOP |
|
PATHSPEC_FROMTOP |
|
||||||
PATHSPEC_MAXDEPTH |
|
PATHSPEC_MAXDEPTH |
|
||||||
PATHSPEC_LITERAL |
|
PATHSPEC_LITERAL |
|
||||||
PATHSPEC_GLOB);
|
PATHSPEC_GLOB |
|
||||||
|
PATHSPEC_ICASE);
|
||||||
|
|
||||||
if (!ps->nr) {
|
if (!ps->nr) {
|
||||||
if (!ps->recursive ||
|
if (!ps->recursive ||
|
||||||
|
|
@ -663,7 +700,7 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
|
||||||
|
|
||||||
if (baselen >= matchlen) {
|
if (baselen >= matchlen) {
|
||||||
/* If it doesn't match, move along... */
|
/* If it doesn't match, move along... */
|
||||||
if (!match_dir_prefix(base_str, match, matchlen))
|
if (!match_dir_prefix(item, base_str, match, matchlen))
|
||||||
goto match_wildcards;
|
goto match_wildcards;
|
||||||
|
|
||||||
if (!ps->recursive ||
|
if (!ps->recursive ||
|
||||||
|
|
@ -679,8 +716,8 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Either there must be no base, or the base must match. */
|
/* Either there must be no base, or the base must match. */
|
||||||
if (baselen == 0 || !strncmp(base_str, match, baselen)) {
|
if (baselen == 0 || !basecmp(item, base_str, match, baselen)) {
|
||||||
if (match_entry(entry, pathlen,
|
if (match_entry(item, entry, pathlen,
|
||||||
match + baselen, matchlen - baselen,
|
match + baselen, matchlen - baselen,
|
||||||
&never_interesting))
|
&never_interesting))
|
||||||
return entry_interesting;
|
return entry_interesting;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue