mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-01 06:42:26 +00:00
fix(windows): git path resolution for modified files across Git Bash, MSYS2, and Cygwin (#16422)
This commit is contained in:
@@ -111,8 +111,10 @@ export const TuiThreadCommand = cmd({
|
||||
}
|
||||
|
||||
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
|
||||
const root = process.env.PWD ?? process.cwd()
|
||||
const cwd = args.project ? path.resolve(root, args.project) : process.cwd()
|
||||
const root = Filesystem.resolve(process.env.PWD ?? process.cwd())
|
||||
const cwd = args.project
|
||||
? Filesystem.resolve(path.isAbsolute(args.project) ? args.project : path.join(root, args.project))
|
||||
: root
|
||||
const file = await target()
|
||||
try {
|
||||
process.chdir(cwd)
|
||||
|
||||
@@ -62,13 +62,14 @@ function track(directory: string, next: Promise<Context>) {
|
||||
|
||||
export const Instance = {
|
||||
async provide<R>(input: { directory: string; init?: () => Promise<any>; fn: () => R }): Promise<R> {
|
||||
let existing = cache.get(input.directory)
|
||||
const directory = Filesystem.resolve(input.directory)
|
||||
let existing = cache.get(directory)
|
||||
if (!existing) {
|
||||
Log.Default.info("creating instance", { directory: input.directory })
|
||||
Log.Default.info("creating instance", { directory })
|
||||
existing = track(
|
||||
input.directory,
|
||||
directory,
|
||||
boot({
|
||||
directory: input.directory,
|
||||
directory,
|
||||
init: input.init,
|
||||
}),
|
||||
)
|
||||
@@ -103,11 +104,12 @@ export const Instance = {
|
||||
return State.create(() => Instance.directory, init, dispose)
|
||||
},
|
||||
async reload(input: { directory: string; init?: () => Promise<any>; project?: Project.Info; worktree?: string }) {
|
||||
Log.Default.info("reloading instance", { directory: input.directory })
|
||||
await State.dispose(input.directory)
|
||||
cache.delete(input.directory)
|
||||
const next = track(input.directory, boot(input))
|
||||
emit(input.directory)
|
||||
const directory = Filesystem.resolve(input.directory)
|
||||
Log.Default.info("reloading instance", { directory })
|
||||
await State.dispose(directory)
|
||||
cache.delete(directory)
|
||||
const next = track(directory, boot({ ...input, directory }))
|
||||
emit(directory)
|
||||
return await next
|
||||
},
|
||||
async dispose() {
|
||||
|
||||
@@ -38,6 +38,7 @@ import type { ContentfulStatusCode } from "hono/utils/http-status"
|
||||
import { websocket } from "hono/bun"
|
||||
import { HTTPException } from "hono/http-exception"
|
||||
import { errors } from "./error"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { QuestionRoutes } from "./routes/question"
|
||||
import { PermissionRoutes } from "./routes/permission"
|
||||
import { GlobalRoutes } from "./routes/global"
|
||||
@@ -198,13 +199,15 @@ export namespace Server {
|
||||
if (c.req.path === "/log") return next()
|
||||
const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace")
|
||||
const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
|
||||
const directory = (() => {
|
||||
try {
|
||||
return decodeURIComponent(raw)
|
||||
} catch {
|
||||
return raw
|
||||
}
|
||||
})()
|
||||
const directory = Filesystem.resolve(
|
||||
(() => {
|
||||
try {
|
||||
return decodeURIComponent(raw)
|
||||
} catch {
|
||||
return raw
|
||||
}
|
||||
})(),
|
||||
)
|
||||
|
||||
return WorkspaceContext.provide({
|
||||
workspaceID,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { chmod, mkdir, readFile, writeFile } from "fs/promises"
|
||||
import { createWriteStream, existsSync, statSync } from "fs"
|
||||
import { lookup } from "mime-types"
|
||||
import { realpathSync } from "fs"
|
||||
import { dirname, join, relative } from "path"
|
||||
import { dirname, join, relative, resolve as pathResolve } from "path"
|
||||
import { Readable } from "stream"
|
||||
import { pipeline } from "stream/promises"
|
||||
import { Glob } from "./glob"
|
||||
@@ -113,16 +113,22 @@ export namespace Filesystem {
|
||||
}
|
||||
}
|
||||
|
||||
// We cannot rely on path.resolve() here because git.exe may come from Git Bash, Cygwin, or MSYS2, so we need to translate these paths at the boundary.
|
||||
export function resolve(p: string): string {
|
||||
return normalizePath(pathResolve(windowsPath(p)))
|
||||
}
|
||||
|
||||
export function windowsPath(p: string): string {
|
||||
if (process.platform !== "win32") return p
|
||||
return (
|
||||
p
|
||||
.replace(/^\/([a-zA-Z]):(?:[\\/]|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
|
||||
// Git Bash for Windows paths are typically /<drive>/...
|
||||
.replace(/^\/([a-zA-Z])\//, (_, drive) => `${drive.toUpperCase()}:/`)
|
||||
.replace(/^\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
|
||||
// Cygwin git paths are typically /cygdrive/<drive>/...
|
||||
.replace(/^\/cygdrive\/([a-zA-Z])\//, (_, drive) => `${drive.toUpperCase()}:/`)
|
||||
.replace(/^\/cygdrive\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
|
||||
// WSL paths are typically /mnt/<drive>/...
|
||||
.replace(/^\/mnt\/([a-zA-Z])\//, (_, drive) => `${drive.toUpperCase()}:/`)
|
||||
.replace(/^\/mnt\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
|
||||
)
|
||||
}
|
||||
export function overlaps(a: string, b: string) {
|
||||
|
||||
@@ -3,8 +3,8 @@ import whichPkg from "which"
|
||||
export function which(cmd: string, env?: NodeJS.ProcessEnv) {
|
||||
const result = whichPkg.sync(cmd, {
|
||||
nothrow: true,
|
||||
path: env?.PATH,
|
||||
pathExt: env?.PATHEXT,
|
||||
path: env?.PATH ?? env?.Path ?? process.env.PATH ?? process.env.Path,
|
||||
pathExt: env?.PATHEXT ?? env?.PathExt ?? process.env.PATHEXT ?? process.env.PathExt,
|
||||
})
|
||||
return typeof result === "string" ? result : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user