mirror of
https://github.com/octoleo/restic.git
synced 2025-01-03 07:12:28 +00:00
Add --human-readable to ls and find output
Modifies format module to add options for human readable storage size formatting, using size parsing already in ui/format. Cmd flag --human-readable added to ls and find commands. Additional option added to formatNode to support printing size in regular or new human readable format
This commit is contained in:
parent
88a10a368f
commit
8812dcd56a
13
changelog/unreleased/issue-4159
Normal file
13
changelog/unreleased/issue-4159
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Enhancement: Add --human-readable flag to ls and find commands
|
||||||
|
|
||||||
|
Previously, the `ls` and `find` commands showed with the `-l` option
|
||||||
|
showed size in bytes, and did not have an option for a more human
|
||||||
|
readable format that converted these longer numbers into values such
|
||||||
|
as MiB or GiB
|
||||||
|
|
||||||
|
The new `--human-readable` option for `ls` and `find` when used with
|
||||||
|
the `-l` option will convert longer size values into more human friendly
|
||||||
|
values, with an appropriate suffix depending on the output size. For
|
||||||
|
example, a value of `14680064` will be shown as `14.000 MiB`.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/issues/4159
|
@ -51,6 +51,7 @@ type FindOptions struct {
|
|||||||
PackID, ShowPackID bool
|
PackID, ShowPackID bool
|
||||||
CaseInsensitive bool
|
CaseInsensitive bool
|
||||||
ListLong bool
|
ListLong bool
|
||||||
|
HumanReadable bool
|
||||||
restic.SnapshotFilter
|
restic.SnapshotFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +70,7 @@ func init() {
|
|||||||
f.BoolVar(&findOptions.ShowPackID, "show-pack-id", false, "display the pack-ID the blobs belong to (with --blob or --tree)")
|
f.BoolVar(&findOptions.ShowPackID, "show-pack-id", false, "display the pack-ID the blobs belong to (with --blob or --tree)")
|
||||||
f.BoolVarP(&findOptions.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern")
|
f.BoolVarP(&findOptions.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern")
|
||||||
f.BoolVarP(&findOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
f.BoolVarP(&findOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
||||||
|
f.BoolVar(&findOptions.HumanReadable, "human-readable", false, "print sizes in human readable format")
|
||||||
|
|
||||||
initMultiSnapshotFilter(f, &findOptions.SnapshotFilter, true)
|
initMultiSnapshotFilter(f, &findOptions.SnapshotFilter, true)
|
||||||
}
|
}
|
||||||
@ -105,6 +107,7 @@ func parseTime(str string) (time.Time, error) {
|
|||||||
|
|
||||||
type statefulOutput struct {
|
type statefulOutput struct {
|
||||||
ListLong bool
|
ListLong bool
|
||||||
|
HumanReadable bool
|
||||||
JSON bool
|
JSON bool
|
||||||
inuse bool
|
inuse bool
|
||||||
newsn *restic.Snapshot
|
newsn *restic.Snapshot
|
||||||
@ -164,7 +167,7 @@ func (s *statefulOutput) PrintPatternNormal(path string, node *restic.Node) {
|
|||||||
s.oldsn = s.newsn
|
s.oldsn = s.newsn
|
||||||
Verbosef("Found matching entries in snapshot %s from %s\n", s.oldsn.ID().Str(), s.oldsn.Time.Local().Format(TimeFormat))
|
Verbosef("Found matching entries in snapshot %s from %s\n", s.oldsn.ID().Str(), s.oldsn.Time.Local().Format(TimeFormat))
|
||||||
}
|
}
|
||||||
Println(formatNode(path, node, s.ListLong))
|
Println(formatNode(path, node, s.ListLong, s.HumanReadable))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintPattern(path string, node *restic.Node) {
|
func (s *statefulOutput) PrintPattern(path string, node *restic.Node) {
|
||||||
@ -594,7 +597,7 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
|
|||||||
f := &Finder{
|
f := &Finder{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
pat: pat,
|
pat: pat,
|
||||||
out: statefulOutput{ListLong: opts.ListLong, JSON: gopts.JSON},
|
out: statefulOutput{ListLong: opts.ListLong, HumanReadable: opts.HumanReadable, JSON: gopts.JSON},
|
||||||
ignoreTrees: restic.NewIDSet(),
|
ignoreTrees: restic.NewIDSet(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ type LsOptions struct {
|
|||||||
ListLong bool
|
ListLong bool
|
||||||
restic.SnapshotFilter
|
restic.SnapshotFilter
|
||||||
Recursive bool
|
Recursive bool
|
||||||
|
HumanReadable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var lsOptions LsOptions
|
var lsOptions LsOptions
|
||||||
@ -62,6 +63,7 @@ func init() {
|
|||||||
initSingleSnapshotFilter(flags, &lsOptions.SnapshotFilter)
|
initSingleSnapshotFilter(flags, &lsOptions.SnapshotFilter)
|
||||||
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
||||||
flags.BoolVar(&lsOptions.Recursive, "recursive", false, "include files in subfolders of the listed directories")
|
flags.BoolVar(&lsOptions.Recursive, "recursive", false, "include files in subfolders of the listed directories")
|
||||||
|
flags.BoolVar(&lsOptions.HumanReadable, "human-readable", false, "print sizes in human readable format")
|
||||||
}
|
}
|
||||||
|
|
||||||
type lsSnapshot struct {
|
type lsSnapshot struct {
|
||||||
@ -206,7 +208,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||||||
Verbosef("snapshot %s of %v filtered by %v at %s):\n", sn.ID().Str(), sn.Paths, dirs, sn.Time)
|
Verbosef("snapshot %s of %v filtered by %v at %s):\n", sn.ID().Str(), sn.Paths, dirs, sn.Time)
|
||||||
}
|
}
|
||||||
printNode = func(path string, node *restic.Node) {
|
printNode = func(path string, node *restic.Node) {
|
||||||
Printf("%s\n", formatNode(path, node, lsOptions.ListLong))
|
Printf("%s\n", formatNode(path, node, lsOptions.ListLong, lsOptions.HumanReadable))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,9 +5,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
"github.com/restic/restic/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatNode(path string, n *restic.Node, long bool) string {
|
func formatNode(path string, n *restic.Node, long bool, human bool) string {
|
||||||
if !long {
|
if !long {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@ -15,6 +16,11 @@ func formatNode(path string, n *restic.Node, long bool) string {
|
|||||||
var mode os.FileMode
|
var mode os.FileMode
|
||||||
var target string
|
var target string
|
||||||
|
|
||||||
|
size := fmt.Sprintf("%6d", n.Size)
|
||||||
|
if human {
|
||||||
|
size = ui.FormatBytes(n.Size)
|
||||||
|
}
|
||||||
|
|
||||||
switch n.Type {
|
switch n.Type {
|
||||||
case "file":
|
case "file":
|
||||||
mode = 0
|
mode = 0
|
||||||
@ -33,8 +39,8 @@ func formatNode(path string, n *restic.Node, long bool) string {
|
|||||||
mode = os.ModeSocket
|
mode = os.ModeSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s %5d %5d %6d %s %s%s",
|
return fmt.Sprintf("%s %5d %5d %s %s %s%s",
|
||||||
mode|n.Mode, n.UID, n.GID, n.Size,
|
mode|n.Mode, n.UID, n.GID, size,
|
||||||
n.ModTime.Local().Format(TimeFormat), path,
|
n.ModTime.Local().Format(TimeFormat), path,
|
||||||
target)
|
target)
|
||||||
}
|
}
|
||||||
|
65
cmd/restic/format_test.go
Normal file
65
cmd/restic/format_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
|
rtest "github.com/restic/restic/internal/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatNode(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
path string
|
||||||
|
restic.Node
|
||||||
|
long bool
|
||||||
|
human bool
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
path: "/test/path",
|
||||||
|
Node: restic.Node{
|
||||||
|
Name: "baz",
|
||||||
|
Type: "file",
|
||||||
|
Size: 14680064,
|
||||||
|
UID: 1000,
|
||||||
|
GID: 2000,
|
||||||
|
ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
},
|
||||||
|
long: false,
|
||||||
|
human: false,
|
||||||
|
expect: "/test/path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/test/path",
|
||||||
|
Node: restic.Node{
|
||||||
|
Name: "baz",
|
||||||
|
Type: "file",
|
||||||
|
Size: 14680064,
|
||||||
|
UID: 1000,
|
||||||
|
GID: 2000,
|
||||||
|
ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
},
|
||||||
|
long: true,
|
||||||
|
human: false,
|
||||||
|
expect: "---------- 1000 2000 14680064 2020-01-01 22:04:05 /test/path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/test/path",
|
||||||
|
Node: restic.Node{
|
||||||
|
Name: "baz",
|
||||||
|
Type: "file",
|
||||||
|
Size: 14680064,
|
||||||
|
UID: 1000,
|
||||||
|
GID: 2000,
|
||||||
|
ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
},
|
||||||
|
long: true,
|
||||||
|
human: true,
|
||||||
|
expect: "---------- 1000 2000 14.000 MiB 2020-01-01 22:04:05 /test/path",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
r := formatNode(c.path, &c.Node, c.long, c.human)
|
||||||
|
rtest.Equals(t, r, c.expect)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user