From 34f9598a19a69b657011b784f2796d778f885ff3 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Mon, 23 Mar 2026 21:51:01 -0700 Subject: [PATCH] Add list command to display all tracked entries Adds Garden.List() method that returns manifest entries, unit tests for empty and populated repos, and a CLI command that formats output by entry type (file with hash prefix, link with target, directory). Co-Authored-By: Claude Opus 4.6 (1M context) --- cmd/sgard/list.go | 41 +++++++++++++++++++++++++++ garden/list.go | 8 ++++++ garden/list_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 cmd/sgard/list.go create mode 100644 garden/list.go create mode 100644 garden/list_test.go diff --git a/cmd/sgard/list.go b/cmd/sgard/list.go new file mode 100644 index 0000000..d692936 --- /dev/null +++ b/cmd/sgard/list.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + + "github.com/kisom/sgard/garden" + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List all tracked files", + RunE: func(cmd *cobra.Command, args []string) error { + g, err := garden.Open(repoFlag) + if err != nil { + return err + } + + entries := g.List() + for _, e := range entries { + switch e.Type { + case "file": + hash := e.Hash + if len(hash) > 8 { + hash = hash[:8] + } + fmt.Printf("%-6s %s\t%s\n", "file", e.Path, hash) + case "link": + fmt.Printf("%-6s %s\t-> %s\n", "link", e.Path, e.Target) + case "directory": + fmt.Printf("%-6s %s\n", "dir", e.Path) + } + } + + return nil + }, +} + +func init() { + rootCmd.AddCommand(listCmd) +} diff --git a/garden/list.go b/garden/list.go new file mode 100644 index 0000000..b04768c --- /dev/null +++ b/garden/list.go @@ -0,0 +1,8 @@ +package garden + +import "github.com/kisom/sgard/manifest" + +// List returns all tracked entries from the manifest. +func (g *Garden) List() []manifest.Entry { + return g.manifest.Files +} diff --git a/garden/list_test.go b/garden/list_test.go new file mode 100644 index 0000000..30555c5 --- /dev/null +++ b/garden/list_test.go @@ -0,0 +1,67 @@ +package garden + +import ( + "os" + "path/filepath" + "testing" +) + +func TestListEmpty(t *testing.T) { + root := t.TempDir() + repoDir := filepath.Join(root, "repo") + + g, err := Init(repoDir) + if err != nil { + t.Fatalf("Init: %v", err) + } + + entries := g.List() + if len(entries) != 0 { + t.Errorf("expected 0 entries, got %d", len(entries)) + } +} + +func TestListAfterAdd(t *testing.T) { + root := t.TempDir() + repoDir := filepath.Join(root, "repo") + + g, err := Init(repoDir) + if err != nil { + t.Fatalf("Init: %v", err) + } + + // Create two files and add them. + file1 := filepath.Join(root, "file1") + if err := os.WriteFile(file1, []byte("one"), 0o644); err != nil { + t.Fatalf("writing file1: %v", err) + } + file2 := filepath.Join(root, "file2") + if err := os.WriteFile(file2, []byte("two"), 0o644); err != nil { + t.Fatalf("writing file2: %v", err) + } + + if err := g.Add([]string{file1, file2}); err != nil { + t.Fatalf("Add: %v", err) + } + + entries := g.List() + if len(entries) != 2 { + t.Fatalf("expected 2 entries, got %d", len(entries)) + } + + // Verify entries have the correct paths (in tilde form). + paths := make(map[string]bool) + for _, e := range entries { + paths[e.Path] = true + } + + want1 := toTildePath(file1) + want2 := toTildePath(file2) + + if !paths[want1] { + t.Errorf("missing entry for %s", want1) + } + if !paths[want2] { + t.Errorf("missing entry for %s", want2) + } +}