mirror of
https://github.com/octoleo/restic.git
synced 2024-12-22 10:58:55 +00:00
restic ls --json: print "size":0 for empty files
This commit is contained in:
parent
af3de702c7
commit
d7322a5f36
8
changelog/unreleased/issue-3247
Normal file
8
changelog/unreleased/issue-3247
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Change: Empty files now have size of 0 in restic ls --json output
|
||||||
|
|
||||||
|
Restic ls --json used to omit the sizes of empty files in its output. It now
|
||||||
|
reports "size":0 explicitly for regular files, while omitting the size field
|
||||||
|
for all other types.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/issues/3247
|
||||||
|
https://github.com/restic/restic/pull/3257
|
@ -74,18 +74,42 @@ type lsSnapshot struct {
|
|||||||
StructType string `json:"struct_type"` // "snapshot"
|
StructType string `json:"struct_type"` // "snapshot"
|
||||||
}
|
}
|
||||||
|
|
||||||
type lsNode struct {
|
// Print node in our custom JSON format, followed by a newline.
|
||||||
Name string `json:"name"`
|
func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error {
|
||||||
Type string `json:"type"`
|
n := &struct {
|
||||||
Path string `json:"path"`
|
Name string `json:"name"`
|
||||||
UID uint32 `json:"uid"`
|
Type string `json:"type"`
|
||||||
GID uint32 `json:"gid"`
|
Path string `json:"path"`
|
||||||
Size uint64 `json:"size,omitempty"`
|
UID uint32 `json:"uid"`
|
||||||
Mode os.FileMode `json:"mode,omitempty"`
|
GID uint32 `json:"gid"`
|
||||||
ModTime time.Time `json:"mtime,omitempty"`
|
Size *uint64 `json:"size,omitempty"`
|
||||||
AccessTime time.Time `json:"atime,omitempty"`
|
Mode os.FileMode `json:"mode,omitempty"`
|
||||||
ChangeTime time.Time `json:"ctime,omitempty"`
|
ModTime time.Time `json:"mtime,omitempty"`
|
||||||
StructType string `json:"struct_type"` // "node"
|
AccessTime time.Time `json:"atime,omitempty"`
|
||||||
|
ChangeTime time.Time `json:"ctime,omitempty"`
|
||||||
|
StructType string `json:"struct_type"` // "node"
|
||||||
|
|
||||||
|
size uint64 // Target for Size pointer.
|
||||||
|
}{
|
||||||
|
Name: node.Name,
|
||||||
|
Type: node.Type,
|
||||||
|
Path: path,
|
||||||
|
UID: node.UID,
|
||||||
|
GID: node.GID,
|
||||||
|
size: node.Size,
|
||||||
|
Mode: node.Mode,
|
||||||
|
ModTime: node.ModTime,
|
||||||
|
AccessTime: node.AccessTime,
|
||||||
|
ChangeTime: node.ChangeTime,
|
||||||
|
StructType: "node",
|
||||||
|
}
|
||||||
|
// Always print size for regular files, even when empty,
|
||||||
|
// but never for other types.
|
||||||
|
if node.Type == "file" {
|
||||||
|
n.Size = &n.size
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.Encode(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
||||||
@ -159,7 +183,7 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
|||||||
enc := json.NewEncoder(gopts.stdout)
|
enc := json.NewEncoder(gopts.stdout)
|
||||||
|
|
||||||
printSnapshot = func(sn *restic.Snapshot) {
|
printSnapshot = func(sn *restic.Snapshot) {
|
||||||
err = enc.Encode(lsSnapshot{
|
err := enc.Encode(lsSnapshot{
|
||||||
Snapshot: sn,
|
Snapshot: sn,
|
||||||
ID: sn.ID(),
|
ID: sn.ID(),
|
||||||
ShortID: sn.ID().Str(),
|
ShortID: sn.ID().Str(),
|
||||||
@ -171,19 +195,7 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
printNode = func(path string, node *restic.Node) {
|
printNode = func(path string, node *restic.Node) {
|
||||||
err = enc.Encode(lsNode{
|
err := lsNodeJSON(enc, path, node)
|
||||||
Name: node.Name,
|
|
||||||
Type: node.Type,
|
|
||||||
Path: path,
|
|
||||||
UID: node.UID,
|
|
||||||
GID: node.GID,
|
|
||||||
Size: node.Size,
|
|
||||||
Mode: node.Mode,
|
|
||||||
ModTime: node.ModTime,
|
|
||||||
AccessTime: node.AccessTime,
|
|
||||||
ChangeTime: node.ChangeTime,
|
|
||||||
StructType: "node",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("JSON encode failed: %v\n", err)
|
Warnf("JSON encode failed: %v\n", err)
|
||||||
}
|
}
|
||||||
|
91
cmd/restic/cmd_ls_test.go
Normal file
91
cmd/restic/cmd_ls_test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
|
rtest "github.com/restic/restic/internal/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLsNodeJSON(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
path string
|
||||||
|
restic.Node
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
// Mode is omitted when zero.
|
||||||
|
{
|
||||||
|
path: "/bar/baz",
|
||||||
|
Node: restic.Node{
|
||||||
|
Name: "baz",
|
||||||
|
Type: "file",
|
||||||
|
Size: 12345,
|
||||||
|
UID: 10000000,
|
||||||
|
GID: 20000000,
|
||||||
|
|
||||||
|
User: "nobody",
|
||||||
|
Group: "nobodies",
|
||||||
|
Links: 1,
|
||||||
|
},
|
||||||
|
expect: `{"name":"baz","type":"file","path":"/bar/baz","uid":10000000,"gid":20000000,"size":12345,"mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Even empty files get an explicit size.
|
||||||
|
{
|
||||||
|
path: "/foo/empty",
|
||||||
|
Node: restic.Node{
|
||||||
|
Name: "empty",
|
||||||
|
Type: "file",
|
||||||
|
Size: 0,
|
||||||
|
UID: 1001,
|
||||||
|
GID: 1001,
|
||||||
|
|
||||||
|
User: "not printed",
|
||||||
|
Group: "not printed",
|
||||||
|
Links: 0xF00,
|
||||||
|
},
|
||||||
|
expect: `{"name":"empty","type":"file","path":"/foo/empty","uid":1001,"gid":1001,"size":0,"mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Non-regular files do not get a size.
|
||||||
|
// Mode is printed in decimal, including the type bits.
|
||||||
|
{
|
||||||
|
path: "/foo/link",
|
||||||
|
Node: restic.Node{
|
||||||
|
Name: "link",
|
||||||
|
Type: "symlink",
|
||||||
|
Mode: os.ModeSymlink | 0777,
|
||||||
|
LinkTarget: "not printed",
|
||||||
|
},
|
||||||
|
expect: `{"name":"link","type":"symlink","path":"/foo/link","uid":0,"gid":0,"mode":134218239,"mtime":"0001-01-01T00:00:00Z","atime":"0001-01-01T00:00:00Z","ctime":"0001-01-01T00:00:00Z","struct_type":"node"}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: "/some/directory",
|
||||||
|
Node: restic.Node{
|
||||||
|
Name: "directory",
|
||||||
|
Type: "dir",
|
||||||
|
Mode: os.ModeDir | 0755,
|
||||||
|
ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
AccessTime: time.Date(2021, 2, 3, 4, 5, 6, 7, time.UTC),
|
||||||
|
ChangeTime: time.Date(2022, 3, 4, 5, 6, 7, 8, time.UTC),
|
||||||
|
},
|
||||||
|
expect: `{"name":"directory","type":"dir","path":"/some/directory","uid":0,"gid":0,"mode":2147484141,"mtime":"2020-01-02T03:04:05Z","atime":"2021-02-03T04:05:06.000000007Z","ctime":"2022-03-04T05:06:07.000000008Z","struct_type":"node"}`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
err := lsNodeJSON(enc, c.path, &c.Node)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
rtest.Equals(t, c.expect+"\n", buf.String())
|
||||||
|
|
||||||
|
// Sanity check: output must be valid JSON.
|
||||||
|
var v interface{}
|
||||||
|
err = json.NewDecoder(buf).Decode(&v)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user