Allows multiple --include/--exclude/--filter options - fixes #875

This commit is contained in:
Nick Craig-Wood 2016-12-07 13:37:40 +00:00
parent 3bdfa284a9
commit dcf53a1d12
3 changed files with 127 additions and 79 deletions

View file

@ -19,10 +19,6 @@ and exclude rules like `--include`, `--exclude`, `--include-from`,
try them out is using the `ls` command, or `--dry-run` together with try them out is using the `ls` command, or `--dry-run` together with
`-v`. `-v`.
**Important** Due to limitations of the command line parser you can
only use any of these options once - if you duplicate them then rclone
will use the last one only.
## Patterns ## ## Patterns ##
The patterns used to match files for inclusion or exclusion are based The patterns used to match files for inclusion or exclusion are based
@ -159,16 +155,44 @@ based remotes (eg s3, swift, google compute storage, b2).
Filtering rules are added with the following command line flags. Filtering rules are added with the following command line flags.
### Repeating options ##
You can repeat the following options to add more than one rule of that
type.
* `--include`
* `--include-from`
* `--exclude`
* `--exclude-from`
* `--filter`
* `--filter-from`
Note that all the options of the same type are processed together in
the order above, regardless of what order they were placed on the
command line.
So all `--include` options are processed first in the order they
appeared on the command line, then all `--include-from` options etc.
To mix up the order includes and excludes, the `--filter` flag can be
used.
### `--exclude` - Exclude files matching pattern ### ### `--exclude` - Exclude files matching pattern ###
Add a single exclude rule with `--exclude`. Add a single exclude rule with `--exclude`.
This flag can be repeated. See above for the order the flags are
processed in.
Eg `--exclude *.bak` to exclude all bak files from the sync. Eg `--exclude *.bak` to exclude all bak files from the sync.
### `--exclude-from` - Read exclude patterns from file ### ### `--exclude-from` - Read exclude patterns from file ###
Add exclude rules from a file. Add exclude rules from a file.
This flag can be repeated. See above for the order the flags are
processed in.
Prepare a file like this `exclude-file.txt` Prepare a file like this `exclude-file.txt`
# a sample exclude rule file # a sample exclude rule file
@ -184,6 +208,9 @@ This is useful if you have a lot of rules.
Add a single include rule with `--include`. Add a single include rule with `--include`.
This flag can be repeated. See above for the order the flags are
processed in.
Eg `--include *.{png,jpg}` to include all `png` and `jpg` files in the Eg `--include *.{png,jpg}` to include all `png` and `jpg` files in the
backup and no others. backup and no others.
@ -197,6 +224,9 @@ flexibility then you must use `--filter-from`.
Add include rules from a file. Add include rules from a file.
This flag can be repeated. See above for the order the flags are
processed in.
Prepare a file like this `include-file.txt` Prepare a file like this `include-file.txt`
# a sample include rule file # a sample include rule file
@ -221,12 +251,18 @@ This can be used to add a single include or exclude rule. Include
rules start with `+ ` and exclude rules start with `- `. A special rules start with `+ ` and exclude rules start with `- `. A special
rule called `!` can be used to clear the existing rules. rule called `!` can be used to clear the existing rules.
This flag can be repeated. See above for the order the flags are
processed in.
Eg `--filter "- *.bak"` to exclude all bak files from the sync. Eg `--filter "- *.bak"` to exclude all bak files from the sync.
### `--filter-from` - Read filtering patterns from a file ### ### `--filter-from` - Read filtering patterns from a file ###
Add include/exclude rules from a file. Add include/exclude rules from a file.
This flag can be repeated. See above for the order the flags are
processed in.
Prepare a file like this `filter-file.txt` Prepare a file like this `filter-file.txt`
# a sample exclude rule file # a sample exclude rule file
@ -250,6 +286,9 @@ This reads a list of file names from the file passed in and **only**
these files are transferred. The filtering rules are ignored these files are transferred. The filtering rules are ignored
completely if you use this option. completely if you use this option.
This option can be repeated to read from more than one file. These
are read in the order that they are placed on the command line.
Prepare a file like this `files-from.txt` Prepare a file like this `files-from.txt`
# comment # comment

View file

@ -20,13 +20,13 @@ import (
var ( var (
// Flags // Flags
deleteExcluded = pflag.BoolP("delete-excluded", "", false, "Delete files on dest excluded from sync") deleteExcluded = pflag.BoolP("delete-excluded", "", false, "Delete files on dest excluded from sync")
filterRule = pflag.StringP("filter", "f", "", "Add a file-filtering rule") filterRule = pflag.StringArrayP("filter", "f", nil, "Add a file-filtering rule")
filterFrom = pflag.StringP("filter-from", "", "", "Read filtering patterns from a file") filterFrom = pflag.StringArrayP("filter-from", "", nil, "Read filtering patterns from a file")
excludeRule = pflag.StringP("exclude", "", "", "Exclude files matching pattern") excludeRule = pflag.StringArrayP("exclude", "", nil, "Exclude files matching pattern")
excludeFrom = pflag.StringP("exclude-from", "", "", "Read exclude patterns from file") excludeFrom = pflag.StringArrayP("exclude-from", "", nil, "Read exclude patterns from file")
includeRule = pflag.StringP("include", "", "", "Include files matching pattern") includeRule = pflag.StringArrayP("include", "", nil, "Include files matching pattern")
includeFrom = pflag.StringP("include-from", "", "", "Read include patterns from file") includeFrom = pflag.StringArrayP("include-from", "", nil, "Read include patterns from file")
filesFrom = pflag.StringP("files-from", "", "", "Read list of source-file names from file") filesFrom = pflag.StringArrayP("files-from", "", nil, "Read list of source-file names from file")
minAge = pflag.StringP("min-age", "", "", "Don't transfer any file younger than this in s or suffix ms|s|m|h|d|w|M|y") minAge = pflag.StringP("min-age", "", "", "Don't transfer any file younger than this in s or suffix ms|s|m|h|d|w|M|y")
maxAge = pflag.StringP("max-age", "", "", "Don't transfer any file older than this in s or suffix ms|s|m|h|d|w|M|y") maxAge = pflag.StringP("max-age", "", "", "Don't transfer any file older than this in s or suffix ms|s|m|h|d|w|M|y")
minSize = SizeSuffix(-1) minSize = SizeSuffix(-1)
@ -157,54 +157,68 @@ func NewFilter() (f *Filter, err error) {
} }
addImplicitExclude := false addImplicitExclude := false
if *includeRule != "" { if includeRule != nil {
err = f.Add(true, *includeRule) for _, rule := range *includeRule {
if err != nil { err = f.Add(true, rule)
return nil, err if err != nil {
} return nil, err
addImplicitExclude = true }
} addImplicitExclude = true
if *includeFrom != "" {
err := forEachLine(*includeFrom, func(line string) error {
return f.Add(true, line)
})
if err != nil {
return nil, err
}
addImplicitExclude = true
}
if *excludeRule != "" {
err = f.Add(false, *excludeRule)
if err != nil {
return nil, err
} }
} }
if *excludeFrom != "" { if includeFrom != nil {
err := forEachLine(*excludeFrom, func(line string) error { for _, rule := range *includeFrom {
return f.Add(false, line) err := forEachLine(rule, func(line string) error {
}) return f.Add(true, line)
if err != nil { })
return nil, err if err != nil {
return nil, err
}
addImplicitExclude = true
} }
} }
if *filterRule != "" { if excludeRule != nil {
err = f.AddRule(*filterRule) for _, rule := range *excludeRule {
if err != nil { err = f.Add(false, rule)
return nil, err if err != nil {
return nil, err
}
} }
} }
if *filterFrom != "" { if excludeFrom != nil {
err := forEachLine(*filterFrom, f.AddRule) for _, rule := range *excludeFrom {
if err != nil { err := forEachLine(rule, func(line string) error {
return nil, err return f.Add(false, line)
})
if err != nil {
return nil, err
}
} }
} }
if *filesFrom != "" { if filterRule != nil {
err := forEachLine(*filesFrom, func(line string) error { for _, rule := range *filterRule {
return f.AddFile(line) err = f.AddRule(rule)
}) if err != nil {
if err != nil { return nil, err
return nil, err }
}
}
if filterFrom != nil {
for _, rule := range *filterFrom {
err := forEachLine(rule, f.AddRule)
if err != nil {
return nil, err
}
}
}
if filesFrom != nil {
for _, rule := range *filesFrom {
err := forEachLine(rule, func(line string) error {
return f.AddFile(line)
})
if err != nil {
return nil, err
}
} }
} }
if addImplicitExclude { if addImplicitExclude {

View file

@ -54,13 +54,8 @@ func TestNewFilterDefault(t *testing.T) {
assert.True(t, f.InActive()) assert.True(t, f.InActive())
} }
// return a pointer to the string
func stringP(s string) *string {
return &s
}
// testFile creates a temp file with the contents // testFile creates a temp file with the contents
func testFile(t *testing.T, contents string) *string { func testFile(t *testing.T, contents string) string {
out, err := ioutil.TempFile("", "filter_test") out, err := ioutil.TempFile("", "filter_test")
require.NoError(t, err) require.NoError(t, err)
defer func() { defer func() {
@ -70,25 +65,24 @@ func testFile(t *testing.T, contents string) *string {
_, err = out.Write([]byte(contents)) _, err = out.Write([]byte(contents))
require.NoError(t, err) require.NoError(t, err)
s := out.Name() s := out.Name()
return &s return s
} }
func TestNewFilterFull(t *testing.T) { func TestNewFilterFull(t *testing.T) {
mins := int64(100 * 1024) mins := int64(100 * 1024)
maxs := int64(1000 * 1024) maxs := int64(1000 * 1024)
emptyString := ""
isFalse := false isFalse := false
isTrue := true isTrue := true
// Set up the input // Set up the input
deleteExcluded = &isTrue deleteExcluded = &isTrue
filterRule = stringP("- filter1") filterRule = &[]string{"- filter1", "- filter1b"}
filterFrom = testFile(t, "#comment\n+ filter2\n- filter3\n") filterFrom = &[]string{testFile(t, "#comment\n+ filter2\n- filter3\n")}
excludeRule = stringP("exclude1") excludeRule = &[]string{"exclude1"}
excludeFrom = testFile(t, "#comment\nexclude2\nexclude3\n") excludeFrom = &[]string{testFile(t, "#comment\nexclude2\nexclude3\n")}
includeRule = stringP("include1") includeRule = &[]string{"include1"}
includeFrom = testFile(t, "#comment\ninclude2\ninclude3\n") includeFrom = &[]string{testFile(t, "#comment\ninclude2\ninclude3\n")}
filesFrom = testFile(t, "#comment\nfiles1\nfiles2\n") filesFrom = &[]string{testFile(t, "#comment\nfiles1\nfiles2\n")}
minSize = SizeSuffix(mins) minSize = SizeSuffix(mins)
maxSize = SizeSuffix(maxs) maxSize = SizeSuffix(maxs)
@ -100,20 +94,20 @@ func TestNewFilterFull(t *testing.T) {
} }
// Reset the input // Reset the input
defer func() { defer func() {
rm(*filterFrom) rm((*filterFrom)[0])
rm(*excludeFrom) rm((*excludeFrom)[0])
rm(*includeFrom) rm((*includeFrom)[0])
rm(*filesFrom) rm((*filesFrom)[0])
minSize = -1 minSize = -1
maxSize = -1 maxSize = -1
deleteExcluded = &isFalse deleteExcluded = &isFalse
filterRule = &emptyString filterRule = nil
filterFrom = &emptyString filterFrom = nil
excludeRule = &emptyString excludeRule = nil
excludeFrom = &emptyString excludeFrom = nil
includeRule = &emptyString includeRule = nil
includeFrom = &emptyString includeFrom = nil
filesFrom = &emptyString filesFrom = nil
}() }()
f, err := NewFilter() f, err := NewFilter()
@ -130,6 +124,7 @@ func TestNewFilterFull(t *testing.T) {
- (^|/)exclude2$ - (^|/)exclude2$
- (^|/)exclude3$ - (^|/)exclude3$
- (^|/)filter1$ - (^|/)filter1$
- (^|/)filter1b$
+ (^|/)filter2$ + (^|/)filter2$
- (^|/)filter3$ - (^|/)filter3$
- ^.*$ - ^.*$
@ -356,11 +351,11 @@ four
five five
six `) six `)
defer func() { defer func() {
err := os.Remove(*file) err := os.Remove(file)
require.NoError(t, err) require.NoError(t, err)
}() }()
lines := []string{} lines := []string{}
err := forEachLine(*file, func(s string) error { err := forEachLine(file, func(s string) error {
lines = append(lines, s) lines = append(lines, s)
return nil return nil
}) })