fix(watch): capture Windows file identity before external moves
This commit is contained in:
1 parent
1a8f70ca13
commit
18bbb60a3b
4 files changed
+41
-4
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func snapshotInfo(path string) (os.FileInfo, error) { return os.Stat(path) }
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func snapshotInfo(path string) (os.FileInfo, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// File.Stat captures the file ID from its open handle, before external renames.
|
||||||
|
info, err := file.Stat()
|
||||||
|
return info, errors.Join(err, file.Close())
|
||||||
|
}
|
||||||
+12
-1
@@ -10,6 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"rawmark/internal/i18n"
|
"rawmark/internal/i18n"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -135,8 +136,18 @@ func (w *Watcher) scan() (map[string]fileState, error) {
|
|||||||
next := make(map[string]fileState, len(entries))
|
next := make(map[string]fileState, len(entries))
|
||||||
dirs := map[string]bool{w.v.Root(): true}
|
dirs := map[string]bool{w.v.Root(): true}
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
st := fileState{dir: e.Dir, size: e.Size, mtime: e.Info.ModTime().UnixNano(), info: e.Info}
|
|
||||||
abs := filepath.Join(w.v.Root(), filepath.FromSlash(e.Path))
|
abs := filepath.Join(w.v.Root(), filepath.FromSlash(e.Path))
|
||||||
|
info := e.Info
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
info, err = snapshotInfo(abs)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st := fileState{dir: e.Dir, size: info.Size(), mtime: info.ModTime().UnixNano(), info: info}
|
||||||
if e.Dir {
|
if e.Dir {
|
||||||
dirs[abs] = true
|
dirs[abs] = true
|
||||||
} else if vault.IsNote(e.Path) {
|
} else if vault.IsNote(e.Path) {
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ func TestUnreadableNotePreservesSnapshot(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
allowRead := testutil.DenyRead(t, p)
|
allowRead := testutil.DenyRead(t, p)
|
||||||
|
if _, err := os.ReadFile(p); err == nil {
|
||||||
|
t.Fatal("filesystem failure fixture did not block reading")
|
||||||
|
}
|
||||||
w.rescan()
|
w.rescan()
|
||||||
if w.snap["note.md"].rev != old || scans != 0 {
|
if w.snap["note.md"].rev != old || scans != 0 {
|
||||||
t.Fatal("读取失败不能提交不完整快照或更新索引")
|
t.Fatal("读取失败不能提交不完整快照或更新索引")
|
||||||
@@ -69,7 +72,7 @@ func TestDiff(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
state := func(p, rev string) fileState {
|
state := func(p, rev string) fileState {
|
||||||
info, err := os.Stat(filepath.Join(root, p))
|
info, err := snapshotInfo(filepath.Join(root, p))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -125,7 +128,7 @@ func TestDirectoryMovesCoverChildrenAndEmptyDirectories(t *testing.T) {
|
|||||||
snapshot := func(dir string) map[string]fileState {
|
snapshot := func(dir string) map[string]fileState {
|
||||||
out := map[string]fileState{}
|
out := map[string]fileState{}
|
||||||
for _, rel := range []string{dir, dir + "/empty", dir + "/a.md"} {
|
for _, rel := range []string{dir, dir + "/empty", dir + "/a.md"} {
|
||||||
info, err := os.Stat(filepath.Join(root, rel))
|
info, err := snapshotInfo(filepath.Join(root, rel))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -170,7 +173,7 @@ func TestImageReplacementRefreshesTree(t *testing.T) {
|
|||||||
if err := os.WriteFile(p, []byte("image"), 0o644); err != nil {
|
if err := os.WriteFile(p, []byte("image"), 0o644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
info, err := os.Stat(p)
|
info, err := snapshotInfo(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user