Support unlinking package subpaths

This commit is contained in:
2026-02-19 17:07:38 +00:00
parent c5d1aef52b
commit 6f2be07272

151
main.go
View File

@@ -87,11 +87,33 @@ func parsePackageFlags(args []string) (packageFlags, string, error) {
pkg = arg pkg = arg
} }
if pkg == "" { if pkg == "" {
return flags, "", errors.New("missing package name") return flags, "", errors.New("missing package")
} }
return flags, pkg, nil return flags, pkg, nil
} }
func splitPackageSpec(spec string) (string, string, error) {
if spec == "" {
return "", "", errors.New("missing package")
}
parts := strings.SplitN(spec, ":", 2)
pkg := parts[0]
rel := ""
if len(parts) == 2 {
rel = parts[1]
}
pkg = strings.Trim(pkg, "/")
rel = strings.TrimPrefix(rel, "/")
if pkg == "" {
return "", "", errors.New("invalid package")
}
return pkg, rel, nil
}
func applyCmd(args []string) error { func applyCmd(args []string) error {
prune := false prune := false
for _, arg := range args { for _, arg := range args {
@@ -275,7 +297,12 @@ func addCmd(args []string) error {
} }
func unlinkCmd(args []string) error { func unlinkCmd(args []string) error {
flags, pkgName, err := parsePackageFlags(args) flags, pkgSpec, err := parsePackageFlags(args)
if err != nil {
return err
}
pkgName, relPath, err := splitPackageSpec(pkgSpec)
if err != nil { if err != nil {
return err return err
} }
@@ -302,7 +329,11 @@ func unlinkCmd(args []string) error {
return err return err
} }
return restorePackage(filesDir, targetRoot, flags.dryRun) if relPath == "" {
return restorePackage(filesDir, targetRoot, flags.dryRun)
}
return restorePath(filesDir, targetRoot, relPath, flags.dryRun)
} }
func removeCmd(args []string) error { func removeCmd(args []string) error {
@@ -708,44 +739,88 @@ func restorePackage(filesDir, targetRoot string, dryRun bool) error {
return err return err
} }
targetPath := filepath.Join(targetRoot, rel) targetPath := filepath.Join(targetRoot, rel)
return restoreOne(path, targetPath, filesAbs, dryRun)
info, err := os.Lstat(targetPath)
if errors.Is(err, os.ErrNotExist) {
return nil
}
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)
if !strings.HasPrefix(src, filesAbs+string(os.PathSeparator)) && src != filesAbs {
return nil
}
if dryRun {
fmt.Printf("dry-run: restore %s\n", targetPath)
return nil
}
if err := os.Remove(targetPath); err != nil {
return err
}
return copyFile(path, targetPath)
}) })
} }
func restorePath(filesDir, targetRoot, relPath string, dryRun bool) error {
filesAbs, err := filepath.Abs(filesDir)
if err != nil {
return err
}
relPath = filepath.Clean(relPath)
if strings.HasPrefix(relPath, "..") || filepath.IsAbs(relPath) {
return fmt.Errorf("invalid relative path %q", relPath)
}
sourcePath := filepath.Join(filesDir, relPath)
info, err := os.Lstat(sourcePath)
if err != nil {
return err
}
if info.IsDir() {
return filepath.WalkDir(sourcePath, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() {
return nil
}
rel, err := filepath.Rel(filesDir, path)
if err != nil {
return err
}
targetPath := filepath.Join(targetRoot, rel)
return restoreOne(path, targetPath, filesAbs, dryRun)
})
}
rel, err := filepath.Rel(filesDir, sourcePath)
if err != nil {
return err
}
return restoreOne(sourcePath, filepath.Join(targetRoot, rel), filesAbs, dryRun)
}
func restoreOne(sourcePath, targetPath, filesAbs string, dryRun bool) error {
info, err := os.Lstat(targetPath)
if errors.Is(err, os.ErrNotExist) {
return nil
}
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)
if !strings.HasPrefix(src, filesAbs+string(os.PathSeparator)) && src != filesAbs {
return nil
}
if dryRun {
fmt.Printf("dry-run: restore %s\n", targetPath)
return nil
}
if err := os.Remove(targetPath); err != nil {
return err
}
return copyFile(sourcePath, targetPath)
}
func copyFile(src, dst string) error { func copyFile(src, dst string) error {
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return err return err