diff --git a/main.go b/main.go index 8c6f48d..fa5e528 100644 --- a/main.go +++ b/main.go @@ -188,15 +188,7 @@ func applyCmd(args []string) error { } fmt.Printf("Stale links found: %d\n", len(stales)) - ok, err := promptYesNo("Prune them?", false) - if err != nil { - return err - } - if ok { - return removeLinks(stales, false) - } - - return nil + return handleStaleLinks(stales) } func addCmd(args []string) error { @@ -761,6 +753,104 @@ func removeLinks(paths []string, dryRun bool) error { return nil } +func handleStaleLinks(stales []string) error { + if len(stales) == 0 { + return nil + } + + repo, err := repoPath() + if err != nil { + return err + } + + reader := bufio.NewReader(os.Stdin) + for _, path := range stales { + fmt.Printf("stale: %s\n", path) + fmt.Print("action [p=prune, u=unlink, i=ignore]: ") + choice, err := reader.ReadString('\n') + if err != nil { + return err + } + choice = strings.TrimSpace(strings.ToLower(choice)) + if choice == "" || choice == "i" { + continue + } + if choice == "p" { + if err := os.Remove(path); err != nil { + return err + } + fmt.Printf("removed %s\n", path) + continue + } + if choice == "u" { + if err := unlinkStale(path, repo); err != nil { + return err + } + continue + } + fmt.Println("invalid choice; skipping") + } + + return nil +} + +func unlinkStale(targetPath, repo string) error { + info, err := os.Lstat(targetPath) + if err != nil { + return err + } + if info.Mode()&os.ModeSymlink == 0 { + return nil + } + + src, err := os.Readlink(targetPath) + if err != nil { + return err + } + if !filepath.IsAbs(src) { + src = filepath.Join(filepath.Dir(targetPath), src) + } + src = filepath.Clean(src) + + repoAbs, err := filepath.Abs(repo) + if err != nil { + return err + } + + rel, err := filepath.Rel(repoAbs, src) + if err != nil { + return err + } + if strings.HasPrefix(rel, "..") { + return nil + } + + parts := strings.Split(rel, string(os.PathSeparator)) + if len(parts) < 3 { + return nil + } + + if parts[1] != filesDirName { + return nil + } + + repoPath := filepath.Join(repoAbs, rel) + if err := os.Remove(targetPath); err != nil { + return err + } + + if err := copyFile(repoPath, targetPath); err != nil { + return err + } + + if err := os.Remove(repoPath); err != nil { + return err + } + + fmt.Printf("unlinked %s (removed %s)\n", targetPath, repoPath) + return nil +} + func restorePackage(filesDir, targetRoot string, dryRun bool) error { filesAbs, err := filepath.Abs(filesDir) if err != nil {