mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 10:51:54 -04:00
Preserve leading slash context for absolute double-star filters
This commit is contained in:
parent
fcb6a5ca91
commit
fa81c20ee6
4 changed files with 170 additions and 7 deletions
|
|
@ -247,8 +247,8 @@ static int filter_apply(struct snapraid_filter* filter, struct snapraid_filter**
|
|||
int ret = 0;
|
||||
|
||||
if (filter->is_abs) {
|
||||
/* skip initial slash, as always missing in the path */
|
||||
if (wnmatch_sub(filter->pattern + 1, path, match_sub) == 0)
|
||||
/* preserve the skipped slash because it gives an initial ** its recursive meaning */
|
||||
if (wnmatch_sub_prev(filter->pattern + 1, path, match_sub, '/') == 0)
|
||||
ret = filter->direction;
|
||||
} else {
|
||||
/* the path is relative, first try to match from the root */
|
||||
|
|
|
|||
|
|
@ -791,6 +791,10 @@ struct {
|
|||
{ "**pkg/init.py", "libs/core/pkg/init.py", 0, 1 },
|
||||
{ "**.log", "error.log", 0, 0 },
|
||||
{ "**.log", "var/log/sys.log", 0, 1 },
|
||||
{ "**.jpg", "photo.jpg", 0, 0 },
|
||||
{ "**.jpg", "dir/photo.jpg", 0, 1 },
|
||||
{ "foo/*.jpg", "foo/photo.jpg", 0, 0 },
|
||||
{ "foo/*.jpg", "foo/a/photo.jpg", 0, 1 },
|
||||
{ "src/**.js", "src/app.js", 0, 0 },
|
||||
{ "src/**.js", "src/components/ui/button.js", 0, 0 }, /* <<<<< .GITIGNORE DIFFERS */
|
||||
{ "a**b.txt", "ab.txt", 0, 0 },
|
||||
|
|
@ -905,6 +909,29 @@ struct {
|
|||
{ 0 }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const char* pattern;
|
||||
const char* text;
|
||||
int match_sub;
|
||||
int result;
|
||||
} WNMATCH_ABSOLUTE_TEST[] = {
|
||||
/* an absolute filter skips its slash but preserves it as initial context */
|
||||
{ "**.jpg", "photo.jpg", 0, 0 },
|
||||
{ "**.jpg", "dir/photo.jpg", 0, 0 },
|
||||
{ "**.jpg", "a/b/photo.jpg", 0, 0 },
|
||||
{ "**.jpg", "a/b/photo.png", 0, 1 },
|
||||
{ "**foo", "foo", 0, 0 },
|
||||
{ "**foo", "a/b/foo", 0, 0 },
|
||||
{ "**foo", "a/b/bar", 0, 1 },
|
||||
{ "**", "a/b/file", 0, 0 },
|
||||
{ "**foo", "a/b/foo/file", 1, 0 },
|
||||
{ "**foo", "a/b/bar/file", 1, 1 },
|
||||
{ "**/foo", "foo", 0, 0 },
|
||||
{ "**/foo", "a/b/foo", 0, 0 },
|
||||
{ "foo/**/bar", "foo/a/b/bar", 0, 0 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static void test_wnmatch(void)
|
||||
{
|
||||
for (int i = 0; WNMATCH_TEST[i].pattern; ++i) {
|
||||
|
|
@ -915,6 +942,15 @@ static void test_wnmatch(void)
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; WNMATCH_ABSOLUTE_TEST[i].pattern; ++i) {
|
||||
if (wnmatch_sub_prev(WNMATCH_ABSOLUTE_TEST[i].pattern, WNMATCH_ABSOLUTE_TEST[i].text, WNMATCH_ABSOLUTE_TEST[i].match_sub, '/') != WNMATCH_ABSOLUTE_TEST[i].result) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal(EINTERNAL, "Failed absolute wnmatch test %s %s, expected %d\n", WNMATCH_ABSOLUTE_TEST[i].pattern, WNMATCH_ABSOLUTE_TEST[i].text, WNMATCH_ABSOLUTE_TEST[i].result);
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_path(void)
|
||||
|
|
@ -1344,6 +1380,108 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
|
|||
{ 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"absolute leading double-star include",
|
||||
{
|
||||
{ 1, 0, "", "/**.jpg" },
|
||||
{ 0 }
|
||||
},
|
||||
{
|
||||
{ "d1", "photo.jpg", 0, 0, -1 },
|
||||
{ "d1", "dir/photo.jpg", 0, 0, -1 },
|
||||
{ "d1", "a/b/photo.jpg", 0, 0, -1 },
|
||||
{ "d1", "photo.png", -1, 0, -1 },
|
||||
{ "d1", "dir/photo.png", -1, 0, -1 },
|
||||
{ 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"absolute leading double-star exclude",
|
||||
{
|
||||
{ -1, 0, "", "/**.tmp" },
|
||||
{ 0 }
|
||||
},
|
||||
{
|
||||
{ "d1", "file.tmp", -1, 0, 0 },
|
||||
{ "d1", "a/file.tmp", -1, 0, 0 },
|
||||
{ "d1", "a/b/file.tmp", -1, 0, 0 },
|
||||
{ "d1", "file.txt", 0, 0, 0 },
|
||||
{ "d1", "a/b/file.txt", 0, 0, 0 },
|
||||
{ 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"local absolute leading double-star exclude",
|
||||
{
|
||||
{ -1, 0, "projects/sub/", "/**.tmp" },
|
||||
{ 0 }
|
||||
},
|
||||
{
|
||||
{ "d1", "projects/sub/file.tmp", -1, 0, 0 },
|
||||
{ "d1", "projects/sub/a/file.tmp", -1, 0, 0 },
|
||||
{ "d1", "projects/sub/a/b/file.tmp", -1, 0, 0 },
|
||||
{ "d1", "projects/file.tmp", 0, 0, 0 },
|
||||
{ "d1", "projects/submarine/file.tmp", 0, 0, 0 },
|
||||
{ 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"absolute leading double-star suffix",
|
||||
{
|
||||
{ 1, 0, "", "/**foo" },
|
||||
{ 0 }
|
||||
},
|
||||
{
|
||||
{ "d1", "foo", 0, 0, -1 },
|
||||
{ "d1", "dir/foo", 0, 0, -1 },
|
||||
{ "d1", "a/b/foo", 0, 0, -1 },
|
||||
{ "d1", "bar", -1, 0, -1 },
|
||||
{ 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"absolute trailing double-star",
|
||||
{
|
||||
{ 1, 0, "", "/**" },
|
||||
{ 0 }
|
||||
},
|
||||
{
|
||||
{ "d1", "file", 0, 0, -1 },
|
||||
{ "d1", "dir/file", 0, 0, -1 },
|
||||
{ "d1", "a/b/file", 0, 0, -1 },
|
||||
{ 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"absolute leading double-star directory",
|
||||
{
|
||||
{ -1, 0, "", "/**foo/" },
|
||||
{ 0 }
|
||||
},
|
||||
{
|
||||
{ "d1", "foo", 0, -1, -1 },
|
||||
{ "d1", "a/b/foo", 0, -1, -1 },
|
||||
{ "d1", "a/b/foo/file", -1, 0, 0 },
|
||||
{ "d1", "a/b/bar", 0, 0, 0 },
|
||||
{ 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"established absolute double-star forms",
|
||||
{
|
||||
{ 1, 0, "", "/**/foo" },
|
||||
{ 1, 0, "", "/foo/**/bar" },
|
||||
{ 0 }
|
||||
},
|
||||
{
|
||||
{ "d1", "foo", 0, 0, -1 },
|
||||
{ "d1", "a/b/foo", 0, 0, -1 },
|
||||
{ "d1", "foo/bar", 0, 0, -1 },
|
||||
{ "d1", "foo/a/b/bar", 0, 0, -1 },
|
||||
{ "d1", "other/bar", -1, 0, -1 },
|
||||
{ 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"filter with literal caret in pattern and root",
|
||||
{
|
||||
|
|
|
|||
|
|
@ -373,9 +373,15 @@ static inline int is_wild_escape(char c)
|
|||
#endif
|
||||
}
|
||||
|
||||
int wnmatch_sub(const char* p, const char* t, int match_sub)
|
||||
/*
|
||||
* Match with the previous pattern character provided in prev.
|
||||
*
|
||||
* An initial '**' needs to know if it was preceded by '/' to distinguish
|
||||
* between multi-directory recursive matching (like /##.ext) and single-directory
|
||||
* matching (like ##.ext).
|
||||
*/
|
||||
int wnmatch_sub_prev(const char* p, const char* t, int match_sub, char prev)
|
||||
{
|
||||
char p1 = 0; /* previous char */
|
||||
while (*p) {
|
||||
char p0 = *p;
|
||||
switch (p0) {
|
||||
|
|
@ -417,7 +423,7 @@ int wnmatch_sub(const char* p, const char* t, int match_sub)
|
|||
++p;
|
||||
|
||||
/* if its not near a slash, it's like a single * */
|
||||
if (p1 == '/' || *p == '/') {
|
||||
if (prev == '/' || *p == '/') {
|
||||
/* a ** at end matches everything */
|
||||
if (*p == 0)
|
||||
return 0;
|
||||
|
|
@ -432,7 +438,7 @@ int wnmatch_sub(const char* p, const char* t, int match_sub)
|
|||
* "##/file.txt" matching "file.txt"
|
||||
* "x##/file.txt" NOT matching "xfile.txt"
|
||||
*/
|
||||
if (*p == '/' && (p1 == 0 || p1 == '/')) {
|
||||
if (*p == '/' && (prev == 0 || prev == '/')) {
|
||||
/* try reducing to nothing */
|
||||
if (wnmatch_sub(p + 1, t, match_sub) == 0)
|
||||
return 0;
|
||||
|
|
@ -489,9 +495,14 @@ int wnmatch_sub(const char* p, const char* t, int match_sub)
|
|||
++t;
|
||||
break;
|
||||
}
|
||||
p1 = p0;
|
||||
prev = p0;
|
||||
}
|
||||
|
||||
return wnmatch_end(t, match_sub);
|
||||
}
|
||||
|
||||
int wnmatch_sub(const char* p, const char* t, int match_sub)
|
||||
{
|
||||
return wnmatch_sub_prev(p, t, match_sub, 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,20 @@ unsigned strtou(const char* nptr, char** endptr, int base);
|
|||
*/
|
||||
int wnmatch_sub(const char* p, const char* t, int match_sub);
|
||||
|
||||
/**
|
||||
* Match with the pattern character immediately before p provided as prev context.
|
||||
*
|
||||
* This is needed for absolute filters where the leading '/' in the filter pattern
|
||||
* is stripped before calling match (because target paths do not have a leading slash).
|
||||
* Providing prev='/' preserves the information that an initial '**' was preceded by
|
||||
* a slash, enabling recursive multi-directory matching (e.g. /##.ext or /##foo,
|
||||
* using # instead of * to avoid C comment delimiter collision).
|
||||
*
|
||||
* Recursive sub-matches intentionally start with prev=0 because intermediate matching
|
||||
* evaluates suffixes where preceding pattern characters are tracked in the loop.
|
||||
*/
|
||||
int wnmatch_sub_prev(const char* p, const char* t, int match_sub, char prev);
|
||||
|
||||
static inline int wnmatch(const char* p, const char* t)
|
||||
{
|
||||
return wnmatch_sub(p, t, 0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue