Skip to content

Commit 060b779

Browse files
committed
fix(windows): canonicalize FileTime paths to prevent false overwrite rejections
1 parent 3c32013 commit 060b779

2 files changed

Lines changed: 24 additions & 0 deletions

File tree

packages/opencode/src/file/time.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export namespace FileTime {
6464
)
6565

6666
const getLock = Effect.fn("FileTime.lock")(function* (filepath: string) {
67+
filepath = Filesystem.normalizePath(filepath)
6768
const locks = (yield* InstanceState.get(state)).locks
6869
const lock = locks.get(filepath)
6970
if (lock) return lock
@@ -74,18 +75,21 @@ export namespace FileTime {
7475
})
7576

7677
const read = Effect.fn("FileTime.read")(function* (sessionID: SessionID, file: string) {
78+
file = Filesystem.normalizePath(file)
7779
const reads = (yield* InstanceState.get(state)).reads
7880
log.info("read", { sessionID, file })
7981
session(reads, sessionID).set(file, yield* stamp(file))
8082
})
8183

8284
const get = Effect.fn("FileTime.get")(function* (sessionID: SessionID, file: string) {
85+
file = Filesystem.normalizePath(file)
8386
const reads = (yield* InstanceState.get(state)).reads
8487
return reads.get(sessionID)?.get(file)?.read
8588
})
8689

8790
const assert = Effect.fn("FileTime.assert")(function* (sessionID: SessionID, filepath: string) {
8891
if (disableCheck) return
92+
filepath = Filesystem.normalizePath(filepath)
8993

9094
const reads = (yield* InstanceState.get(state)).reads
9195
const time = reads.get(sessionID)?.get(filepath)
@@ -101,6 +105,7 @@ export namespace FileTime {
101105
})
102106

103107
const withLock = Effect.fn("FileTime.withLock")(function* <T>(filepath: string, fn: () => Promise<T>) {
108+
filepath = Filesystem.normalizePath(filepath)
104109
return yield* Effect.promise(fn).pipe((yield* getLock(filepath)).withPermits(1))
105110
})
106111

packages/opencode/test/file/time.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ afterEach(async () => {
1111
await Instance.disposeAll()
1212
})
1313

14+
const wintest = process.platform === "win32" ? test : test.skip
15+
1416
async function touch(file: string, time: number) {
1517
const date = new Date(time)
1618
await fs.utimes(file, date, date)
@@ -181,6 +183,23 @@ describe("file/time", () => {
181183
},
182184
})
183185
})
186+
187+
wintest("treats equivalent Windows path variants as the same file", async () => {
188+
await using tmp = await tmpdir()
189+
const filepath = path.join(tmp.path, "file.txt")
190+
const variant = filepath.replace(/^[A-Za-z]:/, "").replaceAll("\\", "/").toLowerCase()
191+
await fs.writeFile(filepath, "content", "utf-8")
192+
193+
await Instance.provide({
194+
directory: tmp.path,
195+
fn: async () => {
196+
await FileTime.read(sessionID, variant)
197+
198+
expect(await FileTime.get(sessionID, filepath)).toBeInstanceOf(Date)
199+
await FileTime.assert(sessionID, filepath)
200+
},
201+
})
202+
})
184203
})
185204

186205
describe("withLock()", () => {

0 commit comments

Comments
 (0)