summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-02 10:56:42 +0200
committerPaul Buetow <paul@buetow.org>2026-03-02 10:56:42 +0200
commitb636b12566ea6c2862bf3adc6686c57e0d785ca3 (patch)
tree5c94a13179bf4a6bba5616f8bbd74ec8d61e4d0c
parent88f6ca2fb24973b78afe76f82ea86171e40fccff (diff)
store: make Data carry injected deps (task 402)
-rw-r--r--internal/cli/cli.go12
-rw-r--r--internal/cli/cli_test.go11
-rw-r--r--internal/store/data.go24
-rw-r--r--internal/store/data_test.go29
-rw-r--r--internal/store/store.go14
-rw-r--r--internal/store/store_test.go16
6 files changed, 54 insertions, 52 deletions
diff --git a/internal/cli/cli.go b/internal/cli/cli.go
index b6d47fc..7b18c34 100644
--- a/internal/cli/cli.go
+++ b/internal/cli/cli.go
@@ -55,7 +55,6 @@ type CLI struct {
g *git.Git
clip *clipboard.Clipboard
sh *shell.Shell
- cipher *crypto.Cipher
lastResult string // most recent search result description
}
@@ -101,11 +100,10 @@ func newCLI(ctx context.Context) (*CLI, error) {
clip := clipboard.New(cfg.GnomeClipboardCmd, cfg.MacOSClipboardCmd)
c := &CLI{
- cfg: &cfg,
- st: st,
- g: g,
- clip: clip,
- cipher: ciph,
+ cfg: &cfg,
+ st: st,
+ g: g,
+ clip: clip,
}
// Create the shell with a completion function that references the CLI.
@@ -577,7 +575,7 @@ func (c *CLI) makeActionFn(ctx context.Context, action store.Action) func(contex
if err := externalEdit(ctx, c.cfg.ExportDir, c.cfg.EditCmd, exportName); err != nil {
return err
}
- return d.ReimportAfterExport(ctx, c.cipher, c.g)
+ return d.ReimportAfterExport(ctx)
}
default:
diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go
index 39c79aa..9ff0510 100644
--- a/internal/cli/cli_test.go
+++ b/internal/cli/cli_test.go
@@ -62,12 +62,11 @@ func testCLI(t *testing.T) (*CLI, *config.Config) {
t.Cleanup(func() { sh.Close() })
return &CLI{
- cfg: cfg,
- st: st,
- g: g,
- clip: clipboard.New("", ""),
- sh: sh,
- cipher: ciph,
+ cfg: cfg,
+ st: st,
+ g: g,
+ clip: clipboard.New("", ""),
+ sh: sh,
}, cfg
}
diff --git a/internal/store/data.go b/internal/store/data.go
index 25e64b1..8ab8429 100644
--- a/internal/store/data.go
+++ b/internal/store/data.go
@@ -17,11 +17,13 @@ type Data struct {
Content []byte
DataPath string // absolute path to .data file
ExportedPath string // set by Export(), used by ReimportAfterExport()
+ encryptor Encryptor
+ committer Committer
}
// loadData decrypts a .data file and returns a Data struct with Content populated.
// absoluteDataPath must be the full filesystem path to the encrypted .data file.
-func loadData(ctx context.Context, absoluteDataPath string, encryptor Encryptor) (*Data, error) {
+func loadData(ctx context.Context, absoluteDataPath string, encryptor Encryptor, committer Committer) (*Data, error) {
ciphertext, err := os.ReadFile(absoluteDataPath)
if err != nil {
return nil, fmt.Errorf("reading data file %q: %w", absoluteDataPath, err)
@@ -36,8 +38,10 @@ func loadData(ctx context.Context, absoluteDataPath string, encryptor Encryptor)
}
return &Data{
- Content: plain,
- DataPath: absoluteDataPath,
+ Content: plain,
+ DataPath: absoluteDataPath,
+ encryptor: encryptor,
+ committer: committer,
}, nil
}
@@ -69,21 +73,21 @@ func (d *Data) Export(ctx context.Context, exportDir, destinationFile string) er
// ReimportAfterExport reads the (possibly edited) file from ExportedPath back
// into Content and then commits it. This is used by the edit workflow: export →
// user edits in external editor → reimport.
-func (d *Data) ReimportAfterExport(ctx context.Context, encryptor Encryptor, committer Committer) error {
+func (d *Data) ReimportAfterExport(ctx context.Context) error {
content, err := os.ReadFile(d.ExportedPath)
if err != nil {
return fmt.Errorf("reading exported file %q: %w", d.ExportedPath, err)
}
d.Content = content
- return d.Commit(ctx, encryptor, committer, true)
+ return d.Commit(ctx, true)
}
// Commit encrypts Content and writes it to DataPath, then stages the file with git.
// If force is false and the file already exists, the commit is silently skipped
// (matching the Ruby CommitFile#commit_content behaviour that avoids overwrites
// without explicit force).
-func (d *Data) Commit(ctx context.Context, encryptor Encryptor, committer Committer, force bool) error {
+func (d *Data) Commit(ctx context.Context, force bool) error {
if !force {
if _, err := os.Stat(d.DataPath); err == nil {
// File already exists; skip without error to preserve existing data.
@@ -96,10 +100,10 @@ func (d *Data) Commit(ctx context.Context, encryptor Encryptor, committer Commit
return fmt.Errorf("creating data directory for %q: %w", d.DataPath, err)
}
- if encryptor == nil {
+ if d.encryptor == nil {
return fmt.Errorf("encrypting data for %q: missing encryptor", d.DataPath)
}
- ciphertext, err := encryptor.Encrypt(d.Content)
+ ciphertext, err := d.encryptor.Encrypt(d.Content)
if err != nil {
return fmt.Errorf("encrypting data for %q: %w", d.DataPath, err)
}
@@ -108,10 +112,10 @@ func (d *Data) Commit(ctx context.Context, encryptor Encryptor, committer Commit
return fmt.Errorf("writing data file %q: %w", d.DataPath, err)
}
- if committer == nil {
+ if d.committer == nil {
return fmt.Errorf("git add data %q: missing committer", d.DataPath)
}
- if err := committer.Add(ctx, d.DataPath); err != nil {
+ if err := d.committer.Add(ctx, d.DataPath); err != nil {
return fmt.Errorf("git add data %q: %w", d.DataPath, err)
}
diff --git a/internal/store/data_test.go b/internal/store/data_test.go
index 7bffd71..1c05a42 100644
--- a/internal/store/data_test.go
+++ b/internal/store/data_test.go
@@ -93,7 +93,7 @@ func TestDataCommitAndLoad(t *testing.T) {
t.Fatalf("WriteFile: %v", err)
}
- loaded, err := loadData(ctx, dataPath, c)
+ loaded, err := loadData(ctx, dataPath, c, nil)
if err != nil {
t.Fatalf("loadData: %v", err)
}
@@ -157,7 +157,7 @@ func TestLoadDataMissingFile(t *testing.T) {
ctx := context.Background()
c := newTestCipher(t)
- _, err := loadData(ctx, "/nonexistent/path/to.data", c)
+ _, err := loadData(ctx, "/nonexistent/path/to.data", c, nil)
if err == nil {
t.Error("loadData with missing file: expected error, got nil")
}
@@ -178,7 +178,7 @@ func TestLoadDataCorrupted(t *testing.T) {
t.Fatalf("writing bad file: %v", err)
}
- _, err := loadData(ctx, badPath, c)
+ _, err := loadData(ctx, badPath, c, nil)
if err == nil {
t.Error("loadData with corrupted file: expected error, got nil")
}
@@ -221,16 +221,14 @@ func TestDataCommitSkipsExisting(t *testing.T) {
}
d := &Data{
- Content: []byte("new content that should NOT overwrite"),
- DataPath: dataPath,
+ Content: []byte("new content that should NOT overwrite"),
+ DataPath: dataPath,
+ encryptor: c,
}
- // Commit with force=false must not overwrite; it also tries git.Add which
- // we can't test without a real repo, so we only check the file is untouched.
- // Since Commit calls git.Add on success, and that will fail without a repo,
- // we skip the git.Add path by checking the file is unchanged after the skip.
- // The function prints a warning and returns nil when force=false and file exists.
- err := d.Commit(ctx, c, nil, false) // nil git — should not be reached when skipping
+ // Commit with force=false must not overwrite and should return before
+ // encrypting or touching git dependencies.
+ err := d.Commit(ctx, false)
if err != nil {
t.Errorf("Commit(force=false) with existing file returned error: %v", err)
}
@@ -250,7 +248,7 @@ func TestDataCommitMissingEncryptor(t *testing.T) {
DataPath: filepath.Join(dir, "entry.data"),
}
- err := d.Commit(ctx, nil, nil, true)
+ err := d.Commit(ctx, true)
if err == nil {
t.Fatal("Commit with nil encryptor: expected error, got nil")
}
@@ -264,11 +262,12 @@ func TestDataCommitMissingCommitter(t *testing.T) {
c := newTestCipher(t)
dir := t.TempDir()
d := &Data{
- Content: []byte("content"),
- DataPath: filepath.Join(dir, "entry.data"),
+ Content: []byte("content"),
+ DataPath: filepath.Join(dir, "entry.data"),
+ encryptor: c,
}
- err := d.Commit(ctx, c, nil, true)
+ err := d.Commit(ctx, true)
if err == nil {
t.Fatal("Commit with nil committer: expected error, got nil")
}
diff --git a/internal/store/store.go b/internal/store/store.go
index b92f011..8d251b3 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -194,7 +194,7 @@ func (s *Store) applyAction(ctx context.Context, idx *Index, action Action, acti
// ActionPaste, ActionOpen, ActionEdit — require external tools;
// delegate to the caller-supplied callback.
if actionFn != nil {
- d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher)
+ d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher, s.git)
if err != nil {
return err
}
@@ -211,7 +211,7 @@ func (s *Store) actionCat(ctx context.Context, idx *Index) error {
fmt.Println("Not displaying/pasting binary data!")
return nil
}
- d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher)
+ d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher, s.git)
if err != nil {
return err
}
@@ -223,7 +223,7 @@ func (s *Store) actionCat(ctx context.Context, idx *Index) error {
// When fullPath is true the full description is used as the destination path;
// when false only the basename is used (matching Ruby's :export vs :pathexport).
func (s *Store) actionExport(ctx context.Context, idx *Index, fullPath bool) error {
- d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher)
+ d, err := loadData(ctx, filepath.Join(s.cfg.DataDir, idx.DataFile), s.cipher, s.git)
if err != nil {
return err
}
@@ -431,7 +431,7 @@ func (s *Store) Add(ctx context.Context, description, data string) error {
idx, dataObj := s.buildPair(description, hash)
dataObj.Content = []byte(data)
- if err := dataObj.Commit(ctx, s.cipher, s.git, false); err != nil {
+ if err := dataObj.Commit(ctx, false); err != nil {
return fmt.Errorf("committing data for %q: %w", description, err)
}
if err := idx.CommitIndex(ctx, s.cipher, s.git, false); err != nil {
@@ -456,7 +456,7 @@ func (s *Store) Import(ctx context.Context, srcPath, destPath string, force bool
idx, dataObj := s.buildPair(destPath, hash)
dataObj.Content = content
- if err := dataObj.Commit(ctx, s.cipher, s.git, force); err != nil {
+ if err := dataObj.Commit(ctx, force); err != nil {
return fmt.Errorf("committing data for %q: %w", destPath, err)
}
if err := idx.CommitIndex(ctx, s.cipher, s.git, force); err != nil {
@@ -594,7 +594,9 @@ func (s *Store) buildPair(description, hash string) (*Index, *Data) {
Hash: hashBase,
}
dataObj := &Data{
- DataPath: dataPath,
+ DataPath: dataPath,
+ encryptor: s.cipher,
+ committer: s.git,
}
return idx, dataObj
}
diff --git a/internal/store/store_test.go b/internal/store/store_test.go
index 270fe00..86b7d0d 100644
--- a/internal/store/store_test.go
+++ b/internal/store/store_test.go
@@ -139,7 +139,7 @@ func TestAddAndSearch(t *testing.T) {
}
dataPath := filepath.Join(cfg.DataDir, idx.DataFile)
- d, err := loadData(ctx, dataPath, c)
+ d, err := loadData(ctx, dataPath, c, nil)
if err != nil {
t.Fatalf("loadData: %v", err)
}
@@ -221,7 +221,7 @@ func TestImport(t *testing.T) {
}
dataPath := filepath.Join(cfg.DataDir, found[0].DataFile)
- d, err := loadData(ctx, dataPath, c)
+ d, err := loadData(ctx, dataPath, c, nil)
if err != nil {
t.Fatalf("loadData: %v", err)
}
@@ -259,7 +259,7 @@ func TestExport(t *testing.T) {
}
dataPath := filepath.Join(cfg.DataDir, idx.DataFile)
- d, err := loadData(ctx, dataPath, c)
+ d, err := loadData(ctx, dataPath, c, nil)
if err != nil {
t.Fatalf("loadData: %v", err)
}
@@ -595,7 +595,7 @@ func TestImportRecursive(t *testing.T) {
}())
continue
}
- d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c)
+ d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c, nil)
if err != nil {
t.Fatalf("loadData for %q: %v", desc, err)
}
@@ -624,7 +624,7 @@ func TestReimportAfterExport(t *testing.T) {
t.Fatalf("WalkIndexes: %v", err)
}
- d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c)
+ d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c, g)
if err != nil {
t.Fatalf("loadData: %v", err)
}
@@ -641,12 +641,12 @@ func TestReimportAfterExport(t *testing.T) {
}
// Reimport overwrites the encrypted .data with the updated content.
- if err := d.ReimportAfterExport(ctx, c, g); err != nil {
+ if err := d.ReimportAfterExport(ctx); err != nil {
t.Fatalf("ReimportAfterExport: %v", err)
}
// Reload from disk and verify the update was persisted.
- reloaded, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c)
+ reloaded, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c, nil)
if err != nil {
t.Fatalf("loadData after reimport: %v", err)
}
@@ -1029,7 +1029,7 @@ func TestImportForceOverwrite(t *testing.T) {
if err := store.WalkIndexes(ctx, "", func(i *Index) error { idx = i; return nil }); err != nil {
t.Fatalf("WalkIndexes: %v", err)
}
- d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c)
+ d, err := loadData(ctx, filepath.Join(cfg.DataDir, idx.DataFile), c, nil)
if err != nil {
t.Fatalf("loadData: %v", err)
}