From efaebc873eed9d05f8a3c3ed9ab8d134b7cd4be5 Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 25 Mar 2026 21:55:26 -0600 Subject: [PATCH] Add restic ls JSON parsing with test fixture repo --- internal/restic/lsnodes.go | 171 ++++++++ internal/restic/lsnodes_test.go | 411 ++++++++++++++++++ internal/restic/testdata/repo/config | Bin 0 -> 155 bytes ...e747e307c5a6da50bb5999855d610a422f6447fb25 | Bin 0 -> 2588 bytes ...6110cce4816fa78b562b35b92340a194588b885f28 | Bin 0 -> 9885 bytes ...db4f66fb18f844b63e7e9198f757a04ab400dce843 | Bin 0 -> 2524 bytes ...865e61521b64ec76a343d33b6c6c2dfcbd1e3c163c | 1 + ...a9a8585468141672577a9310128ae7674d1deed1bd | 2 + .../testdata/source/.config/keld/config.toml | 1 + .../source/documents/notes/2026/february.md | 1 + .../source/documents/notes/2026/january.md | 1 + .../testdata/source/documents/notes/todo.txt | 1 + .../source/documents/tax returns/2025.pdf | 1 + .../live/01 - Take Five.flac" | 1 + .../Jazz Caf\303\251/live/02 - So What.flac" | 1 + .../live/03 - Blue in Green.flac" | 1 + ...\345\244\234\346\230\216\343\201\221.flac" | 1 + ...\351\242\250\343\201\256\346\255\214.flac" | 1 + .../03 - \346\230\237\347\251\272.flac" | 1 + .../01 - \346\265\267\350\276\272.flac" | 1 + ...\346\243\256\343\201\256\344\270\255.flac" | 1 + .../source/photos/2025/february/IMG_0010.jpg | 1 + .../source/photos/2025/january/IMG_0001.jpg | 1 + .../source/photos/2025/january/IMG_0002.jpg | 1 + .../source/photos/2025/january/IMG_0003.jpg | 1 + .../testdata/source/projects/keld/README.md | 1 + .../testdata/source/projects/keld/cmd/root.go | 1 + .../testdata/source/projects/keld/go.mod | 1 + .../projects/keld/internal/config/config.go | 1 + .../projects/keld/internal/config/resolve.go | 1 + .../projects/keld/internal/restic/exec.go | 1 + .../keld/internal/restic/snapshots.go | 1 + .../source/projects/web app/package.json | 1 + .../source/projects/web app/public/index.html | 1 + .../source/projects/web app/public/style.css | 1 + .../projects/web app/src/components/App.tsx | 1 + .../projects/web app/src/components/Nav.tsx | 1 + 37 files changed, 614 insertions(+) create mode 100644 internal/restic/lsnodes.go create mode 100644 internal/restic/lsnodes_test.go create mode 100644 internal/restic/testdata/repo/config create mode 100644 internal/restic/testdata/repo/data/63/6354facc46c38cc3e9ac18e747e307c5a6da50bb5999855d610a422f6447fb25 create mode 100644 internal/restic/testdata/repo/data/b2/b2b71324d483e3599b4e0d6110cce4816fa78b562b35b92340a194588b885f28 create mode 100644 internal/restic/testdata/repo/index/ac37b4026a4655d219044edb4f66fb18f844b63e7e9198f757a04ab400dce843 create mode 100644 internal/restic/testdata/repo/keys/2f82b6a71d8b04bbf33fa5865e61521b64ec76a343d33b6c6c2dfcbd1e3c163c create mode 100644 internal/restic/testdata/repo/snapshots/03b061a61be6cc57836effa9a8585468141672577a9310128ae7674d1deed1bd create mode 100644 internal/restic/testdata/source/.config/keld/config.toml create mode 100644 internal/restic/testdata/source/documents/notes/2026/february.md create mode 100644 internal/restic/testdata/source/documents/notes/2026/january.md create mode 100644 internal/restic/testdata/source/documents/notes/todo.txt create mode 100644 internal/restic/testdata/source/documents/tax returns/2025.pdf create mode 100644 "internal/restic/testdata/source/music/Jazz Caf\303\251/live/01 - Take Five.flac" create mode 100644 "internal/restic/testdata/source/music/Jazz Caf\303\251/live/02 - So What.flac" create mode 100644 "internal/restic/testdata/source/music/Jazz Caf\303\251/live/03 - Blue in Green.flac" create mode 100644 "internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/01 - \345\244\234\346\230\216\343\201\221.flac" create mode 100644 "internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/02 - \351\242\250\343\201\256\346\255\214.flac" create mode 100644 "internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/03 - \346\230\237\347\251\272.flac" create mode 100644 "internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-02/01 - \346\265\267\350\276\272.flac" create mode 100644 "internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-02/02 - \346\243\256\343\201\256\344\270\255.flac" create mode 100644 internal/restic/testdata/source/photos/2025/february/IMG_0010.jpg create mode 100644 internal/restic/testdata/source/photos/2025/january/IMG_0001.jpg create mode 100644 internal/restic/testdata/source/photos/2025/january/IMG_0002.jpg create mode 100644 internal/restic/testdata/source/photos/2025/january/IMG_0003.jpg create mode 100644 internal/restic/testdata/source/projects/keld/README.md create mode 100644 internal/restic/testdata/source/projects/keld/cmd/root.go create mode 100644 internal/restic/testdata/source/projects/keld/go.mod create mode 100644 internal/restic/testdata/source/projects/keld/internal/config/config.go create mode 100644 internal/restic/testdata/source/projects/keld/internal/config/resolve.go create mode 100644 internal/restic/testdata/source/projects/keld/internal/restic/exec.go create mode 100644 internal/restic/testdata/source/projects/keld/internal/restic/snapshots.go create mode 100644 internal/restic/testdata/source/projects/web app/package.json create mode 100644 internal/restic/testdata/source/projects/web app/public/index.html create mode 100644 internal/restic/testdata/source/projects/web app/public/style.css create mode 100644 internal/restic/testdata/source/projects/web app/src/components/App.tsx create mode 100644 internal/restic/testdata/source/projects/web app/src/components/Nav.tsx diff --git a/internal/restic/lsnodes.go b/internal/restic/lsnodes.go new file mode 100644 index 0000000000000000000000000000000000000000..9d73717b63d0e4a248041e67e9668f28b535e43a --- /dev/null +++ b/internal/restic/lsnodes.go @@ -0,0 +1,171 @@ +package restic + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "os/exec" + "time" + + "git.secluded.site/keld/internal/config" +) + +// LsNode represents a single file or directory entry from +// `restic ls --json`. Each JSON line with message_type "node" +// produces one LsNode. +type LsNode struct { + Name string `json:"name"` + Type string `json:"type"` // "dir", "file", etc. + Path string `json:"path"` + Size int64 `json:"size,omitempty"` // absent for directories + Mode int `json:"mode"` + Permissions string `json:"permissions"` + Mtime time.Time `json:"mtime"` +} + +// lsLine is the minimal envelope used to distinguish snapshot headers +// from node entries when scanning `restic ls --json` output. +type lsLine struct { + MessageType string `json:"message_type"` +} + +// ParseLsNodes parses the JSON-lines output of `restic ls --json`, +// skipping the snapshot header line (message_type "snapshot") and +// returning all node entries in their original order. +// +// This is a convenience wrapper around parseLsReader for callers that +// already have the complete output in memory (e.g. tests). +func ParseLsNodes(data []byte) ([]LsNode, error) { + return parseLsReader(bytes.NewReader(data)) +} + +// parseLsReader scans JSON lines from r, skipping non-node lines +// (snapshot headers, blank lines), and returns all node entries in +// order. This is the core parser used by both ParseLsNodes and RunLs. +func parseLsReader(r io.Reader) ([]LsNode, error) { + var nodes []LsNode + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Bytes() + if len(bytes.TrimSpace(line)) == 0 { + continue + } + + // Peek at message_type to decide whether to skip. + var envelope lsLine + if err := json.Unmarshal(line, &envelope); err != nil { + return nil, fmt.Errorf("parsing ls JSON line: %w", err) + } + if envelope.MessageType != "node" { + continue + } + + var node LsNode + if err := json.Unmarshal(line, &node); err != nil { + return nil, fmt.Errorf("parsing ls node: %w", err) + } + nodes = append(nodes, node) + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("scanning ls output: %w", err) + } + + return nodes, nil +} + +// RunLs runs `restic ls --json ` using connection details +// from the given resolved config and returns the parsed nodes. +// +// Stdout is stream-parsed rather than buffered entirely into memory, +// so large snapshots don't require holding the full JSON output at +// once. Stderr is captured for error reporting. +// +// The config's environ map is copied before resolving _COMMAND entries +// so the caller's config is not mutated. +func RunLs(cfg *config.ResolvedConfig, snapshotID string) ([]LsNode, error) { + argv, err := buildLsCmd(cfg, snapshotID) + if err != nil { + return nil, err + } + + env := copyEnviron(cfg.Environ) + if err := resolveEnvironCommands(env); err != nil { + return nil, fmt.Errorf("resolving environ for ls: %w", err) + } + + cmd := exec.Command(argv[0], argv[1:]...) //nolint:gosec + cmd.Env = buildEnv(env) + + if cfg.Workdir != "" { + cmd.Dir = cfg.Workdir + } + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("creating stdout pipe for restic ls: %w", err) + } + + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("starting restic ls: %w", err) + } + + nodes, parseErr := parseLsReader(stdout) + + // Always wait for the process to finish, even if parsing failed. + waitErr := cmd.Wait() + + if parseErr != nil { + return nil, fmt.Errorf("parsing restic ls output: %w", parseErr) + } + if waitErr != nil { + return nil, fmt.Errorf("running restic ls: %w\n%s", + waitErr, bytes.TrimSpace(stderr.Bytes())) + } + + return nodes, nil +} + +// snapshotSelectors are flags that affect which snapshot the special ID +// "latest" resolves to. They are accepted by both `restic ls` and +// `restic restore`, so we forward them from the resolved restore config +// to ensure the picker browses the same snapshot the restore will use. +// +// Sourced from `restic ls --help`. +var snapshotSelectors = map[string]bool{ + "--host": true, + "-H": true, + "--path": true, + "--tag": true, +} + +// buildLsCmd constructs the argument vector for running +// `restic ls --json `, extracting global flags and +// snapshot-selection flags from the given resolved config. The snapshot +// ID is always the last argument. Returns an error if no repository +// source is available. +func buildLsCmd(cfg *config.ResolvedConfig, snapshotID string) ([]string, error) { + if !hasRepoSource(cfg) { + return nil, ErrNoRepo + } + + argv := []string{executable(), "ls", "--json"} + + for _, f := range cfg.Flags { + if !globalFlags[f.Name] && !snapshotSelectors[f.Name] { + continue + } + argv = append(argv, f.Name) + if f.Value != "" { + argv = append(argv, f.Value) + } + } + + argv = append(argv, snapshotID) + + return argv, nil +} diff --git a/internal/restic/lsnodes_test.go b/internal/restic/lsnodes_test.go new file mode 100644 index 0000000000000000000000000000000000000000..268a5e754a4ceb57f1e51fc2a725793de6655767 --- /dev/null +++ b/internal/restic/lsnodes_test.go @@ -0,0 +1,411 @@ +package restic + +import ( + "errors" + "os" + "path/filepath" + "testing" + "time" + + "git.secluded.site/keld/internal/config" +) + +// fixtureLsJSON is real output from: +// +// keld --preset music-final@b2 ls --json --recursive 8bd729b1 /home/amolith/Music/Final/3nd +// +// 24 lines: 1 snapshot header + 4 directories + 19 .flac files across 3 album +// directories. The snapshot header line has message_type "snapshot" and must be +// skipped; all remaining lines have message_type "node". +const fixtureLsJSON = `{"time":"2026-03-24T00:20:53.867687549-06:00","parent":"2a1eb755327d446b938ef5a6e991f83f86abf12911f3bc87602fc696a7d1d4af","tree":"011fe38da2e96cb6ea9b2bb8bbc710d5b92ab9f4ea2d91be089bba532d329aae","paths":["/home/amolith/Music/Final"],"hostname":"angmar","username":"amolith","uid":1000,"gid":1000,"program_version":"restic 0.18.1","summary":{"backup_start":"2026-03-24T00:20:53.867687549-06:00","backup_end":"2026-03-24T00:20:58.420948969-06:00","files_new":0,"files_changed":4,"files_unmodified":6010,"dirs_new":0,"dirs_changed":4,"dirs_unmodified":862,"data_blobs":1,"tree_blobs":5,"data_added":89361,"data_added_packed":13072,"total_files_processed":6014,"total_bytes_processed":121289853644},"id":"8bd729b16043cef621423ca2f6100832e8add471374a3a54c555d624b011988c","short_id":"8bd729b1","message_type":"snapshot","struct_type":"snapshot"} +{"name":"3nd","type":"dir","path":"/home/amolith/Music/Final/3nd","uid":1000,"gid":1000,"mode":2147484157,"permissions":"drwxrwxr-x","mtime":"2021-04-17T18:01:57.376612-06:00","atime":"2021-04-17T18:01:57.376612-06:00","ctime":"2024-09-26T19:41:45.573886986-06:00","inode":42207275,"message_type":"node","struct_type":"node"} +{"name":"World Tour","type":"dir","path":"/home/amolith/Music/Final/3nd/World Tour","uid":1000,"gid":1000,"mode":2147484157,"permissions":"drwxrwxr-x","mtime":"2024-06-10T09:00:35.86776-06:00","atime":"2024-06-10T09:00:35.86776-06:00","ctime":"2024-09-26T19:41:45.563886805-06:00","inode":42207421,"message_type":"node","struct_type":"node"} +{"name":"01 - monsoon.flac","type":"file","path":"/home/amolith/Music/Final/3nd/World Tour/01 - monsoon.flac","uid":1000,"gid":1000,"size":30037820,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:20.840315-07:00","atime":"2021-11-25T17:31:20.840315-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42213020,"message_type":"node","struct_type":"node"} +{"name":"02 - china.flac","type":"file","path":"/home/amolith/Music/Final/3nd/World Tour/02 - china.flac","uid":1000,"gid":1000,"size":17705986,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:22.00033-07:00","atime":"2021-11-25T17:31:22.00033-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42213321,"message_type":"node","struct_type":"node"} +{"name":"03 - SSK.flac","type":"file","path":"/home/amolith/Music/Final/3nd/World Tour/03 - SSK.flac","uid":1000,"gid":1000,"size":32263802,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:22.753673-07:00","atime":"2021-11-25T17:31:22.753673-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42213685,"message_type":"node","struct_type":"node"} +{"name":"04 - midroll.flac","type":"file","path":"/home/amolith/Music/Final/3nd/World Tour/04 - midroll.flac","uid":1000,"gid":1000,"size":37431281,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:23.643684-07:00","atime":"2021-11-25T17:31:23.643684-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42213686,"message_type":"node","struct_type":"node"} +{"name":"05 - LOTUS.flac","type":"file","path":"/home/amolith/Music/Final/3nd/World Tour/05 - LOTUS.flac","uid":1000,"gid":1000,"size":33509064,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:24.157024-07:00","atime":"2021-11-25T17:31:24.157024-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42213687,"message_type":"node","struct_type":"node"} +{"name":"06 - 眠る.flac","type":"file","path":"/home/amolith/Music/Final/3nd/World Tour/06 - 眠る.flac","uid":1000,"gid":1000,"size":21319060,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:24.383693-07:00","atime":"2021-11-25T17:31:24.383693-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42213249,"message_type":"node","struct_type":"node"} +{"name":"07 - filter in dust.flac","type":"file","path":"/home/amolith/Music/Final/3nd/World Tour/07 - filter in dust.flac","uid":1000,"gid":1000,"size":21849635,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:24.613696-07:00","atime":"2021-11-25T17:31:24.613696-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42213250,"message_type":"node","struct_type":"node"} +{"name":"08 - 夏終る.flac","type":"file","path":"/home/amolith/Music/Final/3nd/World Tour/08 - 夏終る.flac","uid":1000,"gid":1000,"size":28061640,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:24.817032-07:00","atime":"2021-11-25T17:31:24.817032-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42212532,"message_type":"node","struct_type":"node"} +{"name":"view from here","type":"dir","path":"/home/amolith/Music/Final/3nd/view from here","uid":1000,"gid":1000,"mode":2147484157,"permissions":"drwxrwxr-x","mtime":"2024-06-10T09:00:35.874427-06:00","atime":"2024-06-10T09:00:35.874427-06:00","ctime":"2024-09-26T19:41:45.570553593-06:00","inode":42207422,"message_type":"node","struct_type":"node"} +{"name":"01 - clockworker.flac","type":"file","path":"/home/amolith/Music/Final/3nd/view from here/01 - clockworker.flac","uid":1000,"gid":1000,"size":38342556,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:12.526878-07:00","atime":"2021-11-25T17:31:12.526878-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42214320,"message_type":"node","struct_type":"node"} +{"name":"02 - augustline.flac","type":"file","path":"/home/amolith/Music/Final/3nd/view from here/02 - augustline.flac","uid":1000,"gid":1000,"size":27357365,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:13.413555-07:00","atime":"2021-11-25T17:31:13.413555-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42212371,"message_type":"node","struct_type":"node"} +{"name":"03 - season.flac","type":"file","path":"/home/amolith/Music/Final/3nd/view from here/03 - season.flac","uid":1000,"gid":1000,"size":26918886,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:14.256899-07:00","atime":"2021-11-25T17:31:14.256899-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42212245,"message_type":"node","struct_type":"node"} +{"name":"04 - ハルオト.flac","type":"file","path":"/home/amolith/Music/Final/3nd/view from here/04 - ハルオト.flac","uid":1000,"gid":1000,"size":45722382,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:15.253578-07:00","atime":"2021-11-25T17:31:15.253578-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42212533,"message_type":"node","struct_type":"node"} +{"name":"05 - walk in the brown.flac","type":"file","path":"/home/amolith/Music/Final/3nd/view from here/05 - walk in the brown.flac","uid":1000,"gid":1000,"size":38267170,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:16.300258-07:00","atime":"2021-11-25T17:31:16.300258-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42214321,"message_type":"node","struct_type":"node"} +{"name":"we dance × avec toi","type":"dir","path":"/home/amolith/Music/Final/3nd/we dance × avec toi","uid":1000,"gid":1000,"mode":2147484157,"permissions":"drwxrwxr-x","mtime":"2024-06-10T09:00:35.87776-06:00","atime":"2024-06-10T09:00:35.87776-06:00","ctime":"2024-09-26T19:41:45.573886986-06:00","inode":42207423,"message_type":"node","struct_type":"node"} +{"name":"01 - Bender.flac","type":"file","path":"/home/amolith/Music/Final/3nd/we dance × avec toi/01 - Bender.flac","uid":1000,"gid":1000,"size":24044464,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:17.563608-07:00","atime":"2021-11-25T17:31:17.563608-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42211884,"message_type":"node","struct_type":"node"} +{"name":"02 - テレプシコラー.flac","type":"file","path":"/home/amolith/Music/Final/3nd/we dance × avec toi/02 - テレプシコラー.flac","uid":1000,"gid":1000,"size":29003735,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:18.106948-07:00","atime":"2021-11-25T17:31:18.106948-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42211885,"message_type":"node","struct_type":"node"} +{"name":"03 - コロコロ転がる.flac","type":"file","path":"/home/amolith/Music/Final/3nd/we dance × avec toi/03 - コロコロ転がる.flac","uid":1000,"gid":1000,"size":27127530,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:18.643621-07:00","atime":"2021-11-25T17:31:18.643621-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42212285,"message_type":"node","struct_type":"node"} +{"name":"04 - algorhythm.flac","type":"file","path":"/home/amolith/Music/Final/3nd/we dance × avec toi/04 - algorhythm.flac","uid":1000,"gid":1000,"size":36728278,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:19.070293-07:00","atime":"2021-11-25T17:31:19.070293-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42212286,"message_type":"node","struct_type":"node"} +{"name":"05 - Rain Song.flac","type":"file","path":"/home/amolith/Music/Final/3nd/we dance × avec toi/05 - Rain Song.flac","uid":1000,"gid":1000,"size":27059422,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:19.540299-07:00","atime":"2021-11-25T17:31:19.540299-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42212287,"message_type":"node","struct_type":"node"} +{"name":"06 - untroubled terror.flac","type":"file","path":"/home/amolith/Music/Final/3nd/we dance × avec toi/06 - untroubled terror.flac","uid":1000,"gid":1000,"size":6965876,"mode":509,"permissions":"-rwxrwxr-x","mtime":"2021-11-25T17:31:19.853636-07:00","atime":"2021-11-25T17:31:19.853636-07:00","ctime":"2024-09-26T14:45:44.103763451-06:00","inode":42211886,"message_type":"node","struct_type":"node"} +` + +func TestParseLsNodes(t *testing.T) { + t.Parallel() + + nodes, err := ParseLsNodes([]byte(fixtureLsJSON)) + if err != nil { + t.Fatalf("ParseLsNodes(): unexpected error: %v", err) + } + + // 23 node lines (4 dirs + 19 files), snapshot header skipped. + if got := len(nodes); got != 23 { + t.Fatalf("len(nodes): got %d, want 23", got) + } + + // First node should be the root dir of the listing. + first := nodes[0] + if first.Name != "3nd" { + t.Errorf("first node name: got %q, want %q", first.Name, "3nd") + } + if first.Type != "dir" { + t.Errorf("first node type: got %q, want %q", first.Type, "dir") + } + if first.Path != "/home/amolith/Music/Final/3nd" { + t.Errorf("first node path: got %q, want %q", first.Path, "/home/amolith/Music/Final/3nd") + } + if first.Size != 0 { + t.Errorf("first node size: got %d, want 0 (dirs have no size)", first.Size) + } + if first.Mode != 2147484157 { + t.Errorf("first node mode: got %d, want %d", first.Mode, 2147484157) + } + if first.Permissions != "drwxrwxr-x" { + t.Errorf("first node permissions: got %q, want %q", first.Permissions, "drwxrwxr-x") + } + wantMtime, _ := time.Parse(time.RFC3339Nano, "2021-04-17T18:01:57.376612-06:00") + if !first.Mtime.Equal(wantMtime) { + t.Errorf("first node mtime: got %v, want %v", first.Mtime, wantMtime) + } + + // Spot-check a file node: first .flac in "World Tour". + monsoon := nodes[2] + if monsoon.Name != "01 - monsoon.flac" { + t.Errorf("monsoon name: got %q, want %q", monsoon.Name, "01 - monsoon.flac") + } + if monsoon.Type != "file" { + t.Errorf("monsoon type: got %q, want %q", monsoon.Type, "file") + } + if monsoon.Path != "/home/amolith/Music/Final/3nd/World Tour/01 - monsoon.flac" { + t.Errorf("monsoon path: got %q, want %q", + monsoon.Path, "/home/amolith/Music/Final/3nd/World Tour/01 - monsoon.flac") + } + if monsoon.Size != 30037820 { + t.Errorf("monsoon size: got %d, want %d", monsoon.Size, 30037820) + } + if monsoon.Mode != 509 { + t.Errorf("monsoon mode: got %d, want %d", monsoon.Mode, 509) + } + if monsoon.Permissions != "-rwxrwxr-x" { + t.Errorf("monsoon permissions: got %q, want %q", monsoon.Permissions, "-rwxrwxr-x") + } + + // Spot-check a file with Unicode in the name. + nemuru := nodes[7] + if nemuru.Name != "06 - 眠る.flac" { + t.Errorf("nemuru name: got %q, want %q", nemuru.Name, "06 - 眠る.flac") + } + if nemuru.Size != 21319060 { + t.Errorf("nemuru size: got %d, want %d", nemuru.Size, 21319060) + } + + // Count dirs vs files. + var dirs, files int + for _, n := range nodes { + switch n.Type { + case "dir": + dirs++ + case "file": + files++ + default: + t.Errorf("unexpected node type %q for %q", n.Type, n.Path) + } + } + if dirs != 4 { + t.Errorf("dir count: got %d, want 4", dirs) + } + if files != 19 { + t.Errorf("file count: got %d, want 19", files) + } +} + +func TestParseLsNodesEmpty(t *testing.T) { + t.Parallel() + + nodes, err := ParseLsNodes([]byte("")) + if err != nil { + t.Fatalf("ParseLsNodes(empty): unexpected error: %v", err) + } + if len(nodes) != 0 { + t.Errorf("expected 0 nodes from empty input, got %d", len(nodes)) + } +} + +func TestParseLsNodesSnapshotOnly(t *testing.T) { + t.Parallel() + + // A snapshot header with no node lines — e.g. an empty snapshot or a + // path filter that matched nothing. + input := `{"time":"2026-03-24T00:20:53.867687549-06:00","paths":["/home/amolith/Music/Final"],"hostname":"angmar","id":"8bd729b1","short_id":"8bd729b1","message_type":"snapshot","struct_type":"snapshot"} +` + + nodes, err := ParseLsNodes([]byte(input)) + if err != nil { + t.Fatalf("ParseLsNodes(snapshot-only): unexpected error: %v", err) + } + if len(nodes) != 0 { + t.Errorf("expected 0 nodes from snapshot-only input, got %d", len(nodes)) + } +} + +func TestBuildLsCmd(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cfg *config.ResolvedConfig + snapshot string + wantArgv []string + wantErr bool + }{ + { + name: "basic repo flag", + cfg: &config.ResolvedConfig{ + Command: "restore", + Flags: []config.Flag{ + {Name: "--repo", Value: "/srv/backup"}, + {Name: "--target", Value: "/tmp/restore"}, + }, + }, + snapshot: "8bd729b1", + wantArgv: []string{"restic", "ls", "--json", "--repo", "/srv/backup", "8bd729b1"}, + }, + { + name: "repo with password-file and non-global flags stripped", + cfg: &config.ResolvedConfig{ + Command: "restore", + Flags: []config.Flag{ + {Name: "--repo", Value: "rclone:remote:/backup"}, + {Name: "--password-file", Value: "/etc/restic/pw"}, + {Name: "--target", Value: "/tmp/restore"}, + {Name: "--overwrite", Value: "if-changed"}, + }, + }, + snapshot: "latest", + wantArgv: []string{ + "restic", "ls", "--json", + "--repo", "rclone:remote:/backup", + "--password-file", "/etc/restic/pw", + "latest", + }, + }, + { + name: "cache-dir and no-lock forwarded", + cfg: &config.ResolvedConfig{ + Command: "restore", + Flags: []config.Flag{ + {Name: "--repo", Value: "/srv/backup"}, + {Name: "--cache-dir", Value: "/tmp/cache"}, + {Name: "--no-lock"}, + {Name: "--target", Value: "/tmp/restore"}, + }, + }, + snapshot: "abc123", + wantArgv: []string{ + "restic", "ls", "--json", + "--repo", "/srv/backup", + "--cache-dir", "/tmp/cache", + "--no-lock", + "abc123", + }, + }, + { + name: "snapshot selectors forwarded for latest", + cfg: &config.ResolvedConfig{ + Command: "restore", + Flags: []config.Flag{ + {Name: "--repo", Value: "/srv/backup"}, + {Name: "--host", Value: "angmar"}, + {Name: "--path", Value: "/home/amolith/Music"}, + {Name: "--tag", Value: "daily"}, + {Name: "--target", Value: "/tmp/restore"}, + }, + }, + snapshot: "latest", + wantArgv: []string{ + "restic", "ls", "--json", + "--repo", "/srv/backup", + "--host", "angmar", + "--path", "/home/amolith/Music", + "--tag", "daily", + "latest", + }, + }, + { + name: "short host flag forwarded", + cfg: &config.ResolvedConfig{ + Command: "restore", + Flags: []config.Flag{ + {Name: "--repo", Value: "/srv/backup"}, + {Name: "-H", Value: "angmar"}, + }, + }, + snapshot: "latest", + wantArgv: []string{ + "restic", "ls", "--json", + "--repo", "/srv/backup", + "-H", "angmar", + "latest", + }, + }, + { + name: "repo via environ only", + cfg: &config.ResolvedConfig{ + Command: "restore", + Flags: []config.Flag{ + {Name: "--target", Value: "/tmp/restore"}, + }, + Environ: map[string]string{ + "RESTIC_REPOSITORY": "/srv/backup", + }, + }, + snapshot: "8bd729b1", + wantArgv: []string{"restic", "ls", "--json", "8bd729b1"}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + argv, err := buildLsCmd(tt.cfg, tt.snapshot) + if (err != nil) != tt.wantErr { + t.Fatalf("buildLsCmd(): err=%v, wantErr=%v", err, tt.wantErr) + } + if err != nil { + return + } + + if len(argv) != len(tt.wantArgv) { + t.Fatalf("argv length: got %d %v, want %d %v", + len(argv), argv, len(tt.wantArgv), tt.wantArgv) + } + for i := range argv { + if argv[i] != tt.wantArgv[i] { + t.Errorf("argv[%d]: got %q, want %q", i, argv[i], tt.wantArgv[i]) + } + } + }) + } +} + +func TestBuildLsCmdNoRepo(t *testing.T) { + t.Setenv("RESTIC_REPOSITORY", "") + t.Setenv("RESTIC_REPOSITORY_FILE", "") + + cfg := &config.ResolvedConfig{ + Command: "restore", + Flags: []config.Flag{ + {Name: "--target", Value: "/tmp/restore"}, + }, + } + + _, err := buildLsCmd(cfg, "8bd729b1") + if !errors.Is(err, ErrNoRepo) { + t.Errorf("expected ErrNoRepo, got %v", err) + } +} + +// testdataRepoPath returns the absolute path to the committed test +// restic repository. Tests using this fixture require restic to be +// installed. +func testdataRepoPath(t *testing.T) string { + t.Helper() + // Discover the repo relative to this test file's package dir. + path, err := filepath.Abs("testdata/repo") + if err != nil { + t.Fatalf("resolving testdata/repo: %v", err) + } + if _, err := os.Stat(path); err != nil { + t.Fatalf("testdata/repo not found at %s: %v", path, err) + } + return path +} + +func TestRunLs(t *testing.T) { + t.Parallel() + + repoPath := testdataRepoPath(t) + cfg := &config.ResolvedConfig{ + Command: "restore", + Flags: []config.Flag{ + {Name: "--repo", Value: repoPath}, + }, + Environ: map[string]string{ + "RESTIC_PASSWORD": "test", + }, + } + + nodes, err := RunLs(cfg, "03b061a6") + if err != nil { + t.Fatalf("RunLs(): unexpected error: %v", err) + } + + // The test repo has 29 files + 26 dirs = 55 nodes. + if got := len(nodes); got != 55 { + t.Fatalf("len(nodes): got %d, want 55", got) + } + + // Count types. + var dirs, files int + for _, n := range nodes { + switch n.Type { + case "dir": + dirs++ + case "file": + files++ + default: + t.Errorf("unexpected node type %q for %q", n.Type, n.Path) + } + } + if dirs != 26 { + t.Errorf("dir count: got %d, want 26", dirs) + } + if files != 29 { + t.Errorf("file count: got %d, want 29", files) + } + + // Spot-check a Unicode path. + var found bool + for _, n := range nodes { + if n.Name == "01 - 夜明け.flac" { + found = true + if n.Type != "file" { + t.Errorf("夜明け type: got %q, want %q", n.Type, "file") + } + break + } + } + if !found { + t.Error("expected to find node '01 - 夜明け.flac'") + } + + // Spot-check a space-in-path entry. + found = false + for _, n := range nodes { + if n.Name == "Jazz Café" { + found = true + if n.Type != "dir" { + t.Errorf("Jazz Café type: got %q, want %q", n.Type, "dir") + } + break + } + } + if !found { + t.Error("expected to find node 'Jazz Café'") + } +} diff --git a/internal/restic/testdata/repo/config b/internal/restic/testdata/repo/config new file mode 100644 index 0000000000000000000000000000000000000000..d730be7b96d8d5ae6175348577bfcfb88f4de75d GIT binary patch literal 155 zcmV;M0A&9(XCB78ZG2h~g>W@d95L@->H56mqK;a+lKed8B(GH;^93Cx&=f?jb)14b* zMnuI#Qbqal*IhlU3^o7b!$1Y;=JNF??nurngD@5qmDa`c6@1j-Ysh};j;ha7?{D_b JJ2>YQZ*_JD&}ZfoYPRVtK@ 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 literal 0 HcmV?d00001 diff --git a/internal/restic/testdata/repo/data/b2/b2b71324d483e3599b4e0d6110cce4816fa78b562b35b92340a194588b885f28 b/internal/restic/testdata/repo/data/b2/b2b71324d483e3599b4e0d6110cce4816fa78b562b35b92340a194588b885f28 new file mode 100644 index 0000000000000000000000000000000000000000..b16d4d9f8fa713dc9b8f0892202ae291172c0923 GIT binary patch literal 9885 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 PO>79tVS`;da|8eY)$Agv literal 0 HcmV?d00001 diff --git a/internal/restic/testdata/repo/index/ac37b4026a4655d219044edb4f66fb18f844b63e7e9198f757a04ab400dce843 b/internal/restic/testdata/repo/index/ac37b4026a4655d219044edb4f66fb18f844b63e7e9198f757a04ab400dce843 new file mode 100644 index 0000000000000000000000000000000000000000..2ac7b349fa87c8ba83ea1fd04d94a6282baef198 GIT binary patch literal 2524 zcmV<22_yEt=G7~!+cQUVCGWw$5r*u0F^a)h`*;u@QaD5O1mY?R+*L48AG2OFzP<@K z{Gm&tj5rKLGX2-k#PfRfOmPicKnSQ306^fQ*`+1AP-}QEVKV?x8;z2|dHjkHhg<7> zWmiDpc~`#=AMpt|D67kRoaV9rg@Yiwm$5jys)(2~p6;JcQ5(a=aek&GB46Q(6P<x4 zi{b-M5)v%Qp=nzxGh>!1eA&&ixDd08+9Na**rMG`N0x1U!TLaMhU78mWp@gV7ax{q z{R)Rq{d4rP$!daflqZcyQ?R6y>%$;eYT7BaL6djTc`EwLBIem;5Jg@%2>nG|QbD*- z`)>r3NW0r6w(L;H*Pg}Ap}hi_MF+CpS;$xEE>!||&RJ679&RL7#nN9JVtKJMLy$wf z*$Ym7#K7rD32gp7uOAay9>F_Z$Wfnf4GHyfNK$nqvnH~u^dMs@pCb)WP)=`|1{9L* zbwsMn(ex61vz?jAXY4vqSMg)gAQQ7F$2y3e=dg566pg?JQa2oBD@<@HW3@+#qVncu z?PK4|i;cVG8f)TNUODNz1&`t7h@;qdEkr*K9F}hRWyFu1oD z=?0}|5C#D6jL$qX{R%yJf9``Qq( z`=zy{dpHgggBcAJRk@PY$eC@#`Pdh&>oHfpCztm+iDGdfE5ZiqzDWrtS`g|8e82)t z4z62K$`PC98b$Vg2hK{5&gue66UgU>u&s-3!UK_w$#2EQn6IdER)4Upnk!p^o*_z! znb?fSq%VRlk(>^!JmfYfqC;|sbQrVedS7_Dl{2?uIOV58JPntIMWZs?(;u8H2-~N} z@?aTVn;U{^U#@al_#MiuV%y`;Npjvtv%zVexDh80tr6i2w<`xR%wkxeOS$BIUY z&!ArGQ)T{BXqy*7i@8ubtoyH@9+D*Z`cZ*MtB^RoSw zM9wdirIv4KxoZ7;Af6(+t{)Jd{I!n1iZ70YDOlL0_heCMlw2A{e7f}54|gMOyI?l8 zXKMYEl9Lq-?|yf*0o51G{BUKx;S|)FRvD(yc2NS91W4izFSEv>Mj%n>JH`)*ZdjRw z4PlEP7Lq&ux+$N=DGs(eQ|(80fmJjVFQe?L0P874)D-GS%6NhE3>HK97@`+=j1UroPy0aitEqBGLBgH1OHsAOllAy;jYalL)| zfp}zP9@;Z!X`|-ZATVsF#aUCRMoA=DF&|L93H%BCJ{A!f_~{Q(kKcA(!K%Lu$W&#> zTyYZN2Th9e8A8F+b5dflwk)+Dz#X|Ao6y_Rfs4`|-AJe}u0M9Je8FkUz5Tl~Qk`ZK z<62K(mxIF;>X8p5iB*!;Y!qEUd2kXr+$d*oIgLzmSELehw$oUj_+_$%k< zBnH9BmPs>dP+NYbr=zHQo1srAfyWyWFpAV2d$g;eM26DHrNm<|N@wrk9qH7LaRJRDG_!+S)-yE1 zv#Ne$Nhs>FeTtw9TX+STM`~Af?M9_2CK8bm&w=pSAyy+T72=PG0XF%}l&bZ@0dA%L zKkf(%Vc2ld5*$9Jr4R5`m_1q??=*?JD^ZSY8}--1G7{m-g}~C;e)AOi2^!b@{LJpv z(yzlW==ktnk2p6nzKY-z2%)%WTGts+mP8z6QQzY8N567$CI)vc9>+ZdcDr- zo#D=t%@Ad63;nb1*}6@Ra_1Q@1Pb)Z=n*JC<>d!hpBP_dm{P!}Y&ib?x%B^LiPQ^^F3q$wB2d^ywjTZBKzw^< z3|QvUL>1Y4vv#1yhe9vwsq*F|lU@Ecnz(@wsl)C$vCV*q#Ut>0939saW!Uyw@|fvQ z$G%SieOtpAy^~~dJI>9`_N}TwCakv<-#*>#+2}?(TpX`J)I zXI-ZL4;>sOPkH;rjgfbufZqS~oc>iE3x6aGDn~59p>{em5n6_4@08rKz+?=b^G|e9 m1KhEa%t)Ap!KjZ0fk?oft$}go*RoBUaYORMWgmt8jHSr~uHvi! literal 0 HcmV?d00001 diff --git a/internal/restic/testdata/repo/keys/2f82b6a71d8b04bbf33fa5865e61521b64ec76a343d33b6c6c2dfcbd1e3c163c b/internal/restic/testdata/repo/keys/2f82b6a71d8b04bbf33fa5865e61521b64ec76a343d33b6c6c2dfcbd1e3c163c new file mode 100644 index 0000000000000000000000000000000000000000..490588656a75c07ea3df4b131702a9bdebf1c181 --- /dev/null +++ b/internal/restic/testdata/repo/keys/2f82b6a71d8b04bbf33fa5865e61521b64ec76a343d33b6c6c2dfcbd1e3c163c @@ -0,0 +1 @@ +{"created":"2026-03-25T22:39:06.12977768-06:00","username":"amolith","hostname":"angmar","kdf":"scrypt","N":32768,"r":8,"p":5,"salt":"5kzsKx80K6wVrcqKvyQXAHi3TgY0aZ0fAyw/6Nezt7pFqplrZl2yCul9FzR+660VjAqjjQ1E4pwenUWWeNtmpQ==","data":"oTOqX5z0nwCnKgNmVwUkI57AZviSTh1e6yuS7BXskq/OHkkh4UDA3VrFkpHt6g+CxancYtuoHCqMdiusjPLFCgJRUMnNvlDKPEFeXbTsaRJnairTs1OK5f89+QAPByey+fNvBko5aJyBcfKBnl6l5BuQKWlwdIb1I1gEvARTyC7yEuOQ9CihydCMV8hjMTmkiJNhds2wjpofT+tg0VODmA=="} \ No newline at end of file diff --git a/internal/restic/testdata/repo/snapshots/03b061a61be6cc57836effa9a8585468141672577a9310128ae7674d1deed1bd b/internal/restic/testdata/repo/snapshots/03b061a61be6cc57836effa9a8585468141672577a9310128ae7674d1deed1bd new file mode 100644 index 0000000000000000000000000000000000000000..48e1e2cbfe446de072a8aa3db49d365424844bd6 --- /dev/null +++ b/internal/restic/testdata/repo/snapshots/03b061a61be6cc57836effa9a8585468141672577a9310128ae7674d1deed1bd @@ -0,0 +1,2 @@ +!My:R{0\!cU#*|A0rGТ>E.z^ VmR*)/.x!TRįĈ"\x q +IPȀeHC>ϒݪG6Μzz(c|d1)gsBc}a'(A8`OcF8 TmZ_ulYPFvJVj7BWn/djE5B/l6=YS3o^OQc;7Ty`٧(UHF;6۬-GjbMq5V]vn;~n!b%eώp]+ gN7tIm9;P!~ Vyk`t|GLv)R) \ No newline at end of file diff --git a/internal/restic/testdata/source/.config/keld/config.toml b/internal/restic/testdata/source/.config/keld/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..488f610eb13533d3b9dd692a670336dec1a91316 --- /dev/null +++ b/internal/restic/testdata/source/.config/keld/config.toml @@ -0,0 +1 @@ +[global] diff --git a/internal/restic/testdata/source/documents/notes/2026/february.md b/internal/restic/testdata/source/documents/notes/2026/february.md new file mode 100644 index 0000000000000000000000000000000000000000..708c53dfa0c42967a5357561bf6d2d9c134362c7 --- /dev/null +++ b/internal/restic/testdata/source/documents/notes/2026/february.md @@ -0,0 +1 @@ +project ideas diff --git a/internal/restic/testdata/source/documents/notes/2026/january.md b/internal/restic/testdata/source/documents/notes/2026/january.md new file mode 100644 index 0000000000000000000000000000000000000000..1a5b6bf542892b3ccb05d6a3d033f5a140986c3e --- /dev/null +++ b/internal/restic/testdata/source/documents/notes/2026/january.md @@ -0,0 +1 @@ +meeting notes diff --git a/internal/restic/testdata/source/documents/notes/todo.txt b/internal/restic/testdata/source/documents/notes/todo.txt new file mode 100644 index 0000000000000000000000000000000000000000..5b7d43c4ba7123a39b781072082e7de493e15d37 --- /dev/null +++ b/internal/restic/testdata/source/documents/notes/todo.txt @@ -0,0 +1 @@ +grocery list diff --git a/internal/restic/testdata/source/documents/tax returns/2025.pdf b/internal/restic/testdata/source/documents/tax returns/2025.pdf new file mode 100644 index 0000000000000000000000000000000000000000..904db9984f87e39221a8e72754dad48c2b94638d --- /dev/null +++ b/internal/restic/testdata/source/documents/tax returns/2025.pdf @@ -0,0 +1 @@ +======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== \ No newline at end of file diff --git "a/internal/restic/testdata/source/music/Jazz Caf\303\251/live/01 - Take Five.flac" "b/internal/restic/testdata/source/music/Jazz Caf\303\251/live/01 - Take Five.flac" new file mode 100644 index 0000000000000000000000000000000000000000..fbd1b2f36fd2d78f015cea447a631262b5c0178e --- /dev/null +++ "b/internal/restic/testdata/source/music/Jazz Caf\303\251/live/01 - Take Five.flac" @@ -0,0 +1 @@ +take five diff --git "a/internal/restic/testdata/source/music/Jazz Caf\303\251/live/02 - So What.flac" "b/internal/restic/testdata/source/music/Jazz Caf\303\251/live/02 - So What.flac" new file mode 100644 index 0000000000000000000000000000000000000000..52ee6baa07d9f61cd8995512241751354a38cada --- /dev/null +++ "b/internal/restic/testdata/source/music/Jazz Caf\303\251/live/02 - So What.flac" @@ -0,0 +1 @@ +so what diff --git "a/internal/restic/testdata/source/music/Jazz Caf\303\251/live/03 - Blue in Green.flac" "b/internal/restic/testdata/source/music/Jazz Caf\303\251/live/03 - Blue in Green.flac" new file mode 100644 index 0000000000000000000000000000000000000000..a1cd8f5ae970773e96f69bf1ad7d30c2b1bddb9f --- /dev/null +++ "b/internal/restic/testdata/source/music/Jazz Caf\303\251/live/03 - Blue in Green.flac" @@ -0,0 +1 @@ +blue in green diff --git "a/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/01 - \345\244\234\346\230\216\343\201\221.flac" "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/01 - \345\244\234\346\230\216\343\201\221.flac" new file mode 100644 index 0000000000000000000000000000000000000000..abe334939e2268795b6c80774d288f03424c7e7d --- /dev/null +++ "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/01 - \345\244\234\346\230\216\343\201\221.flac" @@ -0,0 +1 @@ +fake flac data 1 diff --git "a/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/02 - \351\242\250\343\201\256\346\255\214.flac" "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/02 - \351\242\250\343\201\256\346\255\214.flac" new file mode 100644 index 0000000000000000000000000000000000000000..c1a5f8ed9701cd1606fb4fd80d600cc183f7608f --- /dev/null +++ "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/02 - \351\242\250\343\201\256\346\255\214.flac" @@ -0,0 +1 @@ +fake flac data 2 diff --git "a/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/03 - \346\230\237\347\251\272.flac" "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/03 - \346\230\237\347\251\272.flac" new file mode 100644 index 0000000000000000000000000000000000000000..f44399f140225b73a3ffd061ddd14e986ea41aad --- /dev/null +++ "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-01/03 - \346\230\237\347\251\272.flac" @@ -0,0 +1 @@ +fake flac data 3 diff --git "a/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-02/01 - \346\265\267\350\276\272.flac" "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-02/01 - \346\265\267\350\276\272.flac" new file mode 100644 index 0000000000000000000000000000000000000000..14684b85aa23a1b6a54dd82092caf749fbd3deff --- /dev/null +++ "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-02/01 - \346\265\267\350\276\272.flac" @@ -0,0 +1 @@ +fake flac data 4 diff --git "a/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-02/02 - \346\243\256\343\201\256\344\270\255.flac" "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-02/02 - \346\243\256\343\201\256\344\270\255.flac" new file mode 100644 index 0000000000000000000000000000000000000000..c926270e8e8e6645bbc0490607b34b6da581ab2d --- /dev/null +++ "b/internal/restic/testdata/source/music/\343\202\242\343\203\274\343\203\206\343\202\243\343\202\271\343\203\210/album-02/02 - \346\243\256\343\201\256\344\270\255.flac" @@ -0,0 +1 @@ +fake flac data 5 diff --git a/internal/restic/testdata/source/photos/2025/february/IMG_0010.jpg b/internal/restic/testdata/source/photos/2025/february/IMG_0010.jpg new file mode 100644 index 0000000000000000000000000000000000000000..716ef6939b5acf59552a05b3b52ba0a6506af3b7 --- /dev/null +++ b/internal/restic/testdata/source/photos/2025/february/IMG_0010.jpg @@ -0,0 +1 @@ +jpg4 diff --git a/internal/restic/testdata/source/photos/2025/january/IMG_0001.jpg b/internal/restic/testdata/source/photos/2025/january/IMG_0001.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a35fcb82c0450d6cde8096580e7020b93866389b --- /dev/null +++ b/internal/restic/testdata/source/photos/2025/january/IMG_0001.jpg @@ -0,0 +1 @@ +jpg1 diff --git a/internal/restic/testdata/source/photos/2025/january/IMG_0002.jpg b/internal/restic/testdata/source/photos/2025/january/IMG_0002.jpg new file mode 100644 index 0000000000000000000000000000000000000000..99b7d44acd0ba2aad4e51bd1bb740fe01db80277 --- /dev/null +++ b/internal/restic/testdata/source/photos/2025/january/IMG_0002.jpg @@ -0,0 +1 @@ +jpg2 diff --git a/internal/restic/testdata/source/photos/2025/january/IMG_0003.jpg b/internal/restic/testdata/source/photos/2025/january/IMG_0003.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a4ae340b93de1abaa43e2ce451cba668d414dca --- /dev/null +++ b/internal/restic/testdata/source/photos/2025/january/IMG_0003.jpg @@ -0,0 +1 @@ +jpg3 diff --git a/internal/restic/testdata/source/projects/keld/README.md b/internal/restic/testdata/source/projects/keld/README.md new file mode 100644 index 0000000000000000000000000000000000000000..974907ec9aa244cef680f0c9c34f571cfee6c9cc --- /dev/null +++ b/internal/restic/testdata/source/projects/keld/README.md @@ -0,0 +1 @@ +# keld diff --git a/internal/restic/testdata/source/projects/keld/cmd/root.go b/internal/restic/testdata/source/projects/keld/cmd/root.go new file mode 100644 index 0000000000000000000000000000000000000000..06ab7d0f9a35a7d1070711496d6ca1cb892a258f --- /dev/null +++ b/internal/restic/testdata/source/projects/keld/cmd/root.go @@ -0,0 +1 @@ +package main diff --git a/internal/restic/testdata/source/projects/keld/go.mod b/internal/restic/testdata/source/projects/keld/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..daa75d78c187fd8300c668a2b1f1b53ab46d179f --- /dev/null +++ b/internal/restic/testdata/source/projects/keld/go.mod @@ -0,0 +1 @@ +module git.secluded.site/keld diff --git a/internal/restic/testdata/source/projects/keld/internal/config/config.go b/internal/restic/testdata/source/projects/keld/internal/config/config.go new file mode 100644 index 0000000000000000000000000000000000000000..d912156bec00a9f00850ab2ec3a3baf1016c2141 --- /dev/null +++ b/internal/restic/testdata/source/projects/keld/internal/config/config.go @@ -0,0 +1 @@ +package config diff --git a/internal/restic/testdata/source/projects/keld/internal/config/resolve.go b/internal/restic/testdata/source/projects/keld/internal/config/resolve.go new file mode 100644 index 0000000000000000000000000000000000000000..d912156bec00a9f00850ab2ec3a3baf1016c2141 --- /dev/null +++ b/internal/restic/testdata/source/projects/keld/internal/config/resolve.go @@ -0,0 +1 @@ +package config diff --git a/internal/restic/testdata/source/projects/keld/internal/restic/exec.go b/internal/restic/testdata/source/projects/keld/internal/restic/exec.go new file mode 100644 index 0000000000000000000000000000000000000000..43aafe9dad6d25e8c01e008e8b19593571954a7d --- /dev/null +++ b/internal/restic/testdata/source/projects/keld/internal/restic/exec.go @@ -0,0 +1 @@ +package restic diff --git a/internal/restic/testdata/source/projects/keld/internal/restic/snapshots.go b/internal/restic/testdata/source/projects/keld/internal/restic/snapshots.go new file mode 100644 index 0000000000000000000000000000000000000000..43aafe9dad6d25e8c01e008e8b19593571954a7d --- /dev/null +++ b/internal/restic/testdata/source/projects/keld/internal/restic/snapshots.go @@ -0,0 +1 @@ +package restic diff --git a/internal/restic/testdata/source/projects/web app/package.json b/internal/restic/testdata/source/projects/web app/package.json new file mode 100644 index 0000000000000000000000000000000000000000..5268a59e5092845b678dd7bc22e15636c9ed2a1c --- /dev/null +++ b/internal/restic/testdata/source/projects/web app/package.json @@ -0,0 +1 @@ +{"name":"app"} diff --git a/internal/restic/testdata/source/projects/web app/public/index.html b/internal/restic/testdata/source/projects/web app/public/index.html new file mode 100644 index 0000000000000000000000000000000000000000..1a189798ecbcf58d1f6be3999484b65829561fdd --- /dev/null +++ b/internal/restic/testdata/source/projects/web app/public/index.html @@ -0,0 +1 @@ + diff --git a/internal/restic/testdata/source/projects/web app/public/style.css b/internal/restic/testdata/source/projects/web app/public/style.css new file mode 100644 index 0000000000000000000000000000000000000000..208d16d4213b91be6d840400703325d41ca9cb5e --- /dev/null +++ b/internal/restic/testdata/source/projects/web app/public/style.css @@ -0,0 +1 @@ +body {} diff --git a/internal/restic/testdata/source/projects/web app/src/components/App.tsx b/internal/restic/testdata/source/projects/web app/src/components/App.tsx new file mode 100644 index 0000000000000000000000000000000000000000..93ad8ac773f4b3a8cd4b8442f090a46725849d56 --- /dev/null +++ b/internal/restic/testdata/source/projects/web app/src/components/App.tsx @@ -0,0 +1 @@ +export default App diff --git a/internal/restic/testdata/source/projects/web app/src/components/Nav.tsx b/internal/restic/testdata/source/projects/web app/src/components/Nav.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d1a9f95648d29c6c45f297e67ce9a794144ced05 --- /dev/null +++ b/internal/restic/testdata/source/projects/web app/src/components/Nav.tsx @@ -0,0 +1 @@ +export default Nav