Paths added to the manifest's exclude list are skipped during Add, MirrorUp, and MirrorDown directory walks. Excluding a directory excludes everything under it. Already-tracked entries matching a new exclusion are removed from the manifest. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
332 lines
8.7 KiB
Go
332 lines
8.7 KiB
Go
package garden
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestExcludeAddsToManifest(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
secretFile := filepath.Join(root, "secret.key")
|
|
if err := os.WriteFile(secretFile, []byte("secret"), 0o600); err != nil {
|
|
t.Fatalf("writing secret: %v", err)
|
|
}
|
|
|
|
if err := g.Exclude([]string{secretFile}); err != nil {
|
|
t.Fatalf("Exclude: %v", err)
|
|
}
|
|
|
|
if len(g.manifest.Exclude) != 1 {
|
|
t.Fatalf("expected 1 exclusion, got %d", len(g.manifest.Exclude))
|
|
}
|
|
|
|
expected := toTildePath(secretFile)
|
|
if g.manifest.Exclude[0] != expected {
|
|
t.Errorf("exclude[0] = %q, want %q", g.manifest.Exclude[0], expected)
|
|
}
|
|
|
|
// Verify persistence.
|
|
g2, err := Open(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("re-Open: %v", err)
|
|
}
|
|
if len(g2.manifest.Exclude) != 1 {
|
|
t.Errorf("persisted excludes = %d, want 1", len(g2.manifest.Exclude))
|
|
}
|
|
}
|
|
|
|
func TestExcludeDeduplicates(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
secretFile := filepath.Join(root, "secret.key")
|
|
if err := os.WriteFile(secretFile, []byte("secret"), 0o600); err != nil {
|
|
t.Fatalf("writing secret: %v", err)
|
|
}
|
|
|
|
if err := g.Exclude([]string{secretFile}); err != nil {
|
|
t.Fatalf("first Exclude: %v", err)
|
|
}
|
|
if err := g.Exclude([]string{secretFile}); err != nil {
|
|
t.Fatalf("second Exclude: %v", err)
|
|
}
|
|
|
|
if len(g.manifest.Exclude) != 1 {
|
|
t.Errorf("expected 1 exclusion after dedup, got %d", len(g.manifest.Exclude))
|
|
}
|
|
}
|
|
|
|
func TestExcludeRemovesTrackedEntry(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
secretFile := filepath.Join(root, "secret.key")
|
|
if err := os.WriteFile(secretFile, []byte("secret"), 0o600); err != nil {
|
|
t.Fatalf("writing secret: %v", err)
|
|
}
|
|
|
|
// Add the file first.
|
|
if err := g.Add([]string{secretFile}); err != nil {
|
|
t.Fatalf("Add: %v", err)
|
|
}
|
|
if len(g.manifest.Files) != 1 {
|
|
t.Fatalf("expected 1 file, got %d", len(g.manifest.Files))
|
|
}
|
|
|
|
// Now exclude it — should remove from tracked files.
|
|
if err := g.Exclude([]string{secretFile}); err != nil {
|
|
t.Fatalf("Exclude: %v", err)
|
|
}
|
|
|
|
if len(g.manifest.Files) != 0 {
|
|
t.Errorf("expected 0 files after exclude, got %d", len(g.manifest.Files))
|
|
}
|
|
}
|
|
|
|
func TestIncludeRemovesFromExcludeList(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
secretFile := filepath.Join(root, "secret.key")
|
|
if err := os.WriteFile(secretFile, []byte("secret"), 0o600); err != nil {
|
|
t.Fatalf("writing secret: %v", err)
|
|
}
|
|
|
|
if err := g.Exclude([]string{secretFile}); err != nil {
|
|
t.Fatalf("Exclude: %v", err)
|
|
}
|
|
if len(g.manifest.Exclude) != 1 {
|
|
t.Fatalf("expected 1 exclusion, got %d", len(g.manifest.Exclude))
|
|
}
|
|
|
|
if err := g.Include([]string{secretFile}); err != nil {
|
|
t.Fatalf("Include: %v", err)
|
|
}
|
|
if len(g.manifest.Exclude) != 0 {
|
|
t.Errorf("expected 0 exclusions after include, got %d", len(g.manifest.Exclude))
|
|
}
|
|
}
|
|
|
|
func TestAddSkipsExcludedFile(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
testDir := filepath.Join(root, "config")
|
|
if err := os.MkdirAll(testDir, 0o755); err != nil {
|
|
t.Fatalf("creating dir: %v", err)
|
|
}
|
|
|
|
normalFile := filepath.Join(testDir, "settings.yaml")
|
|
secretFile := filepath.Join(testDir, "credentials.key")
|
|
if err := os.WriteFile(normalFile, []byte("settings"), 0o644); err != nil {
|
|
t.Fatalf("writing normal file: %v", err)
|
|
}
|
|
if err := os.WriteFile(secretFile, []byte("secret"), 0o600); err != nil {
|
|
t.Fatalf("writing secret file: %v", err)
|
|
}
|
|
|
|
// Exclude the secret file before adding the directory.
|
|
if err := g.Exclude([]string{secretFile}); err != nil {
|
|
t.Fatalf("Exclude: %v", err)
|
|
}
|
|
|
|
if err := g.Add([]string{testDir}); err != nil {
|
|
t.Fatalf("Add: %v", err)
|
|
}
|
|
|
|
if len(g.manifest.Files) != 1 {
|
|
t.Fatalf("expected 1 file, got %d", len(g.manifest.Files))
|
|
}
|
|
|
|
expectedPath := toTildePath(normalFile)
|
|
if g.manifest.Files[0].Path != expectedPath {
|
|
t.Errorf("tracked file = %q, want %q", g.manifest.Files[0].Path, expectedPath)
|
|
}
|
|
}
|
|
|
|
func TestAddSkipsExcludedDirectory(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
testDir := filepath.Join(root, "config")
|
|
subDir := filepath.Join(testDir, "secrets")
|
|
if err := os.MkdirAll(subDir, 0o755); err != nil {
|
|
t.Fatalf("creating dirs: %v", err)
|
|
}
|
|
|
|
normalFile := filepath.Join(testDir, "settings.yaml")
|
|
secretFile := filepath.Join(subDir, "token.key")
|
|
if err := os.WriteFile(normalFile, []byte("settings"), 0o644); err != nil {
|
|
t.Fatalf("writing normal file: %v", err)
|
|
}
|
|
if err := os.WriteFile(secretFile, []byte("token"), 0o600); err != nil {
|
|
t.Fatalf("writing secret file: %v", err)
|
|
}
|
|
|
|
// Exclude the entire secrets subdirectory.
|
|
if err := g.Exclude([]string{subDir}); err != nil {
|
|
t.Fatalf("Exclude: %v", err)
|
|
}
|
|
|
|
if err := g.Add([]string{testDir}); err != nil {
|
|
t.Fatalf("Add: %v", err)
|
|
}
|
|
|
|
if len(g.manifest.Files) != 1 {
|
|
t.Fatalf("expected 1 file, got %d", len(g.manifest.Files))
|
|
}
|
|
|
|
expectedPath := toTildePath(normalFile)
|
|
if g.manifest.Files[0].Path != expectedPath {
|
|
t.Errorf("tracked file = %q, want %q", g.manifest.Files[0].Path, expectedPath)
|
|
}
|
|
}
|
|
|
|
func TestMirrorUpSkipsExcluded(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
testDir := filepath.Join(root, "config")
|
|
if err := os.MkdirAll(testDir, 0o755); err != nil {
|
|
t.Fatalf("creating dir: %v", err)
|
|
}
|
|
|
|
normalFile := filepath.Join(testDir, "settings.yaml")
|
|
secretFile := filepath.Join(testDir, "credentials.key")
|
|
if err := os.WriteFile(normalFile, []byte("settings"), 0o644); err != nil {
|
|
t.Fatalf("writing normal file: %v", err)
|
|
}
|
|
if err := os.WriteFile(secretFile, []byte("secret"), 0o600); err != nil {
|
|
t.Fatalf("writing secret file: %v", err)
|
|
}
|
|
|
|
// Exclude the secret file.
|
|
if err := g.Exclude([]string{secretFile}); err != nil {
|
|
t.Fatalf("Exclude: %v", err)
|
|
}
|
|
|
|
if err := g.MirrorUp([]string{testDir}); err != nil {
|
|
t.Fatalf("MirrorUp: %v", err)
|
|
}
|
|
|
|
// Only the normal file should be tracked.
|
|
if len(g.manifest.Files) != 1 {
|
|
t.Fatalf("expected 1 file, got %d", len(g.manifest.Files))
|
|
}
|
|
|
|
expectedPath := toTildePath(normalFile)
|
|
if g.manifest.Files[0].Path != expectedPath {
|
|
t.Errorf("tracked file = %q, want %q", g.manifest.Files[0].Path, expectedPath)
|
|
}
|
|
}
|
|
|
|
func TestMirrorDownLeavesExcludedAlone(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
testDir := filepath.Join(root, "config")
|
|
if err := os.MkdirAll(testDir, 0o755); err != nil {
|
|
t.Fatalf("creating dir: %v", err)
|
|
}
|
|
|
|
normalFile := filepath.Join(testDir, "settings.yaml")
|
|
secretFile := filepath.Join(testDir, "credentials.key")
|
|
if err := os.WriteFile(normalFile, []byte("settings"), 0o644); err != nil {
|
|
t.Fatalf("writing normal file: %v", err)
|
|
}
|
|
if err := os.WriteFile(secretFile, []byte("secret"), 0o600); err != nil {
|
|
t.Fatalf("writing secret file: %v", err)
|
|
}
|
|
|
|
// Add only the normal file.
|
|
if err := g.Add([]string{normalFile}); err != nil {
|
|
t.Fatalf("Add: %v", err)
|
|
}
|
|
|
|
// Exclude the secret file.
|
|
if err := g.Exclude([]string{secretFile}); err != nil {
|
|
t.Fatalf("Exclude: %v", err)
|
|
}
|
|
|
|
// MirrorDown with force — excluded file should NOT be deleted.
|
|
if err := g.MirrorDown([]string{testDir}, true, nil); err != nil {
|
|
t.Fatalf("MirrorDown: %v", err)
|
|
}
|
|
|
|
if _, err := os.Stat(secretFile); err != nil {
|
|
t.Error("excluded file should not have been deleted by MirrorDown")
|
|
}
|
|
}
|
|
|
|
func TestIsExcludedDirectoryPrefix(t *testing.T) {
|
|
root := t.TempDir()
|
|
repoDir := filepath.Join(root, "repo")
|
|
|
|
g, err := Init(repoDir)
|
|
if err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
|
|
// Exclude a directory.
|
|
g.manifest.Exclude = []string{"~/config/secrets"}
|
|
|
|
if !g.manifest.IsExcluded("~/config/secrets") {
|
|
t.Error("exact match should be excluded")
|
|
}
|
|
if !g.manifest.IsExcluded("~/config/secrets/token.key") {
|
|
t.Error("file under excluded dir should be excluded")
|
|
}
|
|
if !g.manifest.IsExcluded("~/config/secrets/nested/deep.key") {
|
|
t.Error("deeply nested file under excluded dir should be excluded")
|
|
}
|
|
if g.manifest.IsExcluded("~/config/secrets-backup/file.key") {
|
|
t.Error("path with similar prefix but different dir should not be excluded")
|
|
}
|
|
if g.manifest.IsExcluded("~/config/other.yaml") {
|
|
t.Error("unrelated path should not be excluded")
|
|
}
|
|
}
|