restic/cmd/restic/cmd_ls.go

186 lines
5.9 KiB
Go
Raw Normal View History

2014-10-05 12:44:59 +00:00
package main
import (
"context"
2018-08-11 21:25:22 +00:00
"path"
"strings"
2014-10-05 12:44:59 +00:00
2016-09-17 10:36:05 +00:00
"github.com/spf13/cobra"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/errors"
2018-08-11 21:25:22 +00:00
"github.com/restic/restic/internal/fs"
2017-07-24 15:42:25 +00:00
"github.com/restic/restic/internal/restic"
2018-06-09 21:31:31 +00:00
"github.com/restic/restic/internal/walker"
2014-10-05 12:44:59 +00:00
)
2016-09-17 10:36:05 +00:00
var cmdLs = &cobra.Command{
2018-08-11 04:10:02 +00:00
Use: "ls [flags] [snapshotID] [dir...]",
Short: "List files in a snapshot",
2016-09-17 10:36:05 +00:00
Long: `
2018-08-11 04:10:02 +00:00
The "ls" command lists files and directories in a snapshot.
The special snapshot ID "latest" can be used to list files and
directories of the latest snapshot in the repository. The
--host flag can be used in conjunction to select the latest
snapshot originating from a certain host only.
File listings can optionally be filtered by directories. Any
positional arguments after the snapshot ID are interpreted as
2018-08-11 21:25:22 +00:00
absolute directory paths, and only files inside those directories
will be listed. If the --recursive flag is used, then the filter
2018-08-11 04:10:02 +00:00
will allow traversing into matching directories' subfolders.
2018-08-11 21:25:22 +00:00
Any directory paths specified must be absolute (starting with
a path separator); paths use the forward slash '/' as separator.
2016-09-17 10:36:05 +00:00
`,
DisableAutoGenTag: true,
2016-09-17 10:36:05 +00:00
RunE: func(cmd *cobra.Command, args []string) error {
return runLs(lsOptions, globalOptions, args)
2016-09-17 10:36:05 +00:00
},
}
2014-12-07 15:30:52 +00:00
2017-01-14 03:19:47 +00:00
// LsOptions collects all options for the ls command.
type LsOptions struct {
ListLong bool
Host string
Tags restic.TagLists
Paths []string
Recursive bool
2017-01-14 03:19:47 +00:00
}
var lsOptions LsOptions
2016-09-17 10:36:05 +00:00
2014-11-30 21:39:58 +00:00
func init() {
2016-09-17 10:36:05 +00:00
cmdRoot.AddCommand(cmdLs)
2017-01-14 03:19:47 +00:00
flags := cmdLs.Flags()
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
flags.StringVarP(&lsOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
flags.Var(&lsOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot ID is given")
flags.StringArrayVar(&lsOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
flags.BoolVar(&lsOptions.Recursive, "recursive", false, "include files in subfolders of the listed directories")
2014-11-30 21:39:58 +00:00
}
func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
if len(args) == 0 && opts.Host == "" && len(opts.Tags) == 0 && len(opts.Paths) == 0 {
return errors.Fatal("Invalid arguments, either give one or more snapshot IDs or set filters.")
2014-12-07 15:30:52 +00:00
}
// extract any specific directories to walk
2018-08-11 04:10:02 +00:00
var dirs []string
if len(args) > 1 {
dirs = args[1:]
2018-08-11 21:25:22 +00:00
for _, dir := range dirs {
if !strings.HasPrefix(dir, "/") {
return errors.Fatal("All path filters must be absolute, starting with a forward slash '/'")
}
}
2018-08-11 04:10:02 +00:00
}
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
if err = repo.LoadIndex(gopts.ctx); err != nil {
return err
}
ctx, cancel := context.WithCancel(gopts.ctx)
defer cancel()
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args[:1]) {
2018-08-11 21:25:22 +00:00
Verbosef("snapshot %s of %v filtered by %v at %s):\n", sn.ID().Str(), sn.Paths, dirs, sn.Time)
2017-01-12 11:24:08 +00:00
2018-06-09 21:31:31 +00:00
err := walker.Walk(ctx, repo, *sn.Tree, nil, func(nodepath string, node *restic.Node, err error) (bool, error) {
if err != nil {
return false, err
}
if node == nil {
return false, nil
}
// apply any directory filters
if len(dirs) > 0 {
nodeDir := path.Dir(nodepath)
2018-08-11 21:25:22 +00:00
// this first iteration ensures we do not traverse branches that
// are not in matching trees or will not lead us to matching trees
var walk bool
for _, dir := range dirs {
2018-08-12 20:18:44 +00:00
// the current node path is a prefix for one of the
// directories, so we're interested in something deeper in the
// tree. Example:
// nodepath: "/test"
// dir: "/test/foo"
2018-08-12 20:01:38 +00:00
approachingMatchingTree := fs.HasPathPrefix(nodepath, dir)
2018-08-12 20:18:44 +00:00
// we're within one of the selected dirs, example:
// nodepath: "/test/foo"
// dir: "/test"
inMatchingTree := fs.HasPathPrefix(dir, nodepath)
// this condition is complex, but it basically requires that we
// are either approaching a matching tree (not yet deep enough)
// or: if recursive, we have entered a matching tree; if non-
// recursive, then that we are at exactly the right depth
// (we can do the walk correctly by just using the condition of
// "approachingMatchingTree || inMatchingTree", but it will be
// much slower for non-recursive queries since it will continue
// to traverse subtrees that are too deep and won't match -- this
// extra check allows us to return SkipNode if we've gone TOO deep,
// which skips all its subfolders)
if approachingMatchingTree || opts.Recursive || (inMatchingTree && dir == nodeDir) {
2018-08-11 21:25:22 +00:00
walk = true
break
}
}
2018-08-11 21:25:22 +00:00
if !walk {
if node.Type == "dir" {
// signal Walk() that it should not descend into the tree.
return false, walker.SkipNode
}
// we must not return SkipNode for non-dir nodes because
// then the remaining nodes in the same tree would be
// skipped, so return nil instead
return false, nil
2018-08-11 21:25:22 +00:00
}
// this second iteration ensures that we get an exact match
// according to the filter and whether we should match subfolders
var match bool
for _, dir := range dirs {
if nodepath == dir {
// special case: match the directory filter exactly,
// which may or may not be desirable depending on your
// use case (for example, this is unnecessary when
// wanting to simply list the contents of a folder,
// rather than all files matching a directory prefix)
match = true
break
}
2018-08-11 21:25:22 +00:00
if opts.Recursive && fs.HasPathPrefix(dir, nodepath) {
match = true
break
}
if !opts.Recursive && nodeDir == dir {
2018-08-11 21:25:22 +00:00
match = true
break
}
}
if !match {
2018-08-11 21:25:22 +00:00
return false, nil
}
}
2018-06-09 21:31:31 +00:00
Printf("%s\n", formatNode(nodepath, node, lsOptions.ListLong))
2018-08-11 21:25:22 +00:00
2018-06-09 21:31:31 +00:00
return false, nil
})
if err != nil {
return err
2017-01-12 11:24:08 +00:00
}
2014-10-05 12:44:59 +00:00
}
return nil
2014-10-05 12:44:59 +00:00
}