filter: make [^...] in globs exclude / and document it

A glob like file[^a].txt was translated to (^|/)file[^a]\.txt$ in path
mode, so it matched file/.txt across a directory boundary, unlike ?
and the [!...] form.

Treat a leading ^ in a character class the same as a leading !: in
path mode it now also excludes /, and [^] is rejected as an empty
character class. Document both negation forms in the filtering
grammar.
This commit is contained in:
kevin9327 authored and Nick Craig-Wood committed 2026-10-31 12:26:02 +00:00
1 parent c434df94d1
commit 0de32e2269
4 files changed
+33 -21

No files matched your search

+1 -1
View File
@@ -43,7 +43,7 @@ Rclone matching rules follow a glob style:
* matches any sequence of non-separator (/) characters
** matches any sequence of characters including / separators
? matches any single non-separator (/) character
[ [ ! ] { character-range } ]
[ [ ! | ^ ] { character-range } ]
character class (must be non-empty)
{ pattern-list }
pattern alternatives
+19 -15
View File
@@ -627,22 +627,26 @@ func TestNewFilterMatchesRegexp(t *testing.T) {
}
func TestNewFilterMatchesNegatedCharacterClass(t *testing.T) {
f, err := NewFilter(nil)
require.NoError(t, err)
add := func(s string) {
err := f.AddRule(s)
require.NoError(t, err)
for _, negate := range []string{"!", "^"} {
t.Run(negate, func(t *testing.T) {
f, err := NewFilter(nil)
require.NoError(t, err)
add := func(s string) {
err := f.AddRule(s)
require.NoError(t, err)
}
add("+ file[" + negate + "a].txt")
add("- *")
testInclude(t, f, []includeTest{
{"fileb.txt", 100, 0, true},
{"sub/fileb.txt", 100, 0, true},
{"filea.txt", 100, 0, false},
{"file" + negate + ".txt", 100, 0, true},
{"file/.txt", 100, 0, false},
})
assert.False(t, f.InActive())
})
}
add("+ file[!a].txt")
add("- *")
testInclude(t, f, []includeTest{
{"fileb.txt", 100, 0, true},
{"sub/fileb.txt", 100, 0, true},
{"filea.txt", 100, 0, false},
{"file!.txt", 100, 0, true},
{"file/.txt", 100, 0, false},
})
assert.False(t, f.InActive())
}
func TestNewFilterHashFilter(t *testing.T) {
+5 -5
View File
@@ -86,7 +86,7 @@ func globToRegexp(glob string, pathMode bool, addAnchors bool, ignoreCase bool)
braceDepth := 0
inBrackets := 0
bracketStart := false // just opened a character class
bracketNegated := false // just read the ! of a [! character class
bracketNegated := false // just read the ! or ^ of a [! or [^ character class
slashed := false
inRegexp := false // inside {{ ... }}
inRegexpEnd := false // have received }} waiting for more
@@ -127,10 +127,10 @@ func globToRegexp(glob string, pathMode bool, addAnchors bool, ignoreCase bool)
}
}
if inBrackets > 0 {
if bracketStart && c == '!' {
// [!...] is a negated character class in glob
// syntax, which is [^...] in a regexp. Like ?, it
// does not match the / separator in path mode.
if bracketStart && (c == '!' || c == '^') {
// [!...] and [^...] are negated character classes
// in glob syntax, which is [^...] in a regexp. Like
// ?, they do not match the / separator in path mode.
bracketStart = false
bracketNegated = true
_ = re.WriteByte('^')
+8
View File
@@ -25,6 +25,10 @@ func TestGlobStringToRegexp(t *testing.T) {
{`potat[![:alpha:]]or`, `potat[^[:alpha:]]or`, ``},
{`potat[a!]`, `potat[a!]`, ``},
{`potat[!]`, ``, `empty character class`},
{`potat[^oa]`, `potat[^oa]`, ``},
{`potat[^[:alpha:]]or`, `potat[^[:alpha:]]or`, ``},
{`potat[a^]`, `potat[a^]`, ``},
{`potat[^]`, ``, `empty character class`},
{`'.' '+' '(' ')' '|' '^' '$'`, `'\.' '\+' '\(' '\)' '\|' '\^' '\$'`, ``},
{`*.jpg`, `.*\.jpg`, ``},
{`a{b,c,d}e`, `a(b|c|d)e`, ``},
@@ -99,6 +103,10 @@ func TestGlobPathToRegexp(t *testing.T) {
{`potat[![:alpha:]]or`, `(^|/)potat[^/[:alpha:]]or$`, ``},
{`potat[a!]`, `(^|/)potat[a!]$`, ``},
{`potat[!]`, ``, `empty character class`},
{`potat[^oa]`, `(^|/)potat[^/oa]$`, ``},
{`potat[^[:alpha:]]or`, `(^|/)potat[^/[:alpha:]]or$`, ``},
{`potat[a^]`, `(^|/)potat[a^]$`, ``},
{`potat[^]`, ``, `empty character class`},
{`'.' '+' '(' ')' '|' '^' '$'`, `(^|/)'\.' '\+' '\(' '\)' '\|' '\^' '\$'$`, ``},
{`*.jpg`, `(^|/)[^/]*\.jpg$`, ``},
{`a{b,c,d}e`, `(^|/)a(b|c|d)e$`, ``},