diff --git a/file_handling.go b/file_handling.go index 7c0b1a7..9d132c8 100644 --- a/file_handling.go +++ b/file_handling.go @@ -14,6 +14,7 @@ import ( type File struct { Path string info os.FileInfo + isDir *bool } // NewFile resolves path to an absolute path and wraps it in a *File. It @@ -27,6 +28,16 @@ func NewFile(path string) (*File, error) { return &File{Path: absPath}, nil } +// NewChildFile joins name under an already-absolute parent without calling filepath.Abs again. +func NewChildFile(parent *File, name string, entry os.DirEntry) *File { + child := &File{Path: filepath.Join(parent.Path, name)} + if entry != nil { + isDir := entry.IsDir() + child.isDir = &isDir + } + return child +} + func (f *File) Base() string { return filepath.Base(f.Path) } @@ -35,6 +46,18 @@ func (f *File) Dir() string { return filepath.Dir(f.Path) } +// IsDir reports whether f is a directory, using DirEntry metadata when available. +func (f *File) IsDir() bool { + if f.isDir != nil { + return *f.isDir + } + info, err := f.Info() + if err != nil { + return false + } + return info.IsDir() +} + // Info lazily stats the file and caches the result. It returns an error if // the underlying os.Stat fails. func (f *File) Info() (os.FileInfo, error) { diff --git a/file_handling_test.go b/file_handling_test.go index 91ee538..246aa2e 100644 --- a/file_handling_test.go +++ b/file_handling_test.go @@ -6,6 +6,20 @@ import ( ) // TestNewFile exercises NewFile's path-resolution behavior. + +func TestNewChildFile(t *testing.T) { + tmp := t.TempDir() + parent, err := NewFile(tmp) + if err != nil { + t.Fatal(err) + } + child := NewChildFile(parent, "sub", nil) + want := filepath.Join(parent.Path, "sub") + if child.Path != want { + t.Fatalf("NewChildFile path = %q; want %q", child.Path, want) + } +} + func TestNewFile(t *testing.T) { tmp := t.TempDir() diff --git a/find_replace.go b/find_replace.go index 9bdbe79..16cf8c6 100644 --- a/find_replace.go +++ b/find_replace.go @@ -123,14 +123,8 @@ func (fr *findReplace) WalkDir(f *File) { return } - for _, file := range files { - childPath := filepath.Join(f.Path, file.Name()) - childFile, err := NewFile(childPath) - if err != nil { - log.Print(err) - fr.errs.add(err) - continue - } + for _, entry := range files { + childFile := NewChildFile(f, entry.Name(), entry) wg.Add(1) go func() { defer wg.Done() @@ -151,13 +145,8 @@ func (fr *findReplace) WalkDir(f *File) { // the rename step; the failure is returned so the walker can log it and // continue with siblings. func (fr *findReplace) HandleFile(f *File) error { - info, err := f.Info() - if err != nil { - return err - } - // If file is a directory, recurse immediately (depth-first). - if info.IsDir() { + if f.IsDir() { // Ignore certain directories if f.Base() == ".git" { return nil