Merge pull request #4351 from kenny-y-dev/human-readable-ls

Add --human-readable to ls and find output
This commit is contained in:
Michael Eischer 2023-07-16 11:44:41 +00:00 committed by GitHub
commit 47206a6579
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 13 deletions

View File

@ -0,0 +1,13 @@
Enhancement: Add `--human-readable` flag to `ls` and `find` commands
Previously, when using the -l option with the ls and find commands,
the displayed size was always in bytes, without an option for a more
human readable format such as MiB or GiB.
The new `--human-readable` option will convert longer size values into
more human friendly values with an appropriate suffix depending on the
output size. For example, a size of `14680064` will be shown as
`14.000 MiB`.
https://github.com/restic/restic/issues/4159
https://github.com/restic/restic/pull/4351

View File

@ -51,6 +51,7 @@ type FindOptions struct {
PackID, ShowPackID bool
CaseInsensitive bool
ListLong bool
HumanReadable bool
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.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.BoolVar(&findOptions.HumanReadable, "human-readable", false, "print sizes in human readable format")
initMultiSnapshotFilter(f, &findOptions.SnapshotFilter, true)
}
@ -104,12 +106,13 @@ func parseTime(str string) (time.Time, error) {
}
type statefulOutput struct {
ListLong bool
JSON bool
inuse bool
newsn *restic.Snapshot
oldsn *restic.Snapshot
hits int
ListLong bool
HumanReadable bool
JSON bool
inuse bool
newsn *restic.Snapshot
oldsn *restic.Snapshot
hits int
}
func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
@ -164,7 +167,7 @@ func (s *statefulOutput) PrintPatternNormal(path string, node *restic.Node) {
s.oldsn = s.newsn
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) {
@ -594,7 +597,7 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
f := &Finder{
repo: repo,
pat: pat,
out: statefulOutput{ListLong: opts.ListLong, JSON: gopts.JSON},
out: statefulOutput{ListLong: opts.ListLong, HumanReadable: opts.HumanReadable, JSON: gopts.JSON},
ignoreTrees: restic.NewIDSet(),
}

View File

@ -50,7 +50,8 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
type LsOptions struct {
ListLong bool
restic.SnapshotFilter
Recursive bool
Recursive bool
HumanReadable bool
}
var lsOptions LsOptions
@ -62,6 +63,7 @@ func init() {
initSingleSnapshotFilter(flags, &lsOptions.SnapshotFilter)
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.HumanReadable, "human-readable", false, "print sizes in human readable format")
}
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)
}
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))
}
}

View File

@ -5,9 +5,10 @@ import (
"os"
"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 {
return path
}
@ -15,6 +16,13 @@ func formatNode(path string, n *restic.Node, long bool) string {
var mode os.FileMode
var target string
var size string
if human {
size = ui.FormatBytes(n.Size)
} else {
size = fmt.Sprintf("%6d", n.Size)
}
switch n.Type {
case "file":
mode = 0
@ -33,8 +41,8 @@ func formatNode(path string, n *restic.Node, long bool) string {
mode = os.ModeSocket
}
return fmt.Sprintf("%s %5d %5d %6d %s %s%s",
mode|n.Mode, n.UID, n.GID, n.Size,
return fmt.Sprintf("%s %5d %5d %s %s %s%s",
mode|n.Mode, n.UID, n.GID, size,
n.ModTime.Local().Format(TimeFormat), path,
target)
}

61
cmd/restic/format_test.go Normal file
View File

@ -0,0 +1,61 @@
package main
import (
"testing"
"time"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
)
func TestFormatNode(t *testing.T) {
// overwrite time zone to ensure the data is formatted reproducibly
tz := time.Local
time.Local = time.UTC
defer func() {
time.Local = tz
}()
testPath := "/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),
}
for _, c := range []struct {
path string
restic.Node
long bool
human bool
expect string
}{
{
path: testPath,
Node: node,
long: false,
human: false,
expect: testPath,
},
{
path: testPath,
Node: node,
long: true,
human: false,
expect: "---------- 1000 2000 14680064 2020-01-02 03:04:05 " + testPath,
},
{
path: testPath,
Node: node,
long: true,
human: true,
expect: "---------- 1000 2000 14.000 MiB 2020-01-02 03:04:05 " + testPath,
},
} {
r := formatNode(c.path, &c.Node, c.long, c.human)
rtest.Equals(t, c.expect, r)
}
}