Make add idempotent: skip already-tracked files instead of erroring.
Enables glob workflows like `sgard add ~/.config/mcp/services/*` to pick up new files without failing on ones already tracked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,9 @@ ARCHITECTURE.md for design details.
|
|||||||
|
|
||||||
## Current Status
|
## Current Status
|
||||||
|
|
||||||
**Phase:** Phase 5 complete. File exclusion feature added.
|
**Phase:** Phase 5 complete. File exclusion feature added. Add is now idempotent.
|
||||||
|
|
||||||
**Last updated:** 2026-03-27
|
**Last updated:** 2026-03-30
|
||||||
|
|
||||||
## Completed Steps
|
## Completed Steps
|
||||||
|
|
||||||
@@ -114,3 +114,4 @@ Phase 6: Manifest Signing (to be planned).
|
|||||||
| 2026-03-26 | — | `sgard list` remote support: uses `resolveRemoteConfig()` to list server manifest via `PullManifest` RPC. Client `List()` method added. |
|
| 2026-03-26 | — | `sgard list` remote support: uses `resolveRemoteConfig()` to list server manifest via `PullManifest` RPC. Client `List()` method added. |
|
||||||
| 2026-03-26 | — | Version derived from git tags via `VERSION` file. flake.nix reads `VERSION`; Makefile `version` target syncs from latest tag, `build` injects via ldflags. |
|
| 2026-03-26 | — | Version derived from git tags via `VERSION` file. flake.nix reads `VERSION`; Makefile `version` target syncs from latest tag, `build` injects via ldflags. |
|
||||||
| 2026-03-27 | — | File exclusion: `sgard exclude`/`include` commands, `Manifest.Exclude` field, Add/MirrorUp/MirrorDown respect exclusions, directory exclusion support. 8 tests. |
|
| 2026-03-27 | — | File exclusion: `sgard exclude`/`include` commands, `Manifest.Exclude` field, Add/MirrorUp/MirrorDown respect exclusions, directory exclusion support. 8 tests. |
|
||||||
|
| 2026-03-30 | — | Idempotent add: `sgard add` silently skips already-tracked files/directories instead of erroring, enabling glob-based workflows. |
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ func (g *Garden) Add(paths []string, opts ...AddOptions) error {
|
|||||||
// Track the directory itself as a structural entry.
|
// Track the directory itself as a structural entry.
|
||||||
tilded := toTildePath(abs)
|
tilded := toTildePath(abs)
|
||||||
if g.findEntry(tilded) != nil {
|
if g.findEntry(tilded) != nil {
|
||||||
return fmt.Errorf("already tracking %s", tilded)
|
continue
|
||||||
}
|
}
|
||||||
entry := manifest.Entry{
|
entry := manifest.Entry{
|
||||||
Path: tilded,
|
Path: tilded,
|
||||||
@@ -277,7 +277,7 @@ func (g *Garden) Add(paths []string, opts ...AddOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := g.addEntry(abs, info, now, false, o); err != nil {
|
if err := g.addEntry(abs, info, now, true, o); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func TestAddSymlink(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddDuplicateRejected(t *testing.T) {
|
func TestAddDuplicateIsIdempotent(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
repoDir := filepath.Join(root, "repo")
|
repoDir := filepath.Join(root, "repo")
|
||||||
|
|
||||||
@@ -218,8 +218,19 @@ func TestAddDuplicateRejected(t *testing.T) {
|
|||||||
t.Fatalf("first Add: %v", err)
|
t.Fatalf("first Add: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.Add([]string{testFile}); err == nil {
|
if err := g.Add([]string{testFile}); err != nil {
|
||||||
t.Fatal("second Add of same path should fail")
|
t.Fatalf("second Add of same path should be idempotent: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := g.List()
|
||||||
|
count := 0
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.Path == toTildePath(testFile) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
t.Fatalf("expected 1 entry, got %d", count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user