Author SHA1 Message Date
sneak c7aee94566 Move documents to docs/ and the example config to configs/ (closes #411)
check / check (push) Failing after 18m51s
ARCHITECTURE.md and TODO.md move to docs/, config.example.yml to
configs/, as REPO_POLICIES.md asks of the root. Links, the
no-snapshots error message and the example-config test follow them.

Model: opus-5-5
2026-11-10 20:02:10 +00:00
sneak 514e4f9f8b Run make fmt over the Markdown
Only prettier's output; no text change.

Model: opus-5-5
2026-11-10 20:02:10 +00:00
sneak 7a3cd581b2 Format Markdown with prettier in script/fmt and script/fmt-check
script/fmt and script/fmt-check now run prettier over all Markdown
after the Go step. The prettier config, package.json, yarn.lock and the
node and yarn steps of script/bootstrap are copied from sneak/prompts
with the same pinned versions.

script/bootstrap also installs curl when missing: script/install-go
needs it, so a host with only docker and git stopped there.

Model: opus-5-5
2026-11-10 20:02:10 +00:00
15 changed files with 47 additions and 314 deletions

No files matched your search

+3 -4
View File
@@ -32,8 +32,8 @@ mkdir /Volumes/usbstick/mybackup
# (System Settings → Privacy & Security → Full Disk Access), otherwise
# the backup will abort with a permission error on protected directories
# run your first backup (the default config backs up ~, and /Applications
# on macOS only, with sensible excludes)
# run your first backup (the default config backs up ~ and /Applications
# with sensible excludes)
vaultik snapshot create
# see what you have
@@ -831,8 +831,7 @@ Work planned after 1.0. Loosely ordered by priority.
Every command's user-facing output is governed by `internal/ui`, in one of two
ways. Color is enabled when the stream written to is a TTY and the `NO_COLOR`
environment variable is unset (https://no-color.org/). The same rule governs the
colorized log lines `internal/log` writes to a terminal.
environment variable is unset (https://no-color.org/).
- **Status, progress, warnings, and errors** go through the `internal/ui`
message methods below: marker-prefixed, colored on a TTY, and — except
-10
View File
@@ -28,16 +28,6 @@ runs the release build in `.gitea/workflows/release.yml`.
([issue #411](https://git.eeqj.de/sneak/vaultik/issues/411)). The architecture
document and this file moved to `docs/`, and the example config to `configs/`.
- 2026-11-10: Printed the status and progress lines of `remote info` and
`snapshot verify` through `internal/ui`, with its marker and color, as the
README describes ([issue #410](https://git.eeqj.de/sneak/vaultik/issues/410)).
Their reports stay plain.
- 2026-11-10: Made `config init` write the `apps` snapshot, which backs up
`/Applications`, only on macOS
([issue #408](https://git.eeqj.de/sneak/vaultik/issues/408)). On Linux the
first `snapshot create` stopped on the missing directory.
- 2026-10-15: Stopped `snapshot create --prune` pruning after a backup in which
a volume mounted below a configured path in an earlier snapshot of the same
name was not mounted
+1 -1
View File
@@ -90,7 +90,7 @@ func setupGlobals(
// blank line. Used both from the fx hook (for subcommand invocations) and
// from the root cobra Run handler (for `vaultik` with no subcommand).
func writeStartupBanner(w *ui.Writer, startTime time.Time, shortCommit string) {
w.Bannerf("%s %s by %s (commit %s, commit date %s) starting up at %s.",
w.Bannerf("%s %s by %s (commit %s, built on %s) starting up at %s.",
globals.Appname, globals.Version, globals.Author,
shortCommit, globals.CommitDate,
startTime.Format(time.RFC3339))
+4 -27
View File
@@ -8,7 +8,6 @@ import (
"os/exec"
"path/filepath"
"reflect"
"runtime"
"slices"
"strconv"
"strings"
@@ -31,7 +30,7 @@ const configSetArgs = 2
// parent config dirs (e.g. ~/.config) are conventionally traversable.
const configDirMode = 0o755
// configYAMLIndent matches the 2-space indentation of the default config,
// configYAMLIndent matches the 2-space indentation of defaultConfigTemplate,
// so `config set` writes the file back with the same indentation rather than
// yaml.Marshal's 4-space default.
const configYAMLIndent = 2
@@ -51,22 +50,7 @@ var (
"so the file was not changed")
)
// macOS is the value of runtime.GOOS on macOS.
const macOS = "darwin"
// defaultConfigText returns the file `config init` writes on the operating
// system goos, named as runtime.GOOS names it. Only macOS gets the apps
// snapshot, since /Applications exists nowhere else.
func defaultConfigText(goos string) string {
if goos == macOS {
return defaultConfigHead + appsSnapshotConfig + defaultConfigTail
}
return defaultConfigHead + defaultConfigTail
}
// defaultConfigHead is the default config up to the end of the home snapshot.
const defaultConfigHead = `# vaultik configuration
const defaultConfigTemplate = `# vaultik configuration
# Documentation: https://sneak.berlin/go/vaultik
# ─── REQUIRED ────────────────────────────────────────────────────────────────
@@ -184,10 +168,7 @@ snapshots:
- "/Movies/ProxyMedia"
- "/Music/iTunes/Album Artwork"
- "/Pictures/iPod Photo Cache"
`
// appsSnapshotConfig is the apps snapshot of the default config.
const appsSnapshotConfig = `
# Third-party applications. OS-provided apps live in /System/Applications
# on modern macOS and are never in /Applications, but Apple-installed
# App Store apps (Safari, GarageBand, iWork, iMovie) are excluded since
@@ -208,10 +189,7 @@ const appsSnapshotConfig = `
- "/Steam.app"
- "/VirtualBox.app"
- "/Utilities/Adobe Installers"
`
// defaultConfigTail is the default config after the snapshots.
const defaultConfigTail = `
# Storage backend (pick ONE of the three forms below).
#
# S3-compatible:
@@ -333,8 +311,7 @@ on macOS, ~/.config/ on Linux, /etc/vaultik/ as root).`,
return fmt.Errorf("creating config directory %s: %w", dir, err)
}
err = os.WriteFile(path, []byte(defaultConfigText(runtime.GOOS)),
configFileMode)
err = os.WriteFile(path, []byte(defaultConfigTemplate), configFileMode)
if err != nil {
return fmt.Errorf("writing config file: %w", err)
}
@@ -583,7 +560,7 @@ func yamlPathOnly(root *yaml.Node, keys []string) (*yaml.Node, error) {
}
// marshalConfigYAML renders a config document tree with 2-space indentation,
// matching the default config. yaml.Marshal defaults to 4 spaces, which
// matching defaultConfigTemplate. yaml.Marshal defaults to 4 spaces, which
// would reindent the whole file on the first `config set` despite the promise
// to preserve formatting.
func marshalConfigYAML(root *yaml.Node) ([]byte, error) {
+7 -41
View File
@@ -13,15 +13,14 @@ import (
"sneak.berlin/go/vaultik/internal/ui"
)
// TestDefaultConfigParsesOnMacOS ensures the config `config init` writes on
// macOS is valid YAML that unmarshals into the Config struct with the
// expected snapshots.
func TestDefaultConfigParsesOnMacOS(t *testing.T) {
// TestDefaultConfigTemplateParses ensures the init template is valid YAML
// that unmarshals into the Config struct with the expected snapshots.
func TestDefaultConfigTemplateParses(t *testing.T) {
t.Parallel()
var cfg config.Config
err := yaml.Unmarshal([]byte(defaultConfigText("darwin")), &cfg)
err := yaml.Unmarshal([]byte(defaultConfigTemplate), &cfg)
if err != nil {
t.Fatalf("default config template is not valid YAML: %v", err)
}
@@ -59,39 +58,6 @@ func TestDefaultConfigParsesOnMacOS(t *testing.T) {
}
}
// TestDefaultConfigOnLinux checks that the config `config init` writes on
// Linux loads with its home snapshot and without the apps snapshot, whose
// /Applications exists only on macOS.
func TestDefaultConfigOnLinux(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), "config.yml")
err := os.WriteFile(path, []byte(defaultConfigText("linux")), configFileMode)
if err != nil {
t.Fatalf("write config: %v", err)
}
err = writeConfigSet(ui.NewWithColor(&bytes.Buffer{}, false),
path, "storage_url", "file:///mnt/backups")
if err != nil {
t.Fatalf("config set storage_url: %v", err)
}
cfg, err := config.Load(path)
if err != nil {
t.Fatalf("config.Load: %v", err)
}
if _, ok := cfg.Snapshots["home"]; !ok {
t.Error("expected 'home' snapshot in the Linux default config")
}
if _, ok := cfg.Snapshots["apps"]; ok {
t.Error("the Linux default config should have no 'apps' snapshot")
}
}
// testRecipient is a valid age public key.
const testRecipient = "age1278m9q7dp3chsh2dcy82qk27v047zywyvtxwnj4cvt0z65jw6a7q5dqhfj"
@@ -105,7 +71,7 @@ func TestConfigSetRecipientOnFreshConfig(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.yml")
err := os.WriteFile(path, []byte(defaultConfigText(macOS)), configFileMode)
err := os.WriteFile(path, []byte(defaultConfigTemplate), configFileMode)
if err != nil {
t.Fatalf("write config: %v", err)
}
@@ -254,7 +220,7 @@ func TestConfigSetKeepsFileThatWouldNotLoad(t *testing.T) {
{levelReference, "blob_size_limit", "1MB",
"blob_size_limit must be at least"},
// The type check on a config that does not load yet.
{defaultConfigText(macOS), "snapshots.apps.paths", "/srv",
{defaultConfigTemplate, "snapshots.apps.paths", "/srv",
"into []string"},
// The type check on a config that cannot be read here.
{configWithUnsetReference, "snapshots.home.paths", "/x",
@@ -414,7 +380,7 @@ func loadAfterConfigSet(t *testing.T, key, value string) *config.Config {
path := filepath.Join(t.TempDir(), "config.yml")
err := os.WriteFile(path, []byte(defaultConfigText(macOS)), configFileMode)
err := os.WriteFile(path, []byte(defaultConfigTemplate), configFileMode)
if err != nil {
t.Fatalf("write config: %v", err)
}
-30
View File
@@ -13,7 +13,6 @@ import (
"github.com/adrg/xdg"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sneak.berlin/go/vaultik/internal/globals"
)
// Command words and flags used to build argument vectors below. They are
@@ -113,35 +112,6 @@ func TestBannerStillPrintedWithoutSuppressingFlag(t *testing.T) {
}
}
// TestBannerLabelsCommitDate checks that the banner prints the commit
// and the commit date, each after its own label. Commit and CommitDate
// get different values, so printing the wrong one fails.
//
//nolint:paralleltest // sets globals.Commit and globals.CommitDate
func TestBannerLabelsCommitDate(t *testing.T) {
const (
commit = "abc1234"
commitDate = "2026-11-09"
)
previousCommit, previousCommitDate := globals.Commit, globals.CommitDate
t.Cleanup(func() {
globals.Commit = previousCommit
globals.CommitDate = previousCommitDate
})
globals.Commit = commit
globals.CommitDate = commitDate
var banner bytes.Buffer
emitStartupBanner(nil, &banner)
assert.Contains(t, banner.String(),
"(commit "+commit+", commit date "+commitDate+")")
}
// TestBannerSuppressedInArgs covers the suppression scan directly,
// including the flags that suppressed the banner before --json joined
// them, so that adding --json cannot regress them.
+7 -7
View File
@@ -37,13 +37,13 @@ func writeVersion(out *ui.Writer) {
w := out.Out()
_, _ = fmt.Fprintf(w, "vaultik %s\n", globals.Version)
_, _ = fmt.Fprintf(w, " commit: %s\n", globals.Commit)
_, _ = fmt.Fprintf(w, " commit date: %s\n", globals.CommitDate)
_, _ = fmt.Fprintf(w, " go: %s\n", runtime.Version())
_, _ = fmt.Fprintf(w, " os/arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
_, _ = fmt.Fprintf(w, " author: %s\n", globals.Author)
_, _ = fmt.Fprintf(w, " homepage: %s\n", globals.Homepage)
_, _ = fmt.Fprintf(w, " license: %s\n", globals.License)
_, _ = fmt.Fprintf(w, " commit: %s\n", globals.Commit)
_, _ = fmt.Fprintf(w, " build date: %s\n", globals.CommitDate)
_, _ = fmt.Fprintf(w, " go: %s\n", runtime.Version())
_, _ = fmt.Fprintf(w, " os/arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
_, _ = fmt.Fprintf(w, " author: %s\n", globals.Author)
_, _ = fmt.Fprintf(w, " homepage: %s\n", globals.Homepage)
_, _ = fmt.Fprintf(w, " license: %s\n", globals.License)
if globals.IsDevVersion(globals.Version) {
_, _ = fmt.Fprintln(w)
-37
View File
@@ -2,7 +2,6 @@ package cli_test
import (
"bytes"
"runtime"
"strings"
"testing"
@@ -50,42 +49,6 @@ func TestVersionCommandReportsBuildVersion(t *testing.T) {
}
}
// TestVersionCommandReportLines checks the label, padding and value of
// every line of the report. Commit and CommitDate get different values,
// so a line that prints the wrong one fails.
//
//nolint:paralleltest // sets globals.Commit and globals.CommitDate
func TestVersionCommandReportLines(t *testing.T) {
const (
commit = "abc1234"
commitDate = "2026-11-09"
)
previousCommit, previousCommitDate := globals.Commit, globals.CommitDate
t.Cleanup(func() {
globals.Commit = previousCommit
globals.CommitDate = previousCommitDate
})
globals.Commit = commit
globals.CommitDate = commitDate
want := "vaultik " + globals.Version + "\n" +
" commit: " + commit + "\n" +
" commit date: " + commitDate + "\n" +
" go: " + runtime.Version() + "\n" +
" os/arch: " + runtime.GOOS + "/" + runtime.GOARCH + "\n" +
" author: " + globals.Author + "\n" +
" homepage: " + globals.Homepage + "\n" +
" license: " + globals.License + "\n"
out := runVersionCommand(t)
if !strings.HasPrefix(out, want) {
t.Errorf("report:\n%s\nwant it to start with:\n%s", out, want)
}
}
// TestVersionCommandFlagsDevelopmentBuild is the regression test for
// the thing this command exists to prevent: a build that is not a
// release must say so. The notice used to be gated on the version
+11 -32
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"io"
"log/slog"
"os"
"strconv"
"strings"
"sync"
@@ -61,12 +60,6 @@ type TTYHandler struct {
mu *sync.Mutex
out io.Writer
// color reports whether records are written with ANSI color. It is
// false when NO_COLOR is set, matching internal/ui's shouldColor
// test; the caller already chose this handler for a terminal, so
// TTY-ness is not re-checked here.
color bool
// attrs are the attributes accumulated through WithAttrs, emitted
// ahead of each record's own attributes. Their keys already carry
// the group path that was open when they were added, so no
@@ -79,19 +72,16 @@ type TTYHandler struct {
groups []string
}
// NewTTYHandler creates a new TTY handler. Color is enabled unless the
// NO_COLOR environment variable is set to a non-empty value, the same
// test internal/ui applies to status lines.
// NewTTYHandler creates a new TTY handler with colored output.
func NewTTYHandler(out io.Writer, opts *slog.HandlerOptions) *TTYHandler {
if opts == nil {
opts = &slog.HandlerOptions{}
}
return &TTYHandler{
out: out,
opts: *opts,
mu: &sync.Mutex{},
color: os.Getenv("NO_COLOR") == "",
out: out,
opts: *opts,
mu: &sync.Mutex{},
}
}
@@ -134,10 +124,10 @@ func (h *TTYHandler) Handle(_ context.Context, r slog.Record) error {
// are written around it: it can carry text from an untrusted source
// (a storage error, for one), and a raw control character would
// otherwise reach the terminal.
_, _ = fmt.Fprintf(h.out, "%s %s %s",
h.paint(colorGray, timestamp),
h.paint(levelColor, level),
h.paint(colorBold, sanitize(r.Message)))
_, _ = fmt.Fprintf(h.out, "%s%s%s %s%s%s %s%s%s",
colorGray, timestamp, colorReset,
levelColor, level, colorReset,
colorBold, sanitize(r.Message), colorReset)
// Attributes carried by the handler come first, then the record's
// own. Handler attributes were qualified when they were added; the
@@ -246,7 +236,6 @@ func (h *TTYHandler) clone() *TTYHandler {
opts: h.opts,
mu: h.mu,
out: h.out,
color: h.color,
attrs: make([]slog.Attr, len(h.attrs), len(h.attrs)+1),
groups: make([]string, len(h.groups), len(h.groups)+1),
}
@@ -284,19 +273,9 @@ func (h *TTYHandler) writeAttr(a slog.Attr) {
// the snapshot database — so a control character in one of them must
// be rendered as an escape sequence rather than reaching the terminal,
// where it could move the cursor or inject its own colours.
_, _ = fmt.Fprintf(h.out, " %s=%s",
h.paint(colorCyan, sanitize(a.Key)),
h.paint(colorBlue, sanitize(value)))
}
// paint wraps s in the given ANSI color when color is enabled, and
// returns s unchanged otherwise.
func (h *TTYHandler) paint(color, s string) string {
if !h.color {
return s
}
return color + s + colorReset
_, _ = fmt.Fprintf(h.out, " %s%s%s=%s%s%s",
colorCyan, sanitize(a.Key), colorReset,
colorBlue, sanitize(value), colorReset)
}
// sanitize returns s unchanged when every rune in it is printable, and a
-41
View File
@@ -421,44 +421,3 @@ func TestTTYHandlerEmptyGroupAndAttrsAreNoOps(t *testing.T) {
assert.Contains(t, plain, "kept=yes")
assert.NotContains(t, plain, " =")
}
// TestTTYHandlerRespectsNoColor checks that only a non-empty NO_COLOR
// turns color off, matching internal/ui's shouldColor test. The handler
// is only ever chosen for a terminal, so this is the one remaining
// condition that can still leave color in log output.
func TestTTYHandlerRespectsNoColor(t *testing.T) {
cases := []struct {
name string
noColor string
colored bool
}{
{
name: "NO_COLOR turns color off",
noColor: "1",
},
{
name: "empty NO_COLOR keeps color on",
noColor: "",
colored: true,
},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
t.Setenv("NO_COLOR", testCase.noColor)
var buf bytes.Buffer
logger := slog.New(log.NewTTYHandler(&buf, debugHandlerOptions()))
logger.Info("hello", "key", "value")
if testCase.colored {
assert.Contains(t, buf.String(), "\x1b[",
"an empty NO_COLOR must leave ANSI color in log output")
} else {
assert.NotContains(t, buf.String(), "\x1b[",
"NO_COLOR must suppress every ANSI escape in log output")
}
})
}
}
+3 -3
View File
@@ -246,7 +246,7 @@ func (v *Vaultik) RemoteInfo(jsonOutput bool) error {
v.stdoutf("Type: %s\n", storageInfo.Type)
v.stdoutf("Location: %s\n", storageInfo.Location)
v.printlnStdout()
v.UI.Beginf("Scanning snapshot metadata...")
v.stdoutf("Scanning snapshot metadata...\n")
}
snapshotMetadata, snapshotIDs, skippedManifestCount, err := v.collectSnapshotMetadata()
@@ -265,7 +265,7 @@ func (v *Vaultik) RemoteInfo(jsonOutput bool) error {
}
}
v.UI.Beginf("Downloading %d manifest(s)...", manifestCount)
v.stdoutf("Downloading %d manifest(s)...\n", manifestCount)
}
referencedBlobs, unreadableManifests := v.collectReferencedBlobsFromManifests(
@@ -447,7 +447,7 @@ func (v *Vaultik) scanRemoteBlobStorage(
result *RemoteInfoResult, referencedBlobs map[string]int64, showText bool,
) error {
if showText {
v.UI.Beginf("Scanning blobs...")
v.stdoutf("Scanning blobs...\n")
}
// Use prune's listing, so a file under blobs/ that prune would never
-22
View File
@@ -12,7 +12,6 @@ import (
"github.com/stretchr/testify/require"
"sneak.berlin/go/vaultik/internal/log"
"sneak.berlin/go/vaultik/internal/snapshot"
"sneak.berlin/go/vaultik/internal/ui"
)
// testBlobHashB is a blob that the manifest written by addRemote does
@@ -260,24 +259,3 @@ func TestRemoteInfo_DirectoryWithoutManifestLeavesOrphansKnown(t *testing.T) {
assert.NotContains(t, doc, "unreadable_manifests")
assert.NotContains(t, doc, "skipped_manifest_count")
}
// TestRemoteInfo_StatusLineUsesUIMarker checks that the remote info
// status lines go through internal/ui: marker-prefixed, and silenced by
// --quiet.
func TestRemoteInfo_StatusLineUsesUIMarker(t *testing.T) {
log.Initialize(log.Config{})
t.Parallel()
env := newListEnv(t)
env.addRemote(t, listRemoteID, time.Date(2026, 3, 2, 0, 0, 0, 0, time.UTC))
const statusLine = ui.Marker + " Scanning snapshot metadata...\n"
require.NoError(t, env.v.RemoteInfo(false))
assert.Contains(t, env.stdout.String(), statusLine)
env.stdout.Reset()
env.v.UI.SetQuiet(true)
require.NoError(t, env.v.RemoteInfo(false))
assert.NotContains(t, env.stdout.String(), "Scanning snapshot metadata")
}
+1 -1
View File
@@ -1019,7 +1019,7 @@ func (v *Vaultik) VerifySnapshotWithOptions(
v.printlnStdout()
// Check each blob is present with the size the manifest records.
v.UI.Beginf("Checking blob presence and sizes...")
v.stdoutf("Checking blob presence and sizes...\n")
}
v.checkSnapshotDatabase(remoteKey, result)
+10 -10
View File
@@ -190,7 +190,7 @@ func (v *Vaultik) loadVerificationData(
log.Info("Downloading manifest", "remote_key", remoteKey)
if !opts.JSON && !v.UI.Quiet() {
v.UI.Beginf("Downloading manifest...")
v.stdoutf("Downloading manifest...\n")
}
manifest, err := v.downloadManifestByKey(remoteKey)
@@ -205,9 +205,9 @@ func (v *Vaultik) loadVerificationData(
"manifest_total_size", ubytes(manifest.TotalCompressedSize))
if !opts.JSON && !v.UI.Quiet() {
v.UI.Completef("Manifest loaded: %d blobs (%s)",
v.stdoutf("Manifest loaded: %d blobs (%s)\n",
manifest.BlobCount, ubytes(manifest.TotalCompressedSize))
v.UI.Beginf("Downloading and decrypting database...")
v.stdoutf("Downloading and decrypting database...\n")
}
tdb, err := v.downloadVerifiedSnapshotDB(
@@ -235,7 +235,7 @@ func (v *Vaultik) loadVerificationData(
"db_total_size", ubytes(dbTotalSize))
if !opts.JSON && !v.UI.Quiet() {
v.UI.Completef("Database loaded: %d blobs (%s)",
v.stdoutf("Database loaded: %d blobs (%s)\n",
len(dbBlobs), ubytes(dbTotalSize))
}
@@ -295,7 +295,7 @@ func (v *Vaultik) runVerificationSteps(
identities []age.Identity,
) error {
if !opts.JSON && !v.UI.Quiet() {
v.UI.Beginf("Verifying manifest against database...")
v.stdoutf("Verifying manifest against database...\n")
}
err := v.verifyManifestAgainstDatabase(manifest, dbBlobs)
@@ -304,8 +304,8 @@ func (v *Vaultik) runVerificationSteps(
}
if !opts.JSON && !v.UI.Quiet() {
v.UI.Completef("Manifest verified.")
v.UI.Beginf("Checking blob existence in remote storage...")
v.stdoutf("Manifest verified.\n")
v.stdoutf("Checking blob existence in remote storage...\n")
}
err = v.verifyBlobExistenceFromDB(dbBlobs, result)
@@ -314,8 +314,8 @@ func (v *Vaultik) runVerificationSteps(
}
if !opts.JSON && !v.UI.Quiet() {
v.UI.Completef("All blobs exist.")
v.UI.Beginf("Downloading and verifying blob contents (%d blobs, %s)...",
v.stdoutf("All blobs exist.\n")
v.stdoutf("Downloading and verifying blob contents (%d blobs, %s)...\n",
len(dbBlobs), ubytes(totalSize))
}
@@ -790,7 +790,7 @@ func (v *Vaultik) performDeepVerificationFromDB(
)
if !opts.JSON && !v.UI.Quiet() {
v.UI.Progressf("Verified %d/%d blobs (%d remaining) - %s/%s - elapsed %s, eta %s",
v.stdoutf(" Verified %d/%d blobs (%d remaining) - %s/%s - elapsed %s, eta %s\n",
i+1, len(blobs), remaining,
ubytes(bytesProcessed),
ubytes(totalBytesExpected),
-48
View File
@@ -100,51 +100,3 @@ func TestVerify_QuietSuppressesReport(t *testing.T) {
require.Empty(t, stdout.String(),
"a failed verify must write no report under --quiet")
}
// TestVerify_DeepStatusLineUsesUIMarker checks that the deep verify
// status lines go through internal/ui: marker-prefixed, and silenced by
// --quiet.
func TestVerify_DeepStatusLineUsesUIMarker(t *testing.T) {
log.Initialize(log.Config{})
t.Parallel()
fs := afero.NewOsFs()
tempDir := t.TempDir()
dataDir := filepath.Join(tempDir, "source")
storeDir := t.TempDir()
dbPath := filepath.Join(tempDir, "index.sqlite")
require.NoError(t, fs.MkdirAll(dataDir, 0o755))
require.NoError(t, afero.WriteFile(fs,
filepath.Join(dataDir, "data.bin"),
bytesPattern("marker-", 64*1024), 0o644))
ctx := context.Background()
cfg, storer, snapshotID := runFileStorageBackup(
ctx, t, fs, dataDir, storeDir, dbPath, 32*1024, 128*1024)
var stdout bytes.Buffer
v := &vaultik.Vaultik{
Config: cfg,
Storage: storer,
Fs: fs,
Stdout: &stdout,
Stderr: io.Discard,
UI: ui.NewWithColor(&stdout, false),
}
v.SetContext(ctx)
opts := &vaultik.VerifyOptions{Deep: true}
require.NoError(t, v.VerifySnapshotWithOptions(snapshotID, opts))
require.Contains(t, stdout.String(),
"\n"+ui.Marker+" Downloading manifest...\n")
stdout.Reset()
v.UI.SetQuiet(true)
require.NoError(t, v.VerifySnapshotWithOptions(snapshotID, opts))
require.NotContains(t, stdout.String(), "Downloading manifest")
}