diff --git a/packages/opencode/src/file/time.ts b/packages/opencode/src/file/time.ts index 08f7e9a95127..90707f59ea33 100644 --- a/packages/opencode/src/file/time.ts +++ b/packages/opencode/src/file/time.ts @@ -4,6 +4,7 @@ import { makeRuntime } from "@/effect/run-service" import { AppFileSystem } from "@/filesystem" import { Flag } from "@/flag/flag" import type { SessionID } from "@/session/schema" +import { Filesystem } from "../util/filesystem" import { Log } from "../util/log" export namespace FileTime { @@ -62,6 +63,7 @@ export namespace FileTime { ) const getLock = Effect.fn("FileTime.lock")(function* (filepath: string) { + filepath = Filesystem.normalizePath(filepath) const locks = (yield* InstanceState.get(state)).locks const lock = locks.get(filepath) if (lock) return lock @@ -72,18 +74,21 @@ export namespace FileTime { }) const read = Effect.fn("FileTime.read")(function* (sessionID: SessionID, file: string) { + file = Filesystem.normalizePath(file) const reads = (yield* InstanceState.get(state)).reads log.info("read", { sessionID, file }) session(reads, sessionID).set(file, yield* stamp(file)) }) const get = Effect.fn("FileTime.get")(function* (sessionID: SessionID, file: string) { + file = Filesystem.normalizePath(file) const reads = (yield* InstanceState.get(state)).reads return reads.get(sessionID)?.get(file)?.read }) const assert = Effect.fn("FileTime.assert")(function* (sessionID: SessionID, filepath: string) { if (disableCheck) return + filepath = Filesystem.normalizePath(filepath) const reads = (yield* InstanceState.get(state)).reads const time = reads.get(sessionID)?.get(filepath) @@ -99,6 +104,7 @@ export namespace FileTime { }) const withLock = Effect.fn("FileTime.withLock")(function* (filepath: string, fn: () => Promise) { + filepath = Filesystem.normalizePath(filepath) return yield* Effect.promise(fn).pipe((yield* getLock(filepath)).withPermits(1)) }) diff --git a/packages/opencode/test/file/time.test.ts b/packages/opencode/test/file/time.test.ts index db7eaaae0d8b..be3ee0edc574 100644 --- a/packages/opencode/test/file/time.test.ts +++ b/packages/opencode/test/file/time.test.ts @@ -11,6 +11,8 @@ afterEach(async () => { await Instance.disposeAll() }) +const wintest = process.platform === "win32" ? test : test.skip + async function touch(file: string, time: number) { const date = new Date(time) await fs.utimes(file, date, date) @@ -181,6 +183,23 @@ describe("file/time", () => { }, }) }) + + wintest("treats equivalent Windows path variants as the same file", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "file.txt") + const variant = filepath.replace(/^[A-Za-z]:/, "").replaceAll("\\", "/").toLowerCase() + await fs.writeFile(filepath, "content", "utf-8") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await FileTime.read(sessionID, variant) + + expect(await FileTime.get(sessionID, filepath)).toBeInstanceOf(Date) + await FileTime.assert(sessionID, filepath) + }, + }) + }) }) describe("withLock()", () => {