From 70f008a21a7aea368371b40422a98600228148b8 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Thu, 8 Oct 2026 04:59:30 +0200 Subject: [PATCH] Document that --json skips the confirmation prompt (closes #268) snapshot remove and prune delete without asking under --json, because a prompt on stdout would break the JSON document. Their --json help and README entries named only --force as skipping the prompt. Both now say --json skips it too. One test checks the --json help of both commands; another runs prune with --json, no --force and empty stdin, and checks that the unreferenced blob is deleted and stdout holds only the JSON document. Model: opus-5-5 --- README.md | 6 +++-- TODO.md | 8 ++++++ internal/cli/json_help_test.go | 24 +++++++++++++++++ internal/cli/prune.go | 3 ++- internal/cli/snapshot.go | 3 ++- internal/vaultik/prune_json_test.go | 40 +++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 internal/cli/json_help_test.go create mode 100644 internal/vaultik/prune_json_test.go diff --git a/README.md b/README.md index 78eaea7..5d2188b 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,8 @@ on the destination in one go, use `vaultik remote nuke --force`. * `--local-only`: Skip remote cleanup; only touch the local index * `--dry-run`: Show what would be deleted without deleting * `--force`: Skip confirmation prompt -* `--json`: Output result as JSON +* `--json`: Output result as JSON. Also skips the confirmation prompt, as + `--force` does. **`snapshot restore`**: Restore files from a backup snapshot. * Requires `VAULTIK_AGE_SECRET_KEY` environment variable @@ -383,7 +384,8 @@ manifests — network cost scales with the number of snapshots. `snapshot create --prune` runs the same cleanup automatically; this is the manual entry point for the same work. * `--force`: Skip confirmation prompt -* `--json`: Output stats as JSON +* `--json`: Output stats as JSON. Also skips the confirmation prompt, as + `--force` does. **`info`**: Display system configuration, storage settings, encryption recipients, and local database statistics. diff --git a/TODO.md b/TODO.md index 9d6ca17..906798c 100644 --- a/TODO.md +++ b/TODO.md @@ -30,6 +30,14 @@ the tag exists and is exercised; what is left is merging `next` to snapshots.id`. When the local index already has a snapshot with the ID, the create now waits a second and takes a new timestamp. +- 2026-10-07: Documented that `--json` skips the confirmation prompt of + `snapshot remove` and `prune` + ([issue #268](https://git.eeqj.de/sneak/vaultik/issues/268)). Both + commands delete without asking under `--json`, since a prompt on stdout + would break the JSON document, but the help and the README described + only `--force` as skipping it. The `--json` help of both commands and + their README entries now say so. + - 2026-10-07: Made a symlink whose target cannot be read stop the backup ([issue #269](https://git.eeqj.de/sneak/vaultik/issues/269)). It was left out of the snapshot with only a debug log line, even without diff --git a/internal/cli/json_help_test.go b/internal/cli/json_help_test.go new file mode 100644 index 0000000..3d25048 --- /dev/null +++ b/internal/cli/json_help_test.go @@ -0,0 +1,24 @@ +package cli //nolint:testpackage // exercises the unexported command constructor + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +// TestJSONHelpSaysConfirmationPromptIsSkipped checks that the --json help +// of `snapshot remove` and `prune` says the flag skips the confirmation +// prompt. Both delete without asking under --json, since a prompt on +// stdout would break the JSON document. +func TestJSONHelpSaysConfirmationPromptIsSkipped(t *testing.T) { + t.Parallel() + + for _, cmd := range []*cobra.Command{ + newSnapshotRemoveCommand(), + NewPruneCommand(), + } { + assert.Contains(t, cmd.Flags().Lookup("json").Usage, + "skips the confirmation prompt", cmd.Name()) + } +} diff --git a/internal/cli/prune.go b/internal/cli/prune.go index e73d45d..707617b 100644 --- a/internal/cli/prune.go +++ b/internal/cli/prune.go @@ -57,7 +57,8 @@ referenced.`, } cmd.Flags().BoolVar(&opts.Force, "force", false, "Skip confirmation prompt") - cmd.Flags().BoolVar(&opts.JSON, "json", false, "Output pruning stats as JSON") + cmd.Flags().BoolVar(&opts.JSON, "json", false, + "Output pruning stats as JSON; skips the confirmation prompt, as --force does") return cmd } diff --git a/internal/cli/snapshot.go b/internal/cli/snapshot.go index a9c6e5a..58b32db 100644 --- a/internal/cli/snapshot.go +++ b/internal/cli/snapshot.go @@ -280,7 +280,8 @@ nuke --force' — it is the single supported entry point for that.`, cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Skip confirmation prompt") cmd.Flags().BoolVar(&opts.DryRun, "dry-run", false, "Show what would be removed without removing") - cmd.Flags().BoolVar(&opts.JSON, "json", false, "Output result as JSON") + cmd.Flags().BoolVar(&opts.JSON, "json", false, + "Output result as JSON; skips the confirmation prompt, as --force does") cmd.Flags().BoolVar(&opts.LocalOnly, "local-only", false, "Skip remote cleanup; only touch the local index") diff --git a/internal/vaultik/prune_json_test.go b/internal/vaultik/prune_json_test.go new file mode 100644 index 0000000..96f6d01 --- /dev/null +++ b/internal/vaultik/prune_json_test.go @@ -0,0 +1,40 @@ +package vaultik_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sneak.berlin/go/vaultik/internal/log" + "sneak.berlin/go/vaultik/internal/vaultik" +) + +// TestPruneBlobs_JSONDeletesWithoutAsking checks that prune with --json +// and without --force deletes an unreferenced blob without the +// confirmation prompt. Stdin is empty, so a prompt would read no answer +// and cancel, and its text would come before the JSON document. +func TestPruneBlobs_JSONDeletesWithoutAsking(t *testing.T) { + log.Initialize(log.Config{}) + t.Parallel() + + env := newListEnv(t) + + // The store holds no manifest, so nothing references this blob. + addBlob(t, env.store.testStorer, testBlobHashA) + + err := env.v.PruneBlobs(&vaultik.PruneOptions{JSON: true}) + require.NoError(t, err) + + blobKey := "blobs/" + testBlobHashA[:2] + "/" + testBlobHashA[2:4] + + "/" + testBlobHashA + assert.False(t, env.store.hasKey(blobKey), + "the unreferenced blob must be deleted") + + var result vaultik.PruneBlobsResult + + require.NoError(t, json.Unmarshal(env.stdout.Bytes(), &result), + "stdout must hold only the JSON document, got:\n%s", + env.stdout.String()) + assert.Equal(t, 1, result.BlobsDeleted) +}