diff --git a/find_replace.go b/find_replace.go index 9bdbe79..8525104 100644 --- a/find_replace.go +++ b/find_replace.go @@ -184,14 +184,9 @@ func (fr *findReplace) RenameFile(f *File) error { } newPath := filepath.Join(f.Dir(), newBaseName) - if _, err := os.Stat(newPath); err == nil { - return fmt.Errorf("refusing to rename %v to %v: %v already exists", f.Path, newBaseName, newPath) - } else if !errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("stat rename destination %v: %w", newPath, err) - } log.Printf("Renaming %v to %v", f.Path, newBaseName) - if err := os.Rename(f.Path, newPath); err != nil { + if err := atomicRenameNoReplace(f.Path, newPath); err != nil { return fmt.Errorf("rename %v to %v: %w", f.Path, newBaseName, err) } return nil diff --git a/go.mod b/go.mod index 66fd9af..144cdc5 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module github.com/dolph/find-replace go 1.20 require golang.org/x/tools v0.7.0 + +require golang.org/x/sys v0.26.0 diff --git a/go.sum b/go.sum index b522ba0..7ce55e9 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= diff --git a/rename_darwin.go b/rename_darwin.go new file mode 100644 index 0000000..b798428 --- /dev/null +++ b/rename_darwin.go @@ -0,0 +1,18 @@ +//go:build darwin + +package main + +import ( + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +func atomicRenameNoReplace(oldpath, newpath string) error { + err := unix.RenameatxNp(unix.AT_FDCWD, oldpath, unix.AT_FDCWD, newpath, unix.RENAME_EXCL) + if errors.Is(err, unix.EEXIST) { + return fmt.Errorf("refusing to rename %v to %v: %v already exists", oldpath, newpath, newpath) + } + return err +} diff --git a/rename_linux.go b/rename_linux.go new file mode 100644 index 0000000..3df46ca --- /dev/null +++ b/rename_linux.go @@ -0,0 +1,18 @@ +//go:build linux + +package main + +import ( + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +func atomicRenameNoReplace(oldpath, newpath string) error { + err := unix.Renameat2(unix.AT_FDCWD, oldpath, unix.AT_FDCWD, newpath, unix.RENAME_NOREPLACE) + if errors.Is(err, unix.EEXIST) { + return fmt.Errorf("refusing to rename %v to %v: %v already exists", oldpath, newpath, newpath) + } + return err +} diff --git a/rename_other.go b/rename_other.go new file mode 100644 index 0000000..b16846d --- /dev/null +++ b/rename_other.go @@ -0,0 +1,18 @@ +//go:build !linux && !darwin && !windows + +package main + +import ( + "errors" + "fmt" + "os" +) + +func atomicRenameNoReplace(oldpath, newpath string) error { + if _, err := os.Stat(newpath); err == nil { + return fmt.Errorf("refusing to rename %v to %v: %v already exists", oldpath, newpath, newpath) + } else if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("stat rename destination %v: %w", newpath, err) + } + return os.Rename(oldpath, newpath) +} diff --git a/rename_windows.go b/rename_windows.go new file mode 100644 index 0000000..3f9bce4 --- /dev/null +++ b/rename_windows.go @@ -0,0 +1,18 @@ +//go:build windows + +package main + +import ( + "errors" + "fmt" + "os" +) + +func atomicRenameNoReplace(oldpath, newpath string) error { + if _, err := os.Stat(newpath); err == nil { + return fmt.Errorf("refusing to rename %v to %v: %v already exists", oldpath, newpath, newpath) + } else if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("stat rename destination %v: %w", newpath, err) + } + return os.Rename(oldpath, newpath) +}