2014-04-27 22:00:15 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2016-04-01 11:50:45 +00:00
|
|
|
"bufio"
|
2015-07-06 21:02:16 +00:00
|
|
|
"errors"
|
2014-04-27 22:00:15 +00:00
|
|
|
"fmt"
|
2014-09-23 20:39:12 +00:00
|
|
|
"os"
|
2015-03-02 13:48:47 +00:00
|
|
|
"path/filepath"
|
2016-02-14 14:29:28 +00:00
|
|
|
"restic"
|
|
|
|
"restic/backend"
|
|
|
|
"restic/debug"
|
|
|
|
"restic/filter"
|
Fix 567 (#570)
* Patch for https://github.com/restic/restic/issues/567
Backup also files on windows with longer pathnames than 255 chars (e.g. from node).
as fd0 says "So, as far as I can see, we need to have custom methods for all functions that accept a path, so that on Windows we can substitute the normal (possibly relative) path used within restic by an (absolute) UNC path, and only then call the underlying functions like os.Stat(), os.Lstat(), os.Open() and so on.
I've already thought about adding a generic abstraction for the file system (so we can mock this easier in tests), and this looks like a good opportunity to build it."
* fixed building tests
* Restructured patches
Add Wrapper for filepath.Walk
* using \\?\ requires absolute pathes to be used.
Now all tests run
* used gofmt on the code
* Restructured Code. No patches dir, integrate the file functions into restic/fs/
There is still an issue, because restic.fs.Open has a different api the os.Open, which returns the result of OpenFile, but takes only a string
* Changed the last os.Open() calls to fs.Open() after extending the File interface
* fixed name-clash of restic.fs and fuse.fs detected by travis
* fixed fmt with gofmt
* c&p failure: removed fixpath() call.
* missing include
* fixed includes in linux variant
* Fix for Linux. Fd() is required on File interface
* done gofmt
2016-08-15 19:59:13 +00:00
|
|
|
"restic/fs"
|
2016-04-16 20:04:29 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/crypto/ssh/terminal"
|
2014-04-27 22:00:15 +00:00
|
|
|
)
|
|
|
|
|
2015-03-02 13:48:47 +00:00
|
|
|
type CmdBackup struct {
|
2016-05-10 19:51:56 +00:00
|
|
|
Parent string `short:"p" long:"parent" description:"use this parent snapshot (default: last snapshot in repo that has the same target)"`
|
|
|
|
Force bool `short:"f" long:"force" description:"Force re-reading the target. Overrides the \"parent\" flag"`
|
|
|
|
Excludes []string `short:"e" long:"exclude" description:"Exclude a pattern (can be specified multiple times)"`
|
|
|
|
ExcludeFile string `long:"exclude-file" description:"Read exclude-patterns from file"`
|
|
|
|
Stdin bool `long:"stdin" description:"read backup data from stdin"`
|
|
|
|
StdinFilename string `long:"stdin-filename" default:"stdin" description:"file name to use when reading from stdin"`
|
2015-06-21 11:02:56 +00:00
|
|
|
|
|
|
|
global *GlobalOptions
|
2015-03-02 13:48:47 +00:00
|
|
|
}
|
2014-12-07 15:30:52 +00:00
|
|
|
|
2014-11-30 21:39:58 +00:00
|
|
|
func init() {
|
2014-12-07 15:30:52 +00:00
|
|
|
_, err := parser.AddCommand("backup",
|
|
|
|
"save file/directory",
|
|
|
|
"The backup command creates a snapshot of a file or directory",
|
2015-06-21 11:02:56 +00:00
|
|
|
&CmdBackup{global: &globalOpts})
|
2014-12-07 15:30:52 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2014-11-30 21:39:58 +00:00
|
|
|
}
|
|
|
|
|
2015-04-24 23:39:32 +00:00
|
|
|
func formatBytes(c uint64) string {
|
2014-11-16 20:29:11 +00:00
|
|
|
b := float64(c)
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case c > 1<<40:
|
2014-11-23 13:53:46 +00:00
|
|
|
return fmt.Sprintf("%.3f TiB", b/(1<<40))
|
2014-11-16 20:29:11 +00:00
|
|
|
case c > 1<<30:
|
2014-11-23 13:53:46 +00:00
|
|
|
return fmt.Sprintf("%.3f GiB", b/(1<<30))
|
2014-11-16 20:29:11 +00:00
|
|
|
case c > 1<<20:
|
2014-11-23 13:53:46 +00:00
|
|
|
return fmt.Sprintf("%.3f MiB", b/(1<<20))
|
2014-11-16 20:29:11 +00:00
|
|
|
case c > 1<<10:
|
2014-11-23 13:53:46 +00:00
|
|
|
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
2014-11-16 20:29:11 +00:00
|
|
|
default:
|
2014-11-23 11:05:43 +00:00
|
|
|
return fmt.Sprintf("%dB", c)
|
2014-11-16 20:29:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-24 23:39:32 +00:00
|
|
|
func formatSeconds(sec uint64) string {
|
2014-11-23 11:05:43 +00:00
|
|
|
hours := sec / 3600
|
|
|
|
sec -= hours * 3600
|
|
|
|
min := sec / 60
|
|
|
|
sec -= min * 60
|
|
|
|
if hours > 0 {
|
|
|
|
return fmt.Sprintf("%d:%02d:%02d", hours, min, sec)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%d:%02d", min, sec)
|
|
|
|
}
|
|
|
|
|
2015-04-25 17:20:41 +00:00
|
|
|
func formatPercent(numerator uint64, denominator uint64) string {
|
|
|
|
if denominator == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
percent := 100.0 * float64(numerator) / float64(denominator)
|
|
|
|
|
|
|
|
if percent > 100 {
|
|
|
|
percent = 100
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%3.2f%%", percent)
|
|
|
|
}
|
|
|
|
|
|
|
|
func formatRate(bytes uint64, duration time.Duration) string {
|
|
|
|
sec := float64(duration) / float64(time.Second)
|
|
|
|
rate := float64(bytes) / sec / (1 << 20)
|
|
|
|
return fmt.Sprintf("%.2fMiB/s", rate)
|
|
|
|
}
|
|
|
|
|
2015-04-24 23:39:32 +00:00
|
|
|
func formatDuration(d time.Duration) string {
|
2015-01-04 17:23:00 +00:00
|
|
|
sec := uint64(d / time.Second)
|
2015-04-24 23:39:32 +00:00
|
|
|
return formatSeconds(sec)
|
2015-01-04 17:23:00 +00:00
|
|
|
}
|
|
|
|
|
2015-04-24 23:39:32 +00:00
|
|
|
func printTree2(indent int, t *restic.Tree) {
|
2015-01-10 22:40:10 +00:00
|
|
|
for _, node := range t.Nodes {
|
2015-01-04 21:39:30 +00:00
|
|
|
if node.Tree() != nil {
|
2014-11-16 20:29:11 +00:00
|
|
|
fmt.Printf("%s%s/\n", strings.Repeat(" ", indent), node.Name)
|
2015-04-24 23:39:32 +00:00
|
|
|
printTree2(indent+1, node.Tree())
|
2014-11-16 20:29:11 +00:00
|
|
|
} else {
|
|
|
|
fmt.Printf("%s%s\n", strings.Repeat(" ", indent), node.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-07 15:30:52 +00:00
|
|
|
func (cmd CmdBackup) Usage() string {
|
2015-05-02 00:05:09 +00:00
|
|
|
return "DIR/FILE [DIR/FILE] [...]"
|
2014-12-07 15:30:52 +00:00
|
|
|
}
|
|
|
|
|
2015-06-21 11:02:56 +00:00
|
|
|
func (cmd CmdBackup) newScanProgress() *restic.Progress {
|
|
|
|
if !cmd.global.ShowProgress() {
|
2015-02-21 13:23:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-21 14:32:48 +00:00
|
|
|
p := restic.NewProgress(time.Second)
|
|
|
|
p.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
|
2016-08-21 20:38:22 +00:00
|
|
|
fmt.Printf("%s[%s] %d directories, %d files, %s\r", ClearLine(), formatDuration(d), s.Dirs, s.Files, formatBytes(s.Bytes))
|
2015-02-21 14:32:48 +00:00
|
|
|
}
|
|
|
|
p.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
|
2016-08-21 20:38:22 +00:00
|
|
|
fmt.Printf("%sscanned %d directories, %d files in %s\n", ClearLine(), s.Dirs, s.Files, formatDuration(d))
|
2015-02-21 14:32:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2015-06-21 11:02:56 +00:00
|
|
|
func (cmd CmdBackup) newArchiveProgress(todo restic.Stat) *restic.Progress {
|
|
|
|
if !cmd.global.ShowProgress() {
|
2015-02-21 13:23:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
archiveProgress := restic.NewProgress(time.Second)
|
|
|
|
|
|
|
|
var bps, eta uint64
|
|
|
|
itemsTodo := todo.Files + todo.Dirs
|
|
|
|
|
|
|
|
archiveProgress.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
|
|
|
|
sec := uint64(d / time.Second)
|
|
|
|
if todo.Bytes > 0 && sec > 0 && ticker {
|
|
|
|
bps = s.Bytes / sec
|
2015-03-16 19:20:53 +00:00
|
|
|
if s.Bytes >= todo.Bytes {
|
|
|
|
eta = 0
|
|
|
|
} else if bps > 0 {
|
2015-02-21 13:23:49 +00:00
|
|
|
eta = (todo.Bytes - s.Bytes) / bps
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
itemsDone := s.Files + s.Dirs
|
2015-03-16 19:20:53 +00:00
|
|
|
|
2015-04-26 10:09:14 +00:00
|
|
|
status1 := fmt.Sprintf("[%s] %s %s/s %s / %s %d / %d items %d errors ",
|
2015-04-24 23:39:32 +00:00
|
|
|
formatDuration(d),
|
2015-04-25 17:20:41 +00:00
|
|
|
formatPercent(s.Bytes, todo.Bytes),
|
2015-04-24 23:39:32 +00:00
|
|
|
formatBytes(bps),
|
|
|
|
formatBytes(s.Bytes), formatBytes(todo.Bytes),
|
2015-04-26 01:54:35 +00:00
|
|
|
itemsDone, itemsTodo,
|
|
|
|
s.Errors)
|
2015-04-24 23:39:32 +00:00
|
|
|
status2 := fmt.Sprintf("ETA %s ", formatSeconds(eta))
|
2015-03-16 19:48:29 +00:00
|
|
|
|
|
|
|
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
|
|
|
|
if err == nil {
|
2016-08-22 15:27:03 +00:00
|
|
|
maxlen := w - len(status2) -1
|
2016-02-10 16:28:48 +00:00
|
|
|
|
|
|
|
if maxlen < 4 {
|
|
|
|
status1 = ""
|
|
|
|
} else if len(status1) > maxlen {
|
|
|
|
status1 = status1[:maxlen-4]
|
|
|
|
status1 += "... "
|
2015-03-16 19:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-21 20:38:22 +00:00
|
|
|
fmt.Printf("%s%s%s\r", ClearLine(), status1, status2)
|
2015-02-21 13:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
archiveProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
|
2015-04-25 17:20:41 +00:00
|
|
|
fmt.Printf("\nduration: %s, %s\n", formatDuration(d), formatRate(todo.Bytes, d))
|
2015-02-21 13:23:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return archiveProgress
|
2015-02-16 22:44:26 +00:00
|
|
|
}
|
|
|
|
|
2016-05-10 19:51:56 +00:00
|
|
|
func (cmd CmdBackup) newArchiveStdinProgress() *restic.Progress {
|
|
|
|
if !cmd.global.ShowProgress() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
archiveProgress := restic.NewProgress(time.Second)
|
|
|
|
|
|
|
|
var bps uint64
|
|
|
|
|
|
|
|
archiveProgress.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
|
|
|
|
sec := uint64(d / time.Second)
|
|
|
|
if s.Bytes > 0 && sec > 0 && ticker {
|
|
|
|
bps = s.Bytes / sec
|
|
|
|
}
|
|
|
|
|
|
|
|
status1 := fmt.Sprintf("[%s] %s %s/s", formatDuration(d),
|
|
|
|
formatBytes(s.Bytes),
|
|
|
|
formatBytes(bps))
|
|
|
|
|
|
|
|
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
|
|
|
|
if err == nil {
|
|
|
|
maxlen := w - len(status1)
|
|
|
|
|
|
|
|
if maxlen < 4 {
|
|
|
|
status1 = ""
|
|
|
|
} else if len(status1) > maxlen {
|
|
|
|
status1 = status1[:maxlen-4]
|
|
|
|
status1 += "... "
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-21 20:38:22 +00:00
|
|
|
fmt.Printf("%s%s\r", ClearLine(), status1)
|
2016-05-10 19:51:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
archiveProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
|
|
|
|
fmt.Printf("\nduration: %s, %s\n", formatDuration(d), formatRate(s.Bytes, d))
|
|
|
|
}
|
|
|
|
|
|
|
|
return archiveProgress
|
|
|
|
}
|
|
|
|
|
2015-07-06 21:02:16 +00:00
|
|
|
// filterExisting returns a slice of all existing items, or an error if no
|
|
|
|
// items exist at all.
|
|
|
|
func filterExisting(items []string) (result []string, err error) {
|
|
|
|
for _, item := range items {
|
Fix 567 (#570)
* Patch for https://github.com/restic/restic/issues/567
Backup also files on windows with longer pathnames than 255 chars (e.g. from node).
as fd0 says "So, as far as I can see, we need to have custom methods for all functions that accept a path, so that on Windows we can substitute the normal (possibly relative) path used within restic by an (absolute) UNC path, and only then call the underlying functions like os.Stat(), os.Lstat(), os.Open() and so on.
I've already thought about adding a generic abstraction for the file system (so we can mock this easier in tests), and this looks like a good opportunity to build it."
* fixed building tests
* Restructured patches
Add Wrapper for filepath.Walk
* using \\?\ requires absolute pathes to be used.
Now all tests run
* used gofmt on the code
* Restructured Code. No patches dir, integrate the file functions into restic/fs/
There is still an issue, because restic.fs.Open has a different api the os.Open, which returns the result of OpenFile, but takes only a string
* Changed the last os.Open() calls to fs.Open() after extending the File interface
* fixed name-clash of restic.fs and fuse.fs detected by travis
* fixed fmt with gofmt
* c&p failure: removed fixpath() call.
* missing include
* fixed includes in linux variant
* Fix for Linux. Fd() is required on File interface
* done gofmt
2016-08-15 19:59:13 +00:00
|
|
|
_, err := fs.Lstat(item)
|
2015-07-06 21:02:16 +00:00
|
|
|
if err != nil && os.IsNotExist(err) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
result = append(result, item)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(result) == 0 {
|
|
|
|
return nil, errors.New("all target directories/files do not exist")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-05-10 19:51:56 +00:00
|
|
|
func (cmd CmdBackup) readFromStdin(args []string) error {
|
|
|
|
if len(args) != 0 {
|
|
|
|
return fmt.Errorf("when reading from stdin, no additional files can be specified")
|
|
|
|
}
|
|
|
|
|
|
|
|
repo, err := cmd.global.OpenRepository()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
lock, err := lockRepo(repo)
|
|
|
|
defer unlockRepo(lock)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = repo.LoadIndex()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, id, err := restic.ArchiveReader(repo, cmd.newArchiveStdinProgress(), os.Stdin, cmd.StdinFilename)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("archived as %v\n", id.Str())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-12-07 15:30:52 +00:00
|
|
|
func (cmd CmdBackup) Execute(args []string) error {
|
2016-05-10 19:51:56 +00:00
|
|
|
if cmd.Stdin {
|
|
|
|
return cmd.readFromStdin(args)
|
|
|
|
}
|
|
|
|
|
2015-03-02 13:48:47 +00:00
|
|
|
if len(args) == 0 {
|
2014-12-07 15:30:52 +00:00
|
|
|
return fmt.Errorf("wrong number of parameters, Usage: %s", cmd.Usage())
|
|
|
|
}
|
|
|
|
|
2015-03-02 13:48:47 +00:00
|
|
|
target := make([]string, 0, len(args))
|
|
|
|
for _, d := range args {
|
|
|
|
if a, err := filepath.Abs(d); err == nil {
|
|
|
|
d = a
|
|
|
|
}
|
|
|
|
target = append(target, d)
|
|
|
|
}
|
|
|
|
|
2015-07-06 21:02:16 +00:00
|
|
|
target, err := filterExisting(target)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-06-27 12:36:46 +00:00
|
|
|
repo, err := cmd.global.OpenRepository()
|
2014-12-07 15:30:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-04-27 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
2015-06-27 13:05:20 +00:00
|
|
|
lock, err := lockRepo(repo)
|
|
|
|
defer unlockRepo(lock)
|
2015-06-27 12:40:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-06-27 12:36:46 +00:00
|
|
|
err = repo.LoadIndex()
|
2015-04-26 15:44:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-25 15:05:45 +00:00
|
|
|
var parentSnapshotID *backend.ID
|
2014-11-30 21:34:21 +00:00
|
|
|
|
2015-04-03 19:18:09 +00:00
|
|
|
// Force using a parent
|
|
|
|
if !cmd.Force && cmd.Parent != "" {
|
2015-07-25 15:05:45 +00:00
|
|
|
id, err := restic.FindSnapshot(repo, cmd.Parent)
|
2014-11-30 21:34:21 +00:00
|
|
|
if err != nil {
|
2015-03-02 13:48:47 +00:00
|
|
|
return fmt.Errorf("invalid id %q: %v", cmd.Parent, err)
|
2014-11-30 21:34:21 +00:00
|
|
|
}
|
|
|
|
|
2015-07-25 15:05:45 +00:00
|
|
|
parentSnapshotID = &id
|
2014-11-30 21:34:21 +00:00
|
|
|
}
|
2014-04-27 22:00:15 +00:00
|
|
|
|
2015-04-03 19:18:09 +00:00
|
|
|
// Find last snapshot to set it as parent, if not already set
|
2015-05-17 18:48:59 +00:00
|
|
|
if !cmd.Force && parentSnapshotID == nil {
|
2016-04-27 16:36:48 +00:00
|
|
|
id, err := restic.FindLatestSnapshot(repo, target, "")
|
2015-07-25 15:05:45 +00:00
|
|
|
if err == nil {
|
|
|
|
parentSnapshotID = &id
|
2016-04-27 16:36:48 +00:00
|
|
|
} else if err != restic.ErrNoSnapshotFound {
|
2015-07-25 15:05:45 +00:00
|
|
|
return err
|
2015-04-03 19:18:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-25 16:09:38 +00:00
|
|
|
if parentSnapshotID != nil {
|
|
|
|
cmd.global.Verbosef("using parent snapshot %v\n", parentSnapshotID.Str())
|
|
|
|
}
|
|
|
|
|
2015-06-21 11:25:26 +00:00
|
|
|
cmd.global.Verbosef("scan %v\n", target)
|
2014-11-16 20:29:11 +00:00
|
|
|
|
2016-04-01 11:50:45 +00:00
|
|
|
// add patterns from file
|
|
|
|
if cmd.ExcludeFile != "" {
|
Fix 567 (#570)
* Patch for https://github.com/restic/restic/issues/567
Backup also files on windows with longer pathnames than 255 chars (e.g. from node).
as fd0 says "So, as far as I can see, we need to have custom methods for all functions that accept a path, so that on Windows we can substitute the normal (possibly relative) path used within restic by an (absolute) UNC path, and only then call the underlying functions like os.Stat(), os.Lstat(), os.Open() and so on.
I've already thought about adding a generic abstraction for the file system (so we can mock this easier in tests), and this looks like a good opportunity to build it."
* fixed building tests
* Restructured patches
Add Wrapper for filepath.Walk
* using \\?\ requires absolute pathes to be used.
Now all tests run
* used gofmt on the code
* Restructured Code. No patches dir, integrate the file functions into restic/fs/
There is still an issue, because restic.fs.Open has a different api the os.Open, which returns the result of OpenFile, but takes only a string
* Changed the last os.Open() calls to fs.Open() after extending the File interface
* fixed name-clash of restic.fs and fuse.fs detected by travis
* fixed fmt with gofmt
* c&p failure: removed fixpath() call.
* missing include
* fixed includes in linux variant
* Fix for Linux. Fd() is required on File interface
* done gofmt
2016-08-15 19:59:13 +00:00
|
|
|
file, err := fs.Open(cmd.ExcludeFile)
|
2016-04-01 11:50:45 +00:00
|
|
|
if err != nil {
|
|
|
|
cmd.global.Warnf("error reading exclude patterns: %v", err)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
if !strings.HasPrefix(line, "#") {
|
2016-04-16 20:04:29 +00:00
|
|
|
line = os.ExpandEnv(line)
|
2016-04-01 11:50:45 +00:00
|
|
|
cmd.Excludes = append(cmd.Excludes, line)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-19 22:13:39 +00:00
|
|
|
selectFilter := func(item string, fi os.FileInfo) bool {
|
2015-07-22 20:33:23 +00:00
|
|
|
matched, err := filter.List(cmd.Excludes, item)
|
2015-07-19 22:13:39 +00:00
|
|
|
if err != nil {
|
|
|
|
cmd.global.Warnf("error for exclude pattern: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-10-27 21:34:30 +00:00
|
|
|
if matched {
|
|
|
|
debug.Log("backup.Execute", "path %q excluded by a filter", item)
|
|
|
|
}
|
|
|
|
|
2015-07-19 22:13:39 +00:00
|
|
|
return !matched
|
|
|
|
}
|
2014-11-16 20:29:11 +00:00
|
|
|
|
2015-07-19 22:13:39 +00:00
|
|
|
stat, err := restic.Scan(target, selectFilter, cmd.newScanProgress())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-11-16 20:29:11 +00:00
|
|
|
|
2015-06-27 12:36:46 +00:00
|
|
|
arch := restic.NewArchiver(repo)
|
2015-07-22 20:33:23 +00:00
|
|
|
arch.Excludes = cmd.Excludes
|
2015-07-19 22:13:39 +00:00
|
|
|
arch.SelectFilter = selectFilter
|
2014-11-23 11:05:43 +00:00
|
|
|
|
2015-01-04 17:23:00 +00:00
|
|
|
arch.Error = func(dir string, fi os.FileInfo, err error) error {
|
|
|
|
// TODO: make ignoring errors configurable
|
2016-08-21 20:38:22 +00:00
|
|
|
cmd.global.Warnf("%s\rerror for %s: %v\n", ClearLine(), dir, err)
|
2015-01-04 17:23:00 +00:00
|
|
|
return nil
|
2014-11-16 20:29:11 +00:00
|
|
|
}
|
|
|
|
|
2015-06-21 11:02:56 +00:00
|
|
|
_, id, err := arch.Snapshot(cmd.newArchiveProgress(stat), target, parentSnapshotID)
|
2014-11-16 20:29:11 +00:00
|
|
|
if err != nil {
|
2015-02-03 21:05:46 +00:00
|
|
|
return err
|
2014-11-16 20:29:11 +00:00
|
|
|
}
|
|
|
|
|
2015-06-21 11:25:26 +00:00
|
|
|
cmd.global.Verbosef("snapshot %s saved\n", id.Str())
|
2014-04-27 22:00:15 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|