chore(go): wrap core filesystem errors with context
This commit is contained in:
@@ -99,7 +99,7 @@ func (a *App) Clone(uri string, destination string) error {
|
||||
|
||||
func (a *App) List() error {
|
||||
if err := os.MkdirAll(a.ReposDir, 0o750); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("ensure repos directory: %w", err)
|
||||
}
|
||||
|
||||
var castles []string
|
||||
@@ -114,13 +114,13 @@ func (a *App) List() error {
|
||||
castleRoot := filepath.Dir(path)
|
||||
rel, err := filepath.Rel(a.ReposDir, castleRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("resolve castle path %q: %w", castleRoot, err)
|
||||
}
|
||||
castles = append(castles, rel)
|
||||
return filepath.SkipDir
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("scan repos directory: %w", err)
|
||||
}
|
||||
|
||||
sort.Strings(castles)
|
||||
@@ -132,7 +132,7 @@ func (a *App) List() error {
|
||||
}
|
||||
_, writeErr := fmt.Fprintf(a.Stdout, "%s %s\n", castle, strings.TrimSpace(remote))
|
||||
if writeErr != nil {
|
||||
return writeErr
|
||||
return fmt.Errorf("write castle listing: %w", writeErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,13 +234,13 @@ func (a *App) Destroy(castle string) error {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("castle %q not found", castle)
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("stat castle %q: %w", castle, err)
|
||||
}
|
||||
|
||||
if !a.Force {
|
||||
confirmed, confirmErr := a.confirmDestroy(castle)
|
||||
if confirmErr != nil {
|
||||
return confirmErr
|
||||
return fmt.Errorf("confirm destroy for %q: %w", castle, confirmErr)
|
||||
}
|
||||
if !confirmed {
|
||||
return nil
|
||||
@@ -252,7 +252,7 @@ func (a *App) Destroy(castle string) error {
|
||||
castleHome := filepath.Join(castleRoot, "home")
|
||||
if info, statErr := os.Stat(castleHome); statErr == nil && info.IsDir() {
|
||||
if unlinkErr := a.UnlinkCastle(castle); unlinkErr != nil {
|
||||
return unlinkErr
|
||||
return fmt.Errorf("unlink castle %q before destroy: %w", castle, unlinkErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,12 +267,12 @@ func (a *App) confirmDestroy(castle string) (bool, error) {
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(a.Stdout, "Destroy castle %q? [y/N]: ", castle); err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("write destroy prompt: %w", err)
|
||||
}
|
||||
|
||||
line, err := bufio.NewReader(reader).ReadString('\n')
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return false, err
|
||||
return false, fmt.Errorf("read destroy confirmation: %w", err)
|
||||
}
|
||||
|
||||
return isAffirmativeResponse(line), nil
|
||||
@@ -392,15 +392,15 @@ func (a *App) Generate(castlePath string) error {
|
||||
|
||||
absCastle, err := filepath.Abs(trimmed)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("resolve castle path %q: %w", trimmed, err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(absCastle, 0o750); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("create castle path %q: %w", absCastle, err)
|
||||
}
|
||||
|
||||
if err := a.runGit(absCastle, "init"); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("initialize git repository %q: %w", absCastle, err)
|
||||
}
|
||||
|
||||
githubUser := ""
|
||||
@@ -412,11 +412,15 @@ func (a *App) Generate(castlePath string) error {
|
||||
repoName := filepath.Base(absCastle)
|
||||
url := fmt.Sprintf("git@github.com:%s/%s.git", githubUser, repoName)
|
||||
if err := a.runGit(absCastle, "remote", "add", "origin", url); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("add origin remote for %q: %w", absCastle, err)
|
||||
}
|
||||
}
|
||||
|
||||
return os.MkdirAll(filepath.Join(absCastle, "home"), 0o750)
|
||||
if err := os.MkdirAll(filepath.Join(absCastle, "home"), 0o750); err != nil {
|
||||
return fmt.Errorf("create home directory for %q: %w", absCastle, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) Link(castle string) error {
|
||||
@@ -435,11 +439,11 @@ func (a *App) LinkCastle(castle string) error {
|
||||
|
||||
subdirs, err := readSubdirs(filepath.Join(a.ReposDir, castle, ".homesick_subdir"))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("read subdirs for castle %q: %w", castle, err)
|
||||
}
|
||||
|
||||
if err := a.linkEach(castleHome, castleHome, subdirs); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("link castle %q: %w", castle, err)
|
||||
}
|
||||
|
||||
for _, subdir := range subdirs {
|
||||
@@ -448,11 +452,11 @@ func (a *App) LinkCastle(castle string) error {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("stat subdir %q for castle %q: %w", base, castle, err)
|
||||
}
|
||||
|
||||
if err := a.linkEach(castleHome, base, subdirs); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("link subdir %q for castle %q: %w", subdir, castle, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,11 +479,11 @@ func (a *App) UnlinkCastle(castle string) error {
|
||||
|
||||
subdirs, err := readSubdirs(filepath.Join(a.ReposDir, castle, ".homesick_subdir"))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("read subdirs for castle %q: %w", castle, err)
|
||||
}
|
||||
|
||||
if err := a.unlinkEach(castleHome, castleHome, subdirs); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unlink castle %q: %w", castle, err)
|
||||
}
|
||||
|
||||
for _, subdir := range subdirs {
|
||||
@@ -488,11 +492,11 @@ func (a *App) UnlinkCastle(castle string) error {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("stat subdir %q for castle %q: %w", base, castle, err)
|
||||
}
|
||||
|
||||
if err := a.unlinkEach(castleHome, base, subdirs); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unlink subdir %q for castle %q: %w", subdir, castle, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,15 +526,15 @@ func (a *App) TrackPath(filePath string, castle string) error {
|
||||
|
||||
absolutePath, err := filepath.Abs(strings.TrimRight(trimmedFile, string(filepath.Separator)))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("resolve tracked file %q: %w", trimmedFile, err)
|
||||
}
|
||||
if _, err := os.Lstat(absolutePath); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("stat tracked file %q: %w", absolutePath, err)
|
||||
}
|
||||
|
||||
relativeDir, err := filepath.Rel(a.HomeDir, filepath.Dir(absolutePath))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("resolve tracked file directory for %q: %w", absolutePath, err)
|
||||
}
|
||||
if relativeDir == ".." || strings.HasPrefix(relativeDir, ".."+string(filepath.Separator)) {
|
||||
return fmt.Errorf("track requires file under %s", a.HomeDir)
|
||||
@@ -541,18 +545,18 @@ func (a *App) TrackPath(filePath string, castle string) error {
|
||||
castleTargetDir = castleHome
|
||||
}
|
||||
if err := os.MkdirAll(castleTargetDir, 0o750); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("create tracked file directory %q: %w", castleTargetDir, err)
|
||||
}
|
||||
|
||||
trackedPath := filepath.Join(castleTargetDir, filepath.Base(absolutePath))
|
||||
if _, err := os.Lstat(trackedPath); err == nil {
|
||||
return fmt.Errorf("%s already exists", trackedPath)
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
return fmt.Errorf("stat tracked destination %q: %w", trackedPath, err)
|
||||
}
|
||||
|
||||
if err := os.Rename(absolutePath, trackedPath); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("move tracked file into castle %q: %w", trackedPath, err)
|
||||
}
|
||||
|
||||
subdirChanged := false
|
||||
@@ -560,21 +564,21 @@ func (a *App) TrackPath(filePath string, castle string) error {
|
||||
subdirPath := filepath.Join(castleRoot, ".homesick_subdir")
|
||||
subdirChanged, err = appendUniqueSubdir(subdirPath, relativeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("record tracked subdir %q: %w", relativeDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.linkPath(trackedPath, absolutePath); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("relink tracked file %q: %w", absolutePath, err)
|
||||
}
|
||||
|
||||
repo, err := git.PlainOpen(castleRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("open git repository for castle %q: %w", castle, err)
|
||||
}
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("open worktree for castle %q: %w", castle, err)
|
||||
}
|
||||
|
||||
trackedRelativePath := filepath.Join("home", relativeDir, filepath.Base(absolutePath))
|
||||
@@ -582,12 +586,12 @@ func (a *App) TrackPath(filePath string, castle string) error {
|
||||
trackedRelativePath = filepath.Join("home", filepath.Base(absolutePath))
|
||||
}
|
||||
if _, err := worktree.Add(filepath.ToSlash(filepath.Clean(trackedRelativePath))); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("stage tracked file %q: %w", trackedRelativePath, err)
|
||||
}
|
||||
|
||||
if subdirChanged {
|
||||
if _, err := worktree.Add(".homesick_subdir"); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("stage subdir metadata: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,7 +601,7 @@ func (a *App) TrackPath(filePath string, castle string) error {
|
||||
func appendUniqueSubdir(path string, subdir string) (bool, error) {
|
||||
existing, err := readSubdirs(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("load subdir metadata %q: %w", path, err)
|
||||
}
|
||||
|
||||
cleanSubdir := filepath.Clean(subdir)
|
||||
@@ -609,12 +613,12 @@ func appendUniqueSubdir(path string, subdir string) (bool, error) {
|
||||
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) // #nosec G304 — internal metadata file
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("open subdir metadata %q: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := file.WriteString(cleanSubdir + "\n"); err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("write subdir metadata %q: %w", path, err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -623,7 +627,7 @@ func appendUniqueSubdir(path string, subdir string) (bool, error) {
|
||||
func (a *App) linkEach(castleHome string, baseDir string, subdirs []string) error {
|
||||
entries, err := os.ReadDir(baseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("read castle directory %q: %w", baseDir, err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
@@ -635,7 +639,7 @@ func (a *App) linkEach(castleHome string, baseDir string, subdirs []string) erro
|
||||
source := filepath.Join(baseDir, name)
|
||||
ignore, err := matchesIgnoredDir(castleHome, source, subdirs)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("check ignored directory %q: %w", source, err)
|
||||
}
|
||||
if ignore {
|
||||
continue
|
||||
@@ -643,7 +647,7 @@ func (a *App) linkEach(castleHome string, baseDir string, subdirs []string) erro
|
||||
|
||||
relDir, err := filepath.Rel(castleHome, baseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("resolve castle relative directory %q: %w", baseDir, err)
|
||||
}
|
||||
|
||||
destination := filepath.Join(a.HomeDir, relDir, name)
|
||||
@@ -652,7 +656,7 @@ func (a *App) linkEach(castleHome string, baseDir string, subdirs []string) erro
|
||||
}
|
||||
|
||||
if err := a.linkPath(source, destination); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("link %q to %q: %w", source, destination, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +666,7 @@ func (a *App) linkEach(castleHome string, baseDir string, subdirs []string) erro
|
||||
func (a *App) unlinkEach(castleHome string, baseDir string, subdirs []string) error {
|
||||
entries, err := os.ReadDir(baseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("read castle directory %q: %w", baseDir, err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
@@ -674,7 +678,7 @@ func (a *App) unlinkEach(castleHome string, baseDir string, subdirs []string) er
|
||||
source := filepath.Join(baseDir, name)
|
||||
ignore, err := matchesIgnoredDir(castleHome, source, subdirs)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("check ignored directory %q: %w", source, err)
|
||||
}
|
||||
if ignore {
|
||||
continue
|
||||
@@ -682,7 +686,7 @@ func (a *App) unlinkEach(castleHome string, baseDir string, subdirs []string) er
|
||||
|
||||
relDir, err := filepath.Rel(castleHome, baseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("resolve castle relative directory %q: %w", baseDir, err)
|
||||
}
|
||||
|
||||
destination := filepath.Join(a.HomeDir, relDir, name)
|
||||
@@ -691,7 +695,7 @@ func (a *App) unlinkEach(castleHome string, baseDir string, subdirs []string) er
|
||||
}
|
||||
|
||||
if err := unlinkPath(destination); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unlink %q: %w", destination, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,11 +721,11 @@ func unlinkPath(destination string) error {
|
||||
func (a *App) linkPath(source string, destination string) error {
|
||||
absSource, err := filepath.Abs(source)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("resolve link source %q: %w", source, err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(destination), 0o750); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("create destination parent %q: %w", filepath.Dir(destination), err)
|
||||
}
|
||||
|
||||
info, err := os.Lstat(destination)
|
||||
@@ -738,14 +742,14 @@ func (a *App) linkPath(source string, destination string) error {
|
||||
}
|
||||
|
||||
if rmErr := os.RemoveAll(destination); rmErr != nil {
|
||||
return rmErr
|
||||
return fmt.Errorf("remove existing destination %q: %w", destination, rmErr)
|
||||
}
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
return fmt.Errorf("stat destination %q: %w", destination, err)
|
||||
}
|
||||
|
||||
if err := os.Symlink(absSource, destination); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("create symlink %q -> %q: %w", destination, absSource, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -757,7 +761,7 @@ func readSubdirs(path string) ([]string, error) {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("read subdirs %q: %w", path, err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
@@ -776,7 +780,7 @@ func readSubdirs(path string) ([]string, error) {
|
||||
func matchesIgnoredDir(castleHome string, candidate string, subdirs []string) (bool, error) {
|
||||
absCandidate, err := filepath.Abs(candidate)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("resolve candidate path %q: %w", candidate, err)
|
||||
}
|
||||
|
||||
ignoreSet := map[string]struct{}{}
|
||||
@@ -853,7 +857,7 @@ func (a *App) Rc(castle string, force bool) error {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("castle %q not found", castle)
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("stat castle %q: %w", castle, err)
|
||||
}
|
||||
|
||||
homesickD := filepath.Join(castleRoot, ".homesick.d")
|
||||
@@ -890,12 +894,12 @@ func (a *App) Rc(castle string, force bool) error {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("stat rc hooks directory %q: %w", homesickD, err)
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(homesickD)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("read rc hooks %q: %w", homesickD, err)
|
||||
}
|
||||
|
||||
// ReadDir returns entries in sorted order already.
|
||||
@@ -905,7 +909,7 @@ func (a *App) Rc(castle string, force bool) error {
|
||||
}
|
||||
info, infoErr := entry.Info()
|
||||
if infoErr != nil {
|
||||
return infoErr
|
||||
return fmt.Errorf("read rc hook metadata %q: %w", entry.Name(), infoErr)
|
||||
}
|
||||
if info.Mode()&0o111 == 0 {
|
||||
// Not executable — skip.
|
||||
|
||||
Reference in New Issue
Block a user