From 4e2f3445975f7826adc2a1770e58da0c9d62d35c Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Thu, 15 Oct 2026 16:22:57 +0000 Subject: [PATCH] fix: skip dot-prefixed and .DS_Store sources in infect linkDirectory prefixed a dot onto entries that already had one, so link/.DS_Store and link/.zsh_favlist became ~/..DS_Store and ~/..zsh_favlist. Skip top-level dot entries in link/ and specific/, and Finder's .DS_Store anywhere. Co-Authored-By: Claude Opus 5.5 --- dotool/infect.go | 13 +++++++++++++ dotool/infect_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/dotool/infect.go b/dotool/infect.go index cb024d1b..b9e37ea2 100644 --- a/dotool/infect.go +++ b/dotool/infect.go @@ -6,6 +6,7 @@ import ( "log" "os" "path/filepath" + "strings" "time" ) @@ -51,6 +52,9 @@ func linkDirectory(sourceDir, homeDir string, addDot bool) error { } for _, entry := range entries { + if skipSource(entry.Name()) { + continue + } sourcePath := filepath.Join(sourceDir, entry.Name()) name := entry.Name() if addDot { @@ -79,6 +83,9 @@ func linkSpecificFiles(homeDir string) error { if err != nil { return fmt.Errorf("failed to get relative path for %s: %w", path, err) } + if skipSource(relativePath) { + return nil + } targetPath := filepath.Join(homeDir, "."+relativePath) if err := os.MkdirAll(filepath.Dir(targetPath), 0750); err != nil { @@ -89,6 +96,12 @@ func linkSpecificFiles(homeDir string) error { }) } +// skipSource reports whether rel, a path under link/ or specific/, must not be linked. +// A dot-prefixed top-level name would land at ~/..name, and .DS_Store is Finder litter. +func skipSource(rel string) bool { + return strings.HasPrefix(rel, ".") || filepath.Base(rel) == ".DS_Store" +} + func linkBinFiles(homeDir string) error { entries, err := os.ReadDir("bin") if err != nil { diff --git a/dotool/infect_test.go b/dotool/infect_test.go index 4088abdc..71d3bb11 100644 --- a/dotool/infect_test.go +++ b/dotool/infect_test.go @@ -110,3 +110,39 @@ func TestLinkBinFiles(t *testing.T) { } } } + +func TestLinkSkipsDotEntries(t *testing.T) { + tempDir := t.TempDir() + t.Chdir(tempDir) + + for _, file := range []string{"link/.DS_Store", "link/.zsh_favlist", "link/zshrc", "specific/.DS_Store", "specific/config/app/.DS_Store", "specific/config/app/.envrc"} { + if err := os.MkdirAll(filepath.Dir(file), 0750); err != nil { + t.Fatalf("failed to create directory for %s: %v", file, err) + } + if err := os.WriteFile(file, []byte("x"), 0600); err != nil { + t.Fatalf("failed to create test file %s: %v", file, err) + } + } + + homeDir := filepath.Join(tempDir, "home") + if err := os.MkdirAll(homeDir, 0750); err != nil { + t.Fatalf("failed to create home directory: %v", err) + } + if err := linkDirectory("link", homeDir, true); err != nil { + t.Fatalf("linkDirectory() error = %v", err) + } + if err := linkSpecificFiles(homeDir); err != nil { + t.Fatalf("linkSpecificFiles() error = %v", err) + } + + for _, want := range []string{".zshrc", ".config/app/.envrc"} { + if _, err := os.Lstat(filepath.Join(homeDir, want)); err != nil { + t.Errorf("expected symlink %s: %v", want, err) + } + } + for _, unwanted := range []string{"..DS_Store", "..zsh_favlist", ".config/app/.DS_Store"} { + if _, err := os.Lstat(filepath.Join(homeDir, unwanted)); err == nil { + t.Errorf("unexpected symlink %s", unwanted) + } + } +}