2014-10-05 14:44:59 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-03-08 20:29:31 +01:00
|
|
|
"context"
|
2018-08-10 19:48:42 -06:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2014-10-05 14:44:59 +02:00
|
|
|
|
2016-09-17 12:36:05 +02:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
2017-07-23 14:21:03 +02:00
|
|
|
"github.com/restic/restic/internal/errors"
|
2017-07-24 17:42:25 +02:00
|
|
|
"github.com/restic/restic/internal/restic"
|
2018-06-09 23:31:31 +02:00
|
|
|
"github.com/restic/restic/internal/walker"
|
2014-10-05 14:44:59 +02:00
|
|
|
)
|
|
|
|
|
2016-09-17 12:36:05 +02:00
|
|
|
var cmdLs = &cobra.Command{
|
2018-08-10 22:10:02 -06:00
|
|
|
Use: "ls [flags] [snapshotID] [dir...]",
|
2017-09-11 09:32:44 -07:00
|
|
|
Short: "List files in a snapshot",
|
2016-09-17 12:36:05 +02:00
|
|
|
Long: `
|
2018-08-10 22:10:02 -06: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
|
|
|
|
directory paths, and only files inside those directories will
|
|
|
|
be listed. If the --recursive flag is used, then the filter
|
|
|
|
will allow traversing into matching directories' subfolders.
|
2016-09-17 12:36:05 +02:00
|
|
|
`,
|
2017-08-06 21:02:16 +02:00
|
|
|
DisableAutoGenTag: true,
|
2016-09-17 12:36:05 +02:00
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2017-03-08 20:29:31 +01:00
|
|
|
return runLs(lsOptions, globalOptions, args)
|
2016-09-17 12:36:05 +02:00
|
|
|
},
|
2015-06-21 13:02:56 +02:00
|
|
|
}
|
2014-12-07 16:30:52 +01:00
|
|
|
|
2017-01-14 11:19:47 +08:00
|
|
|
// LsOptions collects all options for the ls command.
|
|
|
|
type LsOptions struct {
|
2018-08-10 19:48:42 -06:00
|
|
|
ListLong bool
|
|
|
|
Host string
|
|
|
|
Tags restic.TagLists
|
|
|
|
Paths []string
|
|
|
|
Recursive bool
|
2017-01-14 11:19:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
var lsOptions LsOptions
|
2016-09-17 12:36:05 +02:00
|
|
|
|
2014-11-30 22:39:58 +01:00
|
|
|
func init() {
|
2016-09-17 12:36:05 +02:00
|
|
|
cmdRoot.AddCommand(cmdLs)
|
|
|
|
|
2017-01-14 11:19:47 +08:00
|
|
|
flags := cmdLs.Flags()
|
|
|
|
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
2017-03-08 20:29:31 +01:00
|
|
|
flags.StringVarP(&lsOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
2017-07-09 12:45:49 +02:00
|
|
|
flags.Var(&lsOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot ID is given")
|
2017-07-07 03:19:06 +02:00
|
|
|
flags.StringArrayVar(&lsOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
|
2018-08-10 19:48:42 -06:00
|
|
|
flags.BoolVar(&lsOptions.Recursive, "recursive", false, "include files in subfolders of the listed directories")
|
2014-11-30 22:39:58 +01:00
|
|
|
}
|
|
|
|
|
2017-03-08 20:29:31 +01: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 16:30:52 +01:00
|
|
|
}
|
|
|
|
|
2016-09-17 12:36:05 +02:00
|
|
|
repo, err := OpenRepository(gopts)
|
2014-12-07 16:30:52 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-10-05 14:44:59 +02:00
|
|
|
}
|
|
|
|
|
2017-12-06 05:48:39 -05:00
|
|
|
if err = repo.LoadIndex(gopts.ctx); err != nil {
|
2015-05-05 20:50:10 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-08-10 19:48:42 -06:00
|
|
|
// extract any specific directories to walk
|
2018-08-10 22:10:02 -06:00
|
|
|
var dirs []string
|
|
|
|
if len(args) > 1 {
|
|
|
|
dirs = args[1:]
|
|
|
|
}
|
2018-08-10 19:48:42 -06:00
|
|
|
|
2017-03-08 20:29:31 +01:00
|
|
|
ctx, cancel := context.WithCancel(gopts.ctx)
|
|
|
|
defer cancel()
|
2018-08-10 19:48:42 -06:00
|
|
|
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args[:1]) {
|
2018-08-10 22:10:02 -06:00
|
|
|
dirsToPrint := opts.Paths
|
|
|
|
if len(dirs) > 0 {
|
|
|
|
dirsToPrint = dirs
|
|
|
|
}
|
|
|
|
Verbosef("snapshot %s of %v at %s):\n", sn.ID().Str(), dirsToPrint, sn.Time)
|
2017-01-12 19:24:08 +08:00
|
|
|
|
2018-06-09 23:31:31 +02: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
|
|
|
|
}
|
2018-08-10 19:48:42 -06:00
|
|
|
|
|
|
|
// apply any directory filters
|
|
|
|
if len(dirs) > 0 {
|
|
|
|
var nodeDir string
|
|
|
|
if !opts.Recursive {
|
|
|
|
// only needed for exact directory match; i.e. no subfolders
|
|
|
|
nodeDir = filepath.Dir(nodepath)
|
|
|
|
}
|
|
|
|
var match bool
|
|
|
|
for _, dir := range dirs {
|
|
|
|
if opts.Recursive {
|
|
|
|
if strings.HasPrefix(nodepath, dir) {
|
|
|
|
match = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if nodeDir == dir {
|
|
|
|
match = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-09 23:31:31 +02:00
|
|
|
Printf("%s\n", formatNode(nodepath, node, lsOptions.ListLong))
|
|
|
|
return false, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
2017-03-08 20:29:31 +01:00
|
|
|
return err
|
2017-01-12 19:24:08 +08:00
|
|
|
}
|
2014-10-05 14:44:59 +02:00
|
|
|
}
|
2017-03-08 20:29:31 +01:00
|
|
|
return nil
|
2014-10-05 14:44:59 +02:00
|
|
|
}
|