Step 7: Add remove command to stop tracking files.
Implements Garden.Remove() which unregisters paths from the manifest, plus unit tests and the CLI wiring via cobra. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
31
cmd/sgard/remove.go
Normal file
31
cmd/sgard/remove.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kisom/sgard/garden"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var removeCmd = &cobra.Command{
|
||||||
|
Use: "remove <path>...",
|
||||||
|
Short: "Stop tracking files",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
g, err := garden.Open(repoFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.Remove(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Removed %d path(s)\n", len(args))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(removeCmd)
|
||||||
|
}
|
||||||
38
garden/remove.go
Normal file
38
garden/remove.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package garden
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove stops tracking the given paths. Each path is resolved to absolute
|
||||||
|
// form, converted to a tilde path, and removed from the manifest. An error
|
||||||
|
// is returned if any path is not currently tracked.
|
||||||
|
func (g *Garden) Remove(paths []string) error {
|
||||||
|
for _, p := range paths {
|
||||||
|
abs, err := filepath.Abs(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolving path %s: %w", p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tilded := toTildePath(abs)
|
||||||
|
|
||||||
|
if g.findEntry(tilded) == nil {
|
||||||
|
return fmt.Errorf("not tracking %s", tilded)
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := g.manifest.Files[:0]
|
||||||
|
for _, e := range g.manifest.Files {
|
||||||
|
if e.Path != tilded {
|
||||||
|
filtered = append(filtered, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.manifest.Files = filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.manifest.Save(g.manifestPath); err != nil {
|
||||||
|
return fmt.Errorf("saving manifest: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
65
garden/remove_test.go
Normal file
65
garden/remove_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package garden
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveTrackedFile(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
repoDir := filepath.Join(root, "repo")
|
||||||
|
|
||||||
|
g, err := Init(repoDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Init: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and add a file.
|
||||||
|
testFile := filepath.Join(root, "testfile")
|
||||||
|
if err := os.WriteFile(testFile, []byte("hello\n"), 0o644); err != nil {
|
||||||
|
t.Fatalf("writing test file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.Add([]string{testFile}); err != nil {
|
||||||
|
t.Fatalf("Add: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.manifest.Files) != 1 {
|
||||||
|
t.Fatalf("expected 1 file after add, got %d", len(g.manifest.Files))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove it.
|
||||||
|
if err := g.Remove([]string{testFile}); err != nil {
|
||||||
|
t.Fatalf("Remove: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.manifest.Files) != 0 {
|
||||||
|
t.Errorf("expected 0 files after remove, got %d", len(g.manifest.Files))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the manifest was persisted.
|
||||||
|
g2, err := Open(repoDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("re-Open: %v", err)
|
||||||
|
}
|
||||||
|
if len(g2.manifest.Files) != 0 {
|
||||||
|
t.Errorf("persisted manifest has %d files, want 0", len(g2.manifest.Files))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveUntrackedPathErrors(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
repoDir := filepath.Join(root, "repo")
|
||||||
|
|
||||||
|
g, err := Init(repoDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Init: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try removing a path that was never added.
|
||||||
|
bogus := filepath.Join(root, "nonexistent")
|
||||||
|
if err := g.Remove([]string{bogus}); err == nil {
|
||||||
|
t.Fatal("Remove of untracked path should return an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user