diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index dc84ed280..1e771460e 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -21,6 +21,7 @@ import ( "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/textfile" "github.com/restic/restic/internal/ui" + "github.com/restic/restic/internal/ui/config" "github.com/restic/restic/internal/ui/termstatus" ) @@ -43,6 +44,11 @@ given as the arguments. }, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { + err := config.ApplyFlags(&backupOptions.Config, cmd.Flags()) + if err != nil { + return err + } + if backupOptions.Stdin && backupOptions.FilesFrom == "-" { return errors.Fatal("cannot use both `--stdin` and `--files-from -`") } @@ -51,7 +57,7 @@ given as the arguments. term := termstatus.New(globalOptions.stdout, globalOptions.stderr, globalOptions.Quiet) t.Go(func() error { term.Run(t.Context(globalOptions.ctx)); return nil }) - err := runBackup(backupOptions, globalOptions, term, args) + err = runBackup(backupOptions, globalOptions, term, args) if err != nil { return err } @@ -62,9 +68,10 @@ given as the arguments. // BackupOptions bundles all options for the backup command. type BackupOptions struct { + Config config.Backup + Parent string Force bool - Excludes []string ExcludeFiles []string ExcludeOtherFS bool ExcludeIfPresent []string @@ -86,7 +93,9 @@ func init() { f := cmdBackup.Flags() f.StringVar(&backupOptions.Parent, "parent", "", "use this parent snapshot (default: last snapshot in the repo that has the same target files/directories)") f.BoolVarP(&backupOptions.Force, "force", "f", false, `force re-reading the target files/directories (overrides the "parent" flag)`) - f.StringArrayVarP(&backupOptions.Excludes, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)") + + f.StringArrayP("exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)") + f.StringArrayVar(&backupOptions.ExcludeFiles, "exclude-file", nil, "read exclude patterns from a `file` (can be specified multiple times)") f.BoolVarP(&backupOptions.ExcludeOtherFS, "one-file-system", "x", false, "exclude other file systems") f.StringArrayVar(&backupOptions.ExcludeIfPresent, "exclude-if-present", nil, "takes filename[:header], exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)") @@ -188,12 +197,12 @@ func (opts BackupOptions) Check(gopts GlobalOptions, args []string) error { // collectRejectFuncs returns a list of all functions which may reject data // from being saved in a snapshot -func collectRejectFuncs(opts BackupOptions, repo *repository.Repository, targets []string) (fs []RejectFunc, err error) { +func collectRejectFuncs(opts BackupOptions, repo *repository.Repository, targets []string) (fs []RejectFunc, excludes []string, err error) { // allowed devices if opts.ExcludeOtherFS { f, err := rejectByDevice(targets) if err != nil { - return nil, err + return nil, nil, err } fs = append(fs, f) } @@ -202,19 +211,21 @@ func collectRejectFuncs(opts BackupOptions, repo *repository.Repository, targets if repo.Cache != nil { f, err := rejectResticCache(repo) if err != nil { - return nil, err + return nil, nil, err } fs = append(fs, f) } + excludes = append(excludes, opts.Config.Excludes...) + // add patterns from file if len(opts.ExcludeFiles) > 0 { - opts.Excludes = append(opts.Excludes, readExcludePatternsFromFiles(opts.ExcludeFiles)...) + excludes = append(excludes, readExcludePatternsFromFiles(opts.ExcludeFiles)...) } - if len(opts.Excludes) > 0 { - fs = append(fs, rejectByPattern(opts.Excludes)) + if len(excludes) > 0 { + fs = append(fs, rejectByPattern(excludes)) } if opts.ExcludeCaches { @@ -224,13 +235,13 @@ func collectRejectFuncs(opts BackupOptions, repo *repository.Repository, targets for _, spec := range opts.ExcludeIfPresent { f, err := rejectIfPresent(spec) if err != nil { - return nil, err + return nil, nil, err } fs = append(fs, f) } - return fs, nil + return fs, excludes, nil } // readExcludePatternsFromFiles reads all exclude files and returns the list of @@ -381,7 +392,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina } // rejectFuncs collect functions that can reject items from the backup - rejectFuncs, err := collectRejectFuncs(opts, repo, targets) + rejectFuncs, excludes, err := collectRejectFuncs(opts, repo, targets) if err != nil { return err } @@ -443,7 +454,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina } snapshotOpts := archiver.SnapshotOptions{ - Excludes: opts.Excludes, + Excludes: excludes, Tags: opts.Tags, Time: timeStamp, Hostname: opts.Hostname, diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index c16097b97..384b0fa08 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -390,14 +390,14 @@ func TestBackupExclude(t *testing.T) { rtest.Assert(t, includes(files, "/testdata/foo.tar.gz"), "expected file %q in first snapshot, but it's not included", "foo.tar.gz") - opts.Excludes = []string{"*.tar.gz"} + opts.Config.Excludes = []string{"*.tar.gz"} testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts) snapshots, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts)) files = testRunLs(t, env.gopts, snapshotID) rtest.Assert(t, !includes(files, "/testdata/foo.tar.gz"), "expected file %q not in first snapshot, but it's included", "foo.tar.gz") - opts.Excludes = []string{"*.tar.gz", "private/secret"} + opts.Config.Excludes = []string{"*.tar.gz", "private/secret"} testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts) _, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts)) files = testRunLs(t, env.gopts, snapshotID) diff --git a/cmd/restic/main.go b/cmd/restic/main.go index da66c7cc9..c38e8afa5 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -8,6 +8,7 @@ import ( "os" "runtime" + "github.com/davecgh/go-spew/spew" "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/options" "github.com/restic/restic/internal/restic" @@ -46,6 +47,8 @@ directories in an encrypted repository stored on different backends. return err } + spew.Dump(globalOptions.Config) + // set verbosity, default is one globalOptions.verbosity = 1 if globalOptions.Quiet && (globalOptions.Verbose > 1) { diff --git a/internal/ui/config/config.go b/internal/ui/config/config.go index 91023b61a..fd3949777 100644 --- a/internal/ui/config/config.go +++ b/internal/ui/config/config.go @@ -20,7 +20,7 @@ type Config struct { PasswordFile string `config:"password_file" flag:"password-file" env:"RESTIC_PASSWORD_FILE"` Backends map[string]Backend `config:"backend"` - Backup *Backup `config:"backup"` + Backup Backup `config:"backup"` } // Backend is a configured backend to store a repository. @@ -31,7 +31,8 @@ type Backend struct { // Backup sets the options for the "backup" command. type Backup struct { - Target []string `config:"target"` + Target []string `config:"target"` + Excludes []string `config:"exclude" flag:"exclude"` } // listTags returns the all the top-level tags with the name tagname of obj. @@ -190,6 +191,13 @@ func ApplyFlags(cfg interface{}, fset *pflag.FlagSet) error { return } field.SetString(v) + case "stringArray": + v, err := fset.GetStringArray(flag.Name) + if err != nil { + visitError = err + return + } + field.SetSlice(v) default: visitError = errors.Errorf("flag %v has unknown type %v", flag.Name, flag.Value.Type()) return