Add prune option to apply

This commit is contained in:
2026-02-19 16:42:47 +00:00
parent d8af076a17
commit 3b9af4f0a1

111
main.go
View File

@@ -33,7 +33,7 @@ func main() {
var err error var err error
switch args[0] { switch args[0] {
case "apply": case "apply":
err = applyCmd() err = applyCmd(args[1:])
case "add": case "add":
err = addCmd(args[1:]) err = addCmd(args[1:])
case "help", "-h", "--help": case "help", "-h", "--help":
@@ -52,11 +52,20 @@ func main() {
func usage() { func usage() {
fmt.Println("sigil: minimal dotfile symlink manager") fmt.Println("sigil: minimal dotfile symlink manager")
fmt.Println("usage:") fmt.Println("usage:")
fmt.Println(" sigil apply") fmt.Println(" sigil apply [--prune]")
fmt.Println(" sigil add <path>") fmt.Println(" sigil add <path>")
} }
func applyCmd() error { func applyCmd(args []string) error {
prune := false
for _, arg := range args {
if arg == "--prune" {
prune = true
continue
}
return fmt.Errorf("unknown flag %q", arg)
}
repo, err := repoPath() repo, err := repoPath()
if err != nil { if err != nil {
return err return err
@@ -67,6 +76,8 @@ func applyCmd() error {
return err return err
} }
var stales []string
for _, entry := range entries { for _, entry := range entries {
if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") { if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") {
continue continue
@@ -102,6 +113,28 @@ func applyCmd() error {
if err := applyPackage(filesDir, targetRoot); err != nil { if err := applyPackage(filesDir, targetRoot); err != nil {
return fmt.Errorf("%s: %w", entry.Name(), err) return fmt.Errorf("%s: %w", entry.Name(), err)
} }
stale, err := findStaleLinks(filesDir, targetRoot)
if err != nil {
return fmt.Errorf("%s: %w", entry.Name(), err)
}
stales = append(stales, stale...)
}
if len(stales) == 0 {
return nil
}
if prune {
return removeLinks(stales)
}
ok, err := promptYesNo("Found stale links. Prune them?", false)
if err != nil {
return err
}
if ok {
return removeLinks(stales)
} }
return nil return nil
@@ -395,6 +428,24 @@ func promptWithDefault(reader *bufio.Reader, label, def string) string {
return text return text
} }
func promptYesNo(message string, def bool) (bool, error) {
reader := bufio.NewReader(os.Stdin)
defLabel := "y/N"
if def {
defLabel = "Y/n"
}
fmt.Printf("%s [%s]: ", message, defLabel)
text, err := reader.ReadString('\n')
if err != nil {
return false, err
}
text = strings.TrimSpace(strings.ToLower(text))
if text == "" {
return def, nil
}
return text == "y" || text == "yes", nil
}
func moveDirContents(srcDir, destDir string) error { func moveDirContents(srcDir, destDir string) error {
entries, err := os.ReadDir(srcDir) entries, err := os.ReadDir(srcDir)
if err != nil { if err != nil {
@@ -418,3 +469,57 @@ func moveDirContents(srcDir, destDir string) error {
return nil return nil
} }
func findStaleLinks(filesDir, targetRoot string) ([]string, error) {
known := make(map[string]struct{})
err := filepath.WalkDir(filesDir, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if path == filesDir || entry.IsDir() {
return nil
}
rel, err := filepath.Rel(filesDir, path)
if err != nil {
return err
}
known[filepath.Join(targetRoot, rel)] = struct{}{}
return nil
})
if err != nil {
return nil, err
}
var stale []string
for targetPath := range known {
info, err := os.Lstat(targetPath)
if errors.Is(err, os.ErrNotExist) {
continue
}
if err != nil {
return nil, err
}
if info.Mode()&os.ModeSymlink == 0 {
continue
}
src, err := os.Readlink(targetPath)
if err != nil {
return nil, err
}
if _, err := os.Stat(src); errors.Is(err, os.ErrNotExist) {
stale = append(stale, targetPath)
}
}
return stale, nil
}
func removeLinks(paths []string) error {
for _, path := range paths {
if err := os.Remove(path); err != nil {
return err
}
}
return nil
}